From 124e63403bd5dabfd35a38ea60c9484053406fa6 Mon Sep 17 00:00:00 2001 From: Simeon Armenchev Date: Wed, 2 Apr 2025 15:03:32 +0300 Subject: [PATCH 01/24] refactor(mcl/dub): Remove deprecated dflags for improved build configuration --- packages/mcl/dub.sdl | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/mcl/dub.sdl b/packages/mcl/dub.sdl index 011a343f..2b61bed2 100644 --- a/packages/mcl/dub.sdl +++ b/packages/mcl/dub.sdl @@ -14,8 +14,6 @@ buildType "unittest-debug" { buildOptions "unittests" "debugMode" "debugInfo" } -dflags "-preview=in" -dflags "-preview=shortenedMethods" dflags "-defaultlib=libphobos2.so" platform="dmd" lflags "-fuse-ld=gold" platform="dmd" dflags "-mcpu=generic" platform="ldc" From 80dfd5151e09fe0326237b937f3ffb84442fc4f4 Mon Sep 17 00:00:00 2001 From: Simeon Armenchev Date: Wed, 2 Apr 2025 15:09:01 +0300 Subject: [PATCH 02/24] fix(ci_matrix): Initialize params directly in ci_matrix function --- packages/mcl/src/src/mcl/commands/ci_matrix.d | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/mcl/src/src/mcl/commands/ci_matrix.d b/packages/mcl/src/src/mcl/commands/ci_matrix.d index 91d262fb..f6823877 100755 --- a/packages/mcl/src/src/mcl/commands/ci_matrix.d +++ b/packages/mcl/src/src/mcl/commands/ci_matrix.d @@ -141,16 +141,12 @@ version (unittest) ]; } -immutable Params params; +Params params; -version (unittest) {} else -shared static this() -{ - params = parseEnv!Params; -} export void ci_matrix(string[] args) { + params = parseEnv!Params; createResultDirs(); nixEvalForAllSystems().array.printTableForCacheStatus(); } From c03af2c1b6ae061469581d6fc14a282d4e8a5694 Mon Sep 17 00:00:00 2001 From: Simeon Armenchev Date: Wed, 2 Apr 2025 15:11:51 +0300 Subject: [PATCH 03/24] feat(mcl/main): Integrate argparse for command-line argument parsing and enhance command handling --- packages/mcl/dub.sdl | 1 + packages/mcl/dub.selections.json | 1 + packages/mcl/src/main.d | 105 ++++++++++++++++--------------- 3 files changed, 58 insertions(+), 49 deletions(-) diff --git a/packages/mcl/dub.sdl b/packages/mcl/dub.sdl index 2b61bed2..ea896f42 100644 --- a/packages/mcl/dub.sdl +++ b/packages/mcl/dub.sdl @@ -21,3 +21,4 @@ dflags "-mcpu=baseline" platform="dmd" dependency "mir-cpuid" version="~>1.2.11" dependency "silly" version="~>1.1.1" +dependency "argparse" version="~>1.4.1" diff --git a/packages/mcl/dub.selections.json b/packages/mcl/dub.selections.json index 2977f10a..a3f31e8e 100644 --- a/packages/mcl/dub.selections.json +++ b/packages/mcl/dub.selections.json @@ -1,6 +1,7 @@ { "fileVersion": 1, "versions": { + "argparse": "1.4.1", "mir-core": "1.7.1", "mir-cpuid": "1.2.11", "silly": "1.1.1" diff --git a/packages/mcl/src/main.d b/packages/mcl/src/main.d index 4412d7ea..cca9e30a 100644 --- a/packages/mcl/src/main.d +++ b/packages/mcl/src/main.d @@ -1,59 +1,76 @@ +module mcl.main; + import std.stdio : writefln, writeln, stderr; import std.array : replace; import std.getopt : getopt; import std.logger : infof, errorf, LogLevel; - +import std.sumtype : SumType, match; +import std.string : stripRight, stripLeft; +import std.algorithm : endsWith; +import std.format : format; import mcl.utils.path : rootDir; import mcl.utils.tui : bold; -import cmds = mcl.commands; +import mcl.commands; -alias supportedCommands = imported!`std.traits`.AliasSeq!( - cmds.get_fstab, - cmds.deploy_spec, - cmds.ci_matrix, - cmds.print_table, - cmds.shard_matrix, - cmds.host_info, - cmds.ci, - cmds.machine, - cmds.config, -); +import argparse; -int main(string[] args) + +@(Command(" ").Description(" ")) +struct unknown_command_args {} +int unknown_command(unknown_command_args unused) { - if (args.length < 2) - return wrongUsage("no command selected"); + stderr.writeln("Unknown command. Use --help for a list of available commands."); + return 1; +} - string cmd = args[1]; - LogLevel logLevel = LogLevel.info; - args.getopt("log-level", &logLevel); +template genSubCommandArgs() +{ + const char[] genSubCommandArgs = + "@SubCommands\n"~ + "SumType!("~ + "Default!unknown_command_args"~ + ") cmd;"; +} - setLogLevel(logLevel); +template genSubCommandMatch() +{ + const char[] generateMatchString = () { + alias CmdTypes = typeof(MCLArgs.cmd).Types; + string match = "int result = args.cmd.match!("; - infof("Git root: '%s'", rootDir.bold); + static foreach (CmdType; CmdTypes) + {{ + string name = CmdType.stringof.replace("Default!(", "").stripRight(")"); + match ~= format("\n\t(%s a) => %s(a)", name, name.replace("_args", "")) ~ ", "; + }} + match = match.stripRight(", "); + match ~= "\n);"; - try switch (cmd) - { - default: - return wrongUsage("unknown command: `" ~ cmd ~ "`"); + return match; + }(); +} - static foreach (command; supportedCommands) - case __traits(identifier, command): - { +struct MCLArgs +{ + @NamedArgument(["log-level"]) + LogLevel logLevel = cast(LogLevel)-1; + mixin(genSubCommandArgs!()); +} + +mixin CLI!MCLArgs.main!((args) +{ + static assert(is(typeof(args) == MCLArgs)); + + LogLevel logLevel = LogLevel.info; + if (args.logLevel != cast(LogLevel)-1) + logLevel = args.logLevel; + setLogLevel(logLevel); + + mixin genSubCommandMatch; - infof("Running %s task", cmd.bold); - command(args[2..$]); - infof("Execution Succesfull"); return 0; - } - } - catch (Exception e) - { - errorf("Task %s failed. Error:\n%s", cmd.bold, e); - return 1; - } -} +}); void setLogLevel(LogLevel l) { @@ -61,13 +78,3 @@ void setLogLevel(LogLevel l) globalLogLevel = l; (cast()sharedLog()).logLevel = l; } - -int wrongUsage(string error) -{ - writefln("Error: %s.", error); - writeln("Usage:\n"); - static foreach (cmd; supportedCommands) - writefln(" mcl %s", __traits(identifier, cmd)); - - return 1; -} From 000ee138ddd3824e8cc1bded346e6c34dafd77cf Mon Sep 17 00:00:00 2001 From: Simeon Armenchev Date: Wed, 2 Apr 2025 15:13:25 +0300 Subject: [PATCH 04/24] feat(commands/get_fstab): Enhance get_fstab command with argparse integration and structured argument handling --- packages/mcl/src/main.d | 1 + packages/mcl/src/src/mcl/commands/get_fstab.d | 45 +++++++++++-------- packages/mcl/src/src/mcl/commands/package.d | 2 +- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/packages/mcl/src/main.d b/packages/mcl/src/main.d index cca9e30a..b0e78e39 100644 --- a/packages/mcl/src/main.d +++ b/packages/mcl/src/main.d @@ -29,6 +29,7 @@ template genSubCommandArgs() const char[] genSubCommandArgs = "@SubCommands\n"~ "SumType!("~ + "get_fstab_args,"~ "Default!unknown_command_args"~ ") cmd;"; } diff --git a/packages/mcl/src/src/mcl/commands/get_fstab.d b/packages/mcl/src/src/mcl/commands/get_fstab.d index d063b7ed..feb20166 100755 --- a/packages/mcl/src/src/mcl/commands/get_fstab.d +++ b/packages/mcl/src/src/mcl/commands/get_fstab.d @@ -6,6 +6,8 @@ import std.json : JSONValue; import std.format : fmt = format; import std.exception : enforce; +import argparse; + import mcl.utils.cachix : cachixNixStoreUrl, getCachixDeploymentApiUrl; import mcl.utils.env : optional, parseEnv; import mcl.utils.fetch : fetchJson; @@ -13,40 +15,45 @@ import mcl.utils.nix : queryStorePath, nix; import mcl.utils.string : camelCaseToCapitalCase; import mcl.utils.process : execute; -export void get_fstab(string[] args) +export int get_fstab(get_fstab_args args) { - const params = parseEnv!Params; - const machineStorePath = getCachixDeploymentStorePath(params); + args.cachixStoreUrl = cachixNixStoreUrl(args.cachixCache); + if (!args.cachixDeployWorkspace) + args.cachixDeployWorkspace = args.cachixCache; + + const machineStorePath = getCachixDeploymentStorePath(args); const fstabStorePath = queryStorePath( machineStorePath, ["-etc", "-etc-fstab"], - params.cachixStoreUrl + args.cachixStoreUrl ); nix.build(fstabStorePath); writeln(fstabStorePath); + return 0; } -struct Params -{ +@(Command("get-fstab").Description("Get the store path of the fstab file for a deployment")) +export struct get_fstab_args { + @(NamedArgument(["cachix-auth-token"]).Required().Placeholder("XXX").Description("Auth Token for Cachix")) string cachixAuthToken; + @(NamedArgument(["cachix-cache"]).Required().Placeholder("cache").Description("Which Cachix cache to use")) string cachixCache; - @optional() string cachixStoreUrl; - @optional() string cachixDeployWorkspace; - string machineName; - uint deploymentId; - void setup() - { + @(NamedArgument(["cachix-store-url"]).Placeholder("https://...").Description("URL of the Cachix store")) + string cachixStoreUrl = ""; + @(NamedArgument(["cachix-deploy-workspace"]).Placeholder("agent-workspace").Description("Cachix workspace to deploy to")) + string cachixDeployWorkspace = ""; - cachixStoreUrl = cachixNixStoreUrl(cachixCache); - if (!cachixDeployWorkspace) - cachixDeployWorkspace = cachixCache; - } + @(PositionalArgument(0).Placeholder("machine-name").Description("Name of the machine")) + string machineName; + @(PositionalArgument(1).Placeholder("deployment-id").Description("ID of the deployment")) + uint deploymentId; } -string getCachixDeploymentStorePath(Params p) + +string getCachixDeploymentStorePath(get_fstab_args args) { - const url = getCachixDeploymentApiUrl(p.cachixDeployWorkspace, p.machineName, p.deploymentId); - const response = fetchJson(url, p.cachixAuthToken); + const url = getCachixDeploymentApiUrl(args.cachixDeployWorkspace, args.machineName, args.deploymentId); + const response = fetchJson(url, args.cachixAuthToken); return response["storePath"].get!string; } diff --git a/packages/mcl/src/src/mcl/commands/package.d b/packages/mcl/src/src/mcl/commands/package.d index 4ab52cce..fda9a02a 100644 --- a/packages/mcl/src/src/mcl/commands/package.d +++ b/packages/mcl/src/src/mcl/commands/package.d @@ -1,6 +1,6 @@ module mcl.commands; -public import mcl.commands.get_fstab : get_fstab; +public import mcl.commands.get_fstab : get_fstab, get_fstab_args; public import mcl.commands.deploy_spec : deploy_spec; public import mcl.commands.ci_matrix : ci_matrix, print_table; public import mcl.commands.shard_matrix : shard_matrix; From 2eff735bf05de33486f191f2db2184ede52a6767 Mon Sep 17 00:00:00 2001 From: Simeon Armenchev Date: Wed, 2 Apr 2025 15:14:31 +0300 Subject: [PATCH 05/24] feat(commands/deploy_spec): Enhance deploy_spec command with argparse integration and structured argument handling --- packages/mcl/src/main.d | 1 + packages/mcl/src/src/mcl/commands/deploy_spec.d | 11 ++++++++++- packages/mcl/src/src/mcl/commands/package.d | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/mcl/src/main.d b/packages/mcl/src/main.d index b0e78e39..554c7917 100644 --- a/packages/mcl/src/main.d +++ b/packages/mcl/src/main.d @@ -30,6 +30,7 @@ template genSubCommandArgs() "@SubCommands\n"~ "SumType!("~ "get_fstab_args,"~ + "deploy_spec_args,"~ "Default!unknown_command_args"~ ") cmd;"; } diff --git a/packages/mcl/src/src/mcl/commands/deploy_spec.d b/packages/mcl/src/src/mcl/commands/deploy_spec.d index 819fd4a2..3bacc8a8 100644 --- a/packages/mcl/src/src/mcl/commands/deploy_spec.d +++ b/packages/mcl/src/src/mcl/commands/deploy_spec.d @@ -5,6 +5,8 @@ import std.logger : infof, warningf; import std.file : exists; import std.path : buildPath; +import argparse; + import mcl.utils.process : spawnProcessInline; import mcl.utils.path : resultDir; import mcl.utils.cachix : cachixNixStoreUrl, DeploySpec, createMachineDeploySpec; @@ -13,7 +15,12 @@ import mcl.utils.json : tryDeserializeFromJsonFile, writeJsonFile; import mcl.commands.ci_matrix : flakeAttr, params, nixEvalJobs, SupportedSystem; -export void deploy_spec(string[] args) + +@(Command("deploy-spec").Description("Evaluate the Nixos machine configurations in bareMetalMachines and deploy them to cachix.")) +export struct deploy_spec_args { +} + +export int deploy_spec(deploy_spec_args args) { const deploySpecFile = resultDir.buildPath("cachix-deploy-spec.json"); @@ -56,4 +63,6 @@ export void deploy_spec(string[] args) spawnProcessInline([ "cachix", "deploy", "activate", deploySpecFile, "--async" ]); + + return 0; } diff --git a/packages/mcl/src/src/mcl/commands/package.d b/packages/mcl/src/src/mcl/commands/package.d index fda9a02a..35a7dbfa 100644 --- a/packages/mcl/src/src/mcl/commands/package.d +++ b/packages/mcl/src/src/mcl/commands/package.d @@ -1,7 +1,7 @@ module mcl.commands; public import mcl.commands.get_fstab : get_fstab, get_fstab_args; -public import mcl.commands.deploy_spec : deploy_spec; +public import mcl.commands.deploy_spec : deploy_spec, deploy_spec_args; public import mcl.commands.ci_matrix : ci_matrix, print_table; public import mcl.commands.shard_matrix : shard_matrix; public import mcl.commands.ci : ci; From 3378d0e8759aadb1b9ea7739cfbfd17695ea56fb Mon Sep 17 00:00:00 2001 From: Simeon Armenchev Date: Wed, 2 Apr 2025 15:15:21 +0300 Subject: [PATCH 06/24] feat(commands/host_info): Enhance host_info command with argparse integration and structured argument handling --- packages/mcl/src/main.d | 1 + packages/mcl/src/src/mcl/commands/deploy_spec.d | 2 +- packages/mcl/src/src/mcl/commands/host_info.d | 15 ++++++++++----- packages/mcl/src/src/mcl/commands/package.d | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/mcl/src/main.d b/packages/mcl/src/main.d index 554c7917..49c75af1 100644 --- a/packages/mcl/src/main.d +++ b/packages/mcl/src/main.d @@ -31,6 +31,7 @@ template genSubCommandArgs() "SumType!("~ "get_fstab_args,"~ "deploy_spec_args,"~ + "host_info_args,"~ "Default!unknown_command_args"~ ") cmd;"; } diff --git a/packages/mcl/src/src/mcl/commands/deploy_spec.d b/packages/mcl/src/src/mcl/commands/deploy_spec.d index 3bacc8a8..1431b814 100644 --- a/packages/mcl/src/src/mcl/commands/deploy_spec.d +++ b/packages/mcl/src/src/mcl/commands/deploy_spec.d @@ -58,7 +58,7 @@ export int deploy_spec(deploy_spec_args args) infof("%s machines will be deployed.", spec.agents.length); if (!spec.agents.length) - return; + return 1; spawnProcessInline([ "cachix", "deploy", "activate", deploySpecFile, "--async" diff --git a/packages/mcl/src/src/mcl/commands/host_info.d b/packages/mcl/src/src/mcl/commands/host_info.d index 87270e7d..b241a1cb 100644 --- a/packages/mcl/src/src/mcl/commands/host_info.d +++ b/packages/mcl/src/src/mcl/commands/host_info.d @@ -19,6 +19,8 @@ import std.format : format; import std.system : nativeEndian = endian;; import core.stdc.string : strlen; +import argparse; + import mcl.utils.env : parseEnv, optional; import mcl.utils.json : toJSON; import mcl.utils.process : execute, isRoot; @@ -30,9 +32,7 @@ import mcl.utils.coda : CodaApiClient, RowValues, CodaCell; struct Params { @optional() string codaApiToken; - void setup() - { - } + void setup() {} } string[string] cpuinfo; @@ -59,7 +59,10 @@ string[string] getProcInfo(string fileOrData, bool file = true) return r; } -export void host_info(string[] args) +@(Command("host-info").Description("Get information about the host machine")) +struct host_info_args {} + +export int host_info(host_info_args args) { const Params params = parseEnv!Params; @@ -72,13 +75,15 @@ export void host_info(string[] args) if (!params.codaApiToken) { writeln("No Coda API token specified -> not uploading"); - return; + return 1; } writeln("Coda API token specified -> uploading"); auto coda = CodaApiClient(params.codaApiToken); coda.uploadHostInfo(hostInfo); + return 1; + } Info gatherHostInfo() diff --git a/packages/mcl/src/src/mcl/commands/package.d b/packages/mcl/src/src/mcl/commands/package.d index 35a7dbfa..08996f88 100644 --- a/packages/mcl/src/src/mcl/commands/package.d +++ b/packages/mcl/src/src/mcl/commands/package.d @@ -5,6 +5,6 @@ public import mcl.commands.deploy_spec : deploy_spec, deploy_spec_args; public import mcl.commands.ci_matrix : ci_matrix, print_table; public import mcl.commands.shard_matrix : shard_matrix; public import mcl.commands.ci : ci; -public import mcl.commands.host_info : host_info; +public import mcl.commands.host_info : host_info, host_info_args; public import mcl.commands.machine : machine; public import mcl.commands.config : config; From 46131a9befc4cd02dba27eb93aedfd0f05e87d2b Mon Sep 17 00:00:00 2001 From: Simeon Armenchev Date: Wed, 2 Apr 2025 15:16:01 +0300 Subject: [PATCH 07/24] feat(commands/config): Enhance config command with argparse integration and structured argument handling --- packages/mcl/src/main.d | 1 + packages/mcl/src/src/mcl/commands/config.d | 207 +++++++++++++------- packages/mcl/src/src/mcl/commands/package.d | 2 +- 3 files changed, 134 insertions(+), 76 deletions(-) diff --git a/packages/mcl/src/main.d b/packages/mcl/src/main.d index 49c75af1..771f4929 100644 --- a/packages/mcl/src/main.d +++ b/packages/mcl/src/main.d @@ -32,6 +32,7 @@ template genSubCommandArgs() "get_fstab_args,"~ "deploy_spec_args,"~ "host_info_args,"~ + "config_args,"~ "Default!unknown_command_args"~ ") cmd;"; } diff --git a/packages/mcl/src/src/mcl/commands/config.d b/packages/mcl/src/src/mcl/commands/config.d index 4ead6f48..4623c14f 100755 --- a/packages/mcl/src/src/mcl/commands/config.d +++ b/packages/mcl/src/src/mcl/commands/config.d @@ -6,6 +6,9 @@ import std.process : ProcessPipes, Redirect, wait, environment; import std.range : drop, front; import std.stdio : writeln; import std.string : indexOf; +import std.sumtype : SumType, match; + +import argparse; import mcl.utils.env : optional, parseEnv; import mcl.utils.fetch : fetchJson; @@ -14,112 +17,166 @@ import mcl.utils.nix : nix, queryStorePath; import mcl.utils.process : execute; import mcl.utils.string : camelCaseToCapitalCase; -export void config(string[] args) { - if (args.length == 0) { - errorAndExit("Usage: mcl config [args]"); - } - if (!checkRepo()) { - errorAndExit("This command must be run from a repository containing a NixOS machine configuration"); - } +@(Command("config").Description("Manage NixOS machine configurations")) +export struct config_args +{ + @SubCommands SumType!( + sys_args, + home_args, + start_vm_args, + Default!unknown_command_args + ) cmd; +} + +@(Command("sys").Description("Manage system configurations")) +struct sys_args +{ + @SubCommands SumType!( + sys_apply_args, + sys_edit_args, + Default!unknown_command_args + ) cmd; +} + +@(Command("apply").Description("Apply system configuration")) +struct sys_apply_args +{ + @(PositionalArgument(0).Placeholder("machine-name").Description("Name of the machine")) + string machineName; +} + +@(Command("edit").Description("Edit system configuration")) +struct sys_edit_args +{ + @(PositionalArgument(0).Placeholder("machine-name").Description("Name of the machine")) + string machineName; +} + +@(Command("home").Description("Manage home configurations")) +struct home_args +{ + @SubCommands SumType!( + home_apply_args, + home_edit_args, + Default!unknown_command_args + ) cmd; +} + +@(Command("apply").Description("Apply user configuration")) +struct home_apply_args +{ + @(PositionalArgument(0).Placeholder("desktop/server").Description("Type of home configuration")) + string type; +} + +@(Command("edit").Description("Edit user configuration")) +struct home_edit_args +{ + @(PositionalArgument(0).Placeholder("desktop/server").Description("Type of home configuration")) + string type; +} + +@(Command("start-vm").Description("Start a VM")) +struct start_vm_args +{ + @(PositionalArgument(0).Optional().Placeholder("vm-name").Description("Name of the VM")) + string vmName = ""; +} + +@(Command(" ").Description(" ")) +struct unknown_command_args +{ +} + +int unknown_command(unknown_command_args unused) +{ + errorAndExit("Unknown command. Use --help for a list of available commands."); + return 1; +} - string subcommand = args.front; - - switch (subcommand) { - case "sys": - sys(args.drop(1)); - break; - case "home": - home(args.drop(1)); - break; - case "start-vm": - startVM(args.drop(1)); - break; - default: - errorAndExit("Unknown config subcommand " ~ subcommand ~ ". Supported subcommands: sys, home, start-vm"); - break; +export int config(config_args args) +{ + if (!checkRepo()) + { + errorAndExit( + "This command must be run from a repository containing a NixOS machine configuration"); } + + return args.cmd.match!( + (sys_args a) => sys(a), + (home_args a) => home(a), + (start_vm_args a) => startVM(a.vmName), + (unknown_command_args a) => unknown_command(a) + ); } bool checkRepo() { const string[] validRepos = ["nixos-machine-config", "infra-lido"]; - string remoteOriginUrl = execute(["git", "config", "--get", "remote.origin.url"], false); + string remoteOriginUrl = execute([ + "git", "config", "--get", "remote.origin.url" + ], false); - foreach (string repo; validRepos) { - if (remoteOriginUrl.indexOf(repo) != -1) { + foreach (string repo; validRepos) + { + if (remoteOriginUrl.indexOf(repo) != -1) + { return true; } } return false; } -void executeCommand(string command) { +int executeCommand(string command) +{ auto exec = execute!ProcessPipes(command, true, false, Redirect.stderrToStdout); - wait(exec.pid); + return wait(exec.pid); } -void edit(string type, string path) { +int edit(string type, string path) +{ string editor = environment.get("EDITOR", "vim"); string user = environment.get("USER", "root"); writeln("Editing " ~ path ~ " configuration from: ", path); - final switch (type) { - case "system": - executeCommand(editor ~ " machines/*/" ~ path ~ "/*.nix"); - break; - case "user": - executeCommand(editor~ " users/" ~ user ~ "/gitconfig " ~ "users/" ~ user ~ "/*.nix " ~ "users/" ~ user ~ "/home-"~path~"/*.nix"); - break; + final switch (type) + { + case "system": + return executeCommand(editor ~ " machines/*/" ~ path ~ "/*.nix"); + case "user": + return executeCommand(editor ~ " users/" ~ user ~ "/gitconfig " ~ "users/" ~ user ~ "/*.nix " ~ "users/" ~ user ~ "/home-" ~ path ~ "/*.nix"); } } -void sys(string[] args) +int apply(string type, string value) { - if ((args.length < 1 || args.length > 2) && !["apply", "edit"].canFind(args.front)) - { - errorAndExit("Usage: mcl config sys apply or mcl config sys apply \n"~ - " mcl config sys edit or mcl config sys edit "); - } + writeln("Applying ", type, " configuration from: ", value); + return executeCommand("just switch-" ~ type ~ " " ~ value); +} - string machineName = args.length > 1 ? args[1] : ""; - final switch (args.front) { - case "apply": - writeln("Applying system configuration from: ", machineName); - executeCommand("just switch-system " ~ machineName); - break; - case "edit": - edit("system", machineName); - break; - } +int sys(sys_args args) +{ + return args.cmd.match!( + (sys_apply_args a) => apply("system", a.machineName), + (sys_edit_args a) => edit("system", a.machineName), + (unknown_command_args a) => unknown_command(a) + ); } -void home(string[] args) +int home(home_args args) { - if ((args.length != 2) && args.front != "apply") - { - errorAndExit("Usage: mcl config home apply \n"~ - " mcl config home edit "); - } - auto type = args[1]; - final switch (args.front) { - case "apply": - writeln("Applying home configuration from: ", type); - executeCommand("just switch-home " ~ type); - break; - case "edit": - edit("user", type); - break; - } + return args.cmd.match!( + (home_apply_args a) { + writeln("Applying home configuration from: ", a.type); + return executeCommand("just switch-home " ~ a.type); + }, + (home_edit_args a) => "user".edit(a.type), + (unknown_command_args a) => unknown_command(a) + ); } -void startVM(string[] args) +int startVM(string vmName) { - if (args.length != 1) - { - errorAndExit("Usage: mcl config start-vm "); - } - - string vmName = args.front; writeln("Starting VM: ", vmName); - executeCommand("just start-vm " ~ vmName); + return executeCommand("just start-vm " ~ vmName); } diff --git a/packages/mcl/src/src/mcl/commands/package.d b/packages/mcl/src/src/mcl/commands/package.d index 08996f88..4b9d69ce 100644 --- a/packages/mcl/src/src/mcl/commands/package.d +++ b/packages/mcl/src/src/mcl/commands/package.d @@ -7,4 +7,4 @@ public import mcl.commands.shard_matrix : shard_matrix; public import mcl.commands.ci : ci; public import mcl.commands.host_info : host_info, host_info_args; public import mcl.commands.machine : machine; -public import mcl.commands.config : config; +public import mcl.commands.config : config, config_args; From 6463b5818a8eda42246476383e4cbdc1e84a298e Mon Sep 17 00:00:00 2001 From: Simeon Armenchev Date: Wed, 2 Apr 2025 15:16:40 +0300 Subject: [PATCH 08/24] feat(commands/machine): Enhance machine command with argparse integration and structured argument handling --- packages/mcl/src/main.d | 1 + packages/mcl/src/src/mcl/commands/machine.d | 98 ++++++++++++--------- packages/mcl/src/src/mcl/commands/package.d | 2 +- 3 files changed, 60 insertions(+), 41 deletions(-) diff --git a/packages/mcl/src/main.d b/packages/mcl/src/main.d index 771f4929..eb613d65 100644 --- a/packages/mcl/src/main.d +++ b/packages/mcl/src/main.d @@ -33,6 +33,7 @@ template genSubCommandArgs() "deploy_spec_args,"~ "host_info_args,"~ "config_args,"~ + "machine_args,"~ "Default!unknown_command_args"~ ") cmd;"; } diff --git a/packages/mcl/src/src/mcl/commands/machine.d b/packages/mcl/src/src/mcl/commands/machine.d index 008d0a86..df48ac26 100755 --- a/packages/mcl/src/src/mcl/commands/machine.d +++ b/packages/mcl/src/src/mcl/commands/machine.d @@ -1,6 +1,9 @@ module mcl.commands.machine; import std; + +import argparse; + import mcl.utils.log : prompt; import mcl.utils.process : execute; import mcl.utils.nix : nix, toNix, Literal, mkDefault; @@ -173,22 +176,22 @@ string[] getGroups() return groups; } -User createUser() { +User createUser(create_machine_args args) { - auto createUser = params.createUser || prompt!bool("Create new user"); + auto createUser = args.createUser || prompt!bool("Create new user"); if (!createUser) { string[] existingUsers = getExistingUsers(); - string userName = params.userName != "" ? params.userName : prompt!string("Select an existing username", existingUsers); + string userName = args.userName != "" ? args.userName : prompt!string("Select an existing username", existingUsers); return getUser(userName); } else { User user; - user.userName = params.userName != "" ? params.userName : prompt!string("Enter the new username"); - user.userInfo.description = params.description != "" ? params.description : prompt!string("Enter the user's description/full name"); - user.userInfo.isNormalUser = params.isNormalUser || prompt!bool("Is this a normal or root user"); - user.userInfo.extraGroups = (params.extraGroups != "" ? params.extraGroups : prompt!string("Enter the user's extra groups (comma delimited)", getGroups())).split(",").map!(strip).array; + user.userName = args.userName != "" ? args.userName : prompt!string("Enter the new username"); + user.userInfo.description = args.description != "" ? args.description : prompt!string("Enter the user's description/full name"); + user.userInfo.isNormalUser = args.isNormalUser || prompt!bool("Is this a normal or root user"); + user.userInfo.extraGroups = (args.extraGroups != "" ? args.extraGroups : prompt!string("Enter the user's extra groups (comma delimited)", getGroups())).split(",").map!(strip).array; createUserDir(user); return user; } @@ -220,8 +223,8 @@ struct MachineConfiguration MachineUserInfo users; } -void createMachine(MachineType machineType, string machineName, User user) { - auto infoJSON = execute(["ssh", params.sshPath, "sudo nix --experimental-features \\'nix-command flakes\\' --refresh --accept-flake-config run github:metacraft-labs/nixos-modules/#mcl host_info"],false, false); +void createMachine(create_machine_args args, MachineType machineType, string machineName, User user) { + auto infoJSON = execute(["ssh", args.sshPath, "sudo nix --experimental-features \\'nix-command flakes\\' --refresh --accept-flake-config run github:metacraft-labs/nixos-modules/#mcl host_info"],false, false); auto infoJSONParsed = infoJSON.parseJSON; Info info = infoJSONParsed.fromJSON!Info; @@ -273,7 +276,7 @@ void createMachine(MachineType machineType, string machineName, User user) { // Disks hardwareConfiguration.disko.DISKO.makeZfsPartitions.swapSizeGB = (info.hardwareInfo.memoryInfo.totalGB.to!double*1.5).to!int; auto nvmeDevices = info.hardwareInfo.storageInfo.devices.filter!(a => a.dev.indexOf("nvme") != -1 || a.model.indexOf("SSD") != -1).array.map!(a => a.model.replace(" ", "_") ~ "_" ~ a.serial).array; - string[] disks = (nvmeDevices.length == 1 ? nvmeDevices[0] : (params.disks != "" ? params.disks : prompt!string("Enter the disks to use (comma delimited)", nvmeDevices))).split(",").map!(strip).array.map!(a => "/dev/disk/by-id/nvme-" ~ a).array; + string[] disks = (nvmeDevices.length == 1 ? nvmeDevices[0] : (args.disks != "" ? args.disks : prompt!string("Enter the disks to use (comma delimited)", nvmeDevices))).split(",").map!(strip).array.map!(a => "/dev/disk/by-id/nvme-" ~ a).array; hardwareConfiguration.disko.DISKO.makeZfsPartitions.disks = disks; hardwareConfiguration = hardwareConfiguration.uniqArrays; @@ -365,43 +368,58 @@ struct HardwareConfiguration { Services services; } -void createMachineConfiguration() +int createMachineConfiguration(create_machine_args args) { checkifNixosMachineConfigRepo(); - auto machineType = cast(int)params.machineType != 0 ? params.machineType : prompt!MachineType("Machine type"); - auto machineName = params.machineName != "" ? params.machineName : prompt!string("Enter the name of the machine"); + auto machineType = cast(int)args.machineType != 0 ? args.machineType : prompt!MachineType("Machine type"); + auto machineName = args.machineName != "" ? args.machineName : prompt!string("Enter the name of the machine"); User user; - user = createUser(); - machineType.createMachine( machineName, user); + user = createUser(args); + args.createMachine(machineType, machineName, user); + return 0; } -Params params; -export void machine(string[] args) +export int machine(machine_args args) { - params = parseEnv!Params; - switch (args.front) - { - case "create": - createMachineConfiguration(); - break; - default: - assert(0, "Unknown machine action: " ~ args.front); - } + return args.cmd.match!( + (create_machine_args a) => createMachineConfiguration(a), + (unknown_command_args a) => unknown_command(a) + ); } -struct Params -{ +@(Command("create").Description("Create a new machine")) +struct create_machine_args { + + @(PositionalArgument(0).Placeholder("ssh").Description("SSH path to the machine")) string sshPath; - @optional() bool createUser; - @optional() string userName; - @optional() string machineName; - @optional() string description; - @optional() bool isNormalUser; - @optional() string extraGroups; - @optional() MachineType machineType = cast(MachineType)0; - @optional() string disks; - - void setup() - { - } + @(NamedArgument(["create-user"]).Placeholder("true/false").Description("Create a new user")) + bool createUser; + @(NamedArgument(["user-name"]).Placeholder("username").Description("Username")) + string userName; + @(NamedArgument(["machine-name"]).Placeholder("machine-name").Description("Name of the machine")) + string machineName; + @(NamedArgument(["description"]).Placeholder("description").Description("Description of the user")) + string description; + @(NamedArgument(["is-normal-user"]).Placeholder("true/false").Description("Is this a normal user")) + bool isNormalUser; + @(NamedArgument(["extra-groups"]).Placeholder("group1,group2").Description("Extra groups for the user")) + string extraGroups; + @(NamedArgument(["machine-type"]).Placeholder("desktop/server/container").Description("Type of machine")) + MachineType machineType = cast(MachineType)0; + @(NamedArgument(["disks"]).Placeholder("CT2000P3PSSD8_2402E88C1519,...").Description("Disks to use")) + string disks; +} +@(Command(" ").Description(" ")) +struct unknown_command_args {} +int unknown_command(unknown_command_args unused) +{ + stderr.writeln("Unknown machine command. Use --help for a list of available commands."); + return 1; +} + +@(Command("machine").Description("Manage machines")) +struct machine_args +{ + + @SubCommands SumType!(create_machine_args,Default!unknown_command_args) cmd; } diff --git a/packages/mcl/src/src/mcl/commands/package.d b/packages/mcl/src/src/mcl/commands/package.d index 4b9d69ce..6f5d4a74 100644 --- a/packages/mcl/src/src/mcl/commands/package.d +++ b/packages/mcl/src/src/mcl/commands/package.d @@ -6,5 +6,5 @@ public import mcl.commands.ci_matrix : ci_matrix, print_table; public import mcl.commands.shard_matrix : shard_matrix; public import mcl.commands.ci : ci; public import mcl.commands.host_info : host_info, host_info_args; -public import mcl.commands.machine : machine; +public import mcl.commands.machine : machine, machine_args; public import mcl.commands.config : config, config_args; From 02da4ea61928475e6599d164221b5f32945de515 Mon Sep 17 00:00:00 2001 From: Simeon Armenchev Date: Wed, 2 Apr 2025 15:17:19 +0300 Subject: [PATCH 09/24] feat(commands): Enhance ci/matrix commands with argparse integration and structured argument handling where applicable --- packages/mcl/src/main.d | 6 ++++- packages/mcl/src/src/mcl/commands/ci.d | 7 +++++ packages/mcl/src/src/mcl/commands/ci_matrix.d | 19 +++++++++++--- packages/mcl/src/src/mcl/commands/package.d | 6 ++--- .../mcl/src/src/mcl/commands/shard_matrix.d | 26 +++++++++---------- 5 files changed, 44 insertions(+), 20 deletions(-) diff --git a/packages/mcl/src/main.d b/packages/mcl/src/main.d index eb613d65..bb93e89c 100644 --- a/packages/mcl/src/main.d +++ b/packages/mcl/src/main.d @@ -34,6 +34,10 @@ template genSubCommandArgs() "host_info_args,"~ "config_args,"~ "machine_args,"~ + "ci_matrix_args,"~ + "ci_args,"~ + "print_table_args,"~ + "shard_matrix_args,"~ "Default!unknown_command_args"~ ") cmd;"; } @@ -74,7 +78,7 @@ mixin CLI!MCLArgs.main!((args) mixin genSubCommandMatch; - return 0; + return 0; }); void setLogLevel(LogLevel l) diff --git a/packages/mcl/src/src/mcl/commands/ci.d b/packages/mcl/src/src/mcl/commands/ci.d index 72383894..ec6171c6 100644 --- a/packages/mcl/src/src/mcl/commands/ci.d +++ b/packages/mcl/src/src/mcl/commands/ci.d @@ -8,6 +8,8 @@ import std.array : array, join; import std.conv : to; import std.process : ProcessPipes; +import argparse; + import mcl.utils.env : optional, parseEnv; import mcl.commands.ci_matrix: nixEvalJobs, SupportedSystem, Params, flakeAttr; import mcl.commands.shard_matrix: generateShardMatrix; @@ -18,6 +20,11 @@ import mcl.utils.json : toJSON; Params params; +@(Command("ci").Description("Run CI")) +struct ci_args +{ +} + export void ci(string[] args) { params = parseEnv!Params; diff --git a/packages/mcl/src/src/mcl/commands/ci_matrix.d b/packages/mcl/src/src/mcl/commands/ci_matrix.d index f6823877..5ef6b3d8 100755 --- a/packages/mcl/src/src/mcl/commands/ci_matrix.d +++ b/packages/mcl/src/src/mcl/commands/ci_matrix.d @@ -16,6 +16,8 @@ import std.exception : enforce; import std.format : fmt = format; import std.logger : tracef, infof, errorf, warningf; +import argparse; + import mcl.utils.env : optional, MissingEnvVarsException, parseEnv; import mcl.utils.string : enumToString, StringRepresentation, MaxWidth, writeRecordAsTable; import mcl.utils.json : toJSON; @@ -143,8 +145,11 @@ version (unittest) Params params; - -export void ci_matrix(string[] args) +@(Command("ci-matrix").Description("Print a table of the cache status of each package")) +struct ci_matrix_args +{ +} +export void ci_matrix(ci_matrix_args args) { params = parseEnv!Params; createResultDirs(); @@ -182,13 +187,20 @@ Package[] checkCacheStatus(Package[] packages) return packages; } -export void print_table(string[] args) +@(Command("print-table").Description("Print a table of the cache status of each package")) +struct print_table_args +{ +} + +export int print_table(print_table_args args) { createResultDirs(); getPrecalcMatrix() .checkCacheStatus() .printTableForCacheStatus(); + + return 0; } struct Params @@ -199,6 +211,7 @@ struct Params @optional() int maxWorkers; @optional() int maxMemory; @optional() bool isInitial; + string cachixCache; string cachixAuthToken; diff --git a/packages/mcl/src/src/mcl/commands/package.d b/packages/mcl/src/src/mcl/commands/package.d index 6f5d4a74..0deef952 100644 --- a/packages/mcl/src/src/mcl/commands/package.d +++ b/packages/mcl/src/src/mcl/commands/package.d @@ -2,9 +2,9 @@ module mcl.commands; public import mcl.commands.get_fstab : get_fstab, get_fstab_args; public import mcl.commands.deploy_spec : deploy_spec, deploy_spec_args; -public import mcl.commands.ci_matrix : ci_matrix, print_table; -public import mcl.commands.shard_matrix : shard_matrix; -public import mcl.commands.ci : ci; +public import mcl.commands.ci_matrix : ci_matrix,ci_matrix_args, print_table,print_table_args; +public import mcl.commands.shard_matrix : shard_matrix, shard_matrix_args; +public import mcl.commands.ci : ci, ci_args; public import mcl.commands.host_info : host_info, host_info_args; public import mcl.commands.machine : machine, machine_args; public import mcl.commands.config : config, config_args; diff --git a/packages/mcl/src/src/mcl/commands/shard_matrix.d b/packages/mcl/src/src/mcl/commands/shard_matrix.d index b4307844..691ad45e 100644 --- a/packages/mcl/src/src/mcl/commands/shard_matrix.d +++ b/packages/mcl/src/src/mcl/commands/shard_matrix.d @@ -13,26 +13,26 @@ import std.regex : matchFirst, regex; import std.stdio : writeln; import std.string : strip; +import argparse; + import mcl.utils.env : parseEnv, optional; import mcl.utils.json : toJSON; import mcl.utils.nix : nix; import mcl.utils.path : createResultDirs, resultDir, rootDir; -export void shard_matrix(string[] args) +@(Command("shard-matrix").Description("Generate a shard matrix for a flake")) +struct shard_matrix_args { - const params = parseEnv!Params; - auto matrix = generateShardMatrix(); - saveShardMatrix(matrix, params); - + @(NamedArgument(["github-output"]).Placeholder("output").Description("Output to GitHub Actions")) + string githubOutput; } -struct Params +export int shard_matrix(shard_matrix_args args) { - @optional() string githubOutput; + auto matrix = generateShardMatrix(); + saveShardMatrix(matrix, args); + return 0; - void setup() - { - } } struct Shard @@ -138,15 +138,15 @@ unittest } -void saveShardMatrix(ShardMatrix matrix, Params params) +void saveShardMatrix(ShardMatrix matrix, shard_matrix_args args) { const matrixJson = matrix.toJSON(); const matrixString = matrixJson.toString(); infof("Shard matrix: %s", matrixJson.toPrettyString); const envLine = "gen_matrix=" ~ matrixString; - if (params.githubOutput != "") + if (args.githubOutput != "") { - params.githubOutput.append(envLine); + args.githubOutput.append(envLine); } else { From 96e0b8d843feb5b433cc730693d52173f766e370 Mon Sep 17 00:00:00 2001 From: Simeon Armenchev Date: Thu, 10 Apr 2025 18:14:32 +0300 Subject: [PATCH 10/24] fix(actions): Correct command names in GitHub workflows and action configuration --- .github/print-matrix/action.yml | 2 +- .github/workflows/reusable-flake-checks-ci-matrix.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/print-matrix/action.yml b/.github/print-matrix/action.yml index 3fb0b2d8..e9993400 100644 --- a/.github/print-matrix/action.yml +++ b/.github/print-matrix/action.yml @@ -69,7 +69,7 @@ runs: CACHIX_AUTH_TOKEN: ${{ inputs.cachix-auth-token }} PRECALC_MATRIX: ${{ inputs.precalc_matrix }} MCL_BRANCH: ${{ github.repository == 'metacraft-labs/nixos-modules' && github.sha || 'main' }} - run: nix run --accept-flake-config github:metacraft-labs/nixos-modules/${{ env.MCL_BRANCH }}#mcl print_table + run: nix run --accept-flake-config github:metacraft-labs/nixos-modules/${{ env.MCL_BRANCH }}#mcl print-table - name: Update GitHub Comment uses: marocchino/sticky-pull-request-comment@v2.9.0 diff --git a/.github/workflows/reusable-flake-checks-ci-matrix.yml b/.github/workflows/reusable-flake-checks-ci-matrix.yml index c75c61cb..a89abd78 100644 --- a/.github/workflows/reusable-flake-checks-ci-matrix.yml +++ b/.github/workflows/reusable-flake-checks-ci-matrix.yml @@ -65,7 +65,7 @@ jobs: CACHIX_CACHE: ${{ vars.CACHIX_CACHE }} CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} MCL_BRANCH: ${{ github.repository == 'metacraft-labs/nixos-modules' && github.sha || 'main' }} - run: nix run --accept-flake-config github:metacraft-labs/nixos-modules/${{ env.MCL_BRANCH }}#mcl shard_matrix + run: nix run --accept-flake-config github:metacraft-labs/nixos-modules/${{ env.MCL_BRANCH }}#mcl shard-matrix outputs: gen_matrix: ${{ steps.generate-matrix.outputs.gen_matrix }} @@ -100,7 +100,7 @@ jobs: FLAKE_PRE: ${{ matrix.prefix }} FLAKE_POST: ${{ matrix.postfix }} MCL_BRANCH: ${{ github.repository == 'metacraft-labs/nixos-modules' && github.sha || 'main' }} - run: nix run --accept-flake-config github:metacraft-labs/nixos-modules/${{ env.MCL_BRANCH }}#mcl ci_matrix + run: nix run --accept-flake-config github:metacraft-labs/nixos-modules/${{ env.MCL_BRANCH }}#mcl ci-matrix - uses: actions/upload-artifact@v5 with: @@ -259,4 +259,4 @@ jobs: CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} CACHIX_ACTIVATE_TOKEN: '${{ secrets.CACHIX_ACTIVATE_TOKEN }}' MCL_BRANCH: ${{ github.repository == 'metacraft-labs/nixos-modules' && github.sha || 'main' }} - run: nix run --accept-flake-config github:metacraft-labs/nixos-modules/${{ env.MCL_BRANCH }}#mcl deploy_spec + run: nix run --accept-flake-config github:metacraft-labs/nixos-modules/${{ env.MCL_BRANCH }}#mcl deploy-spec From 5c6224ad90488f7c656e404f1dee67922af8d425 Mon Sep 17 00:00:00 2001 From: Simeon Armenchev Date: Thu, 10 Apr 2025 18:17:46 +0300 Subject: [PATCH 11/24] fix(commands/machine): Correct command name in createMachine function for host info retrieval --- packages/mcl/src/src/mcl/commands/machine.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mcl/src/src/mcl/commands/machine.d b/packages/mcl/src/src/mcl/commands/machine.d index df48ac26..65a0d228 100755 --- a/packages/mcl/src/src/mcl/commands/machine.d +++ b/packages/mcl/src/src/mcl/commands/machine.d @@ -224,7 +224,7 @@ struct MachineConfiguration } void createMachine(create_machine_args args, MachineType machineType, string machineName, User user) { - auto infoJSON = execute(["ssh", args.sshPath, "sudo nix --experimental-features \\'nix-command flakes\\' --refresh --accept-flake-config run github:metacraft-labs/nixos-modules/#mcl host_info"],false, false); + auto infoJSON = execute(["ssh", args.sshPath, "sudo nix --experimental-features \\'nix-command flakes\\' --refresh --accept-flake-config run github:metacraft-labs/nixos-modules/#mcl host-info"],false, false); auto infoJSONParsed = infoJSON.parseJSON; Info info = infoJSONParsed.fromJSON!Info; From e413b22223056a00b6bfe2bf89df3a9a90905771 Mon Sep 17 00:00:00 2001 From: Simeon Armenchev Date: Fri, 11 Apr 2025 12:27:06 +0300 Subject: [PATCH 12/24] feat(mcl): Add command aliases for backwards compatibility --- packages/mcl/src/src/mcl/commands/ci_matrix.d | 4 ++-- packages/mcl/src/src/mcl/commands/deploy_spec.d | 2 +- packages/mcl/src/src/mcl/commands/get_fstab.d | 2 +- packages/mcl/src/src/mcl/commands/host_info.d | 2 +- packages/mcl/src/src/mcl/commands/shard_matrix.d | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/mcl/src/src/mcl/commands/ci_matrix.d b/packages/mcl/src/src/mcl/commands/ci_matrix.d index 5ef6b3d8..247d0ea1 100755 --- a/packages/mcl/src/src/mcl/commands/ci_matrix.d +++ b/packages/mcl/src/src/mcl/commands/ci_matrix.d @@ -145,7 +145,7 @@ version (unittest) Params params; -@(Command("ci-matrix").Description("Print a table of the cache status of each package")) +@(Command("ci-matrix", "ci_matrix").Description("Print a table of the cache status of each package")) struct ci_matrix_args { } @@ -187,7 +187,7 @@ Package[] checkCacheStatus(Package[] packages) return packages; } -@(Command("print-table").Description("Print a table of the cache status of each package")) +@(Command("print-table", "print_table").Description("Print a table of the cache status of each package")) struct print_table_args { } diff --git a/packages/mcl/src/src/mcl/commands/deploy_spec.d b/packages/mcl/src/src/mcl/commands/deploy_spec.d index 1431b814..593c7d00 100644 --- a/packages/mcl/src/src/mcl/commands/deploy_spec.d +++ b/packages/mcl/src/src/mcl/commands/deploy_spec.d @@ -16,7 +16,7 @@ import mcl.utils.json : tryDeserializeFromJsonFile, writeJsonFile; import mcl.commands.ci_matrix : flakeAttr, params, nixEvalJobs, SupportedSystem; -@(Command("deploy-spec").Description("Evaluate the Nixos machine configurations in bareMetalMachines and deploy them to cachix.")) +@(Command("deploy-spec", "deploy_spec").Description("Evaluate the Nixos machine configurations in bareMetalMachines and deploy them to cachix.")) export struct deploy_spec_args { } diff --git a/packages/mcl/src/src/mcl/commands/get_fstab.d b/packages/mcl/src/src/mcl/commands/get_fstab.d index feb20166..72bbb579 100755 --- a/packages/mcl/src/src/mcl/commands/get_fstab.d +++ b/packages/mcl/src/src/mcl/commands/get_fstab.d @@ -32,7 +32,7 @@ export int get_fstab(get_fstab_args args) return 0; } -@(Command("get-fstab").Description("Get the store path of the fstab file for a deployment")) +@(Command("get-fstab", "get_fstab").Description("Get the store path of the fstab file for a deployment")) export struct get_fstab_args { @(NamedArgument(["cachix-auth-token"]).Required().Placeholder("XXX").Description("Auth Token for Cachix")) string cachixAuthToken; diff --git a/packages/mcl/src/src/mcl/commands/host_info.d b/packages/mcl/src/src/mcl/commands/host_info.d index b241a1cb..28c766ea 100644 --- a/packages/mcl/src/src/mcl/commands/host_info.d +++ b/packages/mcl/src/src/mcl/commands/host_info.d @@ -59,7 +59,7 @@ string[string] getProcInfo(string fileOrData, bool file = true) return r; } -@(Command("host-info").Description("Get information about the host machine")) +@(Command("host-info", "host_info").Description("Get information about the host machine")) struct host_info_args {} export int host_info(host_info_args args) diff --git a/packages/mcl/src/src/mcl/commands/shard_matrix.d b/packages/mcl/src/src/mcl/commands/shard_matrix.d index 691ad45e..3239e483 100644 --- a/packages/mcl/src/src/mcl/commands/shard_matrix.d +++ b/packages/mcl/src/src/mcl/commands/shard_matrix.d @@ -20,7 +20,7 @@ import mcl.utils.json : toJSON; import mcl.utils.nix : nix; import mcl.utils.path : createResultDirs, resultDir, rootDir; -@(Command("shard-matrix").Description("Generate a shard matrix for a flake")) +@(Command("shard-matrix", "shard_matrix").Description("Generate a shard matrix for a flake")) struct shard_matrix_args { @(NamedArgument(["github-output"]).Placeholder("output").Description("Output to GitHub Actions")) From 30e153eee1b8725ed68859322b5a16ba3f82a8d5 Mon Sep 17 00:00:00 2001 From: Petar Kirov Date: Fri, 14 Nov 2025 17:20:30 +0200 Subject: [PATCH 13/24] fix(mcl.commands): Fix function signature of `ci` and `ci_matrix` commands --- packages/mcl/src/src/mcl/commands/ci.d | 3 ++- packages/mcl/src/src/mcl/commands/ci_matrix.d | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/mcl/src/src/mcl/commands/ci.d b/packages/mcl/src/src/mcl/commands/ci.d index ec6171c6..1c268d17 100644 --- a/packages/mcl/src/src/mcl/commands/ci.d +++ b/packages/mcl/src/src/mcl/commands/ci.d @@ -25,7 +25,7 @@ struct ci_args { } -export void ci(string[] args) +export int ci(ci_args args) { params = parseEnv!Params; @@ -76,4 +76,5 @@ export void ci(string[] args) } } + return 0; } diff --git a/packages/mcl/src/src/mcl/commands/ci_matrix.d b/packages/mcl/src/src/mcl/commands/ci_matrix.d index 247d0ea1..94b7e427 100755 --- a/packages/mcl/src/src/mcl/commands/ci_matrix.d +++ b/packages/mcl/src/src/mcl/commands/ci_matrix.d @@ -149,11 +149,12 @@ Params params; struct ci_matrix_args { } -export void ci_matrix(ci_matrix_args args) +export int ci_matrix(ci_matrix_args args) { params = parseEnv!Params; createResultDirs(); nixEvalForAllSystems().array.printTableForCacheStatus(); + return 0; } string flakeAttr(string prefix, SupportedSystem system, string postfix) From ae0ac08ff8a044c9dc22d0eb1e42dc0cdfd2d432 Mon Sep 17 00:00:00 2001 From: Petar Kirov Date: Fri, 14 Nov 2025 17:23:07 +0200 Subject: [PATCH 14/24] refactor(mcl.commands): Improve meta-programming to automatically define top-level commands --- packages/mcl/src/main.d | 56 ++++++--------------- packages/mcl/src/src/mcl/commands/package.d | 32 +++++++++--- 2 files changed, 38 insertions(+), 50 deletions(-) diff --git a/packages/mcl/src/main.d b/packages/mcl/src/main.d index bb93e89c..0e9abf36 100644 --- a/packages/mcl/src/main.d +++ b/packages/mcl/src/main.d @@ -8,14 +8,15 @@ import std.sumtype : SumType, match; import std.string : stripRight, stripLeft; import std.algorithm : endsWith; import std.format : format; +import std.meta : staticMap; +import std.traits : Parameters; import mcl.utils.path : rootDir; import mcl.utils.tui : bold; -import mcl.commands; +import mcl.commands : SubCommandFunctions; import argparse; - @(Command(" ").Description(" ")) struct unknown_command_args {} int unknown_command(unknown_command_args unused) @@ -24,59 +25,30 @@ int unknown_command(unknown_command_args unused) return 1; } -template genSubCommandArgs() -{ - const char[] genSubCommandArgs = - "@SubCommands\n"~ - "SumType!("~ - "get_fstab_args,"~ - "deploy_spec_args,"~ - "host_info_args,"~ - "config_args,"~ - "machine_args,"~ - "ci_matrix_args,"~ - "ci_args,"~ - "print_table_args,"~ - "shard_matrix_args,"~ - "Default!unknown_command_args"~ - ") cmd;"; -} - -template genSubCommandMatch() -{ - const char[] generateMatchString = () { - alias CmdTypes = typeof(MCLArgs.cmd).Types; - string match = "int result = args.cmd.match!("; - - static foreach (CmdType; CmdTypes) - {{ - string name = CmdType.stringof.replace("Default!(", "").stripRight(")"); - match ~= format("\n\t(%s a) => %s(a)", name, name.replace("_args", "")) ~ ", "; - }} - match = match.stripRight(", "); - match ~= "\n);"; - - return match; - }(); -} - struct MCLArgs { @NamedArgument(["log-level"]) LogLevel logLevel = cast(LogLevel)-1; - mixin(genSubCommandArgs!()); + + @SubCommands + SumType!( + staticMap!(Parameters, SubCommandFunctions), + Default!unknown_command_args + ) cmd; } +alias SumTypeCase(alias func) = (Parameters!func args) => func(args); + mixin CLI!MCLArgs.main!((args) { - static assert(is(typeof(args) == MCLArgs)); - LogLevel logLevel = LogLevel.info; if (args.logLevel != cast(LogLevel)-1) logLevel = args.logLevel; setLogLevel(logLevel); - mixin genSubCommandMatch; + int result = args.cmd.match!( + staticMap!(SumTypeCase, unknown_command, SubCommandFunctions), + ); return 0; }); diff --git a/packages/mcl/src/src/mcl/commands/package.d b/packages/mcl/src/src/mcl/commands/package.d index 0deef952..0fab51ce 100644 --- a/packages/mcl/src/src/mcl/commands/package.d +++ b/packages/mcl/src/src/mcl/commands/package.d @@ -1,10 +1,26 @@ module mcl.commands; -public import mcl.commands.get_fstab : get_fstab, get_fstab_args; -public import mcl.commands.deploy_spec : deploy_spec, deploy_spec_args; -public import mcl.commands.ci_matrix : ci_matrix,ci_matrix_args, print_table,print_table_args; -public import mcl.commands.shard_matrix : shard_matrix, shard_matrix_args; -public import mcl.commands.ci : ci, ci_args; -public import mcl.commands.host_info : host_info, host_info_args; -public import mcl.commands.machine : machine, machine_args; -public import mcl.commands.config : config, config_args; +import std.meta : ApplyLeft, staticMap; + +private enum commandModulesToExport = +[ + "mcl.commands.deploy_spec" : ["deploy_spec"], + "mcl.commands.ci_matrix" : ["ci_matrix", "print_table"], + "mcl.commands.shard_matrix" : ["shard_matrix"], + "mcl.commands.ci" : ["ci"], + "mcl.commands.host_info" : ["host_info"], + "mcl.commands.machine" : ["machine"], + "mcl.commands.config" : ["config"], +]; + +template ImportAll(alias aa) +{ + import std.meta : AliasSeq; + alias A = AliasSeq!(); + static foreach (moduleName, symbols; aa) + static foreach (symbolName; symbols) + A = AliasSeq!(A, __traits(getMember, imported!moduleName, symbolName)); + alias ImportAll = A; +} + +alias SubCommandFunctions = ImportAll!commandModulesToExport; From df6e63be6e3fac7e8118fbcf4744eb3a4a1fbd21 Mon Sep 17 00:00:00 2001 From: Petar Kirov Date: Fri, 14 Nov 2025 17:23:53 +0200 Subject: [PATCH 15/24] fix(mcl.commands.config): Update nixos config repo names --- packages/mcl/src/src/mcl/commands/config.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mcl/src/src/mcl/commands/config.d b/packages/mcl/src/src/mcl/commands/config.d index 4623c14f..d5c1084e 100755 --- a/packages/mcl/src/src/mcl/commands/config.d +++ b/packages/mcl/src/src/mcl/commands/config.d @@ -112,7 +112,7 @@ export int config(config_args args) bool checkRepo() { - const string[] validRepos = ["nixos-machine-config", "infra-lido"]; + const string[] validRepos = ["infra"]; string remoteOriginUrl = execute([ "git", "config", "--get", "remote.origin.url" ], false); From 80f00e0250b932ec76d28a5483224ae4c542bb5c Mon Sep 17 00:00:00 2001 From: reo101 Date: Tue, 18 Nov 2025 18:26:36 +0200 Subject: [PATCH 16/24] feat(mcl): `PascalCase` all command arg structs --- packages/mcl/src/main.d | 6 +- packages/mcl/src/src/mcl/commands/ci.d | 4 +- packages/mcl/src/src/mcl/commands/ci_matrix.d | 9 +-- packages/mcl/src/src/mcl/commands/config.d | 66 +++++++++---------- .../mcl/src/src/mcl/commands/deploy_spec.d | 4 +- packages/mcl/src/src/mcl/commands/get_fstab.d | 7 +- packages/mcl/src/src/mcl/commands/host_info.d | 4 +- packages/mcl/src/src/mcl/commands/machine.d | 28 ++++---- .../mcl/src/src/mcl/commands/shard_matrix.d | 6 +- 9 files changed, 68 insertions(+), 66 deletions(-) diff --git a/packages/mcl/src/main.d b/packages/mcl/src/main.d index 0e9abf36..7dc48ec5 100644 --- a/packages/mcl/src/main.d +++ b/packages/mcl/src/main.d @@ -18,8 +18,8 @@ import mcl.commands : SubCommandFunctions; import argparse; @(Command(" ").Description(" ")) -struct unknown_command_args {} -int unknown_command(unknown_command_args unused) +struct UnknownCommandArgs {} +int unknown_command(UnknownCommandArgs unused) { stderr.writeln("Unknown command. Use --help for a list of available commands."); return 1; @@ -33,7 +33,7 @@ struct MCLArgs @SubCommands SumType!( staticMap!(Parameters, SubCommandFunctions), - Default!unknown_command_args + Default!UnknownCommandArgs ) cmd; } diff --git a/packages/mcl/src/src/mcl/commands/ci.d b/packages/mcl/src/src/mcl/commands/ci.d index 1c268d17..d459308f 100644 --- a/packages/mcl/src/src/mcl/commands/ci.d +++ b/packages/mcl/src/src/mcl/commands/ci.d @@ -21,11 +21,11 @@ import mcl.utils.json : toJSON; Params params; @(Command("ci").Description("Run CI")) -struct ci_args +struct CiArgs { } -export int ci(ci_args args) +export int ci(CiArgs args) { params = parseEnv!Params; diff --git a/packages/mcl/src/src/mcl/commands/ci_matrix.d b/packages/mcl/src/src/mcl/commands/ci_matrix.d index 94b7e427..ea6ccce9 100755 --- a/packages/mcl/src/src/mcl/commands/ci_matrix.d +++ b/packages/mcl/src/src/mcl/commands/ci_matrix.d @@ -146,10 +146,11 @@ version (unittest) Params params; @(Command("ci-matrix", "ci_matrix").Description("Print a table of the cache status of each package")) -struct ci_matrix_args +struct CiMatrixArgs { } -export int ci_matrix(ci_matrix_args args) + +export int ci_matrix(CiMatrixArgs args) { params = parseEnv!Params; createResultDirs(); @@ -189,11 +190,11 @@ Package[] checkCacheStatus(Package[] packages) } @(Command("print-table", "print_table").Description("Print a table of the cache status of each package")) -struct print_table_args +struct PrintTableArgs { } -export int print_table(print_table_args args) +export int print_table(PrintTableArgs args) { createResultDirs(); diff --git a/packages/mcl/src/src/mcl/commands/config.d b/packages/mcl/src/src/mcl/commands/config.d index d5c1084e..cef00d19 100755 --- a/packages/mcl/src/src/mcl/commands/config.d +++ b/packages/mcl/src/src/mcl/commands/config.d @@ -18,83 +18,83 @@ import mcl.utils.process : execute; import mcl.utils.string : camelCaseToCapitalCase; @(Command("config").Description("Manage NixOS machine configurations")) -export struct config_args +export struct ConfigArgs { @SubCommands SumType!( - sys_args, - home_args, - start_vm_args, - Default!unknown_command_args + SysArgs, + HomeArgs, + StartVmArgs, + Default!UnknownCommandArgs ) cmd; } @(Command("sys").Description("Manage system configurations")) -struct sys_args +struct SysArgs { @SubCommands SumType!( - sys_apply_args, - sys_edit_args, - Default!unknown_command_args + SysApplyArgs, + SysEditArgs, + Default!UnknownCommandArgs ) cmd; } @(Command("apply").Description("Apply system configuration")) -struct sys_apply_args +struct SysApplyArgs { @(PositionalArgument(0).Placeholder("machine-name").Description("Name of the machine")) string machineName; } @(Command("edit").Description("Edit system configuration")) -struct sys_edit_args +struct SysEditArgs { @(PositionalArgument(0).Placeholder("machine-name").Description("Name of the machine")) string machineName; } @(Command("home").Description("Manage home configurations")) -struct home_args +struct HomeArgs { @SubCommands SumType!( - home_apply_args, - home_edit_args, - Default!unknown_command_args + HomeApplyArgs, + HomeEditArgs, + Default!UnknownCommandArgs ) cmd; } @(Command("apply").Description("Apply user configuration")) -struct home_apply_args +struct HomeApplyArgs { @(PositionalArgument(0).Placeholder("desktop/server").Description("Type of home configuration")) string type; } @(Command("edit").Description("Edit user configuration")) -struct home_edit_args +struct HomeEditArgs { @(PositionalArgument(0).Placeholder("desktop/server").Description("Type of home configuration")) string type; } @(Command("start-vm").Description("Start a VM")) -struct start_vm_args +struct StartVmArgs { @(PositionalArgument(0).Optional().Placeholder("vm-name").Description("Name of the VM")) string vmName = ""; } @(Command(" ").Description(" ")) -struct unknown_command_args +struct UnknownCommandArgs { } -int unknown_command(unknown_command_args unused) +int unknown_command(UnknownCommandArgs unused) { errorAndExit("Unknown command. Use --help for a list of available commands."); return 1; } -export int config(config_args args) +export int config(ConfigArgs args) { if (!checkRepo()) { @@ -103,10 +103,10 @@ export int config(config_args args) } return args.cmd.match!( - (sys_args a) => sys(a), - (home_args a) => home(a), - (start_vm_args a) => startVM(a.vmName), - (unknown_command_args a) => unknown_command(a) + (SysArgs a) => sys(a), + (HomeArgs a) => home(a), + (StartVmArgs a) => startVM(a.vmName), + (UnknownCommandArgs a) => unknown_command(a) ); } @@ -153,25 +153,25 @@ int apply(string type, string value) return executeCommand("just switch-" ~ type ~ " " ~ value); } -int sys(sys_args args) +int sys(SysArgs args) { return args.cmd.match!( - (sys_apply_args a) => apply("system", a.machineName), - (sys_edit_args a) => edit("system", a.machineName), - (unknown_command_args a) => unknown_command(a) + (SysApplyArgs a) => apply("system", a.machineName), + (SysEditArgs a) => edit("system", a.machineName), + (UnknownCommandArgs a) => unknown_command(a) ); } -int home(home_args args) +int home(HomeArgs args) { return args.cmd.match!( - (home_apply_args a) { + (HomeApplyArgs a) { writeln("Applying home configuration from: ", a.type); return executeCommand("just switch-home " ~ a.type); }, - (home_edit_args a) => "user".edit(a.type), - (unknown_command_args a) => unknown_command(a) + (HomeEditArgs a) => "user".edit(a.type), + (UnknownCommandArgs a) => unknown_command(a) ); } diff --git a/packages/mcl/src/src/mcl/commands/deploy_spec.d b/packages/mcl/src/src/mcl/commands/deploy_spec.d index 593c7d00..aa7a3d72 100644 --- a/packages/mcl/src/src/mcl/commands/deploy_spec.d +++ b/packages/mcl/src/src/mcl/commands/deploy_spec.d @@ -17,10 +17,10 @@ import mcl.commands.ci_matrix : flakeAttr, params, nixEvalJobs, SupportedSystem; @(Command("deploy-spec", "deploy_spec").Description("Evaluate the Nixos machine configurations in bareMetalMachines and deploy them to cachix.")) -export struct deploy_spec_args { +export struct DeploySpecArgs { } -export int deploy_spec(deploy_spec_args args) +export int deploy_spec(DeploySpecArgs args) { const deploySpecFile = resultDir.buildPath("cachix-deploy-spec.json"); diff --git a/packages/mcl/src/src/mcl/commands/get_fstab.d b/packages/mcl/src/src/mcl/commands/get_fstab.d index 72bbb579..016a546f 100755 --- a/packages/mcl/src/src/mcl/commands/get_fstab.d +++ b/packages/mcl/src/src/mcl/commands/get_fstab.d @@ -15,7 +15,7 @@ import mcl.utils.nix : queryStorePath, nix; import mcl.utils.string : camelCaseToCapitalCase; import mcl.utils.process : execute; -export int get_fstab(get_fstab_args args) +export int get_fstab(GetFstabArgs args) { args.cachixStoreUrl = cachixNixStoreUrl(args.cachixCache); if (!args.cachixDeployWorkspace) @@ -33,7 +33,7 @@ export int get_fstab(get_fstab_args args) } @(Command("get-fstab", "get_fstab").Description("Get the store path of the fstab file for a deployment")) -export struct get_fstab_args { +export struct GetFstabArgs { @(NamedArgument(["cachix-auth-token"]).Required().Placeholder("XXX").Description("Auth Token for Cachix")) string cachixAuthToken; @(NamedArgument(["cachix-cache"]).Required().Placeholder("cache").Description("Which Cachix cache to use")) @@ -50,8 +50,7 @@ export struct get_fstab_args { uint deploymentId; } - -string getCachixDeploymentStorePath(get_fstab_args args) +string getCachixDeploymentStorePath(GetFstabArgs args) { const url = getCachixDeploymentApiUrl(args.cachixDeployWorkspace, args.machineName, args.deploymentId); const response = fetchJson(url, args.cachixAuthToken); diff --git a/packages/mcl/src/src/mcl/commands/host_info.d b/packages/mcl/src/src/mcl/commands/host_info.d index 28c766ea..27a2071a 100644 --- a/packages/mcl/src/src/mcl/commands/host_info.d +++ b/packages/mcl/src/src/mcl/commands/host_info.d @@ -60,9 +60,9 @@ string[string] getProcInfo(string fileOrData, bool file = true) } @(Command("host-info", "host_info").Description("Get information about the host machine")) -struct host_info_args {} +struct HostInfoArgs {} -export int host_info(host_info_args args) +export int host_info(HostInfoArgs args) { const Params params = parseEnv!Params; diff --git a/packages/mcl/src/src/mcl/commands/machine.d b/packages/mcl/src/src/mcl/commands/machine.d index 65a0d228..a76291c8 100755 --- a/packages/mcl/src/src/mcl/commands/machine.d +++ b/packages/mcl/src/src/mcl/commands/machine.d @@ -176,7 +176,7 @@ string[] getGroups() return groups; } -User createUser(create_machine_args args) { +User createUser(CreateMachineArgs args) { auto createUser = args.createUser || prompt!bool("Create new user"); if (!createUser) @@ -223,7 +223,7 @@ struct MachineConfiguration MachineUserInfo users; } -void createMachine(create_machine_args args, MachineType machineType, string machineName, User user) { +void createMachine(CreateMachineArgs args, MachineType machineType, string machineName, User user) { auto infoJSON = execute(["ssh", args.sshPath, "sudo nix --experimental-features \\'nix-command flakes\\' --refresh --accept-flake-config run github:metacraft-labs/nixos-modules/#mcl host-info"],false, false); auto infoJSONParsed = infoJSON.parseJSON; Info info = infoJSONParsed.fromJSON!Info; @@ -368,7 +368,7 @@ struct HardwareConfiguration { Services services; } -int createMachineConfiguration(create_machine_args args) +int createMachineConfiguration(CreateMachineArgs args) { checkifNixosMachineConfigRepo(); auto machineType = cast(int)args.machineType != 0 ? args.machineType : prompt!MachineType("Machine type"); @@ -380,16 +380,17 @@ int createMachineConfiguration(create_machine_args args) } -export int machine(machine_args args) +export int machine(MachineArgs args) { return args.cmd.match!( - (create_machine_args a) => createMachineConfiguration(a), - (unknown_command_args a) => unknown_command(a) + (CreateMachineArgs a) => createMachineConfiguration(a), + (UnknownCommandArgs a) => unknown_command(a) ); } -@(Command("create").Description("Create a new machine")) -struct create_machine_args { +@(Command("create").Description("Create a new machine")) +struct CreateMachineArgs +{ @(PositionalArgument(0).Placeholder("ssh").Description("SSH path to the machine")) string sshPath; @(NamedArgument(["create-user"]).Placeholder("true/false").Description("Create a new user")) @@ -409,17 +410,18 @@ struct create_machine_args { @(NamedArgument(["disks"]).Placeholder("CT2000P3PSSD8_2402E88C1519,...").Description("Disks to use")) string disks; } + @(Command(" ").Description(" ")) -struct unknown_command_args {} -int unknown_command(unknown_command_args unused) +struct UnknownCommandArgs {} + +int unknown_command(UnknownCommandArgs unused) { stderr.writeln("Unknown machine command. Use --help for a list of available commands."); return 1; } @(Command("machine").Description("Manage machines")) -struct machine_args +struct MachineArgs { - - @SubCommands SumType!(create_machine_args,Default!unknown_command_args) cmd; + @SubCommands SumType!(CreateMachineArgs,Default!UnknownCommandArgs) cmd; } diff --git a/packages/mcl/src/src/mcl/commands/shard_matrix.d b/packages/mcl/src/src/mcl/commands/shard_matrix.d index 3239e483..fdc749ce 100644 --- a/packages/mcl/src/src/mcl/commands/shard_matrix.d +++ b/packages/mcl/src/src/mcl/commands/shard_matrix.d @@ -21,13 +21,13 @@ import mcl.utils.nix : nix; import mcl.utils.path : createResultDirs, resultDir, rootDir; @(Command("shard-matrix", "shard_matrix").Description("Generate a shard matrix for a flake")) -struct shard_matrix_args +struct ShardMatrixArgs { @(NamedArgument(["github-output"]).Placeholder("output").Description("Output to GitHub Actions")) string githubOutput; } -export int shard_matrix(shard_matrix_args args) +export int shard_matrix(ShardMatrixArgs args) { auto matrix = generateShardMatrix(); saveShardMatrix(matrix, args); @@ -138,7 +138,7 @@ unittest } -void saveShardMatrix(ShardMatrix matrix, shard_matrix_args args) +void saveShardMatrix(ShardMatrix matrix, ShardMatrixArgs args) { const matrixJson = matrix.toJSON(); const matrixString = matrixJson.toString(); From 7d473e1f21941aea8b98bccf74a66f534e594a7b Mon Sep 17 00:00:00 2001 From: Petar Kirov Date: Tue, 18 Nov 2025 19:03:44 +0200 Subject: [PATCH 17/24] refactor(mcl.commands): Use selective import for `argparse` --- packages/mcl/src/main.d | 2 +- packages/mcl/src/src/mcl/commands/ci.d | 2 +- packages/mcl/src/src/mcl/commands/ci_matrix.d | 2 +- packages/mcl/src/src/mcl/commands/config.d | 2 +- packages/mcl/src/src/mcl/commands/deploy_spec.d | 2 +- packages/mcl/src/src/mcl/commands/get_fstab.d | 2 +- packages/mcl/src/src/mcl/commands/host_info.d | 2 +- packages/mcl/src/src/mcl/commands/machine.d | 2 +- packages/mcl/src/src/mcl/commands/shard_matrix.d | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/mcl/src/main.d b/packages/mcl/src/main.d index 7dc48ec5..046e51dd 100644 --- a/packages/mcl/src/main.d +++ b/packages/mcl/src/main.d @@ -15,7 +15,7 @@ import mcl.utils.tui : bold; import mcl.commands : SubCommandFunctions; -import argparse; +import argparse : Command, Description, SubCommands, NamedArgument, Default, CLI; @(Command(" ").Description(" ")) struct UnknownCommandArgs {} diff --git a/packages/mcl/src/src/mcl/commands/ci.d b/packages/mcl/src/src/mcl/commands/ci.d index d459308f..e3b17c6d 100644 --- a/packages/mcl/src/src/mcl/commands/ci.d +++ b/packages/mcl/src/src/mcl/commands/ci.d @@ -8,7 +8,7 @@ import std.array : array, join; import std.conv : to; import std.process : ProcessPipes; -import argparse; +import argparse : Command, Description; import mcl.utils.env : optional, parseEnv; import mcl.commands.ci_matrix: nixEvalJobs, SupportedSystem, Params, flakeAttr; diff --git a/packages/mcl/src/src/mcl/commands/ci_matrix.d b/packages/mcl/src/src/mcl/commands/ci_matrix.d index ea6ccce9..291fbb59 100755 --- a/packages/mcl/src/src/mcl/commands/ci_matrix.d +++ b/packages/mcl/src/src/mcl/commands/ci_matrix.d @@ -16,7 +16,7 @@ import std.exception : enforce; import std.format : fmt = format; import std.logger : tracef, infof, errorf, warningf; -import argparse; +import argparse : Command, Description; import mcl.utils.env : optional, MissingEnvVarsException, parseEnv; import mcl.utils.string : enumToString, StringRepresentation, MaxWidth, writeRecordAsTable; diff --git a/packages/mcl/src/src/mcl/commands/config.d b/packages/mcl/src/src/mcl/commands/config.d index cef00d19..bb0da84a 100755 --- a/packages/mcl/src/src/mcl/commands/config.d +++ b/packages/mcl/src/src/mcl/commands/config.d @@ -8,7 +8,7 @@ import std.stdio : writeln; import std.string : indexOf; import std.sumtype : SumType, match; -import argparse; +import argparse : Command, Description, SubCommands, Default, PositionalArgument, Placeholder, Optional; import mcl.utils.env : optional, parseEnv; import mcl.utils.fetch : fetchJson; diff --git a/packages/mcl/src/src/mcl/commands/deploy_spec.d b/packages/mcl/src/src/mcl/commands/deploy_spec.d index aa7a3d72..97eecd8e 100644 --- a/packages/mcl/src/src/mcl/commands/deploy_spec.d +++ b/packages/mcl/src/src/mcl/commands/deploy_spec.d @@ -5,7 +5,7 @@ import std.logger : infof, warningf; import std.file : exists; import std.path : buildPath; -import argparse; +import argparse : Command, Description; import mcl.utils.process : spawnProcessInline; import mcl.utils.path : resultDir; diff --git a/packages/mcl/src/src/mcl/commands/get_fstab.d b/packages/mcl/src/src/mcl/commands/get_fstab.d index 016a546f..d4b9707f 100755 --- a/packages/mcl/src/src/mcl/commands/get_fstab.d +++ b/packages/mcl/src/src/mcl/commands/get_fstab.d @@ -6,7 +6,7 @@ import std.json : JSONValue; import std.format : fmt = format; import std.exception : enforce; -import argparse; +import argparse : Command, Description, NamedArgument, PositionalArgument, Required, Placeholder; import mcl.utils.cachix : cachixNixStoreUrl, getCachixDeploymentApiUrl; import mcl.utils.env : optional, parseEnv; diff --git a/packages/mcl/src/src/mcl/commands/host_info.d b/packages/mcl/src/src/mcl/commands/host_info.d index 27a2071a..e4f597a2 100644 --- a/packages/mcl/src/src/mcl/commands/host_info.d +++ b/packages/mcl/src/src/mcl/commands/host_info.d @@ -19,7 +19,7 @@ import std.format : format; import std.system : nativeEndian = endian;; import core.stdc.string : strlen; -import argparse; +import argparse : Command, Description; import mcl.utils.env : parseEnv, optional; import mcl.utils.json : toJSON; diff --git a/packages/mcl/src/src/mcl/commands/machine.d b/packages/mcl/src/src/mcl/commands/machine.d index a76291c8..5ba4d053 100755 --- a/packages/mcl/src/src/mcl/commands/machine.d +++ b/packages/mcl/src/src/mcl/commands/machine.d @@ -2,7 +2,7 @@ module mcl.commands.machine; import std; -import argparse; +import argparse : Command, Description, NamedArgument, PositionalArgument, Placeholder, SubCommands, Default; import mcl.utils.log : prompt; import mcl.utils.process : execute; diff --git a/packages/mcl/src/src/mcl/commands/shard_matrix.d b/packages/mcl/src/src/mcl/commands/shard_matrix.d index fdc749ce..89265be4 100644 --- a/packages/mcl/src/src/mcl/commands/shard_matrix.d +++ b/packages/mcl/src/src/mcl/commands/shard_matrix.d @@ -13,7 +13,7 @@ import std.regex : matchFirst, regex; import std.stdio : writeln; import std.string : strip; -import argparse; +import argparse : Command, Description, NamedArgument, Placeholder; import mcl.utils.env : parseEnv, optional; import mcl.utils.json : toJSON; From ad5199204d02007594c013ed32559e5807b02900 Mon Sep 17 00:00:00 2001 From: Petar Kirov Date: Tue, 18 Nov 2025 19:55:40 +0200 Subject: [PATCH 18/24] style(mcl.commands): Reformat command definition --- packages/mcl/src/main.d | 2 +- packages/mcl/src/src/mcl/commands/ci.d | 4 +--- packages/mcl/src/src/mcl/commands/ci_matrix.d | 14 ++++++-------- packages/mcl/src/src/mcl/commands/config.d | 6 ++---- packages/mcl/src/src/mcl/commands/deploy_spec.d | 6 +++--- packages/mcl/src/src/mcl/commands/get_fstab.d | 6 ++++-- packages/mcl/src/src/mcl/commands/host_info.d | 5 +++-- packages/mcl/src/src/mcl/commands/machine.d | 2 +- packages/mcl/src/src/mcl/commands/shard_matrix.d | 3 ++- 9 files changed, 23 insertions(+), 25 deletions(-) diff --git a/packages/mcl/src/main.d b/packages/mcl/src/main.d index 046e51dd..2ac904de 100644 --- a/packages/mcl/src/main.d +++ b/packages/mcl/src/main.d @@ -18,7 +18,7 @@ import mcl.commands : SubCommandFunctions; import argparse : Command, Description, SubCommands, NamedArgument, Default, CLI; @(Command(" ").Description(" ")) -struct UnknownCommandArgs {} +struct UnknownCommandArgs { } int unknown_command(UnknownCommandArgs unused) { stderr.writeln("Unknown command. Use --help for a list of available commands."); diff --git a/packages/mcl/src/src/mcl/commands/ci.d b/packages/mcl/src/src/mcl/commands/ci.d index e3b17c6d..71269dc3 100644 --- a/packages/mcl/src/src/mcl/commands/ci.d +++ b/packages/mcl/src/src/mcl/commands/ci.d @@ -21,9 +21,7 @@ import mcl.utils.json : toJSON; Params params; @(Command("ci").Description("Run CI")) -struct CiArgs -{ -} +struct CiArgs { } export int ci(CiArgs args) { diff --git a/packages/mcl/src/src/mcl/commands/ci_matrix.d b/packages/mcl/src/src/mcl/commands/ci_matrix.d index 291fbb59..fc9787ac 100755 --- a/packages/mcl/src/src/mcl/commands/ci_matrix.d +++ b/packages/mcl/src/src/mcl/commands/ci_matrix.d @@ -145,10 +145,9 @@ version (unittest) Params params; -@(Command("ci-matrix", "ci_matrix").Description("Print a table of the cache status of each package")) -struct CiMatrixArgs -{ -} +@(Command("ci-matrix", "ci_matrix") + .Description("Print a table of the cache status of each package")) +struct CiMatrixArgs { } export int ci_matrix(CiMatrixArgs args) { @@ -189,10 +188,9 @@ Package[] checkCacheStatus(Package[] packages) return packages; } -@(Command("print-table", "print_table").Description("Print a table of the cache status of each package")) -struct PrintTableArgs -{ -} +@(Command("print-table", "print_table") + .Description("Print a table of the cache status of each package")) +struct PrintTableArgs { } export int print_table(PrintTableArgs args) { diff --git a/packages/mcl/src/src/mcl/commands/config.d b/packages/mcl/src/src/mcl/commands/config.d index bb0da84a..23f9ef28 100755 --- a/packages/mcl/src/src/mcl/commands/config.d +++ b/packages/mcl/src/src/mcl/commands/config.d @@ -18,7 +18,7 @@ import mcl.utils.process : execute; import mcl.utils.string : camelCaseToCapitalCase; @(Command("config").Description("Manage NixOS machine configurations")) -export struct ConfigArgs +struct ConfigArgs { @SubCommands SumType!( SysArgs, @@ -84,9 +84,7 @@ struct StartVmArgs } @(Command(" ").Description(" ")) -struct UnknownCommandArgs -{ -} +struct UnknownCommandArgs { } int unknown_command(UnknownCommandArgs unused) { diff --git a/packages/mcl/src/src/mcl/commands/deploy_spec.d b/packages/mcl/src/src/mcl/commands/deploy_spec.d index 97eecd8e..6d710616 100644 --- a/packages/mcl/src/src/mcl/commands/deploy_spec.d +++ b/packages/mcl/src/src/mcl/commands/deploy_spec.d @@ -16,9 +16,9 @@ import mcl.utils.json : tryDeserializeFromJsonFile, writeJsonFile; import mcl.commands.ci_matrix : flakeAttr, params, nixEvalJobs, SupportedSystem; -@(Command("deploy-spec", "deploy_spec").Description("Evaluate the Nixos machine configurations in bareMetalMachines and deploy them to cachix.")) -export struct DeploySpecArgs { -} +@(Command("deploy-spec", "deploy_spec") + .Description("Evaluate the Nixos machine configurations in bareMetalMachines and deploy them to cachix.")) +struct DeploySpecArgs { } export int deploy_spec(DeploySpecArgs args) { diff --git a/packages/mcl/src/src/mcl/commands/get_fstab.d b/packages/mcl/src/src/mcl/commands/get_fstab.d index d4b9707f..efe3027a 100755 --- a/packages/mcl/src/src/mcl/commands/get_fstab.d +++ b/packages/mcl/src/src/mcl/commands/get_fstab.d @@ -32,8 +32,10 @@ export int get_fstab(GetFstabArgs args) return 0; } -@(Command("get-fstab", "get_fstab").Description("Get the store path of the fstab file for a deployment")) -export struct GetFstabArgs { +@(Command("get-fstab", "get_fstab") + .Description("Get the store path of the fstab file for a deployment")) +struct GetFstabArgs +{ @(NamedArgument(["cachix-auth-token"]).Required().Placeholder("XXX").Description("Auth Token for Cachix")) string cachixAuthToken; @(NamedArgument(["cachix-cache"]).Required().Placeholder("cache").Description("Which Cachix cache to use")) diff --git a/packages/mcl/src/src/mcl/commands/host_info.d b/packages/mcl/src/src/mcl/commands/host_info.d index e4f597a2..9c138c69 100644 --- a/packages/mcl/src/src/mcl/commands/host_info.d +++ b/packages/mcl/src/src/mcl/commands/host_info.d @@ -59,8 +59,9 @@ string[string] getProcInfo(string fileOrData, bool file = true) return r; } -@(Command("host-info", "host_info").Description("Get information about the host machine")) -struct HostInfoArgs {} +@(Command("host-info", "host_info") + .Description("Get information about the host machine")) +struct HostInfoArgs { } export int host_info(HostInfoArgs args) { diff --git a/packages/mcl/src/src/mcl/commands/machine.d b/packages/mcl/src/src/mcl/commands/machine.d index 5ba4d053..fc6b363b 100755 --- a/packages/mcl/src/src/mcl/commands/machine.d +++ b/packages/mcl/src/src/mcl/commands/machine.d @@ -412,7 +412,7 @@ struct CreateMachineArgs } @(Command(" ").Description(" ")) -struct UnknownCommandArgs {} +struct UnknownCommandArgs { } int unknown_command(UnknownCommandArgs unused) { diff --git a/packages/mcl/src/src/mcl/commands/shard_matrix.d b/packages/mcl/src/src/mcl/commands/shard_matrix.d index 89265be4..57c89c9c 100644 --- a/packages/mcl/src/src/mcl/commands/shard_matrix.d +++ b/packages/mcl/src/src/mcl/commands/shard_matrix.d @@ -20,7 +20,8 @@ import mcl.utils.json : toJSON; import mcl.utils.nix : nix; import mcl.utils.path : createResultDirs, resultDir, rootDir; -@(Command("shard-matrix", "shard_matrix").Description("Generate a shard matrix for a flake")) +@(Command("shard-matrix", "shard_matrix") + .Description("Generate a shard matrix for a flake")) struct ShardMatrixArgs { @(NamedArgument(["github-output"]).Placeholder("output").Description("Output to GitHub Actions")) From 458720575c8d94ded341cff326965dfd56c971a6 Mon Sep 17 00:00:00 2001 From: Petar Kirov Date: Tue, 18 Nov 2025 20:56:04 +0200 Subject: [PATCH 19/24] refactor(mcl.env): Remove the requirement of a `setup` function --- packages/mcl/src/src/mcl/commands/host_info.d | 1 - packages/mcl/src/src/mcl/utils/env.d | 7 ++----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/mcl/src/src/mcl/commands/host_info.d b/packages/mcl/src/src/mcl/commands/host_info.d index 9c138c69..65ce1333 100644 --- a/packages/mcl/src/src/mcl/commands/host_info.d +++ b/packages/mcl/src/src/mcl/commands/host_info.d @@ -32,7 +32,6 @@ import mcl.utils.coda : CodaApiClient, RowValues, CodaCell; struct Params { @optional() string codaApiToken; - void setup() {} } string[string] cpuinfo; diff --git a/packages/mcl/src/src/mcl/utils/env.d b/packages/mcl/src/src/mcl/utils/env.d index d7a9e1e8..89184a84 100644 --- a/packages/mcl/src/src/mcl/utils/env.d +++ b/packages/mcl/src/src/mcl/utils/env.d @@ -47,7 +47,8 @@ T parseEnv(T)() "missing environment variables:\n%(* %s\n%)".fmt(missingEnvVars) ); - result.setup(); + static if (__traits(hasMember, T, "setup")) + result.setup(); return result; } @@ -60,10 +61,6 @@ version (unittest) int a; string b; float c = 1.0; - - void setup() - { - } } } From cfd622a7756c1df3a0171311efa050a0c8e206b7 Mon Sep 17 00:00:00 2001 From: Petar Kirov Date: Wed, 19 Nov 2025 14:49:37 +0200 Subject: [PATCH 20/24] feat(mcl): Upgrade `argparse`: `1.4.1` -> `2.0.2` * Switch to nixpkgs version of `buildDubPackage` * Use `nix-to-dub` to generate dub lock file: nix run nixpkgs#dub-to-nix > dub-lock.json * Enable `checkPhase` (default is off in nixpkgs, unlike dlang.nix) * Escape flags disabled tests flag passed to `dubTestFlags` * Add installPhase --- packages/default.nix | 4 +-- packages/mcl/default.nix | 32 ++++++++++++++------- packages/mcl/dub-lock.json | 20 +++++++++++++ packages/mcl/dub.sdl | 2 +- packages/mcl/dub.selections.json | 4 +-- packages/mcl/src/main.d | 7 ++--- packages/mcl/src/src/mcl/commands/config.d | 15 +++++----- packages/mcl/src/src/mcl/commands/machine.d | 6 ++-- 8 files changed, 59 insertions(+), 31 deletions(-) create mode 100644 packages/mcl/dub-lock.json diff --git a/packages/default.nix b/packages/default.nix index 394fd7ee..df8cf6d7 100644 --- a/packages/default.nix +++ b/packages/default.nix @@ -67,9 +67,7 @@ } // optionalAttrs (system == "x86_64-linux") { mcl = pkgs.callPackage ./mcl { - buildDubPackage = inputs'.dlang-nix.legacyPackages.buildDubPackage.override { - dCompiler = inputs'.dlang-nix.packages."ldc-binary-1_38_0"; - }; + dCompiler = inputs'.dlang-nix.packages."ldc-binary-1_38_0"; inherit (legacyPackages.inputs.nixpkgs) cachix nix nix-eval-jobs; }; }; diff --git a/packages/mcl/default.nix b/packages/mcl/default.nix index 998abcfd..be5225d6 100644 --- a/packages/mcl/default.nix +++ b/packages/mcl/default.nix @@ -1,6 +1,6 @@ { lib, - buildDubPackage, + dCompiler, pkgs, nix, nix-eval-jobs, @@ -43,9 +43,10 @@ let ] ); in -buildDubPackage rec { +pkgs.buildDubPackage rec { pname = "mcl"; version = "unstable"; + src = lib.fileset.toSource { root = ./.; fileset = lib.fileset.fileFilter ( @@ -59,22 +60,33 @@ buildDubPackage rec { ) ./.; }; + inherit dCompiler; + + dubLock = ./dub-lock.json; + nativeBuildInputs = [ pkgs.makeWrapper ] ++ deps; - postFixup = '' - wrapProgram $out/bin/${pname} --set PATH "${lib.makeBinPath deps}" --set LD_LIBRARY_PATH "${lib.makeLibraryPath deps}" - ''; + dubBuildType = "debug"; - dubBuildFlags = [ - "-b" - "debug" - ]; + doCheck = true; dubTestFlags = [ "--" "-e" - excludedTests + (lib.escapeShellArg excludedTests) ]; + installPhase = '' + runHook preInstall + install -Dm755 ./build/${pname} -t $out/bin/ + runHook postInstall + ''; + + postFixup = '' + wrapProgram $out/bin/${pname} \ + --set PATH "${lib.makeBinPath deps}" \ + --set LD_LIBRARY_PATH "${lib.makeLibraryPath deps}" + ''; + meta.mainProgram = pname; } diff --git a/packages/mcl/dub-lock.json b/packages/mcl/dub-lock.json new file mode 100644 index 00000000..e9674ebc --- /dev/null +++ b/packages/mcl/dub-lock.json @@ -0,0 +1,20 @@ +{ + "dependencies": { + "argparse": { + "version": "2.0.2", + "sha256": "1p3496rcndgaf68mxizfi4wrkf5frgzzfpp9frlvbjnkinizb2q9" + }, + "mir-core": { + "version": "1.7.3", + "sha256": "0yy1jg1b0i2n7xzkj5ykl1sg3qljna9nhjid4mfjz7ncybls49l9" + }, + "mir-cpuid": { + "version": "1.2.11", + "sha256": "1gxrnvvy34cfi71p8a0s904z3zp1l7k9qvliacxmhvykxr1nhrsf" + }, + "silly": { + "version": "1.1.1", + "sha256": "0fz7ib715sfk3w69i6xns5pwd4caahvfqjf32v13daxm1ms8xdzz" + } + } +} diff --git a/packages/mcl/dub.sdl b/packages/mcl/dub.sdl index ea896f42..b7d84cc0 100644 --- a/packages/mcl/dub.sdl +++ b/packages/mcl/dub.sdl @@ -21,4 +21,4 @@ dflags "-mcpu=baseline" platform="dmd" dependency "mir-cpuid" version="~>1.2.11" dependency "silly" version="~>1.1.1" -dependency "argparse" version="~>1.4.1" +dependency "argparse" version="~>2.0.2" diff --git a/packages/mcl/dub.selections.json b/packages/mcl/dub.selections.json index a3f31e8e..a032fdd6 100644 --- a/packages/mcl/dub.selections.json +++ b/packages/mcl/dub.selections.json @@ -1,8 +1,8 @@ { "fileVersion": 1, "versions": { - "argparse": "1.4.1", - "mir-core": "1.7.1", + "argparse": "2.0.2", + "mir-core": "1.7.3", "mir-cpuid": "1.2.11", "silly": "1.1.1" } diff --git a/packages/mcl/src/main.d b/packages/mcl/src/main.d index 2ac904de..b184a29b 100644 --- a/packages/mcl/src/main.d +++ b/packages/mcl/src/main.d @@ -15,7 +15,7 @@ import mcl.utils.tui : bold; import mcl.commands : SubCommandFunctions; -import argparse : Command, Description, SubCommands, NamedArgument, Default, CLI; +import argparse : Command, Description, SubCommand, NamedArgument, Default, CLI, matchCmd; @(Command(" ").Description(" ")) struct UnknownCommandArgs { } @@ -30,8 +30,7 @@ struct MCLArgs @NamedArgument(["log-level"]) LogLevel logLevel = cast(LogLevel)-1; - @SubCommands - SumType!( + SubCommand!( staticMap!(Parameters, SubCommandFunctions), Default!UnknownCommandArgs ) cmd; @@ -46,7 +45,7 @@ mixin CLI!MCLArgs.main!((args) logLevel = args.logLevel; setLogLevel(logLevel); - int result = args.cmd.match!( + int result = args.cmd.matchCmd!( staticMap!(SumTypeCase, unknown_command, SubCommandFunctions), ); diff --git a/packages/mcl/src/src/mcl/commands/config.d b/packages/mcl/src/src/mcl/commands/config.d index 23f9ef28..51ec954d 100755 --- a/packages/mcl/src/src/mcl/commands/config.d +++ b/packages/mcl/src/src/mcl/commands/config.d @@ -6,9 +6,8 @@ import std.process : ProcessPipes, Redirect, wait, environment; import std.range : drop, front; import std.stdio : writeln; import std.string : indexOf; -import std.sumtype : SumType, match; -import argparse : Command, Description, SubCommands, Default, PositionalArgument, Placeholder, Optional; +import argparse : Command, Description, SubCommand, Default, PositionalArgument, Placeholder, Optional, matchCmd; import mcl.utils.env : optional, parseEnv; import mcl.utils.fetch : fetchJson; @@ -20,7 +19,7 @@ import mcl.utils.string : camelCaseToCapitalCase; @(Command("config").Description("Manage NixOS machine configurations")) struct ConfigArgs { - @SubCommands SumType!( + SubCommand!( SysArgs, HomeArgs, StartVmArgs, @@ -31,7 +30,7 @@ struct ConfigArgs @(Command("sys").Description("Manage system configurations")) struct SysArgs { - @SubCommands SumType!( + SubCommand!( SysApplyArgs, SysEditArgs, Default!UnknownCommandArgs @@ -55,7 +54,7 @@ struct SysEditArgs @(Command("home").Description("Manage home configurations")) struct HomeArgs { - @SubCommands SumType!( + SubCommand!( HomeApplyArgs, HomeEditArgs, Default!UnknownCommandArgs @@ -100,7 +99,7 @@ export int config(ConfigArgs args) "This command must be run from a repository containing a NixOS machine configuration"); } - return args.cmd.match!( + return args.cmd.matchCmd!( (SysArgs a) => sys(a), (HomeArgs a) => home(a), (StartVmArgs a) => startVM(a.vmName), @@ -153,7 +152,7 @@ int apply(string type, string value) int sys(SysArgs args) { - return args.cmd.match!( + return args.cmd.matchCmd!( (SysApplyArgs a) => apply("system", a.machineName), (SysEditArgs a) => edit("system", a.machineName), (UnknownCommandArgs a) => unknown_command(a) @@ -163,7 +162,7 @@ int sys(SysArgs args) int home(HomeArgs args) { - return args.cmd.match!( + return args.cmd.matchCmd!( (HomeApplyArgs a) { writeln("Applying home configuration from: ", a.type); return executeCommand("just switch-home " ~ a.type); diff --git a/packages/mcl/src/src/mcl/commands/machine.d b/packages/mcl/src/src/mcl/commands/machine.d index fc6b363b..7a7c0e14 100755 --- a/packages/mcl/src/src/mcl/commands/machine.d +++ b/packages/mcl/src/src/mcl/commands/machine.d @@ -2,7 +2,7 @@ module mcl.commands.machine; import std; -import argparse : Command, Description, NamedArgument, PositionalArgument, Placeholder, SubCommands, Default; +import argparse : Command, Description, NamedArgument, PositionalArgument, Placeholder, SubCommand, Default, matchCmd; import mcl.utils.log : prompt; import mcl.utils.process : execute; @@ -382,7 +382,7 @@ int createMachineConfiguration(CreateMachineArgs args) export int machine(MachineArgs args) { - return args.cmd.match!( + return args.cmd.matchCmd!( (CreateMachineArgs a) => createMachineConfiguration(a), (UnknownCommandArgs a) => unknown_command(a) ); @@ -423,5 +423,5 @@ int unknown_command(UnknownCommandArgs unused) @(Command("machine").Description("Manage machines")) struct MachineArgs { - @SubCommands SumType!(CreateMachineArgs,Default!UnknownCommandArgs) cmd; + SubCommand!(CreateMachineArgs,Default!UnknownCommandArgs) cmd; } From ccb2c67a5763d9f0fd2577232150a8bc142a0897 Mon Sep 17 00:00:00 2001 From: Petar Kirov Date: Tue, 18 Nov 2025 21:01:32 +0200 Subject: [PATCH 21/24] fix(mcl.commands.shard_matrix): Fallback to `GITHUB_OUTPUT` env variable --- packages/mcl/src/src/mcl/commands/shard_matrix.d | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/mcl/src/src/mcl/commands/shard_matrix.d b/packages/mcl/src/src/mcl/commands/shard_matrix.d index 57c89c9c..aa810c21 100644 --- a/packages/mcl/src/src/mcl/commands/shard_matrix.d +++ b/packages/mcl/src/src/mcl/commands/shard_matrix.d @@ -13,7 +13,7 @@ import std.regex : matchFirst, regex; import std.stdio : writeln; import std.string : strip; -import argparse : Command, Description, NamedArgument, Placeholder; +import argparse : Command, Description, NamedArgument, Placeholder, EnvFallback; import mcl.utils.env : parseEnv, optional; import mcl.utils.json : toJSON; @@ -24,7 +24,11 @@ import mcl.utils.path : createResultDirs, resultDir, rootDir; .Description("Generate a shard matrix for a flake")) struct ShardMatrixArgs { - @(NamedArgument(["github-output"]).Placeholder("output").Description("Output to GitHub Actions")) + @(NamedArgument(["github-output"]) + .Placeholder("output") + .Description("Output to GitHub Actions") + .EnvFallback("GITHUB_OUTPUT") + ) string githubOutput; } From 6160e6e1acbb99565c895aece418f9e2d6fcc380 Mon Sep 17 00:00:00 2001 From: Petar Kirov Date: Wed, 19 Nov 2025 16:02:00 +0200 Subject: [PATCH 22/24] Revert "fix(ci_matrix): Initialize params directly in ci_matrix function" This reverts commit 0fdda48709620fbe2dd28776cb0d5a8090355b5e. --- packages/mcl/src/src/mcl/commands/ci_matrix.d | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/mcl/src/src/mcl/commands/ci_matrix.d b/packages/mcl/src/src/mcl/commands/ci_matrix.d index fc9787ac..10d9b779 100755 --- a/packages/mcl/src/src/mcl/commands/ci_matrix.d +++ b/packages/mcl/src/src/mcl/commands/ci_matrix.d @@ -143,7 +143,13 @@ version (unittest) ]; } -Params params; +immutable Params params; + +version (unittest) {} else +shared static this() +{ + params = parseEnv!Params; +} @(Command("ci-matrix", "ci_matrix") .Description("Print a table of the cache status of each package")) @@ -151,7 +157,6 @@ struct CiMatrixArgs { } export int ci_matrix(CiMatrixArgs args) { - params = parseEnv!Params; createResultDirs(); nixEvalForAllSystems().array.printTableForCacheStatus(); return 0; From 2662d7ac3e9d098fe00353bffe800e37cf0f09ec Mon Sep 17 00:00:00 2001 From: Simeon Armenchev Date: Thu, 20 Nov 2025 16:43:28 +0200 Subject: [PATCH 23/24] refactor(): Remove Params and switch it to argparse --- packages/mcl/src/src/mcl/commands/ci.d | 26 +-- packages/mcl/src/src/mcl/commands/ci_matrix.d | 220 +++++++++++------- packages/mcl/src/src/mcl/commands/config.d | 1 - .../mcl/src/src/mcl/commands/deploy_spec.d | 10 +- packages/mcl/src/src/mcl/commands/get_fstab.d | 7 +- packages/mcl/src/src/mcl/commands/host_info.d | 23 +- packages/mcl/src/src/mcl/commands/machine.d | 1 - .../mcl/src/src/mcl/commands/shard_matrix.d | 1 - packages/mcl/src/src/mcl/utils/env.d | 90 ------- 9 files changed, 166 insertions(+), 213 deletions(-) delete mode 100644 packages/mcl/src/src/mcl/utils/env.d diff --git a/packages/mcl/src/src/mcl/commands/ci.d b/packages/mcl/src/src/mcl/commands/ci.d index 71269dc3..8c014d9e 100644 --- a/packages/mcl/src/src/mcl/commands/ci.d +++ b/packages/mcl/src/src/mcl/commands/ci.d @@ -10,34 +10,32 @@ import std.process : ProcessPipes; import argparse : Command, Description; -import mcl.utils.env : optional, parseEnv; -import mcl.commands.ci_matrix: nixEvalJobs, SupportedSystem, Params, flakeAttr; +import mcl.commands.ci_matrix: nixEvalJobs, SupportedSystem, flakeAttr, CiMatrixBaseArgs; import mcl.commands.shard_matrix: generateShardMatrix; import mcl.utils.path : rootDir, createResultDirs; import mcl.utils.process : execute; import mcl.utils.nix : nix; import mcl.utils.json : toJSON; -Params params; @(Command("ci").Description("Run CI")) -struct CiArgs { } +struct CiArgs { + mixin CiMatrixBaseArgs!(); +} export int ci(CiArgs args) { - params = parseEnv!Params; - auto shardMatrix = generateShardMatrix(); foreach (shard; shardMatrix.include) { - params.flakePre = shard.prefix; - params.flakePost = shard.postfix; + args.flakePre = shard.prefix; + args.flakePost = shard.postfix; - if (params.flakePre == "") + if (args.flakePre == "") { - params.flakePre = "checks"; + args.flakePre = "checks"; } - string cachixUrl = "https://" ~ params.cachixCache ~ ".cachix.org"; + string cachixUrl = "https://" ~ args.cachixCache ~ ".cachix.org"; version (AArch64) { string arch = "aarch64"; } @@ -52,8 +50,8 @@ export int ci(CiArgs args) string os = "darwin"; } - auto matrix = flakeAttr(params.flakePre, arch, os, params.flakePost) - .nixEvalJobs(cachixUrl, false); + auto matrix = flakeAttr(args.flakePre, arch, os, args.flakePost) + .nixEvalJobs(cachixUrl, args, false); foreach (pkg; matrix) { @@ -70,7 +68,7 @@ export int ci(CiArgs args) "".writeln; auto json = parseJSON(res.stdout.byLine.join("\n").to!string); auto path = json.array[0]["outputs"]["out"].str; - execute(["cachix", "push", params.cachixCache, path], false, true).writeln; + execute(["cachix", "push", args.cachixCache, path], false, true).writeln; } } diff --git a/packages/mcl/src/src/mcl/commands/ci_matrix.d b/packages/mcl/src/src/mcl/commands/ci_matrix.d index 10d9b779..10ea42c6 100755 --- a/packages/mcl/src/src/mcl/commands/ci_matrix.d +++ b/packages/mcl/src/src/mcl/commands/ci_matrix.d @@ -16,15 +16,17 @@ import std.exception : enforce; import std.format : fmt = format; import std.logger : tracef, infof, errorf, warningf; -import argparse : Command, Description; +import argparse : Command, Description, NamedArgument, Required, Placeholder, EnvFallback; -import mcl.utils.env : optional, MissingEnvVarsException, parseEnv; import mcl.utils.string : enumToString, StringRepresentation, MaxWidth, writeRecordAsTable; import mcl.utils.json : toJSON; import mcl.utils.path : rootDir, resultDir, gcRootsDir, createResultDirs; import mcl.utils.process : execute; import mcl.utils.nix : nix; +import mcl.commands.ci: CiArgs; +import mcl.commands.deploy_spec: DeploySpecArgs; + enum GitHubOS { @StringRepresentation("ubuntu-latest") ubuntuLatest, @@ -143,22 +145,83 @@ version (unittest) ]; } -immutable Params params; - -version (unittest) {} else -shared static this() +mixin template CiMatrixBaseArgs() { - params = parseEnv!Params; + import argparse : Command, Description, NamedArgument, Required, Placeholder, EnvFallback; + + @(NamedArgument(["flake-pre"]) + .Placeholder("prefix") + ) + string flakePre = "checks"; + + @(NamedArgument(["flake-post"]) + .Placeholder("postfix") + ) + string flakePost; + + @(NamedArgument(["max-workers"]) + .Placeholder("count") + ) + int maxWorkers; + + @(NamedArgument(["max-memory"]) + .Placeholder("mb") + ) + int maxMemory; + + @(NamedArgument(["initial"]) + ) + bool isInitial; + + @(NamedArgument(["cachix-cache"]) + .Placeholder("cache") + .EnvFallback("CACHIX_CACHE") + .Required() + ) + string cachixCache; + + @(NamedArgument(["cachix-auth-token"]) + .Placeholder("token") + .EnvFallback("CACHIX_AUTH_TOKEN") + .Required() + ) + string cachixAuthToken; } @(Command("ci-matrix", "ci_matrix") .Description("Print a table of the cache status of each package")) -struct CiMatrixArgs { } +struct CiMatrixArgs +{ + mixin CiMatrixBaseArgs!(); +} + +@(Command("print-table", "print_table") + .Description("Print a table of the cache status of each package")) +struct PrintTableArgs +{ + mixin CiMatrixBaseArgs!(); + + @(NamedArgument(["precalc-matrix"]) + .Placeholder("matrix") + ) + string precalcMatrix; +} export int ci_matrix(CiMatrixArgs args) { createResultDirs(); - nixEvalForAllSystems().array.printTableForCacheStatus(); + nixEvalForAllSystems(args).array.printTableForCacheStatus(args); + return 0; +} + +export int print_table(PrintTableArgs args) +{ + createResultDirs(); + + getPrecalcMatrix(args) + .checkCacheStatus(args) + .printTableForCacheStatus(args); + return 0; } @@ -174,14 +237,15 @@ string flakeAttr(string prefix, string arch, string os, string postfix) return "%s.%s-%s%s".fmt(prefix, arch, os, postfix); } -Package[] checkCacheStatus(Package[] packages) +Package[] checkCacheStatus(T)(Package[] packages, auto ref T args) + if (is(T == CiMatrixArgs) || is(T == PrintTableArgs) || is(T == CiArgs) || is(T == DeploySpecArgs)) { import std.array : appender; import std.parallelism : parallel; foreach (ref pkg; packages.parallel) { - pkg = checkPackage(pkg); + pkg = checkPackage(pkg, args); struct Output { string isCached, name, storePath; } auto res = appender!string; writeRecordAsTable( @@ -193,39 +257,7 @@ Package[] checkCacheStatus(Package[] packages) return packages; } -@(Command("print-table", "print_table") - .Description("Print a table of the cache status of each package")) -struct PrintTableArgs { } -export int print_table(PrintTableArgs args) -{ - createResultDirs(); - - getPrecalcMatrix() - .checkCacheStatus() - .printTableForCacheStatus(); - - return 0; -} - -struct Params -{ - @optional() string flakePre; - @optional() string flakePost; - @optional() string precalcMatrix; - @optional() int maxWorkers; - @optional() int maxMemory; - @optional() bool isInitial; - - string cachixCache; - string cachixAuthToken; - - void setup() - { - if (this.flakePre == "") - this.flakePre = "checks"; - } -} GitHubOS systemToGHPlatform(SupportedSystem os) { @@ -322,28 +354,28 @@ unittest )); } } - -Package[] nixEvalJobs(string flakeAttrPrefix, string cachixUrl, bool doCheck = true) +Package[] nixEvalJobs(T)(string flakeAttrPrefix, string cachixUrl, auto ref T args, bool doCheck = true) + if (is(T == CiMatrixArgs) || is(T == PrintTableArgs) || is(T == CiArgs) || is(T == DeploySpecArgs)) { Package[] result = []; bool hasError = false; - int maxMemoryMB = getAvailableMemoryMB(); - int maxWorkers = getNixEvalWorkerCount(); + int maxMemoryMB = getAvailableMemoryMB(args); + int maxWorkers = getNixEvalWorkerCount(args); - const args = [ + const nixArgs = [ "nix-eval-jobs", "--quiet", "--option", "warn-dirty", "false", "--check-cache-status", "--gc-roots-dir", gcRootsDir, "--workers", maxWorkers.to!string, "--max-memory-size", maxMemoryMB.to!string, "--flake", rootDir ~ "#" ~ flakeAttrPrefix ]; - const commandString = args.join(" "); + const commandString = nixArgs.join(" "); - tracef("%-(%s %)", args); + tracef("%-(%s %)", nixArgs); - auto pipes = pipeProcess(args, Redirect.stdout | Redirect.stderr); + auto pipes = pipeProcess(nixArgs, Redirect.stdout | Redirect.stderr); void logWarning(const char[] msg) { @@ -367,7 +399,7 @@ Package[] nixEvalJobs(string flakeAttrPrefix, string cachixUrl, bool doCheck = t Package pkg = json.packageFromNixEvalJobsJson( flakeAttrPrefix, cachixUrl); - if (doCheck)pkg = pkg.checkPackage(); + if (doCheck)pkg = pkg.checkPackage(args); result ~= pkg; @@ -419,16 +451,17 @@ SupportedSystem[] getSupportedSystems(string flakeRef = ".") return json.array.map!(system => getSystem(system.str)).array; } -Package[] nixEvalForAllSystems() +Package[] nixEvalForAllSystems(T)(auto ref T args) + if (is(T == CiMatrixArgs) || is(T == PrintTableArgs) || is(T == CiArgs) || is(T == DeploySpecArgs)) { - const cachixUrl = "https://" ~ params.cachixCache ~ ".cachix.org"; + const cachixUrl = "https://" ~ args.cachixCache ~ ".cachix.org"; const systems = getSupportedSystems(); infof("Evaluating flake for: %s", systems); return systems.map!(system => - flakeAttr(params.flakePre, system, params.flakePost) - .nixEvalJobs(cachixUrl) + flakeAttr(args.flakePre, system, args.flakePost) + .nixEvalJobs(cachixUrl, args) ) .reduce!((a, b) => a ~ b) .array @@ -436,21 +469,26 @@ Package[] nixEvalForAllSystems() .array; } -int getNixEvalWorkerCount() +int getNixEvalWorkerCount(T)(auto ref T args) + if (is(T == CiMatrixArgs) || is(T == PrintTableArgs) || is(T == CiArgs) || is(T == DeploySpecArgs)) { - return params.maxWorkers == 0 ? (threadsPerCPU() < 8 ? threadsPerCPU() : 8) : params.maxWorkers; + return args.maxWorkers == 0 ? (threadsPerCPU() < 8 ? threadsPerCPU() : 8) : args.maxWorkers; } @("getNixEvalWorkerCount") unittest { - assert(getNixEvalWorkerCount() == (threadsPerCPU() < 8 ? threadsPerCPU() : 8)); + CiMatrixArgs args; + args.maxWorkers = 0; + assert(getNixEvalWorkerCount(args) == (threadsPerCPU() < 8 ? threadsPerCPU() : 8)); + + args.maxWorkers = 4; + assert(getNixEvalWorkerCount(args) == 4); } -int getAvailableMemoryMB() +int getAvailableMemoryMB(T)(auto ref T args) + if (is(T == CiMatrixArgs) || is(T == PrintTableArgs) || is(T == CiArgs) || is(T == DeploySpecArgs)) { - - // free="$(< /proc/meminfo grep MemFree | tr -s ' ' | cut -d ' ' -f 2)" int free = "/proc/meminfo".readText .splitLines .find!(a => a.indexOf("MemFree") != -1) @@ -471,15 +509,20 @@ int getAvailableMemoryMB() .find!(a => a.indexOf("Shmem:") != -1) .front .split[1].to!int; - int maxMemoryMB = params.maxMemory == 0 ? ((free + cached + buffers + shmem) / 1024) - : params.maxMemory; + int maxMemoryMB = args.maxMemory == 0 ? ((free + cached + buffers + shmem) / 1024) + : args.maxMemory; return maxMemoryMB; } @("getAvailableMemoryMB") unittest { - assert(getAvailableMemoryMB() > 0); + CiMatrixArgs args; + args.maxMemory = 0; + assert(getAvailableMemoryMB(args) > 0); + + args.maxMemory = 1024; + assert(getAvailableMemoryMB(args) == 1024); } void saveCachixDeploySpec(Package[] packages) @@ -504,12 +547,13 @@ unittest assert(testPackageArray[1].output == deploySpec[0]["out"].str); } -void saveGHCIMatrix(Package[] packages) +void saveGHCIMatrix(T)(Package[] packages, auto ref T args) + if (is(T == CiMatrixArgs) || is(T == PrintTableArgs) || is(T == CiArgs) || is(T == DeploySpecArgs)) { auto matrix = JSONValue([ "include": JSONValue(packages.map!(pkg => pkg.toJSON()).array) ]); - string resPath = rootDir.buildPath(params.isInitial ? "matrix-pre.json" : "matrix-post.json"); + string resPath = rootDir.buildPath(args.isInitial ? "matrix-pre.json" : "matrix-post.json"); resPath.write(JSONValue(matrix).toString(JSONOptions.doNotEscapeSlashes)); } @@ -519,9 +563,11 @@ unittest import std.file : rmdirRecurse; createResultDirs(); - saveGHCIMatrix(cast(Package[]) testPackageArray); + CiMatrixArgs args; + args.isInitial = false; + saveGHCIMatrix(cast(Package[]) testPackageArray, args); JSONValue matrix = rootDir - .buildPath(params.isInitial ? "matrix-pre.json" : "matrix-post.json") + .buildPath(args.isInitial ? "matrix-pre.json" : "matrix-post.json") .readText .parseJSON; assert(testPackageArray[0].name == matrix["include"][0]["name"].str); @@ -647,24 +693,28 @@ unittest } -void printTableForCacheStatus(Package[] packages) +void printTableForCacheStatus(T)(Package[] packages, auto ref T args) + if (is(T == CiMatrixArgs) || is(T == PrintTableArgs) || is(T == CiArgs) || is(T == DeploySpecArgs)) { - if (params.precalcMatrix == "") - { - saveGHCIMatrix(packages); + static if (is(T == PrintTableArgs)) { + if (args.precalcMatrix == "") + { + saveGHCIMatrix(packages, args); + } } saveCachixDeploySpec(packages); - saveGHCIComment(convertNixEvalToTableSummary(packages, params.isInitial)); + saveGHCIComment(convertNixEvalToTableSummary(packages, args.isInitial)); } -Package checkPackage(Package pkg) +Package checkPackage(T)(Package pkg, auto ref T args) + if (is(T == CiMatrixArgs) || is(T == PrintTableArgs) || is(T == CiArgs) || is(T == DeploySpecArgs)) { import std.algorithm : canFind; import std.string : lineSplitter; import std.net.curl : HTTP, httpGet = get, HTTPStatusException; auto http = HTTP(); - http.addRequestHeader("Authorization", "Bearer " ~ params.cachixAuthToken); + http.addRequestHeader("Authorization", "Bearer " ~ args.cachixAuthToken); try { @@ -695,21 +745,20 @@ unittest cacheUrl: nixosCacheEndpoint ~ storePathHash ~ ".narinfo", ); + CiMatrixArgs args; + args.cachixAuthToken = ""; + assert(!testPackage.isCached); - assert(checkPackage(testPackage).isCached); + assert(checkPackage(testPackage, args).isCached); testPackage.cacheUrl = nixosCacheEndpoint ~ "nonexistent.narinfo"; - assert(!checkPackage(testPackage).isCached); + assert(!checkPackage(testPackage, args).isCached); } -Package[] getPrecalcMatrix() +Package[] getPrecalcMatrix(PrintTableArgs args) { - auto precalcMatrixStr = params.precalcMatrix == "" ? "{\"include\": []}" : params.precalcMatrix; - enforce!MissingEnvVarsException( - params.precalcMatrix != "", - "missing environment variables: %s".fmt("precalcMatrix") - ); + auto precalcMatrixStr = args.precalcMatrix == "" ? "{\"include\": []}" : args.precalcMatrix; return parseJSON(precalcMatrixStr)["include"].array.map!((pkg) { Package result = { name: pkg["name"].str, @@ -722,5 +771,4 @@ Package[] getPrecalcMatrix() output: pkg["output"].str}; return result; }).array; - - } +} diff --git a/packages/mcl/src/src/mcl/commands/config.d b/packages/mcl/src/src/mcl/commands/config.d index 51ec954d..87917985 100755 --- a/packages/mcl/src/src/mcl/commands/config.d +++ b/packages/mcl/src/src/mcl/commands/config.d @@ -9,7 +9,6 @@ import std.string : indexOf; import argparse : Command, Description, SubCommand, Default, PositionalArgument, Placeholder, Optional, matchCmd; -import mcl.utils.env : optional, parseEnv; import mcl.utils.fetch : fetchJson; import mcl.utils.log : errorAndExit; import mcl.utils.nix : nix, queryStorePath; diff --git a/packages/mcl/src/src/mcl/commands/deploy_spec.d b/packages/mcl/src/src/mcl/commands/deploy_spec.d index 6d710616..ecdd76d7 100644 --- a/packages/mcl/src/src/mcl/commands/deploy_spec.d +++ b/packages/mcl/src/src/mcl/commands/deploy_spec.d @@ -13,12 +13,14 @@ import mcl.utils.cachix : cachixNixStoreUrl, DeploySpec, createMachineDeploySpec import mcl.utils.tui : bold; import mcl.utils.json : tryDeserializeFromJsonFile, writeJsonFile; -import mcl.commands.ci_matrix : flakeAttr, params, nixEvalJobs, SupportedSystem; +import mcl.commands.ci_matrix : flakeAttr, nixEvalJobs, SupportedSystem,CiMatrixBaseArgs; @(Command("deploy-spec", "deploy_spec") .Description("Evaluate the Nixos machine configurations in bareMetalMachines and deploy them to cachix.")) -struct DeploySpecArgs { } +struct DeploySpecArgs { + mixin CiMatrixBaseArgs!(); +} export int deploy_spec(DeploySpecArgs args) { @@ -29,7 +31,7 @@ export int deploy_spec(DeploySpecArgs args) if (!exists(deploySpecFile)) { auto nixosConfigs = flakeAttr("legacyPackages", SupportedSystem.x86_64_linux, "serverMachines") - .nixEvalJobs(params.cachixCache.cachixNixStoreUrl); + .nixEvalJobs(args.cachixCache.cachixNixStoreUrl, args); auto configsMissingFromCachix = nixosConfigs.filter!(c => !c.isCached); @@ -51,7 +53,7 @@ export int deploy_spec(DeploySpecArgs args) else { warningf("Reusing existing deploy spec at:\n'%s'", deploySpecFile.bold); - spec = deploySpecFile.tryDeserializeFromJsonFile!DeploySpec; + spec = deploySpecFile.tryDeserializeFromJsonFile!DeploySpec(); } infof("\n---\n%s\n---", spec); diff --git a/packages/mcl/src/src/mcl/commands/get_fstab.d b/packages/mcl/src/src/mcl/commands/get_fstab.d index efe3027a..270d1e70 100755 --- a/packages/mcl/src/src/mcl/commands/get_fstab.d +++ b/packages/mcl/src/src/mcl/commands/get_fstab.d @@ -6,10 +6,9 @@ import std.json : JSONValue; import std.format : fmt = format; import std.exception : enforce; -import argparse : Command, Description, NamedArgument, PositionalArgument, Required, Placeholder; +import argparse : Command, Description, NamedArgument, PositionalArgument, Required, Placeholder, EnvFallback; import mcl.utils.cachix : cachixNixStoreUrl, getCachixDeploymentApiUrl; -import mcl.utils.env : optional, parseEnv; import mcl.utils.fetch : fetchJson; import mcl.utils.nix : queryStorePath, nix; import mcl.utils.string : camelCaseToCapitalCase; @@ -36,9 +35,9 @@ export int get_fstab(GetFstabArgs args) .Description("Get the store path of the fstab file for a deployment")) struct GetFstabArgs { - @(NamedArgument(["cachix-auth-token"]).Required().Placeholder("XXX").Description("Auth Token for Cachix")) + @(NamedArgument(["cachix-auth-token"]).Required().Placeholder("XXX").Description("Auth Token for Cachix").EnvFallback("CACHIX_AUTH_TOKEN")) string cachixAuthToken; - @(NamedArgument(["cachix-cache"]).Required().Placeholder("cache").Description("Which Cachix cache to use")) + @(NamedArgument(["cachix-cache"]).Required().Placeholder("cache").Description("Which Cachix cache to use").EnvFallback("CACHIX_CACHE")) string cachixCache; @(NamedArgument(["cachix-store-url"]).Placeholder("https://...").Description("URL of the Cachix store")) diff --git a/packages/mcl/src/src/mcl/commands/host_info.d b/packages/mcl/src/src/mcl/commands/host_info.d index 65ce1333..bbf53dc3 100644 --- a/packages/mcl/src/src/mcl/commands/host_info.d +++ b/packages/mcl/src/src/mcl/commands/host_info.d @@ -16,12 +16,11 @@ import std.json; import std.process : ProcessPipes, environment; import std.bitmanip : peek; import std.format : format; -import std.system : nativeEndian = endian;; +import std.system : nativeEndian = endian; import core.stdc.string : strlen; -import argparse : Command, Description; +import argparse : Command, Description, NamedArgument, Placeholder, EnvFallback; -import mcl.utils.env : parseEnv, optional; import mcl.utils.json : toJSON; import mcl.utils.process : execute, isRoot; import mcl.utils.number : humanReadableSize; @@ -29,11 +28,6 @@ import mcl.utils.array : uniqIfSame; import mcl.utils.nix : Literal; import mcl.utils.coda : CodaApiClient, RowValues, CodaCell; -struct Params -{ - @optional() string codaApiToken; -} - string[string] cpuinfo; string[string] meminfo; @@ -60,11 +54,16 @@ string[string] getProcInfo(string fileOrData, bool file = true) @(Command("host-info", "host_info") .Description("Get information about the host machine")) -struct HostInfoArgs { } +struct HostInfoArgs { + @(NamedArgument(["coda-api-token"]) + .Placeholder("token") + .EnvFallback("CODA_API_TOKEN") + ) + string codaApiToken; +} export int host_info(HostInfoArgs args) { - const Params params = parseEnv!Params; const hostInfo = gatherHostInfo(); @@ -73,13 +72,13 @@ export int host_info(HostInfoArgs args) .toPrettyString(JSONOptions.doNotEscapeSlashes) .writeln(); - if (!params.codaApiToken) { + if (!args.codaApiToken) { writeln("No Coda API token specified -> not uploading"); return 1; } writeln("Coda API token specified -> uploading"); - auto coda = CodaApiClient(params.codaApiToken); + auto coda = CodaApiClient(args.codaApiToken); coda.uploadHostInfo(hostInfo); return 1; diff --git a/packages/mcl/src/src/mcl/commands/machine.d b/packages/mcl/src/src/mcl/commands/machine.d index 7a7c0e14..07dcb775 100755 --- a/packages/mcl/src/src/mcl/commands/machine.d +++ b/packages/mcl/src/src/mcl/commands/machine.d @@ -8,7 +8,6 @@ import mcl.utils.log : prompt; import mcl.utils.process : execute; import mcl.utils.nix : nix, toNix, Literal, mkDefault; import mcl.utils.json : toJSON, fromJSON; -import mcl.utils.env : optional, parseEnv; import mcl.utils.array : uniqArrays; import mcl.commands.host_info: Info; diff --git a/packages/mcl/src/src/mcl/commands/shard_matrix.d b/packages/mcl/src/src/mcl/commands/shard_matrix.d index aa810c21..946de5f4 100644 --- a/packages/mcl/src/src/mcl/commands/shard_matrix.d +++ b/packages/mcl/src/src/mcl/commands/shard_matrix.d @@ -15,7 +15,6 @@ import std.string : strip; import argparse : Command, Description, NamedArgument, Placeholder, EnvFallback; -import mcl.utils.env : parseEnv, optional; import mcl.utils.json : toJSON; import mcl.utils.nix : nix; import mcl.utils.path : createResultDirs, resultDir, rootDir; diff --git a/packages/mcl/src/src/mcl/utils/env.d b/packages/mcl/src/src/mcl/utils/env.d deleted file mode 100644 index 89184a84..00000000 --- a/packages/mcl/src/src/mcl/utils/env.d +++ /dev/null @@ -1,90 +0,0 @@ -module mcl.utils.env; -import mcl.utils.test; - -import std.stdio : writeln; - -struct Optional -{ -} - -auto optional() => Optional(); - -enum isOptional(alias field) = imported!`std.traits`.hasUDA!(field, Optional); - -class MissingEnvVarsException : Exception -{ - import std.exception : basicExceptionCtors; - - mixin basicExceptionCtors; -} - -T parseEnv(T)() -{ - import std.conv : to; - import std.exception : enforce; - import std.format : fmt = format; - import std.process : environment; - import std.traits : Fields; - - import mcl.utils.string : camelCaseToCapitalCase; - - T result; - string[] missingEnvVars = []; - - static foreach (idx, field; T.tupleof) - { - { - string envVarName = field.stringof.camelCaseToCapitalCase; - if (auto envVar = environment.get(envVarName)) - result.tupleof[idx] = envVar.to!(typeof(field)); - else if (!isOptional!field) - missingEnvVars ~= envVarName; - } - } - - enforce!MissingEnvVarsException( - missingEnvVars.length == 0, - "missing environment variables:\n%(* %s\n%)".fmt(missingEnvVars) - ); - - static if (__traits(hasMember, T, "setup")) - result.setup(); - - return result; -} - -version (unittest) -{ - struct Config - { - @optional() string opt; - int a; - string b; - float c = 1.0; - } -} - -@("parseEnv") -unittest -{ - import std.process : environment; - import std.exception : assertThrown; - - environment["A"] = "1"; - environment["B"] = "2"; - environment["C"] = "1.0"; - - auto config = parseEnv!Config; - - assert(config.a == 1); - assert(config.b == "2"); - assert(config.c == 1.0); - assert(config.opt is null); - - environment["OPT"] = "3"; - config = parseEnv!Config; - assert(config.opt == "3"); - - environment.remove("A"); - assertThrown(config = parseEnv!Config, "missing environment variables:\nA\n"); -} From 79fb35f4f35483b669b70972d706198eac341c09 Mon Sep 17 00:00:00 2001 From: Simeon Armenchev Date: Fri, 21 Nov 2025 12:37:36 +0200 Subject: [PATCH 24/24] fix(): Review Compliance --- packages/mcl/src/src/mcl/commands/ci_matrix.d | 6 +++- packages/mcl/src/src/mcl/commands/config.d | 31 ++++++++++--------- .../mcl/src/src/mcl/commands/deploy_spec.d | 2 +- packages/mcl/src/src/mcl/commands/host_info.d | 11 ++++--- packages/mcl/src/src/mcl/commands/machine.d | 5 --- 5 files changed, 30 insertions(+), 25 deletions(-) diff --git a/packages/mcl/src/src/mcl/commands/ci_matrix.d b/packages/mcl/src/src/mcl/commands/ci_matrix.d index 10ea42c6..e54a1e64 100755 --- a/packages/mcl/src/src/mcl/commands/ci_matrix.d +++ b/packages/mcl/src/src/mcl/commands/ci_matrix.d @@ -151,11 +151,13 @@ mixin template CiMatrixBaseArgs() @(NamedArgument(["flake-pre"]) .Placeholder("prefix") + .EnvFallback("FLAKE_PRE") ) string flakePre = "checks"; @(NamedArgument(["flake-post"]) .Placeholder("postfix") + .EnvFallback("FLAKE_POST") ) string flakePost; @@ -169,7 +171,9 @@ mixin template CiMatrixBaseArgs() ) int maxMemory; - @(NamedArgument(["initial"]) + @(NamedArgument(["is-initial"]) + .Description("Is this the initial run of the CI?") + .EnvFallback("IS_INITIAL") ) bool isInitial; diff --git a/packages/mcl/src/src/mcl/commands/config.d b/packages/mcl/src/src/mcl/commands/config.d index 87917985..a2e2cf5d 100755 --- a/packages/mcl/src/src/mcl/commands/config.d +++ b/packages/mcl/src/src/mcl/commands/config.d @@ -1,13 +1,14 @@ module mcl.commands.config; import std.algorithm : canFind; -import std.array : array; +import std.array : array, join; import std.process : ProcessPipes, Redirect, wait, environment; import std.range : drop, front; import std.stdio : writeln; import std.string : indexOf; +import std.conv : to; -import argparse : Command, Description, SubCommand, Default, PositionalArgument, Placeholder, Optional, matchCmd; +import argparse : Command, Description, SubCommand, Default, PositionalArgument, Placeholder, Optional, matchCmd, NamedArgument, EnvFallback; import mcl.utils.fetch : fetchJson; import mcl.utils.log : errorAndExit; @@ -60,18 +61,23 @@ struct HomeArgs ) cmd; } +enum HomeConfigType { desktop, server } + @(Command("apply").Description("Apply user configuration")) struct HomeApplyArgs { @(PositionalArgument(0).Placeholder("desktop/server").Description("Type of home configuration")) - string type; + HomeConfigType type; + + @(NamedArgument(["user"]).Optional().Placeholder("username").Description("Username to apply the configuration for").EnvFallback("USER")) + string user = ""; } @(Command("edit").Description("Edit user configuration")) struct HomeEditArgs { @(PositionalArgument(0).Placeholder("desktop/server").Description("Type of home configuration")) - string type; + HomeConfigType type; } @(Command("start-vm").Description("Start a VM")) @@ -143,17 +149,17 @@ int edit(string type, string path) } } -int apply(string type, string value) +int apply(string type, string[] args) { - writeln("Applying ", type, " configuration from: ", value); - return executeCommand("just switch-" ~ type ~ " " ~ value); + writeln("Applying ", type, " configuration from: ", args); + return executeCommand("just switch-" ~ type ~ " " ~ args.join(" ")); } int sys(SysArgs args) { return args.cmd.matchCmd!( - (SysApplyArgs a) => apply("system", a.machineName), - (SysEditArgs a) => edit("system", a.machineName), + (SysApplyArgs a) => "system".apply([a.machineName]), + (SysEditArgs a) => "system".edit(a.machineName), (UnknownCommandArgs a) => unknown_command(a) ); } @@ -162,11 +168,8 @@ int home(HomeArgs args) { return args.cmd.matchCmd!( - (HomeApplyArgs a) { - writeln("Applying home configuration from: ", a.type); - return executeCommand("just switch-home " ~ a.type); - }, - (HomeEditArgs a) => "user".edit(a.type), + (HomeApplyArgs a) => "home".apply([a.type.to!string, a.user]), + (HomeEditArgs a) => "user".edit(a.type.to!string), (UnknownCommandArgs a) => unknown_command(a) ); } diff --git a/packages/mcl/src/src/mcl/commands/deploy_spec.d b/packages/mcl/src/src/mcl/commands/deploy_spec.d index ecdd76d7..337a77a9 100644 --- a/packages/mcl/src/src/mcl/commands/deploy_spec.d +++ b/packages/mcl/src/src/mcl/commands/deploy_spec.d @@ -60,7 +60,7 @@ export int deploy_spec(DeploySpecArgs args) infof("%s machines will be deployed.", spec.agents.length); if (!spec.agents.length) - return 1; + return 0; spawnProcessInline([ "cachix", "deploy", "activate", deploySpecFile, "--async" diff --git a/packages/mcl/src/src/mcl/commands/host_info.d b/packages/mcl/src/src/mcl/commands/host_info.d index bbf53dc3..03de6ef1 100644 --- a/packages/mcl/src/src/mcl/commands/host_info.d +++ b/packages/mcl/src/src/mcl/commands/host_info.d @@ -57,9 +57,12 @@ string[string] getProcInfo(string fileOrData, bool file = true) struct HostInfoArgs { @(NamedArgument(["coda-api-token"]) .Placeholder("token") - .EnvFallback("CODA_API_TOKEN") - ) + .EnvFallback("CODA_API_TOKEN")) string codaApiToken; + + @(NamedArgument(["upload-to-coda"]) + .Description("Upload the host info to Coda")) + bool uploadToCoda = false; } export int host_info(HostInfoArgs args) @@ -72,7 +75,7 @@ export int host_info(HostInfoArgs args) .toPrettyString(JSONOptions.doNotEscapeSlashes) .writeln(); - if (!args.codaApiToken) { + if (args.uploadToCoda &&!args.codaApiToken) { writeln("No Coda API token specified -> not uploading"); return 1; } @@ -81,7 +84,7 @@ export int host_info(HostInfoArgs args) auto coda = CodaApiClient(args.codaApiToken); coda.uploadHostInfo(hostInfo); - return 1; + return 0; } diff --git a/packages/mcl/src/src/mcl/commands/machine.d b/packages/mcl/src/src/mcl/commands/machine.d index 07dcb775..77908142 100755 --- a/packages/mcl/src/src/mcl/commands/machine.d +++ b/packages/mcl/src/src/mcl/commands/machine.d @@ -35,7 +35,6 @@ struct User { struct UserInfo { - bool isNormalUser; string description; string[] extraGroups; string hashedPassword; @@ -59,7 +58,6 @@ User getUser(string userName) auto userJson = nix.eval!JSONValue("users/" ~ userName ~ "/user-info.nix", ["--file"]); User user; user.userName = userName; - user.userInfo.isNormalUser = userJson["userInfo"]["isNormalUser"].boolean; user.userInfo.description = userJson["userInfo"]["description"].str; user.userInfo.extraGroups = userJson["userInfo"]["extraGroups"].array.map!(a => a.str).array; user.userInfo.hashedPassword = userJson["userInfo"]["hashedPassword"].str; @@ -189,7 +187,6 @@ User createUser(CreateMachineArgs args) { User user; user.userName = args.userName != "" ? args.userName : prompt!string("Enter the new username"); user.userInfo.description = args.description != "" ? args.description : prompt!string("Enter the user's description/full name"); - user.userInfo.isNormalUser = args.isNormalUser || prompt!bool("Is this a normal or root user"); user.userInfo.extraGroups = (args.extraGroups != "" ? args.extraGroups : prompt!string("Enter the user's extra groups (comma delimited)", getGroups())).split(",").map!(strip).array; createUserDir(user); return user; @@ -400,8 +397,6 @@ struct CreateMachineArgs string machineName; @(NamedArgument(["description"]).Placeholder("description").Description("Description of the user")) string description; - @(NamedArgument(["is-normal-user"]).Placeholder("true/false").Description("Is this a normal user")) - bool isNormalUser; @(NamedArgument(["extra-groups"]).Placeholder("group1,group2").Description("Extra groups for the user")) string extraGroups; @(NamedArgument(["machine-type"]).Placeholder("desktop/server/container").Description("Type of machine"))