constructor
constructor copied to clipboard
Failures when "exclude"d packages cause inconsistent environments
I am using the Maxiconda example, and the installer is built fine with no errors reported. When the installer is run there is no error reported however the installation is incomplete. The package cache is created but no packages are installed. If I open "show details" during the installation I can see that the environment creation failed because one of the excluded packages is required by another package:
Collecting package metadata (current_repodata.json): ...working... done
Solving environment: ...working... failed
Collecting package metadata (repodata.json): ...working... done
Solving environment: ...working... failed
PackagesNotFoundError: The following packages are not available from current channels:
- matplotlib==3.0.0=py35hd159220_0 -> pyqt=5.9
Current channels:
- http://repo.anaconda.com/pkgs/main/win-64
- http://repo.anaconda.com/pkgs/main/noarch
You can see that the environment solution failed because matplotlib couldn't be installed due to the missing pyqt dependency (pyqt is excluded in the construct.yaml).
I would expect that either 1) the installer fails to build when the impossible environment is detected or 2) the installation fails when the environment creation fails. The way that the installer reports "Setup was completed successfully" smells like a bug to me.
How are excluded packages supposed to work? Are the examples out of date?
I am using constructor 3.0.0 on windows 7.
Yeah I think that example is out of date. The installers created by constructor will do an offline install, so of course, all packages must be included in the installer for that install to be satisfiable. Perhaps previously you could have a set of packages in the installer, and others downloaded from the internet at installation time to save space in the installers themselves.
Do you have a use-case where you would want to exclude a package? I'm not aware of a reason why we'd want to support that. Your other question is definitely a bit of a bug - we should handle and report errors on windows better.
@forrestwaters There is the most widely applicable use case where someone wants to exclude a large package "X" which is brought in as a dependency of another package "Y", but "Y" can still be used without "X" for example like QT and matplotlib. Although this can be solved by using the matplotlib-base package from conda-forge for example.
In my particular case what I need to do is remove some packages which we can't have on the machines where we are deploying the installer due to security restrictions. In particular we can't deploy software which uses openssl, QT, and some other libraries with reported vulnerabilities.
As a work-around I have found that if a package we want in the environment brings in one we can't have as a dependency I can do conda remove --force
in the post_install script. We then have to manually test if we can use that package without the dependency make sure it still works.
I had originally thought that this is essentially what would happen to packages we listed in the exclude section. I realize that this is a very odd situation and probably not something that constructor needs to support.
I would suggest that either:
- Exclusions are handled via a force-remove after install like my work around, which should come with a big "this environment may be broken" warning.
- Support for exclusions should be dropped by constructor and few people who need them can mess with force removals knowing they may break things and need to check carefully.
Thanks for that explanation - makes total sense. After looking at this some more, prior to constructor 3.0, packages installed when running the installer happened without conda. In constructor 3, the standalone conda executable was added, which indirectly means that environments created with constructor must be satisfiable at install time, thus breaking the ability to exclude packages.
I think the best way to accomplish what you're describing would be to create your own conda packages with updated dependencies that fit your needs. Removing packages that are explicit dependencies will break that environment (both when using conda against that environment, as well as the software itself in all likelihood).
I'll try to get the examples and docs updated for our next release so that an outdated feature is no longer advertised
I think this is a bug with the --offline
flag and the --file
flag. The --offline
flag seems to force a full resolve.
I can confirm that if i change the following line:
https://github.com/conda/constructor/blob/926707a34def8cb51be640b98842180260e7fa0a/constructor/header.sh#L444
to
"$CONDA_EXEC" install --use-local --no-deps --file "$PREFIX/pkgs/env.txt" -yp "$PREFIX" || exit 1
things seem to work.
Now, I haven't tested a full offline install honestly. kinda hard to test that on my dev environment, but hopefully this helps others.
My hunch is that the solver tries to "solve" for things offline looking only in the pkgs directory that was given to it. but when ti doesn't find the excluded source, then it thinks it has failed. But I don't know too much about the inner workings of conda.
Well, this is my hack around things that don't need to create stable conda environments, if it helps anybody else: https://github.com/hmaarrfk/constructor/pull/1
exclude
does have uses even though it causes failures if the resulting environment is not consistent. For instance, when building custom Anaconda installers I always exclude the anaconda
metapackage.
I think the @hmaarrfk workaround is interesting although the problem is that now there's no installing in dependency order, which could potentially cause issues in some cases.
All in all, I think all my hacks are not very useful. I've opted to simply repackage conda-forge packages without the dependencies I wanted to exclude.
My ultimate reason was that I felt that my hacks for constructor were orthogonal to growing direction of how conda was being packages, and would be bad in terms of self documentation.
Fair enough. In the case of matplotlib
, I think the community has solved it by repackaging as well, so that it is possible to use standard conda
installs to build environments with matplotlib
and not qt
.
Is there any potential workaround for this issue? I currently have exactly the use case which is called out in the documentation ( https://github.com/conda/constructor/blob/master/CONSTRUCT.md#exclude ) - building an installer that doesn't include readline, since readline is GPL. But it builds a broken installer when I use exclude as documented.
I'm not aware of one I'm afraid.
Hi there, thank you for your contribution!
This issue has been automatically marked as stale because it has not had recent activity. It will be closed automatically if no further activity occurs.
If you would like this issue to remain open please:
- Verify that you can still reproduce the issue at hand
- Comment that the issue is still reproducible and include: - What OS and version you reproduced the issue on - What steps you followed to reproduce the issue
NOTE: If this issue was closed prematurely, please leave a comment.
Thanks!
@ceejatec @hmaarrfk - Yes this does seem to be the result of core difference between the way conda handles optional dependencies versus pip. The exclude option simply doesn't work to solve the major issue that people are trying to solve here (likely most people running into SBOMs showing GPL).
Without making major changes to the conda system it goes back to fixing the recipes for the packages to list the proper dependencies based on what they need vs what is optional. Too many packages list optional dependencies as run requirements (all options enabled) versus using run_constrained. Conda doesn't provide nearly as nice of an interface for the user installing packages to specify extra functionality.
Obviously packages that switch from run to run_constrained will break downstream packages that depend on the optional dependency. Ideally packages should specify their actual dependency especially when subpackages are available.
For the moment the solutions available to constructor users are duplicating package recipes in our own channels or trying to convince the maintainers of existing packages to move optional dependencies to run_constrained and let the environment specify the optional dependency.
I think the only way exclude
is useful now is to remove leaf packages so you only get the dependencies, but not the package itself?
Example:
specs:
- python
- scipy
exclude:
- scipy
should give you an environment with Python and all the dependencies of scipy, but without scipy itself.
Given the way it works now, I'd argue we should mark the exclude
option as obsolete.
While the --no-deps
flag suggested by @hmaarrfk would work, it kind of defies the purpose of having a full solve.
Force removing the packages kind of works too, but it still doesn't prevent the installer from shipping the tarball 🤷 In these cases, the same can be achieved with a post_install
script that activates the environment and runs the conda remove --force <package>
command.
I'm not sure why we want to have a full solve in the install process. It seems problematic for offline workloads.
It was introduced in V3 here, I think. The rationale is not disclosed there, but I see a lot of references to issues about noarch
support, so I guess that was part of it.
Given how it worked before, I think we could try setting --no-deps
and maybe --force-reinstall
too? We have solved the environment already at build time, so I don't really know why a second solve at install time is needed.
#559 will fix this