tor-browser

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

jar_utils.py (3501B)


      1 # Copyright 2023 The Chromium Authors
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 """Methods to run tools over jars and cache their output."""
      5 
      6 import logging
      7 import pathlib
      8 import subprocess
      9 import zipfile
     10 from typing import List, Optional, Union
     11 
     12 _SRC_PATH = pathlib.Path(__file__).resolve().parents[4]
     13 _JDEPS_PATH = _SRC_PATH / 'third_party/jdk/current/bin/jdeps'
     14 
     15 _IGNORED_JAR_PATHS = [
     16    # This matches org_ow2_asm_asm_commons and org_ow2_asm_asm_analysis, both of
     17    # which fail jdeps (not sure why), see: https://crbug.com/348423879
     18    'cipd/libs/org_ow2_asm_asm',
     19 ]
     20 
     21 def _should_ignore(jar_path: pathlib.Path) -> bool:
     22  for ignored_jar_path in _IGNORED_JAR_PATHS:
     23    if ignored_jar_path in str(jar_path):
     24      return True
     25  return False
     26 
     27 
     28 def run_jdeps(filepath: pathlib.Path,
     29              *,
     30              jdeps_path: pathlib.Path = _JDEPS_PATH,
     31              verbose: bool = False) -> Optional[str]:
     32  """Runs jdeps on the given filepath and returns the output."""
     33  if not filepath.exists() or _should_ignore(filepath):
     34    # Some __compile_java targets do not generate a .jar file, skipping these
     35    # does not affect correctness.
     36    return None
     37 
     38  cmd = [
     39      str(jdeps_path),
     40      '-verbose:class',
     41      '-filter:none',  # Necessary to include intra-package deps.
     42      '--multi-release',  # Some jars support multiple JDK releases.
     43      'base',
     44      str(filepath),
     45  ]
     46 
     47  if verbose:
     48    logging.debug('Starting %s', filepath)
     49  try:
     50    return subprocess.run(
     51        cmd,
     52        check=True,
     53        text=True,
     54        capture_output=True,
     55    ).stdout
     56  except subprocess.CalledProcessError as e:
     57    # Pack all the information into the error message since that is the last
     58    # thing visible in the output.
     59    raise RuntimeError(f'\nFilepath:\n{filepath}\ncmd:\n{" ".join(cmd)}\n'
     60                       f'stdout:\n{e.stdout}\nstderr:{e.stderr}\n') from e
     61  finally:
     62    if verbose:
     63      logging.debug('Finished %s', filepath)
     64 
     65 
     66 def extract_full_class_names_from_jar(
     67    jar_path: Union[str, pathlib.Path]) -> List[str]:
     68  """Returns set of fully qualified class names in passed-in jar."""
     69  out = set()
     70  with zipfile.ZipFile(jar_path) as z:
     71    for zip_entry_name in z.namelist():
     72      if not zip_entry_name.endswith('.class'):
     73        continue
     74      # Remove .class suffix
     75      full_java_class = zip_entry_name[:-6]
     76 
     77      # Remove inner class names after the first $.
     78      full_java_class = full_java_class.replace('/', '.')
     79      dollar_index = full_java_class.find('$')
     80      if dollar_index >= 0:
     81        full_java_class = full_java_class[0:dollar_index]
     82 
     83      out.add(full_java_class)
     84  return sorted(out)
     85 
     86 
     87 def parse_full_java_class(source_path: pathlib.Path) -> str:
     88  """Guess the fully qualified class name from the path to the source file."""
     89  if source_path.suffix not in ('.java', '.kt'):
     90    logging.warning('"%s" does not end in .java or .kt.', source_path)
     91    return ''
     92 
     93  directory_path = source_path.parent
     94  package_list_reversed = []
     95  for part in reversed(directory_path.parts):
     96    if part == 'java':
     97      break
     98    package_list_reversed.append(part)
     99    if part in ('com', 'org'):
    100      break
    101  else:
    102    logging.debug(
    103        'File %s not in a subdir of "org" or "com", cannot detect '
    104        'package heuristically.', source_path)
    105    return ''
    106 
    107  package = '.'.join(reversed(package_list_reversed))
    108  class_name = source_path.stem
    109  return f'{package}.{class_name}'