| 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 glob | 10 import glob |
| (...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 114 for line in ninja_output.splitlines(): | 114 for line in ninja_output.splitlines(): |
| 115 ninja_target = line.rsplit(':', 1)[0] | 115 ninja_target = line.rsplit(':', 1)[0] |
| 116 # Ignore root aliases by ensure a : exists. | 116 # Ignore root aliases by ensure a : exists. |
| 117 if ':' in ninja_target and ninja_target.endswith('__build_config'): | 117 if ':' in ninja_target and ninja_target.endswith('__build_config'): |
| 118 ret.append('//' + ninja_target[:-SUFFIX_LEN]) | 118 ret.append('//' + ninja_target[:-SUFFIX_LEN]) |
| 119 return ret | 119 return ret |
| 120 | 120 |
| 121 | 121 |
| 122 class _ProjectEntry(object): | 122 class _ProjectEntry(object): |
| 123 """Helper class for project entries.""" | 123 """Helper class for project entries.""" |
| 124 |
| 125 _cached_entries = {} |
| 126 |
| 124 def __init__(self, gn_target): | 127 def __init__(self, gn_target): |
| 128 # Use _ProjectEntry.FromGnTarget instead for caching. |
| 129 self._gn_target = gn_target |
| 130 self._build_config = None |
| 131 self._java_files = None |
| 132 self._all_entries = None |
| 133 self.android_test_entry = None |
| 134 |
| 135 @classmethod |
| 136 def FromGnTarget(cls, gn_target): |
| 125 assert gn_target.startswith('//'), gn_target | 137 assert gn_target.startswith('//'), gn_target |
| 126 if ':' not in gn_target: | 138 if ':' not in gn_target: |
| 127 gn_target = '%s:%s' % (gn_target, os.path.basename(gn_target)) | 139 gn_target = '%s:%s' % (gn_target, os.path.basename(gn_target)) |
| 128 self._gn_target = gn_target | 140 if gn_target not in cls._cached_entries: |
| 129 self._build_config = None | 141 cls._cached_entries[gn_target] = cls(gn_target) |
| 130 self._java_files = None | 142 return cls._cached_entries[gn_target] |
| 131 self.android_test_entry = None | |
| 132 | 143 |
| 133 @classmethod | 144 @classmethod |
| 134 def FromBuildConfigPath(cls, path): | 145 def FromBuildConfigPath(cls, path): |
| 135 prefix = 'gen/' | 146 prefix = 'gen/' |
| 136 suffix = '.build_config' | 147 suffix = '.build_config' |
| 137 assert path.startswith(prefix) and path.endswith(suffix), path | 148 assert path.startswith(prefix) and path.endswith(suffix), path |
| 138 subdir = path[len(prefix):-len(suffix)] | 149 subdir = path[len(prefix):-len(suffix)] |
| 139 return cls('//%s:%s' % (os.path.split(subdir))) | 150 gn_target = '//%s:%s' % (os.path.split(subdir)) |
| 151 return cls.FromGnTarget(gn_target) |
| 140 | 152 |
| 141 def __hash__(self): | 153 def __hash__(self): |
| 142 return hash(self._gn_target) | 154 return hash(self._gn_target) |
| 143 | 155 |
| 144 def __eq__(self, other): | 156 def __eq__(self, other): |
| 145 return self._gn_target == other.GnTarget() | 157 return self._gn_target == other.GnTarget() |
| 146 | 158 |
| 147 def GnTarget(self): | 159 def GnTarget(self): |
| 148 return self._gn_target | 160 return self._gn_target |
| 149 | 161 |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 190 def JavaFiles(self): | 202 def JavaFiles(self): |
| 191 if self._java_files is None: | 203 if self._java_files is None: |
| 192 java_sources_file = self.Gradle().get('java_sources_file') | 204 java_sources_file = self.Gradle().get('java_sources_file') |
| 193 java_files = [] | 205 java_files = [] |
| 194 if java_sources_file: | 206 if java_sources_file: |
| 195 java_sources_file = _RebasePath(java_sources_file) | 207 java_sources_file = _RebasePath(java_sources_file) |
| 196 java_files = build_utils.ReadSourcesList(java_sources_file) | 208 java_files = build_utils.ReadSourcesList(java_sources_file) |
| 197 self._java_files = java_files | 209 self._java_files = java_files |
| 198 return self._java_files | 210 return self._java_files |
| 199 | 211 |
| 212 def GeneratedJavaFiles(self): |
| 213 return [p for p in self.JavaFiles() if not p.startswith('..')] |
| 214 |
| 215 def PrebuiltJars(self): |
| 216 return self.Gradle()['dependent_prebuilt_jars'] |
| 217 |
| 218 def AllEntries(self): |
| 219 """Returns a list of all entries that the current entry depends on. |
| 220 |
| 221 This includes the entry itself to make iterating simpler.""" |
| 222 if self._all_entries is None: |
| 223 logging.debug('Generating entries for %s', self.GnTarget()) |
| 224 deps = [_ProjectEntry.FromBuildConfigPath(p) |
| 225 for p in self.Gradle()['dependent_android_projects']] |
| 226 deps.extend(_ProjectEntry.FromBuildConfigPath(p) |
| 227 for p in self.Gradle()['dependent_java_projects']) |
| 228 all_entries = set() |
| 229 for dep in deps: |
| 230 all_entries.update(dep.AllEntries()) |
| 231 all_entries.add(self) |
| 232 self._all_entries = list(all_entries) |
| 233 return self._all_entries |
| 234 |
| 200 | 235 |
| 201 class _ProjectContextGenerator(object): | 236 class _ProjectContextGenerator(object): |
| 202 """Helper class to generate gradle build files""" | 237 """Helper class to generate gradle build files""" |
| 203 def __init__(self, project_dir, build_vars, use_gradle_process_resources, | 238 def __init__(self, project_dir, build_vars, use_gradle_process_resources, |
| 204 jinja_processor): | 239 jinja_processor): |
| 205 self.project_dir = project_dir | 240 self.project_dir = project_dir |
| 206 self.build_vars = build_vars | 241 self.build_vars = build_vars |
| 207 self.use_gradle_process_resources = use_gradle_process_resources | 242 self.use_gradle_process_resources = use_gradle_process_resources |
| 208 self.jinja_processor = jinja_processor | 243 self.jinja_processor = jinja_processor |
| 209 | 244 |
| 210 def _GenJniLibs(self, entry): | 245 def _GenJniLibs(self, entry): |
| 211 native_section = entry.BuildConfig().get('native') | 246 native_section = entry.BuildConfig().get('native') |
| 212 if native_section: | 247 if native_section: |
| 213 jni_libs = _CreateJniLibsDir( | 248 jni_libs = _CreateJniLibsDir( |
| 214 constants.GetOutDirectory(), self.EntryOutputDir(entry), | 249 constants.GetOutDirectory(), self.EntryOutputDir(entry), |
| 215 native_section.get('libraries')) | 250 native_section.get('libraries')) |
| 216 else: | 251 else: |
| 217 jni_libs = [] | 252 jni_libs = [] |
| 218 return jni_libs | 253 return jni_libs |
| 219 | 254 |
| 220 def _GenJavaDirs(self, entry): | 255 def _GenJavaDirs(self, entry): |
| 221 java_dirs, excludes = _CreateJavaSourceDir( | 256 java_dirs, excludes = _ComputeJavaSourceDirsAndExcludes( |
| 222 constants.GetOutDirectory(), entry.JavaFiles()) | 257 constants.GetOutDirectory(), entry.JavaFiles()) |
| 223 if self.Srcjars(entry): | 258 if self.Srcjars(entry): |
| 224 java_dirs.append( | 259 java_dirs.append( |
| 225 os.path.join(self.EntryOutputDir(entry), _SRCJARS_SUBDIR)) | 260 os.path.join(self.EntryOutputDir(entry), _SRCJARS_SUBDIR)) |
| 226 return java_dirs, excludes | 261 return java_dirs, excludes |
| 227 | 262 |
| 228 def _GenResDirs(self, entry): | 263 def _GenResDirs(self, entry): |
| 229 res_dirs = list(entry.DepsInfo().get('owned_resources_dirs', [])) | 264 res_dirs = list(entry.DepsInfo().get('owned_resources_dirs', [])) |
| 230 if entry.ResZips(): | 265 if entry.ResZips(): |
| 231 res_dirs.append(os.path.join(self.EntryOutputDir(entry), _RES_SUBDIR)) | 266 res_dirs.append(os.path.join(self.EntryOutputDir(entry), _RES_SUBDIR)) |
| 232 return res_dirs | 267 return res_dirs |
| 233 | 268 |
| 234 def _GenCustomManifest(self, entry): | 269 def _GenCustomManifest(self, entry): |
| 235 """Returns the path to the generated AndroidManifest.xml.""" | 270 """Returns the path to the generated AndroidManifest.xml. |
| 236 javac = entry.Javac() | |
| 237 resource_packages = javac['resource_packages'] | |
| 238 output_file = os.path.join( | |
| 239 self.EntryOutputDir(entry), 'AndroidManifest.xml') | |
| 240 | 271 |
| 272 Gradle uses package id from manifest when generating R.class. So, we need |
| 273 to generate a custom manifest if we let gradle process resources. We cannot |
| 274 simply set android.defaultConfig.applicationId because it is not supported |
| 275 for library targets.""" |
| 276 resource_packages = entry.Javac().get('resource_packages') |
| 241 if not resource_packages: | 277 if not resource_packages: |
| 242 logging.error('Target ' + entry.GnTarget() + ' includes resources from ' | 278 logging.debug('Target ' + entry.GnTarget() + ' includes resources from ' |
| 243 'unknown package. Unable to process with gradle.') | 279 'unknown package. Unable to process with gradle.') |
| 244 return _DEFAULT_ANDROID_MANIFEST_PATH | 280 return _DEFAULT_ANDROID_MANIFEST_PATH |
| 245 elif len(resource_packages) > 1: | 281 elif len(resource_packages) > 1: |
| 246 logging.error('Target ' + entry.GnTarget() + ' includes resources from ' | 282 logging.debug('Target ' + entry.GnTarget() + ' includes resources from ' |
| 247 'multiple packages. Unable to process with gradle.') | 283 'multiple packages. Unable to process with gradle.') |
| 248 return _DEFAULT_ANDROID_MANIFEST_PATH | 284 return _DEFAULT_ANDROID_MANIFEST_PATH |
| 249 | 285 |
| 250 variables = {} | 286 variables = {} |
| 251 variables['compile_sdk_version'] = self.build_vars['android_sdk_version'] | 287 variables['compile_sdk_version'] = self.build_vars['android_sdk_version'] |
| 252 variables['package'] = resource_packages[0] | 288 variables['package'] = resource_packages[0] |
| 253 | 289 |
| 290 output_file = os.path.join( |
| 291 self.EntryOutputDir(entry), 'AndroidManifest.xml') |
| 254 data = self.jinja_processor.Render(_TemplatePath('manifest'), variables) | 292 data = self.jinja_processor.Render(_TemplatePath('manifest'), variables) |
| 255 _WriteFile(output_file, data) | 293 _WriteFile(output_file, data) |
| 256 | 294 |
| 257 return output_file | 295 return output_file |
| 258 | 296 |
| 259 def _Relativize(self, entry, paths): | 297 def _Relativize(self, entry, paths): |
| 260 return _RebasePath(paths, self.EntryOutputDir(entry)) | 298 return _RebasePath(paths, self.EntryOutputDir(entry)) |
| 261 | 299 |
| 262 def EntryOutputDir(self, entry): | 300 def EntryOutputDir(self, entry): |
| 263 return os.path.join(self.project_dir, entry.GradleSubdir()) | 301 return os.path.join(self.project_dir, entry.GradleSubdir()) |
| 264 | 302 |
| 265 def Srcjars(self, entry): | 303 def Srcjars(self, entry): |
| 266 srcjars = _RebasePath(entry.Gradle().get('bundled_srcjars', [])) | 304 srcjars = _RebasePath(entry.Gradle().get('bundled_srcjars', [])) |
| 267 if not self.use_gradle_process_resources: | 305 if not self.use_gradle_process_resources: |
| 268 srcjars += _RebasePath(entry.BuildConfig()['javac']['srcjars']) | 306 srcjars += _RebasePath(entry.BuildConfig()['javac']['srcjars']) |
| 269 return srcjars | 307 return srcjars |
| 270 | 308 |
| 271 def GeneratedInputs(self, entry): | 309 def GeneratedInputs(self, entry): |
| 272 generated_inputs = [] | 310 generated_inputs = [] |
| 273 generated_inputs.extend(self.Srcjars(entry)) | 311 generated_inputs.extend(self.Srcjars(entry)) |
| 274 generated_inputs.extend(_RebasePath(entry.ResZips())) | 312 generated_inputs.extend(_RebasePath(entry.ResZips())) |
| 275 generated_inputs.extend( | 313 generated_inputs.extend(entry.GeneratedJavaFiles()) |
| 276 p for p in entry.JavaFiles() if not p.startswith('..')) | 314 generated_inputs.extend(entry.PrebuiltJars()) |
| 277 generated_inputs.extend(entry.Gradle()['dependent_prebuilt_jars']) | |
| 278 return generated_inputs | 315 return generated_inputs |
| 279 | 316 |
| 280 def Generate(self, entry): | 317 def Generate(self, entry): |
| 281 variables = {} | 318 variables = {} |
| 282 java_dirs, excludes = self._GenJavaDirs(entry) | 319 java_dirs, excludes = self._GenJavaDirs(entry) |
| 283 variables['java_dirs'] = self._Relativize(entry, java_dirs) | 320 variables['java_dirs'] = self._Relativize(entry, java_dirs) |
| 284 variables['java_excludes'] = excludes | 321 variables['java_excludes'] = excludes |
| 285 variables['jni_libs'] = self._Relativize(entry, self._GenJniLibs(entry)) | 322 variables['jni_libs'] = self._Relativize(entry, self._GenJniLibs(entry)) |
| 286 variables['res_dirs'] = self._Relativize(entry, self._GenResDirs(entry)) | 323 variables['res_dirs'] = self._Relativize(entry, self._GenResDirs(entry)) |
| 287 android_manifest = entry.Gradle().get('android_manifest') | 324 android_manifest = entry.Gradle().get('android_manifest') |
| 288 if not android_manifest: | 325 if not android_manifest: |
| 289 # Gradle uses package id from manifest when generating R.class. So, we | 326 android_manifest = self._GenCustomManifest(entry) |
| 290 # need to generate a custom manifest if we let gradle process resources. | |
| 291 # We cannot simply set android.defaultConfig.applicationId because it is | |
| 292 # not supported for library targets. | |
| 293 if variables['res_dirs']: | |
| 294 android_manifest = self._GenCustomManifest(entry) | |
| 295 else: | |
| 296 android_manifest = _DEFAULT_ANDROID_MANIFEST_PATH | |
| 297 variables['android_manifest'] = self._Relativize(entry, android_manifest) | 327 variables['android_manifest'] = self._Relativize(entry, android_manifest) |
| 328 # TODO(agrieve): Add an option to use interface jars and see if that speeds |
| 329 # things up at all. |
| 330 variables['prebuilts'] = self._Relativize(entry, entry.PrebuiltJars()) |
| 298 deps = [_ProjectEntry.FromBuildConfigPath(p) | 331 deps = [_ProjectEntry.FromBuildConfigPath(p) |
| 299 for p in entry.Gradle()['dependent_android_projects']] | 332 for p in entry.Gradle()['dependent_android_projects']] |
| 300 variables['android_project_deps'] = [d.ProjectName() for d in deps] | 333 variables['android_project_deps'] = [d.ProjectName() for d in deps] |
| 301 # TODO(agrieve): Add an option to use interface jars and see if that speeds | |
| 302 # things up at all. | |
| 303 variables['prebuilts'] = self._Relativize( | |
| 304 entry, entry.Gradle()['dependent_prebuilt_jars']) | |
| 305 deps = [_ProjectEntry.FromBuildConfigPath(p) | 334 deps = [_ProjectEntry.FromBuildConfigPath(p) |
| 306 for p in entry.Gradle()['dependent_java_projects']] | 335 for p in entry.Gradle()['dependent_java_projects']] |
| 307 variables['java_project_deps'] = [d.ProjectName() for d in deps] | 336 variables['java_project_deps'] = [d.ProjectName() for d in deps] |
| 308 return variables | 337 return variables |
| 309 | 338 |
| 310 | 339 |
| 311 def _ComputeJavaSourceDirs(java_files): | 340 def _ComputeJavaSourceDirs(java_files): |
| 312 """Returns a dictionary of source dirs with each given files in one.""" | 341 """Returns a dictionary of source dirs with each given files in one.""" |
| 313 found_roots = {} | 342 found_roots = {} |
| 314 for path in java_files: | 343 for path in java_files: |
| (...skipping 30 matching lines...) Expand all Loading... |
| 345 found_files = set(glob.glob(target_exclude)) | 374 found_files = set(glob.glob(target_exclude)) |
| 346 valid_files = found_files & files_to_include | 375 valid_files = found_files & files_to_include |
| 347 if valid_files: | 376 if valid_files: |
| 348 excludes.append(os.path.relpath(unwanted_file, parent_dir)) | 377 excludes.append(os.path.relpath(unwanted_file, parent_dir)) |
| 349 else: | 378 else: |
| 350 excludes.append(os.path.relpath(target_exclude, parent_dir)) | 379 excludes.append(os.path.relpath(target_exclude, parent_dir)) |
| 351 files_to_exclude -= found_files | 380 files_to_exclude -= found_files |
| 352 return excludes | 381 return excludes |
| 353 | 382 |
| 354 | 383 |
| 355 def _CreateJavaSourceDir(output_dir, java_files): | 384 def _ComputeJavaSourceDirsAndExcludes(output_dir, java_files): |
| 356 """Computes the list of java source directories and exclude patterns. | 385 """Computes the list of java source directories and exclude patterns. |
| 357 | 386 |
| 358 1. Computes the root java source directories from the list of files. | 387 1. Computes the root java source directories from the list of files. |
| 359 2. Compute exclude patterns that exclude all extra files only. | 388 2. Compute exclude patterns that exclude all extra files only. |
| 360 3. Returns the list of java source directories and exclude patterns. | 389 3. Returns the list of java source directories and exclude patterns. |
| 361 """ | 390 """ |
| 362 java_dirs = [] | 391 java_dirs = [] |
| 363 excludes = [] | 392 excludes = [] |
| 364 if java_files: | 393 if java_files: |
| 365 java_files = _RebasePath(java_files) | 394 java_files = _RebasePath(java_files) |
| (...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 460 build_vars['android_sdk_build_tools_version']) | 489 build_vars['android_sdk_build_tools_version']) |
| 461 variables['compile_sdk_version'] = build_vars['android_sdk_version'] | 490 variables['compile_sdk_version'] = build_vars['android_sdk_version'] |
| 462 variables['main'] = generator.Generate(entry) | 491 variables['main'] = generator.Generate(entry) |
| 463 bootclasspath = gradle.get('bootclasspath') | 492 bootclasspath = gradle.get('bootclasspath') |
| 464 if bootclasspath: | 493 if bootclasspath: |
| 465 # Must use absolute path here. | 494 # Must use absolute path here. |
| 466 variables['bootclasspath'] = _RebasePath(bootclasspath) | 495 variables['bootclasspath'] = _RebasePath(bootclasspath) |
| 467 if entry.android_test_entry: | 496 if entry.android_test_entry: |
| 468 variables['android_test'] = generator.Generate( | 497 variables['android_test'] = generator.Generate( |
| 469 entry.android_test_entry) | 498 entry.android_test_entry) |
| 499 for key, value in variables['android_test'].iteritems(): |
| 500 if isinstance(value, list): |
| 501 variables['android_test'][key] = list( |
| 502 set(value) - set(variables['main'][key])) |
| 470 | 503 |
| 471 return jinja_processor.Render( | 504 return jinja_processor.Render( |
| 472 _TemplatePath(target_type.split('_')[0]), variables) | 505 _TemplatePath(target_type.split('_')[0]), variables) |
| 473 | 506 |
| 474 | 507 |
| 475 def _GenerateRootGradle(jinja_processor): | 508 def _GenerateRootGradle(jinja_processor): |
| 476 """Returns the data for the root project's build.gradle.""" | 509 """Returns the data for the root project's build.gradle.""" |
| 477 return jinja_processor.Render(_TemplatePath('root')) | 510 return jinja_processor.Render(_TemplatePath('root')) |
| 478 | 511 |
| 479 | 512 |
| (...skipping 119 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 599 _RunNinja(constants.GetOutDirectory(), ['build.ninja']) | 632 _RunNinja(constants.GetOutDirectory(), ['build.ninja']) |
| 600 # Query ninja for all __build_config targets. | 633 # Query ninja for all __build_config targets. |
| 601 targets = _QueryForAllGnTargets(output_dir) | 634 targets = _QueryForAllGnTargets(output_dir) |
| 602 else: | 635 else: |
| 603 targets = args.targets or _DEFAULT_TARGETS | 636 targets = args.targets or _DEFAULT_TARGETS |
| 604 targets = [re.sub(r'_test_apk$', '_test_apk__apk', t) for t in targets] | 637 targets = [re.sub(r'_test_apk$', '_test_apk__apk', t) for t in targets] |
| 605 # TODO(wnwen): Utilize Gradle's test constructs for our junit tests? | 638 # TODO(wnwen): Utilize Gradle's test constructs for our junit tests? |
| 606 targets = [re.sub(r'_junit_tests$', '_junit_tests__java_binary', t) | 639 targets = [re.sub(r'_junit_tests$', '_junit_tests__java_binary', t) |
| 607 for t in targets] | 640 for t in targets] |
| 608 | 641 |
| 609 main_entries = [_ProjectEntry(t) for t in targets] | 642 main_entries = [_ProjectEntry.FromGnTarget(t) for t in targets] |
| 610 | 643 |
| 611 logging.warning('Building .build_config files...') | 644 logging.warning('Building .build_config files...') |
| 612 _RunNinja(output_dir, [e.NinjaBuildConfigTarget() for e in main_entries]) | 645 _RunNinja(output_dir, [e.NinjaBuildConfigTarget() for e in main_entries]) |
| 613 | 646 |
| 614 # There are many unused libraries, so restrict to those that are actually used | 647 # There are many unused libraries, so restrict to those that are actually used |
| 615 # when using --all. | 648 # when using --all. |
| 616 if args.all: | 649 if args.all: |
| 617 main_entries = [e for e in main_entries if e.GetType() == 'android_apk'] | 650 main_entries = [e for e in main_entries if e.GetType() == 'android_apk'] |
| 618 | 651 |
| 619 all_entries = _FindAllProjectEntries(main_entries) | 652 all_entries = _FindAllProjectEntries(main_entries) |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 662 _ExtractZips(generator.project_dir, zip_tuples) | 695 _ExtractZips(generator.project_dir, zip_tuples) |
| 663 | 696 |
| 664 logging.warning('Project created! (%d subprojects)', len(project_entries)) | 697 logging.warning('Project created! (%d subprojects)', len(project_entries)) |
| 665 logging.warning('Generated projects work best with Android Studio 2.2') | 698 logging.warning('Generated projects work best with Android Studio 2.2') |
| 666 logging.warning('For more tips: https://chromium.googlesource.com/chromium' | 699 logging.warning('For more tips: https://chromium.googlesource.com/chromium' |
| 667 '/src.git/+/master/docs/android_studio.md') | 700 '/src.git/+/master/docs/android_studio.md') |
| 668 | 701 |
| 669 | 702 |
| 670 if __name__ == '__main__': | 703 if __name__ == '__main__': |
| 671 main() | 704 main() |
| OLD | NEW |