Skip to content
Merged
Changes from 13 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b527b4c
add cep for the v1 recipe cache output
wolfv Nov 27, 2024
aea349c
update cep
wolfv Feb 5, 2025
558c784
improve the CEP
wolfv Feb 6, 2025
8370b54
update cep
wolfv Jun 18, 2025
540cb21
update cep
wolfv Sep 26, 2025
d144873
more corrections
wolfv Sep 26, 2025
9b650ae
Merge branch 'main' into cache-output
wolfv Feb 17, 2026
1e827a8
Replace references to old `cache` key with `staging`
Hofer-Julian Mar 10, 2026
60bcb45
fix pre-commit
wolfv Mar 25, 2026
4ab52ba
add missing file
wolfv Mar 26, 2026
1ee3a6f
Merge branch 'main' into cache-output
wolfv Mar 26, 2026
c3674ed
fix: pre-commit
Hofer-Julian Mar 26, 2026
05f632e
Apply suggestions from code review
Hofer-Julian Mar 27, 2026
7b7defb
Formalize and clarify the CEP
mgorny Mar 31, 2026
ea98fad
Include/exclude rules
mgorny Mar 31, 2026
47dd911
Update cep-XXXX.md
mgorny Mar 31, 2026
bf4f349
Update cep-XXXX.md
mgorny Mar 31, 2026
beb5519
Update cep-XXXX.md
mgorny Mar 31, 2026
5ae7fa9
Clarify `inherit.from` identifier
mgorny Mar 31, 2026
a27e0d6
Reverse the "what can inherit what" rule
mgorny Mar 31, 2026
0669366
Apply suggestions from code review
mgorny Mar 31, 2026
60c70e3
Merge pull request #14 from mgorny/cache-output-upd
wolfv Apr 1, 2026
90b6862
Follow more suggestions from @jaimergp's comments
mgorny Apr 1, 2026
431ae57
Add more missing bits
mgorny Apr 1, 2026
5ee76d3
Merge pull request #15 from mgorny/cep-staging-pt2
wolfv Apr 1, 2026
463201c
Add missing RFC reference
mgorny Apr 1, 2026
4eb28e2
Merge pull request #16 from mgorny/cep-staging-pt3
wolfv Apr 1, 2026
7740970
Merge branch 'main' of github.com:conda/ceps into pr/wolfv/102
jaimergp Apr 17, 2026
2c5174e
Mint as CEP 41, add changelog
jaimergp Apr 17, 2026
b14cb51
Adjust changelog syntax
jaimergp Apr 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 163 additions & 0 deletions cep-XXXX.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# CEP XXXX - The staging output in v1 recipes / rattler-build

<table>
<tr><td> Title </td><td> The staging output in v1 recipes / rattler-build </td>
<tr><td> Status </td><td> In Discussion </td></tr>
<tr><td> Author(s) </td><td> Wolf Vollprecht &ltw.vollprecht@gmail.com&gt; </td></tr>
<tr><td> Created </td><td> Nov 27, 2024</td></tr>
<tr><td> Updated </td><td> </td></tr>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<tr><td> Updated </td><td> </td></tr>
<tr><td> Updated </td><td> Mar 26, 2026 </td></tr>

<tr><td> Discussion </td><td> </td></tr>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<tr><td> Discussion </td><td> </td></tr>
<tr><td> Discussion </td><td> https://github.com/conda/ceps/pull/102 </td></tr>

<tr><td> Implementation </td><td> rattler-build </td></tr>
</table>

## Abstract

This CEP aims to define the staging output for v1 multi-output recipes.

## Background

Sometimes it is very useful to build some code once, and then split it into multiple build artifacts (such as shared library, header files, etc.). For this reason, `conda-build` has a special, implicit top-level build.

There are many downsides to the behavior of `conda-build`: it's very implicit, hard to understand and hard to debug (for example, if an output is defined with the same name as the top-level recipe, this output will get the same requirements attached as the top-level).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
There are many downsides to the behavior of `conda-build`: it's very implicit, hard to understand and hard to debug (for example, if an output is defined with the same name as the top-level recipe, this output will get the same requirements attached as the top-level).
There are many downsides to the behavior of `conda-build`: it's too implicit, hard to understand, and hard to debug. For example, if an output is defined with the same name as the top-level recipe, this output will get the same requirements attached as the top-level.


For the v1 spec we are attempting to formalize the workings of the "top-level" build. For this, we introduce a new `staging` output, that has the same values as a regular output, but does not produce a package artifact. Instead, we keep changes from the `staging` output in a temporary location on the filesystem and restore from this checkpoint when building other outputs that _inherit_ from this `staging` cache.

## Specification

A recipe can have zero or more staging outputs.

A staging output looks as follows:
Copy link
Copy Markdown
Member

@jaimergp jaimergp Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be encoded in a more accurate way. It's not clear what "looks as" means. I recommend you use RFC language (MUST, MAY, SHOULD, etc) for the required keys. You may build on existing CEPs to simplify language, but the specification should be as specific (sorry to be redundant) as possible.

e.g.:

As per CEP XXX, outputs MUST be a list of dictionaries, each representing an artifact. We extend that schema with an alternative dictionary type, which MUST comply to this schema:

  • staging MUST be a required key, whose value follows the same schema as package in CEP XX`.
  • Everything else follows the schema described in CEP X, Y and Z.

The existing package outputs MAY replace the build key with a inherit key, whose value MUST comply to this schema:
...


```yaml
outputs:
- staging:
name: foo-cache # required, string, follows rules of `PackageName`

source:
- url: https://foo.bar/source.tar.bz
sha256: ...

requirements:
build:
- ${{ compiler('c') }}
- cmake
- ninja
host:
- libzlib
- libfoo
# the `run` and `run_constraints` sections are not allowed here
ignore_run_exports:
by_name:
- libfoo

build:
# only the script key is allowed here
script: build_cache.sh

- package:
name: foo-headers
version: "1.0.0"

# long form of newly added `inherit` key
inherit:
from: foo-cache
run_exports: false

build:
files:
- include/

- package:
name: foo
version: "1.0.0"

# short form, inherits run exports by default
inherit: foo-cache

# will bundle everything else except files already present in `foo-headers`
requirements:
host:
- ${{ pin_subpackage("foo-headers", exact=True) }}
```

> [!WARNING]
> When using `outputs` we are going to remove the implicit `build.script` pointing to `script.sh`. Going forward, the script name / content has to be set explicitly.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't sound like it belongs in the specification of the CEP. Perhaps in further work, if anywhere. I'd just remove it.


When computing variants and used variables, rattler-build looks at the union of a given `output` and the `staging` cache. That means, even if an output does not define any requirements, the `staging` cache would still add a variant for the `c_compiler`.

When rattler-build executes the recipe, it will start by building the `staging` cache outputs that are appropriate for the current variant. This is computed by looking at all "used-variables" for the `staging` cache output and computing a "hash" for it. The build itself is executed in the same way as any other build.

The variant keys that are injected at build time is the subset used by the `staging` output.

When the `staging` build is done, the newly created files are moved outside of the `host-prefix`. Post-processing is not performed on the files beyond memoizing what files contain the `$PREFIX` (which is later replaced in binaries and text files with the actual build-prefix).

The staging output restores files that were added to the host environment (`$PREFIX`) and the "dirty" source directory, including any changes that were made by the build script. They are cloned to a special staging location from which they are restored.

If the `staging` build and the package build are not running in the same exact location, the path leading up to the work dir / host prefix needs to be replaced in the `staging` artifacts (for example when running time-stamped builds in folders such as `/folder/to/bld/libfoo_1745399500/{work_dir,h_env_...}`).

When a package output adds a `source` and inherits from a staging output, care must be taken by the user to not clobber files (e.g. by using `target_directory`). The build program should warn if files are overwritten in the work dir.

New files in the prefix (from the staging output) can be used in the outputs with the `build.files` key:

```yaml
outputs:
- staging:
name: foo-cache

- package:
name: foo-headers
version: "1.0.0"

inherit:
from: foo-cache
run_exports: false

build:
files:
- include/**

- package:
name: libfoo
version: "1.0.0"

inherit: foo-cache

build:
files:
- lib/**

- package:
name: foo-devel
version: "1.0.0"

inherit: foo-cache

requirements:
run:
- ${{ pin_subpackage("libfoo") }}
- ${{ pin_subpackage("foo-headers") }}
```

The glob list syntax can also be a dictionary with `include / exclude` keys, e.g.

```yaml
files:
include:
- include/**
exclude:
- lib/**
```

## The `inherit` key and the logic of inheritance

The `inherit` key is used to inherit from a staging output. We also generalize the logic to "top-level" inheritance, which is what happens when the inherit key is set to `null`.

Both, `staging` and `package` outputs can inherit, however, a `staging` cannot inherit from a `package`.

When inheriting, values from `build` and `about` are deeply merged with the values from the staging output, except for the value of `build.script`.

Requirements are not inherited, however, `run_exports` are. The inheritance of `run_exports` can be disabled by setting the `run_exports` key to `false` in the `inherit` map. To ignore certain run-exports they can be either ignored in the staging output or in the package output (both follow the same rules).

### Top-level inheritance

Inheriting from the top-level is a special case of regular "staging" inheritance. If the output does not specify any `inherit` key or explicitly sets `inherit: null` then we inherit from the top-level and apply `recipe.version`, `source`, `build` and `about` from the top-level to each output. In the case of top-level inheritance, requirements and build script are forbidden and thus ignored. This unifies the rules for both staging and top-level.