-
Notifications
You must be signed in to change notification settings - Fork 449
Reuse dependencies between project and tools #12526
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
gridbugs
wants to merge
1
commit into
ocaml:main
Choose a base branch
from
gridbugs:share-build-artifacts-between-projec-and-dev-tools
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+701
−213
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
open Import | ||
|
||
let term = | ||
let+ builder = Common.Builder.term | ||
and+ package_name = | ||
Arg.( | ||
required | ||
& pos 0 (some string) None | ||
& info [] ~doc:"The name of the package" ~docv:"PACKAGE") | ||
in | ||
let builder = Common.Builder.forbid_builds builder in | ||
let common, config = Common.init builder in | ||
let package_name = Package_name.of_string package_name in | ||
let context_name = Context_name.default in | ||
Scheduler.go_with_rpc_server ~common ~config (fun () -> | ||
let open Fiber.O in | ||
let* lock_dir_path_opt = | ||
Memo.run (Dune_rules.Lock_dir.get_path_source context_name) | ||
in | ||
match lock_dir_path_opt with | ||
| None -> User_error.raise [ Pp.textf "No lockdir found" ] | ||
| Some lock_dir_path -> | ||
let lock_dir = Dune_pkg.Lock_dir.read_disk_exn (Path.source lock_dir_path) in | ||
let+ platform = Pkg_common.poll_solver_env_from_current_system () in | ||
let packages_by_name = | ||
Dune_pkg.Lock_dir.Packages.pkgs_on_platform_by_name lock_dir.packages ~platform | ||
in | ||
(match Package_name.Map.find packages_by_name package_name with | ||
| None -> | ||
User_error.raise | ||
[ Pp.textf "No such package: %s" (Package_name.to_string package_name) ] | ||
| Some package -> | ||
let slug = Dune_pkg.Lock_dir.Pkg.slug package in | ||
print_endline (Dune_pkg.Lock_dir.Pkg_slug.to_string slug))) | ||
;; | ||
|
||
let info = | ||
let doc = "Print the slug of a package in the project's lockdir." in | ||
Cmd.info "print-slug" ~doc | ||
;; | ||
|
||
let command = Cmd.v info term |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
open Import | ||
|
||
(** Command to print the slug of a given package within the current project. *) | ||
val command : unit Cmd.t |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -534,6 +534,81 @@ let in_source_tree path = | |
[ "path", Path.External.to_dyn e ] | ||
;; | ||
|
||
module Pkg_slug = struct | ||
module T = struct | ||
type t = | ||
{ name : Package_name.t | ||
; version : Package_version.t | ||
; lockfile_and_dependency_digest : Dune_digest.t | ||
(* A hash of the package's lockfile as well as of all lockfiles of the | ||
dependency closure of the package. *) | ||
} | ||
|
||
let equal { name; version; lockfile_and_dependency_digest } t = | ||
Package_name.equal name t.name | ||
&& Package_version.equal version t.version | ||
&& Dune_digest.equal lockfile_and_dependency_digest t.lockfile_and_dependency_digest | ||
;; | ||
|
||
let compare { name; version; lockfile_and_dependency_digest } t = | ||
let open Ordering.O in | ||
let= () = Package_name.compare name t.name in | ||
let= () = Package_version.compare version t.version in | ||
Dune_digest.compare lockfile_and_dependency_digest t.lockfile_and_dependency_digest | ||
;; | ||
|
||
let to_dyn { name; version; lockfile_and_dependency_digest } = | ||
Dyn.record | ||
[ "name", Package_name.to_dyn name | ||
; "version", Package_version.to_dyn version | ||
; ( "lockfile_and_dependency_digest" | ||
, Dune_digest.to_dyn lockfile_and_dependency_digest ) | ||
] | ||
;; | ||
|
||
let hash { name; version; lockfile_and_dependency_digest } = | ||
Tuple.T3.hash | ||
Package_name.hash | ||
Package_version.hash | ||
Dune_digest.hash | ||
(name, version, lockfile_and_dependency_digest) | ||
;; | ||
end | ||
|
||
include T | ||
include Comparable.Make (T) | ||
|
||
let to_string { name; version; lockfile_and_dependency_digest } = | ||
sprintf | ||
"%s.%s-%s" | ||
(Package_name.to_string name) | ||
(Package_version.to_string version) | ||
(Dune_digest.to_string lockfile_and_dependency_digest) | ||
;; | ||
|
||
let of_string s = | ||
let parse_error msg = | ||
User_error.raise [ Pp.textf "Failed to parse %S as a package slug." s; msg ] | ||
in | ||
match String.lsplit2 s ~on:'.' with | ||
| Some (name, rest) -> | ||
(match String.rsplit2 rest ~on:'-' with | ||
| Some (version, lockfile_and_dependency_digest) -> | ||
(match Dune_digest.from_hex lockfile_and_dependency_digest with | ||
| Some lockfile_and_dependency_digest -> | ||
let name = Package_name.of_string name in | ||
let version = Package_version.of_string version in | ||
{ name; version; lockfile_and_dependency_digest } | ||
| None -> | ||
parse_error | ||
(Pp.textf "Failed to parse %S as digest" lockfile_and_dependency_digest)) | ||
| None -> parse_error (Pp.text "Missing '-' between version and lockfile digest.")) | ||
| None -> parse_error (Pp.text "Missing '.' between name and version.") | ||
;; | ||
|
||
let name t = t.name | ||
end | ||
|
||
module Pkg = struct | ||
type t = | ||
{ build_command : Build_command.t Conditional_choice.t | ||
|
@@ -543,6 +618,7 @@ module Pkg = struct | |
; info : Pkg_info.t | ||
; exported_env : String_with_vars.t Action.Env_update.t list | ||
; enabled_on_platforms : Solver_env_disjunction.t | ||
; slug : Pkg_slug.t Lazy.t option | ||
} | ||
|
||
let equal | ||
|
@@ -553,6 +629,7 @@ module Pkg = struct | |
; info | ||
; exported_env | ||
; enabled_on_platforms | ||
; slug = _ | ||
} | ||
t | ||
= | ||
|
@@ -577,6 +654,7 @@ module Pkg = struct | |
; info | ||
; exported_env | ||
; enabled_on_platforms | ||
; slug = _ | ||
} | ||
= | ||
Poly.hash | ||
|
@@ -598,6 +676,7 @@ module Pkg = struct | |
; info | ||
; exported_env | ||
; enabled_on_platforms | ||
; slug = _ | ||
} | ||
= | ||
Conditional_choice.digest_feed Digest_feed.generic hasher build_command; | ||
|
@@ -617,6 +696,7 @@ module Pkg = struct | |
; info | ||
; exported_env | ||
; enabled_on_platforms | ||
; slug | ||
} | ||
= | ||
{ info = Pkg_info.remove_locs info | ||
|
@@ -627,6 +707,7 @@ module Pkg = struct | |
; build_command = Conditional_choice.map build_command ~f:Build_command.remove_locs | ||
; install_command = Conditional_choice.map install_command ~f:Action.remove_locs | ||
; enabled_on_platforms | ||
; slug | ||
} | ||
;; | ||
|
||
|
@@ -638,6 +719,7 @@ module Pkg = struct | |
; info | ||
; exported_env | ||
; enabled_on_platforms | ||
; slug | ||
} | ||
= | ||
Dyn.record | ||
|
@@ -649,6 +731,7 @@ module Pkg = struct | |
; ( "exported_env" | ||
, Dyn.list (Action.Env_update.to_dyn String_with_vars.to_dyn) exported_env ) | ||
; "enabled_on_platforms", Solver_env_disjunction.to_dyn enabled_on_platforms | ||
; "slug", Dyn.option Dyn.opaque slug | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is it opaque here? |
||
] | ||
;; | ||
|
||
|
@@ -768,6 +851,7 @@ module Pkg = struct | |
; info | ||
; exported_env | ||
; enabled_on_platforms | ||
; slug = None | ||
} | ||
;; | ||
|
||
|
@@ -788,6 +872,7 @@ module Pkg = struct | |
; info = { Pkg_info.name = _; extra_sources; version; dev; avoid; source } | ||
; exported_env | ||
; enabled_on_platforms | ||
; slug = _ | ||
} | ||
= | ||
let open Encoder in | ||
|
@@ -941,6 +1026,8 @@ module Pkg = struct | |
List.is_empty t.enabled_on_platforms | ||
|| Solver_env_disjunction.matches_platform t.enabled_on_platforms ~platform | ||
;; | ||
|
||
let slug t = Lazy.force (Option.value_exn t.slug) | ||
end | ||
|
||
module Repositories = struct | ||
|
@@ -991,6 +1078,7 @@ end | |
module Packages = struct | ||
type t = Pkg.t Package_version.Map.t Package_name.Map.t | ||
|
||
let empty = Package_name.Map.empty | ||
let remove_locs = Package_name.Map.map ~f:(Package_version.Map.map ~f:Pkg.remove_locs) | ||
let equal = Package_name.Map.equal ~equal:(Package_version.Map.equal ~equal:Pkg.equal) | ||
let to_dyn = Package_name.Map.to_dyn (Package_version.Map.to_dyn Pkg.to_dyn) | ||
|
@@ -1039,6 +1127,85 @@ module Packages = struct | |
| Some x, None | None, Some x -> Some x | ||
| Some a, Some b -> Some (Pkg.merge_conditionals a b)))) | ||
;; | ||
|
||
exception | ||
Dependency_cycle of | ||
{ at : Pkg.t | ||
; through : Pkg.t list | ||
} | ||
|
||
(* [compute_slug t pkg] computes the slug of the package [pkg] using [t] to | ||
resolve dependencies of [pkg]. *) | ||
let compute_slug (t : t) (pkg : Pkg.t) = | ||
let iter_all_versions_of_non_dune_dependencies f = | ||
List.iter pkg.depends ~f:(fun { Conditional.value = depends; _ } -> | ||
List.iter depends ~f:(fun { Dependency.name = dep_name; _ } -> | ||
(* Don't call [f] on a dependency on the dune package | ||
itself as these aren't stored in the lockdir. *) | ||
if not (Package_name.equal dep_name Dune_dep.name) | ||
then ( | ||
let dep_version_map = | ||
(* This is a map rather than a single package because when | ||
portable lockdirs are enabled there may be several different | ||
versions of a package in the lockdir. Since we don't know the | ||
user's platform here, err on the side of caution and hash all | ||
versions of the dependency. *) | ||
Package_name.Map.find_exn t dep_name | ||
in | ||
Package_version.Map.iter dep_version_map ~f))) | ||
in | ||
(* Force the computation of the slugs of this package's | ||
dependencies. This needs to happen before the call to | ||
[Digest_feed.compute_digest_with_hasher] because the hasher is a | ||
singleton. *) | ||
(try | ||
iter_all_versions_of_non_dune_dependencies (fun dep -> | ||
try | ||
let _ : Pkg_slug.t = Pkg.slug dep in | ||
() | ||
with | ||
| Lazy.Undefined -> | ||
(* Forcing the computation of the slug of this dependency is was | ||
already in progress, indicating a dependency cycle. Raise an | ||
exception with information about the cycle so the ancestor of | ||
[pkg] which is also [dep] can catch the exception and print an | ||
error message with the sequence of dependencies which form the | ||
cycle. *) | ||
raise (Dependency_cycle { at = dep; through = [ dep ] })) | ||
with | ||
| Dependency_cycle { at; through } -> | ||
let through = pkg :: through in | ||
if Pkg.equal pkg at | ||
then | ||
User_error.raise | ||
[ Pp.textf "Dependency cycle between packages:" | ||
; Pp.chain through ~f:(fun (pkg : Pkg.t) -> | ||
Pp.textf | ||
"%s.%s" | ||
(Package_name.to_string pkg.info.name) | ||
(Package_version.to_string pkg.info.version)) | ||
] | ||
else raise (Dependency_cycle { at; through })); | ||
let lockfile_and_dependency_digest = | ||
Digest_feed.compute_digest_with_hasher (fun hasher -> | ||
(* compute the digest of the lockfile. note that | ||
[Pkg.digest_feed does not force the [slug] field. *) | ||
Pkg.digest_feed hasher (Pkg.remove_locs pkg); | ||
(* Evaluate the slugs for the transitive dependency closure | ||
of this package, and feed the digests from the slugs of the | ||
immediate dependencies into the current hasher. Because the digest | ||
of one package is influenced by its dependencies, this will cause | ||
the digest of the current package to be influenced by its entire | ||
dependency closure. *) | ||
iter_all_versions_of_non_dune_dependencies (fun dep -> | ||
let dep_slug = Pkg.slug dep in | ||
Digest_feed.digest hasher dep_slug.lockfile_and_dependency_digest)) | ||
in | ||
{ Pkg_slug.name = pkg.info.name | ||
; version = pkg.info.version | ||
; lockfile_and_dependency_digest | ||
} | ||
;; | ||
end | ||
|
||
type t = | ||
|
@@ -1610,6 +1777,11 @@ struct | |
| Some x -> true, x | ||
| None -> false, (Loc.none, []) | ||
in | ||
let packages_cell = | ||
(* Cell to break the cycle between computing the table of packages and | ||
computing the slugs of packages *) | ||
ref Packages.empty | ||
in | ||
let+ packages = | ||
Io.readdir_with_kinds lock_dir_path | ||
>>| List.filter_map ~f:(fun (name, (kind : Unix.file_kind)) -> | ||
|
@@ -1629,9 +1801,11 @@ struct | |
package_name | ||
maybe_package_version | ||
in | ||
pkg) | ||
let slug = lazy (Packages.compute_slug !packages_cell pkg) in | ||
{ pkg with slug = Some slug }) | ||
>>| Packages.of_pkg_list | ||
in | ||
packages_cell := packages; | ||
check_packages packages ~lock_dir_path | ||
|> Result.map ~f:(fun () -> | ||
{ version | ||
|
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe calling it
Summary
would be a better name? I've only ever encountered the word slug in IT in Django and even there the connection is more due to publishing.