1010
1111from rattler_build .stage0 import MultiOutputRecipe , SingleOutputRecipe
1212
13+ # Import for type hints only - avoid circular import
14+ if TYPE_CHECKING :
15+ from rattler_build .tool_config import ToolConfiguration
16+
17+ # Type for context values - can be strings, numbers, bools, or lists
18+ ContextValue = Union [str , int , float , bool , List [Union [str , int , float , bool ]]]
19+
1320# Try to import TypeAlias for better type hint support
1421try :
1522 from typing import TypeAlias
1825
1926if TYPE_CHECKING :
2027 from rattler_build .variant_config import VariantConfig
28+
2129 # For type checking, use Any placeholders
2230 _RenderConfig = Any
2331 _RenderedVariant = Any
@@ -166,7 +174,7 @@ def __init__(
166174 recipe_path = recipe_path ,
167175 )
168176
169- def set_context (self , key : str , value : Any ) -> None :
177+ def set_context (self , key : str , value : ContextValue ) -> None :
170178 """Add an extra context variable for Jinja rendering.
171179
172180 Args:
@@ -175,7 +183,7 @@ def set_context(self, key: str, value: Any) -> None:
175183 """
176184 self ._config .set_context (key , value )
177185
178- def get_context (self , key : str ) -> Optional [Any ]:
186+ def get_context (self , key : str ) -> Optional [ContextValue ]:
179187 """Get an extra context variable.
180188
181189 Args:
@@ -186,7 +194,7 @@ def get_context(self, key: str) -> Optional[Any]:
186194 """
187195 return self ._config .get_context (key )
188196
189- def get_all_context (self ) -> Dict [str , Any ]:
197+ def get_all_context (self ) -> Dict [str , ContextValue ]:
190198 """Get all extra context variables as a dictionary."""
191199 return self ._config .get_all_context ()
192200
@@ -263,7 +271,7 @@ class RenderedVariant:
263271 ... print(f"Build string: {variant.recipe().build().string()}")
264272 """
265273
266- def __init__ (self , inner : Any ):
274+ def __init__ (self , inner : _RenderedVariant ):
267275 """Create a RenderedVariant from the Rust object."""
268276 self ._inner = inner
269277
@@ -313,6 +321,48 @@ def pin_subpackages(self) -> Dict[str, PinSubpackageInfo]:
313321 inner_dict = self ._inner .pin_subpackages ()
314322 return {name : PinSubpackageInfo (info ) for name , info in inner_dict .items ()}
315323
324+ def run_build (
325+ self ,
326+ tool_config : Optional ["ToolConfiguration" ] = None ,
327+ output_dir : Optional [Union [str , Path ]] = None ,
328+ channel : Optional [List [str ]] = None ,
329+ ** kwargs : Any ,
330+ ) -> None :
331+ """Build this rendered variant.
332+
333+ This method builds a single rendered variant directly without needing
334+ to go back through the Stage0 recipe.
335+
336+ Args:
337+ tool_config: Optional ToolConfiguration to use for the build.
338+ output_dir: Directory to store the built package. Defaults to current directory.
339+ channel: List of channels to use for resolving dependencies.
340+ **kwargs: Additional arguments passed to build (e.g., keep_build, test, etc.)
341+
342+ Example:
343+ >>> from rattler_build.stage0 import Recipe
344+ >>> from rattler_build.variant_config import VariantConfig
345+ >>> from rattler_build.render import render_recipe
346+ >>>
347+ >>> recipe = Recipe.from_yaml(yaml_string)
348+ >>> rendered = render_recipe(recipe, VariantConfig())
349+ >>> # Build just the first variant
350+ >>> rendered[0].run_build(output_dir="./output")
351+ """
352+ from . import rattler_build as _rb
353+
354+ # Extract the inner ToolConfiguration if provided
355+ tool_config_inner = tool_config ._inner if (tool_config and hasattr (tool_config , "_inner" )) else tool_config
356+
357+ # Build this single variant
358+ _rb .build_from_rendered_variants_py (
359+ rendered_variants = [self ._inner ],
360+ tool_config = tool_config_inner ,
361+ output_dir = Path (output_dir ) if output_dir else None ,
362+ channel = channel ,
363+ ** kwargs ,
364+ )
365+
316366 def __repr__ (self ) -> str :
317367 return repr (self ._inner )
318368
@@ -390,7 +440,7 @@ def render_recipe(
390440 if isinstance (recipe , Path ):
391441 # Definitely a file path
392442 parsed = Recipe .from_file (recipe )
393- elif recipe .endswith (' .yaml' ) or recipe .endswith (' .yml' ) or '/' in recipe or ' \\ ' in recipe :
443+ elif recipe .endswith (" .yaml" ) or recipe .endswith (" .yml" ) or "/" in recipe or " \\ " in recipe :
394444 # String that looks like a file path
395445 parsed = Recipe .from_file (recipe )
396446 else :
@@ -408,7 +458,12 @@ def render_recipe(
408458 variant_config = VC .from_file (variant_config )
409459 else :
410460 # Check if it's a file path or YAML string
411- if variant_config .endswith ('.yaml' ) or variant_config .endswith ('.yml' ) or '/' in variant_config or '\\ ' in variant_config :
461+ if (
462+ variant_config .endswith (".yaml" )
463+ or variant_config .endswith (".yml" )
464+ or "/" in variant_config
465+ or "\\ " in variant_config
466+ ):
412467 variant_config = VC .from_file (variant_config )
413468 else :
414469 variant_config = VC .from_yaml (variant_config )
@@ -428,10 +483,66 @@ def render_recipe(
428483 return all_rendered
429484
430485
486+ def build_rendered_variants (
487+ rendered_variants : List [RenderedVariant ],
488+ tool_config : Optional ["ToolConfiguration" ] = None ,
489+ output_dir : Optional [Union [str , Path ]] = None ,
490+ channel : Optional [List [str ]] = None ,
491+ ** kwargs : Any ,
492+ ) -> None :
493+ """Build multiple rendered variants.
494+
495+ This is a convenience function for building multiple rendered variants
496+ in one call, useful when you want to build all variants from a recipe.
497+
498+ Args:
499+ rendered_variants: List of RenderedVariant objects to build
500+ tool_config: Optional ToolConfiguration to use for the build.
501+ output_dir: Directory to store the built packages. Defaults to current directory.
502+ channel: List of channels to use for resolving dependencies.
503+ **kwargs: Additional arguments passed to build (e.g., keep_build, test, etc.)
504+
505+ Example:
506+ >>> from rattler_build.stage0 import Recipe
507+ >>> from rattler_build.variant_config import VariantConfig
508+ >>> from rattler_build.render import render_recipe, build_rendered_variants
509+ >>>
510+ >>> # Parse and render recipe
511+ >>> recipe = Recipe.from_yaml(yaml_string)
512+ >>> variant_config = VariantConfig.from_yaml('''
513+ ... python:
514+ ... - "3.9"
515+ ... - "3.10"
516+ ... - "3.11"
517+ ... ''')
518+ >>> rendered = render_recipe(recipe, variant_config)
519+ >>>
520+ >>> # Build all variants at once
521+ >>> build_rendered_variants(rendered, output_dir="./output")
522+ >>>
523+ >>> # Or build a subset
524+ >>> build_rendered_variants(rendered[:2], output_dir="./output")
525+ """
526+ from . import rattler_build as _rb
527+
528+ # Extract the inner ToolConfiguration if provided
529+ tool_config_inner = tool_config ._inner if (tool_config and hasattr (tool_config , "_inner" )) else tool_config
530+
531+ # Build all variants
532+ _rb .build_from_rendered_variants_py (
533+ rendered_variants = [v ._inner for v in rendered_variants ],
534+ tool_config = tool_config_inner ,
535+ output_dir = Path (output_dir ) if output_dir else None ,
536+ channel = channel ,
537+ ** kwargs ,
538+ )
539+
540+
431541__all__ = [
432542 "RenderConfig" ,
433543 "RenderedVariant" ,
434544 "HashInfo" ,
435545 "PinSubpackageInfo" ,
436546 "render_recipe" ,
547+ "build_rendered_variants" ,
437548]
0 commit comments