diff --git a/buildPostgresqlExtension.nix b/buildPostgresqlExtension.nix new file mode 100644 index 0000000..03797b6 --- /dev/null +++ b/buildPostgresqlExtension.nix @@ -0,0 +1,147 @@ +# PostgreSQL's build system for extensions (PGXS) makes the following assumptions: +# - All extensions will be installed in the same prefix as PostgreSQL itself. +# - pg_config is able to return the correct paths for bindir/libdir/datadir etc. +# +# Both of those assumptions break with nix. Since each extension is a separate +# derivation, we need to put all its files into a different folder. At the same +# time, pg_config only points to the PostgreSQL derivation's paths. +# +# When building extensions, the paths provided by pg_config are used for two +# purposes: +# - To find postgres libs and headers and reference those paths via -L and -I flags. +# - To determine the correct install directory. +# +# The PGXS Makefiles also support an environment variable DESTDIR, which is added as +# a prefix to all install locations. This is primarily used for temporary installs +# while running the test suite. Since pg_config returns absolute paths to /nix/store +# for us, using DESTDIR will result in install locations of the form: +# $DESTIDR/nix/store//... +# +# In multiple iterations, the following approaches have been tried to work around all +# of this: +# 1. For a long time, all extensions in nixpkgs just overwrote the installPhase +# and moved the respective files to the correct location manually. This approach +# is not maintainable, because whenever upstream adds a new file, we'd have to +# make sure the file is correctly installed as well. Additionally, it makes adding +# a new extension harder than it should be. +# +# 2. A wrapper around pg_config could just replace the returned paths with paths to +# $out of currently building derivation, i.e. the extension. This works for install- +# ation, but breaks for any of the libs and headers the extension needs from postgres +# itself. +# +# 3. A variation of 2., but make the pg_config wrapper only return the changed paths +# during the installPahse. During configure and build, it would return the regular +# paths to the PostgreSQL derivation. This works better, but not for every case. +# Some extensions try to be smarter and search for the "postgres" binary to deduce +# the necessary paths from that. Those would still need special handling. +# +# 4. Use the fact that DESTDIR is prepended to every installation directory - and only +# there, to run a replacement of all Makefiles in postgres' lib/pgxs/ folder and +# all Makefiles in the extension's source. "$DESTDIR/$bindir" can be replaced with +# "$out/bin" etc. - thus mapping the installPhase directly into the right output. +# This works beautifully - for the majority of cases. But it doesn't work for +# some extensions that use CMake. And it doesn't work for some extensions that use +# custom variables instead of the default "bindir" and friends. +# +# 5. Just set DESTDIR to the extensions's output and then clean up afterward. This will +# result in paths like this: +# /nix/store//nix/store//... +# Directly after the installPhase, we'll move the files in the right folder. +# This seems to work consistently across all extensions we have in nixpkgs right now. +# Of course, it would break down for any extension that doesn't support DESTDIR - +# but that just means PGXS is not used either, so that's OK. +# +# This last approach is the one we're taking in this file. To make sure the removal of the +# nested nix/store happens immediately after the installPhase, before any other postInstall +# hooks run, this needs to be run in an override of `mkDerivation` and not in a setup hook. + +{ + lib, + stdenv, + postgresql, + nix-update-script, +}: + +args: + +let + buildPostgresqlExtension = + finalAttrs: + { + enableUpdateScript ? true, + ... + }@prevAttrs: + { + passthru = + prevAttrs.passthru or { } + // lib.optionalAttrs enableUpdateScript { + updateScript = + prevAttrs.passthru.updateScript or (nix-update-script ( + lib.optionalAttrs (lib.hasInfix "unstable" prevAttrs.version) { + extraArgs = [ "--version=branch" ]; + } + )); + }; + + buildInputs = [ postgresql ] ++ prevAttrs.buildInputs or [ ]; + + installFlags = [ + "DESTDIR=${placeholder "out"}" + ] ++ prevAttrs.installFlags or [ ]; + + postInstall = + '' + # DESTDIR + pg_config install the files into + # /nix/store//nix/store//... + # We'll now remove the /nix/store/ part: + if [[ -d "$out${postgresql}" ]]; then + cp -alt "$out" "$out${postgresql}"/* + rm -r "$out${postgresql}" + fi + + if [[ -d "$out${postgresql.dev}" ]]; then + mkdir -p "''${dev:-$out}" + cp -alt "''${dev:-$out}" "$out${postgresql.dev}"/* + rm -r "$out${postgresql.dev}" + fi + + if [[ -d "$out${postgresql.lib}" ]]; then + mkdir -p "''${lib:-$out}" + cp -alt "''${lib:-$out}" "$out${postgresql.lib}"/* + rm -r "$out${postgresql.lib}" + fi + + if [[ -d "$out${postgresql.doc}" ]]; then + mkdir -p "''${doc:-$out}" + cp -alt "''${doc:-$out}" "$out${postgresql.doc}"/* + rm -r "$out${postgresql.doc}" + fi + + if [[ -d "$out${postgresql.man}" ]]; then + mkdir -p "''${man:-$out}" + cp -alt "''${man:-$out}" "$out${postgresql.man}"/* + rm -r "$out${postgresql.man}" + fi + + # In some cases (postgis) parts of the install script + # actually work "OK", before we add DESTDIR, so some + # files end up in + # /nix/store//nix/store//... + if [[ -d "$out$out" ]]; then + cp -alt "$out" "$out$out"/* + rm -r "$out$out" + fi + + if [[ -d "$out/nix/store" ]]; then + if ! rmdir "$out/nix/store" "$out/nix"; then + find "$out/nix" + nixErrorLog 'Found left-overs in $out/nix/store, make sure to move them into $out properly.' + exit 1 + fi + fi + '' + + prevAttrs.postInstall or ""; + }; +in +stdenv.mkDerivation (lib.extends buildPostgresqlExtension (lib.toFunction args)) diff --git a/flake.nix b/flake.nix index c0fd22b..b8c24f2 100644 --- a/flake.nix +++ b/flake.nix @@ -51,31 +51,37 @@ in forAllSystemsWithPkgs [ (import rust-overlay) ] ({ system, pkgs }: { - overlays.${system} = + overlays.default = final: prev: ( - self.packages.${system} // { - postgresql_17.pkgs.http = - let - buildPostgresqlExtension = pkgs.callPackage (import (builtins.path { - name = "extension-builder"; - path = "${final.outPath}/pkgs/servers/sql/postgresql/buildPostgresqlExtension.nix"; - })) { inherit (prev) postgresql_17; }; - version = "1.6.1"; - in - buildPostgresqlExtension { + let + version = "1.6.1"; + buildHttpExt = versionSuffix: let + buildPostgresqlExtension = + pkgs.callPackage (import (builtins.path { + name = "extension-builder"; + path = ./buildPostgresqlExtension.nix; + })) { + postgresql = prev."postgresql_${versionSuffix}"; + }; + in buildPostgresqlExtension { pname = "http"; - inherit version; - + inherit version; src = prev.fetchFromGitHub { owner = "pramsey"; repo = "pgsql-http"; - rev = "v${version}"; + rev = "v${version}"; hash = "sha256-C8eqi0q1dnshUAZjIsZFwa5FTYc7vmATF3vv2CReWPM="; }; - nativeBuildInputs = with prev; [ pkg-config curl ]; }; - }); + in + { + hectic = self.packages.${system}; + postgresql_17.pkgs.http = buildHttpExt "17"; + postgresql_16.pkgs.http = buildHttpExt "16"; + postgresql_15.pkgs.http = buildHttpExt "15"; + postgresql_14.pkgs.http = buildHttpExt "14"; + }); packages.${system} = let rust = {