mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2025-02-06 02:25:23 +00:00
708161aad7
binaries Test: build locally Change-Id: I4a9b96a0387c3c26ff2975680851943c2252946b Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/3598240 Reviewed-by: Dennis Kempin <denniskempin@google.com> Tested-by: kokoro <noreply+kokoro@google.com> Commit-Queue: Yuankai Chen <yuankaichen@google.com>
591 lines
19 KiB
Python
591 lines
19 KiB
Python
#!/usr/bin/env python3
|
|
# Copyright 2017 The Chromium OS Authors. All rights reserved.
|
|
# 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:]))
|