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

Side by Side Diff: build/android/gradle/generate_gradle.py

Issue 2697313004: Android: Cache android studio project entries (Closed)
Patch Set: Rebase and avoid error logs Created 3 years, 10 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
« no previous file with comments | « build/android/gradle/android.jinja ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 glob
(...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after
110 ret = [] 110 ret = []
111 SUFFIX_LEN = len('__build_config') 111 SUFFIX_LEN = len('__build_config')
112 for line in ninja_output.splitlines(): 112 for line in ninja_output.splitlines():
113 ninja_target = line.rsplit(':', 1)[0] 113 ninja_target = line.rsplit(':', 1)[0]
114 # Ignore root aliases by ensure a : exists. 114 # Ignore root aliases by ensure a : exists.
115 if ':' in ninja_target and ninja_target.endswith('__build_config'): 115 if ':' in ninja_target and ninja_target.endswith('__build_config'):
116 ret.append('//' + ninja_target[:-SUFFIX_LEN]) 116 ret.append('//' + ninja_target[:-SUFFIX_LEN])
117 return ret 117 return ret
118 118
119 119
120 def _Dedup(entries):
agrieve 2017/02/17 03:07:01 nit: this doesn't save any lines over just calling
Peter Wen 2017/02/17 16:37:26 Done.
121 """Return unique entries in iterable in the same order."""
122 return _DedupAgainst(entries, [])
123
124
125 def _DedupAgainst(entries, existing_entries):
126 """Return unique entries in iterable given existing entries."""
agrieve 2017/02/17 03:07:01 You return a list, so I'd say that rather than ite
Peter Wen 2017/02/17 16:37:26 Done.
127 seen = set(existing_entries)
128 unique_entries = []
129 for entry in entries:
130 if entry not in seen:
131 unique_entries.append(entry)
132 seen.add(entry)
133 return unique_entries
134
135
120 class _ProjectEntry(object): 136 class _ProjectEntry(object):
121 """Helper class for project entries.""" 137 """Helper class for project entries."""
138
139 _cached_entries = {}
140
122 def __init__(self, gn_target): 141 def __init__(self, gn_target):
142 # Use _ProjectEntry.FromGnTarget instead for caching.
143 self._gn_target = gn_target
144 self._build_config = None
145 self._java_files = None
146 self._all_entries = None
147 self.android_test_entry = None
148
149 @classmethod
150 def FromGnTarget(cls, gn_target):
123 assert gn_target.startswith('//'), gn_target 151 assert gn_target.startswith('//'), gn_target
124 if ':' not in gn_target: 152 if ':' not in gn_target:
125 gn_target = '%s:%s' % (gn_target, os.path.basename(gn_target)) 153 gn_target = '%s:%s' % (gn_target, os.path.basename(gn_target))
126 self._gn_target = gn_target 154 if gn_target not in cls._cached_entries:
127 self._build_config = None 155 cls._cached_entries[gn_target] = cls(gn_target)
128 self._java_files = None 156 return cls._cached_entries[gn_target]
129 self.android_test_entry = None
130 157
131 @classmethod 158 @classmethod
132 def FromBuildConfigPath(cls, path): 159 def FromBuildConfigPath(cls, path):
133 prefix = 'gen/' 160 prefix = 'gen/'
134 suffix = '.build_config' 161 suffix = '.build_config'
135 assert path.startswith(prefix) and path.endswith(suffix), path 162 assert path.startswith(prefix) and path.endswith(suffix), path
136 subdir = path[len(prefix):-len(suffix)] 163 subdir = path[len(prefix):-len(suffix)]
137 return cls('//%s:%s' % (os.path.split(subdir))) 164 gn_target = '//%s:%s' % (os.path.split(subdir))
165 return cls.FromGnTarget(gn_target)
138 166
139 def __hash__(self): 167 def __hash__(self):
140 return hash(self._gn_target) 168 return hash(self.GnTarget())
agrieve 2017/02/17 03:07:01 nit: I think it's generally better to leave these
Peter Wen 2017/02/17 16:37:25 Done. We don't do any checks/calculations in the
141 169
142 def __eq__(self, other): 170 def __eq__(self, other):
143 return self._gn_target == other.GnTarget() 171 return self.GnTarget() == other.GnTarget()
144 172
145 def GnTarget(self): 173 def GnTarget(self):
146 return self._gn_target 174 return self._gn_target
147 175
148 def NinjaTarget(self): 176 def NinjaTarget(self):
149 return self._gn_target[2:] 177 return self.GnTarget()[2:]
150 178
151 def GnBuildConfigTarget(self): 179 def GnBuildConfigTarget(self):
152 return '%s__build_config' % self._gn_target 180 return '%s__build_config' % self.GnTarget()
153 181
154 def NinjaBuildConfigTarget(self): 182 def NinjaBuildConfigTarget(self):
155 return '%s__build_config' % self.NinjaTarget() 183 return '%s__build_config' % self.NinjaTarget()
156 184
157 def GradleSubdir(self): 185 def GradleSubdir(self):
158 """Returns the output subdirectory.""" 186 """Returns the output subdirectory."""
159 return self.NinjaTarget().replace(':', os.path.sep) 187 return self.NinjaTarget().replace(':', os.path.sep)
160 188
161 def ProjectName(self): 189 def ProjectName(self):
162 """Returns the Gradle project name.""" 190 """Returns the Gradle project name."""
(...skipping 25 matching lines...) Expand all
188 def JavaFiles(self): 216 def JavaFiles(self):
189 if self._java_files is None: 217 if self._java_files is None:
190 java_sources_file = self.Gradle().get('java_sources_file') 218 java_sources_file = self.Gradle().get('java_sources_file')
191 java_files = [] 219 java_files = []
192 if java_sources_file: 220 if java_sources_file:
193 java_sources_file = _RebasePath(java_sources_file) 221 java_sources_file = _RebasePath(java_sources_file)
194 java_files = build_utils.ReadSourcesList(java_sources_file) 222 java_files = build_utils.ReadSourcesList(java_sources_file)
195 self._java_files = java_files 223 self._java_files = java_files
196 return self._java_files 224 return self._java_files
197 225
226 def SubdirJavaFiles(self):
agrieve 2017/02/17 03:07:01 Is this the same as saying generated java files? I
Peter Wen 2017/02/17 16:37:25 Done.
227 return [p for p in self.JavaFiles() if not p.startswith('..')]
228
229 def PrebuiltJars(self):
230 return self.Gradle()['dependent_prebuilt_jars']
231
232 def AllEntries(self):
agrieve 2017/02/17 03:07:01 This is worth adding pydoc for.
Peter Wen 2017/02/17 16:37:26 Done.
233 if self._all_entries is None:
234 logging.debug('Generating entries for %s', self.GnTarget())
235 deps = [_ProjectEntry.FromBuildConfigPath(p)
236 for p in self.Gradle()['dependent_android_projects']]
237 deps.extend([_ProjectEntry.FromBuildConfigPath(p)
agrieve 2017/02/17 03:07:01 nit: shouldn't need the [].
Peter Wen 2017/02/17 16:37:26 Done.
238 for p in self.Gradle()['dependent_java_projects']])
239 all_entries = []
240 for dep in deps:
241 all_entries += dep.AllEntries()
agrieve 2017/02/17 03:07:01 I think it might be easier to follow if you de-dup
Peter Wen 2017/02/17 16:37:25 Switched to using sets instead, it's better to sor
242 all_entries.append(self)
243 self._all_entries = _Dedup(all_entries)
244 return self._all_entries
245
198 246
199 class _ProjectContextGenerator(object): 247 class _ProjectContextGenerator(object):
200 """Helper class to generate gradle build files""" 248 """Helper class to generate gradle build files"""
201 def __init__(self, project_dir, build_vars, use_gradle_process_resources, 249 def __init__(self, project_dir, build_vars, use_gradle_process_resources,
202 jinja_processor): 250 jinja_processor):
203 self.project_dir = project_dir 251 self.project_dir = project_dir
204 self.build_vars = build_vars 252 self.build_vars = build_vars
205 self.use_gradle_process_resources = use_gradle_process_resources 253 self.use_gradle_process_resources = use_gradle_process_resources
206 self.jinja_processor = jinja_processor 254 self.jinja_processor = jinja_processor
207 255
208 def _GenJniLibs(self, entry): 256 def _GenJniLibs(self, entry):
209 native_section = entry.BuildConfig().get('native') 257 native_section = entry.BuildConfig().get('native')
210 if native_section: 258 if native_section:
211 jni_libs = _CreateJniLibsDir( 259 jni_libs = _CreateJniLibsDir(
212 constants.GetOutDirectory(), self.EntryOutputDir(entry), 260 constants.GetOutDirectory(), self.EntryOutputDir(entry),
213 native_section.get('libraries')) 261 native_section.get('libraries'))
214 else: 262 else:
215 jni_libs = [] 263 jni_libs = []
216 return jni_libs 264 return jni_libs
217 265
218 def _GenJavaDirs(self, entry): 266 def _GenJavaDirs(self, entry):
219 java_dirs, excludes = _CreateJavaSourceDir( 267 java_dirs, excludes = _ComputeJavaSourceDirsAndExcludes(
220 constants.GetOutDirectory(), entry.JavaFiles()) 268 constants.GetOutDirectory(), entry.JavaFiles())
221 if self.Srcjars(entry): 269 if self.Srcjars(entry):
222 java_dirs.append( 270 java_dirs.append(
223 os.path.join(self.EntryOutputDir(entry), _SRCJARS_SUBDIR)) 271 os.path.join(self.EntryOutputDir(entry), _SRCJARS_SUBDIR))
224 return java_dirs, excludes 272 return java_dirs, excludes
225 273
226 def _GenResDirs(self, entry): 274 def _GenResDirs(self, entry):
227 res_dirs = list(entry.DepsInfo().get('owned_resources_dirs', [])) 275 res_dirs = list(entry.DepsInfo().get('owned_resources_dirs', []))
228 if entry.ResZips(): 276 if entry.ResZips():
229 res_dirs.append(os.path.join(self.EntryOutputDir(entry), _RES_SUBDIR)) 277 res_dirs.append(os.path.join(self.EntryOutputDir(entry), _RES_SUBDIR))
230 return res_dirs 278 return res_dirs
231 279
232 def _GenCustomManifest(self, entry): 280 def _GenCustomManifest(self, entry):
233 """Returns the path to the generated AndroidManifest.xml.""" 281 """Returns the path to the generated AndroidManifest.xml.
234 javac = entry.Javac()
235 resource_packages = javac['resource_packages']
236 output_file = os.path.join(
237 self.EntryOutputDir(entry), 'AndroidManifest.xml')
238 282
283 Gradle uses package id from manifest when generating R.class. So, we need
284 to generate a custom manifest if we let gradle process resources. We cannot
285 simply set android.defaultConfig.applicationId because it is not supported
286 for library targets."""
287 resource_packages = entry.Javac().get('resource_packages')
239 if not resource_packages: 288 if not resource_packages:
240 logging.error('Target ' + entry.GnTarget() + ' includes resources from ' 289 logging.debug('Target ' + entry.GnTarget() + ' includes resources from '
241 'unknown package. Unable to process with gradle.') 290 'unknown package. Unable to process with gradle.')
242 return _DEFAULT_ANDROID_MANIFEST_PATH 291 return _DEFAULT_ANDROID_MANIFEST_PATH
243 elif len(resource_packages) > 1: 292 elif len(resource_packages) > 1:
244 logging.error('Target ' + entry.GnTarget() + ' includes resources from ' 293 logging.debug('Target ' + entry.GnTarget() + ' includes resources from '
245 'multiple packages. Unable to process with gradle.') 294 'multiple packages. Unable to process with gradle.')
246 return _DEFAULT_ANDROID_MANIFEST_PATH 295 return _DEFAULT_ANDROID_MANIFEST_PATH
247 296
248 variables = {} 297 variables = {}
249 variables['compile_sdk_version'] = self.build_vars['android_sdk_version'] 298 variables['compile_sdk_version'] = self.build_vars['android_sdk_version']
250 variables['package'] = resource_packages[0] 299 variables['package'] = resource_packages[0]
251 300
301 output_file = os.path.join(
302 self.EntryOutputDir(entry), 'AndroidManifest.xml')
252 data = self.jinja_processor.Render(_TemplatePath('manifest'), variables) 303 data = self.jinja_processor.Render(_TemplatePath('manifest'), variables)
253 _WriteFile(output_file, data) 304 _WriteFile(output_file, data)
254 305
255 return output_file 306 return output_file
256 307
257 def _Relativize(self, entry, paths): 308 def _Relativize(self, entry, paths):
258 return _RebasePath(paths, self.EntryOutputDir(entry)) 309 return _RebasePath(paths, self.EntryOutputDir(entry))
259 310
260 def EntryOutputDir(self, entry): 311 def EntryOutputDir(self, entry):
261 return os.path.join(self.project_dir, entry.GradleSubdir()) 312 return os.path.join(self.project_dir, entry.GradleSubdir())
262 313
263 def Srcjars(self, entry): 314 def Srcjars(self, entry):
264 srcjars = _RebasePath(entry.Gradle().get('bundled_srcjars', [])) 315 srcjars = _RebasePath(entry.Gradle().get('bundled_srcjars', []))
265 if not self.use_gradle_process_resources: 316 if not self.use_gradle_process_resources:
266 srcjars += _RebasePath(entry.BuildConfig()['javac']['srcjars']) 317 srcjars += _RebasePath(entry.BuildConfig()['javac']['srcjars'])
267 return srcjars 318 return srcjars
268 319
269 def GeneratedInputs(self, entry): 320 def GeneratedInputs(self, entry):
270 generated_inputs = [] 321 generated_inputs = []
271 generated_inputs.extend(self.Srcjars(entry)) 322 generated_inputs.extend(self.Srcjars(entry))
272 generated_inputs.extend(_RebasePath(entry.ResZips())) 323 generated_inputs.extend(_RebasePath(entry.ResZips()))
273 generated_inputs.extend( 324 generated_inputs.extend(entry.SubdirJavaFiles())
274 p for p in entry.JavaFiles() if not p.startswith('..')) 325 generated_inputs.extend(entry.PrebuiltJars())
275 generated_inputs.extend(entry.Gradle()['dependent_prebuilt_jars'])
276 return generated_inputs 326 return generated_inputs
277 327
278 def Generate(self, entry): 328 def Generate(self, entry):
279 variables = {} 329 variables = {}
280 java_dirs, excludes = self._GenJavaDirs(entry) 330 java_dirs, excludes = self._GenJavaDirs(entry)
281 variables['java_dirs'] = self._Relativize(entry, java_dirs) 331 variables['java_dirs'] = self._Relativize(entry, java_dirs)
282 variables['java_excludes'] = excludes 332 variables['java_excludes'] = excludes
283 variables['jni_libs'] = self._Relativize(entry, self._GenJniLibs(entry)) 333 variables['jni_libs'] = self._Relativize(entry, self._GenJniLibs(entry))
284 variables['res_dirs'] = self._Relativize(entry, self._GenResDirs(entry)) 334 variables['res_dirs'] = self._Relativize(entry, self._GenResDirs(entry))
285 android_manifest = entry.Gradle().get('android_manifest') 335 android_manifest = entry.Gradle().get('android_manifest')
286 if not android_manifest: 336 if not android_manifest:
287 # Gradle uses package id from manifest when generating R.class. So, we 337 android_manifest = self._GenCustomManifest(entry)
288 # need to generate a custom manifest if we let gradle process resources.
289 # We cannot simply set android.defaultConfig.applicationId because it is
290 # not supported for library targets.
291 if variables['res_dirs']:
292 android_manifest = self._GenCustomManifest(entry)
293 else:
294 android_manifest = _DEFAULT_ANDROID_MANIFEST_PATH
295 variables['android_manifest'] = self._Relativize(entry, android_manifest) 338 variables['android_manifest'] = self._Relativize(entry, android_manifest)
339 # TODO(agrieve): Add an option to use interface jars and see if that speeds
340 # things up at all.
341 variables['prebuilts'] = self._Relativize(entry, entry.PrebuiltJars())
296 deps = [_ProjectEntry.FromBuildConfigPath(p) 342 deps = [_ProjectEntry.FromBuildConfigPath(p)
297 for p in entry.Gradle()['dependent_android_projects']] 343 for p in entry.Gradle()['dependent_android_projects']]
298 variables['android_project_deps'] = [d.ProjectName() for d in deps] 344 variables['android_project_deps'] = [d.ProjectName() for d in deps]
299 # TODO(agrieve): Add an option to use interface jars and see if that speeds
300 # things up at all.
301 variables['prebuilts'] = self._Relativize(
302 entry, entry.Gradle()['dependent_prebuilt_jars'])
303 deps = [_ProjectEntry.FromBuildConfigPath(p) 345 deps = [_ProjectEntry.FromBuildConfigPath(p)
304 for p in entry.Gradle()['dependent_java_projects']] 346 for p in entry.Gradle()['dependent_java_projects']]
305 variables['java_project_deps'] = [d.ProjectName() for d in deps] 347 variables['java_project_deps'] = [d.ProjectName() for d in deps]
306 return variables 348 return variables
307 349
308 350
309 def _ComputeJavaSourceDirs(java_files): 351 def _ComputeJavaSourceDirs(java_files):
310 """Returns a dictionary of source dirs with each given files in one.""" 352 """Returns a dictionary of source dirs with each given files in one."""
311 found_roots = {} 353 found_roots = {}
312 for path in java_files: 354 for path in java_files:
(...skipping 30 matching lines...) Expand all
343 found_files = set(glob.glob(target_exclude)) 385 found_files = set(glob.glob(target_exclude))
344 valid_files = found_files & files_to_include 386 valid_files = found_files & files_to_include
345 if valid_files: 387 if valid_files:
346 excludes.append(os.path.relpath(unwanted_file, parent_dir)) 388 excludes.append(os.path.relpath(unwanted_file, parent_dir))
347 else: 389 else:
348 excludes.append(os.path.relpath(target_exclude, parent_dir)) 390 excludes.append(os.path.relpath(target_exclude, parent_dir))
349 files_to_exclude -= found_files 391 files_to_exclude -= found_files
350 return excludes 392 return excludes
351 393
352 394
353 def _CreateJavaSourceDir(output_dir, java_files): 395 def _ComputeJavaSourceDirsAndExcludes(output_dir, java_files):
354 """Computes the list of java source directories and exclude patterns. 396 """Computes the list of java source directories and exclude patterns.
355 397
356 1. Computes the root java source directories from the list of files. 398 1. Computes the root java source directories from the list of files.
357 2. Compute exclude patterns that exclude all extra files only. 399 2. Compute exclude patterns that exclude all extra files only.
358 3. Returns the list of java source directories and exclude patterns. 400 3. Returns the list of java source directories and exclude patterns.
359 """ 401 """
360 java_dirs = [] 402 java_dirs = []
361 excludes = [] 403 excludes = []
362 if java_files: 404 if java_files:
363 java_files = _RebasePath(java_files) 405 java_files = _RebasePath(java_files)
(...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after
454 variables['template_type'] = target_type 496 variables['template_type'] = target_type
455 variables['use_gradle_process_resources'] = ( 497 variables['use_gradle_process_resources'] = (
456 generator.use_gradle_process_resources) 498 generator.use_gradle_process_resources)
457 variables['build_tools_version'] = ( 499 variables['build_tools_version'] = (
458 build_vars['android_sdk_build_tools_version']) 500 build_vars['android_sdk_build_tools_version'])
459 variables['compile_sdk_version'] = build_vars['android_sdk_version'] 501 variables['compile_sdk_version'] = build_vars['android_sdk_version']
460 variables['main'] = generator.Generate(entry) 502 variables['main'] = generator.Generate(entry)
461 if entry.android_test_entry: 503 if entry.android_test_entry:
462 variables['android_test'] = generator.Generate( 504 variables['android_test'] = generator.Generate(
463 entry.android_test_entry) 505 entry.android_test_entry)
506 for key, value in variables['android_test'].iteritems():
507 if isinstance(value, list):
508 variables['android_test'][key] = _DedupAgainst(
509 value, variables['main'][key])
464 510
465 return jinja_processor.Render( 511 return jinja_processor.Render(
466 _TemplatePath(target_type.split('_')[0]), variables) 512 _TemplatePath(target_type.split('_')[0]), variables)
467 513
468 514
469 def _GenerateRootGradle(jinja_processor): 515 def _GenerateRootGradle(jinja_processor):
470 """Returns the data for the root project's build.gradle.""" 516 """Returns the data for the root project's build.gradle."""
471 return jinja_processor.Render(_TemplatePath('root')) 517 return jinja_processor.Render(_TemplatePath('root'))
472 518
473 519
(...skipping 119 matching lines...) Expand 10 before | Expand all | Expand 10 after
593 _RunNinja(constants.GetOutDirectory(), ['build.ninja']) 639 _RunNinja(constants.GetOutDirectory(), ['build.ninja'])
594 # Query ninja for all __build_config targets. 640 # Query ninja for all __build_config targets.
595 targets = _QueryForAllGnTargets(output_dir) 641 targets = _QueryForAllGnTargets(output_dir)
596 else: 642 else:
597 targets = args.targets or _DEFAULT_TARGETS 643 targets = args.targets or _DEFAULT_TARGETS
598 targets = [re.sub(r'_test_apk$', '_test_apk__apk', t) for t in targets] 644 targets = [re.sub(r'_test_apk$', '_test_apk__apk', t) for t in targets]
599 # TODO(wnwen): Utilize Gradle's test constructs for our junit tests? 645 # TODO(wnwen): Utilize Gradle's test constructs for our junit tests?
600 targets = [re.sub(r'_junit_tests$', '_junit_tests__java_binary', t) 646 targets = [re.sub(r'_junit_tests$', '_junit_tests__java_binary', t)
601 for t in targets] 647 for t in targets]
602 648
603 main_entries = [_ProjectEntry(t) for t in targets] 649 main_entries = [_ProjectEntry.FromGnTarget(t) for t in targets]
604 650
605 logging.warning('Building .build_config files...') 651 logging.warning('Building .build_config files...')
606 _RunNinja(output_dir, [e.NinjaBuildConfigTarget() for e in main_entries]) 652 _RunNinja(output_dir, [e.NinjaBuildConfigTarget() for e in main_entries])
607 653
608 # There are many unused libraries, so restrict to those that are actually used 654 # There are many unused libraries, so restrict to those that are actually used
609 # when using --all. 655 # when using --all.
610 if args.all: 656 if args.all:
611 main_entries = [e for e in main_entries if e.GetType() == 'android_apk'] 657 main_entries = [e for e in main_entries if e.GetType() == 'android_apk']
612 658
613 all_entries = _FindAllProjectEntries(main_entries) 659 all_entries = _FindAllProjectEntries(main_entries)
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
656 _ExtractZips(generator.project_dir, zip_tuples) 702 _ExtractZips(generator.project_dir, zip_tuples)
657 703
658 logging.warning('Project created! (%d subprojects)', len(project_entries)) 704 logging.warning('Project created! (%d subprojects)', len(project_entries))
659 logging.warning('Generated projects work best with Android Studio 2.2') 705 logging.warning('Generated projects work best with Android Studio 2.2')
660 logging.warning('For more tips: https://chromium.googlesource.com/chromium' 706 logging.warning('For more tips: https://chromium.googlesource.com/chromium'
661 '/src.git/+/master/docs/android_studio.md') 707 '/src.git/+/master/docs/android_studio.md')
662 708
663 709
664 if __name__ == '__main__': 710 if __name__ == '__main__':
665 main() 711 main()
OLDNEW
« no previous file with comments | « build/android/gradle/android.jinja ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698