diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a59ae19b..0f132a4d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Re-enabled `sparc64-unknown-linux-gnu` image - #582 - Added `libprocstat.so` to FreeBSD images - #665 - when not using [env.volumes](https://github.com/cross-rs/cross#mounting-volumes-into-the-build-environment), mount project in /project +- #494 - Parse Cargo's --manifest-path option to determine mounted docker root ### Removed diff --git a/README.md b/README.md index a6adcdb76..becb8b9d6 100644 --- a/README.md +++ b/README.md @@ -367,6 +367,9 @@ $ QEMU_STRACE=1 cross run --target aarch64-unknown-linux-gnu - path dependencies (in Cargo.toml) that point outside the Cargo project won't work because `cross` use docker containers only mounts the Cargo project so the container doesn't have access to the rest of the filesystem. + However, you may use Cargo's `--manifest-path` option to reference your + target crate, executed from a common root directory from which all your + dependencies are available. ## Minimum Supported Rust Version (MSRV) diff --git a/src/cargo.rs b/src/cargo.rs index 654ed580a..0a288add2 100644 --- a/src/cargo.rs +++ b/src/cargo.rs @@ -1,9 +1,12 @@ use serde::Deserialize; +use std::io::Write; use std::path::{Path, PathBuf}; use std::process::{Command, ExitStatus}; +use crate::cli::Args; +use crate::config::Config; use crate::errors::*; -use crate::extensions::CommandExt; +use crate::extensions::{CommandExt, OutputExt}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Subcommand { @@ -52,42 +55,63 @@ impl<'a> From<&'a str> for Subcommand { } } -#[derive(Debug)] -pub struct Root { - pub path: PathBuf, +#[derive(Debug, Deserialize)] +pub struct CargoMetadata { + pub workspace_root: PathBuf, } -impl Root { - pub fn path(&self) -> &Path { - &self.path +impl CargoMetadata { + pub fn workspace_root(&self) -> &Path { + &self.workspace_root } } -/// Cargo project root -pub fn root(cd: Option<&Path>) -> Result> { - #[derive(Deserialize)] - struct Manifest { - workspace_root: PathBuf, - } +/// Cargo metadata with specific invocation +pub fn cargo_metadata_with_args( + cd: Option<&Path>, + args: Option<&Args>, +) -> Result> { let mut command = std::process::Command::new( std::env::var("CARGO") .ok() .unwrap_or_else(|| "cargo".to_string()), ); - command - .arg("metadata") - .arg("--format-version=1") - .arg("--no-deps"); + command.arg("metadata").arg("--format-version=1"); + if let Some(cd) = cd { + command.current_dir(cd); + } + if let Some(config) = args { + if let Some(ref manifest_path) = config.manifest_path { + command.args(["--manifest-path".as_ref(), manifest_path.as_os_str()]); + } + } else { + command.arg("--no-deps"); + } + #[derive(Deserialize)] + struct Manifest { + workspace_root: PathBuf, + } if let Some(cd) = cd { command.current_dir(cd); } - let output = command.output()?; + let output = command.run_and_get_output(false)?; + if !output.status.success() { + let mut stderr = std::io::stderr(); + stderr.write_all(&output.stderr)?; + stderr.flush()?; + std::process::exit(output.status.code().unwrap_or(1)); + } let manifest: Option = serde_json::from_slice(&output.stdout)?; - Ok(manifest.map(|m| Root { - path: m.workspace_root, + Ok(manifest.map(|m| CargoMetadata { + workspace_root: m.workspace_root, })) } +/// Cargo metadata +pub fn cargo_metadata(cd: Option<&Path>) -> Result> { + cargo_metadata_with_args(cd, None) +} + /// Pass-through mode pub fn run(args: &[String], verbose: bool) -> Result { Command::new("cargo").args(args).run_and_get_status(verbose) diff --git a/src/cli.rs b/src/cli.rs index 64f78e47f..596b6a46d 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -15,6 +15,7 @@ pub struct Args { pub target_dir: Option, pub docker_in_docker: bool, pub enable_doctests: bool, + pub manifest_path: Option, } // Fix for issue #581. target_dir must be absolute. @@ -72,6 +73,7 @@ pub fn fmt_subcommands(stdout: &str) { pub fn parse(target_list: &TargetList) -> Result { let mut channel = None; let mut target = None; + let mut manifest_path: Option = None; let mut target_dir = None; let mut sc = None; let mut all: Vec = Vec::new(); @@ -82,7 +84,21 @@ pub fn parse(target_list: &TargetList) -> Result { if arg.is_empty() { continue; } - if let ("+", ch) = arg.split_at(1) { + if arg == "--manifest-path" { + all.push(arg); + if let Some(m) = args.next() { + let p = PathBuf::from(&m); + all.push(m); + manifest_path = env::current_dir().ok().map(|cwd| cwd.join(p)); + } + } else if arg.starts_with("--manifest-path=") { + manifest_path = arg + .split_once('=') + .map(|x| x.1) + .map(PathBuf::from) + .and_then(|p| env::current_dir().ok().map(|cwd| cwd.join(p))); + all.push(arg); + } else if let ("+", ch) = arg.split_at(1) { channel = Some(ch.to_string()); } else if arg == "--target" { all.push(arg); @@ -131,5 +147,6 @@ pub fn parse(target_list: &TargetList) -> Result { target_dir, docker_in_docker, enable_doctests, + manifest_path, }) } diff --git a/src/docker.rs b/src/docker.rs index bccb1edf1..40d9b1732 100644 --- a/src/docker.rs +++ b/src/docker.rs @@ -2,7 +2,7 @@ use std::path::{Path, PathBuf}; use std::process::{Command, ExitStatus}; use std::{env, fs}; -use crate::cargo::Root; +use crate::cargo::CargoMetadata; use crate::errors::*; use crate::extensions::{CommandExt, SafeCommand}; use crate::id; @@ -70,12 +70,13 @@ pub fn run( target: &Target, args: &[String], target_dir: &Option, - root: &Root, + metadata: &CargoMetadata, config: &Config, uses_xargo: bool, sysroot: &Path, verbose: bool, docker_in_docker: bool, + cwd: &Path, ) -> Result { let mount_finder = if docker_in_docker { MountFinder::new(docker_read_mount_paths()?) @@ -83,14 +84,15 @@ pub fn run( MountFinder::default() }; - let root = root.path(); let home_dir = home::home_dir().ok_or_else(|| eyre::eyre!("could not find home directory"))?; let cargo_dir = home::cargo_home()?; let xargo_dir = env::var_os("XARGO_HOME") .map(PathBuf::from) .unwrap_or_else(|| home_dir.join(".xargo")); let nix_store_dir = env::var_os("NIX_STORE").map(PathBuf::from); - let target_dir = target_dir.clone().unwrap_or_else(|| root.join("target")); + let target_dir = target_dir + .clone() + .unwrap_or_else(|| metadata.workspace_root().join("target")); // create the directories we are going to mount before we mount them, // otherwise `docker` will create them but they will be owned by `root` @@ -102,7 +104,12 @@ pub fn run( let cargo_dir = mount_finder.find_mount_path(cargo_dir); let xargo_dir = mount_finder.find_mount_path(xargo_dir); let target_dir = mount_finder.find_mount_path(target_dir); - let host_root = mount_finder.find_mount_path(root); + // root is either workspace_root, or, if we're outside the workspace root, the current directory + let host_root = mount_finder.find_mount_path(if metadata.workspace_root().starts_with(cwd) { + cwd + } else { + metadata.workspace_root() + }); let mount_root: PathBuf; #[cfg(target_os = "windows")] { @@ -117,11 +124,11 @@ pub fn run( #[cfg(target_os = "windows")] { // On Windows, we can not mount the directory name directly. Instead, we use wslpath to convert the path to a linux compatible path. - mount_cwd = wslpath(&std::env::current_dir()?, verbose)?; + mount_cwd = wslpath(&cwd, verbose)?; } #[cfg(not(target_os = "windows"))] { - mount_cwd = mount_finder.find_mount_path(std::env::current_dir()?); + mount_cwd = mount_finder.find_mount_path(cwd); } let sysroot = mount_finder.find_mount_path(sysroot); @@ -145,6 +152,12 @@ pub fn run( docker.args(&["-e", var]); } let mut env_volumes = false; + // FIXME(emilgardis 2022-04-07): This is a fallback so that if it's hard for use to do mounting logic, make it simple(r) + // Preferably we would not have to do this. + if cwd.strip_prefix(metadata.workspace_root()).is_err() { + env_volumes = true; + } + for ref var in config.env_volumes(target)? { let (var, value) = validate_env_var(var)?; let value = match value { @@ -247,12 +260,12 @@ pub fn run( if env_volumes { docker.args(&["-w".as_ref(), mount_cwd.as_os_str()]); - } else if mount_cwd == root { + } else if mount_cwd == metadata.workspace_root() { docker.args(&["-w", "/project"]); } else { // We do this to avoid clashes with path separators. Windows uses `\` as a path separator on Path::join - let cwd = &std::env::current_dir()?; - let working_dir = Path::new("project").join(cwd.strip_prefix(root)?); + let cwd = &cwd; + let working_dir = Path::new("project").join(cwd.strip_prefix(metadata.workspace_root())?); // No [T].join for OsStr let mut mount_wd = std::ffi::OsString::new(); for part in working_dir.iter() { diff --git a/src/main.rs b/src/main.rs index 0012ffa6c..d470001c8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,7 +25,7 @@ use config::Config; use rustc_version::Channel; use serde::Deserialize; -use self::cargo::{Root, Subcommand}; +use self::cargo::{CargoMetadata, Subcommand}; use self::cross_toml::CrossToml; use self::errors::*; use self::extensions::OutputExt; @@ -282,9 +282,10 @@ fn run() -> Result { let host_version_meta = rustc_version::version_meta().wrap_err("couldn't fetch the `rustc` version")?; - if let Some(root) = cargo::root(None)? { + let cwd = std::env::current_dir()?; + if let Some(metadata) = cargo::cargo_metadata_with_args(Some(&cwd), Some(&args))? { let host = host_version_meta.host(); - let toml = toml(&root)?; + let toml = toml(&metadata)?; let config = Config::new(toml); let target = args .target @@ -407,16 +408,18 @@ fn run() -> Result { docker::register(&target, verbose)? } + let docker_root = env::current_dir()?; return docker::run( &target, &filtered_args, &args.target_dir, - &root, + &metadata, &config, uses_xargo, &sysroot, verbose, args.docker_in_docker, + &cwd, ); } } @@ -489,10 +492,10 @@ pub(crate) fn warn_host_version_mismatch( /// Parses the `Cross.toml` at the root of the Cargo project or from the /// `CROSS_CONFIG` environment variable (if any exist in either location). -fn toml(root: &Root) -> Result> { +fn toml(root: &CargoMetadata) -> Result> { let path = match env::var("CROSS_CONFIG") { Ok(var) => PathBuf::from(var), - Err(_) => root.path().join("Cross.toml"), + Err(_) => root.workspace_root().join("Cross.toml"), }; if path.exists() { @@ -505,7 +508,7 @@ fn toml(root: &Root) -> Result> { Ok(Some(config)) } else { // Checks if there is a lowercase version of this file - if root.path().join("cross.toml").exists() { + if root.workspace_root().join("cross.toml").exists() { eprintln!("There's a file named cross.toml, instead of Cross.toml. You may want to rename it, or it won't be considered."); } Ok(None) diff --git a/src/tests.rs b/src/tests.rs index 6970ff077..565935da2 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -7,7 +7,6 @@ use std::{ use once_cell::sync::OnceCell; use rustc_version::VersionMeta; -use serde::Deserialize; static WORKSPACE: OnceCell = OnceCell::new(); @@ -15,10 +14,10 @@ static WORKSPACE: OnceCell = OnceCell::new(); pub fn get_cargo_workspace() -> &'static Path { let manifest_dir = env!("CARGO_MANIFEST_DIR"); WORKSPACE.get_or_init(|| { - crate::cargo::root(Some(manifest_dir.as_ref())) + crate::cargo::cargo_metadata(Some(manifest_dir.as_ref())) .unwrap() .unwrap() - .path + .workspace_root }) }