Skip to content

Commit

Permalink
feat: Parse Cargo's --manifest-path option to determine mounted docker
Browse files Browse the repository at this point in the history
root

This commits adds support for parsing the `--manifest-path` option to cross. So
far, the `Cargo.toml` manifest of the crate (or its Cargo workspace) to compile
has been assumed to be in the current working directory. This means, that
relative crate dependencies were not supported, because those paths were not
mapped into the docker container.

Take the following example structure, where `my-bin` depends on `my-lib`:
.
├── my-bin
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── my-lib
    ├── Cargo.toml
    └── src
        └── lib.rs

This commits enables such scenarios, by running cross from `.` like so:
`cross build --manifest-path=my-lib/Cargo.toml --target x86_64-pc-windows-gnu`,
as `.` is mapped as the container's root, and the options passed through to
Cargo.

Related #388 #139 #277 #78

Co-authored-by: Kviring Alexey <alex@kviring.com>
  • Loading branch information
2 people authored and Emilgardis committed Jun 3, 2022
1 parent 0eecee1 commit 09e5d0e
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 41 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
64 changes: 44 additions & 20 deletions src/cargo.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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<Option<Root>> {
#[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<Option<CargoMetadata>> {
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<Manifest> = 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<Option<CargoMetadata>> {
cargo_metadata_with_args(cd, None)
}

/// Pass-through mode
pub fn run(args: &[String], verbose: bool) -> Result<ExitStatus> {
Command::new("cargo").args(args).run_and_get_status(verbose)
Expand Down
19 changes: 18 additions & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub struct Args {
pub target_dir: Option<PathBuf>,
pub docker_in_docker: bool,
pub enable_doctests: bool,
pub manifest_path: Option<PathBuf>,
}

// Fix for issue #581. target_dir must be absolute.
Expand Down Expand Up @@ -72,6 +73,7 @@ pub fn fmt_subcommands(stdout: &str) {
pub fn parse(target_list: &TargetList) -> Result<Args> {
let mut channel = None;
let mut target = None;
let mut manifest_path: Option<PathBuf> = None;
let mut target_dir = None;
let mut sc = None;
let mut all: Vec<String> = Vec::new();
Expand All @@ -82,7 +84,21 @@ pub fn parse(target_list: &TargetList) -> Result<Args> {
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);
Expand Down Expand Up @@ -131,5 +147,6 @@ pub fn parse(target_list: &TargetList) -> Result<Args> {
target_dir,
docker_in_docker,
enable_doctests,
manifest_path,
})
}
33 changes: 23 additions & 10 deletions src/docker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -70,27 +70,29 @@ pub fn run(
target: &Target,
args: &[String],
target_dir: &Option<PathBuf>,
root: &Root,
metadata: &CargoMetadata,
config: &Config,
uses_xargo: bool,
sysroot: &Path,
verbose: bool,
docker_in_docker: bool,
cwd: &Path,
) -> Result<ExitStatus> {
let mount_finder = if docker_in_docker {
MountFinder::new(docker_read_mount_paths()?)
} else {
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`
Expand All @@ -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")]
{
Expand All @@ -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);

Expand All @@ -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 {
Expand Down Expand Up @@ -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() {
Expand Down
17 changes: 10 additions & 7 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -282,9 +282,10 @@ fn run() -> Result<ExitStatus> {

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
Expand Down Expand Up @@ -407,16 +408,18 @@ fn run() -> Result<ExitStatus> {
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,
);
}
}
Expand Down Expand Up @@ -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<Option<CrossToml>> {
fn toml(root: &CargoMetadata) -> Result<Option<CrossToml>> {
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() {
Expand All @@ -505,7 +508,7 @@ fn toml(root: &Root) -> Result<Option<CrossToml>> {
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)
Expand Down
5 changes: 2 additions & 3 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,17 @@ use std::{

use once_cell::sync::OnceCell;
use rustc_version::VersionMeta;
use serde::Deserialize;

static WORKSPACE: OnceCell<PathBuf> = OnceCell::new();

/// Returns the cargo workspace for the manifest
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
})
}

Expand Down

0 comments on commit 09e5d0e

Please sign in to comment.