constructor icon indicating copy to clipboard operation
constructor copied to clipboard

Failures when "exclude"d packages cause inconsistent environments

Open amjames opened this issue 4 years ago • 17 comments

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.

amjames avatar Jan 07 '20 16:01 amjames

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 avatar Jan 20 '20 15:01 forrestwaters

@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.

amjames avatar Jan 21 '20 17:01 amjames

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

forrestwaters avatar Jan 21 '20 23:01 forrestwaters

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.

hmaarrfk avatar Sep 21 '20 22:09 hmaarrfk

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.

hmaarrfk avatar Sep 21 '20 23:09 hmaarrfk

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

hmaarrfk avatar Sep 22 '20 00:09 hmaarrfk

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.

mcg1969 avatar Sep 27 '20 18:09 mcg1969

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.

hmaarrfk avatar Sep 27 '20 19:09 hmaarrfk

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.

mcg1969 avatar Sep 27 '20 19:09 mcg1969

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.

ceejatec avatar Mar 31 '21 23:03 ceejatec

I'm not aware of one I'm afraid.

mcg1969 avatar Apr 01 '21 15:04 mcg1969

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:

  1. Verify that you can still reproduce the issue at hand
  2. 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!

github-actions[bot] avatar May 14 '22 04:05 github-actions[bot]

@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.

bryan-hunt avatar May 23 '22 16:05 bryan-hunt

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.

jaimergp avatar Jul 29 '22 11:07 jaimergp

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.

jaimergp avatar Jul 29 '22 11:07 jaimergp

I'm not sure why we want to have a full solve in the install process. It seems problematic for offline workloads.

hmaarrfk avatar Jul 29 '22 15:07 hmaarrfk

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.

jaimergp avatar Aug 01 '22 07:08 jaimergp

#559 will fix this

jaimergp avatar Aug 30 '22 14:08 jaimergp