Improve Visual Studio Code Java setup
This CL introduces a new way to set up VS Code to edit Java code in Chromium. It makes it possible for the Java language server to successfully build .java files in a way that is (mostly) identical to the way the Chromium build system builds them, and without relying on a manually curated Eclipse classpath file. This makes it possible to use the full potential of the VS Code Java extension including full semantic analysis, real-time reporting of build errors, etc. Change-Id: I3a8e7260216a029936b9a684d64814da43dfb307 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4604730 Reviewed-by: Andrew Grieve <agrieve@chromium.org> Commit-Queue: Etienne Dechamps <edechamps@google.com> Auto-Submit: Etienne Dechamps <edechamps@google.com> Cr-Commit-Position: refs/heads/main@{#1156225}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
da61b62e98
commit
2d40c5554e
@ -4,3 +4,5 @@ mheikal@chromium.org
|
||||
pasko@chromium.org
|
||||
smaier@chromium.org
|
||||
wnwen@chromium.org
|
||||
|
||||
per-file generate_vscode_classpath.py=edechamps@google.com
|
||||
|
175
build/android/generate_vscode_classpath.py
Executable file
175
build/android/generate_vscode_classpath.py
Executable file
@ -0,0 +1,175 @@
|
||||
#!/usr/bin/env vpython3
|
||||
# Copyright 2023 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
"""Given a .build_config.json file, generates a .classpath file that can be
|
||||
used with the "Language Support for Java™ by Red Hat" Visual Studio Code
|
||||
extension. See //docs/vscode.md for details.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import xml.etree.ElementTree
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), 'gyp'))
|
||||
from util import build_utils
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir))
|
||||
import gn_helpers
|
||||
|
||||
|
||||
def _WithoutSuffix(string, suffix):
|
||||
if not string.endswith(suffix):
|
||||
raise ValueError(f'{string!r} does not end with {suffix!r}')
|
||||
return string[:-len(suffix)]
|
||||
|
||||
|
||||
def _GetJavaRoot(path):
|
||||
# The authoritative way to determine the Java root for a given source file is
|
||||
# to parse the source code and extract the package and class names, but let's
|
||||
# keep things simple and use some heuristics to try to guess the Java root
|
||||
# from the file path instead.
|
||||
while True:
|
||||
dirname, basename = os.path.split(path)
|
||||
if not basename:
|
||||
raise RuntimeError(f'Unable to determine the Java root for {path!r}')
|
||||
if basename in ('java', 'src'):
|
||||
return path
|
||||
if basename in ('javax', 'org', 'com'):
|
||||
return dirname
|
||||
path = dirname
|
||||
|
||||
|
||||
def _ProcessSourceFile(output_dir, source_file_path, source_dirs):
|
||||
source_file_path = os.path.normpath(os.path.join(output_dir,
|
||||
source_file_path))
|
||||
java_root = _GetJavaRoot(source_file_path)
|
||||
logging.debug('Extracted java root `%s` from source file path `%s`',
|
||||
java_root, source_file_path)
|
||||
source_dirs.add(java_root)
|
||||
|
||||
|
||||
def _ProcessSourcesFile(output_dir, sources_file_path, source_dirs):
|
||||
for source_file_path in build_utils.ReadSourcesList(
|
||||
os.path.join(output_dir, sources_file_path)):
|
||||
_ProcessSourceFile(output_dir, source_file_path, source_dirs)
|
||||
|
||||
|
||||
def _ProcessBuildConfigFile(output_dir, build_config_path, source_dirs, libs,
|
||||
already_processed_build_config_files,
|
||||
android_sdk_build_tools_version):
|
||||
if build_config_path in already_processed_build_config_files:
|
||||
return
|
||||
already_processed_build_config_files.add(build_config_path)
|
||||
|
||||
logging.info('Processing build config: %s', build_config_path)
|
||||
|
||||
with open(os.path.join(output_dir, build_config_path)) as build_config_file:
|
||||
build_config = json.load(build_config_file)
|
||||
|
||||
deps_info = build_config['deps_info']
|
||||
target_sources_file = deps_info.get('target_sources_file')
|
||||
if target_sources_file is not None:
|
||||
_ProcessSourcesFile(output_dir, target_sources_file, source_dirs)
|
||||
else:
|
||||
unprocessed_jar_path = deps_info.get('unprocessed_jar_path')
|
||||
if unprocessed_jar_path is not None:
|
||||
lib_path = os.path.normpath(os.path.join(output_dir,
|
||||
unprocessed_jar_path))
|
||||
logging.debug('Found lib `%s', lib_path)
|
||||
libs.add(lib_path)
|
||||
|
||||
source_dirs.add(
|
||||
os.path.join(output_dir,
|
||||
_WithoutSuffix(build_config_path, '.build_config.json'),
|
||||
'generated_java', 'input_srcjars'))
|
||||
|
||||
android = build_config.get('android')
|
||||
if android is not None:
|
||||
# This works around an issue where the language server complains about
|
||||
# `java.lang.invoke.LambdaMetafactory` not being found. The normal Android
|
||||
# build process is fine with this class being missing because d8 removes
|
||||
# references to LambdaMetafactory from the bytecode - see:
|
||||
# https://jakewharton.com/androids-java-8-support/#native-lambdas
|
||||
# When JDT builds the code, d8 doesn't run, so the references are still
|
||||
# there. Fortunately, the Android SDK provides a convenience JAR to fill
|
||||
# that gap in:
|
||||
# //third_party/android_sdk/public/build-tools/*/core-lambda-stubs.jar
|
||||
libs.add(
|
||||
os.path.normpath(
|
||||
os.path.join(
|
||||
output_dir,
|
||||
os.path.dirname(build_config['android']['sdk_jars'][0]),
|
||||
os.pardir, os.pardir, 'build-tools',
|
||||
android_sdk_build_tools_version, 'core-lambda-stubs.jar')))
|
||||
|
||||
for dep_config in deps_info['deps_configs']:
|
||||
_ProcessBuildConfigFile(output_dir, dep_config, source_dirs, libs,
|
||||
already_processed_build_config_files,
|
||||
android_sdk_build_tools_version)
|
||||
|
||||
|
||||
def _GenerateClasspathEntry(kind, path):
|
||||
classpathentry = xml.etree.ElementTree.Element('classpathentry')
|
||||
classpathentry.set('kind', kind)
|
||||
classpathentry.set('path', f'_/{path}')
|
||||
return classpathentry
|
||||
|
||||
|
||||
def _GenerateClasspathFile(source_dirs, libs):
|
||||
classpath = xml.etree.ElementTree.Element('classpath')
|
||||
for source_dir in source_dirs:
|
||||
classpath.append(_GenerateClasspathEntry('src', source_dir))
|
||||
for lib in libs:
|
||||
classpath.append(_GenerateClasspathEntry('lib', lib))
|
||||
|
||||
xml.etree.ElementTree.ElementTree(classpath).write(sys.stdout,
|
||||
encoding='unicode')
|
||||
|
||||
|
||||
def _ParseArguments(argv):
|
||||
parser = argparse.ArgumentParser(
|
||||
description=
|
||||
'Given Chromium Java build config files, dumps an Eclipse JDT classpath '
|
||||
'file to standard output that can be used with the "Language Support for '
|
||||
'Java™ by Red Hat" Visual Studio Code extension. See //docs/vscode.md '
|
||||
'for details.')
|
||||
parser.add_argument(
|
||||
'--output-dir',
|
||||
required=True,
|
||||
help='Relative path to the output directory, e.g. "out/Debug"')
|
||||
parser.add_argument(
|
||||
'--build-config',
|
||||
action='append',
|
||||
required=True,
|
||||
help='Path to the .build_config.json file to use as input, relative to '
|
||||
'`--output-dir`. May be repeated.')
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
def main(argv):
|
||||
build_utils.InitLogging('GENERATE_VSCODE_CLASSPATH_DEBUG')
|
||||
args = _ParseArguments(argv)
|
||||
output_dir = args.output_dir
|
||||
|
||||
build_vars = gn_helpers.ReadBuildVars(output_dir)
|
||||
|
||||
source_dirs = set()
|
||||
libs = set()
|
||||
already_processed_build_config_files = set()
|
||||
for build_config_path in args.build_config:
|
||||
_ProcessBuildConfigFile(output_dir, build_config_path, source_dirs, libs,
|
||||
already_processed_build_config_files,
|
||||
build_vars['android_sdk_build_tools_version'])
|
||||
|
||||
logging.info('Done processing %d build config files',
|
||||
len(already_processed_build_config_files))
|
||||
|
||||
_GenerateClasspathFile(source_dirs, libs)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv[1:]))
|
106
docs/vscode.md
106
docs/vscode.md
@ -226,65 +226,61 @@ Note: See also [Key Bindings for Visual Studio Code
|
||||
|
||||
### Java/Android Support
|
||||
|
||||
*Before anything*, add these to your settings.json.
|
||||
```
|
||||
// LightWeight is the language support, the feature we care about. The other
|
||||
// modes include build functionality with Maven and Gradle. They try to build
|
||||
// on their own and end up showing thousands of errors.
|
||||
"java.server.launchMode": "LightWeight",
|
||||
// Avoids overwriting the custom .classpath file (c.f. next section).
|
||||
"java.configuration.updateBuildConfiguration": "disabled",
|
||||
```
|
||||
Then install the "Language Support for Java" extension. If you installed it
|
||||
before setting the configs above, uninstall, delete the <project> folder (c.f.
|
||||
next section) and reinstall. You also don't need any of the remaining extensions
|
||||
in "Extension Pack for Java".
|
||||
Follow these steps to get full IDE support (outline, autocompletion, jump to
|
||||
definition including automatic decompilation of prebuilts, real-time reporting
|
||||
of compile errors/warnings, Javadocs, etc.) when editing `.java` files in
|
||||
Chromium:
|
||||
|
||||
#### Setting up code completion/reference finding/etc.
|
||||
1. **Add the following to your VS Code workspace `settings.json`:**
|
||||
* `"java.import.gradle.enabled": false`
|
||||
`"java.import.maven.enabled": false`
|
||||
This will prevent the language server from attempting to build *all*
|
||||
Gradle and Maven projects that can be found anywhere in the Chromium
|
||||
source tree, which typically results in hilarity.
|
||||
* `"java.jdt.ls.java.home": "<< ABSOLUTE PATH TO YOUR WORKING COPY OF CHROMIUM >>/src/third_party/jdk/current"`
|
||||
This one is optional but reduces the likelihood of problems by making sure
|
||||
the language server uses the same JDK as the Chromium build system (as
|
||||
opposed to some random JDK from your host system).
|
||||
2. **Install the
|
||||
[*Language Support for Java™ by Red Hat*](https://marketplace.visualstudio.com/items?itemName=redhat.java)
|
||||
extension.**
|
||||
You do not need any other extension.
|
||||
3. In the VS Code explorer (left-hand pane) **right-click on a folder (any folder)
|
||||
and click *Add Folder to Java Source Path***.
|
||||
This is just one way to force the extension to generate an internal build
|
||||
project for the language server. The specific folder doesn't matter because
|
||||
the source path configuration will be overwritten in step 6.
|
||||
4. **Wait for the *Successfully added '...' to the project src_...'s source
|
||||
path* pop-up notification to appear**.
|
||||
Note that this can take some time (1-2 minutes), during which VS Code seems
|
||||
to be idle.
|
||||
5. **Build your code** in the usual way (i.e. using gn and ninja commands).
|
||||
This will produce build config files that are necessary for the next step. It
|
||||
will also make autogenerated code visible to the language server.
|
||||
6. **Generate the `.classpath` file** for the internal build project by running
|
||||
`build/android/generate_vscode_classpath.py` with the appropriate arguments
|
||||
from the root of your VS Code workspace folder.
|
||||
For example, if your VS Code workspace is rooted in `src`, your build
|
||||
output directory is `out/Debug-x86` and your build target is
|
||||
`//components/cronet/android:cronet_javatests`, run:
|
||||
`build/android/generate_vscode_classpath.py --output-dir out/Debug-x86 --build-config gen/components/cronet/android/cronet_javatests.build_config.json > ~/.vscode*/data/User/workspaceStorage/*/redhat.java/jdt_ws/.metadata/.plugins/org.eclipse.core.resources/.projects/src_*/.classpath`
|
||||
7. **Reload** your VS Code window.
|
||||
8. **Open a Java source file then wait a couple of minutes** for the language
|
||||
server to build the project.
|
||||
9. **Done!** You should now have full Java language support for any `.java` file
|
||||
that is included in the build.
|
||||
|
||||
You'll need to generate a placeholder .classpath file and locate it. In order
|
||||
to generate it, right click on any Java source folder in the left panel and
|
||||
choose "Add folder to java source path". Its location will depend on whether
|
||||
you're doing local or remote development. Local path on linux will look
|
||||
something like:
|
||||
*** note
|
||||
**Warning:** do not attempt to change the extension's source path settings, as
|
||||
that risks overwriting the generated `.classpath` file.
|
||||
***
|
||||
|
||||
`~/.vscode/data/User/workspaceStorage/<hash>/redhat.java/jdt_ws/<project>/.classpath`
|
||||
#### Known issues
|
||||
|
||||
You might find multiple folders when looking for `<project>`. Choose anything except
|
||||
`jdt.ls-java-project`. If you only see `jdt.ls-java-project`, try using the
|
||||
"Add folder to java source path" option again.
|
||||
|
||||
If doing remote development, the file will be under `~/.vscode-server/` on your
|
||||
remote machine.
|
||||
|
||||
You'll need to replace all of the contents of that file with the contents of
|
||||
`tools/android/eclipse/.classpath` (external) or
|
||||
`clank/development/ide/eclipse/.classpath` (generated by gclient runhooks for
|
||||
Chrome developers), and then replace some paths as vscode interprets some paths
|
||||
differently from eclipse.
|
||||
* Replace: `kind="src" path="` with `kind="src" path="_/`
|
||||
* eg. `<classpathentry kind="src" path="_/android_webview/glue/java/src"/>`
|
||||
* Replace: `kind="lib" path="../src` with `kind="lib" path="_`
|
||||
* eg.
|
||||
`<classpathentry kind="lib" path="_/out/Debug/lib.java/base/base_java.jar"/>`
|
||||
* Remove all nested paths (or exclude them from their parents). At time of
|
||||
writing:
|
||||
* `third_party/android_protobuf/src/java/src/main/java`
|
||||
* `third_party/junit/src/src/main/java`
|
||||
|
||||
Also, make sure
|
||||
`export ANDROID_HOME=/usr/local/google/home/{your_ldap}/Android/Sdk` is in the
|
||||
remote machine's `~/.bashrc`.
|
||||
|
||||
Then restart vscode, open a Java file, and wait for a bit.
|
||||
|
||||
Debugging tips:
|
||||
* Right clicking on a folder in vscode and clicking "Add folder to java source
|
||||
path" will error if there are syntax problems with your classpath. (Don't use
|
||||
this actually add new paths to your classpath as it won't work correctly)
|
||||
* If there are no syntax errors, ensure the correct .classpath file is being
|
||||
used by seeing if the folder was actually added to the .classpath file you
|
||||
edited.
|
||||
* Errors related to `GEN_JNI` are caused by the language server (rightfully)
|
||||
getting confused about multiple definitions of the
|
||||
[autogenerated](/base/android/jni_generator/README.md) `GEN_JNI` class. This
|
||||
is a known quirk of the JNI generator.
|
||||
|
||||
## Setup For Chromium
|
||||
|
||||
|
Reference in New Issue
Block a user