diff --git a/README.md b/README.md index 741b5b63..c8987cf3 100644 --- a/README.md +++ b/README.md @@ -118,9 +118,7 @@ nix-build shell.nix | cachix push ### Runnable as a Nix application -Devshells are runnable as Nix applications (via `nix run`). This makes it -possible to run commands defined in your devshell without entering a -`nix-shell` or `nix develop` session: +Devshells are runnable (via `nix run`). This makes it possible to run commands defined in your devshell without entering a `nix-shell` or `nix develop` session: ```sh nix run '.#' -- @@ -132,7 +130,7 @@ This project itself exposes a Nix application; you can try it out with: nix run 'github:numtide/devshell' -- hello ``` -See [here](docs/flake-app.md) for how to export your devshell as a flake app. +See [here](docs/src/flake-app.md) for more details. ## TODO diff --git a/docs/src/flake-app.md b/docs/src/flake-app.md index a506a73f..252accab 100644 --- a/docs/src/flake-app.md +++ b/docs/src/flake-app.md @@ -1,12 +1,16 @@ -# Using a devshell as a Nix application +# Using a devshell as a Nix package -Devshells provide the attribute `flakeApp`, which contains an attribute set -suitable for use as an entry in the `apps` flake output structure. Export this -attribute under `apps..`, and then you can run commands within -your devshell as follows: +Devshells can be treated as executable packages. This allows running commands inside a devshell's environment without having to enter it first via `nix-shell` or `nix develop`. +Each devshell in a flake can be executed using nix run: ```sh -nix run '.#' -- +nix run '.#devShells..' -- +``` + +To simplify this command further, re-expose the devshell under `packages..`. This allows running it like this: + +```sh +nix run '.#' -- ``` For example, given the following `flake.nix`: @@ -18,7 +22,7 @@ For example, given the following `flake.nix`: outputs = { self, flake-utils, devshell, nixpkgs }: flake-utils.lib.eachDefaultSystem (system: { - apps.devshell = self.outputs.devShells.${system}.default.flakeApp; + packages.devshell = self.outputs.devShells.${system}.default; devShells.default = let diff --git a/flake.nix b/flake.nix index eb28fe0f..0f2c22cd 100644 --- a/flake.nix +++ b/flake.nix @@ -36,6 +36,8 @@ 'nix-instantiate ./nixpkgs-mkshell.nix' ''; }; + # expose devshell as an executable package + default = devShells.default; }; devShells.default = devshell.fromTOML ./devshell.toml; @@ -46,8 +48,6 @@ nixpkgs = pkgs; }; - apps.default = devShells.default.flakeApp; - checks = with pkgs.lib; pipe (import ./tests { inherit pkgs; }) [ diff --git a/modules/devshell.nix b/modules/devshell.nix index 2b5186a2..7da291d7 100644 --- a/modules/devshell.nix +++ b/modules/devshell.nix @@ -2,6 +2,7 @@ with lib; let cfg = config.devshell; + sanitizedName = strings.sanitizeDerivationName cfg.name; ansi = import ../nix/ansi.nix; @@ -186,14 +187,31 @@ let ''; # Builds the DEVSHELL_DIR with all the dependencies - devshell_dir = pkgs.buildEnv { - name = "devshell-dir"; + devshell_dir = pkgs.buildEnv rec { + name = "${sanitizedName}-dir"; paths = cfg.packages; postBuild = '' substitute ${envBash} $out/env.bash --subst-var-by DEVSHELL_DIR $out substitute ${entrypoint} $out/entrypoint --subst-var-by DEVSHELL_DIR $out chmod +x $out/entrypoint + + mainProgram="${meta.mainProgram}" + # ensure mainProgram doesn't collide + if [ -e "$out/bin/$mainProgram" ]; then + echo "Warning: Cannot create entry point for this devshell at '\$out/bin/$mainProgram' because an executable with that name already exists." >&2 + echo "Set meta.mainProgram to something else than '$mainProgram'." >&2 + else + # if $out/bin is a single symlink, transform it into a directory tree + # (buildEnv does that when there is only one package in the environment) + if [ -L "$out/bin" ]; then + mv "$out/bin" bin-tmp + mkdir "$out/bin" + ln -s bin-tmp/* "$out/bin/" + fi + ln -s $out/entrypoint "$out/bin/$mainProgram" + fi ''; + meta.mainProgram = config.meta.mainProgram or sanitizedName; }; # Returns a list of all the input derivation ... for a derivation. @@ -421,11 +439,16 @@ in # Use a naked derivation to limit the amount of noise passed to nix-shell. shell = mkNakedShell { - name = strings.sanitizeDerivationName cfg.name; - inherit (cfg) meta; + name = sanitizedName; + meta = + # set default for meta.mainProgram here to gain compatibility with: + # `lib.getExe`, `nix run`, `nix bundle`, etc. + {mainProgram = cfg.package.meta.mainProgram;} + // cfg.meta; profile = cfg.package; passthru = { inherit config; + # keep flakeApp attribute for backward compatibility flakeApp = mkFlakeApp "${devshell_dir}/entrypoint"; hook = mkSetupHook "${devshell_dir}/env.bash"; inherit (config._module.args) pkgs; diff --git a/tests/core/devshell.nix b/tests/core/devshell.nix index e994b2b1..47562539 100644 --- a/tests/core/devshell.nix +++ b/tests/core/devshell.nix @@ -80,4 +80,20 @@ # Packages available through entrypoint in pure mode entrypoint_clean --pure --env-bin env --prj-root . /bin/sh -c 'type -p git' ''; + + # Use devshell as executable + devshell-executable-1 = + let + shell = devshell.mkShell { + devshell.name = "devshell-executable-1"; + devshell.packages = [ pkgs.hello ]; + }; + in + runTest "devshell-executable-1" { } '' + # Devshell is executable + assert -x ${pkgs.lib.getExe shell} + + # Packages inside the devshell are executable + ${pkgs.lib.getExe shell} hello + ''; }