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

feature: Private Dependencies #9743

Open
wants to merge 12 commits into
base: master
Choose a base branch
from

Conversation

alt-romes
Copy link
Collaborator

@alt-romes alt-romes commented Feb 26, 2024

Tracking ticket for private dependencies: #4035

See the commit message of the commit with the same name as the pull request for the long explanation.

TODO checklist:

  • Fail cabal-check with private dependencies (we need to ensure it all works with hackage before allowing packages with private dependencies to be uploaded
  • Any changes that could be relevant to users have been recorded in the changelog.
  • The documentation has been updated, if necessary.
  • Stress testing private dependencies

Template Α: This PR modifies cabal behaviour

Include the following checklist in your PR:

  • Patches conform to the coding conventions.
  • Tests have been added. (Ask for help if you don’t know how to write them! Ask for an exemption if tests are too complex for too little coverage!)

@alt-romes alt-romes self-assigned this Feb 26, 2024
@alt-romes alt-romes force-pushed the wip/private-dependencies branch 5 times, most recently from 493cdd4 to ed3579a Compare February 26, 2024 16:41
@alt-romes alt-romes marked this pull request as draft February 27, 2024 09:16
@alt-romes alt-romes force-pushed the wip/private-dependencies branch 2 times, most recently from 61ee29b to 6a9c45b Compare February 27, 2024 11:42
@alt-romes alt-romes force-pushed the wip/private-dependencies branch 8 times, most recently from 4b7608e to 08689b9 Compare March 5, 2024 11:54
@alt-romes alt-romes force-pushed the wip/private-dependencies branch 12 times, most recently from 9c97b3e to ae07142 Compare March 11, 2024 15:30
This reverts commit c3b5013.

We don't need to fail in cabal check for private dependencies in
particular because hackage will already reject packages whose Cabal
version is "too new" to prevent experimental features to be used in
hackage.

This way, it is hackage which controls when private dependencies make it
there, rather than controlling this via the Cabal version.
Improvement/refactor in the implementation of private dependencies to
use the more descriptive `IsPrivate` type rather than the isomorphic
`Maybe PrivateAlias`
Documents private dependencies in the user facing user guide
Redesigns the algorithm for checking the private scope closure property
in the solver.

The algorithm is described in detail in the comment of
`findBadPrivClosures`. A key change is that we construct and propagate
together with the `RevDepMap` a cache of the private scopes at every
node, which allow us to efficiently look up the "root" packages from
which we can easily validate the property holds.

This change was prompted by stress testing private dependencies:
The stress test uncovered that way too much time was being spent on
validating the closure property, despite the property already being
checked at every node.

Specifically, the baseline for solving without
the check was 11s, however, with the check in place, solving instead
took over 50s when NO private dependencies were in the plan, and over 2
minutes when there were a lot of private scopes.

After this change, the check of the private scope closure property is
negligible on performance, being slightly faster (40ms) when there are a
lot of private scopes (because the algorithm short circuits faster) and
slightly slower (90ms) when there are no private scopes at all. This is
more in line with the `findCycles` check takes some 110ms on my machine.

This commit also adds the stress test which uncovered the problem,
however, this stress test is disabled by default and should only be run
manually since testing time-based performance in CI can be problematic.
Originally, we were qualifying a private dependency with the component
with that private dep. Now, we qualify a private dependency without the
component, i.e. only with the package name and the scope name.

However, we want the same private scope name to be shared across all
components of a package (and, if different dependencies are desired
across components, different scope names should be used). For example,
the following package should not be possible to solve (and does not with
this commit):

    cabal-version:   3.0
    name:            pkgC
    version:         0.1.0.0
    build-type:      Simple

    library
        private-build-depends: SameName with (libA == 0.1.0.0)
        build-depends:    base

    executable myexe
        private-build-depends: SameName with (libA == 0.2.0.0)
        build-depends: base

See the second case of the `same-scope-name` test.
Since we dropped the `Component` field of `ScopePrivate` in the previous
commit, we can now use `ScopeQualified` instead of having a specific
constructor `ScopePrivate`. This is mostly a clean up.
This test makes a long chain of packages violate the private closure
property. In particular, it ensures all of the packages that are missing
from the private scope are listed in the error message.
We would previously include terminal nodes if these were not private.
@grayjay
Copy link
Collaborator

grayjay commented May 1, 2024

@alt-romes I had a question about the design before I start reviewing the code. How similar is this PR to the design described in this document? #4035 (comment)

If I understood the first commit message correctly, this PR already differs by not automatically pulling intermediate dependencies into private scopes.

@alt-romes
Copy link
Collaborator Author

@grayjay In general, the implementation/final version should be very similar to the original draft design.

You point out that we do not implicitly qualify packages into the private scope: that's right. I suppose in the original draft we would.

@alt-romes
Copy link
Collaborator Author

@grayjay I have attempted to do the backtracking test by changing Cabal to "randomly" qualify certain packages privately instead of at the top-level. In principle, this should always be possible because having scopes with singleton private dependencies should only make more plans valid, not the other way around.

Unfortunately, I wasn't able to get a fully working example of the hackage-benchmarks test using this strategy. I got the modified version of Cabal to finish in roughly the same time as the non-modified version, but the modified Cabal produced "Unknown" final results rather than "OK".

Given time constraints, I haven't pursued this second benchmark any further. I still believe it's important, but given said time constraints and the robust results of the first stress test I'm of the opinion we can proceed with merging this PR before such test is concluded.

I recall that the first stress test, after iteration, showed negligible performance impact on solving for a plan with thousands of dependencies roughly half of which were private. Adding to this the fact that private dependencies are introduced as "shallow" goals (ie not transitive), I'm reasonably confident that the performance impact even with backtracking should be negligible, and that we could merge this as is before having the results of the second test which we can address with time, without worrying about this PR bit rotting in the meanwhile. I can open a follow-up ticket to track stress-testing private dependencies with backtracking further.

If you don't oppose to this omission, I would greatly appreciate your review to move this PR forward diligently.

@grayjay
Copy link
Collaborator

grayjay commented Jun 3, 2024

@alt-romes Sorry for the delay. Thank you for working on the backtracking stress test. It sounds promising that the runtimes were comparable to master. I'm not sure I understand what you meant by "unknown" test results. Is that error specific to the benchmark, or was it an error given by the cabal executable under test? Did it occur in all runs, or only when cabal failed to find a solution? Even if the benchmark doesn't complete, are you able to capture any -v3 logs that show how cabal handles the new types of conflicts added by the PR?

I still think that it is important to analyze the performance of private dependencies when there are conflicts, in some way, before merging. The first stress test mainly evaluated the overhead added to each step of dependency solving, but this test mainly evaluates the total number of steps, which is almost orthogonal. It is easy for a change to add almost no overhead to the individual steps but add an exponential number of steps to resolve conflicts, for example, by adding too many variables to the conflict set. It is very difficult to address some types of performance issues once a feature is released and being used by packages on Hackage.

In principle, this should always be possible because having scopes with singleton private dependencies should only make more plans valid, not the other way around.

Is it possible to combine some of the dependencies into the same scope so that they aren't all singletons? I'm concerned that singleton scopes won't exercise all of the conflict types, such as enforcement of the scope closure property. The best test cases would involve many realistic conflicts, of various types, and significant amounts of backtracking, with some cases having solutions and others having no solution due to private dependencies. It's fine for packages to go from succeeding to failing and vice versa, compared to master, since these changes already can't be directly compared to master.

@alt-romes
Copy link
Collaborator Author

@grayjay the details of my attempt are slightly fuzzy, but they were, in the end, inconclusive about the performance of the solver with private dependencies when backtracking is involved.

I was wondering, given your expertise with the solver and recent suggestions, if you could independently attempt to benchmark this PR on your machine with the backtracking relevant machinery. I wasn't able to get the benchmark working properly in my backtracking attempt, and currently cannot afford to spend a lot of time figuring out what was wrong, but perhaps you may be able to get a working solution without trouble since you've worked with these benchmarks in the past.

@grayjay
Copy link
Collaborator

grayjay commented Jun 12, 2024

@alt-romes I don't have very much time to spend on Cabal currently, but I could try running your benchmark to see if I have any ideas about how to proceed, especially debugging the "unknown" result. Do you have a link to the branch?

@alt-romes
Copy link
Collaborator Author

@alt-romes I don't have very much time to spend on Cabal currently, but I could try running your benchmark to see if I have any ideas about how to proceed, especially debugging the "unknown" result. Do you have a link to the branch?

That would be great! Here's a commit with the change: https://github.com/alt-romes/cabal/tree/wip/romes/private-deps-bench

@grayjay
Copy link
Collaborator

grayjay commented Jun 15, 2024

@alt-romes I tried running the Hackage benchmark manually on your branch, and I was able to reproduce the "Unknown" error. It indicates that the cabal under test failed but the benchmark didn't recognize the error message. Then I manually ran the modified cabal on one of the packages producing the error. Here is an example error from a lower level package:

$ cabal --ignore-project install --dry-run --lib primitive
Warning: this is a debug build of cabal-install with assertions enabled.
Resolving dependencies...
internal error: could not construct a valid install plan.
The proposed (invalid) plan contained the following problems:
Package primitive-0.9.0.0 has an invalid configuration, in particular:
  the package has a dependency base >=4.9 && <4.21 but no package has been selected to satisfy it.
  the package has a dependency template-haskell >=2.11 but no package has been selected to satisfy it.
  the package has a dependency transformers >=0.5 && <0.7 but no package has been selected to satisfy it.
  the package configuration specifies PRIVATE-base.base-4.18.0.0 but (with the given flag assignment) the package does not actually depend on any version of that package.
  the package configuration specifies PRIVATE-template-haskell.template-haskell-2.20.0.0 but (with the given flag assignment) the package does not actually depend on any version of that package.
  the package configuration specifies PRIVATE-transformers.transformers-0.6.1.0 but (with the given flag assignment) the package does not actually depend on any version of that package.

Proposed plan:
PreExisting rts-1.0.2 (rts-1.0.2)
PreExisting ghc-prim-0.10.0 (ghc-prim-0.10.0)
PreExisting ghc-bignum-1.3 (ghc-bignum-1.3)
PreExisting base-4.18.0.0 (base-4.18.0.0)
PreExisting ghc-boot-th-9.6.2 (ghc-boot-th-9.6.2)
PreExisting transformers-0.6.1.0 (transformers-0.6.1.0)
PreExisting array-0.5.5.0 (array-0.5.5.0)
PreExisting deepseq-1.4.8.1 (deepseq-1.4.8.1)
PreExisting pretty-1.1.3.6 (pretty-1.1.3.6)
PreExisting template-haskell-2.20.0.0 (template-haskell-2.20.0.0)
Configured primitive-0.9.0.0 lib

CallStack (from HasCallStack):
  error, called at src/Distribution/Client/Dependency.hs:903:17 in cabal-install-3.13.0.0-inplace:Distribution.Client.Dependency

It looks like the latest commit modifies qualifiers in the middle of solving, which causes an inconsistency. I think it could be fixed by only modifying packages at the start of solving, such as during index conversion, in convPIs.

@alt-romes
Copy link
Collaborator Author

@grayjay thanks for investigating.

Unfortunately, convPIs doesn't quite work because we don't do any kind of qualification there it seems, so I don't have a way to change the qualifier there and in the adjacent functions.

alt-romes added a commit to alt-romes/cabal that referenced this pull request Jul 1, 2024
Hackage is not yet ready for private dependencies, so we mustn't allow
packages with private deps to be uploaded just yet.

Hackage already rejects packages using too-recent cabal versions,
however, private dependencies is a special case which must be guarded
against until more extensive benchmarks regarding the performance of
solving private dependencies mixed in with a lot of backtracking.

See haskell#9743
alt-romes added a commit to alt-romes/cabal that referenced this pull request Jul 1, 2024
Hackage is not yet ready for private dependencies, so we mustn't allow
packages with private deps to be uploaded just yet.

Hackage already rejects packages using too-recent cabal versions,
however, private dependencies is a special case which must be guarded
against until more extensive benchmarks regarding the performance of
solving private dependencies mixed in with a lot of backtracking.

See haskell#9743
@grayjay
Copy link
Collaborator

grayjay commented Jul 4, 2024

We discussed code review at the last meeting Cabal meeting. I'd like to focus on the solver part of this pull request. If I understand correctly, @fgaz is interested in reviewing the changes to the Cabal library. I think that we should also have another reviewer for the high level design and user visible behavior, and others at the meeting suggested @gbaz. @gbaz are you interested in being a reviewer?

We also discussed making this feature experimental, so that Hackage rejects packages with private dependencies, until we've finished testing the performance, especially performance in the presence of conflicts.

@grayjay
Copy link
Collaborator

grayjay commented Jul 4, 2024

Unfortunately, convPIs doesn't quite work because we don't do any kind of qualification there it seems, so I don't have a way to change the qualifier there and in the adjacent functions.

I meant that you could directly insert private dependencies during index conversion, as if they were read from the GenericPackageDescription. For example, you could insert them here:

++ [ D.Simple singleDep comp
| dep <- privateDependencies ds
, singleDep <- convLibDepsAs dr dep ] -- unconditional package dependencies

I would expect the solver to add the correct qualifier later.

@grayjay grayjay mentioned this pull request Jul 9, 2024
alt-romes added a commit to alt-romes/cabal that referenced this pull request Aug 2, 2024
Hackage is not yet ready for private dependencies, so we mustn't allow
packages with private deps to be uploaded just yet.

Hackage already rejects packages using too-recent cabal versions,
however, private dependencies is a special case which must be guarded
against until more extensive benchmarks regarding the performance of
solving private dependencies mixed in with a lot of backtracking.

See haskell#9743
@grayjay
Copy link
Collaborator

grayjay commented Dec 1, 2024

@alt-romes I finally finished adding private dependencies to the QuickCheck tests as I mentioned in the meeting, and I opened a PR for this branch: mpickering#2

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

Successfully merging this pull request may close these issues.

3 participants