beamPackages.buildMix: move phases to hooks

This commit is contained in:
Adam C. Stephens
2025-12-29 13:21:40 -05:00
parent 8bdef3ae03
commit d1972278b5
12 changed files with 169 additions and 115 deletions
+33 -73
View File
@@ -2,8 +2,13 @@
elixir,
erlang,
hex,
beamCopySourceHook,
beamModuleInstallHook,
mixBuildDirHook,
mixCompileHook,
mixAppConfigPatchHook,
lib,
mixConfigureHook,
stdenv,
writeText,
}:
@@ -16,42 +21,46 @@ lib.extendMkDerivation {
extendDrvArgs =
finalAttrs:
{
nativeBuildInputs ? [ ],
propagatedBuildInputs ? [ ],
passthru ? { },
beamDeps ? [ ],
mixEnv ? "prod",
mixTarget ? "host",
enableDebugInfo ? false,
# A handful of libraries require compile time configuration
# Allow passing compile time config instead of an empty config
appConfigPath ? null,
enableDebugInfo ? false,
erlangCompilerOptions ? [ ],
# Deterministic Erlang builds remove full system paths from debug information
# among other things to keep builds more reproducible. See their docs for more:
# https://www.erlang.org/doc/man/compile
erlangDeterministicBuilds ? true,
beamDeps ? [ ],
...
}@args:
{
name = "erlang-${erlang.version}-${args.name}-${finalAttrs.version}";
name = "erlang${erlang.version}-${args.name}-${finalAttrs.version}";
env = {
MIX_ENV = mixEnv;
MIX_TARGET = mixTarget;
MIX_BUILD_PREFIX = (if mixTarget == "host" then "" else "${mixTarget}_") + "${mixEnv}";
MIX_DEBUG = if enableDebugInfo then 1 else 0;
HEX_OFFLINE = 1;
ERL_COMPILER_OPTIONS =
let
options = erlangCompilerOptions ++ lib.optionals erlangDeterministicBuilds [ "deterministic" ];
in
"[${lib.concatStringsSep "," options}]";
MIX_ENV = mixEnv;
MIX_TARGET = mixTarget;
MIX_BUILD_PREFIX = (if mixTarget == "host" then "" else "${mixTarget}_") + "${mixEnv}";
MIX_DEBUG = if enableDebugInfo then 1 else 0;
HEX_OFFLINE = 1;
LANG = if stdenv.hostPlatform.isLinux then "C.UTF-8" else "C";
LC_CTYPE = if stdenv.hostPlatform.isLinux then "C.UTF-8" else "UTF-8";
__darwinAllowLocalNetworking = true;
# some hooks need name-version, but we've overridden name above for the nix package
beamModuleName = args.name;
};
__darwinAllowLocalNetworking = true;
# add to ERL_LIBS so other modules can find at runtime.
# http://erlang.org/doc/man/code.html#code-path
# Mix also searches the code path when compiling with the --no-deps-check flag
@@ -60,70 +69,21 @@ lib.extendMkDerivation {
addToSearchPath ERL_LIBS "$1/lib/erlang/lib"
'';
nativeBuildInputs = nativeBuildInputs ++ [
nativeBuildInputs = (args.nativeBuildInputs or [ ]) ++ [
elixir
hex
mixConfigureHook
beamCopySourceHook
beamModuleInstallHook
mixBuildDirHook
mixCompileHook
mixAppConfigPatchHook
];
propagatedBuildInputs = propagatedBuildInputs ++ beamDeps;
# We don't want to include whatever config a dependency brings; per
# https://hexdocs.pm/elixir/main/Config.html, config is application specific.
patchPhase =
args.patchPhase or (
''
runHook prePatch
propagatedBuildInputs = (args.propagatedBuildInputs or [ ]) ++ beamDeps;
rm -rf config
''
+ lib.optionalString (!isNull appConfigPath) ''
cp -r ${appConfigPath} config
''
+ ''
runHook postPatch
''
);
buildPhase =
args.buildPhase or ''
runHook preBuild
export HEX_HOME="$TEMPDIR/hex"
export MIX_HOME="$TEMPDIR/mix"
mix compile --no-deps-check
runHook postBuild
'';
installPhase =
args.installPhase or ''
runHook preInstall
# This uses the install path convention established by nixpkgs maintainers
# for all beam packages. Changing this will break compatibility with other
# builder functions like buildRebar3 and buildErlangMk.
mkdir -p "$out/lib/erlang/lib/${finalAttrs.name}-${finalAttrs.version}-${args.version}"
# Some packages like db_connection will use _build/shared instead of
# honoring the $MIX_ENV variable.
for reldir in _build/{$MIX_BUILD_PREFIX,shared}/lib/${finalAttrs.name}-${finalAttrs.version}/{src,ebin,priv,include} ; do
if test -d $reldir ; then
# Some builds produce symlinks (eg: phoenix priv dircetory). They must
# be followed with -H flag.
cp -Hrt "$out/lib/erlang/lib/${finalAttrs.name}-${finalAttrs.version}" "$reldir"
fi
done
runHook postInstall
'';
# stripping does not have any effect on beam files
# it is however needed for dependencies with NIFs like bcrypt for example
dontStrip = args.dontStrip or false;
passthru = {
passthru = (args.passthru or { }) // {
inherit beamDeps;
}
// passthru;
};
};
}
+5 -1
View File
@@ -101,7 +101,11 @@ let
webdriver = callPackage ./webdriver { };
inherit (callPackages ./hooks { })
mixConfigureHook
beamCopySourceHook
beamModuleInstallHook
mixBuildDirHook
mixCompileHook
mixAppConfigPatchHook
;
};
in
@@ -0,0 +1,16 @@
# shellcheck shell=bash
#
# Copy the source so it can be used by mix projects to assemble `deps`
# do this before building to avoid build artifacts but after patching
# to include any user modifications to the source
beamCopySourceHook() {
echo "Executing beamCopySourceHook"
mkdir -p "$out/src"
cp -r "." "$out/src"
echo "Finished beamCopySourceHook"
}
postPatchHooks+=(beamCopySourceHook)
@@ -0,0 +1,25 @@
# shellcheck shell=bash
#
# This uses the install path convention established by nixpkgs maintainers
# for all beam packages. Changing this will break compatibility with other
# builder functions like buildRebar3 and buildErlangMk.
beamModuleInstallHook() {
echo "Executing beamModuleInstallHook"
mkdir -p "$out/lib/erlang/lib/${beamModuleName}-${version}"
for reldir in _build/{$MIX_BUILD_PREFIX,shared}/lib/${beamModuleName}/{src,ebin,priv,include}; do
if test -d "$reldir"; then
# Some builds produce symlinks (eg: phoenix priv directory). They must
# be followed with -H flag.
cp -vHrt "$out/lib/erlang/lib/${beamModuleName}-${version}" "$reldir"
fi
done
echo "Finished beamModuleInstallHook"
}
if [ -z "${dontBeamModuleInstall-}" ] && [ -z "${installPhase-}" ]; then
installPhase=beamModuleInstallHook
fi
@@ -1,8 +1,22 @@
{ makeSetupHook }:
{
makeSetupHook,
}:
{
mixConfigureHook = makeSetupHook {
beamCopySourceHook = makeSetupHook {
name = "beam-copy-source-hook.sh";
} ./beam-copy-source-hook.sh;
beamModuleInstallHook = makeSetupHook {
name = "beam-module-install-hook.sh";
} ./beam-module-install-hook.sh;
mixBuildDirHook = makeSetupHook {
name = "mix-configure-hook.sh";
} ./mix-configure-hook.sh;
} ./mix-build-dir-hook.sh;
mixCompileHook = makeSetupHook {
name = "mix-compile-hook.sh";
} ./mix-compile-hook.sh;
mixAppConfigPatchHook = makeSetupHook {
name = "mix-config-patch-hook.sh";
} ./mix-app-config-patch-hook.sh;
}
@@ -0,0 +1,19 @@
# shellcheck shell=bash
#
# We don't want to include whatever config a dependency brings; per
# https://hexdocs.pm/elixir/main/Config.html, config is application specific.
mixAppConfigPatchHook() {
echo "Executing mixAppConfigPatchHook"
rm -rvf config
# But we still need a way to provide config for deps that need compile time config
if [ ! -z "${appConfigPath}" ]; then
ln -sf "${appConfigPath}" config
fi
echo "Finished mixAppConfigPatchHook"
}
prePatchHooks+=(mixAppConfigPatchHook)
+26
View File
@@ -0,0 +1,26 @@
# shellcheck shell=bash
#
# Symlink all dependencies found in ERL_LIBS since Elixir does not honor ERL_LIBS
mixBuildDirHook() {
echo "Executing mixBuildDirHook"
mkdir -p _build/"$MIX_BUILD_PREFIX"/lib
while read -r -d ':' lib; do
for dir in "$lib"/*; do
# Strip version number for directory name if it exists, so naming of
# all libs matches what mix's expectation.
dest=$(basename "$dir" | cut -d '-' -f1)
build_dir="_build/$MIX_BUILD_PREFIX/lib/$dest"
# Symlink libs to _build so that mix can find them when compiling.
# This is what allows mix to compile the package without searching
# for dependencies over the network.
ln -sv "$dir" "$build_dir"
done
done <<<"$ERL_LIBS:"
echo "Finished mixBuildDirHook"
}
preConfigureHooks+=(mixBuildDirHook)
+20
View File
@@ -0,0 +1,20 @@
# shellcheck shell=bash
mixCompileHook() {
echo "Executing mixCompileHook"
runHook preBuild
local flagsArray=()
concatTo flagsArray mixCompileFlags
mix compile --no-deps-check "${flagsArray[@]}"
runHook postBuild
echo "Finished mixCompileHook"
}
if [ -z "${dontMixCompile-}" ] && [ -z "${buildPhase-}" ]; then
buildPhase=mixCompileHook
fi
@@ -1,34 +0,0 @@
# shellcheck shell=bash
mixConfigureHook() {
echo "Executing mixConfigureHook"
runHook preConfigure
# Copy the source so it can be used by mix projects
# do this before building to avoid build artifacts but after patching
# to include any modifications
mkdir -p "$out/src"
cp -r "." "$out/src"
# Symlink all dependencies found in ERL_LIBS since Elixir does not honor ERL_LIBS
mkdir -p _build/"$MIX_BUILD_PREFIX"/lib
while read -r -d ':' lib; do
for dir in "$lib"/*; do
# Strip version number for directory name if it exists, so naming of
# all libs matches what mix's expectation.
dest=$(basename "$dir" | cut -d '-' -f1)
build_dir="_build/$MIX_BUILD_PREFIX/lib/$dest"
((MIX_DEBUG == 1)) && echo "Linking $dir to $build_dir"
# Symlink libs to _build so that mix can find them when compiling.
# This is what allows mix to compile the package without searching
# for dependencies over the network.
ln -s "$dir" "$build_dir"
done
done <<<"$ERL_LIBS:"
runHook postConfigure
}
if [ -z "${dontCargoConfigure-}" ] && [ -z "${configurePhase-}" ]; then
configurePhase=mixConfigureHook
fi
+5
View File
@@ -0,0 +1,5 @@
# Added 2026-02-12 for external users
echo "mix-configure-hook.sh has been removed and is now a setup hook.
Add 'beamPackages.mixBuildDirHook' to 'nativeBuildInputs' instead.
" >&2
exit 1
@@ -171,8 +171,6 @@ stdenv.mkDerivation (
attrs.configurePhase or ''
runHook preConfigure
${./mix-configure-hook.sh}
# This is needed for projects that have a specific compile step
# the dependency needs to be compiled in order for the task
# to be available.
+1
View File
@@ -146,6 +146,7 @@ mixRelease rec {
${lib.getExe nixfmt} pkgs/servers/mobilizon/mix.nix
'';
elixirPackage = beamPackages.elixir;
inherit mixNixDeps;
};
meta = {