tor-browser

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

run_build_script.py (7582B)


      1 #!/usr/bin/env vpython3
      2 
      3 # Copyright 2021 The Chromium Authors
      4 # Use of this source code is governed by a BSD-style license that can be
      5 # found in the LICENSE file.
      6 
      7 # This is a wrapper script which runs a Cargo build.rs build script
      8 # executable in a Cargo-like environment. Build scripts can do arbitrary
      9 # things and we can't support everything. Moreover, we do not WANT
     10 # to support everything because that means the build is not deterministic.
     11 # Code review processes must be applied to ensure that the build script
     12 # depends upon only these inputs:
     13 #
     14 # * The environment variables set by Cargo here:
     15 #   https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts
     16 # * Output from rustc commands, e.g. to figure out the Rust version.
     17 #
     18 # Similarly, the only allowable output from such a build script
     19 # is currently:
     20 #
     21 # * Generated .rs files
     22 # * cargo:rustc-cfg output.
     23 #
     24 # That's it. We don't even support the other standard cargo:rustc-
     25 # output messages.
     26 
     27 import argparse
     28 import io
     29 import os
     30 import platform
     31 import re
     32 import subprocess
     33 import sys
     34 import tempfile
     35 
     36 # Set up path to be able to import action_helpers
     37 sys.path.append(
     38    os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir,
     39                 os.pardir, 'build'))
     40 import action_helpers
     41 
     42 
     43 RUSTC_VERSION_LINE = re.compile(r"(\w+): (.*)")
     44 
     45 
     46 def rustc_name():
     47  if platform.system() == 'Windows':
     48    return "rustc.exe"
     49  else:
     50    return "rustc"
     51 
     52 
     53 def host_triple(rustc_path):
     54  """ Works out the host rustc target. """
     55  args = [rustc_path, "-vV"]
     56  known_vars = dict()
     57  proc = subprocess.Popen(args, stdout=subprocess.PIPE)
     58  for line in io.TextIOWrapper(proc.stdout, encoding="utf-8"):
     59    m = RUSTC_VERSION_LINE.match(line.rstrip())
     60    if m:
     61      known_vars[m.group(1)] = m.group(2)
     62  return known_vars["host"]
     63 
     64 
     65 def set_cargo_cfg_target_env_variables(rustc_path, env):
     66  """ Sets CARGO_CFG_TARGET_... based on output from rustc. """
     67  target_triple = env["TARGET"]
     68  assert target_triple
     69 
     70  # TODO(lukasza): Check if command-line flags other `--target` may affect the
     71  # output of `--print-cfg`.  If so, then consider also passing extra `args`
     72  # (derived from `rustflags` maybe?).
     73  args = [rustc_path, "--print=cfg", f"--target={target_triple}"]
     74 
     75  proc = subprocess.Popen(args, stdout=subprocess.PIPE)
     76  for line in io.TextIOWrapper(proc.stdout, encoding="utf-8"):
     77    line = line.strip()
     78    if "=" not in line: continue
     79    key, value = line.split("=")
     80    if key.startswith("target_"):
     81      key = "CARGO_CFG_" + key.upper()
     82      value = value.strip('"')
     83      if key in env:
     84        env[key] = env[key] + f",{value}"
     85      else:
     86        env[key] = value
     87 
     88 
     89 # Before 1.77, the format was `cargo:rustc-cfg=`. As of 1.77 the format is now
     90 # `cargo::rustc-cfg=`.
     91 RUSTC_CFG_LINE = re.compile("cargo::?rustc-cfg=(.*)")
     92 
     93 
     94 def main():
     95  parser = argparse.ArgumentParser(description='Run Rust build script.')
     96  parser.add_argument('--build-script',
     97                      required=True,
     98                      help='build script to run')
     99  parser.add_argument('--output',
    100                      required=True,
    101                      help='where to write output rustc flags')
    102  parser.add_argument('--target', help='rust target triple')
    103  parser.add_argument('--target-abi', help='rust target_abi')
    104  parser.add_argument('--features', help='features', nargs='+')
    105  parser.add_argument('--env', help='environment variable', nargs='+')
    106  parser.add_argument('--rustflags',
    107                      help=('path to a file of newline-separated command line '
    108                            'flags for rustc'))
    109  parser.add_argument('--rust-prefix', required=True, help='rust path prefix')
    110  parser.add_argument('--generated-files', nargs='+', help='any generated file')
    111  parser.add_argument('--out-dir', required=True, help='target out dir')
    112  parser.add_argument('--src-dir', required=True, help='target source dir')
    113 
    114  args = parser.parse_args()
    115 
    116  rustc_path = os.path.join(args.rust_prefix, rustc_name())
    117 
    118  # We give the build script an OUT_DIR of a temporary directory,
    119  # and copy out only any files which gn directives say that it
    120  # should generate. Mostly this is to ensure we can atomically
    121  # create those files, but it also serves to avoid side-effects
    122  # from the build script.
    123  # In the future, we could consider isolating this build script
    124  # into a chroot jail or similar on some platforms, but ultimately
    125  # we are always going to be reliant on code review to ensure the
    126  # build script is deterministic and trustworthy, so this would
    127  # really just be a backup to humans.
    128  with tempfile.TemporaryDirectory() as tempdir:
    129    env = {}  # try to avoid build scripts depending on other things
    130    env["RUSTC"] = os.path.abspath(rustc_path)
    131    env["OUT_DIR"] = tempdir
    132    env["CARGO_MANIFEST_DIR"] = os.path.abspath(args.src_dir)
    133    env["HOST"] = host_triple(rustc_path)
    134    if args.target is None:
    135      env["TARGET"] = env["HOST"]
    136    else:
    137      env["TARGET"] = args.target
    138    set_cargo_cfg_target_env_variables(rustc_path, env)
    139    if args.features:
    140      for f in args.features:
    141        feature_name = f.upper().replace("-", "_")
    142        env["CARGO_FEATURE_%s" % feature_name] = "1"
    143    if args.rustflags:
    144      with open(args.rustflags) as flags:
    145        for flag in flags:
    146          if "-Copt-level" in flag:
    147            (_, opt) = flag.split("=")
    148            env["OPT_LEVEL"] = opt.rstrip()
    149        flags.seek(0)
    150        env["CARGO_ENCODED_RUSTFLAGS"] = '\x1f'.join(flags.readlines())
    151    if args.env:
    152      for e in args.env:
    153        (k, v) = e.split("=")
    154        env[k] = v
    155    if "OPT_LEVEL" not in env:
    156      env["OPT_LEVEL"] = "0"
    157 
    158    # Pass through a couple which are useful for diagnostics
    159    if os.environ.get("RUST_BACKTRACE"):
    160      env["RUST_BACKTRACE"] = os.environ.get("RUST_BACKTRACE")
    161    if os.environ.get("RUST_LOG"):
    162      env["RUST_LOG"] = os.environ.get("RUST_LOG")
    163 
    164    # In the future we should, set all the variables listed here:
    165    # https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts
    166 
    167    proc = subprocess.run([os.path.abspath(args.build_script)],
    168                          env=env,
    169                          cwd=args.src_dir,
    170                          encoding='utf8',
    171                          stdout=subprocess.PIPE,
    172                          stderr=subprocess.PIPE)
    173 
    174    if proc.stderr.rstrip():
    175      print(proc.stderr.rstrip(), file=sys.stderr)
    176    proc.check_returncode()
    177 
    178    flags = ""
    179    for line in proc.stdout.split("\n"):
    180      m = RUSTC_CFG_LINE.match(line.rstrip())
    181      if m:
    182        flags = "%s--cfg\n%s\n" % (flags, m.group(1))
    183 
    184    # AtomicOutput will ensure we only write to the file on disk if what we
    185    # give to write() is different than what's currently on disk.
    186    with action_helpers.atomic_output(args.output) as output:
    187      output.write(flags.encode("utf-8"))
    188 
    189    # Copy any generated code out of the temporary directory,
    190    # atomically.
    191    if args.generated_files:
    192      for generated_file in args.generated_files:
    193        in_path = os.path.join(tempdir, generated_file)
    194        out_path = os.path.join(args.out_dir, generated_file)
    195        out_dir = os.path.dirname(out_path)
    196        if not os.path.exists(out_dir):
    197          os.makedirs(out_dir)
    198        with open(in_path, 'rb') as input:
    199          with action_helpers.atomic_output(out_path) as output:
    200            content = input.read()
    201            output.write(content)
    202 
    203 
    204 if __name__ == '__main__':
    205  sys.exit(main())