1#!/usr/bin/python 2# 3# Copyright (C) 2011 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17 18import re 19import os 20import sys 21import getopt 22import shutil 23import subprocess 24import zipfile 25 26VERBOSE = False 27TOP_FOLDER = "src" 28_RE_PKG = re.compile("^\s*package\s+([^\s;]+)\s*;.*") 29 30# Holds cmd-line arguments and context information 31class Params(object): 32 def __init__(self): 33 self.EXEC_ZIP = False 34 self.DRY = False 35 self.PROPS = None 36 self.SRC = None 37 self.DST = None 38 self.CNT_USED = 0 39 self.CNT_NOPKG = 0 40 # DIR is the list of directories to scan in TOPDIR. 41 self.DIR = "frameworks libcore" 42 self.IGNORE_DIR = [ "hosttests", "tools", "tests", "samples" ] 43 # IGNORE is a list of namespaces to ignore. Must be java 44 # package definitions (e.g. "com.blah.foo.") 45 self.IGNORE = [ "sun.", "libcore.", "dalvik.", 46 "com.test.", "com.google.", 47 "coretestutils.", "test.", "test2.", "tests." ] 48 self.zipfile = None 49 50 51def verbose(msg, *args): 52 """Prints a verbose message to stderr if --verbose is set.""" 53 global VERBOSE 54 if VERBOSE: 55 if args: 56 msg = msg % args 57 print >>sys.stderr, msg 58 59 60# Prints a usage summary 61def usage(error=None): 62 print """ 63 Description: 64 This script collects all framework Java sources from the current android 65 source code and places them in a source.zip file that can be distributed 66 by the SDK Manager. 67 68 Usage: 69 %s [-n|-v|-z] <source.properties> <sources.zip> <topdir> 70 71 The source.properties file must exist and will be injected in the Zip file. 72 The source directory must already exist. 73 Use -v for verbose output (lists each file being picked up or ignored). 74 Use -n for a dry-run (doesn't write the zip file). 75 Use -z to use the system 'zip' command instead of the Python Zip module. 76 77""" % sys.argv[0] 78 79 if error: 80 print >>sys.stderr, "Error:", error 81 82 83# Parse command line args, returns a Params instance or sys.exit(2) on error 84# after printing the error and the usage. 85def parseArgs(argv): 86 global VERBOSE 87 p = Params() 88 error = None 89 90 try: 91 opts, args = getopt.getopt(argv[1:], 92 "zvns:", 93 [ "exec-zip", "verbose", "dry", "sourcedir=" ]) 94 except getopt.GetoptError, e: 95 error = str(e) 96 97 if error is None: 98 for o, a in opts: 99 if o in [ "-n", "--dry" ]: 100 # Dry mode: don't copy/zip, print what would be done. 101 p.DRY = True 102 if o in [ "-v", "--verbose" ]: 103 # Verbose mode. Display everything that's going on. 104 VERBOSE = True 105 elif o in [ "-s", "--sourcedir" ]: 106 # The source directories to process (space separated list) 107 p.DIR = a 108 elif o in [ "-z", "--exec-zip" ]: 109 # Don't use Python zip, instead call the 'zip' system exec. 110 p.EXEC_ZIP = True 111 112 if len(args) != 3: 113 error = "Missing arguments: <source> <dest>" 114 else: 115 p.PROPS = args[0] 116 p.DST = args[1] 117 p.SRC = args[2] 118 119 if not os.path.isfile(p.PROPS): 120 error = "%s is not a file" % p.PROPS 121 if not os.path.isdir(p.SRC): 122 error = "%s is not a directory" % p.SRC 123 124 if error: 125 usage(error) 126 sys.exit(2) 127 128 return p 129 130 131# Recursively parses the given directory and processes java files found 132def parseSrcDir(p, srcdir): 133 if not os.path.exists(srcdir): 134 verbose("Error: Skipping unknown directory %s", srcdir) 135 return 136 137 for filename in os.listdir(srcdir): 138 filepath = os.path.join(srcdir, filename) 139 if filename.endswith(".java") and os.path.isfile(filepath): 140 pkg = checkJavaFile(filepath) 141 if not pkg: 142 verbose("No package found in %s", filepath) 143 if pkg: 144 # Should we ignore this package? 145 pkg2 = pkg 146 if not "." in pkg2: 147 pkg2 += "." 148 for ignore in p.IGNORE: 149 if pkg2.startswith(ignore): 150 verbose("Ignore package %s [%s]", pkg, filepath) 151 pkg = None 152 break 153 154 if pkg: 155 pkg = pkg.replace(".", os.path.sep) # e.g. android.view => android/view 156 copy(p, filepath, pkg) 157 p.CNT_USED += 1 158 else: 159 p.CNT_NOPKG += 1 160 elif os.path.isdir(filepath): 161 if not filename in p.IGNORE_DIR: 162 parseSrcDir(p, filepath) 163 164 165# Check a java file to find its package declaration, if any 166def checkJavaFile(path): 167 try: 168 f = None 169 try: 170 f = file(path) 171 for l in f.readlines(): 172 m = _RE_PKG.match(l) 173 if m: 174 return m.group(1) 175 finally: 176 if f: f.close() 177 except Exception: 178 pass 179 180 return None 181 182 183USED_ARC_PATH = {} 184 185# Copy the given file (given its absolute filepath) to 186# the relative desk_pkg directory in the zip file. 187def copy(p, filepath, dest_pkg): 188 arc_path = os.path.join(TOP_FOLDER, dest_pkg, os.path.basename(filepath)) 189 if arc_path in USED_ARC_PATH: 190 verbose("Ignore duplicate archive path %s", arc_path) 191 USED_ARC_PATH[arc_path] = 1 192 if p.DRY: 193 print >>sys.stderr, "zip %s [%s]" % (arc_path, filepath) 194 elif p.zipfile is not None: 195 if p.EXEC_ZIP: 196 # zipfile is a path. Copy to it. 197 dest_path = os.path.join(p.zipfile, arc_path) 198 dest_dir = os.path.dirname(dest_path) 199 if not os.path.isdir(dest_dir): 200 os.makedirs(dest_dir) 201 shutil.copyfile(filepath, dest_path) 202 else: 203 # zipfile is a ZipFile object. Compress with it. 204 p.zipfile.write(filepath, arc_path) 205 206 207def shellExec(*cmd): 208 """ 209 Executes the given system command. 210 211 The command must be split into a list (c.f. shext.split().) 212 213 This raises an exception if the command fails. 214 Stdin/out/err are not being redirected. 215 """ 216 verbose("exec: %s", repr(cmd)) 217 subprocess.check_call(cmd) 218 219 220def main(): 221 p = parseArgs(sys.argv) 222 z = None 223 try: 224 if not p.DRY: 225 if p.EXEC_ZIP: 226 p.zipfile = p.DST + "_temp_dir" 227 if os.path.exists(p.zipfile): 228 shutil.rmtree(p.zipfile) 229 props_dest = os.path.join(p.zipfile, TOP_FOLDER + "/source.properties") 230 os.makedirs(os.path.dirname(props_dest)) 231 shutil.copyfile(p.PROPS, props_dest) 232 else: 233 p.zipfile = z = zipfile.ZipFile(p.DST, "w", zipfile.ZIP_DEFLATED) 234 z.write(p.PROPS, TOP_FOLDER + "/source.properties") 235 for d in p.DIR.split(): 236 if d: 237 parseSrcDir(p, os.path.join(p.SRC, d)) 238 if p.EXEC_ZIP and not p.DRY: 239 curr_dir = os.getcwd() 240 os.chdir(p.zipfile) 241 if os.path.exists("_temp.zip"): 242 os.unlink("_temp.zip"); 243 shellExec("zip", "-9r", "_temp.zip", TOP_FOLDER) 244 os.chdir(curr_dir) 245 shutil.move(os.path.join(p.zipfile, "_temp.zip"), p.DST) 246 shutil.rmtree(p.zipfile) 247 finally: 248 if z is not None: 249 z.close() 250 print "%s: %d java files copied" % (p.DST, p.CNT_USED) 251 if p.CNT_NOPKG: 252 print "%s: %d java files ignored" % (p.DST, p.CNT_NOPKG) 253 if p.DRY: 254 print >>sys.stderr, "This was in *DRY* mode. No copies done." 255 256 257if __name__ == "__main__": 258 main() 259 260# For emacs: 261# -*- tab-width: 4; -*- 262