Repository: cross-rs/cross
Pull request #1087 (2022-10-19 19:13:41)

Support crosstool for uclibc, musl, and newlib.

Alexhuszagh: > minor nit

What's the nit?
Alexhuszagh: bors r-
This actually needs a mirror config option for pre-1.0 uclibc, which is required for some embedded systems.
bors[bot]: Canceled.

Patch:
diff --git a/.gitattributes b/.gitattributes
deleted file mode 100644
index 39a109d..0000000
--- a/.gitattributes
+++ /dev/null
@@ -1,1 +0,0 @@
-Dockerfile.* linguist-language=Dockerfile
diff --git a/docker/crosstool-config/arm-unknown-linux-gnueabihf.config b/docker/crosstool-config/arm-unknown-linux-gnueabihf.config
index 0b40388..e8988f4 100644
--- a/docker/crosstool-config/arm-unknown-linux-gnueabihf.config
+++ b/docker/crosstool-config/arm-unknown-linux-gnueabihf.config
@@ -260,7 +260,6 @@ CT_LINUX_PATCH_ORDER="global"
 CT_LINUX_V_4_19=y
 # CT_LINUX_NO_VERSIONS is not set
 CT_LINUX_VERSION="4.19.21"
-
 CT_LINUX_MIRRORS="$(CT_Mirrors kernel.org linux ${CT_LINUX_VERSION})"
 CT_LINUX_ARCHIVE_FILENAME="@{pkg_name}-@{version}"
 CT_LINUX_ARCHIVE_DIRNAME="@{pkg_name}-@{version}"
@@ -272,7 +271,6 @@ CT_LINUX_later_than_3_7=y
 CT_LINUX_3_7_or_later=y
 CT_LINUX_later_than_3_2=y
 CT_LINUX_3_2_or_later=y
-
 CT_KERNEL_LINUX_VERBOSITY_0=y
 # CT_KERNEL_LINUX_VERBOSITY_1 is not set
 # CT_KERNEL_LINUX_VERBOSITY_2 is not set
@@ -370,7 +368,6 @@ CT_GLIBC_PATCH_ORDER="global"
 CT_GLIBC_V_2_17=y
 # CT_GLIBC_NO_VERSIONS is not set
 CT_GLIBC_VERSION="2.17"
-
 CT_GLIBC_MIRRORS="$(CT_Mirrors GNU glibc)"
 CT_GLIBC_ARCHIVE_FILENAME="@{pkg_name}-@{version}"
 CT_GLIBC_ARCHIVE_DIRNAME="@{pkg_name}-@{version}"
@@ -394,7 +391,6 @@ CT_GLIBC_2_17_or_later=y
 CT_GLIBC_2_17_or_older=y
 CT_GLIBC_later_than_2_14=y
 CT_GLIBC_2_14_or_later=y
-
 CT_GLIBC_DEP_KERNEL_HEADERS_VERSION=y
 CT_GLIBC_DEP_BINUTILS=y
 CT_GLIBC_DEP_GCC=y
@@ -459,7 +455,6 @@ CT_GCC_PATCH_ORDER="global"
 CT_GCC_V_8=y
 # CT_GCC_NO_VERSIONS is not set
 CT_GCC_VERSION="8.3.0"
-
 CT_GCC_MIRRORS="$(CT_Mirrors GNU gcc/gcc-${CT_GCC_VERSION}) $(CT_Mirrors sourceware gcc/releases/gcc-${CT_GCC_VERSION})"
 CT_GCC_ARCHIVE_FILENAME="@{pkg_name}-@{version}"
 CT_GCC_ARCHIVE_DIRNAME="@{pkg_name}-@{version}"
@@ -475,7 +470,6 @@ CT_GCC_later_than_4_9=y
 CT_GCC_4_9_or_later=y
 CT_GCC_later_than_4_8=y
 CT_GCC_4_8_or_later=y
-
 CT_CC_GCC_HAS_LIBMPX=y
 CT_CC_GCC_ENABLE_CXX_FLAGS=""
 CT_CC_GCC_CORE_EXTRA_CONFIG_ARRAY=""
@@ -722,3 +716,4 @@ CT_ZLIB=y
 # CT_COMP_TOOLS_M4 is not set
 # CT_COMP_TOOLS_MAKE is not set
 CT_ALL_COMP_TOOLS_CHOICES="AUTOCONF AUTOMAKE BISON DTC LIBTOOL M4 MAKE"
+
diff --git a/src/bin/commands/containers.rs b/src/bin/commands/containers.rs
index 0043280..65f121c 100644
--- a/src/bin/commands/containers.rs
+++ b/src/bin/commands/containers.rs
@@ -396,7 +396,7 @@ pub fn create_persistent_volume(
     if let Some(channel) = channel {
         toolchain.channel = channel.channel.clone();
     };
-    let mount_finder = docker::MountFinder::create(engine, msg_info)?;
+    let mount_finder = docker::MountFinder::create(engine)?;
     let dirs = docker::ToolchainDirectories::assemble(&mount_finder, toolchain.clone())?;
     let container_id = dirs.unique_container_identifier(&toolchain.host().target)?;
     let volume_id = dirs.unique_toolchain_identifier()?;
@@ -464,7 +464,7 @@ pub fn remove_persistent_volume(
     if let Some(channel) = channel {
         toolchain.channel = channel.channel.clone();
     };
-    let mount_finder = docker::MountFinder::create(engine, msg_info)?;
+    let mount_finder = docker::MountFinder::create(engine)?;
     let dirs = docker::ToolchainDirectories::assemble(&mount_finder, toolchain)?;
     let volume_id = dirs.unique_toolchain_identifier()?;
     let volume = docker::DockerVolume::new(engine, &volume_id);
diff --git a/src/bin/commands/images.rs b/src/bin/commands/images.rs
index c511b50..fa59f83 100644
--- a/src/bin/commands/images.rs
+++ b/src/bin/commands/images.rs
@@ -1,6 +1,5 @@
 use std::collections::{BTreeMap, BTreeSet};
 
-use clap::builder::PossibleValue;
 use clap::{Args, Subcommand};
 use cross::docker::{self, CROSS_CUSTOM_DOCKERFILE_IMAGE_PREFIX};
 use cross::shell::MessageInfo;
@@ -27,9 +26,6 @@ pub struct ListImages {
     /// Container engine (such as docker or podman).
     #[clap(long)]
     pub engine: Option<String>,
-    /// Output format
-    #[clap(long, default_value = "human")]
-    pub format: OutputFormat,
     /// Only list images for specific target(s). By default, list all targets.
     pub targets: Vec<String>,
 }
@@ -40,25 +36,6 @@ impl ListImages {
     }
 }
 
-#[derive(Clone, Debug)]
-pub enum OutputFormat {
-    Human,
-    Json,
-}
-
-impl clap::ValueEnum for OutputFormat {
-    fn value_variants<'a>() -> &'a [Self] {
-        &[Self::Human, Self::Json]
-    }
-
-    fn to_possible_value(&self) -> Option<PossibleValue> {
-        match self {
-            OutputFormat::Human => Some(PossibleValue::new("human")),
-            OutputFormat::Json => Some(PossibleValue::new("json")),
-        }
-    }
-}
-
 #[derive(Args, Debug)]
 pub struct RemoveImages {
     /// If not provided, remove all images.
@@ -141,7 +118,7 @@ impl Images {
     }
 }
 
-#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, serde::Serialize)]
+#[derive(Debug, PartialOrd, Ord, PartialEq, Eq)]
 struct Image {
     repository: String,
     tag: String,
@@ -281,9 +258,7 @@ fn get_image_target(
 }
 
 pub fn list_images(
-    ListImages {
-        targets, format, ..
-    }: ListImages,
+    ListImages { targets, .. }: ListImages,
     engine: &docker::Engine,
     msg_info: &mut MessageInfo,
 ) -> cross::Result<()> {
@@ -306,55 +281,45 @@ pub fn list_images(
     let mut keys: Vec<&str> = map.keys().map(|k| k.as_ref()).collect();
     keys.sort_unstable();
 
-    match format {
-        OutputFormat::Json => {
-            msg_info.info(format_args!("{}", serde_json::to_string(&map)?))?;
-        }
-        OutputFormat::Human => {
-            let print_string =
-                |col1: &str, col2: &str, fill: char, info: &mut MessageInfo| -> cross::Result<()> {
-                    let mut row = String::new();
-                    row.push('|');
-                    row.push(fill);
-                    row.push_str(col1);
-                    let spaces = max_target_len.max(col1.len()) + 1 - col1.len();
-                    for _ in 0..spaces {
-                        row.push(fill);
-                    }
-                    row.push('|');
-                    row.push(fill);
-                    row.push_str(col2);
-                    let spaces = max_image_len.max(col2.len()) + 1 - col2.len();
-                    for _ in 0..spaces {
-                        row.push(fill);
-                    }
-                    row.push('|');
-                    info.print(row)
-                };
-
-            if targets.len() != 1 {
-                print_string("Targets", "Images", ' ', msg_info)?;
-                print_string("-------", "------", '-', msg_info)?;
+    let print_string =
+        |col1: &str, col2: &str, fill: char, info: &mut MessageInfo| -> cross::Result<()> {
+            let mut row = String::new();
+            row.push('|');
+            row.push(fill);
+            row.push_str(col1);
+            let spaces = max_target_len.max(col1.len()) + 1 - col1.len();
+            for _ in 0..spaces {
+                row.push(fill);
             }
+            row.push('|');
+            row.push(fill);
+            row.push_str(col2);
+            let spaces = max_image_len.max(col2.len()) + 1 - col2.len();
+            for _ in 0..spaces {
+                row.push(fill);
+            }
+            row.push('|');
+            info.print(row)
+        };
+
+    if targets.len() != 1 {
+        print_string("Targets", "Images", ' ', msg_info)?;
+        print_string("-------", "------", '-', msg_info)?;
+    }
 
-            let print_single = |_: &str,
-                                image: &Image,
-                                info: &mut MessageInfo|
-             -> cross::Result<()> { info.print(image) };
-            let print_table =
-                |target: &str, image: &Image, info: &mut MessageInfo| -> cross::Result<()> {
-                    let name = image.name();
-                    print_string(target, &name, ' ', info)
-                };
-
-            for target in keys {
-                for image in map.get(target).expect("map must have key").iter() {
-                    if targets.len() == 1 {
-                        print_single(target, image, msg_info)?;
-                    } else {
-                        print_table(target, image, msg_info)?;
-                    }
-                }
+    let print_single =
+        |_: &str, image: &Image, info: &mut MessageInfo| -> cross::Result<()> { info.print(image) };
+    let print_table = |target: &str, image: &Image, info: &mut MessageInfo| -> cross::Result<()> {
+        let name = image.name();
+        print_string(target, &name, ' ', info)
+    };
+
+    for target in keys {
+        for image in map.get(target).expect("map must have key").iter() {
+            if targets.len() == 1 {
+                print_single(target, image, msg_info)?;
+            } else {
+                print_table(target, image, msg_info)?;
             }
         }
     }
diff --git a/src/bin/cross.rs b/src/bin/cross.rs
index 64536d2..9210683 100644
--- a/src/bin/cross.rs
+++ b/src/bin/cross.rs
@@ -18,7 +18,13 @@ pub fn main() -> cross::Result<()> {
     let target_list = rustc::target_list(&mut Verbosity::Quiet.into())?;
     let args = cli::parse(&target_list)?;
     let subcommand = args.subcommand;
-    let mut msg_info = shell::MessageInfo::create(args.verbose, args.quiet, args.color.as_deref())?;
+    let mut msg_info = shell::MessageInfo::create(
+        args.verbose
+            + cross::config::bool_from_envvar(&std::env::var("CROSS_DEBUG").unwrap_or_default())
+                as u8,
+        args.quiet,
+        args.color.as_deref(),
+    )?;
     let status = match cross::run(args, target_list, &mut msg_info)? {
         Some(status) => status,
         None => {
diff --git a/src/docker/remote.rs b/src/docker/remote.rs
index 2a05553..8f4dede 100644
--- a/src/docker/remote.rs
+++ b/src/docker/remote.rs
@@ -29,7 +29,6 @@ macro_rules! bail_container_exited {
     }};
 }
 
-#[track_caller]
 fn subcommand_or_exit(engine: &Engine, cmd: &str) -> Result<Command> {
     bail_container_exited!();
     Ok(engine.subcommand(cmd))
@@ -43,7 +42,6 @@ impl<'a, 'b, 'c> ContainerDataVolume<'a, 'b, 'c> {
     // NOTE: `reldir` should be a relative POSIX path to the root directory
     // on windows, this should be something like `mnt/c`. that is, all paths
     // inside the container should not have the mount prefix.
-    #[track_caller]
     fn create_dir(
         &self,
         reldir: &str,
@@ -59,7 +57,6 @@ impl<'a, 'b, 'c> ContainerDataVolume<'a, 'b, 'c> {
 
     // copy files for a docker volume, for remote host support
     // NOTE: `reldst` has the same caveats as `reldir` in `create_dir`.
-    #[track_caller]
     fn copy_files(
         &self,
         src: &Path,
@@ -75,7 +72,6 @@ impl<'a, 'b, 'c> ContainerDataVolume<'a, 'b, 'c> {
     }
 
     // copy files for a docker volume, for remote host support
-    #[track_caller]
     fn copy_files_nocache(
         &self,
         src: &Path,
@@ -96,7 +92,6 @@ impl<'a, 'b, 'c> ContainerDataVolume<'a, 'b, 'c> {
 
     // copy files for a docker volume, for remote host support
     // provides a list of files relative to src.
-    #[track_caller]
     fn copy_file_list(
         &self,
         src: &Path,
@@ -121,7 +116,6 @@ impl<'a, 'b, 'c> ContainerDataVolume<'a, 'b, 'c> {
 
     // removed files from a docker volume, for remote host support
     // provides a list of files relative to src.
-    #[track_caller]
     fn remove_file_list(
         &self,
         reldst: &str,
@@ -162,7 +156,6 @@ impl<'a, 'b, 'c> ContainerDataVolume<'a, 'b, 'c> {
             .run_and_get_status(msg_info, true)
     }
 
-    #[track_caller]
     fn container_path_exists(
         &self,
         relpath: &str,
@@ -180,7 +173,6 @@ impl<'a, 'b, 'c> ContainerDataVolume<'a, 'b, 'c> {
             .success())
     }
 
-    #[track_caller]
     pub fn copy_xargo(&self, mount_prefix: &str, msg_info: &mut MessageInfo) -> Result<()> {
         let dirs = &self.toolchain_dirs;
         let reldst = dirs.xargo_mount_path_relative()?;
@@ -202,7 +194,6 @@ impl<'a, 'b, 'c> ContainerDataVolume<'a, 'b, 'c> {
         Ok(())
     }
 
-    #[track_caller]
     pub fn copy_cargo(
         &self,
         mount_prefix: &str,
@@ -239,7 +230,6 @@ impl<'a, 'b, 'c> ContainerDataVolume<'a, 'b, 'c> {
     }
 
     // copy over files needed for all targets in the toolchain that should never change
-    #[track_caller]
     fn copy_rust_base(&self, mount_prefix: &str, msg_info: &mut MessageInfo) -> Result<()> {
         let dirs = &self.toolchain_dirs;
 
@@ -283,7 +273,6 @@ impl<'a, 'b, 'c> ContainerDataVolume<'a, 'b, 'c> {
         warn_symlinks(had_symlinks, msg_info)
     }
 
-    #[track_caller]
     fn copy_rust_manifest(&self, mount_prefix: &str, msg_info: &mut MessageInfo) -> Result<()> {
         let dirs = &self.toolchain_dirs;
 
@@ -309,7 +298,6 @@ impl<'a, 'b, 'c> ContainerDataVolume<'a, 'b, 'c> {
     }
 
     // copy over the toolchain for a specific triple
-    #[track_caller]
     fn copy_rust_triple(
         &self,
         target_triple: &TargetTriple,
@@ -347,7 +335,6 @@ impl<'a, 'b, 'c> ContainerDataVolume<'a, 'b, 'c> {
         Ok(())
     }
 
-    #[track_caller]
     pub fn copy_rust(
         &self,
         target_triple: Option<&TargetTriple>,
@@ -368,7 +355,6 @@ impl<'a, 'b, 'c> ContainerDataVolume<'a, 'b, 'c> {
         Ok(())
     }
 
-    #[track_caller]
     fn copy_mount(
         &self,
         src: &Path,
diff --git a/src/docker/shared.rs b/src/docker/shared.rs
index 8f41f9b..e4874ee 100644
--- a/src/docker/shared.rs
+++ b/src/docker/shared.rs
@@ -200,9 +200,8 @@ impl DockerPaths {
         metadata: CargoMetadata,
         cwd: PathBuf,
         toolchain: QualifiedToolchain,
-        msg_info: &mut MessageInfo,
     ) -> Result<Self> {
-        let mount_finder = MountFinder::create(engine, msg_info)?;
+        let mount_finder = MountFinder::create(engine)?;
         let (directories, metadata) =
             Directories::assemble(&mount_finder, metadata, &cwd, toolchain)?;
         Ok(Self {
@@ -696,26 +695,22 @@ impl<'a, 'b> DockerVolume<'a, 'b> {
         Self { engine, name }
     }
 
-    #[track_caller]
     pub fn create(&self, msg_info: &mut MessageInfo) -> Result<ExitStatus> {
         self.engine
             .run_and_get_status(&["volume", "create", self.name], msg_info)
     }
 
-    #[track_caller]
     pub fn remove(&self, msg_info: &mut MessageInfo) -> Result<ExitStatus> {
         self.engine
             .run_and_get_status(&["volume", "rm", self.name], msg_info)
     }
 
-    #[track_caller]
     pub fn exists(&self, msg_info: &mut MessageInfo) -> Result<bool> {
         self.engine
             .run_and_get_output(&["volume", "inspect", self.name], msg_info)
             .map(|output| output.status.success())
     }
 
-    #[track_caller]
     pub fn existing(
         engine: &Engine,
         toolchain: &QualifiedToolchain,
@@ -838,7 +833,6 @@ impl Engine {
         command
     }
 
-    #[track_caller]
     pub(crate) fn run_and_get_status(
         &self,
         args: &[&str],
@@ -847,7 +841,6 @@ impl Engine {
         self.command().args(args).run_and_get_status(msg_info, true)
     }
 
-    #[track_caller]
     pub(crate) fn run_and_get_output(
         &self,
         args: &[&str],
@@ -1332,10 +1325,7 @@ pub(crate) fn get_image(config: &Config, target: &Target, uses_zig: bool) -> Res
     Ok(image)
 }
 
-fn docker_read_mount_paths(
-    engine: &Engine,
-    msg_info: &mut MessageInfo,
-) -> Result<Vec<MountDetail>> {
+fn docker_read_mount_paths(engine: &Engine) -> Result<Vec<MountDetail>> {
     let hostname = env::var("HOSTNAME").wrap_err("HOSTNAME environment variable not found")?;
 
     let mut docker: Command = {
@@ -1344,7 +1334,7 @@ fn docker_read_mount_paths(
         command
     };
 
-    let output = docker.run_and_get_stdout(msg_info)?;
+    let output = docker.run_and_get_stdout(&mut Verbosity::Quiet.into())?;
     let info = serde_json::from_str(&output).wrap_err("failed to parse docker inspect output")?;
     dockerinfo_parse_mounts(&info)
 }
@@ -1420,9 +1410,9 @@ impl MountFinder {
         MountFinder { mounts }
     }
 
-    pub fn create(engine: &Engine, msg_info: &mut MessageInfo) -> Result<MountFinder> {
+    pub fn create(engine: &Engine) -> Result<MountFinder> {
         Ok(if engine.in_docker {
-            MountFinder::new(docker_read_mount_paths(engine, msg_info)?)
+            MountFinder::new(docker_read_mount_paths(engine)?)
         } else {
             MountFinder::default()
         })
@@ -1705,12 +1695,12 @@ mod tests {
                 return Ok(());
             }
 
-            let mount_finder = MountFinder::create(&engine, &mut msg_info)?;
+            let mount_finder = MountFinder::create(&engine)?;
             let metadata = cargo_metadata(true, &mut msg_info)?;
             let (directories, _) = get_directories(metadata, &mount_finder)?;
             let toolchain_dirs = directories.toolchain_directories();
             let package_dirs = directories.package_directories();
-            let mount_finder = MountFinder::new(docker_read_mount_paths(&engine, &mut msg_info)?);
+            let mount_finder = MountFinder::new(docker_read_mount_paths(&engine)?);
             let mount_path = |p| mount_finder.find_mount_path(p);
 
             paths_equal(toolchain_dirs.cargo(), &mount_path(home()?.join(".cargo")))?;
diff --git a/src/extensions.rs b/src/extensions.rs
index e8f7d23..a9c2e8c 100644
--- a/src/extensions.rs
+++ b/src/extensions.rs
@@ -10,19 +10,16 @@ pub const STRIPPED_BINS: &[&str] = &[crate::docker::DOCKER, crate::docker::PODMA
 pub trait CommandExt {
     fn fmt_message(&self, msg_info: &mut MessageInfo) -> String;
 
-    #[track_caller]
     fn print(&self, msg_info: &mut MessageInfo) -> Result<()> {
         let msg = self.fmt_message(msg_info);
         msg_info.print(&msg)
     }
 
-    #[track_caller]
     fn info(&self, msg_info: &mut MessageInfo) -> Result<()> {
         let msg = self.fmt_message(msg_info);
         msg_info.info(&msg)
     }
 
-    #[track_caller]
     fn debug(&self, msg_info: &mut MessageInfo) -> Result<()> {
         let msg = self.fmt_message(msg_info);
         msg_info.debug(&msg)
@@ -34,17 +31,13 @@ pub trait CommandExt {
         status: ExitStatus,
         output: Option<&Output>,
     ) -> Result<(), CommandError>;
-    #[track_caller]
     fn run(&mut self, msg_info: &mut MessageInfo, silence_stdout: bool) -> Result<()>;
-    #[track_caller]
     fn run_and_get_status(
         &mut self,
         msg_info: &mut MessageInfo,
         silence_stdout: bool,
     ) -> Result<ExitStatus>;
-    #[track_caller]
     fn run_and_get_stdout(&mut self, msg_info: &mut MessageInfo) -> Result<String>;
-    #[track_caller]
     fn run_and_get_output(&mut self, msg_info: &mut MessageInfo) -> Result<std::process::Output>;
     fn command_pretty(
         &self,
@@ -91,33 +84,20 @@ impl CommandExt for Command {
         format!("{}", C(self, msg_info, strip))
     }
 
-    #[track_caller]
     fn fmt_message(&self, mut msg_info: &mut MessageInfo) -> String {
-        use std::fmt::Write;
         let msg_info = &mut msg_info;
-        let mut string = String::new();
-        if let Some(caller) = msg_info.caller() {
-            write!(string, "[{}] ->\n+ ", caller).unwrap();
-        } else {
-            write!(string, "+ ").unwrap();
-        };
         if let Some(cwd) = self.get_current_dir() {
-            write!(
-                string,
-                "{:?} {}",
+            format!(
+                "+ {:?} {}",
                 cwd,
                 msg_info.as_verbose(|info| self.command_pretty(info, |_| false))
             )
-            .unwrap();
         } else {
-            write!(
-                string,
-                "{}",
+            format!(
+                "+ {}",
                 msg_info.as_verbose(|info| self.command_pretty(info, |_| false))
             )
-            .unwrap();
         }
-        string
     }
 
     fn status_result(
@@ -147,7 +127,6 @@ impl CommandExt for Command {
     }
 
     /// Runs the command to completion
-    #[track_caller]
     fn run_and_get_status(
         &mut self,
         msg_info: &mut MessageInfo,
@@ -167,7 +146,6 @@ impl CommandExt for Command {
     }
 
     /// Runs the command to completion and returns its stdout
-    #[track_caller]
     fn run_and_get_stdout(&mut self, msg_info: &mut MessageInfo) -> Result<String> {
         let out = self.run_and_get_output(msg_info)?;
         self.status_result(msg_info, out.status, Some(&out))
@@ -180,7 +158,6 @@ impl CommandExt for Command {
     /// # Notes
     ///
     /// This command does not check the status.
-    #[track_caller]
     fn run_and_get_output(&mut self, msg_info: &mut MessageInfo) -> Result<std::process::Output> {
         self.debug(msg_info)?;
         self.output().map_err(|e| {
diff --git a/src/lib.rs b/src/lib.rs
index 99bb900..363b013 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -707,13 +707,7 @@ To override the toolchain mounted in the image, set `target.{}.image.toolchain =
                     engine.register_binfmt(&target, msg_info)?;
                 }
 
-                let paths = docker::DockerPaths::create(
-                    &engine,
-                    metadata,
-                    cwd,
-                    toolchain.clone(),
-                    msg_info,
-                )?;
+                let paths = docker::DockerPaths::create(&engine, metadata, cwd, toolchain.clone())?;
                 let options = docker::DockerOptions::new(
                     engine,
                     target.clone(),
diff --git a/src/shell.rs b/src/shell.rs
index 0ec20ae..b32eaca 100644
--- a/src/shell.rs
+++ b/src/shell.rs
@@ -6,7 +6,6 @@ use std::fmt;
 use std::io::{self, Write};
 use std::str::FromStr;
 
-use crate::config::bool_from_envvar;
 use crate::errors::Result;
 use owo_colors::{self, OwoColorize};
 
@@ -40,11 +39,8 @@ macro_rules! message {
     (@status $stream:ident, $status:expr, $message:expr, $color:ident, $msg_info:expr $(,)?) => {{
         write_style!($stream, $msg_info, $status, bold, $color);
         write_style!($stream, $msg_info, ":", bold);
-        if let Some(caller) = $msg_info.caller() {
-            write!($stream, " [{}]", caller)?;
-        }
         match $message {
-            Some(message) => writeln!($stream, " {}", message, )?,
+            Some(message) => writeln!($stream, " {}", message)?,
             None => write!($stream, " ")?,
         }
 
@@ -143,20 +139,15 @@ pub struct MessageInfo {
     pub verbosity: Verbosity,
     pub stdout_needs_erase: bool,
     pub stderr_needs_erase: bool,
-    pub cross_debug: bool,
 }
 
 impl MessageInfo {
-    pub fn new(color_choice: ColorChoice, verbosity: Verbosity) -> MessageInfo {
+    pub const fn new(color_choice: ColorChoice, verbosity: Verbosity) -> MessageInfo {
         MessageInfo {
             color_choice,
             verbosity,
             stdout_needs_erase: false,
             stderr_needs_erase: false,
-            cross_debug: std::env::var("CROSS_DEBUG")
-                .as_deref()
-                .map(bool_from_envvar)
-                .unwrap_or_default(),
         }
     }
 
@@ -164,17 +155,12 @@ impl MessageInfo {
         let color_choice = get_color_choice(color)?;
         let verbosity = get_verbosity(color_choice, verbose, quiet)?;
 
-        Ok(Self::new(color_choice, verbosity))
-    }
-
-    #[track_caller]
-    pub fn caller(&mut self) -> Option<impl fmt::Display> {
-        if self.cross_debug {
-            let loc = std::panic::Location::caller();
-            Some(format!("{}:{}", loc.file(), loc.line()))
-        } else {
-            None
-        }
+        Ok(MessageInfo {
+            color_choice,
+            verbosity,
+            stdout_needs_erase: false,
+            stderr_needs_erase: false,
+        })
     }
 
     #[must_use]
@@ -225,7 +211,6 @@ impl MessageInfo {
     }
 
     /// prints a red 'error' message and terminates.
-    #[track_caller]
     pub fn fatal<T: fmt::Display>(&mut self, message: T, code: i32) -> ! {
         self.error(message)
             .expect("could not display fatal message");
@@ -233,14 +218,12 @@ impl MessageInfo {
     }
 
     /// prints a red 'error' message.
-    #[track_caller]
     pub fn error<T: fmt::Display>(&mut self, message: T) -> Result<()> {
         self.stderr_check_erase()?;
         status!(@stderr cross_prefix!("error"), Some(&message), red, self)
     }
 
     /// prints an amber 'warning' message.
-    #[track_caller]
     pub fn warn<T: fmt::Display>(&mut self, message: T) -> Result<()> {
         match self.verbosity {
             Verbosity::Quiet => Ok(()),
@@ -254,7 +237,6 @@ impl MessageInfo {
     }
 
     /// prints a cyan 'note' message.
-    #[track_caller]
     pub fn note<T: fmt::Display>(&mut self, message: T) -> Result<()> {
         match self.verbosity {
             Verbosity::Quiet => Ok(()),
@@ -273,7 +255,6 @@ impl MessageInfo {
     }
 
     /// prints a high-priority message to stdout.
-    #[track_caller]
     pub fn print<T: fmt::Display>(&mut self, message: T) -> Result<()> {
         self.stdout_check_erase()?;
         println!("{}", message);
@@ -281,7 +262,6 @@ impl MessageInfo {
     }
 
     /// prints a normal message to stdout.
-    #[track_caller]
     pub fn info<T: fmt::Display>(&mut self, message: T) -> Result<()> {
         match self.verbosity {
             Verbosity::Quiet => Ok(()),
@@ -293,7 +273,6 @@ impl MessageInfo {
     }
 
     /// prints a debugging message to stdout.
-    #[track_caller]
     pub fn debug<T: fmt::Display>(&mut self, message: T) -> Result<()> {
         match self.verbosity {
             Verbosity::Quiet | Verbosity::Normal => Ok(()),
diff --git a/xtask/src/crosstool.rs b/xtask/src/crosstool.rs
index 6d12e42..505f69f 100644
--- a/xtask/src/crosstool.rs
+++ b/xtask/src/crosstool.rs
@@ -1,3 +1,4 @@
+use std::cmp::Ordering;
 use std::fmt::Write;
 use std::fs;
 use std::path::{Path, PathBuf};
@@ -9,6 +10,9 @@ use cross::ToUtf8;
 
 const DEFAULT_GCC_VERSION: &str = "8.3.0";
 const DEFAULT_GLIBC_VERSION: &str = "2.17.0";
+const DEFAULT_UCLIBC_VERSION: &str = "1.0.31";
+const DEFAULT_MUSL_VERSION: &str = "1.1.24";
+const DEFAULT_NEWLIB_VERSION: &str = "3.1.0.20181231";
 const DEFAULT_LINUX_VERSION: &str = "4.19.21";
 const DOCKER: &str = "docker";
 const CT_NG: &str = "crosstool-ng";
@@ -32,6 +36,15 @@ pub struct ConfigureCrosstool {
     /// The glibc version to configure for.
     #[clap(long, env = "GLIBC_VERSION")]
     pub glibc_version: Option<String>,
+    /// The uclibc version to configure for.
+    #[clap(long, env = "UCLIBC_VERSION")]
+    pub uclibc_version: Option<String>,
+    /// The musl version to configure for.
+    #[clap(long, env = "MUSL_VERSION")]
+    pub musl_version: Option<String>,
+    /// The newlib version to configure for.
+    #[clap(long, env = "NEWLIB_VERSION")]
+    pub newlib_version: Option<String>,
     /// The linux version to configure for.
     #[clap(long, env = "LINUX_VERSION")]
     pub linux_version: Option<String>,
@@ -75,33 +88,238 @@ fn read_config_dir(dir: &Path) -> cross::Result<Vec<String>> {
     Ok(targets)
 }
 
+fn configure_glibc(
+    glibc_version: &str,
+) -> cross::Result<(&'static str, String, String, Option<String>)> {
+    // configure the `CT_GLIBC` values
+    let glibc_versions: Vec<&str> = glibc_version.split('.').collect();
+    if !matches!(glibc_versions.len(), 2 | 3) {
+        eyre::bail!("invalid glibc version, got {glibc_version}");
+    }
+
+    let glibc_major = glibc_versions[0].parse::<u32>()?;
+    let glibc_minor = glibc_versions[1].parse::<u32>()?;
+    let _glibc_patch = glibc_versions.get(2).unwrap_or(&"0").parse::<u32>()?;
+    if glibc_major != 2 {
+        eyre::bail!("glibc major versions other than 2 currently unsupported, got {glibc_major}");
+    }
+    let ct_glibc_v = format!(
+        r#"CT_GLIBC_V_{glibc_major}_{glibc_minor}=y
+# CT_GLIBC_NO_VERSIONS is not set
+CT_GLIBC_VERSION="{glibc_major}.{glibc_minor}""#
+    );
+    let mut ct_glibc = String::new();
+    let glibc_older = [29, 27, 26, 25, 24, 23, 20];
+    for minor in glibc_older {
+        if glibc_minor <= minor {
+            write!(ct_glibc, "\nCT_GLIBC_2_{minor}_or_older=y")?;
+        }
+        if glibc_minor < minor {
+            write!(ct_glibc, "\nCT_GLIBC_older_than_2_{minor}=y")?;
+        }
+    }
+    if glibc_minor >= 17 {
+        ct_glibc.push_str("\nCT_GLIBC_2_17_or_later=y");
+    }
+    if glibc_minor <= 17 {
+        ct_glibc.push_str("\nCT_GLIBC_2_17_or_older=y");
+    }
+    if glibc_minor > 14 {
+        ct_glibc.push_str("\nCT_GLIBC_later_than_2_14=y");
+    }
+    if glibc_minor >= 14 {
+        ct_glibc.push_str("\nCT_GLIBC_2_14_or_later=y");
+    }
+
+    Ok(("GLIBC", ct_glibc_v, ct_glibc, None))
+}
+
+fn configure_uclibc(
+    uclibc_version: &str,
+) -> cross::Result<(&'static str, String, String, Option<String>)> {
+    // configure the `CT_UCLIBC` values
+    let uclibc_versions: Vec<&str> = uclibc_version.split('.').collect();
+    if !matches!(uclibc_versions.len(), 3 | 4) {
+        eyre::bail!("invalid uclibc version, got {uclibc_version}");
+    }
+
+    let uclibc_major = uclibc_versions[0].parse::<u32>()?;
+    let uclibc_minor = uclibc_versions[1].parse::<u32>()?;
+    let uclibc_patch = uclibc_versions[2].parse::<u32>()?;
+    let uclibc_dev = uclibc_versions.get(3).unwrap_or(&"0").parse::<u32>()?;
+    let (key, version) = match uclibc_dev {
+        0 => (
+            format!("{uclibc_major}_{uclibc_minor}_{uclibc_patch}"),
+            format!("{uclibc_major}.{uclibc_minor}.{uclibc_patch}"),
+        ),
+        _ => (
+            format!("{uclibc_major}_{uclibc_minor}_{uclibc_patch}_{uclibc_dev}"),
+            format!("{uclibc_major}.{uclibc_minor}.{uclibc_patch}.{uclibc_dev}"),
+        ),
+    };
+    let (ct_uclibc_v, ct_extras) = match uclibc_major {
+        0 => (
+            format!(
+                r#"CT_UCLIBC_V_{key}=y
+# CT_UCLIBC_NO_VERSIONS is not set
+CT_UCLIBC_VERSION="{version}""#
+            ),
+            r#"CT_UCLIBC_USE_UCLIBC_ORG=y
+CT_UCLIBC_USE="UCLIBC"
+CT_UCLIBC_PKG_NAME="uClibc"
+CT_UCLIBC_SRC_RELEASE=y
+CT_UCLIBC_PATCH_ORDER="global"
+CT_UCLIBC_MIRRORS="https://uclibc.org/downloads/"
+CT_UCLIBC_ARCHIVE_FILENAME="@{pkg_name}-@{version}"
+CT_UCLIBC_ARCHIVE_DIRNAME="@{pkg_name}-@{version}"
+CT_UCLIBC_ARCHIVE_FORMATS=".tar.xz .tar.bz2"
+CT_UCLIBC_SIGNATURE_FORMAT="packed/.asc""#,
+        ),
+        _ => (
+            format!(
+                r#"CT_UCLIBC_NG_V_{key}=y
+# CT_UCLIBC_NG_NO_VERSIONS is not set
+CT_UCLIBC_NG_VERSION="{version}""#
+            ),
+            r#"CT_UCLIBC_USE_UCLIBC_NG_ORG=y
+CT_UCLIBC_USE="UCLIBC_NG"
+CT_UCLIBC_NG_PKG_NAME="uClibc-ng"
+CT_UCLIBC_NG_SRC_RELEASE=y
+CT_UCLIBC_NG_PATCH_ORDER="global"
+CT_UCLIBC_NG_MIRRORS="http://downloads.uclibc-ng.org/releases/${CT_UCLIBC_NG_VERSION}"
+CT_UCLIBC_NG_ARCHIVE_FILENAME="@{pkg_name}-@{version}"
+CT_UCLIBC_NG_ARCHIVE_DIRNAME="@{pkg_name}-@{version}"
+CT_UCLIBC_NG_ARCHIVE_FORMATS=".tar.xz .tar.lz .tar.bz2 .tar.gz"
+CT_UCLIBC_NG_SIGNATURE_FORMAT="packed/.asc""#,
+        ),
+    };
+
+    let mut ct_uclibc = String::new();
+    let version = (uclibc_major, uclibc_minor, uclibc_patch, uclibc_dev);
+    let uclibc_older = [
+        (1, 0, 23, 0),
+        (1, 0, 21, 0),
+        (1, 0, 15, 0),
+        (1, 0, 0, 0),
+        (0, 9, 33, 2),
+    ];
+    for older in uclibc_older {
+        let cmp = older.cmp(&version);
+        let (major, minor, patch, dev) = older;
+        let key = match dev {
+            0 => format!("{major}_{minor}_{patch}"),
+            _ => format!("{major}_{minor}_{patch}_{dev}"),
+        };
+        match cmp {
+            Ordering::Greater => {
+                write!(ct_uclibc, "\nCT_UCLIBC_{key}_or_older=y")?;
+                write!(ct_uclibc, "\nCT_UCLIBC_older_than_{key}=y")?;
+            }
+            Ordering::Equal => {
+                write!(ct_uclibc, "\nCT_UCLIBC_{key}_or_later=y")?;
+                write!(ct_uclibc, "\nCT_UCLIBC_{key}_or_older=y")?;
+            }
+            Ordering::Less => {
+                write!(ct_uclibc, "\nCT_UCLIBC_{key}_or_later=y")?;
+                write!(ct_uclibc, "\nCT_UCLIBC_later_than_{key}=y")?;
+            }
+        }
+    }
+
+    Ok(("UCLIBC", ct_uclibc_v, ct_uclibc, Some(ct_extras.to_owned())))
+}
+
+fn configure_musl(
+    musl_version: &str,
+) -> cross::Result<(&'static str, String, String, Option<String>)> {
+    let musl_versions: Vec<&str> = musl_version.split('.').collect();
+    if !matches!(musl_versions.len(), 3) {
+        eyre::bail!("invalid musl version, got {musl_version}");
+    }
+
+    let musl_major = musl_versions[0].parse::<u32>()?;
+    let musl_minor = musl_versions[1].parse::<u32>()?;
+    let musl_patch = musl_versions[2].parse::<u32>()?;
+    let ct_musl_v = format!(
+        r#"CT_MUSL_V_{musl_major}_{musl_minor}_{musl_patch}=y
+# CT_MUSL_NO_VERSIONS is not set
+CT_MUSL_VERSION="{musl_major}.{musl_minor}.{musl_patch}""#
+    );
+
+    Ok(("MUSL", ct_musl_v, "".to_owned(), None))
+}
+
+fn configure_newlib(
+    newlib_version: &str,
+) -> cross::Result<(&'static str, String, String, Option<String>)> {
+    let newlib_versions: Vec<&str> = newlib_version.split('.').collect();
+    if !matches!(newlib_versions.len(), 3 | 4) {
+        eyre::bail!("invalid newlib version, got {newlib_version}");
+    }
+
+    let newlib_major = newlib_versions[0].parse::<u32>()?;
+    let newlib_minor = newlib_versions[1].parse::<u32>()?;
+    let newlib_patch = newlib_versions[2].parse::<u32>()?;
+    let newlib_dev = newlib_versions.get(3).unwrap_or(&"0").parse::<u32>()?;
+    let ct_newlib_v = format!(
+        r#"CT_NEWLIB_V_{newlib_major}_{newlib_minor}=y
+# CT_NEWLIB_NO_VERSIONS is not set
+CT_NEWLIB_VERSION="{newlib_major}.{newlib_minor}.{newlib_patch}.{newlib_dev}""#
+    );
+
+    let mut ct_newlib = String::new();
+    let version = (newlib_major, newlib_minor);
+    let newlib_older = [(2, 2), (2, 1), (2, 0)];
+    for older in newlib_older {
+        let cmp = older.cmp(&version);
+        let (major, minor) = older;
+        let key = format!("{major}_{minor}");
+        match cmp {
+            Ordering::Greater => {
+                write!(ct_newlib, "\nCT_NEWLIB_{key}_or_older=y")?;
+                write!(ct_newlib, "\nCT_NEWLIB_older_than_{key}=y")?;
+            }
+            Ordering::Equal => {
+                write!(ct_newlib, "\nCT_NEWLIB_{key}_or_later=y")?;
+                write!(ct_newlib, "\nCT_NEWLIB_{key}_or_older=y")?;
+            }
+            Ordering::Less => {
+                write!(ct_newlib, "\nCT_NEWLIB_{key}_or_later=y")?;
+                write!(ct_newlib, "\nCT_NEWLIB_later_than_{key}=y")?;
+            }
+        }
+    }
+
+    Ok(("NEWLIB", ct_newlib_v, ct_newlib, None))
+}
+
 fn configure_target(
     src_file: &Path,
     gcc_version: &str,
     glibc_version: &str,
+    uclibc_version: &str,
+    musl_version: &str,
+    newlib_version: &str,
     linux_version: &str,
 ) -> cross::Result<String> {
+    let file_name = src_file
+        .file_name()
+        .ok_or(eyre::eyre!("unable to get filename for {src_file:?}"))?
+        .to_utf8()?;
+    let mut contents = fs::read_to_string(src_file)?;
+
+    // configure the `CT_GCC` values
     let gcc_versions: Vec<&str> = gcc_version.split('.').collect();
-    let glibc_versions: Vec<&str> = glibc_version.split('.').collect();
-    let linux_versions: Vec<&str> = linux_version.split('.').collect();
     if !matches!(gcc_versions.len(), 2 | 3) {
         eyre::bail!("invalid GCC version, got {gcc_version}");
     }
-    if !matches!(glibc_versions.len(), 2 | 3) {
-        eyre::bail!("invalid glibc version, got {glibc_version}");
-    }
-    if !matches!(linux_versions.len(), 2 | 3) {
-        eyre::bail!("invalid linux version, got {linux_version}");
-    }
-
-    // configure the `CT_GCC` values
     let gcc_major = gcc_versions[0].parse::<u32>()?;
     let gcc_minor = gcc_versions[1].parse::<u32>()?;
     let gcc_patch = gcc_versions.get(2).unwrap_or(&"0").parse::<u32>()?;
     let ct_gcc_v = format!(
-        "CT_GCC_V_{gcc_major}=y
+        r#"CT_GCC_V_{gcc_major}=y
 # CT_GCC_NO_VERSIONS is not set
-CT_GCC_VERSION=\"{gcc_major}.{gcc_minor}.{gcc_patch}\""
+CT_GCC_VERSION="{gcc_major}.{gcc_minor}.{gcc_patch}""#
     );
     let mut ct_gcc = String::new();
     for major in (5..=7).rev() {
@@ -124,84 +342,88 @@ CT_GCC_VERSION=\"{gcc_major}.{gcc_minor}.{gcc_patch}\""
     if gcc_major > 4 || (gcc_major == 4 && gcc_major >= 8) {
         ct_gcc.push_str("\nCT_GCC_4_8_or_later=y");
     }
-
-    // configure the `CT_GLIBC` values
-    let glibc_major = glibc_versions[0].parse::<u32>()?;
-    let glibc_minor = glibc_versions[1].parse::<u32>()?;
-    let _glibc_patch = glibc_versions.get(2).unwrap_or(&"0").parse::<u32>()?;
-    if glibc_major != 2 {
-        eyre::bail!("glibc major versions other than 2 currently unsupported, got {glibc_major}");
+    if ct_gcc.starts_with('\n') {
+        ct_gcc.remove(0);
     }
-    let ct_glibc_v = format!(
-        "CT_GLIBC_V_{glibc_major}_{glibc_minor}=y
-# CT_GLIBC_NO_VERSIONS is not set
-CT_GLIBC_VERSION=\"{glibc_major}.{glibc_minor}\""
-    );
-    let mut ct_glibc = String::new();
-    let glibc_older = [29, 27, 26, 25, 24, 23, 20];
-    for minor in glibc_older {
-        if glibc_minor <= minor {
-            write!(ct_glibc, "\nCT_GLIBC_2_{minor}_or_older=y")?;
-        }
-        if glibc_minor < minor {
-            write!(ct_glibc, "\nCT_GLIBC_older_than_2_{minor}=y")?;
-        }
-    }
-    if glibc_minor >= 17 {
-        ct_glibc.push_str("\nCT_GLIBC_2_17_or_later=y");
-    }
-    if glibc_minor <= 17 {
-        ct_glibc.push_str("\nCT_GLIBC_2_17_or_older=y");
-    }
-    if glibc_minor > 14 {
-        ct_glibc.push_str("\nCT_GLIBC_later_than_2_14=y");
+    contents = contents
+        .replacen("%CT_GCC_V%", &ct_gcc_v, 1)
+        .replacen("%CT_GCC%", &ct_gcc, 1);
+
+    // configure the libc versions
+    let (key, libc_v, mut libc, extras) = if file_name.contains("gnu") {
+        configure_glibc(glibc_version)?
+    } else if file_name.contains("uclibc") {
+        configure_uclibc(uclibc_version)?
+    } else if file_name.contains("musl") {
+        configure_musl(musl_version)?
+    } else if file_name.contains("none") {
+        configure_newlib(newlib_version)?
+    } else {
+        eyre::bail!("unsupported rust target for file {file_name}: unknown libc version");
+    };
+    if libc.starts_with('\n') {
+        libc.remove(0);
     }
-    if glibc_minor >= 14 {
-        ct_glibc.push_str("\nCT_GLIBC_2_14_or_later=y");
+    contents = contents
+        .replacen(&format!("%CT_{key}_V%"), &libc_v, 1)
+        .replacen(&format!("%CT_{key}%"), &libc, 1);
+    if let Some(extras) = extras {
+        contents = contents.replacen(&format!("%CT_{key}_EXTRAS%"), &extras, 1);
     }
 
     // configure the `CT_LINUX` values
-    let linux_major = linux_versions[0].parse::<u32>()?;
-    let linux_minor = linux_versions[1].parse::<u32>()?;
-    let linux_patch = linux_versions.get(2).unwrap_or(&"0").parse::<u32>()?;
-    let ct_linux_v = format!(
-        "CT_LINUX_V_{linux_major}_{linux_minor}=y
+    if file_name.contains("linux") {
+        let linux_versions: Vec<&str> = linux_version.split('.').collect();
+        if !matches!(linux_versions.len(), 2 | 3) {
+            eyre::bail!("invalid linux version, got {linux_version}");
+        }
+        let linux_major = linux_versions[0].parse::<u32>()?;
+        let linux_minor = linux_versions[1].parse::<u32>()?;
+        let linux_patch = linux_versions.get(2).unwrap_or(&"0").parse::<u32>()?;
+        let ct_linux_v = format!(
+            r#"CT_LINUX_V_{linux_major}_{linux_minor}=y
 # CT_LINUX_NO_VERSIONS is not set
-CT_LINUX_VERSION=\"{linux_major}.{linux_minor}.{linux_patch}\""
-    );
-    let mut ct_linux = String::new();
-    if linux_major < 4 || (linux_major == 4 && linux_minor < 8) {
-        ct_linux.push_str("\nCT_LINUX_older_than_4_8=y");
-        ct_linux.push_str("\nCT_LINUX_4_8_or_older=y");
-    } else {
-        ct_linux.push_str("\nCT_LINUX_later_than_4_8=y");
-        ct_linux.push_str("\nCT_LINUX_4_8_or_later=y");
-    }
-    if linux_major < 3 || (linux_major == 3 && linux_minor < 7) {
-        ct_linux.push_str("\nCT_LINUX_older_than_3_7=y");
-        ct_linux.push_str("\nCT_LINUX_3_7_or_older=y");
-        ct_linux.push_str("\nCT_LINUX_older_than_3_2=y");
-        ct_linux.push_str("\nCT_LINUX_3_2_or_older=y");
-    } else {
-        ct_linux.push_str("\nCT_LINUX_later_than_3_7=y");
-        ct_linux.push_str("\nCT_LINUX_3_7_or_later=y");
-        ct_linux.push_str("\nCT_LINUX_later_than_3_2=y");
-        ct_linux.push_str("\nCT_LINUX_3_2_or_later=y");
+CT_LINUX_VERSION="{linux_major}.{linux_minor}.{linux_patch}""#
+        );
+        let mut ct_linux = String::new();
+        if linux_major < 4 || (linux_major == 4 && linux_minor < 8) {
+            ct_linux.push_str("\nCT_LINUX_older_than_4_8=y");
+            ct_linux.push_str("\nCT_LINUX_4_8_or_older=y");
+        } else {
+            ct_linux.push_str("\nCT_LINUX_later_than_4_8=y");
+            ct_linux.push_str("\nCT_LINUX_4_8_or_later=y");
+        }
+        if linux_major < 3 || (linux_major == 3 && linux_minor < 7) {
+            ct_linux.push_str("\nCT_LINUX_older_than_3_7=y");
+            ct_linux.push_str("\nCT_LINUX_3_7_or_older=y");
+            ct_linux.push_str("\nCT_LINUX_older_than_3_2=y");
+            ct_linux.push_str("\nCT_LINUX_3_2_or_older=y");
+        } else {
+            ct_linux.push_str("\nCT_LINUX_later_than_3_7=y");
+            ct_linux.push_str("\nCT_LINUX_3_7_or_later=y");
+            ct_linux.push_str("\nCT_LINUX_later_than_3_2=y");
+            ct_linux.push_str("\nCT_LINUX_3_2_or_later=y");
+        }
+        if ct_linux.starts_with('\n') {
+            ct_linux.remove(0);
+        }
+
+        contents =
+            contents
+                .replacen("%CT_LINUX_V%", &ct_linux_v, 1)
+                .replacen("%CT_LINUX%", &ct_linux, 1);
     }
 
-    Ok(fs::read_to_string(src_file)?
-        .replacen("%CT_GCC_V%", &ct_gcc_v, 1)
-        .replacen("%CT_GCC%", &ct_gcc, 1)
-        .replacen("%CT_GLIBC_V%", &ct_glibc_v, 1)
-        .replacen("%CT_GLIBC%", &ct_glibc, 1)
-        .replacen("%CT_LINUX_V%", &ct_linux_v, 1)
-        .replacen("%CT_LINUX%", &ct_linux, 1))
+    Ok(contents)
 }
 
 pub fn configure_crosstool(
     ConfigureCrosstool {
         gcc_version,
         glibc_version,
+        uclibc_version,
+        musl_version,
+        newlib_version,
         linux_version,
         mut targets,
         ..
@@ -210,6 +432,9 @@ pub fn configure_crosstool(
 ) -> cross::Result<()> {
     let gcc_version = gcc_version.as_deref().unwrap_or(DEFAULT_GCC_VERSION);
     let glibc_version = glibc_version.as_deref().unwrap_or(DEFAULT_GLIBC_VERSION);
+    let uclibc_version = uclibc_version.as_deref().unwrap_or(DEFAULT_UCLIBC_VERSION);
+    let musl_version = musl_version.as_deref().unwrap_or(DEFAULT_MUSL_VERSION);
+    let newlib_version = newlib_version.as_deref().unwrap_or(DEFAULT_NEWLIB_VERSION);
     let linux_version = linux_version.as_deref().unwrap_or(DEFAULT_LINUX_VERSION);
 
     let root = project_dir(msg_info)?;
@@ -226,7 +451,15 @@ pub fn configure_crosstool(
         .map(|t| locate_ctng_config(&t, &root, &cross_toolchains_root))
         .collect::<cross::Result<Vec<_>>>()?;
     for (src_file, dst_file) in config_files {
-        let configured = configure_target(&src_file, gcc_version, glibc_version, linux_version)?;
+        let configured = configure_target(
+            &src_file,
+            gcc_version,
+            glibc_version,
+            uclibc_version,
+            musl_version,
+            newlib_version,
+            linux_version,
+        )?;
         write_to_string(&dst_file, &configured)?;
     }
 


Test patch:
diff --git a/ci/test-remote.sh b/ci/test-remote.sh
index b6e506c..8b676f3 100755
--- a/ci/test-remote.sh
+++ b/ci/test-remote.sh
@@ -45,9 +45,7 @@ cross_test_cpp() {
 
     pushd "${td}"
     retry cargo fetch
-    "${CROSS}" run --target "${TARGET}" | grep "Hello, world!"
-    sed -i 's/Hello, world/Hello, test/g' hellopp.cc
-    "${CROSS}" run --target "${TARGET}" | grep "Hello, test!"
+    "${CROSS}" run --target "${TARGET}"
     popd
 
     rm -rf "${td}"