Home | History | Annotate | Download | only in sdk
      1 #!/usr/bin/python
      2 
      3 import os, sys, getopt, zipfile, re
      4 import argparse
      5 import subprocess
      6 from shutil import copyfile, rmtree
      7 from distutils.version import LooseVersion
      8 
      9 current_path = 'current'
     10 system_path = 'system_current'
     11 support_path = os.path.join(current_path, 'support')
     12 
     13 # See go/fetch_artifact
     14 FETCH_ARTIFACT = '/google/data/ro/projects/android/fetch_artifact'
     15 
     16 # Does not import support-v4, which is handled as a separate Android.mk (../support-v4) to
     17 # statically include dependencies. Similarly, the support-v13 module is imported as
     18 # support-v13-nodeps and then handled as a separate Android.mk (../support-v13) to statically
     19 # include dependencies.
     20 maven_to_make = {
     21     'animated-vector-drawable':     ['android-support-animatedvectordrawable',      'graphics/drawable'],
     22     'appcompat-v7':                 ['android-support-v7-appcompat-nodeps',         'v7/appcompat'],
     23     'cardview-v7':                  ['android-support-v7-cardview',                 'v7/cardview'],
     24     'customtabs':                   ['android-support-customtabs',                  'customtabs'],
     25     'design':                       ['android-support-design',                      'design'],
     26     'exifinterface':                ['android-support-exifinterface',               'exifinterface'],
     27     'gridlayout-v7':                ['android-support-v7-gridlayout',               'v7/gridlayout'],
     28     'leanback-v17':                 ['android-support-v17-leanback',                'v17/leanback'],
     29     'mediarouter-v7':               ['android-support-v7-mediarouter',              'v7/mediarouter'],
     30     'multidex':                     ['android-support-multidex',                    'multidex/library'],
     31     'multidex-instrumentation':     ['android-support-multidex-instrumentation',    'multidex/instrumentation'],
     32     'palette-v7':                   ['android-support-v7-palette',                  'v7/palette'],
     33     'percent':                      ['android-support-percent',                     'percent'],
     34     'preference-leanback-v17':      ['android-support-v17-preference-leanback',     'v17/preference-leanback'],
     35     'preference-v14':               ['android-support-v14-preference',              'v14/preference'],
     36     'preference-v7':                ['android-support-v7-preference',               'v7/preference'],
     37     'recommendation':               ['android-support-recommendation',              'recommendation'],
     38     'recyclerview-v7':              ['android-support-v7-recyclerview',             'v7/recyclerview'],
     39     'support-annotations':          ['android-support-annotations',                 'annotations'],
     40     'support-compat':               ['android-support-compat',                      'compat'],
     41     'support-core-ui':              ['android-support-core-ui',                     'core-ui'],
     42     'support-core-utils':           ['android-support-core-utils',                  'core-utils'],
     43     'support-dynamic-animation':    ['android-support-dynamic-animation',           'dynamic-animation'],
     44     'support-emoji':                ['android-support-emoji',                       'emoji'],
     45     'support-emoji-appcompat':      ['android-support-emoji-appcompat',             'emoji-appcompat'],
     46     'support-emoji-bundled':        ['android-support-emoji-bundled',               'emoji-bundled'],
     47     'support-fragment':             ['android-support-fragment',                    'fragment'],
     48     'support-media-compat':         ['android-support-media-compat',                'media-compat'],
     49     'support-tv-provider':          ['android-support-tv-provider',                 'tv-provider'],
     50     'support-v13':                  ['android-support-v13-nodeps',                  'v13'],
     51     'support-vector-drawable':      ['android-support-vectordrawable',              'graphics/drawable'],
     52     'transition':                   ['android-support-transition',                  'transition'],
     53     'wear':                         ['android-support-wear',                        'wear']
     54 }
     55 
     56 # Always remove these files.
     57 blacklist_files = [
     58     'annotations.zip',
     59     'public.txt',
     60     'R.txt',
     61     'AndroidManifest.xml'
     62 ]
     63 
     64 artifact_pattern = re.compile(r"^(.+?)-(\d+\.\d+\.\d+(?:-\w+\d*)?)\.(jar|aar)$")
     65 
     66 
     67 def touch(fname, times=None):
     68     with open(fname, 'a'):
     69         os.utime(fname, times)
     70 
     71 
     72 def path(*path_parts):
     73     return reduce((lambda x, y: os.path.join(x, y)), path_parts)
     74 
     75 
     76 def rm(path):
     77     if os.path.isdir(path):
     78         rmtree(path)
     79     elif os.path.exists(path):
     80         os.remove(path)
     81 
     82 
     83 def mv(src_path, dst_path):
     84     rm(dst_path)
     85     os.rename(src_path, dst_path)
     86 
     87 
     88 def transform_support(repoDir):
     89     cwd = os.getcwd()
     90 
     91     # Use a temporary working directory.
     92     working_dir = os.path.join(cwd, 'support_tmp')
     93     if os.path.exists(working_dir):
     94         rmtree(working_dir)
     95     os.mkdir(working_dir)
     96 
     97     maven_lib_info = {}
     98 
     99     # Find the latest revision for each artifact.
    100     for root, dirs, files in os.walk(repoDir):
    101         for file in files:
    102             matcher = artifact_pattern.match(file)
    103             if matcher:
    104                 maven_lib_name = matcher.group(1)
    105                 maven_lib_vers = LooseVersion(matcher.group(2))
    106 
    107                 if maven_lib_name in maven_to_make:
    108                     if maven_lib_name not in maven_lib_info \
    109                             or maven_lib_vers > maven_lib_info[maven_lib_name][0]:
    110                         maven_lib_info[maven_lib_name] = [maven_lib_vers, root, file]
    111 
    112     for info in maven_lib_info.values():
    113         transform_maven_lib(working_dir, info[1], info[2])
    114 
    115     # Replace the old directory.
    116     output_dir = os.path.join(cwd, support_path)
    117     if os.path.exists(output_dir):
    118         rmtree(output_dir)
    119     os.rename(working_dir, output_dir)
    120 
    121 
    122 def transform_maven_lib(working_dir, root, file):
    123     matcher = artifact_pattern.match(file)
    124     maven_lib_name = matcher.group(1)
    125     maven_lib_vers = matcher.group(2)
    126     maven_lib_type = matcher.group(3)
    127 
    128     make_lib_name = maven_to_make[maven_lib_name][0]
    129     make_dir_name = maven_to_make[maven_lib_name][1]
    130     artifact_file = os.path.join(root, file)
    131     target_dir = os.path.join(working_dir, make_dir_name)
    132     if not os.path.exists(target_dir):
    133         os.makedirs(target_dir)
    134 
    135     if maven_lib_type == "aar":
    136         process_aar(artifact_file, target_dir, make_lib_name)
    137     else:
    138         target_file = os.path.join(target_dir, make_lib_name + ".jar")
    139         os.rename(artifact_file, target_file)
    140 
    141     print maven_lib_vers, ":", maven_lib_name, "->", make_lib_name
    142 
    143 
    144 def process_aar(artifact_file, target_dir, make_lib_name):
    145     # Extract AAR file to target_dir.
    146     with zipfile.ZipFile(artifact_file) as zip:
    147         zip.extractall(target_dir)
    148 
    149     # Rename classes.jar to match the make artifact
    150     classes_jar = os.path.join(target_dir, "classes.jar")
    151     if os.path.exists(classes_jar):
    152         # If it has resources, it needs a libs dir.
    153         res_dir = os.path.join(target_dir, "res")
    154         if os.path.exists(res_dir) and os.listdir(res_dir):
    155             libs_dir = os.path.join(target_dir, "libs")
    156             if not os.path.exists(libs_dir):
    157                 os.mkdir(libs_dir)
    158         else:
    159             libs_dir = target_dir
    160         target_jar = os.path.join(libs_dir, make_lib_name + ".jar")
    161         os.rename(classes_jar, target_jar)
    162 
    163     # Remove or preserve empty dirs.
    164     for root, dirs, files in os.walk(target_dir):
    165         for dir in dirs:
    166             dir_path = os.path.join(root, dir)
    167             if not os.listdir(dir_path):
    168                 os.rmdir(dir_path)
    169 
    170     # Remove top-level cruft.
    171     for file in blacklist_files:
    172         file_path = os.path.join(target_dir, file)
    173         if os.path.exists(file_path):
    174             os.remove(file_path)
    175 
    176 def fetch_artifact(target, buildId, artifact_path):
    177     print 'Fetching %s from %s...' % (artifact_path, target)
    178     fetchCmd = [FETCH_ARTIFACT, '--bid', str(buildId), '--target', target, artifact_path]
    179     try:
    180         subprocess.check_output(fetchCmd, stderr=subprocess.STDOUT)
    181     except subprocess.CalledProcessError:
    182         print >> sys.stderr, 'FAIL: Unable to retrieve %s artifact for build ID %d' % (artifact_path, buildId)
    183         return None
    184     return artifact_path
    185 
    186 
    187 def update_support(target, buildId):
    188     platform = 'darwin' if 'mac' in target else 'linux'
    189     artifact_path = fetch_artifact(target, buildId, 'top-of-tree-m2repository-%s.zip' % (buildId))
    190     if not artifact_path:
    191         return
    192 
    193     # Unzip the repo archive into a separate directory.
    194     repoDir = os.path.basename(artifact_path)[:-4]
    195     with zipfile.ZipFile(artifact_path) as zipFile:
    196         zipFile.extractall(repoDir)
    197 
    198     # Transform the repo archive into a Makefile-compatible format.
    199     transform_support(repoDir)
    200 
    201 
    202 def extract_to(zip_file, paths, filename, parent_path):
    203     zip_path = filter(lambda path: filename in path, paths)[0]
    204     src_path = zip_file.extract(zip_path)
    205     dst_path = path(parent_path, filename)
    206     mv(src_path, dst_path)
    207 
    208 
    209 def update_sdk_repo(target, buildId):
    210     platform = 'darwin' if 'mac' in target else 'linux'
    211     artifact_path = fetch_artifact(target, buildId, 'sdk-repo-%s-platforms-%s.zip' % (platform, buildId))
    212     if not artifact_path:
    213         return
    214 
    215     with zipfile.ZipFile(artifact_path) as zipFile:
    216         paths = zipFile.namelist()
    217 
    218         extract_to(zipFile, paths, 'android.jar', current_path)
    219         extract_to(zipFile, paths, 'uiautomator.jar', current_path)
    220         extract_to(zipFile, paths, 'framework.aidl', current_path)
    221 
    222         # Unclear if this is actually necessary.
    223         extract_to(zipFile, paths, 'framework.aidl', system_path)
    224 
    225 
    226 def update_system(target, buildId):
    227     artifact_path = fetch_artifact(target, buildId, 'android_system.jar')
    228     if not artifact_path:
    229         return
    230 
    231     mv(artifact_path, path(system_path, 'android.jar'))
    232 
    233 
    234 parser = argparse.ArgumentParser(
    235     description=('Update current prebuilts'))
    236 parser.add_argument(
    237     'buildId',
    238     type=int,
    239     help='Build server build ID')
    240 parser.add_argument(
    241     '-s', '--support', action="store_true",
    242     help='If specified, updates only the Support Library')
    243 parser.add_argument(
    244     '-p', '--platform', action="store_true",
    245     help='If specified, updates only the Android Platform')
    246 args = parser.parse_args()
    247 if not args.buildId:
    248     parser.error("You must specify a build ID")
    249     sys.exit(1)
    250 
    251 try:
    252     # Make sure we don't overwrite any pending changes.
    253     subprocess.check_call(['git', 'diff', '--quiet', '--', '**'])
    254     subprocess.check_call(['git', 'diff', '--quiet', '--cached', '--', '**'])
    255 except subprocess.CalledProcessError:
    256     print >> sys.stderr, "FAIL: There are uncommitted changes here; please revert or stash"
    257     sys.exit(1)
    258 
    259 try:
    260     has_args = args.support or args.platform
    261 
    262     if has_args and args.support:
    263         update_support('support_library', args.buildId)
    264     if has_args and args.platform:
    265         update_sdk_repo('sdk_phone_armv7-sdk_mac', args.buildId)
    266         update_system('sdk_phone_armv7-sdk_mac', args.buildId)
    267 
    268     # Commit all changes.
    269     subprocess.check_call(['git', 'add', current_path])
    270     subprocess.check_call(['git', 'add', system_path])
    271     msg = "Import support libs from build %s" % args.buildId
    272     subprocess.check_call(['git', 'commit', '-m', msg])
    273     print 'Be sure to upload this manually to gerrit.'
    274 
    275 finally:
    276     # Revert all stray files, including the downloaded zip.
    277     try:
    278         with open(os.devnull, 'w') as bitbucket:
    279             subprocess.check_call(['git', 'add', '-Af', '.'], stdout=bitbucket)
    280             subprocess.check_call(
    281                 ['git', 'commit', '-m', 'COMMIT TO REVERT - RESET ME!!!'], stdout=bitbucket)
    282             subprocess.check_call(['git', 'reset', '--hard', 'HEAD~1'], stdout=bitbucket)
    283     except subprocess.CalledProcessError:
    284         print >> sys.stderr, "ERROR: Failed cleaning up, manual cleanup required!!!"