Skip to content

Commit

Permalink
Merge #1280
Browse files Browse the repository at this point in the history
1280: Allow to run arbitrary commands in containers using cross-util run ... r=Emilgardis a=lumasepa

Implementation for #1266

Co-authored-by: Sergio Medina <[email protected]>
  • Loading branch information
bors[bot] and lumasepa authored Jun 27, 2023
2 parents ed713d9 + 1aab4cf commit 1511a28
Show file tree
Hide file tree
Showing 10 changed files with 207 additions and 51 deletions.
6 changes: 6 additions & 0 deletions .changes/1280.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"description": "Allow to run arbitrary commands in containers using `cross-util run ...`",
"issues": [1266],
"type": "added",
"breaking": false
}
2 changes: 2 additions & 0 deletions src/bin/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -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::*;
116 changes: 116 additions & 0 deletions src/bin/commands/run.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
use clap::Args as ClapArgs;
use cross::config::Config;
use cross::shell::{MessageInfo, Verbosity};
use cross::{
cargo_metadata_with_args, cli::Args, docker, rustc, setup, toml, CommandVariant, 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<String>,
/// Container engine (such as docker or podman).
#[clap(long)]
pub engine: Option<String>,
/// Target
#[clap(short, long)]
pub target: String,
/// Interactive session
#[clap(short, long, default_value = "false")]
pub interactive: bool,
/// Command to run, will be run in a shell
#[clap(last = true)]
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,
CommandVariant::Shell,
None,
self.interactive,
);

let mut args = vec![String::from("-c")];
args.push(self.command.clone());

docker::run(options, paths, &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()
}
}
7 changes: 7 additions & 0 deletions src/bin/cross-util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
Expand Down Expand Up @@ -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(())
Expand Down
2 changes: 1 addition & 1 deletion src/docker/custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ impl<'a> Dockerfile<'a> {
build_args: impl IntoIterator<Item = (impl AsRef<str>, impl AsRef<str>)>,
msg_info: &mut MessageInfo,
) -> Result<String> {
let uses_zig = options.cargo_variant.uses_zig();
let uses_zig = options.command_variant.uses_zig();
let mut docker_build = options.engine.command();
docker_build.invoke_build_command();
docker_build.disable_scan_suggest();
Expand Down
2 changes: 1 addition & 1 deletion src/docker/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
7 changes: 6 additions & 1 deletion src/docker/local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ 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();
let mut cmd = options.command_variant.safe_command();
cmd.args(args);

let mut docker = engine.subcommand("run");
Expand Down Expand Up @@ -134,6 +134,11 @@ pub(crate) fn run(
if io::Stdin::is_atty() && io::Stdout::is_atty() && io::Stderr::is_atty() {
docker.arg("-t");
}

if options.interactive {
docker.arg("-i");
}

let mut image_name = options.image.name.clone();
if options.needs_custom_image() {
image_name = options
Expand Down
61 changes: 36 additions & 25 deletions src/docker/remote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -895,34 +895,40 @@ 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}"));
let mut cmd = options.command_variant.safe_command();

if !options.command_variant.is_shell() {
// `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()];
Expand Down Expand Up @@ -971,6 +977,11 @@ symlink_recurse \"${{prefix}}\"
docker.add_cwd(&paths)?;
docker.arg(&container_id);
docker.add_build_command(toolchain_dirs, &cmd);

if options.interactive {
docker.arg("-i");
}

bail_container_exited!();
let status = docker
.run_and_get_status(msg_info, false)
Expand Down
19 changes: 11 additions & 8 deletions src/docker/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use crate::file::{self, write_file, PathExt, ToUtf8};
use crate::id;
use crate::rustc::QualifiedToolchain;
use crate::shell::{ColorChoice, MessageInfo, Verbosity};
use crate::{CargoVariant, OutputExt, Target, TargetTriple};
use crate::{CommandVariant, OutputExt, Target, TargetTriple};

use rustc_version::Version as RustcVersion;

Expand All @@ -33,9 +33,10 @@ pub struct DockerOptions {
pub target: Target,
pub config: Config,
pub image: Image,
pub cargo_variant: CargoVariant,
pub command_variant: CommandVariant,
// not all toolchains will provide this
pub rustc_version: Option<RustcVersion>,
pub interactive: bool,
}

impl DockerOptions {
Expand All @@ -44,16 +45,18 @@ impl DockerOptions {
target: Target,
config: Config,
image: Image,
cargo_variant: CargoVariant,
cargo_variant: CommandVariant,
rustc_version: Option<RustcVersion>,
interactive: bool,
) -> DockerOptions {
DockerOptions {
engine,
target,
config,
image,
cargo_variant,
command_variant: cargo_variant,
rustc_version,
interactive,
}
}

Expand Down Expand Up @@ -928,8 +931,8 @@ fn validate_env_var<'a>(
Ok((key, value))
}

impl CargoVariant {
pub(crate) fn safe_command(self) -> SafeCommand {
impl CommandVariant {
pub(crate) fn safe_command(&self) -> SafeCommand {
SafeCommand::new(self.to_str())
}
}
Expand Down Expand Up @@ -1041,7 +1044,7 @@ impl DockerCommandExt for Command {
])
.args(["-e", "CARGO_TARGET_DIR=/target"])
.args(["-e", &cross_runner]);
if options.cargo_variant.uses_zig() {
if options.command_variant.uses_zig() {
// otherwise, zig has a permission error trying to create the cache
self.args(["-e", "XDG_CACHE_HOME=/target/.zig-cache"]);
}
Expand Down Expand Up @@ -1271,7 +1274,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<PossibleImage> {
pub fn get_image(config: &Config, target: &Target, uses_zig: bool) -> Result<PossibleImage> {
if let Some(image) = config.image(target)? {
return Ok(image);
}
Expand Down
Loading

0 comments on commit 1511a28

Please sign in to comment.