| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2016 The Chromium Authors. All rights reserved. | 2 # Copyright 2016 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Generates an Android Studio project from a GN target.""" | 6 """Generates an Android Studio project from a GN target.""" |
| 7 | 7 |
| 8 import argparse | 8 import argparse |
| 9 import codecs | 9 import codecs |
| 10 import logging | 10 import logging |
| (...skipping 18 matching lines...) Expand all Loading... |
| 29 | 29 |
| 30 _DEFAULT_ANDROID_MANIFEST_PATH = os.path.join( | 30 _DEFAULT_ANDROID_MANIFEST_PATH = os.path.join( |
| 31 host_paths.DIR_SOURCE_ROOT, 'build', 'android', 'AndroidManifest.xml') | 31 host_paths.DIR_SOURCE_ROOT, 'build', 'android', 'AndroidManifest.xml') |
| 32 _JINJA_TEMPLATE_PATH = os.path.join( | 32 _JINJA_TEMPLATE_PATH = os.path.join( |
| 33 os.path.dirname(__file__), 'build.gradle.jinja') | 33 os.path.dirname(__file__), 'build.gradle.jinja') |
| 34 | 34 |
| 35 _JAVA_SUBDIR = 'symlinked-java' | 35 _JAVA_SUBDIR = 'symlinked-java' |
| 36 _SRCJARS_SUBDIR = 'extracted-srcjars' | 36 _SRCJARS_SUBDIR = 'extracted-srcjars' |
| 37 | 37 |
| 38 _DEFAULT_TARGETS = [ | 38 _DEFAULT_TARGETS = [ |
| 39 '//android_webview:system_webview_apk', | 39 # TODO(agrieve): Requires alternate android.jar to compile. |
| 40 # '//android_webview:system_webview_apk', |
| 40 '//android_webview/test:android_webview_apk', | 41 '//android_webview/test:android_webview_apk', |
| 41 '//android_webview/test:android_webview_test_apk', | 42 '//android_webview/test:android_webview_test_apk', |
| 43 '//base:base_junit_tests', |
| 44 '//chrome/android:chrome_junit_tests', |
| 42 '//chrome/android:chrome_public_apk', | 45 '//chrome/android:chrome_public_apk', |
| 43 '//chrome/android:chrome_public_test_apk', | 46 '//chrome/android:chrome_public_test_apk', |
| 44 '//chrome/android:chrome_sync_shell_apk', | 47 '//chrome/android:chrome_sync_shell_apk', |
| 45 '//chrome/android:chrome_sync_shell_test_apk', | 48 '//chrome/android:chrome_sync_shell_test_apk', |
| 49 '//content/public/android:content_junit_tests', |
| 50 '//content/shell/android:content_shell_apk', |
| 46 ] | 51 ] |
| 47 | 52 |
| 48 def _RebasePath(path_or_list, new_cwd=None, old_cwd=None): | 53 def _RebasePath(path_or_list, new_cwd=None, old_cwd=None): |
| 49 """Makes the given path(s) relative to new_cwd, or absolute if not specified. | 54 """Makes the given path(s) relative to new_cwd, or absolute if not specified. |
| 50 | 55 |
| 51 If new_cwd is not specified, absolute paths are returned. | 56 If new_cwd is not specified, absolute paths are returned. |
| 52 If old_cwd is not specified, constants.GetOutDirectory() is assumed. | 57 If old_cwd is not specified, constants.GetOutDirectory() is assumed. |
| 53 """ | 58 """ |
| 54 if not isinstance(path_or_list, basestring): | 59 if not isinstance(path_or_list, basestring): |
| 55 return [_RebasePath(p, new_cwd, old_cwd) for p in path_or_list] | 60 return [_RebasePath(p, new_cwd, old_cwd) for p in path_or_list] |
| (...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 158 """Returns the target type from its .build_config.""" | 163 """Returns the target type from its .build_config.""" |
| 159 return self.BuildConfig()['deps_info']['type'] | 164 return self.BuildConfig()['deps_info']['type'] |
| 160 | 165 |
| 161 | 166 |
| 162 def _ComputeJavaSourceDirs(java_files): | 167 def _ComputeJavaSourceDirs(java_files): |
| 163 """Returns the list of source directories for the given files.""" | 168 """Returns the list of source directories for the given files.""" |
| 164 found_roots = set() | 169 found_roots = set() |
| 165 for path in java_files: | 170 for path in java_files: |
| 166 path_root = path | 171 path_root = path |
| 167 # Recognize these tokens as top-level. | 172 # Recognize these tokens as top-level. |
| 168 while os.path.basename(path_root) not in ('javax', 'org', 'com', 'src'): | 173 while True: |
| 169 assert path_root, 'Failed to find source dir for ' + path | |
| 170 path_root = os.path.dirname(path_root) | 174 path_root = os.path.dirname(path_root) |
| 171 # Assume that if we've hit "src", the we're at the root. | 175 basename = os.path.basename(path_root) |
| 172 if os.path.basename(path_root) != 'src': | 176 assert basename, 'Failed to find source dir for ' + path |
| 173 path_root = os.path.dirname(path_root) | 177 if basename in ('java', 'src'): |
| 178 break |
| 179 if basename in ('javax', 'org', 'com'): |
| 180 path_root = os.path.dirname(path_root) |
| 181 break |
| 174 found_roots.add(path_root) | 182 found_roots.add(path_root) |
| 175 return list(found_roots) | 183 return list(found_roots) |
| 176 | 184 |
| 177 | 185 |
| 178 def _CreateSymlinkTree(entry_output_dir, symlink_dir, desired_files, | 186 def _CreateSymlinkTree(entry_output_dir, symlink_dir, desired_files, |
| 179 parent_dirs): | 187 parent_dirs): |
| 180 """Creates a directory tree of symlinks to the given files. | 188 """Creates a directory tree of symlinks to the given files. |
| 181 | 189 |
| 182 The idea here is to replicate a directory tree while leaving out files within | 190 The idea here is to replicate a directory tree while leaving out files within |
| 183 it not listed by |desired_files|. | 191 it not listed by |desired_files|. |
| 184 """ | 192 """ |
| 185 assert _IsSubpathOf(symlink_dir, entry_output_dir) | 193 assert _IsSubpathOf(symlink_dir, entry_output_dir) |
| 186 if os.path.exists(symlink_dir): | |
| 187 shutil.rmtree(symlink_dir) | |
| 188 | 194 |
| 189 for target_path in desired_files: | 195 for target_path in desired_files: |
| 190 prefix = next(d for d in parent_dirs if target_path.startswith(d)) | 196 prefix = next(d for d in parent_dirs if target_path.startswith(d)) |
| 191 subpath = os.path.relpath(target_path, prefix) | 197 subpath = os.path.relpath(target_path, prefix) |
| 192 symlinked_path = os.path.join(symlink_dir, subpath) | 198 symlinked_path = os.path.join(symlink_dir, subpath) |
| 193 symlinked_dir = os.path.dirname(symlinked_path) | 199 symlinked_dir = os.path.dirname(symlinked_path) |
| 194 if not os.path.exists(symlinked_dir): | 200 if not os.path.exists(symlinked_dir): |
| 195 os.makedirs(symlinked_dir) | 201 os.makedirs(symlinked_dir) |
| 196 relpath = os.path.relpath(target_path, symlinked_dir) | 202 relpath = os.path.relpath(target_path, symlinked_dir) |
| 197 logging.debug('Creating symlink %s -> %s', symlinked_path, relpath) | 203 logging.debug('Creating symlink %s -> %s', symlinked_path, relpath) |
| (...skipping 13 matching lines...) Expand all Loading... |
| 211 if java_files: | 217 if java_files: |
| 212 java_files = _RebasePath(java_files) | 218 java_files = _RebasePath(java_files) |
| 213 java_dirs = _ComputeJavaSourceDirs(java_files) | 219 java_dirs = _ComputeJavaSourceDirs(java_files) |
| 214 | 220 |
| 215 found_java_files = build_utils.FindInDirectories(java_dirs, '*.java') | 221 found_java_files = build_utils.FindInDirectories(java_dirs, '*.java') |
| 216 unwanted_java_files = set(found_java_files) - set(java_files) | 222 unwanted_java_files = set(found_java_files) - set(java_files) |
| 217 missing_java_files = set(java_files) - set(found_java_files) | 223 missing_java_files = set(java_files) - set(found_java_files) |
| 218 # Warn only about non-generated files that are missing. | 224 # Warn only about non-generated files that are missing. |
| 219 missing_java_files = [p for p in missing_java_files | 225 missing_java_files = [p for p in missing_java_files |
| 220 if not p.startswith(output_dir)] | 226 if not p.startswith(output_dir)] |
| 227 |
| 228 symlink_dir = os.path.join(entry_output_dir, _JAVA_SUBDIR) |
| 229 shutil.rmtree(symlink_dir, True) |
| 230 |
| 221 if unwanted_java_files: | 231 if unwanted_java_files: |
| 222 logging.debug('Target requires .java symlinks: %s', entry_output_dir) | 232 logging.debug('Target requires .java symlinks: %s', entry_output_dir) |
| 223 symlink_dir = os.path.join(entry_output_dir, _JAVA_SUBDIR) | |
| 224 _CreateSymlinkTree(entry_output_dir, symlink_dir, java_files, java_dirs) | 233 _CreateSymlinkTree(entry_output_dir, symlink_dir, java_files, java_dirs) |
| 225 java_dirs = [symlink_dir] | 234 java_dirs = [symlink_dir] |
| 226 | 235 |
| 227 if missing_java_files: | 236 if missing_java_files: |
| 228 logging.warning('Some java files were not found: %s', missing_java_files) | 237 logging.warning('Some java files were not found: %s', missing_java_files) |
| 229 | 238 |
| 230 return java_dirs | 239 return java_dirs |
| 231 | 240 |
| 232 | 241 |
| 233 def _GenerateLocalProperties(sdk_dir): | 242 def _GenerateLocalProperties(sdk_dir): |
| 234 """Returns the data for project.properties as a string.""" | 243 """Returns the data for project.properties as a string.""" |
| 235 return '\n'.join([ | 244 return '\n'.join([ |
| 236 '# Generated by //build/android/gradle/generate_gradle.py', | 245 '# Generated by //build/android/gradle/generate_gradle.py', |
| 237 'sdk.dir=%s' % sdk_dir, | 246 'sdk.dir=%s' % sdk_dir, |
| 238 '']) | 247 '']) |
| 239 | 248 |
| 240 | 249 |
| 241 def _GenerateGradleFile(build_config, build_vars, java_dirs, relativize, | 250 def _GenerateGradleFile(build_config, build_vars, java_dirs, relativize, |
| 242 use_gradle_process_resources, jinja_processor): | 251 use_gradle_process_resources, jinja_processor): |
| 243 """Returns the data for a project's build.gradle.""" | 252 """Returns the data for a project's build.gradle.""" |
| 244 deps_info = build_config['deps_info'] | 253 deps_info = build_config['deps_info'] |
| 245 gradle = build_config['gradle'] | 254 gradle = build_config['gradle'] |
| 246 | 255 |
| 256 variables = { |
| 257 'sourceSetName': 'main', |
| 258 'depCompileName': 'compile', |
| 259 } |
| 247 if deps_info['type'] == 'android_apk': | 260 if deps_info['type'] == 'android_apk': |
| 248 target_type = 'android_apk' | 261 target_type = 'android_apk' |
| 249 elif deps_info['type'] == 'java_library' and not deps_info['is_prebuilt']: | 262 elif deps_info['type'] == 'java_library': |
| 263 if deps_info['is_prebuilt'] or deps_info['gradle_treat_as_prebuilt']: |
| 264 return None |
| 265 |
| 250 if deps_info['requires_android']: | 266 if deps_info['requires_android']: |
| 251 target_type = 'android_library' | 267 target_type = 'android_library' |
| 252 else: | 268 else: |
| 253 target_type = 'java_library' | 269 target_type = 'java_library' |
| 270 elif deps_info['type'] == 'java_binary': |
| 271 if gradle['main_class'] == 'org.chromium.testing.local.JunitTestMain': |
| 272 target_type = 'android_junit' |
| 273 variables['sourceSetName'] = 'test' |
| 274 variables['depCompileName'] = 'testCompile' |
| 275 else: |
| 276 target_type = 'java_binary' |
| 277 variables['main_class'] = gradle['main_class'] |
| 254 else: | 278 else: |
| 255 return None | 279 return None |
| 256 | 280 |
| 257 variables = {} | 281 variables['target_name'] = os.path.splitext(deps_info['name'])[0] |
| 258 variables['template_type'] = target_type | 282 variables['template_type'] = target_type |
| 259 variables['use_gradle_process_resources'] = use_gradle_process_resources | 283 variables['use_gradle_process_resources'] = use_gradle_process_resources |
| 260 variables['build_tools_version'] = ( | 284 variables['build_tools_version'] = ( |
| 261 build_vars['android_sdk_build_tools_version']) | 285 build_vars['android_sdk_build_tools_version']) |
| 262 variables['compile_sdk_version'] = build_vars['android_sdk_version'] | 286 variables['compile_sdk_version'] = build_vars['android_sdk_version'] |
| 263 android_manifest = gradle.get('android_manifest', | 287 android_manifest = gradle.get('android_manifest', |
| 264 _DEFAULT_ANDROID_MANIFEST_PATH) | 288 _DEFAULT_ANDROID_MANIFEST_PATH) |
| 265 variables['android_manifest'] = relativize(android_manifest) | 289 variables['android_manifest'] = relativize(android_manifest) |
| 266 variables['java_dirs'] = relativize(java_dirs) | 290 variables['java_dirs'] = relativize(java_dirs) |
| 291 # TODO(agrieve): Add an option to use interface jars and see if that speeds |
| 292 # things up at all. |
| 267 variables['prebuilts'] = relativize(gradle['dependent_prebuilt_jars']) | 293 variables['prebuilts'] = relativize(gradle['dependent_prebuilt_jars']) |
| 268 deps = [_ProjectEntry.FromBuildConfigPath(p) | 294 deps = [_ProjectEntry.FromBuildConfigPath(p) |
| 269 for p in gradle['dependent_android_projects']] | 295 for p in gradle['dependent_android_projects']] |
| 270 | 296 |
| 271 variables['android_project_deps'] = [d.ProjectName() for d in deps] | 297 variables['android_project_deps'] = [d.ProjectName() for d in deps] |
| 272 deps = [_ProjectEntry.FromBuildConfigPath(p) | 298 deps = [_ProjectEntry.FromBuildConfigPath(p) |
| 273 for p in gradle['dependent_java_projects']] | 299 for p in gradle['dependent_java_projects']] |
| 274 variables['java_project_deps'] = [d.ProjectName() for d in deps] | 300 variables['java_project_deps'] = [d.ProjectName() for d in deps] |
| 275 | 301 |
| 276 return jinja_processor.Render(_JINJA_TEMPLATE_PATH, variables) | 302 return jinja_processor.Render(_JINJA_TEMPLATE_PATH, variables) |
| (...skipping 20 matching lines...) Expand all Loading... |
| 297 lines.append('project(":%s").projectDir = new File(settingsDir, "%s")' % | 323 lines.append('project(":%s").projectDir = new File(settingsDir, "%s")' % |
| 298 (entry.ProjectName(), entry.GradleSubdir())) | 324 (entry.ProjectName(), entry.GradleSubdir())) |
| 299 return '\n'.join(lines) | 325 return '\n'.join(lines) |
| 300 | 326 |
| 301 | 327 |
| 302 def _ExtractSrcjars(entry_output_dir, srcjar_tuples): | 328 def _ExtractSrcjars(entry_output_dir, srcjar_tuples): |
| 303 """Extracts all srcjars to the directory given by the tuples.""" | 329 """Extracts all srcjars to the directory given by the tuples.""" |
| 304 extracted_paths = set(s[1] for s in srcjar_tuples) | 330 extracted_paths = set(s[1] for s in srcjar_tuples) |
| 305 for extracted_path in extracted_paths: | 331 for extracted_path in extracted_paths: |
| 306 assert _IsSubpathOf(extracted_path, entry_output_dir) | 332 assert _IsSubpathOf(extracted_path, entry_output_dir) |
| 307 if os.path.exists(extracted_path): | 333 shutil.rmtree(extracted_path, True) |
| 308 shutil.rmtree(extracted_path) | |
| 309 | 334 |
| 310 for srcjar_path, extracted_path in srcjar_tuples: | 335 for srcjar_path, extracted_path in srcjar_tuples: |
| 311 logging.info('Extracting %s to %s', srcjar_path, extracted_path) | 336 logging.info('Extracting %s to %s', srcjar_path, extracted_path) |
| 312 with zipfile.ZipFile(srcjar_path) as z: | 337 with zipfile.ZipFile(srcjar_path) as z: |
| 313 z.extractall(extracted_path) | 338 z.extractall(extracted_path) |
| 314 | 339 |
| 315 | 340 |
| 316 def _FindAllProjectEntries(main_entries): | 341 def _FindAllProjectEntries(main_entries): |
| 317 """Returns the list of all _ProjectEntry instances given the root project.""" | 342 """Returns the list of all _ProjectEntry instances given the root project.""" |
| 318 found = set() | 343 found = set() |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 368 if args.all: | 393 if args.all: |
| 369 # Run GN gen if necessary (faster than running "gn gen" in the no-op case). | 394 # Run GN gen if necessary (faster than running "gn gen" in the no-op case). |
| 370 _RunNinja(output_dir, ['build.ninja']) | 395 _RunNinja(output_dir, ['build.ninja']) |
| 371 # Query ninja for all __build_config targets. | 396 # Query ninja for all __build_config targets. |
| 372 targets = _QueryForAllGnTargets(output_dir) | 397 targets = _QueryForAllGnTargets(output_dir) |
| 373 else: | 398 else: |
| 374 targets = args.targets or _DEFAULT_TARGETS | 399 targets = args.targets or _DEFAULT_TARGETS |
| 375 # TODO(agrieve): See if it makes sense to utilize Gradle's test constructs | 400 # TODO(agrieve): See if it makes sense to utilize Gradle's test constructs |
| 376 # for our instrumentation tests. | 401 # for our instrumentation tests. |
| 377 targets = [re.sub(r'_test_apk$', '_test_apk__apk', t) for t in targets] | 402 targets = [re.sub(r'_test_apk$', '_test_apk__apk', t) for t in targets] |
| 403 targets = [re.sub(r'_junit_tests$', '_junit_tests__java_binary', t) |
| 404 for t in targets] |
| 378 | 405 |
| 379 main_entries = [_ProjectEntry(t) for t in targets] | 406 main_entries = [_ProjectEntry(t) for t in targets] |
| 380 | 407 |
| 381 logging.warning('Building .build_config files...') | 408 logging.warning('Building .build_config files...') |
| 382 _RunNinja(output_dir, [e.NinjaBuildConfigTarget() for e in main_entries]) | 409 _RunNinja(output_dir, [e.NinjaBuildConfigTarget() for e in main_entries]) |
| 383 | 410 |
| 384 # There are many unused libraries, so restrict to those that are actually used | 411 # There are many unused libraries, so restrict to those that are actually used |
| 385 # when using --all. | 412 # when using --all. |
| 386 if args.all: | 413 if args.all: |
| 387 main_entries = [e for e in main_entries if e.GetType() == 'android_apk'] | 414 main_entries = [e for e in main_entries if e.GetType() == 'android_apk'] |
| 388 | 415 |
| 389 all_entries = _FindAllProjectEntries(main_entries) | 416 all_entries = _FindAllProjectEntries(main_entries) |
| 390 logging.info('Found %d dependent build_config targets.', len(all_entries)) | 417 logging.info('Found %d dependent build_config targets.', len(all_entries)) |
| 391 | 418 |
| 392 logging.warning('Writing .gradle files...') | 419 logging.warning('Writing .gradle files...') |
| 393 jinja_processor = jinja_template.JinjaProcessor(host_paths.DIR_SOURCE_ROOT) | 420 jinja_processor = jinja_template.JinjaProcessor(host_paths.DIR_SOURCE_ROOT) |
| 394 build_vars = _ReadBuildVars(output_dir) | 421 build_vars = _ReadBuildVars(output_dir) |
| 395 project_entries = [] | 422 project_entries = [] |
| 396 srcjar_tuples = [] | 423 srcjar_tuples = [] |
| 397 generated_inputs = [] | 424 generated_inputs = [] |
| 398 for entry in all_entries: | 425 for entry in all_entries: |
| 399 if entry.GetType() not in ('android_apk', 'java_library'): | 426 if entry.GetType() not in ('android_apk', 'java_library', 'java_binary'): |
| 400 continue | 427 continue |
| 401 | 428 |
| 402 entry_output_dir = os.path.join(gradle_output_dir, entry.GradleSubdir()) | 429 entry_output_dir = os.path.join(gradle_output_dir, entry.GradleSubdir()) |
| 403 relativize = lambda x, d=entry_output_dir: _RebasePath(x, d) | 430 relativize = lambda x, d=entry_output_dir: _RebasePath(x, d) |
| 404 build_config = entry.BuildConfig() | 431 build_config = entry.BuildConfig() |
| 405 | 432 |
| 406 srcjars = _RebasePath(build_config['gradle'].get('bundled_srcjars', [])) | 433 srcjars = _RebasePath(build_config['gradle'].get('bundled_srcjars', [])) |
| 407 if not args.use_gradle_process_resources: | 434 if not args.use_gradle_process_resources: |
| 408 srcjars += _RebasePath(build_config['javac']['srcjars']) | 435 srcjars += _RebasePath(build_config['javac']['srcjars']) |
| 409 | 436 |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 450 _ExtractSrcjars(gradle_output_dir, srcjar_tuples) | 477 _ExtractSrcjars(gradle_output_dir, srcjar_tuples) |
| 451 | 478 |
| 452 logging.warning('Project created! (%d subprojects)', len(project_entries)) | 479 logging.warning('Project created! (%d subprojects)', len(project_entries)) |
| 453 logging.warning('Generated projects work best with Android Studio 2.2') | 480 logging.warning('Generated projects work best with Android Studio 2.2') |
| 454 logging.warning('For more tips: https://chromium.googlesource.com/chromium' | 481 logging.warning('For more tips: https://chromium.googlesource.com/chromium' |
| 455 '/src.git/+/master/docs/android_studio.md') | 482 '/src.git/+/master/docs/android_studio.md') |
| 456 | 483 |
| 457 | 484 |
| 458 if __name__ == '__main__': | 485 if __name__ == '__main__': |
| 459 main() | 486 main() |
| OLD | NEW |