Skip to content

Commit d95ecff

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. Key implementation details: - Extensions' existing postPatch mechanisms handle external Cargo.lock files - No IFD (Import From Derivation) issues during cross-compilation evaluation - Crane finds lockfiles during build phase, not evaluation phase - Full cross-compilation support for aarch64-linux and aarch64-darwin Changes include: - Added crane dependency to flake.nix and overlays - Enhanced buildPgrxExtension.nix with crane support while preserving rustPlatform fallback - Enabled crane for all three pgrx extensions (pg_jsonschema, pg_graphql, wrappers) - Comprehensive documentation of IFD considerations and solutions All extensions tested and confirmed working with crane's incremental builds!
1 parent becd92c commit d95ecff

File tree

8 files changed

+243
-91
lines changed

8 files changed

+243
-91
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: 177 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,26 @@
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+
# IMPORTANT: External Cargo.lock files are handled by extensions' postPatch phases,
48+
# not by copying during evaluation. This avoids IFD (Import From Derivation) issues
49+
# that caused cross-compilation failures when evaluating aarch64 packages on x86_64.
50+
#
51+
# Additional arguments:
4352
# - `postgresql` postgresql package of the version of postgresql this extension should be build for.
4453
# Needs to be the build platform variant.
4554
# - `useFakeRustfmt` Whether to use a noop fake command as rustfmt. cargo-pgrx tries to call rustfmt.
@@ -138,83 +147,181 @@ let
138147
pg_ctl stop
139148
'';
140149

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-
'';
150+
# Crane-specific: Determine if we're using crane and handle cargo lock info
151+
# Note: External lockfiles are handled by extensions' postPatch, not here, to avoid
152+
# creating platform-specific derivations during evaluation (prevents IFD issues)
153+
useCrane = craneLib != null;
154+
cargoLockInfo = args.cargoLock or null;
155+
156+
# External Cargo.lock files are handled by the extension's postPatch phase
157+
# which creates symlinks. Crane finds them during build, not evaluation.
158+
# This approach prevents IFD cross-compilation issues.
159+
160+
# Use rustPlatform.importCargoLock instead of crane's vendorCargoDeps for git dependencies.
161+
# crane's vendorCargoDeps uses builtins.fetchGit which only searches the default branch,
162+
# causing errors like:
163+
# "error: Cannot find Git revision 'e565bc43c1b9fa6b25a601f68bcec1423a984cc1' in ref
164+
# 'refs/heads/main' of repository 'https://github.com/burmecia/iceberg-rust'!"
165+
# rustPlatform.importCargoLock with allowBuiltinFetchGit searches all refs (branches/tags).
166+
cargoVendorDir =
167+
if useCrane && cargoLockInfo != null && cargoLockInfo ? outputHashes then
168+
rustPlatform.importCargoLock {
169+
lockFile = cargoLockInfo.lockFile;
170+
outputHashes = cargoLockInfo.outputHashes;
171+
allowBuiltinFetchGit = true;
172+
}
173+
else
174+
null;
175+
176+
# Remove rustPlatform-specific args and pgrx-specific args.
177+
# For crane, also remove build/install phases (added back later).
178+
argsForBuilder = builtins.removeAttrs args (
179+
[
180+
"postgresql"
181+
"useFakeRustfmt"
182+
"usePgTestCheckFeature"
183+
]
184+
++ lib.optionals useCrane [
185+
"cargoHash" # rustPlatform uses this, crane uses Cargo.lock directly
186+
"cargoLock" # handled separately via modifiedSrc and cargoVendorDir
187+
"installPhase" # we provide our own pgrx-specific install phase
188+
"buildPhase" # we provide our own pgrx-specific build phase
189+
]
190+
);
191+
192+
# Common arguments for both rustPlatform and crane
193+
commonArgs =
194+
argsForBuilder
195+
// {
196+
src = args.src; # Use original source - extensions handle external lockfiles via postPatch
197+
strictDeps = true;
198+
199+
buildInputs = (args.buildInputs or [ ]);
200+
201+
nativeBuildInputs =
202+
(args.nativeBuildInputs or [ ])
203+
++ [
204+
cargo-pgrx
205+
postgresql
206+
pkg-config
207+
bindgenHook
208+
]
209+
++ lib.optionals useFakeRustfmt [ fakeRustfmt ];
210+
211+
PGRX_PG_SYS_SKIP_BINDING_REWRITE = "1";
212+
CARGO_BUILD_INCREMENTAL = "false";
213+
RUST_BACKTRACE = "full";
214+
215+
checkNoDefaultFeatures = true;
216+
checkFeatures =
217+
(args.checkFeatures or [ ])
218+
++ (lib.optionals usePgTestCheckFeature [ "pg_test" ])
219+
++ [ "pg${pgrxPostgresMajor}" ];
220+
}
221+
// lib.optionalAttrs (cargoVendorDir != null) {
222+
inherit cargoVendorDir;
223+
};
224+
225+
# Shared build and install phases for both rustPlatform and crane
226+
sharedBuildPhase = ''
227+
runHook preBuild
228+
229+
${preBuildAndTest}
230+
${maybeEnterBuildAndTestSubdir}
231+
232+
export PGRX_BUILD_FLAGS="--frozen -j $NIX_BUILD_CORES ${builtins.concatStringsSep " " cargoBuildFlags}"
233+
export PGX_BUILD_FLAGS="$PGRX_BUILD_FLAGS"
234+
235+
${lib.optionalString needsRustcWrapper ''
236+
export PATH="${rustcWrapper}/bin:$PATH"
237+
export RUSTC="${rustcWrapper}/bin/rustc"
238+
''}
239+
240+
${lib.optionalString stdenv.hostPlatform.isDarwin ''RUSTFLAGS="''${RUSTFLAGS:+''${RUSTFLAGS} }-Clink-args=-Wl,-undefined,dynamic_lookup"''} \
241+
cargo ${pgrxBinaryName} package \
242+
--pg-config ${lib.getDev postgresql}/bin/pg_config \
243+
${maybeDebugFlag} \
244+
--features "${builtins.concatStringsSep " " buildFeatures}" \
245+
--out-dir "$out"
246+
247+
${maybeLeaveBuildAndTestSubdir}
248+
249+
runHook postBuild
250+
'';
251+
252+
sharedInstallPhase = ''
253+
runHook preInstall
254+
255+
${maybeEnterBuildAndTestSubdir}
188256
257+
cargo-${pgrxBinaryName} ${pgrxBinaryName} stop all
258+
259+
mv $out/${postgresql}/* $out
260+
mv $out/${postgresql.lib}/* $out
261+
rm -rf $out/nix
262+
263+
${maybeLeaveBuildAndTestSubdir}
264+
265+
runHook postInstall
266+
'';
267+
268+
# Arguments for rustPlatform.buildRustPackage
269+
rustPlatformArgs = commonArgs // {
270+
buildPhase = sharedBuildPhase;
271+
installPhase = sharedInstallPhase;
189272
preCheck = preBuildAndTest + args.preCheck or "";
273+
};
190274

191-
installPhase = ''
192-
runHook preInstall
275+
# Crane's two-phase build: first build dependencies, then build the extension.
276+
# buildDepsOnly creates a derivation containing only Cargo dependency artifacts.
277+
# This is cached separately, so changing extension code doesn't rebuild dependencies.
278+
cargoArtifacts =
279+
if useCrane then
280+
craneLib.buildDepsOnly (
281+
commonArgs
282+
// {
283+
pname = "${args.pname or "pgrx-extension"}-deps";
193284

194-
echo "Executing buildPgrxExtension install"
285+
# pgrx-pg-sys needs PGRX_HOME during dependency build
286+
preBuild = ''
287+
${preBuildAndTest}
288+
${maybeEnterBuildAndTestSubdir}
289+
''
290+
+ (args.preBuild or "");
195291

196-
${maybeEnterBuildAndTestSubdir}
292+
postBuild = ''
293+
${maybeLeaveBuildAndTestSubdir}
294+
''
295+
+ (args.postBuild or "");
197296

198-
cargo-${pgrxBinaryName} ${pgrxBinaryName} stop all
297+
# Dependencies don't have a postInstall phase
298+
postInstall = "";
199299

200-
mv $out/${postgresql}/* $out
201-
mv $out/${postgresql.lib}/* $out
202-
rm -rf $out/nix
300+
# Need to specify PostgreSQL version feature for pgrx dependencies
301+
# and disable default features to avoid multiple pg version conflicts
302+
cargoExtraArgs = "--no-default-features --features ${
303+
builtins.concatStringsSep "," ([ "pg${pgrxPostgresMajor}" ] ++ buildFeatures)
304+
}";
305+
}
306+
)
307+
else
308+
null;
203309

204-
${maybeLeaveBuildAndTestSubdir}
310+
# Arguments for crane.buildPackage
311+
craneArgs = commonArgs // {
312+
inherit cargoArtifacts;
313+
pname = args.pname or "pgrx-extension";
205314

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

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

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

nix/cargo-pgrx/mkPgrxExtension.nix

Lines changed: 16 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,19 @@ 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+
craneLib =
63+
if useCrane then
64+
assert crane != null;
65+
(crane.mkLib pkgs).overrideToolchain rust-bin.stable.${rustVersion}.default
66+
else
67+
null;
5468
in
69+
# Use unified builder that supports both crane and rustPlatform
5570
callPackage ./buildPgrxExtension.nix {
56-
inherit rustPlatform;
57-
inherit cargo-pgrx;
71+
inherit rustPlatform cargo-pgrx craneLib;
5872
defaultBindgenHook = bindgenHook;
5973
}

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 = true;
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";

0 commit comments

Comments
 (0)