Skip to content

Commit 6b10f5e

Browse files
maisteart-wLeonidas-from-XIV
authored
fix: support package pinning via http+tar (ocaml#11446)
This is a non-optimal version of HTTP+Tar. It will download the files multiple time. Optimizations will be done later based on this version. Signed-off-by: Etienne Marais <[email protected]> Co-authored-by: ArthurW <[email protected]> Co-authored-by: Marek Kubica <[email protected]>
1 parent d81b922 commit 6b10f5e

File tree

10 files changed

+134
-36
lines changed

10 files changed

+134
-36
lines changed

bin/pkg/pkg_common.ml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,15 @@ let get_repos repos ~repositories =
8888
| Some repo ->
8989
let loc, opam_url = Repository.opam_url repo in
9090
let module Opam_repo = Dune_pkg.Opam_repo in
91-
(match Dune_pkg.OpamUrl.local_or_git_only opam_url loc with
91+
(match Dune_pkg.OpamUrl.local_or_git_or_tar_only opam_url loc with
9292
| `Git -> Opam_repo.of_git_repo loc opam_url
93-
| `Path path -> Fiber.return @@ Opam_repo.of_opam_repo_dir_path loc path))
93+
| `Path path -> Fiber.return @@ Opam_repo.of_opam_repo_dir_path loc path
94+
| `Tar ->
95+
User_error.raise
96+
~loc
97+
[ Pp.textf "Repositories stored in archives (%s) are currently unsupported"
98+
@@ OpamUrl.to_string opam_url
99+
]))
94100
;;
95101

96102
let find_local_packages =

src/dune_pkg/fetch.ml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,9 +201,10 @@ let fetch_local ~checksum ~target (url, url_loc) =
201201
if not (OpamUrl.is_local url)
202202
then Code_error.raise "fetch_local: url should be file://" [ "url", OpamUrl.to_dyn url ];
203203
let path =
204-
match OpamUrl.local_or_git_only url url_loc with
204+
match OpamUrl.local_or_git_or_tar_only url url_loc with
205205
| `Path p -> p
206-
| `Git -> Code_error.raise "fetch_local: not a path" [ "url", OpamUrl.to_dyn url ]
206+
| `Git | `Tar ->
207+
Code_error.raise "fetch_local: not a path" [ "url", OpamUrl.to_dyn url ]
207208
in
208209
match check_checksum checksum path with
209210
| Error _ as e -> Fiber.return e

src/dune_pkg/mount.ml

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ let backend t = t
1111

1212
let of_opam_url loc url =
1313
let* () = Fiber.return () in
14-
match OpamUrl.local_or_git_only url loc with
14+
match OpamUrl.local_or_git_or_tar_only url loc with
1515
| `Path dir -> Fiber.return (Path dir)
1616
| `Git ->
1717
let+ rev =
@@ -23,6 +23,40 @@ let of_opam_url loc url =
2323
>>| User_error.ok_exn
2424
in
2525
Git rev
26+
| `Tar ->
27+
(* To prevent cache dir from growing too much, `/tmp/` stores the archive
28+
when running `dune pkg lock`. We download and extract the archive
29+
everytime the command runs.
30+
CR-someday maiste: downloading and extracting should be cached to be
31+
reused by the `dune build` command. *)
32+
let dir = Temp.(create Dir ~prefix:"dune" ~suffix:"fetch-pinning") in
33+
Source.fetch_archive_cached (loc, url)
34+
>>= (function
35+
| Error message_opt ->
36+
let message =
37+
Option.value
38+
~default:
39+
(User_message.make
40+
[ Pp.textf
41+
"Failed to retrieve source archive from: %s"
42+
(OpamUrl.to_string url)
43+
])
44+
message_opt
45+
in
46+
raise (User_error.E message)
47+
| Ok archive ->
48+
let target =
49+
let file_digest = Path.to_string archive |> Digest.file |> Digest.to_hex in
50+
Path.relative dir file_digest
51+
in
52+
let+ path =
53+
Tar.extract ~archive ~target
54+
>>| function
55+
| Error () ->
56+
User_error.raise [ Pp.textf "unable to extract %S" (Path.to_string target) ]
57+
| Ok () -> target
58+
in
59+
Path path)
2660
;;
2761

2862
let read t file =

src/dune_pkg/opamUrl0.ml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,18 @@ let is_version_control t =
3030

3131
let is_local t = String.equal t.transport "file"
3232

33-
let local_or_git_only url loc =
33+
let is_tarball t =
34+
let supported_compress_format = [ ".tar"; ".tar.gz"; ".tgz"; ".tar.bz2"; ".tbz" ] in
35+
List.exists
36+
~f:(fun suffix -> Filename.check_suffix t.path suffix)
37+
supported_compress_format
38+
;;
39+
40+
let local_or_git_or_tar_only url loc =
3441
match (url : t).backend with
3542
| `rsync when is_local url -> `Path (Path.of_string url.path)
3643
| `git -> `Git
44+
| `http when is_tarball url -> `Tar
3745
| `rsync | `http | `darcs | `hg ->
3846
User_error.raise
3947
~loc

src/dune_pkg/opamUrl0.mli

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ val is_version_control : t -> bool
1515
(** [is_file t] is true iff [t] is a url beginning with "file://" *)
1616
val is_local : t -> bool
1717

18-
(* [local_or_git_only t loc] returns [`Path p] for a URL pointing to a local
19-
file system or [`Git] if it's a git repository (remote or otherwise). If
20-
it's neither of those cases, it will error out. *)
21-
val local_or_git_only : t -> Loc.t -> [ `Path of Path.t | `Git ]
18+
(* [local_or_git_or_tar_only t loc] returns [`Path p] for a URL pointing to a
19+
local file system or [`Git] if it's a git repository (remote or otherwise)
20+
or [`Tar] if it's an http server with a tar resource. If it's neither of
21+
those cases, it will error out. *)
22+
val local_or_git_or_tar_only : t -> Loc.t -> [ `Path of Path.t | `Git | `Tar ]
2223

2324
module Map : Map.S with type key = t
2425
module Set : Set.S with type elt = t and type 'a map = 'a Map.t

src/dune_pkg/opam_solver.ml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,8 @@ module Context = struct
135135
let packages pkg (formula : OpamTypes.formula) =
136136
OpamFormula.iter
137137
(fun (name, _) ->
138-
let name = Package_name.of_opam_package_name name in
139-
Table.Multi.cons acc name pkg)
138+
let name = Package_name.of_opam_package_name name in
139+
Table.Multi.cons acc name pkg)
140140
formula
141141
in
142142
Lazy.force local_packages

src/dune_pkg/source.ml

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,27 +31,31 @@ let to_dyn { url = _loc, url; checksum } =
3131
]
3232
;;
3333

34-
let fetch_and_hash_archive_cached =
34+
let fetch_archive_cached =
3535
let cache = Single_run_file_cache.create () in
3636
fun (url_loc, url) ->
37-
let open Fiber.O in
3837
Single_run_file_cache.with_ cache ~key:(OpamUrl.to_string url) ~f:(fun target ->
3938
Fetch.fetch_without_checksum ~unpack:false ~target ~url:(url_loc, url))
40-
>>| function
41-
| Ok target -> Some (Dune_digest.file target |> Checksum.of_dune_digest)
42-
| Error message_opt ->
43-
let message =
44-
match message_opt with
45-
| Some message -> message
46-
| None ->
47-
User_message.make
48-
[ Pp.textf
49-
"Failed to retrieve source archive from: %s"
50-
(OpamUrl.to_string url)
51-
]
52-
in
53-
User_warning.emit_message message;
54-
None
39+
;;
40+
41+
let fetch_and_hash_archive_cached (url_loc, url) =
42+
let open Fiber.O in
43+
fetch_archive_cached (url_loc, url)
44+
>>| function
45+
| Ok target -> Some (Dune_digest.file target |> Checksum.of_dune_digest)
46+
| Error message_opt ->
47+
let message =
48+
Option.value
49+
~default:
50+
(User_message.make
51+
[ Pp.textf
52+
"Failed to retrieve source archive from: %s"
53+
(OpamUrl.to_string url)
54+
])
55+
message_opt
56+
in
57+
User_warning.emit_message message;
58+
None
5559
;;
5660

5761
let compute_missing_checksum

src/dune_pkg/source.mli

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,9 @@ val remove_locs : t -> t
1313
val compute_missing_checksum : t -> Package_name.t -> pinned:bool -> t Fiber.t
1414
val external_copy : Loc.t * Path.External.t -> t
1515
val kind : t -> [ `Directory_or_archive of Path.External.t | `Fetch ]
16+
17+
(** [fetch_archive_cached (loc, url)] downloads and extract a source relying on
18+
caching to reduce network calls. *)
19+
val fetch_archive_cached
20+
: Loc.t * OpamUrl.t
21+
-> (Import.Path.t, Import.User_message.t option) result Fiber.t

test/blackbox-tests/test-cases/pkg/dune

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
ocamlformat-dev-tool
4747
source-caching
4848
tarball
49+
pin-depends
4950
extra-sources))
5051

5152
(cram
@@ -58,7 +59,7 @@
5859

5960
(cram
6061
(deps %{bin:tar})
61-
(applies_to source-caching tarball extra-sources))
62+
(applies_to source-caching tarball pin-depends extra-sources))
6263

6364
(cram
6465
(deps %{bin:ocaml_index})

test/blackbox-tests/test-cases/pkg/pin-depends.t

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,47 @@ Pin to an invalid opam file
175175
unsupported or missing file format version; should be 2.0 or older
176176
[1]
177177

178-
Pin to an HTTP archive doesn't work
178+
Pin to an HTTP archive work
179+
180+
$ mkdir _source/
181+
$ cat > _source/bar.opam << EOF
182+
> opam-version: "2.0"
183+
> EOF
184+
$ tar cf tarball.tar -C _source bar.opam
185+
$ MD5_CHECKSUM=$(md5sum tarball.tar | cut -f1 -d' ')
186+
$ echo tarball.tar > fake-curls
187+
$ PORT=1
188+
$ runtest "http://0.0.0.0:$PORT/tarball.tar" > output
189+
Solution for dune.lock:
190+
- bar.1.0.0
191+
$ grep "md5=$MD5_CHECKSUM" output 2>&1 > /dev/null && echo "Checksum matches"
192+
Checksum matches
193+
194+
Pin to an HTTP archive detects wrong hash
195+
196+
$ cat << EOF > dune
197+
> (library
198+
> (name foo)
199+
> (libraries bar))
200+
> EOF
201+
$ sed -i.tmp "s/$MD5_CHECKSUM/92449184682b45b5f07e811fdd61d35f/g" dune.lock/bar.pkg
202+
$ rm -rf already-served
203+
$ dune build 2>&1 | grep -v "md5"
204+
File "dune.lock/bar.pkg", line 6, characters 12-48:
205+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
206+
Error: Invalid checksum, got
207+
208+
Pin to an HTTP archive needs `dune pkg lock` to download and compute the hash
209+
of the target again
210+
211+
$ rm tarball.tar already-served
212+
$ echo "update checksum" > _source/random_file
213+
$ tar cf tarball.tar -C _source bar.opam random_file
214+
$ MD5_CHECKSUM=$(md5sum tarball.tar | cut -f1 -d' ')
215+
$ echo tarball.tar > fake-curls
216+
$ runtest "http://0.0.0.0:$PORT/tarball.tar" > output
217+
Solution for dune.lock:
218+
- bar.1.0.0
219+
$ grep "md5=$MD5_CHECKSUM" output 2>&1 > /dev/null && echo "Checksum matches"
220+
Checksum matches
179221
180-
$ runtest "http://0.0.0.0/tarball.tgz"
181-
File "foo.opam", line 1, characters 0-0:
182-
Error: Could not determine location of repository http://0.0.0.0/tarball.tgz
183-
Hint: Specify either a file path or git repo via SSH/HTTPS
184-
[1]

0 commit comments

Comments
 (0)