Skip to content

Commit

Permalink
pkg: relax version constraints for ocamlformat dev tool (#11019)
Browse files Browse the repository at this point in the history
Previously dune would install the exact version of ocamlformat specified
in the .ocamlformat file in the project's root. This prevents
alternative distributions of the ocamlformat package from being used, as
these typically append a suffix to the version number.

The motivation for this change is supporting a binary dev tools
distribution where the ocamlformat package is named like
"ocamlformat.0.26.2+binary", and we want that package to be installable
when the .ocamlformat file of a project specifies "version=0.26.2".

Signed-off-by: Stephen Sherratt <stephen@sherra.tt>
  • Loading branch information
gridbugs authored Oct 22, 2024
1 parent 2fa3ab4 commit bd29b15
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 4 deletions.
30 changes: 26 additions & 4 deletions bin/lock_dev_tool.ml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,31 @@ let is_enabled =
| `Disabled -> false)
;;

(* Returns a version constraint accepting (almost) all versions whose prefix is
the given version. This allows alternative distributions of packages to be
chosen, such as choosing "ocamlformat.0.26.2+binary" when .ocamlformat
contains "version=0.26.2". *)
let relaxed_version_constraint_of_version version =
let open Dune_lang in
let min_version = Package_version.to_string version in
(* The goal here is to add a suffix to [min_version] to construct a version
number higher than than any version number likely to appear with
[min_version] as a prefix. "_" is the highest ascii symbol that can appear
in version numbers, excluding "~" which has a special meaning. It's
conceivable that one or two consecutive "_" characters may be used in a
version, so this appends "___" to [min_version].
Read more at: https://opam.ocaml.org/doc/Manual.html#Version-ordering
*)
let max_version = min_version ^ "___MAX_VERSION" in
Package_constraint.And
[ Package_constraint.Uop
(Relop.Gte, Package_constraint.Value.String_literal min_version)
; Package_constraint.Uop
(Relop.Lte, Package_constraint.Value.String_literal max_version)
]
;;

(* The solver satisfies dependencies for local packages, but dev tools
are not local packages. As a workaround, create an empty local package
which depends on the dev tool package. *)
Expand All @@ -24,10 +49,7 @@ let make_local_package_wrapping_dev_tool ~dev_tool ~dev_tool_version ~extra_depe
let open Dune_lang in
let open Package_dependency in
let constraint_ =
Option.map dev_tool_version ~f:(fun version ->
Package_constraint.Uop
( Relop.Eq
, Package_constraint.Value.String_literal (Package_version.to_string version) ))
Option.map dev_tool_version ~f:relaxed_version_constraint_of_version
in
{ name = dev_tool_pkg_name; constraint_ }
in
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
Check that dune can choose a version of ocamlformat with a suffix (e.g.
0.24+foo) to satisfy a .ocamlformat config that specifies a matching version
without the suffix.
$ . ./helpers.sh
$ mkrepo
$ make_project_with_dev_tool_lockdir

Fake ocamlformat package that appends a comment with the ocamlformat version to the end of the file:
$ ocamlformat_package() {
> cat <<EOF
> install: [
> [ "sh" "-c" "echo '#!/bin/sh' > %{bin}%/ocamlformat" ]
> [ "sh" "-c" "echo 'cat \$2' >> %{bin}%/ocamlformat" ]
> [ "sh" "-c" "echo 'echo \$2 | grep .*.ml >/dev/null && echo \"(* formatted with fake ocamlformat %{version}% *)\"' >> %{bin}%/ocamlformat" ]
> [ "sh" "-c" "chmod a+x %{bin}%/ocamlformat" ]
> ]
> EOF
> }

Make some fake ocamlformat packages:
$ ocamlformat_package | mkpkg ocamlformat 0.24+foo
$ ocamlformat_package | mkpkg ocamlformat 0.25+bar

Initial file:
$ cat foo.ml
let () = print_endline "Hello, world"

This should choose the 0.24+foo version:
$ echo "version=0.24" > .ocamlformat
$ DUNE_CONFIG__LOCK_DEV_TOOL=enabled dune fmt
Solution for dev-tools.locks/ocamlformat:
- ocamlformat.0.24+foo
File "foo.ml", line 1, characters 0-0:
Error: Files _build/default/foo.ml and _build/default/.formatted/foo.ml
differ.
Promoting _build/default/.formatted/foo.ml to foo.ml.
[1]
$ cat foo.ml
let () = print_endline "Hello, world"
(* formatted with fake ocamlformat 0.24+foo *)

This should choose the 0.24+bar version:
$ echo "version=0.25" > .ocamlformat
$ rm -rf dev-tools.locks
$ DUNE_CONFIG__LOCK_DEV_TOOL=enabled dune fmt
Solution for dev-tools.locks/ocamlformat:
- ocamlformat.0.25+bar
File "foo.ml", line 1, characters 0-0:
Error: Files _build/default/foo.ml and _build/default/.formatted/foo.ml
differ.
Promoting _build/default/.formatted/foo.ml to foo.ml.
[1]
$ cat foo.ml
let () = print_endline "Hello, world"
(* formatted with fake ocamlformat 0.24+foo *)
(* formatted with fake ocamlformat 0.25+bar *)

This should fail as there is no version matching 0.24.1:
$ echo "version=0.24.1" > .ocamlformat
$ rm -rf dev-tools.locks
$ DUNE_CONFIG__LOCK_DEV_TOOL=enabled dune fmt
Error: Unable to solve dependencies for the following lock directories:
Lock directory dev-tools.locks/ocamlformat:
Can't find all required versions.
Selected: ocamlformat_dev_tool_wrapper.dev
- ocamlformat -> (problem)
ocamlformat_dev_tool_wrapper dev requires >= 0.24.1 & <=
0.24.1___MAX_VERSION
Rejected candidates:
ocamlformat.0.25+bar: Incompatible with restriction: >= 0.24.1 & <=
0.24.1___MAX_VERSION
ocamlformat.0.24+foo: Incompatible with restriction: >= 0.24.1 & <=
0.24.1___MAX_VERSION
[1]

0 comments on commit bd29b15

Please sign in to comment.