Skip to content

Commit 09e5d0e

Browse files
wngravkviring
authored andcommitted
feat: Parse Cargo's --manifest-path option to determine mounted docker
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 cross-rs#388 cross-rs#139 cross-rs#277 cross-rs#78 Co-authored-by: Kviring Alexey <[email protected]>
1 parent 0eecee1 commit 09e5d0e

File tree

7 files changed

+101
-41
lines changed

7 files changed

+101
-41
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
4545
- Re-enabled `sparc64-unknown-linux-gnu` image
4646
- #582 - Added `libprocstat.so` to FreeBSD images
4747
- #665 - when not using [env.volumes](https://github.com/cross-rs/cross#mounting-volumes-into-the-build-environment), mount project in /project
48+
- #494 - Parse Cargo's --manifest-path option to determine mounted docker root
4849

4950

5051
### Removed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,9 @@ $ QEMU_STRACE=1 cross run --target aarch64-unknown-linux-gnu
367367
- path dependencies (in Cargo.toml) that point outside the Cargo project won't
368368
work because `cross` use docker containers only mounts the Cargo project so
369369
the container doesn't have access to the rest of the filesystem.
370+
However, you may use Cargo's `--manifest-path` option to reference your
371+
target crate, executed from a common root directory from which all your
372+
dependencies are available.
370373

371374
## Minimum Supported Rust Version (MSRV)
372375

src/cargo.rs

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
use serde::Deserialize;
2+
use std::io::Write;
23
use std::path::{Path, PathBuf};
34
use std::process::{Command, ExitStatus};
45

6+
use crate::cli::Args;
7+
use crate::config::Config;
58
use crate::errors::*;
6-
use crate::extensions::CommandExt;
9+
use crate::extensions::{CommandExt, OutputExt};
710

811
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
912
pub enum Subcommand {
@@ -52,42 +55,63 @@ impl<'a> From<&'a str> for Subcommand {
5255
}
5356
}
5457

55-
#[derive(Debug)]
56-
pub struct Root {
57-
pub path: PathBuf,
58+
#[derive(Debug, Deserialize)]
59+
pub struct CargoMetadata {
60+
pub workspace_root: PathBuf,
5861
}
5962

60-
impl Root {
61-
pub fn path(&self) -> &Path {
62-
&self.path
63+
impl CargoMetadata {
64+
pub fn workspace_root(&self) -> &Path {
65+
&self.workspace_root
6366
}
6467
}
6568

66-
/// Cargo project root
67-
pub fn root(cd: Option<&Path>) -> Result<Option<Root>> {
68-
#[derive(Deserialize)]
69-
struct Manifest {
70-
workspace_root: PathBuf,
71-
}
69+
/// Cargo metadata with specific invocation
70+
pub fn cargo_metadata_with_args(
71+
cd: Option<&Path>,
72+
args: Option<&Args>,
73+
) -> Result<Option<CargoMetadata>> {
7274
let mut command = std::process::Command::new(
7375
std::env::var("CARGO")
7476
.ok()
7577
.unwrap_or_else(|| "cargo".to_string()),
7678
);
77-
command
78-
.arg("metadata")
79-
.arg("--format-version=1")
80-
.arg("--no-deps");
79+
command.arg("metadata").arg("--format-version=1");
80+
if let Some(cd) = cd {
81+
command.current_dir(cd);
82+
}
83+
if let Some(config) = args {
84+
if let Some(ref manifest_path) = config.manifest_path {
85+
command.args(["--manifest-path".as_ref(), manifest_path.as_os_str()]);
86+
}
87+
} else {
88+
command.arg("--no-deps");
89+
}
90+
#[derive(Deserialize)]
91+
struct Manifest {
92+
workspace_root: PathBuf,
93+
}
8194
if let Some(cd) = cd {
8295
command.current_dir(cd);
8396
}
84-
let output = command.output()?;
97+
let output = command.run_and_get_output(false)?;
98+
if !output.status.success() {
99+
let mut stderr = std::io::stderr();
100+
stderr.write_all(&output.stderr)?;
101+
stderr.flush()?;
102+
std::process::exit(output.status.code().unwrap_or(1));
103+
}
85104
let manifest: Option<Manifest> = serde_json::from_slice(&output.stdout)?;
86-
Ok(manifest.map(|m| Root {
87-
path: m.workspace_root,
105+
Ok(manifest.map(|m| CargoMetadata {
106+
workspace_root: m.workspace_root,
88107
}))
89108
}
90109

110+
/// Cargo metadata
111+
pub fn cargo_metadata(cd: Option<&Path>) -> Result<Option<CargoMetadata>> {
112+
cargo_metadata_with_args(cd, None)
113+
}
114+
91115
/// Pass-through mode
92116
pub fn run(args: &[String], verbose: bool) -> Result<ExitStatus> {
93117
Command::new("cargo").args(args).run_and_get_status(verbose)

src/cli.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub struct Args {
1515
pub target_dir: Option<PathBuf>,
1616
pub docker_in_docker: bool,
1717
pub enable_doctests: bool,
18+
pub manifest_path: Option<PathBuf>,
1819
}
1920

2021
// Fix for issue #581. target_dir must be absolute.
@@ -72,6 +73,7 @@ pub fn fmt_subcommands(stdout: &str) {
7273
pub fn parse(target_list: &TargetList) -> Result<Args> {
7374
let mut channel = None;
7475
let mut target = None;
76+
let mut manifest_path: Option<PathBuf> = None;
7577
let mut target_dir = None;
7678
let mut sc = None;
7779
let mut all: Vec<String> = Vec::new();
@@ -82,7 +84,21 @@ pub fn parse(target_list: &TargetList) -> Result<Args> {
8284
if arg.is_empty() {
8385
continue;
8486
}
85-
if let ("+", ch) = arg.split_at(1) {
87+
if arg == "--manifest-path" {
88+
all.push(arg);
89+
if let Some(m) = args.next() {
90+
let p = PathBuf::from(&m);
91+
all.push(m);
92+
manifest_path = env::current_dir().ok().map(|cwd| cwd.join(p));
93+
}
94+
} else if arg.starts_with("--manifest-path=") {
95+
manifest_path = arg
96+
.split_once('=')
97+
.map(|x| x.1)
98+
.map(PathBuf::from)
99+
.and_then(|p| env::current_dir().ok().map(|cwd| cwd.join(p)));
100+
all.push(arg);
101+
} else if let ("+", ch) = arg.split_at(1) {
86102
channel = Some(ch.to_string());
87103
} else if arg == "--target" {
88104
all.push(arg);
@@ -131,5 +147,6 @@ pub fn parse(target_list: &TargetList) -> Result<Args> {
131147
target_dir,
132148
docker_in_docker,
133149
enable_doctests,
150+
manifest_path,
134151
})
135152
}

src/docker.rs

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::path::{Path, PathBuf};
22
use std::process::{Command, ExitStatus};
33
use std::{env, fs};
44

5-
use crate::cargo::Root;
5+
use crate::cargo::CargoMetadata;
66
use crate::errors::*;
77
use crate::extensions::{CommandExt, SafeCommand};
88
use crate::id;
@@ -70,27 +70,29 @@ pub fn run(
7070
target: &Target,
7171
args: &[String],
7272
target_dir: &Option<PathBuf>,
73-
root: &Root,
73+
metadata: &CargoMetadata,
7474
config: &Config,
7575
uses_xargo: bool,
7676
sysroot: &Path,
7777
verbose: bool,
7878
docker_in_docker: bool,
79+
cwd: &Path,
7980
) -> Result<ExitStatus> {
8081
let mount_finder = if docker_in_docker {
8182
MountFinder::new(docker_read_mount_paths()?)
8283
} else {
8384
MountFinder::default()
8485
};
8586

86-
let root = root.path();
8787
let home_dir = home::home_dir().ok_or_else(|| eyre::eyre!("could not find home directory"))?;
8888
let cargo_dir = home::cargo_home()?;
8989
let xargo_dir = env::var_os("XARGO_HOME")
9090
.map(PathBuf::from)
9191
.unwrap_or_else(|| home_dir.join(".xargo"));
9292
let nix_store_dir = env::var_os("NIX_STORE").map(PathBuf::from);
93-
let target_dir = target_dir.clone().unwrap_or_else(|| root.join("target"));
93+
let target_dir = target_dir
94+
.clone()
95+
.unwrap_or_else(|| metadata.workspace_root().join("target"));
9496

9597
// create the directories we are going to mount before we mount them,
9698
// otherwise `docker` will create them but they will be owned by `root`
@@ -102,7 +104,12 @@ pub fn run(
102104
let cargo_dir = mount_finder.find_mount_path(cargo_dir);
103105
let xargo_dir = mount_finder.find_mount_path(xargo_dir);
104106
let target_dir = mount_finder.find_mount_path(target_dir);
105-
let host_root = mount_finder.find_mount_path(root);
107+
// root is either workspace_root, or, if we're outside the workspace root, the current directory
108+
let host_root = mount_finder.find_mount_path(if metadata.workspace_root().starts_with(cwd) {
109+
cwd
110+
} else {
111+
metadata.workspace_root()
112+
});
106113
let mount_root: PathBuf;
107114
#[cfg(target_os = "windows")]
108115
{
@@ -117,11 +124,11 @@ pub fn run(
117124
#[cfg(target_os = "windows")]
118125
{
119126
// On Windows, we can not mount the directory name directly. Instead, we use wslpath to convert the path to a linux compatible path.
120-
mount_cwd = wslpath(&std::env::current_dir()?, verbose)?;
127+
mount_cwd = wslpath(&cwd, verbose)?;
121128
}
122129
#[cfg(not(target_os = "windows"))]
123130
{
124-
mount_cwd = mount_finder.find_mount_path(std::env::current_dir()?);
131+
mount_cwd = mount_finder.find_mount_path(cwd);
125132
}
126133
let sysroot = mount_finder.find_mount_path(sysroot);
127134

@@ -145,6 +152,12 @@ pub fn run(
145152
docker.args(&["-e", var]);
146153
}
147154
let mut env_volumes = false;
155+
// 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)
156+
// Preferably we would not have to do this.
157+
if cwd.strip_prefix(metadata.workspace_root()).is_err() {
158+
env_volumes = true;
159+
}
160+
148161
for ref var in config.env_volumes(target)? {
149162
let (var, value) = validate_env_var(var)?;
150163
let value = match value {
@@ -247,12 +260,12 @@ pub fn run(
247260

248261
if env_volumes {
249262
docker.args(&["-w".as_ref(), mount_cwd.as_os_str()]);
250-
} else if mount_cwd == root {
263+
} else if mount_cwd == metadata.workspace_root() {
251264
docker.args(&["-w", "/project"]);
252265
} else {
253266
// We do this to avoid clashes with path separators. Windows uses `\` as a path separator on Path::join
254-
let cwd = &std::env::current_dir()?;
255-
let working_dir = Path::new("project").join(cwd.strip_prefix(root)?);
267+
let cwd = &cwd;
268+
let working_dir = Path::new("project").join(cwd.strip_prefix(metadata.workspace_root())?);
256269
// No [T].join for OsStr
257270
let mut mount_wd = std::ffi::OsString::new();
258271
for part in working_dir.iter() {

src/main.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use config::Config;
2525
use rustc_version::Channel;
2626
use serde::Deserialize;
2727

28-
use self::cargo::{Root, Subcommand};
28+
use self::cargo::{CargoMetadata, Subcommand};
2929
use self::cross_toml::CrossToml;
3030
use self::errors::*;
3131
use self::extensions::OutputExt;
@@ -282,9 +282,10 @@ fn run() -> Result<ExitStatus> {
282282

283283
let host_version_meta =
284284
rustc_version::version_meta().wrap_err("couldn't fetch the `rustc` version")?;
285-
if let Some(root) = cargo::root(None)? {
285+
let cwd = std::env::current_dir()?;
286+
if let Some(metadata) = cargo::cargo_metadata_with_args(Some(&cwd), Some(&args))? {
286287
let host = host_version_meta.host();
287-
let toml = toml(&root)?;
288+
let toml = toml(&metadata)?;
288289
let config = Config::new(toml);
289290
let target = args
290291
.target
@@ -407,16 +408,18 @@ fn run() -> Result<ExitStatus> {
407408
docker::register(&target, verbose)?
408409
}
409410

411+
let docker_root = env::current_dir()?;
410412
return docker::run(
411413
&target,
412414
&filtered_args,
413415
&args.target_dir,
414-
&root,
416+
&metadata,
415417
&config,
416418
uses_xargo,
417419
&sysroot,
418420
verbose,
419421
args.docker_in_docker,
422+
&cwd,
420423
);
421424
}
422425
}
@@ -489,10 +492,10 @@ pub(crate) fn warn_host_version_mismatch(
489492

490493
/// Parses the `Cross.toml` at the root of the Cargo project or from the
491494
/// `CROSS_CONFIG` environment variable (if any exist in either location).
492-
fn toml(root: &Root) -> Result<Option<CrossToml>> {
495+
fn toml(root: &CargoMetadata) -> Result<Option<CrossToml>> {
493496
let path = match env::var("CROSS_CONFIG") {
494497
Ok(var) => PathBuf::from(var),
495-
Err(_) => root.path().join("Cross.toml"),
498+
Err(_) => root.workspace_root().join("Cross.toml"),
496499
};
497500

498501
if path.exists() {
@@ -505,7 +508,7 @@ fn toml(root: &Root) -> Result<Option<CrossToml>> {
505508
Ok(Some(config))
506509
} else {
507510
// Checks if there is a lowercase version of this file
508-
if root.path().join("cross.toml").exists() {
511+
if root.workspace_root().join("cross.toml").exists() {
509512
eprintln!("There's a file named cross.toml, instead of Cross.toml. You may want to rename it, or it won't be considered.");
510513
}
511514
Ok(None)

src/tests.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,17 @@ use std::{
77

88
use once_cell::sync::OnceCell;
99
use rustc_version::VersionMeta;
10-
use serde::Deserialize;
1110

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

1413
/// Returns the cargo workspace for the manifest
1514
pub fn get_cargo_workspace() -> &'static Path {
1615
let manifest_dir = env!("CARGO_MANIFEST_DIR");
1716
WORKSPACE.get_or_init(|| {
18-
crate::cargo::root(Some(manifest_dir.as_ref()))
17+
crate::cargo::cargo_metadata(Some(manifest_dir.as_ref()))
1918
.unwrap()
2019
.unwrap()
21-
.path
20+
.workspace_root
2221
})
2322
}
2423

0 commit comments

Comments
 (0)