SDK: Generate source packages as part of 'make sdk_repo'

Change-Id: Iaea55bae22b2af95993d0f97023a015f2c5a5e77
diff --git a/build/tools/mk_sources_zip.py b/build/tools/mk_sources_zip.py
new file mode 100755
index 0000000..8abccb8
--- /dev/null
+++ b/build/tools/mk_sources_zip.py
@@ -0,0 +1,201 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2011 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import re
+import os
+import sys
+import getopt
+import zipfile
+
+VERBOSE = False
+TOP_FOLDER = "src"
+_RE_PKG = re.compile("^\s*package\s+([^\s;]+)\s*;.*")
+
+# Holds cmd-line arguments and context information
+class Params(object):
+    def __init__(self):
+        self.DRY = False
+        self.PROPS = None
+        self.SRC = None
+        self.DST = None
+        self.CNT_USED = 0
+        self.CNT_NOPKG = 0
+        # DIR is the list of directories to scan in TOPDIR.
+        self.DIR = "frameworks libcore"
+        # IGNORE is a list of namespaces to ignore. Must be java
+        # package definitions (e.g. "com.something.foo.")
+        self.IGNORE = [ "sun.", "com.", "libcore.", "dalvik.",
+                        "coretestutils.", "test.", "test2.", "tests." ]
+        self.zipfile = None
+
+
+def verbose(msg, *args):
+    """Prints a verbose message to stderr if --verbose is set."""
+    global VERBOSE
+    if VERBOSE:
+        if args:
+            msg = msg % args
+        print >>sys.stderr, msg
+
+
+# Prints a usage summary
+def usage(error=None):
+    print """
+ Description:
+   This script collects all framework Java sources from the current android
+   source code and places them in a source.zip file that can be distributed
+   by the SDK Manager.
+
+ Usage:
+   %s [-n|-v] <source.properties> <sources.zip> <topdir>
+
+ The source.properties file must exist and will be injected in the Zip file.
+ The source directory must already exist.
+ Use -v for verbose output (lists each file being picked up or ignored).
+ Use -n for a dry-run (doesn't write the zip file).
+
+""" % sys.argv[0]
+
+    if error:
+        print >>sys.stderr, "Error:", error
+
+
+# Parse command line args, returns a Params instance or sys.exit(2) on error
+# after printing the error and the usage.
+def parseArgs(argv):
+    global VERBOSE
+    p = Params()
+    error = None
+
+    try:
+        opts, args = getopt.getopt(argv[1:],
+                                   "vns:",
+                                   [ "--verbose", "--dry", "--sourcedir=" ])
+    except getopt.GetoptError, e:
+        error = str(e)
+
+    if error is None:
+        for o, a in opts:
+            if o in [ "-n", "--dry" ]:
+                p.DRY = True
+            if o in [ "-v", "--verbose" ]:
+                VERBOSE = True
+            elif o in [ "-s", "--sourcedir" ]:
+                p.DIR = a
+
+        if len(args) != 3:
+            error = "Missing arguments: <source> <dest>"
+        else:
+            p.PROPS = args[0]
+            p.DST = args[1]
+            p.SRC = args[2]
+
+            if not os.path.isfile(p.PROPS):
+                error = "%s is not a file" % p.PROPS
+            if not os.path.isdir(p.SRC):
+                error = "%s is not a directory" % p.SRC
+
+    if error:
+        usage(error)
+        sys.exit(2)
+
+    return p
+
+
+# Recursively parses the given directory and processes java files found
+def parseSrcDir(p, srcdir):
+    if not os.path.exists(srcdir):
+        verbose("Error: Skipping unknown directory %s", srcdir)
+        return
+
+    for filename in os.listdir(srcdir):
+        filepath = os.path.join(srcdir, filename)
+        if filename.endswith(".java") and os.path.isfile(filepath):
+            pkg = checkJavaFile(filepath)
+            if not pkg:
+                verbose("No package found in %s", filepath)
+            if pkg:
+                # Should we ignore this package?
+                for ignore in p.IGNORE:
+                    if pkg.startswith(ignore):
+                        verbose("Ignore package %s [%s]", pkg, filepath)
+                        pkg = None
+                        break
+
+            if pkg:
+                pkg = pkg.replace(".", os.path.sep)  # e.g. android.view => android/view
+                copy(p, filepath, pkg)
+                p.CNT_USED += 1
+            else:
+                p.CNT_NOPKG += 1
+        elif os.path.isdir(filepath):
+            parseSrcDir(p, filepath)
+
+
+# Check a java file to find its package declaration, if any
+def checkJavaFile(path):
+    try:
+        f = None
+        try:
+            f = file(path)
+            for l in f.readlines():
+                m = _RE_PKG.match(l)
+                if m:
+                    return m.group(1)
+        finally:
+            if f: f.close()
+    except Exception:
+        pass
+
+    return None
+
+
+# Copy the given file (given its absolute filepath) to
+# the relative desk_pkg directory in the zip file.
+def copy(p, filepath, dest_pkg):
+    arc_path = os.path.join(TOP_FOLDER, dest_pkg, os.path.basename(filepath))
+    if p.DRY:
+        print >>sys.stderr, "zip %s [%s]" % (arc_path, filepath)
+    elif p.zipfile is not None:
+        p.zipfile.write(filepath, arc_path)
+
+
+def main():
+    p = parseArgs(sys.argv)
+    z = None
+    try:
+        if not p.DRY:
+            p.zipfile = z = zipfile.ZipFile(p.DST, "w", zipfile.ZIP_DEFLATED)
+            z.write(p.PROPS, TOP_FOLDER + "/source.properties")
+        for d in p.DIR.split():
+            if d:
+                parseSrcDir(p, os.path.join(p.SRC, d))
+    finally:
+        if z is not None:
+            z.close()
+    print "%s: %d java files copied" % (p.DST, p.CNT_USED)
+    if p.CNT_NOPKG:
+        print "%s: %d java files ignored" % (p.DST, p.CNT_NOPKG)
+    if p.DRY:
+        print >>sys.stderr, "This was in *DRY* mode. No copies done."
+
+
+if __name__ == "__main__":
+    main()
+
+# For emacs:
+# -*- tab-width: 4; -*-