init compatibility with: nix bundle, nix run, lib.getExe (#306)

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
```
This commit is contained in:
DavHau 2024-04-15 22:44:12 +07:00 committed by GitHub
parent 2d45b54ca4
commit 2c8e04e5c2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 58 additions and 17 deletions

View File

@ -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>
@ -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

View File

@ -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 '.#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 '.#<myapp>' -- <devshell-command> <and-args>
nix run '.#<myshell>' -- <devshell-command> <and-args>
```
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

View File

@ -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; }) [

View File

@ -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;

View File

@ -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
'';
}