xref: /aosp_15_r20/external/angle/build/android/generate_vscode_project.py (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
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