package mirage

  1. Overview
  2. Docs

Source file opam.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
(*
 * Copyright (c) 2013-2020 Thomas Gazagnaire <thomas@gazagnaire.org>
 * Copyright (c) 2013-2020 Anil Madhavapeddy <anil@recoil.org>
 * Copyright (c) 2015-2020 Gabriel Radanne <drupyog@zoho.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *)

open Action.Syntax

let find_git () =
  let is_git p = Action.is_dir Fpath.(p / ".git") in
  let app_opt p d = match p with None -> d | Some p -> Fpath.(d // p) in
  let rec find p path =
    if Fpath.is_root p then Action.ok None
    else
      let* has_git = is_git p in
      if has_git then Action.ok (Some path)
      else find (Fpath.parent p) (Some (app_opt path (Fpath.base p)))
  in
  let* cwd = Action.pwd () in
  (* this is invoked from within the mirage subdirectory *)
  let* cwd = find (Fpath.parent cwd) None in
  match cwd with
  | None -> Action.ok None
  | Some subdir ->
      let git_branch =
        Bos.Cmd.(v "git" % "rev-parse" % "--abbrev-ref" % "HEAD")
      in
      let* branch = Action.(run_cmd_out ~err:`Null git_branch) in
      let git_remote = Bos.Cmd.(v "git" % "remote" % "get-url" % "origin") in
      let+ git_url = Action.(run_cmd_out ~err:`Null git_remote) in
      Some (subdir, branch, git_url)

module Endpoint = struct
  type t = {
    scheme : [ `SSH of string | `Git | `HTTP | `HTTPS | `Scheme of string ];
    port : int option;
    path : string;
    hostname : string;
  }

  let of_string str =
    let open Rresult in
    let parse_ssh str =
      let len = String.length str in
      Emile.of_string_raw ~off:0 ~len str
      |> R.reword_error (R.msgf "%a" Emile.pp_error)
      >>= fun (consumed, m) ->
      match
        Astring.String.cut ~sep:":" (String.sub str consumed (len - consumed))
      with
      | Some ("", path) ->
          let path = "/" ^ path in
          let local =
            List.map
              (function `Atom x -> x | `String x -> Fmt.str "%S" x)
              m.Emile.local
          in
          let user = String.concat "." local in
          let hostname =
            match fst m.Emile.domain with
            | `Domain vs -> String.concat "." vs
            | `Literal v -> v
            | `Addr (Emile.IPv4 v) -> Ipaddr.V4.to_string v
            | `Addr (Emile.IPv6 v) -> Ipaddr.V6.to_string v
            | `Addr (Emile.Ext (k, v)) -> Fmt.str "%s:%s" k v
          in
          R.ok { scheme = `SSH user; path; port = None; hostname }
      | _ -> R.error_msg "Invalid SSH pattern"
    in
    let parse_uri str =
      let uri = Uri.of_string str in
      let path = Uri.path uri in
      match (Uri.scheme uri, Uri.host uri, Uri.port uri) with
      | Some "git", Some hostname, port ->
          R.ok { scheme = `Git; path; port; hostname }
      | Some "http", Some hostname, port ->
          R.ok { scheme = `HTTP; path; port; hostname }
      | Some "https", Some hostname, port ->
          R.ok { scheme = `HTTPS; path; port; hostname }
      | Some scheme, Some hostname, port ->
          R.ok { scheme = `Scheme scheme; path; port; hostname }
      | _ -> R.error_msgf "Invalid uri: %a" Uri.pp uri
    in
    match (parse_ssh str, parse_uri str) with
    | Ok v, _ -> Ok v
    | _, Ok v -> Ok v
    | Error _, Error _ -> R.error_msgf "Invalid endpoint: %s" str
end

let guess_src () =
  let git_info =
    match Action.run @@ find_git () with
    | Error _ | Ok None -> None
    | Ok (Some (subdir, branch, git_url)) -> Some (subdir, branch, git_url)
  in
  match git_info with
  | None -> (None, None)
  | Some (subdir, branch, origin) ->
      (* TODO is there a library for git urls anywhere? *)
      let public =
        match Endpoint.of_string origin with
        | Ok
            { Endpoint.scheme = `Scheme scheme; port = None; path; hostname; _ }
          ->
            Fmt.str "%s://%s%s" scheme hostname path
        | Ok
            {
              Endpoint.scheme = `Scheme scheme;
              port = Some port;
              path;
              hostname;
              _;
            } ->
            Fmt.str "%s://%s:%d%s" scheme hostname port path
        | Ok { Endpoint.port = None; path; hostname; _ } ->
            Fmt.str "git+https://%s%s" hostname path
        | Ok { Endpoint.port = Some port; path; hostname; _ } ->
            Fmt.str "git+https://%s:%d%s" hostname port path
        | _ -> "git+https://invalid/endpoint"
      in
      (subdir, Some (Fmt.str "%s#%s" public branch))

type t = {
  name : string;
  depends : Package.t list;
  configure : string option;
  pre_build : (Fpath.t option -> string) option;
  lock_location : (Fpath.t option -> string -> string) option;
  build : (Fpath.t option -> string) option;
  install : Install.t;
  extra_repo : (string * string) list;
  pins : (string * string) list;
  src : string option;
  subdir : Fpath.t option;
  opam_name : string;
}

let v ?configure ?pre_build ?lock_location ?build ?(install = Install.empty)
    ?(extra_repo = []) ?(depends = []) ?(pins = []) ?subdir ~src ~opam_name name
    =
  let subdir, src =
    match src with
    | `Auto ->
        let subdir', src = guess_src () in
        ((match subdir with None -> subdir' | Some _ as s -> s), src)
    | `None -> (subdir, None)
    | `Some d -> (subdir, Some d)
  in
  {
    name;
    depends;
    configure;
    pre_build;
    lock_location;
    build;
    install;
    extra_repo;
    pins;
    src;
    subdir;
    opam_name;
  }

let pp_packages ppf packages =
  Fmt.pf ppf "\n  %a\n"
    Fmt.(list ~sep:(any "\n  ") (Package.pp ~surround:"\""))
    packages

let pp_pins ppf = function
  | [] -> ()
  | pins ->
      let pp_pin ppf (package, url) = Fmt.pf ppf "[\"%s\" %S]" package url in
      Fmt.pf ppf "@.pin-depends: [ @[<hv>%a@]@ ]@."
        Fmt.(list ~sep:(any "@ ") pp_pin)
        pins

let pp_src ppf = function
  | None -> ()
  | Some src -> Fmt.pf ppf {|@.url { src: %S }|} src

let pp_switch_package ppf s = Fmt.pf ppf "%S" s

let pp ppf t =
  let pp_cmd = function
    | None -> ""
    | Some cmd ->
        Fmt.str {|"sh" "-exc" "%a%s"|}
          Fmt.(option ~none:(any "") (any "cd " ++ Fpath.pp ++ any " && "))
          t.subdir cmd
  in
  let pp_with_sub ppf = function
    | None -> ()
    | Some f -> Fmt.string ppf (f t.subdir)
  in
  let pp_repo =
    Fmt.(
      list ~sep:(any "\n")
        (brackets (pair ~sep:(any " ") (quote string) (quote string))))
  in
  let switch_packages =
    List.filter_map
      (fun p ->
        match Package.scope p with
        | `Switch -> Some (Package.name p)
        | `Monorepo -> None)
      t.depends
  in
  Fmt.pf ppf
    {|opam-version: "2.0"
maintainer: "dummy"
authors: "dummy"
homepage: "dummy"
bug-reports: "dummy"
dev-repo: "git://dummy"
synopsis: "Unikernel %s - switch dependencies"
description: """
It assumes that local dependencies are already
fetched.
"""

build: [%a]

install: [%a]

depends: [%a]

x-mirage-opam-lock-location: %S

x-mirage-configure: [%s]

x-mirage-pre-build: [%a]

x-mirage-extra-repo: [%a]

x-opam-monorepo-opam-provided: [%a]
%a%a|}
    t.name pp_with_sub t.build
    (Install.pp_opam ?subdir:t.subdir ())
    t.install pp_packages t.depends
    (Option.fold ~none:""
       ~some:(fun l -> l t.subdir t.opam_name)
       t.lock_location)
    (pp_cmd t.configure) pp_with_sub t.pre_build pp_repo t.extra_repo
    (Fmt.list ~sep:(Fmt.any " ") pp_switch_package)
    switch_packages pp_src t.src pp_pins t.pins
OCaml

Innovation. Community. Security.