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 |