Mon 17 Feb 2025

Previous: Mon 10 Feb 2025

Babel: opam repository with pubgrub-opam

DONE tests

DONE PubGrub 0.3.0

  • Uv is using the development branch of PubGrub.
  • There’s a lot of improvements to be had since the last release was 4 years ago.
    • The new version bounds will cleanly express version constraints, e.g. stop converting <= 1.0.0 to < 1.0.0.1
    • It looks like we might be able to manually add conflicts with add_incompatibility
      • Ah, actually this is an internal thing.
  • Completed here.

DONE conjunctions and disjunctions in filtered package formula

Take "D" { test & > "2.0.0"} as an example. We encode this as,

(filtered-package-formula-variable-version, 1.0.0) -> (D {(test & = >2.0.0)}, *)
(D {(test & = >2.0.0)}, false) -> (`test`, false)
(D {(test & = >2.0.0)}, true) -> (`test`, true), (A, >1.0.0)
(`test`, false)
(`test`, true)
(A, 2.0.0) -> ...

Note we introduce a proxy package that depends on either the filter being false (with versions stripped out), or the filter being true (with versions part of the equation, taking the union on conjunctions and intersection on disjunctions).

Take "A" { test | !test } as an example. We encode this as,

(filtered-package-formula-or, 1.0.0) -> (A {(test | !test)}, *)
(A {(test | !test)}, false) -> (`test`, ∅)
(A {(test | !test)}, true) -> (A {(test | !test)}, *)
(A {(test | !test)}, lhs) -> (`test`, true), (A, *)
(`test`, true)
(A, 1.0.0) -> ...

Note we combine the versions of the variable test with an intersection which leads to the empty set.

DONE comparison of booleans

done here e.g.

(filtered-package-formula-equality, 1.0.0) -> (A {(test = build)}, *)
versions of A {(test = build)}: false, true
(A {(test = build)}, false) -> ({(test != build)}, *)
versions of {(test != build)}: lhs, rhs
({(test != build)}, lhs) -> (`test`, true), (`build`, false)
versions of `test`: false, true
(`test`, true)
versions of `build`: false, true
(`build`, false)

DONE add support for a ‘root’ package to support setting variable values

done here

DONE ocaml-variants.5.3.1+trunk

I’m reproducing a bug in opam-repository’s constraints that Patrick found.

$ opam sw create 5.3.1+trunk
<><> Installing new switch packages <><><><><><><><><><><><><><><><><><><><>  🐫
Switch invariant: ["ocaml-variants" {= "5.3.1+trunk"}]
[ERROR] Could not determine which packages to install for this switch:
  * No agreement on the version of ocaml-variants:
    - (invariant) → ocaml-variants = 5.3.1+trunk → ocaml-compiler < 5.3.0~alpha1 → ocaml = 5.3.0 → ocaml-variants < 5.3.1~
    - (invariant) → ocaml-variants = 5.3.1+trunk
    You can temporarily relax the switch invariant with `--update-invariant'
  * Incompatible packages:
    - (invariant) → ocaml-variants = 5.3.1+trunk → ocaml-compiler < 5.3.0~alpha1 → ocaml = 5.3.0 → dkml-base-compiler < 5.3.1~
    - (invariant) → ocaml-variants = 5.3.1+trunk
  * Incompatible packages:
    - (invariant) → ocaml-variants = 5.3.1+trunk → ocaml-compiler < 5.3.0~alpha1 → ocaml = 5.3.0 → ocaml-base-compiler >= 5.3.0~
    - (invariant) → ocaml-variants = 5.3.1+trunk
  * Missing dependency:
    - (invariant) → ocaml-variants = 5.3.1+trunk → ocaml-compiler < 5.3.0~alpha1 → ocaml = 5.3.0 → ocaml-variants < 5.3.1~ →
      system-msvc
    unmet availability conditions: 'os = "win32"'

After adding conflict-class support (which are required for the ocaml compiler packages) we can reproduce the error:

Because ocaml-system >=5.3.0~, <5.3.1~ depends on Conflict class ocaml-core-compiler ocaml-system and ((ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~})) | (ocaml-system {= >=5.3.0~, <5.3.1~}) rhs depends on ocaml-system >=5.3.0~, <5.3.1~, ((ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~})) | (ocaml-system {= >=5.3.0~, <5.3.1~}) rhs depends on Conflict class ocaml-core-compiler ocaml-system. (1)

Because ocaml-base-compiler >=5.3.0~, <5.3.1~ depends on Conflict class ocaml-core-compiler ocaml-base-compiler and (ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~}) lhs depends on ocaml-base-compiler >=5.3.0~, <5.3.1~, (ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~}) lhs depends on Conflict class ocaml-core-compiler ocaml-base-compiler.
And because (ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~}) <lhs | >lhs depends on ocaml-variants >=5.3.0~, <5.3.1~ and ((ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~})) | (ocaml-system {= >=5.3.0~, <5.3.1~}) lhs depends on (ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~}), Conflict class ocaml-core-compiler Not ( ocaml-base-compiler ), ((ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~})) | (ocaml-system {= >=5.3.0~, <5.3.1~}) lhs, ocaml-variants Not ( >=5.3.0~, <5.3.1~ ) are incompatible.
And because ((ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~})) | (ocaml-system {= >=5.3.0~, <5.3.1~}) rhs depends on Conflict class ocaml-core-compiler ocaml-system (1), Conflict class ocaml-core-compiler Not ( ocaml-base-compiler | ocaml-system ), ((ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~})) | (ocaml-system {= >=5.3.0~, <5.3.1~}) *, ocaml-variants Not ( >=5.3.0~, <5.3.1~ ) are incompatible.
And because (((ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~})) | (ocaml-system {= >=5.3.0~, <5.3.1~})) | (dkml-base-compiler {= >=5.3.0~, <5.3.1~}) lhs depends on ((ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~})) | (ocaml-system {= >=5.3.0~, <5.3.1~}) and (((ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~})) | (ocaml-system {= >=5.3.0~, <5.3.1~})) | (dkml-base-compiler {= >=5.3.0~, <5.3.1~}) <lhs | >lhs depends on dkml-base-compiler >=5.3.0~, <5.3.1~, Conflict class ocaml-core-compiler Not ( ocaml-base-compiler | ocaml-system ), (((ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~})) | (ocaml-system {= >=5.3.0~, <5.3.1~})) | (dkml-base-compiler {= >=5.3.0~, <5.3.1~}) *, ocaml-variants Not ( >=5.3.0~, <5.3.1~ ) are incompatible.
And because ocaml 5.3.0 depends on (((ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~})) | (ocaml-system {= >=5.3.0~, <5.3.1~})) | (dkml-base-compiler {= >=5.3.0~, <5.3.1~}) and ocaml {(= 5.3.0 & post)} true depends on ocaml 5.3.0, ocaml {(= 5.3.0 & post)} true, Conflict class ocaml-core-compiler Not ( ocaml-base-compiler | ocaml-system ), ocaml-variants Not ( >=5.3.0~, <5.3.1~ ) are incompatible.
And because ocaml {(= 5.3.0 & post)} false depends on `post` false and ocaml-compiler 5.3 depends on ocaml {(= 5.3.0 & post)}, ocaml-compiler 5.3, Conflict class ocaml-core-compiler Not ( ocaml-base-compiler | ocaml-system ), `post` Not ( false ), ocaml-variants Not ( >=5.3.0~, <5.3.1~ ) are incompatible.
And because ocaml-variants 5.3.1+trunk depends on ocaml-compiler 5.3 and ocaml-variants 5.3.1+trunk depends on Conflict class ocaml-core-compiler ocaml-variants, ocaml-variants 5.3.1+trunk depends on `post` false.
And because Root  depends on `post` true and Root  depends on ocaml-variants 5.3.1+trunk, Root  is forbidden.

Let’s break this down.

 
Because ocaml-system >=5.3.0~, <5.3.1~ depends on Conflict class ocaml-core-compiler ocaml-system and ((ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~})) | (ocaml-system {= >=5.3.0~, <5.3.1~}) rhs depends on ocaml-system >=5.3.0~, <5.3.1~, ((ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~})) | (ocaml-system {= >=5.3.0~, <5.3.1~}) rhs depends on Conflict class ocaml-core-compiler ocaml-system. (1)
 

Because ocaml-system is in conflict class ocaml-core-compiler and this formula’s right hand side (RHS) depends on ocaml-system, the RHS of the formula depends on ocaml-system in the ocaml-core-compiler conflict class.

 
Because ocaml-base-compiler >=5.3.0~, <5.3.1~ depends on Conflict class ocaml-core-compiler ocaml-base-compiler and (ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~}) lhs depends on ocaml-base-compiler >=5.3.0~, <5.3.1~, (ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~}) lhs depends on Conflict class ocaml-core-compiler ocaml-base-compiler.
 

Because ocaml-base-compiler is in conflict class ocaml-core-compiler and this formula’s left hand side (LHS) depends on ocaml-base-compiler, the LHS of the formula depends on ocaml-base-compiler in the ocaml-core-compiler conflict class.

 
And because (ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~}) lhs depends on ocaml-variants >=5.3.0~, <5.3.1~ and ((ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~})) | (ocaml-system {= >=5.3.0~, <5.3.1~}) lhs depends on (ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~}), Conflict class ocaml-core-compiler Not ( ocaml-base-compiler ), ((ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~})) | (ocaml-system {= >=5.3.0~, <5.3.1~}) lhs, ocaml-variants Not ( >=5.3.0~, <5.3.1~ ) are incompatible.
 

And because the parent formula’s LHS has it’s RHS depends on ocaml-variants, and the formula’s LHS depends on the formula’s LHS (duh), then we can’t select the LHS of the formula (ocaml-base-compiler or ocaml-variants) and not select ocaml-base-compiler and not select ocaml-variants (phew).

Basically, the LHS of the formula depends on either ocaml-base-compiler or ocaml-variants.

 
And because ((ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~})) | (ocaml-system {= >=5.3.0~, <5.3.1~}) rhs depends on Conflict class ocaml-core-compiler ocaml-system (1), Conflict class ocaml-core-compiler Not ( ocaml-base-compiler | ocaml-system ), ((ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~})) | (ocaml-system {= >=5.3.0~, <5.3.1~}) *, ocaml-variants Not ( >=5.3.0~, <5.3.1~ ) are incompatible.
 

Because the formula’s RHS selects ocaml-system in conflict class ocaml-core-compiler, then we can’t select (either side of) the formula without selecting ocaml-base-compiler or ocaml-system in ocaml-core-compiler, and ocam-variants.

 
And because (((ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~})) | (ocaml-system {= >=5.3.0~, <5.3.1~})) | (dkml-base-compiler {= >=5.3.0~, <5.3.1~}) lhs depends on ((ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~})) | (ocaml-system {= >=5.3.0~, <5.3.1~}) and (((ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~})) | (ocaml-system {= >=5.3.0~, <5.3.1~})) | (dkml-base-compiler {= >=5.3.0~, <5.3.1~}) lhs depends on dkml-base-compiler >=5.3.0~, <5.3.1~, Conflict class ocaml-core-compiler Not ( ocaml-base-compiler | ocaml-system ), (((ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~})) | (ocaml-system {= >=5.3.0~, <5.3.1~})) | (dkml-base-compiler {= >=5.3.0~, <5.3.1~}) *, ocaml-variants Not ( >=5.3.0~, <5.3.1~ ) are incompatible.
 

We can’t select this formula without selecting ocaml-base-compiler or ocaml-system in ocaml-core-compiler, and ocam-variants. Note dkml-base-compiler is ignored as there’s not compatible versions.

 
And because ocaml 5.3.0 depends on (((ocaml-base-compiler {= >=5.3.0~, <5.3.1~}) | (ocaml-variants {= >=5.3.0~, <5.3.1~})) | (ocaml-system {= >=5.3.0~, <5.3.1~})) | (dkml-base-compiler {= >=5.3.0~, <5.3.1~}) and ocaml {(= 5.3.0 & post)} true depends on ocaml 5.3.0, ocaml {(= 5.3.0 & post)} true, Conflict class ocaml-core-compiler Not ( ocaml-base-compiler | ocaml-system ), ocaml-variants Not ( >=5.3.0~, <5.3.1~ ) are incompatible.
 

If the proxy package associated with the filtered package formula ocaml ocaml {(= 5.3.0 & post)} is selected (i.e. version true) then we must have ocaml-base-compiler or ocaml-system in ocaml-core-compiler, and ocam-variants.

 
And because ocaml {(= 5.3.0 & post)} false depends on `post` false and ocaml-compiler 5.3 depends on ocaml {(= 5.3.0 & post)}, ocaml-compiler 5.3, Conflict class ocaml-core-compiler Not ( ocaml-base-compiler | ocaml-system ), `post` Not ( false ), ocaml-variants Not ( >=5.3.0~, <5.3.1~ ) are incompatible.
 

If we don’t select this package formula, we need post to be false, and ocaml-compiler depends on this formula, so we can’t select ocaml-compiler with post=true without ocaml-base-compiler or ocaml-system in ocaml-core-compiler, and ocam-variants.

 
And because ocaml-variants 5.3.1+trunk depends on ocaml-compiler 5.3 and ocaml-variants 5.3.1+trunk depends on Conflict class ocaml-core-compiler ocaml-variants, ocaml-variants 5.3.1+trunk depends on `post` false.
 

Because ocaml-variants depends on ocaml-compiler and is in conflict class ocaml-core-compiler, we can’t select it without having post=false.

 
And because Root  depends on `post` true and Root  depends on ocaml-variants 5.3.1+trunk, Root  is forbidden.
 

Since we set post=true at the root, we have a conflict.

This is all a very roundabout way of telling us that we have a conflict class. This provides a good example to explore using a custom error provider for.

After applying the fix, we successfully solve the dependencies:

Solution Set:
        opam-version = 2.1.0
        (host-arch-arm64, 1)
        (base-domains, base)
        (ocaml, 5.3.1)
        (host-system-other, 1)
        (base-threads, base)
        os = macos
        (base-unix, base)
        (ocaml-compiler, 5.3)
        (base-bigarray, base)
        (ocaml-config, 3)
        (base-nnp, base)
        (base-effects, base)
        (ocaml-variants, 5.3.1+trunk)
        post = true
        arch = arm64

Resolved Dependency Graph:
        (base-bigarray, base)
        (base-domains, base) -> (ocaml, 5.3.1)
        (base-effects, base) -> (ocaml, 5.3.1)
        (base-nnp, base) -> (base-domains, base)
        (base-threads, base)
        (base-unix, base)
        (host-arch-arm64, 1)
        (host-system-other, 1)
        (ocaml, 5.3.1) -> (ocaml-config, 3), (ocaml-variants, 5.3.1+trunk)
        (ocaml-compiler, 5.3) -> (`arch`, arm64), (`opam-version`, 2.1.0), (`os`, macos), (`post`, true), (base-bigarray, base), (base-domains, base), (base-effects, base), (base-nnp, base), (base-threads, base), (base-unix, base), (host-arch-arm64, 1), (host-system-other, 1), (ocaml, 5.3.1)
        (ocaml-config, 3) -> (`os`, macos)
        (ocaml-variants, 5.3.1+trunk) -> (`opam-version`, 2.1.0), (ocaml-compiler, 5.3)

TODO a Debian/Alpine encoding in PubGrub, which I think should be much simpler than Opam

  1. TODO and tie into opam with depexts for cross-ecosystem resolutions

TODO PubGrub custom error provider

  • look at test_package_formula_or_error
  • look at test_opam_repository_ocaml_variants

TODO optional dependencies

TODO conflicts

tracking issue here

TODO boolean and integer filter literals

TODO statically configure possible variable values

TODO read up on answer set programming

https://pubgrub-rs-guide.pages.dev/internals/intro is a good starting point

Teaching

Supervision 2 of Robinson Computer Networking