Skip to content

Commit 35d5716

Browse files
committed
feat(pkg): autolocking
Signed-off-by: Ali Caglayan <[email protected]>
1 parent b202280 commit 35d5716

File tree

12 files changed

+707
-40
lines changed

12 files changed

+707
-40
lines changed

src/dune_pkg/opam_repo.ml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,34 @@ let revision t =
128128
| Directory _ -> Code_error.raise "not a git repo" []
129129
;;
130130

131+
let content_digest t =
132+
match t.source with
133+
| Repo repo ->
134+
Rev_store.At_rev.rev repo |> Rev_store.Object.to_hex |> Dune_digest.string
135+
| Directory path ->
136+
(match Path.lstat path with
137+
| Error e ->
138+
User_error.raise
139+
[ Pp.textf "Can't stat path %s" (Path.to_string path)
140+
; Pp.textf "reason: %s" (Unix_error.Detailed.to_string_hum e)
141+
]
142+
| Ok stats ->
143+
(match
144+
Dune_digest.Stats_for_digest.of_unix_stats stats
145+
|> Dune_digest.path_with_stats ~allow_dirs:true path
146+
with
147+
| Ok digest -> digest
148+
| Error (Unix_error e) ->
149+
User_error.raise
150+
[ Pp.textf "Can't digest path %S:" (Path.to_string path)
151+
; Unix_error.Detailed.pp e
152+
]
153+
| Error Unexpected_kind ->
154+
User_error.raise
155+
[ Pp.textf "Can't digest path %S: unexpected file kind" (Path.to_string path)
156+
]))
157+
;;
158+
131159
let load_opam_package_from_dir ~(dir : Path.t) package =
132160
let opam_file_path = Paths.opam_file package in
133161
match Path.exists (Path.append_local dir opam_file_path) with

src/dune_pkg/opam_repo.mli

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ val of_git_repo : Loc.t -> OpamUrl.t -> t Fiber.t
2626
val revision : t -> Rev_store.At_rev.t
2727
val serializable : t -> Serializable.t option
2828

29+
(** [content_digest t] digests the contents of an opam repository. For a Git
30+
repository, this is a digest of the commit SHA. For a directory-based
31+
repository, this is a digest of the directory's contents.
32+
33+
Raises [User_error] in the directory case if the path cannot be accessed or
34+
digested due to permission errors, the directory being deleted or modified
35+
between stat and digest, or other filesystem errors. *)
36+
val content_digest : t -> Dune_digest.t
37+
2938
module Key : sig
3039
type t
3140

src/dune_pkg/package_universe.ml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,14 +134,16 @@ let check_for_unnecessary_packges_in_lock_dir
134134
])
135135
;;
136136

137-
let up_to_date local_packages ~dependency_hash:saved_dependency_hash =
137+
let dependency_digest local_packages =
138138
let local_packages =
139139
Package_name.Map.values local_packages |> List.map ~f:Local_package.for_solver
140140
in
141-
let dependency_hash =
142-
Local_package.For_solver.non_local_dependencies local_packages
143-
|> Local_package.Dependency_hash.of_dependency_formula
144-
in
141+
Local_package.For_solver.non_local_dependencies local_packages
142+
|> Local_package.Dependency_hash.of_dependency_formula
143+
;;
144+
145+
let up_to_date local_packages ~dependency_hash:saved_dependency_hash =
146+
let dependency_hash = dependency_digest local_packages in
145147
match saved_dependency_hash, dependency_hash with
146148
| None, None -> `Valid
147149
| Some lock_dir_dependency_hash, Some non_local_dependencies_hash

src/dune_pkg/package_universe.mli

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ val create
1414
-> Lock_dir.t
1515
-> (t, User_message.t) result
1616

17+
val dependency_digest
18+
: Local_package.t Package_name.Map.t
19+
-> Local_package.Dependency_hash.t option
20+
1721
(** Verifies if the dependencies described in the project file are still
1822
synchronized with the dependencies selected in the lock directroy. If it is
1923
not the case, it returns the hash of the new dependency set. *)

src/dune_pkg/resolved_package.ml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ let opam_file = function
3838
| Rest t -> t.opam_file
3939
;;
4040

41+
let extra_files = function
42+
| Dune -> None
43+
| Rest t -> Some t.extra_files
44+
;;
45+
4146
let add_opam_package_to_opam_file package opam_file =
4247
opam_file
4348
|> OpamFile.OPAM.with_version (OpamPackage.version package)
@@ -188,3 +193,70 @@ let get_opam_package_files resolved_packages =
188193
| Some _ -> Some (Option.value files ~default:[]))
189194
|> Int.Map.values
190195
;;
196+
197+
let digest_extra_files : extra_files -> Dune_digest.t = function
198+
| Inside_files_dir path_opt ->
199+
(match path_opt with
200+
| None ->
201+
Sexp.List [ Atom "inside_files_dir"; Atom "none" ]
202+
|> Sexp.to_string
203+
|> Dune_digest.string
204+
| Some path ->
205+
(match Path.lstat path with
206+
| Error e ->
207+
User_error.raise
208+
[ Pp.textf "Can't stat path %s" (Path.to_string path)
209+
; Pp.textf "reason: %s" (Unix_error.Detailed.to_string_hum e)
210+
]
211+
| Ok stats ->
212+
let stats_for_digest = Dune_digest.Stats_for_digest.of_unix_stats stats in
213+
(match Dune_digest.path_with_stats ~allow_dirs:true path stats_for_digest with
214+
| Ok digest -> digest
215+
| Error (Unix_error e) ->
216+
User_error.raise
217+
[ Pp.textf "Can't digest path %S:" (Path.to_string path)
218+
; Unix_error.Detailed.pp e
219+
]
220+
| Error Unexpected_kind ->
221+
User_error.raise
222+
[ Pp.textf
223+
"Can't digest path %S: unexpected file kind"
224+
(Path.to_string path)
225+
])))
226+
| Git_files (path_opt, rev) ->
227+
let path_str =
228+
match path_opt with
229+
| None -> "none"
230+
| Some p -> Path.Local.to_string p
231+
in
232+
Sexp.List
233+
[ Atom "git_files"
234+
; Atom path_str
235+
; Atom (Rev_store.At_rev.rev rev |> Rev_store.Object.to_hex)
236+
]
237+
|> Sexp.to_string
238+
|> Dune_digest.string
239+
;;
240+
241+
let digest res_pkg =
242+
Sexp.record
243+
[ "opam_file", Atom (OpamFile.OPAM.write_to_string (opam_file res_pkg))
244+
; ( "package"
245+
, let opam_pkg = package res_pkg in
246+
Sexp.record
247+
[ "name", Atom (OpamPackage.name opam_pkg |> OpamPackage.Name.to_string)
248+
; "version", Atom (OpamPackage.version opam_pkg |> OpamPackage.Version.to_string)
249+
] )
250+
; "loc", Atom (loc res_pkg |> Dyn.opaque |> Dyn.to_string)
251+
; "dune_build", Atom (dune_build res_pkg |> Bool.to_string)
252+
; ( "extra_files"
253+
, Atom
254+
(extra_files res_pkg
255+
|> Option.map ~f:digest_extra_files
256+
|> Dune_digest.Feed.compute_digest
257+
(Dune_digest.Feed.option Dune_digest.Feed.digest)
258+
|> Dune_digest.to_string) )
259+
]
260+
|> Sexp.to_string
261+
|> Dune_digest.string
262+
;;

src/dune_pkg/resolved_package.mli

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,12 @@ val local_package
3535
val get_opam_package_files
3636
: t list
3737
-> (File_entry.t list list, User_message.t) result Fiber.t
38+
39+
(** [digest t] computes a digest of the resolved package contents, excluding the
40+
source location. For directory-based extra files, the digest of the
41+
directory contents is included. For git-based extra files, the commit SHA is
42+
included.
43+
44+
Raises [User_error] if extra files in a directory cannot be accessed or
45+
digested due to permission errors, filesystem errors. *)
46+
val digest : t -> Dune_digest.t

src/dune_rules/lock_dir.ml

Lines changed: 69 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -177,22 +177,12 @@ let get_path ctx_name =
177177
| false -> None
178178
| true -> Some ctx)
179179
in
180-
let* lock_dir_paths =
181-
match ctx with
182-
| None | Some (Default { lock_dir = None; _ }) ->
183-
Memo.return (Some (default_source_path, default_path))
184-
| Some (Default { lock_dir = Some lock_dir_selection; _ }) ->
185-
let+ source_lock_dir = select_lock_dir lock_dir_selection in
186-
Some (source_lock_dir, lock_dir_of_source source_lock_dir)
187-
| Some (Opam _) -> Memo.return None
188-
in
189-
match lock_dir_paths with
190-
| None -> Memo.return None
191-
| Some (source_path, lock_dir_path) ->
192-
let* in_source_tree = Source_tree.find_dir source_path in
193-
(match in_source_tree with
194-
| Some _ -> Memo.return (Some lock_dir_path)
195-
| None -> Memo.return None)
180+
match ctx with
181+
| None | Some (Default { lock_dir = None; _ }) -> Memo.return (Some default_path)
182+
| Some (Default { lock_dir = Some lock_dir_selection; _ }) ->
183+
let+ source_lock_dir = select_lock_dir lock_dir_selection in
184+
Some (lock_dir_of_source source_lock_dir)
185+
| Some (Opam _) -> Memo.return None
196186
;;
197187

198188
let get_workspace_lock_dir ctx =
@@ -250,15 +240,77 @@ let of_dev_tool_if_lock_dir_exists dev_tool =
250240
else Memo.return None
251241
;;
252242

243+
let lock_dirs_of_workspace (workspace : Workspace.t) =
244+
let module Set = Path.Source.Set in
245+
let+ lock_dirs_from_ctx =
246+
Memo.List.map workspace.contexts ~f:(function
247+
| Opam _ | Default { lock_dir = None; _ } -> Memo.return None
248+
| Default { lock_dir = Some selection; _ } ->
249+
let+ path = select_lock_dir selection in
250+
Some path)
251+
>>| List.filter_opt
252+
in
253+
match lock_dirs_from_ctx, workspace.lock_dirs with
254+
| [], [] -> Set.singleton default_source_path
255+
| lock_dirs_from_ctx, lock_dirs_from_toplevel ->
256+
let lock_paths_from_toplevel =
257+
List.map lock_dirs_from_toplevel ~f:(fun (lock_dir : Workspace.Lock_dir.t) ->
258+
lock_dir.path)
259+
in
260+
Set.union (Set.of_list lock_paths_from_toplevel) (Set.of_list lock_dirs_from_ctx)
261+
;;
262+
263+
let enabled () =
264+
match !Clflags.ignore_lock_dir with
265+
| true -> Memo.return false
266+
| false ->
267+
let* workspace = Workspace.workspace () in
268+
(match workspace.config.pkg_enabled with
269+
| Set (_, `Enabled) -> Memo.return true
270+
| Set (_, `Disabled) -> Memo.return false
271+
| Unset ->
272+
let* lock_dirs = lock_dirs_of_workspace workspace in
273+
Path.Source.Set.to_list lock_dirs
274+
|> Memo.List.exists ~f:(fun lock_dir ->
275+
Fs_memo.dir_exists (Path.Outside_build_dir.In_source_dir lock_dir)))
276+
;;
277+
278+
let get_path_if_source_exists ctx_name =
279+
let* workspace = Workspace.workspace () in
280+
let ctx =
281+
List.find_map workspace.contexts ~f:(fun ctx ->
282+
match Context_name.equal (Workspace.Context.name ctx) ctx_name with
283+
| false -> None
284+
| true -> Some ctx)
285+
in
286+
let* lock_dir_paths =
287+
match ctx with
288+
| None | Some (Default { lock_dir = None; _ }) ->
289+
Memo.return (Some (default_source_path, default_path))
290+
| Some (Default { lock_dir = Some lock_dir_selection; _ }) ->
291+
let+ source_lock_dir = select_lock_dir lock_dir_selection in
292+
Some (source_lock_dir, lock_dir_of_source source_lock_dir)
293+
| Some (Opam _) -> Memo.return None
294+
in
295+
match lock_dir_paths with
296+
| None -> Memo.return None
297+
| Some (source_path, lock_dir_path) ->
298+
let* in_source_tree = Source_tree.find_dir source_path in
299+
(match in_source_tree with
300+
| Some _ -> Memo.return (Some lock_dir_path)
301+
| None -> Memo.return None)
302+
;;
303+
253304
let lock_dir_active ctx =
254305
let open Memo.O in
255306
if !Clflags.ignore_lock_dir
256307
then Memo.return false
257308
else
258309
let* workspace = Workspace.workspace () in
259310
match workspace.config.pkg_enabled with
311+
| Set (_, `Enabled) -> Memo.return true
260312
| Set (_, `Disabled) -> Memo.return false
261-
| Set (_, `Enabled) | Unset -> get_path ctx >>| Option.is_some
313+
| Unset -> get_path_if_source_exists ctx >>| Option.is_some
262314
;;
263315

264316
let source_kind (source : Dune_pkg.Source.t) =

src/dune_rules/lock_dir.mli

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ val dev_tool_lock_dir : Dune_pkg.Dev_tool.t -> Path.t
2929

3030
val select_lock_dir : Workspace.Lock_dir_selection.t -> Path.Source.t Memo.t
3131

32+
(** Returns true if lock dir functionality is active, be it via explicitly
33+
opting in via [(pkg enabled)] or inferred through the existence of lock
34+
directories *)
35+
val enabled : unit -> bool Memo.t
36+
3237
module Sys_vars : sig
3338
type t =
3439
{ os : string option Memo.Lazy.t

0 commit comments

Comments
 (0)