diff --git a/src/bin/commands/list_targets.rs b/src/bin/commands/list_targets.rs new file mode 100644 index 000000000..36cc04ad9 --- /dev/null +++ b/src/bin/commands/list_targets.rs @@ -0,0 +1,141 @@ +use std::collections::{BTreeMap, BTreeSet}; +use std::str::FromStr; + +use clap::builder::PossibleValue; +use clap::{Args, Subcommand}; +use cross::docker::{self, CROSS_CUSTOM_DOCKERFILE_IMAGE_PREFIX}; +use cross::shell::MessageInfo; +use cross::{CommandExt, TargetList}; + +// known image prefixes, with their registry +// the docker.io registry can also be implicit +const GHCR_IO: &str = docker::CROSS_IMAGE; +const RUST_EMBEDDED: &str = "rustembedded/cross"; +const DOCKER_IO: &str = "docker.io/rustembedded/cross"; +const IMAGE_PREFIXES: &[&str] = &[GHCR_IO, DOCKER_IO, RUST_EMBEDDED]; + +#[derive(Args, Debug)] +pub struct ListTargets { + /// Format version + #[clap(long)] + format_version: Option, + /// Coloring: auto, always, never + #[clap(long)] + pub color: Option, +} + +#[derive(Debug, Clone, Copy, serde::Serialize)] +pub enum FormatVersion { + #[serde(rename = "1")] + One, +} + +#[derive(Debug, thiserror::Error)] +#[error("invalid format version")] +pub struct FormatVersionError; + +impl FromStr for FormatVersion { + type Err = FormatVersionError; + + fn from_str(s: &str) -> Result { + match s { + "1" => Ok(FormatVersion::One), + _ => Err(FormatVersionError), + } + } +} + +#[derive(serde::Serialize)] +pub struct Output { + format_version: FormatVersion, + #[serde(flatten)] + other: serde_json::Value, +} + +impl ListTargets { + pub fn verbose(&self) -> bool { + false + } + + pub fn quiet(&self) -> bool { + false + } + + pub fn color(&self) -> Option<&str> { + self.color.as_deref() + } + + pub fn run(self, msg_info: &mut MessageInfo) -> cross::Result<()> { + let toml = if let Some(metadata) = cross::cargo_metadata_with_args(None, None, msg_info)? { + cross::toml(&metadata, msg_info)? + } else { + None + }; + + let config = cross::config::Config::new(toml); + let version = if let Some(version) = self.format_version { + version + } else { + msg_info.warn( + "please specify `--format-version` flag explicitly to avoid compatibility problems", + )?; + FormatVersion::One + }; + let data = match version { + FormatVersion::One => self.run_v1(&config, msg_info)?, + }; + println!( + "{}", + serde_json::to_string_pretty(&Output { + format_version: version, + other: data, + })? + ); + Ok(()) + } + + pub fn run_v1( + self, + config: &cross::config::Config, + _msg_info: &mut MessageInfo, + ) -> cross::Result { + #[derive(serde::Serialize)] + struct Target { + triplet: String, + platforms: Vec, + } + let mut targets: Vec<_> = cross::docker::PROVIDED_IMAGES + .iter() + .filter_map(|i| { + Some(Target { + triplet: Some(i.name).filter(|i| *i != "zig")?.to_owned(), + platforms: i.platforms.iter().map(ToString::to_string).collect(), + }) + }) + .collect(); + if let Some(toml_targets) = config.targets() { + for (target, config) in toml_targets { + if targets.iter().any(|t| t.triplet == target.triple()) { + continue; + } + targets.push(Target { + triplet: target.triple().to_owned(), + platforms: config + .image + .as_ref() + .map(|i| { + i.toolchain + .iter() + .map(ToString::to_string) + .collect::>() + }) + .filter(|v| !v.is_empty()) + .unwrap_or_else(|| vec![docker::ImagePlatform::DEFAULT.to_string()]), + }) + } + } + Ok(serde_json::json!({ + "targets": targets, + })) + } +} diff --git a/src/bin/commands/mod.rs b/src/bin/commands/mod.rs index 30a1c771a..290a6c392 100644 --- a/src/bin/commands/mod.rs +++ b/src/bin/commands/mod.rs @@ -1,7 +1,9 @@ mod clean; mod containers; mod images; +mod list_targets; pub use self::clean::*; pub use self::containers::*; pub use self::images::*; +pub use self::list_targets::*; diff --git a/src/bin/cross-util.rs b/src/bin/cross-util.rs index cc90a6824..1b9e72b86 100644 --- a/src/bin/cross-util.rs +++ b/src/bin/cross-util.rs @@ -39,6 +39,8 @@ enum Commands { Containers(commands::Containers), /// Clean all cross data in local storage. Clean(commands::Clean), + /// List all cross targets, including those specialized for the current project (if in one). + ListTargets(commands::ListTargets), } fn is_toolchain(toolchain: &str) -> cross::Result { @@ -103,6 +105,10 @@ pub fn main() -> cross::Result<()> { let engine = get_engine!(args, false, msg_info)?; args.run(engine, &mut msg_info)?; } + Commands::ListTargets(args) => { + let mut msg_info = get_msg_info!(args)?; + args.run(&mut msg_info)?; + } } Ok(()) diff --git a/src/config.rs b/src/config.rs index 063c2088f..06e7840e2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -212,6 +212,13 @@ impl Config { } } + /// Grabs all defined targets in toml + pub fn targets( + &self, + ) -> Option> { + self.toml.as_ref().map(|toml| toml.targets.iter()) + } + 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 { diff --git a/src/cross_toml.rs b/src/cross_toml.rs index b079b04ce..274e5dd5b 100644 --- a/src/cross_toml.rs +++ b/src/cross_toml.rs @@ -43,7 +43,7 @@ pub struct CrossTargetConfig { #[serde(default, deserialize_with = "opt_string_bool_or_struct")] zig: Option, #[serde(default, deserialize_with = "opt_string_or_struct")] - image: Option, + pub image: Option, #[serde(default, deserialize_with = "opt_string_or_struct")] dockerfile: Option, #[serde(default, deserialize_with = "opt_string_or_string_vec")] diff --git a/src/docker/image.rs b/src/docker/image.rs index a6f11be7a..b25951f9e 100644 --- a/src/docker/image.rs +++ b/src/docker/image.rs @@ -155,6 +155,12 @@ impl Serialize for ImagePlatform { } } +impl std::fmt::Display for ImagePlatform { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.serialize(f) + } +} + impl std::str::FromStr for ImagePlatform { type Err = eyre::Report; // [os/arch[/variant]=]toolchain diff --git a/src/lib.rs b/src/lib.rs index 798177f42..61f09e387 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -279,13 +279,13 @@ impl Target { triple: TargetTriple::DEFAULT, }; - fn new_built_in(triple: &str) -> Self { + pub fn new_built_in(triple: &str) -> Self { Target::BuiltIn { triple: triple.into(), } } - fn new_custom(triple: &str) -> Self { + pub fn new_custom(triple: &str) -> Self { Target::Custom { triple: triple.into(), } @@ -861,7 +861,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),