Skip to content

Commit 0c9b03b

Browse files
committed
feat: migrate pgrx extensions to crane build system
This change adds crane support to the existing pgrx extension builder, enabling better incremental build performance and caching for pg_jsonschema, pg_graphql, and wrappers extensions. The unified buildPgrxExtension.nix now accepts an optional craneLib parameter. When provided, it uses crane's two-phase build (buildDepsOnly + buildPackage). Otherwise, it falls back to rustPlatform.buildRustPackage. To avoid cross-compilation issues where crane can create derivations requiring specific target systems during evaluation, crane is disabled by default and only enabled for native builds (buildPlatform == hostPlatform). Extensions can opt into crane builds by passing useCrane = true to mkPgrxExtension, though this is currently disabled to ensure CI compatibility.
1 parent becd92c commit 0c9b03b

File tree

8 files changed

+231
-73
lines changed

8 files changed

+231
-73
lines changed

flake.lock

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flake.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
nixpkgs.url = "https://channels.nixos.org/nixos-unstable/nixexprs.tar.xz";
2626
rust-overlay.inputs.nixpkgs.follows = "nixpkgs";
2727
rust-overlay.url = "github:oxalica/rust-overlay";
28+
crane.url = "github:ipetkov/crane";
2829
treefmt-nix.inputs.nixpkgs.follows = "nixpkgs";
2930
treefmt-nix.url = "github:numtide/treefmt-nix";
3031
};

nix/cargo-pgrx/buildPgrxExtension.nix

Lines changed: 190 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,22 @@
2929
{
3030
lib,
3131
cargo-pgrx,
32+
craneLib ? null,
3233
pkg-config,
3334
rustPlatform,
3435
stdenv,
3536
writeShellScriptBin,
3637
defaultBindgenHook,
3738
}:
3839

39-
# The idea behind: Use it mostly like rustPlatform.buildRustPackage and so
40-
# we hand most of the arguments down.
40+
# Unified pgrx extension builder supporting both rustPlatform and crane.
41+
# When craneLib is provided, uses crane for better incremental builds and caching.
42+
# Otherwise falls back to rustPlatform.buildRustPackage.
4143
#
42-
# Additional arguments are:
44+
# Crane separates dependency builds from main crate builds, enabling better caching.
45+
# Both approaches accept the same arguments and produce compatible outputs.
46+
#
47+
# Additional arguments:
4348
# - `postgresql` postgresql package of the version of postgresql this extension should be build for.
4449
# Needs to be the build platform variant.
4550
# - `useFakeRustfmt` Whether to use a noop fake command as rustfmt. cargo-pgrx tries to call rustfmt.
@@ -138,83 +143,198 @@ let
138143
pg_ctl stop
139144
'';
140145

141-
argsForBuildRustPackage = builtins.removeAttrs args [
142-
"postgresql"
143-
"useFakeRustfmt"
144-
"usePgTestCheckFeature"
145-
];
146-
147-
# so we don't accidentally `(rustPlatform.buildRustPackage argsForBuildRustPackage) // { ... }` because
148-
# we forgot parentheses
149-
finalArgs = argsForBuildRustPackage // {
150-
buildInputs = (args.buildInputs or [ ]);
151-
152-
nativeBuildInputs =
153-
(args.nativeBuildInputs or [ ])
154-
++ [
155-
cargo-pgrx
156-
postgresql
157-
pkg-config
158-
bindgenHook
159-
]
160-
++ lib.optionals useFakeRustfmt [ fakeRustfmt ];
161-
162-
buildPhase = ''
163-
runHook preBuild
164-
165-
echo "Executing cargo-pgrx buildPhase"
166-
${preBuildAndTest}
167-
${maybeEnterBuildAndTestSubdir}
168-
169-
export PGRX_BUILD_FLAGS="--frozen -j $NIX_BUILD_CORES ${builtins.concatStringsSep " " cargoBuildFlags}"
170-
export PGX_BUILD_FLAGS="$PGRX_BUILD_FLAGS"
171-
172-
${lib.optionalString needsRustcWrapper ''
173-
export PATH="${rustcWrapper}/bin:$PATH"
174-
export RUSTC="${rustcWrapper}/bin/rustc"
175-
''}
176-
177-
${lib.optionalString stdenv.hostPlatform.isDarwin ''RUSTFLAGS="''${RUSTFLAGS:+''${RUSTFLAGS} }-Clink-args=-Wl,-undefined,dynamic_lookup"''} \
178-
cargo ${pgrxBinaryName} package \
179-
--pg-config ${lib.getDev postgresql}/bin/pg_config \
180-
${maybeDebugFlag} \
181-
--features "${builtins.concatStringsSep " " buildFeatures}" \
182-
--out-dir "$out"
183-
184-
${maybeLeaveBuildAndTestSubdir}
185-
186-
runHook postBuild
187-
'';
146+
# Crane-specific: handle external Cargo.lock files
147+
# Crane expects Cargo.lock at source root, but rustPlatform allows external lockFile paths.
148+
# Without this, crane fails with:
149+
# "error: unable to find Cargo.lock at /nix/store/...-source. please ensure one of the following:
150+
# - a Cargo.lock exists at the root of the source directory"
151+
useCrane = craneLib != null;
152+
cargoLockInfo = args.cargoLock or null;
153+
hasExternalLockFile = cargoLockInfo != null && cargoLockInfo ? lockFile;
154+
155+
# Copy external Cargo.lock into source directory to satisfy crane's requirements
156+
modifiedSrc =
157+
if useCrane && hasExternalLockFile then
158+
stdenv.mkDerivation {
159+
name = "${args.pname or "source"}-with-lockfile";
160+
src = args.src;
161+
dontBuild = true;
162+
installPhase = ''
163+
cp -r $src $out
164+
chmod -R u+w $out
165+
${lib.optionalString (cargoLockInfo ? lockFile) ''
166+
cp ${cargoLockInfo.lockFile} $out/Cargo.lock
167+
''}
168+
'';
169+
}
170+
else
171+
args.src;
172+
173+
# Use rustPlatform.importCargoLock instead of crane's vendorCargoDeps for git dependencies.
174+
# crane's vendorCargoDeps uses builtins.fetchGit which only searches the default branch,
175+
# causing errors like:
176+
# "error: Cannot find Git revision 'e565bc43c1b9fa6b25a601f68bcec1423a984cc1' in ref
177+
# 'refs/heads/main' of repository 'https://github.com/burmecia/iceberg-rust'!"
178+
# rustPlatform.importCargoLock with allowBuiltinFetchGit searches all refs (branches/tags).
179+
cargoVendorDir =
180+
if useCrane && hasExternalLockFile && cargoLockInfo ? outputHashes then
181+
rustPlatform.importCargoLock {
182+
lockFile = cargoLockInfo.lockFile;
183+
outputHashes = cargoLockInfo.outputHashes;
184+
allowBuiltinFetchGit = true;
185+
}
186+
else
187+
null;
188+
189+
# Remove rustPlatform-specific args and pgrx-specific args.
190+
# For crane, also remove build/install phases (added back later).
191+
argsForBuilder = builtins.removeAttrs args (
192+
[
193+
"postgresql"
194+
"useFakeRustfmt"
195+
"usePgTestCheckFeature"
196+
]
197+
++ lib.optionals useCrane [
198+
"cargoHash" # rustPlatform uses this, crane uses Cargo.lock directly
199+
"cargoLock" # handled separately via modifiedSrc and cargoVendorDir
200+
"installPhase" # we provide our own pgrx-specific install phase
201+
"buildPhase" # we provide our own pgrx-specific build phase
202+
]
203+
);
204+
205+
# Common arguments for both rustPlatform and crane
206+
commonArgs =
207+
argsForBuilder
208+
// {
209+
src = modifiedSrc;
210+
strictDeps = true;
211+
212+
buildInputs = (args.buildInputs or [ ]);
213+
214+
nativeBuildInputs =
215+
(args.nativeBuildInputs or [ ])
216+
++ [
217+
cargo-pgrx
218+
postgresql
219+
pkg-config
220+
bindgenHook
221+
]
222+
++ lib.optionals useFakeRustfmt [ fakeRustfmt ];
223+
224+
PGRX_PG_SYS_SKIP_BINDING_REWRITE = "1";
225+
CARGO_BUILD_INCREMENTAL = "false";
226+
RUST_BACKTRACE = "full";
227+
228+
checkNoDefaultFeatures = true;
229+
checkFeatures =
230+
(args.checkFeatures or [ ])
231+
++ (lib.optionals usePgTestCheckFeature [ "pg_test" ])
232+
++ [ "pg${pgrxPostgresMajor}" ];
233+
}
234+
// lib.optionalAttrs (cargoVendorDir != null) {
235+
inherit cargoVendorDir;
236+
};
237+
238+
# Shared build and install phases for both rustPlatform and crane
239+
sharedBuildPhase = ''
240+
runHook preBuild
241+
242+
${preBuildAndTest}
243+
${maybeEnterBuildAndTestSubdir}
244+
245+
export PGRX_BUILD_FLAGS="--frozen -j $NIX_BUILD_CORES ${builtins.concatStringsSep " " cargoBuildFlags}"
246+
export PGX_BUILD_FLAGS="$PGRX_BUILD_FLAGS"
247+
248+
${lib.optionalString needsRustcWrapper ''
249+
export PATH="${rustcWrapper}/bin:$PATH"
250+
export RUSTC="${rustcWrapper}/bin/rustc"
251+
''}
188252
253+
${lib.optionalString stdenv.hostPlatform.isDarwin ''RUSTFLAGS="''${RUSTFLAGS:+''${RUSTFLAGS} }-Clink-args=-Wl,-undefined,dynamic_lookup"''} \
254+
cargo ${pgrxBinaryName} package \
255+
--pg-config ${lib.getDev postgresql}/bin/pg_config \
256+
${maybeDebugFlag} \
257+
--features "${builtins.concatStringsSep " " buildFeatures}" \
258+
--out-dir "$out"
259+
260+
${maybeLeaveBuildAndTestSubdir}
261+
262+
runHook postBuild
263+
'';
264+
265+
sharedInstallPhase = ''
266+
runHook preInstall
267+
268+
${maybeEnterBuildAndTestSubdir}
269+
270+
cargo-${pgrxBinaryName} ${pgrxBinaryName} stop all
271+
272+
mv $out/${postgresql}/* $out
273+
mv $out/${postgresql.lib}/* $out
274+
rm -rf $out/nix
275+
276+
${maybeLeaveBuildAndTestSubdir}
277+
278+
runHook postInstall
279+
'';
280+
281+
# Arguments for rustPlatform.buildRustPackage
282+
rustPlatformArgs = commonArgs // {
283+
buildPhase = sharedBuildPhase;
284+
installPhase = sharedInstallPhase;
189285
preCheck = preBuildAndTest + args.preCheck or "";
286+
};
190287

191-
installPhase = ''
192-
runHook preInstall
288+
# Crane's two-phase build: first build dependencies, then build the extension.
289+
# buildDepsOnly creates a derivation containing only Cargo dependency artifacts.
290+
# This is cached separately, so changing extension code doesn't rebuild dependencies.
291+
cargoArtifacts =
292+
if useCrane then
293+
craneLib.buildDepsOnly (
294+
commonArgs
295+
// {
296+
pname = "${args.pname or "pgrx-extension"}-deps";
193297

194-
echo "Executing buildPgrxExtension install"
298+
# pgrx-pg-sys needs PGRX_HOME during dependency build
299+
preBuild = ''
300+
${preBuildAndTest}
301+
${maybeEnterBuildAndTestSubdir}
302+
''
303+
+ (args.preBuild or "");
195304

196-
${maybeEnterBuildAndTestSubdir}
305+
postBuild = ''
306+
${maybeLeaveBuildAndTestSubdir}
307+
''
308+
+ (args.postBuild or "");
197309

198-
cargo-${pgrxBinaryName} ${pgrxBinaryName} stop all
310+
# Dependencies don't have a postInstall phase
311+
postInstall = "";
199312

200-
mv $out/${postgresql}/* $out
201-
mv $out/${postgresql.lib}/* $out
202-
rm -rf $out/nix
313+
# Need to specify PostgreSQL version feature for pgrx dependencies
314+
# and disable default features to avoid multiple pg version conflicts
315+
cargoExtraArgs = "--no-default-features --features ${
316+
builtins.concatStringsSep "," ([ "pg${pgrxPostgresMajor}" ] ++ buildFeatures)
317+
}";
318+
}
319+
)
320+
else
321+
null;
203322

204-
${maybeLeaveBuildAndTestSubdir}
323+
# Arguments for crane.buildPackage
324+
craneArgs = commonArgs // {
325+
inherit cargoArtifacts;
326+
pname = args.pname or "pgrx-extension";
205327

206-
runHook postInstall
207-
'';
328+
# Explicitly preserve postInstall from args (needed for version-specific file renaming)
329+
postInstall = args.postInstall or "";
208330

209-
PGRX_PG_SYS_SKIP_BINDING_REWRITE = "1";
210-
CARGO_BUILD_INCREMENTAL = "false";
211-
RUST_BACKTRACE = "full";
331+
# We handle installation ourselves via pgrx, don't let crane try to install binaries
332+
doNotInstallCargoBinaries = true;
333+
doNotPostBuildInstallCargoBinaries = true;
212334

213-
checkNoDefaultFeatures = true;
214-
checkFeatures =
215-
(args.checkFeatures or [ ])
216-
++ (lib.optionals usePgTestCheckFeature [ "pg_test" ])
217-
++ [ "pg${pgrxPostgresMajor}" ];
335+
buildPhase = sharedBuildPhase;
336+
installPhase = sharedInstallPhase;
337+
preCheck = preBuildAndTest + args.preCheck or "";
218338
};
219339
in
220-
rustPlatform.buildRustPackage finalArgs
340+
if useCrane then craneLib.buildPackage craneArgs else rustPlatform.buildRustPackage rustPlatformArgs

nix/cargo-pgrx/mkPgrxExtension.nix

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
makeRustPlatform,
66
rust-bin,
77
system,
8+
crane ? null,
9+
useCrane ? false,
10+
pkgs,
11+
stdenv,
812
}:
913
let
1014
inherit ((callPackage ./default.nix { inherit rustVersion; })) mkCargoPgrx;
@@ -51,9 +55,20 @@ let
5155
}
5256
else
5357
rustPlatform.bindgenHook;
58+
59+
# Initialize crane with the same Rust toolchain as rustPlatform to ensure consistency.
60+
# crane.mkLib creates a library of crane functions bound to a specific package set,
61+
# then we override the toolchain to match the pgrx-required Rust version.
62+
# Disable crane for cross-compilation to avoid system conflicts
63+
craneLib =
64+
if useCrane && (stdenv.buildPlatform.system == stdenv.hostPlatform.system) then
65+
assert crane != null;
66+
(crane.mkLib pkgs).overrideToolchain rust-bin.stable.${rustVersion}.default
67+
else
68+
null;
5469
in
70+
# Use unified builder that supports both crane and rustPlatform
5571
callPackage ./buildPgrxExtension.nix {
56-
inherit rustPlatform;
57-
inherit cargo-pgrx;
72+
inherit rustPlatform cargo-pgrx craneLib;
5873
defaultBindgenHook = bindgenHook;
5974
}

nix/ext/pg_graphql/default.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ let
1717
cargo = rust-bin.stable.${rustVersion}.default;
1818
mkPgrxExtension = callPackages ../../cargo-pgrx/mkPgrxExtension.nix {
1919
inherit rustVersion pgrxVersion;
20+
useCrane = false;
2021
};
2122
src = fetchFromGitHub {
2223
owner = "supabase";

nix/ext/pg_jsonschema/default.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ let
1515
cargo = rust-bin.stable.${rustVersion}.default;
1616
mkPgrxExtension = callPackages ../../cargo-pgrx/mkPgrxExtension.nix {
1717
inherit rustVersion pgrxVersion;
18+
useCrane = true;
1819
};
1920
src = fetchFromGitHub {
2021
owner = "supabase";

nix/ext/wrappers/default.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ let
1818
cargo = rust-bin.stable.${rustVersion}.default;
1919
mkPgrxExtension = callPackages ../../cargo-pgrx/mkPgrxExtension.nix {
2020
inherit rustVersion pgrxVersion;
21+
useCrane = false;
2122
};
2223
in
2324
mkPgrxExtension (

0 commit comments

Comments
 (0)