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
lefthook.enable = true;
task.",".tasks = { ... };
}
```

View file

@ -4,44 +4,79 @@
config,
...
}: let
inherit (lib) mkEnableOption mkOption mkIf 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}
'';
};
inherit (lib) mkOption types;
in {
options.process-compose = {
enable =
mkEnableOption "Process-Compose"
// {
default = cfg.config != {};
options.process-compose = mkOption {
type = types.attrsOf (types.submodule ({
config,
name,
...
}: {
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 {
type = types.str;
default = "pc";
description = ''
Alias for `process-compose`.
'';
};
config = mkOption {
type = types.attrs;
default = {};
description = ''
Configure process-compose here.
'';
};
config = rec {
configFile = (pkgs.formats.yaml {}).generate "process-compose.yaml" config.config;
pcAlias = pkgs.writeTextFile {
name = "pc-alias";
destination = "/bin/${config.alias}";
executable = true;
text = let
configScript =
if config.lazy
then
# sh
"$(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 {
packages = [pcAlias];
};
config.packages = map (val: val.pcAlias) (builtins.attrValues config.process-compose);
}

View file

@ -14,7 +14,7 @@ in {
script = let
shell = devshell.mkShell {
imports = [module];
process-compose.enable = true;
process-compose."default" = {};
};
in
# sh
@ -23,6 +23,21 @@ in {
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,
...
}: let
inherit (lib) mapAttrs mkEnableOption mkOption mkIf 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;
inherit (lib) mapAttrs mkOption types;
generator = name: value:
pkgs.writeTextFile {
inherit name;
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 {
options.task = {
enable =
mkEnableOption "Task"
// {
default = cfg.tasks != {};
options.task = mkOption {
type = types.attrsOf (types.submodule ({
config,
name,
...
}: {
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 {
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 = "task";
description = ''
Alias for `task`, eg. set to `,` to be able to run `, --list-all`.
'';
};
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.
'';
};
config = let
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}'';
})
config.tasks;
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;
taskfile = generator "taskfile" {
version = 3;
inherit (config) 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/${config.alias}";
executable = true;
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 {
packages = [taskAlias];
};
config.packages = map (val: val.taskAlias) (builtins.attrValues config.task);
}

View file

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

View file

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