diff options
Diffstat (limited to 'lib/input.ml')
| -rw-r--r-- | lib/input.ml | 362 |
1 files changed, 362 insertions, 0 deletions
diff --git a/lib/input.ml b/lib/input.ml new file mode 100644 index 0000000..6ff5e6e --- /dev/null +++ b/lib/input.ml @@ -0,0 +1,362 @@ +(*─────────────────────────────────────────────────────────────────────────────┐ +│ SPDX-FileCopyrightText: 2025 toastal <https://toast.al/contact/> │ +│ SPDX-License-Identifier: LGPL-2.1-or-later WITH OCaml-LGPL-linking-exception │ +└─────────────────────────────────────────────────────────────────────────────*) +open Name + +type jg_models2 = string -> Jingoo.Jg_types.tvalue + +module Template = struct + type t = + Template of string + [@@unboxed] + [@@deriving show] + + let [@inline]make t = Template t + let [@inline]take (Template t) = t + let [@inline]fill ~(models : jg_models2) tpl = + Jingoo.Jg_template2.from_string ~models (take tpl) +end + +module Latest = struct + module Cmd = struct + type 'a non_empty_list = + ('a * 'a list) + [@@deriving show] + + type cmd = { + prog: Template.t; + args: Template.t list; + } + [@@deriving show, make] + + type t = cmd non_empty_list + [@@deriving show] + + let (~$) x = (x, []) + let (|:) (x, xs) x' = (x, x' :: xs) + let (@) (x, xs) (y, ys) = (x, xs @ y :: ys) + end + + type t = { + cmd: Cmd.t option; + value: string option; + } + [@@deriving show, make] +end + +(* KINDS **********************************************************************) + +module File = struct + type t = { + url: Template.t; + mirrors: Template.t list; + } + [@@deriving show, make] +end + +module Archive = struct + type t = { + url: Template.t; + mirrors: Template.t list; + } + [@@deriving show, make] +end + +module Git = struct + module Reference = struct + type t = [ + | `Branch of string + | `Ref of string + ] + [@@deriving show] + end + + type t = { + repository: Template.t; + mirrors: Template.t list; + reference: Reference.t; + datetime: string option; (* ISO 8601 RFC 3339 *) + submodules: bool; [@default false] + lfs: bool; [@default false] + latest_revision: string option; + } + [@@deriving show, make] + + let default_latest_cmd git : Latest.Cmd.t = + let open Latest.Cmd in + let git_ls_remote flag value : t = + let m = Latest.Cmd.make_cmd in + let t = Template.make in + ~$(m ~prog: (t "git") ~args: [t "ls-remote"; t flag; git.repository; t "--refs"; t value] ()) + |: (m ~prog: (t "cut") ~args: [t "-f1"] ()) + in + match git.reference with + | `Branch b -> git_ls_remote "--branches" b + | `Ref r -> git_ls_remote "--heads" r +end + +module Darcs = struct + module Reference = struct + type t = [ + | `Context of [`Assumed of string option | `Stated of string] + | `Tag of string + ] + [@@deriving show] + end + + type t = { + repository: Template.t; + mirrors: Template.t list; + reference: Reference.t; + datetime: string option; (* ISO 8601 RFC 3339 *) + latest_weak_hash: string option; + } + [@@deriving show, make] + + let pp fmt t = Fmt.pf fmt "%s" (show t) +end + +module Pijul = struct + module Reference = struct + type t = [ + | `Channel of string + | `State of string + | `Change of string + ] + [@@deriving show] + end + + type t = { + remote: Template.t; + mirrors: Template.t list; + reference: Reference.t; + datetime: string option; (* ISO 8601 RFC 3339 *) + latest_state: string option; + } + [@@deriving show, make] +end + +module Hash = struct + type algorithm = + | SHA256 + | SHA512 + | BLAKE3 + [@@deriving enum, eq, ord, show] + + let algorithm_to_string = function + | SHA256 -> "SHA256" + | SHA512 -> "SHA512" + | BLAKE3 -> "BLAKE3" + + let algorithm_to_string_lower = + Fun.compose String.lowercase_ascii algorithm_to_string + + let algorithm_of_string = function + | "SHA256" | "sha256" -> Some SHA256 + | "SHA512" | "sha512" -> Some SHA512 + | "BLAKE3" | "blake3" -> Some BLAKE3 + | _ -> None + + (* many of the builtin fetchers may only work with SHA256 *) + let default_algorithm = SHA256 + + type t = { + algorithm: algorithm; + [@default default_algorithm] + (* None is for not yet calculated *) + value: string option; + (* used to assert in fetching for manually-updated pins *) + expected: string option; + } + [@@deriving show, make] +end + +(* INPUT *******************************************************************) + +module Kind = struct + type t = [ + | `File of File.t + | `Archive of Archive.t + | `Git of Git.t + | `Darcs of Darcs.t + | `Pijul of Pijul.t + ] + [@@deriving show] +end + +let make_kind_file ~url ?mirrors () = + `File (File.make ~url ?mirrors ()) + +let make_kind_archive ~url ?mirrors () = + `Archive (Archive.make ~url ?mirrors ()) + +let make_kind_darcs ~repository ?mirrors ~reference ?latest_weak_hash () = + `Darcs (Darcs.make ~repository ?mirrors ~reference ?latest_weak_hash ()) + +let make_kind_pijul ~remote ?mirrors ~reference ?latest_state () = + `Pijul (Pijul.make ~remote ?mirrors ~reference ?latest_state ()) + +let make_kind_git ~repository ?mirrors ~reference ?latest_revision ?submodules ?lfs () = + `Git (Git.make ~repository ?mirrors ~reference ?latest_revision ?submodules ?lfs ()) + +type t = { + name: Name.t; + kind: Kind.t; + (* This is use to override or provide a command to get the latest change or + revision or timestamp or whatever. *) + latest: Latest.t; [@default Latest.make ()] + hash: Hash.t; [@default Hash.make ()] + frozen: bool; [@default false] +} +[@@deriving show, make] + +let latest_cmd (input : t) : Latest.Cmd.t option = + match input.latest.cmd with + | None -> + ( + match input.kind with + | `Git g -> Some (Git.default_latest_cmd g) + (* Would be nice if other tools did a better job letting you query the + remote repository directly, but that isn’t where we are *) + | _ -> None + ) + | Some cmd -> Some cmd + +(* JINGOO MODELS **************************************************************) + +let jg_models2 (input : t) (needle : string) : Jingoo.Jg_types.tvalue = + let open Jingoo.Jg_types in + let opt_count = Option.fold ~none: 0 ~some: (Fun.const 1) in + (* presupplied with global values *) + let make_hashtbl (further_size : int) : (string, tvalue) Hashtbl.t = + let size = 1 + opt_count input.latest.value in + let htbl = Hashtbl.create (size + further_size) in + Hashtbl.add htbl "name" (Tstr (Name.take input.name)); + Option.iter (fun v -> Hashtbl.add htbl "cmd_value" (Tstr v)) input.latest.value; + htbl + in + let hashtbl = + match input.kind with + | `File _ -> + make_hashtbl 0 + | `Archive _ -> + make_hashtbl 0 + | `Git g -> + begin + let htbl = make_hashtbl 5 in + ( + match g.reference with + | `Branch b -> Hashtbl.add htbl "branch" (Tstr b) + | `Ref r -> Hashtbl.add htbl "ref" (Tstr r) + ); + Option.iter (fun d -> Hashtbl.add htbl "datetime" (Tstr d)) g.datetime; + Hashtbl.add htbl "lfs" (Tbool g.lfs); + Hashtbl.add htbl "submodules" (Tbool g.submodules); + Option.iter + (fun r -> + List.iter (fun key -> Hashtbl.add htbl key (Tstr r)) ["rev"; "revision"] + ) + g.latest_revision; + htbl + end + | `Darcs d -> + begin + let htbl = make_hashtbl 2 in + ( + match d.reference with + | `Context (`Stated sc) -> + Hashtbl.add htbl "context" (Tstr sc) + | `Context (`Assumed ac) -> + Option.iter (fun c -> Hashtbl.add htbl "context" (Tstr c)) ac + | `Tag t -> + Hashtbl.add htbl "tag" (Tstr t) + ); + Option.iter (fun d -> Hashtbl.add htbl "datetime" (Tstr d)) d.datetime; + Option.iter (fun w -> Hashtbl.add htbl "weak_hash" (Tstr w)) d.latest_weak_hash; + htbl + end + | `Pijul p -> + begin + let htbl = make_hashtbl 2 in + ( + match p.reference with + | `Channel c -> Hashtbl.add htbl "channel" (Tstr c) + | `State s -> Hashtbl.add htbl "state" (Tstr s) + | `Change c -> Hashtbl.add htbl "change" (Tstr c) + ); + Option.iter (fun d -> Hashtbl.add htbl "datetime" (Tstr d)) p.datetime; + Option.iter (fun s -> Hashtbl.add htbl "state" (Tstr s)) p.latest_state; + htbl + end + in + try Hashtbl.find hashtbl needle with Not_found -> Tnull + +(* NIXPKGS ********************************************************************) + +(* Nixpkgs is so critical & valuable to the Nix ecosystem that it gets its own + special treatment; it is also *required* to get access to many of the + fetchers *) +module Nixpkgs = struct + let name = Name.make "nixpkgs" + + let default_git_repository = Template.make "https://github.com/NixOS/nixpkgs.git" + + (* NOTE: "refs/heads/nixpkgs-unstable" is probably good enough for your + project, but defaulting to nixos-unstable since it is ‘safer’, requiring + that all the NixOS tests pass *) + let default_ref = "refs/heads/nixos-unstable" + + let default_hash = Hash.make ~algorithm: Hash.SHA256 () + + let known_git_mirrors : Template.t list = + List.map Template.make [ + "https://mirrors.tuna.tsinghua.edu.cn/git/nixpkgs.git" + ] + + let mk_latest ~reference ?latest_value () : Latest.t = + let mk_latest_cmd ~flag ~arg : Latest.Cmd.t = + let open Latest.Cmd in + let m = Latest.Cmd.make_cmd in + let t = Template.make in + ~$(m ~prog: (t "git") ~args: [t "ls-remote"; t flag; default_git_repository; t "--refs"; t arg] ()) + |: (m ~prog: (t "cut") ~args: [t "-f1"] ()) + in + { + cmd = begin + match reference with + | `Ref r -> Some (mk_latest_cmd ~flag: "--heads" ~arg: r); + | `Branch b -> Some (mk_latest_cmd ~flag: "--branches" ~arg: b); + end; + value = latest_value; + } + + let make_archive ?(reference = `Ref default_ref) ?latest_value () = + let latest = mk_latest ~reference ?latest_value () in + let url = + Template.make "https://github.com/NixOS/nixpkgs/archive/{{cmd_value}}.tar.gz" + in + let kind = make_kind_archive ~url () in + make ~name ~kind ~latest ~hash: default_hash () + + (* The TUNA mirror is a Git mirror, so normalize on Git *) + let make_git_with_known_mirrors + ?(extra_mirrors = []) + ?(reference = `Ref default_ref) + ?latest_revision + ?submodules + ?lfs + () + = + let kind = + make_kind_git + ~repository: default_git_repository + ~mirrors: (known_git_mirrors @ extra_mirrors) + ~reference + ?latest_revision + ?submodules + ?lfs + () + in + make ~name ~kind ~hash: default_hash () +end |
