diff --git a/Cargo.lock b/Cargo.lock index c588c6d33..69ab05298 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,6 +106,7 @@ dependencies = [ "libc", "nix", "rustc_version", + "serde", "serde_json", "shell-escape", "toml", @@ -242,6 +243,24 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4af2ec4714533fcdf07e886f17025ace8b997b9ce51204ee69b6da831c3da57" +dependencies = [ + "proc-macro2", +] + [[package]] name = "rustc-demangle" version = "0.1.21" @@ -274,6 +293,20 @@ name = "serde" version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "serde_json" @@ -301,6 +334,17 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" +[[package]] +name = "syn" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea297be220d52398dcc07ce15a209fce436d361735ac1db700cab3b6cdfb9f54" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "thread_local" version = "1.1.4" @@ -361,6 +405,12 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + [[package]] name = "valuable" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index d78bc348b..ee5b91ec2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ rustc_version = "0.4" toml = "0.5" which = { version = "4", default_features = false } shell-escape = "0.1" +serde = { version = "1", features = ["derive"] } serde_json = "1" [target.'cfg(not(windows))'.dependencies] diff --git a/src/config.rs b/src/config.rs index 3c5a37fb5..4d5a9abaf 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,4 @@ -use crate::{Result, Target, Toml}; +use crate::{Result, Target, CrossToml}; use crate::errors::*; use std::collections::HashMap; @@ -101,12 +101,12 @@ fn split_to_cloned_by_ws(string: &str) -> Vec { #[derive(Debug)] pub struct Config { - toml: Option, + toml: Option, env: Environment, } impl Config { - pub fn new(toml: Option) -> Self { + pub fn new(toml: Option) -> Self { Config { toml, env: Environment::new(None), @@ -114,14 +114,14 @@ impl Config { } #[cfg(test)] - fn new_with(toml: Option, env: Environment) -> Self { + fn new_with(toml: Option, env: Environment) -> Self { Config { toml, env } } pub fn xargo(&self, target: &Target) -> Result> { let (build_xargo, target_xargo) = self.env.xargo(target)?; let (toml_build_xargo, toml_target_xargo) = if let Some(ref toml) = self.toml { - toml.xargo(target)? + toml.xargo(target) } else { (None, None) }; @@ -145,7 +145,7 @@ impl Config { if let Some(env_value) = env_value { return Ok(Some(env_value)); } - self.toml.as_ref().map_or(Ok(None), |t| t.image(target)) + self.toml.as_ref().map_or(Ok(None), |t| Ok(t.image(target))) } pub fn runner(&self, target: &Target) -> Result> { @@ -153,7 +153,7 @@ impl Config { if let Some(env_value) = env_value { return Ok(Some(env_value)); } - self.toml.as_ref().map_or(Ok(None), |t| t.runner(target)) + self.toml.as_ref().map_or(Ok(None), |t| Ok(t.runner(target))) } pub fn env_passthrough(&self, target: &Target) -> Result> { @@ -179,15 +179,15 @@ impl Config { Ok(collected) } - fn sum_of_env_toml_values<'a>( - toml_getter: impl FnOnce() -> Option>>, + fn sum_of_env_toml_values( + toml_getter: impl FnOnce() -> Option>, env_values: Option>, ) -> Result> { let mut collect = vec![]; if let Some(mut vars) = env_values { collect.append(&mut vars); } else if let Some(toml_values) = toml_getter() { - collect.extend(toml_values?.into_iter().map(|v| v.to_string())); + collect.extend(toml_values.into_iter().map(|v| v.to_string())); } Ok(collect) @@ -277,13 +277,8 @@ mod tests { use super::*; use std::matches; - fn toml(content: &str) -> Result { - Ok(crate::Toml { - table: match content.parse().wrap_err("couldn't parse toml")? { - toml::Value::Table(table) => table, - _ => eyre::bail!("couldn't parse toml as TOML table"), - }, - }) + fn toml(content: &str) -> Result { + Ok(CrossToml::from_str(content).wrap_err("couldn't parse toml")?) } #[test] diff --git a/src/cross_toml.rs b/src/cross_toml.rs new file mode 100644 index 000000000..c2357d101 --- /dev/null +++ b/src/cross_toml.rs @@ -0,0 +1,185 @@ +use std::collections::HashMap; +use serde::Deserialize; +use crate::errors::*; +use crate::Target; + +/// Build environment configuration +#[derive(Debug, Deserialize, PartialEq)] +pub struct CrossBuildEnvConfig { + volumes: Option>, + passthrough: Option>, +} + +/// Build configuration +#[derive(Debug, Deserialize, PartialEq)] +pub struct CrossBuildConfig { + env: Option, + xargo: Option, +} + +/// Target configuration +#[derive(Debug, Deserialize, PartialEq)] +pub struct CrossTargetConfig { + passthrough: Option>, + volumes: Option>, + xargo: Option, + image: Option, + runner: Option, +} + +/// Wrapper struct for `Target` -> `CrossTargetConfig` mappings +/// +/// This is used to circumvent that serde's flatten and field aliases +/// currently don't work together: https://github.com/serde-rs/serde/issues/1504 +#[derive(Debug, Deserialize, PartialEq)] +pub struct CrossTargetMapConfig { + #[serde(flatten)] + inner: HashMap, +} + +/// Cross configuration +#[derive(Debug, Deserialize, PartialEq)] +pub struct CrossToml { + #[serde(rename = "target")] + targets: Option, + build: Option, +} + +impl CrossToml { + /// Parses the `CrossConfig` from a string + pub fn from_str(toml_str: &str) -> Result { + let cfg: CrossToml = toml::from_str(toml_str)?; + Ok(cfg) + } + + /// Returns the `target.{}.image` part of `Cross.toml` + pub fn image(&self, target: &Target) -> Option { + self.get_target(target).and_then(|t| t.image.clone()) + } + + /// Returns the `target.{}.runner` part of `Cross.toml` + pub fn runner(&self, target: &Target) -> Option { + self.get_target(target).and_then(|t| t.runner.clone()) + } + + /// Returns the `build.xargo` or the `target.{}.xargo` part of `Cross.toml` + pub fn xargo(&self, target: &Target) -> (Option, Option) { + let build_xargo = self.build.as_ref().and_then(|b| b.xargo); + let target_xargo = self.get_target(target).and_then(|t| t.xargo); + + (build_xargo, target_xargo) + } + + /// Returns the list of environment variables to pass through for `build`, + pub fn env_passthrough_build(&self) -> Vec { + self.get_build_env() + .and_then(|e| e.passthrough.as_ref()) + .map_or(Vec::new(), |v| v.to_vec()) + } + + /// Returns the list of environment variables to pass through for `target`, + pub fn env_passthrough_target(&self, target: &Target) -> Vec { + self.get_target(target) + .and_then(|t| t.passthrough.as_ref()) + .map_or(Vec::new(), |v| v.to_vec()) + } + + /// Returns the list of environment variables to pass through for `build`, + pub fn env_volumes_build(&self) -> Vec { + self.get_build_env() + .and_then(|e| e.volumes.as_ref()) + .map_or(Vec::new(), |v| v.to_vec()) + } + + /// Returns the list of environment variables to pass through for `target`, + pub fn env_volumes_target(&self, target: &Target) -> Vec { + self.get_target(target) + .and_then(|t| t.volumes.as_ref()) + .map_or(Vec::new(), |v| v.to_vec()) + } + + /// Returns a reference to the `CrossTargetConfig` of a specific `target` + fn get_target(&self, target: &Target) -> Option<&CrossTargetConfig> { + self.targets.as_ref().and_then(|t| t.inner.get(target)) + } + + /// Returns a reference to the `CrossBuildEnvConfig` + fn get_build_env(&self) -> Option<&CrossBuildEnvConfig> { + self.build.as_ref().and_then(|b| b.env.as_ref()) + } +} + +mod tests { + use super::*; + + #[test] + pub fn parse_empty_toml() -> Result<()> { + let cfg = CrossToml { targets: None, build: None }; + let parsed_cfg = CrossToml::from_str("")?; + + assert_eq!(parsed_cfg, cfg); + + Ok(()) + } + + #[test] + pub fn parse_build_toml() -> Result<()> { + let cfg = CrossToml { + targets: None, + build: Some(CrossBuildConfig { + env: Some(CrossBuildEnvConfig { + volumes: Some(vec!["vol1".to_string(), "vol2".to_string()]), + passthrough: Some(vec!["VAR1".to_string(), "VAR2".to_string()]) + }), + xargo: Some(true), + }) + }; + + let test_str = r#" + [build] + xargo = true + + [build.env] + volumes = ["vol1", "vol2"] + passthrough = ["VAR1", "VAR2"] + "#; + let parsed_cfg = CrossToml::from_str(test_str)?; + + assert_eq!(parsed_cfg, cfg); + + Ok(()) + } + + #[test] + pub fn parse_target_toml() -> Result<()> { + let mut target_map = HashMap::new(); + target_map.insert( + Target::BuiltIn { triple: "aarch64-unknown-linux-gnu".to_string() }, + CrossTargetConfig { + passthrough: Some(vec!["VAR1".to_string(), "VAR2".to_string()]), + volumes: Some(vec!["vol1".to_string(), "vol2".to_string()]), + xargo: Some(false), + image: Some("test-image".to_string()), + runner: None, + } + ); + + let cfg = CrossToml { + targets: Some(CrossTargetMapConfig { inner: target_map }), + build: None, + }; + + let test_str = r#" + [target.aarch64-unknown-linux-gnu] + volumes = ["vol1", "vol2"] + passthrough = ["VAR1", "VAR2"] + xargo = false + image = "test-image" + "#; + let parsed_cfg = CrossToml::from_str(test_str)?; + + assert_eq!(parsed_cfg, cfg); + + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs index 347c2e077..1451ccb88 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ #![deny(missing_debug_implementations, rust_2018_idioms)] +mod cross_toml; mod cargo; mod cli; mod config; @@ -17,8 +18,9 @@ use std::path::PathBuf; use std::process::ExitStatus; use config::Config; -use toml::{value::Table, Value}; +use serde::Deserialize; +use self::cross_toml::CrossToml; use self::cargo::{Root, Subcommand}; use self::errors::*; use self::rustc::{TargetList, VersionMetaExt}; @@ -87,6 +89,7 @@ impl Host { } } + /// Returns the `Target` as target triple string fn triple(&self) -> &str { match self { Host::X86_64AppleDarwin => "x86_64-apple-darwin", @@ -116,7 +119,8 @@ impl<'a> From<&'a str> for Host { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)] +#[serde(from = "&str")] pub enum Target { BuiltIn { triple: String }, Custom { triple: String }, @@ -233,6 +237,13 @@ impl From for Target { } } +impl From<&str> for Target { + fn from(target_str: &str) -> Target { + let target_host: Host = target_str.into(); + target_host.into() + } +} + pub fn main() -> Result<()> { install_panic_hook()?; run()?; @@ -376,153 +387,9 @@ fn run() -> Result { cargo::run(&args.all, verbose) } -/// Parsed `Cross.toml` -#[derive(Debug)] -pub struct Toml { - table: Table, -} - -impl Toml { - /// Returns the `target.{}.image` part of `Cross.toml` - pub fn image(&self, target: &Target) -> Result> { - let triple = target.triple(); - - if let Some(value) = self - .table - .get("target") - .and_then(|t| t.get(triple)) - .and_then(|t| t.get("image")) - { - Ok(Some( - value - .as_str() - .ok_or_else(|| eyre::eyre!("target.{triple}.image must be a string"))? - .to_string(), - )) - } else { - Ok(None) - } - } - - /// Returns the `target.{}.runner` part of `Cross.toml` - pub fn runner(&self, target: &Target) -> Result> { - let triple = target.triple(); - - if let Some(value) = self - .table - .get("target") - .and_then(|t| t.get(triple)) - .and_then(|t| t.get("runner")) - { - let value = value - .as_str() - .ok_or_else(|| eyre::eyre!("target.{triple}.runner must be a string"))? - .to_string(); - Ok(Some(value)) - } else { - Ok(None) - } - } - - /// Returns the `build.image` or the `target.{}.xargo` part of `Cross.toml` - pub fn xargo(&self, target: &Target) -> Result<(Option, Option)> { - let triple = target.triple(); - - if let Some(value) = self.table.get("build").and_then(|b| b.get("xargo")) { - return Ok(( - Some( - value - .as_bool() - .ok_or_else(|| eyre::eyre!("build.xargo must be a boolean"))?, - ), - None, - )); - } - - if let Some(value) = self - .table - .get("target") - .and_then(|b| b.get(triple)) - .and_then(|t| t.get("xargo")) - { - Ok(( - None, - Some( - value - .as_bool() - .ok_or_else(|| eyre::eyre!("target.{triple}.xargo must be a boolean"))?, - ), - )) - } else { - Ok((None, None)) - } - } - - /// Returns the list of environment variables to pass through for `build`, - pub fn env_passthrough_build(&self) -> Result> { - self.build_env("passthrough") - } - - /// Returns the list of environment variables to pass through for `target`, - pub fn env_passthrough_target(&self, target: &Target) -> Result> { - self.target_env(target, "passthrough") - } - - /// Returns the list of environment variables to pass through for `build`, - pub fn env_volumes_build(&self) -> Result> { - self.build_env("volumes") - } - - /// Returns the list of environment variables to pass through for `target`, - pub fn env_volumes_target(&self, target: &Target) -> Result> { - self.target_env(target, "volumes") - } - - fn target_env(&self, target: &Target, key: &str) -> Result> { - let triple = target.triple(); - - if let Some(&Value::Array(ref vec)) = self - .table - .get("target") - .and_then(|t| t.get(triple)) - .and_then(|t| t.get("env")) - .and_then(|e| e.get(key)) - { - vec.iter() - .map(|val| { - val.as_str().ok_or_else(|| { - eyre::eyre!("every target.{triple}.env.{key} element must be a string",) - }) - }) - .collect() - } else { - Ok(Vec::new()) - } - } - - fn build_env(&self, key: &str) -> Result> { - if let Some(&Value::Array(ref vec)) = self - .table - .get("build") - .and_then(|b| b.get("env")) - .and_then(|e| e.get(key)) - { - vec.iter() - .map(|val| { - val.as_str().ok_or_else(|| { - eyre::eyre!("every build.env.{key} element must be a string") - }) - }) - .collect() - } else { - Ok(Vec::new()) - } - } -} - /// Parses the `Cross.toml` at the root of the Cargo project or from the /// `CROSS_CONFIG` environment variable (if any exist in either location). -fn toml(root: &Root) -> Result> { +fn toml(root: &Root) -> Result> { let path = match env::var("CROSS_CONFIG") { Ok(var) => PathBuf::from(var), Err(_) => root.path().join("Cross.toml"), @@ -531,10 +398,13 @@ fn toml(root: &Root) -> Result> { if path.exists() { let content = file::read(&path) .wrap_err_with(|| format!("could not read file `{}`", path.display()))?; - parse_toml(&content) - .wrap_err_with(|| format!("failed to parse file `{}` as TOML", path.display())) + + let config = CrossToml::from_str(&content) + .wrap_err_with(|| format!("failed to parse file `{}` as TOML", path.display()))?; + + Ok(Some(config)) } else { - // Let's check if there is a lower case version of the file + // Checks if there is a lowercase version of this file if root.path().join("cross.toml").exists() { eprintln!("There's a file named cross.toml, instead of Cross.toml. You may want to rename it, or it won't be considered."); } @@ -542,11 +412,3 @@ fn toml(root: &Root) -> Result> { } } -fn parse_toml(content: &str) -> Result> { - Ok(Some(crate::Toml { - table: match content.parse()? { - toml::Value::Table(table) => table, - _ => eyre::bail!("couldn't parse as TOML table"), - }, - })) -}