package opam_bin_lib

  1. Overview
  2. Docs

Source file commandPreBuild.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
(**************************************************************************)
(*                                                                        *)
(*    Copyright 2020 OCamlPro & Origin Labs                               *)
(*                                                                        *)
(*  All rights reserved. This file is distributed under the terms of the  *)
(*  GNU Lesser General Public License version 2.1, with the special       *)
(*  exception on linking described in the file LICENSE.                   *)
(*                                                                        *)
(**************************************************************************)

open Ezcmd.TYPES
open EzFile.OP
open EzConfig.OP
open OpamParserTypes

let cmd_name = "pre-build"

let cache_file ~cache ~md5 =
  cache // "md5" // String.sub md5 0 2 // md5

let check_cache_file ~cache ~md5 =
  let file = cache_file ~cache ~md5 in
  if Sys.file_exists file then Some file else None

let find_archive_in_cache ~repo ~md5 =
  match check_cache_file ~cache:Globals.opam_cache_dir ~md5 with
  | Some file -> Some file
  | None ->
    match check_cache_file ~cache:Globals.opambin_cache_dir ~md5 with
    | Some file -> Some file
    | None ->
      ignore repo;
    (* TODO: lookup repo specific caches ?
(* We should read .opam/repos-config to get the URL of the repository:
```
repositories: [
  "default" {"file:///home/---/GIT/opam-repository-relocatable"}
  "local-bin" {"file:///home/---/.opam/opam-bin/store/repo"}
]
```
together with .opam/repo/XXX/repo:
```
opam-version: "2.0"
archive-mirrors: "../../cache"
```
 *)
    match check_cache_file ~cache:(repo // "cache") ~md5 with
    | Some file -> Some file
    | None ->
      match OpamParser.file (repo // "repo") with
      | exception _ -> None
      | Some opam ->
        let cache = ref None in
        List.iter (function
            | Variable ( _, "archive-mirrors" , String ( _, v ) ) ->
              cache := v
          ) opam.file_contents ;
        match !cache with
        | None -> None
        | Some cache ->
*)
      None

let check_cached_binary_archive ~version ~repo ~package =
  Misc.global_log "found binary package in repo %s" repo;
  let package_dir = repo // "packages" // package // version in
  let src = ref None in
  let md5 = ref None in
  let opam = OpamParser.file ( package_dir // "opam" ) in
  List.iter (function
      | Section ( _ , { section_kind = "url" ; section_items ; _ } ) ->
        List.iter (function
              Variable ( _, "src", String ( _ , v )) -> src := Some v
            | Variable ( _, "checksum",
                         List ( _, [ String ( _, v ) ] )) ->
              assert ( EzString.starts_with v ~prefix:"md5=" );
              let len = String.length v in
              md5 := Some ( String.sub v 4 (len-4) )
            | _ -> ()
          ) section_items
      | _ -> ()
    ) opam.file_contents ;
  let binary_archive =
    match !md5 with
    | None ->
      Misc.global_log "url.checksum.md5 not found";
      None
    | Some md5 ->
      match find_archive_in_cache ~repo ~md5 with
      | Some binary_archive ->
        Some binary_archive
      | None ->
        match !src with
        | None ->
          Printf.eprintf "error: url.src not found\n%!";
          exit 2
        | Some url ->

          let temp_dir = Globals.opambin_switch_temp_dir () in
          let output = temp_dir // md5 in
          match Misc.wget ~url ~output with
          | None ->
            Printf.eprintf "Error: could not download archive at %S\n%!" url;
            exit 2
          | Some binary_archive ->
            let digest = Digest.file binary_archive in
            assert ( Digest.to_hex digest = md5 );
            let cache_dir =
              Globals.opam_cache_dir //
              "md5" // String.sub md5 0 2 in
            let cached_file = cache_dir // md5 in
            EzFile.make_dir ~p:true cache_dir;
            Sys.rename binary_archive cached_file ;
            Some cached_file
  in
  EzFile.make_dir Globals.marker_cached ;
  let install_file = package ^ ".install" in
  if Sys.file_exists install_file then
    Sys.remove install_file ;
  Unix.chdir Globals.marker_cached ;
  let package_files = package_dir // "files" in
  let s = EzFile.read_file
      ( package_files // Globals.package_version ) in
  EzFile.write_file Globals.package_version s ;
  begin
    match EzFile.read_file
            ( package_files // Globals.package_config ) with
    | exception _ -> ()
    | s ->
      EzFile.write_file Globals.package_config s
  end;
  begin
    match binary_archive with
    | None -> ()
    | Some binary_archive ->
      Misc.call [| "tar" ; "zxf" ; binary_archive |] ;
  end;
  true

let chop_prefix s ~prefix =
  if EzString.starts_with s ~prefix then
    let prefix_len = String.length prefix in
    let len = String.length s in
    Some ( String.sub s prefix_len (len - prefix_len) )
  else
    None

let chop_suffix s ~suffix =
  if EzString.ends_with s ~suffix then
    let suffix_len = String.length suffix in
    let len = String.length s in
    Some ( String.sub s 0 (len - suffix_len) )
  else
    None

let has_equal_suffix v =
  let len = String.length v in
  assert ( len > 0 );
  v.[len-1] = '='

let maybe_apply_patch ~name ~version =
  let keep_version = version in
  let patches_dir =
    let patches_url = !!Config.patches_url in
    match chop_prefix patches_url ~prefix:"file://" with
    | Some s -> s
    | None -> Globals.opambin_patches_dir
  in
  if not ( Sys.file_exists patches_dir ) then
    Printf.kprintf failwith
      {|
Error: patches dir '%s' does not exist.\n
  Maybe you didn't use 'opam-bin install patches' ?\n%!|}
      patches_dir ;
  let rec iter_package package =
    let package_dir = patches_dir // "patches" // package in
    if Sys.file_exists package_dir then
      let files = Sys.readdir package_dir in
      Misc.global_log "package %s needs relocation" name;
      let versions = ref [] in
      let alias = ref None in
      Array.iter (fun file ->
          match chop_suffix file ~suffix:".alias" with
          | Some package -> alias := Some package
          | None ->
            match chop_suffix file ~suffix:".patch" with
            | Some version -> versions := version :: !versions
            | None -> ()
        ) files;
      match !alias with
      | Some package ->
          Misc.global_log "lookup patches for %s instead" package;
          iter_package package
      | None ->
        let versions = Array.of_list !versions in
        Array.sort VersionCompare.compare versions ;
        let rec iter version versions current =
          match versions with
          | [] -> current
          | v :: versions ->
            if has_equal_suffix v then
              if v = version ^ "=" then
                Some v
              else
                iter version versions current
            else
            if VersionCompare.compare version v >= 0 then
              iter version versions (Some v)
            else current
        in
        match iter version (Array.to_list versions) None with
        | None ->
          Misc.global_log_err
            "Package %S is not relocatable, but no patch found for version %S.\n%!"
            name version;
          Misc.global_log_err
            "You may have to disable opam-bin to install that version.\n%!";
          false
        | Some version ->
          let patch = package_dir // version ^ ".patch" in
          Misc.global_log "Using patch %s for %s.%s"
            patch name keep_version ;
          Misc.call [| "cp" ; "-f";
                              patch ; Globals.marker_patch |];
          Misc.call [| "patch" ; "-p1"; "-i"; patch |] ;
          if Sys.file_exists "reloc-patch.sh" then begin
            Misc.call [| "sh"; "./reloc-patch.sh" |];
          end;
          true
    else
      true
  in
  iter_package name

let cached_binary_archive ~name ~version ~depends =
  if not ( maybe_apply_patch ~name ~version ) then
    `NotRelocatable
  else
    let ( source_md5, _depends, _dependset, missing_versions, _opam_file ) =
      CommandPostInstall.compute_hash
        ~name ~version ~depends () in
    if missing_versions <> [] then
      `MissingVersions missing_versions
    else
      let version_prefix = Printf.sprintf "%s.%s+bin+%s+"
          name version source_md5 in
      if Misc.iter_repos ~cont:(fun x -> x)
          ( Misc.all_repos () )
          (fun ~repo ~package ~version ->
          if EzString.starts_with version ~prefix:version_prefix then begin
            check_cached_binary_archive ~package ~repo ~version
          end else
            false
        ) then
        `BinaryArchiveFound
      else begin
        Misc.global_log "Could not find cached binary package %s"
          version_prefix ;
        `NoBinaryArchiveFound source_md5
      end

let error_on_compile =
  match Sys.getenv "OPAM_BIN_FORCE" with
  | exception _ -> false
  | _ -> true

let error_on_non_reloc =
  match Sys.getenv "OPAM_BIN_RELOC" with
  | exception _ -> false
  | _ -> true

let action args =
  Misc.log_cmd cmd_name args ;
  match args with
  | name :: version :: depends :: [] ->
    let marker_skip = Globals.marker_skip in
    if not !!Config.enabled
    || Misc.not_this_switch () then begin
      Misc.global_log "opam-bin is disabled";
      EzFile.write_file marker_skip
        "opam-bin is disabled";
    end else
      let marker_source = Globals.marker_source in
      let marker_opam = Globals.marker_opam in
      let marker_patch = Globals.marker_patch in
      if Sys.file_exists marker_source then begin
        Misc.global_log "removing marker_source";
        Sys.remove marker_source ;
      end;
      if Sys.file_exists marker_opam then begin
        Misc.global_log "removing marker_opam";
        Sys.remove marker_opam ;
      end;
      if Sys.file_exists marker_patch then begin
        Misc.global_log "removing marker_patch";
        Sys.remove marker_patch ;
      end;
      if Sys.file_exists Globals.marker_cached then begin
        Misc.global_log "%s should not already exist!"
          Globals.marker_cached;
        exit 2
      end else
      if Sys.file_exists Globals.package_version then begin
        Misc.global_log "already a binary package";
        EzFile.write_file marker_source "already-a-binary-package";
      end else begin
        Misc.global_log "checking for cached archive";
        match cached_binary_archive ~name ~version ~depends with
        | `BinaryArchiveFound ->
          Misc.global_log "found a binary archive in cache";
          (* this should have created a marker_cached/ directory *)
        | `NoBinaryArchiveFound source_md5 ->
          Misc.global_log "no binary archive found.";
          if error_on_compile then begin
            Printf.eprintf
              "Error: opam-bin is configured to prevent compilation.\n%!";
            exit 2
          end;
          EzFile.write_file marker_source source_md5
        | `MissingVersions missing_versions ->
          EzFile.write_file marker_skip
            ( Printf.sprintf "Missing binary deps: %s"
                ( String.concat " " missing_versions ) )
        | `NotRelocatable ->
          if error_on_non_reloc then begin
            Printf.eprintf
              "Error: opam-bin is configured to force relocation.\n%!";
            exit 2
          end;
          EzFile.write_file marker_skip
            "Missing relocation patch for unrelocatable package"
      end
  | _ ->
    Misc.global_log "unexpected arg.";
    Printf.eprintf
      "Unexpected args: usage is '%s %s name version depends cmd...'\n%!" Globals.command cmd_name ;
    exit 2


let cmd =
  let args = ref [] in
  Arg.{
    cmd_name ;
    cmd_action = (fun () -> action !args) ;
    cmd_args = [
      [ "opamfile" ] , Arg.String (fun s ->
          CommandPostInstall.opamfile_arg := Some s),
      Ezcmd.info "filename of the opam package description";
      [], Anons (fun list -> args := list),
      Ezcmd.info "args"
    ];
    cmd_man = [];
    cmd_doc = "(opam hook) Backup the sources before building the package";
  }
OCaml

Innovation. Community. Security.