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 logging | 10 import logging |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
60 def _WriteFile(path, data): | 60 def _WriteFile(path, data): |
61 """Writes |data| to |path|, constucting parent directories if necessary.""" | 61 """Writes |data| to |path|, constucting parent directories if necessary.""" |
62 logging.info('Writing %s', path) | 62 logging.info('Writing %s', path) |
63 dirname = os.path.dirname(path) | 63 dirname = os.path.dirname(path) |
64 if not os.path.exists(dirname): | 64 if not os.path.exists(dirname): |
65 os.makedirs(dirname) | 65 os.makedirs(dirname) |
66 with codecs.open(path, 'w', 'utf-8') as output_file: | 66 with codecs.open(path, 'w', 'utf-8') as output_file: |
67 output_file.write(data) | 67 output_file.write(data) |
68 | 68 |
69 | 69 |
70 def _RunNinja(output_dir, ninja_targets): | 70 def _RunNinja(output_dir, args): |
71 cmd = ['ninja', '-C', output_dir, '-j50'] | 71 cmd = ['ninja', '-C', output_dir, '-j50'] |
72 cmd.extend(ninja_targets) | 72 cmd.extend(args) |
73 if len(args) < 10: | |
jbudorick
2016/09/22 02:34:46
Why < 10? What about only logging part of cmd if i
agrieve
2016/09/22 15:51:25
meh, removed the check. It shows only when running
| |
74 logging.info('Running: %r', cmd) | |
75 subprocess.check_call(cmd) | |
76 | |
77 | |
78 def _QueryForAllGnTargets(output_dir): | |
79 cmd = ['ninja', '-C', output_dir, '-t', 'targets'] | |
73 logging.info('Running: %r', cmd) | 80 logging.info('Running: %r', cmd) |
74 subprocess.check_call(cmd) | 81 ninja_output = build_utils.CheckOutput(cmd) |
82 ret = [] | |
83 SUFFIX_LEN = len('__build_config') | |
84 for line in ninja_output.splitlines(): | |
85 ninja_target = line.rsplit(':', 1)[0] | |
86 # Ignore root aliases by ensure a : exists. | |
87 if ':' in ninja_target and ninja_target.endswith('__build_config'): | |
jbudorick
2016/09/22 02:34:46
Why is this checking for ':'? We'd never hit a __b
agrieve
2016/09/22 15:51:25
It's because we're querying for ninja rather than
jbudorick
2016/09/22 15:57:43
Ah, yeah, I was thinking about the GN targets.
| |
88 ret.append('//' + ninja_target[:-SUFFIX_LEN]) | |
89 return ret | |
75 | 90 |
76 | 91 |
77 class _ProjectEntry(object): | 92 class _ProjectEntry(object): |
78 """Helper class for various path transformations.""" | 93 """Helper class for various path transformations.""" |
79 def __init__(self, gn_target): | 94 def __init__(self, gn_target): |
80 assert gn_target.startswith('//'), gn_target | 95 assert gn_target.startswith('//'), gn_target |
81 if ':' not in gn_target: | 96 if ':' not in gn_target: |
82 gn_target = '%s:%s' % (gn_target, os.path.basename(gn_target)) | 97 gn_target = '%s:%s' % (gn_target, os.path.basename(gn_target)) |
83 self._gn_target = gn_target | 98 self._gn_target = gn_target |
84 self._build_config = None | 99 self._build_config = None |
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
157 subpath = os.path.relpath(target_path, prefix) | 172 subpath = os.path.relpath(target_path, prefix) |
158 symlinked_path = os.path.join(symlink_dir, subpath) | 173 symlinked_path = os.path.join(symlink_dir, subpath) |
159 symlinked_dir = os.path.dirname(symlinked_path) | 174 symlinked_dir = os.path.dirname(symlinked_path) |
160 if not os.path.exists(symlinked_dir): | 175 if not os.path.exists(symlinked_dir): |
161 os.makedirs(symlinked_dir) | 176 os.makedirs(symlinked_dir) |
162 relpath = os.path.relpath(target_path, symlinked_dir) | 177 relpath = os.path.relpath(target_path, symlinked_dir) |
163 logging.debug('Creating symlink %s -> %s', symlinked_path, relpath) | 178 logging.debug('Creating symlink %s -> %s', symlinked_path, relpath) |
164 os.symlink(relpath, symlinked_path) | 179 os.symlink(relpath, symlinked_path) |
165 | 180 |
166 | 181 |
167 def _CreateJavaSourceDir(entry_output_dir, java_sources_file): | 182 def _CreateJavaSourceDir(output_dir, entry_output_dir, java_sources_file): |
168 """Computes and constructs when necessary the list of java source directories. | 183 """Computes and constructs when necessary the list of java source directories. |
169 | 184 |
170 1. Computes the root java source directories from the list of files. | 185 1. Computes the root java source directories from the list of files. |
171 2. Determines whether there are any .java files in them that are not included | 186 2. Determines whether there are any .java files in them that are not included |
172 in |java_sources_file|. | 187 in |java_sources_file|. |
173 3. If not, returns the list of java source directories. If so, constructs a | 188 3. If not, returns the list of java source directories. If so, constructs a |
174 tree of symlinks within |entry_output_dir| of all files in | 189 tree of symlinks within |entry_output_dir| of all files in |
175 |java_sources_file|. | 190 |java_sources_file|. |
176 """ | 191 """ |
177 java_dirs = [] | 192 java_dirs = [] |
178 if java_sources_file: | 193 if java_sources_file: |
179 java_files = _RebasePath(build_utils.ReadSourcesList(java_sources_file)) | 194 java_files = _RebasePath(build_utils.ReadSourcesList(java_sources_file)) |
180 java_dirs = _ComputeJavaSourceDirs(java_files) | 195 java_dirs = _ComputeJavaSourceDirs(java_files) |
181 | 196 |
182 found_java_files = build_utils.FindInDirectories(java_dirs, '*.java') | 197 found_java_files = build_utils.FindInDirectories(java_dirs, '*.java') |
183 unwanted_java_files = set(found_java_files) - set(java_files) | 198 unwanted_java_files = set(found_java_files) - set(java_files) |
184 missing_java_files = set(java_files) - set(found_java_files) | 199 missing_java_files = set(java_files) - set(found_java_files) |
200 # Warn only about non-generated files that are missing. | |
201 missing_java_files = [p for p in missing_java_files | |
202 if not p.startswith(output_dir)] | |
185 if unwanted_java_files: | 203 if unwanted_java_files: |
186 logging.debug('Target requires .java symlinks: %s', entry_output_dir) | 204 logging.debug('Target requires .java symlinks: %s', entry_output_dir) |
187 symlink_dir = os.path.join(entry_output_dir, _JAVA_SUBDIR) | 205 symlink_dir = os.path.join(entry_output_dir, _JAVA_SUBDIR) |
188 _CreateSymlinkTree(entry_output_dir, symlink_dir, java_files, java_dirs) | 206 _CreateSymlinkTree(entry_output_dir, symlink_dir, java_files, java_dirs) |
189 java_dirs = [symlink_dir] | 207 java_dirs = [symlink_dir] |
208 | |
190 if missing_java_files: | 209 if missing_java_files: |
191 logging.warning('Some java files were not found: %s', missing_java_files) | 210 logging.warning('Some java files were not found: %s', missing_java_files) |
192 | 211 |
193 return java_dirs | 212 return java_dirs |
194 | 213 |
195 | 214 |
196 def _GenerateLocalProperties(sdk_dir): | 215 def _GenerateLocalProperties(sdk_dir): |
197 """Returns the data for project.properties as a string.""" | 216 """Returns the data for project.properties as a string.""" |
198 return '\n'.join([ | 217 return '\n'.join([ |
199 '# Generated by //build/android/gradle/generate_gradle.py', | 218 '# Generated by //build/android/gradle/generate_gradle.py', |
200 'sdk.dir=%s' % sdk_dir, | 219 'sdk.dir=%s' % sdk_dir, |
201 '']) | 220 '']) |
202 | 221 |
203 | 222 |
204 def _GenerateGradleFile(build_config, config_json, java_dirs, relativize, | 223 def _GenerateGradleFile(build_config, config_json, java_dirs, relativize, |
205 use_gradle_process_resources): | 224 use_gradle_process_resources, jinja_processor): |
206 """Returns the data for a project's build.gradle.""" | 225 """Returns the data for a project's build.gradle.""" |
207 deps_info = build_config['deps_info'] | 226 deps_info = build_config['deps_info'] |
208 gradle = build_config['gradle'] | 227 gradle = build_config['gradle'] |
209 | 228 |
210 if deps_info['type'] == 'android_apk': | 229 if deps_info['type'] == 'android_apk': |
211 target_type = 'android_apk' | 230 target_type = 'android_apk' |
212 elif deps_info['type'] == 'java_library' and not deps_info['is_prebuilt']: | 231 elif deps_info['type'] == 'java_library' and not deps_info['is_prebuilt']: |
213 if deps_info['requires_android']: | 232 if deps_info['requires_android']: |
214 target_type = 'android_library' | 233 target_type = 'android_library' |
215 else: | 234 else: |
(...skipping 12 matching lines...) Expand all Loading... | |
228 variables['java_dirs'] = relativize(java_dirs) | 247 variables['java_dirs'] = relativize(java_dirs) |
229 variables['prebuilts'] = relativize(gradle['dependent_prebuilt_jars']) | 248 variables['prebuilts'] = relativize(gradle['dependent_prebuilt_jars']) |
230 deps = [_ProjectEntry.FromBuildConfigPath(p) | 249 deps = [_ProjectEntry.FromBuildConfigPath(p) |
231 for p in gradle['dependent_android_projects']] | 250 for p in gradle['dependent_android_projects']] |
232 | 251 |
233 variables['android_project_deps'] = [d.ProjectName() for d in deps] | 252 variables['android_project_deps'] = [d.ProjectName() for d in deps] |
234 deps = [_ProjectEntry.FromBuildConfigPath(p) | 253 deps = [_ProjectEntry.FromBuildConfigPath(p) |
235 for p in gradle['dependent_java_projects']] | 254 for p in gradle['dependent_java_projects']] |
236 variables['java_project_deps'] = [d.ProjectName() for d in deps] | 255 variables['java_project_deps'] = [d.ProjectName() for d in deps] |
237 | 256 |
238 processor = jinja_template.JinjaProcessor(host_paths.DIR_SOURCE_ROOT) | 257 return jinja_processor.Render(_JINJA_TEMPLATE_PATH, variables) |
239 return processor.Render(_JINJA_TEMPLATE_PATH, variables) | |
240 | 258 |
241 | 259 |
242 def _GenerateRootGradle(): | 260 def _GenerateRootGradle(jinja_processor): |
243 """Returns the data for the root project's build.gradle.""" | 261 """Returns the data for the root project's build.gradle.""" |
244 variables = {'template_type': 'root'} | 262 variables = {'template_type': 'root'} |
245 processor = jinja_template.JinjaProcessor(host_paths.DIR_SOURCE_ROOT) | 263 return jinja_processor.Render(_JINJA_TEMPLATE_PATH, variables) |
246 return processor.Render(_JINJA_TEMPLATE_PATH, variables) | |
247 | 264 |
248 | 265 |
249 def _GenerateSettingsGradle(project_entries): | 266 def _GenerateSettingsGradle(project_entries): |
250 """Returns the data for settings.gradle.""" | 267 """Returns the data for settings.gradle.""" |
251 project_name = os.path.basename(os.path.dirname(host_paths.DIR_SOURCE_ROOT)) | 268 project_name = os.path.basename(os.path.dirname(host_paths.DIR_SOURCE_ROOT)) |
252 lines = [] | 269 lines = [] |
253 lines.append('// Generated by //build/android/gradle/generate_gradle.py') | 270 lines.append('// Generated by //build/android/gradle/generate_gradle.py') |
254 lines.append('rootProject.name = "%s"' % project_name) | 271 lines.append('rootProject.name = "%s"' % project_name) |
255 lines.append('rootProject.projectDir = settingsDir') | 272 lines.append('rootProject.projectDir = settingsDir') |
256 lines.append('') | 273 lines.append('') |
(...skipping 13 matching lines...) Expand all Loading... | |
270 assert _IsSubpathOf(extracted_path, entry_output_dir) | 287 assert _IsSubpathOf(extracted_path, entry_output_dir) |
271 if os.path.exists(extracted_path): | 288 if os.path.exists(extracted_path): |
272 shutil.rmtree(extracted_path) | 289 shutil.rmtree(extracted_path) |
273 | 290 |
274 for srcjar_path, extracted_path in srcjar_tuples: | 291 for srcjar_path, extracted_path in srcjar_tuples: |
275 logging.info('Extracting %s to %s', srcjar_path, extracted_path) | 292 logging.info('Extracting %s to %s', srcjar_path, extracted_path) |
276 with zipfile.ZipFile(srcjar_path) as z: | 293 with zipfile.ZipFile(srcjar_path) as z: |
277 z.extractall(extracted_path) | 294 z.extractall(extracted_path) |
278 | 295 |
279 | 296 |
280 def _FindAllProjectEntries(main_entry): | 297 def _FindAllProjectEntries(main_entries): |
281 """Returns the list of all _ProjectEntry instances given the root project.""" | 298 """Returns the list of all _ProjectEntry instances given the root project.""" |
282 found = set() | 299 found = set() |
283 to_scan = [main_entry] | 300 to_scan = list(main_entries) |
284 while to_scan: | 301 while to_scan: |
285 cur_entry = to_scan.pop() | 302 cur_entry = to_scan.pop() |
286 if cur_entry in found: | 303 if cur_entry in found: |
287 continue | 304 continue |
288 found.add(cur_entry) | 305 found.add(cur_entry) |
289 build_config = cur_entry.BuildConfig() | 306 build_config = cur_entry.BuildConfig() |
290 sub_config_paths = build_config['deps_info']['deps_configs'] | 307 sub_config_paths = build_config['deps_info']['deps_configs'] |
291 to_scan.extend( | 308 to_scan.extend( |
292 _ProjectEntry.FromBuildConfigPath(p) for p in sub_config_paths) | 309 _ProjectEntry.FromBuildConfigPath(p) for p in sub_config_paths) |
293 return list(found) | 310 return list(found) |
294 | 311 |
295 | 312 |
296 def main(): | 313 def main(): |
297 parser = argparse.ArgumentParser() | 314 parser = argparse.ArgumentParser() |
298 parser.add_argument('--output-directory', | 315 parser.add_argument('--output-directory', |
299 help='Path to the root build directory.') | 316 help='Path to the root build directory.') |
300 parser.add_argument('-v', | 317 parser.add_argument('-v', |
301 '--verbose', | 318 '--verbose', |
302 dest='verbose_count', | 319 dest='verbose_count', |
303 default=0, | 320 default=0, |
304 action='count', | 321 action='count', |
305 help='Verbose level') | 322 help='Verbose level') |
306 parser.add_argument('--target', | 323 parser.add_argument('--target', |
307 help='GN target to generate project for.', | 324 dest='targets', |
308 default='//chrome/android:chrome_public_apk') | 325 action='append', |
326 help='GN target to generate project for. ' | |
327 'May be repeated.', | |
328 default=['//chrome/android:chrome_public_test_apk']) | |
309 parser.add_argument('--project-dir', | 329 parser.add_argument('--project-dir', |
310 help='Root of the output project.', | 330 help='Root of the output project.', |
311 default=os.path.join('$CHROMIUM_OUTPUT_DIR', 'gradle')) | 331 default=os.path.join('$CHROMIUM_OUTPUT_DIR', 'gradle')) |
332 parser.add_argument('--all', | |
333 action='store_true', | |
334 help='Generate all java targets (slows down IDE)') | |
312 parser.add_argument('--use-gradle-process-resources', | 335 parser.add_argument('--use-gradle-process-resources', |
313 action='store_true', | 336 action='store_true', |
314 help='Have gradle generate R.java rather than ninja') | 337 help='Have gradle generate R.java rather than ninja') |
315 args = parser.parse_args() | 338 args = parser.parse_args() |
316 if args.output_directory: | 339 if args.output_directory: |
317 constants.SetOutputDirectory(args.output_directory) | 340 constants.SetOutputDirectory(args.output_directory) |
318 constants.CheckOutputDirectory() | 341 constants.CheckOutputDirectory() |
319 output_dir = constants.GetOutDirectory() | 342 output_dir = constants.GetOutDirectory() |
320 devil_chromium.Initialize(output_directory=output_dir) | 343 devil_chromium.Initialize(output_directory=output_dir) |
321 run_tests_helper.SetLogLevel(args.verbose_count) | 344 run_tests_helper.SetLogLevel(args.verbose_count) |
322 | 345 |
323 gradle_output_dir = os.path.abspath( | 346 gradle_output_dir = os.path.abspath( |
324 args.project_dir.replace('$CHROMIUM_OUTPUT_DIR', output_dir)) | 347 args.project_dir.replace('$CHROMIUM_OUTPUT_DIR', output_dir)) |
325 logging.warning('Creating project at: %s', gradle_output_dir) | 348 logging.warning('Creating project at: %s', gradle_output_dir) |
326 | 349 |
327 main_entry = _ProjectEntry(args.target) | 350 if args.all: |
351 # Run GN gen if necessary. | |
352 _RunNinja(output_dir, ['build.ninja']) | |
jbudorick
2016/09/22 02:34:46
This is ... building build.ninja? I take it that's
agrieve
2016/09/22 15:51:25
Added a comment - it's faster than running gn gen
jbudorick
2016/09/22 15:57:43
ah, sgtm
| |
353 # Query ninja for all __build_config targets. | |
354 targets = _QueryForAllGnTargets(output_dir) | |
355 else: | |
356 targets = args.targets | |
357 | |
358 main_entries = [_ProjectEntry(t) for t in targets] | |
328 logging.warning('Building .build_config files...') | 359 logging.warning('Building .build_config files...') |
329 _RunNinja(output_dir, [main_entry.NinjaBuildConfigTarget()]) | 360 _RunNinja(output_dir, [e.NinjaBuildConfigTarget() for e in main_entries]) |
330 | 361 |
331 all_entries = _FindAllProjectEntries(main_entry) | 362 all_entries = _FindAllProjectEntries(main_entries) |
332 logging.info('Found %d dependent build_config targets.', len(all_entries)) | 363 logging.info('Found %d dependent build_config targets.', len(all_entries)) |
333 | 364 |
365 jinja_processor = jinja_template.JinjaProcessor(host_paths.DIR_SOURCE_ROOT) | |
334 config_json = build_utils.ReadJson( | 366 config_json = build_utils.ReadJson( |
335 os.path.join(output_dir, 'gradle', 'config.json')) | 367 os.path.join(output_dir, 'gradle', 'config.json')) |
336 project_entries = [] | 368 project_entries = [] |
337 srcjar_tuples = [] | 369 srcjar_tuples = [] |
338 for entry in all_entries: | 370 for entry in all_entries: |
339 build_config = entry.BuildConfig() | 371 build_config = entry.BuildConfig() |
340 if build_config['deps_info']['type'] not in ('android_apk', 'java_library'): | 372 if build_config['deps_info']['type'] not in ('android_apk', 'java_library'): |
341 continue | 373 continue |
342 | 374 |
343 entry_output_dir = os.path.join(gradle_output_dir, entry.GradleSubdir()) | 375 entry_output_dir = os.path.join(gradle_output_dir, entry.GradleSubdir()) |
344 relativize = lambda x, d=entry_output_dir: _RebasePath(x, d) | 376 relativize = lambda x, d=entry_output_dir: _RebasePath(x, d) |
345 | 377 |
346 srcjars = _RebasePath(build_config['gradle'].get('bundled_srcjars', [])) | 378 srcjars = _RebasePath(build_config['gradle'].get('bundled_srcjars', [])) |
347 if not args.use_gradle_process_resources: | 379 if not args.use_gradle_process_resources: |
348 srcjars += _RebasePath(build_config['javac']['srcjars']) | 380 srcjars += _RebasePath(build_config['javac']['srcjars']) |
349 | 381 |
350 java_sources_file = build_config['gradle'].get('java_sources_file') | 382 java_sources_file = build_config['gradle'].get('java_sources_file') |
351 if java_sources_file: | 383 if java_sources_file: |
352 java_sources_file = _RebasePath(java_sources_file) | 384 java_sources_file = _RebasePath(java_sources_file) |
353 | 385 |
354 java_dirs = _CreateJavaSourceDir(entry_output_dir, java_sources_file) | 386 java_dirs = _CreateJavaSourceDir(output_dir, entry_output_dir, |
387 java_sources_file) | |
355 if srcjars: | 388 if srcjars: |
356 java_dirs.append(os.path.join(entry_output_dir, _SRCJARS_SUBDIR)) | 389 java_dirs.append(os.path.join(entry_output_dir, _SRCJARS_SUBDIR)) |
357 | 390 |
358 data = _GenerateGradleFile(build_config, config_json, java_dirs, relativize, | 391 data = _GenerateGradleFile(build_config, config_json, java_dirs, relativize, |
359 args.use_gradle_process_resources) | 392 args.use_gradle_process_resources, |
393 jinja_processor) | |
360 if data: | 394 if data: |
361 project_entries.append(entry) | 395 project_entries.append(entry) |
362 srcjar_tuples.extend( | 396 srcjar_tuples.extend( |
363 (s, os.path.join(entry_output_dir, _SRCJARS_SUBDIR)) for s in srcjars) | 397 (s, os.path.join(entry_output_dir, _SRCJARS_SUBDIR)) for s in srcjars) |
364 _WriteFile(os.path.join(entry_output_dir, 'build.gradle'), data) | 398 _WriteFile(os.path.join(entry_output_dir, 'build.gradle'), data) |
365 | 399 |
366 _WriteFile(os.path.join(gradle_output_dir, 'build.gradle'), | 400 _WriteFile(os.path.join(gradle_output_dir, 'build.gradle'), |
367 _GenerateRootGradle()) | 401 _GenerateRootGradle(jinja_processor)) |
368 | 402 |
369 _WriteFile(os.path.join(gradle_output_dir, 'settings.gradle'), | 403 _WriteFile(os.path.join(gradle_output_dir, 'settings.gradle'), |
370 _GenerateSettingsGradle(project_entries)) | 404 _GenerateSettingsGradle(project_entries)) |
371 | 405 |
372 sdk_path = _RebasePath(config_json['android_sdk_root']) | 406 sdk_path = _RebasePath(config_json['android_sdk_root']) |
373 _WriteFile(os.path.join(gradle_output_dir, 'local.properties'), | 407 _WriteFile(os.path.join(gradle_output_dir, 'local.properties'), |
374 _GenerateLocalProperties(sdk_path)) | 408 _GenerateLocalProperties(sdk_path)) |
375 | 409 |
376 if srcjar_tuples: | 410 if srcjar_tuples: |
377 logging.warning('Building all .srcjar files...') | 411 logging.warning('Building all .srcjar files...') |
378 targets = _RebasePath([s[0] for s in srcjar_tuples], output_dir) | 412 targets = _RebasePath([s[0] for s in srcjar_tuples], output_dir) |
379 _RunNinja(output_dir, targets) | 413 _RunNinja(output_dir, targets) |
380 _ExtractSrcjars(gradle_output_dir, srcjar_tuples) | 414 _ExtractSrcjars(gradle_output_dir, srcjar_tuples) |
381 logging.warning('Project created successfully!') | 415 logging.warning('Project created successfully!') |
382 logging.warning('Generated projects work best with Android Studio 2.2') | 416 logging.warning('Generated projects work best with Android Studio 2.2') |
383 logging.warning('For more tips: https://chromium.googlesource.com/chromium' | 417 logging.warning('For more tips: https://chromium.googlesource.com/chromium' |
384 '/src.git/+/master/docs/android_studio.md') | 418 '/src.git/+/master/docs/android_studio.md') |
385 | 419 |
386 | 420 |
387 if __name__ == '__main__': | 421 if __name__ == '__main__': |
388 main() | 422 main() |
OLD | NEW |