tcl: Add tclPackageHook and mkTclDerivation

Implement tclPackageHook, a setup hook that adjusts TCLLIBPATH to include the
paths of any installed Tcl packages, propagates that TCLLIBPATH to
anti-dependencies, and wraps any installed binaries to set their TCLLIBPATH.

Additionally, implement a makePythonPackage-style mkDerivation wrapper to use
reasonable defaults for Tcl packages and use tclPackageHook.
This commit is contained in:
Andrew Brooks 2021-04-30 17:25:06 -05:00
parent d0fd8a029d
commit c6ca1ba9c7
3 changed files with 197 additions and 40 deletions

View File

@ -1,55 +1,69 @@
{ lib, stdenv
{ lib, stdenv, callPackage, makeSetupHook, makeWrapper
# Version specific stuff
, release, version, src
, ...
}:
stdenv.mkDerivation {
pname = "tcl";
inherit version;
let
baseInterp =
stdenv.mkDerivation {
pname = "tcl";
inherit version;
inherit src;
inherit src;
outputs = [ "out" "man" ];
outputs = [ "out" "man" ];
setOutputFlags = false;
setOutputFlags = false;
preConfigure = ''
cd unix
'';
preConfigure = ''
cd unix
'';
configureFlags = [
"--enable-threads"
# Note: using $out instead of $man to prevent a runtime dependency on $man.
"--mandir=${placeholder "out"}/share/man"
"--enable-man-symlinks"
# Don't install tzdata because NixOS already has a more up-to-date copy.
"--with-tzdata=no"
"tcl_cv_strtod_unbroken=ok"
] ++ lib.optional stdenv.is64bit "--enable-64bit";
configureFlags = [
"--enable-threads"
# Note: using $out instead of $man to prevent a runtime dependency on $man.
"--mandir=${placeholder "out"}/share/man"
"--enable-man-symlinks"
# Don't install tzdata because NixOS already has a more up-to-date copy.
"--with-tzdata=no"
"tcl_cv_strtod_unbroken=ok"
] ++ lib.optional stdenv.is64bit "--enable-64bit";
enableParallelBuilding = true;
enableParallelBuilding = true;
postInstall = let
dllExtension = stdenv.hostPlatform.extensions.sharedLibrary;
in ''
make install-private-headers
ln -s $out/bin/tclsh${release} $out/bin/tclsh
ln -s $out/lib/libtcl${release}${dllExtension} $out/lib/libtcl${dllExtension}
'';
postInstall = let
dllExtension = stdenv.hostPlatform.extensions.sharedLibrary;
in ''
make install-private-headers
ln -s $out/bin/tclsh${release} $out/bin/tclsh
ln -s $out/lib/libtcl${release}${dllExtension} $out/lib/libtcl${dllExtension}
'';
meta = with lib; {
description = "The Tcl scripting language";
homepage = "https://www.tcl.tk/";
license = licenses.tcltk;
platforms = platforms.all;
maintainers = with maintainers; [ vrthra ];
};
meta = with lib; {
description = "The Tcl scripting language";
homepage = "https://www.tcl.tk/";
license = licenses.tcltk;
platforms = platforms.all;
maintainers = with maintainers; [ vrthra ];
};
passthru = rec {
inherit release version;
libPrefix = "tcl${release}";
libdir = "lib/${libPrefix}";
};
}
passthru = rec {
inherit release version;
libPrefix = "tcl${release}";
libdir = "lib/${libPrefix}";
tclPackageHook = callPackage ({}: makeSetupHook {
name = "tcl-package-hook";
deps = [ makeWrapper ];
} ./tcl-package-hook.sh) {};
};
};
mkTclDerivation = callPackage ./mk-tcl-derivation.nix { tcl = baseInterp; };
in baseInterp.overrideAttrs (self: {
passthru = self.passthru // {
inherit mkTclDerivation;
};
})

View File

@ -0,0 +1,69 @@
# Generic builder for tcl packages/applications, generally based on mk-python-derivation.nix
{ tcl
, lib
, makeWrapper
, runCommand
, writeScript
}:
{ buildInputs ? []
, nativeBuildInputs ? []
, propagatedBuildInputs ? []
, checkInputs ? []
# true if we should skip the configuration phase altogether
, dontConfigure ? false
# Extra flags passed to configure step
, configureFlags ? []
# Whether or not we should add common Tcl-related configure flags
, addTclConfigureFlags ? true
, meta ? {}
, passthru ? {}
, doCheck ? true
, ... } @ attrs:
let
inherit (tcl) stdenv;
inherit (lib) getBin optionalAttrs optionals;
defaultTclPkgConfigureFlags = [
"--with-tcl=${tcl}/lib"
"--with-tclinclude=${tcl}/include"
"--exec-prefix=\${out}"
];
self = (stdenv.mkDerivation ((builtins.removeAttrs attrs [
"addTclConfigureFlags" "checkPhase" "checkInputs" "doCheck"
]) // {
buildInputs = buildInputs ++ [ makeWrapper tcl.tclPackageHook ];
nativeBuildInputs = nativeBuildInputs ++ [ tcl ];
propagatedBuildInputs = propagatedBuildInputs ++ [ tcl ];
TCLSH = "${getBin tcl}/bin/tclsh";
# Run tests after install, at which point we've done all TCLLIBPATH setup
doCheck = false;
doInstallCheck = attrs.doCheck or ((attrs ? doInstallCheck) && attrs.doInstallCheck);
installCheckInputs = checkInputs ++ (optionals (attrs ? installCheckInputs) attrs.installCheckInputs);
# Add typical values expected by TEA for configureFlags
configureFlags =
if (!dontConfigure && addTclConfigureFlags)
then (configureFlags ++ defaultTclPkgConfigureFlags)
else configureFlags;
meta = {
platforms = tcl.meta.platforms;
} // meta;
} // optionalAttrs (attrs?checkPhase) {
installCheckPhase = attrs.checkPhase;
}
));
in lib.extendDerivation true passthru self

View File

@ -0,0 +1,74 @@
# This hook ensures that we do the following in post-fixup:
# * wrap any installed executables with a wrapper that configures TCLLIBPATH
# * write a setup hook that extends the TCLLIBPATH of any anti-dependencies
# Add a directory to TCLLIBPATH, provided that it exists
_addToTclLibPath() {
local -r tclPkg="$1"
if [ -z "$tclPkg" ]; then
return
fi
if [ ! -d "$tclPkg" ]; then
>&2 echo "can't add $tclPkg to TCLLIBPATH; that directory doesn't exist"
exit 1
fi
if [[ "$tclPkg" == *" "* ]]; then
tclPkg="{$tclPkg}"
fi
if [ -z "${TCLLIBPATH-}" ]; then
export TCLLIBPATH="$tclPkg"
else
if [[ "$TCLLIBPATH" != *"$tclPkg"* ]]; then
export TCLLIBPATH="${TCLLIBPATH} $tclPkg"
fi
fi
}
# Locate any directory containing an installed pkgIndex file
findInstalledTclPkgs() {
local -r newLibDir="${!outputLib}/lib"
if [ ! -d "$newLibDir" ]; then
>&2 echo "Assuming no loadable tcl packages installed ($newLibDir does not exist)"
return
fi
echo "$(find "$newLibDir" -name pkgIndex.tcl -exec dirname {} \;)"
}
# Wrap any freshly-installed binaries and set up their TCLLIBPATH
wrapTclBins() {
if [ -z "${TCLLIBPATH-}" ]; then
echo "skipping automatic Tcl binary wrapping (nothing to do)"
return
fi
local -r tclBinsDir="${!outputBin}/bin"
if [ ! -d "$tclBinsDir" ]; then
echo "No outputBin found, not using any TCLLIBPATH wrapper"
return
fi
find "$tclBinsDir" -type f -executable -print |
while read -r someBin; do
echo "Adding TCLLIBPATH wrapper for $someBin"
wrapProgram "$someBin" --set TCLLIBPATH "$TCLLIBPATH"
done
}
# Generate hook to adjust TCLLIBPATH in anti-dependencies
writeTclLibPathHook() {
local -r hookPath="${!outputLib}/nix-support/setup-hook"
mkdir -p "$(dirname "$hookPath")"
typeset -f _addToTclLibPath >> "$hookPath"
local -r tclPkgs=$(findInstalledTclPkgs)
while IFS= read -r tclPkg; do
echo "_addToTclLibPath \"$tclPkg\"" >> "$hookPath"
_addToTclLibPath "$tclPkg" true
done <<< "$tclPkgs"
}
postFixupHooks+=(writeTclLibPathHook)
postFixupHooks+=(wrapTclBins)