Skip to content

Commit

Permalink
init compatibility with: nix bundle, nix run, lib.getExe (#306)
Browse files Browse the repository at this point in the history
This makes all devshells compatible with `lib.getExe`, `nix run`and `nix bundle` by setting `meta.mainProgram` and creating an executable under `$out/bin/${meta.mainProgram}`

Also remove the now unnecessary flakeApp workaround from the docs and replace it with updated instructions.

My main motivation behind this is to create portable executable devshells via `nix bundle`.
For example, to bundle the devShell of devshell itself into a static executable:

```sh
nix bundle --bundler github:DavHau/nix-portable github:numtide/devshell#devShells.x86_64-linux.default
```
  • Loading branch information
DavHau authored Apr 15, 2024
1 parent 2d45b54 commit 2c8e04e
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 17 deletions.
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,7 @@ nix-build shell.nix | cachix push <mycache>

### 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 '.#<myapp>' -- <devshell-command> <and-args>
Expand All @@ -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

Expand Down
18 changes: 11 additions & 7 deletions docs/src/flake-app.md
Original file line number Diff line number Diff line change
@@ -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.<system>.<myapp>`, 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 '.#<myapp>' -- <devshell-command> <and-args>
nix run '.#devShells.<system>.<myshell>' -- <devshell-command> <and-args>
```

To simplify this command further, re-expose the devshell under `packages.<system>.<myshell>`. This allows running it like this:

```sh
nix run '.#<myshell>' -- <devshell-command> <and-args>
```

For example, given the following `flake.nix`:
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
'nix-instantiate ./nixpkgs-mkshell.nix'
'';
};
# expose devshell as an executable package
default = devShells.default;
};

devShells.default = devshell.fromTOML ./devshell.toml;
Expand All @@ -46,8 +48,6 @@
nixpkgs = pkgs;
};

apps.default = devShells.default.flakeApp;

checks =
with pkgs.lib;
pipe (import ./tests { inherit pkgs; }) [
Expand Down
31 changes: 27 additions & 4 deletions modules/devshell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
with lib;
let
cfg = config.devshell;
sanitizedName = strings.sanitizeDerivationName cfg.name;

ansi = import ../nix/ansi.nix;

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand Down
16 changes: 16 additions & 0 deletions tests/core/devshell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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
'';
}

0 comments on commit 2c8e04e

Please sign in to comment.