1*8975f5c5SAndroid Build Coastguard Worker#!/usr/bin/env vpython3 2*8975f5c5SAndroid Build Coastguard Worker# Copyright 2023 The Chromium Authors 3*8975f5c5SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 4*8975f5c5SAndroid Build Coastguard Worker# found in the LICENSE file. 5*8975f5c5SAndroid Build Coastguard Worker"""Given a .build_config.json file, generates an Eclipse JDT project that can 6*8975f5c5SAndroid Build Coastguard Workerbe used with the "Language Support for Java™ by Red Hat" Visual Studio Code 7*8975f5c5SAndroid Build Coastguard Workerextension. See //docs/vscode.md for details. 8*8975f5c5SAndroid Build Coastguard Worker""" 9*8975f5c5SAndroid Build Coastguard Worker 10*8975f5c5SAndroid Build Coastguard Workerimport argparse 11*8975f5c5SAndroid Build Coastguard Workerimport logging 12*8975f5c5SAndroid Build Coastguard Workerimport json 13*8975f5c5SAndroid Build Coastguard Workerimport os 14*8975f5c5SAndroid Build Coastguard Workerimport sys 15*8975f5c5SAndroid Build Coastguard Workerimport xml.etree.ElementTree 16*8975f5c5SAndroid Build Coastguard Worker 17*8975f5c5SAndroid Build Coastguard Workersys.path.append(os.path.join(os.path.dirname(__file__), 'gyp')) 18*8975f5c5SAndroid Build Coastguard Workerfrom util import build_utils 19*8975f5c5SAndroid Build Coastguard Worker 20*8975f5c5SAndroid Build Coastguard Workersys.path.append(os.path.join(os.path.dirname(__file__), os.pardir)) 21*8975f5c5SAndroid Build Coastguard Workerimport gn_helpers 22*8975f5c5SAndroid Build Coastguard Worker 23*8975f5c5SAndroid Build Coastguard Worker 24*8975f5c5SAndroid Build Coastguard Workerdef _WithoutSuffix(string, suffix): 25*8975f5c5SAndroid Build Coastguard Worker if not string.endswith(suffix): 26*8975f5c5SAndroid Build Coastguard Worker raise ValueError(f'{string!r} does not end with {suffix!r}') 27*8975f5c5SAndroid Build Coastguard Worker return string[:-len(suffix)] 28*8975f5c5SAndroid Build Coastguard Worker 29*8975f5c5SAndroid Build Coastguard Worker 30*8975f5c5SAndroid Build Coastguard Workerdef _GetJavaRoot(path): 31*8975f5c5SAndroid Build Coastguard Worker # The authoritative way to determine the Java root for a given source file is 32*8975f5c5SAndroid Build Coastguard Worker # to parse the source code and extract the package and class names, but let's 33*8975f5c5SAndroid Build Coastguard Worker # keep things simple and use some heuristics to try to guess the Java root 34*8975f5c5SAndroid Build Coastguard Worker # from the file path instead. 35*8975f5c5SAndroid Build Coastguard Worker while True: 36*8975f5c5SAndroid Build Coastguard Worker dirname, basename = os.path.split(path) 37*8975f5c5SAndroid Build Coastguard Worker if not basename: 38*8975f5c5SAndroid Build Coastguard Worker raise RuntimeError(f'Unable to determine the Java root for {path!r}') 39*8975f5c5SAndroid Build Coastguard Worker if basename in ('java', 'src'): 40*8975f5c5SAndroid Build Coastguard Worker return path 41*8975f5c5SAndroid Build Coastguard Worker if basename in ('javax', 'org', 'com'): 42*8975f5c5SAndroid Build Coastguard Worker return dirname 43*8975f5c5SAndroid Build Coastguard Worker path = dirname 44*8975f5c5SAndroid Build Coastguard Worker 45*8975f5c5SAndroid Build Coastguard Worker 46*8975f5c5SAndroid Build Coastguard Workerdef _ProcessSourceFile(output_dir, source_file_path, source_dirs): 47*8975f5c5SAndroid Build Coastguard Worker source_file_path = os.path.normpath(os.path.join(output_dir, 48*8975f5c5SAndroid Build Coastguard Worker source_file_path)) 49*8975f5c5SAndroid Build Coastguard Worker java_root = _GetJavaRoot(source_file_path) 50*8975f5c5SAndroid Build Coastguard Worker logging.debug('Extracted java root `%s` from source file path `%s`', 51*8975f5c5SAndroid Build Coastguard Worker java_root, source_file_path) 52*8975f5c5SAndroid Build Coastguard Worker source_dirs.add(java_root) 53*8975f5c5SAndroid Build Coastguard Worker 54*8975f5c5SAndroid Build Coastguard Worker 55*8975f5c5SAndroid Build Coastguard Workerdef _ProcessSourcesFile(output_dir, sources_file_path, source_dirs): 56*8975f5c5SAndroid Build Coastguard Worker for source_file_path in build_utils.ReadSourcesList( 57*8975f5c5SAndroid Build Coastguard Worker os.path.join(output_dir, sources_file_path)): 58*8975f5c5SAndroid Build Coastguard Worker _ProcessSourceFile(output_dir, source_file_path, source_dirs) 59*8975f5c5SAndroid Build Coastguard Worker 60*8975f5c5SAndroid Build Coastguard Worker 61*8975f5c5SAndroid Build Coastguard Workerdef _ProcessBuildConfigFile(output_dir, build_config_path, source_dirs, libs, 62*8975f5c5SAndroid Build Coastguard Worker already_processed_build_config_files, 63*8975f5c5SAndroid Build Coastguard Worker android_sdk_build_tools_version): 64*8975f5c5SAndroid Build Coastguard Worker if build_config_path in already_processed_build_config_files: 65*8975f5c5SAndroid Build Coastguard Worker return 66*8975f5c5SAndroid Build Coastguard Worker already_processed_build_config_files.add(build_config_path) 67*8975f5c5SAndroid Build Coastguard Worker 68*8975f5c5SAndroid Build Coastguard Worker logging.info('Processing build config: %s', build_config_path) 69*8975f5c5SAndroid Build Coastguard Worker 70*8975f5c5SAndroid Build Coastguard Worker with open(os.path.join(output_dir, build_config_path)) as build_config_file: 71*8975f5c5SAndroid Build Coastguard Worker build_config = json.load(build_config_file) 72*8975f5c5SAndroid Build Coastguard Worker 73*8975f5c5SAndroid Build Coastguard Worker deps_info = build_config['deps_info'] 74*8975f5c5SAndroid Build Coastguard Worker target_sources_file = deps_info.get('target_sources_file') 75*8975f5c5SAndroid Build Coastguard Worker if target_sources_file is not None: 76*8975f5c5SAndroid Build Coastguard Worker _ProcessSourcesFile(output_dir, target_sources_file, source_dirs) 77*8975f5c5SAndroid Build Coastguard Worker else: 78*8975f5c5SAndroid Build Coastguard Worker unprocessed_jar_path = deps_info.get('unprocessed_jar_path') 79*8975f5c5SAndroid Build Coastguard Worker if unprocessed_jar_path is not None: 80*8975f5c5SAndroid Build Coastguard Worker lib_path = os.path.normpath(os.path.join(output_dir, 81*8975f5c5SAndroid Build Coastguard Worker unprocessed_jar_path)) 82*8975f5c5SAndroid Build Coastguard Worker logging.debug('Found lib `%s', lib_path) 83*8975f5c5SAndroid Build Coastguard Worker libs.add(lib_path) 84*8975f5c5SAndroid Build Coastguard Worker 85*8975f5c5SAndroid Build Coastguard Worker input_srcjars = os.path.join(output_dir, 86*8975f5c5SAndroid Build Coastguard Worker _WithoutSuffix(build_config_path, '.build_config.json'), 87*8975f5c5SAndroid Build Coastguard Worker 'generated_java', 'input_srcjars') 88*8975f5c5SAndroid Build Coastguard Worker if os.path.exists(input_srcjars): 89*8975f5c5SAndroid Build Coastguard Worker source_dirs.add(input_srcjars) 90*8975f5c5SAndroid Build Coastguard Worker 91*8975f5c5SAndroid Build Coastguard Worker android = build_config.get('android') 92*8975f5c5SAndroid Build Coastguard Worker if android is not None: 93*8975f5c5SAndroid Build Coastguard Worker # This works around an issue where the language server complains about 94*8975f5c5SAndroid Build Coastguard Worker # `java.lang.invoke.LambdaMetafactory` not being found. The normal Android 95*8975f5c5SAndroid Build Coastguard Worker # build process is fine with this class being missing because d8 removes 96*8975f5c5SAndroid Build Coastguard Worker # references to LambdaMetafactory from the bytecode - see: 97*8975f5c5SAndroid Build Coastguard Worker # https://jakewharton.com/androids-java-8-support/#native-lambdas 98*8975f5c5SAndroid Build Coastguard Worker # When JDT builds the code, d8 doesn't run, so the references are still 99*8975f5c5SAndroid Build Coastguard Worker # there. Fortunately, the Android SDK provides a convenience JAR to fill 100*8975f5c5SAndroid Build Coastguard Worker # that gap in: 101*8975f5c5SAndroid Build Coastguard Worker # //third_party/android_sdk/public/build-tools/*/core-lambda-stubs.jar 102*8975f5c5SAndroid Build Coastguard Worker libs.add( 103*8975f5c5SAndroid Build Coastguard Worker os.path.normpath( 104*8975f5c5SAndroid Build Coastguard Worker os.path.join( 105*8975f5c5SAndroid Build Coastguard Worker output_dir, 106*8975f5c5SAndroid Build Coastguard Worker os.path.dirname(build_config['android']['sdk_jars'][0]), 107*8975f5c5SAndroid Build Coastguard Worker os.pardir, os.pardir, 'build-tools', 108*8975f5c5SAndroid Build Coastguard Worker android_sdk_build_tools_version, 'core-lambda-stubs.jar'))) 109*8975f5c5SAndroid Build Coastguard Worker 110*8975f5c5SAndroid Build Coastguard Worker for dep_config in deps_info['deps_configs']: 111*8975f5c5SAndroid Build Coastguard Worker _ProcessBuildConfigFile(output_dir, dep_config, source_dirs, libs, 112*8975f5c5SAndroid Build Coastguard Worker already_processed_build_config_files, 113*8975f5c5SAndroid Build Coastguard Worker android_sdk_build_tools_version) 114*8975f5c5SAndroid Build Coastguard Worker 115*8975f5c5SAndroid Build Coastguard Worker 116*8975f5c5SAndroid Build Coastguard Workerdef _GenerateClasspathEntry(kind, path): 117*8975f5c5SAndroid Build Coastguard Worker classpathentry = xml.etree.ElementTree.Element('classpathentry') 118*8975f5c5SAndroid Build Coastguard Worker classpathentry.set('kind', kind) 119*8975f5c5SAndroid Build Coastguard Worker classpathentry.set('path', path) 120*8975f5c5SAndroid Build Coastguard Worker return classpathentry 121*8975f5c5SAndroid Build Coastguard Worker 122*8975f5c5SAndroid Build Coastguard Worker 123*8975f5c5SAndroid Build Coastguard Workerdef _GenerateProject(source_dirs, libs, output_dir): 124*8975f5c5SAndroid Build Coastguard Worker classpath = xml.etree.ElementTree.Element('classpath') 125*8975f5c5SAndroid Build Coastguard Worker for source_dir in sorted(source_dirs): 126*8975f5c5SAndroid Build Coastguard Worker classpath.append(_GenerateClasspathEntry('src', source_dir)) 127*8975f5c5SAndroid Build Coastguard Worker for lib in sorted(libs): 128*8975f5c5SAndroid Build Coastguard Worker classpath.append(_GenerateClasspathEntry('lib', lib)) 129*8975f5c5SAndroid Build Coastguard Worker classpath.append( 130*8975f5c5SAndroid Build Coastguard Worker _GenerateClasspathEntry('output', os.path.join(output_dir, 'jdt_output'))) 131*8975f5c5SAndroid Build Coastguard Worker 132*8975f5c5SAndroid Build Coastguard Worker xml.etree.ElementTree.ElementTree(classpath).write( 133*8975f5c5SAndroid Build Coastguard Worker '.classpath', encoding='unicode') 134*8975f5c5SAndroid Build Coastguard Worker print('Generated .classpath', file=sys.stderr) 135*8975f5c5SAndroid Build Coastguard Worker 136*8975f5c5SAndroid Build Coastguard Worker with open('.project', 'w') as f: 137*8975f5c5SAndroid Build Coastguard Worker f.write("""<?xml version="1.0" encoding="UTF-8"?> 138*8975f5c5SAndroid Build Coastguard Worker<projectDescription> 139*8975f5c5SAndroid Build Coastguard Worker <name>chromium</name> 140*8975f5c5SAndroid Build Coastguard Worker <buildSpec> 141*8975f5c5SAndroid Build Coastguard Worker <buildCommand> 142*8975f5c5SAndroid Build Coastguard Worker <name>org.eclipse.jdt.core.javabuilder</name> 143*8975f5c5SAndroid Build Coastguard Worker <arguments /> 144*8975f5c5SAndroid Build Coastguard Worker </buildCommand> 145*8975f5c5SAndroid Build Coastguard Worker </buildSpec> 146*8975f5c5SAndroid Build Coastguard Worker <natures><nature>org.eclipse.jdt.core.javanature</nature></natures> 147*8975f5c5SAndroid Build Coastguard Worker</projectDescription> 148*8975f5c5SAndroid Build Coastguard Worker""") 149*8975f5c5SAndroid Build Coastguard Worker print('Generated .project', file=sys.stderr) 150*8975f5c5SAndroid Build Coastguard Worker 151*8975f5c5SAndroid Build Coastguard Worker # Tell the Eclipse compiler not to use java.lang.invoke.StringConcatFactory 152*8975f5c5SAndroid Build Coastguard Worker # in the generated bytecodes as the class is unavailable in Android. 153*8975f5c5SAndroid Build Coastguard Worker os.makedirs('.settings', exist_ok=True) 154*8975f5c5SAndroid Build Coastguard Worker with open('.settings/org.eclipse.jdt.core.prefs', 'w') as f: 155*8975f5c5SAndroid Build Coastguard Worker f.write("""eclipse.preferences.version=1 156*8975f5c5SAndroid Build Coastguard Workerorg.eclipse.jdt.core.compiler.codegen.useStringConcatFactory=disabled 157*8975f5c5SAndroid Build Coastguard Worker""") 158*8975f5c5SAndroid Build Coastguard Worker print('Generated .settings', file=sys.stderr) 159*8975f5c5SAndroid Build Coastguard Worker 160*8975f5c5SAndroid Build Coastguard Worker 161*8975f5c5SAndroid Build Coastguard Workerdef _ParseArguments(argv): 162*8975f5c5SAndroid Build Coastguard Worker parser = argparse.ArgumentParser( 163*8975f5c5SAndroid Build Coastguard Worker description= 164*8975f5c5SAndroid Build Coastguard Worker 'Given Chromium Java build config files, generates an Eclipse JDT ' 165*8975f5c5SAndroid Build Coastguard Worker 'project that can be used with the "Language Support for Java™ by ' 166*8975f5c5SAndroid Build Coastguard Worker 'Red Hat" Visual Studio Code extension. See //docs/vscode.md ' 167*8975f5c5SAndroid Build Coastguard Worker 'for details.') 168*8975f5c5SAndroid Build Coastguard Worker parser.add_argument( 169*8975f5c5SAndroid Build Coastguard Worker '--output-dir', 170*8975f5c5SAndroid Build Coastguard Worker required=True, 171*8975f5c5SAndroid Build Coastguard Worker help='Relative path to the output directory, e.g. "out/Debug"') 172*8975f5c5SAndroid Build Coastguard Worker parser.add_argument( 173*8975f5c5SAndroid Build Coastguard Worker '--build-config', 174*8975f5c5SAndroid Build Coastguard Worker action='append', 175*8975f5c5SAndroid Build Coastguard Worker required=True, 176*8975f5c5SAndroid Build Coastguard Worker help='Path to the .build_config.json file to use as input, relative to ' 177*8975f5c5SAndroid Build Coastguard Worker '`--output-dir`. May be repeated.') 178*8975f5c5SAndroid Build Coastguard Worker return parser.parse_args(argv) 179*8975f5c5SAndroid Build Coastguard Worker 180*8975f5c5SAndroid Build Coastguard Worker 181*8975f5c5SAndroid Build Coastguard Workerdef main(argv): 182*8975f5c5SAndroid Build Coastguard Worker build_utils.InitLogging('GENERATE_VSCODE_CLASSPATH_DEBUG') 183*8975f5c5SAndroid Build Coastguard Worker 184*8975f5c5SAndroid Build Coastguard Worker assert os.path.exists('.gn'), 'This script must be run from the src directory' 185*8975f5c5SAndroid Build Coastguard Worker 186*8975f5c5SAndroid Build Coastguard Worker args = _ParseArguments(argv) 187*8975f5c5SAndroid Build Coastguard Worker output_dir = args.output_dir 188*8975f5c5SAndroid Build Coastguard Worker 189*8975f5c5SAndroid Build Coastguard Worker build_vars = gn_helpers.ReadBuildVars(output_dir) 190*8975f5c5SAndroid Build Coastguard Worker 191*8975f5c5SAndroid Build Coastguard Worker source_dirs = set() 192*8975f5c5SAndroid Build Coastguard Worker libs = set() 193*8975f5c5SAndroid Build Coastguard Worker already_processed_build_config_files = set() 194*8975f5c5SAndroid Build Coastguard Worker for build_config_path in args.build_config: 195*8975f5c5SAndroid Build Coastguard Worker _ProcessBuildConfigFile(output_dir, build_config_path, source_dirs, libs, 196*8975f5c5SAndroid Build Coastguard Worker already_processed_build_config_files, 197*8975f5c5SAndroid Build Coastguard Worker build_vars['android_sdk_build_tools_version']) 198*8975f5c5SAndroid Build Coastguard Worker 199*8975f5c5SAndroid Build Coastguard Worker logging.info('Done processing %d build config files', 200*8975f5c5SAndroid Build Coastguard Worker len(already_processed_build_config_files)) 201*8975f5c5SAndroid Build Coastguard Worker 202*8975f5c5SAndroid Build Coastguard Worker _GenerateProject(source_dirs, libs, output_dir) 203*8975f5c5SAndroid Build Coastguard Worker 204*8975f5c5SAndroid Build Coastguard Worker 205*8975f5c5SAndroid Build Coastguard Workerif __name__ == '__main__': 206*8975f5c5SAndroid Build Coastguard Worker sys.exit(main(sys.argv[1:])) 207