Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,12 @@ let

config =
lib.asserts.checkAssertWarn configEval.config.assertions configEval.config.warnings
configEval.config;
configEval.config
// {
# Injected lazily for toDevShell passthru — only forced when a user
# actually calls drv.toDevShell, at which point pkgs is fully resolved.
mkDevShell = pkgs.mkDevShell;
};
Comment on lines +148 to +153

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not a fan of this


# A few packages make a new package set to draw their dependencies from.
# (Currently to get a cross tool chain, or forced-i686 package.) Rather than
Expand Down
77 changes: 72 additions & 5 deletions dev-shell/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,16 @@ let
name = "dev-shell";
phases = [ "buildPhase" ];
buildPhase = ''
echo "This derivation is not meant to be built, only to be used with nix-shell"
touch $out
{ echo "------------------------------------------------------------";
echo " WARNING: the existence of this path is not guaranteed.";
echo " It is an internal implementation detail for mkDevShell.";
echo "------------------------------------------------------------";
echo;
# Record all build inputs as runtime dependencies
export;
} >> "$out"
'';
preferLocalBuild = true;
shellHook = "";
}
// attrs
Expand All @@ -116,13 +123,25 @@ in
# Main function: Create a development shell with services
mkDevShell =
{
# Shell name
name ? "dev-shell",

# Service and language configuration via modules
modules ? [ ],

# Traditional mkShell options
packages ? [ ],
shellHook ? "",
buildInputs ? [ ],
nativeBuildInputs ? [ ],
propagatedBuildInputs ? [ ],
propagatedNativeBuildInputs ? [ ],

# Propagate all inputs from the given derivations
inputsFrom ? [ ],

# Environment variables to set in the shell (attrset of strings)
env ? { },

# process-compose specific options
processCompose ? {
Expand Down Expand Up @@ -189,9 +208,39 @@ in
) langVariables
);

# Merge inputs from inputsFrom derivations (same logic as mkShell/to-dev-shell)
mergeInputs =
attr:
(args.${attr} or [ ])
++ (lib.subtractLists inputsFrom (lib.flatten (lib.catAttrs attr inputsFrom)));

mergedBuildInputs = mergeInputs "buildInputs";
mergedNativeBuildInputs = mergeInputs "nativeBuildInputs";
mergedPropagatedBuildInputs = mergeInputs "propagatedBuildInputs";
mergedPropagatedNativeBuildInputs = mergeInputs "propagatedNativeBuildInputs";

# Merge shellHooks from inputsFrom
inputsFromShellHook = lib.concatStringsSep "\n" (
lib.catAttrs "shellHook" (lib.reverseList inputsFrom)
);

# Generate export statements for env variables
envExports = lib.concatStringsSep "\n" (
lib.mapAttrsToList (n: v: "export ${n}=${lib.escapeShellArg (toString v)}") env
);

# Extract non-service options for mkShell
shellArgs = builtins.removeAttrs args [
"name"
"modules"
"packages"
"buildInputs"
"nativeBuildInputs"
"propagatedBuildInputs"
"propagatedNativeBuildInputs"
"inputsFrom"
"env"
"shellHook"
"processCompose"
];

Expand All @@ -201,20 +250,25 @@ in
mkdir -p ${processCompose.logDir}
mkdir -p ${processCompose.dataDir}

${lib.optionalString (env != { }) ''
# Derivation environment variables
${envExports}
''}

${lib.optionalString (langVariables != { }) ''
# Language environment variables
${langExports}
''}

# Display service information
echo "================================================"
echo "Development Shell with Services"
echo "Development Shell: ${name}"
echo "================================================"
${lib.optionalString (enabledServices != { }) ''
echo ""
echo "Available services:"
${lib.concatStringsSep "\n" (
lib.mapAttrsToList (name: _: " echo \" - ${name}\"") enabledServices
lib.mapAttrsToList (svcName: _: " echo \" - ${svcName}\"") enabledServices
)}
echo ""
echo "Service management commands:"
Expand Down Expand Up @@ -248,6 +302,9 @@ in
trap _pc_cleanup EXIT
''}

# Shell hooks from inputsFrom derivations
${inputsFromShellHook}

# User's custom shellHook
${shellHook}
'';
Expand All @@ -256,8 +313,18 @@ in
mkShell (
shellArgs
// {
inherit name;

buildInputs =
buildInputs ++ packages ++ langPackages ++ [ processComposePackage ] ++ (lib.attrValues utilities);
mergedBuildInputs
++ packages
++ langPackages
++ [ processComposePackage ]
++ (lib.attrValues utilities);

nativeBuildInputs = mergedNativeBuildInputs;
propagatedBuildInputs = mergedPropagatedBuildInputs;
propagatedNativeBuildInputs = mergedPropagatedNativeBuildInputs;

shellHook = enhancedShellHook;

Expand Down
24 changes: 24 additions & 0 deletions docs/major-differences-nixpkgs.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,27 @@ changes differ significantly from whath one would expct with Nixpkgs.
run its tests.
- This decouples test execution from the main build, allowing test failures
or test-only dependency churn to avoid invalidating downstream consumers.

## Development shells

- Every `mkDerivation` output exposes a `toDevShell` passthru attribute that
converts the derivation into a development shell via `mkDevShell`.
- `toDevShell` preserves the original derivation's build environment
(dependencies, compiler, flags, environment variables) and forwards them
to `mkDevShell`, so the resulting shell automatically gains ekaos service
modules, language modules, and process-compose integration.
- Usage: `myPkg.toDevShell { }` returns a shell derivation directly.
Additional `mkDevShell` options (`modules`, `packages`, `shellHook`, etc.)
can be passed in the attrset argument.
- Accepts a function form for conditional inputs:
`myPkg.toDevShell (stdenv: { packages = lib.optionals stdenv.isLinux [ pkgs.strace ]; })`.
- `mkDevShell` replaces `mkShell` as the primary shell builder.
- Accepts `modules` for ekaos service and language configuration, with
automatic process-compose integration for running services in the
background.
- Supports `inputsFrom` for propagating inputs from other derivations,
`env` for arbitrary environment variables, and the full set of dependency
list parameters (`nativeBuildInputs`, `propagatedBuildInputs`,
`propagatedNativeBuildInputs`).
- Nixpkgs has no equivalent; `mkShell` there is a thin wrapper around
`mkDerivation` with no service or module integration.
14 changes: 14 additions & 0 deletions stdenv/generic/make-derivation.nix
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,20 @@ let
}
);

# Convert this derivation to a development shell, preserving its
# build environment and gaining mkDevShell features (services,
# language modules, process-compose).
# myPkg.toDevShell { }
# myPkg.toDevShell { modules = [ ... ]; packages = [ ... ]; }
# Accepts either an attrset or a function (stdenv -> attrset).
toDevShell =
let
originalArgs = removeAttrs derivationArg attrsToRemoveLast;
toShell = import ./to-dev-shell.nix lib originalArgs;
shellFunc = f: if builtins.isFunction f then f stdenv else f;
in
f: config.mkDevShell (toShell (shellFunc f));

inherit passthru overrideAttrs;
inherit meta;
}
Expand Down
166 changes: 166 additions & 0 deletions stdenv/generic/to-dev-shell.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
lib:

originalArgs:

# Extract shell-relevant information from a mkDerivation's derivationArg
# and merge it with user-supplied overrides, producing an attrset
# suitable for mkDevShell.
{
name ? if originalArgs ? name then "${originalArgs.name}-dev-shell" else "dev-shell",
# a list of packages to add to the shell environment
packages ? [ ],
# propagate all the inputs from the given derivations
inputsFrom ? [ ],
buildInputs ? [ ],
nativeBuildInputs ? [ ],
propagatedBuildInputs ? [ ],
propagatedNativeBuildInputs ? [ ],
shellHook ? "",
modules ? [ ],
env ? { },
...
}@attrs:
let
# Merge original derivation inputs with user-supplied overrides
mergeInputs =
attrName:
(originalArgs.${attrName} or [ ])
++ (attrs.${attrName} or [ ])
++ (lib.subtractLists inputsFrom (lib.flatten (lib.catAttrs attrName inputsFrom)));

# Attrs from derivationArg that are build infrastructure, not environment
# variables. Anything not in this set AND string/path-valued will be
# forwarded as an env var to the dev shell.
infrastructureAttrs = [
"name"
"pname"
"version"
"builder"
"args"
"system"
"outputs"
"out"
"src"
"srcs"
"sourceRoot"
"setSourceRoot"
"stdenv"
"buildInputs"
"nativeBuildInputs"
"propagatedBuildInputs"
"propagatedNativeBuildInputs"
"depsBuildBuild"
"depsBuildBuildPropagated"
"depsBuildTarget"
"depsBuildTargetPropagated"
"depsHostHost"
"depsHostHostPropagated"
"depsTargetTarget"
"depsTargetTargetPropagated"
"shellHook"
"patches"
"patchFlags"
"doCheck"
"doInstallCheck"
"strictDeps"
"userHook"
"__ignoreNulls"
"__structuredAttrs"
"__contentAddressed"
"outputHashAlgo"
"outputHashMode"
"outputHash"
"preferLocalBuild"
"allowSubstitutes"
"enableParallelBuilding"
"enableParallelChecking"
"enableParallelInstalling"
"meta"
"passthru"
"pos"
"separateDebugInfo"
"hardeningEnable"
"hardeningDisable"
"NIX_HARDENING_ENABLE"
"requiredSystemFeatures"
"__darwinAllowLocalNetworking"
"__sandboxProfile"
"__propagatedSandboxProfile"
"__impureHostDeps"
"__propagatedImpureHostDeps"
"allowedImpureDLLs"
"outputChecks"
"disallowedReferences"
"disallowedRequisites"
"allowedReferences"
"allowedRequisites"
"cmakeFlags"
"mesonFlags"
"configureFlags"
"configurePlatforms"
"makeFlags"
"makefile"
"installFlags"
"installTargets"
"dontInstall"
"dontBuild"
"dontConfigure"
"dontFixup"
"dontPatchShebangs"
"dontPatchELF"
"dontStrip"
"forceShare"
"setupHook"
"setupHooks"
"passAsFile"
];

# Phase hooks and scripts (pre/post hooks, phase definitions, configure
# scripts, etc.) are build-time concerns and should not leak as env vars.
isPhaseAttr =
name:
lib.hasPrefix "pre" name
|| lib.hasPrefix "post" name
|| lib.hasSuffix "Phase" name
|| lib.hasSuffix "Phases" name
|| lib.hasSuffix "Hook" name
|| lib.hasSuffix "Script" name
|| lib.hasSuffix "Flags" name;

# Environment variables from the original derivation (everything that's
# a string and not infrastructure — these are typically set via `env` or
# as top-level attrs in mkDerivation).
originalEnv = lib.filterAttrs (
n: v:
!(builtins.elem n infrastructureAttrs)
&& !(isPhaseAttr n)
&& (builtins.isString v || builtins.isPath v)
) originalArgs;

rest = builtins.removeAttrs attrs [
"name"
"packages"
"inputsFrom"
"buildInputs"
"nativeBuildInputs"
"propagatedBuildInputs"
"propagatedNativeBuildInputs"
"shellHook"
"modules"
"env"
];
in
{
inherit name modules;

buildInputs = mergeInputs "buildInputs";
nativeBuildInputs = packages ++ (mergeInputs "nativeBuildInputs");
propagatedBuildInputs = mergeInputs "propagatedBuildInputs";
propagatedNativeBuildInputs = mergeInputs "propagatedNativeBuildInputs";

inherit inputsFrom shellHook;

# Merge original derivation env vars with user-supplied env
env = originalEnv // env;
}
// rest
Loading