diff --git a/CHANGELOG.md b/CHANGELOG.md index f4e09d48e..4ac2e5807 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - ReleaseDate +- #1266 Allow to run arbitrary commands in containers using `cross-util run --target --command ` + ## [v0.2.5] - 2023-02-04 ## Fixed diff --git a/src/bin/commands/mod.rs b/src/bin/commands/mod.rs index 30a1c771a..2f63ea608 100644 --- a/src/bin/commands/mod.rs +++ b/src/bin/commands/mod.rs @@ -1,7 +1,9 @@ mod clean; mod containers; mod images; +mod run; pub use self::clean::*; pub use self::containers::*; pub use self::images::*; +pub use self::run::*; diff --git a/src/bin/commands/run.rs b/src/bin/commands/run.rs new file mode 100644 index 000000000..108541905 --- /dev/null +++ b/src/bin/commands/run.rs @@ -0,0 +1,115 @@ +use clap::Args as ClapArgs; +use cross::config::Config; +use cross::shell::{MessageInfo, Verbosity}; +use cross::SafeCommand; +use cross::{ + cargo_metadata_with_args, cli::Args, docker, rustc, setup, toml, CargoVariant, CrossSetup, + Target, +}; +use eyre::Context; + +#[derive(ClapArgs, Debug)] +pub struct Run { + /// Provide verbose diagnostic output. + #[clap(short, long)] + pub verbose: bool, + /// Do not print cross log messages. + #[clap(short, long)] + pub quiet: bool, + /// Coloring: auto, always, never + #[clap(long)] + pub color: Option, + /// Container engine (such as docker or podman). + #[clap(long)] + pub engine: Option, + + #[clap(short, long)] + pub target: String, + + #[clap(short, long)] + pub command: String, +} + +impl Run { + pub fn run(&self, engine: docker::Engine, msg_info: &mut MessageInfo) -> cross::Result<()> { + let target_list = rustc::target_list(&mut Verbosity::Quiet.into())?; + let target = Target::from(&self.target, &target_list); + + let cwd = std::env::current_dir()?; + let host_version_meta = rustc::version_meta()?; + + let args = Args { + cargo_args: vec![], + rest_args: vec![], + subcommand: None, + channel: None, + target: Some(target.clone()), + features: vec![], + target_dir: None, + manifest_path: None, + version: false, + verbose: if self.verbose { 1 } else { 0 }, + quiet: self.quiet, + color: self.color.clone(), + }; + + if let Some(metadata) = cargo_metadata_with_args(None, Some(&args), msg_info)? { + let CrossSetup { toolchain, .. } = + match setup(&host_version_meta, &metadata, &args, target_list, msg_info)? { + Some(setup) => setup, + _ => { + eyre::bail!("Error: cannot setup cross environment"); + } + }; + + let toml = toml(&metadata, msg_info)?; + let config = Config::new(toml); + + let image = match docker::get_image(&config, &target, false) { + Ok(i) => i, + Err(err) => { + msg_info.warn(&err)?; + eyre::bail!("Error: {}", &err); + } + }; + + let image = image.to_definite_with(&engine, msg_info); + + let paths = + docker::DockerPaths::create(&engine, metadata, cwd, toolchain, msg_info)?; + let options = docker::DockerOptions::new( + engine, + target, + config, + image, + CargoVariant::None, + None, + ); + + let command = SafeCommand::new(&"sh"); + let mut args = vec![String::from("-c")]; + args.push(self.command.clone()); + + docker::run(options, paths, command, &args, None, msg_info) + .wrap_err("could not run container")?; + } + + 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/cross-util.rs b/src/bin/cross-util.rs index cc90a6824..df5f67a96 100644 --- a/src/bin/cross-util.rs +++ b/src/bin/cross-util.rs @@ -37,6 +37,8 @@ enum Commands { /// Work with cross containers in local storage. #[clap(subcommand)] Containers(commands::Containers), + /// Run in cross container. + Run(commands::Run), /// Clean all cross data in local storage. Clean(commands::Clean), } @@ -103,6 +105,11 @@ pub fn main() -> cross::Result<()> { let engine = get_engine!(args, false, msg_info)?; args.run(engine, &mut msg_info)?; } + Commands::Run(args) => { + let mut msg_info = get_msg_info!(args)?; + let engine = get_engine!(args, false, msg_info)?; + args.run(engine, &mut msg_info)?; + } } Ok(()) diff --git a/src/docker/image.rs b/src/docker/image.rs index 4ff4562bb..ae5429c9c 100644 --- a/src/docker/image.rs +++ b/src/docker/image.rs @@ -27,7 +27,7 @@ pub struct PossibleImage { } impl PossibleImage { - pub(crate) fn to_definite_with(&self, engine: &Engine, msg_info: &mut MessageInfo) -> Image { + pub fn to_definite_with(&self, engine: &Engine, msg_info: &mut MessageInfo) -> Image { if self.toolchain.is_empty() { Image { name: self.name.clone(), diff --git a/src/docker/local.rs b/src/docker/local.rs index 5b8ec195a..f46032ede 100644 --- a/src/docker/local.rs +++ b/src/docker/local.rs @@ -5,7 +5,7 @@ use std::sync::atomic::Ordering; use super::shared::*; use crate::errors::Result; -use crate::extensions::CommandExt; +use crate::extensions::{CommandExt, SafeCommand}; use crate::file::{PathExt, ToUtf8}; use crate::shell::{MessageInfo, Stream}; use eyre::Context; @@ -29,6 +29,7 @@ fn mount( pub(crate) fn run( options: DockerOptions, paths: DockerPaths, + mut cmd: SafeCommand, args: &[String], msg_info: &mut MessageInfo, ) -> Result { @@ -36,7 +37,6 @@ pub(crate) fn run( let toolchain_dirs = paths.directories.toolchain_directories(); let package_dirs = paths.directories.package_directories(); - let mut cmd = options.cargo_variant.safe_command(); cmd.args(args); let mut docker = engine.subcommand("run"); diff --git a/src/docker/mod.rs b/src/docker/mod.rs index 7be9790e4..55c95e858 100644 --- a/src/docker/mod.rs +++ b/src/docker/mod.rs @@ -17,6 +17,7 @@ pub use image::{Architecture, Image, ImagePlatform, Os as ContainerOs, PossibleI use std::process::ExitStatus; use crate::errors::*; +use crate::extensions::SafeCommand; use crate::shell::MessageInfo; #[derive(Debug)] @@ -44,6 +45,7 @@ pub fn image_name(target: &str, sub: Option<&str>, repository: &str, tag: &str) pub fn run( options: DockerOptions, paths: DockerPaths, + command: SafeCommand, args: &[String], subcommand: Option, msg_info: &mut MessageInfo, @@ -55,9 +57,9 @@ pub fn run( ); } if options.is_remote() { - remote::run(options, paths, args, subcommand, msg_info) + remote::run(options, paths, command, args, subcommand, msg_info) .wrap_err("could not complete remote run") } else { - local::run(options, paths, args, msg_info) + local::run(options, paths, command, args, msg_info) } } diff --git a/src/docker/remote.rs b/src/docker/remote.rs index b3a4eed57..8e4f459e5 100644 --- a/src/docker/remote.rs +++ b/src/docker/remote.rs @@ -10,12 +10,12 @@ use super::engine::Engine; use super::shared::*; use crate::config::bool_from_envvar; use crate::errors::Result; -use crate::extensions::CommandExt; +use crate::extensions::{CommandExt, SafeCommand}; use crate::file::{self, PathExt, ToUtf8}; use crate::rustc::{self, QualifiedToolchain, VersionMetaExt}; use crate::shell::{MessageInfo, Stream}; -use crate::temp; use crate::TargetTriple; +use crate::{temp, CargoVariant}; // prevent further commands from running if we handled // a signal earlier, and the volume is exited. @@ -667,6 +667,7 @@ impl QualifiedToolchain { pub(crate) fn run( options: DockerOptions, paths: DockerPaths, + mut cmd: SafeCommand, args: &[String], subcommand: Option, msg_info: &mut MessageInfo, @@ -895,34 +896,38 @@ pub(crate) fn run( } } - // `clean` doesn't handle symlinks: it will just unlink the target - // directory, so we should just substitute it our target directory - // for it. we'll still have the same end behavior - let mut final_args = vec![]; - let mut iter = args.iter().cloned(); - let mut has_target_dir = false; - while let Some(arg) = iter.next() { - if arg == "--target-dir" { - has_target_dir = true; - final_args.push(arg); - if iter.next().is_some() { - final_args.push(target_dir.clone()); - } - } else if arg.starts_with("--target-dir=") { - has_target_dir = true; - if arg.split_once('=').is_some() { - final_args.push(format!("--target-dir={target_dir}")); + if options.cargo_variant != CargoVariant::None { + // `clean` doesn't handle symlinks: it will just unlink the target + // directory, so we should just substitute it our target directory + // for it. we'll still have the same end behavior + let mut final_args = vec![]; + let mut iter = args.iter().cloned(); + let mut has_target_dir = false; + while let Some(arg) = iter.next() { + if arg == "--target-dir" { + has_target_dir = true; + final_args.push(arg); + if iter.next().is_some() { + final_args.push(target_dir.clone()); + } + } else if arg.starts_with("--target-dir=") { + has_target_dir = true; + if arg.split_once('=').is_some() { + final_args.push(format!("--target-dir={target_dir}")); + } + } else { + final_args.push(arg); } - } else { - final_args.push(arg); } + if !has_target_dir && subcommand.map_or(true, |s| s.needs_target_in_command()) { + final_args.push("--target-dir".to_owned()); + final_args.push(target_dir.clone()); + } + + cmd.args(final_args); + } else { + cmd.args(args); } - if !has_target_dir && subcommand.map_or(true, |s| s.needs_target_in_command()) { - final_args.push("--target-dir".to_owned()); - final_args.push(target_dir.clone()); - } - let mut cmd = options.cargo_variant.safe_command(); - cmd.args(final_args); // 5. create symlinks for copied data let mut symlink = vec!["set -e pipefail".to_owned()]; diff --git a/src/docker/shared.rs b/src/docker/shared.rs index 314a0c657..c8dcad439 100644 --- a/src/docker/shared.rs +++ b/src/docker/shared.rs @@ -1271,7 +1271,7 @@ pub fn get_image_name(config: &Config, target: &Target, uses_zig: bool) -> Resul .image_name(CROSS_IMAGE, version)) } -pub(crate) fn get_image(config: &Config, target: &Target, uses_zig: bool) -> Result { +pub fn get_image(config: &Config, target: &Target, uses_zig: bool) -> Result { if let Some(image) = config.image(target)? { return Ok(image); } diff --git a/src/lib.rs b/src/lib.rs index b89fbdd18..8aeb98a75 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,7 +63,7 @@ use self::errors::Context; use self::shell::{MessageInfo, Verbosity}; pub use self::errors::{install_panic_hook, install_termination_hook, Result}; -pub use self::extensions::{CommandExt, OutputExt}; +pub use self::extensions::{CommandExt, OutputExt, SafeCommand}; pub use self::file::{pretty_path, ToUtf8}; pub use self::rustc::{TargetList, VersionMetaExt}; @@ -447,6 +447,7 @@ pub enum CargoVariant { Cargo, Xargo, Zig, + None, } impl CargoVariant { @@ -464,6 +465,7 @@ impl CargoVariant { CargoVariant::Cargo => "cargo", CargoVariant::Xargo => "xargo", CargoVariant::Zig => "cargo-zigbuild", + CargoVariant::None => "", } } @@ -615,9 +617,16 @@ pub fn run( &options, msg_info, )?; - - let status = docker::run(options, paths, &filtered_args, args.subcommand, msg_info) - .wrap_err("could not run container")?; + let cmd = options.cargo_variant.safe_command(); + let status = docker::run( + options, + paths, + cmd, + &filtered_args, + args.subcommand, + msg_info, + ) + .wrap_err("could not run container")?; let needs_host = args.subcommand.map_or(false, |sc| sc.needs_host(is_remote)); if !status.success() { warn_on_failure(&target, &toolchain, msg_info)?; @@ -864,7 +873,7 @@ macro_rules! commit_info { /// /// 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: &mut MessageInfo) -> Result> { +pub 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),