use std::collections::BTreeMap;
use std::io::{self, BufRead, Read, Write};
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus, Output};
use std::sync::atomic::{AtomicBool, Ordering};
use std::{env, fs, time};
use eyre::Context;
use super::engine::Engine;
use super::shared::*;
use crate::cargo::CargoMetadata;
use crate::config::bool_from_envvar;
use crate::errors::Result;
use crate::extensions::CommandExt;
use crate::file::{self, PathExt, ToUtf8};
use crate::rustc::{self, VersionMetaExt};
use crate::rustup;
use crate::shell::{ColorChoice, MessageInfo, Stream, Verbosity};
use crate::temp;
use crate::{Host, Target};
pub const MOUNT_PREFIX: &str = "/cross";
pub const DEFAULT_TIMEOUT: u32 = 2;
pub const NO_TIMEOUT: u32 = 0;
pub(crate) static mut CONTAINER: Option<DeleteContainer> = None;
pub(crate) static mut CONTAINER_EXISTS: AtomicBool = AtomicBool::new(false);
pub(crate) struct DeleteContainer(Engine, String, u32, ColorChoice, Verbosity);
impl Drop for DeleteContainer {
fn drop(&mut self) {
unsafe {
if CONTAINER_EXISTS.swap(false, Ordering::SeqCst) {
let mut msg_info = MessageInfo::new(self.3, self.4);
container_stop(&self.0, &self.1, self.2, &mut msg_info).ok();
container_rm(&self.0, &self.1, &mut msg_info).ok();
}
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum ContainerState {
Created,
Running,
Paused,
Restarting,
Dead,
Exited,
DoesNotExist,
}
impl ContainerState {
pub fn new(state: &str) -> Result<Self> {
match state {
"created" => Ok(ContainerState::Created),
"running" => Ok(ContainerState::Running),
"paused" => Ok(ContainerState::Paused),
"restarting" => Ok(ContainerState::Restarting),
"dead" => Ok(ContainerState::Dead),
"exited" => Ok(ContainerState::Exited),
"" => Ok(ContainerState::DoesNotExist),
_ => eyre::bail!("unknown container state: got {state}"),
}
}
#[must_use]
pub fn is_stopped(&self) -> bool {
matches!(self, Self::Exited | Self::DoesNotExist)
}
#[must_use]
pub fn exists(&self) -> bool {
!matches!(self, Self::DoesNotExist)
}
}
#[derive(Debug, Clone)]
enum VolumeId {
Keep(String),
Discard,
}
impl VolumeId {
fn create(engine: &Engine, toolchain: &str, msg_info: &mut MessageInfo) -> Result<Self> {
if volume_exists(engine, toolchain, msg_info)? {
Ok(Self::Keep(toolchain.to_owned()))
} else {
Ok(Self::Discard)
}
}
}
macro_rules! bail_container_exited {
() => {{
if !container_exists() {
eyre::bail!("container already exited due to signal");
}
}};
}
pub fn create_container_deleter(engine: Engine, container: String) {
unsafe {
CONTAINER_EXISTS.store(true, Ordering::Relaxed);
CONTAINER = Some(DeleteContainer(
engine,
container,
NO_TIMEOUT,
ColorChoice::Never,
Verbosity::Quiet,
));
}
}
pub fn drop_container(is_tty: bool, msg_info: &mut MessageInfo) {
unsafe {
if let Some(container) = &mut CONTAINER {
if is_tty {
container.2 = DEFAULT_TIMEOUT;
}
container.3 = msg_info.color_choice;
container.4 = msg_info.verbosity;
}
CONTAINER = None;
}
}
fn container_exists() -> bool {
unsafe { CONTAINER_EXISTS.load(Ordering::Relaxed) }
}
fn subcommand_or_exit(engine: &Engine, cmd: &str) -> Result<Command> {
bail_container_exited!();
Ok(subcommand(engine, cmd))
}
fn create_volume_dir(
engine: &Engine,
container: &str,
dir: &Path,
msg_info: &mut MessageInfo,
) -> Result<ExitStatus> {
subcommand_or_exit(engine, "exec")?
.arg(container)
.args(&["sh", "-c", &format!("mkdir -p '{}'", dir.as_posix()?)])
.run_and_get_status(msg_info, false)
}
fn copy_volume_files(
engine: &Engine,
container: &str,
src: &Path,
dst: &Path,
msg_info: &mut MessageInfo,
) -> Result<ExitStatus> {
subcommand_or_exit(engine, "cp")?
.arg("-a")
.arg(src.to_utf8()?)
.arg(format!("{container}:{}", dst.as_posix()?))
.run_and_get_status(msg_info, false)
}
fn is_cachedir_tag(path: &Path) -> Result<bool> {
let mut buffer = [b'0'; 43];
let mut file = fs::OpenOptions::new().read(true).open(path)?;
file.read_exact(&mut buffer)?;
Ok(&buffer == b"Signature: 8a477f597d28d172789f06886806bc55")
}
fn is_cachedir(entry: &fs::DirEntry) -> bool {
if entry.file_type().map(|t| t.is_dir()).unwrap_or(false) {
let path = entry.path().join("CACHEDIR.TAG");
path.exists() && is_cachedir_tag(&path).unwrap_or(false)
} else {
false
}
}
fn container_path_exists(
engine: &Engine,
container: &str,
path: &Path,
msg_info: &mut MessageInfo,
) -> Result<bool> {
Ok(subcommand_or_exit(engine, "exec")?
.arg(container)
.args(&["bash", "-c", &format!("[[ -d '{}' ]]", path.as_posix()?)])
.run_and_get_status(msg_info, true)?
.success())
}
fn copy_volume_files_nocache(
engine: &Engine,
container: &str,
src: &Path,
dst: &Path,
copy_symlinks: bool,
msg_info: &mut MessageInfo,
) -> Result<ExitStatus> {
let tempdir = unsafe { temp::TempDir::new()? };
let temppath = tempdir.path();
let had_symlinks = copy_dir(src, temppath, copy_symlinks, 0, |e, _| is_cachedir(e))?;
warn_symlinks(had_symlinks, msg_info)?;
copy_volume_files(engine, container, temppath, dst, msg_info)
}
pub fn copy_volume_container_xargo(
engine: &Engine,
container: &str,
xargo_dir: &Path,
target: &Target,
mount_prefix: &Path,
msg_info: &mut MessageInfo,
) -> Result<()> {
let triple = target.triple();
let relpath = Path::new("lib").join("rustlib").join(&triple);
let src = xargo_dir.join(&relpath);
let dst = mount_prefix.join("xargo").join(&relpath);
if Path::new(&src).exists() {
create_volume_dir(
engine,
container,
dst.parent().expect("destination should have a parent"),
msg_info,
)?;
copy_volume_files(engine, container, &src, &dst, msg_info)?;
}
Ok(())
}
pub fn copy_volume_container_cargo(
engine: &Engine,
container: &str,
cargo_dir: &Path,
mount_prefix: &Path,
copy_registry: bool,
msg_info: &mut MessageInfo,
) -> Result<()> {
let dst = mount_prefix.join("cargo");
let copy_registry = env::var("CROSS_REMOTE_COPY_REGISTRY")
.map(|s| bool_from_envvar(&s))
.unwrap_or(copy_registry);
if copy_registry {
copy_volume_files(engine, container, cargo_dir, &dst, msg_info)?;
} else {
create_volume_dir(engine, container, &dst, msg_info)?;
for entry in fs::read_dir(cargo_dir)
.wrap_err_with(|| format!("when reading directory {cargo_dir:?}"))?
{
let file = entry?;
let basename = file
.file_name()
.to_utf8()
.wrap_err_with(|| format!("when reading file {file:?}"))?
.to_owned();
if !basename.starts_with('.') && !matches!(basename.as_ref(), "git" | "registry") {
copy_volume_files(engine, container, &file.path(), &dst, msg_info)?;
}
}
}
Ok(())
}
fn copy_dir<Skip>(
src: &Path,
dst: &Path,
copy_symlinks: bool,
depth: u32,
skip: Skip,
) -> Result<bool>
where
Skip: Copy + Fn(&fs::DirEntry, u32) -> bool,
{
let mut had_symlinks = false;
for entry in fs::read_dir(src).wrap_err_with(|| format!("when reading directory {src:?}"))? {
let file = entry?;
if skip(&file, depth) {
continue;
}
let src_path = file.path();
let dst_path = dst.join(file.file_name());
if file.file_type()?.is_file() {
fs::copy(&src_path, &dst_path)
.wrap_err_with(|| format!("when copying file {src_path:?} -> {dst_path:?}"))?;
} else if file.file_type()?.is_dir() {
fs::create_dir(&dst_path).ok();
had_symlinks = copy_dir(&src_path, &dst_path, copy_symlinks, depth + 1, skip)?;
} else if copy_symlinks {
had_symlinks = true;
let link_dst = fs::read_link(src_path)?;
#[cfg(target_family = "unix")]
{
std::os::unix::fs::symlink(link_dst, dst_path)?;
}
#[cfg(target_family = "windows")]
{
let link_dst_absolute = if link_dst.is_absolute() {
link_dst.clone()
} else {
src.join(&link_dst)
};
if link_dst_absolute.is_dir() {
std::os::windows::fs::symlink_dir(link_dst, dst_path)?;
} else {
std::os::windows::fs::symlink_file(link_dst, dst_path)?;
}
}
} else {
had_symlinks = true;
}
}
Ok(had_symlinks)
}
fn warn_symlinks(had_symlinks: bool, msg_info: &mut MessageInfo) -> Result<()> {
if had_symlinks {
msg_info.warn("copied directory contained symlinks. if the volume the link points to was not mounted, the remote build may fail")
} else {
Ok(())
}
}
fn copy_volume_container_rust_base(
engine: &Engine,
container: &str,
sysroot: &Path,
mount_prefix: &Path,
msg_info: &mut MessageInfo,
) -> Result<()> {
let dst = mount_prefix.join("rust");
let rustlib = Path::new("lib").join("rustlib");
create_volume_dir(engine, container, &dst.join(&rustlib), msg_info)?;
for basename in ["bin", "libexec", "etc"] {
let file = sysroot.join(basename);
copy_volume_files(engine, container, &file, &dst, msg_info)?;
}
let tempdir = unsafe { temp::TempDir::new()? };
let temppath = tempdir.path();
fs::create_dir_all(&temppath.join(&rustlib))?;
let mut had_symlinks = copy_dir(
&sysroot.join("lib"),
&temppath.join("lib"),
true,
0,
|e, d| d == 0 && e.file_name() == "rustlib",
)?;
had_symlinks |= copy_dir(
&sysroot.join(&rustlib),
&temppath.join(&rustlib),
true,
0,
|e, d| d == 0 && !(e.file_name() == "src" || e.file_name() == "etc"),
)?;
copy_volume_files(engine, container, &temppath.join("lib"), &dst, msg_info)?;
warn_symlinks(had_symlinks, msg_info)
}
fn copy_volume_container_rust_manifest(
engine: &Engine,
container: &str,
sysroot: &Path,
mount_prefix: &Path,
msg_info: &mut MessageInfo,
) -> Result<()> {
let dst = mount_prefix.join("rust");
let rustlib = Path::new("lib").join("rustlib");
let tempdir = unsafe { temp::TempDir::new()? };
let temppath = tempdir.path();
fs::create_dir_all(&temppath.join(&rustlib))?;
let had_symlinks = copy_dir(
&sysroot.join(&rustlib),
&temppath.join(&rustlib),
true,
0,
|e, d| d != 0 || e.file_type().map(|t| !t.is_file()).unwrap_or(true),
)?;
copy_volume_files(engine, container, &temppath.join("lib"), &dst, msg_info)?;
warn_symlinks(had_symlinks, msg_info)
}
pub fn copy_volume_container_rust_triple(
engine: &Engine,
container: &str,
sysroot: &Path,
triple: &str,
mount_prefix: &Path,
skip_exists: bool,
msg_info: &mut MessageInfo,
) -> Result<()> {
let dst = mount_prefix.join("rust");
let rustlib = Path::new("lib").join("rustlib");
let dst_rustlib = dst.join(&rustlib);
let src_toolchain = sysroot.join(&rustlib).join(triple);
let dst_toolchain = dst_rustlib.join(triple);
let mut skip = false;
if skip_exists {
skip = container_path_exists(engine, container, &dst_toolchain, msg_info)?;
}
if !skip {
copy_volume_files(engine, container, &src_toolchain, &dst_rustlib, msg_info)?;
}
if !skip && skip_exists {
copy_volume_container_rust_manifest(engine, container, sysroot, mount_prefix, msg_info)?;
}
Ok(())
}
pub fn copy_volume_container_rust(
engine: &Engine,
container: &str,
sysroot: &Path,
target: &Target,
mount_prefix: &Path,
skip_target: bool,
msg_info: &mut MessageInfo,
) -> Result<()> {
let target_triple = target.triple();
let image_triple = Host::X86_64UnknownLinuxGnu.triple();
copy_volume_container_rust_base(engine, container, sysroot, mount_prefix, msg_info)?;
copy_volume_container_rust_manifest(engine, container, sysroot, mount_prefix, msg_info)?;
copy_volume_container_rust_triple(
engine,
container,
sysroot,
image_triple,
mount_prefix,
false,
msg_info,
)?;
if !skip_target && target_triple != image_triple {
copy_volume_container_rust_triple(
engine,
container,
sysroot,
target_triple,
mount_prefix,
false,
msg_info,
)?;
}
Ok(())
}
type FingerprintMap = BTreeMap<String, time::SystemTime>;
fn parse_project_fingerprint(path: &Path) -> Result<FingerprintMap> {
let epoch = time::SystemTime::UNIX_EPOCH;
let file = fs::OpenOptions::new().read(true).open(path)?;
let reader = io::BufReader::new(file);
let mut result = BTreeMap::new();
for line in reader.lines() {
let line = line?;
let (timestamp, relpath) = line
.split_once('\t')
.ok_or_else(|| eyre::eyre!("unable to parse fingerprint line '{line}'"))?;
let modified = epoch + time::Duration::from_millis(timestamp.parse::<u64>()?);
result.insert(relpath.to_owned(), modified);
}
Ok(result)
}
fn write_project_fingerprint(path: &Path, fingerprint: &FingerprintMap) -> Result<()> {
let epoch = time::SystemTime::UNIX_EPOCH;
let mut file = fs::OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(path)?;
for (relpath, modified) in fingerprint {
let timestamp = modified.duration_since(epoch)?.as_millis() as u64;
writeln!(file, "{timestamp}\t{relpath}")?;
}
Ok(())
}
fn read_dir_fingerprint(
home: &Path,
path: &Path,
map: &mut FingerprintMap,
copy_cache: bool,
) -> Result<()> {
let epoch = time::SystemTime::UNIX_EPOCH;
for entry in fs::read_dir(path)? {
let file = entry?;
let file_type = file.file_type()?;
if file_type.is_dir() {
if copy_cache || !is_cachedir(&file) {
read_dir_fingerprint(home, &path.join(file.file_name()), map, copy_cache)?;
}
} else if file_type.is_file() || file_type.is_symlink() {
let modified = file.metadata()?.modified()?;
let millis = modified.duration_since(epoch)?.as_millis() as u64;
let rounded = epoch + time::Duration::from_millis(millis);
let relpath = file.path().strip_prefix(home)?.as_posix()?;
map.insert(relpath, rounded);
}
}
Ok(())
}
fn get_project_fingerprint(home: &Path, copy_cache: bool) -> Result<FingerprintMap> {
let mut result = BTreeMap::new();
read_dir_fingerprint(home, home, &mut result, copy_cache)?;
Ok(result)
}
fn get_fingerprint_difference<'a, 'b>(
previous: &'a FingerprintMap,
current: &'b FingerprintMap,
) -> (Vec<&'b str>, Vec<&'a str>) {
let changed: Vec<&str> = current
.iter()
.filter(|(k, v1)| previous.get(*k).map_or(true, |v2| v1 != &v2))
.map(|(k, _)| k.as_str())
.collect();
let removed: Vec<&str> = previous
.iter()
.filter(|(k, _)| !current.contains_key(*k))
.map(|(k, _)| k.as_str())
.collect();
(changed, removed)
}
fn copy_volume_file_list(
engine: &Engine,
container: &str,
src: &Path,
dst: &Path,
files: &[&str],
msg_info: &mut MessageInfo,
) -> Result<ExitStatus> {
let tempdir = unsafe { temp::TempDir::new()? };
let temppath = tempdir.path();
for file in files {
let src_path = src.join(file);
let dst_path = temppath.join(file);
fs::create_dir_all(dst_path.parent().expect("must have parent"))?;
fs::copy(&src_path, &dst_path)?;
}
copy_volume_files(engine, container, temppath, dst, msg_info)
}
fn remove_volume_file_list(
engine: &Engine,
container: &str,
dst: &Path,
files: &[&str],
msg_info: &mut MessageInfo,
) -> Result<ExitStatus> {
const PATH: &str = "/tmp/remove_list";
let mut script = vec![];
if msg_info.is_verbose() {
script.push("set -x".to_owned());
}
script.push(format!(
"cat \"{PATH}\" | while read line; do
rm -f \"${{line}}\"
done
rm \"{PATH}\"
"
));
let mut tempfile = unsafe { temp::TempFile::new()? };
for file in files {
writeln!(tempfile.file(), "{}", dst.join(file).as_posix()?)?;
}
subcommand_or_exit(engine, "cp")?
.arg(tempfile.path())
.arg(format!("{container}:{PATH}"))
.run_and_get_status(msg_info, true)?;
subcommand_or_exit(engine, "exec")?
.arg(container)
.args(&["sh", "-c", &script.join("\n")])
.run_and_get_status(msg_info, true)
}
fn copy_volume_container_project(
engine: &Engine,
container: &str,
src: &Path,
dst: &Path,
volume: &VolumeId,
copy_cache: bool,
msg_info: &mut MessageInfo,
) -> Result<()> {
let copy_all = |info: &mut MessageInfo| {
if copy_cache {
copy_volume_files(engine, container, src, dst, info)
} else {
copy_volume_files_nocache(engine, container, src, dst, true, info)
}
};
match volume {
VolumeId::Keep(_) => {
let parent = temp::dir()?;
fs::create_dir_all(&parent)?;
let fingerprint = parent.join(container);
let current = get_project_fingerprint(src, copy_cache)?;
if fingerprint.exists() && container_path_exists(engine, container, dst, msg_info)? {
let previous = parse_project_fingerprint(&fingerprint)?;
let (changed, removed) = get_fingerprint_difference(&previous, ¤t);
write_project_fingerprint(&fingerprint, ¤t)?;
if !changed.is_empty() {
copy_volume_file_list(engine, container, src, dst, &changed, msg_info)?;
}
if !removed.is_empty() {
remove_volume_file_list(engine, container, dst, &removed, msg_info)?;
}
} else {
write_project_fingerprint(&fingerprint, ¤t)?;
copy_all(msg_info)?;
}
}
VolumeId::Discard => {
copy_all(msg_info)?;
}
}
Ok(())
}
fn run_and_get_status(
engine: &Engine,
args: &[&str],
msg_info: &mut MessageInfo,
) -> Result<ExitStatus> {
command(engine)
.args(args)
.run_and_get_status(msg_info, true)
}
fn run_and_get_output(
engine: &Engine,
args: &[&str],
msg_info: &mut MessageInfo,
) -> Result<Output> {
command(engine).args(args).run_and_get_output(msg_info)
}
pub fn volume_create(
engine: &Engine,
volume: &str,
msg_info: &mut MessageInfo,
) -> Result<ExitStatus> {
run_and_get_status(engine, &["volume", "create", volume], msg_info)
}
pub fn volume_rm(engine: &Engine, volume: &str, msg_info: &mut MessageInfo) -> Result<ExitStatus> {
run_and_get_status(engine, &["volume", "rm", volume], msg_info)
}
pub fn volume_exists(engine: &Engine, volume: &str, msg_info: &mut MessageInfo) -> Result<bool> {
run_and_get_output(engine, &["volume", "inspect", volume], msg_info)
.map(|output| output.status.success())
}
pub fn container_stop(
engine: &Engine,
container: &str,
timeout: u32,
msg_info: &mut MessageInfo,
) -> Result<ExitStatus> {
run_and_get_status(
engine,
&["stop", container, "--time", &timeout.to_string()],
msg_info,
)
}
pub fn container_stop_default(
engine: &Engine,
container: &str,
msg_info: &mut MessageInfo,
) -> Result<ExitStatus> {
container_stop(engine, container, DEFAULT_TIMEOUT, msg_info)
}
pub fn container_rm(
engine: &Engine,
container: &str,
msg_info: &mut MessageInfo,
) -> Result<ExitStatus> {
run_and_get_output(engine, &["rm", container], msg_info).map(|output| output.status)
}
pub fn container_state(
engine: &Engine,
container: &str,
msg_info: &mut MessageInfo,
) -> Result<ContainerState> {
let stdout = command(engine)
.args(&["ps", "-a"])
.args(&["--filter", &format!("name={container}")])
.args(&["--format", "{{.State}}"])
.run_and_get_stdout(msg_info)?;
ContainerState::new(stdout.trim())
}
pub fn unique_toolchain_identifier(sysroot: &Path) -> Result<String> {
let commit_hash = if let Some(version) = rustup::rustc_version_string(sysroot)? {
rustc::hash_from_version_string(&version, 1)
} else {
rustc::version_meta()?.commit_hash()
};
let toolchain_name = sysroot
.file_name()
.expect("should be able to get toolchain name")
.to_utf8()?;
let toolchain_hash = path_hash(sysroot)?;
Ok(format!(
"cross-{toolchain_name}-{toolchain_hash}-{commit_hash}"
))
}
pub fn unique_container_identifier(
target: &Target,
metadata: &CargoMetadata,
dirs: &Directories,
) -> Result<String> {
let workspace_root = &metadata.workspace_root;
let package = metadata
.packages
.iter()
.find(|p| {
p.manifest_path
.parent()
.expect("manifest path should have a parent directory")
== workspace_root
})
.unwrap_or_else(|| {
metadata
.packages
.get(0)
.expect("should have at least 1 package")
});
let name = &package.name;
let triple = target.triple();
let toolchain_id = unique_toolchain_identifier(&dirs.sysroot)?;
let project_hash = path_hash(&package.manifest_path)?;
Ok(format!("{toolchain_id}-{triple}-{name}-{project_hash}"))
}
fn mount_path(val: &Path) -> Result<String> {
let host_path = file::canonicalize(val)?;
canonicalize_mount_path(&host_path)
}
pub(crate) fn run(
options: DockerOptions,
paths: DockerPaths,
args: &[String],
msg_info: &mut MessageInfo,
) -> Result<ExitStatus> {
let engine = &options.engine;
let target = &options.target;
let dirs = &paths.directories;
let mount_prefix = MOUNT_PREFIX;
let toolchain_id = unique_toolchain_identifier(&dirs.sysroot)?;
let container = unique_container_identifier(target, &paths.metadata, dirs)?;
let volume = VolumeId::create(engine, &toolchain_id, msg_info)?;
let state = container_state(engine, &container, msg_info)?;
if !state.is_stopped() {
msg_info.warn(format_args!("container {container} was running."))?;
container_stop_default(engine, &container, msg_info)?;
}
if state.exists() {
msg_info.warn(format_args!("container {container} was exited."))?;
container_rm(engine, &container, msg_info)?;
}
let mut docker = subcommand(engine, "run");
docker_userns(&mut docker);
docker.args(&["--name", &container]);
docker.arg("--rm");
let volume_mount = match volume {
VolumeId::Keep(ref id) => format!("{id}:{mount_prefix}"),
VolumeId::Discard => mount_prefix.to_owned(),
};
docker.args(&["-v", &volume_mount]);
let mut volumes = vec![];
let mount_volumes = docker_mount(
&mut docker,
&options,
&paths,
|_, val| mount_path(val),
|(src, dst)| volumes.push((src, dst)),
)
.wrap_err("could not determine mount points")?;
docker_seccomp(&mut docker, engine.kind, target, &paths.metadata)
.wrap_err("when copying seccomp profile")?;
docker.args(&["-v", &format!("{mount_prefix}/cargo/bin")]);
if let Some(ref nix_store) = dirs.nix_store {
let nix_string = nix_store.to_utf8()?;
volumes.push((nix_string.to_owned(), nix_string.to_owned()));
}
docker.arg("-d");
let is_tty = io::Stdin::is_atty() && io::Stdout::is_atty() && io::Stderr::is_atty();
if is_tty {
docker.arg("-t");
}
docker.arg(&image_name(&options.config, target)?);
if !is_tty {
docker.args(&["sh", "-c", "sleep infinity"]);
}
create_container_deleter(engine.clone(), container.clone());
docker.run_and_get_status(msg_info, true)?;
let copy_cache = env::var("CROSS_REMOTE_COPY_CACHE")
.map(|s| bool_from_envvar(&s))
.unwrap_or_default();
let copy = |src, dst: &PathBuf, info: &mut MessageInfo| {
if copy_cache {
copy_volume_files(engine, &container, src, dst, info)
} else {
copy_volume_files_nocache(engine, &container, src, dst, true, info)
}
};
let mount_prefix_path = mount_prefix.as_ref();
if let VolumeId::Discard = volume {
copy_volume_container_xargo(
engine,
&container,
&dirs.xargo,
target,
mount_prefix_path,
msg_info,
)
.wrap_err("when copying xargo")?;
copy_volume_container_cargo(
engine,
&container,
&dirs.cargo,
mount_prefix_path,
false,
msg_info,
)
.wrap_err("when copying cargo")?;
copy_volume_container_rust(
engine,
&container,
&dirs.sysroot,
target,
mount_prefix_path,
false,
msg_info,
)
.wrap_err("when copying rust")?;
} else {
copy_volume_container_rust_triple(
engine,
&container,
&dirs.sysroot,
target.triple(),
mount_prefix_path,
true,
msg_info,
)
.wrap_err("when copying rust target files")?;
}
let mount_root = if mount_volumes {
let rel_mount_root = dirs
.mount_root
.strip_prefix('/')
.expect("mount root should be absolute");
let mount_root = mount_prefix_path.join(rel_mount_root);
if !rel_mount_root.is_empty() {
create_volume_dir(
engine,
&container,
mount_root
.parent()
.expect("mount root should have a parent directory"),
msg_info,
)
.wrap_err("when creating mount root")?;
}
mount_root
} else {
mount_prefix_path.join("project")
};
copy_volume_container_project(
engine,
&container,
&dirs.host_root,
&mount_root,
&volume,
copy_cache,
msg_info,
)
.wrap_err("when copying project")?;
let mut copied = vec![
(&dirs.xargo, mount_prefix_path.join("xargo")),
(&dirs.cargo, mount_prefix_path.join("cargo")),
(&dirs.sysroot, mount_prefix_path.join("rust")),
(&dirs.host_root, mount_root.clone()),
];
let mut to_symlink = vec![];
let target_dir = file::canonicalize(&dirs.target)?;
let target_dir = if let Ok(relpath) = target_dir.strip_prefix(&dirs.host_root) {
mount_root.join(relpath)
} else {
let target_dir = mount_prefix_path.join("target");
if copy_cache {
copy(&dirs.target, &target_dir, msg_info)?;
} else {
create_volume_dir(engine, &container, &target_dir, msg_info)?;
}
copied.push((&dirs.target, target_dir.clone()));
target_dir
};
for (src, dst) in &volumes {
let src: &Path = src.as_ref();
if let Some((psrc, pdst)) = copied.iter().find(|(p, _)| src.starts_with(p)) {
let relpath = src
.strip_prefix(psrc)
.expect("source should start with prefix");
to_symlink.push((pdst.join(relpath), dst));
} else {
let rel_dst = dst
.strip_prefix('/')
.expect("destination should be absolute");
let mount_dst = mount_prefix_path.join(rel_dst);
if !rel_dst.is_empty() {
create_volume_dir(
engine,
&container,
mount_dst
.parent()
.expect("destination should have a parent directory"),
msg_info,
)?;
}
copy(src, &mount_dst, msg_info)?;
}
}
let mut final_args = vec![];
let mut iter = args.iter().cloned();
let mut has_target_dir = false;
let target_dir_string = target_dir.as_posix()?;
while let Some(arg) = iter.next() {
if arg == "--target-dir" {
has_target_dir = true;
final_args.push(arg);
if iter.next().is_some() {
final_args.push(target_dir_string.clone());
}
} else if arg.starts_with("--target-dir=") {
has_target_dir = true;
if arg.split_once('=').is_some() {
final_args.push(format!("--target-dir={target_dir_string}"));
}
} else {
final_args.push(arg);
}
}
if !has_target_dir {
final_args.push("--target-dir".to_owned());
final_args.push(target_dir_string);
}
let mut cmd = cargo_safe_command(options.uses_xargo);
cmd.args(final_args);
let mut symlink = vec!["set -e pipefail".to_owned()];
if msg_info.is_verbose() {
symlink.push("set -x".to_owned());
}
symlink.push(format!(
"chown -R {uid}:{gid} {mount_prefix}",
uid = user_id(),
gid = group_id(),
));
symlink.push(format!(
"prefix=\"{mount_prefix}\"
symlink_recurse() {{
for f in \"${{1}}\"/*; do
dst=${{f#\"$prefix\"}}
if [ -f \"${{dst}}\" ]; then
echo \"invalid: got unexpected file at ${{dst}}\" 1>&2
exit 1
elif [ -d \"${{dst}}\" ]; then
symlink_recurse \"${{f}}\"
else
ln -s \"${{f}}\" \"${{dst}}\"
fi
done
}}
symlink_recurse \"${{prefix}}\"
"
));
for (src, dst) in to_symlink {
symlink.push(format!("ln -s \"{}\" \"{}\"", src.as_posix()?, dst));
}
subcommand_or_exit(engine, "exec")?
.arg(&container)
.args(&["sh", "-c", &symlink.join("\n")])
.run_and_get_status(msg_info, false)
.wrap_err("when creating symlinks to provide consistent host/mount paths")?;
let mut docker = subcommand(engine, "exec");
docker_user_id(&mut docker, engine.kind);
docker_envvars(&mut docker, &options.config, target, msg_info)?;
docker_cwd(&mut docker, &paths, mount_volumes)?;
docker.arg(&container);
docker.args(&["sh", "-c", &format!("PATH=$PATH:/rust/bin {:?}", cmd)]);
bail_container_exited!();
let status = docker
.run_and_get_status(msg_info, false)
.map_err(Into::into);
let skip_artifacts = env::var("CROSS_REMOTE_SKIP_BUILD_ARTIFACTS")
.map(|s| bool_from_envvar(&s))
.unwrap_or_default();
bail_container_exited!();
if !skip_artifacts && container_path_exists(engine, &container, &target_dir, msg_info)? {
subcommand_or_exit(engine, "cp")?
.arg("-a")
.arg(&format!("{container}:{}", target_dir.as_posix()?))
.arg(
&dirs
.target
.parent()
.expect("target directory should have a parent"),
)
.run_and_get_status(msg_info, false)
.map_err::<eyre::ErrReport, _>(Into::into)?;
}
drop_container(is_tty, msg_info);
status
}