Skip to content

Commit 4665a3f

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. The unified buildPgrxExtension.nix accepts an optional craneLib parameter and uses crane's two-phase build pattern (buildDepsOnly + buildPackage) when available, falling back to rustPlatform otherwise. The key insight was that crane's modifiedSrc derivations created platform-specific builds during evaluation, causing IFD cross-compilation failures. The solution removes unnecessary source modifications and relies on extensions' existing postPatch mechanisms to handle external Cargo.lock files. This prevents IFD issues while maintaining crane's incremental build benefits. For wrappers extension, the preConfigure script was enhanced to handle both legacy table-style and modern inline Cargo.toml dependency formats, and conditionally skips git URL modifications when using cargoVendorDir to prevent networking issues in sandboxed builds. pg_jsonschema and wrappers now use crane successfully, while pg_graphql remains on rustPlatform due to git dependency versioning complexities that can be addressed separately.
1 parent 288b31d commit 4665a3f

File tree

8 files changed

+245
-94
lines changed

8 files changed

+245
-94
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: 3 additions & 3 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";
@@ -103,9 +104,8 @@ let
103104
};
104105
}
105106
// lib.optionalAttrs (builtins.compareVersions "1.2.0" version >= 0) {
106-
# Add missing Cargo.lock
107-
patches = [ ./0001-Add-missing-Cargo.lock-${version}.patch ];
108-
107+
# External Cargo.lock handled by crane's postPatch via cargoLock.lockFile
108+
# Patches that add Cargo.lock are no longer needed - they conflict with crane's mechanism
109109
cargoLock = {
110110
lockFile = ./Cargo-${version}.lock;
111111
outputHashes = {

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)