Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
101 changes: 76 additions & 25 deletions src/dune_pkg/opam_solver.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1661,6 +1661,60 @@ let package_kind =
else `Non_compiler
;;

let resolve_url (file_url : OpamFile.URL.t) =
let url = OpamFile.URL.url file_url in
let+ url : OpamUrl.t =
match url.backend with
| `git ->
let loc = Loc.none in
let+ mount = Mount.of_opam_url loc url in
(match Mount.backend mount with
| Path path ->
Code_error.raise
"Attempted to resolve git URL but loading it resolved to a local path"
[ "url", OpamUrl.to_dyn url; "path", Path.to_dyn path ]
| Git at_rev ->
let resolved_hash = at_rev |> Rev_store.At_rev.rev |> Rev_store.Object.to_hex in
(match !Dune_engine.Clflags.display, url.hash with
| Short, Some old_hash ->
(match String.equal resolved_hash old_hash with
| true -> ()
| false ->
User_warning.emit
[ Pp.textf
"Locking repository URL %s to commit %s"
(OpamUrl.to_string url)
resolved_hash
])
| _ -> ());
{ url with hash = Some resolved_hash })
| _ -> Fiber.return url
in
OpamFile.URL.with_url url file_url
;;

let resolve_opam_packages opam_packages_to_lock ~resolve_package =
opam_packages_to_lock
|> List.map ~f:(fun opam_package ->
let name = OpamPackage.name opam_package |> Package_name.of_opam_package_name in
let version = OpamPackage.version opam_package in
let resolved_package = resolve_package name version in
(* resolve URLs *)
let+ resolved_package =
let opam_file = Resolved_package.opam_file resolved_package in
let+ opam_file =
match OpamFile.OPAM.url opam_file with
| None -> Fiber.return opam_file
| Some url ->
let+ url = resolve_url url in
OpamFile.OPAM.with_url url opam_file
in
Resolved_package.with_opam_file opam_file resolved_package
in
name, opam_package, resolved_package)
|> Fiber.all
;;

let solve_lock_dir
solver_env
version_preference
Expand Down Expand Up @@ -1738,8 +1792,7 @@ let solve_lock_dir
(Table.find_exn candidates_cache name).resolved
|> OpamPackage.Version.Map.find version
in
let pkgs_by_name =
let open Result.O in
let* pkgs_by_name =
let+ pkgs =
let version_by_package_name =
Package_name.Map.of_list_map_exn
Expand All @@ -1748,13 +1801,10 @@ let solve_lock_dir
( Package_name.of_opam_package_name (OpamPackage.name package)
, Package_version.of_opam_package_version (OpamPackage.version package) ))
in
List.map opam_packages_to_lock ~f:(fun opam_package ->
let name =
OpamPackage.name opam_package |> Package_name.of_opam_package_name
in
let resolved_package =
resolve_package name (OpamPackage.version opam_package)
in
let+ resolved_pkgs =
resolve_opam_packages opam_packages_to_lock ~resolve_package
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about passing the table instead of the function? I prefer first order code when possible.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, however the resolve_package function is used further down, where in computes ocaml, so I can't completely eliminate it without major rewriting.

in
List.map resolved_pkgs ~f:(fun (name, opam_package, resolved_package) ->
Lock_pkg.opam_package_to_lock_file_pkg
solver_env
stats_updater
Expand All @@ -1765,22 +1815,23 @@ let solve_lock_dir
~portable_lock_dir)
|> Result.List.all
in
match Package_name.Map.of_list_map pkgs ~f:(fun pkg -> pkg.info.name, pkg) with
| Error (name, _pkg1, _pkg2) ->
Code_error.raise
"Solver selected multiple versions for the same package"
[ "name", Package_name.to_dyn name ]
| Ok pkgs_by_name ->
let reachable =
reject_unreachable_packages
solver_env
~dune_version:
(Package_version.of_opam_package_version context.dune_version)
~local_packages
~pkgs_by_name
in
Package_name.Map.filteri pkgs_by_name ~f:(fun name _ ->
Package_name.Set.mem reachable name)
Result.map pkgs ~f:(fun pkgs ->
match Package_name.Map.of_list_map pkgs ~f:(fun pkg -> pkg.info.name, pkg) with
| Error (name, _pkg1, _pkg2) ->
Code_error.raise
"Solver selected multiple versions for the same package"
[ "name", Package_name.to_dyn name ]
| Ok pkgs_by_name ->
let reachable =
reject_unreachable_packages
solver_env
~dune_version:
(Package_version.of_opam_package_version context.dune_version)
~local_packages
~pkgs_by_name
in
Package_name.Map.filteri pkgs_by_name ~f:(fun name _ ->
Package_name.Set.mem reachable name))
in
let ocaml =
let open Result.O in
Expand Down
5 changes: 5 additions & 0 deletions src/dune_pkg/resolved_package.ml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ let opam_file = function
| Rest t -> t.opam_file
;;

let with_opam_file opam_file = function
| Dune -> Dune
| Rest r -> Rest { r with opam_file }
;;

let extra_files = function
| Dune -> None
| Rest t -> Some t.extra_files
Expand Down
1 change: 1 addition & 0 deletions src/dune_pkg/resolved_package.mli
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ type t
val package : t -> OpamPackage.t
val opam_file : t -> OpamFile.OPAM.t
val loc : t -> Loc.t
val with_opam_file : OpamFile.OPAM.t -> t -> t

(** Determines whether the package is to be built using Dune or not *)
val dune_build : t -> bool
Expand Down
4 changes: 4 additions & 0 deletions test/blackbox-tests/test-cases/pkg/dune
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@
(deps %{bin:awk} %{bin:cmp})
(applies_to git-repo))

(cram
(deps fakegit.sh)
(applies_to opam-source-conversion))

(cram
(deps %{bin:make})
(applies_to make))
Expand Down
55 changes: 55 additions & 0 deletions test/blackbox-tests/test-cases/pkg/fakegit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/bin/bash

# dummy implementation of just enough git to be able to create a lock file
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit surprised that you need this. Isn't it possible to use a remote that is on the local file system? Or does that not test everything?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The opam-source-conversions.t test tries multiple types of remotes:

  • git+file which can be tested by redirecting it to an existing local repo (see the second commit in this PR)
  • git+http which would need to be redirected to a local HTTP server. Possible but given how many issues the one-shot HTTP server had, I'd rather not launch and interact with an HTTP server in Cram tests.
  • git+foobar can't really be tested locally.

I've created #13333 to discuss this issue and it seemed to me that fitting in a fake git in the git+http and git+foobar cases would be the best course of action.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, so the tests are using the real git binary for git+file then?

Copy link
Collaborator Author

@Leonidas-from-XIV Leonidas-from-XIV Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is only used in the git+http and git+foobar cases where our fake git binary in _bin/git is injected via

  $ PATH=_bin:$PATH solve testpkg

The git+file case uses the real git binary provided by the system. I've specifically tested that it resolves correctly because I remember that there is some weirdness when setting environment variables in front of function calls on sh. But given we run with bash this issue is not happening.


# we need access to the real git binary, so we expect the env to have a
# REAL_GIT variable pointing to the actual git
REAL_GIT=${REAL_GIT:-false}

case $1 in
init)
# we pass this over to actual git
$REAL_GIT "$@"
exit $?
;;
ls-remote)
# hardcoded output, just HEAD pointing to a revision
echo "058003b274f05b092a391555ffdee8b36f1897b3 HEAD"
;;
fetch)
# we don't fetch from anywhere
exit 0
;;
ls-tree)
# the revision that HEAD is pointing to, just a single empty file
echo "100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 empty"
exit 0
;;
cat-file)
IFS=':' read -r -a rev_and_path <<< "$3"
if [[ -z ${rev_and_path[1]} ]]; then
echo ""
exit 0
fi
if [ "${rev_and_path[1]}" = ".gitmodules" ]; then
echo "does not exist" >&2
exit 128
fi
echo "Unsupported cat-file command: $@" >&2
exit 2
;;
rev-parse)
# let git answer this one
if [ "$2" = "--is-bare-repository" ]; then
$REAL_GIT "$@"
exit $?
fi
echo "Unsupported rev-parse command: $@" >&2
exit 2
;;
*)
# unsupported, exit out
echo "Unsupported command: $@" >&2
exit 2
;;
esac
36 changes: 26 additions & 10 deletions test/blackbox-tests/test-cases/pkg/opam-source-conversion.t
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Unsupported backends:
> }
> EOF

$ solve testpkg 2>&1
$ solve testpkg
Solution for dune.lock:
- testpkg.0.0.1
$ showpkg
Expand All @@ -72,6 +72,15 @@ Unsupported backends:
(url hg+http://no-support.com/foo)
(checksum md5=069aa55d40e548280f92af693f6c625a)))

Create a local git repo to resolve to

$ mkdir _repo
$ git -C _repo init --initial-branch=main --quiet
$ touch _repo/content
$ git -C _repo add -A
$ git -C _repo commit -m "Initial commit" --quiet
$ expected_hash=$(git -C _repo rev-parse HEAD)

git+http

$ rm -rf ${default_lock_dir}
Expand All @@ -82,35 +91,41 @@ git+http
> }
> EOF

$ solve testpkg 2>&1
$ mkdir _bin
$ cp fakegit.sh _bin/git
$ export REAL_GIT=$(which git)

$ PATH=_bin:$PATH solve testpkg
Solution for dune.lock:
- testpkg.0.0.1
$ showpkg
(version 0.0.1)

(source
(fetch
(url git+http://github.com/foo)
(url git+http://github.com/foo#058003b274f05b092a391555ffdee8b36f1897b3)
(checksum md5=069aa55d40e548280f92af693f6c625a)))

git+file

$ rm -rf ${default_lock_dir}

$ mkpkg testpkg <<EOF
> url {
> src: "git+file://here"
> src: "git+file://$PWD/_repo"
> checksum: "md5=069aa55d40e548280f92af693f6c625a"
> }
> EOF
$ solve testpkg 2>&1
$ solve testpkg
Solution for dune.lock:
- testpkg.0.0.1
$ showpkg
$ showpkg | dune_cmd subst "$PWD" '$PWD' | dune_cmd subst "$expected_hash" '$EXPECTED_HASH'
(version 0.0.1)

(source
(fetch
(url git+file://here)
(url
git+file://$PWD/_repo#$EXPECTED_HASH)
(checksum md5=069aa55d40e548280f92af693f6c625a)))

git+foobar
Expand All @@ -122,15 +137,16 @@ git+foobar
> checksum: "md5=069aa55d40e548280f92af693f6c625a"
> }
> EOF
$ solve testpkg 2>&1
$ PATH=_bin:$PATH solve testpkg
Solution for dune.lock:
- testpkg.0.0.1
$ showpkg
(version 0.0.1)

(source
(fetch
(url git+foobar://random-thing-here)
(url
git+foobar://random-thing-here#058003b274f05b092a391555ffdee8b36f1897b3)
(checksum md5=069aa55d40e548280f92af693f6c625a)))

file+git
Expand All @@ -142,7 +158,7 @@ file+git
> checksum: "md5=069aa55d40e548280f92af693f6c625a"
> }
> EOF
$ solve testpkg 2>&1
$ solve testpkg
Solution for dune.lock:
- testpkg.0.0.1
$ showpkg
Expand Down
15 changes: 9 additions & 6 deletions test/blackbox-tests/test-cases/pkg/pin-depends.t
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,14 @@ Git pinned source:
> EOF
$ git add -A
$ git commit --quiet -m "Initial commit"
$ expected_commit=$(git rev-parse HEAD)
$ cd ..
$ runtest "git+file://$PWD/$dir"
$ runtest "git+file://$PWD/$dir" | dune_cmd subst $expected_commit '$EXPECTED_COMMIT'
Solution for dune.lock:
- bar.1.0.0
(version 1.0.0)
(dev)
(source (fetch (url git+file://PWD/_bar_git)))
(source (fetch (url git+file://PWD/_bar_git#$EXPECTED_COMMIT)))

Git pinned source with toplevel opam file:

Expand All @@ -109,13 +110,14 @@ Git pinned source with toplevel opam file:
> EOF
$ git add -A
$ git commit --quiet -m "Initial commit"
$ expected_commit=$(git rev-parse HEAD)
$ cd ..
$ runtest "git+file://$PWD/$dir"
$ runtest "git+file://$PWD/$dir" | dune_cmd subst $expected_commit '$EXPECTED_COMMIT'
Solution for dune.lock:
- bar.1.0.0
(version 1.0.0)
(dev)
(source (fetch (url git+file://PWD/_bar_opam_git)))
(source (fetch (url git+file://PWD/_bar_opam_git#$EXPECTED_COMMIT)))

Git pinned source with toplevel opam dir 1

Expand All @@ -129,13 +131,14 @@ Git pinned source with toplevel opam dir 1
> EOF
$ git add -A
$ git commit --quiet -m "Initial commit"
$ expected_commit=$(git rev-parse HEAD)
$ cd ..
$ runtest "git+file://$PWD/$dir"
$ runtest "git+file://$PWD/$dir" | dune_cmd subst $expected_commit '$EXPECTED_COMMIT'
Solution for dune.lock:
- bar.1.0.0
(version 1.0.0)
(dev)
(source (fetch (url git+file://PWD/_bar_opam_dir_git1)))
(source (fetch (url git+file://PWD/_bar_opam_dir_git1#$EXPECTED_COMMIT)))

Git pinned source with toplevel opam dir 2

Expand Down
Loading
Loading