Skip to content

Commit fe67d68

Browse files
committed
Merge remote-tracking branch 'origin/main' into integration-tests
2 parents e760886 + 8eff19d commit fe67d68

12 files changed

+701
-125
lines changed

conda_forge_tick/migrators/cross_compile.py

Lines changed: 68 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,14 @@ def migrate(self, recipe_dir: str, attrs: "AttrsTypedDict", **kwargs: Any) -> No
148148
f.write("".join(lines))
149149

150150

151-
class CrossPythonMigrator(CrossCompilationMigratorBase):
151+
class CrossPythonMigrator(MiniMigrator):
152+
allowed_schema_versions = {0, 1}
153+
post_migration = True
154+
152155
def filter(self, attrs: "AttrsTypedDict", not_bad_str_start: str = "") -> bool:
156+
if super().filter(attrs, not_bad_str_start):
157+
return True
158+
153159
host_reqs = attrs.get("requirements", {}).get("host", set())
154160
build_reqs = attrs.get("requirements", {}).get("build", set())
155161
return (
@@ -161,7 +167,8 @@ def filter(self, attrs: "AttrsTypedDict", not_bad_str_start: str = "") -> bool:
161167
def migrate(self, recipe_dir: str, attrs: "AttrsTypedDict", **kwargs: Any) -> None:
162168
host_reqs = attrs.get("requirements", {}).get("host", set())
163169
with pushd(recipe_dir):
164-
with open("meta.yaml") as f:
170+
recipe_file = next(filter(os.path.exists, ("recipe.yaml", "meta.yaml")))
171+
with open(recipe_file) as f:
165172
lines = f.readlines()
166173
in_reqs = False
167174
for i, line in enumerate(lines):
@@ -189,24 +196,37 @@ def migrate(self, recipe_dir: str, attrs: "AttrsTypedDict", **kwargs: Any) -> No
189196
for pkg in reversed(
190197
[
191198
"python",
192-
"cross-python_{{ target_platform }}",
199+
(
200+
"cross-python_${{ host_platform }}"
201+
if recipe_file == "recipe.yaml"
202+
else "cross-python_{{ target_platform }}"
203+
),
193204
"cython",
194205
"numpy",
195206
"cffi",
196207
"pybind11",
197208
],
198209
):
199210
if pkg in host_reqs or pkg.startswith("cross-python"):
200-
new_line = (
201-
" " * spaces
202-
+ "- "
203-
+ pkg.ljust(37)
204-
+ " # [build_platform != target_platform]\n"
205-
)
206-
lines.insert(i + 1, new_line)
211+
if recipe_file == "recipe.yaml":
212+
new_line = (
213+
" " * spaces
214+
+ "- if: build_platform != host_platform\n"
215+
)
216+
lines.insert(i + 1, new_line)
217+
new_line = " " * spaces + f" then: {pkg}\n"
218+
lines.insert(i + 2, new_line)
219+
else:
220+
new_line = (
221+
" " * spaces
222+
+ "- "
223+
+ pkg.ljust(37)
224+
+ " # [build_platform != target_platform]\n"
225+
)
226+
lines.insert(i + 1, new_line)
207227
break
208228

209-
with open("meta.yaml", "w") as f:
229+
with open(recipe_file, "w") as f:
210230
f.write("".join(lines))
211231

212232

@@ -325,20 +345,24 @@ def migrate(self, recipe_dir: str, attrs: "AttrsTypedDict", **kwargs: Any) -> No
325345
"""
326346

327347

328-
class CrossRBaseMigrator(CrossCompilationMigratorBase):
348+
class CrossRBaseMigrator(MiniMigrator):
349+
allowed_schema_versions = {0, 1}
350+
post_migration = True
351+
329352
def filter(self, attrs: "AttrsTypedDict", not_bad_str_start: str = "") -> bool:
353+
if super().filter(attrs, not_bad_str_start):
354+
return True
355+
330356
host_reqs = attrs.get("requirements", {}).get("host", set())
331-
skip_schema = skip_migrator_due_to_schema(attrs, self.allowed_schema_versions)
332-
if (
333-
"r-base" in host_reqs or attrs.get("name", "").startswith("r-")
334-
) and not skip_schema:
357+
if "r-base" in host_reqs or attrs.get("name", "").startswith("r-"):
335358
return False
336359
else:
337360
return True
338361

339362
def migrate(self, recipe_dir: str, attrs: "AttrsTypedDict", **kwargs: Any) -> None:
340363
with pushd(recipe_dir):
341-
with open("meta.yaml") as fp:
364+
recipe_file = next(filter(os.path.exists, ("recipe.yaml", "meta.yaml")))
365+
with open(recipe_file) as fp:
342366
meta_yaml = fp.readlines()
343367

344368
new_lines = []
@@ -347,10 +371,20 @@ def migrate(self, recipe_dir: str, attrs: "AttrsTypedDict", **kwargs: Any) -> No
347371
for line in meta_yaml:
348372
if previous_was_build:
349373
nspaces = len(line) - len(line.lstrip())
350-
new_lines.append(
351-
" " * nspaces
352-
+ "- cross-r-base {{ r_base }} # [build_platform != target_platform]\n",
353-
)
374+
if recipe_file == "recipe.yaml":
375+
new_lines.extend(
376+
[
377+
" " * nspaces
378+
+ "- if: build_platform != host_platform\n",
379+
" " * nspaces + " then:\n",
380+
" " * nspaces + " - cross-r-base ${{ r_base }}\n",
381+
]
382+
)
383+
else:
384+
new_lines.append(
385+
" " * nspaces
386+
+ "- cross-r-base {{ r_base }} # [build_platform != target_platform]\n",
387+
)
354388
# Add host R requirements to build
355389
host_reqs = attrs.get("requirements", {}).get("host", set())
356390
r_host_reqs = [
@@ -359,15 +393,18 @@ def migrate(self, recipe_dir: str, attrs: "AttrsTypedDict", **kwargs: Any) -> No
359393
if req.startswith("r-") and req != "r-base"
360394
]
361395
for r_req in r_host_reqs:
362-
# Ensure nice formatting
363-
post_nspaces = max(0, 25 - len(r_req))
364-
new_lines.append(
365-
" " * nspaces
366-
+ "- "
367-
+ r_req
368-
+ " " * post_nspaces
369-
+ " # [build_platform != target_platform]\n",
370-
)
396+
if recipe_file == "recipe.yaml":
397+
new_lines.append(" " * nspaces + f" - {r_req}\n")
398+
else:
399+
# Ensure nice formatting
400+
post_nspaces = max(0, 25 - len(r_req))
401+
new_lines.append(
402+
" " * nspaces
403+
+ "- "
404+
+ r_req
405+
+ " " * post_nspaces
406+
+ " # [build_platform != target_platform]\n",
407+
)
371408
in_req = False
372409
previous_was_build = False
373410
if "requirements:" in line:
@@ -376,7 +413,7 @@ def migrate(self, recipe_dir: str, attrs: "AttrsTypedDict", **kwargs: Any) -> No
376413
previous_was_build = True
377414
new_lines.append(line)
378415

379-
with open("meta.yaml", "w") as f:
416+
with open(recipe_file, "w") as f:
380417
f.write("".join(new_lines))
381418

382419
if os.path.exists("build.sh"):

conda_forge_tick/migrators/recipe_v1.py

Lines changed: 88 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
from pathlib import Path
44
from typing import Any
55

6+
from jinja2 import Environment
7+
from jinja2.nodes import Compare, Node, Not
8+
from jinja2.parser import Parser
9+
610
from conda_forge_tick.migrators.core import MiniMigrator
711
from conda_forge_tick.recipe_parser._parser import _get_yaml_parser
812

@@ -12,56 +16,95 @@
1216
logger = logging.getLogger(__name__)
1317

1418

15-
def combine_conditions(node):
19+
def get_condition(node: Any) -> Node | None:
20+
if isinstance(node, dict) and "if" in node:
21+
return Parser(
22+
Environment(), node["if"].strip(), state="variable"
23+
).parse_expression()
24+
return None
25+
26+
27+
def is_same_condition(a: Node, b: Node) -> bool:
28+
return a == b
29+
30+
31+
INVERSE_OPS = {
32+
"eq": "ne",
33+
"ne": "eq",
34+
"gt": "lteq",
35+
"gteq": "lt",
36+
"lt": "gteq",
37+
"lteq": "gt",
38+
"in": "notin",
39+
"notin": "in",
40+
}
41+
42+
43+
def is_negated_condition(a: Node, b: Node) -> bool:
44+
# X <-> not X
45+
if Not(a) == b or a == Not(b):
46+
return True
47+
48+
# unwrap (not X) <-> (not Y)
49+
if isinstance(a, Not) and isinstance(b, Not):
50+
a = a.node
51+
b = b.node
52+
53+
# A == B <-> A != B
54+
if (
55+
isinstance(a, Compare)
56+
and isinstance(b, Compare)
57+
and len(a.ops) == len(b.ops) == 1
58+
and a.expr == b.expr
59+
and a.ops[0].expr == b.ops[0].expr
60+
and a.ops[0].op == INVERSE_OPS[b.ops[0].op]
61+
):
62+
return True
63+
64+
return False
65+
66+
67+
def fold_branch(source: Any, dest: Any, branch: str, dest_branch: str) -> None:
68+
if branch not in source:
69+
return
70+
71+
source_l = source[branch]
72+
if isinstance(source_l, str):
73+
if dest_branch not in dest:
74+
# special-case: do not expand a single string to list
75+
dest[dest_branch] = source_l
76+
return
77+
source_l = [source_l]
78+
79+
if dest_branch not in dest:
80+
dest[dest_branch] = []
81+
elif isinstance(dest[dest_branch], str):
82+
dest[dest_branch] = [dest[dest_branch]]
83+
dest[dest_branch].extend(source_l)
84+
85+
86+
def combine_conditions(node: Any):
1687
"""Breadth first recursive call to combine list conditions"""
1788

1889
# recursion is breadth first because we go through each element here
1990
# before calling `combine_conditions` on any element in the node
2091
if isinstance(node, list):
21-
# 1. loop through list elements, gather the if conditions
22-
23-
# condition ("if:") -> [(then, else), (then, else)...]
24-
conditions = {}
25-
26-
for i in node:
27-
if isinstance(i, dict) and "if" in i:
28-
conditions.setdefault(i["if"], []).append((i["then"], i.get("else")))
29-
30-
# 2. if elements share a compatible if condition
31-
# combine their if...then...else statements
32-
to_drop = []
33-
for i in range(len(node)):
34-
if isinstance(node[i], dict) and "if" in node[i]:
35-
condition = node[i]["if"]
36-
if condition not in conditions:
37-
# already combined it, so drop the repeat instance
38-
to_drop.append(i)
39-
continue
40-
if len(conditions[condition]) > 1:
41-
new_then = []
42-
new_else = []
43-
for sub_then, sub_else in conditions[node[i]["if"]]:
44-
if isinstance(sub_then, list):
45-
new_then.extend(sub_then)
46-
else:
47-
assert sub_then is not None
48-
new_then.append(sub_then)
49-
if isinstance(sub_else, list):
50-
new_else.extend(sub_else)
51-
elif sub_else is not None:
52-
new_else.append(sub_else)
53-
node[i]["then"] = new_then
54-
if new_else:
55-
# TODO: preserve inline "else" instead of converting it to a list?
56-
node[i]["else"] = new_else
57-
else:
58-
assert "else" not in node[i]
59-
# remove it from the dict, so we don't output it again
60-
del conditions[condition]
61-
62-
# drop the repeated conditions
63-
for i in reversed(to_drop):
64-
del node[i]
92+
# iterate in reverse order, so we can remove elements on the fly
93+
# start at index 1, since we can only fold to the previous node
94+
for i in reversed(range(1, len(node))):
95+
node_cond = get_condition(node[i])
96+
prev_cond = get_condition(node[i - 1])
97+
if node_cond is None or prev_cond is None:
98+
continue
99+
100+
if is_same_condition(node_cond, prev_cond):
101+
fold_branch(node[i], node[i - 1], "then", "then")
102+
fold_branch(node[i], node[i - 1], "else", "else")
103+
del node[i]
104+
elif is_negated_condition(node_cond, prev_cond):
105+
fold_branch(node[i], node[i - 1], "then", "else")
106+
fold_branch(node[i], node[i - 1], "else", "then")
107+
del node[i]
65108

66109
# then we descend down the tree
67110
if isinstance(node, dict):

0 commit comments

Comments
 (0)