Skip to content

Commit f6effa2

Browse files
committed
feat: support multiple versions of the pgrouting extension
Build multiple versions of the pgrouting extension on different PostgreSQL versions. Add test for the extensions and their upgrade on PostgreSQL 15 and 17.
1 parent 77cbe8d commit f6effa2

File tree

3 files changed

+302
-55
lines changed

3 files changed

+302
-55
lines changed

nix/ext/pgrouting.nix

Lines changed: 127 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -6,69 +6,141 @@
66
perl,
77
cmake,
88
boost,
9+
buildEnv,
910
}:
10-
11-
stdenv.mkDerivation rec {
11+
let
1212
pname = "pgrouting";
13-
version = "3.4.1";
1413

15-
nativeBuildInputs = [
16-
cmake
17-
perl
18-
];
19-
buildInputs = [
20-
postgresql
21-
boost
22-
];
14+
# Load version configuration from external file
15+
allVersions = (builtins.fromJSON (builtins.readFile ./versions.json)).${pname};
2316

24-
src = fetchFromGitHub {
25-
owner = "pgRouting";
26-
repo = pname;
27-
rev = "v${version}";
28-
hash = "sha256-QC77AnPGpPQGEWi6JtJdiNsB2su5+aV2pKg5ImR2B0k=";
29-
};
17+
# Filter versions compatible with current PostgreSQL version
18+
supportedVersions = lib.filterAttrs (
19+
_: value: builtins.elem (lib.versions.major postgresql.version) value.postgresql
20+
) allVersions;
3021

31-
#disable compile time warnings for incompatible pointer types only on macos and pg16
32-
NIX_CFLAGS_COMPILE = lib.optionalString (
33-
stdenv.isDarwin && lib.versionAtLeast postgresql.version "16"
34-
) "-Wno-error=int-conversion -Wno-error=incompatible-pointer-types";
35-
36-
cmakeFlags =
37-
[ "-DPOSTGRESQL_VERSION=${postgresql.version}" ]
38-
++ lib.optionals (stdenv.isDarwin && lib.versionAtLeast postgresql.version "16") [
39-
"-DCMAKE_MACOSX_RPATH=ON"
40-
"-DCMAKE_SHARED_MODULE_SUFFIX=.dylib"
41-
"-DCMAKE_SHARED_LIBRARY_SUFFIX=.dylib"
42-
];
43-
44-
preConfigure = lib.optionalString (stdenv.isDarwin && lib.versionAtLeast postgresql.version "16") ''
45-
export DLSUFFIX=.dylib
46-
export CMAKE_SHARED_LIBRARY_SUFFIX=.dylib
47-
export CMAKE_SHARED_MODULE_SUFFIX=.dylib
48-
export MACOSX_RPATH=ON
49-
'';
22+
# Derived version information
23+
versions = lib.naturalSort (lib.attrNames supportedVersions);
24+
latestVersion = lib.last versions;
25+
numberOfVersions = builtins.length versions;
26+
packages = builtins.attrValues (
27+
lib.mapAttrs (name: value: build name value.hash) supportedVersions
28+
);
5029

51-
postBuild = lib.optionalString (stdenv.isDarwin && lib.versionAtLeast postgresql.version "16") ''
52-
shopt -s nullglob
53-
for file in lib/libpgrouting-*.so; do
54-
if [ -f "$file" ]; then
55-
mv "$file" "''${file%.so}.dylib"
56-
fi
57-
done
58-
shopt -u nullglob
59-
'';
30+
# Build function for individual versions
31+
build =
32+
version: hash:
33+
stdenv.mkDerivation rec {
34+
inherit pname version;
35+
36+
nativeBuildInputs = [
37+
cmake
38+
perl
39+
];
40+
buildInputs = [
41+
postgresql
42+
boost
43+
];
44+
45+
src = fetchFromGitHub {
46+
owner = "pgRouting";
47+
repo = pname;
48+
rev = "v${version}";
49+
inherit hash;
50+
};
51+
52+
#disable compile time warnings for incompatible pointer types only on macos and pg16
53+
NIX_CFLAGS_COMPILE = lib.optionalString (
54+
stdenv.isDarwin && lib.versionAtLeast postgresql.version "16"
55+
) "-Wno-error=int-conversion -Wno-error=incompatible-pointer-types";
56+
57+
cmakeFlags =
58+
[
59+
"-DPOSTGRESQL_VERSION=${postgresql.version}"
60+
]
61+
++ lib.optionals (stdenv.isDarwin && lib.versionAtLeast postgresql.version "16") [
62+
"-DCMAKE_MACOSX_RPATH=ON"
63+
"-DCMAKE_SHARED_MODULE_SUFFIX=.dylib"
64+
"-DCMAKE_SHARED_LIBRARY_SUFFIX=.dylib"
65+
];
66+
67+
preConfigure = lib.optionalString (stdenv.isDarwin && lib.versionAtLeast postgresql.version "16") ''
68+
export DLSUFFIX=.dylib
69+
export CMAKE_SHARED_LIBRARY_SUFFIX=.dylib
70+
export CMAKE_SHARED_MODULE_SUFFIX=.dylib
71+
export MACOSX_RPATH=ON
72+
'';
73+
74+
postBuild = lib.optionalString (stdenv.isDarwin && lib.versionAtLeast postgresql.version "16") ''
75+
shopt -s nullglob
76+
for file in lib/libpgrouting-*.so; do
77+
if [ -f "$file" ]; then
78+
mv "$file" "''${file%.so}.dylib"
79+
fi
80+
done
81+
shopt -u nullglob
82+
'';
83+
84+
installPhase = ''
85+
MAJ_MIN_VERSION=${lib.concatStringsSep "." (lib.take 2 (builtins.splitVersion version))}
86+
87+
mkdir -p $out/{lib,share/postgresql/extension}
88+
89+
# Install shared library with version suffix
90+
install -D lib/libpgrouting-$MAJ_MIN_VERSION${postgresql.dlSuffix} -t $out/lib
91+
92+
# Create version-specific control file
93+
sed -e "/^default_version =/d" \
94+
-e "s|^module_pathname = .*|module_pathname = '\$libdir/lib${pname}-$MAJ_MIN_VERSION'|" \
95+
sql/common/${pname}.control > $out/share/postgresql/extension/${pname}--${version}.control
96+
97+
# Copy SQL upgrade scripts
98+
cp sql/${pname}--*.sql $out/share/postgresql/extension
99+
100+
if [[ "${version}" == "${latestVersion}" ]]; then
101+
{
102+
echo "default_version = '${version}'"
103+
cat $out/share/postgresql/extension/${pname}--${version}.control
104+
} > $out/share/postgresql/extension/${pname}.control
105+
ln -sfn $out/lib/lib${pname}-$MAJ_MIN_VERSION${postgresql.dlSuffix} $out/lib/lib${pname}${postgresql.dlSuffix}
106+
fi
107+
'';
108+
109+
meta = with lib; {
110+
description = "A PostgreSQL/PostGIS extension that provides geospatial routing functionality";
111+
homepage = "https://pgrouting.org/";
112+
changelog = "https://github.com/pgRouting/pgrouting/releases/tag/v${version}";
113+
license = licenses.gpl2Plus;
114+
inherit (postgresql.meta) platforms;
115+
};
116+
};
117+
in
118+
buildEnv {
119+
name = pname;
120+
paths = packages;
121+
122+
pathsToLink = [
123+
"/lib"
124+
"/share/postgresql/extension"
125+
];
126+
127+
postBuild = ''
128+
#Verify all expected library files are present
129+
expectedFiles=${toString (numberOfVersions + 1)}
130+
actualFiles=$(ls -l $out/lib/lib${pname}*${postgresql.dlSuffix} | wc -l)
60131
61-
installPhase = ''
62-
install -D lib/*${postgresql.dlSuffix} -t $out/lib
63-
install -D sql/pgrouting--*.sql -t $out/share/postgresql/extension
64-
install -D sql/common/pgrouting.control -t $out/share/postgresql/extension
132+
if [[ "$actualFiles" != "$expectedFiles" ]]; then
133+
echo "Error: Expected $expectedFiles library files, found $actualFiles"
134+
echo "Files found:"
135+
ls -la $out/lib/*${postgresql.dlSuffix} || true
136+
exit 1
137+
fi
65138
'';
66139

67-
meta = with lib; {
68-
description = "A PostgreSQL/PostGIS extension that provides geospatial routing functionality";
69-
homepage = "https://pgrouting.org/";
70-
changelog = "https://github.com/pgRouting/pgrouting/releases/tag/v${version}";
71-
platforms = postgresql.meta.platforms;
72-
license = licenses.gpl2Plus;
140+
passthru = {
141+
inherit versions numberOfVersions;
142+
pname = "${pname}-all";
143+
version =
144+
"multi-" + lib.concatStringsSep "-" (map (v: lib.replaceStrings [ "." ] [ "-" ] v) versions);
73145
};
74146
}

nix/ext/tests/pgrouting.nix

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
{ self, pkgs }:
2+
let
3+
pname = "pgrouting";
4+
inherit (pkgs) lib;
5+
installedExtension =
6+
postgresMajorVersion: self.packages.${pkgs.system}."psql_${postgresMajorVersion}/exts/${pname}-all";
7+
versions = postgresqlMajorVersion: (installedExtension postgresqlMajorVersion).versions;
8+
postgresqlWithExtension =
9+
postgresql:
10+
let
11+
majorVersion = lib.versions.major postgresql.version;
12+
pkg = pkgs.buildEnv {
13+
name = "postgresql-${majorVersion}-${pname}";
14+
paths = [
15+
postgresql
16+
postgresql.lib
17+
(installedExtension majorVersion)
18+
self.packages.${pkgs.system}."psql_${majorVersion}/exts/postgis"
19+
];
20+
passthru = {
21+
inherit (postgresql) version psqlSchema;
22+
lib = pkg;
23+
withPackages = _: pkg;
24+
};
25+
nativeBuildInputs = [ pkgs.makeWrapper ];
26+
pathsToLink = [
27+
"/"
28+
"/bin"
29+
"/lib"
30+
];
31+
postBuild = ''
32+
wrapProgram $out/bin/postgres --set NIX_PGLIBDIR $out/lib
33+
wrapProgram $out/bin/pg_ctl --set NIX_PGLIBDIR $out/lib
34+
wrapProgram $out/bin/pg_upgrade --set NIX_PGLIBDIR $out/lib
35+
'';
36+
};
37+
in
38+
pkg;
39+
in
40+
self.inputs.nixpkgs.lib.nixos.runTest {
41+
name = pname;
42+
hostPkgs = pkgs;
43+
nodes.server =
44+
{ config, ... }:
45+
{
46+
virtualisation = {
47+
forwardPorts = [
48+
{
49+
from = "host";
50+
host.port = 13022;
51+
guest.port = 22;
52+
}
53+
];
54+
};
55+
services.openssh = {
56+
enable = true;
57+
};
58+
users.users.root.openssh.authorizedKeys.keys = [
59+
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIo+ulCUfJjnCVgfM4946Ih5Nm8DeZZiayYeABHGPEl7 jfroche"
60+
];
61+
62+
services.postgresql = {
63+
enable = true;
64+
package = postgresqlWithExtension self.packages.${pkgs.system}.postgresql_15;
65+
};
66+
67+
specialisation.postgresql17.configuration = {
68+
services.postgresql = {
69+
package = lib.mkForce (postgresqlWithExtension self.packages.${pkgs.system}.postgresql_17);
70+
};
71+
72+
systemd.services.postgresql-migrate = {
73+
serviceConfig = {
74+
Type = "oneshot";
75+
RemainAfterExit = true;
76+
User = "postgres";
77+
Group = "postgres";
78+
StateDirectory = "postgresql";
79+
WorkingDirectory = "${builtins.dirOf config.services.postgresql.dataDir}";
80+
};
81+
script =
82+
let
83+
oldPostgresql = postgresqlWithExtension self.packages.${pkgs.system}.postgresql_15;
84+
newPostgresql = postgresqlWithExtension self.packages.${pkgs.system}.postgresql_17;
85+
oldDataDir = "${builtins.dirOf config.services.postgresql.dataDir}/${oldPostgresql.psqlSchema}";
86+
newDataDir = "${builtins.dirOf config.services.postgresql.dataDir}/${newPostgresql.psqlSchema}";
87+
in
88+
''
89+
if [[ ! -d ${newDataDir} ]]; then
90+
install -d -m 0700 -o postgres -g postgres "${newDataDir}"
91+
${newPostgresql}/bin/initdb -D "${newDataDir}"
92+
${newPostgresql}/bin/pg_upgrade --old-datadir "${oldDataDir}" --new-datadir "${newDataDir}" \
93+
--old-bindir "${oldPostgresql}/bin" --new-bindir "${newPostgresql}/bin"
94+
else
95+
echo "${newDataDir} already exists"
96+
fi
97+
'';
98+
};
99+
100+
systemd.services.postgresql = {
101+
after = [ "postgresql-migrate.service" ];
102+
requires = [ "postgresql-migrate.service" ];
103+
};
104+
};
105+
106+
};
107+
testScript =
108+
{ nodes, ... }:
109+
let
110+
pg17-configuration = "${nodes.server.system.build.toplevel}/specialisation/postgresql17";
111+
in
112+
''
113+
versions = {
114+
"15": [${lib.concatStringsSep ", " (map (s: ''"${s}"'') (versions "15"))}],
115+
"17": [${lib.concatStringsSep ", " (map (s: ''"${s}"'') (versions "17"))}],
116+
}
117+
118+
def run_sql(query):
119+
return server.succeed(f"""sudo -u postgres psql -t -A -F\",\" -c \"{query}\" """).strip()
120+
121+
def check_upgrade_path(pg_version):
122+
with subtest("Check ${pname} upgrade path"):
123+
firstVersion = versions[pg_version][0]
124+
server.succeed("sudo -u postgres psql -c 'DROP EXTENSION IF EXISTS ${pname};'")
125+
run_sql(f"""CREATE EXTENSION ${pname} WITH VERSION '{firstVersion}' CASCADE;""")
126+
installed_version = run_sql(r"""SELECT extversion FROM pg_extension WHERE extname = '${pname}';""")
127+
assert installed_version == firstVersion, f"Expected ${pname} version {firstVersion}, but found {installed_version}"
128+
for version in versions[pg_version][1:]:
129+
run_sql(f"""ALTER EXTENSION ${pname} UPDATE TO '{version}';""")
130+
installed_version = run_sql(r"""SELECT extversion FROM pg_extension WHERE extname = '${pname}';""")
131+
assert installed_version == version, f"Expected ${pname} version {version}, but found {installed_version}"
132+
133+
start_all()
134+
135+
server.wait_for_unit("multi-user.target")
136+
server.wait_for_unit("postgresql.service")
137+
138+
check_upgrade_path("15")
139+
140+
with subtest("Check ${pname} latest extension version"):
141+
server.succeed("sudo -u postgres psql -c 'DROP EXTENSION ${pname};'")
142+
server.succeed("sudo -u postgres psql -c 'CREATE EXTENSION ${pname} CASCADE;'")
143+
installed_extensions=run_sql(r"""SELECT extname, extversion FROM pg_extension;""")
144+
latestVersion = versions["15"][-1]
145+
assert f"${pname},{latestVersion}" in installed_extensions
146+
147+
with subtest("switch to postgresql 17"):
148+
server.succeed(
149+
"${pg17-configuration}/bin/switch-to-configuration test >&2"
150+
)
151+
152+
with subtest("Check ${pname} latest extension version after upgrade"):
153+
installed_extensions=run_sql(r"""SELECT extname, extversion FROM pg_extension;""")
154+
latestVersion = versions["17"][-1]
155+
assert f"${pname},{latestVersion}" in installed_extensions
156+
157+
check_upgrade_path("17")
158+
'';
159+
}

nix/ext/versions.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,22 @@
146146
"hash": "sha256-Cpi2iASi1QJoED0Qs1dANqg/BNZTsz5S+pw8iYyW03Y="
147147
}
148148
},
149+
"pgrouting": {
150+
"3.4.1": {
151+
"postgresql": [
152+
"15",
153+
"17"
154+
],
155+
"hash": "sha256-QC77AnPGpPQGEWi6JtJdiNsB2su5+aV2pKg5ImR2B0k="
156+
},
157+
"3.8.0": {
158+
"postgresql": [
159+
"15",
160+
"17"
161+
],
162+
"hash": "sha256-Lvf7TQ3GywbzZmcd9wi3s8I5sCXIQAPeXNTRk/J46to="
163+
}
164+
},
149165
"pgsodium": {
150166
"3.0.4": {
151167
"postgresql": [

0 commit comments

Comments
 (0)