Skip to content

Commit a6d3620

Browse files
authored
Rollup merge of rust-lang#39400 - alexcrichton:arm-cross-test, r=brson
Add support for test suites emulated in QEMU This commit adds support to the build system to execute test suites that cannot run natively but can instead run inside of a QEMU emulator. A proof-of-concept builder was added for the `arm-unknown-linux-gnueabihf` target to show off how this might work. In general the architecture is to have a server running inside of the emulator which a local client connects to. The protocol between the server/client supports compiling tests on the host and running them on the target inside the emulator. Closes rust-lang#33114
2 parents 73bb2d1 + 1747ce2 commit a6d3620

File tree

23 files changed

+3773
-50
lines changed

23 files changed

+3773
-50
lines changed

.travis.yml

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ matrix:
1313
include:
1414
# Linux builders, all docker images
1515
- env: IMAGE=android DEPLOY=1
16+
- env: IMAGE=armhf-gnu
1617
- env: IMAGE=cross DEPLOY=1
1718
- env: IMAGE=linux-tested-targets DEPLOY=1
1819
- env: IMAGE=dist-arm-linux DEPLOY=1

configure

+1
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,7 @@ valopt musl-root-arm "" "arm-unknown-linux-musleabi install directory"
684684
valopt musl-root-armhf "" "arm-unknown-linux-musleabihf install directory"
685685
valopt musl-root-armv7 "" "armv7-unknown-linux-musleabihf install directory"
686686
valopt extra-filename "" "Additional data that is hashed and passed to the -C extra-filename flag"
687+
valopt qemu-armhf-rootfs "" "rootfs in qemu testing, you probably don't want to use this"
687688

688689
if [ -e ${CFG_SRC_DIR}.git ]
689690
then

src/Cargo.lock

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ members = [
1111
"tools/rustbook",
1212
"tools/tidy",
1313
"tools/build-manifest",
14+
"tools/qemu-test-client",
15+
"tools/qemu-test-server",
1416
]
1517

1618
# Curiously, compiletest will segfault if compiled with opt-level=3 on 64-bit

src/bootstrap/check.rs

+93-26
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use build_helper::output;
2626

2727
use {Build, Compiler, Mode};
2828
use dist;
29-
use util::{self, dylib_path, dylib_path_var};
29+
use util::{self, dylib_path, dylib_path_var, exe};
3030

3131
const ADB_TEST_DIR: &'static str = "/data/tmp";
3232

@@ -221,6 +221,12 @@ pub fn compiletest(build: &Build,
221221
.arg("--llvm-cxxflags").arg("");
222222
}
223223

224+
if build.qemu_rootfs(target).is_some() {
225+
cmd.arg("--qemu-test-client")
226+
.arg(build.tool(&Compiler::new(0, &build.config.build),
227+
"qemu-test-client"));
228+
}
229+
224230
// Running a C compiler on MSVC requires a few env vars to be set, to be
225231
// sure to set them here.
226232
//
@@ -403,9 +409,9 @@ pub fn krate(build: &Build,
403409
dylib_path.insert(0, build.sysroot_libdir(&compiler, target));
404410
cargo.env(dylib_path_var(), env::join_paths(&dylib_path).unwrap());
405411

406-
if target.contains("android") {
407-
cargo.arg("--no-run");
408-
} else if target.contains("emscripten") {
412+
if target.contains("android") ||
413+
target.contains("emscripten") ||
414+
build.qemu_rootfs(target).is_some() {
409415
cargo.arg("--no-run");
410416
}
411417

@@ -423,6 +429,9 @@ pub fn krate(build: &Build,
423429
} else if target.contains("emscripten") {
424430
build.run(&mut cargo);
425431
krate_emscripten(build, &compiler, target, mode);
432+
} else if build.qemu_rootfs(target).is_some() {
433+
build.run(&mut cargo);
434+
krate_qemu(build, &compiler, target, mode);
426435
} else {
427436
cargo.args(&build.flags.cmd.test_args());
428437
build.run(&mut cargo);
@@ -480,23 +489,46 @@ fn krate_emscripten(build: &Build,
480489
compiler: &Compiler,
481490
target: &str,
482491
mode: Mode) {
483-
let mut tests = Vec::new();
484-
let out_dir = build.cargo_out(compiler, mode, target);
485-
find_tests(&out_dir, target, &mut tests);
486-
find_tests(&out_dir.join("deps"), target, &mut tests);
487-
488-
for test in tests {
489-
let test_file_name = test.to_string_lossy().into_owned();
490-
println!("running {}", test_file_name);
491-
let nodejs = build.config.nodejs.as_ref().expect("nodejs not configured");
492-
let mut cmd = Command::new(nodejs);
493-
cmd.arg(&test_file_name);
494-
if build.config.quiet_tests {
495-
cmd.arg("--quiet");
496-
}
497-
build.run(&mut cmd);
498-
}
499-
}
492+
let mut tests = Vec::new();
493+
let out_dir = build.cargo_out(compiler, mode, target);
494+
find_tests(&out_dir, target, &mut tests);
495+
find_tests(&out_dir.join("deps"), target, &mut tests);
496+
497+
for test in tests {
498+
let test_file_name = test.to_string_lossy().into_owned();
499+
println!("running {}", test_file_name);
500+
let nodejs = build.config.nodejs.as_ref().expect("nodejs not configured");
501+
let mut cmd = Command::new(nodejs);
502+
cmd.arg(&test_file_name);
503+
if build.config.quiet_tests {
504+
cmd.arg("--quiet");
505+
}
506+
build.run(&mut cmd);
507+
}
508+
}
509+
510+
fn krate_qemu(build: &Build,
511+
compiler: &Compiler,
512+
target: &str,
513+
mode: Mode) {
514+
let mut tests = Vec::new();
515+
let out_dir = build.cargo_out(compiler, mode, target);
516+
find_tests(&out_dir, target, &mut tests);
517+
find_tests(&out_dir.join("deps"), target, &mut tests);
518+
519+
let tool = build.tool(&Compiler::new(0, &build.config.build),
520+
"qemu-test-client");
521+
for test in tests {
522+
let mut cmd = Command::new(&tool);
523+
cmd.arg("run")
524+
.arg(&test);
525+
if build.config.quiet_tests {
526+
cmd.arg("--quiet");
527+
}
528+
cmd.args(&build.flags.cmd.test_args());
529+
build.run(&mut cmd);
530+
}
531+
}
500532

501533

502534
fn find_tests(dir: &Path,
@@ -516,13 +548,15 @@ fn find_tests(dir: &Path,
516548
}
517549
}
518550

519-
pub fn android_copy_libs(build: &Build,
520-
compiler: &Compiler,
521-
target: &str) {
522-
if !target.contains("android") {
523-
return
551+
pub fn emulator_copy_libs(build: &Build, compiler: &Compiler, target: &str) {
552+
if target.contains("android") {
553+
android_copy_libs(build, compiler, target)
554+
} else if let Some(s) = build.qemu_rootfs(target) {
555+
qemu_copy_libs(build, compiler, target, s)
524556
}
557+
}
525558

559+
fn android_copy_libs(build: &Build, compiler: &Compiler, target: &str) {
526560
println!("Android copy libs to emulator ({})", target);
527561
build.run(Command::new("adb").arg("wait-for-device"));
528562
build.run(Command::new("adb").arg("remount"));
@@ -548,6 +582,39 @@ pub fn android_copy_libs(build: &Build,
548582
}
549583
}
550584

585+
fn qemu_copy_libs(build: &Build,
586+
compiler: &Compiler,
587+
target: &str,
588+
rootfs: &Path) {
589+
println!("QEMU copy libs to emulator ({})", target);
590+
assert!(target.starts_with("arm"), "only works with arm for now");
591+
t!(fs::create_dir_all(build.out.join("tmp")));
592+
593+
// Copy our freshly compiled test server over to the rootfs
594+
let server = build.cargo_out(compiler, Mode::Tool, target)
595+
.join(exe("qemu-test-server", target));
596+
t!(fs::copy(&server, rootfs.join("testd")));
597+
598+
// Spawn the emulator and wait for it to come online
599+
let tool = build.tool(&Compiler::new(0, &build.config.build),
600+
"qemu-test-client");
601+
build.run(Command::new(&tool)
602+
.arg("spawn-emulator")
603+
.arg(rootfs)
604+
.arg(build.out.join("tmp")));
605+
606+
// Push all our dylibs to the emulator
607+
for f in t!(build.sysroot_libdir(compiler, target).read_dir()) {
608+
let f = t!(f);
609+
let name = f.file_name().into_string().unwrap();
610+
if util::is_dylib(&name) {
611+
build.run(Command::new(&tool)
612+
.arg("push")
613+
.arg(f.path()));
614+
}
615+
}
616+
}
617+
551618
/// Run "distcheck", a 'make check' from a tarball
552619
pub fn distcheck(build: &Build) {
553620
if build.config.build != "x86_64-unknown-linux-gnu" {

src/bootstrap/compile.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -382,10 +382,10 @@ fn add_to_sysroot(out_dir: &Path, sysroot_dst: &Path) {
382382
///
383383
/// This will build the specified tool with the specified `host` compiler in
384384
/// `stage` into the normal cargo output directory.
385-
pub fn tool(build: &Build, stage: u32, host: &str, tool: &str) {
386-
println!("Building stage{} tool {} ({})", stage, tool, host);
385+
pub fn tool(build: &Build, stage: u32, target: &str, tool: &str) {
386+
println!("Building stage{} tool {} ({})", stage, tool, target);
387387

388-
let compiler = Compiler::new(stage, host);
388+
let compiler = Compiler::new(stage, &build.config.build);
389389

390390
// FIXME: need to clear out previous tool and ideally deps, may require
391391
// isolating output directories or require a pseudo shim step to
@@ -396,7 +396,7 @@ pub fn tool(build: &Build, stage: u32, host: &str, tool: &str) {
396396
// let out_dir = build.cargo_out(stage, &host, Mode::Librustc, target);
397397
// build.clear_if_dirty(&out_dir, &libstd_stamp(build, stage, &host, target));
398398

399-
let mut cargo = build.cargo(&compiler, Mode::Tool, host, "build");
399+
let mut cargo = build.cargo(&compiler, Mode::Tool, target, "build");
400400
cargo.arg("--manifest-path")
401401
.arg(build.src.join(format!("src/tools/{}/Cargo.toml", tool)));
402402

src/bootstrap/config.rs

+9
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ pub struct Target {
114114
pub cxx: Option<PathBuf>,
115115
pub ndk: Option<PathBuf>,
116116
pub musl_root: Option<PathBuf>,
117+
pub qemu_rootfs: Option<PathBuf>,
117118
}
118119

119120
/// Structure of the `config.toml` file that configuration is read from.
@@ -222,6 +223,7 @@ struct TomlTarget {
222223
cxx: Option<String>,
223224
android_ndk: Option<String>,
224225
musl_root: Option<String>,
226+
qemu_rootfs: Option<String>,
225227
}
226228

227229
impl Config {
@@ -360,6 +362,7 @@ impl Config {
360362
target.cxx = cfg.cxx.clone().map(PathBuf::from);
361363
target.cc = cfg.cc.clone().map(PathBuf::from);
362364
target.musl_root = cfg.musl_root.clone().map(PathBuf::from);
365+
target.qemu_rootfs = cfg.qemu_rootfs.clone().map(PathBuf::from);
363366

364367
config.target_config.insert(triple.clone(), target);
365368
}
@@ -562,6 +565,12 @@ impl Config {
562565
.map(|s| s.to_string())
563566
.collect();
564567
}
568+
"CFG_QEMU_ARMHF_ROOTFS" if value.len() > 0 => {
569+
let target = "arm-unknown-linux-gnueabihf".to_string();
570+
let target = self.target_config.entry(target)
571+
.or_insert(Target::default());
572+
target.qemu_rootfs = Some(parse_configure_path(value));
573+
}
565574
_ => {}
566575
}
567576
}

src/bootstrap/lib.rs

+11
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,17 @@ impl Build {
878878
.map(|p| &**p)
879879
}
880880

881+
/// Returns the root of the "rootfs" image that this target will be using,
882+
/// if one was configured.
883+
///
884+
/// If `Some` is returned then that means that tests for this target are
885+
/// emulated with QEMU and binaries will need to be shipped to the emulator.
886+
fn qemu_rootfs(&self, target: &str) -> Option<&Path> {
887+
self.config.target_config.get(target)
888+
.and_then(|t| t.qemu_rootfs.as_ref())
889+
.map(|p| &**p)
890+
}
891+
881892
/// Path to the python interpreter to use
882893
fn python(&self) -> &Path {
883894
self.config.python.as_ref().unwrap()

0 commit comments

Comments
 (0)