123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591 |
- #!/usr/bin/env python3
- # Copyright 2017 The ChromiumOS Authors
- # Use of this source code is governed by a BSD-style license that can be
- # found in the LICENSE file.
- """Builds crosvm in debug/release mode on all supported target architectures.
- A sysroot for each target architectures is required. The defaults are all generic boards' sysroots,
- but they can be changed with the command line arguments.
- To test changes more quickly, set the --noclean option. This prevents the target directories from
- being removed before building and testing.
- For easy binary size comparison, use the --size-only option to only do builds that will result in a
- binary size output, which are non-test release builds.
- This script automatically determines which packages will need to be tested based on the directory
- structure with Cargo.toml files. Only top-level crates are tested directly. To skip a top-level
- package, add an empty .build_test_skip file to the directory. Rarely, if a package needs to have its
- tests run single-threaded, add an empty .build_test_serial file to the directory.
- """
- from __future__ import print_function
- import argparse
- import functools
- import multiprocessing.pool
- import os
- import shutil
- import subprocess
- import sys
- sys.path.append(os.path.dirname(sys.path[0]))
- from enabled_features import ENABLED_FEATURES, BUILD_FEATURES
- from files_to_include import DLLS, BINARIES
- from prepare_dlls import build_dlls, copy_dlls
- # Is Windows
- IS_WINDOWS = os.name == "nt"
- ARM_TRIPLE = os.getenv("ARM_TRIPLE", "armv7a-cros-linux-gnueabihf")
- AARCH64_TRIPLE = os.getenv("AARCH64_TRIPLE", "aarch64-cros-linux-gnu")
- X86_64_TRIPLE = os.getenv("X86_64_TRIPLE", "x86_64-unknown-linux-gnu")
- X86_64_WIN_MSVC_TRIPLE = os.getenv("X86_64_WIN_MSVC_TRIPLE", "x86_64-pc-windows-msvc")
- SYMBOL_EXPORTS = ["NvOptimusEnablement", "AmdPowerXpressRequestHighPerformance"]
- LINUX_BUILD_ONLY_MODULES = [
- "io_jail",
- "poll_token_derive",
- "wire_format_derive",
- "bit_field_derive",
- "linux_input_sys",
- "vfio_sys",
- ]
- # Bright green.
- PASS_COLOR = "\033[1;32m"
- # Bright red.
- FAIL_COLOR = "\033[1;31m"
- # Default color.
- END_COLOR = "\033[0m"
- def crosvm_binary_name():
- return "crosvm.exe" if IS_WINDOWS else "crosvm"
- def get_target_path(triple, kind, test_it):
- """Constructs a target path based on the configuration parameters.
- Args:
- triple: Target triple. Example: 'x86_64-unknown-linux-gnu'.
- kind: 'debug' or 'release'.
- test_it: If this target is tested.
- """
- target_path = os.path.abspath(os.path.join(os.sep, "tmp", "{}_{}".format(triple, kind)))
- if test_it:
- target_path += "_test"
- return target_path
- def validate_symbols(triple, is_release):
- kind = "release" if is_release else "debug"
- target_path = get_target_path(triple, kind, False)
- binary_path = os.path.join(target_path, triple, kind, crosvm_binary_name())
- with open(binary_path, mode="rb") as f:
- contents = f.read().decode("ascii", errors="ignore")
- return all(symbol in contents for symbol in SYMBOL_EXPORTS)
- def build_target(
- triple,
- is_release,
- env,
- only_build_targets,
- test_module_parallel,
- test_module_serial,
- ):
- """Does a cargo build for the triple in release or debug mode.
- Args:
- triple: Target triple. Example: 'x86_64-unknown-linux-gnu'.
- is_release: True to build a release version.
- env: Enviroment variables to run cargo with.
- only_build_targets: Only build packages that will be tested.
- """
- args = ["cargo", "build", "--target=%s" % triple]
- if is_release:
- args.append("--release")
- if only_build_targets:
- test_modules = test_module_parallel + test_module_serial
- if not IS_WINDOWS:
- test_modules += LINUX_BUILD_ONLY_MODULES
- for mod in test_modules:
- args.append("-p")
- args.append(mod)
- args.append("--features")
- args.append(",".join(BUILD_FEATURES))
- if subprocess.Popen(args, env=env).wait() != 0:
- return False, "build error"
- if IS_WINDOWS and not validate_symbols(triple, is_release):
- return False, "error validating discrete gpu symbols"
- return True, "pass"
- def test_target_modules(triple, is_release, env, no_run, modules, parallel):
- """Does a cargo test on given modules for the triple and configuration.
- Args:
- triple: Target triple. Example: 'x86_64-unknown-linux-gnu'.
- is_release: True to build a release version.
- env: Enviroment variables to run cargo with.
- no_run: True to pass --no-run flag to cargo test.
- modules: List of module strings to test.
- parallel: True to run the tests in parallel threads.
- """
- args = ["cargo", "test", "--target=%s" % triple]
- if is_release:
- args.append("--release")
- if no_run:
- args.append("--no-run")
- for mod in modules:
- args.append("-p")
- args.append(mod)
- args.append("--features")
- args.append(",".join(ENABLED_FEATURES))
- if not parallel:
- args.append("--")
- args.append("--test-threads=1")
- return subprocess.Popen(args, env=env).wait() == 0
- def test_target(triple, is_release, env, no_run, test_modules_parallel, test_modules_serial):
- """Does a cargo test for the given triple and configuration.
- Args:
- triple: Target triple. Example: 'x86_64-unknown-linux-gnu'.
- is_release: True to build a release version.
- env: Enviroment variables to run cargo with.
- no_run: True to pass --no-run flag to cargo test.
- """
- parallel_result = test_target_modules(
- triple, is_release, env, no_run, test_modules_parallel, True
- )
- serial_result = test_target_modules(triple, is_release, env, no_run, test_modules_serial, False)
- return parallel_result and serial_result
- def build_or_test(
- sysroot,
- triple,
- kind,
- skip_file_name,
- test_it=False,
- no_run=False,
- clean=False,
- copy_output=False,
- copy_directory=None,
- only_build_targets=False,
- ):
- """Runs relevant builds/tests for the given triple and configuration
- Args:
- sysroot: path to the target's sysroot directory.
- triple: Target triple. Example: 'x86_64-unknown-linux-gnu'.
- kind: 'debug' or 'release'.
- skip_file_name: Skips building and testing a crate if this file is found in
- crate's root directory.
- test_it: True to test this triple and kind.
- no_run: True to just compile and not run tests (only if test_it=True)
- clean: True to skip cleaning the target path.
- copy_output: True to copy build artifacts to external directory.
- output_directory: Destination of copy of build artifacts.
- only_build_targets: Only build packages that will be tested.
- """
- if not os.path.isdir(sysroot) and not IS_WINDOWS:
- return False, "sysroot missing"
- target_path = get_target_path(triple, kind, test_it)
- if clean:
- shutil.rmtree(target_path, True)
- is_release = kind == "release"
- env = os.environ.copy()
- env["TARGET_CC"] = "%s-clang" % triple
- env["SYSROOT"] = sysroot
- env["CARGO_TARGET_DIR"] = target_path
- if not IS_WINDOWS:
- # The lib dir could be in either lib or lib64 depending on the target. Rather than checking to see
- # which one is valid, just add both and let the dynamic linker and pkg-config search.
- libdir = os.path.join(sysroot, "usr", "lib")
- lib64dir = os.path.join(sysroot, "usr", "lib64")
- libdir_pc = os.path.join(libdir, "pkgconfig")
- lib64dir_pc = os.path.join(lib64dir, "pkgconfig")
- # This line that changes the dynamic library path is needed for upstream, but breaks
- # downstream's crosvm linux kokoro presubmits.
- # env['LD_LIBRARY_PATH'] = libdir + ':' + lib64dir
- env["PKG_CONFIG_ALLOW_CROSS"] = "1"
- env["PKG_CONFIG_LIBDIR"] = libdir_pc + ":" + lib64dir_pc
- env["PKG_CONFIG_SYSROOT_DIR"] = sysroot
- if "KOKORO_JOB_NAME" not in os.environ:
- env["RUSTFLAGS"] = "-C linker=" + env["TARGET_CC"]
- if is_release:
- env["RUSTFLAGS"] += " -Cembed-bitcode=yes -Clto"
- if IS_WINDOWS and not test_it:
- for symbol in SYMBOL_EXPORTS:
- env["RUSTFLAGS"] = env.get("RUSTFLAGS", "") + " -C link-args=/EXPORT:{}".format(symbol)
- deps_dir = os.path.join(target_path, triple, kind, "deps")
- if not os.path.exists(deps_dir):
- os.makedirs(deps_dir)
- target_dirs = [deps_dir]
- if copy_output:
- os.makedirs(os.path.join(copy_directory, kind), exist_ok=True)
- if not test_it:
- target_dirs.append(os.path.join(copy_directory, kind))
- copy_dlls(os.getcwd(), target_dirs, kind)
- (test_modules_parallel, test_modules_serial) = get_test_modules(skip_file_name)
- print("modules to test in parallel:\n", test_modules_parallel)
- print("modules to test serially:\n", test_modules_serial)
- if not test_modules_parallel and not test_modules_serial:
- print("All build and tests skipped.")
- return True, "pass"
- if test_it:
- if not test_target(
- triple, is_release, env, no_run, test_modules_parallel, test_modules_serial
- ):
- return False, "test error"
- else:
- res, err = build_target(
- triple,
- is_release,
- env,
- only_build_targets,
- test_modules_parallel,
- test_modules_serial,
- )
- if not res:
- return res, err
- # We only care about the non-test binaries, so only copy the output from cargo build.
- if copy_output and not test_it:
- binary_src = os.path.join(target_path, triple, kind, crosvm_binary_name())
- pdb_src = binary_src.replace(".exe", "") + ".pdb"
- binary_dst = os.path.join(copy_directory, kind)
- shutil.copy(binary_src, binary_dst)
- shutil.copy(pdb_src, binary_dst)
- return True, "pass"
- def get_test_modules(skip_file_name):
- """Returns a list of modules to test.
- Args:
- skip_file_name: Skips building and testing a crate if this file is found in
- crate's root directory.
- """
- if IS_WINDOWS and not os.path.isfile(skip_file_name):
- test_modules_parallel = ["crosvm"]
- else:
- test_modules_parallel = []
- test_modules_serial = []
- file_in_crate = lambda file_name: os.path.isfile(os.path.join(crate.path, file_name))
- serial_file_name = "{}build_test_serial".format(".win_" if IS_WINDOWS else ".")
- with os.scandir() as it:
- for crate in it:
- if file_in_crate("Cargo.toml"):
- if file_in_crate(skip_file_name):
- continue
- if file_in_crate(serial_file_name):
- test_modules_serial.append(crate.name)
- else:
- test_modules_parallel.append(crate.name)
- test_modules_parallel.sort()
- test_modules_serial.sort()
- return (test_modules_parallel, test_modules_serial)
- def get_stripped_size(triple):
- """Returns the formatted size of the given triple's release binary.
- Args:
- triple: Target triple. Example: 'x86_64-unknown-linux-gnu'.
- """
- target_path = get_target_path(triple, "release", False)
- bin_path = os.path.join(target_path, triple, "release", crosvm_binary_name())
- proc = subprocess.Popen(["%s-strip" % triple, bin_path])
- if proc.wait() != 0:
- return "failed"
- return "%dKiB" % (os.path.getsize(bin_path) / 1024)
- def get_parser():
- """Gets the argument parser"""
- parser = argparse.ArgumentParser(description=__doc__)
- if IS_WINDOWS:
- parser.add_argument(
- "--x86_64-msvc-sysroot",
- default="build/amd64-msvc",
- help="x86_64 sysroot directory (default=%(default)s)",
- )
- else:
- parser.add_argument(
- "--arm-sysroot",
- default="/build/arm-generic",
- help="ARM sysroot directory (default=%(default)s)",
- )
- parser.add_argument(
- "--aarch64-sysroot",
- default="/build/arm64-generic",
- help="AARCH64 sysroot directory (default=%(default)s)",
- )
- parser.add_argument(
- "--x86_64-sysroot",
- default="/build/amd64-generic",
- help="x86_64 sysroot directory (default=%(default)s)",
- )
- parser.add_argument(
- "--noclean",
- dest="clean",
- default=True,
- action="store_false",
- help="Keep the tempororary build directories.",
- )
- parser.add_argument(
- "--copy",
- default=False,
- help="Copies .exe files to an output directory for later use",
- )
- parser.add_argument(
- "--copy-directory",
- default="/output",
- help="Destination of .exe files when using --copy",
- )
- parser.add_argument(
- "--serial",
- default=True,
- action="store_false",
- dest="parallel",
- help="Run cargo build serially rather than in parallel",
- )
- # TODO(b/154029826): Remove this option once all sysroots are available.
- parser.add_argument(
- "--x86_64-only",
- default=False,
- action="store_true",
- help="Only runs tests on x86_64 sysroots",
- )
- parser.add_argument(
- "--only-build-targets",
- default=False,
- action="store_true",
- help="Builds only the tested modules. If false, builds the entire crate",
- )
- parser.add_argument(
- "--size-only",
- dest="size_only",
- default=False,
- action="store_true",
- help="Only perform builds that output their binary size (i.e. release non-test).",
- )
- parser.add_argument(
- "--job_type",
- default="local",
- choices=["kokoro", "local"],
- help="Set to kokoro if this script is executed by a kokoro job, otherwise local",
- )
- parser.add_argument(
- "--skip_file_name",
- default=".win_build_test_skip" if IS_WINDOWS else ".build_test_skip",
- choices=[
- ".build_test_skip",
- ".win_build_test_skip",
- ".windows_build_test_skip",
- ],
- help="Skips building and testing a crate if the crate contains specified file in its root directory.",
- )
- parser.add_argument(
- "--build_mode",
- default="release",
- choices=["release", "debug"],
- help="Build mode of the binaries.",
- )
- return parser
- def main(argv):
- opts = get_parser().parse_args(argv)
- os.environ["RUST_BACKTRACE"] = "1"
- if IS_WINDOWS:
- if opts.build_mode == "release":
- build_test_cases = [
- # (sysroot path, target triple, debug/release, skip_file_name, should test?)
- (
- opts.x86_64_msvc_sysroot,
- X86_64_WIN_MSVC_TRIPLE,
- "release",
- opts.skip_file_name,
- True,
- ),
- (
- opts.x86_64_msvc_sysroot,
- X86_64_WIN_MSVC_TRIPLE,
- "release",
- opts.skip_file_name,
- False,
- ),
- ]
- elif opts.build_mode == "debug":
- build_test_cases = [
- (
- opts.x86_64_msvc_sysroot,
- X86_64_WIN_MSVC_TRIPLE,
- "debug",
- opts.skip_file_name,
- True,
- ),
- ]
- else:
- build_test_cases = [
- # (sysroot path, target triple, debug/release, skip_file_name, should test?)
- (opts.x86_64_sysroot, X86_64_TRIPLE, "debug", opts.skip_file_name, False),
- (opts.x86_64_sysroot, X86_64_TRIPLE, "release", opts.skip_file_name, False),
- (opts.x86_64_sysroot, X86_64_TRIPLE, "debug", opts.skip_file_name, True),
- (opts.x86_64_sysroot, X86_64_TRIPLE, "release", opts.skip_file_name, True),
- ]
- if not opts.x86_64_only:
- build_test_cases = [
- # (sysroot path, target triple, debug/release, skip_file_name, should test?)
- (opts.arm_sysroot, ARM_TRIPLE, "debug", opts.skip_file_name, False),
- (opts.arm_sysroot, ARM_TRIPLE, "release", opts.skip_file_name, False),
- (
- opts.aarch64_sysroot,
- AARCH64_TRIPLE,
- "debug",
- opts.skip_file_name,
- False,
- ),
- (
- opts.aarch64_sysroot,
- AARCH64_TRIPLE,
- "release",
- opts.skip_file_name,
- False,
- ),
- ] + build_test_cases
- os.chdir(os.path.dirname(sys.argv[0]))
- if opts.size_only:
- # Only include non-test release builds
- build_test_cases = [
- case for case in build_test_cases if case[2] == "release" and not case[4]
- ]
- # First we need to build necessary DLLs.
- # Because build_or_test may be called by multithreads in parallel,
- # we want to build the DLLs only once up front.
- modes = set()
- for case in build_test_cases:
- modes.add(case[2])
- for mode in modes:
- build_dlls(os.getcwd(), mode, opts.job_type, BUILD_FEATURES)
- # set keyword args to build_or_test based on opts
- build_partial = functools.partial(
- build_or_test,
- no_run=True,
- clean=opts.clean,
- copy_output=opts.copy,
- copy_directory=opts.copy_directory,
- only_build_targets=opts.only_build_targets,
- )
- if opts.parallel:
- pool = multiprocessing.pool.Pool(len(build_test_cases))
- results = pool.starmap(build_partial, build_test_cases, 1)
- else:
- results = [build_partial(*case) for case in build_test_cases]
- print_summary("build", build_test_cases, results, opts)
- # exit early if any builds failed
- if not all([r[0] for r in results]):
- return 1
- # run tests for cases where should_test is True
- test_cases = [case for case in build_test_cases if case[4]]
- # Run tests serially. We set clean=False so it re-uses the results of the build phase.
- results = [
- build_or_test(
- *case,
- no_run=False,
- clean=False,
- copy_output=opts.copy,
- copy_directory=opts.copy_directory,
- only_build_targets=opts.only_build_targets,
- )
- for case in test_cases
- ]
- print_summary("test", test_cases, results, opts)
- if not all([r[0] for r in results]):
- return 1
- return 0
- def print_summary(title, cases, results, opts):
- print("---")
- print(f"{title} summary:")
- for test_case, result in zip(cases, results):
- _, triple, kind, _, test_it = test_case
- title = "%s_%s" % (triple.split("-")[0], kind)
- if test_it:
- title += "_test"
- success, result_msg = result
- result_color = FAIL_COLOR
- if success:
- result_color = PASS_COLOR
- display_size = ""
- # Stripped binary isn't available when only certain packages are built, the tool is not available
- # on Windows.
- if (
- success
- and kind == "release"
- and not test_it
- and not opts.only_build_targets
- and not IS_WINDOWS
- ):
- display_size = get_stripped_size(triple) + " stripped binary"
- print("%20s: %s%15s%s %s" % (title, result_color, result_msg, END_COLOR, display_size))
- if __name__ == "__main__":
- sys.exit(main(sys.argv[1:]))
|