Skip to content

Commit 66281b8

Browse files
committed
Add support for cargo clean on remote hosts.
Required to patch #724 without deleting the entire volume for persistent data volumes. A few changes were required: the entire `/cross` mount prefix must be owned by the user so `/cross/target` can be removed. Next, we use the full path to the mounted target directory, rather than the symlink, since `cargo clean` would just delete the symlink. Finally, we've added `cargo clean` to a list of known subcommands, and it only needs docker if we use a remote host.
1 parent b59cc91 commit 66281b8

File tree

6 files changed

+95
-38
lines changed

6 files changed

+95
-38
lines changed

src/bin/cross-util.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ fn get_container_engine(engine: Option<&str>, verbose: bool) -> cross::Result<do
4444
} else {
4545
docker::get_container_engine()?
4646
};
47-
docker::Engine::from_path(engine, verbose)
47+
docker::Engine::from_path(engine, None, verbose)
4848
}
4949

5050
pub fn main() -> cross::Result<()> {

src/cargo.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,20 @@ pub enum Subcommand {
1919
Clippy,
2020
Metadata,
2121
List,
22+
Clean,
2223
}
2324

2425
impl Subcommand {
25-
pub fn needs_docker(self) -> bool {
26-
!matches!(self, Subcommand::Other | Subcommand::List)
26+
pub fn needs_docker(self, is_remote: bool) -> bool {
27+
match self {
28+
Subcommand::Other | Subcommand::List => false,
29+
Subcommand::Clean if !is_remote => false,
30+
_ => true,
31+
}
32+
}
33+
34+
pub fn needs_host(self, is_remote: bool) -> bool {
35+
self == Subcommand::Clean && is_remote
2736
}
2837

2938
pub fn needs_interpreter(self) -> bool {
@@ -40,6 +49,7 @@ impl<'a> From<&'a str> for Subcommand {
4049
match s {
4150
"b" | "build" => Subcommand::Build,
4251
"c" | "check" => Subcommand::Check,
52+
"clean" => Subcommand::Clean,
4353
"doc" => Subcommand::Doc,
4454
"r" | "run" => Subcommand::Run,
4555
"rustc" => Subcommand::Rustc,

src/docker/engine.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,16 @@ pub struct Engine {
2525
}
2626

2727
impl Engine {
28-
pub fn new(verbose: bool) -> Result<Engine> {
28+
pub fn new(is_remote: Option<bool>, verbose: bool) -> Result<Engine> {
2929
let path = get_container_engine()
3030
.map_err(|_| eyre::eyre!("no container engine found"))
3131
.with_suggestion(|| "is docker or podman installed?")?;
32-
Self::from_path(path, verbose)
32+
Self::from_path(path, is_remote, verbose)
3333
}
3434

35-
pub fn from_path(path: PathBuf, verbose: bool) -> Result<Engine> {
35+
pub fn from_path(path: PathBuf, is_remote: Option<bool>, verbose: bool) -> Result<Engine> {
3636
let kind = get_engine_type(&path, verbose)?;
37-
let is_remote = env::var("CROSS_REMOTE")
38-
.map(|s| bool_from_envvar(&s))
39-
.unwrap_or_default();
37+
let is_remote = is_remote.unwrap_or_else(Self::is_remote);
4038
Ok(Engine {
4139
path,
4240
kind,
@@ -47,6 +45,12 @@ impl Engine {
4745
pub fn needs_remote(&self) -> bool {
4846
self.is_remote && self.kind == EngineType::Podman
4947
}
48+
49+
pub fn is_remote() -> bool {
50+
env::var("CROSS_REMOTE")
51+
.map(|s| bool_from_envvar(&s))
52+
.unwrap_or_default()
53+
}
5054
}
5155

5256
// determine if the container engine is docker. this fixes issues with

src/docker/remote.rs

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,19 @@ fn is_cachedir(entry: &fs::DirEntry) -> bool {
146146
}
147147
}
148148

149+
fn container_path_exists(
150+
engine: &Engine,
151+
container: &str,
152+
path: &Path,
153+
verbose: bool,
154+
) -> Result<bool> {
155+
Ok(subcommand(engine, "exec")
156+
.arg(container)
157+
.args(&["bash", "-c", &format!("[[ -d '{}' ]]", path.as_posix()?)])
158+
.run_and_get_status(verbose, true)?
159+
.success())
160+
}
161+
149162
// copy files for a docker volume, for remote host support
150163
fn copy_volume_files_nocache(
151164
engine: &Engine,
@@ -329,15 +342,7 @@ pub fn copy_volume_container_rust_triple(
329342
// or the first run of the target toolchain, we know it doesn't exist.
330343
let mut skip = false;
331344
if skip_exists {
332-
skip = subcommand(engine, "exec")
333-
.arg(container)
334-
.args(&[
335-
"bash",
336-
"-c",
337-
&format!("[[ -d '{}' ]]", dst_toolchain.as_posix()?),
338-
])
339-
.run_and_get_status(verbose, true)?
340-
.success();
345+
skip = container_path_exists(engine, container, &dst_toolchain, verbose)?;
341346
}
342347
if !skip {
343348
copy_volume_files(engine, container, &src_toolchain, &dst_rustlib, verbose)?;
@@ -490,9 +495,6 @@ pub(crate) fn run(
490495
) -> Result<ExitStatus> {
491496
let dirs = Directories::create(engine, metadata, cwd, sysroot, docker_in_docker, verbose)?;
492497

493-
let mut cmd = cargo_safe_command(uses_xargo);
494-
cmd.args(args);
495-
496498
let mount_prefix = MOUNT_PREFIX;
497499

498500
// the logic is broken into the following steps
@@ -654,10 +656,7 @@ pub(crate) fn run(
654656
let mut to_symlink = vec![];
655657
let target_dir = file::canonicalize(&dirs.target)?;
656658
let target_dir = if let Ok(relpath) = target_dir.strip_prefix(&dirs.host_root) {
657-
// target dir is in the project, just symlink it in
658-
let target_dir = mount_root.join(relpath);
659-
to_symlink.push((target_dir.clone(), "/target".to_string()));
660-
target_dir
659+
mount_root.join(relpath)
661660
} else {
662661
// outside project, need to copy the target data over
663662
// only do if we're copying over cached files.
@@ -687,13 +686,43 @@ pub(crate) fn run(
687686
}
688687
}
689688

689+
// `clean` doesn't handle symlinks: it will just unlink the target
690+
// directory, so we should just substitute it our target directory
691+
// for it. we'll still have the same end behavior
692+
let mut final_args = vec![];
693+
let mut iter = args.iter().cloned();
694+
let mut has_target_dir = false;
695+
let target_dir_string = target_dir.to_utf8()?.to_string();
696+
while let Some(arg) = iter.next() {
697+
if arg == "--target-dir" {
698+
has_target_dir = true;
699+
final_args.push(arg);
700+
if iter.next().is_some() {
701+
final_args.push(target_dir_string.clone());
702+
}
703+
} else if arg.starts_with("--target-dir=") {
704+
has_target_dir = true;
705+
if arg.split_once('=').is_some() {
706+
final_args.push(format!("--target-dir={target_dir_string}"));
707+
}
708+
} else {
709+
final_args.push(arg);
710+
}
711+
}
712+
if !has_target_dir {
713+
final_args.push("--target-dir".to_string());
714+
final_args.push(target_dir_string);
715+
}
716+
let mut cmd = cargo_safe_command(uses_xargo);
717+
cmd.args(final_args);
718+
690719
// 5. create symlinks for copied data
691720
let mut symlink = vec!["set -e pipefail".to_string()];
692721
if verbose {
693722
symlink.push("set -x".to_string());
694723
}
695724
symlink.push(format!(
696-
"chown -R {uid}:{gid} {mount_prefix}/*",
725+
"chown -R {uid}:{gid} {mount_prefix}",
697726
uid = user_id(),
698727
gid = group_id(),
699728
));
@@ -738,12 +767,15 @@ symlink_recurse \"${{prefix}}\"
738767
.map_err(Into::into);
739768

740769
// 7. copy data from our target dir back to host
741-
subcommand(engine, "cp")
742-
.arg("-a")
743-
.arg(&format!("{container}:{}", target_dir.as_posix()?))
744-
.arg(&dirs.target.parent().unwrap())
745-
.run_and_get_status(verbose, false)
746-
.map_err::<eyre::ErrReport, _>(Into::into)?;
770+
// this might not exist if we ran `clean`.
771+
if container_path_exists(engine, &container, &target_dir, verbose)? {
772+
subcommand(engine, "cp")
773+
.arg("-a")
774+
.arg(&format!("{container}:{}", target_dir.as_posix()?))
775+
.arg(&dirs.target.parent().unwrap())
776+
.run_and_get_status(verbose, false)
777+
.map_err::<eyre::ErrReport, _>(Into::into)?;
778+
}
747779

748780
status
749781
}

src/lib.rs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -478,9 +478,13 @@ pub fn run() -> Result<ExitStatus> {
478478
filtered_args.push("-Zbuild-std".to_string());
479479
}
480480

481-
if target.needs_docker() && args.subcommand.map(|sc| sc.needs_docker()).unwrap_or(false)
482-
{
483-
let engine = docker::Engine::new(verbose)?;
481+
let is_remote = docker::Engine::is_remote();
482+
let needs_docker = args
483+
.subcommand
484+
.map(|sc| sc.needs_docker(is_remote))
485+
.unwrap_or(false);
486+
if target.needs_docker() && needs_docker {
487+
let engine = docker::Engine::new(Some(is_remote), verbose)?;
484488
if host_version_meta.needs_interpreter()
485489
&& needs_interpreter
486490
&& target.needs_interpreter()
@@ -489,7 +493,7 @@ pub fn run() -> Result<ExitStatus> {
489493
docker::register(&engine, &target, verbose)?
490494
}
491495

492-
return docker::run(
496+
let status = docker::run(
493497
&engine,
494498
&target,
495499
&filtered_args,
@@ -500,7 +504,14 @@ pub fn run() -> Result<ExitStatus> {
500504
verbose,
501505
args.docker_in_docker,
502506
&cwd,
503-
);
507+
)?;
508+
let needs_host = args
509+
.subcommand
510+
.map(|sc| sc.needs_host(is_remote))
511+
.unwrap_or(false);
512+
if !(status.success() && needs_host) {
513+
return Ok(status);
514+
}
504515
}
505516
}
506517
}

xtask/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,5 @@ fn get_container_engine(engine: Option<&str>, verbose: bool) -> cross::Result<do
8585
} else {
8686
docker::get_container_engine()?
8787
};
88-
docker::Engine::from_path(engine, verbose)
88+
docker::Engine::from_path(engine, None, verbose)
8989
}

0 commit comments

Comments
 (0)