| Take every jolt-native half from the release, and only from there a008d3b nandi 18d ago | 1 | #!/bin/sh |
| 2 | #_( |
| 3 | exec "$(dirname "$0")/bb" "$0" "$@" |
| 4 | ) |
| 5 | |
| 6 | ;; Move every jolt-native pin in this tree to a release of it. |
| 7 | ;; |
| 8 | ;; scripts/bump-jolt-native.bb # the latest release |
| 9 | ;; scripts/bump-jolt-native.bb v0.1.3 # a named one |
| 10 | ;; |
| 11 | ;; A jolt-native bump is four facts in three places: the tag, the URL, the size |
| Build the APK as derivations only, and retire the buck2 graph 3bc3aaa nandi 16d ago | 12 | ;; and the digest of each archive in scripts/*.dotslash, the same URL and digest |
| 13 | ;; for the Android archives in nix/android.nix, and the release's commit in |
| 14 | ;; deps.edn. Done by hand it is a lot of copying between a browser and a |
| 15 | ;; sha256sum, and the failure mode is a pin that still says v0.1.2 while its |
| 16 | ;; digest is v0.1.3's — which DotSlash catches, but only on the machine that |
| 17 | ;; next fetches it, and which fetchurl catches at build time and no sooner. |
| 18 | ;; |
| 19 | ;; The two places exist because the two builds fetch differently and neither can |
| 20 | ;; read the other's pin: `just lib` and a released-halves run resolve DotSlash |
| 21 | ;; manifests, and the APK is a nix derivation, whose inputs have to be fetchurl. |
| 22 | ;; The digests are the same bytes either way — DotSlash's `digest` is over the |
| 23 | ;; archive, which is what fetchurl hashes too. |
| Take every jolt-native half from the release, and only from there a008d3b nandi 18d ago | 24 | ;; |
| 25 | ;; So: ask GitLab what the release holds, fetch each archive once, weigh it, |
| Build the APK as derivations only, and retire the buck2 graph 3bc3aaa nandi 16d ago | 26 | ;; write the manifests and nix/android.nix back, and put the release's commit in |
| 27 | ;; deps.edn — which is what jolt resolves glimmer-vidya from, and so what the |
| 28 | ;; boot image compiles against. |
| Take every jolt-native half from the release, and only from there a008d3b nandi 18d ago | 29 | ;; |
| 30 | ;; Nothing here decides whether the new release is a good idea. It only makes |
| 31 | ;; the tree say one version instead of two. |
| 32 | (require '[babashka.classpath :as cp]) |
| 33 | (cp/add-classpath (str (babashka.fs/parent *file*))) |
| 34 | (require '[frq.paths :as paths] |
| 35 | '[babashka.fs :as fs] |
| 36 | '[babashka.process :as p] |
| 37 | '[cheshire.core :as json] |
| 38 | '[clojure.string :as str]) |
| 39 | |
| 40 | (def here (fs/parent (fs/canonicalize *file*))) |
| 41 | (def root (str (fs/parent here))) |
| 42 | (def project "nandithebull%2Fjolt-native") |
| 43 | (def repo "https://gitlab.com/nandithebull/jolt-native") |
| 44 | |
| 45 | ;; The release, as GitLab has it. `?per_page=1` when no tag is asked for: the |
| 46 | ;; list is newest first, and the newest is what "latest" means here — the |
| 47 | ;; /releases/permalink/latest endpoint sorts by release date, which is not the |
| 48 | ;; same thing once a release is edited. |
| 49 | (defn release [tag] |
| 50 | (let [url (if tag |
| 51 | (str "https://gitlab.com/api/v4/projects/" project "/releases/" tag) |
| 52 | (str "https://gitlab.com/api/v4/projects/" project "/releases?per_page=1")) |
| 53 | body (json/parse-string (paths/out "curl" "-fsSL" url))] |
| 54 | (if tag |
| 55 | body |
| 56 | (or (first body) (paths/die (str "no releases at all under " repo)))))) |
| 57 | |
| 58 | ;; The manifests this tree pins out of jolt-native. Every DotSlash file beside |
| 59 | ;; this script whose providers point at the repo — rather than a list written |
| 60 | ;; here, which would be a sixth place to forget. |
| 61 | (defn manifests [] |
| 62 | (->> (fs/glob here "*.dotslash") |
| 63 | sort |
| 64 | (keep (fn [file] |
| 65 | (let [text (slurp (str file)) |
| 66 | json (json/parse-string (subs text (str/index-of text "{")))] |
| 67 | (when (->> (get json "platforms") |
| 68 | vals |
| 69 | (some #(str/starts-with? (get-in % ["providers" 0 "url"]) repo))) |
| 70 | {:file file :text text :json json})))))) |
| 71 | |
| 72 | ;; The archive an entry names, at the new tag. The URL a manifest carries is the |
| 73 | ;; release permalink — /-/releases/<tag>/downloads/<asset> — so the asset's name |
| 74 | ;; is its last segment, and the name carries the version too. Both move. |
| 75 | (defn retag [url old new] |
| 76 | (-> url |
| 77 | (str/replace (str "/releases/" old "/") (str "/releases/" new "/")) |
| 78 | (str/replace (str "-" old ".") (str "-" new ".")))) |
| 79 | |
| 80 | (defn tag-of [url] |
| 81 | (second (re-find #"/-/releases/([^/]+)/downloads/" url))) |
| 82 | |
| 83 | ;; Fetch once per distinct URL, then weigh it: DotSlash wants the size and the |
| 84 | ;; sha256 of the archive as downloaded, not of anything inside it. |
| 85 | (def weigh |
| 86 | (memoize |
| 87 | (fn [dir url] |
| 88 | (let [file (fs/path dir (last (str/split url #"/")))] |
| 89 | (println (str " fetching " (fs/file-name file))) |
| 90 | (p/shell "curl" "-fsSL" "-o" (str file) url) |
| 91 | {:size (fs/size file) |
| 92 | :digest (first (str/split (paths/out "sha256sum" (str file)) #"\s+")) |
| 93 | :file file})))) |
| 94 | |
| 95 | ;; What the shim will reach for inside the archive. A release that moved a file |
| 96 | ;; leaves a manifest that fetches and verifies and then cannot resolve, which is |
| 97 | ;; a worse thing to find out from than this. |
| 98 | (defn check-path! [{:keys [file]} path] |
| 99 | (let [names (set (str/split-lines (paths/out "tar" "-tzf" (str file))))] |
| 100 | (when-not (some #(= path (str/replace % #"^\./" "")) names) |
| 101 | (paths/die (str "no " path " in " (fs/file-name file)) |
| 102 | "The release moved it; the manifest's `path` needs a hand.")))) |
| 103 | |
| 104 | ;; The JSON back out, in the shape it went in: two-space indentation, a value |
| 105 | ;; after each key, and the `//` comment block above it kept — that block is why |
| 106 | ;; anyone reading the manifest knows what the object is for. |
| 107 | (def pretty |
| 108 | (json/create-pretty-printer |
| 109 | (assoc json/default-pretty-print-options |
| 110 | :indentation " " |
| 111 | :indent-arrays? true |
| 112 | :object-field-value-separator ": "))) |
| 113 | |
| 114 | (defn rewrite [{:keys [file text json]} tmp tag] |
| 115 | (let [platforms (get json "platforms") |
| 116 | updated |
| 117 | (into (array-map) |
| 118 | (for [[platform entry] platforms |
| 119 | :let [url (retag (get-in entry ["providers" 0 "url"]) (tag-of (get-in entry ["providers" 0 "url"])) tag) |
| 120 | {:keys [size digest] :as got} (weigh tmp url)]] |
| 121 | (do |
| 122 | (check-path! got (get entry "path")) |
| 123 | [platform (-> entry |
| 124 | (assoc "size" size "digest" digest) |
| 125 | (assoc-in ["providers" 0 "url"] url))]))) |
| 126 | head (subs text 0 (str/index-of text "{")) |
| 127 | ;; The prose above the JSON names the release too — "from jolt-native's |
| 128 | ;; v0.1.3 release". Left saying the old one it would be a lie the moment |
| 129 | ;; this script succeeds. |
| 130 | head (reduce (fn [h old] (str/replace h old tag)) |
| 131 | head |
| 132 | (distinct (keep #(tag-of (get-in % ["providers" 0 "url"])) (vals platforms))))] |
| 133 | (spit (str file) (str head (json/generate-string (assoc json "platforms" updated) {:pretty pretty}) "\n")) |
| 134 | (println (str " " (fs/file-name file))))) |
| 135 | |
| Build the APK as derivations only, and retire the buck2 graph 3bc3aaa nandi 16d ago | 136 | ;; nix/android.nix fetches two of the same archives as `pkgs.fetchurl`, because |
| 137 | ;; a derivation cannot resolve a DotSlash manifest: the fetch is the input, and |
| 138 | ;; an input has to be a fixed-output derivation with the digest written down. |
| 139 | ;; Rewritten as text for the same reason deps.edn is — the file is mostly the |
| 140 | ;; comments explaining each step, and there is no round-tripping Nix as data |
| 141 | ;; here anyway. |
| 142 | ;; |
| 143 | ;; Every fetchurl whose url points at the repo, whatever it is called: a third |
| 144 | ;; archive appearing in android.nix should move with the other two rather than |
| 145 | ;; be remembered about. |
| 146 | (defn bump-nix! [tmp tag] |
| 147 | (let [file (fs/path root "nix" "android.nix") |
| 148 | text (slurp (str file)) |
| 149 | ;; url and sha256 as one match, so the pair moves together. Anything |
| 150 | ;; between them — a comment, another attribute — would not match, and |
| 151 | ;; not matching is the safe direction: it leaves the pin alone and says |
| 152 | ;; nothing was written. |
| 153 | pattern (re-pattern (str "(url\\s*=\\s*\")(" (java.util.regex.Pattern/quote repo) |
| 154 | "[^\"]*)(\";\\s*\n\\s*sha256\\s*=\\s*\")([^\"]*)(\")")) |
| 155 | seen (atom []) |
| 156 | updated (str/replace text pattern |
| 157 | (fn [[_ head url mid _ tail]] |
| 158 | (let [new-url (retag url (tag-of url) tag) |
| 159 | {:keys [digest]} (weigh tmp new-url)] |
| 160 | (swap! seen conj (last (str/split new-url #"/"))) |
| 161 | (str head new-url mid digest tail))))] |
| 162 | (when (empty? @seen) |
| 163 | (paths/die (str "no fetchurl in " file " points at " repo))) |
| 164 | (spit (str file) updated) |
| 165 | (doseq [name @seen] (println (str " nix/android.nix " name))))) |
| 166 | |
| Take every jolt-native half from the release, and only from there a008d3b nandi 18d ago | 167 | ;; deps.edn takes glimmer-vidya as a git dependency, and the boot image is |
| 168 | ;; compiled from the source root jolt resolves that to. Replaced as text rather |
| 169 | ;; than round-tripped as EDN: the file is mostly comments explaining why each |
| 170 | ;; pin is where it is, and rewriting it as data would throw all of them away. |
| 171 | (defn bump-deps! [commit] |
| 172 | (let [file (fs/path root "deps.edn") |
| 173 | text (slurp (str file)) |
| 174 | old (paths/dep-sha root repo)] |
| 175 | (cond |
| 176 | (nil? old) (paths/die (str "no dependency on " repo " in deps.edn")) |
| 177 | (= old commit) (println " deps.edn already at this commit") |
| 178 | :else (do (spit (str file) (str/replace text old commit)) |
| 179 | (println (str " deps.edn " (subs old 0 8) " -> " (subs commit 0 8))))))) |
| 180 | |
| 181 | (let [tag (first *command-line-args*) |
| 182 | rel (release tag) |
| 183 | tag (get rel "tag_name") |
| 184 | commit (get-in rel ["commit" "id"]) |
| 185 | files (manifests) |
| 186 | tmp (fs/create-temp-dir {:prefix "jolt-native-"})] |
| 187 | (when (empty? files) |
| 188 | (paths/die (str "no DotSlash manifest in " here " points at " repo))) |
| 189 | (println (str "jolt-native " tag " (" (subs commit 0 8) ")")) |
| 190 | (try |
| 191 | (doseq [m files] (rewrite m tmp tag)) |
| 192 | (finally (fs/delete-tree tmp))) |
| Build the APK as derivations only, and retire the buck2 graph 3bc3aaa nandi 16d ago | 193 | (bump-nix! tmp tag) |
| Take every jolt-native half from the release, and only from there a008d3b nandi 18d ago | 194 | (bump-deps! commit) |
| 195 | (println (str "\nNow: git diff, then `just lib` and `just apk` — the pins are " |
| 196 | "written, nothing is built."))) |