Skip to content

Commit 6277b84

Browse files
committed
Make Rust installation persistent
Remove Rust toolchain from the container image. Instead, create bind mounts for `~/.cargo` and `~/.rustup`, bootstrap the toolchain in a separate step, so it stays persistent. Fixes #10
1 parent 9f6f937 commit 6277b84

File tree

3 files changed

+82
-19
lines changed

3 files changed

+82
-19
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ oci-client = { version = "0.14", default-features = false, features = ["rustls-t
3333
# icedragon.
3434
protobuf = "=3.2.0"
3535
rand = { version = "0.9", default-features = false, features = ["os_rng", "std_rng"] }
36+
reqwest = { version = "0.12", default-features = false }
3637
target-lexicon = { version = "0.12", default-features = false }
3738
tar = { version = "0.4", default-features = false }
3839
thiserror = { version = "1.0.64", default-features = false }

containers/Dockerfile

-13
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,6 @@ COPY package.accept_keywords/* /etc/portage/package.accept_keywords/
8282
# * Regular expressions:
8383
# * libpcre2
8484
#
85-
# Install stable and beta Rust toolchains with `default` rustup profile
86-
# (containing rust-docs, rustfmt, and clippy) for all supported targets.
87-
#
88-
# Install nightly Rust toolchains with `complete` rustup profile (containing
89-
# all components provided by rustup, available only for nightly toolchains)
90-
# for all supported targets.
91-
#
9285
# [0] https://wiki.gentoo.org/wiki/Crossdev
9386
# [1] https://github.com/llvm/llvm-project/tree/main/llvm-libgcc
9487
# [2] https://github.com/rust-lang/rust/issues/119504
@@ -164,12 +157,6 @@ RUN emerge-webrsync \
164157
sys-libs/error-standalone \
165158
sys-libs/fts-standalone \
166159
sys-libs/zlib \
167-
&& curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \
168-
&& rustup toolchain install stable beta --profile=default \
169-
--target=aarch64-unknown-linux-musl,x86_64-unknown-linux-musl \
170-
&& rustup toolchain install nightly --profile=complete \
171-
--target=aarch64-unknown-linux-musl,x86_64-unknown-linux-musl \
172-
&& cargo install btfdump \
173160
&& rm -rf \
174161
/var/cache/binpkgs/* \
175162
/var/cache/distfiles/* \

src/main.rs

+81-6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::{
44
collections::VecDeque,
55
env,
66
ffi::{OsStr, OsString},
7+
os::unix::fs::PermissionsExt,
78
path::{Path, PathBuf},
89
process::Stdio,
910
str::FromStr as _,
@@ -551,7 +552,7 @@ fn bind_mount(src: impl Into<PathBuf>, dst: impl Into<PathBuf>) -> anyhow::Resul
551552

552553
/// Returns mounts for the container - predefined defaults and the bind mounts
553554
/// based on user-provided `volumes`.
554-
fn mounts(volumes: Vec<String>) -> anyhow::Result<Vec<Mount>> {
555+
fn mounts(volumes: Vec<String>, cargo_dir: &Path, rustup_dir: &Path) -> anyhow::Result<Vec<Mount>> {
555556
let mut mounts = get_rootless_mounts();
556557
// Mount the current directory.
557558
let src_mount = bind_mount(env::current_dir()?, "/src")?;
@@ -568,6 +569,11 @@ fn mounts(volumes: Vec<String>) -> anyhow::Result<Vec<Mount>> {
568569
let ssh_keys_dir = Path::new(&home_dir).join(".ssh");
569570
let ssh_keys_mount = bind_mount(ssh_keys_dir, "/root/.ssh")?;
570571
mounts.push(ssh_keys_mount);
572+
// Mount Rust toolchain.
573+
let cargo_mount = bind_mount(cargo_dir, "/root/.cargo")?;
574+
mounts.push(cargo_mount);
575+
let rustup_mount = bind_mount(rustup_dir, "/root/.rustup")?;
576+
mounts.push(rustup_mount);
571577
// Mount all the user-provided volumes.
572578
for volume in volumes {
573579
let parts: Vec<&str> = volume.split(':').collect();
@@ -584,6 +590,8 @@ fn mounts(volumes: Vec<String>) -> anyhow::Result<Vec<Mount>> {
584590
fn container_spec(
585591
interactive: bool,
586592
rootfs_dir: &Path,
593+
cargo_dir: &Path,
594+
rustup_dir: &Path,
587595
triple: &Triple,
588596
volumes: Vec<String>,
589597
cmd_args: impl Into<Vec<String>>,
@@ -605,7 +613,7 @@ fn container_spec(
605613
.build()?;
606614
let spec = SpecBuilder::default()
607615
.root(root)
608-
.mounts(mounts(volumes)?)
616+
.mounts(mounts(volumes, cargo_dir, rustup_dir)?)
609617
.process(process)
610618
.linux(linux_spec)
611619
.build()?;
@@ -621,7 +629,7 @@ async fn run_container(
621629
) -> anyhow::Result<()> {
622630
let container_id = rand_string(rng, 5);
623631
let container_id = format!("icdrgn-{container_id}");
624-
let container_task = tokio::task::spawn_blocking({
632+
tokio::task::spawn_blocking({
625633
move || {
626634
let mut container = ContainerBuilder::new(container_id, SyscallType::Linux)
627635
.with_executor(DefaultExecutor {})
@@ -638,8 +646,59 @@ async fn run_container(
638646
waitpid(container.pid().expect("container should have a pid"), None).unwrap();
639647
container.delete(true).unwrap();
640648
}
641-
});
642-
container_task.await?;
649+
})
650+
.await?;
651+
652+
Ok(())
653+
}
654+
655+
async fn bootstrap_rustup(
656+
rng: &mut StdRng,
657+
rootfs_dir: &Path,
658+
cargo_dir: &Path,
659+
rustup_dir: &Path,
660+
triple: &Triple,
661+
) -> anyhow::Result<()> {
662+
let bundle_dir = Path::new("/tmp/icedragon-rustup");
663+
fs::create_dir_all(bundle_dir).await?;
664+
fs::create_dir_all(cargo_dir).await?;
665+
fs::create_dir_all(rustup_dir).await?;
666+
667+
let rustup_init_file = rootfs_dir.join("rustup-init.sh");
668+
669+
let response = reqwest::get("https://sh.rustup.rs").await?;
670+
let mut response = response.error_for_status()?;
671+
let content_length = response.content_length().ok_or(anyhow!(
672+
"failed to get the content-length of the rustup-install script"
673+
))?;
674+
675+
let pb_style = ProgressStyle::with_template("{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})")?
676+
.progress_chars("#>-");
677+
let pb = ProgressBar::new(content_length);
678+
pb.set_style(pb_style.clone());
679+
680+
let mut rustup_init_file = File::create(rustup_init_file).await?;
681+
while let Some(chunk) = response.chunk().await? {
682+
rustup_init_file.write_all(&chunk).await?;
683+
pb.inc(chunk.len() as u64);
684+
}
685+
pb.finish_and_clear();
686+
687+
let mut perms = rustup_init_file.metadata().await?.permissions();
688+
perms.set_mode(0o755);
689+
rustup_init_file.set_permissions(perms).await?;
690+
691+
let volumes = vec![];
692+
let cmd_args = vec!["/bin/sh".into(), "/rustup-init.sh".into(), "-y".into()];
693+
694+
let spec_file = bundle_dir.join("config.json");
695+
let spec = container_spec(
696+
false, rootfs_dir, cargo_dir, rustup_dir, triple, volumes, cmd_args,
697+
)?;
698+
spec.save(&spec_file)?;
699+
700+
run_container(rng, bundle_dir.into(), rootfs_dir.into()).await?;
701+
643702
Ok(())
644703
}
645704

@@ -678,8 +737,24 @@ async fn run_command(
678737

679738
let rootfs_dir = state_dir.join("rootfs");
680739
pull_image(&mut rng, state_dir, &rootfs_dir, container_image).await?;
740+
741+
// Bootstrap the Rust toolchain, if not present.
742+
let cargo_dir = state_dir.join("cargo");
743+
let rustup_dir = state_dir.join("rustup");
744+
if !cargo_dir.exists() || !rustup_dir.exists() {
745+
bootstrap_rustup(&mut rng, &rootfs_dir, &cargo_dir, &rustup_dir, triple).await?;
746+
}
747+
681748
let spec_file = bundle_dir.join("config.json");
682-
let spec = container_spec(interactive, &rootfs_dir, triple, volumes, cmd_args)?;
749+
let spec = container_spec(
750+
interactive,
751+
&rootfs_dir,
752+
&cargo_dir,
753+
&rustup_dir,
754+
triple,
755+
volumes,
756+
cmd_args,
757+
)?;
683758
spec.save(&spec_file)?;
684759

685760
run_container(&mut rng, bundle_dir.clone(), rootfs_dir).await?;

0 commit comments

Comments
 (0)