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" ]
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