Home | History | Annotate | Download | only in tools
      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 
     18 import re
     19 import os
     20 import sys
     21 import getopt
     22 import shutil
     23 import subprocess
     24 import zipfile
     25 
     26 VERBOSE = False
     27 TOP_FOLDER = "src"
     28 _RE_PKG = re.compile("^\s*package\s+([^\s;]+)\s*;.*")
     29 
     30 # Holds cmd-line arguments and context information
     31 class 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 
     51 def 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
     61 def 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.
     85 def 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
    132 def 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
    166 def 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 
    183 USED_ARC_PATH = {}
    184 
    185 # Copy the given file (given its absolute filepath) to
    186 # the relative desk_pkg directory in the zip file.
    187 def 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 
    207 def 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 
    220 def 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 
    257 if __name__ == "__main__":
    258     main()
    259 
    260 # For emacs:
    261 # -*- tab-width: 4; -*-
    262