| #!/usr/bin/env python3 |
| # Copyright 2024 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| '''Run cargo from the chromium Rust toolchain. |
| |
| Arguments are passed through to cargo. |
| |
| Should be run from the checkout root (i.e. as `tools/crates/run_cargo.py ...`) |
| ''' |
| |
| import argparse |
| import os |
| import platform |
| import subprocess |
| import sys |
| import pathlib |
| |
| DEFAULT_SYSROOT = pathlib.Path(__file__).parents[2].joinpath( |
| 'third_party', 'rust-toolchain') |
| |
| # Determine the cargo executable name based on whether `subprocess` thinks |
| # we're on a Windows platform or not, which is more accurate than checking it |
| # ourselves. See https://bugs.python.org/issue8110 for more details. |
| _IS_WIN = 'STARTUPINFO' in subprocess.__all__ |
| _CARGO_EXE = 'cargo.exe' if _IS_WIN else 'cargo' |
| |
| |
| # On windows, we need to point cargo and rustc to our hermetic windows |
| # toolchain, rather than assuming the windows tools are installed elsewhere |
| # on the system. |
| # This returns a string of flags separated by the 0x1f character, for use in |
| # the CARGO_ENCODED_RUSTFLAGS environment variable |
| def _GetWindowsToolchainFlags(): |
| # Get VS toolchain helpers from //build/vs_toolchain.py |
| sys.path.append( |
| os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', |
| 'build')) |
| |
| from vs_toolchain import FindVCComponentRoot, SetEnvironmentAndGetSDKDir, SDK_VERSION |
| |
| # If DEPOT_TOOLS_WIN_TOOLCHAINset to 0, the user has explicitly requested |
| # that we use their toolchain installation instead of the hermetic one |
| if (not _IS_WIN or os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '1') == '0'): |
| return "" |
| |
| # The vs_toolchain function calls mess with the environment, so save a |
| # backup to be restored after we're done. |
| environ_backup = dict(os.environ) |
| |
| # Explicitly point to the linker executable |
| |
| # The paths for other architectures aren't clear, we can figure |
| # them out if we need to. |
| if (sys.maxsize <= 2**32 or sys.maxsize > 2**64 |
| or 'arm' in platform.machine().lower()): |
| print("WARNING: run_cargo.py: non-x64 Windows host is not supported.\n", |
| "This may fail in unexpected ways.") |
| |
| short_arch_path = 'x64' |
| bin_arch_path = os.path.join('Hostx64', 'x64') |
| |
| linker_path = os.path.join(FindVCComponentRoot('Tools'), 'bin', |
| bin_arch_path, 'link.exe') |
| include_paths = [ |
| os.path.join(FindVCComponentRoot('Tools'), 'lib', short_arch_path), |
| os.path.join(SetEnvironmentAndGetSDKDir(), 'Lib', SDK_VERSION, 'um', |
| short_arch_path), |
| os.path.join(SetEnvironmentAndGetSDKDir(), 'Lib', SDK_VERSION, 'ucrt', |
| short_arch_path), |
| ] |
| |
| flags = [f'-Clinker={linker_path}' |
| ] + [f"-Lnative={path}" for path in include_paths] |
| |
| os.environ.clear() |
| os.environ.update(environ_backup) |
| return chr(0x1f).join(flags) |
| |
| |
| def _PrependOrInsert(dict, key, sep, value): |
| """Insert `value` into `dict[key]` if it doesn't already exist, |
| otherwise prepend `sep + value` to the existing entry |
| """ |
| if key in dict: |
| dict[key] = value + sep + dict[key] |
| else: |
| dict[key] = value |
| |
| def RunCargo(rust_sysroot, home_dir, cargo_args): |
| rust_sysroot = pathlib.Path(rust_sysroot) |
| if not rust_sysroot.exists(): |
| print(f'WARNING: Rust sysroot missing at "{rust_sysroot}"') |
| |
| bin_dir = rust_sysroot.absolute().joinpath('bin') |
| cargo_path = bin_dir.joinpath(_CARGO_EXE) |
| |
| cargo_env = dict(os.environ) |
| if home_dir: |
| cargo_env['CARGO_HOME'] = home_dir |
| |
| _PrependOrInsert(cargo_env, 'PATH', os.pathsep, str(bin_dir)) |
| _PrependOrInsert(cargo_env, 'CARGO_ENCODED_RUSTFLAGS', chr(0x1f), |
| _GetWindowsToolchainFlags()) |
| |
| # https://docs.python.org/3/library/subprocess.html#subprocess.Popen: |
| # **Warning**: For maximum reliability, use a fully qualified path for |
| # the executable. |
| # |
| # Resolving the path of executable (or the first item of args) is |
| # platform dependent. [...] For Windows, see the documentation of the |
| # `lpApplicationName` and `lpCommandLine` parameters of WinAPI |
| # `CreateProcess`, and note that when resolving or searching for the |
| # executable path with `shell=False`, _cwd_ does not override the |
| # current working directory and _env_ cannot override the `PATH` |
| # environment variable. |
| # |
| # The `CreateProcessW` documentation states that `lpApplicationName` and |
| # `lpCommandLine` uses the **current process'** `PATH` environment variable |
| # to look up executables. However, the created process' environment |
| # variables can still be specified using `lpEnvironment`. |
| # |
| # Therefore, there is no need for `shell=True` here if we provide a fully |
| # qualified path to cargo. |
| return subprocess.run(['cargo'] + cargo_args, |
| env=cargo_env, |
| executable=cargo_path).returncode |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser(description='run cargo') |
| parser.add_argument('--rust-sysroot', |
| default=DEFAULT_SYSROOT, |
| type=pathlib.Path, |
| help='use cargo and rustc from here') |
| (args, cargo_args) = parser.parse_known_args() |
| return RunCargo(args.rust_sysroot, None, cargo_args) |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |