|
29 | 29 | { |
30 | 30 | lib, |
31 | 31 | cargo-pgrx, |
| 32 | + craneLib ? null, |
32 | 33 | pkg-config, |
33 | 34 | rustPlatform, |
34 | 35 | stdenv, |
35 | 36 | writeShellScriptBin, |
36 | 37 | defaultBindgenHook, |
37 | 38 | }: |
38 | 39 |
|
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. |
41 | 43 | # |
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: |
43 | 52 | # - `postgresql` postgresql package of the version of postgresql this extension should be build for. |
44 | 53 | # Needs to be the build platform variant. |
45 | 54 | # - `useFakeRustfmt` Whether to use a noop fake command as rustfmt. cargo-pgrx tries to call rustfmt. |
@@ -139,84 +148,184 @@ let |
139 | 148 | pg_ctl stop |
140 | 149 | ''; |
141 | 150 |
|
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 | + # Handle git dependencies based on build system |
| 162 | + cargoVendorDir = |
| 163 | + if useCrane && cargoLockInfo != null then |
| 164 | + # For crane, use vendorCargoDeps with external Cargo.lock file |
| 165 | + craneLib.vendorCargoDeps { |
| 166 | + src = args.src; |
| 167 | + cargoLock = cargoLockInfo.lockFile; |
| 168 | + } |
| 169 | + else if cargoLockInfo != null && cargoLockInfo ? outputHashes then |
| 170 | + # For rustPlatform, use importCargoLock for git dependencies |
| 171 | + rustPlatform.importCargoLock { |
| 172 | + lockFile = cargoLockInfo.lockFile; |
| 173 | + outputHashes = cargoLockInfo.outputHashes; |
| 174 | + allowBuiltinFetchGit = true; |
| 175 | + } |
| 176 | + else |
| 177 | + null; |
| 178 | + |
| 179 | + # Remove rustPlatform-specific args and pgrx-specific args. |
| 180 | + # For crane, also remove build/install phases (added back later). |
| 181 | + argsForBuilder = builtins.removeAttrs args ( |
| 182 | + [ |
| 183 | + "postgresql" |
| 184 | + "useFakeRustfmt" |
| 185 | + "usePgTestCheckFeature" |
| 186 | + ] |
| 187 | + ++ lib.optionals useCrane [ |
| 188 | + "cargoHash" # rustPlatform uses this, crane uses Cargo.lock directly |
| 189 | + "cargoLock" # handled separately via modifiedSrc and cargoVendorDir |
| 190 | + "installPhase" # we provide our own pgrx-specific install phase |
| 191 | + "buildPhase" # we provide our own pgrx-specific build phase |
| 192 | + ] |
| 193 | + ); |
| 194 | + |
| 195 | + # Common arguments for both rustPlatform and crane |
| 196 | + commonArgs = |
| 197 | + argsForBuilder |
| 198 | + // { |
| 199 | + src = args.src; # Use original source - extensions handle external lockfiles via postPatch |
| 200 | + strictDeps = true; |
| 201 | + |
| 202 | + buildInputs = (args.buildInputs or [ ]); |
| 203 | + |
| 204 | + nativeBuildInputs = |
| 205 | + (args.nativeBuildInputs or [ ]) |
| 206 | + ++ [ |
| 207 | + cargo-pgrx |
| 208 | + postgresql |
| 209 | + pkg-config |
| 210 | + bindgenHook |
| 211 | + ] |
| 212 | + ++ lib.optionals useFakeRustfmt [ fakeRustfmt ]; |
| 213 | + |
| 214 | + PGRX_PG_SYS_SKIP_BINDING_REWRITE = "1"; |
| 215 | + CARGO_BUILD_INCREMENTAL = "false"; |
| 216 | + RUST_BACKTRACE = "full"; |
| 217 | + |
| 218 | + checkNoDefaultFeatures = true; |
| 219 | + checkFeatures = |
| 220 | + (args.checkFeatures or [ ]) |
| 221 | + ++ (lib.optionals usePgTestCheckFeature [ "pg_test" ]) |
| 222 | + ++ [ "pg${pgrxPostgresMajor}" ]; |
| 223 | + } |
| 224 | + // lib.optionalAttrs (cargoVendorDir != null) { |
| 225 | + inherit cargoVendorDir; |
| 226 | + }; |
| 227 | + |
| 228 | + # Shared build and install phases for both rustPlatform and crane |
| 229 | + sharedBuildPhase = '' |
| 230 | + runHook preBuild |
| 231 | +
|
| 232 | + ${preBuildAndTest} |
| 233 | + ${maybeEnterBuildAndTestSubdir} |
| 234 | +
|
| 235 | + export PGRX_BUILD_FLAGS="--frozen -j $NIX_BUILD_CORES ${builtins.concatStringsSep " " cargoBuildFlags}" |
| 236 | + export PGX_BUILD_FLAGS="$PGRX_BUILD_FLAGS" |
| 237 | +
|
| 238 | + ${lib.optionalString needsRustcWrapper '' |
| 239 | + export ORIGINAL_RUSTC="$(command -v ${stdenv.cc.targetPrefix}rustc || command -v rustc)" |
| 240 | + export PATH="${rustcWrapper}/bin:$PATH" |
| 241 | + export RUSTC="${rustcWrapper}/bin/rustc" |
| 242 | + ''} |
| 243 | +
|
| 244 | + ${lib.optionalString stdenv.hostPlatform.isDarwin ''RUSTFLAGS="''${RUSTFLAGS:+''${RUSTFLAGS} }-Clink-args=-Wl,-undefined,dynamic_lookup"''} \ |
| 245 | + cargo ${pgrxBinaryName} package \ |
| 246 | + --pg-config ${lib.getDev postgresql}/bin/pg_config \ |
| 247 | + ${maybeDebugFlag} \ |
| 248 | + --features "${builtins.concatStringsSep " " buildFeatures}" \ |
| 249 | + --out-dir "$out" |
| 250 | +
|
| 251 | + ${maybeLeaveBuildAndTestSubdir} |
| 252 | +
|
| 253 | + runHook postBuild |
| 254 | + ''; |
| 255 | + |
| 256 | + sharedInstallPhase = '' |
| 257 | + runHook preInstall |
| 258 | +
|
| 259 | + ${maybeEnterBuildAndTestSubdir} |
190 | 260 |
|
| 261 | + cargo-${pgrxBinaryName} ${pgrxBinaryName} stop all |
| 262 | +
|
| 263 | + mv $out/${postgresql}/* $out |
| 264 | + mv $out/${postgresql.lib}/* $out |
| 265 | + rm -rf $out/nix |
| 266 | +
|
| 267 | + ${maybeLeaveBuildAndTestSubdir} |
| 268 | +
|
| 269 | + runHook postInstall |
| 270 | + ''; |
| 271 | + |
| 272 | + # Arguments for rustPlatform.buildRustPackage |
| 273 | + rustPlatformArgs = commonArgs // { |
| 274 | + buildPhase = sharedBuildPhase; |
| 275 | + installPhase = sharedInstallPhase; |
191 | 276 | preCheck = preBuildAndTest + args.preCheck or ""; |
| 277 | + }; |
192 | 278 |
|
193 | | - installPhase = '' |
194 | | - runHook preInstall |
| 279 | + # Crane's two-phase build: first build dependencies, then build the extension. |
| 280 | + # buildDepsOnly creates a derivation containing only Cargo dependency artifacts. |
| 281 | + # This is cached separately, so changing extension code doesn't rebuild dependencies. |
| 282 | + cargoArtifacts = |
| 283 | + if useCrane then |
| 284 | + craneLib.buildDepsOnly ( |
| 285 | + commonArgs |
| 286 | + // { |
| 287 | + pname = "${args.pname or "pgrx-extension"}-deps"; |
195 | 288 |
|
196 | | - echo "Executing buildPgrxExtension install" |
| 289 | + # pgrx-pg-sys needs PGRX_HOME during dependency build |
| 290 | + preBuild = '' |
| 291 | + ${preBuildAndTest} |
| 292 | + ${maybeEnterBuildAndTestSubdir} |
| 293 | + '' |
| 294 | + + (args.preBuild or ""); |
197 | 295 |
|
198 | | - ${maybeEnterBuildAndTestSubdir} |
| 296 | + postBuild = '' |
| 297 | + ${maybeLeaveBuildAndTestSubdir} |
| 298 | + '' |
| 299 | + + (args.postBuild or ""); |
199 | 300 |
|
200 | | - cargo-${pgrxBinaryName} ${pgrxBinaryName} stop all |
| 301 | + # Dependencies don't have a postInstall phase |
| 302 | + postInstall = ""; |
201 | 303 |
|
202 | | - mv $out/${postgresql}/* $out |
203 | | - mv $out/${postgresql.lib}/* $out |
204 | | - rm -rf $out/nix |
| 304 | + # Need to specify PostgreSQL version feature for pgrx dependencies |
| 305 | + # and disable default features to avoid multiple pg version conflicts |
| 306 | + cargoExtraArgs = "--no-default-features --features ${ |
| 307 | + builtins.concatStringsSep "," ([ "pg${pgrxPostgresMajor}" ] ++ buildFeatures) |
| 308 | + }"; |
| 309 | + } |
| 310 | + ) |
| 311 | + else |
| 312 | + null; |
205 | 313 |
|
206 | | - ${maybeLeaveBuildAndTestSubdir} |
| 314 | + # Arguments for crane.buildPackage |
| 315 | + craneArgs = commonArgs // { |
| 316 | + inherit cargoArtifacts; |
| 317 | + pname = args.pname or "pgrx-extension"; |
207 | 318 |
|
208 | | - runHook postInstall |
209 | | - ''; |
| 319 | + # Explicitly preserve postInstall from args (needed for version-specific file renaming) |
| 320 | + postInstall = args.postInstall or ""; |
210 | 321 |
|
211 | | - PGRX_PG_SYS_SKIP_BINDING_REWRITE = "1"; |
212 | | - CARGO_BUILD_INCREMENTAL = "false"; |
213 | | - RUST_BACKTRACE = "full"; |
| 322 | + # We handle installation ourselves via pgrx, don't let crane try to install binaries |
| 323 | + doNotInstallCargoBinaries = true; |
| 324 | + doNotPostBuildInstallCargoBinaries = true; |
214 | 325 |
|
215 | | - checkNoDefaultFeatures = true; |
216 | | - checkFeatures = |
217 | | - (args.checkFeatures or [ ]) |
218 | | - ++ (lib.optionals usePgTestCheckFeature [ "pg_test" ]) |
219 | | - ++ [ "pg${pgrxPostgresMajor}" ]; |
| 326 | + buildPhase = sharedBuildPhase; |
| 327 | + installPhase = sharedInstallPhase; |
| 328 | + preCheck = preBuildAndTest + args.preCheck or ""; |
220 | 329 | }; |
221 | 330 | in |
222 | | -rustPlatform.buildRustPackage finalArgs |
| 331 | +if useCrane then craneLib.buildPackage craneArgs else rustPlatform.buildRustPackage rustPlatformArgs |
0 commit comments