Enhance nix-shell shebang handling in flake.nix by supporting multiple interpreters and improving package resolution for Python scripts. Introduced logic to create proper Python wrappers and separate system packages from Python packages for better environment management.

This commit is contained in:
Danilo Reyes
2025-12-20 18:11:36 -06:00
parent 3d86a2b288
commit e3395daced

View File

@@ -58,31 +58,82 @@
};
packages.x86_64-linux =
let
# Handle nix-shell shebangs by extracting packages and using writeShellApplication
# Handle nix-shell shebangs by extracting packages and interpreter
scriptBin = path: name:
let
content = builtins.readFile path;
# Match the nix-shell package line: #! nix-shell -i bash -p package1 package2 ...
nixShellMatch = builtins.match ".*#! nix-shell -i [^ ]+ -p ([^\n]+).*" content;
# Match the nix-shell line: #! nix-shell -i interpreter -p package1 package2 ...
nixShellMatch = builtins.match ".*#! nix-shell -i ([^ ]+) -p ([^\n]+).*" content;
in
if nixShellMatch != null then
let
packagesStr = builtins.head nixShellMatch;
interpreter = builtins.head nixShellMatch;
packagesStr = builtins.elemAt nixShellMatch 1;
# Split by spaces and filter empty strings
packages = builtins.filter (s: s != "") (pkgs.lib.splitString " " packagesStr);
# Resolve package references - handle python3Packages.* and regular packages
resolvePackage = pkgName:
if pkgs.lib.hasPrefix "python3Packages." pkgName then
let
pythonPkgName = pkgs.lib.removePrefix "python3Packages." pkgName;
in
pkgs.python3Packages.${pythonPkgName}
else if pkgName == "python3" then
pkgs.python3
else
pkgs.${pkgName};
# Get package references from pkgs
runtimeInputs = builtins.map (pkgName: pkgs.${pkgName}) packages;
runtimeInputs = builtins.map resolvePackage packages;
# Remove the nix-shell shebang lines
scriptContent = builtins.replaceStrings
[ "#!/usr/bin/env nix-shell\n" "#! nix-shell -i bash -p ${packagesStr}\n" ]
[ "#!/usr/bin/env nix-shell\n" "#! nix-shell -i ${interpreter} -p ${packagesStr}\n" ]
[ "" "" ]
content;
in
pkgs.writeShellApplication {
inherit name;
runtimeInputs = runtimeInputs;
text = scriptContent;
}
if interpreter == "python3" then
# For Python scripts, create a proper wrapper with Python shebang
let
# Separate Python packages from system packages
pythonPkgNames = builtins.filter (pkg: pkgs.lib.hasPrefix "python3Packages." pkg) packages;
systemPkgNames = builtins.filter (pkg: !(pkgs.lib.hasPrefix "python3Packages." pkg) && pkg != "python3") packages;
# Resolve Python packages for python3.withPackages
pythonPkgs = builtins.map (pkgName:
let pythonPkgName = pkgs.lib.removePrefix "python3Packages." pkgName;
in pkgs.python3Packages.${pythonPkgName}
) pythonPkgNames;
# Create Python environment with required packages
pythonEnv = if pythonPkgs != [] then
pkgs.python3.withPackages (ps: pythonPkgs)
else
pkgs.python3;
# Resolve system packages for PATH
systemRuntimeInputs = builtins.map resolvePackage systemPkgNames;
scriptFile = pkgs.writeText "${name}.py" ''
#!${pythonEnv}/bin/python3
${scriptContent}
'';
in
pkgs.stdenv.mkDerivation {
inherit name;
buildInputs = [ pkgs.makeWrapper ];
dontUnpack = true;
installPhase = ''
mkdir -p $out/bin
cp ${scriptFile} $out/bin/${name}
chmod +x $out/bin/${name}
${pkgs.lib.optionalString (systemRuntimeInputs != []) ''
wrapProgram $out/bin/${name} \
--prefix PATH : ${pkgs.lib.makeBinPath systemRuntimeInputs}
''}
'';
}
else
# For shell scripts, use writeShellApplication
pkgs.writeShellApplication {
inherit name;
runtimeInputs = runtimeInputs;
text = scriptContent;
}
else
pkgs.writeScriptBin name content;
pkgsBin =