chore: initial commit

This commit is contained in:
technofab 2025-07-31 12:37:19 +02:00
commit fbacfc149b
No known key found for this signature in database
21 changed files with 748 additions and 0 deletions

3
.envrc Normal file
View file

@ -0,0 +1,3 @@
source $(fetchurl https://gitlab.com/rensa-nix/direnv/-/raw/v0.2.0/direnvrc "sha256-PFFxlZWNz/LLuNHA1Zpu2qdC3MF+oukv/TxFj5Utixk=")
REN_FLAKE_ATTR=__std
use ren //repo/devShells/default

19
.nix/repo/benchmark.nix Normal file
View file

@ -0,0 +1,19 @@
{
inputs,
cell,
}: {
bench = inputs.nixpkgs.writeShellApplication {
name = "benchmark";
runtimeInputs = [inputs.nixpkgs.hyperfine];
text = ''
echo "Comparison cases first:"
hyperfine -w 3 \
'nix-instantiate ${inputs.self}/benchmark/shared.nix' \
'nix-instantiate ${inputs.self}/benchmark/empty.nix'
echo "Now real benchmark:"
hyperfine -w 3 \
'nix-instantiate ${inputs.self}/benchmark/nixpkgs-shell.nix' \
'nix-instantiate ${inputs.self}/benchmark/devshell.nix'
'';
};
}

17
.nix/repo/devShells.nix Normal file
View file

@ -0,0 +1,17 @@
{
inputs,
cell,
}: let
devshell = import "${inputs.self}/lib" {
pkgs = inputs.nixpkgs;
};
in {
default = devshell.mkShell {
packages = [inputs.nixpkgs.alejandra];
env."HELLO".value = "world!";
enterShellCommands.test = {
text = "echo Hello $HELLO";
deps = ["env"];
};
};
}

28
.nix/repo/flake.lock generated Normal file
View file

@ -0,0 +1,28 @@
{
"nodes": {
"nixtest-lib": {
"locked": {
"dir": "lib",
"lastModified": 1753957623,
"narHash": "sha256-kdImwKx57N0QL8HPUUb5ADwXFgSjaNOk39b/eKlzyTo=",
"owner": "TECHNOFAB",
"repo": "nixtest",
"rev": "22b43c9fe83be73c3f0648bbb54bc3c1cf7f96df",
"type": "gitlab"
},
"original": {
"dir": "lib",
"owner": "TECHNOFAB",
"repo": "nixtest",
"type": "gitlab"
}
},
"root": {
"inputs": {
"nixtest-lib": "nixtest-lib"
}
}
},
"root": "root",
"version": 7
}

6
.nix/repo/flake.nix Normal file
View file

@ -0,0 +1,6 @@
{
inputs = {
nixtest-lib.url = "gitlab:TECHNOFAB/nixtest?dir=lib";
};
outputs = i: i;
}

15
.nix/repo/tests.nix Normal file
View file

@ -0,0 +1,15 @@
{
inputs,
cell,
}: let
pkgs = inputs.nixpkgs;
ntlib = inputs.nixtest-lib.lib {inherit pkgs;};
devshell = import "${inputs.self}/lib" {inherit pkgs;};
in {
tests = ntlib.mkNixtest {
modules = ntlib.autodiscover {dir = "${inputs.self}/tests";};
args = {
inherit ntlib devshell pkgs;
};
};
}

22
LICENSE.md Normal file
View file

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2025 TECHNOFAB
Copyright (c) 2021 Numtide and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

4
README.md Normal file
View file

@ -0,0 +1,4 @@
# Rensa DevShell
Minimal devshell implementation using Modules.
Inspired by [numtide/devshell](https://github.com/numtide/devshell).

5
benchmark/devshell.nix Normal file
View file

@ -0,0 +1,5 @@
let
shared = import ./shared.nix;
devshell = import ./../lib {inherit (shared) pkgs;};
in
devshell.mkShell {}

6
benchmark/empty.nix Normal file
View file

@ -0,0 +1,6 @@
# minimal derivation to see what the fastest time of nix-instantiate can be
derivation {
name = "empty";
builder = "true";
system = builtins.currentSystem;
}

View file

@ -0,0 +1,4 @@
let
shared = import ./shared.nix;
in
shared.pkgs.mkShell {}

12
benchmark/shared.nix Normal file
View file

@ -0,0 +1,12 @@
rec {
nixpkgs = let
gitRev = "835e9a7acce59c78130d2d4cc66a07a20403b185";
in
builtins.fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/${gitRev}.tar.gz";
sha256 = "0j86vmk6z9fi19nqyp22cpml0zgslaw20b66v3w9lw988mql3615";
};
pkgs = import nixpkgs {
system = builtins.currentSystem;
};
}

208
flake.lock generated Normal file
View file

@ -0,0 +1,208 @@
{
"nodes": {
"call-flake": {
"locked": {
"lastModified": 1687380775,
"narHash": "sha256-bmhE1TmrJG4ba93l9WQTLuYM53kwGQAjYHRvHOeuxWU=",
"owner": "divnix",
"repo": "call-flake",
"rev": "74061f6c241227cd05e79b702db9a300a2e4131a",
"type": "github"
},
"original": {
"owner": "divnix",
"repo": "call-flake",
"type": "github"
}
},
"haumea": {
"inputs": {
"nixpkgs": [
"std",
"lib"
]
},
"locked": {
"lastModified": 1685133229,
"narHash": "sha256-FePm/Gi9PBSNwiDFq3N+DWdfxFq0UKsVVTJS3cQPn94=",
"owner": "nix-community",
"repo": "haumea",
"rev": "34dd58385092a23018748b50f9b23de6266dffc2",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "v0.2.2",
"repo": "haumea",
"type": "github"
}
},
"incl": {
"inputs": {
"nixlib": [
"std",
"lib"
]
},
"locked": {
"lastModified": 1693483555,
"narHash": "sha256-Beq4WhSeH3jRTZgC1XopTSU10yLpK1nmMcnGoXO0XYo=",
"owner": "divnix",
"repo": "incl",
"rev": "526751ad3d1e23b07944b14e3f6b7a5948d3007b",
"type": "github"
},
"original": {
"owner": "divnix",
"repo": "incl",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1753694789,
"narHash": "sha256-cKgvtz6fKuK1Xr5LQW/zOUiAC0oSQoA9nOISB0pJZqM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "dc9637876d0dcc8c9e5e22986b857632effeb727",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nosys": {
"locked": {
"lastModified": 1668010795,
"narHash": "sha256-JBDVBnos8g0toU7EhIIqQ1If5m/nyBqtHhL3sicdPwI=",
"owner": "divnix",
"repo": "nosys",
"rev": "feade0141487801c71ff55623b421ed535dbdefa",
"type": "github"
},
"original": {
"owner": "divnix",
"repo": "nosys",
"type": "github"
}
},
"paisano": {
"inputs": {
"call-flake": "call-flake",
"nixpkgs": [
"std",
"nixpkgs"
],
"nosys": "nosys",
"yants": [
"std",
"yants"
]
},
"locked": {
"lastModified": 1708640854,
"narHash": "sha256-EpcAmvIS4ErqhXtVEfd2GPpU/E/s8CCRSfYzk6FZ/fY=",
"owner": "paisano-nix",
"repo": "core",
"rev": "adcf742bc9463c08764ca9e6955bd5e7dcf3a3fe",
"type": "github"
},
"original": {
"owner": "paisano-nix",
"ref": "0.2.0",
"repo": "core",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"std": "std"
}
},
"std": {
"inputs": {
"arion": [
"std",
"blank"
],
"blank": [],
"devshell": [
"std",
"blank"
],
"dmerge": [],
"haumea": "haumea",
"incl": "incl",
"lib": [
"nixpkgs"
],
"makes": [
"std",
"blank"
],
"microvm": [
"std",
"blank"
],
"n2c": [
"std",
"blank"
],
"nixago": [
"std",
"blank"
],
"nixpkgs": [
"nixpkgs"
],
"paisano": "paisano",
"paisano-tui": [],
"terranix": [
"std",
"blank"
],
"yants": "yants"
},
"locked": {
"lastModified": 1747378812,
"narHash": "sha256-bx+Bt2tEpCkrY7ImaklaTQvH6VGrB7FAmnfs7tItYIs=",
"owner": "divnix",
"repo": "std",
"rev": "29f79b7ae7d1716ff13944b698fe76cb0675c5f6",
"type": "github"
},
"original": {
"owner": "divnix",
"repo": "std",
"type": "github"
}
},
"yants": {
"inputs": {
"nixpkgs": [
"std",
"lib"
]
},
"locked": {
"lastModified": 1686863218,
"narHash": "sha256-kooxYm3/3ornWtVBNHM3Zh020gACUyFX2G0VQXnB+mk=",
"owner": "divnix",
"repo": "yants",
"rev": "8f0da0dba57149676aa4817ec0c880fbde7a648d",
"type": "github"
},
"original": {
"owner": "divnix",
"repo": "yants",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

34
flake.nix Normal file
View file

@ -0,0 +1,34 @@
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
std = {
url = "github:divnix/std";
inputs = {
nixpkgs.follows = "nixpkgs";
lib.follows = "nixpkgs";
paisano-tui.follows = "";
dmerge.follows = "";
blank.follows = "";
};
};
};
outputs = {
self,
std,
...
} @ inputs:
std.growOn
{
inherit inputs;
cellsFrom = ./.nix;
cellBlocks = with std.blockTypes; [
(devshells "devShells")
(pkgs "tests")
(runnables "benchmark")
];
}
{
packages = std.harvest self [["repo" "tests"] ["repo" "benchmark"]];
};
}

19
lib/default.nix Normal file
View file

@ -0,0 +1,19 @@
{
pkgs,
lib ? pkgs.lib,
...
}: rec {
modules = import ./modules;
eval = {config, ...}: let
res = lib.evalModules {
modules = [config modules];
specialArgs = {
inherit pkgs;
};
};
in {
inherit (res) config options;
shell = res.config.shell.finalPackage;
};
mkShell = config: (eval {inherit config;}).shell;
}

5
lib/flake.nix Normal file
View file

@ -0,0 +1,5 @@
{
outputs = i: {
lib = import ./.;
};
}

6
lib/modules/default.nix Normal file
View file

@ -0,0 +1,6 @@
{
imports = [
./main.nix
./env.nix
];
}

99
lib/modules/env.nix Normal file
View file

@ -0,0 +1,99 @@
{
lib,
pkgs,
config,
...
}: let
inherit (lib) filter assertMsg head length escapeShellArg types mkOption concatStringsSep;
envToBash = {
name,
value,
eval,
prefix,
...
} @ args: let
vals = filter (key: args.${key} != null && args.${key} != false) [
"eval"
"prefix"
"unset"
"value"
];
valType = head vals;
in
assert assertMsg (
(length vals) > 0
) "[devshell/env]: ${name} expected one of (value|eval|prefix|unset) to be set.";
assert assertMsg ((length vals) < 2)
"[devshell/env]: ${name} expected only one of (value|eval|prefix|unset) to be set. Not ${toString vals}";
assert assertMsg (!(name == "PATH" && valType == "value")) "[devshell/env]: ${name} should not override the value. Use 'prefix' instead.";
if valType == "value"
then "export ${name}=${escapeShellArg (toString value)}"
else if valType == "eval"
then "export ${name}=${eval}"
else if valType == "prefix"
then ''export ${name}=$(${pkgs.coreutils}/bin/realpath --canonicalize-missing "${prefix}")''${${name}+:''${${name}}}''
else if valType == "unset"
then ''unset ${name}''
else throw "[devshell] BUG in the env module. This should never be reached.";
envType = types.submodule ({name, ...}: {
options = {
name = mkOption {
type = types.str;
default = name;
};
value = mkOption {
type = with types;
nullOr (oneOf [
str
int
bool
package
]);
default = null;
};
eval = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
Like value but not evaluated by Bash. This allows to inject other
variable names or even commands using the `$()` notation.
'';
example = "$OTHER_VAR";
};
prefix = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
Prepend to PATH-like environment variables.
For example name = "PATH"; prefix = "bin"; will expand the path of
./bin and prepend it to the PATH, separated by ':'.
'';
example = "bin";
};
unset = mkOption {
type = types.bool;
default = false;
description = ''Unset the env variable'';
};
};
});
in {
options.env = mkOption {
type = types.attrsOf envType;
default = {};
description = '''';
};
config = {
env = {
"XDG_DATA_DIRS".eval = "$DEVSHELL_DIR/share:\${XDG_DATA_DIRS-}";
};
enterShellCommands."env".text = concatStringsSep "\n" (map envToBash (builtins.attrValues config.env));
};
}

113
lib/modules/main.nix Normal file
View file

@ -0,0 +1,113 @@
{
pkgs,
lib ? pkgs.lib,
config,
...
}: let
inherit (lib) mapAttrs mkOption types textClosureMap id attrNames;
mkNakedShell = pkgs.callPackage ./../utils/mkNakedShell.nix {};
addAttributeName = prefix:
mapAttrs (
k: v:
v
// {
text = ''
#### ${prefix}.${k}
${v.text}
'';
}
);
envBash =
pkgs.writeText "devshell-env.bash"
# sh
''
export DEVSHELL_DIR=@DEVSHELL_DIR@
# nicely handle it at the front or between/end
PATH=''${PATH#/path-not-set:}
PATH=''${PATH#:/path-not-set}
export PATH=$DEVSHELL_DIR/bin:$PATH
${textClosureMap
id
(addAttributeName "startup" config.enterShellCommands)
(attrNames config.enterShellCommands)}
# interactive session
if [[ $- == *i* ]]; then
true
${textClosureMap
id
(addAttributeName "interactive" config.interactiveShellCommands)
(attrNames config.interactiveShellCommands)}
fi # interactive session
unset DEVSHELL_DIR
'';
entryType = types.submodule {
options = {
text = mkOption {
type = types.str;
description = ''
Script to run.
'';
};
deps = mkOption {
type = types.listOf types.str;
default = [];
description = ''
A list of other steps that this one depends on.
'';
};
};
};
in {
options = {
shell = {
finalPackage = mkOption {
type = types.package;
internal = true;
};
finalProfile = mkOption {
type = types.package;
internal = true;
};
};
name = mkOption {
type = types.str;
default = "devshell";
};
packages = mkOption {
type = types.listOf types.package;
default = [];
};
enterShellCommands = mkOption {
type = types.attrsOf entryType;
default = {};
};
interactiveShellCommands = mkOption {
type = types.attrsOf entryType;
default = {};
};
};
config.shell = {
finalProfile = pkgs.buildEnv {
name = "${config.name}-profile";
paths = config.packages;
postBuild = ''
substitute ${envBash} $out/env.bash --subst-var-by DEVSHELL_DIR $out
# NOTE: maybe also add an entrypoint shell script at $out/bin/devshell-${config.name}?
'';
meta.mainProgram = "devshell-${config.name}";
};
finalPackage = mkNakedShell {
name = config.name;
profile = config.shell.finalProfile;
};
};
}

View file

@ -0,0 +1,70 @@
{
bashInteractive,
coreutils,
stdenv,
writeTextFile,
}: let
bashPath = "${bashInteractive}/bin/bash";
nakedStdenv = writeTextFile {
name = "naked-stdenv";
destination = "/setup";
text = ''
# Fix for `nix develop`
: ''${outputs:=out}
runHook() {
eval "$shellHook"
unset runHook
}
'';
};
in
{
name,
# A path to a buildEnv that will be loaded by the shell.
# We assume that the buildEnv contains an ./env.bash script.
profile,
meta ? {},
passthru ? {},
}:
(derivation {
inherit name;
system = stdenv.hostPlatform.system;
# `nix develop` actually checks and uses builder. And it must be bash.
builder = bashPath;
# Bring in the dependencies on `nix-build`
args = [
"-ec"
"${coreutils}/bin/ln -s ${profile} $out; exit 0"
];
# $stdenv/setup is loaded by nix-shell during startup.
# https://github.com/nixos/nix/blob/377345e26f1ac4bbc87bb21debcc52a1d03230aa/src/nix-build/nix-build.cc#L429-L432
stdenv = nakedStdenv;
# The shellHook is loaded directly by `nix develop`. But nix-shell
# requires that other trampoline.
shellHook = ''
# Remove all the unnecessary noise that is set by the build env
unset NIX_BUILD_TOP NIX_BUILD_CORES NIX_STORE
unset TEMP TEMPDIR TMP TMPDIR
unset builder out shellHook stdenv system name
# Flakes stuff
unset dontAddDisableDepTrack outputs
# For `nix develop`. We get /noshell on Linux and /sbin/nologin on macOS.
if [[ "$SHELL" == "/noshell" || "$SHELL" == "/sbin/nologin" ]]; then
export SHELL=${bashPath}
fi
# Load the environment
source "${profile}/env.bash"
'';
})
// {
inherit meta passthru;
}
// passthru

53
tests/devshell_test.nix Normal file
View file

@ -0,0 +1,53 @@
{
pkgs,
ntlib,
devshell,
...
}: {
suites."Devshell Tests" = {
pos = __curPos;
tests = [
{
name = "basic";
type = "script";
script = let
shell = devshell.mkShell {};
in
# sh
''
${ntlib.helpers.scriptHelpers}
assert_file_contains ${shell}/env.bash "XDG_DATA_DIRS" "should contain XDG_DATA_DIRS"
'';
}
{
name = "packages";
type = "script";
script = let
shell = devshell.mkShell {
packages = [pkgs.hello];
};
in
# sh
''
${ntlib.helpers.scriptHelpers}
assert "-f ${shell}/bin/hello" "/bin/hello should exist"
'';
}
{
name = "env";
type = "script";
script = let
shell = devshell.mkShell {
env."HELLO".value = "world";
};
in
# sh
''
${ntlib.helpers.scriptHelpers}
assert_file_contains ${shell}/env.bash "HELLO" "should contain HELLO"
assert_file_contains ${shell}/env.bash "world" "should contain world"
'';
}
];
};
}