Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
Currently nothing is narrowed, so a lockdir binary shadows an identically-named
binary on the ambient PATH -- [%{bin:X}] and [(system X)] both resolve to the
lockdir copy, not the ambient one. Once narrowing hides the lockdir copy (its
package isn't a declared dep), [Context.which] for a Lock context falls back to
[Which.which ~path:builder.path] (the ambient/system PATH), so the binary would
then resolve from the surrounding environment. This test pins the current
"lockdir shadows ambient" behavior; it flips to the ambient fallback once
narrowing lands.

$ make_lockdir

A lockdir package [provider] installs [mybin] ("from lockdir"), which we will
NOT declare as a dependency:

$ make_lockpkg provider <<'EOF'
> (version 0.0.1)
> (build
> (progn
> (system "echo '#!/bin/sh' > mybin")
> (system "echo 'echo from lockdir' >> mybin")
> (system "chmod +x mybin")
> (system "echo 'bin: [ \"mybin\" ]' > provider.install")))
> EOF

A [mybin] on the ambient PATH ("from system"):

$ mkdir fakebin
$ cat >fakebin/mybin <<'EOF'
> #!/bin/sh
> echo from system
> EOF
$ chmod +x fakebin/mybin

$ cat >dune <<'EOF'
> (rule
> (with-stdout-to mybin-avail (echo %{bin-available:mybin})))
> (rule
> (enabled_if %{bin-available:mybin})
> (action (with-stdout-to mybin-out (run %{bin:mybin}))))
> (rule
> (enabled_if %{bin-available:mybin})
> (action (with-stdout-to mybin-system (system mybin))))
> (rule
> (action (with-stdout-to path-output (bash "echo $PATH"))))
> EOF

$ make_dune_project 3.25
$ cat >> dune-project << 'EOF'
> (package (name mypkg) (allow_empty) (dir .))
> EOF

[mybin] is not a declared dep, but the lockdir copy is still resolved before
the ambient PATH copy:

$ PATH="$PWD/fakebin:$PATH" dune build @all
$ cat _build/default/mybin-avail
true
$ cat _build/default/mybin-out
from lockdir

[(system mybin)] resolves via $PATH. The lockdir provider's bin layout is on
$PATH and the shell finds the lockdir binary.

$ cat _build/default/mybin-system
from lockdir

$ env_added "$(cat _build/default/path-output)" "$PATH" | censor
$PWD/_build/_private/default/.pkg/provider.0.0.1-$DIGEST/target/bin
$PWD/fakebin
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
Two workspace packages installing a binary of the SAME name produce a "more
than one definition" error.

$ make_lockdir

Two workspace packages, each installing a binary named [dup]:

$ mkdir -p a b
$ cat >a/dup.sh <<'EOF'
> #!/bin/sh
> echo from a
> EOF
$ chmod +x a/dup.sh
$ cat >a/dune <<'EOF'
> (install (package pkg-a) (section bin) (files (dup.sh as dup)))
> EOF
$ cat >b/dup.sh <<'EOF'
> #!/bin/sh
> echo from b
> EOF
$ chmod +x b/dup.sh
$ cat >b/dune <<'EOF'
> (install (package pkg-b) (section bin) (files (dup.sh as dup)))
> EOF

A consumer that only depends on [pkg-a]:

$ mkdir -p c
$ cat >c/dune <<'EOF'
> (rule (with-stdout-to dup-out (run %{bin:dup})))
> EOF

$ make_dune_project 3.25
$ cat >> dune-project << 'EOF'
> (package (name pkg-a) (allow_empty) (dir a))
> (package (name pkg-b) (allow_empty) (dir b))
> (package (name pkg-c) (allow_empty) (dir c) (depends pkg-a))
> EOF

Today the lookup errors with "more than one definition". Once narrowing lands,
it would instead select [pkg-a]'s [dup], the only declared dep:

$ dune build c/dup-out 2>&1
File "b/dune", line 1, characters 47-53:
1 | (install (package pkg-b) (section bin) (files (dup.sh as dup)))
^^^^^^
Error: binary "dup" is available from more than one definition. It is also
available in:
- a/dune:1
[1]
34 changes: 34 additions & 0 deletions test/blackbox-tests/test-cases/pkg/bin-narrowing/env-binaries.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
A binary registered via [(env (_ (binaries ...)))] becomes a [Resolved] entry
in [local_bins] (Artifacts.add_binaries) with NO owning package. So,
env-registered binaries are exempt from narrowing entirely.

$ make_lockdir

A workspace executable exposed under a different name via [(env (binaries ...))]:

$ cat >mytool.ml <<'EOF'
> let () = print_endline "from env binary"
> EOF
$ cat >dune <<'EOF'
> (executable (name mytool))
> (env (_ (binaries (mytool.exe as mybin))))
> (rule
> (with-stdout-to mybin-avail (echo %{bin-available:mybin})))
> (rule
> (enabled_if %{bin-available:mybin})
> (action (with-stdout-to mybin-out (run %{bin:mybin}))))
> EOF

$ make_dune_project 3.25
$ cat >> dune-project << 'EOF'
> (package (name mypkg) (allow_empty) (dir .))
> EOF

[mybin] resolves despite no declared deps, because env-registered binaries are
not narrowed:

$ dune build @all
$ cat _build/default/mybin-avail
true
$ cat _build/default/mybin-out
from env binary
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
A "mixed" transitive dependency chain [p] -> [q] -> [r] has one edge crossing
the workspace/lockdir boundary. The two directions behave differently today.

[p] (workspace) -> [q] (workspace) -> [r] (lockdir). The [q] -> [r] edge is
allowed, so this builds and [r-tool] resolves from [p]'s stanza.

$ make_lockdir

A lockdir package [r] that installs [r-tool]:

$ make_lockpkg r <<'EOF'
> (version 0.0.1)
> (build
> (progn
> (system "echo '#!/bin/sh' > r-tool")
> (system "echo 'echo from r' >> r-tool")
> (system "chmod +x r-tool")
> (system "echo 'bin: [ \"r-tool\" ]' > r.install")))
> EOF

$ mkdir -p p q
$ cat >p/dune <<'EOF'
> (rule (with-stdout-to r-avail (echo %{bin-available:r-tool})))
> EOF

$ make_dune_project 3.25
$ cat >> dune-project <<'EOF'
> (package (name p) (allow_empty) (dir p) (depends q))
> (package (name q) (allow_empty) (dir q) (depends r))
> EOF

$ dune build p/r-avail
[r-tool] is resolved since all lockdir packages binaries are available:

$ cat _build/default/p/r-avail
true

Declaring [r] directly on [p] works too:

$ make_dune_project 3.25
$ cat >> dune-project <<'EOF'
> (package (name p) (allow_empty) (dir p) (depends q r))
> (package (name q) (allow_empty) (dir q) (depends r))
> EOF
$ dune clean
$ dune build p/r-avail
$ cat _build/default/p/r-avail
true

Both blocks above print [true] today, so the transitive-only block looks
redundant. It earns its place only once narrowing lands.

[p] (workspace) -> [q] (lockdir) -> [r] (workspace). Now the [q] -> [r] edge is
a lockdir package depending on a workspace package. This is a lock-VALIDATION
restriction, not a narrowing case: it is rejected before any binary resolution
runs, so narrowing does not change it -- it flips only when the in-out work
lifts the restriction (see [../lockdir-workspace-deps/basic.t] for the bare
rejection), at which point [p] should resolve [r-tool], since [r] is then in
[p]'s transitive closure.

$ rm -rf p q r dune.lock
$ dune clean

$ make_lockdir
$ make_lockpkg q <<'EOF'
> (version 0.0.1)
> (depends r)
> (build (system "true"))
> EOF

$ mkdir -p p r
$ cat >r/r-tool.sh <<'EOF'
> #!/bin/sh
> echo from r
> EOF
$ chmod +x r/r-tool.sh
$ cat >r/dune <<'EOF'
> (install (package r) (section bin) (files (r-tool.sh as r-tool)))
> EOF
$ cat >p/dune <<'EOF'
> (rule (with-stdout-to r-avail (echo %{bin-available:r-tool})))
> EOF

$ make_dune_project 3.25
$ cat >> dune-project <<'EOF'
> (package (name p) (allow_empty) (dir p) (depends q))
> (package (name r) (allow_empty) (dir r))
> EOF

$ dune build p/r-avail 2>&1
File "_build/_private/default/.lock/dune.lock/q.pkg", line 2, characters
9-10:
The package "q" depends on the package "r", but "r" does not appear in the
lockdir _build/_private/default/.lock/dune.lock.
Error: At least one package dependency is itself not present as a package in
the lockdir _build/_private/default/.lock/dune.lock.
Hint: This could indicate that the lockdir is corrupted. Delete it and then
regenerate it by running: 'dune pkg lock'
[1]
94 changes: 94 additions & 0 deletions test/blackbox-tests/test-cases/pkg/bin-narrowing/multi-package.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
Per-package narrowing with multiple owning packages: Currently, any workspace
package's stanzas resolve all the lockdir packages' binaries. Every workspace
package can see and resolve all the binaries provided by the lockdir packages.

$ make_lockdir

Two independent lockdir packages, each installing a distinct binary:

$ make_lockpkg tool-a <<'EOF'
> (version 0.0.1)
> (build
> (progn
> (system "echo '#!/bin/sh' > bin-a")
> (system "echo 'echo from-a' >> bin-a")
> (system "chmod +x bin-a")
> (system "echo 'bin: [ \"bin-a\" ]' > tool-a.install")))
> EOF

$ make_lockpkg tool-b <<'EOF'
> (version 0.0.1)
> (build
> (progn
> (system "echo '#!/bin/sh' > bin-b")
> (system "echo 'echo from-b' >> bin-b")
> (system "chmod +x bin-b")
> (system "echo 'bin: [ \"bin-b\" ]' > tool-b.install")))
> EOF

Two workspace packages, each owning a subdirectory and depending on a
different lockdir tool:

$ mkdir -p a b
$ cat >a/dune <<'EOF'
> (rule (with-stdout-to a-sees-a (echo %{bin-available:bin-a})))
> (rule (with-stdout-to a-sees-b (echo %{bin-available:bin-b})))
> (rule (action (with-stdout-to a-path (bash "echo $PATH"))))
> EOF
$ cat >b/dune <<'EOF'
> (rule (with-stdout-to b-sees-a (echo %{bin-available:bin-a})))
> (rule (with-stdout-to b-sees-b (echo %{bin-available:bin-b})))
> (rule (action (with-stdout-to b-path (bash "echo $PATH"))))
> EOF

$ make_dune_project 3.25
$ cat >> dune-project << 'EOF'
> (package (name pkg-a) (allow_empty) (dir a) (depends tool-a))
> (package (name pkg-b) (allow_empty) (dir b) (depends tool-b))
> EOF

$ dune build @all
pkg-a (depends tool-a) sees both bin-a and bin-b:

$ cat _build/default/a/a-sees-a
true
$ cat _build/default/a/a-sees-b
true

pkg-b (depends tool-b) sees both bin-a and bin-b:

$ cat _build/default/b/b-sees-b
true
$ cat _build/default/b/b-sees-a
true

Both packages' env also gets every lockdir package's bin layout on $PATH,
regardless of which tool each declares:

$ env_added "$(cat _build/default/a/a-path)" "$PATH" | censor
$PWD/_build/_private/default/.pkg/tool-b.0.0.1-$DIGEST1/target/bin
$PWD/_build/_private/default/.pkg/tool-a.0.0.1-$DIGEST2/target/bin
$ env_added "$(cat _build/default/b/b-path)" "$PATH" | censor
$PWD/_build/_private/default/.pkg/tool-b.0.0.1-$DIGEST1/target/bin
$PWD/_build/_private/default/.pkg/tool-a.0.0.1-$DIGEST2/target/bin

The narrowing is ultimately about the build-dependency set, not just what is
visible: an unnarrowed lookup forces every lockdir package's cookie. From a
clean build, resolving only [pkg-a]'s rule still builds [tool-b] -- its install
cookie is materialised -- even though [pkg-a] does not depend on it:

$ dune clean
$ dune build a/a-sees-a
$ if test -f "$(get_build_pkg_dir tool-b)/target/cookie"
> then echo "tool-b *was* built"; else echo "tool-b was not built"; fi
tool-b *was* built

Once narrowing lands, each package sees only its own declared tool: [a-sees-b]
and [b-sees-a] flip to false while [a-sees-a]/[b-sees-b] stay true, and each
[*-path] shrinks to just its own tool. Crucially, the cookie check above flips
-- [tool-b] is no longer built -- proving the force-set itself is narrowed per
owner, not merely visibility and PATH. That is the property that actually
breaks the in-and-out cycle. The load-bearing pair is [a-sees-b] false
alongside [b-sees-b] true -- the same lockdir package [tool-b] is hidden from
[pkg-a] yet visible to [pkg-b], showing the narrowing set is keyed on each
dir's owning package, not a single global set.
Loading
Loading