Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(545)

Unified Diff: build/android/generate_gradle.py

Issue 2130933002: 🎧 Initial version of Android Studio project generation (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: build/android/generate_gradle.py
diff --git a/build/android/generate_gradle.py b/build/android/generate_gradle.py
new file mode 100755
index 0000000000000000000000000000000000000000..58912a2f8333297ccbf0afa4051ac8bf32ce4916
--- /dev/null
+++ b/build/android/generate_gradle.py
@@ -0,0 +1,358 @@
+#!/usr/bin/env python
+# Copyright 2016 The Chromium Authors. All rights reserved.
jbudorick 2016/07/07 22:32:29 Again, //build/android/gradle/?
agrieve 2016/07/08 19:07:28 Done.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Generates an Android Studio project from a GN target."""
+
+import argparse
+import codecs
+import logging
+import os
+import shutil
+import subprocess
+import sys
+import zipfile
+
+from pylib import constants
+from pylib.constants import host_paths
+from pylib.utils import run_tests_helper
+
+sys.path.append(os.path.join(os.path.dirname(__file__), 'gyp'))
+import jinja_template
+from util import build_utils
+
+
+_JINJA_TEMPLATE_PATH = os.path.join(
jbudorick 2016/07/07 22:32:29 nit: sort
agrieve 2016/07/08 19:07:28 Done.
+ os.path.dirname(__file__), 'build.gradle.jinja')
+_DEFAULT_ANDROID_MANIFEST_PATH = os.path.join(
+ host_paths.DIR_SOURCE_ROOT, 'build', 'android', 'AndroidManifest.xml')
+_SRCJARS_SUBDIR = 'extracted-srcjars'
+_JAVA_SUBDIR = 'symlinked-java'
+
+
+def _WriteFile(path, data):
+ logging.info('Writing %s', path)
+ dirname = os.path.dirname(path)
+ if not os.path.exists(dirname):
+ os.makedirs(dirname)
+ with codecs.open(path, 'w', 'utf-8') as output_file:
+ output_file.write(data)
+
+
+def _RunNinja(output_dir, ninja_targets):
+ cmd = ['ninja', '-C', output_dir, '-j50']
+ cmd.extend(ninja_targets)
+ logging.info('Running: %r', cmd)
+ subprocess.check_call(cmd)
+
+
+class _ProjectEntry(object):
+ """Helper class for various path transformations."""
+ def __init__(self, gn_target):
+ assert gn_target.startswith('//') and ':' in gn_target, gn_target
+ self._gn_target = gn_target
+
+ @classmethod
+ def FromBuildConfigPath(cls, path):
+ prefix = 'gen/'
+ suffix = '.build_config'
+ assert path.startswith(prefix) and path.endswith(suffix), path
+ subdir = path[len(prefix):-len(suffix)]
+ return cls('//%s:%s' % (os.path.split(subdir)))
+
+ def GnTarget(self):
+ return self._gn_target
+
+ def NinjaTarget(self):
+ return self._gn_target[2:]
+
+ def GnBuildConfigTarget(self):
+ return '%s__build_config' % self._gn_target
+
+ def NinjaBuildConfigTarget(self):
+ return '%s__build_config' % self.NinjaTarget()
+
+ def GradleSubdir(self):
+ """Returns the output subdirectory."""
+ return self.NinjaTarget().replace(':', os.path.sep)
+
+ def BuildConfigPath(self):
+ """Returns the relative path to the .build_config"""
+ return os.path.join('gen', self.GradleSubdir() + '.build_config')
+
+ def ProjectName(self):
+ """Returns the Gradle project name."""
+ return self.GradleSubdir().replace(os.path.sep, '\\$')
+
+
+def _ComputeJavaSourceDirs(java_files):
+ """Returns the list of source directories for the given files."""
+ found_roots = set()
+ for path in java_files:
+ path_root = path
+ # Recognize these tokens as top-level.
+ while os.path.basename(path_root) not in ('javax', 'org', 'com'):
jbudorick 2016/07/07 22:32:30 nit: I think this is a bit simpler than while/brea
agrieve 2016/07/08 19:07:28 Done.
+ assert path_root, 'Failed to find source dir for ' + path
+ # Assume that if we've hit "src", the we're at the root.
+ if os.path.basename(path_root) == 'src':
+ break
+ path_root = os.path.dirname(path_root)
+ else:
+ path_root = os.path.dirname(path_root)
+ found_roots.add(path_root)
+ return list(found_roots)
+
+
+def _CreateSymlinkTree(output_dir, symlink_dir, desired_files, parent_dirs):
+ """Creates a directory tree of symlinks to the given files.
+
+ The idea here is to replicate a directory tree while leaving out files within
+ it not listed by |desired_files|.
+ """
+ if os.path.exists(symlink_dir):
+ shutil.rmtree(symlink_dir)
jbudorick 2016/07/07 22:32:30 I would expect this case to be an error. We could
agrieve 2016/07/08 19:07:28 It happens when regenerating. There are .idea file
+
+ for target_path in desired_files:
jbudorick 2016/07/07 22:32:29 Are the target_paths relative or absolute?
agrieve 2016/07/08 19:07:28 Reworked this to be less brain-hurting.
+ prefix = next(d for d in parent_dirs if target_path.startswith(d))
+ subpath = target_path[len(prefix) + 1:]
jbudorick 2016/07/07 22:32:29 os.path.relpath(target_path, prefix) ?
agrieve 2016/07/08 19:07:28 Done.
+ symlinked_path = os.path.join(symlink_dir, subpath)
+ symlinked_dir = os.path.dirname(symlinked_path)
+ if not os.path.exists(symlinked_dir):
+ os.makedirs(symlinked_dir)
+ relpath = os.path.relpath(os.path.join(output_dir, target_path),
jbudorick 2016/07/07 22:32:29 ... if the target paths are absolute, this seems o
agrieve 2016/07/08 19:07:28 Done.
+ symlinked_dir)
+ logging.debug('Creating symlink %s -> %s', symlinked_path, relpath)
+ os.symlink(relpath, symlinked_path)
+
+
+def _CreateJavaSourceDir(output_dir, gradle_dir, java_sources_file):
+ """Computes and constructs when necessary the list of java source directories.
+
+ 1. Computes the root java source directories from the list of files.
+ 2. Determines whether there are any .java files in them that are not included
+ in |java_sources_file|.
+ 3. If not, returns the list of java source directories. If so, constructs a
+ tree of symlinks within |gradle_dir| of all files in |java_sources_file|.
+ """
+ java_dirs = []
+ if java_sources_file:
+ with open(java_sources_file) as f:
+ java_files = [l.strip() for l in f]
+ java_dirs = _ComputeJavaSourceDirs(java_files)
+ abs_java_dirs = [os.path.join(output_dir, d) for d in java_dirs]
+
+ found_java_files = build_utils.FindInDirectories(abs_java_dirs, '*.java')
+ found_java_files = [os.path.relpath(p, output_dir)
+ for p in found_java_files]
+
+ unwanted_java_files = set(found_java_files) - set(java_files)
+ if unwanted_java_files:
+ logging.debug('Target requires .java symlinks: %s', gradle_dir)
+ symlink_dir = os.path.join(gradle_dir, _JAVA_SUBDIR)
+ _CreateSymlinkTree(output_dir, symlink_dir, java_files, java_dirs)
+ java_dirs = [symlink_dir]
+
+ return java_dirs
+
+
+def _GenerateLocalProperties(sdk_dir):
+ """Returns the data for project.properties as a string."""
+ return '\n'.join([
+ '# Generated by //build/android/generate_gradle.py',
+ 'sdk.dir=%s' % sdk_dir,
+ ''])
+
+
+def _GenerateGradleFile(build_config, config_json, java_dirs, relativize,
+ use_gradle_process_resources):
+ """Returns the data for a project's build.gradle."""
+ deps_info = build_config['deps_info']
+ gradle = build_config['gradle']
+
+ if deps_info['type'] == 'android_apk':
+ target_type = 'android_apk'
+ elif deps_info['type'] == 'java_library' and not deps_info['is_prebuilt']:
+ if deps_info['requires_android']:
jbudorick 2016/07/07 22:32:29 I'm a bit surprised that we write both of these as
agrieve 2016/07/08 19:07:28 Meh, in rules.gni, android_library is just a short
+ target_type = 'android_library'
+ else:
+ target_type = 'java_library'
+ else:
+ return None
+
+ variables = {}
+ variables['template_type'] = target_type
+ variables['use_gradle_process_resources'] = use_gradle_process_resources
+ variables['build_tools_version'] = config_json['build_tools_version']
+ variables['compile_sdk_version'] = config_json['compile_sdk_version']
+ android_manifest = gradle.get('android_manifest',
+ _DEFAULT_ANDROID_MANIFEST_PATH)
+ variables['android_manifest'] = relativize(android_manifest)
+
+ variables['java_dirs'] = relativize(java_dirs)
+ variables['prebuilts'] = relativize(gradle['dependent_prebuilt_jars'])
+ deps = [_ProjectEntry.FromBuildConfigPath(p)
+ for p in gradle['dependent_android_projects']]
+
+ variables['android_project_deps'] = [d.ProjectName() for d in deps]
+ deps = [_ProjectEntry.FromBuildConfigPath(p)
+ for p in gradle['dependent_java_projects']]
+ variables['java_project_deps'] = [d.ProjectName() for d in deps]
+
+ processor = jinja_template.JinjaProcessor(host_paths.DIR_SOURCE_ROOT)
+ return processor.Render(_JINJA_TEMPLATE_PATH, variables)
+
+
+def _GenerateRootGradle():
+ """Returns the data for the root project's build.gradle."""
+ variables = {'template_type': 'root'}
+ processor = jinja_template.JinjaProcessor(host_paths.DIR_SOURCE_ROOT)
+ return processor.Render(_JINJA_TEMPLATE_PATH, variables)
+
+
+def _GenerateSettingsGradle(project_entries):
+ """Returns the data for settings.gradle."""
+ project_name = os.path.basename(os.path.dirname(host_paths.DIR_SOURCE_ROOT))
+ lines = []
+ lines.append('// Generated by //build/android/generate_gradle.py')
+ lines.append('rootProject.name = "%s"' % project_name)
+ lines.append('rootProject.projectDir = settingsDir')
+ lines.append('')
+
+ for entry in project_entries:
+ # Example target: android_webview:android_webview_java__build_config
+ lines.append('include ":%s"' % entry.ProjectName())
+ lines.append('project(":%s").projectDir = new File(settingsDir, "%s")' %
+ (entry.ProjectName(), entry.GradleSubdir()))
+ return '\n'.join(lines)
+
+
+def _ExtractSrcjars(output_dir, srcjar_tuples):
+ """Extracts all srcjars to the directory given by the tuples."""
+ extracted_paths = set(s[1] for s in srcjar_tuples)
+ for extracted_path in extracted_paths:
+ if os.path.exists(extracted_path):
+ shutil.rmtree(extracted_path)
jbudorick 2016/07/07 22:32:29 Same comment as before about making destruction no
agrieve 2016/07/08 19:07:28 Done.
+
+ for srcjar_path, extracted_path in srcjar_tuples:
+ logging.info('Extracting %s to %s', srcjar_path, extracted_path)
+ with zipfile.ZipFile(os.path.join(output_dir, srcjar_path)) as z:
+ z.extractall(extracted_path)
+
+
+def _Relativizer(old_cwd, new_cwd):
+ """Returns a function that rebases a path from |old_cwd| to |new_cwd|."""
+ old_cwd = os.path.abspath(old_cwd)
+ new_cwd = os.path.abspath(new_cwd)
+ def rebase_path(path_or_list):
+ if isinstance(path_or_list, basestring):
+ return os.path.relpath(os.path.join(old_cwd, path_or_list), new_cwd)
+ return [rebase_path(p) for p in path_or_list]
+ return rebase_path
+
+
+def _FindAllProjectEntries(output_dir, main_entry):
+ """Returns the list of all _ProjectEntry instances given the root project."""
+ found = set()
+ to_scan = [main_entry.BuildConfigPath()]
+ while to_scan:
+ build_config_path = to_scan.pop()
+ if build_config_path in found:
+ continue
+ found.add(build_config_path)
+ build_config = build_utils.ReadJson(
+ os.path.join(output_dir, build_config_path))
+ to_scan.extend(build_config['deps_info']['deps_configs'])
+ return [_ProjectEntry.FromBuildConfigPath(p) for p in found]
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--output-directory',
+ help='Path to the root build directory.')
+ parser.add_argument('-v',
+ '--verbose',
+ dest='verbose_count',
+ default=0,
+ action='count',
+ help='Verbose level')
+ parser.add_argument('--target',
+ help='GN target to generate project for.',
+ default='//chrome/android:chrome_public_apk')
+ parser.add_argument('--project-dir',
+ help='Root of the output project.',
+ default=os.path.join('$CHROMIUM_OUTPUT_DIR', 'gradle'))
+ parser.add_argument('--use-gradle-process-resources',
+ action='store_true',
+ help='Have gradle generate R.java rather than ninja')
+ args = parser.parse_args()
+ run_tests_helper.SetLogLevel(args.verbose_count)
+ if args.output_directory:
+ constants.SetOutputDirectory(args.output_directory)
+ constants.CheckOutputDirectory()
+ output_dir = constants.GetOutDirectory()
+
+ gradle_output_dir = os.path.abspath(
+ args.project_dir.replace('$CHROMIUM_OUTPUT_DIR', output_dir))
+ logging.warning('Creating project at: %s', gradle_output_dir)
+
+ main_entry = _ProjectEntry(args.target)
+ logging.warning('Building .build_config files...')
+ _RunNinja(output_dir, [main_entry.NinjaBuildConfigTarget()])
+
+ all_entries = _FindAllProjectEntries(output_dir, main_entry)
+ logging.info('Found %d dependent build_config targets.', len(all_entries))
+
+ config_json = build_utils.ReadJson(
+ os.path.join(output_dir, 'gradle', 'config.json'))
+ project_entries = []
+ srcjar_tuples = []
+ for entry in all_entries:
+ build_config = build_utils.ReadJson(
+ os.path.join(output_dir, entry.BuildConfigPath()))
+ if build_config['deps_info']['type'] not in ('android_apk', 'java_library'):
+ continue
+
+ gradle_dir = os.path.join(gradle_output_dir, entry.GradleSubdir())
jbudorick 2016/07/07 22:32:29 optional nit: maybe gradle_subdir instead of gradl
agrieve 2016/07/08 19:07:28 changed to entry_output_dir
+ relativize = _Relativizer(output_dir, gradle_dir)
+
+ srcjars = build_config['gradle'].get('bundled_srcjars', [])
+ if not args.use_gradle_process_resources:
+ srcjars += build_config['javac']['srcjars']
+
+ java_sources_file = build_config['gradle'].get('java_sources_file')
+ if java_sources_file:
+ java_sources_file = os.path.join(output_dir, java_sources_file)
+
+ java_dirs = _CreateJavaSourceDir(output_dir, gradle_dir, java_sources_file)
+ if srcjars:
+ java_dirs.append(os.path.join(gradle_dir, _SRCJARS_SUBDIR))
+
+ data = _GenerateGradleFile(build_config, config_json, java_dirs, relativize,
+ args.use_gradle_process_resources)
+ if data:
+ project_entries.append(entry)
+ srcjar_tuples.extend(
+ (s, os.path.join(gradle_dir, _SRCJARS_SUBDIR)) for s in srcjars)
+ _WriteFile(os.path.join(gradle_dir, 'build.gradle'), data)
+
+ _WriteFile(os.path.join(gradle_output_dir, 'build.gradle'),
+ _GenerateRootGradle())
+
+ _WriteFile(os.path.join(gradle_output_dir, 'settings.gradle'),
+ _GenerateSettingsGradle(project_entries))
+
+ sdk_path = os.path.abspath(os.path.join(output_dir,
+ config_json['android_sdk_root']))
+ _WriteFile(os.path.join(gradle_output_dir, 'local.properties'),
+ _GenerateLocalProperties(sdk_path))
+
+ if srcjar_tuples:
+ logging.warning('Building all .srcjar files...')
+ _RunNinja(output_dir, [s[0] for s in srcjar_tuples])
+ _ExtractSrcjars(output_dir, srcjar_tuples)
+ logging.warning('Project created successfully!')
+
+
+if __name__ == '__main__':
+ main()

Powered by Google App Engine
This is Rietveld 408576698