Skip to content

Commit 0c1d76d

Browse files
bors[bot]EmilgardiswngrAlexhuszagh
authored
Merge #684
684: enable cargo workspace and path dependencies to work seamlessly r=Alexhuszagh a=Emilgardis This will allow cargo workspaces to work seamlessly with cross, assuming they don't reference anything outside the current workspace root. This change also automatically mounts (if needed) path dependencies. If they do reference things outside the workspace root, and that fact is not know to `cargo metadata` (e.g, it's not a workspace member or a path dependency), you'll have to mount it with ```toml [build.env] volumes = ["THING"] ``` and set `$THING="/path/to/thing"` (we need to work on accessibility for this feature too, a lot of reported issues are solved by it) I'm sure there is an issue directly related to workspaces, but I can't find any. fixes #388 Co-authored-by: Emil Gardström <[email protected]> Co-authored-by: wngr <[email protected]> Co-authored-by: Alexander Huszagh <[email protected]>
2 parents 6b42263 + 3e59d85 commit 0c1d76d

File tree

9 files changed

+233
-71
lines changed

9 files changed

+233
-71
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
1616
- #543 - Added environment variables to control the UID and GID in the container
1717
- #524 - docker: Add Nix Store volume support
1818
- Added support for mounting volumes.
19+
- #684 - Enable cargo workspaces to work from any path in the workspace, and make path dependencies mount seamlessly.
1920

2021
### Changed
2122

@@ -45,6 +46,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
4546
- Re-enabled `sparc64-unknown-linux-gnu` image
4647
- #582 - Added `libprocstat.so` to FreeBSD images
4748
- #665 - when not using [env.volumes](https://github.com/cross-rs/cross#mounting-volumes-into-the-build-environment), mount project in /project
49+
- #494 - Parse Cargo's --manifest-path option to determine mounted docker root
4850

4951

5052
### Removed

README.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -362,12 +362,6 @@ $ QEMU_STRACE=1 cross run --target aarch64-unknown-linux-gnu
362362
9 exit_group(0)
363363
```
364364

365-
## Caveats
366-
367-
- path dependencies (in Cargo.toml) that point outside the Cargo project won't
368-
work because `cross` use docker containers only mounts the Cargo project so
369-
the container doesn't have access to the rest of the filesystem.
370-
371365
## Minimum Supported Rust Version (MSRV)
372366

373367
This crate is guaranteed to compile on stable Rust 1.58.1 and up. It *might*

ci/test.sh

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ function retry {
2525
return ${exit_code}
2626
}
2727

28+
workspace_test() {
29+
# "${@}" is an unbound variable for bash 3.2, which is the installed version on macOS
30+
cross build --target "${TARGET}" --workspace "$@"
31+
cross run --target "${TARGET}" -p binary "$@"
32+
cross run --target "${TARGET}" --bin dependencies \
33+
--features=dependencies "$@"
34+
}
35+
2836
main() {
2937
local td=
3038

@@ -135,6 +143,21 @@ EOF
135143
popd
136144

137145
rm -rf "${td}"
146+
td=$(mktemp -d)
147+
git clone \
148+
--depth 1 \
149+
--recursive \
150+
https://github.com/cross-rs/test-workspace "${td}"
151+
152+
pushd "${td}"
153+
TARGET="${TARGET}" workspace_test --manifest-path="./workspace/Cargo.toml"
154+
pushd "workspace"
155+
TARGET="${TARGET}" workspace_test
156+
pushd "binary"
157+
cross run --target "${TARGET}"
158+
popd
159+
popd
160+
popd
138161
;;
139162
esac
140163

src/cargo.rs

Lines changed: 76 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
use serde::Deserialize;
12
use std::path::{Path, PathBuf};
23
use std::process::{Command, ExitStatus};
3-
use std::{env, fs};
44

5+
use crate::cli::Args;
56
use crate::errors::*;
67
use crate::extensions::CommandExt;
78

@@ -52,38 +53,90 @@ impl<'a> From<&'a str> for Subcommand {
5253
}
5354
}
5455

55-
#[derive(Debug)]
56-
pub struct Root {
57-
path: PathBuf,
56+
#[derive(Debug, Deserialize)]
57+
pub struct CargoMetadata {
58+
pub workspace_root: PathBuf,
59+
pub target_directory: PathBuf,
60+
pub packages: Vec<Package>,
61+
pub workspace_members: Vec<String>,
5862
}
5963

60-
impl Root {
61-
pub fn path(&self) -> &Path {
62-
&self.path
64+
impl CargoMetadata {
65+
fn non_workspace_members(&self) -> impl Iterator<Item = &Package> {
66+
self.packages
67+
.iter()
68+
.filter(|p| !self.workspace_members.iter().any(|m| m == &p.id))
6369
}
64-
}
6570

66-
/// Cargo project root
67-
pub fn root() -> Result<Option<Root>> {
68-
let cd = env::current_dir().wrap_err("couldn't get current directory")?;
71+
pub fn path_dependencies(&self) -> impl Iterator<Item = &Path> {
72+
// TODO: Also filter out things that are in workspace, but not a workspace member
73+
self.non_workspace_members().filter_map(|p| p.crate_path())
74+
}
75+
}
6976

70-
let mut dir = &*cd;
71-
loop {
72-
let toml = dir.join("Cargo.toml");
77+
#[derive(Debug, Deserialize)]
78+
pub struct Package {
79+
id: String,
80+
manifest_path: PathBuf,
81+
source: Option<String>,
82+
}
7383

74-
if fs::metadata(&toml).is_ok() {
75-
return Ok(Some(Root {
76-
path: dir.to_owned(),
77-
}));
84+
impl Package {
85+
/// Returns the absolute path to the packages manifest "folder"
86+
fn crate_path(&self) -> Option<&Path> {
87+
// when source is none, this package is a path dependency or a workspace member
88+
if self.source.is_none() {
89+
self.manifest_path.parent()
90+
} else {
91+
None
7892
}
93+
}
94+
}
7995

80-
match dir.parent() {
81-
Some(p) => dir = p,
82-
None => break,
96+
/// Cargo metadata with specific invocation
97+
pub fn cargo_metadata_with_args(
98+
cd: Option<&Path>,
99+
args: Option<&Args>,
100+
verbose: bool,
101+
) -> Result<Option<CargoMetadata>> {
102+
let mut command = std::process::Command::new(
103+
std::env::var("CARGO")
104+
.ok()
105+
.unwrap_or_else(|| "cargo".to_string()),
106+
);
107+
command.arg("metadata").arg("--format-version=1");
108+
if let Some(cd) = cd {
109+
command.current_dir(cd);
110+
}
111+
if let Some(config) = args {
112+
if let Some(ref manifest_path) = config.manifest_path {
113+
command.args(["--manifest-path".as_ref(), manifest_path.as_os_str()]);
83114
}
115+
} else {
116+
command.arg("--no-deps");
84117
}
85-
86-
Ok(None)
118+
if let Some(target) = args.and_then(|a| a.target.as_ref()) {
119+
command.args(["--filter-platform", target.triple()]);
120+
}
121+
if let Some(features) = args.map(|a| &a.features).filter(|v| !v.is_empty()) {
122+
command.args([String::from("--features"), features.join(",")]);
123+
}
124+
let output = command.run_and_get_output(verbose)?;
125+
if !output.status.success() {
126+
// TODO: logging
127+
return Ok(None);
128+
}
129+
let manifest: Option<CargoMetadata> = serde_json::from_slice(&output.stdout)?;
130+
manifest
131+
.map(|m| -> Result<_> {
132+
Ok(CargoMetadata {
133+
target_directory: args
134+
.and_then(|a| a.target_dir.clone())
135+
.unwrap_or(m.target_directory),
136+
..m
137+
})
138+
})
139+
.transpose()
87140
}
88141

89142
/// Pass-through mode

src/cli.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ pub struct Args {
1212
pub subcommand: Option<Subcommand>,
1313
pub channel: Option<String>,
1414
pub target: Option<Target>,
15+
pub features: Vec<String>,
1516
pub target_dir: Option<PathBuf>,
1617
pub docker_in_docker: bool,
1718
pub enable_doctests: bool,
19+
pub manifest_path: Option<PathBuf>,
1820
}
1921

2022
// Fix for issue #581. target_dir must be absolute.
@@ -72,6 +74,8 @@ pub fn fmt_subcommands(stdout: &str) {
7274
pub fn parse(target_list: &TargetList) -> Result<Args> {
7375
let mut channel = None;
7476
let mut target = None;
77+
let mut features = Vec::new();
78+
let mut manifest_path: Option<PathBuf> = None;
7579
let mut target_dir = None;
7680
let mut sc = None;
7781
let mut all: Vec<String> = Vec::new();
@@ -82,7 +86,21 @@ pub fn parse(target_list: &TargetList) -> Result<Args> {
8286
if arg.is_empty() {
8387
continue;
8488
}
85-
if let ("+", ch) = arg.split_at(1) {
89+
if arg == "--manifest-path" {
90+
all.push(arg);
91+
if let Some(m) = args.next() {
92+
let p = PathBuf::from(&m);
93+
all.push(m);
94+
manifest_path = env::current_dir().ok().map(|cwd| cwd.join(p));
95+
}
96+
} else if arg.starts_with("--manifest-path=") {
97+
manifest_path = arg
98+
.split_once('=')
99+
.map(|x| x.1)
100+
.map(PathBuf::from)
101+
.and_then(|p| env::current_dir().ok().map(|cwd| cwd.join(p)));
102+
all.push(arg);
103+
} else if let ("+", ch) = arg.split_at(1) {
86104
channel = Some(ch.to_string());
87105
} else if arg == "--target" {
88106
all.push(arg);
@@ -95,6 +113,15 @@ pub fn parse(target_list: &TargetList) -> Result<Args> {
95113
.split_once('=')
96114
.map(|(_, t)| Target::from(t, target_list));
97115
all.push(arg);
116+
} else if arg == "--features" {
117+
all.push(arg);
118+
if let Some(t) = args.next() {
119+
features.push(t.clone());
120+
all.push(t);
121+
}
122+
} else if arg.starts_with("--features=") {
123+
features.extend(arg.split_once('=').map(|(_, t)| t.to_owned()));
124+
all.push(arg);
98125
} else if arg == "--target-dir" {
99126
all.push(arg);
100127
if let Some(td) = args.next() {
@@ -128,8 +155,10 @@ pub fn parse(target_list: &TargetList) -> Result<Args> {
128155
subcommand: sc,
129156
channel,
130157
target,
158+
features,
131159
target_dir,
132160
docker_in_docker,
133161
enable_doctests,
162+
manifest_path,
134163
})
135164
}

0 commit comments

Comments
 (0)