| 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 logging | 11 import logging |
| 11 import os | 12 import os |
| 12 import re | 13 import re |
| 13 import shutil | 14 import shutil |
| 14 import subprocess | 15 import subprocess |
| 15 import sys | 16 import sys |
| 16 import zipfile | 17 import zipfile |
| 17 | 18 |
| 18 _BUILD_ANDROID = os.path.join(os.path.dirname(__file__), os.pardir) | 19 _BUILD_ANDROID = os.path.join(os.path.dirname(__file__), os.pardir) |
| 19 sys.path.append(_BUILD_ANDROID) | 20 sys.path.append(_BUILD_ANDROID) |
| 20 import devil_chromium | 21 import devil_chromium |
| 21 from devil.utils import run_tests_helper | 22 from devil.utils import run_tests_helper |
| 22 from pylib import constants | 23 from pylib import constants |
| 23 from pylib.constants import host_paths | 24 from pylib.constants import host_paths |
| 24 | 25 |
| 25 sys.path.append(os.path.join(_BUILD_ANDROID, 'gyp')) | 26 sys.path.append(os.path.join(_BUILD_ANDROID, 'gyp')) |
| 26 import jinja_template | 27 import jinja_template |
| 27 from util import build_utils | 28 from util import build_utils |
| 28 | 29 |
| 29 | 30 |
| 30 _DEFAULT_ANDROID_MANIFEST_PATH = os.path.join( | 31 _DEFAULT_ANDROID_MANIFEST_PATH = os.path.join( |
| 31 host_paths.DIR_SOURCE_ROOT, 'build', 'android', 'AndroidManifest.xml') | 32 host_paths.DIR_SOURCE_ROOT, 'build', 'android', 'AndroidManifest.xml') |
| 32 _FILE_DIR = os.path.dirname(__file__) | 33 _FILE_DIR = os.path.dirname(__file__) |
| 33 _JAVA_SUBDIR = 'symlinked-java' | |
| 34 _SRCJARS_SUBDIR = 'extracted-srcjars' | 34 _SRCJARS_SUBDIR = 'extracted-srcjars' |
| 35 _JNI_LIBS_SUBDIR = 'symlinked-libs' | 35 _JNI_LIBS_SUBDIR = 'symlinked-libs' |
| 36 _ARMEABI_SUBDIR = 'armeabi' | 36 _ARMEABI_SUBDIR = 'armeabi' |
| 37 | 37 |
| 38 _DEFAULT_TARGETS = [ | 38 _DEFAULT_TARGETS = [ |
| 39 # TODO(agrieve): Requires alternate android.jar to compile. | 39 # TODO(agrieve): Requires alternate android.jar to compile. |
| 40 # '//android_webview:system_webview_apk', | 40 # '//android_webview:system_webview_apk', |
| 41 '//android_webview/test:android_webview_apk', | 41 '//android_webview/test:android_webview_apk', |
| 42 '//android_webview/test:android_webview_test_apk', | 42 '//android_webview/test:android_webview_test_apk', |
| 43 '//base:base_junit_tests', | 43 '//base:base_junit_tests', |
| (...skipping 153 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 197 native_section = entry.BuildConfig().get('native') | 197 native_section = entry.BuildConfig().get('native') |
| 198 if native_section: | 198 if native_section: |
| 199 jni_libs = _CreateJniLibsDir( | 199 jni_libs = _CreateJniLibsDir( |
| 200 constants.GetOutDirectory(), self.EntryOutputDir(entry), | 200 constants.GetOutDirectory(), self.EntryOutputDir(entry), |
| 201 native_section.get('libraries')) | 201 native_section.get('libraries')) |
| 202 else: | 202 else: |
| 203 jni_libs = [] | 203 jni_libs = [] |
| 204 return jni_libs | 204 return jni_libs |
| 205 | 205 |
| 206 def _GenJavaDirs(self, entry): | 206 def _GenJavaDirs(self, entry): |
| 207 java_dirs = _CreateJavaSourceDir( | 207 java_dirs, excludes = _CreateJavaSourceDir( |
| 208 constants.GetOutDirectory(), self.EntryOutputDir(entry), | 208 constants.GetOutDirectory(), entry.JavaFiles()) |
| 209 entry.JavaFiles()) | |
| 210 if self.Srcjars(entry): | 209 if self.Srcjars(entry): |
| 211 java_dirs.append( | 210 java_dirs.append( |
| 212 os.path.join(self.EntryOutputDir(entry), _SRCJARS_SUBDIR)) | 211 os.path.join(self.EntryOutputDir(entry), _SRCJARS_SUBDIR)) |
| 213 return java_dirs | 212 return java_dirs, excludes |
| 214 | 213 |
| 215 def _Relativize(self, entry, paths): | 214 def _Relativize(self, entry, paths): |
| 216 return _RebasePath(paths, self.EntryOutputDir(entry)) | 215 return _RebasePath(paths, self.EntryOutputDir(entry)) |
| 217 | 216 |
| 218 def EntryOutputDir(self, entry): | 217 def EntryOutputDir(self, entry): |
| 219 return os.path.join(self.project_dir, entry.GradleSubdir()) | 218 return os.path.join(self.project_dir, entry.GradleSubdir()) |
| 220 | 219 |
| 221 def Srcjars(self, entry): | 220 def Srcjars(self, entry): |
| 222 srcjars = _RebasePath(entry.Gradle().get('bundled_srcjars', [])) | 221 srcjars = _RebasePath(entry.Gradle().get('bundled_srcjars', [])) |
| 223 if not self.use_gradle_process_resources: | 222 if not self.use_gradle_process_resources: |
| 224 srcjars += _RebasePath(entry.BuildConfig()['javac']['srcjars']) | 223 srcjars += _RebasePath(entry.BuildConfig()['javac']['srcjars']) |
| 225 return srcjars | 224 return srcjars |
| 226 | 225 |
| 227 def GeneratedInputs(self, entry): | 226 def GeneratedInputs(self, entry): |
| 228 generated_inputs = [] | 227 generated_inputs = [] |
| 229 generated_inputs.extend(self.Srcjars(entry)) | 228 generated_inputs.extend(self.Srcjars(entry)) |
| 230 generated_inputs.extend( | 229 generated_inputs.extend( |
| 231 p for p in entry.JavaFiles() if not p.startswith('..')) | 230 p for p in entry.JavaFiles() if not p.startswith('..')) |
| 232 generated_inputs.extend(entry.Gradle()['dependent_prebuilt_jars']) | 231 generated_inputs.extend(entry.Gradle()['dependent_prebuilt_jars']) |
| 233 return generated_inputs | 232 return generated_inputs |
| 234 | 233 |
| 235 def Generate(self, entry): | 234 def Generate(self, entry): |
| 236 variables = {} | 235 variables = {} |
| 237 android_test_manifest = entry.Gradle().get( | 236 android_test_manifest = entry.Gradle().get( |
| 238 'android_manifest', _DEFAULT_ANDROID_MANIFEST_PATH) | 237 'android_manifest', _DEFAULT_ANDROID_MANIFEST_PATH) |
| 239 variables['android_manifest'] = self._Relativize( | 238 variables['android_manifest'] = self._Relativize( |
| 240 entry, android_test_manifest) | 239 entry, android_test_manifest) |
| 241 variables['java_dirs'] = self._Relativize(entry, self._GenJavaDirs(entry)) | 240 java_dirs, excludes = self._GenJavaDirs(entry) |
| 241 variables['java_dirs'] = self._Relativize(entry, java_dirs) |
| 242 variables['java_excludes'] = excludes |
| 242 variables['jni_libs'] = self._Relativize(entry, self._GenJniLibs(entry)) | 243 variables['jni_libs'] = self._Relativize(entry, self._GenJniLibs(entry)) |
| 243 deps = [_ProjectEntry.FromBuildConfigPath(p) | 244 deps = [_ProjectEntry.FromBuildConfigPath(p) |
| 244 for p in entry.Gradle()['dependent_android_projects']] | 245 for p in entry.Gradle()['dependent_android_projects']] |
| 245 variables['android_project_deps'] = [d.ProjectName() for d in deps] | 246 variables['android_project_deps'] = [d.ProjectName() for d in deps] |
| 246 # TODO(agrieve): Add an option to use interface jars and see if that speeds | 247 # TODO(agrieve): Add an option to use interface jars and see if that speeds |
| 247 # things up at all. | 248 # things up at all. |
| 248 variables['prebuilts'] = self._Relativize( | 249 variables['prebuilts'] = self._Relativize( |
| 249 entry, entry.Gradle()['dependent_prebuilt_jars']) | 250 entry, entry.Gradle()['dependent_prebuilt_jars']) |
| 250 deps = [_ProjectEntry.FromBuildConfigPath(p) | 251 deps = [_ProjectEntry.FromBuildConfigPath(p) |
| 251 for p in entry.Gradle()['dependent_java_projects']] | 252 for p in entry.Gradle()['dependent_java_projects']] |
| 252 variables['java_project_deps'] = [d.ProjectName() for d in deps] | 253 variables['java_project_deps'] = [d.ProjectName() for d in deps] |
| 253 return variables | 254 return variables |
| 254 | 255 |
| 255 | 256 |
| 256 def _ComputeJavaSourceDirs(java_files): | 257 def _ComputeJavaSourceDirs(java_files): |
| 257 """Returns the list of source directories for the given files.""" | 258 """Returns a dictionary of source dirs with each given files in one.""" |
| 258 found_roots = set() | 259 found_roots = {} |
| 259 for path in java_files: | 260 for path in java_files: |
| 260 path_root = path | 261 path_root = path |
| 261 # Recognize these tokens as top-level. | 262 # Recognize these tokens as top-level. |
| 262 while True: | 263 while True: |
| 263 path_root = os.path.dirname(path_root) | 264 path_root = os.path.dirname(path_root) |
| 264 basename = os.path.basename(path_root) | 265 basename = os.path.basename(path_root) |
| 265 assert basename, 'Failed to find source dir for ' + path | 266 assert basename, 'Failed to find source dir for ' + path |
| 266 if basename in ('java', 'src'): | 267 if basename in ('java', 'src'): |
| 267 break | 268 break |
| 268 if basename in ('javax', 'org', 'com'): | 269 if basename in ('javax', 'org', 'com'): |
| 269 path_root = os.path.dirname(path_root) | 270 path_root = os.path.dirname(path_root) |
| 270 break | 271 break |
| 271 found_roots.add(path_root) | 272 if path_root not in found_roots: |
| 272 return list(found_roots) | 273 found_roots[path_root] = [] |
| 274 found_roots[path_root].append(path) |
| 275 return found_roots |
| 276 |
| 277 |
| 278 def _ComputeExcludeFilters(wanted_files, unwanted_files, parent_dir): |
| 279 """Returns exclude patters to exclude unwanted files but keep wanted files. |
| 280 |
| 281 - Shortens exclude list by globbing if possible. |
| 282 - Exclude patterns are relative paths from the parent directory. |
| 283 """ |
| 284 excludes = [] |
| 285 files_to_include = set(wanted_files) |
| 286 files_to_exclude = set(unwanted_files) |
| 287 while files_to_exclude: |
| 288 unwanted_file = files_to_exclude.pop() |
| 289 target_exclude = os.path.join( |
| 290 os.path.dirname(unwanted_file), '*.java') |
| 291 found_files = set(glob.glob(target_exclude)) |
| 292 valid_files = found_files & files_to_include |
| 293 if valid_files: |
| 294 excludes.append(os.path.relpath(unwanted_file, parent_dir)) |
| 295 else: |
| 296 excludes.append(os.path.relpath(target_exclude, parent_dir)) |
| 297 files_to_exclude -= found_files |
| 298 return excludes |
| 299 |
| 300 |
| 301 def _CreateJavaSourceDir(output_dir, java_files): |
| 302 """Computes the list of java source directories and exclude patterns. |
| 303 |
| 304 1. Computes the root java source directories from the list of files. |
| 305 2. Compute exclude patterns that exclude all extra files only. |
| 306 3. Returns the list of java source directories and exclude patterns. |
| 307 """ |
| 308 java_dirs = [] |
| 309 excludes = [] |
| 310 if java_files: |
| 311 java_files = _RebasePath(java_files) |
| 312 computed_dirs = _ComputeJavaSourceDirs(java_files) |
| 313 java_dirs = computed_dirs.keys() |
| 314 all_found_java_files = set() |
| 315 |
| 316 for directory, files in computed_dirs.iteritems(): |
| 317 found_java_files = build_utils.FindInDirectory(directory, '*.java') |
| 318 all_found_java_files.update(found_java_files) |
| 319 unwanted_java_files = set(found_java_files) - set(files) |
| 320 if unwanted_java_files: |
| 321 logging.debug('Directory requires excludes: %s', directory) |
| 322 excludes.extend( |
| 323 _ComputeExcludeFilters(files, unwanted_java_files, directory)) |
| 324 |
| 325 missing_java_files = set(java_files) - all_found_java_files |
| 326 # Warn only about non-generated files that are missing. |
| 327 missing_java_files = [p for p in missing_java_files |
| 328 if not p.startswith(output_dir)] |
| 329 if missing_java_files: |
| 330 logging.warning( |
| 331 'Some java files were not found: %s', missing_java_files) |
| 332 |
| 333 return java_dirs, excludes |
| 273 | 334 |
| 274 | 335 |
| 275 def _CreateRelativeSymlink(target_path, link_path): | 336 def _CreateRelativeSymlink(target_path, link_path): |
| 276 link_dir = os.path.dirname(link_path) | 337 link_dir = os.path.dirname(link_path) |
| 277 relpath = os.path.relpath(target_path, link_dir) | 338 relpath = os.path.relpath(target_path, link_dir) |
| 278 logging.debug('Creating symlink %s -> %s', link_path, relpath) | 339 logging.debug('Creating symlink %s -> %s', link_path, relpath) |
| 279 os.symlink(relpath, link_path) | 340 os.symlink(relpath, link_path) |
| 280 | 341 |
| 281 | 342 |
| 282 def _CreateSymlinkTree(entry_output_dir, symlink_dir, desired_files, | |
| 283 parent_dirs): | |
| 284 """Creates a directory tree of symlinks to the given files. | |
| 285 | |
| 286 The idea here is to replicate a directory tree while leaving out files within | |
| 287 it not listed by |desired_files|. | |
| 288 """ | |
| 289 assert _IsSubpathOf(symlink_dir, entry_output_dir) | |
| 290 | |
| 291 for target_path in desired_files: | |
| 292 prefix = next(d for d in parent_dirs if target_path.startswith(d)) | |
| 293 subpath = os.path.relpath(target_path, prefix) | |
| 294 symlinked_path = os.path.join(symlink_dir, subpath) | |
| 295 symlinked_dir = os.path.dirname(symlinked_path) | |
| 296 if not os.path.exists(symlinked_dir): | |
| 297 os.makedirs(symlinked_dir) | |
| 298 _CreateRelativeSymlink(target_path, symlinked_path) | |
| 299 | |
| 300 | |
| 301 def _CreateJavaSourceDir(output_dir, entry_output_dir, java_files): | |
| 302 """Computes and constructs when necessary the list of java source directories. | |
| 303 | |
| 304 1. Computes the root java source directories from the list of files. | |
| 305 2. Determines whether there are any .java files in them that are not included | |
| 306 in |java_files|. | |
| 307 3. If not, returns the list of java source directories. If so, constructs a | |
| 308 tree of symlinks within |entry_output_dir| of all files in |java_files|. | |
| 309 """ | |
| 310 java_dirs = [] | |
| 311 if java_files: | |
| 312 java_files = _RebasePath(java_files) | |
| 313 java_dirs = _ComputeJavaSourceDirs(java_files) | |
| 314 | |
| 315 found_java_files = build_utils.FindInDirectories(java_dirs, '*.java') | |
| 316 unwanted_java_files = set(found_java_files) - set(java_files) | |
| 317 missing_java_files = set(java_files) - set(found_java_files) | |
| 318 # Warn only about non-generated files that are missing. | |
| 319 missing_java_files = [p for p in missing_java_files | |
| 320 if not p.startswith(output_dir)] | |
| 321 | |
| 322 symlink_dir = os.path.join(entry_output_dir, _JAVA_SUBDIR) | |
| 323 shutil.rmtree(symlink_dir, True) | |
| 324 | |
| 325 if unwanted_java_files: | |
| 326 logging.debug('Target requires .java symlinks: %s', entry_output_dir) | |
| 327 _CreateSymlinkTree(entry_output_dir, symlink_dir, java_files, java_dirs) | |
| 328 java_dirs = [symlink_dir] | |
| 329 | |
| 330 if missing_java_files: | |
| 331 logging.warning('Some java files were not found: %s', missing_java_files) | |
| 332 | |
| 333 return java_dirs | |
| 334 | |
| 335 | |
| 336 def _CreateJniLibsDir(output_dir, entry_output_dir, so_files): | 343 def _CreateJniLibsDir(output_dir, entry_output_dir, so_files): |
| 337 """Creates directory with symlinked .so files if necessary. | 344 """Creates directory with symlinked .so files if necessary. |
| 338 | 345 |
| 339 Returns list of JNI libs directories.""" | 346 Returns list of JNI libs directories.""" |
| 340 | 347 |
| 341 if so_files: | 348 if so_files: |
| 342 symlink_dir = os.path.join(entry_output_dir, _JNI_LIBS_SUBDIR) | 349 symlink_dir = os.path.join(entry_output_dir, _JNI_LIBS_SUBDIR) |
| 343 shutil.rmtree(symlink_dir, True) | 350 shutil.rmtree(symlink_dir, True) |
| 344 abi_dir = os.path.join(symlink_dir, _ARMEABI_SUBDIR) | 351 abi_dir = os.path.join(symlink_dir, _ARMEABI_SUBDIR) |
| 345 if not os.path.exists(abi_dir): | 352 if not os.path.exists(abi_dir): |
| (...skipping 244 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 590 _ExtractSrcjars(generator.project_dir, srcjar_tuples) | 597 _ExtractSrcjars(generator.project_dir, srcjar_tuples) |
| 591 | 598 |
| 592 logging.warning('Project created! (%d subprojects)', len(project_entries)) | 599 logging.warning('Project created! (%d subprojects)', len(project_entries)) |
| 593 logging.warning('Generated projects work best with Android Studio 2.2') | 600 logging.warning('Generated projects work best with Android Studio 2.2') |
| 594 logging.warning('For more tips: https://chromium.googlesource.com/chromium' | 601 logging.warning('For more tips: https://chromium.googlesource.com/chromium' |
| 595 '/src.git/+/master/docs/android_studio.md') | 602 '/src.git/+/master/docs/android_studio.md') |
| 596 | 603 |
| 597 | 604 |
| 598 if __name__ == '__main__': | 605 if __name__ == '__main__': |
| 599 main() | 606 main() |
| OLD | NEW |