use std ::{
env ::consts ::{ ARCH , OS } ,
ffi ::OsString ,
fmt ::Write as _ ,
fs ::{ copy , create_dir_all , OpenOptions } ,
io ::{ BufRead as _ , BufReader , ErrorKind , Write as _ } ,
path ::{ Path , PathBuf } ,
process ::{ Child , ChildStdin , Command , Output , Stdio } ,
sync ::{ Arc , Mutex } ,
thread ,
} ;
use anyhow ::{ anyhow , bail , Context as _ , Result } ;
use cargo_metadata ::{ Artifact , CompilerMessage , Message , Target } ;
use clap ::Parser ;
xtask: extract `Errors` type
This produces better errors in `cargo xtask public-api` because before
this change we'd print errors as display, which didn't show context.
Before:
```
Error: public API errors:
aya failed to check public API: public_api::Builder::build
aya-obj failed to check public API: public_api::Builder::build
aya-log failed to check public API: public_api::Builder::build
aya-log-common failed to check public API: public_api::Builder::build
aya-log-parser failed to check public API: public_api::Builder::build
aya-tool failed to check public API: public_api::Builder::build
aya-bpf failed to check public API: public_api::Builder::build
aya-bpf-bindings failed to check public API: public_api::Builder::build
aya-bpf-cty failed to check public API: public_api::Builder::build
aya-bpf-macros failed to check public API: public_api::Builder::build
aya-log-ebpf-macros failed to check public API: public_api::Builder::build
```
After:
```
Error: aya failed to check public API
Caused by:
0: public_api::Builder::build
1: unknown variant `type_alias`, expected one of `module`, `extern_crate`, `import`, `struct`, `struct_field`, `union`, `enum`, `variant`, `function`, `typedef`, `opaque_ty`, `constant`, `trait`, `trait_alias`, `impl`, `static`, `foreign_type`, `macro`, `proc_attribute`, `proc_derive`, `assoc_const`, `assoc_type`, `primitive`, `keyword` at line 1 column 3082463
aya-obj failed to check public API
Caused by:
0: public_api::Builder::build
1: unknown variant `type_alias`, expected one of `module`, `extern_crate`, `import`, `union`, `struct`, `struct_field`, `enum`, `variant`, `function`, `trait`, `trait_alias`, `impl`, `typedef`, `opaque_ty`, `constant`, `static`, `foreign_type`, `macro`, `proc_macro`, `primitive`, `assoc_const`, `assoc_type` at line 1 column 129355
aya-log failed to check public API
Caused by:
0: public_api::Builder::build
1: unknown variant `type_alias`, expected one of `module`, `extern_crate`, `import`, `struct`, `struct_field`, `union`, `enum`, `variant`, `function`, `typedef`, `opaque_ty`, `constant`, `trait`, `trait_alias`, `impl`, `static`, `foreign_type`, `macro`, `proc_attribute`, `proc_derive`, `assoc_const`, `assoc_type`, `primitive`, `keyword` at line 1 column 311910
aya-log-common failed to check public API
Caused by:
0: public_api::Builder::build
1: unknown variant `type_alias`, expected one of `module`, `extern_crate`, `import`, `union`, `struct`, `struct_field`, `enum`, `variant`, `function`, `trait`, `trait_alias`, `impl`, `typedef`, `opaque_ty`, `constant`, `static`, `foreign_type`, `macro`, `proc_macro`, `primitive`, `assoc_const`, `assoc_type` at line 1 column 130456
aya-log-parser failed to check public API
Caused by:
0: public_api::Builder::build
1: unknown variant `type_alias`, expected one of `module`, `extern_crate`, `import`, `struct`, `struct_field`, `union`, `enum`, `variant`, `function`, `typedef`, `opaque_ty`, `constant`, `trait`, `trait_alias`, `impl`, `static`, `foreign_type`, `macro`, `proc_attribute`, `proc_derive`, `assoc_const`, `assoc_type`, `primitive`, `keyword` at line 1 column 204930
aya-tool failed to check public API
Caused by:
0: public_api::Builder::build
1: unknown variant `type_alias`, expected one of `module`, `extern_crate`, `import`, `struct`, `struct_field`, `union`, `enum`, `variant`, `function`, `typedef`, `opaque_ty`, `constant`, `trait`, `trait_alias`, `impl`, `static`, `foreign_type`, `macro`, `proc_attribute`, `proc_derive`, `assoc_const`, `assoc_type`, `primitive`, `keyword` at line 1 column 234519
aya-bpf failed to check public API
Caused by:
0: public_api::Builder::build
1: unknown variant `type_alias`, expected one of `module`, `extern_crate`, `import`, `struct`, `struct_field`, `union`, `enum`, `variant`, `function`, `typedef`, `opaque_ty`, `constant`, `trait`, `trait_alias`, `impl`, `static`, `foreign_type`, `macro`, `proc_attribute`, `proc_derive`, `assoc_const`, `assoc_type`, `primitive`, `keyword` at line 1 column 741697
aya-bpf-bindings failed to check public API
Caused by:
0: public_api::Builder::build
1: unknown variant `type_alias`, expected one of `module`, `extern_crate`, `import`, `union`, `struct`, `struct_field`, `enum`, `variant`, `function`, `trait`, `trait_alias`, `impl`, `typedef`, `opaque_ty`, `constant`, `static`, `foreign_type`, `macro`, `proc_macro`, `primitive`, `assoc_const`, `assoc_type` at line 1 column 112445
aya-bpf-cty failed to check public API
Caused by:
0: public_api::Builder::build
1: unknown variant `type_alias`, expected one of `module`, `extern_crate`, `import`, `union`, `struct`, `struct_field`, `enum`, `variant`, `function`, `trait`, `trait_alias`, `impl`, `typedef`, `opaque_ty`, `constant`, `static`, `foreign_type`, `macro`, `proc_macro`, `primitive`, `assoc_const`, `assoc_type` at line 1 column 315
aya-bpf-macros failed to check public API
Caused by:
0: public_api::Builder::build
1: unknown variant `type_alias`, expected one of `module`, `extern_crate`, `import`, `struct`, `struct_field`, `union`, `enum`, `variant`, `function`, `typedef`, `opaque_ty`, `constant`, `trait`, `trait_alias`, `impl`, `static`, `foreign_type`, `macro`, `proc_attribute`, `proc_derive`, `assoc_const`, `assoc_type`, `primitive`, `keyword` at line 1 column 186226
aya-log-ebpf-macros failed to check public API
Caused by:
0: public_api::Builder::build
1: unknown variant `type_alias`, expected one of `module`, `extern_crate`, `import`, `struct`, `struct_field`, `union`, `enum`, `variant`, `function`, `typedef`, `opaque_ty`, `constant`, `trait`, `trait_alias`, `impl`, `static`, `foreign_type`, `macro`, `proc_attribute`, `proc_derive`, `assoc_const`, `assoc_type`, `primitive`, `keyword` at line 1 column 158055
```
1 year ago
use xtask ::{ exec , Errors , AYA_BUILD_INTEGRATION_BPF } ;
#[ derive(Parser) ]
enum Environment {
/// Runs the integration tests locally.
Local {
/// The command used to wrap your application.
#[ clap(short, long, default_value = " sudo -E " ) ]
runner : String ,
} ,
/// Runs the integration tests in a VM.
VM {
/// The kernel images to use.
///
/// You can download some images with:
///
/// wget --accept-regex '.*/linux-image-[0-9\.-]+-cloud-.*-unsigned*' \
/// --recursive ftp://ftp.us.debian.org/debian/pool/main/l/linux/
///
/// You can then extract them with:
///
/// find . -name '*.deb' -print0 \
/// | xargs -0 -I {} sh -c "dpkg --fsys-tarfile {} \
/// | tar --wildcards --extract '*vmlinuz*' --file -"
#[ clap(required = true) ]
kernel_image : Vec < PathBuf > ,
} ,
}
#[ derive(Parser) ]
pub struct Options {
#[ clap(subcommand) ]
environment : Environment ,
/// Arguments to pass to your application.
#[ clap(global = true, last = true) ]
run_args : Vec < OsString > ,
}
pub fn build < F > ( target : Option < & str > , f : F ) -> Result < Vec < ( String , PathBuf ) > >
where
F : FnOnce ( & mut Command ) -> & mut Command ,
{
// Always use rust-lld and -Zbuild-std in case we're cross-compiling.
let mut cmd = Command ::new ( "cargo" ) ;
cmd . args ( [ "build" , "--message-format=json" ] ) ;
if let Some ( target ) = target {
let config = format! ( "target.{target}.linker = \"rust-lld\"" ) ;
cmd . args ( [ "--target" , target , "--config" , & config ] ) ;
}
f ( & mut cmd ) ;
let mut child = cmd
. stdout ( Stdio ::piped ( ) )
. spawn ( )
. with_context ( | | format! ( "failed to spawn {cmd:?}" ) ) ? ;
let Child { stdout , .. } = & mut child ;
let stdout = stdout . take ( ) . unwrap ( ) ;
let stdout = BufReader ::new ( stdout ) ;
let mut executables = Vec ::new ( ) ;
for message in Message ::parse_stream ( stdout ) {
#[ allow(clippy::collapsible_match) ]
match message . context ( "valid JSON" ) ? {
Message ::CompilerArtifact ( Artifact {
executable ,
target : Target { name , .. } ,
..
} ) = > {
if let Some ( executable ) = executable {
executables . push ( ( name , executable . into ( ) ) ) ;
}
}
Message ::CompilerMessage ( CompilerMessage { message , .. } ) = > {
println! ( "{message}" ) ;
}
Message ::TextLine ( line ) = > {
println! ( "{line}" ) ;
}
_ = > { }
}
}
let status = child
. wait ( )
. with_context ( | | format! ( "failed to wait for {cmd:?}" ) ) ? ;
if status . code ( ) ! = Some ( 0 ) {
bail ! ( "{cmd:?} failed: {status:?}" )
}
Ok ( executables )
}
/// Build and run the project.
pub fn run ( opts : Options ) -> Result < ( ) > {
let Options {
environment ,
run_args ,
} = opts ;
type Binary = ( String , PathBuf ) ;
fn binaries ( target : Option < & str > ) -> Result < Vec < ( & str , Vec < Binary > ) > > {
[ "dev" , "release" ]
. into_iter ( )
. map ( | profile | {
let binaries = build ( target , | cmd | {
cmd . env ( AYA_BUILD_INTEGRATION_BPF , "true" ) . args ( [
"--package" ,
"integration-test" ,
"--tests" ,
"--profile" ,
profile ,
] )
} ) ? ;
anyhow ::Ok ( ( profile , binaries ) )
} )
. collect ( )
}
// Use --test-threads=1 to prevent tests from interacting with shared
// kernel state due to the lack of inter-test isolation.
let default_args = [ OsString ::from ( "--test-threads=1" ) ] ;
let run_args = default_args . iter ( ) . chain ( run_args . iter ( ) ) ;
match environment {
Environment ::Local { runner } = > {
let mut args = runner . trim ( ) . split_terminator ( ' ' ) ;
let runner = args . next ( ) . ok_or ( anyhow ! ( "no first argument" ) ) ? ;
let args = args . collect ::< Vec < _ > > ( ) ;
let binaries = binaries ( None ) ? ;
let mut failures = String ::new ( ) ;
for ( profile , binaries ) in binaries {
for ( name , binary ) in binaries {
let mut cmd = Command ::new ( runner ) ;
let cmd = cmd . args ( args . iter ( ) ) . arg ( binary ) . args ( run_args . clone ( ) ) ;
println! ( "{profile}:{name} running {cmd:?}" ) ;
let status = cmd
. status ( )
. with_context ( | | format! ( "failed to run {cmd:?}" ) ) ? ;
if status . code ( ) ! = Some ( 0 ) {
writeln! ( & mut failures , "{profile}:{name} failed: {status:?}" )
. context ( "String write failed" ) ?
}
}
}
if failures . is_empty ( ) {
Ok ( ( ) )
} else {
Err ( anyhow ! ( "failures:\n{}" , failures ) )
}
}
Environment ::VM { kernel_image } = > {
// The user has asked us to run the tests on a VM. This is involved; strap in.
//
// We need tools to build the initramfs; we use gen_init_cpio from the Linux repository,
// taking care to cache it.
//
// Then we iterate the kernel images, using the `file` program to guess the target
// architecture. We then build the init program and our test binaries for that
// architecture, and use gen_init_cpio to build an initramfs containing the test
// binaries. We're almost ready to run the VM.
//
// We consult our OS, our architecture, and the target architecture to determine if
// hardware acceleration is available, and then start QEMU with the provided kernel
// image and the initramfs we built.
//
// We consume the output of QEMU, looking for the output of our init program. This is
// the only way to distinguish success from failure. We batch up the errors across all
// VM images and report to the user. The end.
let cache_dir = Path ::new ( "test/.tmp" ) ;
create_dir_all ( cache_dir ) . context ( "failed to create cache dir" ) ? ;
let gen_init_cpio = cache_dir . join ( "gen_init_cpio" ) ;
if ! gen_init_cpio
. try_exists ( )
. context ( "failed to check existence of gen_init_cpio" ) ?
{
let mut curl = Command ::new ( "curl" ) ;
curl . args ( [
"-sfSL" ,
"https://raw.githubusercontent.com/torvalds/linux/master/usr/gen_init_cpio.c" ,
] ) ;
let mut curl_child = curl
. stdout ( Stdio ::piped ( ) )
. spawn ( )
. with_context ( | | format! ( "failed to spawn {curl:?}" ) ) ? ;
let Child { stdout , .. } = & mut curl_child ;
let curl_stdout = stdout . take ( ) . unwrap ( ) ;
let mut clang = Command ::new ( "clang" ) ;
let clang = exec (
clang
. args ( [ "-g" , "-O2" , "-x" , "c" , "-" , "-o" ] )
. arg ( & gen_init_cpio )
. stdin ( curl_stdout ) ,
) ;
let output = curl_child
. wait_with_output ( )
. with_context ( | | format! ( "failed to wait for {curl:?}" ) ) ? ;
let Output { status , .. } = & output ;
if status . code ( ) ! = Some ( 0 ) {
bail ! ( "{curl:?} failed: {output:?}" )
}
// Check the result of clang *after* checking curl; in case the download failed,
// only curl's output will be useful.
clang ? ;
}
let mut errors = Vec ::new ( ) ;
for kernel_image in kernel_image {
// Guess the guest architecture.
let mut cmd = Command ::new ( "file" ) ;
let output = cmd
. arg ( "--brief" )
. arg ( & kernel_image )
. output ( )
. with_context ( | | format! ( "failed to run {cmd:?}" ) ) ? ;
let Output { status , .. } = & output ;
if status . code ( ) ! = Some ( 0 ) {
bail ! ( "{cmd:?} failed: {output:?}" )
}
let Output { stdout , .. } = output ;
// Now parse the output of the file command, which looks something like
//
// - Linux kernel ARM64 boot executable Image, little-endian, 4K pages
//
// - Linux kernel x86 boot executable bzImage, version 6.1.0-10-cloud-amd64 [..]
let stdout = String ::from_utf8 ( stdout )
. with_context ( | | format! ( "invalid UTF-8 in {cmd:?} stdout" ) ) ? ;
let ( _ , stdout ) = stdout
. split_once ( "Linux kernel" )
. ok_or_else ( | | anyhow ! ( "failed to parse {cmd:?} stdout: {stdout}" ) ) ? ;
let ( guest_arch , _ ) = stdout
. split_once ( "boot executable" )
. ok_or_else ( | | anyhow ! ( "failed to parse {cmd:?} stdout: {stdout}" ) ) ? ;
let guest_arch = guest_arch . trim ( ) ;
let ( guest_arch , machine , cpu ) = match guest_arch {
"ARM64" = > ( "aarch64" , Some ( "virt" ) , Some ( "cortex-a57" ) ) ,
"x86" = > ( "x86_64" , Some ( "q35" ) , Some ( "qemu64" ) ) ,
guest_arch = > ( guest_arch , None , None ) ,
} ;
let target = format! ( "{guest_arch}-unknown-linux-musl" ) ;
// Build our init program. The contract is that it will run anything it finds in /bin.
let init = build ( Some ( & target ) , | cmd | {
cmd . args ( [ "--package" , "init" , "--profile" , "release" ] )
} )
. context ( "building init program failed" ) ? ;
let init = match & * init {
[ ( name , init ) ] = > {
if name ! = "init" {
bail ! ( "expected init program to be named init, found {name}" )
}
init
}
init = > bail ! ( "expected exactly one init program, found {init:?}" ) ,
} ;
let binaries = binaries ( Some ( & target ) ) ? ;
let tmp_dir = tempfile ::tempdir ( ) . context ( "tempdir failed" ) ? ;
let initrd_image = tmp_dir . path ( ) . join ( "qemu-initramfs.img" ) ;
let initrd_image_file = OpenOptions ::new ( )
. create_new ( true )
. write ( true )
. open ( & initrd_image )
. with_context ( | | {
format! ( "failed to create {} for writing" , initrd_image . display ( ) )
} ) ? ;
let mut gen_init_cpio = Command ::new ( & gen_init_cpio ) ;
let mut gen_init_cpio_child = gen_init_cpio
. arg ( "-" )
. stdin ( Stdio ::piped ( ) )
. stdout ( initrd_image_file )
. spawn ( )
. with_context ( | | format! ( "failed to spawn {gen_init_cpio:?}" ) ) ? ;
let Child { stdin , .. } = & mut gen_init_cpio_child ;
let mut stdin = stdin . take ( ) . unwrap ( ) ;
use std ::os ::unix ::ffi ::OsStrExt as _ ;
// Send input into gen_init_cpio which looks something like
//
// file /init path-to-init 0755 0 0
// dir /bin 0755 0 0
// file /bin/foo path-to-foo 0755 0 0
// file /bin/bar path-to-bar 0755 0 0
for bytes in [
"file /init " . as_bytes ( ) ,
init . as_os_str ( ) . as_bytes ( ) ,
" 0755 0 0\n" . as_bytes ( ) ,
"dir /bin 0755 0 0\n" . as_bytes ( ) ,
] {
stdin . write_all ( bytes ) . expect ( "write" ) ;
}
for ( profile , binaries ) in binaries {
for ( name , binary ) in binaries {
let name = format! ( "{}-{}" , profile , name ) ;
let path = tmp_dir . path ( ) . join ( & name ) ;
copy ( & binary , & path ) . with_context ( | | {
format! ( "copy({}, {}) failed" , binary . display ( ) , path . display ( ) )
} ) ? ;
for bytes in [
"file /bin/" . as_bytes ( ) ,
name . as_bytes ( ) ,
" " . as_bytes ( ) ,
path . as_os_str ( ) . as_bytes ( ) ,
" 0755 0 0\n" . as_bytes ( ) ,
] {
stdin . write_all ( bytes ) . expect ( "write" ) ;
}
}
}
// Must explicitly close to signal EOF.
drop ( stdin ) ;
let output = gen_init_cpio_child
. wait_with_output ( )
. with_context ( | | format! ( "failed to wait for {gen_init_cpio:?}" ) ) ? ;
let Output { status , .. } = & output ;
if status . code ( ) ! = Some ( 0 ) {
bail ! ( "{gen_init_cpio:?} failed: {output:?}" )
}
let mut qemu = Command ::new ( format! ( "qemu-system-{guest_arch}" ) ) ;
if let Some ( machine ) = machine {
qemu . args ( [ "-machine" , machine ] ) ;
}
if guest_arch = = ARCH {
match OS {
"linux" = > {
const KVM : & str = "/dev/kvm" ;
match OpenOptions ::new ( ) . read ( true ) . write ( true ) . open ( KVM ) {
Ok ( _file ) = > {
qemu . args ( [ "-accel" , "kvm" ] ) ;
}
Err ( error ) = > match error . kind ( ) {
ErrorKind ::NotFound | ErrorKind ::PermissionDenied = > { }
_kind = > {
return Err ( error )
. with_context ( | | format! ( "failed to open {KVM}" ) ) ;
}
} ,
}
}
"macos" = > {
qemu . args ( [ "-accel" , "hvf" ] ) ;
}
os = > bail ! ( "unsupported OS: {os}" ) ,
}
} else if let Some ( cpu ) = cpu {
qemu . args ( [ "-cpu" , cpu ] ) ;
}
let console = OsString ::from ( "ttyS0" ) ;
let mut kernel_args = std ::iter ::once ( ( "console" , & console ) )
. chain ( run_args . clone ( ) . map ( | run_arg | ( "init.arg" , run_arg ) ) )
. enumerate ( )
. fold ( OsString ::new ( ) , | mut acc , ( i , ( k , v ) ) | {
if i ! = 0 {
acc . push ( " " ) ;
}
acc . push ( k ) ;
acc . push ( "=" ) ;
acc . push ( v ) ;
acc
} ) ;
// We sometimes see kernel panics containing:
//
// [ 0.064000] Kernel panic - not syncing: IO-APIC + timer doesn't work! Boot with apic=debug and send a report. Then try booting with the 'noapic' option.
//
// Heed the advice and boot with noapic. We don't know why this happens.
kernel_args . push ( " noapic" ) ;
qemu . args ( [ "-no-reboot" , "-nographic" , "-m" , "512M" , "-smp" , "2" ] )
. arg ( "-append" )
. arg ( kernel_args )
. arg ( "-kernel" )
. arg ( & kernel_image )
. arg ( "-initrd" )
. arg ( & initrd_image ) ;
if guest_arch = = "aarch64" {
match OS {
"linux" = > {
let mut cmd = Command ::new ( "locate" ) ;
let output = cmd
. arg ( "QEMU_EFI.fd" )
. output ( )
. with_context ( | | format! ( "failed to run {cmd:?}" ) ) ? ;
let Output { status , .. } = & output ;
if status . code ( ) ! = Some ( 0 ) {
bail ! ( "{qemu:?} failed: {output:?}" )
}
let Output { stdout , .. } = output ;
let bios = String ::from_utf8 ( stdout )
. with_context ( | | format! ( "failed to parse output of {cmd:?}" ) ) ? ;
qemu . args ( [ "-bios" , bios . trim ( ) ] ) ;
}
"macos" = > {
let mut cmd = Command ::new ( "brew" ) ;
let output = cmd
. args ( [ "list" , "qemu" , "-1" , "-v" ] )
. output ( )
. with_context ( | | format! ( "failed to run {cmd:?}" ) ) ? ;
let Output { status , .. } = & output ;
if status . code ( ) ! = Some ( 0 ) {
bail ! ( "{qemu:?} failed: {output:?}" )
}
let Output { stdout , .. } = output ;
let output = String ::from_utf8 ( stdout )
. with_context ( | | format! ( "failed to parse output of {cmd:?}" ) ) ? ;
const NAME : & str = "edk2-aarch64-code.fd" ;
let bios = output . lines ( ) . find ( | line | line . contains ( NAME ) ) . ok_or_else (
| | anyhow ! ( "failed to find {NAME} in output of {cmd:?}: {output}" ) ,
) ? ;
qemu . args ( [ "-bios" , bios . trim ( ) ] ) ;
}
os = > bail ! ( "unsupported OS: {os}" ) ,
} ;
}
let mut qemu_child = qemu
. stdin ( Stdio ::piped ( ) )
. stdout ( Stdio ::piped ( ) )
. stderr ( Stdio ::piped ( ) )
. spawn ( )
. with_context ( | | format! ( "failed to spawn {qemu:?}" ) ) ? ;
let Child {
stdin ,
stdout ,
stderr ,
..
} = & mut qemu_child ;
let stdin = stdin . take ( ) . unwrap ( ) ;
let stdin = Arc ::new ( Mutex ::new ( stdin ) ) ;
let stdout = stdout . take ( ) . unwrap ( ) ;
let stdout = BufReader ::new ( stdout ) ;
let stderr = stderr . take ( ) . unwrap ( ) ;
let stderr = BufReader ::new ( stderr ) ;
const TERMINATE_AFTER_COUNT : & [ ( & str , usize ) ] = & [
( "end Kernel panic" , 0 ) ,
( "rcu: RCU grace-period kthread stack dump:" , 0 ) ,
( "watchdog: BUG: soft lockup" , 1 ) ,
] ;
let mut counts = [ 0 ; TERMINATE_AFTER_COUNT . len ( ) ] ;
let mut terminate_if_kernel_hang =
move | line : & str , stdin : & Arc < Mutex < ChildStdin > > | -> anyhow ::Result < ( ) > {
if let Some ( i ) = TERMINATE_AFTER_COUNT
. iter ( )
. position ( | ( marker , _ ) | line . contains ( marker ) )
{
counts [ i ] + = 1 ;
let ( marker , max ) = TERMINATE_AFTER_COUNT [ i ] ;
if counts [ i ] > max {
println! ( "{marker} detected > {max} times; terminating QEMU" ) ;
let mut stdin = stdin . lock ( ) . unwrap ( ) ;
stdin
. write_all ( & [ 0x01 , b'x' ] )
. context ( "failed to write to stdin" ) ? ;
println! ( "waiting for QEMU to terminate" ) ;
}
}
Ok ( ( ) )
} ;
let stderr = {
let stdin = stdin . clone ( ) ;
thread ::Builder ::new ( )
. spawn ( move | | {
for line in stderr . lines ( ) {
let line = line . context ( "failed to read line from stderr" ) ? ;
eprintln! ( "{}" , line ) ;
terminate_if_kernel_hang ( & line , & stdin ) ? ;
}
anyhow ::Ok ( ( ) )
} )
. unwrap ( )
} ;
let mut outcome = None ;
for line in stdout . lines ( ) {
let line = line . context ( "failed to read line from stdout" ) ? ;
println! ( "{}" , line ) ;
terminate_if_kernel_hang ( & line , & stdin ) ? ;
// The init program will print "init: success" or "init: failure" to indicate
// the outcome of running the binaries it found in /bin.
if let Some ( line ) = line . strip_prefix ( "init: " ) {
let previous = match line {
"success" = > outcome . replace ( Ok ( ( ) ) ) ,
"failure" = > outcome . replace ( Err ( ( ) ) ) ,
line = > bail ! ( "unexpected init output: {}" , line ) ,
} ;
if let Some ( previous ) = previous {
bail ! ( "multiple exit status: previous={previous:?}, current={line}" ) ;
}
}
}
let output = qemu_child
. wait_with_output ( )
. with_context ( | | format! ( "failed to wait for {qemu:?}" ) ) ? ;
let Output { status , .. } = & output ;
if status . code ( ) ! = Some ( 0 ) {
bail ! ( "{qemu:?} failed: {output:?}" )
}
stderr . join ( ) . unwrap ( ) ? ;
let outcome = outcome . ok_or ( anyhow ! ( "init did not exit" ) ) ? ;
match outcome {
Ok ( ( ) ) = > { }
Err ( ( ) ) = > {
errors . push ( anyhow ! ( "VM binaries failed on {}" , kernel_image . display ( ) ) )
}
}
}
if errors . is_empty ( ) {
Ok ( ( ) )
} else {
xtask: extract `Errors` type
This produces better errors in `cargo xtask public-api` because before
this change we'd print errors as display, which didn't show context.
Before:
```
Error: public API errors:
aya failed to check public API: public_api::Builder::build
aya-obj failed to check public API: public_api::Builder::build
aya-log failed to check public API: public_api::Builder::build
aya-log-common failed to check public API: public_api::Builder::build
aya-log-parser failed to check public API: public_api::Builder::build
aya-tool failed to check public API: public_api::Builder::build
aya-bpf failed to check public API: public_api::Builder::build
aya-bpf-bindings failed to check public API: public_api::Builder::build
aya-bpf-cty failed to check public API: public_api::Builder::build
aya-bpf-macros failed to check public API: public_api::Builder::build
aya-log-ebpf-macros failed to check public API: public_api::Builder::build
```
After:
```
Error: aya failed to check public API
Caused by:
0: public_api::Builder::build
1: unknown variant `type_alias`, expected one of `module`, `extern_crate`, `import`, `struct`, `struct_field`, `union`, `enum`, `variant`, `function`, `typedef`, `opaque_ty`, `constant`, `trait`, `trait_alias`, `impl`, `static`, `foreign_type`, `macro`, `proc_attribute`, `proc_derive`, `assoc_const`, `assoc_type`, `primitive`, `keyword` at line 1 column 3082463
aya-obj failed to check public API
Caused by:
0: public_api::Builder::build
1: unknown variant `type_alias`, expected one of `module`, `extern_crate`, `import`, `union`, `struct`, `struct_field`, `enum`, `variant`, `function`, `trait`, `trait_alias`, `impl`, `typedef`, `opaque_ty`, `constant`, `static`, `foreign_type`, `macro`, `proc_macro`, `primitive`, `assoc_const`, `assoc_type` at line 1 column 129355
aya-log failed to check public API
Caused by:
0: public_api::Builder::build
1: unknown variant `type_alias`, expected one of `module`, `extern_crate`, `import`, `struct`, `struct_field`, `union`, `enum`, `variant`, `function`, `typedef`, `opaque_ty`, `constant`, `trait`, `trait_alias`, `impl`, `static`, `foreign_type`, `macro`, `proc_attribute`, `proc_derive`, `assoc_const`, `assoc_type`, `primitive`, `keyword` at line 1 column 311910
aya-log-common failed to check public API
Caused by:
0: public_api::Builder::build
1: unknown variant `type_alias`, expected one of `module`, `extern_crate`, `import`, `union`, `struct`, `struct_field`, `enum`, `variant`, `function`, `trait`, `trait_alias`, `impl`, `typedef`, `opaque_ty`, `constant`, `static`, `foreign_type`, `macro`, `proc_macro`, `primitive`, `assoc_const`, `assoc_type` at line 1 column 130456
aya-log-parser failed to check public API
Caused by:
0: public_api::Builder::build
1: unknown variant `type_alias`, expected one of `module`, `extern_crate`, `import`, `struct`, `struct_field`, `union`, `enum`, `variant`, `function`, `typedef`, `opaque_ty`, `constant`, `trait`, `trait_alias`, `impl`, `static`, `foreign_type`, `macro`, `proc_attribute`, `proc_derive`, `assoc_const`, `assoc_type`, `primitive`, `keyword` at line 1 column 204930
aya-tool failed to check public API
Caused by:
0: public_api::Builder::build
1: unknown variant `type_alias`, expected one of `module`, `extern_crate`, `import`, `struct`, `struct_field`, `union`, `enum`, `variant`, `function`, `typedef`, `opaque_ty`, `constant`, `trait`, `trait_alias`, `impl`, `static`, `foreign_type`, `macro`, `proc_attribute`, `proc_derive`, `assoc_const`, `assoc_type`, `primitive`, `keyword` at line 1 column 234519
aya-bpf failed to check public API
Caused by:
0: public_api::Builder::build
1: unknown variant `type_alias`, expected one of `module`, `extern_crate`, `import`, `struct`, `struct_field`, `union`, `enum`, `variant`, `function`, `typedef`, `opaque_ty`, `constant`, `trait`, `trait_alias`, `impl`, `static`, `foreign_type`, `macro`, `proc_attribute`, `proc_derive`, `assoc_const`, `assoc_type`, `primitive`, `keyword` at line 1 column 741697
aya-bpf-bindings failed to check public API
Caused by:
0: public_api::Builder::build
1: unknown variant `type_alias`, expected one of `module`, `extern_crate`, `import`, `union`, `struct`, `struct_field`, `enum`, `variant`, `function`, `trait`, `trait_alias`, `impl`, `typedef`, `opaque_ty`, `constant`, `static`, `foreign_type`, `macro`, `proc_macro`, `primitive`, `assoc_const`, `assoc_type` at line 1 column 112445
aya-bpf-cty failed to check public API
Caused by:
0: public_api::Builder::build
1: unknown variant `type_alias`, expected one of `module`, `extern_crate`, `import`, `union`, `struct`, `struct_field`, `enum`, `variant`, `function`, `trait`, `trait_alias`, `impl`, `typedef`, `opaque_ty`, `constant`, `static`, `foreign_type`, `macro`, `proc_macro`, `primitive`, `assoc_const`, `assoc_type` at line 1 column 315
aya-bpf-macros failed to check public API
Caused by:
0: public_api::Builder::build
1: unknown variant `type_alias`, expected one of `module`, `extern_crate`, `import`, `struct`, `struct_field`, `union`, `enum`, `variant`, `function`, `typedef`, `opaque_ty`, `constant`, `trait`, `trait_alias`, `impl`, `static`, `foreign_type`, `macro`, `proc_attribute`, `proc_derive`, `assoc_const`, `assoc_type`, `primitive`, `keyword` at line 1 column 186226
aya-log-ebpf-macros failed to check public API
Caused by:
0: public_api::Builder::build
1: unknown variant `type_alias`, expected one of `module`, `extern_crate`, `import`, `struct`, `struct_field`, `union`, `enum`, `variant`, `function`, `typedef`, `opaque_ty`, `constant`, `trait`, `trait_alias`, `impl`, `static`, `foreign_type`, `macro`, `proc_attribute`, `proc_derive`, `assoc_const`, `assoc_type`, `primitive`, `keyword` at line 1 column 158055
```
1 year ago
Err ( Errors ::new ( errors ) . into ( ) )
}
}
}
}