@@ -4,6 +4,7 @@ use std::{
4
4
collections:: VecDeque ,
5
5
env,
6
6
ffi:: { OsStr , OsString } ,
7
+ os:: unix:: fs:: PermissionsExt ,
7
8
path:: { Path , PathBuf } ,
8
9
process:: Stdio ,
9
10
str:: FromStr as _,
@@ -551,7 +552,7 @@ fn bind_mount(src: impl Into<PathBuf>, dst: impl Into<PathBuf>) -> anyhow::Resul
551
552
552
553
/// Returns mounts for the container - predefined defaults and the bind mounts
553
554
/// 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 > > {
555
556
let mut mounts = get_rootless_mounts ( ) ;
556
557
// Mount the current directory.
557
558
let src_mount = bind_mount ( env:: current_dir ( ) ?, "/src" ) ?;
@@ -568,6 +569,11 @@ fn mounts(volumes: Vec<String>) -> anyhow::Result<Vec<Mount>> {
568
569
let ssh_keys_dir = Path :: new ( & home_dir) . join ( ".ssh" ) ;
569
570
let ssh_keys_mount = bind_mount ( ssh_keys_dir, "/root/.ssh" ) ?;
570
571
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) ;
571
577
// Mount all the user-provided volumes.
572
578
for volume in volumes {
573
579
let parts: Vec < & str > = volume. split ( ':' ) . collect ( ) ;
@@ -584,6 +590,8 @@ fn mounts(volumes: Vec<String>) -> anyhow::Result<Vec<Mount>> {
584
590
fn container_spec (
585
591
interactive : bool ,
586
592
rootfs_dir : & Path ,
593
+ cargo_dir : & Path ,
594
+ rustup_dir : & Path ,
587
595
triple : & Triple ,
588
596
volumes : Vec < String > ,
589
597
cmd_args : impl Into < Vec < String > > ,
@@ -605,7 +613,7 @@ fn container_spec(
605
613
. build ( ) ?;
606
614
let spec = SpecBuilder :: default ( )
607
615
. root ( root)
608
- . mounts ( mounts ( volumes) ?)
616
+ . mounts ( mounts ( volumes, cargo_dir , rustup_dir ) ?)
609
617
. process ( process)
610
618
. linux ( linux_spec)
611
619
. build ( ) ?;
@@ -621,7 +629,7 @@ async fn run_container(
621
629
) -> anyhow:: Result < ( ) > {
622
630
let container_id = rand_string ( rng, 5 ) ;
623
631
let container_id = format ! ( "icdrgn-{container_id}" ) ;
624
- let container_task = tokio:: task:: spawn_blocking ( {
632
+ tokio:: task:: spawn_blocking ( {
625
633
move || {
626
634
let mut container = ContainerBuilder :: new ( container_id, SyscallType :: Linux )
627
635
. with_executor ( DefaultExecutor { } )
@@ -638,8 +646,59 @@ async fn run_container(
638
646
waitpid ( container. pid ( ) . expect ( "container should have a pid" ) , None ) . unwrap ( ) ;
639
647
container. delete ( true ) . unwrap ( ) ;
640
648
}
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
+
643
702
Ok ( ( ) )
644
703
}
645
704
@@ -678,8 +737,24 @@ async fn run_command(
678
737
679
738
let rootfs_dir = state_dir. join ( "rootfs" ) ;
680
739
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
+
681
748
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
+ ) ?;
683
758
spec. save ( & spec_file) ?;
684
759
685
760
run_container ( & mut rng, bundle_dir. clone ( ) , rootfs_dir) . await ?;
0 commit comments