|
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 | +# Additional arguments: |
43 | 48 | # - `postgresql` postgresql package of the version of postgresql this extension should be build for. |
44 | 49 | # Needs to be the build platform variant. |
45 | 50 | # - `useFakeRustfmt` Whether to use a noop fake command as rustfmt. cargo-pgrx tries to call rustfmt. |
@@ -138,83 +143,198 @@ let |
138 | 143 | pg_ctl stop |
139 | 144 | ''; |
140 | 145 |
|
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 | | - ''; |
| 146 | + # Crane-specific: handle external Cargo.lock files |
| 147 | + # Crane expects Cargo.lock at source root, but rustPlatform allows external lockFile paths. |
| 148 | + # Without this, crane fails with: |
| 149 | + # "error: unable to find Cargo.lock at /nix/store/...-source. please ensure one of the following: |
| 150 | + # - a Cargo.lock exists at the root of the source directory" |
| 151 | + useCrane = craneLib != null; |
| 152 | + cargoLockInfo = args.cargoLock or null; |
| 153 | + hasExternalLockFile = cargoLockInfo != null && cargoLockInfo ? lockFile; |
| 154 | + |
| 155 | + # Copy external Cargo.lock into source directory to satisfy crane's requirements |
| 156 | + modifiedSrc = |
| 157 | + if useCrane && hasExternalLockFile then |
| 158 | + stdenv.mkDerivation { |
| 159 | + name = "${args.pname or "source"}-with-lockfile"; |
| 160 | + src = args.src; |
| 161 | + dontBuild = true; |
| 162 | + installPhase = '' |
| 163 | + cp -r $src $out |
| 164 | + chmod -R u+w $out |
| 165 | + ${lib.optionalString (cargoLockInfo ? lockFile) '' |
| 166 | + cp ${cargoLockInfo.lockFile} $out/Cargo.lock |
| 167 | + ''} |
| 168 | + ''; |
| 169 | + } |
| 170 | + else |
| 171 | + args.src; |
| 172 | + |
| 173 | + # Use rustPlatform.importCargoLock instead of crane's vendorCargoDeps for git dependencies. |
| 174 | + # crane's vendorCargoDeps uses builtins.fetchGit which only searches the default branch, |
| 175 | + # causing errors like: |
| 176 | + # "error: Cannot find Git revision 'e565bc43c1b9fa6b25a601f68bcec1423a984cc1' in ref |
| 177 | + # 'refs/heads/main' of repository 'https://github.com/burmecia/iceberg-rust'!" |
| 178 | + # rustPlatform.importCargoLock with allowBuiltinFetchGit searches all refs (branches/tags). |
| 179 | + cargoVendorDir = |
| 180 | + if useCrane && hasExternalLockFile && cargoLockInfo ? outputHashes then |
| 181 | + rustPlatform.importCargoLock { |
| 182 | + lockFile = cargoLockInfo.lockFile; |
| 183 | + outputHashes = cargoLockInfo.outputHashes; |
| 184 | + allowBuiltinFetchGit = true; |
| 185 | + } |
| 186 | + else |
| 187 | + null; |
| 188 | + |
| 189 | + # Remove rustPlatform-specific args and pgrx-specific args. |
| 190 | + # For crane, also remove build/install phases (added back later). |
| 191 | + argsForBuilder = builtins.removeAttrs args ( |
| 192 | + [ |
| 193 | + "postgresql" |
| 194 | + "useFakeRustfmt" |
| 195 | + "usePgTestCheckFeature" |
| 196 | + ] |
| 197 | + ++ lib.optionals useCrane [ |
| 198 | + "cargoHash" # rustPlatform uses this, crane uses Cargo.lock directly |
| 199 | + "cargoLock" # handled separately via modifiedSrc and cargoVendorDir |
| 200 | + "installPhase" # we provide our own pgrx-specific install phase |
| 201 | + "buildPhase" # we provide our own pgrx-specific build phase |
| 202 | + ] |
| 203 | + ); |
| 204 | + |
| 205 | + # Common arguments for both rustPlatform and crane |
| 206 | + commonArgs = |
| 207 | + argsForBuilder |
| 208 | + // { |
| 209 | + src = modifiedSrc; |
| 210 | + strictDeps = true; |
| 211 | + |
| 212 | + buildInputs = (args.buildInputs or [ ]); |
| 213 | + |
| 214 | + nativeBuildInputs = |
| 215 | + (args.nativeBuildInputs or [ ]) |
| 216 | + ++ [ |
| 217 | + cargo-pgrx |
| 218 | + postgresql |
| 219 | + pkg-config |
| 220 | + bindgenHook |
| 221 | + ] |
| 222 | + ++ lib.optionals useFakeRustfmt [ fakeRustfmt ]; |
| 223 | + |
| 224 | + PGRX_PG_SYS_SKIP_BINDING_REWRITE = "1"; |
| 225 | + CARGO_BUILD_INCREMENTAL = "false"; |
| 226 | + RUST_BACKTRACE = "full"; |
| 227 | + |
| 228 | + checkNoDefaultFeatures = true; |
| 229 | + checkFeatures = |
| 230 | + (args.checkFeatures or [ ]) |
| 231 | + ++ (lib.optionals usePgTestCheckFeature [ "pg_test" ]) |
| 232 | + ++ [ "pg${pgrxPostgresMajor}" ]; |
| 233 | + } |
| 234 | + // lib.optionalAttrs (cargoVendorDir != null) { |
| 235 | + inherit cargoVendorDir; |
| 236 | + }; |
| 237 | + |
| 238 | + # Shared build and install phases for both rustPlatform and crane |
| 239 | + sharedBuildPhase = '' |
| 240 | + runHook preBuild |
| 241 | +
|
| 242 | + ${preBuildAndTest} |
| 243 | + ${maybeEnterBuildAndTestSubdir} |
| 244 | +
|
| 245 | + export PGRX_BUILD_FLAGS="--frozen -j $NIX_BUILD_CORES ${builtins.concatStringsSep " " cargoBuildFlags}" |
| 246 | + export PGX_BUILD_FLAGS="$PGRX_BUILD_FLAGS" |
| 247 | +
|
| 248 | + ${lib.optionalString needsRustcWrapper '' |
| 249 | + export PATH="${rustcWrapper}/bin:$PATH" |
| 250 | + export RUSTC="${rustcWrapper}/bin/rustc" |
| 251 | + ''} |
188 | 252 |
|
| 253 | + ${lib.optionalString stdenv.hostPlatform.isDarwin ''RUSTFLAGS="''${RUSTFLAGS:+''${RUSTFLAGS} }-Clink-args=-Wl,-undefined,dynamic_lookup"''} \ |
| 254 | + cargo ${pgrxBinaryName} package \ |
| 255 | + --pg-config ${lib.getDev postgresql}/bin/pg_config \ |
| 256 | + ${maybeDebugFlag} \ |
| 257 | + --features "${builtins.concatStringsSep " " buildFeatures}" \ |
| 258 | + --out-dir "$out" |
| 259 | +
|
| 260 | + ${maybeLeaveBuildAndTestSubdir} |
| 261 | +
|
| 262 | + runHook postBuild |
| 263 | + ''; |
| 264 | + |
| 265 | + sharedInstallPhase = '' |
| 266 | + runHook preInstall |
| 267 | +
|
| 268 | + ${maybeEnterBuildAndTestSubdir} |
| 269 | +
|
| 270 | + cargo-${pgrxBinaryName} ${pgrxBinaryName} stop all |
| 271 | +
|
| 272 | + mv $out/${postgresql}/* $out |
| 273 | + mv $out/${postgresql.lib}/* $out |
| 274 | + rm -rf $out/nix |
| 275 | +
|
| 276 | + ${maybeLeaveBuildAndTestSubdir} |
| 277 | +
|
| 278 | + runHook postInstall |
| 279 | + ''; |
| 280 | + |
| 281 | + # Arguments for rustPlatform.buildRustPackage |
| 282 | + rustPlatformArgs = commonArgs // { |
| 283 | + buildPhase = sharedBuildPhase; |
| 284 | + installPhase = sharedInstallPhase; |
189 | 285 | preCheck = preBuildAndTest + args.preCheck or ""; |
| 286 | + }; |
190 | 287 |
|
191 | | - installPhase = '' |
192 | | - runHook preInstall |
| 288 | + # Crane's two-phase build: first build dependencies, then build the extension. |
| 289 | + # buildDepsOnly creates a derivation containing only Cargo dependency artifacts. |
| 290 | + # This is cached separately, so changing extension code doesn't rebuild dependencies. |
| 291 | + cargoArtifacts = |
| 292 | + if useCrane then |
| 293 | + craneLib.buildDepsOnly ( |
| 294 | + commonArgs |
| 295 | + // { |
| 296 | + pname = "${args.pname or "pgrx-extension"}-deps"; |
193 | 297 |
|
194 | | - echo "Executing buildPgrxExtension install" |
| 298 | + # pgrx-pg-sys needs PGRX_HOME during dependency build |
| 299 | + preBuild = '' |
| 300 | + ${preBuildAndTest} |
| 301 | + ${maybeEnterBuildAndTestSubdir} |
| 302 | + '' |
| 303 | + + (args.preBuild or ""); |
195 | 304 |
|
196 | | - ${maybeEnterBuildAndTestSubdir} |
| 305 | + postBuild = '' |
| 306 | + ${maybeLeaveBuildAndTestSubdir} |
| 307 | + '' |
| 308 | + + (args.postBuild or ""); |
197 | 309 |
|
198 | | - cargo-${pgrxBinaryName} ${pgrxBinaryName} stop all |
| 310 | + # Dependencies don't have a postInstall phase |
| 311 | + postInstall = ""; |
199 | 312 |
|
200 | | - mv $out/${postgresql}/* $out |
201 | | - mv $out/${postgresql.lib}/* $out |
202 | | - rm -rf $out/nix |
| 313 | + # Need to specify PostgreSQL version feature for pgrx dependencies |
| 314 | + # and disable default features to avoid multiple pg version conflicts |
| 315 | + cargoExtraArgs = "--no-default-features --features ${ |
| 316 | + builtins.concatStringsSep "," ([ "pg${pgrxPostgresMajor}" ] ++ buildFeatures) |
| 317 | + }"; |
| 318 | + } |
| 319 | + ) |
| 320 | + else |
| 321 | + null; |
203 | 322 |
|
204 | | - ${maybeLeaveBuildAndTestSubdir} |
| 323 | + # Arguments for crane.buildPackage |
| 324 | + craneArgs = commonArgs // { |
| 325 | + inherit cargoArtifacts; |
| 326 | + pname = args.pname or "pgrx-extension"; |
205 | 327 |
|
206 | | - runHook postInstall |
207 | | - ''; |
| 328 | + # Explicitly preserve postInstall from args (needed for version-specific file renaming) |
| 329 | + postInstall = args.postInstall or ""; |
208 | 330 |
|
209 | | - PGRX_PG_SYS_SKIP_BINDING_REWRITE = "1"; |
210 | | - CARGO_BUILD_INCREMENTAL = "false"; |
211 | | - RUST_BACKTRACE = "full"; |
| 331 | + # We handle installation ourselves via pgrx, don't let crane try to install binaries |
| 332 | + doNotInstallCargoBinaries = true; |
| 333 | + doNotPostBuildInstallCargoBinaries = true; |
212 | 334 |
|
213 | | - checkNoDefaultFeatures = true; |
214 | | - checkFeatures = |
215 | | - (args.checkFeatures or [ ]) |
216 | | - ++ (lib.optionals usePgTestCheckFeature [ "pg_test" ]) |
217 | | - ++ [ "pg${pgrxPostgresMajor}" ]; |
| 335 | + buildPhase = sharedBuildPhase; |
| 336 | + installPhase = sharedInstallPhase; |
| 337 | + preCheck = preBuildAndTest + args.preCheck or ""; |
218 | 338 | }; |
219 | 339 | in |
220 | | -rustPlatform.buildRustPackage finalArgs |
| 340 | +if useCrane then craneLib.buildPackage craneArgs else rustPlatform.buildRustPackage rustPlatformArgs |
0 commit comments