Multi-Output Recipes#
Overview#
A multi-output recipe produces multiple packages from a single recipe.yaml file.
Multi-output recipes are used when a single source tree produces multiple packages.
Common examples include splitting a C library into runtime, development, and language binding packages.
In a multi-output recipe each output is a self-contained recipe with its own package, build, requirements, tests, and about sections.
Basic Structure#
A minimal multi-output recipe with two package outputs:
recipe:
name: my-project
version: 1.0.0
source:
- url: https://example.com/my-project-1.0.0.tar.gz
sha256: abcdef...
outputs:
- package:
name: my-lib
build:
script:
- mkdir -p $PREFIX/lib
- cp libfoo.so $PREFIX/lib/
requirements:
run:
- some-runtime-dep
tests:
- script:
- test -f $PREFIX/lib/libfoo.so
- package:
name: my-tool
requirements:
run:
- ${{ pin_subpackage('my-lib', upper_bound='x.x') }}
build:
script:
- mkdir -p $PREFIX/bin
- cp my-tool $PREFIX/bin/
tests:
- script:
- my-tool --version
Connecting outputs with pin_subpackage#
Use pin_subpackage() to create a dependency from one output to another within
the same recipe:
With exact=True, the dependent package is injected as a variant. This means if
my-lib has two variant builds (e.g., against openssl 1 and openssl 3),
my-tool will also be built twice — once for each my-lib variant:
outputs:
- package:
name: my-lib
requirements:
host:
- openssl
- package:
name: my-tool
requirements:
run:
- ${{ pin_subpackage('my-lib', exact=True) }}
Topological sorting#
Outputs are topologically sorted based on their dependency relationships.
If one output depends on another (for example via pin_subpackage or by
referencing its name in requirements), the dependent output will be built
after its dependency.
The order of entries in outputs: does not affect build order.
Inheritance Behavior#
Top-level sections are inherited by all outputs unless the output overrides them:
| Section | Inherited? | Notes |
|---|---|---|
source |
Yes | Available to all outputs |
build |
Yes | number, script, etc. can be overridden |
about |
Yes | Per-output about overrides top-level |
tests |
Yes | Top-level tests are prepended to output tests |
extra |
Yes | Merged into each output |
context |
Yes | Variables are available during rendering of all outputs |
The package and requirements keys are forbidden at the top level in
multi-output recipes. Each output must define its own package: and
requirements: sections.
recipe:
name: my-project
version: 2.0.0
# These are inherited by all outputs:
about:
license: MIT
homepage: https://example.com
build:
number: 0
outputs:
- package:
name: output-a
# version defaults to 2.0.0 from recipe.version
# inherits about.license, about.homepage, build.number
- package:
name: output-b
version: 3.0.0 # overrides recipe.version
about:
license: Apache-2.0 # overrides inherited license
Staging Outputs (Experimental)#
Warning
Staging outputs require the --experimental flag:
rattler-build build --experimental -r recipe.yaml
A staging output compiles code once and caches the result. Package outputs then inherit from the staging cache and select subsets of the built files. This avoids rebuilding the same source for each package.
Basic staging example#
recipe:
name: mylib
version: 2.0.0
source:
- url: https://example.com/mylib-2.0.0.tar.gz
sha256: abcdef...
outputs:
# Staging: builds the library once
- staging:
name: mylib-build
requirements:
build:
- ${{ compiler('c') }}
- cmake
- ninja
build:
script:
- cmake -GNinja -DCMAKE_INSTALL_PREFIX=$PREFIX .
- ninja install
# Package: runtime library
- package:
name: libmylib
inherit: mylib-build
build:
files:
- lib/*
requirements:
run_exports:
- ${{ pin_subpackage('libmylib') }}
# Package: development headers
- package:
name: mylib-headers
inherit: mylib-build
build:
files:
- include/*
requirements:
run:
- ${{ pin_subpackage('libmylib', exact=True) }}
The inherit key#
The inherit: key specifies which staging cache a package output inherits from.
Short form — inherit all files and run exports:
Structured form — control run export inheritance:
Run exports from staging#
Run exports from the staging output's build/host dependencies are propagated to
inheriting package outputs by default. Use run_exports: false in the structured
inherit: form to suppress this.
If the staging output has ignore_run_exports, those filters apply at the
staging level. If an inheriting output also ignores run exports, both filters
apply.
Source handling with staging#
The top-level source: is available to the staging output and all package
outputs. Each output restores the (dirty) source from the staging directory, so
outputs can continue from where the staging build left off (e.g., running
cmake install after staging already ran cmake build).
Outputs can add additional sources on top of the staging source:
outputs:
- package:
name: py-mylib
inherit: mylib-build
source:
- path: ../README.md
file_name: extra_file.md
File Selection#
In multi-output recipes, use build: files: to select which files from the
prefix end up in each package. This is especially important with staging outputs
to avoid packaging the same files in multiple packages.
For more advanced selection with include/exclude patterns, see the build options documentation.