Skip to content

Commit 5637b46

Browse files
committed
Add support for multi-output recipes
1 parent 25d657f commit 5637b46

File tree

5 files changed

+122
-31
lines changed

5 files changed

+122
-31
lines changed

conda_build/_rattler_build/compat.py

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from conda.base.context import context
88
from rattler_build.progress import RichProgressCallback
99
from rattler_build.render import RenderConfig
10-
from rattler_build.stage0 import Stage0Recipe
10+
from rattler_build.stage0 import MultiOutputRecipe, Stage0Recipe
1111
from rattler_build.tool_config import PlatformConfig, ToolConfiguration
1212
from rattler_build.variant_config import VariantConfig
1313

@@ -91,8 +91,6 @@ def check_arguments_rattler(
9191
def process_recipes(
9292
recipes: list[str],
9393
variant_config: VariantConfig,
94-
render_config: RenderConfig,
95-
tool_config: ToolConfiguration,
9694
command: str,
9795
output_dir: str,
9896
channels: list[str],
@@ -101,6 +99,15 @@ def process_recipes(
10199
package_format: str,
102100
no_include_recipe: bool,
103101
debug: bool,
102+
target_platform: str,
103+
build_platform: str,
104+
host_platform: str,
105+
experimental: bool,
106+
extra_context: dict[str],
107+
test_strategy: str,
108+
skip_existing: bool,
109+
noarch_build_platform: str,
110+
channel_priority: str,
104111
) -> int:
105112
import yaml
106113

@@ -118,6 +125,32 @@ def process_recipes(
118125
f"Failed to process recipe {recipe_path}: {str(e)}"
119126
)
120127

128+
if isinstance(recipe, MultiOutputRecipe):
129+
for idx, output in enumerate(recipe.outputs, 1):
130+
output_dict = output.to_dict()
131+
if "staging" in output_dict:
132+
experimental = True
133+
134+
# common tool / platform / render configuration
135+
tool_config = ToolConfiguration(
136+
test_strategy=test_strategy,
137+
skip_existing=skip_existing,
138+
noarch_build_platform=noarch_build_platform,
139+
channel_priority=channel_priority,
140+
)
141+
142+
platform_config = PlatformConfig(
143+
target_platform=target_platform,
144+
build_platform=build_platform,
145+
host_platform=host_platform,
146+
experimental=experimental,
147+
)
148+
149+
render_config = RenderConfig(
150+
platform=platform_config,
151+
extra_context=extra_context,
152+
)
153+
121154
# render the recipe
122155
try:
123156
rendered = recipe.render(variant_config, render_config)
@@ -127,8 +160,9 @@ def process_recipes(
127160
)
128161

129162
if command == "render":
130-
data = rendered[0].recipe.to_dict()
131-
print(yaml.safe_dump(data, indent=2, sort_keys=False))
163+
for item in rendered:
164+
data = item.recipe.to_dict()
165+
print(yaml.safe_dump(data, indent=2, sort_keys=False))
132166
succeeded.append(recipe_path_str)
133167
continue
134168

@@ -195,6 +229,7 @@ def run_rattler(command: str, parsed_args: argparse.Namespace, config: Config) -
195229
output_dir: str = config.croot
196230
no_include_recipe: bool = False
197231
no_build_id: bool = False
232+
experimental: bool = False
198233
package_format: str | None = None
199234
debug: bool = False
200235
channels: list[str] = []
@@ -296,33 +331,15 @@ def run_rattler(command: str, parsed_args: argparse.Namespace, config: Config) -
296331
from ..variants import find_config_files
297332

298333
# configure variant
299-
for variant in config_files:
300-
variant_config = VariantConfig.from_file(variant)
301-
302-
# common tool / platform / render configuration
303-
tool_config = ToolConfiguration(
304-
test_strategy=test_strategy,
305-
skip_existing=skip_existing,
306-
noarch_build_platform=noarch_build_platform,
307-
channel_priority=channel_priority,
308-
)
309-
310-
platform_config = PlatformConfig(
311-
target_platform=target_platform,
312-
build_platform=build_platform,
313-
host_platform=host_platform,
314-
)
315-
316-
render_config = RenderConfig(
317-
platform=platform_config,
318-
extra_context=extra_context,
319-
)
334+
if config_files:
335+
for variant in config_files:
336+
variant_config = VariantConfig.from_file(variant)
337+
else:
338+
variant_config = VariantConfig()
320339

321340
return process_recipes(
322341
recipes=recipes,
323342
variant_config=variant_config,
324-
render_config=render_config,
325-
tool_config=tool_config,
326343
command=command,
327344
output_dir=output_dir,
328345
channels=channels,
@@ -331,4 +348,13 @@ def run_rattler(command: str, parsed_args: argparse.Namespace, config: Config) -
331348
package_format=package_format,
332349
no_include_recipe=no_include_recipe,
333350
debug=debug,
351+
target_platform=target_platform,
352+
build_platform=build_platform,
353+
host_platform=host_platform,
354+
experimental=experimental,
355+
extra_context=extra_context,
356+
test_strategy=test_strategy,
357+
skip_existing=skip_existing,
358+
noarch_build_platform=noarch_build_platform,
359+
channel_priority=channel_priority,
334360
)

recipe/meta.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ requirements:
5050
- pkginfo
5151
- psutil
5252
- py-lief
53-
- py-rattler-build >=0.58.0
53+
- py-rattler-build >=0.58.2
5454
- python
5555
- python-libarchive-c
5656
- pytz

tests/cli/test_main_build.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -550,9 +550,23 @@ def test_build_with_empty_channel_fails(empty_channel: Path) -> None:
550550
)
551551

552552

553-
def test_build_with_v1_recipe() -> None:
553+
def test_build_v1_recipe() -> None:
554554
"""Test building a v1 recipe"""
555555
recipe = os.path.join(metadata_dir, "..", "variants", "32_v1_recipe")
556556

557557
args = [recipe]
558558
assert main_build.execute(args) == 0
559+
560+
561+
def test_build_v1_recipe_multi_output(testing_workdir: str) -> None:
562+
"""Test building a multi-output v1 recipe"""
563+
recipe = os.path.join(metadata_dir, "..", "variants", "33_v1_recipe_multi_output")
564+
565+
out = Path(testing_workdir, "out")
566+
out.mkdir(parents=True)
567+
568+
args = [recipe, "--output-folder", str(out)]
569+
assert main_build.execute(args) == 0
570+
571+
conda_packages = list(out.rglob("*.conda"))
572+
assert len(conda_packages) == 2

tests/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
beautifulsoup4
22
chardet
33
conda >=25.11.0
4-
conda-forge::py-rattler-build >=0.58.0 # v1 recipe support
4+
conda-forge::py-rattler-build >=0.58.2 # v1 recipe support
55
conda-index >=0.4.0
66
conda-libmamba-solver >=25.11.0 # includes fix for CondaSolver deprecation warnings
77
conda-package-handling >=2.2.0
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
schema_version: 1
2+
3+
context:
4+
name: myproject
5+
version: "2.0.0"
6+
7+
recipe:
8+
version: ${{ version }}
9+
10+
outputs:
11+
# First output: The library
12+
- package:
13+
name: ${{ name }}-lib
14+
build:
15+
script:
16+
interpreter: python
17+
content: |
18+
import os
19+
from pathlib import Path
20+
21+
prefix = Path(os.environ["PREFIX"])
22+
lib_dir = prefix / "lib" / "python"
23+
lib_dir.mkdir(parents=True, exist_ok=True)
24+
25+
(lib_dir / "myproject_lib.py").write_text('VERSION = "2.0.0"')
26+
print(f"Created library at {lib_dir}")
27+
requirements:
28+
build:
29+
- python
30+
31+
# Second output: Uses the library as a host dependency
32+
- package:
33+
name: ${{ name }}-tools
34+
build:
35+
script:
36+
interpreter: python
37+
content: |
38+
import os
39+
from pathlib import Path
40+
41+
prefix = Path(os.environ["PREFIX"])
42+
43+
# Read and print the lib file (installed as host dependency)
44+
lib_file = prefix / "lib" / "python" / "myproject_lib.py"
45+
print(f"Reading library from: {lib_file}")
46+
print(lib_file.read_text())
47+
requirements:
48+
build:
49+
- python
50+
host:
51+
- ${{ name }}-lib

0 commit comments

Comments
 (0)