From 9bccb84d3bfc9b1799becc7a27dd8c5d01a4577e Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Mon, 15 Apr 2024 20:32:09 +0200 Subject: [PATCH 1/7] do not force expression into string --- src/recipe/jinja.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/recipe/jinja.rs b/src/recipe/jinja.rs index 664f8705..5fdb3fd0 100644 --- a/src/recipe/jinja.rs +++ b/src/recipe/jinja.rs @@ -64,7 +64,7 @@ impl<'a> Jinja<'a> { if expr.is_empty() { return Ok(Value::UNDEFINED); } - let expr = self.env.compile_expression(&expr)?; + let expr = self.env.compile_expression(&str)?; expr.eval(self.context()) } } From 015036941f79f5c2bae5852a395132f0415fcce1 Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Mon, 15 Apr 2024 20:48:18 +0200 Subject: [PATCH 2/7] hard-code true/false as booleans --- src/selectors.rs | 6 +++++- test-data/recipes/if_else/recipe.yaml | 18 ++++++++++++++++++ test-data/recipes/if_else/variant.yaml | 3 +++ test/end-to-end/test_simple.py | 16 +++++++++++++++- 4 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 test-data/recipes/if_else/recipe.yaml create mode 100644 test-data/recipes/if_else/variant.yaml diff --git a/src/selectors.rs b/src/selectors.rs index 129a8fcc..16497523 100644 --- a/src/selectors.rs +++ b/src/selectors.rs @@ -71,7 +71,11 @@ impl SelectorConfig { ); for (key, v) in self.variant { - context.insert(key, Value::from_safe_string(v)); + match v.to_lowercase().as_str() { + "true" => context.insert(key.clone(), Value::from(true)), + "false" => context.insert(key.clone(), Value::from(false)), + _ => context.insert(key, Value::from_safe_string(v)), + }; } context diff --git a/test-data/recipes/if_else/recipe.yaml b/test-data/recipes/if_else/recipe.yaml new file mode 100644 index 00000000..ccc616c2 --- /dev/null +++ b/test-data/recipes/if_else/recipe.yaml @@ -0,0 +1,18 @@ +package: + name: if_else_test + version: '0.1.0' + +build: + noarch: python + number: 0 + +requirements: + host: + - if: new_pydantic + then: + - pydantic >=2 + else: + - pydantic >=1,<2 + # test inline if else + - ${{ 'test' if new_pydantic }} + - ${{ 'notest' if not new_pydantic }} \ No newline at end of file diff --git a/test-data/recipes/if_else/variant.yaml b/test-data/recipes/if_else/variant.yaml new file mode 100644 index 00000000..22385d05 --- /dev/null +++ b/test-data/recipes/if_else/variant.yaml @@ -0,0 +1,3 @@ +new_pydantic: + - true + - false diff --git a/test/end-to-end/test_simple.py b/test/end-to-end/test_simple.py index 7728ec54..e7ebec8a 100644 --- a/test/end-to-end/test_simple.py +++ b/test/end-to-end/test_simple.py @@ -713,9 +713,9 @@ def test_read_only_removal(rattler_build: RattlerBuild, recipes: Path, tmp_path: assert (pkg / "info/index.json").exists() - def test_noarch_variants(rattler_build: RattlerBuild, recipes: Path, tmp_path: Path): path_to_recipe = recipes / "noarch_variant" + args = rattler_build.build_args( path_to_recipe, tmp_path, @@ -875,3 +875,17 @@ def test_include_files(rattler_build: RattlerBuild, recipes: Path, tmp_path: Pat assert len(pp) == 2 assert pp[0]["_path"] == "include/include_file.c" assert pp[1]["_path"] == "include/include_file.h" + +def test_pydantic_true_false(rattler_build: RattlerBuild, recipes: Path, tmp_path: Path): + path_to_recipe = recipes / "if_else" + output = rattler_build(*args, "--variant-config", recipes / "if_else/variant.yaml", "--render-only") + print(output) + # parse json + render = json.loads(output) + + assert len(render) == 2 + assert render[0]["recipe"]["package"]["name"] == "if_else_test" + assert render[0]["recipe"]["requirements"]["host"] == ['pydantic >=2', 'test'] + + assert render[1]["recipe"]["package"]["name"] == "if_else_test" + assert render[1]["recipe"]["requirements"]["host"] == ['pydantic >=1,<2', 'notest'] From eed336e4cfda323c84f81d7d95a0789d8ea86bd8 Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Mon, 15 Apr 2024 20:50:04 +0200 Subject: [PATCH 3/7] special-case true / false values --- src/recipe/jinja.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/recipe/jinja.rs b/src/recipe/jinja.rs index 5fdb3fd0..9707c7b0 100644 --- a/src/recipe/jinja.rs +++ b/src/recipe/jinja.rs @@ -64,7 +64,7 @@ impl<'a> Jinja<'a> { if expr.is_empty() { return Ok(Value::UNDEFINED); } - let expr = self.env.compile_expression(&str)?; + let expr = self.env.compile_expression(str)?; expr.eval(self.context()) } } From 5795a2d126ff92e4dc46803d53d2f95c1b535dad Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Tue, 4 Jun 2024 16:56:22 +0200 Subject: [PATCH 4/7] also special case integers --- src/selectors.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/selectors.rs b/src/selectors.rs index 16497523..c3861330 100644 --- a/src/selectors.rs +++ b/src/selectors.rs @@ -71,10 +71,17 @@ impl SelectorConfig { ); for (key, v) in self.variant { - match v.to_lowercase().as_str() { - "true" => context.insert(key.clone(), Value::from(true)), - "false" => context.insert(key.clone(), Value::from(false)), - _ => context.insert(key, Value::from_safe_string(v)), + let v_lower = v.to_lowercase(); + match v_lower.as_str() { + "true" | "yes" => context.insert(key.clone(), Value::from(true)), + "false" | "no" => context.insert(key.clone(), Value::from(false)), + _ => { + if let Ok(v_num) = v.parse::() { + context.insert(key.clone(), Value::from(v_num)) + } else { + context.insert(key.clone(), Value::from_safe_string(v)) + } + } }; } From 29db58f956f01daa3c11b41b458ae663b7b5f24f Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Tue, 4 Jun 2024 17:06:59 +0200 Subject: [PATCH 5/7] format --- test-data/recipes/if_else/recipe.yaml | 2 +- test/end-to-end/test_simple.py | 20 ++++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/test-data/recipes/if_else/recipe.yaml b/test-data/recipes/if_else/recipe.yaml index ccc616c2..e3f0c4ad 100644 --- a/test-data/recipes/if_else/recipe.yaml +++ b/test-data/recipes/if_else/recipe.yaml @@ -15,4 +15,4 @@ requirements: - pydantic >=1,<2 # test inline if else - ${{ 'test' if new_pydantic }} - - ${{ 'notest' if not new_pydantic }} \ No newline at end of file + - ${{ 'notest' if not new_pydantic }} diff --git a/test/end-to-end/test_simple.py b/test/end-to-end/test_simple.py index e7ebec8a..d9fc5c7f 100644 --- a/test/end-to-end/test_simple.py +++ b/test/end-to-end/test_simple.py @@ -713,6 +713,7 @@ def test_read_only_removal(rattler_build: RattlerBuild, recipes: Path, tmp_path: assert (pkg / "info/index.json").exists() + def test_noarch_variants(rattler_build: RattlerBuild, recipes: Path, tmp_path: Path): path_to_recipe = recipes / "noarch_variant" @@ -876,16 +877,27 @@ def test_include_files(rattler_build: RattlerBuild, recipes: Path, tmp_path: Pat assert pp[0]["_path"] == "include/include_file.c" assert pp[1]["_path"] == "include/include_file.h" -def test_pydantic_true_false(rattler_build: RattlerBuild, recipes: Path, tmp_path: Path): + +def test_pydantic_true_false( + rattler_build: RattlerBuild, recipes: Path, tmp_path: Path +): path_to_recipe = recipes / "if_else" - output = rattler_build(*args, "--variant-config", recipes / "if_else/variant.yaml", "--render-only") + + args = rattler_build.build_args( + path_to_recipe, + tmp_path, + ) + + output = rattler_build( + *args, "--variant-config", recipes / "if_else/variant.yaml", "--render-only" + ) print(output) # parse json render = json.loads(output) assert len(render) == 2 assert render[0]["recipe"]["package"]["name"] == "if_else_test" - assert render[0]["recipe"]["requirements"]["host"] == ['pydantic >=2', 'test'] + assert render[0]["recipe"]["requirements"]["host"] == ["pydantic >=2", "test"] assert render[1]["recipe"]["package"]["name"] == "if_else_test" - assert render[1]["recipe"]["requirements"]["host"] == ['pydantic >=1,<2', 'notest'] + assert render[1]["recipe"]["requirements"]["host"] == ["pydantic >=1,<2", "notest"] From 73ab4536a8399c3d9f5e052a5b1378c06084bddf Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Tue, 4 Jun 2024 18:23:38 +0200 Subject: [PATCH 6/7] introduce `VariantValue` --- src/env_vars.rs | 7 +- src/hash.rs | 42 ++++--- src/lib.rs | 6 +- src/metadata.rs | 7 +- src/post_process/checks.rs | 2 +- src/recipe/jinja.rs | 25 +++-- src/render/resolved_dependencies.rs | 7 +- src/selectors.rs | 11 +- ...nt_config__tests__flatten_selectors-2.snap | 2 +- ...iant_config__tests__flatten_selectors.snap | 6 +- src/utils.rs | 59 ++++++++-- src/variant_config.rs | 106 +++++++++++++----- 12 files changed, 192 insertions(+), 88 deletions(-) diff --git a/src/env_vars.rs b/src/env_vars.rs index af840b0f..c12b44c1 100644 --- a/src/env_vars.rs +++ b/src/env_vars.rs @@ -80,8 +80,9 @@ pub fn python_vars(output: &Output) -> HashMap { } if let Some(npy_version) = output.variant().get("numpy") { - let np_ver = npy_version.split('.').collect::>(); - let np_ver = format!("{}.{}", np_ver[0], np_ver[1]); + let np_ver = npy_version.to_string(); + let parts = np_ver.split('.').collect::>(); + let np_ver = format!("{}.{}", parts[0], parts[1]); result.insert("NPY_VER".to_string(), np_ver); } @@ -101,7 +102,7 @@ pub fn r_vars(output: &Output) -> HashMap { let mut result = HashMap::::new(); if let Some(r_ver) = output.variant().get("r-base") { - result.insert("R_VER".to_string(), r_ver.clone()); + result.insert("R_VER".to_string(), r_ver.to_string()); let r_bin = if output.host_platform().is_windows() { output.prefix().join("Scripts/R.exe") diff --git a/src/hash.rs b/src/hash.rs index 44f7cce1..f9a31800 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -6,6 +6,8 @@ use serde::{Deserialize, Serialize}; use serde_json::ser::Formatter; use sha1::{Digest, Sha1}; +use crate::utils::VariantValue; + /// A hash will be added if all of these are true for any dependency: /// /// 1. package is an explicit dependency in build, host, or run deps @@ -94,12 +96,17 @@ pub struct HashInput(String); impl HashInput { /// Create a new hash input from a variant - pub fn from_variant(variant: &BTreeMap) -> Self { + pub fn from_variant(variant: &BTreeMap) -> Self { + // TODO maybe re-asses what `conda-build` does here - keep numbers or format as string? + let input: BTreeMap = variant + .iter() + .map(|(k, v)| (k.clone(), v.to_string())) + .collect(); let mut buf = Vec::new(); let mut ser = serde_json::Serializer::with_formatter(&mut buf, PythonFormatter {}); // BTree has sorted keys, which is important for hashing - variant + input .serialize(&mut ser) .expect("Failed to serialize input"); @@ -124,7 +131,7 @@ impl std::fmt::Display for HashInfo { } impl HashInfo { - fn hash_prefix(variant: &BTreeMap, noarch: &NoArchType) -> String { + fn hash_prefix(variant: &BTreeMap, noarch: &NoArchType) -> String { if noarch.is_python() { return "py".to_string(); } @@ -148,7 +155,7 @@ impl HashInfo { map.insert( prefix.to_string(), - short_version_from_spec(version_spec, version_length), + short_version_from_spec(&version_spec.to_string(), version_length), ); } @@ -174,7 +181,7 @@ impl HashInfo { } /// Compute the build string for a given variant - pub fn from_variant(variant: &BTreeMap, noarch: &NoArchType) -> Self { + pub fn from_variant(variant: &BTreeMap, noarch: &NoArchType) -> Self { Self { hash: Self::hash_from_input(&HashInput::from_variant(variant)), prefix: Self::hash_prefix(variant, noarch), @@ -188,25 +195,24 @@ mod tests { use std::collections::BTreeMap; #[test] - fn test_hash() { + fn test_hash() -> Result<(), Box> { let mut input = BTreeMap::new(); - input.insert("rust_compiler".to_string(), "rust".to_string()); - input.insert("build_platform".to_string(), "osx-64".to_string()); - input.insert("c_compiler".to_string(), "clang".to_string()); - input.insert("target_platform".to_string(), "osx-arm64".to_string()); - input.insert("openssl".to_string(), "3".to_string()); + input.insert("rust_compiler".to_string(), "rust".parse()?); + input.insert("build_platform".to_string(), "osx-64".parse()?); + input.insert("c_compiler".to_string(), "clang".parse()?); + input.insert("target_platform".to_string(), "osx-arm64".parse()?); + input.insert("openssl".to_string(), "3".parse()?); input.insert( "CONDA_BUILD_SYSROOT".to_string(), - "/Applications/Xcode_13.2.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk".to_string(), - ); - input.insert( - "channel_targets".to_string(), - "conda-forge main".to_string(), + "/Applications/Xcode_13.2.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk".parse()?, ); - input.insert("python".to_string(), "3.11.* *_cpython".to_string()); - input.insert("c_compiler_version".to_string(), "14".to_string()); + input.insert("channel_targets".to_string(), "conda-forge main".parse()?); + input.insert("python".to_string(), "3.11.* *_cpython".parse()?); + input.insert("c_compiler_version".to_string(), "14".parse()?); let build_string_from_output = HashInfo::from_variant(&input, &NoArchType::none()); assert_eq!(build_string_from_output.to_string(), "py311h507f6e9"); + + Ok(()) } } diff --git a/src/lib.rs b/src/lib.rs index 30c68982..cb31fe5c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -200,9 +200,9 @@ pub async fn get_build_output( .load_preset(comfy_table::presets::UTF8_FULL_CONDENSED) .apply_modifier(comfy_table::modifiers::UTF8_ROUND_CORNERS) .set_header(vec!["Variant", "Version"]); - for (key, value) in discovered_output.used_vars.iter() { - table.add_row(vec![key, value]); - } + // for (key, value) in discovered_output.used_vars.iter() { + // table.add_row(vec![key, value]); + // } tracing::info!("\n{}\n", table); } drop(enter); diff --git a/src/metadata.rs b/src/metadata.rs index f49b291f..50b6f742 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -29,6 +29,7 @@ use crate::{ recipe::parser::{Recipe, Source}, render::resolved_dependencies::FinalizedDependencies, system_tools::SystemTools, + utils::VariantValue, }; /// A Git revision #[derive(Serialize, Deserialize, Debug, Clone)] @@ -213,7 +214,7 @@ pub struct BuildConfiguration { /// The build platform (the platform that the build is running on) pub build_platform: Platform, /// The selected variant for this build - pub variant: BTreeMap, + pub variant: BTreeMap, /// THe computed hash of the variant pub hash: HashInfo, /// The directories for the build (work, source, build, host, ...) @@ -361,7 +362,7 @@ impl Output { } /// Shorthand to retrieve the variant configuration for this output - pub fn variant(&self) -> &BTreeMap { + pub fn variant(&self) -> &BTreeMap { &self.build_configuration.variant } @@ -550,7 +551,7 @@ impl Output { table.set_header(vec!["Key", "Value"]); } self.build_configuration.variant.iter().for_each(|(k, v)| { - table.add_row(vec![k, v]); + table.add_row(vec![k, v.to_string().as_str()]); }); writeln!(f, "{}\n", table)?; diff --git a/src/post_process/checks.rs b/src/post_process/checks.rs index 2c551343..841a8788 100644 --- a/src/post_process/checks.rs +++ b/src/post_process/checks.rs @@ -158,7 +158,7 @@ fn find_system_libs(output: &Output) -> Result { .variant .get("CONDA_BUILD_SYSROOT") { - system_libs.add(Glob::new(&format!("{}/**/*", sysroot))?); + system_libs.add(Glob::new(&format!("{}/**/*", sysroot.to_string()))?); } else { for v in default_sysroot { system_libs.add(Glob::new(v)?); diff --git a/src/recipe/jinja.rs b/src/recipe/jinja.rs index 9707c7b0..9ffdcc46 100644 --- a/src/recipe/jinja.rs +++ b/src/recipe/jinja.rs @@ -11,6 +11,7 @@ use rattler_conda_types::{PackageName, ParseStrictness, Platform, Version, Versi use crate::render::pin::PinArgs; pub use crate::render::pin::{Pin, PinExpression}; pub use crate::selectors::SelectorConfig; +use crate::utils::VariantValue; use super::parser::{Dependency, PinCompatible, PinSubpackage}; @@ -192,7 +193,7 @@ fn default_compiler(platform: Platform, language: &str) -> Option { fn compiler_stdlib_eval( lang: &str, platform: Platform, - variant: &Arc>, + variant: &Arc>, prefix: &str, ) -> Result { let variant_key = format!("{lang}_{prefix}"); @@ -206,12 +207,12 @@ fn compiler_stdlib_eval( let res = if let Some(name) = variant .get(&variant_key) - .cloned() + .map(|v| v.to_string()) .or_else(|| default_fn(platform, lang)) { // check if we also have a compiler version if let Some(version) = variant.get(&variant_key_version) { - Some(format!("{name}_{platform} {version}")) + Some(format!("{name}_{platform} {}", version.to_string())) } else { Some(format!("{name}_{platform}")) } @@ -381,10 +382,10 @@ fn set_jinja(config: &SelectorConfig) -> minijinja::Environment<'static> { let arch_str = arch.map(|arch| format!("{arch}")); let cdt_arch = if let Some(s) = variant_clone.get("cdt_arch") { - s.as_str() + s.to_string() } else { match arch { - Some(Arch::X86) => "i686", + Some(Arch::X86) => "i686".to_string(), _ => arch_str .as_ref() .ok_or_else(|| { @@ -393,16 +394,18 @@ fn set_jinja(config: &SelectorConfig) -> minijinja::Environment<'static> { "No target or build architecture provided.", ) })? - .as_str(), + .to_string(), } }; let cdt_name = variant_clone.get("cdt_name").map_or_else( || match arch { - Some(Arch::S390X | Arch::Aarch64 | Arch::Ppc64le | Arch::Ppc64) => "cos7", - _ => "cos6", + Some(Arch::S390X | Arch::Aarch64 | Arch::Ppc64le | Arch::Ppc64) => { + "cos7".to_string() + } + _ => "cos6".to_string(), }, - String::as_str, + ToString::to_string, ); let res = package_name.split_once(' ').map_or_else( @@ -1085,7 +1088,7 @@ mod tests { #[test] #[rustfmt::skip] fn eval_match() { - let variant = BTreeMap::from_iter(vec![("python".to_string(), "3.7".to_string())]); + let variant = BTreeMap::from_iter(vec![("python".to_string(), "3.7".parse().unwrap())]); let options = SelectorConfig { target_platform: Platform::Linux64, @@ -1107,7 +1110,7 @@ mod tests { #[test] #[rustfmt::skip] fn eval_complicated_match() { - let variant = BTreeMap::from_iter(vec![("python".to_string(), "3.7.* *_cpython".to_string())]); + let variant = BTreeMap::from_iter(vec![("python".to_string(), "3.7.* *_cpython".parse().unwrap())]); let options = SelectorConfig { target_platform: Platform::Linux64, diff --git a/src/render/resolved_dependencies.rs b/src/render/resolved_dependencies.rs index 9941e0be..9a8c0396 100644 --- a/src/render/resolved_dependencies.rs +++ b/src/render/resolved_dependencies.rs @@ -9,6 +9,7 @@ use std::{ use crate::{ metadata::{BuildConfiguration, Output}, tool_configuration, + utils::VariantValue, }; use indicatif::HumanBytes; use rattler::package_cache::CacheKey; @@ -432,14 +433,12 @@ pub fn apply_variant( if let Some(version) = variant.get(name.as_normalized()) { // if the variant starts with an alphanumeric character, // we have to add a '=' to the version spec - let mut spec = version.clone(); + let mut spec = version.to_string(); // check if all characters are alphanumeric or ., in that case add // a '=' to get "startswith" behavior if spec.chars().all(|c| c.is_alphanumeric() || c == '.') { - spec = format!("={}", version); - } else { - spec = version.clone(); + spec = format!("={}", spec); } // we split at whitespace to separate into version and build diff --git a/src/selectors.rs b/src/selectors.rs index c3861330..7a7ee006 100644 --- a/src/selectors.rs +++ b/src/selectors.rs @@ -4,6 +4,7 @@ use std::collections::BTreeMap; use crate::{hash::HashInfo, recipe::jinja::Env, recipe::jinja::Git}; +use crate::utils::VariantValue; use minijinja::value::Value; use rattler_conda_types::Platform; @@ -19,7 +20,7 @@ pub struct SelectorConfig { /// The hash, if available pub hash: Option, /// The variant config - pub variant: BTreeMap, + pub variant: BTreeMap, /// Enable experimental features pub experimental: bool, /// Allow undefined variables @@ -71,15 +72,15 @@ impl SelectorConfig { ); for (key, v) in self.variant { - let v_lower = v.to_lowercase(); + let v_lower = v.to_string().to_lowercase(); match v_lower.as_str() { "true" | "yes" => context.insert(key.clone(), Value::from(true)), "false" | "no" => context.insert(key.clone(), Value::from(false)), _ => { - if let Ok(v_num) = v.parse::() { + if let Ok(v_num) = v_lower.parse::() { context.insert(key.clone(), Value::from(v_num)) } else { - context.insert(key.clone(), Value::from_safe_string(v)) + context.insert(key.clone(), Value::from_safe_string(v.to_string())) } } }; @@ -91,7 +92,7 @@ impl SelectorConfig { /// Create a new selector config from an existing one, replacing the variant pub fn new_with_variant( &self, - variant: BTreeMap, + variant: BTreeMap, target_platform: Platform, ) -> Self { Self { diff --git a/src/snapshots/rattler_build__variant_config__tests__flatten_selectors-2.snap b/src/snapshots/rattler_build__variant_config__tests__flatten_selectors-2.snap index 21302061..6286db27 100644 --- a/src/snapshots/rattler_build__variant_config__tests__flatten_selectors-2.snap +++ b/src/snapshots/rattler_build__variant_config__tests__flatten_selectors-2.snap @@ -7,7 +7,7 @@ zip_keys: ~ c_compiler: - vs2019 cplusplus: - - "100" + - 100 cxx_compiler: - vs2019 python: diff --git a/src/snapshots/rattler_build__variant_config__tests__flatten_selectors.snap b/src/snapshots/rattler_build__variant_config__tests__flatten_selectors.snap index 01057b0e..0f56adb9 100644 --- a/src/snapshots/rattler_build__variant_config__tests__flatten_selectors.snap +++ b/src/snapshots/rattler_build__variant_config__tests__flatten_selectors.snap @@ -7,12 +7,12 @@ zip_keys: ~ c_compiler: - super_g++ cplusplus: - - "10" - - "1000" + - 10 + - 1000 cxx_compiler: - super_gcc python: - "3.7" - "3.8" unix_level: - - "1" + - 1 diff --git a/src/utils.rs b/src/utils.rs index 89b55abc..34f86b25 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -8,6 +8,7 @@ use std::collections::btree_map::IntoIter; use std::collections::BTreeMap; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; +use std::str::FromStr; use std::{ path::{Component, Path, PathBuf}, time::{SystemTime, UNIX_EPOCH}, @@ -76,6 +77,44 @@ pub fn to_forward_slash_lossy(path: &Path) -> std::borrow::Cow<'_, str> { } } +/// FOO +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)] +#[serde(untagged)] +pub enum VariantValue { + /// BOO + String(String), + /// BOO + Number(i64), + /// BOO + Boolean(bool), +} + +impl FromStr for VariantValue { + type Err = String; + + fn from_str(s: &str) -> Result { + if let Ok(value) = s.parse::() { + return Ok(VariantValue::Number(value)); + } + match s.to_lowercase().as_str() { + "true" => return Ok(VariantValue::Boolean(true)), + "false" => return Ok(VariantValue::Boolean(false)), + _ => {} + } + Ok(VariantValue::String(s.to_string())) + } +} + +impl ToString for VariantValue { + fn to_string(&self) -> String { + match self { + VariantValue::String(s) => s.to_string(), + VariantValue::Number(n) => n.to_string(), + VariantValue::Boolean(b) => b.to_string(), + } + } +} + #[serde_as] #[derive(Debug, Clone, Default, Serialize, Deserialize)] @@ -84,7 +123,7 @@ pub struct NormalizedKeyBTreeMap { #[serde_as(deserialize_as = "BTreeMap<_, OneOrMany<_, PreferOne>>")] #[serde(flatten)] /// the inner map - pub map: BTreeMap>, + pub map: BTreeMap>, } impl NormalizedKeyBTreeMap { @@ -101,24 +140,24 @@ impl NormalizedKeyBTreeMap { } /// Inserts a key-value pair into the map, where key is normalized - pub fn insert(&mut self, key: String, value: Vec) { + pub fn insert(&mut self, key: String, value: Vec) { let normalized_key = Self::normalize_key(&key); self.map.insert(normalized_key, value); } /// Returns a reference to the value corresponding to the key. /// Key is normalized - pub fn get(&self, key: &str) -> Option<&Vec> { + pub fn get(&self, key: &str) -> Option<&Vec> { // Change value type as needed let normalized_key = Self::normalize_key(key); self.map.get(&normalized_key) } } -impl Extend<(String, Vec)> for NormalizedKeyBTreeMap { +impl Extend<(String, Vec)> for NormalizedKeyBTreeMap { fn extend(&mut self, iter: T) where - T: IntoIterator)>, + T: IntoIterator)>, { for (key, value) in iter { match self.map.entry(Self::normalize_key(&key)) { @@ -135,14 +174,14 @@ impl Extend<(String, Vec)> for NormalizedKeyBTreeMap { impl NormalizedKeyBTreeMap { /// Gets an iterator over the entries of the map, sorted by key. - pub fn iter(&self) -> impl Iterator)> { + pub fn iter(&self) -> impl Iterator)> { self.map.iter() } } impl IntoIterator for NormalizedKeyBTreeMap { - type Item = (String, Vec); - type IntoIter = IntoIter>; + type Item = (String, Vec); + type IntoIter = IntoIter>; fn into_iter(self) -> Self::IntoIter { self.map.into_iter() @@ -150,8 +189,8 @@ impl IntoIterator for NormalizedKeyBTreeMap { } impl<'a> IntoIterator for &'a NormalizedKeyBTreeMap { - type Item = (&'a String, &'a Vec); - type IntoIter = std::collections::btree_map::Iter<'a, String, Vec>; + type Item = (&'a String, &'a Vec); + type IntoIter = std::collections::btree_map::Iter<'a, String, Vec>; fn into_iter(self) -> Self::IntoIter { self.map.iter() diff --git a/src/variant_config.rs b/src/variant_config.rs index 52975547..53c63fa9 100644 --- a/src/variant_config.rs +++ b/src/variant_config.rs @@ -3,6 +3,7 @@ use std::{ collections::{BTreeMap, HashMap, HashSet}, path::PathBuf, + str::FromStr, }; use indexmap::IndexSet; @@ -16,13 +17,16 @@ use crate::{ _partialerror, hash::HashInfo, recipe::{ - custom_yaml::{HasSpan, Node, RenderedMappingNode, RenderedNode, TryConvertNode}, + custom_yaml::{ + HasSpan, Node, RenderedMappingNode, RenderedNode, RenderedScalarNode, TryConvertNode, + }, error::{ErrorKind, ParsingError, PartialParsingError}, parser::Recipe, Jinja, Render, }, selectors::SelectorConfig, used_variables::used_vars_from_expressions, + utils::VariantValue, }; use crate::{recipe::parser::Dependency, utils::NormalizedKeyBTreeMap}; use petgraph::{algo::toposort, graph::DiGraph}; @@ -36,7 +40,7 @@ pub struct DiscoveredOutput { pub noarch_type: NoArchType, pub target_platform: Platform, pub node: Node, - pub used_vars: BTreeMap, + pub used_vars: BTreeMap, } #[derive(Debug, Clone, Default, Deserialize, Serialize)] @@ -274,11 +278,15 @@ impl VariantConfig { // always insert target_platform and build_platform final_config.variants.insert( "target_platform".into(), - vec![selector_config.target_platform.to_string()], + vec![VariantValue::String( + selector_config.target_platform.to_string(), + )], ); final_config.variants.insert( "build_platform".into(), - vec![selector_config.build_platform.to_string()], + vec![VariantValue::String( + selector_config.build_platform.to_string(), + )], ); Ok(final_config) @@ -311,7 +319,7 @@ impl VariantConfig { pub fn combinations( &self, used_vars: &HashSet, - ) -> Result>, VariantError> { + ) -> Result>, VariantError> { self.validate_zip_keys()?; let zip_keys = self.zip_keys.clone().unwrap_or_default(); let used_zip_keys = zip_keys @@ -360,7 +368,7 @@ impl VariantConfig { combination .iter() .cloned() - .collect::>() + .collect::>() }) .collect(); Ok(result) @@ -586,7 +594,7 @@ impl VariantConfig { let mut recipes = IndexSet::new(); for combination in combinations { let mut other_recipes = - HashMap::)>::new(); + HashMap::)>::new(); for (_, (name, output, used_vars, target_platform)) in outputs_map.iter() { let mut used_variables = used_vars.clone(); @@ -604,7 +612,10 @@ impl VariantConfig { let mut combination = combination.clone(); // we need to overwrite the target_platform in case of `noarch`. - combination.insert("target_platform".to_string(), target_platform.to_string()); + combination.insert( + "target_platform".to_string(), + VariantValue::String(target_platform.to_string()), + ); let selector_config_with_variant = selector_config.new_with_variant(combination.clone(), *target_platform); @@ -655,7 +666,10 @@ impl VariantConfig { for p in exact_pins { match other_recipes.get(&p) { Some((version, build, _)) => { - used_filtered.insert(p.clone(), format!("{} {}", version, build)); + used_filtered.insert( + p.clone(), + VariantValue::String(format!("{} {}", version, build)), + ); } None => { return Err(VariantError::MissingOutput(p)); @@ -679,7 +693,10 @@ impl VariantConfig { Some((version, build, _)) => { used_filtered.insert( val.to_owned(), - format!("{} {}", version, build), + VariantValue::String(format!( + "{} {}", + version, build + )), ); } None => { @@ -784,10 +801,29 @@ impl TryConvertNode for RenderedMappingNode { } } +impl TryConvertNode for RenderedNode { + fn try_convert(&self, name: &str) -> Result> { + match self { + RenderedNode::Scalar(scalar) => scalar.try_convert(name), + _ => Err(vec![_partialerror!( + *self.span(), + ErrorKind::ExpectedScalar, + help = "A scalar value is expected here" + )]), + } + } +} + +impl TryConvertNode for RenderedScalarNode { + fn try_convert(&self, name: &str) -> Result> { + Ok(VariantValue::from_str(self.as_str()).unwrap()) + } +} + #[derive(Debug, Clone)] enum VariantKey { - Key(String, Vec), - ZipKey(HashMap>), + Key(String, Vec), + ZipKey(HashMap>), } impl VariantKey { @@ -798,7 +834,7 @@ impl VariantKey { } } - pub fn at(&self, index: usize) -> Option> { + pub fn at(&self, index: usize) -> Option> { match self { VariantKey::Key(key, values) => { values.get(index).map(|v| vec![(key.clone(), v.clone())]) @@ -874,8 +910,8 @@ pub enum VariantError { fn find_combinations( variant_keys: &[VariantKey], index: usize, - current: &mut Vec<(String, String)>, - result: &mut Vec>, + current: &mut Vec<(String, VariantValue)>, + result: &mut Vec>, ) { if index == variant_keys.len() { result.push(current.clone()); @@ -969,7 +1005,7 @@ mod tests { .find_variants(&outputs, &recipe_text, &selector_config) .unwrap(); - let used_variables_all: Vec<&BTreeMap> = outputs_and_variants + let used_variables_all: Vec<&BTreeMap> = outputs_and_variants .as_slice() .into_iter() .map(|s| &s.used_vars) @@ -983,8 +1019,14 @@ mod tests { #[test] fn test_variant_combinations() { let mut variants = NormalizedKeyBTreeMap::new(); - variants.insert("a".to_string(), vec!["1".to_string(), "2".to_string()]); - variants.insert("b".to_string(), vec!["3".to_string(), "4".to_string()]); + variants.insert( + "a".to_string(), + vec!["1".parse().unwrap(), "2".parse().unwrap()], + ); + variants.insert( + "b".to_string(), + vec!["3".parse().unwrap(), "4".parse().unwrap()], + ); let zip_keys = vec![vec!["a".to_string(), "b".to_string()].into_iter().collect()]; let used_vars = vec!["a".to_string()].into_iter().collect(); @@ -1003,17 +1045,29 @@ mod tests { config.variants.insert( "c".to_string(), - vec!["5".to_string(), "6".to_string(), "7".to_string()], + vec![ + "5".parse().unwrap(), + "6".parse().unwrap(), + "7".parse().unwrap(), + ], ); - let used_vars = vec!["a".to_string(), "b".to_string(), "c".to_string()] - .into_iter() - .collect(); + let used_vars = vec![ + "a".parse().unwrap(), + "b".parse().unwrap(), + "c".parse().unwrap(), + ] + .into_iter() + .collect(); let combinations = config.combinations(&used_vars).unwrap(); assert_eq!(combinations.len(), 2 * 3); - let used_vars = vec!["a".to_string(), "b".to_string(), "c".to_string()] - .into_iter() - .collect(); + let used_vars = vec![ + "a".parse().unwrap(), + "b".parse().unwrap(), + "c".parse().unwrap(), + ] + .into_iter() + .collect(); config.zip_keys = None; let combinations = config.combinations(&used_vars).unwrap(); assert_eq!(combinations.len(), 2 * 2 * 3); @@ -1071,7 +1125,7 @@ mod tests { .find_variants(&outputs, &recipe_text, &selector_config) .unwrap(); - let used_variables_all: Vec<&BTreeMap> = outputs_and_variants + let used_variables_all: Vec<&BTreeMap> = outputs_and_variants .as_slice() .into_iter() .map(|s| &s.used_vars) From c7d2f542d62d354c70927da524de764cfdcabc7c Mon Sep 17 00:00:00 2001 From: Wolf Vollprecht Date: Wed, 5 Jun 2024 14:35:06 +0200 Subject: [PATCH 7/7] allow integer and boolean in context, too --- src/recipe/custom_yaml.rs | 3 +- src/recipe/custom_yaml/rendered.rs | 77 ++++++++++++++--------------- src/recipe/parser.rs | 18 +++++-- src/render/resolved_dependencies.rs | 1 - src/selectors.rs | 21 ++++---- src/variant_config.rs | 2 +- 6 files changed, 64 insertions(+), 58 deletions(-) diff --git a/src/recipe/custom_yaml.rs b/src/recipe/custom_yaml.rs index bd94cdb3..b4cd3444 100644 --- a/src/recipe/custom_yaml.rs +++ b/src/recipe/custom_yaml.rs @@ -487,6 +487,7 @@ impl From for ScalarNode { } } } + macro_rules! scalar_from_to_number { ($t:ident, $as:ident) => { impl From<$t> for ScalarNode { @@ -628,7 +629,7 @@ impl fmt::Debug for SequenceNode { /// Mapping nodes in YAML are defined as a key/value mapping where the keys are /// unique and always scalars, whereas values may be YAML nodes of any kind. /// -/// Because ther is an example that on the `context` key-value definition, a later +/// Because there is an example that on the `context` key-value definition, a later /// key was defined as a jinja string using previous values, we need to care about /// insertion order we use [`IndexMap`] for this. /// diff --git a/src/recipe/custom_yaml/rendered.rs b/src/recipe/custom_yaml/rendered.rs index 54249170..4510b457 100644 --- a/src/recipe/custom_yaml/rendered.rs +++ b/src/recipe/custom_yaml/rendered.rs @@ -254,6 +254,11 @@ impl RenderedScalarNode { _ => None, } } + + /// Try to parse the value as integer + pub fn as_integer(&self) -> Option { + self.value.parse().ok() + } } impl HasSpan for RenderedScalarNode { @@ -343,46 +348,6 @@ impl From for RenderedScalarNode { } } } -macro_rules! scalar_from_to_number { - ($t:ident, $as:ident) => { - impl From<$t> for RenderedScalarNode { - #[doc = "Convert from "] - #[doc = stringify!($t)] - #[doc = r#" into a node"#] - fn from(value: $t) -> Self { - format!("{}", value).into() - } - } - - impl RenderedScalarNode { - #[doc = "Treat the scalar node as "] - #[doc = stringify!($t)] - #[doc = r#". - -If this scalar node's value can be represented properly as -a number of the right kind then return it. This is essentially -a shortcut for using the `FromStr` trait on the return value of -`.as_str()`."#] - pub fn $as(&self) -> Option<$t> { - use std::str::FromStr; - $t::from_str(&self.value).ok() - } - } - }; -} - -scalar_from_to_number!(i8, as_i8); -scalar_from_to_number!(i16, as_i16); -scalar_from_to_number!(i32, as_i32); -scalar_from_to_number!(i64, as_i64); -scalar_from_to_number!(i128, as_i128); -scalar_from_to_number!(isize, as_isize); -scalar_from_to_number!(u8, as_u8); -scalar_from_to_number!(u16, as_u16); -scalar_from_to_number!(u32, as_u32); -scalar_from_to_number!(u64, as_u64); -scalar_from_to_number!(u128, as_u128); -scalar_from_to_number!(usize, as_usize); /// A marked YAML sequence node /// @@ -713,3 +678,35 @@ impl Render for SequenceNodeInternal { Ok(RenderedSequenceNode::from(rendered)) } } + +#[cfg(test)] +mod test { + use super::RenderedScalarNode; + + #[test] + fn test_scalar_value() { + let scalar = RenderedScalarNode::from("test"); + assert_eq!(scalar.as_str(), "test"); + + let scalar = RenderedScalarNode::from("true"); + assert_eq!(scalar.as_bool(), Some(true)); + + let scalar = RenderedScalarNode::from("false"); + assert_eq!(scalar.as_bool(), Some(false)); + + let scalar = RenderedScalarNode::from("123"); + assert_eq!(scalar.as_integer(), Some(123)); + + let scalar = RenderedScalarNode::from("123.45"); + assert_eq!(scalar.as_integer(), None); + + let scalar = RenderedScalarNode::from("'true'"); + assert_eq!(scalar.as_bool(), None); + + let scalar = RenderedScalarNode::from("'123'"); + assert_eq!(scalar.as_integer(), None); + + let scalar = RenderedScalarNode::from("'123'"); + assert_eq!(scalar.as_str(), "'123'"); + } +} diff --git a/src/recipe/parser.rs b/src/recipe/parser.rs index e7b93db5..c66fbe9b 100644 --- a/src/recipe/parser.rs +++ b/src/recipe/parser.rs @@ -184,10 +184,20 @@ impl Recipe { val.render(&jinja, &format!("context.{}", k.as_str()))?; if let Some(rendered) = rendered { - jinja.context_mut().insert( - k.as_str().to_owned(), - Value::from_safe_string(rendered.as_str().to_string()), - ); + if let Some(b) = rendered.as_bool() { + jinja + .context_mut() + .insert(k.as_str().to_owned(), Value::from(b)); + } else if let Some(i) = rendered.as_i64() { + jinja + .context_mut() + .insert(k.as_str().to_owned(), Value::from(i)); + } else { + jinja.context_mut().insert( + k.as_str().to_owned(), + Value::from_safe_string(rendered.as_str().to_string()), + ); + } } Ok(()) }) diff --git a/src/render/resolved_dependencies.rs b/src/render/resolved_dependencies.rs index 9a8c0396..351e6923 100644 --- a/src/render/resolved_dependencies.rs +++ b/src/render/resolved_dependencies.rs @@ -9,7 +9,6 @@ use std::{ use crate::{ metadata::{BuildConfiguration, Output}, tool_configuration, - utils::VariantValue, }; use indicatif::HumanBytes; use rattler::package_cache::CacheKey; diff --git a/src/selectors.rs b/src/selectors.rs index 7a7ee006..2b7d0c8e 100644 --- a/src/selectors.rs +++ b/src/selectors.rs @@ -72,18 +72,17 @@ impl SelectorConfig { ); for (key, v) in self.variant { - let v_lower = v.to_string().to_lowercase(); - match v_lower.as_str() { - "true" | "yes" => context.insert(key.clone(), Value::from(true)), - "false" | "no" => context.insert(key.clone(), Value::from(false)), - _ => { - if let Ok(v_num) = v_lower.parse::() { - context.insert(key.clone(), Value::from(v_num)) - } else { - context.insert(key.clone(), Value::from_safe_string(v.to_string())) - } + match v { + VariantValue::String(s) => { + context.insert(key, Value::from_safe_string(s)); } - }; + VariantValue::Number(n) => { + context.insert(key, Value::from(n)); + } + VariantValue::Boolean(b) => { + context.insert(key, Value::from(b)); + } + } } context diff --git a/src/variant_config.rs b/src/variant_config.rs index 53c63fa9..adda7531 100644 --- a/src/variant_config.rs +++ b/src/variant_config.rs @@ -815,7 +815,7 @@ impl TryConvertNode for RenderedNode { } impl TryConvertNode for RenderedScalarNode { - fn try_convert(&self, name: &str) -> Result> { + fn try_convert(&self, _name: &str) -> Result> { Ok(VariantValue::from_str(self.as_str()).unwrap()) } }