feat: Add Support for Rattler-Build Build Backend - #1127
Conversation
…d_recipes in tests
…ls into rattler_support2
…raph.build as methods of MetaOrRattler
utils.parallel_iter was called on a function which forced it to pickle rattler_build.VariantConfig, which resulted in failure to build
…ler_args; minor fixes
Accidentally added rattler-build args instead of conda-build args prior to this.
In single_build, the config must be loaded, otherwise utils.RepoData can't be instantiated. Also the recipe path needed to be converted to a utils.RecipePath.
|
I have done my best to merge with the master branch but as the two were quite divergent it is possible I missed something and caused breaking changes. I will not have time to work on this PR until next week and I didn't get the chance to test the merged code locally yet so please be patient. But if you want you can start reviewing this PR regardless. I will answer you as soon as possible. |
dlaehnemann
left a comment
There was a problem hiding this comment.
Hey @seneschall , @johanneskoester just pointed me here. So I had a quick glance to get a first idea of what you are up to, and left some random comments. And I figured I'd point to some stuff that I'm aware of, in case you have not seen those.
One is the recent linting changes, that already moved towards better support of multiple outputs per recipe, for which I already looked at the new v1 format and tried to make the linting changes support that new format, while also still supporting the meta.yaml format. Those changes are here, if you want to have a quick look:
#1086
So if you need input on updating the linting setup, let me know. I've also recently reworked this a bit, to hopefully make it easier to add and adapt linting test cases.
Generally, for the linting updates, an important reference for me was the section on multiple output recipes recipes in the rattler-build docs, because that was the aim.
Otherwise, during that work I also noticed that conda-forge already implements rattler-build support, so that might be a good source of inspiration. I only stumbled upon the respective linting changes there, but you can probably find respective changes for the build infrastructure:
https://github.com/conda-forge/conda-smithy/blob/b23bf94817f619c47263009545ded147d3e06844/conda_smithy/linter/lints.py#L13
Also, this uses a dedicated package called rattler-build-conda-compat, so maybe this can also help here?
And finally, a general thought I already had while scanning the code: Wouldn't it make sense to have most changes in recipe.py, and fewer in utils.py? And maybe some structure with a generalized Recipe class, which basically just has methods to decide which subclass to return during parsing, and then something like v0Recipe and v1Recipe (or MetaRecipe and RecipeRecipe, or ...) which implement the details for each type of recipe?
| if test_only: | ||
| # testonly calls `conda-build --test` but expects it to work when pointing | ||
| # to a recipe with a `meta.yaml`. However, according to `conda-build` docs: | ||
| # "RECIPE_PATH argument must be a path to built package file". |
There was a problem hiding this comment.
Can we link to the respective docs here? I always find it useful to be able to look at the actual docs and read around in case I need more context to understand something.
There was a problem hiding this comment.
I got that from the output of conda-build --help. Would it be sufficient to link to the page below then? The actual command is a bit buried in the page:
https://docs.conda.io/projects/conda-build/en/stable/resources/commands/conda-build.html
There was a problem hiding this comment.
Actually this deviates from the output I get from running conda-build --test on version 26.3.0. The output says:
-t, --test Test package (assumes package is already built).
RECIPE_PATH argument must be a path to built package
file.
Whereas the page I linked above says:
-t, --test
Test package (assumes package is already built). RECIPE_DIR
argument can be either recipe directory, in which case source
download may be necessary to resolve package version, or path to
built package .tar.bz2 file, in which case no source is neces-
sary.
So maybe this is a recent change in behaviour and the online docs haven't been updated yet?
There was a problem hiding this comment.
Maybe just mention that this is from the conda-build --help command in your particular version 26.3.0 and the five the quote as you do. That should keep it traceable / reproducible.
| import conda_build.config | ||
| import conda_build.metadata as metadata |
There was a problem hiding this comment.
As there are multiple imports from conda_build here now, it might be cleaner to do something like import conda_build as cb (analogous to the rattler_build import below), and then refer to cb.config, cb.metadata and cb.api. This would also make the source of functions clearer while reading the code.
There was a problem hiding this comment.
Hm this doesn't seem to work. If I do this, my linter complains that conda_build doesn't have an attribute called metadata or an attribute called config. Apparently Python won't import submodules with this syntax?
There was a problem hiding this comment.
OK, I just learned something... 😅
Maybe just group them and make the imports somewhat systematic. For example, something like:
import conda_build as cb
import conda_build.config as cb_config
import conda_build.metadata as metadata
| ) -> None: | ||
| if meta is None and rattler is None: | ||
| raise ValueError( | ||
| f"Either meta and rattler must be set but both are None for recipe: {path.path.as_posix()}" |
There was a problem hiding this comment.
| f"Either meta and rattler must be set but both are None for recipe: {path.path.as_posix()}" | |
| f"Either meta or rattler must be set, but both are None for recipe: {path.path.as_posix()}" |
| if self.meta is not None: | ||
| return self.meta["package"]["name"] | ||
| elif self.rattler is not None: | ||
| return self.rattler[0]["package"]["name"] |
There was a problem hiding this comment.
If there are multiple packages in a v1 (rattler-build) recipe, shouldn't this return all of them? Just returning the first one seems arbitrary. So maybe self.rattler should just contain a single package at this point, or there should be some more specific handling of the indexing, here? Or am I misinterpreting what is being indexed over, and these are variants, not multiple outputs/packages?
There was a problem hiding this comment.
I did it this way because I assumed the list elements it to be variants which should all have the same name. But you are right the output could also be a list of rendered multi-output recipes (see https://rattler-build.prefix.dev/dev/py-rattler-build/tutorials/multi_output_and_staging/#example-1-multi-output-with-inter-output-dependencies).
So I suppose we should also differentiate between Multi- and Single-Output recipes when returning the package name. Perhaps this could be handled automatically via a unified Recipe class as you suggested above.
My question for that case would be how the Multi-Output recipes should be handled in building the DAG (see function build from graphy.py). If I see this correctly right now the graph.build function just assumes all dependencies from a given recipe are the dependencies of that single package which can be found in that recipe.
| # elif self.rattler is not None: | ||
| # requirements = self.rattler[0].get("requirements") |
There was a problem hiding this comment.
This is probably a left-over from a previous code iteration?
| # elif self.rattler is not None: | |
| # requirements = self.rattler[0].get("requirements") |
| else: | ||
| # this is just to appease linters. Due to __init__ this will never be called | ||
| raise ValueError( | ||
| f"Either meta and rattler must be set but both are None for recipe: {self.path.path.as_posix()}" |
There was a problem hiding this comment.
| f"Either meta and rattler must be set but both are None for recipe: {self.path.path.as_posix()}" | |
| f"Either meta or rattler must be set, but both are None for recipe: {self.path.path.as_posix()}" |
There was a problem hiding this comment.
Very impressive work, this is a massive change and required a lot of careful code inspection and thinking.
Can you please also add linting support for v1 recipes? I don't want that we develop our own linter for those. Instead, please add a dependency on conda-smithy and use their lintify_meta_yaml (I know, the name suggests v0 recipes, but it actually also supports v1 (rattler) format by setting recipe_version=1) see here.
Further, QC and tests (in part) still fail.
|
Okay, so to the broader points mentioned above:
|
|
Thanks for the explanations! I have not looked at the DAG / graph building code and what it does. But I have looked at the multi output recipes extensively. So to your question regarding this: Each of the outputs has its own But I can imagine that the building of the packages themselves gets interesting once they depend on each other like in the Multi-Output with Inter-Output Dependencies example you also linked above. But at least the example also gives clear code on how to handle this. |
This PR adds support for the rattler-build backend which uses the v1 recipe syntax. In order to enable this, I had to make changes in a lot of places in the code base since rattler-build handles a lot of things differently from conda-build. One important change I made e.g. was to introduce a
RecipePathclass which stores the build-backend for every recipe alongside the path that points to it. This way the rest of the code can automatically determine which build-backend to use for every given recipe based on whether the recipe folder contains ameta.yamlor arecipe.yaml.During these changes I also added type hints wherever possible in order to better understand the existing codebase and in order to catch type errors via the linter instead of at runtime. Wherever possible, I also changed string based paths to pathlib Path objects to both improve code readability by clearly marking strings that are supposed to be paths and to make the manipulation of paths safer and more idiomatic.
Also, I deprecated the
testonlyflag because from my understanding of the existing code, bioconda-utils expects to be able to callconda-build --teston the recipe directory. This definitely doesn't work forrattler-build test, as rattler-build expects to be passed a directory of already built packages, but from what I understandconda-builddoes the same, as per this excerpt from their help: "RECIPE_PATH argument must be a path to built package file". Also, there don't seem to be any existing test cases wheretestonlyis set toTrue. If I am mistaken, please feel free to revert this change.I ran all tests from
test/test_utils.pyandtest/test_recipe.pylocally, but I only tested for native builds and not for docker based builds. The parts of the code that handle the docker build process should thus be examined more closely. The tests all passed except fortest_multi_build. This one also fails on my machine for the version from themasterbranch, however. I also added a single test case,test/test_utils.py::test_rattler_recipe.Parts of the code where I was unsure about something or which should be improved, I have marked with
TODO (rb). If you have any questions about my code, don't hesitate to ask.To summarise:
testonlyflag, as it seems to be broken as far as I understand and it also isn't covered by any of the existing test cases