Skip to content

Commit

Permalink
Initial design of support for custom cargo subcommands.
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexhuszagh committed Jul 19, 2022
1 parent 08fbafa commit 078eab5
Show file tree
Hide file tree
Showing 12 changed files with 320 additions and 56 deletions.
5 changes: 5 additions & 0 deletions .changes/967.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"description": "add support for custom cargo subcommands.",
"type": "added",
"issues": [704, 716]
}
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ walkdir = { version = "2", optional = true }
tempfile = "3.3.0"
owo-colors = { version = "3.4.0", features = ["supports-colors"] }
semver = "1"
once_cell = "1.12"

[target.'cfg(not(windows))'.dependencies]
nix = { version = "0.24", default-features = false, features = ["user"] }
Expand Down
2 changes: 1 addition & 1 deletion src/bin/cross.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub fn main() -> cross::Result<()> {
let cargo_toml = CargoToml::read()?;
let cargo_config = CargoConfig::new(cargo_toml);
let args = cli::parse(&target_list, &cargo_config)?;
let subcommand = args.subcommand;
let subcommand = args.subcommand.clone();
let mut msg_info = shell::MessageInfo::create(args.verbose, args.quiet, args.color.as_deref())?;
let status = match cross::run(args, target_list, cargo_config, &mut msg_info)? {
Some(status) => status,
Expand Down
128 changes: 118 additions & 10 deletions src/cargo.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
use serde::Deserialize;
use std::collections::{BTreeMap, BTreeSet};
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus};

use crate::cli::Args;
use crate::docker::{self, Image};
use crate::errors::*;
use crate::extensions::CommandExt;
use crate::file;
use crate::shell::{self, MessageInfo};
use crate::Target;
use once_cell::sync::OnceCell;
use serde::Deserialize;

type SubcommandSet = BTreeSet<String>;
type SubcommandMap = BTreeMap<String, SubcommandSet>;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Subcommand {
Build,
Check,
Doc,
Other,
Run,
Rustc,
Test,
Expand All @@ -21,32 +30,131 @@ pub enum Subcommand {
Metadata,
List,
Clean,
// both are custom subcommands, however, `Other` is not a
// registered or known custom subcommand, while `Custom` is.
Other(String),
Custom(String),
}

impl Subcommand {
#[must_use]
pub fn needs_docker(self, is_remote: bool) -> bool {
pub fn needs_docker(&self, is_remote: bool) -> bool {
match self {
Subcommand::Other | Subcommand::List => false,
Subcommand::Other(_) | Subcommand::List => false,
Subcommand::Clean if !is_remote => false,
_ => true,
}
}

#[must_use]
pub fn needs_host(self, is_remote: bool) -> bool {
self == Subcommand::Clean && is_remote
pub fn needs_host(&self, is_remote: bool) -> bool {
matches!(self, Subcommand::Clean) && is_remote
}

#[must_use]
pub fn needs_interpreter(self) -> bool {
pub fn needs_interpreter(&self) -> bool {
matches!(self, Subcommand::Run | Subcommand::Test | Subcommand::Bench)
}

#[must_use]
pub fn needs_target_in_command(self) -> bool {
pub fn needs_target_in_command(&self) -> bool {
!matches!(self, Subcommand::Metadata)
}

pub fn known() -> &'static SubcommandSet {
static INSTANCE: OnceCell<SubcommandSet> = OnceCell::new();
INSTANCE.get_or_init(|| {
[
"asm",
"audit",
"binutils",
"deb",
"deny",
"deps",
"emit",
"expand",
"generate",
"hack",
"llvm-cov",
"outdated",
"release",
"tarpaulin",
"tree",
"udeps",
"when",
]
.into_iter()
.map(ToOwned::to_owned)
.collect()
})
}

fn installed_cell() -> &'static mut OnceCell<SubcommandMap> {
static mut INSTANCE: OnceCell<SubcommandMap> = OnceCell::new();
// SAFETY: safe since OnceCell is thread-safe.
unsafe { &mut INSTANCE }
}

fn json_path() -> Result<PathBuf> {
Ok(file::cargo_dir()?.join("subcommands.json"))
}

// parse the installed subcommands, if present
pub fn installed() -> Result<&'static SubcommandMap> {
Self::installed_cell().get_or_try_init(|| {
let path = Self::json_path()?;
if path.exists() {
let contents = fs::read_to_string(&path)
.wrap_err_with(|| eyre::eyre!("cannot find file {path:?}"))?;
serde_json::from_str(&contents).map_err(Into::into)
} else {
Ok(BTreeMap::new())
}
})
}

// install a subcommand and register the subcommand
pub fn install(
engine: &docker::Engine,
subcommand: &str,
dirs: &docker::Directories,
target: &Target,
image: &Image,
msg_info: &mut MessageInfo,
) -> Result<()> {
if Self::is_installed(target, subcommand)? {
return Ok(());
}
let map = Self::installed_cell()
.get_mut()
.ok_or_else(|| eyre::eyre!("installed subcommands not previously initialized."))?;
docker::install_subcommand(engine, subcommand, dirs, target, image, msg_info)?;
if !map.contains_key(&*target.triple()) {
map.insert(target.triple().to_owned(), SubcommandSet::new());
}
let set = map
.get_mut(&*target.triple())
.ok_or_else(|| eyre::eyre!("subcommand map must contain target triple."))?;
// TODO(ahuszagh) This needs to be specific for the toolchain. Fuck.
set.insert(target.triple().to_owned());

let json = serde_json::to_string(&map)?;
let mut file = file::write_file(&Self::json_path()?, true)?;
file.write_all(json.as_bytes())?;

Ok(())
}

pub fn is_known(subcommand: &str) -> bool {
Self::known().contains(subcommand)
}

pub fn is_installed(target: &Target, subcommand: &str) -> Result<bool> {
Ok(Self::installed()?
.get(target.triple())
.and_then(|s| s.get(subcommand))
.is_some())
}
}

impl<'a> From<&'a str> for Subcommand {
Expand All @@ -63,7 +171,7 @@ impl<'a> From<&'a str> for Subcommand {
"clippy" => Subcommand::Clippy,
"metadata" => Subcommand::Metadata,
"--list" => Subcommand::List,
_ => Subcommand::Other,
_ => Subcommand::Other(s.to_owned()),
}
}
}
Expand Down
23 changes: 19 additions & 4 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::path::{Path, PathBuf};

use crate::cargo::Subcommand;
use crate::cargo_config::CargoConfig;
use crate::errors::Result;
use crate::errors::*;
use crate::file::{absolute_path, PathExt};
use crate::rustc::TargetList;
use crate::shell::{self, MessageInfo};
Expand All @@ -22,6 +22,21 @@ pub struct Args {
pub verbose: bool,
pub quiet: bool,
pub color: Option<String>,
pub subcommand_index: Option<usize>,
}

impl Args {
pub fn subcommand(&self) -> Option<&Subcommand> {
self.subcommand.as_ref()
}

pub fn target(&self) -> Option<&Target> {
self.target.as_ref()
}

pub fn channel(&self) -> Option<&String> {
self.channel.as_ref()
}
}

pub fn is_subcommand_list(stdout: &str) -> bool {
Expand All @@ -36,7 +51,7 @@ pub fn group_subcommands(stdout: &str) -> (Vec<&str>, Vec<&str>) {
let first = line.split_whitespace().next();
if let Some(command) = first {
match Subcommand::from(command) {
Subcommand::Other => host.push(line),
Subcommand::Other(_) => host.push(line),
_ => cross.push(line),
}
}
Expand Down Expand Up @@ -167,7 +182,7 @@ fn parse_subcommand(
);
}
let subcommand = Subcommand::from(arg.as_ref());
if subcommand == Subcommand::Other {
if let Subcommand::Other(_) = &subcommand {
if let Some(alias) = config.alias(&arg)? {
seen.push(arg);
let mut iter = alias.iter().cloned();
Expand All @@ -176,10 +191,10 @@ fn parse_subcommand(
}
return Ok(());
}
return Ok(());
}

// fallthrough
result.subcommand_index = Some(result.all.len());
result.all.push(arg);
result.subcommand = Some(subcommand);

Expand Down
14 changes: 11 additions & 3 deletions src/docker/local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ pub(crate) fn run(
let engine = &options.engine;
let dirs = &paths.directories;

let mut cmd = cargo_safe_command(options.cargo_variant);
cmd.args(args);
let cmd = cargo_safe_command(args);

let mut docker = subcommand(engine, "run");
docker_userns(&mut docker);
Expand Down Expand Up @@ -102,6 +101,15 @@ pub(crate) fn run(
]);
}

// need to mount the subcommand directory and ensure it's on the path
let mut path = format!("\"{}/bin\"", dirs.sysroot_mount_path());
if options.custom_subcommand {
let host = subcommand_directory(&dirs.toolchain, &options.target)?;
let mount = host.as_posix_absolute()?;
docker.args(&["-v", &format!("{}:{}:Z,ro", host.to_utf8()?, mount)]);
path = format!("{path}:\"{mount}\"");
}

// If we're using all config settings, we need to mount all `.cargo` dirs.
// We've already mounted the CWD, so start at the parents.
let mut host_cwd = paths.cwd.parent();
Expand Down Expand Up @@ -140,7 +148,7 @@ pub(crate) fn run(

docker
.arg(&image_name)
.args(&["sh", "-c", &build_command(dirs, &cmd)])
.args(&["sh", "-c", &build_command(&path, &cmd)])
.run_and_get_status(msg_info, false)
.map_err(Into::into)
}
9 changes: 6 additions & 3 deletions src/docker/remote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1152,8 +1152,9 @@ pub(crate) fn run(
final_args.push("--target-dir".to_owned());
final_args.push(target_dir_string);
}
let mut cmd = cargo_safe_command(options.cargo_variant);
cmd.args(final_args);
let cmd = cargo_safe_command(&final_args);

// TODO(ahuszagh) Remote will have to copy it in...

// 5. create symlinks for copied data
let mut symlink = vec!["set -e pipefail".to_owned()];
Expand Down Expand Up @@ -1200,6 +1201,8 @@ symlink_recurse \"${{prefix}}\"
.wrap_err("when creating symlinks to provide consistent host/mount paths")?;

// 6. execute our cargo command inside the container
let path = format!("\"{}/bin\"", dirs.sysroot_mount_path());
// TODO(ahuszagh) Going to need to mount this
let mut docker = subcommand(engine, "exec");
docker_user_id(&mut docker, engine.kind);
docker_envvars(
Expand All @@ -1212,7 +1215,7 @@ symlink_recurse \"${{prefix}}\"
)?;
docker_cwd(&mut docker, &paths, options.cargo_config_behavior)?;
docker.arg(&container);
docker.args(&["sh", "-c", &build_command(dirs, &cmd)]);
docker.args(&["sh", "-c", &build_command(&path, &cmd)]);
bail_container_exited!();
let status = docker
.run_and_get_status(msg_info, false)
Expand Down
Loading

0 comments on commit 078eab5

Please sign in to comment.