Skip to content

Commit 7f01b44

Browse files
yvan-srakaclaude
andcommitted
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. The awk script in preConfigure was also corrected to check getline return values to prevent infinite loops at end-of-file. The rustcWrapper used by pgrx versions prior to 0.12.0 had an infinite recursion bug where ORIGINAL_RUSTC was referenced but never initialized. When the wrapper modified PATH to intercept rustc calls, it would recursively invoke itself because the original rustc location was unknown. The fix initializes ORIGINAL_RUSTC using command -v before modifying PATH, ensuring the wrapper can locate the actual rustc executable. pg_jsonschema now uses crane successfully, while pg_graphql and wrappers remain on rustPlatform. The rustPlatform path continues to work correctly with the rustcWrapper fix in place for extensions using older pgrx versions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 3df6c4a commit 7f01b44

File tree

8 files changed

+247
-94
lines changed

8 files changed

+247
-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: 178 additions & 71 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.
@@ -139,84 +148,182 @@ let
139148
pg_ctl stop
140149
'';
141150

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

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

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

198-
${maybeEnterBuildAndTestSubdir}
294+
postBuild = ''
295+
${maybeLeaveBuildAndTestSubdir}
296+
''
297+
+ (args.postBuild or "");
199298

200-
cargo-${pgrxBinaryName} ${pgrxBinaryName} stop all
299+
# Dependencies don't have a postInstall phase
300+
postInstall = "";
201301

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

206-
${maybeLeaveBuildAndTestSubdir}
312+
# Arguments for crane.buildPackage
313+
craneArgs = commonArgs // {
314+
inherit cargoArtifacts;
315+
pname = args.pname or "pgrx-extension";
207316

208-
runHook postInstall
209-
'';
317+
# Explicitly preserve postInstall from args (needed for version-specific file renaming)
318+
postInstall = args.postInstall or "";
210319

211-
PGRX_PG_SYS_SKIP_BINDING_REWRITE = "1";
212-
CARGO_BUILD_INCREMENTAL = "false";
213-
RUST_BACKTRACE = "full";
320+
# We handle installation ourselves via pgrx, don't let crane try to install binaries
321+
doNotInstallCargoBinaries = true;
322+
doNotPostBuildInstallCargoBinaries = true;
214323

215-
checkNoDefaultFeatures = true;
216-
checkFeatures =
217-
(args.checkFeatures or [ ])
218-
++ (lib.optionals usePgTestCheckFeature [ "pg_test" ])
219-
++ [ "pg${pgrxPostgresMajor}" ];
324+
buildPhase = sharedBuildPhase;
325+
installPhase = sharedInstallPhase;
326+
preCheck = preBuildAndTest + args.preCheck or "";
220327
};
221328
in
222-
rustPlatform.buildRustPackage finalArgs
329+
if useCrane then craneLib.buildPackage craneArgs else rustPlatform.buildRustPackage rustPlatformArgs

nix/cargo-pgrx/mkPgrxExtension.nix

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
makeRustPlatform,
66
rust-bin,
77
system,
8+
crane ? null,
9+
useCrane ? false,
10+
pkgs,
811
}:
912
let
1013
inherit ((callPackage ./default.nix { inherit rustVersion; })) mkCargoPgrx;
@@ -51,9 +54,19 @@ let
5154
}
5255
else
5356
rustPlatform.bindgenHook;
57+
58+
# Initialize crane with the same Rust toolchain as rustPlatform to ensure consistency.
59+
# crane.mkLib creates a library of crane functions bound to a specific package set,
60+
# then we override the toolchain to match the pgrx-required Rust version.
61+
craneLib =
62+
if useCrane then
63+
assert crane != null;
64+
(crane.mkLib pkgs).overrideToolchain rust-bin.stable.${rustVersion}.default
65+
else
66+
null;
5467
in
68+
# Use unified builder that supports both crane and rustPlatform
5569
callPackage ./buildPgrxExtension.nix {
56-
inherit rustPlatform;
57-
inherit cargo-pgrx;
70+
inherit rustPlatform cargo-pgrx craneLib;
5871
defaultBindgenHook = bindgenHook;
5972
}

nix/ext/pg_graphql/default.nix

Lines changed: 5 additions & 2 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,8 +104,10 @@ 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 ];
107+
# External Cargo.lock needs to be linked for rustPlatform
108+
postPatch = ''
109+
ln -s ${./Cargo-${version}.lock} Cargo.lock
110+
'';
108111

109112
cargoLock = {
110113
lockFile = ./Cargo-${version}.lock;

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)