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

Side by Side 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 unified diff | Download patch
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # 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.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 """Generates an Android Studio project from a GN target."""
7
8 import argparse
9 import codecs
10 import logging
11 import os
12 import shutil
13 import subprocess
14 import sys
15 import zipfile
16
17 from pylib import constants
18 from pylib.constants import host_paths
19 from pylib.utils import run_tests_helper
20
21 sys.path.append(os.path.join(os.path.dirname(__file__), 'gyp'))
22 import jinja_template
23 from util import build_utils
24
25
26 _JINJA_TEMPLATE_PATH = os.path.join(
jbudorick 2016/07/07 22:32:29 nit: sort
agrieve 2016/07/08 19:07:28 Done.
27 os.path.dirname(__file__), 'build.gradle.jinja')
28 _DEFAULT_ANDROID_MANIFEST_PATH = os.path.join(
29 host_paths.DIR_SOURCE_ROOT, 'build', 'android', 'AndroidManifest.xml')
30 _SRCJARS_SUBDIR = 'extracted-srcjars'
31 _JAVA_SUBDIR = 'symlinked-java'
32
33
34 def _WriteFile(path, data):
35 logging.info('Writing %s', path)
36 dirname = os.path.dirname(path)
37 if not os.path.exists(dirname):
38 os.makedirs(dirname)
39 with codecs.open(path, 'w', 'utf-8') as output_file:
40 output_file.write(data)
41
42
43 def _RunNinja(output_dir, ninja_targets):
44 cmd = ['ninja', '-C', output_dir, '-j50']
45 cmd.extend(ninja_targets)
46 logging.info('Running: %r', cmd)
47 subprocess.check_call(cmd)
48
49
50 class _ProjectEntry(object):
51 """Helper class for various path transformations."""
52 def __init__(self, gn_target):
53 assert gn_target.startswith('//') and ':' in gn_target, gn_target
54 self._gn_target = gn_target
55
56 @classmethod
57 def FromBuildConfigPath(cls, path):
58 prefix = 'gen/'
59 suffix = '.build_config'
60 assert path.startswith(prefix) and path.endswith(suffix), path
61 subdir = path[len(prefix):-len(suffix)]
62 return cls('//%s:%s' % (os.path.split(subdir)))
63
64 def GnTarget(self):
65 return self._gn_target
66
67 def NinjaTarget(self):
68 return self._gn_target[2:]
69
70 def GnBuildConfigTarget(self):
71 return '%s__build_config' % self._gn_target
72
73 def NinjaBuildConfigTarget(self):
74 return '%s__build_config' % self.NinjaTarget()
75
76 def GradleSubdir(self):
77 """Returns the output subdirectory."""
78 return self.NinjaTarget().replace(':', os.path.sep)
79
80 def BuildConfigPath(self):
81 """Returns the relative path to the .build_config"""
82 return os.path.join('gen', self.GradleSubdir() + '.build_config')
83
84 def ProjectName(self):
85 """Returns the Gradle project name."""
86 return self.GradleSubdir().replace(os.path.sep, '\\$')
87
88
89 def _ComputeJavaSourceDirs(java_files):
90 """Returns the list of source directories for the given files."""
91 found_roots = set()
92 for path in java_files:
93 path_root = path
94 # Recognize these tokens as top-level.
95 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.
96 assert path_root, 'Failed to find source dir for ' + path
97 # Assume that if we've hit "src", the we're at the root.
98 if os.path.basename(path_root) == 'src':
99 break
100 path_root = os.path.dirname(path_root)
101 else:
102 path_root = os.path.dirname(path_root)
103 found_roots.add(path_root)
104 return list(found_roots)
105
106
107 def _CreateSymlinkTree(output_dir, symlink_dir, desired_files, parent_dirs):
108 """Creates a directory tree of symlinks to the given files.
109
110 The idea here is to replicate a directory tree while leaving out files within
111 it not listed by |desired_files|.
112 """
113 if os.path.exists(symlink_dir):
114 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
115
116 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.
117 prefix = next(d for d in parent_dirs if target_path.startswith(d))
118 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.
119 symlinked_path = os.path.join(symlink_dir, subpath)
120 symlinked_dir = os.path.dirname(symlinked_path)
121 if not os.path.exists(symlinked_dir):
122 os.makedirs(symlinked_dir)
123 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.
124 symlinked_dir)
125 logging.debug('Creating symlink %s -> %s', symlinked_path, relpath)
126 os.symlink(relpath, symlinked_path)
127
128
129 def _CreateJavaSourceDir(output_dir, gradle_dir, java_sources_file):
130 """Computes and constructs when necessary the list of java source directories.
131
132 1. Computes the root java source directories from the list of files.
133 2. Determines whether there are any .java files in them that are not included
134 in |java_sources_file|.
135 3. If not, returns the list of java source directories. If so, constructs a
136 tree of symlinks within |gradle_dir| of all files in |java_sources_file|.
137 """
138 java_dirs = []
139 if java_sources_file:
140 with open(java_sources_file) as f:
141 java_files = [l.strip() for l in f]
142 java_dirs = _ComputeJavaSourceDirs(java_files)
143 abs_java_dirs = [os.path.join(output_dir, d) for d in java_dirs]
144
145 found_java_files = build_utils.FindInDirectories(abs_java_dirs, '*.java')
146 found_java_files = [os.path.relpath(p, output_dir)
147 for p in found_java_files]
148
149 unwanted_java_files = set(found_java_files) - set(java_files)
150 if unwanted_java_files:
151 logging.debug('Target requires .java symlinks: %s', gradle_dir)
152 symlink_dir = os.path.join(gradle_dir, _JAVA_SUBDIR)
153 _CreateSymlinkTree(output_dir, symlink_dir, java_files, java_dirs)
154 java_dirs = [symlink_dir]
155
156 return java_dirs
157
158
159 def _GenerateLocalProperties(sdk_dir):
160 """Returns the data for project.properties as a string."""
161 return '\n'.join([
162 '# Generated by //build/android/generate_gradle.py',
163 'sdk.dir=%s' % sdk_dir,
164 ''])
165
166
167 def _GenerateGradleFile(build_config, config_json, java_dirs, relativize,
168 use_gradle_process_resources):
169 """Returns the data for a project's build.gradle."""
170 deps_info = build_config['deps_info']
171 gradle = build_config['gradle']
172
173 if deps_info['type'] == 'android_apk':
174 target_type = 'android_apk'
175 elif deps_info['type'] == 'java_library' and not deps_info['is_prebuilt']:
176 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
177 target_type = 'android_library'
178 else:
179 target_type = 'java_library'
180 else:
181 return None
182
183 variables = {}
184 variables['template_type'] = target_type
185 variables['use_gradle_process_resources'] = use_gradle_process_resources
186 variables['build_tools_version'] = config_json['build_tools_version']
187 variables['compile_sdk_version'] = config_json['compile_sdk_version']
188 android_manifest = gradle.get('android_manifest',
189 _DEFAULT_ANDROID_MANIFEST_PATH)
190 variables['android_manifest'] = relativize(android_manifest)
191
192 variables['java_dirs'] = relativize(java_dirs)
193 variables['prebuilts'] = relativize(gradle['dependent_prebuilt_jars'])
194 deps = [_ProjectEntry.FromBuildConfigPath(p)
195 for p in gradle['dependent_android_projects']]
196
197 variables['android_project_deps'] = [d.ProjectName() for d in deps]
198 deps = [_ProjectEntry.FromBuildConfigPath(p)
199 for p in gradle['dependent_java_projects']]
200 variables['java_project_deps'] = [d.ProjectName() for d in deps]
201
202 processor = jinja_template.JinjaProcessor(host_paths.DIR_SOURCE_ROOT)
203 return processor.Render(_JINJA_TEMPLATE_PATH, variables)
204
205
206 def _GenerateRootGradle():
207 """Returns the data for the root project's build.gradle."""
208 variables = {'template_type': 'root'}
209 processor = jinja_template.JinjaProcessor(host_paths.DIR_SOURCE_ROOT)
210 return processor.Render(_JINJA_TEMPLATE_PATH, variables)
211
212
213 def _GenerateSettingsGradle(project_entries):
214 """Returns the data for settings.gradle."""
215 project_name = os.path.basename(os.path.dirname(host_paths.DIR_SOURCE_ROOT))
216 lines = []
217 lines.append('// Generated by //build/android/generate_gradle.py')
218 lines.append('rootProject.name = "%s"' % project_name)
219 lines.append('rootProject.projectDir = settingsDir')
220 lines.append('')
221
222 for entry in project_entries:
223 # Example target: android_webview:android_webview_java__build_config
224 lines.append('include ":%s"' % entry.ProjectName())
225 lines.append('project(":%s").projectDir = new File(settingsDir, "%s")' %
226 (entry.ProjectName(), entry.GradleSubdir()))
227 return '\n'.join(lines)
228
229
230 def _ExtractSrcjars(output_dir, srcjar_tuples):
231 """Extracts all srcjars to the directory given by the tuples."""
232 extracted_paths = set(s[1] for s in srcjar_tuples)
233 for extracted_path in extracted_paths:
234 if os.path.exists(extracted_path):
235 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.
236
237 for srcjar_path, extracted_path in srcjar_tuples:
238 logging.info('Extracting %s to %s', srcjar_path, extracted_path)
239 with zipfile.ZipFile(os.path.join(output_dir, srcjar_path)) as z:
240 z.extractall(extracted_path)
241
242
243 def _Relativizer(old_cwd, new_cwd):
244 """Returns a function that rebases a path from |old_cwd| to |new_cwd|."""
245 old_cwd = os.path.abspath(old_cwd)
246 new_cwd = os.path.abspath(new_cwd)
247 def rebase_path(path_or_list):
248 if isinstance(path_or_list, basestring):
249 return os.path.relpath(os.path.join(old_cwd, path_or_list), new_cwd)
250 return [rebase_path(p) for p in path_or_list]
251 return rebase_path
252
253
254 def _FindAllProjectEntries(output_dir, main_entry):
255 """Returns the list of all _ProjectEntry instances given the root project."""
256 found = set()
257 to_scan = [main_entry.BuildConfigPath()]
258 while to_scan:
259 build_config_path = to_scan.pop()
260 if build_config_path in found:
261 continue
262 found.add(build_config_path)
263 build_config = build_utils.ReadJson(
264 os.path.join(output_dir, build_config_path))
265 to_scan.extend(build_config['deps_info']['deps_configs'])
266 return [_ProjectEntry.FromBuildConfigPath(p) for p in found]
267
268
269 def main():
270 parser = argparse.ArgumentParser()
271 parser.add_argument('--output-directory',
272 help='Path to the root build directory.')
273 parser.add_argument('-v',
274 '--verbose',
275 dest='verbose_count',
276 default=0,
277 action='count',
278 help='Verbose level')
279 parser.add_argument('--target',
280 help='GN target to generate project for.',
281 default='//chrome/android:chrome_public_apk')
282 parser.add_argument('--project-dir',
283 help='Root of the output project.',
284 default=os.path.join('$CHROMIUM_OUTPUT_DIR', 'gradle'))
285 parser.add_argument('--use-gradle-process-resources',
286 action='store_true',
287 help='Have gradle generate R.java rather than ninja')
288 args = parser.parse_args()
289 run_tests_helper.SetLogLevel(args.verbose_count)
290 if args.output_directory:
291 constants.SetOutputDirectory(args.output_directory)
292 constants.CheckOutputDirectory()
293 output_dir = constants.GetOutDirectory()
294
295 gradle_output_dir = os.path.abspath(
296 args.project_dir.replace('$CHROMIUM_OUTPUT_DIR', output_dir))
297 logging.warning('Creating project at: %s', gradle_output_dir)
298
299 main_entry = _ProjectEntry(args.target)
300 logging.warning('Building .build_config files...')
301 _RunNinja(output_dir, [main_entry.NinjaBuildConfigTarget()])
302
303 all_entries = _FindAllProjectEntries(output_dir, main_entry)
304 logging.info('Found %d dependent build_config targets.', len(all_entries))
305
306 config_json = build_utils.ReadJson(
307 os.path.join(output_dir, 'gradle', 'config.json'))
308 project_entries = []
309 srcjar_tuples = []
310 for entry in all_entries:
311 build_config = build_utils.ReadJson(
312 os.path.join(output_dir, entry.BuildConfigPath()))
313 if build_config['deps_info']['type'] not in ('android_apk', 'java_library'):
314 continue
315
316 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
317 relativize = _Relativizer(output_dir, gradle_dir)
318
319 srcjars = build_config['gradle'].get('bundled_srcjars', [])
320 if not args.use_gradle_process_resources:
321 srcjars += build_config['javac']['srcjars']
322
323 java_sources_file = build_config['gradle'].get('java_sources_file')
324 if java_sources_file:
325 java_sources_file = os.path.join(output_dir, java_sources_file)
326
327 java_dirs = _CreateJavaSourceDir(output_dir, gradle_dir, java_sources_file)
328 if srcjars:
329 java_dirs.append(os.path.join(gradle_dir, _SRCJARS_SUBDIR))
330
331 data = _GenerateGradleFile(build_config, config_json, java_dirs, relativize,
332 args.use_gradle_process_resources)
333 if data:
334 project_entries.append(entry)
335 srcjar_tuples.extend(
336 (s, os.path.join(gradle_dir, _SRCJARS_SUBDIR)) for s in srcjars)
337 _WriteFile(os.path.join(gradle_dir, 'build.gradle'), data)
338
339 _WriteFile(os.path.join(gradle_output_dir, 'build.gradle'),
340 _GenerateRootGradle())
341
342 _WriteFile(os.path.join(gradle_output_dir, 'settings.gradle'),
343 _GenerateSettingsGradle(project_entries))
344
345 sdk_path = os.path.abspath(os.path.join(output_dir,
346 config_json['android_sdk_root']))
347 _WriteFile(os.path.join(gradle_output_dir, 'local.properties'),
348 _GenerateLocalProperties(sdk_path))
349
350 if srcjar_tuples:
351 logging.warning('Building all .srcjar files...')
352 _RunNinja(output_dir, [s[0] for s in srcjar_tuples])
353 _ExtractSrcjars(output_dir, srcjar_tuples)
354 logging.warning('Project created successfully!')
355
356
357 if __name__ == '__main__':
358 main()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698