package cabal

  1. Overview
  2. Docs

Source file backend_version.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
(******************************************************************************)
(*                                                                            *)
(* Copyright (c) 2026 Epure Team                                              *)
(* All rights reserved.                                                       *)
(*                                                                            *)
(******************************************************************************)

type semver = {
  major : int;
  minor : int;
  patch : int;
  prerelease : string option;
}

(* Scan [s] from position [i] for a run of ASCII digits.
   Returns [Some (value, next_pos)] or [None] if no digit at [i]. *)
let parse_digits s i =
  let n = String.length s in
  if i >= n then None
  else
    let c = Char.code s.[i] in
    if c < 48 || c > 57 then None
    else begin
      let j = ref i in
      while !j < n && Char.code s.[!j] >= 48 && Char.code s.[!j] <= 57 do
        incr j
      done ;
      Some (int_of_string (String.sub s i (!j - i)), !j)
    end

(* Extract the prerelease identifier after position [pos] in [s].
   Expects a '-' at [pos].  Reads until whitespace, '+', or end-of-string.
   Returns [None] when there is no '-' or the identifier is empty. *)
let extract_prerelease s pos =
  let n = String.length s in
  if pos >= n || s.[pos] <> '-' then None
  else begin
    let i = ref (pos + 1) in
    while
      !i < n
      && s.[!i] <> ' '
      && s.[!i] <> '\t'
      && s.[!i] <> '\n'
      && s.[!i] <> '\r'
      && s.[!i] <> '+'
    do
      incr i
    done ;
    if !i = pos + 1 then None else Some (String.sub s (pos + 1) (!i - pos - 1))
  end

(* Find the first N.N.N triplet in [s].  Scan one character at a time so
   we tolerate any prefix (e.g. "claude ", "v", "gemini-cli/").
   After the patch digits, a '-' prefix starts a prerelease identifier; a
   '+' prefix (no '-') indicates build metadata only (not prerelease). *)
let parse_from_output s =
  let n = String.length s in
  let rec scan i =
    if i >= n then Error "no version string found in output"
    else
      match parse_digits s i with
      | None -> scan (i + 1)
      | Some (major, j) ->
          if j < n && s.[j] = '.' then
            match parse_digits s (j + 1) with
            | None -> scan (i + 1)
            | Some (minor, k) ->
                if k < n && s.[k] = '.' then
                  match parse_digits s (k + 1) with
                  | None -> scan (i + 1)
                  | Some (patch, p) ->
                      let prerelease = extract_prerelease s p in
                      Ok {major; minor; patch; prerelease}
                else scan (i + 1)
          else scan (i + 1)
  in
  scan 0

let of_string s = parse_from_output s

let compare a b =
  let c = Int.compare a.major b.major in
  if c <> 0 then c
  else
    let c = Int.compare a.minor b.minor in
    if c <> 0 then c else Int.compare a.patch b.patch

let is_prerelease v = v.prerelease <> None

let semver_to_string v =
  match v.prerelease with
  | None -> Printf.sprintf "%d.%d.%d" v.major v.minor v.patch
  | Some pre -> Printf.sprintf "%d.%d.%d-%s" v.major v.minor v.patch pre

let check_gate ~descriptor ~installed =
  match of_string descriptor.Backend_registry.baseline_version with
  | Error e ->
      Error
        (Printf.sprintf
           "could not parse baseline version for %s: %s"
           descriptor.Backend_registry.display_name
           e)
  | Ok baseline ->
      if compare installed baseline < 0 then
        Error
          (Printf.sprintf
             "%s %s is below the required baseline %s. Upgrade to %s or later, \
              or bypass the check with --force-backend (unsupported versions \
              may behave unexpectedly)."
             descriptor.Backend_registry.display_name
             (semver_to_string installed)
             descriptor.Backend_registry.baseline_version
             descriptor.Backend_registry.baseline_version)
      else if is_prerelease installed then
        Error
          (Printf.sprintf
             "%s %s is a prerelease version. The stable baseline requires %s \
              or later. Prerelease versions may behave unexpectedly; upgrade \
              to a stable release or bypass the check with --force-backend."
             descriptor.Backend_registry.display_name
             (semver_to_string installed)
             descriptor.Backend_registry.baseline_version)
      else Ok ()