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