package opam-state

  1. Overview
  2. Docs

Source file opamGlobalState.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
(**************************************************************************)
(*                                                                        *)
(*    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 OpamStd.Op
open OpamFilename.Op

open OpamStateTypes

let log fmt = OpamConsole.log "GSTATE" fmt
let slog = OpamConsole.slog

let load_config lock_kind global_lock root =
  let config =
    match OpamStateConfig.load ~lock_kind root with
    | Some c -> c
    | exception (OpamPp.Bad_version _ as e) ->
      OpamFormatUpgrade.hard_upgrade_from_2_1_intermediates ~global_lock root;
      raise e
    | None ->
      if OpamFilename.exists (root // "aliases") then
        OpamFile.Config.(with_opam_version (OpamVersion.of_string "1.1") empty)
      else
        OpamConsole.error_and_exit `Configuration_error
          "%s exists, but does not appear to be a valid opam root. Please \
           remove it and use `opam init', or specify a different `--root' \
           argument"
          (OpamFilename.Dir.to_string root)
  in
  let config =
    OpamFormatUpgrade.as_necessary lock_kind global_lock root config
  in
  config

let inferred_from_system = "Inferred from system"

let load lock_kind =
  let root = OpamStateConfig.(!r.root_dir) in
  log "LOAD-GLOBAL-STATE %@ %a" (slog OpamFilename.Dir.to_string) root;
  (* Always take a global read lock, this is only used to prevent concurrent
     ~/.opam format changes *)
  let has_root = OpamFilename.exists_dir root in
  let global_lock =
    if has_root then
      OpamFilename.flock `Lock_read (OpamPath.lock root)
    else OpamSystem.lock_none
  in
  (* The global_state lock actually concerns the global config file only (and
     the consistence thereof with the repository and switch sets, and the
     currently installed shell init scripts) *)
  if not has_root then
    OpamConsole.error_and_exit `Configuration_error
      "Opam has not been initialised, please run `opam init'";
  let config_lock = OpamFilename.flock lock_kind (OpamPath.config_lock root) in
  let config =
    try load_config lock_kind global_lock root
    with OpamFormatUpgrade.Upgrade_done _ as e ->
      OpamSystem.funlock config_lock;
      raise e
  in
  if OpamStateConfig.is_newer config && lock_kind <> `Lock_write then
    log "root version (%s) is greater than running binary's (%s); \
         load with best-effort (read-only)"
      (OpamVersion.to_string (OpamFile.Config.opam_root_version config))
      (OpamVersion.to_string (OpamFile.Config.root_version));
  let switches =
    List.filter
      (fun sw -> not (OpamSwitch.is_external sw) ||
                 OpamFilename.exists_dir (OpamSwitch.get_root root sw))
      (OpamFile.Config.installed_switches config)
  in
  let config = OpamFile.Config.with_installed_switches switches config in
  let global_variables =
    List.fold_left (fun acc (v,value) ->
        OpamVariable.Map.add v
          (lazy (Some (OpamStd.Option.default (S "unknown") (Lazy.force value))),
           (* Careful on changing it, it is used to determine user defined
              variables on `config report`. See [OpamConfigCommand.help]. *)
           inferred_from_system)
          acc)
      OpamVariable.Map.empty
      (OpamSysPoll.variables)
  in
  let global_variables =
    List.fold_left (fun acc (v,value,doc) ->
        OpamVariable.Map.add v (lazy (Some value), doc) acc)
      global_variables
      (OpamFile.Config.global_variables config)
  in
  let eval_variables = OpamFile.Config.eval_variables config in
  let global_variables =
    let env = lazy (OpamEnv.get_pure () |> OpamTypesBase.env_array) in
    List.fold_left (fun acc (v, cmd, doc) ->
        OpamVariable.Map.update v
          (fun previous_value ->
             (lazy
               (try
                  let ret =
                    OpamSystem.read_command_output
                      ~env:(Lazy.force env)
                      ~allow_stdin:false
                      cmd
                  in
                  Some (S (OpamStd.String.strip (String.concat "\n" ret)))
                with e ->
                  OpamStd.Exn.fatal e;
                  log "Failed to evaluate global variable %a: %a"
                    (slog OpamVariable.to_string) v
                    (slog Printexc.to_string) e;
                  Lazy.force (fst previous_value))),
             doc)
          (lazy None, "")
          acc)
      global_variables eval_variables
  in
  { global_lock = config_lock;
    root;
    config;
    global_variables; }

let switches gt =
  OpamFile.Config.installed_switches gt.config

let fold_switches f gt acc =
  List.fold_left (fun acc switch ->
      f switch
        (OpamStateConfig.Switch.safe_read_selections
           ~lock_kind:`Lock_read gt switch)
        acc
    ) acc (OpamFile.Config.installed_switches gt.config)

let switch_exists gt switch =
  if OpamSwitch.is_external switch then
    OpamStateConfig.local_switch_exists gt.root switch
  else List.mem switch (switches gt)

let all_installed gt =
  fold_switches (fun _ sel acc ->
      OpamPackage.Set.union acc sel.sel_installed)
    gt  OpamPackage.Set.empty

let installed_versions gt name =
  fold_switches (fun switch sel acc ->
      let installed =
        OpamPackage.packages_of_name sel.sel_installed name
      in
      try
        let nv = OpamPackage.Set.choose installed in
        try OpamPackage.Map.add nv (switch::OpamPackage.Map.find nv acc) acc
        with Not_found -> OpamPackage.Map.add nv [switch] acc
      with Not_found -> acc)
    gt OpamPackage.Map.empty

let repos_list gt = OpamFile.Config.repositories gt.config

let unlock gt =
  OpamSystem.funlock gt.global_lock;
  (gt :> unlocked global_state)

let drop gt =
  let _ = unlock gt in ()

let with_write_lock ?dontblock gt f =
  if OpamStateConfig.is_newer_than_self gt then
    OpamConsole.error_and_exit `Locked
      "The opam root has been upgraded by a newer version of opam-state \
       and cannot be written to";
  let ret, gt =
    OpamFilename.with_flock_upgrade `Lock_write ?dontblock gt.global_lock
    @@ fun _ -> f ({ gt with global_lock = gt.global_lock } : rw global_state)
    (* We don't actually change the field value, but this makes restricting the
       phantom lock type possible*)
  in
  ret, { gt with global_lock = gt.global_lock }

let with_ lock f =
  let gt = load lock in
  try let r = f gt in drop gt; r
  with e -> OpamStd.Exn.finalise e (fun () -> drop gt)

let write gt =
  OpamFile.Config.write (OpamPath.config gt.root) gt.config

let fix_switch_list gt =
  let known_switches0 = switches gt in
  let known_switches =
    match OpamStateConfig.get_switch_opt () with
    | None -> known_switches0
    | Some sw ->
      if List.mem sw known_switches0 || not (switch_exists gt sw)
      then known_switches0
      else sw::known_switches0
  in
  let known_switches = List.filter (switch_exists gt) known_switches in
  if known_switches = known_switches0 then gt else
  let config =
    OpamFile.Config.with_installed_switches known_switches gt.config
  in
  let gt = { gt with config } in
  if not OpamCoreConfig.(!r.safe_mode)
  && OpamSystem.get_lock_flag gt.global_lock = `Lock_write then
    try
      snd @@ with_write_lock ~dontblock:true gt @@ fun gt ->
      write gt, gt
    with OpamSystem.Locked -> gt
  else gt

OCaml

Innovation. Community. Security.