diff --git a/src/bin/commands/clean.rs b/src/bin/commands/clean.rs index 35c9db7db..e5b4f6914 100644 --- a/src/bin/commands/clean.rs +++ b/src/bin/commands/clean.rs @@ -3,7 +3,7 @@ use std::fs; use super::containers::*; use super::images::*; use clap::Args; -use cross::shell::{self, MessageInfo}; +use cross::shell::MessageInfo; #[derive(Args, Debug)] pub struct Clean { @@ -31,8 +31,11 @@ pub struct Clean { } impl Clean { - pub fn run(self, engine: cross::docker::Engine) -> cross::Result<()> { - let msg_info = MessageInfo::create(self.verbose, self.quiet, self.color.as_deref())?; + pub fn run( + self, + engine: cross::docker::Engine, + msg_info: &mut MessageInfo, + ) -> cross::Result<()> { let tempdir = cross::temp::dir()?; match self.execute { true => { @@ -40,13 +43,10 @@ impl Clean { fs::remove_dir_all(tempdir)?; } } - false => shell::print( - format!( - "fs::remove_dir_all({})", - cross::pretty_path(&tempdir, |_| false) - ), - msg_info, - )?, + false => msg_info.print(format_args!( + "fs::remove_dir_all({})", + cross::pretty_path(&tempdir, |_| false) + ))?, } // containers -> images -> volumes -> prune to ensure no conflicts. @@ -58,7 +58,7 @@ impl Clean { execute: self.execute, engine: None, }; - remove_containers.run(engine.clone())?; + remove_containers.run(engine.clone(), msg_info)?; let remove_images = RemoveImages { targets: vec![], @@ -70,7 +70,7 @@ impl Clean { execute: self.execute, engine: None, }; - remove_images.run(engine.clone())?; + remove_images.run(engine.clone(), msg_info)?; let remove_volumes = RemoveAllVolumes { verbose: self.verbose, @@ -80,7 +80,7 @@ impl Clean { execute: self.execute, engine: None, }; - remove_volumes.run(engine.clone())?; + remove_volumes.run(engine.clone(), msg_info)?; let prune_volumes = PruneVolumes { verbose: self.verbose, @@ -89,8 +89,24 @@ impl Clean { execute: self.execute, engine: None, }; - prune_volumes.run(engine)?; + prune_volumes.run(engine, msg_info)?; Ok(()) } + + pub fn engine(&self) -> Option<&str> { + self.engine.as_deref() + } + + pub fn verbose(&self) -> bool { + self.verbose + } + + pub fn quiet(&self) -> bool { + self.quiet + } + + pub fn color(&self) -> Option<&str> { + self.color.as_deref() + } } diff --git a/src/bin/commands/containers.rs b/src/bin/commands/containers.rs index a6a3712b5..796894698 100644 --- a/src/bin/commands/containers.rs +++ b/src/bin/commands/containers.rs @@ -1,7 +1,7 @@ use std::io; use clap::{Args, Subcommand}; -use cross::shell::{self, MessageInfo, Stream}; +use cross::shell::{MessageInfo, Stream}; use cross::{docker, CommandExt}; #[derive(Args, Debug)] @@ -21,8 +21,8 @@ pub struct ListVolumes { } impl ListVolumes { - pub fn run(self, engine: docker::Engine) -> cross::Result<()> { - list_volumes(self, &engine) + pub fn run(self, engine: docker::Engine, msg_info: &mut MessageInfo) -> cross::Result<()> { + list_volumes(&engine, msg_info) } } @@ -49,8 +49,8 @@ pub struct RemoveAllVolumes { } impl RemoveAllVolumes { - pub fn run(self, engine: docker::Engine) -> cross::Result<()> { - remove_all_volumes(self, &engine) + pub fn run(self, engine: docker::Engine, msg_info: &mut MessageInfo) -> cross::Result<()> { + remove_all_volumes(self, &engine, msg_info) } } @@ -74,8 +74,8 @@ pub struct PruneVolumes { } impl PruneVolumes { - pub fn run(self, engine: docker::Engine) -> cross::Result<()> { - prune_volumes(self, &engine) + pub fn run(self, engine: docker::Engine, msg_info: &mut MessageInfo) -> cross::Result<()> { + prune_volumes(self, &engine, msg_info) } } @@ -102,8 +102,13 @@ pub struct CreateVolume { } impl CreateVolume { - pub fn run(self, engine: docker::Engine, channel: Option<&str>) -> cross::Result<()> { - create_persistent_volume(self, &engine, channel) + pub fn run( + self, + engine: docker::Engine, + channel: Option<&str>, + msg_info: &mut MessageInfo, + ) -> cross::Result<()> { + create_persistent_volume(self, &engine, channel, msg_info) } } @@ -130,8 +135,13 @@ pub struct RemoveVolume { } impl RemoveVolume { - pub fn run(self, engine: docker::Engine, channel: Option<&str>) -> cross::Result<()> { - remove_persistent_volume(self, &engine, channel) + pub fn run( + self, + engine: docker::Engine, + channel: Option<&str>, + msg_info: &mut MessageInfo, + ) -> cross::Result<()> { + remove_persistent_volume(&engine, channel, msg_info) } } @@ -149,55 +159,59 @@ pub enum Volumes { Remove(RemoveVolume), } +macro_rules! volumes_get_field { + ($self:ident, $field:ident $(.$cb:ident)?) => {{ + match $self { + Volumes::List(l) => l.$field$(.$cb())?, + Volumes::RemoveAll(l) => l.$field$(.$cb())?, + Volumes::Prune(l) => l.$field$(.$cb())?, + Volumes::Create(l) => l.$field$(.$cb())?, + Volumes::Remove(l) => l.$field$(.$cb())?, + } + }}; +} + impl Volumes { - pub fn run(self, engine: docker::Engine, toolchain: Option<&str>) -> cross::Result<()> { + pub fn run( + self, + engine: docker::Engine, + toolchain: Option<&str>, + msg_info: &mut MessageInfo, + ) -> cross::Result<()> { match self { - Volumes::List(args) => args.run(engine), - Volumes::RemoveAll(args) => args.run(engine), - Volumes::Prune(args) => args.run(engine), - Volumes::Create(args) => args.run(engine, toolchain), - Volumes::Remove(args) => args.run(engine, toolchain), + Volumes::List(args) => args.run(engine, msg_info), + Volumes::RemoveAll(args) => args.run(engine, msg_info), + Volumes::Prune(args) => args.run(engine, msg_info), + Volumes::Create(args) => args.run(engine, toolchain, msg_info), + Volumes::Remove(args) => args.run(engine, toolchain, msg_info), } } pub fn engine(&self) -> Option<&str> { + volumes_get_field!(self, engine.as_deref) + } + + // FIXME: remove this in v0.3.0. + pub fn docker_in_docker(&self) -> bool { match self { - Volumes::List(l) => l.engine.as_deref(), - Volumes::RemoveAll(l) => l.engine.as_deref(), - Volumes::Prune(l) => l.engine.as_deref(), - Volumes::Create(l) => l.engine.as_deref(), - Volumes::Remove(l) => l.engine.as_deref(), + Volumes::List(_) => false, + Volumes::RemoveAll(_) => false, + Volumes::Prune(_) => false, + Volumes::Create(l) => l.docker_in_docker, + Volumes::Remove(l) => l.docker_in_docker, } } pub fn verbose(&self) -> bool { - match self { - Volumes::List(l) => l.verbose, - Volumes::RemoveAll(l) => l.verbose, - Volumes::Prune(l) => l.verbose, - Volumes::Create(l) => l.verbose, - Volumes::Remove(l) => l.verbose, - } + volumes_get_field!(self, verbose) } pub fn quiet(&self) -> bool { - match self { - Volumes::List(l) => l.quiet, - Volumes::RemoveAll(l) => l.quiet, - Volumes::Prune(l) => l.quiet, - Volumes::Create(l) => l.quiet, - Volumes::Remove(l) => l.quiet, - } + volumes_get_field!(self, quiet) } pub fn color(&self) -> Option<&str> { - match self { - Volumes::List(l) => l.color.as_deref(), - Volumes::RemoveAll(l) => l.color.as_deref(), - Volumes::Prune(l) => l.color.as_deref(), - Volumes::Create(l) => l.color.as_deref(), - Volumes::Remove(l) => l.color.as_deref(), - } + volumes_get_field!(self, color.as_deref) } } @@ -218,8 +232,8 @@ pub struct ListContainers { } impl ListContainers { - pub fn run(self, engine: docker::Engine) -> cross::Result<()> { - list_containers(self, &engine) + pub fn run(self, engine: docker::Engine, msg_info: &mut MessageInfo) -> cross::Result<()> { + list_containers(&engine, msg_info) } } @@ -246,8 +260,8 @@ pub struct RemoveAllContainers { } impl RemoveAllContainers { - pub fn run(self, engine: docker::Engine) -> cross::Result<()> { - remove_all_containers(self, &engine) + pub fn run(self, engine: docker::Engine, msg_info: &mut MessageInfo) -> cross::Result<()> { + remove_all_containers(self, &engine, msg_info) } } @@ -259,44 +273,44 @@ pub enum Containers { RemoveAll(RemoveAllContainers), } +macro_rules! containers_get_field { + ($self:ident, $field:ident $(.$cb:ident)?) => {{ + match $self { + Containers::List(l) => l.$field$(.$cb())?, + Containers::RemoveAll(l) => l.$field$(.$cb())?, + } + }}; +} + impl Containers { - pub fn run(self, engine: docker::Engine) -> cross::Result<()> { + pub fn run(self, engine: docker::Engine, msg_info: &mut MessageInfo) -> cross::Result<()> { match self { - Containers::List(args) => args.run(engine), - Containers::RemoveAll(args) => args.run(engine), + Containers::List(args) => args.run(engine, msg_info), + Containers::RemoveAll(args) => args.run(engine, msg_info), } } pub fn engine(&self) -> Option<&str> { - match self { - Containers::List(l) => l.engine.as_deref(), - Containers::RemoveAll(l) => l.engine.as_deref(), - } + containers_get_field!(self, engine.as_deref) } pub fn verbose(&self) -> bool { - match self { - Containers::List(l) => l.verbose, - Containers::RemoveAll(l) => l.verbose, - } + containers_get_field!(self, verbose) } pub fn quiet(&self) -> bool { - match self { - Containers::List(l) => l.quiet, - Containers::RemoveAll(l) => l.quiet, - } + containers_get_field!(self, quiet) } pub fn color(&self) -> Option<&str> { - match self { - Containers::List(l) => l.color.as_deref(), - Containers::RemoveAll(l) => l.color.as_deref(), - } + containers_get_field!(self, color.as_deref) } } -fn get_cross_volumes(engine: &docker::Engine, msg_info: MessageInfo) -> cross::Result> { +fn get_cross_volumes( + engine: &docker::Engine, + msg_info: &mut MessageInfo, +) -> cross::Result> { let stdout = docker::subcommand(engine, "volume") .arg("list") .args(&["--format", "{{.Name}}"]) @@ -310,35 +324,19 @@ fn get_cross_volumes(engine: &docker::Engine, msg_info: MessageInfo) -> cross::R Ok(volumes) } -pub fn list_volumes( - ListVolumes { - verbose, - quiet, - color, - .. - }: ListVolumes, - engine: &docker::Engine, -) -> cross::Result<()> { - let msg_info = MessageInfo::create(verbose, quiet, color.as_deref())?; +pub fn list_volumes(engine: &docker::Engine, msg_info: &mut MessageInfo) -> cross::Result<()> { for line in get_cross_volumes(engine, msg_info)?.iter() { - shell::print(line, msg_info)?; + msg_info.print(line)?; } Ok(()) } pub fn remove_all_volumes( - RemoveAllVolumes { - verbose, - quiet, - color, - force, - execute, - .. - }: RemoveAllVolumes, + RemoveAllVolumes { force, execute, .. }: RemoveAllVolumes, engine: &docker::Engine, + msg_info: &mut MessageInfo, ) -> cross::Result<()> { - let msg_info = MessageInfo::create(verbose, quiet, color.as_deref())?; let volumes = get_cross_volumes(engine, msg_info)?; let mut command = docker::subcommand(engine, "volume"); @@ -352,57 +350,37 @@ pub fn remove_all_volumes( } else if execute { command.run(msg_info, false).map_err(Into::into) } else { - shell::note( - "this is a dry run. to remove the volumes, pass the `--execute` flag.", - msg_info, - )?; + msg_info.note("this is a dry run. to remove the volumes, pass the `--execute` flag.")?; command.print(msg_info)?; Ok(()) } } pub fn prune_volumes( - PruneVolumes { - verbose, - quiet, - color, - execute, - .. - }: PruneVolumes, + PruneVolumes { execute, .. }: PruneVolumes, engine: &docker::Engine, + msg_info: &mut MessageInfo, ) -> cross::Result<()> { - let msg_info = MessageInfo::create(verbose, quiet, color.as_deref())?; let mut command = docker::subcommand(engine, "volume"); command.args(&["prune", "--force"]); if execute { command.run(msg_info, false).map_err(Into::into) } else { - shell::note( - "this is a dry run. to prune the volumes, pass the `--execute` flag.", - msg_info, - )?; + msg_info.note("this is a dry run. to prune the volumes, pass the `--execute` flag.")?; command.print(msg_info)?; Ok(()) } } pub fn create_persistent_volume( - CreateVolume { - docker_in_docker, - copy_registry, - verbose, - quiet, - color, - .. - }: CreateVolume, + CreateVolume { copy_registry, .. }: CreateVolume, engine: &docker::Engine, channel: Option<&str>, + msg_info: &mut MessageInfo, ) -> cross::Result<()> { - let msg_info = MessageInfo::create(verbose, quiet, color.as_deref())?; // we only need a triple that needs docker: the actual target doesn't matter. let triple = cross::Host::X86_64UnknownLinuxGnu.triple(); - let (target, metadata, dirs) = - docker::get_package_info(engine, triple, channel, docker_in_docker, msg_info)?; + let (target, metadata, dirs) = docker::get_package_info(engine, triple, channel, msg_info)?; let container = docker::remote::unique_container_identifier(&target, &metadata, &dirs)?; let volume = docker::remote::unique_toolchain_identifier(&dirs.sysroot)?; @@ -417,11 +395,11 @@ pub fn create_persistent_volume( // stop the container if it's already running let state = docker::remote::container_state(engine, &container, msg_info)?; if !state.is_stopped() { - shell::warn("container {container} was running.", msg_info)?; + msg_info.warn("container {container} was running.")?; docker::remote::container_stop(engine, &container, msg_info)?; } if state.exists() { - shell::warn("container {container} was exited.", msg_info)?; + msg_info.warn("container {container} was exited.")?; docker::remote::container_rm(engine, &container, msg_info)?; } @@ -472,21 +450,13 @@ pub fn create_persistent_volume( } pub fn remove_persistent_volume( - RemoveVolume { - docker_in_docker, - verbose, - quiet, - color, - .. - }: RemoveVolume, engine: &docker::Engine, channel: Option<&str>, + msg_info: &mut MessageInfo, ) -> cross::Result<()> { // we only need a triple that needs docker: the actual target doesn't matter. - let msg_info = MessageInfo::create(verbose, quiet, color.as_deref())?; let triple = cross::Host::X86_64UnknownLinuxGnu.triple(); - let (_, _, dirs) = - docker::get_package_info(engine, triple, channel, docker_in_docker, msg_info)?; + let (_, _, dirs) = docker::get_package_info(engine, triple, channel, msg_info)?; let volume = docker::remote::unique_toolchain_identifier(&dirs.sysroot)?; if !docker::remote::volume_exists(engine, &volume, msg_info)? { @@ -500,7 +470,7 @@ pub fn remove_persistent_volume( fn get_cross_containers( engine: &docker::Engine, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> cross::Result> { let stdout = docker::subcommand(engine, "ps") .arg("-a") @@ -515,35 +485,19 @@ fn get_cross_containers( Ok(containers) } -pub fn list_containers( - ListContainers { - verbose, - quiet, - color, - .. - }: ListContainers, - engine: &docker::Engine, -) -> cross::Result<()> { - let msg_info = MessageInfo::create(verbose, quiet, color.as_deref())?; +pub fn list_containers(engine: &docker::Engine, msg_info: &mut MessageInfo) -> cross::Result<()> { for line in get_cross_containers(engine, msg_info)?.iter() { - shell::print(line, msg_info)?; + msg_info.print(line)?; } Ok(()) } pub fn remove_all_containers( - RemoveAllContainers { - verbose, - quiet, - color, - force, - execute, - .. - }: RemoveAllContainers, + RemoveAllContainers { force, execute, .. }: RemoveAllContainers, engine: &docker::Engine, + msg_info: &mut MessageInfo, ) -> cross::Result<()> { - let msg_info = MessageInfo::create(verbose, quiet, color.as_deref())?; let containers = get_cross_containers(engine, msg_info)?; let mut running = vec![]; let mut stopped = vec![]; @@ -580,10 +534,7 @@ pub fn remove_all_containers( command.run(msg_info, false)?; } } else { - shell::note( - "this is a dry run. to remove the containers, pass the `--execute` flag.", - msg_info, - )?; + msg_info.note("this is a dry run. to remove the containers, pass the `--execute` flag.")?; for command in commands { command.print(msg_info)?; } diff --git a/src/bin/commands/images.rs b/src/bin/commands/images.rs index 46ebec3e7..14012923d 100644 --- a/src/bin/commands/images.rs +++ b/src/bin/commands/images.rs @@ -2,7 +2,7 @@ use std::collections::{BTreeMap, BTreeSet}; use clap::{Args, Subcommand}; use cross::docker::{self, CROSS_CUSTOM_DOCKERFILE_IMAGE_PREFIX}; -use cross::shell::{self, MessageInfo, Verbosity}; +use cross::shell::MessageInfo; use cross::{CommandExt, TargetList}; // known image prefixes, with their registry @@ -31,8 +31,8 @@ pub struct ListImages { } impl ListImages { - pub fn run(self, engine: docker::Engine) -> cross::Result<()> { - list_images(self, &engine) + pub fn run(self, engine: docker::Engine, msg_info: &mut MessageInfo) -> cross::Result<()> { + list_images(self, &engine, msg_info) } } @@ -64,11 +64,11 @@ pub struct RemoveImages { } impl RemoveImages { - pub fn run(self, engine: docker::Engine) -> cross::Result<()> { + pub fn run(self, engine: docker::Engine, msg_info: &mut MessageInfo) -> cross::Result<()> { if self.targets.is_empty() { - remove_all_images(self, &engine) + remove_all_images(self, &engine, msg_info) } else { - remove_target_images(self, &engine) + remove_target_images(self, &engine, msg_info) } } } @@ -82,10 +82,10 @@ pub enum Images { } impl Images { - pub fn run(self, engine: docker::Engine) -> cross::Result<()> { + pub fn run(self, engine: docker::Engine, msg_info: &mut MessageInfo) -> cross::Result<()> { match self { - Images::List(args) => args.run(engine), - Images::Remove(args) => args.run(engine), + Images::List(args) => args.run(engine, msg_info), + Images::Remove(args) => args.run(engine, msg_info), } } @@ -163,7 +163,7 @@ fn is_local_image(tag: &str) -> bool { fn get_cross_images( engine: &docker::Engine, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, local: bool, ) -> cross::Result> { let mut images: BTreeSet<_> = cross::docker::subcommand(engine, "images") @@ -217,7 +217,7 @@ fn get_image_target( engine: &cross::docker::Engine, image: &Image, target_list: &TargetList, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> cross::Result { if let Some(stripped) = image.repository.strip_prefix(&format!("{GHCR_IO}/")) { return Ok(stripped.to_string()); @@ -256,18 +256,12 @@ fn get_image_target( } pub fn list_images( - ListImages { - targets, - verbose, - quiet, - color, - .. - }: ListImages, + ListImages { targets, .. }: ListImages, engine: &docker::Engine, + msg_info: &mut MessageInfo, ) -> cross::Result<()> { - let msg_info = MessageInfo::create(verbose, quiet, color.as_deref())?; let cross_images = get_cross_images(engine, msg_info, true)?; - let target_list = cross::rustc::target_list((msg_info.color_choice, Verbosity::Quiet).into())?; + let target_list = msg_info.as_quiet(cross::rustc::target_list)?; let mut map: BTreeMap> = BTreeMap::new(); let mut max_target_len = 0; let mut max_image_len = 0; @@ -285,44 +279,45 @@ pub fn list_images( let mut keys: Vec<&str> = map.iter().map(|(k, _)| k.as_ref()).collect(); keys.sort_unstable(); - let print_string = |col1: &str, col2: &str, fill: char| -> 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 { + 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('|'); - row.push(fill); - row.push_str(col2); - let spaces = max_image_len.max(col2.len()) + 1 - col2.len(); - for _ in 0..spaces { + 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('|'); - shell::print(row, msg_info) - }; + 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", ' ')?; - print_string("-------", "------", '-')?; + print_string("Targets", "Images", ' ', msg_info)?; + print_string("-------", "------", '-', msg_info)?; } let print_single = - |_: &str, image: &Image| -> cross::Result<()> { shell::print(image, msg_info) }; - let print_table = |target: &str, image: &Image| -> cross::Result<()> { + |_: &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, ' ') + 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)?; + print_single(target, image, msg_info)?; } else { - print_table(target, image)?; + print_table(target, image, msg_info)?; } } } @@ -333,7 +328,7 @@ pub fn list_images( fn remove_images( engine: &docker::Engine, images: &[Image], - msg_info: MessageInfo, + msg_info: &mut MessageInfo, force: bool, execute: bool, ) -> cross::Result<()> { @@ -347,10 +342,7 @@ fn remove_images( } else if execute { command.run(msg_info, false).map_err(Into::into) } else { - shell::note( - "this is a dry run. to remove the images, pass the `--execute` flag.", - msg_info, - )?; + msg_info.note("this is a dry run. to remove the images, pass the `--execute` flag.")?; command.print(msg_info)?; Ok(()) } @@ -358,17 +350,14 @@ fn remove_images( pub fn remove_all_images( RemoveImages { - verbose, - quiet, - color, force, local, execute, .. }: RemoveImages, engine: &docker::Engine, + msg_info: &mut MessageInfo, ) -> cross::Result<()> { - let msg_info = MessageInfo::create(verbose, quiet, color.as_deref())?; let images = get_cross_images(engine, msg_info, local)?; remove_images(engine, &images, msg_info, force, execute) } @@ -376,19 +365,16 @@ pub fn remove_all_images( pub fn remove_target_images( RemoveImages { targets, - verbose, - quiet, - color, force, local, execute, .. }: RemoveImages, engine: &docker::Engine, + msg_info: &mut MessageInfo, ) -> cross::Result<()> { - let msg_info = MessageInfo::create(verbose, quiet, color.as_deref())?; let cross_images = get_cross_images(engine, msg_info, local)?; - let target_list = cross::rustc::target_list((msg_info.color_choice, Verbosity::Quiet).into())?; + let target_list = msg_info.as_quiet(cross::rustc::target_list)?; let mut images = vec![]; for image in cross_images { let target = dbg!(get_image_target(engine, &image, &target_list, msg_info)?); diff --git a/src/bin/cross-util.rs b/src/bin/cross-util.rs index e5be6ae18..8468c4c31 100644 --- a/src/bin/cross-util.rs +++ b/src/bin/cross-util.rs @@ -49,14 +49,31 @@ fn is_toolchain(toolchain: &str) -> cross::Result { fn get_container_engine( engine: Option<&str>, - msg_info: MessageInfo, + docker_in_docker: bool, + msg_info: &mut MessageInfo, ) -> cross::Result { let engine = if let Some(ce) = engine { which::which(ce)? } else { docker::get_container_engine()? }; - docker::Engine::from_path(engine, None, msg_info) + let in_docker = match docker_in_docker { + true => Some(true), + false => None, + }; + docker::Engine::from_path(engine, in_docker, None, msg_info) +} + +macro_rules! get_engine { + ($args:ident, $docker_in_docker:expr, $msg_info: ident) => {{ + get_container_engine($args.engine(), $docker_in_docker, &mut $msg_info) + }}; +} + +macro_rules! get_msg_info { + ($args:ident) => {{ + MessageInfo::create($args.verbose(), $args.quiet(), $args.color()) + }}; } pub fn main() -> cross::Result<()> { @@ -64,24 +81,24 @@ pub fn main() -> cross::Result<()> { let cli = Cli::parse(); match cli.command { Commands::Images(args) => { - let msg_info = MessageInfo::create(args.verbose(), args.quiet(), args.color())?; - let engine = get_container_engine(args.engine(), msg_info)?; - args.run(engine)?; + let mut msg_info = get_msg_info!(args)?; + let engine = get_engine!(args, false, msg_info)?; + args.run(engine, &mut msg_info)?; } Commands::Volumes(args) => { - let msg_info = MessageInfo::create(args.verbose(), args.quiet(), args.color())?; - let engine = get_container_engine(args.engine(), msg_info)?; - args.run(engine, cli.toolchain.as_deref())?; + let mut msg_info = get_msg_info!(args)?; + let engine = get_engine!(args, args.docker_in_docker(), msg_info)?; + args.run(engine, cli.toolchain.as_deref(), &mut msg_info)?; } Commands::Containers(args) => { - let msg_info = MessageInfo::create(args.verbose(), args.quiet(), args.color())?; - let engine = get_container_engine(args.engine(), msg_info)?; - args.run(engine)?; + let mut msg_info = get_msg_info!(args)?; + let engine = get_engine!(args, false, msg_info)?; + args.run(engine, &mut msg_info)?; } Commands::Clean(args) => { - let msg_info = MessageInfo::create(args.verbose, args.quiet, args.color.as_deref())?; - let engine = get_container_engine(args.engine.as_deref(), msg_info)?; - args.run(engine)?; + let mut msg_info = get_msg_info!(args)?; + let engine = get_engine!(args, false, msg_info)?; + args.run(engine, &mut msg_info)?; } } diff --git a/src/cargo.rs b/src/cargo.rs index 51a35bdf8..90662cf3d 100644 --- a/src/cargo.rs +++ b/src/cargo.rs @@ -120,7 +120,7 @@ pub fn cargo_command() -> Command { pub fn cargo_metadata_with_args( cd: Option<&Path>, args: Option<&Args>, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> Result> { let mut command = cargo_command(); if let Some(channel) = args.and_then(|x| x.channel.as_deref()) { @@ -145,9 +145,9 @@ pub fn cargo_metadata_with_args( } let output = command.run_and_get_output(msg_info)?; if !output.status.success() { - shell::warn("unable to get metadata for package", msg_info)?; + msg_info.warn("unable to get metadata for package")?; let indented = shell::indent(&String::from_utf8(output.stderr)?, shell::default_ident()); - shell::debug(indented, msg_info)?; + msg_info.debug(indented)?; return Ok(None); } let manifest: Option = serde_json::from_slice(&output.stdout)?; @@ -164,13 +164,16 @@ pub fn cargo_metadata_with_args( } /// Pass-through mode -pub fn run(args: &[String], msg_info: MessageInfo) -> Result { +pub fn run(args: &[String], msg_info: &mut MessageInfo) -> Result { cargo_command() .args(args) .run_and_get_status(msg_info, false) } /// run cargo and get the output, does not check the exit status -pub fn run_and_get_output(args: &[String], msg_info: MessageInfo) -> Result { +pub fn run_and_get_output( + args: &[String], + msg_info: &mut MessageInfo, +) -> Result { cargo_command().args(args).run_and_get_output(msg_info) } diff --git a/src/cli.rs b/src/cli.rs index 82f956a12..c0f9120dc 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,10 +1,9 @@ use std::{env, path::PathBuf}; use crate::cargo::Subcommand; -use crate::config::bool_from_envvar; use crate::errors::Result; use crate::rustc::TargetList; -use crate::shell::{self, MessageInfo}; +use crate::shell::MessageInfo; use crate::Target; #[derive(Debug)] @@ -15,11 +14,11 @@ pub struct Args { pub target: Option, pub features: Vec, pub target_dir: Option, - pub docker_in_docker: bool, - pub enable_doctests: bool, pub manifest_path: Option, pub version: bool, - pub msg_info: MessageInfo, + pub verbose: bool, + pub quiet: bool, + pub color: Option, } // Fix for issue #581. target_dir must be absolute. @@ -52,23 +51,86 @@ pub fn group_subcommands(stdout: &str) -> (Vec<&str>, Vec<&str>) { (cross, host) } -pub fn fmt_subcommands(stdout: &str, msg_info: MessageInfo) -> Result<()> { +pub fn fmt_subcommands(stdout: &str, msg_info: &mut MessageInfo) -> Result<()> { let (cross, host) = group_subcommands(stdout); if !cross.is_empty() { - shell::print("Cross Commands:", msg_info)?; + msg_info.print("Cross Commands:")?; for line in cross.iter() { - shell::print(line, msg_info)?; + msg_info.print(line)?; } } if !host.is_empty() { - shell::print("Host Commands:", msg_info)?; + msg_info.print("Host Commands:")?; for line in cross.iter() { - shell::print(line, msg_info)?; + msg_info.print(line)?; } } Ok(()) } +fn is_verbose(arg: &str) -> bool { + match arg { + "--verbose" => true, + // cargo can handle any number of "v"s + a => a + .get(1..) + .map(|a| a.chars().all(|x| x == 'v')) + .unwrap_or_default(), + } +} + +enum ArgKind { + Next, + Equal, +} + +fn is_value_arg(arg: &str, field: &str) -> Option { + if arg == field { + Some(ArgKind::Next) + } else if arg + .strip_prefix(field) + .map(|a| a.starts_with('=')) + .unwrap_or_default() + { + Some(ArgKind::Equal) + } else { + None + } +} + +fn parse_next_arg( + arg: String, + out: &mut Vec, + parse: impl Fn(&str) -> T, + iter: &mut impl Iterator, +) -> Option { + out.push(arg); + match iter.next() { + Some(next) => { + let result = parse(&next); + out.push(next); + Some(result) + } + None => None, + } +} + +fn parse_equal_arg(arg: String, out: &mut Vec, parse: impl Fn(&str) -> T) -> T { + let result = parse(arg.split_once('=').unwrap().1); + out.push(arg); + + result +} + +fn parse_manifest_path(path: &str) -> Option { + let p = PathBuf::from(path); + env::current_dir().ok().map(|cwd| cwd.join(p)) +} + +fn parse_target_dir(path: &str) -> Result { + absolute_path(PathBuf::from(path)) +} + pub fn parse(target_list: &TargetList) -> Result { let mut channel = None; let mut target = None; @@ -81,7 +143,7 @@ pub fn parse(target_list: &TargetList) -> Result { let mut quiet = false; let mut verbose = false; let mut color = None; - let default_msg_info = MessageInfo::default(); + let mut default_msg_info = MessageInfo::default(); { let mut args = env::args().skip(1); @@ -89,7 +151,7 @@ pub fn parse(target_list: &TargetList) -> Result { if arg.is_empty() { continue; } - if matches!(arg.as_str(), "--verbose" | "-v" | "-vv") { + if is_verbose(arg.as_str()) { verbose = true; all.push(arg); } else if matches!(arg.as_str(), "--version" | "-V") { @@ -97,65 +159,59 @@ pub fn parse(target_list: &TargetList) -> Result { } else if matches!(arg.as_str(), "--quiet" | "-q") { quiet = true; all.push(arg); - } else if arg == "--color" { - all.push(arg); - match args.next() { - Some(arg) => { - color = { - all.push(arg.clone()); - Some(arg) + } else if let Some(kind) = is_value_arg(&arg, "--color") { + color = match kind { + ArgKind::Next => { + match parse_next_arg(arg, &mut all, ToOwned::to_owned, &mut args) { + Some(c) => Some(c), + None => default_msg_info.fatal_usage("--color ", 1), } } - None => { - shell::fatal_usage("--color ", default_msg_info, 1); + ArgKind::Equal => Some(parse_equal_arg(arg, &mut all, ToOwned::to_owned)), + }; + } else if let Some(kind) = is_value_arg(&arg, "--manifest-path") { + manifest_path = match kind { + ArgKind::Next => { + parse_next_arg(arg, &mut all, parse_manifest_path, &mut args).flatten() } - } - } else if arg == "--manifest-path" { - all.push(arg); - if let Some(m) = args.next() { - let p = PathBuf::from(&m); - all.push(m); - manifest_path = env::current_dir().ok().map(|cwd| cwd.join(p)); - } - } else if arg.starts_with("--manifest-path=") { - manifest_path = arg - .split_once('=') - .map(|x| x.1) - .map(PathBuf::from) - .and_then(|p| env::current_dir().ok().map(|cwd| cwd.join(p))); - all.push(arg); + ArgKind::Equal => parse_equal_arg(arg, &mut all, parse_manifest_path), + }; } else if let ("+", ch) = arg.split_at(1) { channel = Some(ch.to_string()); - } else if arg == "--target" { - all.push(arg); - if let Some(t) = args.next() { - target = Some(Target::from(&t, target_list)); - all.push(t); - } - } else if arg.starts_with("--target=") { - target = arg - .split_once('=') - .map(|(_, t)| Target::from(t, target_list)); - all.push(arg); - } else if arg == "--features" { - all.push(arg); - if let Some(t) = args.next() { - features.push(t.clone()); - all.push(t); - } - } else if arg.starts_with("--features=") { - features.extend(arg.split_once('=').map(|(_, t)| t.to_owned())); - all.push(arg); - } else if arg == "--target-dir" { - all.push(arg); - if let Some(td) = args.next() { - target_dir = Some(absolute_path(PathBuf::from(&td))?); - all.push("/target".to_string()); + } else if let Some(kind) = is_value_arg(&arg, "--target") { + target = match kind { + ArgKind::Next => { + parse_next_arg(arg, &mut all, |t| Target::from(t, target_list), &mut args) + } + ArgKind::Equal => Some(parse_equal_arg(arg, &mut all, |t| { + Target::from(t, target_list) + })), + }; + } else if let Some(kind) = is_value_arg(&arg, "--features") { + match kind { + ArgKind::Next => { + let next = parse_next_arg(arg, &mut all, ToOwned::to_owned, &mut args); + if let Some(feature) = next { + features.push(feature); + } + } + ArgKind::Equal => { + features.push(parse_equal_arg(arg, &mut all, ToOwned::to_owned)) + } } - } else if arg.starts_with("--target-dir=") { - if let Some((_, td)) = arg.split_once('=') { - target_dir = Some(absolute_path(PathBuf::from(&td))?); - all.push("--target-dir=/target".into()); + } else if let Some(kind) = is_value_arg(&arg, "--target-dir") { + match kind { + ArgKind::Next => { + all.push(arg); + if let Some(td) = args.next() { + target_dir = Some(parse_target_dir(&td)?); + all.push("/target".to_string()); + } + } + ArgKind::Equal => { + target_dir = Some(parse_target_dir(arg.split_once('=').unwrap().1)?); + all.push("--target-dir=/target".into()); + } } } else { if (!arg.starts_with('-') || arg == "--list") && sc.is_none() { @@ -167,26 +223,6 @@ pub fn parse(target_list: &TargetList) -> Result { } } - let msg_info = shell::MessageInfo::create(verbose, quiet, color.as_deref())?; - let docker_in_docker = if let Ok(value) = env::var("CROSS_CONTAINER_IN_CONTAINER") { - if env::var("CROSS_DOCKER_IN_DOCKER").is_ok() { - shell::warn( - "using both `CROSS_CONTAINER_IN_CONTAINER` and `CROSS_DOCKER_IN_DOCKER`.", - msg_info, - )?; - } - bool_from_envvar(&value) - } else if let Ok(value) = env::var("CROSS_DOCKER_IN_DOCKER") { - // FIXME: remove this when we deprecate CROSS_DOCKER_IN_DOCKER. - bool_from_envvar(&value) - } else { - false - }; - - let enable_doctests = env::var("CROSS_UNSTABLE_ENABLE_DOCTESTS") - .map(|s| bool_from_envvar(&s)) - .unwrap_or_default(); - Ok(Args { all, subcommand: sc, @@ -194,10 +230,10 @@ pub fn parse(target_list: &TargetList) -> Result { target, features, target_dir, - docker_in_docker, - enable_doctests, manifest_path, version, - msg_info, + verbose, + quiet, + color, }) } diff --git a/src/config.rs b/src/config.rs index ecc7edfeb..8a9e6c8e2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,4 @@ -use crate::shell::{self, MessageInfo}; +use crate::shell::MessageInfo; use crate::{CrossToml, Result, Target, TargetList}; use std::collections::HashMap; @@ -99,6 +99,16 @@ impl Environment { self.get_build_var("TARGET") .or_else(|| std::env::var("CARGO_BUILD_TARGET").ok()) } + + fn doctests(&self) -> Option { + env::var("CROSS_UNSTABLE_ENABLE_DOCTESTS") + .map(|s| bool_from_envvar(&s)) + .ok() + } + + fn custom_toolchain(&self) -> bool { + std::env::var("CROSS_CUSTOM_TOOLCHAIN").is_ok() + } } fn split_to_cloned_by_ws(string: &str) -> Vec { @@ -129,7 +139,7 @@ impl Config { } } - pub fn confusable_target(&self, target: &Target, msg_info: MessageInfo) -> Result<()> { + pub fn confusable_target(&self, target: &Target, msg_info: &mut MessageInfo) -> Result<()> { if let Some(keys) = self.toml.as_ref().map(|t| t.targets.keys()) { for mentioned_target in keys { let mentioned_target_norm = mentioned_target @@ -141,11 +151,8 @@ impl Config { .replace(|c| c == '-' || c == '_', "") .to_lowercase(); if mentioned_target != target && mentioned_target_norm == target_norm { - shell::warn("a target named \"{mentioned_target}\" is mentioned in the Cross configuration, but the current specified target is \"{target}\".", msg_info)?; - shell::status( - " > Is the target misspelled in the Cross configuration?", - msg_info, - )?; + msg_info.warn("a target named \"{mentioned_target}\" is mentioned in the Cross configuration, but the current specified target is \"{target}\".")?; + msg_info.status(" > Is the target misspelled in the Cross configuration?")?; } } } @@ -267,6 +274,14 @@ impl Config { self.string_from_config(target, Environment::runner, CrossToml::runner) } + pub fn doctests(&self) -> Option { + self.env.doctests() + } + + pub fn custom_toolchain(&self) -> bool { + self.env.custom_toolchain() + } + pub fn env_passthrough(&self, target: &Target) -> Result>> { self.vec_from_config( target, @@ -457,9 +472,11 @@ mod tests { } fn toml(content: &str) -> Result { - Ok(CrossToml::parse_from_cross(content, MessageInfo::default()) - .wrap_err("couldn't parse toml")? - .0) + Ok( + CrossToml::parse_from_cross(content, &mut MessageInfo::default()) + .wrap_err("couldn't parse toml")? + .0, + ) } #[test] diff --git a/src/cross_toml.rs b/src/cross_toml.rs index 770fa15a3..edb9cb93f 100644 --- a/src/cross_toml.rs +++ b/src/cross_toml.rs @@ -1,6 +1,6 @@ #![doc = include_str!("../docs/cross_toml.md")] -use crate::shell::{self, MessageInfo}; +use crate::shell::MessageInfo; use crate::{config, errors::*}; use crate::{Target, TargetList}; use serde::de::DeserializeOwned; @@ -79,7 +79,7 @@ impl CrossToml { pub fn parse( cargo_toml: &str, cross_toml: &str, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> Result<(Self, BTreeSet)> { let (cross_toml, mut unused) = Self::parse_from_cross(cross_toml, msg_info)?; @@ -94,7 +94,7 @@ impl CrossToml { /// Parses the [`CrossToml`] from a string pub fn parse_from_cross( toml_str: &str, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> Result<(Self, BTreeSet)> { let mut tomld = toml::Deserializer::new(toml_str); Self::parse_from_deserializer(&mut tomld, msg_info) @@ -103,7 +103,7 @@ impl CrossToml { /// Parses the [`CrossToml`] from a string containing the Cargo.toml contents pub fn parse_from_cargo( cargo_toml_str: &str, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> Result)>> { let cargo_toml: toml::Value = toml::from_str(cargo_toml_str)?; let cross_metadata_opt = cargo_toml @@ -124,7 +124,7 @@ impl CrossToml { /// Parses the [`CrossToml`] from a [`Deserializer`] fn parse_from_deserializer<'de, D>( deserializer: D, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> Result<(Self, BTreeSet)> where D: Deserializer<'de>, @@ -136,13 +136,10 @@ impl CrossToml { })?; if !unused.is_empty() { - shell::warn( - format!( - "found unused key(s) in Cross configuration:\n > {}", - unused.clone().into_iter().collect::>().join(", ") - ), - msg_info, - )?; + msg_info.warn(format!( + "found unused key(s) in Cross configuration:\n > {}", + unused.clone().into_iter().collect::>().join(", ") + ))?; } Ok((cfg, unused)) @@ -401,10 +398,13 @@ where #[cfg(test)] mod tests { use super::*; - const MSG_INFO: MessageInfo = MessageInfo { - color_choice: shell::ColorChoice::Never, - verbosity: shell::Verbosity::Quiet, - }; + use crate::shell; + + macro_rules! m { + () => { + MessageInfo::new(shell::ColorChoice::Never, shell::Verbosity::Quiet) + }; + } macro_rules! s { ($x:literal) => { @@ -418,7 +418,7 @@ mod tests { targets: HashMap::new(), build: CrossBuildConfig::default(), }; - let (parsed_cfg, unused) = CrossToml::parse_from_cross("", MSG_INFO)?; + let (parsed_cfg, unused) = CrossToml::parse_from_cross("", &mut m!())?; assert_eq!(parsed_cfg, cfg); assert!(unused.is_empty()); @@ -452,7 +452,7 @@ mod tests { volumes = ["VOL1_ARG", "VOL2_ARG"] passthrough = ["VAR1", "VAR2"] "#; - let (parsed_cfg, unused) = CrossToml::parse_from_cross(test_str, MSG_INFO)?; + let (parsed_cfg, unused) = CrossToml::parse_from_cross(test_str, &mut m!())?; assert_eq!(parsed_cfg, cfg); assert!(unused.is_empty()); @@ -496,7 +496,7 @@ mod tests { image = "test-image" pre-build = [] "#; - let (parsed_cfg, unused) = CrossToml::parse_from_cross(test_str, MSG_INFO)?; + let (parsed_cfg, unused) = CrossToml::parse_from_cross(test_str, &mut m!())?; assert_eq!(parsed_cfg, cfg); assert!(unused.is_empty()); @@ -560,7 +560,7 @@ mod tests { [target.aarch64-unknown-linux-gnu.env] volumes = ["VOL"] "#; - let (parsed_cfg, unused) = CrossToml::parse_from_cross(test_str, MSG_INFO)?; + let (parsed_cfg, unused) = CrossToml::parse_from_cross(test_str, &mut m!())?; assert_eq!(parsed_cfg, cfg); assert!(unused.is_empty()); @@ -579,7 +579,7 @@ mod tests { cross = "1.2.3" "#; - let res = CrossToml::parse_from_cargo(test_str, MSG_INFO)?; + let res = CrossToml::parse_from_cargo(test_str, &mut m!())?; assert!(res.is_none()); Ok(()) @@ -614,7 +614,7 @@ mod tests { xargo = true "#; - if let Some((parsed_cfg, _unused)) = CrossToml::parse_from_cargo(test_str, MSG_INFO)? { + if let Some((parsed_cfg, _unused)) = CrossToml::parse_from_cargo(test_str, &mut m!())? { assert_eq!(parsed_cfg, cfg); } else { panic!("Parsing result is None"); @@ -722,9 +722,9 @@ mod tests { "#; // Parses configs - let (cfg1, _) = CrossToml::parse_from_cross(cfg1_str, MSG_INFO)?; - let (cfg2, _) = CrossToml::parse_from_cross(cfg2_str, MSG_INFO)?; - let (cfg_expected, _) = CrossToml::parse_from_cross(cfg_expected_str, MSG_INFO)?; + let (cfg1, _) = CrossToml::parse_from_cross(cfg1_str, &mut m!())?; + let (cfg2, _) = CrossToml::parse_from_cross(cfg2_str, &mut m!())?; + let (cfg_expected, _) = CrossToml::parse_from_cross(cfg_expected_str, &mut m!())?; // Merges config and compares let cfg_merged = cfg1.merge(cfg2)?; diff --git a/src/docker/custom.rs b/src/docker/custom.rs index 856c4bbde..1e6f3cf05 100644 --- a/src/docker/custom.rs +++ b/src/docker/custom.rs @@ -1,9 +1,9 @@ use std::io::Write; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; -use crate::docker::Engine; +use crate::docker::{DockerOptions, DockerPaths}; use crate::shell::MessageInfo; -use crate::{config::Config, docker, CargoMetadata, Target}; +use crate::{docker, CargoMetadata, Target}; use crate::{errors::*, file, CommandExt, ToUtf8}; use super::{image_name, parse_docker_opts, path_hash}; @@ -23,25 +23,22 @@ pub enum Dockerfile<'a> { } impl<'a> Dockerfile<'a> { - #[allow(clippy::too_many_arguments)] pub fn build( &self, - config: &Config, - metadata: &CargoMetadata, - engine: &Engine, - host_root: &Path, + options: &DockerOptions, + paths: &DockerPaths, build_args: impl IntoIterator, impl AsRef)>, - target_triple: &Target, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> Result { - let mut docker_build = docker::subcommand(engine, "build"); - docker_build.current_dir(host_root); + let mut docker_build = docker::subcommand(&options.engine, "build"); + docker_build.current_dir(paths.host_root()); docker_build.env("DOCKER_SCAN_SUGGEST", "false"); docker_build.args([ "--label", &format!( - "{}.for-cross-target={target_triple}", - crate::CROSS_LABEL_DOMAIN + "{}.for-cross-target={}", + crate::CROSS_LABEL_DOMAIN, + options.target, ), ]); @@ -50,28 +47,29 @@ impl<'a> Dockerfile<'a> { &format!( "{}.workspace_root={}", crate::CROSS_LABEL_DOMAIN, - metadata.workspace_root.to_utf8()? + paths.workspace_root().to_utf8()? ), ]); - let image_name = self.image_name(target_triple, metadata)?; + let image_name = self.image_name(&options.target, &paths.metadata)?; docker_build.args(["--tag", &image_name]); for (key, arg) in build_args.into_iter() { docker_build.args(["--build-arg", &format!("{}={}", key.as_ref(), arg.as_ref())]); } - if let Some(arch) = target_triple.deb_arch() { + if let Some(arch) = options.target.deb_arch() { docker_build.args(["--build-arg", &format!("CROSS_DEB_ARCH={arch}")]); } let path = match self { Dockerfile::File { path, .. } => PathBuf::from(path), Dockerfile::Custom { content } => { - let path = metadata + let path = paths + .metadata .target_directory - .join(target_triple.to_string()) - .join(format!("Dockerfile.{}-custom", target_triple,)); + .join(options.target.to_string()) + .join(format!("Dockerfile.{}-custom", &options.target)); { let mut file = file::write_file(&path, true)?; file.write_all(content.as_bytes())?; @@ -81,7 +79,7 @@ impl<'a> Dockerfile<'a> { }; if matches!(self, Dockerfile::File { .. }) { - if let Ok(cross_base_image) = self::image_name(config, target_triple) { + if let Ok(cross_base_image) = self::image_name(&options.config, &options.target) { docker_build.args([ "--build-arg", &format!("CROSS_BASE_IMAGE={cross_base_image}"), diff --git a/src/docker/engine.rs b/src/docker/engine.rs index 37a15dd38..1694acff9 100644 --- a/src/docker/engine.rs +++ b/src/docker/engine.rs @@ -22,27 +22,38 @@ pub enum EngineType { pub struct Engine { pub kind: EngineType, pub path: PathBuf, + pub in_docker: bool, pub is_remote: bool, } impl Engine { - pub fn new(is_remote: Option, msg_info: MessageInfo) -> Result { + pub fn new( + in_docker: Option, + is_remote: Option, + msg_info: &mut MessageInfo, + ) -> Result { let path = get_container_engine() .map_err(|_| eyre::eyre!("no container engine found")) .with_suggestion(|| "is docker or podman installed?")?; - Self::from_path(path, is_remote, msg_info) + Self::from_path(path, in_docker, is_remote, msg_info) } pub fn from_path( path: PathBuf, + in_docker: Option, is_remote: Option, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> Result { let kind = get_engine_type(&path, msg_info)?; + let in_docker = match in_docker { + Some(v) => v, + None => Self::in_docker(msg_info)?, + }; let is_remote = is_remote.unwrap_or_else(Self::is_remote); Ok(Engine { path, kind, + in_docker, is_remote, }) } @@ -51,6 +62,24 @@ impl Engine { self.is_remote && self.kind == EngineType::Podman } + pub fn in_docker(msg_info: &mut MessageInfo) -> Result { + Ok( + if let Ok(value) = env::var("CROSS_CONTAINER_IN_CONTAINER") { + if env::var("CROSS_DOCKER_IN_DOCKER").is_ok() { + msg_info.warn( + "using both `CROSS_CONTAINER_IN_CONTAINER` and `CROSS_DOCKER_IN_DOCKER`.", + )?; + } + bool_from_envvar(&value) + } else if let Ok(value) = env::var("CROSS_DOCKER_IN_DOCKER") { + // FIXME: remove this when we deprecate CROSS_DOCKER_IN_DOCKER. + bool_from_envvar(&value) + } else { + false + }, + ) + } + pub fn is_remote() -> bool { env::var("CROSS_REMOTE") .map(|s| bool_from_envvar(&s)) @@ -60,7 +89,7 @@ impl Engine { // determine if the container engine is docker. this fixes issues with // any aliases (#530), and doesn't fail if an executable suffix exists. -fn get_engine_type(ce: &Path, msg_info: MessageInfo) -> Result { +fn get_engine_type(ce: &Path, msg_info: &mut MessageInfo) -> Result { let stdout = Command::new(ce) .arg("--help") .run_and_get_stdout(msg_info)? diff --git a/src/docker/local.rs b/src/docker/local.rs index a87666f52..1e1b4f89d 100644 --- a/src/docker/local.rs +++ b/src/docker/local.rs @@ -1,54 +1,40 @@ use std::io; -use std::path::Path; use std::process::ExitStatus; -use super::engine::*; use super::shared::*; -use crate::cargo::CargoMetadata; use crate::errors::Result; use crate::extensions::CommandExt; use crate::file::{PathExt, ToUtf8}; use crate::shell::{MessageInfo, Stream}; -use crate::{Config, Target}; use eyre::Context; -#[allow(clippy::too_many_arguments)] // TODO: refactor pub(crate) fn run( - engine: &Engine, - target: &Target, + options: DockerOptions, + paths: DockerPaths, args: &[String], - metadata: &CargoMetadata, - config: &Config, - uses_xargo: bool, - sysroot: &Path, - msg_info: MessageInfo, - docker_in_docker: bool, - cwd: &Path, + msg_info: &mut MessageInfo, ) -> Result { - let mount_finder = MountFinder::create(engine, docker_in_docker)?; - let dirs = Directories::create(&mount_finder, metadata, cwd, sysroot)?; + let engine = &options.engine; + let dirs = &paths.directories; - let mut cmd = cargo_safe_command(uses_xargo); + let mut cmd = cargo_safe_command(options.uses_xargo); cmd.args(args); let mut docker = subcommand(engine, "run"); docker_userns(&mut docker); - docker_envvars(&mut docker, config, target, msg_info)?; + docker_envvars(&mut docker, &options.config, &options.target, msg_info)?; let mount_volumes = docker_mount( &mut docker, - metadata, - &mount_finder, - config, - target, - cwd, + &options, + &paths, |docker, val| mount(docker, val, ""), |_| {}, )?; docker.arg("--rm"); - docker_seccomp(&mut docker, engine.kind, target, metadata) + docker_seccomp(&mut docker, engine.kind, &options.target, &paths.metadata) .wrap_err("when copying seccomp profile")?; docker_user_id(&mut docker, engine.kind); @@ -68,7 +54,7 @@ pub(crate) fn run( docker .args(&["-v", &format!("{}:/rust:Z,ro", dirs.sysroot.to_utf8()?)]) .args(&["-v", &format!("{}:/target:Z", dirs.target.to_utf8()?)]); - docker_cwd(&mut docker, metadata, &dirs, cwd, mount_volumes)?; + docker_cwd(&mut docker, &paths, mount_volumes)?; // When running inside NixOS or using Nix packaging we need to add the Nix // Store to the running container so it can load the needed binaries. @@ -85,9 +71,10 @@ pub(crate) fn run( docker.arg("-t"); } } - let mut image = image_name(config, target)?; - if needs_custom_image(target, config) { - image = custom_image_build(target, config, metadata, dirs, engine, msg_info) + let mut image = options.image_name()?; + if options.needs_custom_image() { + image = options + .custom_image_build(&paths, msg_info) .wrap_err("when building custom image")? } diff --git a/src/docker/mod.rs b/src/docker/mod.rs index 1e5cbb067..b0b3de413 100644 --- a/src/docker/mod.rs +++ b/src/docker/mod.rs @@ -7,60 +7,26 @@ mod shared; pub use self::engine::*; pub use self::shared::*; -use std::path::Path; use std::process::ExitStatus; -use crate::cargo::CargoMetadata; use crate::errors::*; -use crate::shell::{self, MessageInfo}; -use crate::{Config, Target}; +use crate::shell::MessageInfo; -#[allow(clippy::too_many_arguments)] // TODO: refactor pub fn run( - engine: &Engine, - target: &Target, + options: DockerOptions, + paths: DockerPaths, args: &[String], - metadata: &CargoMetadata, - config: &Config, - uses_xargo: bool, - sysroot: &Path, - msg_info: MessageInfo, - docker_in_docker: bool, - cwd: &Path, + msg_info: &mut MessageInfo, ) -> Result { - if cfg!(target_os = "windows") && docker_in_docker { - shell::fatal( + if cfg!(target_os = "windows") && options.in_docker() { + msg_info.fatal( "running cross insider a container running windows is currently unsupported", - msg_info, 1, ); } - if engine.is_remote { - remote::run( - engine, - target, - args, - metadata, - config, - uses_xargo, - sysroot, - msg_info, - docker_in_docker, - cwd, - ) - .wrap_err("could not complete remote run") + if options.is_remote() { + remote::run(options, paths, args, msg_info).wrap_err("could not complete remote run") } else { - local::run( - engine, - target, - args, - metadata, - config, - uses_xargo, - sysroot, - msg_info, - docker_in_docker, - cwd, - ) + local::run(options, paths, args, msg_info) } } diff --git a/src/docker/remote.rs b/src/docker/remote.rs index b013fcf13..31c487bf0 100644 --- a/src/docker/remote.rs +++ b/src/docker/remote.rs @@ -9,35 +9,40 @@ use eyre::Context; use super::engine::Engine; use super::shared::*; use crate::cargo::CargoMetadata; -use crate::config::{bool_from_envvar, Config}; +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::{self, MessageInfo, Stream}; +use crate::shell::{ColorChoice, MessageInfo, Stream, Verbosity}; use crate::temp; use crate::{Host, Target}; // the mount directory for the data volume. pub const MOUNT_PREFIX: &str = "/cross"; -struct DeleteVolume<'a>(&'a Engine, &'a VolumeId, MessageInfo); +// it's unlikely that we ever need to erase a line in the destructors, +// and it's better than keep global state everywhere, or keeping a ref +// cell which could have already deleted a line +struct DeleteVolume<'a>(&'a Engine, &'a VolumeId, ColorChoice, Verbosity); impl<'a> Drop for DeleteVolume<'a> { fn drop(&mut self) { if let VolumeId::Discard(id) = self.1 { - volume_rm(self.0, id, self.2).ok(); + let mut msg_info = MessageInfo::new(self.2, self.3); + volume_rm(self.0, id, &mut msg_info).ok(); } } } -struct DeleteContainer<'a>(&'a Engine, &'a str, MessageInfo); +struct DeleteContainer<'a>(&'a Engine, &'a str, ColorChoice, Verbosity); impl<'a> Drop for DeleteContainer<'a> { fn drop(&mut self) { - container_stop(self.0, self.1, self.2).ok(); - container_rm(self.0, self.1, self.2).ok(); + let mut msg_info = MessageInfo::new(self.2, self.3); + container_stop(self.0, self.1, &mut msg_info).ok(); + container_rm(self.0, self.1, &mut msg_info).ok(); } } @@ -86,7 +91,7 @@ impl VolumeId { engine: &Engine, toolchain: &str, container: &str, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> Result { if volume_exists(engine, toolchain, msg_info)? { Ok(Self::Keep(toolchain.to_string())) @@ -109,7 +114,7 @@ fn create_volume_dir( engine: &Engine, container: &str, dir: &Path, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> Result { // make our parent directory if needed subcommand(engine, "exec") @@ -124,7 +129,7 @@ fn copy_volume_files( container: &str, src: &Path, dst: &Path, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> Result { subcommand(engine, "cp") .arg("-a") @@ -156,7 +161,7 @@ fn container_path_exists( engine: &Engine, container: &str, path: &Path, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> Result { Ok(subcommand(engine, "exec") .arg(container) @@ -172,7 +177,7 @@ fn copy_volume_files_nocache( src: &Path, dst: &Path, copy_symlinks: bool, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> Result { // avoid any cached directories when copying // see https://bford.info/cachedir/ @@ -190,7 +195,7 @@ pub fn copy_volume_container_xargo( xargo_dir: &Path, target: &Target, mount_prefix: &Path, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> Result<()> { // only need to copy the rustlib files for our current target. let triple = target.triple(); @@ -211,7 +216,7 @@ pub fn copy_volume_container_cargo( cargo_dir: &Path, mount_prefix: &Path, copy_registry: bool, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> Result<()> { let dst = mount_prefix.join("cargo"); let copy_registry = env::var("CROSS_REMOTE_COPY_REGISTRY") @@ -300,9 +305,9 @@ where Ok(had_symlinks) } -fn warn_symlinks(had_symlinks: bool, msg_info: MessageInfo) -> Result<()> { +fn warn_symlinks(had_symlinks: bool, msg_info: &mut MessageInfo) -> Result<()> { if had_symlinks { - shell::warn("copied directory contained symlinks. if the volume the link points to was not mounted, the remote build may fail", msg_info) + msg_info.warn("copied directory contained symlinks. if the volume the link points to was not mounted, the remote build may fail") } else { Ok(()) } @@ -314,7 +319,7 @@ fn copy_volume_container_rust_base( container: &str, sysroot: &Path, mount_prefix: &Path, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> Result<()> { // the rust toolchain is quite large, but most of it isn't needed // we need the bin, libexec, and etc directories, and part of the lib directory. @@ -361,7 +366,7 @@ fn copy_volume_container_rust_manifest( container: &str, sysroot: &Path, mount_prefix: &Path, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> Result<()> { // copy over all the manifest files in rustlib // these are small text files containing names/paths to toolchains @@ -392,7 +397,7 @@ pub fn copy_volume_container_rust_triple( triple: &str, mount_prefix: &Path, skip_exists: bool, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> Result<()> { // copy over the files for a specific triple let dst = mount_prefix.join("rust"); @@ -426,7 +431,7 @@ pub fn copy_volume_container_rust( target: &Target, mount_prefix: &Path, skip_target: bool, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> Result<()> { let target_triple = target.triple(); let image_triple = Host::X86_64UnknownLinuxGnu.triple(); @@ -557,7 +562,7 @@ fn copy_volume_file_list( src: &Path, dst: &Path, files: &[&str], - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> Result { // SAFETY: safe, single-threaded execution. let tempdir = unsafe { temp::TempDir::new()? }; @@ -578,11 +583,11 @@ fn remove_volume_file_list( container: &str, dst: &Path, files: &[&str], - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> Result { const PATH: &str = "/tmp/remove_list"; let mut script = vec![]; - if msg_info.verbose() { + if msg_info.is_verbose() { script.push("set -x".to_string()); } script.push(format!( @@ -620,13 +625,13 @@ fn copy_volume_container_project( dst: &Path, volume: &VolumeId, copy_cache: bool, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> Result<()> { - let copy_all = || { + let copy_all = |info: &mut MessageInfo| { if copy_cache { - copy_volume_files(engine, container, src, dst, msg_info) + copy_volume_files(engine, container, src, dst, info) } else { - copy_volume_files_nocache(engine, container, src, dst, true, msg_info) + copy_volume_files_nocache(engine, container, src, dst, true, info) } }; match volume { @@ -648,32 +653,40 @@ fn copy_volume_container_project( } } else { write_project_fingerprint(&fingerprint, ¤t)?; - copy_all()?; + copy_all(msg_info)?; } } VolumeId::Discard(_) => { - copy_all()?; + copy_all(msg_info)?; } } Ok(()) } -fn run_and_get_status(engine: &Engine, args: &[&str], msg_info: MessageInfo) -> Result { +fn run_and_get_status( + engine: &Engine, + args: &[&str], + msg_info: &mut MessageInfo, +) -> Result { command(engine) .args(args) .run_and_get_status(msg_info, true) } -pub fn volume_create(engine: &Engine, volume: &str, msg_info: MessageInfo) -> Result { +pub fn volume_create( + engine: &Engine, + volume: &str, + msg_info: &mut MessageInfo, +) -> Result { run_and_get_status(engine, &["volume", "create", volume], msg_info) } -pub fn volume_rm(engine: &Engine, volume: &str, msg_info: MessageInfo) -> Result { +pub fn volume_rm(engine: &Engine, volume: &str, msg_info: &mut MessageInfo) -> Result { run_and_get_status(engine, &["volume", "rm", volume], msg_info) } -pub fn volume_exists(engine: &Engine, volume: &str, msg_info: MessageInfo) -> Result { +pub fn volume_exists(engine: &Engine, volume: &str, msg_info: &mut MessageInfo) -> Result { command(engine) .args(&["volume", "inspect", volume]) .run_and_get_output(msg_info) @@ -683,19 +696,23 @@ pub fn volume_exists(engine: &Engine, volume: &str, msg_info: MessageInfo) -> Re pub fn container_stop( engine: &Engine, container: &str, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> Result { run_and_get_status(engine, &["stop", container], msg_info) } -pub fn container_rm(engine: &Engine, container: &str, msg_info: MessageInfo) -> Result { +pub fn container_rm( + engine: &Engine, + container: &str, + msg_info: &mut MessageInfo, +) -> Result { run_and_get_status(engine, &["rm", container], msg_info) } pub fn container_state( engine: &Engine, container: &str, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> Result { let stdout = command(engine) .args(&["ps", "-a"]) @@ -751,21 +768,15 @@ fn mount_path(val: &Path) -> Result { canonicalize_mount_path(&host_path) } -#[allow(clippy::too_many_arguments)] // TODO: refactor pub(crate) fn run( - engine: &Engine, - target: &Target, + options: DockerOptions, + paths: DockerPaths, args: &[String], - metadata: &CargoMetadata, - config: &Config, - uses_xargo: bool, - sysroot: &Path, - msg_info: MessageInfo, - docker_in_docker: bool, - cwd: &Path, + msg_info: &mut MessageInfo, ) -> Result { - let mount_finder = MountFinder::create(engine, docker_in_docker)?; - let dirs = Directories::create(&mount_finder, metadata, cwd, sysroot)?; + let engine = &options.engine; + let target = &options.target; + let dirs = &paths.directories; let mount_prefix = MOUNT_PREFIX; @@ -790,20 +801,20 @@ pub(crate) fn run( // 1. get our unique identifiers and cleanup from a previous run. // this can happen if we didn't gracefully exit before let toolchain_id = unique_toolchain_identifier(&dirs.sysroot)?; - let container = unique_container_identifier(target, metadata, &dirs)?; + let container = unique_container_identifier(target, &paths.metadata, dirs)?; let volume = VolumeId::create(engine, &toolchain_id, &container, msg_info)?; let state = container_state(engine, &container, msg_info)?; if !state.is_stopped() { - shell::warn(format_args!("container {container} was running."), msg_info)?; + msg_info.warn(format_args!("container {container} was running."))?; container_stop(engine, &container, msg_info)?; } if state.exists() { - shell::warn(format_args!("container {container} was exited."), msg_info)?; + msg_info.warn(format_args!("container {container} was exited."))?; container_rm(engine, &container, msg_info)?; } if let VolumeId::Discard(ref id) = volume { if volume_exists(engine, id, msg_info)? { - shell::warn(format_args!("temporary volume {id} existed."), msg_info)?; + msg_info.warn(format_args!("temporary volume {id} existed."))?; volume_rm(engine, id, msg_info)?; } } @@ -812,29 +823,26 @@ pub(crate) fn run( if let VolumeId::Discard(ref id) = volume { volume_create(engine, id, msg_info).wrap_err("when creating volume")?; } - let _volume_deletter = DeleteVolume(engine, &volume, msg_info); + let _volume_deletter = DeleteVolume(engine, &volume, msg_info.color_choice, msg_info.verbosity); // 3. create our start container command here let mut docker = subcommand(engine, "run"); docker_userns(&mut docker); docker.args(&["--name", &container]); docker.args(&["-v", &format!("{}:{mount_prefix}", volume.as_ref())]); - docker_envvars(&mut docker, config, target, msg_info)?; + docker_envvars(&mut docker, &options.config, target, msg_info)?; let mut volumes = vec![]; let mount_volumes = docker_mount( &mut docker, - metadata, - &mount_finder, - config, - target, - cwd, + &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, metadata) + docker_seccomp(&mut docker, engine.kind, target, &paths.metadata) .wrap_err("when copying seccomp profile")?; // Prevent `bin` from being mounted inside the Docker container. @@ -853,21 +861,26 @@ pub(crate) fn run( } docker - .arg(&image_name(config, target)?) + .arg(&image_name(&options.config, target)?) // ensure the process never exits until we stop it .args(&["sh", "-c", "sleep infinity"]) .run_and_get_status(msg_info, true)?; - let _container_deletter = DeleteContainer(engine, &container, msg_info); + let _container_deletter = DeleteContainer( + engine, + &container, + msg_info.color_choice, + msg_info.verbosity, + ); // 4. copy all mounted volumes over let copy_cache = env::var("CROSS_REMOTE_COPY_CACHE") .map(|s| bool_from_envvar(&s)) .unwrap_or_default(); - let copy = |src, dst: &PathBuf| { + let copy = |src, dst: &PathBuf, info: &mut MessageInfo| { if copy_cache { - copy_volume_files(engine, &container, src, dst, msg_info) + copy_volume_files(engine, &container, src, dst, info) } else { - copy_volume_files_nocache(engine, &container, src, dst, true, msg_info) + copy_volume_files_nocache(engine, &container, src, dst, true, info) } }; let mount_prefix_path = mount_prefix.as_ref(); @@ -951,7 +964,7 @@ pub(crate) fn run( // only do if we're copying over cached files. let target_dir = mount_prefix_path.join("target"); if copy_cache { - copy(&dirs.target, &target_dir)?; + copy(&dirs.target, &target_dir, msg_info)?; } else { create_volume_dir(engine, &container, &target_dir, msg_info)?; } @@ -971,7 +984,7 @@ pub(crate) fn run( if !rel_dst.is_empty() { create_volume_dir(engine, &container, mount_dst.parent().unwrap(), msg_info)?; } - copy(src, &mount_dst)?; + copy(src, &mount_dst, msg_info)?; } } @@ -1002,12 +1015,12 @@ pub(crate) fn run( final_args.push("--target-dir".to_string()); final_args.push(target_dir_string); } - let mut cmd = cargo_safe_command(uses_xargo); + let mut cmd = cargo_safe_command(options.uses_xargo); cmd.args(final_args); // 5. create symlinks for copied data let mut symlink = vec!["set -e pipefail".to_string()]; - if msg_info.verbose() { + if msg_info.is_verbose() { symlink.push("set -x".to_string()); } symlink.push(format!( @@ -1048,7 +1061,7 @@ symlink_recurse \"${{prefix}}\" // 6. execute our cargo command inside the container let mut docker = subcommand(engine, "exec"); docker_user_id(&mut docker, engine.kind); - docker_cwd(&mut docker, metadata, &dirs, cwd, mount_volumes)?; + docker_cwd(&mut docker, &paths, mount_volumes)?; docker.arg(&container); docker.args(&["sh", "-c", &format!("PATH=$PATH:/rust/bin {:?}", cmd)]); let status = docker diff --git a/src/docker/shared.rs b/src/docker/shared.rs index 9e8cece36..d04b1dab6 100644 --- a/src/docker/shared.rs +++ b/src/docker/shared.rs @@ -12,7 +12,7 @@ use crate::extensions::{CommandExt, SafeCommand}; use crate::file::{self, write_file, PathExt, ToUtf8}; use crate::id; use crate::rustc::{self, VersionMetaExt}; -use crate::shell::{self, MessageInfo, Verbosity}; +use crate::shell::{MessageInfo, Verbosity}; use crate::Target; pub use super::custom::CROSS_CUSTOM_DOCKERFILE_IMAGE_PREFIX; @@ -28,6 +28,181 @@ const DOCKER_IMAGES: &[&str] = &include!(concat!(env!("OUT_DIR"), "/docker-image // to fork the process, and which podman allows by default. pub(crate) const SECCOMP: &str = include_str!("seccomp.json"); +#[derive(Debug)] +pub struct DockerOptions { + pub engine: Engine, + pub target: Target, + pub config: Config, + pub uses_xargo: bool, +} + +impl DockerOptions { + pub fn new(engine: Engine, target: Target, config: Config, uses_xargo: bool) -> DockerOptions { + DockerOptions { + engine, + target, + config, + uses_xargo, + } + } + + pub fn in_docker(&self) -> bool { + self.engine.in_docker + } + + pub fn is_remote(&self) -> bool { + self.engine.is_remote + } + + pub fn needs_custom_image(&self) -> bool { + self.config + .dockerfile(&self.target) + .unwrap_or_default() + .is_some() + || !self + .config + .pre_build(&self.target) + .unwrap_or_default() + .unwrap_or_default() + .is_empty() + } + + pub(crate) fn custom_image_build( + &self, + paths: &DockerPaths, + msg_info: &mut MessageInfo, + ) -> Result { + let mut image = image_name(&self.config, &self.target)?; + + if let Some(path) = self.config.dockerfile(&self.target)? { + let context = self.config.dockerfile_context(&self.target)?; + let name = self.config.image(&self.target)?; + + let build = Dockerfile::File { + path: &path, + context: context.as_deref(), + name: name.as_deref(), + }; + + image = build + .build( + self, + paths, + self.config + .dockerfile_build_args(&self.target)? + .unwrap_or_default(), + msg_info, + ) + .wrap_err("when building dockerfile")?; + } + let pre_build = self.config.pre_build(&self.target)?; + + if let Some(pre_build) = pre_build { + if !pre_build.is_empty() { + let custom = Dockerfile::Custom { + content: format!( + r#" + FROM {image} + ARG CROSS_DEB_ARCH= + ARG CROSS_CMD + RUN eval "${{CROSS_CMD}}""# + ), + }; + custom + .build( + self, + paths, + Some(("CROSS_CMD", pre_build.join("\n"))), + msg_info, + ) + .wrap_err("when pre-building") + .with_note(|| format!("CROSS_CMD={}", pre_build.join("\n")))?; + image = custom.image_name(&self.target, &paths.metadata)?; + } + } + Ok(image) + } + + pub(crate) fn image_name(&self) -> Result { + if let Some(image) = self.config.image(&self.target)? { + return Ok(image); + } + + if !DOCKER_IMAGES.contains(&self.target.triple()) { + eyre::bail!( + "`cross` does not provide a Docker image for target {target}, \ + specify a custom image in `Cross.toml`.", + target = self.target + ); + } + + let version = if include_str!(concat!(env!("OUT_DIR"), "/commit-info.txt")).is_empty() { + env!("CARGO_PKG_VERSION") + } else { + "main" + }; + + Ok(format!( + "{CROSS_IMAGE}/{target}:{version}", + target = self.target + )) + } +} + +#[derive(Debug)] +pub struct DockerPaths { + pub mount_finder: MountFinder, + pub metadata: CargoMetadata, + pub cwd: PathBuf, + pub sysroot: PathBuf, + pub directories: Directories, +} + +impl DockerPaths { + pub fn create( + engine: &Engine, + metadata: CargoMetadata, + cwd: PathBuf, + sysroot: PathBuf, + ) -> Result { + let mount_finder = MountFinder::create(engine)?; + let directories = Directories::create(&mount_finder, &metadata, &cwd, &sysroot)?; + Ok(Self { + mount_finder, + metadata, + cwd, + sysroot, + directories, + }) + } + + pub fn workspace_root(&self) -> &Path { + &self.metadata.workspace_root + } + + pub fn workspace_dependencies(&self) -> impl Iterator { + self.metadata.path_dependencies() + } + + pub fn workspace_from_cwd(&self) -> Result<&Path> { + self.cwd + .strip_prefix(self.workspace_root()) + .map_err(Into::into) + } + + pub fn in_workspace(&self) -> bool { + self.workspace_from_cwd().is_ok() + } + + pub fn mount_cwd(&self) -> &str { + &self.directories.mount_cwd + } + + pub fn host_root(&self) -> &Path { + &self.directories.host_root + } +} + #[derive(Debug)] pub struct Directories { pub cargo: PathBuf, @@ -141,10 +316,9 @@ pub fn get_package_info( engine: &Engine, target: &str, channel: Option<&str>, - docker_in_docker: bool, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> Result<(Target, CargoMetadata, Directories)> { - let target_list = rustc::target_list((msg_info.color_choice, Verbosity::Quiet).into())?; + let target_list = msg_info.as_quiet(rustc::target_list)?; let target = Target::from(target, &target_list); let metadata = cargo_metadata_with_args(None, None, msg_info)? .ok_or(eyre::eyre!("unable to get project metadata"))?; @@ -153,14 +327,14 @@ pub fn get_package_info( let host = host_meta.host(); let sysroot = rustc::get_sysroot(&host, &target, channel, msg_info)?.1; - let mount_finder = MountFinder::create(engine, docker_in_docker)?; + let mount_finder = MountFinder::create(engine)?; let dirs = Directories::create(&mount_finder, &metadata, &cwd, &sysroot)?; Ok((target, metadata, dirs)) } /// Register binfmt interpreters -pub(crate) fn register(engine: &Engine, target: &Target, msg_info: MessageInfo) -> Result<()> { +pub(crate) fn register(engine: &Engine, target: &Target, msg_info: &mut MessageInfo) -> Result<()> { let cmd = if target.is_windows() { // https://www.kernel.org/doc/html/latest/admin-guide/binfmt-misc.html "mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc && \ @@ -255,7 +429,7 @@ pub(crate) fn docker_envvars( docker: &mut Command, config: &Config, target: &Target, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> Result<()> { for ref var in config.env_passthrough(target)?.unwrap_or_default() { validate_env_var(var)?; @@ -289,10 +463,7 @@ pub(crate) fn docker_envvars( if let Ok(value) = env::var("CROSS_CONTAINER_OPTS") { if env::var("DOCKER_OPTS").is_ok() { - shell::warn( - "using both `CROSS_CONTAINER_OPTS` and `DOCKER_OPTS`.", - msg_info, - )?; + msg_info.warn("using both `CROSS_CONTAINER_OPTS` and `DOCKER_OPTS`.")?; } docker.args(&parse_docker_opts(&value)?); } else if let Ok(value) = env::var("DOCKER_OPTS") { @@ -305,44 +476,41 @@ pub(crate) fn docker_envvars( pub(crate) fn docker_cwd( docker: &mut Command, - metadata: &CargoMetadata, - dirs: &Directories, - cwd: &Path, + paths: &DockerPaths, mount_volumes: bool, ) -> Result<()> { if mount_volumes { - docker.args(&["-w", &dirs.mount_cwd]); - } else if dirs.mount_cwd == metadata.workspace_root.to_utf8()? { + docker.args(&["-w", paths.mount_cwd()]); + } else if paths.mount_cwd() == paths.workspace_root().to_utf8()? { docker.args(&["-w", "/project"]); } else { // We do this to avoid clashes with path separators. Windows uses `\` as a path separator on Path::join - let cwd = &cwd; - let working_dir = Path::new("/project").join(cwd.strip_prefix(&metadata.workspace_root)?); + let working_dir = Path::new("/project").join(paths.workspace_from_cwd()?); docker.args(&["-w", &working_dir.as_posix()?]); } Ok(()) } -#[allow(clippy::too_many_arguments)] // TODO: refactor pub(crate) fn docker_mount( docker: &mut Command, - metadata: &CargoMetadata, - mount_finder: &MountFinder, - config: &Config, - target: &Target, - cwd: &Path, + options: &DockerOptions, + paths: &DockerPaths, mount_cb: impl Fn(&mut Command, &Path) -> Result, mut store_cb: impl FnMut((String, String)), ) -> Result { let mut mount_volumes = false; // FIXME(emilgardis 2022-04-07): This is a fallback so that if it's hard for us to do mounting logic, make it simple(r) // Preferably we would not have to do this. - if cwd.strip_prefix(&metadata.workspace_root).is_err() { + if !paths.in_workspace() { mount_volumes = true; } - for ref var in config.env_volumes(target)?.unwrap_or_default() { + for ref var in options + .config + .env_volumes(&options.target)? + .unwrap_or_default() + { let (var, value) = validate_env_var(var)?; let value = match value { Some(v) => Ok(v.to_string()), @@ -351,7 +519,7 @@ pub(crate) fn docker_mount( if let Ok(val) = value { let canonical_val = file::canonicalize(&val)?; - let host_path = mount_finder.find_path(&canonical_val, true)?; + let host_path = paths.mount_finder.find_path(&canonical_val, true)?; let mount_path = mount_cb(docker, host_path.as_ref())?; docker.args(&["-e", &format!("{}={}", host_path, mount_path)]); store_cb((val, mount_path)); @@ -359,9 +527,9 @@ pub(crate) fn docker_mount( } } - for path in metadata.path_dependencies() { + for path in paths.workspace_dependencies() { let canonical_path = file::canonicalize(path)?; - let host_path = mount_finder.find_path(&canonical_path, true)?; + let host_path = paths.mount_finder.find_path(&canonical_path, true)?; let mount_path = mount_cb(docker, host_path.as_ref())?; store_cb((path.to_utf8()?.to_string(), mount_path)); mount_volumes = true; @@ -454,78 +622,6 @@ pub(crate) fn docker_seccomp( Ok(()) } -pub fn needs_custom_image(target: &Target, config: &Config) -> bool { - config.dockerfile(target).unwrap_or_default().is_some() - || !config - .pre_build(target) - .unwrap_or_default() - .unwrap_or_default() - .is_empty() -} - -pub(crate) fn custom_image_build( - target: &Target, - config: &Config, - metadata: &CargoMetadata, - Directories { host_root, .. }: Directories, - engine: &Engine, - msg_info: MessageInfo, -) -> Result { - let mut image = image_name(config, target)?; - - if let Some(path) = config.dockerfile(target)? { - let context = config.dockerfile_context(target)?; - let name = config.image(target)?; - - let build = Dockerfile::File { - path: &path, - context: context.as_deref(), - name: name.as_deref(), - }; - - image = build - .build( - config, - metadata, - engine, - &host_root, - config.dockerfile_build_args(target)?.unwrap_or_default(), - target, - msg_info, - ) - .wrap_err("when building dockerfile")?; - } - let pre_build = config.pre_build(target)?; - - if let Some(pre_build) = pre_build { - if !pre_build.is_empty() { - let custom = Dockerfile::Custom { - content: format!( - r#" - FROM {image} - ARG CROSS_DEB_ARCH= - ARG CROSS_CMD - RUN eval "${{CROSS_CMD}}""# - ), - }; - custom - .build( - config, - metadata, - engine, - &host_root, - Some(("CROSS_CMD", pre_build.join("\n"))), - target, - msg_info, - ) - .wrap_err("when pre-building") - .with_note(|| format!("CROSS_CMD={}", pre_build.join("\n")))?; - image = custom.image_name(target, metadata)?; - } - } - Ok(image) -} - pub(crate) fn image_name(config: &Config, target: &Target) -> Result { if let Some(image) = config.image(target)? { return Ok(image); @@ -556,7 +652,7 @@ fn docker_read_mount_paths(engine: &Engine) -> Result> { command }; - let output = docker.run_and_get_stdout(Verbosity::Quiet.into())?; + 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) } @@ -631,8 +727,8 @@ impl MountFinder { MountFinder { mounts } } - pub fn create(engine: &Engine, docker_in_docker: bool) -> Result { - Ok(if docker_in_docker { + pub fn create(engine: &Engine) -> Result { + Ok(if engine.in_docker { MountFinder::new(docker_read_mount_paths(engine)?) } else { MountFinder::default() @@ -789,11 +885,11 @@ mod tests { } } - fn create_engine(msg_info: MessageInfo) -> Result { - Engine::from_path(get_container_engine()?, Some(false), msg_info) + fn create_engine(msg_info: &mut MessageInfo) -> Result { + Engine::from_path(get_container_engine()?, None, Some(false), msg_info) } - fn cargo_metadata(subdir: bool, msg_info: MessageInfo) -> Result { + fn cargo_metadata(subdir: bool, msg_info: &mut MessageInfo) -> Result { let mut metadata = cargo_metadata_with_args( Some(Path::new(env!("CARGO_MANIFEST_DIR"))), None, @@ -860,7 +956,7 @@ mod tests { fn test_host() -> Result<()> { let vars = unset_env(); let mount_finder = MountFinder::new(vec![]); - let metadata = cargo_metadata(false, MessageInfo::default())?; + let metadata = cargo_metadata(false, &mut MessageInfo::default())?; let directories = get_directories(&metadata, &mount_finder)?; paths_equal(&directories.cargo, &home()?.join(".cargo"))?; paths_equal(&directories.xargo, &home()?.join(".xargo"))?; @@ -880,7 +976,8 @@ mod tests { fn test_docker_in_docker() -> Result<()> { let vars = unset_env(); - let engine = create_engine(MessageInfo::default()); + let mut msg_info = MessageInfo::default(); + let engine = create_engine(&mut msg_info); let hostname = env::var("HOSTNAME"); if engine.is_err() || hostname.is_err() { eprintln!("could not get container engine or no hostname found"); @@ -888,18 +985,23 @@ mod tests { return Ok(()); } let engine = engine.unwrap(); + if !engine.in_docker { + eprintln!("not in docker"); + reset_env(vars); + return Ok(()); + } let hostname = hostname.unwrap(); let output = subcommand(&engine, "inspect") .arg(hostname) - .run_and_get_output(MessageInfo::default())?; + .run_and_get_output(&mut msg_info)?; if !output.status.success() { eprintln!("inspect failed"); reset_env(vars); return Ok(()); } - let mount_finder = MountFinder::create(&engine, true)?; - let metadata = cargo_metadata(true, MessageInfo::default())?; + let mount_finder = MountFinder::create(&engine)?; + let metadata = cargo_metadata(true, &mut msg_info)?; let directories = get_directories(&metadata, &mount_finder)?; let mount_finder = MountFinder::new(docker_read_mount_paths(&engine)?); let mount_path = |p| mount_finder.find_mount_path(p); diff --git a/src/extensions.rs b/src/extensions.rs index 64f194c54..85be3c7f2 100644 --- a/src/extensions.rs +++ b/src/extensions.rs @@ -3,42 +3,45 @@ use std::fmt; use std::process::{Command, ExitStatus, Output}; use crate::errors::*; -use crate::shell::{self, MessageInfo, Verbosity}; +use crate::shell::MessageInfo; pub const STRIPPED_BINS: &[&str] = &[crate::docker::DOCKER, crate::docker::PODMAN, "cargo"]; pub trait CommandExt { - fn fmt_message(&self, msg_info: MessageInfo) -> String; + fn fmt_message(&self, msg_info: &mut MessageInfo) -> String; - fn print(&self, msg_info: MessageInfo) -> Result<()> { - shell::print(&self.fmt_message(msg_info), msg_info) + fn print(&self, msg_info: &mut MessageInfo) -> Result<()> { + let msg = self.fmt_message(msg_info); + msg_info.print(&msg) } - fn info(&self, msg_info: MessageInfo) -> Result<()> { - shell::info(&self.fmt_message(msg_info), msg_info) + fn info(&self, msg_info: &mut MessageInfo) -> Result<()> { + let msg = self.fmt_message(msg_info); + msg_info.info(&msg) } - fn debug(&self, msg_info: MessageInfo) -> Result<()> { - shell::debug(&self.fmt_message(msg_info), msg_info) + fn debug(&self, msg_info: &mut MessageInfo) -> Result<()> { + let msg = self.fmt_message(msg_info); + msg_info.debug(&msg) } fn status_result( &self, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, status: ExitStatus, output: Option<&Output>, ) -> Result<(), CommandError>; - fn run(&mut self, msg_info: MessageInfo, silence_stdout: bool) -> Result<()>; + fn run(&mut self, msg_info: &mut MessageInfo, silence_stdout: bool) -> Result<()>; fn run_and_get_status( &mut self, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, silence_stdout: bool, ) -> Result; - fn run_and_get_stdout(&mut self, msg_info: MessageInfo) -> Result; - fn run_and_get_output(&mut self, msg_info: MessageInfo) -> Result; + fn run_and_get_stdout(&mut self, msg_info: &mut MessageInfo) -> Result; + fn run_and_get_output(&mut self, msg_info: &mut MessageInfo) -> Result; fn command_pretty( &self, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, strip: impl for<'a> Fn(&'a str) -> bool, ) -> String; } @@ -46,12 +49,12 @@ pub trait CommandExt { impl CommandExt for Command { fn command_pretty( &self, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, strip: impl for<'a> Fn(&'a str) -> bool, ) -> String { // a dummy implementor of display to avoid using unwraps - struct C<'c, F>(&'c Command, MessageInfo, F); - impl std::fmt::Display for C<'_, F> + struct C<'c, 'd, F>(&'c Command, &'d mut MessageInfo, F); + impl<'e, 'f, F> std::fmt::Display for C<'e, 'f, F> where F: for<'a> Fn(&'a str) -> bool, { @@ -61,7 +64,7 @@ impl CommandExt for Command { f, "{}", // if verbose, never strip, if not, let user choose - crate::file::pretty_path(cmd.get_program(), move |c| if self.1.verbose() { + crate::file::pretty_path(cmd.get_program(), move |c| if self.1.is_verbose() { false } else { self.2(c) @@ -81,18 +84,25 @@ impl CommandExt for Command { format!("{}", C(self, msg_info, strip)) } - fn fmt_message(&self, msg_info: MessageInfo) -> String { - let verbose = (msg_info.color_choice, Verbosity::Verbose).into(); + fn fmt_message(&self, mut msg_info: &mut MessageInfo) -> String { + let msg_info = &mut msg_info; if let Some(cwd) = self.get_current_dir() { - format!("+ {:?} {}", cwd, self.command_pretty(verbose, |_| false)) + format!( + "+ {:?} {}", + cwd, + msg_info.as_verbose(|info| self.command_pretty(info, |_| false)) + ) } else { - format!("+ {}", self.command_pretty(verbose, |_| false)) + format!( + "+ {}", + msg_info.as_verbose(|info| self.command_pretty(info, |_| false)) + ) } } fn status_result( &self, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, status: ExitStatus, output: Option<&Output>, ) -> Result<(), CommandError> { @@ -110,7 +120,7 @@ impl CommandExt for Command { } /// Runs the command to completion - fn run(&mut self, msg_info: MessageInfo, silence_stdout: bool) -> Result<()> { + fn run(&mut self, msg_info: &mut MessageInfo, silence_stdout: bool) -> Result<()> { let status = self.run_and_get_status(msg_info, silence_stdout)?; self.status_result(msg_info, status, None) .map_err(Into::into) @@ -119,11 +129,11 @@ impl CommandExt for Command { /// Runs the command to completion fn run_and_get_status( &mut self, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, silence_stdout: bool, ) -> Result { self.debug(msg_info)?; - if silence_stdout && !msg_info.verbose() { + if silence_stdout && msg_info.is_verbose() { self.stdout(std::process::Stdio::null()); } self.status() @@ -136,7 +146,7 @@ impl CommandExt for Command { } /// Runs the command to completion and returns its stdout - fn run_and_get_stdout(&mut self, msg_info: MessageInfo) -> Result { + fn run_and_get_stdout(&mut self, msg_info: &mut MessageInfo) -> Result { let out = self.run_and_get_output(msg_info)?; self.status_result(msg_info, out.status, Some(&out)) .map_err(CommandError::to_section_report)?; @@ -148,7 +158,7 @@ impl CommandExt for Command { /// # Notes /// /// This command does not check the status. - fn run_and_get_output(&mut self, msg_info: MessageInfo) -> Result { + fn run_and_get_output(&mut self, msg_info: &mut MessageInfo) -> Result { self.debug(msg_info)?; self.output().map_err(|e| { CommandError::CouldNotExecute { diff --git a/src/lib.rs b/src/lib.rs index e7999df15..1f5c94692 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -338,7 +338,7 @@ impl From for Target { Host::Aarch64UnknownLinuxMusl => Target::new_built_in("aarch64-unknown-linux-musl"), Host::Other(s) => Target::from( s.as_str(), - &rustc::target_list(Verbosity::Quiet.into()).unwrap(), + &rustc::target_list(&mut Verbosity::Quiet.into()).unwrap(), ), } } @@ -360,18 +360,17 @@ impl Serialize for Target { } } -fn warn_on_failure(target: &Target, toolchain: &str, msg_info: MessageInfo) -> Result<()> { +fn warn_on_failure(target: &Target, toolchain: &str, msg_info: &mut MessageInfo) -> Result<()> { let rust_std = format!("rust-std-{target}"); if target.is_builtin() { let component = rustup::check_component(&rust_std, toolchain, msg_info)?; if component.is_not_available() { - shell::warn(format!("rust-std is not available for {target}"), msg_info)?; - shell::note( + msg_info.warn(format!("rust-std is not available for {target}"))?; + msg_info.note( format_args!( r#"you may need to build components for the target via `-Z build-std=` or in your cross configuration specify `target.{target}.build-std` the available components are core, std, alloc, and proc_macro"# ), - msg_info, )?; } } @@ -379,49 +378,47 @@ fn warn_on_failure(target: &Target, toolchain: &str, msg_info: MessageInfo) -> R } pub fn run() -> Result { - let target_list = rustc::target_list(Verbosity::Quiet.into())?; + let target_list = rustc::target_list(&mut Verbosity::Quiet.into())?; let args = cli::parse(&target_list)?; + let mut msg_info = shell::MessageInfo::create(args.verbose, args.quiet, args.color.as_deref())?; if args.version && args.subcommand.is_none() { let commit_info = include_str!(concat!(env!("OUT_DIR"), "/commit-info.txt")); - shell::print( - format!( - concat!("cross ", env!("CARGO_PKG_VERSION"), "{}"), - commit_info - ), - args.msg_info, - )?; + msg_info.print(format!( + concat!("cross ", env!("CARGO_PKG_VERSION"), "{}"), + commit_info + ))?; } let host_version_meta = rustc::version_meta()?; let cwd = std::env::current_dir()?; - if let Some(metadata) = cargo_metadata_with_args(None, Some(&args), args.msg_info)? { + if let Some(metadata) = cargo_metadata_with_args(None, Some(&args), &mut msg_info)? { let host = host_version_meta.host(); - let toml = toml(&metadata, args.msg_info)?; + let toml = toml(&metadata, &mut msg_info)?; let config = Config::new(toml); let target = args .target .or_else(|| config.target(&target_list)) .unwrap_or_else(|| Target::from(host.triple(), &target_list)); - config.confusable_target(&target, args.msg_info)?; + config.confusable_target(&target, &mut msg_info)?; let image_exists = match docker::image_name(&config, &target) { Ok(_) => true, Err(err) => { - shell::warn(err, args.msg_info)?; + msg_info.warn(err)?; false } }; if image_exists && host.is_supported(Some(&target)) { let (toolchain, sysroot) = - rustc::get_sysroot(&host, &target, args.channel.as_deref(), args.msg_info)?; + rustc::get_sysroot(&host, &target, args.channel.as_deref(), &mut msg_info)?; let mut is_nightly = toolchain.contains("nightly"); - let installed_toolchains = rustup::installed_toolchains(args.msg_info)?; + let installed_toolchains = rustup::installed_toolchains(&mut msg_info)?; if !installed_toolchains.into_iter().any(|t| t == toolchain) { - rustup::install_toolchain(&toolchain, args.msg_info)?; + rustup::install_toolchain(&toolchain, &mut msg_info)?; } // TODO: Provide a way to pick/match the toolchain version as a consumer of `cross`. if let Some((rustc_version, channel, rustc_commit)) = rustup::rustc_version(&sysroot)? { @@ -430,7 +427,7 @@ pub fn run() -> Result { &toolchain, &rustc_version, &rustc_commit, - args.msg_info, + &mut msg_info, )?; is_nightly = channel == Channel::Nightly; } @@ -438,10 +435,10 @@ pub fn run() -> Result { let uses_build_std = config.build_std(&target).unwrap_or(false); let uses_xargo = !uses_build_std && config.xargo(&target).unwrap_or(!target.is_builtin()); - if std::env::var("CROSS_CUSTOM_TOOLCHAIN").is_err() { + if !config.custom_toolchain() { // build-std overrides xargo, but only use it if it's a built-in // tool but not an available target or doesn't have rust-std. - let available_targets = rustup::available_targets(&toolchain, args.msg_info)?; + let available_targets = rustup::available_targets(&toolchain, &mut msg_info)?; if !is_nightly && uses_build_std { eyre::bail!( @@ -454,17 +451,17 @@ pub fn run() -> Result { && !available_targets.is_installed(&target) && available_targets.contains(&target) { - rustup::install(&target, &toolchain, args.msg_info)?; - } else if !rustup::component_is_installed("rust-src", &toolchain, args.msg_info)? { - rustup::install_component("rust-src", &toolchain, args.msg_info)?; + rustup::install(&target, &toolchain, &mut msg_info)?; + } else if !rustup::component_is_installed("rust-src", &toolchain, &mut msg_info)? { + rustup::install_component("rust-src", &toolchain, &mut msg_info)?; } if args .subcommand .map(|sc| sc == Subcommand::Clippy) .unwrap_or(false) - && !rustup::component_is_installed("clippy", &toolchain, args.msg_info)? + && !rustup::component_is_installed("clippy", &toolchain, &mut msg_info)? { - rustup::install_component("clippy", &toolchain, args.msg_info)?; + rustup::install_component("clippy", &toolchain, &mut msg_info)?; } } @@ -503,7 +500,7 @@ pub fn run() -> Result { .subcommand .map(|sc| sc == Subcommand::Test) .unwrap_or(false); - if is_test && args.enable_doctests && is_nightly { + if is_test && config.doctests().unwrap_or_default() && is_nightly { filtered_args.push("-Zdoctest-xcompile".to_string()); } if uses_build_std { @@ -516,34 +513,26 @@ pub fn run() -> Result { .map(|sc| sc.needs_docker(is_remote)) .unwrap_or(false); if target.needs_docker() && needs_docker { - let engine = docker::Engine::new(Some(is_remote), args.msg_info)?; + let engine = docker::Engine::new(None, Some(is_remote), &mut msg_info)?; if host_version_meta.needs_interpreter() && needs_interpreter && target.needs_interpreter() && !interpreter::is_registered(&target)? { - docker::register(&engine, &target, args.msg_info)? + docker::register(&engine, &target, &mut msg_info)? } - let status = docker::run( - &engine, - &target, - &filtered_args, - &metadata, - &config, - uses_xargo, - &sysroot, - args.msg_info, - args.docker_in_docker, - &cwd, - ) - .wrap_err("could not run container")?; + let paths = docker::DockerPaths::create(&engine, metadata, cwd, sysroot)?; + let options = + docker::DockerOptions::new(engine, target.clone(), config, uses_xargo); + let status = docker::run(options, paths, &filtered_args, &mut msg_info) + .wrap_err("could not run container")?; let needs_host = args .subcommand .map(|sc| sc.needs_host(is_remote)) .unwrap_or(false); if !status.success() { - warn_on_failure(&target, &toolchain, args.msg_info)?; + warn_on_failure(&target, &toolchain, &mut msg_info)?; } if !(status.success() && needs_host) { return Ok(status); @@ -554,14 +543,14 @@ pub fn run() -> Result { // if we fallback to the host cargo, use the same invocation that was made to cross let argv: Vec = env::args().skip(1).collect(); - shell::note("Falling back to `cargo` on the host.", args.msg_info)?; + msg_info.note("Falling back to `cargo` on the host.")?; match args.subcommand { Some(Subcommand::List) => { // this won't print in order if we have both stdout and stderr. - let out = cargo::run_and_get_output(&argv, args.msg_info)?; + let out = cargo::run_and_get_output(&argv, &mut msg_info)?; let stdout = out.stdout()?; if out.status.success() && cli::is_subcommand_list(&stdout) { - cli::fmt_subcommands(&stdout, args.msg_info)?; + cli::fmt_subcommands(&stdout, &mut msg_info)?; } else { // Not a list subcommand, which can happen with weird edge-cases. print!("{}", stdout); @@ -569,7 +558,7 @@ pub fn run() -> Result { } Ok(out.status) } - _ => cargo::run(&argv, args.msg_info).map_err(Into::into), + _ => cargo::run(&argv, &mut msg_info).map_err(Into::into), } } @@ -586,7 +575,7 @@ pub(crate) fn warn_host_version_mismatch( toolchain: &str, rustc_version: &rustc_version::Version, rustc_commit: &str, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> Result { let host_commit = (&host_version_meta.short_version_string) .splitn(3, ' ') @@ -605,16 +594,15 @@ pub(crate) fn warn_host_version_mismatch( host_version_meta.short_version_string ); if versions.is_lt() || (versions.is_eq() && dates.is_lt()) { - shell::warn(format!("using older {rustc_warning}.\n > Update with `rustup update --force-non-host {toolchain}`"), msg_info)?; + msg_info.warn(format!("using older {rustc_warning}.\n > Update with `rustup update --force-non-host {toolchain}`"))?; return Ok(VersionMatch::OlderTarget); } else if versions.is_gt() || (versions.is_eq() && dates.is_gt()) { - shell::warn( - format!("using newer {rustc_warning}.\n > Update with `rustup update`"), - msg_info, - )?; + msg_info.warn(format!( + "using newer {rustc_warning}.\n > Update with `rustup update`" + ))?; return Ok(VersionMatch::NewerTarget); } else { - shell::warn(format!("using {rustc_warning}."), msg_info)?; + msg_info.warn(format!("using {rustc_warning}."))?; return Ok(VersionMatch::Different); } } @@ -630,7 +618,7 @@ pub(crate) fn warn_host_version_mismatch( /// /// The values from `CROSS_CONFIG` or `Cross.toml` are concatenated with the package /// metadata in `Cargo.toml`, with `Cross.toml` having the highest priority. -fn toml(metadata: &CargoMetadata, msg_info: MessageInfo) -> Result> { +fn toml(metadata: &CargoMetadata, msg_info: &mut MessageInfo) -> Result> { let root = &metadata.workspace_root; let cross_config_path = match env::var("CROSS_CONFIG") { Ok(var) => PathBuf::from(var), @@ -652,7 +640,7 @@ fn toml(metadata: &CargoMetadata, msg_info: MessageInfo) -> Result Command { Command::new(env_program("RUSTC", "rustc")) } -pub fn target_list(msg_info: MessageInfo) -> Result { +pub fn target_list(msg_info: &mut MessageInfo) -> Result { rustc_command() .args(&["--print", "target-list"]) .run_and_get_stdout(msg_info) @@ -90,7 +90,7 @@ pub fn target_list(msg_info: MessageInfo) -> Result { }) } -pub fn sysroot(host: &Host, target: &Target, msg_info: MessageInfo) -> Result { +pub fn sysroot(host: &Host, target: &Target, msg_info: &mut MessageInfo) -> Result { let mut stdout = rustc_command() .args(&["--print", "sysroot"]) .run_and_get_stdout(msg_info)? @@ -109,7 +109,7 @@ pub fn get_sysroot( host: &Host, target: &Target, channel: Option<&str>, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> Result<(String, PathBuf)> { let mut sysroot = sysroot(host, target, msg_info)?; let default_toolchain = sysroot diff --git a/src/rustup.rs b/src/rustup.rs index 4872eb855..f04fd07e2 100644 --- a/src/rustup.rs +++ b/src/rustup.rs @@ -27,8 +27,11 @@ impl AvailableTargets { } } -fn rustup_command(msg_info: MessageInfo) -> Command { +fn rustup_command(msg_info: &mut MessageInfo, no_flags: bool) -> Command { let mut cmd = Command::new("rustup"); + if no_flags { + return cmd; + } match msg_info.verbosity { Verbosity::Quiet => { cmd.arg("--quiet"); @@ -41,8 +44,8 @@ fn rustup_command(msg_info: MessageInfo) -> Command { cmd } -pub fn installed_toolchains(msg_info: MessageInfo) -> Result> { - let out = Command::new("rustup") +pub fn installed_toolchains(msg_info: &mut MessageInfo) -> Result> { + let out = rustup_command(msg_info, true) .args(&["toolchain", "list"]) .run_and_get_stdout(msg_info)?; @@ -57,8 +60,8 @@ pub fn installed_toolchains(msg_info: MessageInfo) -> Result> { .collect()) } -pub fn available_targets(toolchain: &str, msg_info: MessageInfo) -> Result { - let mut cmd = Command::new("rustup"); +pub fn available_targets(toolchain: &str, msg_info: &mut MessageInfo) -> Result { + let mut cmd = rustup_command(msg_info, true); cmd.args(&["target", "list", "--toolchain", toolchain]); let output = cmd .run_and_get_output(msg_info) @@ -97,24 +100,28 @@ pub fn available_targets(toolchain: &str, msg_info: MessageInfo) -> Result Result<()> { - rustup_command(msg_info) +pub fn install_toolchain(toolchain: &str, msg_info: &mut MessageInfo) -> Result<()> { + rustup_command(msg_info, false) .args(&["toolchain", "add", toolchain, "--profile", "minimal"]) .run(msg_info, false) .wrap_err_with(|| format!("couldn't install toolchain `{toolchain}`")) } -pub fn install(target: &Target, toolchain: &str, msg_info: MessageInfo) -> Result<()> { +pub fn install(target: &Target, toolchain: &str, msg_info: &mut MessageInfo) -> Result<()> { let target = target.triple(); - rustup_command(msg_info) + rustup_command(msg_info, false) .args(&["target", "add", target, "--toolchain", toolchain]) .run(msg_info, false) .wrap_err_with(|| format!("couldn't install `std` for {target}")) } -pub fn install_component(component: &str, toolchain: &str, msg_info: MessageInfo) -> Result<()> { - rustup_command(msg_info) +pub fn install_component( + component: &str, + toolchain: &str, + msg_info: &mut MessageInfo, +) -> Result<()> { + rustup_command(msg_info, false) .args(&["component", "add", component, "--toolchain", toolchain]) .run(msg_info, false) .wrap_err_with(|| format!("couldn't install the `{component}` component")) @@ -139,7 +146,7 @@ impl<'a> Component<'a> { pub fn check_component<'a>( component: &'a str, toolchain: &str, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> Result> { Ok(Command::new("rustup") .args(&["component", "list", "--toolchain", toolchain]) @@ -163,7 +170,7 @@ pub fn check_component<'a>( pub fn component_is_installed( component: &str, toolchain: &str, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> Result { Ok(check_component(component, toolchain, msg_info)?.is_installed()) } diff --git a/src/shell.rs b/src/shell.rs index 66e9edb3a..3805b0063 100644 --- a/src/shell.rs +++ b/src/shell.rs @@ -7,6 +7,61 @@ use std::io::{self, Write}; use crate::errors::Result; use owo_colors::{self, OwoColorize}; +// get the prefix for stderr messages +macro_rules! cross_prefix { + ($s:literal) => { + concat!("[cross]", " ", $s) + }; +} + +// generate the color style +macro_rules! write_style { + ($stream:ident, $msg_info:expr, $message:expr $(, $style:ident)* $(,)?) => {{ + match $msg_info.color_choice { + ColorChoice::Always => write!($stream, "{}", $message $(.$style())*), + ColorChoice::Never => write!($stream, "{}", $message), + ColorChoice::Auto => write!( + $stream, + "{}", + $message $(.if_supports_color($stream.owo(), |text| text.$style()))* + ), + }?; + }}; +} + +// low-level interface for printing colorized messages +macro_rules! message { + // write a status message, which has the following format: + // "{status}: {message}" + // both status and ':' are bold. + (@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); + match $message { + Some(message) => writeln!($stream, " {}", message)?, + None => write!($stream, " ")?, + } + + Ok(()) + }}; + + (@status @name $name:ident, $status:expr, $message:expr, $color:ident, $msg_info:expr $(,)?) => {{ + let mut stream = io::$name(); + message!(@status stream, $status, $message, $color, $msg_info) + }}; +} + +// high-level interface to message +macro_rules! status { + (@stderr $status:expr, $message:expr, $color:ident, $msg_info:expr $(,)?) => {{ + message!(@status @name stderr, $status, $message, $color, $msg_info) + }}; + + (@stdout $status:expr, $message:expr, $color:ident, $msg_info:expr $(,)?) => {{ + message!(@status @name stdout, $status, $message, $color, $msg_info) + }}; +} + /// the requested verbosity of output. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Verbosity { @@ -36,13 +91,24 @@ pub enum ColorChoice { } // Should simplify the APIs a lot. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct MessageInfo { pub color_choice: ColorChoice, pub verbosity: Verbosity, + pub stdout_needs_erase: bool, + pub stderr_needs_erase: bool, } impl MessageInfo { + pub const fn new(color_choice: ColorChoice, verbosity: Verbosity) -> MessageInfo { + MessageInfo { + color_choice, + verbosity, + stdout_needs_erase: false, + stderr_needs_erase: false, + } + } + pub fn create(verbose: bool, quiet: bool, color: Option<&str>) -> Result { let color_choice = get_color_choice(color)?; let verbosity = get_verbosity(color_choice, verbose, quiet)?; @@ -50,205 +116,180 @@ impl MessageInfo { Ok(MessageInfo { color_choice, verbosity, + stdout_needs_erase: false, + stderr_needs_erase: false, }) } - pub fn verbose(self) -> bool { + pub fn is_verbose(&self) -> bool { self.verbosity.verbose() } -} -impl Default for MessageInfo { - fn default() -> MessageInfo { - MessageInfo { - color_choice: ColorChoice::Auto, - verbosity: Verbosity::Normal, - } - } -} + fn as_verbosity T>(&mut self, call: C, new: Verbosity) -> T { + let old = self.verbosity; + self.verbosity = new; + let result = call(self); + self.verbosity = old; -impl From for MessageInfo { - fn from(color_choice: ColorChoice) -> MessageInfo { - MessageInfo { - color_choice, - verbosity: Verbosity::Normal, - } + result } -} -impl From for MessageInfo { - fn from(verbosity: Verbosity) -> MessageInfo { - MessageInfo { - color_choice: ColorChoice::Auto, - verbosity, - } + pub fn as_quiet T>(&mut self, call: C) -> T { + self.as_verbosity(call, Verbosity::Quiet) } -} -impl From<(ColorChoice, Verbosity)> for MessageInfo { - fn from(info: (ColorChoice, Verbosity)) -> MessageInfo { - MessageInfo { - color_choice: info.0, - verbosity: info.1, - } + pub fn as_normal T>(&mut self, call: C) -> T { + self.as_verbosity(call, Verbosity::Normal) } -} -// get the prefix for stderr messages -macro_rules! cross_prefix { - ($s:literal) => { - concat!("[cross]", " ", $s) - }; -} + pub fn as_verbose T>(&mut self, call: C) -> T { + self.as_verbosity(call, Verbosity::Verbose) + } -// generate the color style -macro_rules! write_style { - ($stream:ident, $msg_info:expr, $message:expr $(, $style:ident)* $(,)?) => {{ - match $msg_info.color_choice { - ColorChoice::Always => write!($stream, "{}", $message $(.$style())*), - ColorChoice::Never => write!($stream, "{}", $message), - ColorChoice::Auto => write!( - $stream, - "{}", - $message $(.if_supports_color($stream.owo(), |text| text.$style()))* - ), - }?; - }}; -} + fn erase_line(&mut self, stream: &mut S) -> Result<()> { + // this is the Erase in Line sequence + stream.write_all(b"\x1B[K").map_err(Into::into) + } -// low-level interface for printing colorized messages -macro_rules! message { - // write a status message, which has the following format: - // "{status}: {message}" - // both status and ':' are bold. - (@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); - match $message { - Some(message) => writeln!($stream, " {}", message)?, - None => write!($stream, " ")?, + fn stdout_check_erase(&mut self) -> Result<()> { + if self.stdout_needs_erase { + self.erase_line(&mut io::stdout())?; + self.stdout_needs_erase = false; } + Ok(()) + } + fn stderr_check_erase(&mut self) -> Result<()> { + if self.stderr_needs_erase { + self.erase_line(&mut io::stderr())?; + self.stderr_needs_erase = false; + } Ok(()) - }}; + } - (@status @name $name:ident, $status:expr, $message:expr, $color:ident, $msg_info:expr $(,)?) => {{ - let mut stream = io::$name(); - message!(@status stream, $status, $message, $color, $msg_info) - }}; -} + /// prints a red 'error' message and terminates. + pub fn fatal(&mut self, message: T, code: i32) -> ! { + self.error(message).unwrap(); + std::process::exit(code); + } -// high-level interface to message -macro_rules! status { - (@stderr $status:expr, $message:expr, $color:ident, $msg_info:expr $(,)?) => {{ - message!(@status @name stderr, $status, $message, $color, $msg_info) - }}; + /// prints a red 'error' message. + pub fn error(&mut self, message: T) -> Result<()> { + self.stderr_check_erase()?; + status!(@stderr cross_prefix!("error"), Some(&message), red, self) + } - (@stdout $status:expr, $message:expr, $color:ident, $msg_info:expr $(,)?) => {{ - message!(@status @name stdout, $status, $message, $color, $msg_info) - }}; -} + /// prints an amber 'warning' message. + pub fn warn(&mut self, message: T) -> Result<()> { + match self.verbosity { + Verbosity::Quiet => Ok(()), + _ => status!(@stderr + cross_prefix!("warning"), + Some(&message), + yellow, + self, + ), + } + } -/// prints a red 'error' message and terminates. -pub fn fatal(message: T, msg_info: MessageInfo, code: i32) -> ! { - error(message, msg_info).unwrap(); - std::process::exit(code); -} + /// prints a cyan 'note' message. + pub fn note(&mut self, message: T) -> Result<()> { + match self.verbosity { + Verbosity::Quiet => Ok(()), + _ => status!(@stderr cross_prefix!("note"), Some(&message), cyan, self), + } + } -/// prints a red 'error' message. -pub fn error(message: T, msg_info: MessageInfo) -> Result<()> { - status!(@stderr cross_prefix!("error"), Some(&message), red, msg_info) -} + pub fn status(&mut self, message: T) -> Result<()> { + match self.verbosity { + Verbosity::Quiet => Ok(()), + _ => { + eprintln!("{}", message); + Ok(()) + } + } + } -/// prints an amber 'warning' message. -pub fn warn(message: T, msg_info: MessageInfo) -> Result<()> { - match msg_info.verbosity { - Verbosity::Quiet => Ok(()), - _ => status!(@stderr - cross_prefix!("warning"), - Some(&message), - yellow, - msg_info, - ), + /// prints a high-priority message to stdout. + pub fn print(&mut self, message: T) -> Result<()> { + self.stdout_check_erase()?; + println!("{}", message); + Ok(()) } -} -/// prints a cyan 'note' message. -pub fn note(message: T, msg_info: MessageInfo) -> Result<()> { - match msg_info.verbosity { - Verbosity::Quiet => Ok(()), - _ => status!(@stderr cross_prefix!("note"), Some(&message), cyan, msg_info), + /// prints a normal message to stdout. + pub fn info(&mut self, message: T) -> Result<()> { + match self.verbosity { + Verbosity::Quiet => Ok(()), + _ => { + println!("{}", message); + Ok(()) + } + } } -} -pub fn status(message: T, msg_info: MessageInfo) -> Result<()> { - match msg_info.verbosity { - Verbosity::Quiet => Ok(()), - _ => { - eprintln!("{}", message); - Ok(()) + /// prints a debugging message to stdout. + pub fn debug(&mut self, message: T) -> Result<()> { + match self.verbosity { + Verbosity::Quiet | Verbosity::Normal => Ok(()), + _ => { + println!("{}", message); + Ok(()) + } } } -} -/// prints a high-priority message to stdout. -pub fn print(message: T, _: MessageInfo) -> Result<()> { - println!("{}", message); - Ok(()) + pub fn fatal_usage(&mut self, arg: T, code: i32) -> ! { + self.error_usage(arg).unwrap(); + std::process::exit(code); + } + + fn error_usage(&mut self, arg: T) -> Result<()> { + let mut stream = io::stderr(); + write_style!(stream, self, cross_prefix!("error"), bold, red); + write_style!(stream, self, ":", bold); + write_style!(stream, self, " The argument '"); + write_style!(stream, self, arg, yellow); + write_style!(stream, self, "' requires a value but none was supplied\n"); + write_style!(stream, self, "Usage:\n"); + write_style!( + stream, + self, + " cross [+toolchain] [OPTIONS] [SUBCOMMAND]\n" + ); + write_style!(stream, self, "\n"); + write_style!(stream, self, "For more information try "); + write_style!(stream, self, "--help", green); + write_style!(stream, self, "\n"); + + stream.flush()?; + + Ok(()) + } } -/// prints a normal message to stdout. -pub fn info(message: T, msg_info: MessageInfo) -> Result<()> { - match msg_info.verbosity { - Verbosity::Quiet => Ok(()), - _ => { - println!("{}", message); - Ok(()) - } +impl Default for MessageInfo { + fn default() -> MessageInfo { + MessageInfo::new(ColorChoice::Auto, Verbosity::Normal) } } -/// prints a debugging message to stdout. -pub fn debug(message: T, msg_info: MessageInfo) -> Result<()> { - match msg_info.verbosity { - Verbosity::Quiet | Verbosity::Normal => Ok(()), - _ => { - println!("{}", message); - Ok(()) - } +impl From for MessageInfo { + fn from(color_choice: ColorChoice) -> MessageInfo { + MessageInfo::new(color_choice, Verbosity::Normal) } } -pub fn fatal_usage(arg: T, msg_info: MessageInfo, code: i32) -> ! { - error_usage(arg, msg_info).unwrap(); - std::process::exit(code); +impl From for MessageInfo { + fn from(verbosity: Verbosity) -> MessageInfo { + MessageInfo::new(ColorChoice::Auto, verbosity) + } } -fn error_usage(arg: T, msg_info: MessageInfo) -> Result<()> { - let mut stream = io::stderr(); - write_style!(stream, msg_info, cross_prefix!("error"), bold, red); - write_style!(stream, msg_info, ":", bold); - write_style!(stream, msg_info, " The argument '"); - write_style!(stream, msg_info, arg, yellow); - write_style!( - stream, - msg_info, - "' requires a value but none was supplied\n" - ); - write_style!(stream, msg_info, "Usage:\n"); - write_style!( - stream, - msg_info, - " cross [+toolchain] [OPTIONS] [SUBCOMMAND]\n" - ); - write_style!(stream, msg_info, "\n"); - write_style!(stream, msg_info, "For more information try "); - write_style!(stream, msg_info, "--help", green); - write_style!(stream, msg_info, "\n"); - - stream.flush()?; - - Ok(()) +impl From<(ColorChoice, Verbosity)> for MessageInfo { + fn from(info: (ColorChoice, Verbosity)) -> MessageInfo { + MessageInfo::new(info.0, info.1) + } } fn get_color_choice(color: Option<&str>) -> Result { @@ -265,14 +306,7 @@ fn get_color_choice(color: Option<&str>) -> Result { fn get_verbosity(color_choice: ColorChoice, verbose: bool, quiet: bool) -> Result { match (verbose, quiet) { (true, true) => { - let verbosity = Verbosity::Normal; - error( - "cannot set both --verbose and --quiet", - MessageInfo { - color_choice, - verbosity, - }, - )?; + MessageInfo::from(color_choice).error("cannot set both --verbose and --quiet")?; std::process::exit(101); } (true, false) => Ok(Verbosity::Verbose), diff --git a/src/tests.rs b/src/tests.rs index 98f30be44..23067fb1d 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -13,15 +13,12 @@ static WORKSPACE: OnceCell = OnceCell::new(); /// Returns the cargo workspace for the manifest pub fn get_cargo_workspace() -> &'static Path { let manifest_dir = env!("CARGO_MANIFEST_DIR"); + let mut msg_info = crate::shell::Verbosity::Verbose.into(); WORKSPACE.get_or_init(|| { - crate::cargo_metadata_with_args( - Some(manifest_dir.as_ref()), - None, - crate::shell::Verbosity::Verbose.into(), - ) - .unwrap() - .unwrap() - .workspace_root + crate::cargo_metadata_with_args(Some(manifest_dir.as_ref()), None, &mut msg_info) + .unwrap() + .unwrap() + .workspace_root }) } @@ -73,6 +70,7 @@ release: {version} fn compare(expected: VersionMatch, host: &str, targ: &str) { let host_meta = dbg!(make_meta(host)); let target_meta = dbg!(make_rustc_version(targ)); + let mut msg_info = crate::shell::MessageInfo::default(); assert_eq!( expected, warn_host_version_mismatch( @@ -80,7 +78,7 @@ release: {version} "xxxx", &target_meta.0, &target_meta.1, - crate::shell::MessageInfo::default() + &mut msg_info, ) .unwrap(), "\nhost = {}\ntarg = {}", diff --git a/src/tests/toml.rs b/src/tests/toml.rs index e8cd68f9d..5dac79086 100644 --- a/src/tests/toml.rs +++ b/src/tests/toml.rs @@ -59,17 +59,12 @@ fn toml_check() -> Result<(), Box> { dir_entry.path().to_utf8()?, text_line_no(&contents, fence.range().start), ); + let mut msg_info = crate::shell::MessageInfo::default(); assert!(if !cargo { - crate::cross_toml::CrossToml::parse_from_cross( - &fence_content, - crate::shell::MessageInfo::default(), - )? + crate::cross_toml::CrossToml::parse_from_cross(&fence_content, &mut msg_info)? } else { - crate::cross_toml::CrossToml::parse_from_cargo( - &fence_content, - crate::shell::MessageInfo::default(), - )? - .unwrap_or_default() + crate::cross_toml::CrossToml::parse_from_cargo(&fence_content, &mut msg_info)? + .unwrap_or_default() } .1 .is_empty()); diff --git a/xtask/src/build_docker_image.rs b/xtask/src/build_docker_image.rs index 885ea171a..81c52ab87 100644 --- a/xtask/src/build_docker_image.rs +++ b/xtask/src/build_docker_image.rs @@ -3,7 +3,7 @@ use std::path::Path; use crate::util::{cargo_metadata, gha_error, gha_output, gha_print}; use clap::Args; -use cross::shell::{self, MessageInfo}; +use cross::shell::MessageInfo; use cross::{docker, CommandExt, ToUtf8}; #[derive(Args, Debug)] @@ -97,8 +97,6 @@ pub fn build_docker_image( repository, labels, verbose, - quiet, - color, dry_run, force, push, @@ -112,8 +110,8 @@ pub fn build_docker_image( .. }: BuildDockerImage, engine: &docker::Engine, + msg_info: &mut MessageInfo, ) -> cross::Result<()> { - let msg_info = MessageInfo::create(verbose != 0, quiet, color.as_deref())?; let metadata = cargo_metadata(msg_info)?; let version = metadata .get_package("cross") @@ -266,7 +264,7 @@ pub fn build_docker_image( } else { docker_build.print(msg_info)?; if !dry_run { - shell::fatal("refusing to push, use --force to override", msg_info, 1); + msg_info.fatal("refusing to push, use --force to override", 1); } } if gha { diff --git a/xtask/src/ci.rs b/xtask/src/ci.rs index 125fc8777..8582dd5b1 100644 --- a/xtask/src/ci.rs +++ b/xtask/src/ci.rs @@ -85,7 +85,7 @@ pub fn ci(args: CiJob, metadata: CargoMetadata) -> cross::Result<()> { let search = cargo_command() .args(&["search", "--limit", "1"]) .arg("cross") - .run_and_get_stdout(Verbosity::Verbose.into())?; + .run_and_get_stdout(&mut Verbosity::Verbose.into())?; let (cross, rest) = search .split_once(" = ") .ok_or_else(|| eyre::eyre!("cargo search failed"))?; diff --git a/xtask/src/crosstool.rs b/xtask/src/crosstool.rs index b694f5a1b..6c26455cf 100644 --- a/xtask/src/crosstool.rs +++ b/xtask/src/crosstool.rs @@ -200,17 +200,14 @@ CT_LINUX_VERSION=\"{linux_major}.{linux_minor}.{linux_patch}\"" pub fn configure_crosstool( ConfigureCrosstool { - verbose, - quiet, - color, gcc_version, glibc_version, linux_version, mut targets, .. }: ConfigureCrosstool, + msg_info: &mut MessageInfo, ) -> cross::Result<()> { - let msg_info = MessageInfo::create(verbose, quiet, color.as_deref())?; 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 linux_version = linux_version.as_deref().unwrap_or(DEFAULT_LINUX_VERSION); diff --git a/xtask/src/hooks.rs b/xtask/src/hooks.rs index 35d3d96fe..e1af04846 100644 --- a/xtask/src/hooks.rs +++ b/xtask/src/hooks.rs @@ -5,7 +5,7 @@ use std::process::Command; use crate::util::{cargo, get_channel_prefer_nightly}; use clap::Args; -use cross::shell::{self, MessageInfo}; +use cross::shell::MessageInfo; use cross::CommandExt; const CARGO_FLAGS: &[&str] = &["--all-features", "--all-targets", "--workspace"]; @@ -39,14 +39,14 @@ pub struct Test { pub color: Option, } -fn cargo_fmt(msg_info: MessageInfo, channel: Option<&str>) -> cross::Result<()> { +fn cargo_fmt(msg_info: &mut MessageInfo, channel: Option<&str>) -> cross::Result<()> { cargo(channel) .args(&["fmt", "--", "--check"]) .run(msg_info, false) .map_err(Into::into) } -fn cargo_clippy(msg_info: MessageInfo, channel: Option<&str>) -> cross::Result<()> { +fn cargo_clippy(msg_info: &mut MessageInfo, channel: Option<&str>) -> cross::Result<()> { cargo(channel) .arg("clippy") .args(CARGO_FLAGS) @@ -55,7 +55,7 @@ fn cargo_clippy(msg_info: MessageInfo, channel: Option<&str>) -> cross::Result<( .map_err(Into::into) } -fn cargo_test(msg_info: MessageInfo, channel: Option<&str>) -> cross::Result<()> { +fn cargo_test(msg_info: &mut MessageInfo, channel: Option<&str>) -> cross::Result<()> { cargo(channel) .arg("test") .args(CARGO_FLAGS) @@ -67,14 +67,14 @@ fn splitlines(string: String) -> Vec { string.lines().map(|l| l.to_string()).collect() } -fn staged_files(msg_info: MessageInfo) -> cross::Result> { +fn staged_files(msg_info: &mut MessageInfo) -> cross::Result> { Command::new("git") .args(&["diff", "--cached", "--name-only", "--diff-filter=ACM"]) .run_and_get_stdout(msg_info) .map(splitlines) } -fn all_files(msg_info: MessageInfo) -> cross::Result> { +fn all_files(msg_info: &mut MessageInfo) -> cross::Result> { Command::new("git") .arg("ls-files") .run_and_get_stdout(msg_info) @@ -100,7 +100,7 @@ fn is_shell_script(path: impl AsRef) -> cross::Result { } } -fn shellcheck(all: bool, msg_info: MessageInfo) -> cross::Result<()> { +fn shellcheck(all: bool, msg_info: &mut MessageInfo) -> cross::Result<()> { if which::which("shellcheck").is_ok() { let files = match all { true => all_files(msg_info), @@ -123,16 +123,11 @@ fn shellcheck(all: bool, msg_info: MessageInfo) -> cross::Result<()> { } pub fn check( - Check { - verbose, - quiet, - color, - all, - }: Check, + Check { all, .. }: Check, toolchain: Option<&str>, + msg_info: &mut MessageInfo, ) -> cross::Result<()> { - let msg_info = MessageInfo::create(verbose, quiet, color.as_deref())?; - shell::info("Running rustfmt, clippy, and shellcheck checks.", msg_info)?; + msg_info.info("Running rustfmt, clippy, and shellcheck checks.")?; let channel = get_channel_prefer_nightly(msg_info, toolchain)?; cargo_fmt(msg_info, channel)?; @@ -142,16 +137,8 @@ pub fn check( Ok(()) } -pub fn test( - Test { - verbose, - quiet, - color, - }: Test, - toolchain: Option<&str>, -) -> cross::Result<()> { - let msg_info = MessageInfo::create(verbose, quiet, color.as_deref())?; - shell::info("Running cargo fmt and tests", msg_info)?; +pub fn test(toolchain: Option<&str>, msg_info: &mut MessageInfo) -> cross::Result<()> { + msg_info.info("Running cargo fmt and tests")?; let channel = get_channel_prefer_nightly(msg_info, toolchain)?; cargo_fmt(msg_info, channel)?; diff --git a/xtask/src/install_git_hooks.rs b/xtask/src/install_git_hooks.rs index 41c097e6b..6f52418cb 100644 --- a/xtask/src/install_git_hooks.rs +++ b/xtask/src/install_git_hooks.rs @@ -15,14 +15,7 @@ pub struct InstallGitHooks { pub color: Option, } -pub fn install_git_hooks( - InstallGitHooks { - verbose, - quiet, - color, - }: InstallGitHooks, -) -> cross::Result<()> { - let msg_info = MessageInfo::create(verbose, quiet, color.as_deref())?; +pub fn install_git_hooks(msg_info: &mut MessageInfo) -> cross::Result<()> { let root = project_dir(msg_info)?; let git_hooks = root.join(".git").join("hooks"); let cross_dev = root.join("xtask").join("src"); diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 5e45f3a71..d2c68e211 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -65,35 +65,52 @@ fn is_toolchain(toolchain: &str) -> cross::Result { } } +macro_rules! get_engine { + ($args:ident, $msg_info:ident) => {{ + get_container_engine($args.engine.as_deref(), &mut $msg_info) + }}; +} + +macro_rules! get_msg_info { + ($args:ident, $verbose:expr) => {{ + MessageInfo::create($verbose, $args.quiet, $args.color.as_deref()) + }}; +} + pub fn main() -> cross::Result<()> { cross::install_panic_hook()?; let cli = Cli::parse(); match cli.command { Commands::TargetInfo(args) => { - let msg_info = MessageInfo::create(args.verbose, args.quiet, args.color.as_deref())?; - let engine = get_container_engine(args.engine.as_deref(), msg_info)?; - target_info::target_info(args, &engine)?; + let mut msg_info = get_msg_info!(args, args.verbose)?; + let engine = get_engine!(args, msg_info)?; + target_info::target_info(args, &engine, &mut msg_info)?; } Commands::BuildDockerImage(args) => { - let msg_info = - MessageInfo::create(args.verbose != 0, args.quiet, args.color.as_deref())?; - let engine = get_container_engine(args.engine.as_deref(), msg_info)?; - build_docker_image::build_docker_image(args, &engine)?; + let mut msg_info = get_msg_info!(args, args.verbose != 0)?; + let engine = get_engine!(args, msg_info)?; + build_docker_image::build_docker_image(args, &engine, &mut msg_info)?; } Commands::InstallGitHooks(args) => { - install_git_hooks::install_git_hooks(args)?; + let mut msg_info = get_msg_info!(args, args.verbose)?; + install_git_hooks::install_git_hooks(&mut msg_info)?; } Commands::Check(args) => { - hooks::check(args, cli.toolchain.as_deref())?; + let mut msg_info = get_msg_info!(args, args.verbose)?; + hooks::check(args, cli.toolchain.as_deref(), &mut msg_info)?; } Commands::Test(args) => { - hooks::test(args, cli.toolchain.as_deref())?; + let mut msg_info = get_msg_info!(args, args.verbose)?; + hooks::test(cli.toolchain.as_deref(), &mut msg_info)?; } Commands::CiJob(args) => { - let metadata = cargo_metadata(Verbosity::Verbose.into())?; - ci::ci(args, metadata)? + let metadata = cargo_metadata(&mut Verbosity::Verbose.into())?; + ci::ci(args, metadata)?; + } + Commands::ConfigureCrosstool(args) => { + let mut msg_info = get_msg_info!(args, args.verbose)?; + crosstool::configure_crosstool(args, &mut msg_info)?; } - Commands::ConfigureCrosstool(args) => crosstool::configure_crosstool(args)?, } Ok(()) @@ -101,12 +118,12 @@ pub fn main() -> cross::Result<()> { fn get_container_engine( engine: Option<&str>, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> cross::Result { let engine = if let Some(ce) = engine { which::which(ce)? } else { docker::get_container_engine()? }; - docker::Engine::from_path(engine, None, msg_info) + docker::Engine::from_path(engine, None, None, msg_info) } diff --git a/xtask/src/target_info.rs b/xtask/src/target_info.rs index fa1699173..c54393bd9 100644 --- a/xtask/src/target_info.rs +++ b/xtask/src/target_info.rs @@ -41,7 +41,7 @@ fn image_info( target: &crate::ImageTarget, image: &str, tag: &str, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, has_test: bool, ) -> cross::Result<()> { if !tag.starts_with("local") { @@ -60,24 +60,21 @@ fn image_info( command.arg(image); command.args(&["bash", "-c", TARGET_INFO_SCRIPT]); command - .run(msg_info, !msg_info.verbose()) + .run(msg_info, msg_info.is_verbose()) .map_err(Into::into) } pub fn target_info( TargetInfo { mut targets, - verbose, - quiet, - color, registry, repository, tag, .. }: TargetInfo, engine: &docker::Engine, + msg_info: &mut MessageInfo, ) -> cross::Result<()> { - let msg_info = MessageInfo::create(verbose, quiet, color.as_deref())?; let matrix = crate::util::get_matrix(); let test_map: BTreeMap = matrix .iter() diff --git a/xtask/src/util.rs b/xtask/src/util.rs index 312f1aada..b5884660d 100644 --- a/xtask/src/util.rs +++ b/xtask/src/util.rs @@ -90,7 +90,7 @@ pub fn format_repo(registry: &str, repository: &str) -> String { pub fn pull_image( engine: &docker::Engine, image: &str, - msg_info: MessageInfo, + msg_info: &mut MessageInfo, ) -> cross::Result<()> { let mut command = docker::subcommand(engine, "pull"); command.arg(image); @@ -169,7 +169,7 @@ impl std::fmt::Display for ImageTarget { } } -pub fn has_nightly(msg_info: MessageInfo) -> cross::Result { +pub fn has_nightly(msg_info: &mut MessageInfo) -> cross::Result { cross::cargo_command() .arg("+nightly") .run_and_get_output(msg_info) @@ -177,10 +177,10 @@ pub fn has_nightly(msg_info: MessageInfo) -> cross::Result { .map_err(Into::into) } -pub fn get_channel_prefer_nightly( - msg_info: MessageInfo, - toolchain: Option<&str>, -) -> cross::Result> { +pub fn get_channel_prefer_nightly<'a>( + msg_info: &mut MessageInfo, + toolchain: Option<&'a str>, +) -> cross::Result> { Ok(match toolchain { Some(t) => Some(t), None => match has_nightly(msg_info)? { @@ -198,12 +198,12 @@ pub fn cargo(channel: Option<&str>) -> Command { command } -pub fn cargo_metadata(msg_info: MessageInfo) -> cross::Result { +pub fn cargo_metadata(msg_info: &mut MessageInfo) -> cross::Result { cross::cargo_metadata_with_args(Some(Path::new(env!("CARGO_MANIFEST_DIR"))), None, msg_info)? .ok_or_else(|| eyre::eyre!("could not find cross workspace")) } -pub fn project_dir(msg_info: MessageInfo) -> cross::Result { +pub fn project_dir(msg_info: &mut MessageInfo) -> cross::Result { Ok(cargo_metadata(msg_info)?.workspace_root) } @@ -226,7 +226,7 @@ pub fn gha_output(tag: &str, content: &str) { println!("::set-output name={tag}::{}", content) } -pub fn read_dockerfiles(msg_info: MessageInfo) -> cross::Result> { +pub fn read_dockerfiles(msg_info: &mut MessageInfo) -> cross::Result> { let root = project_dir(msg_info)?; let docker = root.join("docker"); let mut dockerfiles = vec![]; @@ -254,7 +254,8 @@ mod tests { fn check_ubuntu_base() -> cross::Result<()> { // count all the entries of FROM for our images let mut counts = BTreeMap::new(); - let dockerfiles = read_dockerfiles(Verbosity::Verbose.into())?; + let mut msg_info = Verbosity::Verbose.into(); + let dockerfiles = read_dockerfiles(&mut msg_info)?; for (path, dockerfile) in dockerfiles { let lines: Vec<&str> = dockerfile.lines().collect(); let index = lines