0

Android: Add module "_all" for Android Studio

Adding all sources to a "_all" pseudo module fixes Studio's code analysis
functions (imports, refactoring).

In order to have things build properly in gradle, the "_all" module has
all sources excluded (gradle applies the filters, studio does not).

Also fix "--all" targets to include tests. Make it easier to make sweeping
java refactors in Android Studio.

BUG=620034

Review-Url: https://codereview.chromium.org/2812133003
Cr-Commit-Position: refs/heads/master@{#465700}
This commit is contained in:
wnwen
2017-04-19 12:22:24 -07:00
committed by Commit bot
parent e517828908
commit 03427bcc41
3 changed files with 114 additions and 58 deletions

@ -5,28 +5,34 @@
{% if variables is defined %}
{{ prefix }} {
manifest.srcFile "{{ variables.android_manifest }}"
{% if variables.java_dirs is defined %}
java.srcDirs = [
{% for path in variables.java_dirs %}
"{{ path }}",
{% endfor %}
]
{% if variables.java_excludes %}
{% endif %}
{% if variables.java_excludes is defined %}
java.filter.exclude(
{% for path in variables.java_excludes %}
"{{ path }}",
{% endfor %}
)
{% endif %}
{% if variables.jniLibs is defined %}
jniLibs.srcDirs = [
{% for path in variables.jni_libs %}
"{{ path }}",
{% endfor %}
]
{% endif %}
{% if variables.res_dirs is defined %}
res.srcDirs = [
{% for path in variables.res_dirs %}
"{{ path }}",
{% endfor %}
]
{% endif %}
}
{% endif %}
{% endmacro %}

@ -35,6 +35,9 @@ _SRCJARS_SUBDIR = 'extracted-srcjars'
_JNI_LIBS_SUBDIR = 'symlinked-libs'
_ARMEABI_SUBDIR = 'armeabi'
_RES_SUBDIR = 'extracted-res'
_GRADLE_BUILD_FILE = 'build.gradle'
# This needs to come first alphabetically among all modules.
_MODULE_ALL = '_all'
_DEFAULT_TARGETS = [
# TODO(agrieve): .build_config seem not quite right for this target
@ -245,6 +248,8 @@ class _ProjectContextGenerator(object):
self.use_gradle_process_resources = use_gradle_process_resources
self.jinja_processor = jinja_processor
self.split_projects = split_projects
self.processed_java_dirs = set()
self.processed_prebuilts = set()
def _GenJniLibs(self, root_entry):
libraries = []
@ -334,24 +339,23 @@ class _ProjectContextGenerator(object):
# things up at all.
variables = {}
java_dirs, excludes = self._GenJavaDirs(root_entry)
java_dirs.append(
os.path.join(self.EntryOutputDir(root_entry), _SRCJARS_SUBDIR))
self.processed_java_dirs.update(java_dirs)
java_dirs.sort()
variables['java_dirs'] = self._Relativize(root_entry, java_dirs)
variables['java_dirs'].append(_SRCJARS_SUBDIR)
variables['java_excludes'] = excludes
variables['jni_libs'] = self._Relativize(
root_entry, set(self._GenJniLibs(root_entry)))
variables['prebuilts'] = [
p for e in self._GetEntries(root_entry) for p in e.PrebuiltJars()]
variables['res_dirs'] = [
p for e in self._GetEntries(root_entry) for p in e.ResDirs()]
for entry in self._GetEntries(root_entry):
variables['prebuilts'] += entry.PrebuiltJars()
variables['res_dirs'] += entry.ResDirs()
variables['prebuilts'] = self._Relativize(
root_entry, set(variables['prebuilts']))
variables['res_dirs'] = self._Relativize(
root_entry, set(variables['res_dirs']))
variables['res_dirs'].append(_RES_SUBDIR)
prebuilts = set(
p for e in self._GetEntries(root_entry) for p in e.PrebuiltJars())
self.processed_prebuilts.update(prebuilts)
variables['prebuilts'] = self._Relativize(root_entry, prebuilts)
res_dirs = set(
p for e in self._GetEntries(root_entry) for p in e.ResDirs())
res_dirs.add(
os.path.join(self.EntryOutputDir(root_entry), _RES_SUBDIR))
variables['res_dirs'] = self._Relativize(root_entry, res_dirs)
android_manifest = root_entry.Gradle().get('android_manifest')
if not android_manifest:
android_manifest = self._GenCustomManifest(root_entry)
@ -482,15 +486,26 @@ def _GenerateLocalProperties(sdk_dir):
''])
def _GenerateGradleFile(entry, generator, build_vars, jinja_processor):
"""Returns the data for a project's build.gradle."""
deps_info = entry.DepsInfo()
gradle = entry.Gradle()
def _GenerateBaseVars(generator, build_vars, source_properties):
variables = {
'sourceSetName': 'main',
'depCompileName': 'compile',
}
variables['build_tools_version'] = source_properties['Pkg.Revision']
variables['compile_sdk_version'] = (
'android-%s' % build_vars['android_sdk_version'])
variables['use_gradle_process_resources'] = (
generator.use_gradle_process_resources)
return variables
def _GenerateGradleFile(entry, generator, build_vars, source_properties,
jinja_processor):
"""Returns the data for a project's build.gradle."""
deps_info = entry.DepsInfo()
gradle = entry.Gradle()
variables = _GenerateBaseVars(generator, build_vars, source_properties)
if deps_info['type'] == 'android_apk':
target_type = 'android_apk'
elif deps_info['type'] == 'java_library':
@ -513,14 +528,6 @@ def _GenerateGradleFile(entry, generator, build_vars, jinja_processor):
variables['target_name'] = os.path.splitext(deps_info['name'])[0]
variables['template_type'] = target_type
variables['use_gradle_process_resources'] = (
generator.use_gradle_process_resources)
source_properties = _ReadPropertiesFile(
_RebasePath(os.path.join(build_vars['android_sdk_build_tools'],
'source.properties')))
variables['build_tools_version'] = source_properties['Pkg.Revision']
variables['compile_sdk_version'] = (
'android-%s' % build_vars['android_sdk_version'])
variables['main'] = generator.Generate(entry)
bootclasspath = gradle.get('bootclasspath')
if bootclasspath:
@ -531,13 +538,38 @@ def _GenerateGradleFile(entry, generator, build_vars, jinja_processor):
entry.android_test_entry)
for key, value in variables['android_test'].iteritems():
if isinstance(value, list):
variables['android_test'][key] = list(
variables['android_test'][key] = sorted(
set(value) - set(variables['main'][key]))
return jinja_processor.Render(
_TemplatePath(target_type.split('_')[0]), variables)
def _GenerateModuleAll(gradle_output_dir, generator, build_vars,
source_properties, jinja_processor):
"""Returns the data for a pseudo build.gradle of all dirs.
See //docs/android_studio.md for more details."""
variables = _GenerateBaseVars(generator, build_vars, source_properties)
target_type = 'android_apk'
variables['target_name'] = _MODULE_ALL
variables['template_type'] = target_type
java_dirs = sorted(generator.processed_java_dirs)
prebuilts = sorted(generator.processed_prebuilts)
def Relativize(paths):
return _RebasePath(paths, os.path.join(gradle_output_dir, _MODULE_ALL))
variables['main'] = {
'android_manifest': Relativize(_DEFAULT_ANDROID_MANIFEST_PATH),
'java_dirs': Relativize(java_dirs),
'prebuilts': Relativize(prebuilts),
'java_excludes': ['**/*.java'],
}
data = jinja_processor.Render(
_TemplatePath(target_type.split('_')[0]), variables)
_WriteFile(
os.path.join(gradle_output_dir, _MODULE_ALL, _GRADLE_BUILD_FILE), data)
def _GenerateRootGradle(jinja_processor):
"""Returns the data for the root project's build.gradle."""
return jinja_processor.Render(_TemplatePath('root'))
@ -552,6 +584,10 @@ def _GenerateSettingsGradle(project_entries):
lines.append('rootProject.projectDir = settingsDir')
lines.append('')
lines.append('include ":{0}"'.format(_MODULE_ALL))
lines.append(
'project(":{0}").projectDir = new File(settingsDir, "{0}")'.format(
_MODULE_ALL))
for entry in project_entries:
# Example target: android_webview:android_webview_java__build_config
lines.append('include ":%s"' % entry.ProjectName())
@ -662,14 +698,17 @@ def main():
run_tests_helper.SetLogLevel(args.verbose_count)
# TODO(wnwen): Fix packaging so that gradle resources work in this case.
if args.use_gradle_process_resources:
assert args.split_projects, (
'Gradle resources does not yet work without --split-projects.')
if args.split_projects:
assert not args.use_gradle_process_resources, (
'Gradle resources does not work without --split-projects.')
_gradle_output_dir = os.path.abspath(
args.project_dir.replace('$CHROMIUM_OUTPUT_DIR', output_dir))
jinja_processor = jinja_template.JinjaProcessor(_FILE_DIR)
build_vars = _ReadPropertiesFile(os.path.join(output_dir, 'build_vars.txt'))
source_properties = _ReadPropertiesFile(
_RebasePath(os.path.join(build_vars['android_sdk_build_tools'],
'source.properties')))
generator = _ProjectContextGenerator(_gradle_output_dir, build_vars,
args.use_gradle_process_resources, jinja_processor, args.split_projects)
logging.warning('Creating project at: %s', generator.project_dir)
@ -696,7 +735,10 @@ def main():
# There are many unused libraries, so restrict to those that are actually used
# when using --all.
if args.all:
main_entries = [e for e in main_entries if e.GetType() == 'android_apk']
main_entries = [e for e in main_entries if (
e.GetType() == 'android_apk' or
e.GnTarget().endswith('_test_apk__apk') or
e.GnTarget().endswith('_junit_tests__java_binary'))]
if args.split_projects:
main_entries = _FindAllProjectEntries(main_entries)
@ -712,7 +754,8 @@ def main():
if entry.GetType() not in ('android_apk', 'java_library', 'java_binary'):
continue
data = _GenerateGradleFile(entry, generator, build_vars, jinja_processor)
data = _GenerateGradleFile(entry, generator, build_vars, source_properties,
jinja_processor)
if data:
project_entries.append(entry)
# Build all paths references by .gradle that exist within output_dir.
@ -724,9 +767,13 @@ def main():
(s, os.path.join(generator.EntryOutputDir(entry), _RES_SUBDIR))
for s in generator.AllResZips(entry))
_WriteFile(
os.path.join(generator.EntryOutputDir(entry), 'build.gradle'), data)
os.path.join(generator.EntryOutputDir(entry), _GRADLE_BUILD_FILE),
data)
_WriteFile(os.path.join(generator.project_dir, 'build.gradle'),
_GenerateModuleAll(_gradle_output_dir, generator, build_vars,
source_properties, jinja_processor)
_WriteFile(os.path.join(generator.project_dir, _GRADLE_BUILD_FILE),
_GenerateRootGradle(jinja_processor))
_WriteFile(os.path.join(generator.project_dir, 'settings.gradle'),

@ -17,18 +17,18 @@ This creates a project at `out/Debug/gradle`. To create elsewhere:
build/android/gradle/generate_gradle.py --output-directory out/My-Out-Dir --project-dir my-project
```
By default, only common targets are generated. To customize the list of targets
to generate projects for:
By default, common targets are generated. To add more targets to generate
projects for:
```shell
build/android/gradle/generate_gradle.py --target //chrome/android:chrome_public_apk --target //android_webview/test:android_webview_apk
build/android/gradle/generate_gradle.py --extra-target //chrome/android:chrome_public_apk
```
For those upgrading from Android Studio 2.2 to 2.3:
* Regenerate with `generate_gradle.py`.
* Use `gn clean` and `gn gen`
* Clean up in `//third_party/android_tools` with `git clean -ffd`.
* Restart Android Studio with File -> "Invalidate Caches / Restart".
* Remove project from android studio and regenerate with `generate_gradle.py`.
For first-time Android Studio users:
@ -50,24 +50,28 @@ You need to re-run `generate_gradle.py` whenever `BUILD.gn` files change.
* Help -> Find Action -> "Sync Project with Gradle Files"
* After `gn clean` you may need to restart Android Studio.
## How it Works
## How It Works
Android Studio integration works by generating `build.gradle` files based on GN
targets. Each `android_apk` and `android_library` target produces a separate
Gradle sub-project.
targets. Each valid target produces a separate Gradle sub-project.
Instrumentation tests are combined with their `apk_under_test`.
### Excluded files and .srcjars
### Excluded Files
Gradle supports source directories but not source files. However, some
directories in Chromium are split amonst multiple GN targets. To accommodate
this, the script detects such targets and creates exclude patterns to exclude
files not in the current target. You still see them when editing, but they are
excluded in gradle tasks.
***
Gradle supports source directories but not source files. However, files in
Chromium are used amongst multiple targets. To accommodate this, the script
detects such targets and creates exclude patterns to exclude files not in the
current target. The editor does not respect these exclude patterns, so a `_all`
pseudo module is added which includes directories from all targets. This allows
imports and refactorings to be searched across all targets.
### Extracting .srcjars
Most generated .java files in GN are stored as `.srcjars`. Android Studio does
not have support for them, and so the generator script builds and extracts them
all to `extracted-srcjars/` subdirectories for each target that contains them.
not support them, and so the generator script builds and extracts them all to
`extracted-srcjars/` subdirectories for each target that contains them. This is
the reason that the `_all` pseudo module may contain multiple copies of
generated files.
*** note
** TLDR:** Always re-generate project files when `.srcjars` change (this
@ -123,23 +127,22 @@ resources, native libraries, etc.
* Add the line `org.gradle.daemon=true` to `~/.gradle/gradle.properties`,
creating it if necessary.
## Status (as of April 4th, 2017)
## Status (as of April 19th, 2017)
### What works
* Tested with Android Studio v2.3.
* Java editing and gradle compile works.
* Android Studio v2.3.
* Java editing and gradle compile.
* Instrumentation tests included as androidTest.
* Symlinks to existing .so files in jniLibs (doesn't generate them).
* Editing resource xml files.
* Java debugging (see
[here](/docs/android_debugging_instructions.md#Android-Studio)).
* Import resolution and refactoring across all modules.
### What doesn't work (yet) ([crbug](https://bugs.chromium.org/p/chromium/issues/detail?id=620034))
* Proper file resolution and imports for overlapping modules.
* Make gradle aware of assets.
* Gradle being aware of assets.
* Layout editor.
* Add a mode in which gradle is responsible for generating `R.java`.
* Add support for native code editing.
* Make the "Make Project" button work correctly.