| #!/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:])) |