package opam-state

  1. Overview
  2. Docs

Source file opamPinned.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
(**************************************************************************)
(*                                                                        *)
(*    Copyright 2012-2019 OCamlPro                                        *)
(*    Copyright 2012 INRIA                                                *)
(*                                                                        *)
(*  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 OpamTypes
open OpamStateTypes
open OpamFilename.Op
open OpamStd.Option.Op

let log fmt = OpamConsole.log "PIN" fmt

let package st name = OpamPackage.package_of_name st.pinned name

let package_opt st name = try Some (package st name) with Not_found -> None

let version st name = (package st name).version

let version_opt st name = try Some (version st name) with Not_found -> None

let packages st = st.pinned

let possible_definition_filenames dir name = [
  dir / (OpamPackage.Name.to_string name ^ ".opam") // "opam";
  dir // (OpamPackage.Name.to_string name ^ ".opam");
  dir / "opam" / (OpamPackage.Name.to_string name ^ ".opam") // "opam";
  dir / "opam" // (OpamPackage.Name.to_string name ^ ".opam");
  dir / "opam" // "opam";
  dir // "opam"
]

let lock_filename ?locked file =
  locked
  >>+ (fun () ->  OpamStateConfig.(!r.locked))
  >>| (fun e -> OpamFilename.add_extension file e, e)

let check_locked ?locked default =
  match lock_filename ?locked default with
  | None -> default, None
  | Some (locked, ext) ->
    if not (OpamFilename.exists locked) then default, None else
      (log "Lock file found %s" (OpamFilename.to_string default);
       let base_depends =
         OpamFile.make default
         |> OpamFile.OPAM.read
         |> OpamFile.OPAM.depends
       in
       let lock_depends =
         OpamFile.make locked
         |> OpamFile.OPAM.read
         |> OpamFile.OPAM.depends
       in
       let ldep_names =
         OpamFormula.fold_left
           (fun acc (n,_) -> OpamPackage.Name.Set.add n acc)
           OpamPackage.Name.Set.empty lock_depends
       in
       let base_formula =
         OpamFilter.filter_deps ~build:true ~post:true ~test:false ~doc:false
           ~dev_setup:false ~dev:false base_depends
       in
       let lock_formula =
         OpamFilter.filter_deps ~build:true ~post:true ~test:false ~doc:false
           ~dev_setup:false ~dev:false lock_depends
       in
       let lpkg_f =
         lock_formula
         |> OpamFormula.atoms
         |> OpamPackage.Name.Map.of_list
       in
       (* Check consistency between them. It is based on the fact that locked file
          dependencies are an and list with precise version, i.e., pkg { =v0.1}.
          Construction of a two list: missing dependencies and inconsistent ones
          (version mismatch) *)
       let (@) = List.rev_append in
       let rec fold formula =
         List.fold_left cross ([],[]) (OpamFormula.ands_to_list formula)
       and cross (cont,cons) formula =
         match formula with
         | Atom (bn, bvf) ->
           ( let cont =
               if OpamPackage.Name.Set.mem bn ldep_names then cont
               else bn::cont
             in
             let cons =
               match OpamPackage.Name.Map.find_opt bn lpkg_f with
               | Some (Some (`Eq, lv)) ->
                 if OpamFormula.check_version_formula bvf lv then cons
                 else (bn, lv, bvf)::cons
               | _ -> cons
             in
             (cont,cons))
         | Or (or1, or2) ->
           let or1_cont, or1_cons = fold or1 in
           let or2_cont, or2_cons = fold or2 in
           let cont =
             if or1_cont = [] || or2_cont = [] then cont
             else or1_cont @ or2_cont @ cont
           in
           let cons =
             if or1_cons = [] || or2_cons = [] then cons
             else or1_cons @ or2_cons @ cons
           in
           (cont,cons)
         | And (and1, and2) ->
           let and1_cont, and1_cons = fold and1 in
           let and2_cont, and2_cons = fold and2 in
           ((and1_cont @ and2_cont @ cont), (and1_cons @ and2_cons @ cons))
         | Block f -> cross (cont,cons) f
         | Empty -> (cont,cons)
       in
       let contains, consistent = fold base_formula in
       if contains <> [] || consistent <> [] then
         (OpamConsole.warning "Lock file %s is outdated, you may want to re-run opam lock:\n%s"
            (OpamConsole.colorise `underline (OpamFilename.Base.to_string (OpamFilename.basename locked)))
            ((if contains <> [] then
                Printf.sprintf "Dependencies present in opam file not in lock file:\n%s"
                  (OpamStd.Format.itemize OpamPackage.Name.to_string contains)
              else "")
             ^
             (if consistent <> [] then
                Printf.sprintf "Dependencies in lock file not consistent wit opam file filter:\n%s"
                  (OpamStd.Format.itemize (fun (n,lv,(bv: OpamFormula.version_formula)) ->
                       Printf.sprintf "%s: %s in not contained in {%s}"
                         (OpamPackage.Name.to_string n)
                         (OpamPackage.Version.to_string lv)
                         (OpamFormula.string_of_formula
                            (fun (op, vc) ->
                               Printf.sprintf "%s %s"
                                 (OpamPrinter.FullPos.relop_kind op) (OpamPackage.Version.to_string vc))
                            bv))
                      consistent)
              else "")));
       locked, Some ext)

let find_opam_file_in_source ?locked name dir =
  let opt =
    List.find_opt OpamFilename.exists
      (possible_definition_filenames dir name)
  in
  opt
  >>| check_locked ?locked
  >>| (fun (o,l) -> OpamFile.make o, l)

let name_of_opam_filename ?locked dir file =
  let suffix= ".opam" in
  let get_name s =
    if Filename.check_suffix s suffix
    then Some Filename.(chop_suffix (basename s) suffix)
    else None
  in
  let rel = OpamFilename.remove_prefix dir file in
  let rel =
    match locked with
    | None -> rel
    | Some suf ->
      let ext = "."^suf in
      if OpamCompat.String.ends_with ~suffix:(suffix^ext) rel then
        OpamStd.String.remove_suffix ~suffix:ext rel
      else rel
  in
  (get_name (Filename.basename rel) >>+ fun () ->
   get_name (Filename.dirname rel)) >>= fun name ->
  try Some (OpamPackage.Name.of_string name)
  with Failure _ -> None

let files_in_source ?locked ?(recurse=false) ?subpath d =
  let baseopam = OpamFilename.Base.of_string "opam" in
  let files =
    let rec files_aux acc base d =
      let acc =
        List.filter_map (fun f ->
            if OpamFilename.basename f = baseopam ||
               OpamFilename.check_suffix f ".opam" then
              let base =
                match base, subpath with
                | Some b, Some sp ->
                  Some (Filename.concat (OpamFilename.SubPath.to_string sp) b)
                | Some b, _ -> Some b
                | _, Some sp -> Some (OpamFilename.SubPath.to_string sp)
                | None, None -> None
              in
              Some (f, base)
            else
              None)
          (OpamFilename.files d) @ acc
      in
      List.fold_left
        (fun acc d ->
           if OpamCompat.String.ends_with ~suffix:".opam"
               (OpamFilename.Dir.to_string d)
           then
             match OpamFilename.opt_file OpamFilename.Op.(d//"opam") with
             | None -> acc
             | Some f -> (f, base) :: acc
           else
           let base_dir = OpamFilename.basename_dir d in
           let basename = OpamFilename.Base.to_string base_dir in
           if recurse &&
              not (base_dir = OpamFilename.Base.of_string OpamSwitch.external_dirname ||
                   base_dir = OpamFilename.Base.of_string "_build" ||
                   OpamCompat.String.starts_with ~prefix:"." basename)
           then
             let base = match base with
               | None -> Some basename
               | Some base -> Some (Filename.concat base basename) in
             files_aux acc base d
           else
             acc)
        acc (OpamFilename.dirs d)
    in
    files_aux [] None
  in
  let d = OpamFilename.SubPath.(d /? subpath) in
  files d @ files (d / "opam") |>
  List.map (fun (f,s) -> (check_locked ?locked f), s) |>
  List.filter_map
    (fun ((f, locked), subpath) ->
       try
         (* Ignore empty files *)
         if (Unix.stat (OpamFilename.to_string f)).Unix.st_size = 0 then None
         else
           Some { pin_name = name_of_opam_filename ?locked d f;
                  pin = {
                    pin_file = OpamFile.make f;
                    pin_locked = locked;
                    pin_subpath =
                      Option.map OpamFilename.SubPath.of_string subpath;
                    pin_url = ();
                  }}
       with Unix.Unix_error _ ->
         OpamConsole.error "Can not read %s, ignored."
           (OpamFilename.to_string f);
         None)

let files_in_source_w_target ?locked ?recurse ?subpath
    ?(same_kind=fun _ -> true) url dir =
  List.filter_map (fun name_and_file ->
      let pin_url =
        match url.OpamUrl.backend with
        | #OpamUrl.version_control as vc ->
          let module VCS =
            (val match vc with
               | `git -> (module OpamGit.VCS: OpamVCS.VCS)
               | `hg -> (module OpamHg.VCS: OpamVCS.VCS)
               | `darcs -> (module OpamDarcs.VCS: OpamVCS.VCS)
               : OpamVCS.VCS)
          in
          let open OpamProcess.Job.Op in
          let versioned_files =
            OpamProcess.Job.run @@
            VCS.versioned_files dir @@| fun files -> files
          in
          let opamfile =
            OpamFilename.remove_prefix dir
              (OpamFile.filename name_and_file.pin.pin_file)
          in
          if OpamStd.List.mem String.equal opamfile versioned_files
          || not (OpamStd.String.contains opamfile ~sub:Filename.dir_sep) then
            url
          else
            { url with
              transport = "file";
              hash = None;
              backend = `rsync }
        | _ -> url
      in
      if same_kind pin_url then
        Some { name_and_file with pin = { name_and_file.pin with pin_url }}
      else None)
    (files_in_source ?locked ?recurse ?subpath dir)

let orig_opam_file st name opam =
  (match OpamFile.OPAM.metadata_dir opam with
   | None -> None
   | Some (None, abs) ->
     Some (OpamFilename.Dir.of_string abs)
   | Some (Some r, rel) ->
     Some (OpamRepositoryState.get_root st.switch_repos r / rel))
  >>= fun dir ->
  let opam_files = [
    dir // (OpamPackage.Name.to_string name ^ ".opam");
    dir // "opam"
  ] in
  let locked_files =
    match OpamFile.OPAM.locked opam with
    | Some locked ->
      List.map (fun f -> OpamFilename.add_extension f locked) opam_files
    | None -> []
  in
  List.find_opt OpamFilename.exists locked_files
  ++ List.find_opt OpamFilename.exists opam_files
  >>| OpamFile.make