Skip to content
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

Inconsistent 'freeze' behaviour #1896

Closed
mietek opened this issue May 25, 2014 · 17 comments
Closed

Inconsistent 'freeze' behaviour #1896

mietek opened this issue May 25, 2014 · 17 comments

Comments

@mietek
Copy link
Contributor

mietek commented May 25, 2014

A cabal freeze done before installing dependencies gives a different result than a cabal freeze done after installing dependencies.

The difference is that a globally installed package is included in the first result, and is not included in the second result.

To reproduce, start with either ghc-7.6.3 or ghc-7.8.2, and cabal-install-1.20.0.1 or cabal-install-1.20.0.2:

# mkdir /tmp/snap-test

# cd /tmp/snap-test

# cabal sandbox init
Writing a default package environment file to
/tmp/snap-test/cabal.sandbox.config
Creating a new sandbox at /tmp/snap-test/.cabal-sandbox

# cabal update
Downloading the latest package list from hackage.haskell.org
Note: there is a new version of cabal-install available.
To upgrade, run: cabal install cabal-install

# cat > snap-test.cabal << EOF
name:           snap-test
version:        0.13.2.5
build-type:     Simple
cabal-version:  >=1.2

executable snap-test
  build-depends: base, snap ==0.13.2.5
EOF

# cabal freeze
Resolving dependencies...

# mv cabal.config cabal.config.before

# cabal install --dependencies-only
Resolving dependencies...
Notice: installing into a sandbox located at /tmp/snap-test/.cabal-sandbox
...
Installed snap-0.13.2.5

# cabal freeze
Resolving dependencies...

# mv cabal.config cabal.config.after

The result with ghc-7.6.3:

# diff -u cabal.config.before cabal.config.after
--- cabal.config.before 2014-05-25 22:36:41.792754624 +0000
+++ cabal.config.after  2014-05-25 22:41:57.294000214 +0000
@@ -56,7 +56,6 @@
              nats ==0.2,
              network ==2.5.0.0,
              old-locale ==1.0.0.5,
-             old-time ==1.1.0.1,
              parallel ==3.2.0.4,
              parsec ==3.1.5,
              prelude-extras ==0.4,

# ghc-pkg list --global | grep old-time
    old-time-1.1.0.1

The result with ghc-7.8.2:

# diff -u cabal.config.before cabal.config.after
--- cabal.config.before 2014-05-25 22:36:42.601316886 +0000
+++ cabal.config.after  2014-05-25 22:44:32.652330297 +0000
@@ -56,7 +56,6 @@
              nats ==0.2,
              network ==2.5.0.0,
              old-locale ==1.0.0.6,
-             old-time ==1.1.0.2,
              parallel ==3.2.0.4,
              parsec ==3.1.5,
              prelude-extras ==0.4,

# ghc-pkg list --global | grep old-time
    old-time-1.1.0.2
@mietek
Copy link
Contributor Author

mietek commented May 26, 2014

Paging @benarmston.

@benarmston
Copy link
Contributor

The only part of snap that has a dependency on old-time is the snap executable stanza. The difference in behaviour is caused by cabal ignoring the build-depends of the snap executable stanza after having installed it. This can be observed by following your test case and then running:

$ cabal sandbox hc-pkg -- unregister snap 

$ cabal freeze
Resolving dependencies...

$ mv cabal.config cabal.config.unregistered

$ diff -u cabal.config.before cabal.config.unregistered

(Just to be clear, the diff command provides no output.)

Running freeze with the -v3 flag and looking at the goals provides additional evidence that this is the case.

If my understanding is correct, this is related to #779 and will be fixed when that is. @tibbe does my understanding here and of #779 sound reasonable.

@tibbe
Copy link
Member

tibbe commented May 30, 2014

@benarmston I'm a bit unfamiliar with this part of cabal. Are you saying that once cabal installs an executable it will omit its deps in the future? I thought that ghc-pkg, and thus cabal, doesn't track installed executables and thus it's a bit confusing if cabal somehow knows that executables are installed!

A more general point: do we want freeze to take currently installed packages (e.g. the sandbox package DB) when computing the install plan to freeze? On one hand that seems useful; you can test the setup before freezing it. On the other hand it might lead to issues like this, were freeze works differently depending on the state of the sandbox package DB (which is supposed to be an implementation detail).

@mietek
Copy link
Contributor Author

mietek commented Jun 2, 2014

@benarmston: As I understand, #779 is the issue where listing executable-only packages in build-depends causes Cabal to install the packages, and to fail to recognise the packages have been installed. Note snap is not an executable-only package.

@mietek
Copy link
Contributor Author

mietek commented Jun 2, 2014

@tibbe: Consider a working application which does not constrain the version numbers of its dependencies in its *.cabal file, and has no cabal.config. Without taking installed packages into account, how else to get to the desired state of having all known-good dependencies explicitly declared? Note down the entire dependency tree manually?

I think a cabal freeze which never takes installed packages into account would be borderline useless. The only use I see would be noting down the most recently updated versions of dependencies.

@mietek
Copy link
Contributor Author

mietek commented Jun 2, 2014

Same test as above, but with snap-0.13.2.6 instead of 0.13.2.5, is now showing an additional unexpected difference:

--- 79a3c87/cabal.config
+++ f7e3527/cabal.config
@@ -56,7 +56,6 @@
              nats ==0.2,
              network ==2.5.0.0,
              old-locale ==1.0.0.6,
-             old-time ==1.1.0.2,
              parallel ==3.2.0.4,
              parsec ==3.1.5,
              prelude-extras ==0.4,
@@ -88,7 +87,7 @@
              time ==1.4.2,
              transformers ==0.3.0.0,
              transformers-base ==0.4.2,
-             transformers-compat ==0.3.3.3,
+             transformers-compat ==0.3.3.4,
              unix ==2.7.0.1,
              unix-compat ==0.4.1.1,
              unordered-containers ==0.2.4.0,

Paging @ekmett and @kosmikus.

benarmston added a commit to benarmston/cabal-issue-1896 that referenced this issue Jun 7, 2014
With cabal versions prior to 1.7.1 the build-depends of any executable stanzas
are included in the build depends which are registered with ghc-pkg. Cabal's
resolver therefore has a complete set of build-depends when it selects the
installed version of foo. `./test.sh` does not exhibit the bug described in
haskell/cabal#1896.
benarmston added a commit to benarmston/cabal-issue-1896 that referenced this issue Jun 7, 2014
With cabal version's greater than or equal to 1.7.1 the build-depends of any
executable stanzas are not included in the build-depends registered with
ghc-pkg. This causes `./test.sh` to exhibit the bug in
haskell/cabal#1896.
benarmston added a commit to benarmston/cabal-issue-1896 that referenced this issue Jun 7, 2014
If the library stanza is removed the package is not registered with ghc-pkg.
This prevents the cabal's resolver from using the incomplete set of
build-depends that ghc-pkg knows about. `./test.sh` no longer exhibits the bug
described in haskell/cabal#1896.
benarmston added a commit to benarmston/cabal-issue-1896 that referenced this issue Jun 7, 2014
With cabal versions prior to 1.7.1 the build-depends of any executable stanzas
are included in the build depends which are registered with ghc-pkg. Cabal's
resolver therefore has a complete set of build-depends when it selects the
installed version of foo. `./test.sh` does not exhibit the bug described in
haskell/cabal#1896.
benarmston added a commit to benarmston/cabal-issue-1896 that referenced this issue Jun 7, 2014
With cabal version's greater than or equal to 1.7.1 the build-depends of any
executable stanzas are not included in the build-depends registered with
ghc-pkg. This causes `./test.sh` to exhibit the bug in
haskell/cabal#1896.
benarmston added a commit to benarmston/cabal-issue-1896 that referenced this issue Jun 7, 2014
If the library stanza is removed the package is not registered with ghc-pkg.
This prevents the cabal's resolver from using the incomplete set of
build-depends that ghc-pkg knows about. `./test.sh` no longer exhibits the bug
described in haskell/cabal#1896.
@benarmston
Copy link
Contributor

@tibbe that's more or less what I'm telling you. When cabal installs a package, say foo, it registers the package's library (if any) with ghc-pkg. But the information which is registered doesn't contain any details about foo's executables. When cabal's resolver selects the installed version of foo, it queries ghc-pkg to find out what build-depends foo was compiled against. That list contains the build-depends for foo's library but not any build-depends for any of foo's executables.

In the test program provided by @mietek the only dependency on old-time is from the snap package's executable snap. When the snap package has been registered with ghc-pkg and the installed version of snap is selected, any build-depends in the snap package's executables are not available.

My first reply on this ticket unregistered the snap package with ghc-pkg, thereby preventing cabal's resolver from selecting the installed version of snap. This caused cabal's resolver to consider the source package for snap and in doing so was able to determine the build-depends on all executables in the snap package.

@mietek I think that this issue and #779 are somewhat related. However, when I referenced #779 I had not understood that the proposed solution was to prevent adding non-lib packages to build-depends. Given that proposal my reference was probably more noise than signal.

Whilst trying to better understand what was happening I reduced @mietek's example to a much smaller case. It is on github at https://github.com/benarmston/cabal-issue-1896/. That repo contains a test.sh script which runs through the steps provided in @mietek's test case but against a much smaller set of dependencies. There are three tags of interest in the repo: cabal-1.7.0, cabal-1.7.1, and no-lib.

Tag cabal-1.7.1 exhibits the bug described in here. It has a cabal-version constraint of >=1.7.1.

Tag cabal-1.7.0 changes that constraint to >=1.7.0 and no longer exhibits the bug.

Tag no-lib uses a cabal-version constraint of >=1.7.1 but removes the library stanza. It does not exhibit the bug.

The reason for no-lib not exhibiting the bug is that the lack of a library section prevents the foo package from being registered with ghc-pkg, preventing cabal's resolver from using the installed version. Which inturn means that the source version is always considered and therefore the build-depends of the executable stanzas are always available to the resolver.

The changes between the tags can be seen at benarmston/cabal-issue-1896@cabal-1.7.0...cabal-1.7.1 and benarmston/cabal-issue-1896@cabal-1.7.1...no-lib

Whilst my understanding of the cause of this bug may be correct. I'm really not sure what the solution to it might be.

DISCLAIMER: My understanding of the process I've described above has been gained by trial and error whilst trying to understand this ticket. It hasn't been gained through an understanding of the resolver code. There could well be subtleties that I'm missing.

@benarmston
Copy link
Contributor

As an aside, to be clear on why I think this ticket and #779 are somewhat related.

For #779: cabal installs the non-lib package and doesn't register that it has been installed (because there is not a library to register with ghc-pkg). As the package has not been registered, its build-depends are not registered.

For this ticket: cabal installs the lib+exe package and registers the lib with ghc-pkg. The exe is not registered and in particular its build-depends are not registered.

I had wrongly assumed that the solution to #779 would be to register the non-lib package somewhere. If that had been the case, we could have used that registry to include any executables within lib+exe packages including the build-depends. In which case #779 and this ticket would have had the same solution.

But my assumption was wrong.

@benarmston
Copy link
Contributor

@tibbe if freeze doesn't take installed packages into account could it not select different flags for some packages than were selected when installing them (particularly if the user provided certain flags when installing). If it can do that, could this not result in freezing an install plan which won't actually build in the current sandbox? Or would warn about dangerous reinstalls?

If not taking the installed packages into account is the preferred method, I think we'd have to be careful not to present unhelpful (and perhaps scarry) messages to users.

@tibbe
Copy link
Member

tibbe commented Jun 10, 2014

Here's the usage pattern I'm imagining:

  1. Run cabal freeze. We now get an initial cabal.config file, based on the version constraints of the current package, its dependencies and so on.
  2. To make sure that this is actually a got set of packages to freeze, we build everything and run our tests (e.g. cabal install --only-dep --enable-tests && cabal test).

What if a user now runs cabal freeze again? First question: what does the user expect to happen at this point? We've already nailed down all versions in cabal.config and if we feed those constraints to the solver it ought to come up with exactly the same versions in its output and nothing will change. If something does change, something is wrong.

@mietek
Copy link
Contributor Author

mietek commented Jun 10, 2014

@tibbe: I agree.

@mietek
Copy link
Contributor Author

mietek commented Jun 10, 2014

The other possible usage pattern is — once the user has his app's dependencies explicitly declared in a cabal.config, what is the easiest way for the user to update the dependencies and ensure the app continues to work? What tooling can cabal provide to enable making this process automatic?

@tibbe
Copy link
Member

tibbe commented Jun 10, 2014

To bump your dependencies I'd expect the user to either

  • just bump them manually in the cabal.config file (if they know which version they want) or
  • remove a few lines from the file and then re-run freeze (which might pick newer) versions if available.

It's also possible to remove cabal.config (which should be tracked in source control) altogether, re-run freeze, and then use a diff tool to pick some changes and revert some. Note that the user needs to re-run build after this to make sure the manually edited changes actually make sense (i.e. form a valid install plan).

@mietek mietek changed the title Inconsistent freeze behaviour Inconsistent 'freeze' behaviour Jun 26, 2014
@mietek
Copy link
Contributor Author

mietek commented Oct 13, 2014

When the snap package has been registered with ghc-pkg and the installed version of snap is selected, any build-depends in the snap package's executables are not available.

Could the resolver always look at the source version of a package in order to determine dependencies, even if an installed version is selected?

Paging @dcoutts.

@ekmett
Copy link
Member

ekmett commented Oct 15, 2014

Just was pinged by this thread again, assuming because of a recent comment. I'm somewhat late to the party, but I just wanted to note the bit about:

-             transformers-compat ==0.3.3.3,
+             transformers-compat ==0.3.3.4,

note: transformers-compat ships 3 versions 0.3.3.2, 0.3.3.3, and 0.3.3.4, which all are basically the same package with different flags set up to work around the backtracker for cabal. Not sure if that is useful data, but it may explain why you get weird behavior around that package compared to others.

@23Skidoo 23Skidoo removed this from the Cabal 1.24 milestone Feb 21, 2016
@ezyang ezyang modified the milestone: Cabal 2.0 Sep 6, 2016
@gbaz
Copy link
Collaborator

gbaz commented Feb 7, 2018

it would be good to check if new-freeze has this issue, and if not, we can eventually obsolete this

@23Skidoo
Copy link
Member

23Skidoo commented Feb 8, 2018

I believe new-freeze generates foo == x.y.z || foo == u.v.w constraints instead, see #4832 and #3502. Optimistically closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants