blob: 76b5b9e7c8be992fbdefad1f53c6e881261c2b4d [file] [log] [blame]
Raphael Mollda9eabd2011-09-28 11:18:34 -07001#!/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
Raphael Mollbd235022011-11-30 22:39:29 -080022import shutil
23import subprocess
Raphael Mollda9eabd2011-09-28 11:18:34 -070024import 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):
Raphael Mollbd235022011-11-30 22:39:29 -080033 self.EXEC_ZIP = False
Raphael Mollda9eabd2011-09-28 11:18:34 -070034 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"
Raphael Moll9c7b5592012-03-23 10:26:15 -070042 self.IGNORE_DIR = [ "hosttests" "tools" "tests" ]
Raphael Mollda9eabd2011-09-28 11:18:34 -070043 # IGNORE is a list of namespaces to ignore. Must be java
Raphael Mollfdefc022011-10-01 23:42:04 -070044 # package definitions (e.g. "com.blah.foo.")
45 self.IGNORE = [ "sun.", "libcore.", "dalvik.",
46 "com.test.", "com.google.",
Raphael Mollda9eabd2011-09-28 11:18:34 -070047 "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:
Raphael Mollbd235022011-11-30 22:39:29 -080069 %s [-n|-v|-z] <source.properties> <sources.zip> <topdir>
Raphael Mollda9eabd2011-09-28 11:18:34 -070070
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).
Raphael Mollbd235022011-11-30 22:39:29 -080075 Use -z to use the system 'zip' command instead of the Python Zip module.
Raphael Mollda9eabd2011-09-28 11:18:34 -070076
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:],
Raphael Mollbd235022011-11-30 22:39:29 -080092 "zvns:",
93 [ "exec-zip", "verbose", "dry", "sourcedir=" ])
Raphael Mollda9eabd2011-09-28 11:18:34 -070094 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" ]:
Raphael Mollbd235022011-11-30 22:39:29 -0800100 # Dry mode: don't copy/zip, print what would be done.
Raphael Mollda9eabd2011-09-28 11:18:34 -0700101 p.DRY = True
102 if o in [ "-v", "--verbose" ]:
Raphael Mollbd235022011-11-30 22:39:29 -0800103 # Verbose mode. Display everything that's going on.
Raphael Mollda9eabd2011-09-28 11:18:34 -0700104 VERBOSE = True
105 elif o in [ "-s", "--sourcedir" ]:
Raphael Mollbd235022011-11-30 22:39:29 -0800106 # The source directories to process (space separated list)
Raphael Mollda9eabd2011-09-28 11:18:34 -0700107 p.DIR = a
Raphael Mollbd235022011-11-30 22:39:29 -0800108 elif o in [ "-z", "--exec-zip" ]:
109 # Don't use Python zip, instead call the 'zip' system exec.
110 p.EXEC_ZIP = True
Raphael Mollda9eabd2011-09-28 11:18:34 -0700111
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?
Raphael Mollbd235022011-11-30 22:39:29 -0800145 pkg2 = pkg
146 if not "." in pkg2:
147 pkg2 += "."
Raphael Mollda9eabd2011-09-28 11:18:34 -0700148 for ignore in p.IGNORE:
Raphael Mollbd235022011-11-30 22:39:29 -0800149 if pkg2.startswith(ignore):
Raphael Mollda9eabd2011-09-28 11:18:34 -0700150 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):
Raphael Mollbd235022011-11-30 22:39:29 -0800161 if not filename in p.IGNORE_DIR:
162 parseSrcDir(p, filepath)
Raphael Mollda9eabd2011-09-28 11:18:34 -0700163
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
Raphael Mollbd235022011-11-30 22:39:29 -0800183USED_ARC_PATH = {}
184
Raphael Mollda9eabd2011-09-28 11:18:34 -0700185# 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))
Raphael Mollbd235022011-11-30 22:39:29 -0800189 if arc_path in USED_ARC_PATH:
190 verbose("Ignore duplicate archive path %s", arc_path)
191 USED_ARC_PATH[arc_path] = 1
Raphael Mollda9eabd2011-09-28 11:18:34 -0700192 if p.DRY:
193 print >>sys.stderr, "zip %s [%s]" % (arc_path, filepath)
194 elif p.zipfile is not None:
Raphael Mollbd235022011-11-30 22:39:29 -0800195 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)
Raphael Mollda9eabd2011-09-28 11:18:34 -0700218
219
220def main():
221 p = parseArgs(sys.argv)
222 z = None
223 try:
224 if not p.DRY:
Raphael Mollbd235022011-11-30 22:39:29 -0800225 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")
Raphael Mollda9eabd2011-09-28 11:18:34 -0700235 for d in p.DIR.split():
236 if d:
237 parseSrcDir(p, os.path.join(p.SRC, d))
Raphael Mollbd235022011-11-30 22:39:29 -0800238 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)
Raphael Mollda9eabd2011-09-28 11:18:34 -0700247 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; -*-