Skip to content

Commit 434e9a1

Browse files
authored
fix: variant key in context should be ignored in output (#1963)
1 parent 8672322 commit 434e9a1

File tree

4 files changed

+286
-1
lines changed

4 files changed

+286
-1
lines changed

src/variant_render.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,20 @@ impl<S: SourceCode> Stage1Render<S> {
161161

162162
all_vars.extend(used_vars_jinja.iter().cloned());
163163

164+
// Get recipe once to use throughout
165+
let recipe = &self.inner[idx].recipe;
166+
167+
// Filter out any ignore keys
168+
let ignore_keys: HashSet<NormalizedKey> = recipe
169+
.build()
170+
.variant()
171+
.ignore_keys
172+
.iter()
173+
.map(|k| k.as_str().into())
174+
.collect();
175+
176+
all_vars.retain(|var| !ignore_keys.contains(var));
177+
164178
// extract variant
165179
let mut variant = BTreeMap::new();
166180
for var in all_vars {
@@ -170,7 +184,6 @@ impl<S: SourceCode> Stage1Render<S> {
170184
}
171185

172186
// Add in virtual packages
173-
let recipe = &self.inner[idx].recipe;
174187
for run_requirement in recipe.requirements().run() {
175188
if let Dependency::Spec(spec) = run_requirement
176189
&& let Some(ref name) = spec.name
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
schema_version: 1
2+
3+
context:
4+
version: 3.4.0
5+
eigen_max_align_bytes: ${{ "16" if eigen_abi_profile == "100" else ("32" if eigen_abi_profile == "80" else ("64" if eigen_abi_profile == "70" else "unsupported_eigen_abi_profile_value")) }}
6+
7+
recipe:
8+
name: eigen-variant-example
9+
version: ${{ version }}
10+
11+
build:
12+
number: 1
13+
14+
outputs:
15+
- package:
16+
name: eigen
17+
version: ${{ version }}
18+
19+
build:
20+
variant:
21+
ignore_keys:
22+
- eigen_abi_profile
23+
24+
- package:
25+
name: eigen-abi
26+
version: ${{ version }}.${{ eigen_abi_profile }}
27+
requirements:
28+
run_exports:
29+
- ${{ pin_subpackage('eigen-abi', upper_bound='x.x.x.x') }}
30+
run:
31+
- ${{ pin_subpackage('eigen', upper_bound='x.x.x') }}
32+
tests:
33+
- script:
34+
- echo "This is a metapackage, no test is necessary."
35+
36+
- package:
37+
name: eigen-abi-other
38+
version: ${{ version }}
39+
build:
40+
variant:
41+
use_keys:
42+
- some_key
43+
ignore_keys:
44+
- eigen_abi_profile
45+
46+
about:
47+
homepage: http://eigen.tuxfamily.org/
48+
license: MPL-2.0
49+
summary: C++ template library for linear algebra

test-data/recipes/variants/variant_config.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,11 @@ libcblas:
55
- 3.9 *netlib
66
r_base:
77
- 4.2
8+
9+
eigen_abi_profile:
10+
- "100"
11+
- "80"
12+
13+
some_key:
14+
- "1"
15+
- "2"
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
"""Test ignore_keys functionality with context variables."""
2+
3+
from pathlib import Path
4+
from helpers import RattlerBuild
5+
6+
7+
def test_eigen_abi_profile_ignore_keys(
8+
rattler_build: RattlerBuild, recipes: Path, tmp_path: Path
9+
):
10+
"""Test the eigen recipe pattern where eigen ignores eigen_abi_profile but eigen-abi includes it.
11+
12+
This test validates the specific use case from the issue where:
13+
- The 'eigen' output should have the SAME hash across all eigen_abi_profile values (ignored)
14+
- The 'eigen-abi' output should have DIFFERENT hashes for each eigen_abi_profile value (not ignored)
15+
- The 'eigen-abi' package version should include the abi profile (e.g., 3.4.0.100)
16+
"""
17+
recipe_path = recipes / "variants" / "issue_variant_ignore.yaml"
18+
variant_config = recipes / "variants" / "variant_config.yaml"
19+
20+
# Render the recipe to get all variants
21+
rendered = rattler_build.render(
22+
recipe_path, tmp_path, variant_config=variant_config
23+
)
24+
25+
# Separate the outputs by package name
26+
eigen_outputs = []
27+
eigen_abi_outputs = []
28+
eigen_abi_other_outputs = []
29+
30+
for item in rendered:
31+
package_name = item["recipe"]["package"]["name"]
32+
build_config = item.get("build_configuration", {})
33+
variant = build_config.get("variant", {})
34+
build_string = item["recipe"]["build"]["string"]
35+
version = item["recipe"]["package"]["version"]
36+
37+
if package_name == "eigen":
38+
eigen_outputs.append(
39+
{
40+
"version": version,
41+
"build_string": build_string,
42+
"variant": variant,
43+
"abi_profile": variant.get("eigen_abi_profile", "none"),
44+
"some_key": variant.get("some_key", "none"),
45+
}
46+
)
47+
elif package_name == "eigen-abi":
48+
eigen_abi_outputs.append(
49+
{
50+
"version": version,
51+
"build_string": build_string,
52+
"variant": variant,
53+
"abi_profile": variant.get("eigen_abi_profile", "none"),
54+
"some_key": variant.get("some_key", "none"),
55+
}
56+
)
57+
elif package_name == "eigen-abi-other":
58+
eigen_abi_other_outputs.append(
59+
{
60+
"version": version,
61+
"build_string": build_string,
62+
"variant": variant,
63+
"abi_profile": variant.get("eigen_abi_profile", "none"),
64+
"some_key": variant.get("some_key", "none"),
65+
}
66+
)
67+
68+
# Verify we have the expected number of outputs
69+
# eigen: ignores eigen_abi_profile -> 1 build (same hash for all abi profiles)
70+
# eigen-abi: does NOT ignore eigen_abi_profile -> 2 builds (one per abi profile: 100, 80)
71+
# eigen-abi-other: uses use_keys: [some_key] and ignore_keys: [eigen_abi_profile]
72+
# use_keys forces some_key into the variant -> 2 builds (one per some_key: 1, 2)
73+
# ignore_keys excludes eigen_abi_profile from hash
74+
assert len(eigen_outputs) == 1, (
75+
f"Expected 1 eigen output (since ignore_keys makes all abi_profile variants identical), "
76+
f"got {len(eigen_outputs)}"
77+
)
78+
assert (
79+
len(eigen_abi_outputs) == 2
80+
), f"Expected 2 eigen-abi outputs (one per abi_profile), got {len(eigen_abi_outputs)}"
81+
assert len(eigen_abi_other_outputs) == 2, (
82+
f"Expected 2 eigen-abi-other outputs (one per some_key via use_keys), "
83+
f"got {len(eigen_abi_other_outputs)}"
84+
)
85+
86+
# Test 1: eigen output should only be built once (all abi profiles produce same hash)
87+
# This is the whole point of ignore_keys!
88+
eigen_build_string = eigen_outputs[0]["build_string"]
89+
90+
# Test 2: eigen-abi output should have DIFFERENT build strings for each abi profile
91+
eigen_abi_build_strings = [o["build_string"] for o in eigen_abi_outputs]
92+
unique_eigen_abi_builds = set(eigen_abi_build_strings)
93+
assert len(unique_eigen_abi_builds) == 2, (
94+
f"eigen-abi package should have different build strings for each abi_profile value, "
95+
f"but got: {eigen_abi_build_strings}"
96+
)
97+
98+
# Test 3: Verify the variant information displayed
99+
# eigen should NOT show eigen_abi_profile in its variant (it's ignored)
100+
for output in eigen_outputs:
101+
variant = output["variant"]
102+
# The variant dict should NOT contain eigen_abi_profile for the eigen package
103+
# because it's in ignore_keys
104+
assert (
105+
"eigen_abi_profile" not in variant
106+
or variant.get("eigen_abi_profile") is None
107+
), (
108+
f"eigen variant should not include eigen_abi_profile (it's ignored), "
109+
f"but variant is: {variant}"
110+
)
111+
# Should only have target_platform
112+
assert "target_platform" in variant, "eigen variant should have target_platform"
113+
assert (
114+
output["version"] == "3.4.0"
115+
), f"eigen version should be 3.4.0, got {output['version']}"
116+
117+
# Test 4: eigen-abi SHOULD show eigen_abi_profile in its variant (not ignored)
118+
abi_profiles_found = set()
119+
for output in eigen_abi_outputs:
120+
variant = output["variant"]
121+
# The variant dict SHOULD contain eigen_abi_profile for eigen-abi
122+
assert (
123+
"eigen_abi_profile" in variant
124+
), f"eigen-abi variant should include eigen_abi_profile, but variant is: {variant}"
125+
abi_profile = variant["eigen_abi_profile"]
126+
abi_profiles_found.add(str(abi_profile))
127+
128+
# Version should include the abi profile (e.g., 3.4.0.100)
129+
expected_version = f"3.4.0.{abi_profile}"
130+
assert (
131+
output["version"] == expected_version
132+
), f"eigen-abi version should be {expected_version}, got {output['version']}"
133+
134+
# Verify we saw both abi profiles
135+
assert abi_profiles_found == {
136+
"100",
137+
"80",
138+
}, f"Expected to find abi profiles 100 and 80, but found: {abi_profiles_found}"
139+
140+
# eigen-abi should have different hashes
141+
eigen_abi_100 = next(
142+
(o for o in eigen_abi_outputs if o["abi_profile"] == "100"), None
143+
)
144+
eigen_abi_80 = next(
145+
(o for o in eigen_abi_outputs if o["abi_profile"] == "80"), None
146+
)
147+
148+
assert (
149+
eigen_abi_100 is not None
150+
), "Could not find eigen-abi output for abi_profile 100"
151+
assert (
152+
eigen_abi_80 is not None
153+
), "Could not find eigen-abi output for abi_profile 80"
154+
155+
hash_100 = eigen_abi_100["build_string"].split("_")[0]
156+
hash_80 = eigen_abi_80["build_string"].split("_")[0]
157+
158+
# Verify they have different hashes (the actual hash values may vary)
159+
assert hash_100 != hash_80, (
160+
f"eigen-abi outputs for different abi_profiles should have different hashes, "
161+
f"but both have: {hash_100}"
162+
)
163+
164+
# Test 6: eigen-abi-other validates that use_keys and ignore_keys work together
165+
# use_keys forces some_key into variant even though it's not referenced
166+
# ignore_keys prevents eigen_abi_profile from affecting the hash
167+
some_keys_found = set()
168+
eigen_abi_other_by_some_key = {}
169+
170+
for output in eigen_abi_other_outputs:
171+
variant = output["variant"]
172+
173+
# Should have some_key (via use_keys)
174+
assert (
175+
"some_key" in variant
176+
), f"eigen-abi-other variant should include some_key (via use_keys), but variant is: {variant}"
177+
178+
# Should NOT have eigen_abi_profile (via ignore_keys)
179+
assert (
180+
"eigen_abi_profile" not in variant
181+
or variant.get("eigen_abi_profile") is None
182+
), (
183+
f"eigen-abi-other variant should not include eigen_abi_profile (it's ignored), "
184+
f"but variant is: {variant}"
185+
)
186+
187+
some_key_value = variant["some_key"]
188+
some_keys_found.add(str(some_key_value))
189+
eigen_abi_other_by_some_key[str(some_key_value)] = output
190+
191+
# Verify we saw both some_key values
192+
assert some_keys_found == {
193+
"1",
194+
"2",
195+
}, f"Expected to find some_key values 1 and 2, but found: {some_keys_found}"
196+
197+
# Verify different some_key values produce different hashes
198+
hash_1 = eigen_abi_other_by_some_key["1"]["build_string"].split("_")[0]
199+
hash_2 = eigen_abi_other_by_some_key["2"]["build_string"].split("_")[0]
200+
201+
assert hash_1 != hash_2, (
202+
f"eigen-abi-other outputs for different some_key values should have different hashes, "
203+
f"but both have: {hash_1}"
204+
)
205+
206+
print("\n✓ Test passed!")
207+
print(f" eigen (ignores abi_profile): {eigen_build_string}")
208+
print(f" eigen-abi-100: {eigen_abi_100['build_string']}")
209+
print(f" eigen-abi-80: {eigen_abi_80['build_string']}")
210+
print(
211+
f" eigen-abi-other (some_key=1, ignores abi_profile): {eigen_abi_other_by_some_key['1']['build_string']}"
212+
)
213+
print(
214+
f" eigen-abi-other (some_key=2, ignores abi_profile): {eigen_abi_other_by_some_key['2']['build_string']}"
215+
)

0 commit comments

Comments
 (0)