Skip to content

Tasks

New in version 1.2

Tasks allow you to form dependencies between code, executed in parallel.

Defining tasks

devenv.nix
{ pkgs, ... }:

{
  tasks."myapp:hello" = {
    exec = ''echo "Hello, world!"'';
  };
}
$ devenv tasks run myapp:hello
Running tasks     myapp:hello
Succeeded         myapp:hello         9ms
1 Succeeded                           10.14ms

New in version 1.7

You can also run all tasks in a namespace by providing just the namespace prefix:

$ devenv tasks run myapp
Running tasks     myapp:hello myapp:build myapp:test
Succeeded         myapp:hello           9ms
Succeeded         myapp:build         120ms
Succeeded         myapp:test          350ms
3 Succeeded                           479.14ms

enterShell / enterTest

If you'd like the tasks to run as part of the enterShell or enterTest:

devenv.nix
{ pkgs, lib, config, ... }:

{
  tasks = {
    "bash:hello" = {
      exec = "echo 'Hello world from bash!'";
      before = [ "devenv:enterShell" "devenv:enterTest" ];
    };
  };
}
$ devenv shell
...
Running tasks     devenv:enterShell
Succeeded         devenv:pre-commit:install 25ms
Succeeded         bash:hello                 9ms
Succeeded         devenv:enterShell         13ms
3 Succeeded                                 28.14ms

Using your favourite language

Tasks can also use another package for execution, for example when entering the shell:

devenv.nix
{ pkgs, lib, config, ... }:

{
  tasks = {
    "python:hello" = {
      exec = ''
        print("Hello world from Python!")
      '';
      package = config.languages.python.package;
    };
  };
}

Avoiding running expensive exec via status check

If you define a status command, it will be executed first and if it returns 0, exec will be skipped.

devenv.nix
{ pkgs, lib, config, ... }:

{
  tasks = {
    "myapp:migrations" = {
      exec = "db-migrate";
      status = "db-needs-migrations";
    };
  };
}

Tasks using the status attribute will also cache their outputs. When a task is skipped because its status command returns success, the output from the most recent successful run will be restored and passed to dependent tasks.

Executing tasks only when files have been modified

You can specify a list of files to monitor with execIfModified. The task will only run if any of these files have been modified since the last successful run.

devenv.nix
{ pkgs, lib, config, ... }:

{
  tasks = {
    "myapp:build" = {
      exec = "npm run build";
      execIfModified = [
        "src"
        "package.json"
      ];
    };
  };
}

This is particularly useful for tasks that depend on specific files and don't need to run if those files haven't changed.

The system tracks both file modification times and content hashes to detect actual changes. If a file's timestamp changes but its content remains the same (which can happen when touching a file or when saving without making changes), the task will be skipped.

When a task is skipped due to no file changes, any previous outputs from that task are preserved and passed to dependent tasks, making the caching more efficient.

Inputs / Outputs

Tasks support passing inputs and produce outputs, both as JSON objects:

  • $DEVENV_TASK_INPUT: JSON object of tasks."myapp:mytask".input.
  • $DEVENV_TASKS_OUTPUTS: JSON object with dependent tasks as keys and their outputs as values.
  • $DEVENV_TASK_OUTPUT_FILE: a writable file with tasks' outputs in JSON.
devenv.nix
{ pkgs, lib, config, ... }:

{
  tasks = {
    "myapp:mytask" = {
      exec = ''
        echo $DEVENV_TASK_INPUTS> $DEVENV_ROOT/input.json
        echo '{ "output": 1 }' > $DEVENV_TASK_OUTPUT_FILE
        echo $DEVENV_TASKS_OUTPUTS > $DEVENV_ROOT/outputs.json
      '';
      input = {
        value = 1;
      };
    };
  };
}

SDK using Task Server Protocol

See Task Server Protocol for a proposal how defining tasks in your favorite language would look like.