-
Notifications
You must be signed in to change notification settings - Fork 31
CEP XXXX: Improving dependency export infrastructure #129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
pre-commit.ci autofix |
for more information, see https://pre-commit.ci
|
pre-commit.ci autofix |
|
pre-commit.ci autofix |
Wolf mentioned in private that we don't necessarily have to go to a v2 schema over this, because despite being renamed semantically, the new keys would just be extending the v1 schema, not breaking it. Of course, we'd then have to mandate mutual exclusivity between The same approach (consider the new keys if present, error if not mutually exclusive with the old way) could even by used by conda-build to support the CEP1, which would be great because a lot of our compiler feedstocks that would need this the most are not necessarily ready to be ported to v1 yet. :) If people are in agreement over this approach, I can try to write up specification for it. In any case, I think this is in a good enough state to ask for a first round of feedback; I'd be very curious to hear the thoughts of Footnotes
|
|
I read the whole thread and think the proposed solution is a good addition. (From the perspective of writing recipes.) |
Thanks a lot for the feedback @cbouss, I appreciate you taking the time! That reminds me that I wanted to post a comment1 from a recent discussion on zulip which covers an open point of the design here: how to deal with Concretely, if we have # output: a_complicated_package
requirements:
exports:
build_to_host:
- some_package_with_a_run_exportand # output: some_package_with_a_run_export
requirements:
exports:
host_to_run:
- the_export_in_questionand then consume it # output: mypkg
requirements:
build:
- a_complicated_package
host:
# from a_complicated_package's build_to_host
# - some_package_with_a_run_export
run:
# ...should the run-export of some_package_with_a_run_export get triggered here?!
# - the_export_in_questionthe question is whether, why and how we trigger an export of a package that's not explicitly named in the recipe. Obviously this is quite an impactful question, and we certainly do not want to trigger all exports of packages that happen to transitively make it into an environment. On zulip, I discussed this in the context of how injected exports could/would work. Despite not wanting to cover self-exports in this CEP, I've used the motivating use-case for them as an example here, because it illustrates the situation more concretely than just some abstract arithmetic between environments. As I note at the end, this CEP could go either way (i.e. whether Let's take the clearest case that I'm aware of that's been in contention around self-exports vs. conditional dependencies. I'm rephrasing here for consistent language: In other words, if Let's look at the recipes: # output: libA
requirements:
[...] # regular build: / host: / run:
exports:
host_to_run:
- ${{ pin_compatible("libA") }}That's the easy part, which we already know as # output: libB
requirements:
[...] # regular build:
host:
- libA
run:
# regular run-export from libA
# - libA >={{ver_A}},<{{next_ver_A}}
# additional requirement when compiling against libB; same effect as host_to_host
- ${{ pin_compatible("libA") }}; if env.HOST # how to spell this is TBD!
exports:
host_to_run:
- ${{ pin_compatible("libB") }}Now, whether as a conditional dependency or a # output: mypkg
requirements:
[...] # regular build:
host:
- libB
# injected!
# - libA # matches libA-constraint of libB
run:
# regular run-export from libB
# - libB >={{ver_B}},<{{next_ver_B}}
# run-export from injected libA!
# - libA >={{ver_A}},<{{next_ver_A}}
- some_regular_depIn my mental model, the process is as follows:
The "or injected" part is the most open aspect of the design space, but I believe the example with Coming back to your example [for automatically injecting # mycompiler
requirements:
run:
- ${{ stdlib("c") }}
# conditional dependency when in build, which then participates in run-exports; same as build_to_build
- ${{ stdlib("c") }}; if env.BUILDThis would work with the "or injected" scheme above, but as you can see, it's neither very elegant nor obvious why you'd have to repeat the same dependency for IMO that's because conditional dependencies and self-exports are features that are only truly needed for niche cases; any feature can be misused, so any additional expressivity needs to be guarded (e.g. by the linter etc.). As a consequence, I'm convinced that we shouldn't abuse this mechanism for something which every compiled recipe needs. In other words, This comment is already too long, so I'll just note that a more restricted form of [this PR] without the "or injected" is possible, and that this would already solve a lot of the cases where we need host-exports, e.g. the ABI of C++/Fortran modules. It's only when we get to stuff like the Footnotes
|
One thing that gnawed at me since posting that comment on zulip is that I don't like the relationship "conditional dependencies participate in run-exports", which seems too magical, and is by far not explicit enough in the recipe IMO. However, I just had an idea that might solve this case: we can use # output: libB
requirements:
exports:
host_to_run:
- ${{ pin_compatible("libB") }}
host_to_host:
# implemented not as an export, but as a conditional dependency of libB when it appears in `host:`!
- ${{ pin_compatible("libA") }}That would IMO be the best of both worlds: explicit syntax for the most unusual case, as well as a sane environment resolution process, and conditional dependencies don't have to be imbued with some magical pixie dust (pun intended 😉). This would also simplify the rule I had posited above to "we apply exports from packages that are either explicitly named in the environment, or have been injected via exports This would then give the following dependencies between the different proposals ---
config:
securityLevel: loose
---
flowchart TB
this["this CEP"]-->se["self exports CEP"]
cond["conditional dependencies CEP"]-->se
|
|
Nice that we are finding paths forward! Is this similar to the solution I proposed in #129 (comment), or does it differ in some way? If that’s the case, can you elaborate how it’s different? |
Glad to hear it. It's not for lack of wanting to solve the issue that I had descoped self-exports; now I'm beginning to see a path that allows the various pieces to work together in a non-hacky way (where before I couldn't see it at all).
It's different in that we do not have to add a condition like " I've been getting down into the nitty gritty details at least one level deeper (e.g. @jaimergp opened another rabbit hole under my feet about the mechanics of |
|
I've written an update based on the various discussions. Since I'm trying to design things holistically (despite the fact that I want to scope this CEP to a manageable size without external dependencies), I've added a draft of the "self-exports" CEP for now. This goes into much more detail about how things should work in practice. I had a long discussion with @baszalmstra about the list of concrete steps, but I'll be the first to admit that I'm not very familiar with that part of the process, and any mistakes are almost certainly my own. The procedure for self-exports is a superset of steps compared to the "regular" cross-exports, so describing the more complicated workflow should immediately show how the simpler procedure works (i.e. by dropping the steps marked The key point relevant for self-exports that requires conditional exports (and avoids multiple solves) is that there is a necessary pre-processing step between fetching the package metadata and feeding it to the SAT solver. This preprocessing step is where we can apply self-exports (and any matching In fact, conditional dependencies as implemented by resolvo are way more powerful than what we need here. Resolvo can even handle conditional dependencies that depend dynamically on other packages in the resolution, whereas everything we need here is statically known (i.e. "are we solving for the
See 2f3d23f (though I recommend reading the updates to the main exports CEP first) |
First draft after discussion in #77. Does not contain (much) specification yet, because I'm unsure how to go about changing the schema of v1 recipes (does it need a bump in the schema version, or do we specify build tools must translate between them?), and how to deal with the repodata side of things. This is my first CEP, please excuse my lack of experience with a lot of the underlying details.
Help on these questions would be much appreciated! I decided to write up the design in more comprehensive form than originally in this comment though, in order to hopefully facilitate more effective discussion of how to solve the transition issues posed by the new design.
Closes #77 (eventually)