|
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. |
@@ -138,83 +147,181 @@ let |
138 | 147 | pg_ctl stop |
139 | 148 | ''; |
140 | 149 |
|
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} |
188 | 256 |
|
| 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; |
189 | 272 | preCheck = preBuildAndTest + args.preCheck or ""; |
| 273 | + }; |
190 | 274 |
|
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"; |
193 | 284 |
|
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 ""); |
195 | 291 |
|
196 | | - ${maybeEnterBuildAndTestSubdir} |
| 292 | + postBuild = '' |
| 293 | + ${maybeLeaveBuildAndTestSubdir} |
| 294 | + '' |
| 295 | + + (args.postBuild or ""); |
197 | 296 |
|
198 | | - cargo-${pgrxBinaryName} ${pgrxBinaryName} stop all |
| 297 | + # Dependencies don't have a postInstall phase |
| 298 | + postInstall = ""; |
199 | 299 |
|
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; |
203 | 309 |
|
204 | | - ${maybeLeaveBuildAndTestSubdir} |
| 310 | + # Arguments for crane.buildPackage |
| 311 | + craneArgs = commonArgs // { |
| 312 | + inherit cargoArtifacts; |
| 313 | + pname = args.pname or "pgrx-extension"; |
205 | 314 |
|
206 | | - runHook postInstall |
207 | | - ''; |
| 315 | + # Explicitly preserve postInstall from args (needed for version-specific file renaming) |
| 316 | + postInstall = args.postInstall or ""; |
208 | 317 |
|
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; |
212 | 321 |
|
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 ""; |
218 | 325 | }; |
219 | 326 | in |
220 | | -rustPlatform.buildRustPackage finalArgs |
| 327 | +if useCrane then craneLib.buildPackage craneArgs else rustPlatform.buildRustPackage rustPlatformArgs |
0 commit comments