tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

generate_vscode_project.py (7688B)


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