diff --git a/.gitignore b/.gitignore index e6e36d3b1b..0827f2628f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ target .liquid *.dot log +sysroot/ diff --git a/lib/flux-rs/Cargo.toml b/lib/flux-rs/Cargo.toml index bf1b2c59a7..183657bce4 100644 --- a/lib/flux-rs/Cargo.toml +++ b/lib/flux-rs/Cargo.toml @@ -9,3 +9,6 @@ flux-attrs = { path = "../flux-attrs", version = "0.1.0" } [lints] workspace = true + +[package.metadata.flux] +enabled = true diff --git a/lib/flux-rs/src/lib.rs b/lib/flux-rs/src/lib.rs index 4a0c3d980c..a9b572e1e5 100644 --- a/lib/flux-rs/src/lib.rs +++ b/lib/flux-rs/src/lib.rs @@ -1 +1,4 @@ pub use flux_attrs::*; + +#[sig(fn(bool[true]) )] +pub fn assert(_: bool) {} diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 4551bdbc2e..9b6ad8bc95 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -1,30 +1,7 @@ -#![feature(register_tool)] -use std::path::PathBuf; - pub const FLUX_SYSROOT: &str = "FLUX_SYSROOT"; pub const FLUX_FULL_COMPILATION: &str = "FLUX_FULL_COMPILATION"; -pub fn find_flux_path() -> PathBuf { - let executable_name = if cfg!(windows) { "rustc-flux.exe" } else { "rustc-flux" }; - find_file_in_target_dir(executable_name) -} - /// Rustc flags to pass Flux when running tests pub fn default_rustc_flags() -> Vec { vec!["--crate-type=rlib".to_string(), "--edition=2021".to_string()] } - -fn find_file_in_target_dir(file: &str) -> PathBuf { - let target_directory = if cfg!(debug_assertions) { "debug" } else { "release" }; - let local_path: PathBuf = ["target", target_directory, file].into_iter().collect(); - if local_path.exists() { - return local_path; - } - let workspace_path: PathBuf = ["..", "target", target_directory, file] - .into_iter() - .collect(); - if workspace_path.exists() { - return workspace_path; - } - panic!("Could not find {file}"); -} diff --git a/tests/tests/compiletest.rs b/tests/tests/compiletest.rs index 12a429c85f..c2ed24931f 100644 --- a/tests/tests/compiletest.rs +++ b/tests/tests/compiletest.rs @@ -5,19 +5,53 @@ use std::{env, path::PathBuf}; use compiletest_rs::{common::Mode, Config}; use itertools::Itertools; -use tests::{default_rustc_flags, find_flux_path, FLUX_FULL_COMPILATION, FLUX_SYSROOT}; - -fn config() -> Config { - let bless = env::args().any(|arg| arg == "--bless"); - let filters = env::args() - .tuple_windows() - .filter_map(|(arg, val)| if arg == "--test-args" { Some(val) } else { None }) - .collect_vec(); - Config { rustc_path: find_flux_path(), filters, bless, ..Config::default() } +use tests::{default_rustc_flags, FLUX_FULL_COMPILATION, FLUX_SYSROOT}; + +#[derive(Debug)] +struct Args { + filters: Vec, + flux: PathBuf, + sysroot: PathBuf, +} + +impl Args { + fn parse() -> Args { + let mut filters = vec![]; + let mut sysroot = None; + let mut flux = None; + for (arg, val) in env::args().tuple_windows() { + match &arg[..] { + "--filter" => { + filters.push(val); + } + "--flux" => { + if flux.is_some() { + panic!("option '--flux' given more than once"); + } + flux = Some(val); + } + "--sysroot" => { + if sysroot.is_some() { + panic!("option '--sysroot' given more than once"); + } + sysroot = Some(val); + } + _ => {} + } + } + let Some(flux) = flux else { + panic!("option '--flux' must be provided"); + }; + let Some(sysroot) = sysroot else { + panic!("option '--sysroot' must be provided"); + }; + Args { filters, flux: PathBuf::from(flux), sysroot: PathBuf::from(sysroot) } + } } fn test_runner(_: &[&()]) { - let mut config = config().tempdir(); + let args = Args::parse(); + let mut config = Config { rustc_path: args.flux, filters: args.filters, ..Config::default() }; let mut rustc_flags = default_rustc_flags(); @@ -32,7 +66,7 @@ fn test_runner(_: &[&()]) { // Force full compilation to make sure we generate artifacts when annotating tests with `@aux-build` env::set_var(FLUX_FULL_COMPILATION, "1"); - env::set_var(FLUX_SYSROOT, config.rustc_path.parent().unwrap()); + env::set_var(FLUX_SYSROOT, &args.sysroot); let path: PathBuf = ["tests", "pos"].iter().collect(); if path.exists() { diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 9b7fafc8ee..9fc9f054c5 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,10 +1,16 @@ use std::{ env, + ffi::OsStr, path::{Path, PathBuf}, }; -use tests::{find_flux_path, FLUX_SYSROOT}; -use xshell::{cmd, Shell}; +use anyhow::anyhow; +use cargo_metadata::{ + camino::{Utf8Path, Utf8PathBuf}, + Artifact, Message, TargetKind, +}; +use tests::FLUX_SYSROOT; +use xshell::{cmd, PushEnv, Shell}; xflags::xflags! { cmd xtask { @@ -70,26 +76,28 @@ fn main() -> anyhow::Result<()> { XtaskCmd::Run(args) => run(sh, args), XtaskCmd::Install(args) => install(&sh, &args, &extra), XtaskCmd::Doc(args) => doc(sh, args), - XtaskCmd::BuildSysroot(_) => build_sysroot(&sh), + XtaskCmd::BuildSysroot(_) => { + install_sysroot(&sh, false, &local_sysroot_dir()?)?; + Ok(()) + } XtaskCmd::Uninstall(_) => uninstall(&sh), XtaskCmd::Expand(args) => expand(&sh, args), } } -fn prepare(sh: &Shell) -> Result<(), anyhow::Error> { - build_sysroot(sh)?; - cmd!(sh, "cargo build").run()?; - Ok(()) -} - fn test(sh: Shell, args: Test) -> anyhow::Result<()> { + let sysroot = local_sysroot_dir()?; let Test { filter } = args; - prepare(&sh)?; - if let Some(filter) = filter { - cmd!(sh, "cargo test -p tests -- --test-args {filter}").run()?; - } else { - cmd!(sh, "cargo test -p tests").run()?; + let flux = build_binary("rustc-flux", false)?; + install_sysroot(&sh, false, &sysroot)?; + + let mut cmd = cmd!(sh, "cargo test -p tests -- --flux {flux} --sysroot {sysroot}"); + + if let Some(filter) = &filter { + cmd = cmd.args(["--filter", filter]); } + cmd.run()?; + Ok(()) } @@ -114,66 +122,34 @@ fn run_inner( input: PathBuf, flags: impl IntoIterator, ) -> Result<(), anyhow::Error> { - prepare(sh)?; - let flux_path = find_flux_path(); - let _env = sh.push_env(FLUX_SYSROOT, flux_path.parent().unwrap()); + let sysroot = local_sysroot_dir()?; + + install_sysroot(sh, false, &sysroot)?; + let flux = build_binary("rustc-flux", false)?; + + let _env = push_env(sh, FLUX_SYSROOT, &sysroot); let mut rustc_flags = tests::default_rustc_flags(); rustc_flags.extend(flags); - cmd!(sh, "{flux_path} {rustc_flags...} {input}").run()?; + cmd!(sh, "{flux} {rustc_flags...} {input}").run()?; Ok(()) } fn install(sh: &Shell, args: &Install, extra: &[&str]) -> anyhow::Result<()> { + install_sysroot(sh, args.is_release(), &default_sysroot_dir())?; cmd!(sh, "cargo install --path crates/flux-bin --force {extra...}").run()?; - install_driver(sh, args, extra)?; - install_libs(sh, args, extra)?; - - Ok(()) -} - -fn install_driver(sh: &Shell, args: &Install, extra: &[&str]) -> anyhow::Result<()> { - let out_dir = default_sysroot_dir(); - if args.is_release() { - cmd!(sh, "cargo build -Zunstable-options --bin flux-driver --release --artifact-dir {out_dir} {extra...}") - .run()?; - } else { - cmd!( - sh, - "cargo build -Zunstable-options --bin flux-driver --artifact-dir {out_dir} {extra...}" - ) - .run()?; - } - Ok(()) -} - -fn install_libs(sh: &Shell, args: &Install, extra: &[&str]) -> anyhow::Result<()> { - let _env = sh.push_env("FLUX_BUILD_SYSROOT", "1"); - println!("$ export FLUX_BUILD_SYSROOT=1"); - - let out_dir = default_sysroot_dir(); - if args.is_release() { - cmd!( - sh, - "cargo build -Zunstable-options --release -p flux-rs --artifact-dir {out_dir} {extra...}" - ) - .run()?; - } else { - cmd!(sh, "cargo build -Zunstable-options -p flux-rs --artifact-dir {out_dir} {extra...}") - .run()?; - } Ok(()) } fn uninstall(sh: &Shell) -> anyhow::Result<()> { cmd!(sh, "cargo uninstall -p flux-bin").run()?; - println!("$ rm -rf ~/.flux"); - std::fs::remove_dir_all(default_sysroot_dir())?; + eprintln!("$ rm -rf ~/.flux"); + sh.remove_path(default_sysroot_dir())?; Ok(()) } fn doc(sh: Shell, args: Doc) -> anyhow::Result<()> { - let _env = sh.push_env("RUSTDOCFLAGS", "-Zunstable-options --enable-index-page"); + let _env = push_env(&sh, "RUSTDOCFLAGS", "-Zunstable-options --enable-index-page"); cmd!(sh, "cargo doc --workspace --document-private-items --no-deps").run()?; if args.open { opener::open("target/doc/index.html")?; @@ -191,13 +167,64 @@ fn project_root() -> PathBuf { .to_path_buf() } -fn build_sysroot(sh: &Shell) -> anyhow::Result<()> { - let _env = sh.push_env("FLUX_BUILD_SYSROOT", "1"); - println!("$ export FLUX_BUILD_SYSROOT=1"); - cmd!(sh, "cargo build -p flux-rs").run()?; +fn build_binary(bin: &str, release: bool) -> anyhow::Result { + run_cargo("cargo", |cmd| { + cmd.args(["build", "--bin", bin]); + if release { + cmd.arg("--release"); + } + cmd + })? + .into_iter() + .find(|artifact| artifact.target.name == bin && artifact.target.is_kind(TargetKind::Bin)) + .ok_or_else(|| anyhow!("cannot find binary: `{bin}`"))? + .executable + .ok_or_else(|| anyhow!("cannot find binary: `{bin}")) +} + +fn install_sysroot(sh: &Shell, release: bool, sysroot: &Path) -> anyhow::Result<()> { + sh.remove_path(sysroot)?; + sh.create_dir(sysroot)?; + + copy_file(sh, build_binary("flux-driver", release)?, sysroot)?; + + let artifacts = run_cargo(build_binary("cargo-flux", release)?, |cmd| { + cmd.args(["flux", "-p", "flux-rs"]) + .env(FLUX_SYSROOT, sysroot) + })?; + + copy_artifacts(sh, &artifacts, sysroot)?; + Ok(()) +} + +fn copy_artifacts(sh: &Shell, artifacts: &[Artifact], sysroot: &Path) -> anyhow::Result<()> { + for artifact in artifacts { + if !is_flux_lib(artifact) { + continue; + } + + for filename in &artifact.filenames { + copy_artifact(sh, filename, sysroot)?; + } + } Ok(()) } +fn copy_artifact(sh: &Shell, filename: &Utf8Path, dst: &Path) -> anyhow::Result<()> { + copy_file(sh, filename, dst)?; + if filename.extension() == Some("rmeta") { + let fluxmeta = filename.with_extension("fluxmeta"); + if sh.path_exists(&fluxmeta) { + copy_file(sh, &fluxmeta, dst)?; + } + } + Ok(()) +} + +fn is_flux_lib(artifact: &Artifact) -> bool { + ["flux_rs", "flux_attrs"].contains(&&artifact.target.name[..]) +} + impl Install { fn is_release(&self) -> bool { !self.debug @@ -209,3 +236,76 @@ fn default_sysroot_dir() -> PathBuf { .expect("Couldn't find home directory") .join(".flux") } + +fn local_sysroot_dir() -> anyhow::Result { + Ok(Path::new(file!()) + .canonicalize()? + .ancestors() + .nth(3) + .unwrap() + .join("sysroot")) +} + +fn run_cargo>( + cargo_path: S, + f: impl FnOnce(&mut std::process::Command) -> &mut std::process::Command, +) -> anyhow::Result> { + let mut cmd = std::process::Command::new(cargo_path); + + f(&mut cmd); + + cmd.arg("--message-format=json-render-diagnostics") + .stdout(std::process::Stdio::piped()); + + display_command(&cmd); + + let mut child = cmd.spawn()?; + + let mut artifacts = vec![]; + let reader = std::io::BufReader::new(child.stdout.take().unwrap()); + for message in cargo_metadata::Message::parse_stream(reader) { + match message.unwrap() { + Message::CompilerMessage(msg) => { + println!("{msg}"); + } + Message::CompilerArtifact(artifact) => { + artifacts.push(artifact); + } + _ => (), + } + } + + child.wait()?; + + Ok(artifacts) +} + +fn display_command(cmd: &std::process::Command) { + for var in cmd.get_envs() { + if let Some(val) = var.1 { + eprintln!("$ export {}={}", var.0.to_string_lossy(), val.to_string_lossy()); + } + } + + let prog = cmd.get_program(); + eprint!("$ {}", prog.to_string_lossy()); + for arg in cmd.get_args() { + eprint!(" {}", arg.to_string_lossy()); + } + eprintln!(); +} + +fn push_env, V: AsRef>(sh: &Shell, key: K, val: V) -> PushEnv { + let key = key.as_ref(); + let val = val.as_ref(); + eprintln!("$ export {}={}", key.to_string_lossy(), val.to_string_lossy()); + sh.push_env(key, val) +} + +fn copy_file, D: AsRef>(sh: &Shell, src: S, dst: D) -> anyhow::Result<()> { + let src = src.as_ref(); + let dst = dst.as_ref(); + eprintln!("$ cp {} {}", src.to_string_lossy(), dst.to_string_lossy()); + sh.copy_file(src, dst)?; + Ok(()) +}