From bd29b159396e41b256deec430ae08cbf5b70ce77 Mon Sep 17 00:00:00 2001 From: Stephen Sherratt Date: Tue, 22 Oct 2024 12:40:01 +1100 Subject: [PATCH] pkg: relax version constraints for ocamlformat dev tool (#11019) 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 --- bin/lock_dev_tool.ml | 30 +++++++- .../ocamlformat-relaxed-version-constraints.t | 74 +++++++++++++++++++ 2 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 test/blackbox-tests/test-cases/pkg/ocamlformat/ocamlformat-relaxed-version-constraints.t diff --git a/bin/lock_dev_tool.ml b/bin/lock_dev_tool.ml index 7aa04163df9..02f2378f2f7 100644 --- a/bin/lock_dev_tool.ml +++ b/bin/lock_dev_tool.ml @@ -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. *) @@ -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 diff --git a/test/blackbox-tests/test-cases/pkg/ocamlformat/ocamlformat-relaxed-version-constraints.t b/test/blackbox-tests/test-cases/pkg/ocamlformat/ocamlformat-relaxed-version-constraints.t new file mode 100644 index 00000000000..987d9f2d60c --- /dev/null +++ b/test/blackbox-tests/test-cases/pkg/ocamlformat/ocamlformat-relaxed-version-constraints.t @@ -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 < 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]