refactor(modules)!: allow multiple instances for taskfile & process-compose

This commit is contained in:
technofab 2025-12-19 16:05:00 +01:00
parent 25fb9162ff
commit 5262901404
Signed by: technofab
SSH key fingerprint: SHA256:bV4h88OqS/AxjbPn66uUdvK9JsgIW4tv3vwJQ8tpMqQ
6 changed files with 199 additions and 134 deletions

View file

@ -13,6 +13,8 @@ devshell.mkShell {
# use the modules # use the modules
lefthook.enable = true; lefthook.enable = true;
task.",".tasks = { ... };
} }
``` ```

View file

@ -4,44 +4,79 @@
config, config,
... ...
}: let }: let
inherit (lib) mkEnableOption mkOption mkIf types; inherit (lib) mkOption types;
cfg = config.process-compose;
configFile = (pkgs.formats.yaml {}).generate "process-compose.yaml" cfg.config;
pcAlias = pkgs.writeTextFile {
name = "pc-alias";
destination = "/bin/${cfg.alias}";
executable = true;
text =
# sh
''
${pkgs.process-compose}/bin/process-compose --config "${configFile}" ''${@:1}
'';
};
in { in {
options.process-compose = { options.process-compose = mkOption {
enable = type = types.attrsOf (types.submodule ({
mkEnableOption "Process-Compose" config,
// { name,
default = cfg.config != {}; ...
}: {
options = {
alias = mkOption {
type = types.str;
default =
if name == "default"
then "pc"
else name;
description = ''
Alias for `process-compose`.
Defaults to `"pc"` if `${name}` is `"default"`
'';
};
lazy = mkOption {
type = types.bool;
default = true;
description = ''
Whether the process compose config should be built on-demand/lazily.
It will probably not land in the gcroot and thus might get cleaned up with every gc.
On the other hand, this way loading the devshell is faster. Decide for yourself :)
'';
};
config = mkOption {
type = types.attrs;
default = {};
description = ''
Configure process-compose here.
'';
};
configFile = mkOption {
internal = true;
type = types.package;
};
pcAlias = mkOption {
internal = true;
type = types.package;
};
}; };
alias = mkOption { config = rec {
type = types.str; configFile = (pkgs.formats.yaml {}).generate "process-compose.yaml" config.config;
default = "pc"; pcAlias = pkgs.writeTextFile {
description = '' name = "pc-alias";
Alias for `process-compose`. destination = "/bin/${config.alias}";
''; executable = true;
}; text = let
config = mkOption { configScript =
type = types.attrs; if config.lazy
default = {}; then
description = '' # sh
Configure process-compose here. "$(nix build '${builtins.unsafeDiscardOutputDependency configFile.drvPath}^*' --no-link --print-out-paths)"
''; else configFile;
}; in
# sh
''
CONFIG_FILE="${configScript}"
${pkgs.process-compose}/bin/process-compose --config "$CONFIG_FILE" ''${@:1}
'';
};
};
}));
default = {};
description = ''
Define your process-compose instances here.
'';
}; };
config = mkIf cfg.enable { config.packages = map (val: val.pcAlias) (builtins.attrValues config.process-compose);
packages = [pcAlias];
};
} }

View file

@ -14,7 +14,7 @@ in {
script = let script = let
shell = devshell.mkShell { shell = devshell.mkShell {
imports = [module]; imports = [module];
process-compose.enable = true; process-compose."default" = {};
}; };
in in
# sh # sh
@ -23,6 +23,21 @@ in {
assert "-f ${shell}/bin/pc" "/bin/pc should exist" assert "-f ${shell}/bin/pc" "/bin/pc should exist"
''; '';
} }
{
name = "alias";
type = "script";
script = let
shell = devshell.mkShell {
imports = [module];
process-compose."pctest" = {};
};
in
# sh
''
${ntlib.helpers.scriptHelpers}
assert "-f ${shell}/bin/pctest" "/bin/pctest should exist"
'';
}
]; ];
}; };
} }

View file

@ -4,99 +4,118 @@
config, config,
... ...
}: let }: let
inherit (lib) mapAttrs mkEnableOption mkOption mkIf types; inherit (lib) mapAttrs mkOption types;
cfg = config.task;
patchedTasks = mapAttrs (_name: value: let
taskDir = value.dir or "";
absolutePathOrTemplate = (builtins.substring 0 1 taskDir) == "/" || (builtins.substring 0 1 taskDir) == "{";
in
value
// {
dir =
if absolutePathOrTemplate
then taskDir
else ''{{env "TASK_ROOT_DIR" | default .USER_WORKING_DIR}}/${taskDir}'';
})
cfg.tasks;
generator = name: value: generator = name: value:
pkgs.writeTextFile { pkgs.writeTextFile {
inherit name; inherit name;
text = builtins.toJSON value; text = builtins.toJSON value;
}; };
# NOTE: this requires python just to convert json to yaml, since json is valid yaml we just ignore that
# generator = (pkgs.formats.yaml {}).generate;
taskfile = generator "taskfile" {
version = 3;
inherit (cfg) interval;
tasks = patchedTasks;
};
# when using a , as alias for example the store path looks weird.
# This way it can be identified as being the task alias
taskAlias = pkgs.writeTextFile {
name = "task-alias";
destination = "/bin/${cfg.alias}";
executable = true;
text = let
taskfileScript =
if cfg.lazy
then
# sh
"$(nix build '${builtins.unsafeDiscardOutputDependency taskfile.drvPath}^*' --no-link --print-out-paths)"
else taskfile;
in
# sh
''
TASKFILE="${taskfileScript}"
STATE_DIR="''${REN_STATE:-''${DEVENV_STATE:-''${PRJ_CACHE_HOME}}}"
ROOT_DIR="''${REN_ROOT:-''${DEVENV_ROOT:-''${PRJ_ROOT}}}"
TASK_TEMP_DIR="''${STATE_DIR}/.task" \
TASK_ROOT_DIR="$ROOT_DIR" \
${pkgs.go-task}/bin/task --taskfile "$TASKFILE" ''${@:1}
'';
};
in { in {
options.task = { options.task = mkOption {
enable = type = types.attrsOf (types.submodule ({
mkEnableOption "Task" config,
// { name,
default = cfg.tasks != {}; ...
}: {
options = {
lazy = mkOption {
type = types.bool;
default = true;
description = ''
Whether the taskfile should be built on-demand/lazily.
It will probably not land in the gcroot and thus might get cleaned up with every gc.
On the other hand, this way loading the devshell is faster. Decide for yourself :)
'';
};
alias = mkOption {
type = types.str;
default =
if name == "default"
then "task"
else name;
description = ''
Alias for `task`, eg. set to `,` to be able to run `, --list-all`.
Defaults to `"task"` if `${name}` is `"default"`
'';
};
interval = mkOption {
type = types.str;
default = "5000ms";
description = ''
Interval for `task` to check for filesystem changes/watcher updates.
'';
};
tasks = mkOption {
type = types.attrs;
default = {};
description = ''
Configure all your tasks here.
'';
};
taskfile = mkOption {
internal = true;
type = types.package;
};
taskAlias = mkOption {
internal = true;
type = types.package;
};
}; };
lazy = mkOption { config = let
type = types.bool; patchedTasks = mapAttrs (_name: value: let
default = true; taskDir = value.dir or "";
description = '' absolutePathOrTemplate = (builtins.substring 0 1 taskDir) == "/" || (builtins.substring 0 1 taskDir) == "{";
Whether the taskfile should be built on-demand/lazily. in
It will probably not land in the gcroot and thus might get cleaned up with every gc. value
On the other hand, this way loading the devshell is faster. Decide for yourself :) // {
''; dir =
}; if absolutePathOrTemplate
alias = mkOption { then taskDir
type = types.str; else ''{{env "TASK_ROOT_DIR" | default .USER_WORKING_DIR}}/${taskDir}'';
default = "task"; })
description = '' config.tasks;
Alias for `task`, eg. set to `,` to be able to run `, --list-all`. in rec {
''; # NOTE: this requires python just to convert json to yaml, since json is valid yaml we just ignore that
}; # generator = (pkgs.formats.yaml {}).generate;
interval = mkOption { taskfile = generator "taskfile" {
type = types.str; version = 3;
default = "5000ms"; inherit (config) interval;
description = '' tasks = patchedTasks;
Interval for `task` to check for filesystem changes/watcher updates. };
''; # when using a , as alias for example the store path looks weird.
}; # This way it can be identified as being the task alias
tasks = mkOption { taskAlias = pkgs.writeTextFile {
type = types.attrs; name = "task-alias";
default = {}; destination = "/bin/${config.alias}";
description = '' executable = true;
Configure all your tasks here. text = let
''; taskfileScript =
}; if config.lazy
then
# sh
"$(nix build '${builtins.unsafeDiscardOutputDependency taskfile.drvPath}^*' --no-link --print-out-paths)"
else taskfile;
in
# sh
''
TASKFILE="${taskfileScript}"
STATE_DIR="''${REN_STATE:-''${DEVENV_STATE:-''${PRJ_CACHE_HOME}}}"
ROOT_DIR="''${REN_ROOT:-''${DEVENV_ROOT:-''${PRJ_ROOT}}}"
TASK_TEMP_DIR="''${STATE_DIR}/.task" \
TASK_ROOT_DIR="$ROOT_DIR" \
${pkgs.go-task}/bin/task --taskfile "$TASKFILE" ''${@:1}
'';
};
};
}));
default = {};
description = ''
Define your Taskfile instances here.
'';
}; };
config = mkIf cfg.enable { config.packages = map (val: val.taskAlias) (builtins.attrValues config.task);
packages = [taskAlias];
};
} }

View file

@ -14,7 +14,7 @@ in {
script = let script = let
shell = devshell.mkShell { shell = devshell.mkShell {
imports = [module]; imports = [module];
task.enable = true; task."default" = {};
}; };
in in
# sh # sh
@ -29,10 +29,7 @@ in {
script = let script = let
shell = devshell.mkShell { shell = devshell.mkShell {
imports = [module]; imports = [module];
task = { task."," = {};
enable = true;
alias = ",";
};
}; };
in in
# sh # sh

View file

@ -25,12 +25,9 @@ in {
treefmtWrapper treefmtWrapper
]; ];
task = { task.",".tasks = {
alias = ","; "hello" = {
tasks = { cmd = "echo world!";
"hello" = {
cmd = "echo world!";
};
}; };
}; };
@ -54,7 +51,7 @@ in {
}; };
}; };
process-compose.config.processes = { process-compose."default".config.processes = {
hello.command = "echo 'Hello World'"; hello.command = "echo 'Hello World'";
pc = { pc = {
command = "echo 'From Process Compose'"; command = "echo 'From Process Compose'";