From 55da692e6f9c82b82e1637fbd3806bad66800669 Mon Sep 17 00:00:00 2001 From: 06kellyjac Date: Sat, 6 Jun 2020 15:42:45 +0100 Subject: [PATCH] deno: add updateScript for automatic updates This'll save time and avoid human error. Wrote in deno typescript because why not. --- pkgs/development/web/deno/default.nix | 2 + pkgs/development/web/deno/update/common.ts | 52 ++++++++++++++ pkgs/development/web/deno/update/deps.ts | 79 ++++++++++++++++++++++ pkgs/development/web/deno/update/src.ts | 67 ++++++++++++++++++ pkgs/development/web/deno/update/update.ts | 50 ++++++++++++++ 5 files changed, 250 insertions(+) create mode 100644 pkgs/development/web/deno/update/common.ts create mode 100644 pkgs/development/web/deno/update/deps.ts create mode 100644 pkgs/development/web/deno/update/src.ts create mode 100755 pkgs/development/web/deno/update/update.ts diff --git a/pkgs/development/web/deno/default.nix b/pkgs/development/web/deno/default.nix index cb6de9447ac4..28e438fa1e95 100644 --- a/pkgs/development/web/deno/default.nix +++ b/pkgs/development/web/deno/default.nix @@ -61,6 +61,8 @@ rustPlatform.buildRustPackage rec { installShellCompletion deno.{bash,fish} --zsh _deno ''; + passthru.updateScript = ./update/update.ts; + meta = with stdenv.lib; { homepage = "https://deno.land/"; changelog = "${src.meta.homepage}/releases/tag/v${version}"; diff --git a/pkgs/development/web/deno/update/common.ts b/pkgs/development/web/deno/update/common.ts new file mode 100644 index 000000000000..71e4d638f8d7 --- /dev/null +++ b/pkgs/development/web/deno/update/common.ts @@ -0,0 +1,52 @@ +interface GHRelease { + tag_name: string; +} + +const decode = (buffer: Uint8Array) => new TextDecoder("utf-8").decode(buffer); +const run = async (command: string, args: string[]) => { + const cmd = Deno.run( + { cmd: [command, ...args], stdout: "piped", stderr: "piped" }, + ); + if (!(await cmd.status()).success) { + throw await cmd.stderrOutput().then((b) => decode(b)); + } + return cmd.output().then((b) => decode(b).trimEnd()); +}; + +// Exports +export const versionRegExp = /\d+\.\d+\.\d+/; +export const sha256RegExp = /[a-z0-9]{52}/; + +export async function commit( + name: string, + oldVer: string, + newVer: string, + files: string[], +) { + await run("git", ["add", ...files]); + await run("git", ["commit", "-m", `${name}: ${oldVer} -> ${newVer}`]); +} + +export const getExistingVersion = async (filePath: string) => + read(filePath).then((s) => + s.match(genValueRegExp("version", versionRegExp))?.shift() || "" + ); + +export const getLatestVersion = (owner: string, repo: string) => + fetch(`https://api.github.com/repos/${owner}/${repo}/releases`) + .then((res) => res.json()) + .then((res: GHRelease[]) => res[0].tag_name); + +// The (?<=) and (?=) allow replace to only change inside +// Match the regex passed in or empty +export const genValueRegExp = (key: string, regex: RegExp) => + new RegExp(`(?<=${key} = ")(${regex.source}|)(?=")`); + +export const logger = (name: string) => + (...a: any) => console.log(`[${name}]`, ...a); + +export const nixPrefetch = (args: string[]) => run("nix-prefetch", args); +export const nixPrefetchURL = (args: string[]) => run("nix-prefetch-url", args); + +export const read = Deno.readTextFile; +export const write = Deno.writeTextFile; diff --git a/pkgs/development/web/deno/update/deps.ts b/pkgs/development/web/deno/update/deps.ts new file mode 100644 index 000000000000..beedeade3a86 --- /dev/null +++ b/pkgs/development/web/deno/update/deps.ts @@ -0,0 +1,79 @@ +import { + getExistingVersion, + genValueRegExp, + logger, + nixPrefetchURL, + versionRegExp, + write, +} from "./common.ts"; + +const log = logger("deps"); + +export interface Architecture { + nix: string; + rust: string; +} +interface PrefetchResult { + arch: Architecture; + sha256: string; +} + +const getRustyV8Version = async ( + owner: string, + repo: string, + version: string, +) => + fetch( + `https://github.com/${owner}/${repo}/raw/${version}/core/Cargo.toml`, + ) + .then((res) => res.text()) + .then((txt) => + txt.match(genValueRegExp("rusty_v8", versionRegExp))?.shift() + ); + +const archShaTasks = (version: string, arches: Architecture[]) => + arches.map(async (arch: Architecture): Promise => { + log("Fetching:", arch.nix); + const sha256 = await nixPrefetchURL( + [`https://github.com/denoland/rusty_v8/releases/download/v${version}/librusty_v8_release_${arch.rust}.a`], + ); + log("Done: ", arch.nix); + return { arch, sha256 }; + }); + +const templateDeps = (version: string, deps: PrefetchResult[]) => + `# auto-generated file -- DO NOT EDIT! +{}: +rec { + rustyV8Lib = { + version = "${version}"; + sha256s = { +${deps.map((d) => ` ${d.arch.nix} = "${d.sha256}";`).join("\n")} + }; + }; +} +`; + +export async function updateDeps( + filePath: string, + owner: string, + repo: string, + denoVersion: string, + arches: Architecture[], +) { + log("Starting deps update"); + // 0.0.0 + const version = await getRustyV8Version(owner, repo, denoVersion); + if (typeof version !== "string") { + throw "no rusty_v8 version"; + } + log("rusty_v8 version:", version); + const existingVersion = await getExistingVersion(filePath); + if (version === existingVersion) { + log("Version already matches latest, skipping..."); + return; + } + const archShaResults = await Promise.all(archShaTasks(version, arches)); + await write(filePath, templateDeps(version, archShaResults)); + log("Finished deps update"); +} diff --git a/pkgs/development/web/deno/update/src.ts b/pkgs/development/web/deno/update/src.ts new file mode 100644 index 000000000000..fae15acd0d2c --- /dev/null +++ b/pkgs/development/web/deno/update/src.ts @@ -0,0 +1,67 @@ +import { + genValueRegExp, + logger, + nixPrefetch, + read, + sha256RegExp, + versionRegExp, + write, +} from "./common.ts"; + +interface Replacer { + regex: RegExp; + value: string; +} + +const log = logger("src"); + +const prefetchSha256 = (nixpkgs: string, version: string) => + nixPrefetch(["-f", nixpkgs, "deno.src", "--rev", version]); +const prefetchCargoSha256 = (nixpkgs: string) => + nixPrefetch( + [`{ sha256 }: (import ${nixpkgs} {}).deno.cargoDeps.overrideAttrs (_: { outputHash = sha256; })`], + ); + +const replace = (str: string, replacers: Replacer[]) => + replacers.reduce( + (str, r) => str.replace(r.regex, r.value), + str, + ); + +const updateNix = (filePath: string, replacers: Replacer[]) => + read(filePath).then((str) => write(filePath, replace(str, replacers))); + +const genVerReplacer = (k: string, value: string): Replacer => ( + { regex: genValueRegExp(k, versionRegExp), value } +); +const genShaReplacer = (k: string, value: string): Replacer => ( + { regex: genValueRegExp(k, sha256RegExp), value } +); + +export async function updateSrc( + filePath: string, + nixpkgs: string, + denoVersion: string, +) { + log("Starting src update"); + const trimVersion = denoVersion.substr(1); + log("Fetching sha256 for:", trimVersion); + const sha256 = await prefetchSha256(nixpkgs, denoVersion); + log("sha256 to update:", sha256); + await updateNix( + filePath, + [ + genVerReplacer("version", trimVersion), + genShaReplacer("sha256", sha256), + genShaReplacer("cargoSha256", ""), // Empty ready for prefetchCargoSha256 + ], + ); + log("Fetching cargoSha256 for:", sha256); + const cargoSha256 = await prefetchCargoSha256(nixpkgs); + log("cargoSha256 to update:", cargoSha256); + await updateNix( + filePath, + [genShaReplacer("cargoSha256", cargoSha256)], + ); + log("Finished src update"); +} diff --git a/pkgs/development/web/deno/update/update.ts b/pkgs/development/web/deno/update/update.ts new file mode 100755 index 000000000000..ab13cee9dbe2 --- /dev/null +++ b/pkgs/development/web/deno/update/update.ts @@ -0,0 +1,50 @@ +#!/usr/bin/env nix-shell +/* +#!nix-shell -i "deno run --allow-net --allow-run --allow-read --allow-write" -p deno git nix-prefetch nix-prefetch-url +*/ +import { + commit, + getExistingVersion, + getLatestVersion, + logger, +} from "./common.ts"; +import { Architecture, updateDeps } from "./deps.ts"; +import { updateSrc } from "./src.ts"; + +const log = logger("update"); +// TODO: Getting current file position to more-safely point to nixpkgs root +const nixpkgs = Deno.cwd(); +// TODO: Read values from default.nix +const owner = "denoland"; +const repo = "deno"; +const denoDir = `${nixpkgs}/pkgs/development/web/${repo}`; +const src = `${denoDir}/default.nix`; +const deps = `${denoDir}/deps.nix`; +const architectures: Architecture[] = [ + { nix: "x86_64-linux", rust: "x86_64-unknown-linux-gnu" }, + { nix: "aarch64-linux", rust: "aarch64-unknown-linux-gnu" }, + { nix: "x86_64-darwin", rust: "x86_64-apple-darwin" }, +]; + +log("Updating deno"); + +log("Getting latest deno version"); +const version = await getLatestVersion(owner, repo); +const existingVersion = await getExistingVersion(src); +const trimVersion = version.substr(1); // Strip v from v0.0.0 +log("Latest version: ", trimVersion); +log("Extracted version:", existingVersion); +if (trimVersion === existingVersion) { + log("Version already matches latest, skipping..."); + Deno.exit(0); +} + +const tasks = [ + updateSrc(src, nixpkgs, version), + updateDeps(deps, owner, repo, version, architectures), +]; +await Promise.all(tasks); +log("Updating deno complete"); +log("Commiting"); +await commit(repo, existingVersion, trimVersion, [src, deps]); +log("Done");