diff --git a/pkgs/oizys/src/oizys.nim b/pkgs/oizys/src/oizys.nim index e3f51d4..05704d1 100644 --- a/pkgs/oizys/src/oizys.nim +++ b/pkgs/oizys/src/oizys.nim @@ -1,7 +1,7 @@ ## nix begat oizys import std/[os, osproc, sequtils, strformat, strutils, tables] import hwylterm, hwylterm/[hwylcli] -import oizys/[context, github, nix, logging] +import oizys/[context, github, nix, logging, utils] proc checkExes() = if findExe("nix") == "": @@ -37,6 +37,7 @@ hwylCli: - m preSub: setupLoggers() + echo "ORIGINAL UPDATE CONTEXT" updateContext(host, flake, verbose, resetCache) subcommands: @@ -74,11 +75,6 @@ hwylCli: [ci] ... "builtin ci" - # BUG: current behavior adds this block twice... - # when really I want it to only happen in the lowest "subcommand" - # needs to be fixed in hwylterm - preSub: - updateContext(host, flake, verbose, resetCache) subcommands: [update] ... "build current and updated hosts" @@ -162,3 +158,32 @@ hwylCli: updateRepo() nixosRebuild(NixosRebuildSubcmd.switch) + [utils] + ... """ + less common utils operations + + some snippets I've reimplemented in nim so that nix isn't as annoying + """ + subcommands: + [hash] + ... "collect build hash from failure" + positionals: + installable string + run: + stdout.write getBuildHash(installable) + + [narinfo] + ... """ + check active caches for nix derivation + + by default will use [yellow]nix config show[/] to determine + the binary cache urls + """ + positionals: + installables seq[string] + flags: + cache: + ? "url of nix binary cache, can be repeated" + T seq[string] + run: + checkForCache(installables, cache) diff --git a/pkgs/oizys/src/oizys/exec.nim b/pkgs/oizys/src/oizys/exec.nim index de0500b..3854606 100644 --- a/pkgs/oizys/src/oizys/exec.nim +++ b/pkgs/oizys/src/oizys/exec.nim @@ -7,8 +7,14 @@ import hwylterm import hwylterm/spin/spinners # todo: remove after hwylterm update -func addArgs*(cmd: var string, args: varargs[string]) = +func addArgs*(cmd: string, args: varargs[string]): string = + ## append to string for command + result = cmd & " " & args.join(" ") + +func addArgs*(cmd: var string, args: varargs[string]): string {.discardable.} = + ## append to string for command cmd &= " " & args.join(" ") + result = cmd # deprecate in favor of above? func addArg*(cmd: var string, arg: string ) = @@ -50,32 +56,39 @@ proc runCmdCapt*( result.stderr.add line & '\n' result.exitCode = peekExitCode(p) if result.exitCode != -1: - result.stdout.add outstrm.readAll() - result.stderr.add errstrm.readAll() + if CaptStdout in capture: + result.stdout.add outstrm.readAll() + if CaptStderr in capture: + result.stderr.add errstrm.readAll() break close p -proc formatStdoutStderr(stdout: string, stderr: string): BbString = +proc formatSubprocessError*(s: string): BbString = + for line in s.splitLines(): + result.add bb("[red]->[/] " & line & "\n") + +proc formatStdoutStderr*(stdout: string, stderr: string): BbString = template add(stream: string) = if stream.strip() != "": result.add astToStr(stream).bb("bold") & ":\n" - for line in stream.splitlines(): - result.add bb("[red]->[/] " & line & "\n") + result.add formatSubprocessError(stream) add(stdout) add(stderr) + proc runCmdCaptWithSpinner*( cmd: string, msg: BbString | string = bb"", - capture: set[CaptureGrp] = {CaptStdout} + capture: set[CaptureGrp] = {CaptStdout}, + check: bool = true ): tuple[output, err: string] = var output, err: string code: int with(Dots2, msg): (output, err, code) = runCmdCapt(cmd, capture) - if code != 0: + if check and code != 0: stderr.write($formatStdoutStderr(output,err)) error fmt"{cmd} had non zero exit" quit code diff --git a/pkgs/oizys/src/oizys/nix.nim b/pkgs/oizys/src/oizys/nix.nim index 6fdbaea..10f5380 100644 --- a/pkgs/oizys/src/oizys/nix.nim +++ b/pkgs/oizys/src/oizys/nix.nim @@ -3,11 +3,12 @@ import std/[ enumerate, os, sequtils, sets, strformat, strutils, sugar, logging, tables, times ] +export tables import hwylterm, hwylterm/logging, jsony import ./[context, exec] -proc nixCommand(cmd: string, nom: bool = false): string = +proc nixCommand*(cmd: string, nom: bool = false): string = if nom: if findExe("nom") == "": fatalQuit "--nom requires nix-output-monitor is installed" @@ -53,13 +54,13 @@ proc nixosRebuild*(subcmd: NixosRebuildSubcmd, args: openArray[string] = [], rem type Derivation = object - storePath, hash, name: string + storePath*, hash*, name*: string DryRunOutput = object toBuild: seq[Derivation] toFetch: seq[Derivation] -func toDerivation(pkg: string): Derivation = +func toDerivation*(pkg: string): Derivation = let path = pkg.strip() let s = path.split("-", 1) result.storePath = path @@ -108,7 +109,7 @@ proc parseDryRunOutput(err: string): DryRunOutput = result.toBuild.sort(cmpDrv) result.toFetch.sort(cmpDrv) -proc trunc(s: string, limit: int): string = +proc trunc*(s: string, limit: int): string = if s.len <= limit: s else: @@ -143,22 +144,30 @@ proc toBuildNixosConfiguration(): seq[string] = type DerivationOutput = object - path: string + path*: string # hashAlgo: string # hash: string NixDerivation = object - inputDrvs: Table[string, JsonNode] - name: string - outputs: Table[string, DerivationOutput] + inputDrvs*: Table[string, JsonNode] + name*: string + outputs*: Table[string, DerivationOutput] -proc evaluateDerivations(drvs: seq[string]): Table[string, NixDerivation] = +# here a results var would be nice... +proc narHash*(s: string): string = + ## get hash from nix store path + if not s.startsWith("/nix/store/") and s.len >= 44: + fatalQuit "failed to extract narHash from: " & s + let ss = s.split("-") + result = ss[0].split("/")[^1] + +proc evaluateDerivations(drvs: openArray[string]): Table[string, NixDerivation] = var cmd = "nix derivation show -r" cmd.addArgs drvs let (output, _) = runCmdCaptWithSpinner(cmd, "evaluating derivations") fromJson(output, Table[string, NixDerivation]) -proc nixDerivationShow(drvs: seq[string]): Table[string, NixDerivation] = +proc nixDerivationShow*(drvs: openArray[string]): Table[string, NixDerivation] = var cmd = "nix derivation show" cmd.addArgs drvs let (output, _ ) = diff --git a/pkgs/oizys/src/oizys/utils.nim b/pkgs/oizys/src/oizys/utils.nim new file mode 100644 index 0000000..66c6360 --- /dev/null +++ b/pkgs/oizys/src/oizys/utils.nim @@ -0,0 +1,89 @@ +import std/[strformat, strutils, osproc, sugar, httpclient] +import hwylterm +import ./[nix, exec,logging] + + +# TODO: refactor runCmdCaptWithSpinner so it works in getBuildHash +proc checkBuild(installable: string): tuple[stdout: string, stderr: string] = + var + output, err: string + code: int + let cmd = nixCommand("build").addArgs(installable) + with(Dots2, bbfmt"attempt to build: [b]{installable}"): + (output, err, code) = runCmdCapt(cmd, capture = {CaptStdout, CaptStderr}) + if code == 0: + fatalQuit fmt"{cmd} had zero exit" + result = (output, err) + +proc getBuildHash*(installable: string): string = + let (output, err) = checkBuild(installable) + for line in err.splitLines(): + if line.strip().startsWith("got: "): + let s = line.split("got:") + result = s[1].strip() + if result == "": + stderr.write formatStdoutStderr(output, err) & "\n" + fatalQuit "failed to find update hash from above output" + +proc getCaches(): seq[string] = + ## use nix to get the current cache urls + debug "determing caches to check" + let (output, code) = execCmdEx("nix config show") + if code != 0: + echo formatSubprocessError(output) + fatalQuit "error running `nix config show`" + + for line in output.splitLines(): + if line.startsWith("substituters ="): + let s = line.split("=")[1].strip() + for u in s.split(): + result.add u + + if result.len == 0: + echo formatSubprocessError(output) + fatalQuit "error running `nix config show`" + + + + +proc hasNarinfo*(cache: string, path: string): bool = + debug fmt"checking {cache} for {path}" + let + hash = narHash(path) + url = cache & "/" & hash & ".narinfo" + try: + let client = newHttpClient() + result = client.head(url).code == Http200 + except: + result = false + +proc prettyDerivation(path: string): BbString = + let drv = path.toDerivation() + const maxLen = 40 + result.add drv.name.trunc(maxLen).alignLeft(maxLen) + result.add " " + result.add drv.hash.bb("faint") + +proc checkForCache*(installables: seq[string], caches: seq[string]) = + let caches = + if caches.len > 0: caches + else: getCaches() + let drvs = nixDerivationShow(installables) + # outputs['outs'] might blow up + let outs = collect: + for name, drv in drvs: + {name: drv.outputs["out"].path} + + for name, path in outs: + var found = false + for cache in caches: + if hasNarinfo(cache, path): + found = true + info prettyDerivation(path) + info fmt"exists in {cache}" + break + + if not found: + info fmt"failed to find:" + info prettyDerivation(path) + diff --git a/todo.md b/todo.md index 8072c0a..ef13fce 100644 --- a/todo.md +++ b/todo.md @@ -4,7 +4,6 @@ Could make an `oizys check` command: -- `utils hash` given some flake-url or path attempt to build and extract "got: " string - `check lock` could encapsulate any diagnostic code I want to run that does things like check for too many "inputs". essentially getting the same output as this `jq < flake.lock '.nodes | keys[] | select(contains("_"))` - `utils cache ` check for the narinfo in my cache given some nix store path