From 134ee20df1bdda18a98d83af1215ba8016cde540 Mon Sep 17 00:00:00 2001 From: usamoi Date: Fri, 1 Nov 2024 19:07:16 +0800 Subject: [PATCH 1/5] fix cargo-pgrx and pgrx-tests on Windows --- .cargo/config.toml | 3 + Cargo.lock | 361 ++++++++++++-- cargo-pgrx/Cargo.toml | 1 + cargo-pgrx/src/command/init.rs | 62 ++- cargo-pgrx/src/command/install.rs | 146 +++--- cargo-pgrx/src/command/run.rs | 27 +- cargo-pgrx/src/command/start.rs | 11 +- cargo-pgrx/src/command/status.rs | 3 +- cargo-pgrx/src/command/stop.rs | 10 +- cargo-pgrx/src/command/sudo_install.rs | 43 +- cargo-pgrx/src/command/test.rs | 5 + cargo-pgrx/src/command/version.rs | 14 +- cargo-pgrx/src/manifest.rs | 2 +- cargo-pgrx/src/templates/cargo_config_toml | 3 + pgrx-bindgen/Cargo.toml | 1 + pgrx-bindgen/src/build.rs | 62 ++- pgrx-pg-config/src/cargo.rs | 12 +- pgrx-pg-config/src/lib.rs | 53 +- pgrx-pg-sys/src/port.rs | 98 ++++ pgrx-tests/Cargo.toml | 16 +- pgrx-tests/src/framework.rs | 554 ++++++++++++++++----- pgrx-tests/src/framework/shutdown.rs | 12 +- pgrx-tests/src/tests/mod.rs | 1 + pgrx-tests/src/tests/result_tests.rs | 9 +- 24 files changed, 1191 insertions(+), 318 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 51de468659..5ef5ccd240 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -7,3 +7,6 @@ r = "run --features" [target.'cfg(target_os="macos")'] # Postgres symbols won't be available until runtime rustflags = ["-Clink-arg=-Wl,-undefined,dynamic_lookup"] + +[target.'cfg(target_env="msvc")'] +linker = "lld-link" diff --git a/Cargo.lock b/Cargo.lock index 9b533e1be2..afc817f8f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -90,6 +101,15 @@ version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "async-trait" version = "0.1.77" @@ -236,9 +256,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byteorder" @@ -349,6 +369,7 @@ dependencies = [ "tracing-subscriber", "ureq", "url", + "zip-extract", ] [[package]] @@ -433,6 +454,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clang-sys" version = "1.7.0" @@ -556,6 +587,12 @@ version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad159cc964ac8f9d407cbc0aa44b02436c054b541f2b4b5f06972e1efdc54bc7" +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "convert_case" version = "0.6.0" @@ -610,11 +647,26 @@ dependencies = [ "toml 0.7.8", ] +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] @@ -654,6 +706,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "deflate64" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" + [[package]] name = "deranged" version = "0.3.11" @@ -663,6 +721,17 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "digest" version = "0.10.7" @@ -674,6 +743,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "dunce" version = "1.0.4" @@ -760,9 +840,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "filetime" @@ -1057,6 +1137,15 @@ dependencies = [ "hashbrown 0.14.3", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "is-terminal" version = "0.4.12" @@ -1128,9 +1217,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "libgit2-sys" @@ -1205,9 +1294,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" @@ -1219,11 +1308,27 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "lockfree-object-pool" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" + [[package]] name = "log" -version = "0.4.20" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "lzma-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" +dependencies = [ + "byteorder", + "crc", +] [[package]] name = "matchers" @@ -1246,9 +1351,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "minimal-lexical" @@ -1323,6 +1428,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-traits" version = "0.2.17" @@ -1458,6 +1569,16 @@ dependencies = [ "libc", ] +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1518,6 +1639,7 @@ dependencies = [ "pgrx-pg-config", "proc-macro2", "quote", + "regex", "shlex", "syn", "walkdir", @@ -1598,8 +1720,10 @@ dependencies = [ "serde", "serde_json", "sysinfo", + "tempfile", "thiserror", "trybuild", + "winapi", ] [[package]] @@ -1921,9 +2045,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.30" +version = "0.38.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" +checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" dependencies = [ "bitflags 2.4.2", "errno", @@ -2151,6 +2275,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.8" @@ -2177,6 +2312,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "siphasher" version = "0.3.11" @@ -2355,15 +2496,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.9.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", + "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2417,12 +2558,13 @@ dependencies = [ [[package]] name = "time" -version = "0.3.31" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", + "num-conv", "powerfmt", "serde", "time-core", @@ -2437,10 +2579,11 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ + "num-conv", "time-core", ] @@ -2937,7 +3080,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ "windows-core", - "windows-targets 0.52.0", + "windows-targets 0.52.6", ] [[package]] @@ -2946,7 +3089,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.6", ] [[package]] @@ -2964,7 +3107,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -2984,17 +3136,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -3005,9 +3158,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -3017,9 +3170,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -3029,9 +3182,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -3041,9 +3200,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -3053,9 +3212,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -3065,9 +3224,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -3077,9 +3236,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" @@ -3142,3 +3301,105 @@ checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1" dependencies = [ "winapi", ] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zip" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd56a4d5921bc2f99947ac5b3abe5f510b1be7376fdc5e9fce4a23c6a93e87c" +dependencies = [ + "aes", + "arbitrary", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "deflate64", + "displaydoc", + "flate2", + "hmac", + "indexmap 2.1.0", + "lzma-rs", + "memchr", + "pbkdf2", + "rand", + "sha1", + "thiserror", + "time", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zip-extract" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a8c9e90f27d1435088a7b540b6cc8ae6ee525d992a695f16012d2f365b3d3c" +dependencies = [ + "log", + "thiserror", + "zip", +] + +[[package]] +name = "zopfli" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +dependencies = [ + "bumpalo", + "crc32fast", + "lockfree-object-pool", + "log", + "once_cell", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.13+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/cargo-pgrx/Cargo.toml b/cargo-pgrx/Cargo.toml index 95fa659166..de735a1ded 100644 --- a/cargo-pgrx/Cargo.toml +++ b/cargo-pgrx/Cargo.toml @@ -57,6 +57,7 @@ serde-xml-rs = "0.6.0" tar = "0.4.40" ureq = { version = "2.8.0", default-features = false, features = [ "gzip" ] } url.workspace = true +zip-extract = "0.2.1" # SQL schema generation proc-macro2.workspace = true diff --git a/cargo-pgrx/src/command/init.rs b/cargo-pgrx/src/command/init.rs index 6bc533e677..a9b8e9df1e 100644 --- a/cargo-pgrx/src/command/init.rs +++ b/cargo-pgrx/src/command/init.rs @@ -14,7 +14,7 @@ use bzip2::bufread::BzDecoder; use eyre::{eyre, WrapErr}; use owo_colors::OwoColorize; use pgrx_pg_config::{ - get_c_locale_flags, prefix_path, ConfigToml, PgConfig, PgConfigSelector, Pgrx, PgrxHomeError, + get_c_locale_flags, ConfigToml, PgConfig, PgConfigSelector, Pgrx, PgrxHomeError, }; use tar::Archive; @@ -23,9 +23,10 @@ use std::fs::File; use std::io::{Read, Write}; use std::num::NonZeroUsize; use std::path::{Path, PathBuf}; -use std::process::{Command, Stdio}; +use std::process::Stdio; use std::sync::OnceLock; +#[cfg(not(target_os = "windows"))] static PROCESS_ENV_DENYLIST: &[&str] = &[ "DEBUG", "MAKEFLAGS", @@ -287,8 +288,18 @@ fn untar(bytes: &[u8], pgrxdir: &Path, pg_config: &PgConfig, init: &Init) -> eyr pg_config.version()?, unpackdir.display() ); - let mut tar_decoder = Archive::new(BzDecoder::new(bytes)); - tar_decoder.unpack(&unpackdir)?; + + if bytes.starts_with(b"PK\x03\x04") + || bytes.starts_with(b"PK\x05\x06") + || bytes.starts_with(b"PK\x07\x08") + { + // it's a zip download from EDB + use std::io::Cursor; + zip_extract::extract(Cursor::new(bytes), &unpackdir, false)?; + } else { + let mut tar_decoder = Archive::new(BzDecoder::new(bytes)); + tar_decoder.unpack(&unpackdir)?; + } let mut pgdir = pgrxdir.to_path_buf(); pgdir.push(&pg_config.version()?); @@ -327,7 +338,9 @@ fn untar(bytes: &[u8], pgrxdir: &Path, pg_config: &PgConfig, init: &Init) -> eyr Ok(pgdir) } -fn fixup_homebrew_for_icu(configure_cmd: &mut Command) { +#[cfg(not(target_os = "windows"))] +fn fixup_homebrew_for_icu(configure_cmd: &mut std::process::Command) { + use std::process::Command; // See if it's disabled via an argument if configure_cmd.get_args().any(|a| a == "--without-icu") { return; @@ -399,7 +412,10 @@ fn fixup_homebrew_for_icu(configure_cmd: &mut Command) { } } +#[cfg(not(target_os = "windows"))] fn configure_postgres(pg_config: &PgConfig, pgdir: &Path, init: &Init) -> eyre::Result<()> { + use pgrx_pg_config::prefix_path; + let _token = init.jobserver.get().unwrap().acquire().unwrap(); println!("{} Postgres v{}", " Configuring".bold().green(), pg_config.version()?); @@ -463,6 +479,12 @@ fn configure_postgres(pg_config: &PgConfig, pgdir: &Path, init: &Init) -> eyre:: } } +#[cfg(target_os = "windows")] +fn configure_postgres(_pg_config: &PgConfig, _pgdir: &Path, _init: &Init) -> eyre::Result<()> { + Ok(()) +} + +#[cfg(not(target_os = "windows"))] fn make_postgres(pg_config: &PgConfig, pgdir: &Path, init: &Init) -> eyre::Result<()> { println!("{} Postgres v{}", " Compiling".bold().green(), pg_config.version()?); let mut command = std::process::Command::new("make"); @@ -496,6 +518,12 @@ fn make_postgres(pg_config: &PgConfig, pgdir: &Path, init: &Init) -> eyre::Resul } } +#[cfg(target_os = "windows")] +fn make_postgres(_pg_config: &PgConfig, _pgdir: &Path, _init: &Init) -> eyre::Result<()> { + Ok(()) +} + +#[cfg(not(target_os = "windows"))] fn make_install_postgres(version: &PgConfig, pgdir: &Path, init: &Init) -> eyre::Result { println!( "{} Postgres v{} to {}", @@ -536,6 +564,18 @@ fn make_install_postgres(version: &PgConfig, pgdir: &Path, init: &Init) -> eyre: } } +#[cfg(target_os = "windows")] +fn make_install_postgres( + _version: &PgConfig, + pgdir: &Path, + _init: &Init, +) -> eyre::Result { + let mut pg_config = get_pg_installdir(pgdir); + pg_config.push("bin"); + pg_config.push("pg_config.exe"); + Ok(PgConfig::new_with_defaults(pg_config)) +} + fn validate_pg_config(pg_config: &PgConfig) -> eyre::Result<()> { println!( "{} {}", @@ -575,12 +615,18 @@ fn write_config(pg_configs: &Vec, init: &Init) -> eyre::Result<()> { Ok(()) } +#[cfg(not(target_os = "windows"))] fn get_pg_installdir(pgdir: &Path) -> PathBuf { let mut dir = pgdir.to_path_buf(); dir.push("pgrx-install"); dir } +#[cfg(target_os = "windows")] +fn get_pg_installdir(pgdir: &Path) -> PathBuf { + pgdir.to_path_buf() +} + #[cfg(unix)] fn is_root_user() -> bool { // SAFETY: No, the `nix` crate does not do anything more clever: @@ -597,7 +643,11 @@ fn is_root_user() -> bool { pub(crate) fn initdb(bindir: &Path, datadir: &Path) -> eyre::Result<()> { println!(" {} data directory at {}", "Initializing".bold().green(), datadir.display()); - let mut command = std::process::Command::new(format!("{}/initdb", bindir.display())); + #[cfg(not(target_os = "windows"))] + let initdb = bindir.join("initdb"); + #[cfg(target_os = "windows")] + let initdb = bindir.join("initdb.exe"); + let mut command = std::process::Command::new(initdb); command .stdout(Stdio::piped()) .stderr(Stdio::piped()) diff --git a/cargo-pgrx/src/command/install.rs b/cargo-pgrx/src/command/install.rs index 31c9b436ad..37b97fbfb9 100644 --- a/cargo-pgrx/src/command/install.rs +++ b/cargo-pgrx/src/command/install.rs @@ -129,9 +129,6 @@ pub(crate) fn install_extension( features: &clap_cargo::Features, ) -> eyre::Result> { let mut output_tracking = Vec::new(); - let base_directory = base_directory.unwrap_or_else(|| PathBuf::from("/")); - tracing::Span::current() - .record("base_directory", tracing::field::display(&base_directory.display())); let manifest = Manifest::from_path(&package_manifest_path)?; let (control_file, extname) = find_control_file(&package_manifest_path)?; @@ -147,18 +144,25 @@ pub(crate) fn install_extension( build_command_stream.collect::, std::io::Error>>()?; println!("{} extension", " Installing".bold().green()); - let pkgdir = make_relative(pg_config.pkglibdir()?); - let extdir = make_relative(pg_config.extension_dir()?); let shlibpath = find_library_file(&manifest, &build_command_messages)?; + let extdir = if let Some(base_directory) = base_directory.as_ref() { + base_directory.join(make_relative_extdir(pg_config.extension_dir()?)) + } else { + pg_config.extension_dir()? + }; + + let pkglibdir = if let Some(base_directory) = base_directory.as_ref() { + base_directory.join(make_relative_pkglibdir(pg_config.pkglibdir()?)) + } else { + pg_config.pkglibdir()? + }; + { - let mut dest = base_directory.clone(); - dest.push(&extdir); - dest.push( - control_file - .file_name() - .ok_or_else(|| eyre!("Could not get filename for `{}`", control_file.display()))?, - ); + let filename = control_file + .file_name() + .ok_or_else(|| eyre!("Could not get filename for `{}`", control_file.display()))?; + let dest = extdir.join(filename); copy_file( &control_file, dest, @@ -171,9 +175,6 @@ pub(crate) fn install_extension( } { - let mut dest = base_directory.clone(); - dest.push(&pkgdir); - let so_name = if versioned_so { let extver = get_version(&package_manifest_path)?; // note: versioned so-name format must agree with pgrx-utils @@ -183,13 +184,18 @@ pub(crate) fn install_extension( }; // Since Postgres 16, the shared library extension on macOS is `dylib`, not `so`. // Ref https://github.com/postgres/postgres/commit/b55f62abb2c2e07dfae99e19a2b3d7ca9e58dc1a - let so_extension = if cfg!(target_os = "macos") && pg_config.major_version().unwrap() >= 16 - { - "dylib" - } else { + let so_ext = 'e: { + if cfg!(target_os = "macos") { + break 'e if pg_config.major_version().unwrap() >= 16 { "dylib" } else { "so" }; + } + if cfg!(target_os = "windows") { + break 'e "dll"; + } "so" }; - dest.push(format!("{so_name}.{so_extension}")); + let filename = format!("{so_name}.{so_ext}"); + + let dest = pkglibdir.join(filename); // Remove the existing shared libraries if present. This is a workaround for an // issue highlighted by the following apple documentation: @@ -224,7 +230,6 @@ pub(crate) fn install_extension( is_test, features, &extdir, - &base_directory, true, &mut output_tracking, )?; @@ -339,21 +344,6 @@ pub(crate) fn build_extension( } } -fn get_target_sql_file( - manifest_path: impl AsRef, - extdir: &Path, - base_directory: PathBuf, -) -> eyre::Result { - let mut dest = base_directory; - dest.push(extdir); - - let (_, extname) = find_control_file(&manifest_path)?; - let version = get_version(&manifest_path)?; - dest.push(format!("{extname}--{version}.sql")); - - Ok(dest) -} - fn copy_sql_files( user_manifest_path: Option>, user_package: Option<&String>, @@ -363,27 +353,30 @@ fn copy_sql_files( is_test: bool, features: &clap_cargo::Features, extdir: &Path, - base_directory: &Path, skip_build: bool, output_tracking: &mut Vec, ) -> eyre::Result<()> { - let dest = get_target_sql_file(&package_manifest_path, extdir, base_directory.to_path_buf())?; let (_, extname) = find_control_file(&package_manifest_path)?; + { + let version = get_version(&package_manifest_path)?; + let filename = format!("{extname}--{version}.sql"); + let dest = extdir.join(filename); - crate::command::schema::generate_schema( - pg_config, - user_manifest_path, - user_package, - &package_manifest_path, - profile, - is_test, - features, - Some(&dest), - Option::::None, - None, - skip_build, - output_tracking, - )?; + crate::command::schema::generate_schema( + pg_config, + user_manifest_path, + user_package, + &package_manifest_path, + profile, + is_test, + features, + Some(&dest), + Option::::None, + None, + skip_build, + output_tracking, + )?; + } // now copy all the version upgrade files too if let Ok(dir) = fs::read_dir("sql/") { @@ -395,13 +388,9 @@ fn copy_sql_files( regex::Regex::new(&format!(r"^{extname}--.+--.+\.sql$")).unwrap(); if re_update_script_name.is_match(filename.as_str()) { - let mut dest = base_directory.to_path_buf(); - dest.push(extdir); - dest.push(filename); - copy_file( &sql.path(), - dest, + extdir.join(filename), "extension schema upgrade file", true, &package_manifest_path, @@ -422,7 +411,15 @@ pub(crate) fn find_library_file( // cargo sometimes decides to change whether targets are kebab-case or snake_case in metadata, // so normalize away the difference let target_name = manifest.target_name()?.replace('-', "_"); - let so_ext = if cfg!(target_os = "macos") { "dylib" } else { "so" }; + let so_ext = 'so_ext: { + if cfg!(target_os = "macos") { + break 'so_ext "dylib"; + } + if cfg!(target_os = "windows") { + break 'so_ext "dll"; + } + "so" + }; // no hard and fast rule for the lib.so output filename exists, so we implement this routine // which is essentially a cope for cargo's disinterest in writing down any docs so far. @@ -511,17 +508,36 @@ fn get_git_hash(manifest_path: impl AsRef) -> eyre::Result { } } -fn make_relative(path: PathBuf) -> PathBuf { +#[cfg(not(target_os = "windows"))] +fn make_relative_pkglibdir(path: PathBuf) -> PathBuf { + use std::path::Component; if path.is_relative() { return path; } - let mut relative = PathBuf::new(); - let mut components = path.components(); - components.next(); // skip the root - for part in components { - relative.push(part) + path.components() + .skip_while(|x| matches!(x, Component::Prefix(_) | Component::RootDir)) + .collect() +} + +#[cfg(target_os = "windows")] +fn make_relative_pkglibdir(_: PathBuf) -> PathBuf { + "lib".into() +} + +#[cfg(not(target_os = "windows"))] +fn make_relative_extdir(path: PathBuf) -> PathBuf { + use std::path::Component; + if path.is_relative() { + return path; } - relative + path.components() + .skip_while(|x| matches!(x, Component::Prefix(_) | Component::RootDir)) + .collect() +} + +#[cfg(target_os = "windows")] +fn make_relative_extdir(_: PathBuf) -> PathBuf { + "share/extension".into() } pub(crate) fn format_display_path(path: impl AsRef) -> eyre::Result { diff --git a/cargo-pgrx/src/command/run.rs b/cargo-pgrx/src/command/run.rs index 1eda6bc329..9451526905 100644 --- a/cargo-pgrx/src/command/run.rs +++ b/cargo-pgrx/src/command/run.rs @@ -18,7 +18,6 @@ use eyre::eyre; use owo_colors::OwoColorize; use pgrx_pg_config::{createdb, PgConfig, Pgrx}; use std::path::Path; -use std::process::Command; /// Compile/install extension to a pgrx-managed Postgres instance and start psql #[derive(clap::Args, Debug)] @@ -135,6 +134,7 @@ pub(crate) fn run( #[cfg(unix)] pub(crate) fn exec_psql(pg_config: &PgConfig, dbname: &str, pgcli: bool) -> eyre::Result<()> { use std::os::unix::process::CommandExt; + use std::process::Command; let mut command = Command::new(match pgcli { false => pg_config.psql_path()?.into_os_string(), true => "pgcli".to_string().into(), @@ -156,5 +156,28 @@ pub(crate) fn exec_psql(pg_config: &PgConfig, dbname: &str, pgcli: bool) -> eyre #[cfg(not(unix))] pub(crate) fn exec_psql(pg_config: &PgConfig, dbname: &str, pgcli: bool) -> eyre::Result<()> { - panic!("Tried to exec on a platform that doesn't support exec!") + use std::process::Command; + use std::process::Stdio; + let mut command = Command::new(match pgcli { + false => pg_config.psql_path()?.into_os_string(), + true => "pgcli".to_string().into(), + }); + command + .stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .env_remove("PGDATABASE") + .env_remove("PGHOST") + .env_remove("PGPORT") + .env_remove("PGUSER") + .arg("-h") + .arg(pg_config.host()) + .arg("-p") + .arg(pg_config.port()?.to_string()) + .arg(dbname); + let command_str = format!("{command:?}"); + tracing::debug!(command = %command_str, "Running"); + let output = command.output()?; + tracing::trace!(status_code = %output.status, command = %command_str, "Finished"); + Ok(()) } diff --git a/cargo-pgrx/src/command/start.rs b/cargo-pgrx/src/command/start.rs index d96f0e99be..91ba59b261 100644 --- a/cargo-pgrx/src/command/start.rs +++ b/cargo-pgrx/src/command/start.rs @@ -91,7 +91,8 @@ pub(crate) fn start_postgres(pg_config: &PgConfig) -> eyre::Result<()> { pg_config.major_version()?, port.to_string().bold().cyan() ); - let mut command = std::process::Command::new(format!("{}/pg_ctl", bindir.display())); + let pg_ctl = pg_config.pg_ctl_path()?; + let mut command = std::process::Command::new(pg_ctl); command .stdout(Stdio::piped()) .stderr(Stdio::piped()) @@ -101,10 +102,16 @@ pub(crate) fn start_postgres(pg_config: &PgConfig) -> eyre::Result<()> { .arg(&datadir) .arg("-l") .arg(&logfile); + #[cfg(target_os = "windows")] + { + // on windows, created pipes are leaked, so that the command hangs + command.stdout(Stdio::inherit()).stderr(Stdio::inherit()); + } let command_str = format!("{command:?}"); + tracing::debug!(command = %command_str, "Running"); let output = command.output()?; - + tracing::trace!(status_code = %output.status, command = %command_str, "Finished"); if !output.status.success() { return Err(eyre!( "problem running pg_ctl: {}\n\n{}", diff --git a/cargo-pgrx/src/command/status.rs b/cargo-pgrx/src/command/status.rs index 63cf7a055d..87c3d1f531 100644 --- a/cargo-pgrx/src/command/status.rs +++ b/cargo-pgrx/src/command/status.rs @@ -64,8 +64,7 @@ pub(crate) fn status_postgres(pg_config: &PgConfig) -> eyre::Result { return Ok(false); } // if Err, let the filesystem and OS handle our impending failure - let mut pg_ctl = pg_config.bin_dir()?; - pg_ctl.push("pg_ctl"); + let pg_ctl = pg_config.pg_ctl_path()?; let mut command = process::Command::new(pg_ctl); command.stdout(Stdio::piped()).stderr(Stdio::piped()).arg("status").arg("-D").arg(&datadir); let command_str = format!("{command:?}"); diff --git a/cargo-pgrx/src/command/stop.rs b/cargo-pgrx/src/command/stop.rs index 24aea9e735..8370ee7efc 100644 --- a/cargo-pgrx/src/command/stop.rs +++ b/cargo-pgrx/src/command/stop.rs @@ -70,7 +70,6 @@ impl CommandExecute for Stop { #[tracing::instrument(level = "error", skip_all, fields(pg_version = %pg_config.version()?))] pub(crate) fn stop_postgres(pg_config: &PgConfig) -> eyre::Result<()> { let datadir = pg_config.data_dir()?; - let bindir = pg_config.bin_dir()?; if !(status_postgres(pg_config)?) { // it's not running, no need to stop it @@ -80,15 +79,16 @@ pub(crate) fn stop_postgres(pg_config: &PgConfig) -> eyre::Result<()> { println!("{} Postgres v{}", " Stopping".bold().green(), pg_config.major_version()?); - let mut command = std::process::Command::new(format!("{}/pg_ctl", bindir.display())); + let pg_ctl = pg_config.pg_ctl_path()?; + let mut command = std::process::Command::new(pg_ctl); command .stdout(Stdio::piped()) .stderr(Stdio::piped()) .arg("stop") - .arg("-m") - .arg("fast") .arg("-D") - .arg(&datadir); + .arg(&datadir) + .arg("-m") + .arg("fast"); let output = command.output()?; diff --git a/cargo-pgrx/src/command/sudo_install.rs b/cargo-pgrx/src/command/sudo_install.rs index 2e9302a644..dab65e71f3 100644 --- a/cargo-pgrx/src/command/sudo_install.rs +++ b/cargo-pgrx/src/command/sudo_install.rs @@ -67,28 +67,41 @@ impl CommandExecute for SudoInstall { eprintln!(); eprintln!("Using sudo to copy extension files from {}", outdir.display().cyan()); + let outdir = outdir.canonicalize()?; for src in output_files { let src = src.canonicalize()?; let dest_abs = make_absolute(src.strip_prefix(&outdir)?); let dest = dest_abs.canonicalize().unwrap_or(dest_abs); - // we're about to run `sudo` to copy some files, one at a time - let mut command = Command::new("sudo"); // NB: If we ever support Windows... - command.arg("cp").arg(&src).arg(&dest); + if !cfg!(target_os = "windows") { + println!( + "{} sudo cp {} {}", + " Running".bold().green(), + src.display(), + dest.display() + ); - println!( - "{} sudo cp {} {}", - " Running".bold().green(), - src.display(), - dest.display() - ); - let mut child = command.spawn()?; + // we're about to run `sudo` to copy some files, one at a time + let mut command = Command::new("sudo"); // NB: If we ever support Windows... + command.arg("cp").arg(&src).arg(&dest); - let status = child.wait()?; - if !status.success() { - // sudo failed. let the user know and get out now - eprintln!("sudo command failed with status `{}`", format!("{status:?}").red()); - std::process::exit(status.code().unwrap_or(1)); + let mut child = command.spawn()?; + + let status = child.wait()?; + if !status.success() { + // sudo failed. let the user know and get out now + eprintln!("sudo command failed with status `{}`", format!("{status:?}").red()); + std::process::exit(status.code().unwrap_or(1)); + } + } else { + println!( + "{} cp {} {}", + " Running".bold().green(), + src.display(), + dest.display() + ); + + std::fs::copy(src, dest)?; } } diff --git a/cargo-pgrx/src/command/test.rs b/cargo-pgrx/src/command/test.rs index 0be4f96396..fd2ff08b4c 100644 --- a/cargo-pgrx/src/command/test.rs +++ b/cargo-pgrx/src/command/test.rs @@ -126,6 +126,11 @@ pub fn test_extension( runas: Option, pgdata: Option, ) -> eyre::Result<()> { + #[cfg(target_os = "windows")] + if runas.is_some() { + eyre::bail!("`--runas` is not supported on Windows"); + } + if let Some(ref testname) = testname { tracing::Span::current().record("testname", tracing::field::display(&testname.as_ref())); } diff --git a/cargo-pgrx/src/command/version.rs b/cargo-pgrx/src/command/version.rs index 4be017fc1a..9fe745cf2b 100644 --- a/cargo-pgrx/src/command/version.rs +++ b/cargo-pgrx/src/command/version.rs @@ -29,6 +29,16 @@ mod rss { use crate::command::build_agent_for_url; + #[cfg(not(target_os = "windows"))] + fn download_url(major: u16, minor: u16) -> String { + format!("https://ftp.postgresql.org/pub/source/v{major}.{minor}/postgresql-{major}.{minor}.tar.bz2") + } + + #[cfg(target_os = "windows")] + fn download_url(major: u16, minor: u16) -> String { + format!("https://get.enterprisedb.com/postgresql/postgresql-{major}.{minor}-1-windows-x64-binaries.zip") + } + pub(super) struct PostgreSQLVersionRss; impl PostgreSQLVersionRss { @@ -65,9 +75,7 @@ mod rss { if matches!(known_pgver.minor, PgMinorVersion::Latest) { // fill in the latest minor version number and its url known_pgver.minor = PgMinorVersion::Release(minor); - known_pgver.url = Some(Url::parse( - &format!("https://ftp.postgresql.org/pub/source/v{major}.{minor}/postgresql-{major}.{minor}.tar.bz2") - )?); + known_pgver.url = Some(Url::parse(&download_url(major, minor))?); } } } diff --git a/cargo-pgrx/src/manifest.rs b/cargo-pgrx/src/manifest.rs index c1da00c7ae..d941e85714 100644 --- a/cargo-pgrx/src/manifest.rs +++ b/cargo-pgrx/src/manifest.rs @@ -96,7 +96,7 @@ pub(crate) fn modify_features_for_version( // that aren't valid for the manifest if test { features.features.retain(|flag| { - if manifest.features.contains_key(flag) { + if manifest.features.contains_key(flag) || flag == "pgrx/cshim" { true } else { use owo_colors::OwoColorize; diff --git a/cargo-pgrx/src/templates/cargo_config_toml b/cargo-pgrx/src/templates/cargo_config_toml index 13c456b5dd..1dd47369a8 100644 --- a/cargo-pgrx/src/templates/cargo_config_toml +++ b/cargo-pgrx/src/templates/cargo_config_toml @@ -1,3 +1,6 @@ [target.'cfg(target_os="macos")'] # Postgres symbols won't be available until runtime rustflags = ["-Clink-arg=-Wl,-undefined,dynamic_lookup"] + +[target.'cfg(target_env="msvc")'] +linker = "lld-link" diff --git a/pgrx-bindgen/Cargo.toml b/pgrx-bindgen/Cargo.toml index 842df69abb..6ceffeaa19 100644 --- a/pgrx-bindgen/Cargo.toml +++ b/pgrx-bindgen/Cargo.toml @@ -13,6 +13,7 @@ pgrx-pg-config.workspace = true eyre.workspace = true proc-macro2.workspace = true +regex.workspace = true syn.workspace = true walkdir.workspace = true diff --git a/pgrx-bindgen/src/build.rs b/pgrx-bindgen/src/build.rs index c07e73b546..0069029b3f 100644 --- a/pgrx-bindgen/src/build.rs +++ b/pgrx-bindgen/src/build.rs @@ -794,13 +794,12 @@ fn run_bindgen( let configure = pg_config.configure()?; let preferred_clang: Option<&std::path::Path> = configure.get("CLANG").map(|s| s.as_ref()); eprintln!("pg_config --configure CLANG = {preferred_clang:?}"); + let pg_target_includes = pg_target_includes(major_version, pg_config)?; + eprintln!("pg_target_includes = {pg_target_includes:?}"); let (autodetect, includes) = clang::detect_include_paths_for(preferred_clang); let mut binder = bindgen::Builder::default(); binder = add_blocklists(binder); - binder = binder - .allowlist_file(format!("{}.*", pg_target_include(major_version, pg_config)?)) - .allowlist_item("PGERROR") - .allowlist_item("SIG.*"); + binder = add_allowlists(binder, pg_target_includes.iter().map(|x| x.as_str())); binder = add_derives(binder); if !autodetect { let builtin_includes = includes.iter().filter_map(|p| Some(format!("-I{}", p.to_str()?))); @@ -812,7 +811,7 @@ fn run_bindgen( let bindings = binder .header(include_h.display().to_string()) .clang_args(extra_bindgen_clang_args(pg_config)?) - .clang_arg(format!("-I{}", pg_target_include(major_version, pg_config)?)) + .clang_args(pg_target_includes.iter().map(|x| format!("-I{x}"))) .detect_include_paths(autodetect) .parse_callbacks(Box::new(overrides)) .default_enum_style(bindgen::EnumVariation::ModuleConsts) @@ -900,6 +899,16 @@ fn add_blocklists(bind: bindgen::Builder) -> bindgen::Builder { .blocklist_item("ERROR") } +fn add_allowlists<'a>( + mut bind: bindgen::Builder, + pg_target_includes: impl Iterator, +) -> bindgen::Builder { + for pg_target_include in pg_target_includes { + bind = bind.allowlist_file(format!("{}.*", regex::escape(pg_target_include))) + } + bind.allowlist_item("PGERROR").allowlist_item("SIG.*") +} + fn add_derives(bind: bindgen::Builder) -> bindgen::Builder { bind.derive_debug(true) .derive_copy(true) @@ -947,23 +956,44 @@ fn target_env_tracked(s: &str) -> Option { env_tracked(&format!("{s}_{target}")).or_else(|| env_tracked(s)) } -fn pg_target_include(pg_version: u16, pg_config: &PgConfig) -> eyre::Result { - let var = "PGRX_INCLUDEDIR_SERVER"; +fn find_include( + pg_version: u16, + var: &str, + default: impl Fn() -> eyre::Result, +) -> eyre::Result { let value = target_env_tracked(&format!("{var}_PG{pg_version}")).or_else(|| target_env_tracked(var)); let path = match value { // No configured value: ask `pg_config`. - None => pg_config.includedir_server()?, + None => default()?, // Configured to non-empty string: pass to bindgen Some(overridden) => Path::new(&overridden).to_path_buf(), }; let path = std::fs::canonicalize(&path) .wrap_err(format!("cannot find {path:?} for C header files"))? .join("") // returning a `/`-ending path - .to_str() - .ok_or(eyre!("{path:?} is not valid UTF-8 string"))? + .display() .to_string(); - Ok(path) + if let Some(path) = path.strip_prefix("\\\\?\\") { + Ok(path.to_string()) + } else { + Ok(path) + } +} + +fn pg_target_includes(pg_version: u16, pg_config: &PgConfig) -> eyre::Result> { + let mut result = + vec![find_include(pg_version, "PGRX_INCLUDEDIR_SERVER", || pg_config.includedir_server())?]; + if let Some("msvc") = env_tracked("CARGO_CFG_TARGET_ENV").as_deref() { + result.push(find_include(pg_version, "PGRX_PKGINCLUDEDIR", || pg_config.pkgincludedir())?); + result.push(find_include(pg_version, "PGRX_INCLUDEDIR_SERVER_PORT_WIN32", || { + pg_config.includedir_server_port_win32() + })?); + result.push(find_include(pg_version, "PGRX_INCLUDEDIR_SERVER_PORT_WIN32_MSVC", || { + pg_config.includedir_server_port_win32_msvc() + })?); + } + Ok(result) } fn build_shim( @@ -976,7 +1006,15 @@ fn build_shim( std::fs::copy(shim_src, shim_dst).unwrap(); let mut build = cc::Build::new(); - build.flag(&format!("-I{}", pg_target_include(major_version, pg_config)?)); + if let Some("msvc") = env_tracked("CARGO_CFG_TARGET_ENV").as_deref() { + // without this, pgrx_embed would be linked to postgres.lib + build.compiler("clang"); + build.archiver("llvm-lib"); + build.flag("-flto=thin"); + } + for pg_target_include in pg_target_includes(major_version, pg_config)?.iter() { + build.flag(&format!("-I{pg_target_include}")); + } for flag in extra_bindgen_clang_args(pg_config)? { build.flag(&flag); } diff --git a/pgrx-pg-config/src/cargo.rs b/pgrx-pg-config/src/cargo.rs index 3d002728f1..01d9faede3 100644 --- a/pgrx-pg-config/src/cargo.rs +++ b/pgrx-pg-config/src/cargo.rs @@ -89,8 +89,16 @@ impl PgrxManifestExt for Manifest { fn lib_filename(&self) -> eyre::Result { let lib_name = &self.lib_name()?; - let so_extension = if cfg!(target_os = "macos") { "dylib" } else { "so" }; - Ok(format!("lib{}.{}", lib_name.replace('-', "_"), so_extension)) + let (prefix, extension) = 'pe: { + if cfg!(target_os = "macos") { + break 'pe ("lib", "dylib"); + } + if cfg!(target_os = "windows") { + break 'pe ("", "dll"); + } + ("lib", "so") + }; + Ok(format!("{prefix}{}.{}", lib_name.replace('-', "_"), extension)) } } diff --git a/pgrx-pg-config/src/lib.rs b/pgrx-pg-config/src/lib.rs index b7a6eeee39..4048be4959 100644 --- a/pgrx-pg-config/src/lib.rs +++ b/pgrx-pg-config/src/lib.rs @@ -29,11 +29,7 @@ pub static BASE_POSTGRES_TESTING_PORT_NO: u16 = 32200; /// The flags to specify to get a "C.UTF-8" locale on this system, or "C" locale on systems without /// a "C.UTF-8" locale equivalent. pub fn get_c_locale_flags() -> &'static [&'static str] { - #[cfg(target_os = "macos")] - { - &["--locale=C", "--lc-ctype=UTF-8"] - } - #[cfg(not(target_os = "macos"))] + #[cfg(all(target_family = "unix", not(target_os = "macos")))] { match Command::new("locale").arg("-a").output() { Ok(cmd) @@ -47,6 +43,14 @@ pub fn get_c_locale_flags() -> &'static [&'static str] { _ => &["--locale=C"], } } + #[cfg(target_os = "macos")] + { + &["--locale=C", "--lc-ctype=UTF-8"] + } + #[cfg(target_os = "windows")] + { + &["--locale=C"] + } } // These methods were originally in `pgrx-utils`, but in an effort to consolidate @@ -330,32 +334,55 @@ impl PgConfig { pub fn postmaster_path(&self) -> eyre::Result { let mut path = self.bin_dir()?; + #[cfg(not(target_os = "windows"))] path.push("postgres"); - + #[cfg(target_os = "windows")] + path.push("postgres.exe"); Ok(path) } pub fn initdb_path(&self) -> eyre::Result { let mut path = self.bin_dir()?; + #[cfg(not(target_os = "windows"))] path.push("initdb"); + #[cfg(target_os = "windows")] + path.push("initdb.exe"); Ok(path) } pub fn createdb_path(&self) -> eyre::Result { let mut path = self.bin_dir()?; + #[cfg(not(target_os = "windows"))] path.push("createdb"); + #[cfg(target_os = "windows")] + path.push("createdb.exe"); Ok(path) } pub fn dropdb_path(&self) -> eyre::Result { let mut path = self.bin_dir()?; + #[cfg(not(target_os = "windows"))] path.push("dropdb"); + #[cfg(target_os = "windows")] + path.push("dropdb.exe"); + Ok(path) + } + + pub fn pg_ctl_path(&self) -> eyre::Result { + let mut path = self.bin_dir()?; + #[cfg(not(target_os = "windows"))] + path.push("pg_ctl"); + #[cfg(target_os = "windows")] + path.push("pg_ctl.exe"); Ok(path) } pub fn psql_path(&self) -> eyre::Result { let mut path = self.bin_dir()?; + #[cfg(not(target_os = "windows"))] path.push("psql"); + #[cfg(target_os = "windows")] + path.push("psql.exe"); Ok(path) } @@ -385,10 +412,24 @@ impl PgConfig { .collect()) } + pub fn pkgincludedir(&self) -> eyre::Result { + Ok(self.run("--pkgincludedir")?.into()) + } + pub fn includedir_server(&self) -> eyre::Result { Ok(self.run("--includedir-server")?.into()) } + pub fn includedir_server_port_win32(&self) -> eyre::Result { + let includedir_server = self.includedir_server()?; + Ok(includedir_server.join("port").join("win32")) + } + + pub fn includedir_server_port_win32_msvc(&self) -> eyre::Result { + let includedir_server = self.includedir_server()?; + Ok(includedir_server.join("port").join("win32_msvc")) + } + pub fn pkglibdir(&self) -> eyre::Result { Ok(self.run("--pkglibdir")?.into()) } diff --git a/pgrx-pg-sys/src/port.rs b/pgrx-pg-sys/src/port.rs index fd49803a79..aba7fd7872 100644 --- a/pgrx-pg-sys/src/port.rs +++ b/pgrx-pg-sys/src/port.rs @@ -156,11 +156,109 @@ pub fn get_pg_major_version_num() -> u16 { u16::from_str(super::get_pg_major_version_string()).unwrap() } +#[cfg(any(not(target_env = "msvc"), feature = "pg17"))] #[inline] pub fn get_pg_version_string() -> &'static str { super::PG_VERSION_STR.to_str().unwrap() } +#[cfg(all( + target_env = "msvc", + any(feature = "pg12", feature = "pg13", feature = "pg14", feature = "pg15", feature = "pg16") +))] +#[inline] +pub fn get_pg_version_string() -> &'static str { + // bindgen cannot get value of PG_VERSION_STR + // PostgreSQL @0@ on @1@-@2@, compiled by @3@-@4@, @5@-bit + static PG_VERSION_STR: [u8; 256] = const { + let major = super::PG_MAJORVERSION_NUM; + let minor = super::PG_MINORVERSION_NUM; + #[cfg(target_pointer_width = "32")] + let pointer_width = 32_u32; + #[cfg(target_pointer_width = "64")] + let pointer_width = 64_u32; + // a fake value + let msc_ver = b"1700"; + let mut buffer = [0u8; 256]; + let mut pointer = 0; + { + let s = b"PostgreSQL "; + let mut i = 0; + while i < s.len() { + buffer[pointer + i] = s[i]; + i += 1; + } + pointer += s.len(); + } + { + buffer[pointer + 0] = b'0' + (major / 10) as u8; + buffer[pointer + 1] = b'0' + (major % 10) as u8; + pointer += 2; + } + { + let s = b"."; + let mut i = 0; + while i < s.len() { + buffer[pointer + i] = s[i]; + i += 1; + } + pointer += s.len(); + } + if minor < 10 { + buffer[pointer + 0] = b'0' + (minor % 10) as u8; + pointer += 1; + } else { + buffer[pointer + 0] = b'0' + (minor / 10) as u8; + buffer[pointer + 1] = b'0' + (minor % 10) as u8; + pointer += 2; + } + { + let s = b", compiled by Visual C++ build "; + let mut i = 0; + while i < s.len() { + buffer[pointer + i] = s[i]; + i += 1; + } + pointer += s.len(); + } + { + let s = msc_ver; + let mut i = 0; + while i < s.len() { + buffer[pointer + i] = s[i]; + i += 1; + } + pointer += s.len(); + } + { + let s = b", "; + let mut i = 0; + while i < s.len() { + buffer[pointer + i] = s[i]; + i += 1; + } + pointer += s.len(); + } + { + buffer[pointer + 0] = b'0' + (pointer_width / 10) as u8; + buffer[pointer + 1] = b'0' + (pointer_width % 10) as u8; + pointer += 2; + } + { + let s = b"-bit"; + let mut i = 0; + while i < s.len() { + buffer[pointer + i] = s[i]; + i += 1; + } + pointer += s.len(); + } + buffer[pointer] = 0; + buffer + }; + unsafe { std::ffi::CStr::from_ptr(PG_VERSION_STR.as_ptr().cast()).to_str().unwrap() } +} + #[inline] pub fn get_pg_major_minor_version_string() -> &'static str { super::PG_VERSION.to_str().unwrap() diff --git a/pgrx-tests/Cargo.toml b/pgrx-tests/Cargo.toml index 47cbd7c99e..895fe8441b 100644 --- a/pgrx-tests/Cargo.toml +++ b/pgrx-tests/Cargo.toml @@ -39,7 +39,10 @@ pg17 = ["pgrx/pg17"] pg_test = [] proptest = ["dep:proptest"] cshim = ["pgrx/cshim"] -no-schema-generation = ["pgrx/no-schema-generation", "pgrx-macros/no-schema-generation"] +no-schema-generation = [ + "pgrx/no-schema-generation", + "pgrx-macros/no-schema-generation", +] nightly = ["pgrx/nightly"] [package.metadata.docs.rs] @@ -66,6 +69,7 @@ thiserror.workspace = true paste = "1" postgres = "0.19.7" proptest = { version = "1", optional = true } +tempfile = "3.13.0" sysinfo = "0.30.10" rand = "0.8.5" @@ -74,6 +78,16 @@ path = "../pgrx" default-features = false version = "=0.12.7" +[target.'cfg(target_os = "windows")'.dependencies] +winapi = { version = "0.3.9", features = [ + "securitybaseapi", + "minwinbase", + "namedpipeapi", + "winbase", + "winerror", + "winnt", +] } + [dev-dependencies] eyre.workspace = true # testing functions that return `eyre::Result` trybuild = "1" diff --git a/pgrx-tests/src/framework.rs b/pgrx-tests/src/framework.rs index 6dfa5e9c35..011105e82a 100644 --- a/pgrx-tests/src/framework.rs +++ b/pgrx-tests/src/framework.rs @@ -114,7 +114,7 @@ pub fn run_test( ); return Ok(()); } - let (loglines, system_session_id) = initialize_test_framework(postgresql_conf)?; + let (loglines, system_session_id) = get_test_framework(postgresql_conf)?; let (mut client, session_id) = client()?; @@ -190,9 +190,7 @@ fn format_loglines(session_id: &str, loglines: &LogLines) -> String { result } -fn initialize_test_framework( - postgresql_conf: Vec<&'static str>, -) -> eyre::Result<(LogLines, String)> { +fn get_test_framework(postgresql_conf: Vec<&'static str>) -> eyre::Result<(LogLines, String)> { let mut state = TEST_MUTEX .get_or_init(|| { Mutex::new(SetupState { @@ -212,22 +210,31 @@ fn initialize_test_framework( }); if !state.installed { - shutdown::register_shutdown_hook(); - install_extension()?; - initdb(postgresql_conf)?; - - let system_session_id = start_pg(state.loglines.clone())?; - let pg_config = get_pg_config()?; - dropdb()?; - createdb(&pg_config, get_pg_dbname(), true, false, get_runas())?; - create_extension()?; - state.installed = true; - state.system_session_id = system_session_id; + initialize_test_framework(&mut state, postgresql_conf) + .expect("Could not initialize test framework"); } Ok((state.loglines.clone(), state.system_session_id.clone())) } +fn initialize_test_framework( + state: &mut SetupState, + postgresql_conf: Vec<&'static str>, +) -> eyre::Result<()> { + shutdown::register_shutdown_hook(); + install_extension()?; + initdb(postgresql_conf)?; + + let system_session_id = start_pg(state.loglines.clone())?; + let pg_config = get_pg_config()?; + dropdb()?; + createdb(&pg_config, get_pg_dbname(), true, false, get_runas())?; + create_extension()?; + state.installed = true; + state.system_session_id = system_session_id; + Ok(()) +} + fn get_pg_config() -> eyre::Result { let pgrx = Pgrx::from_config().wrap_err("Unable to get PGRX from config")?; @@ -418,7 +425,7 @@ fn maybe_make_pgdata>(pgdata: P) -> eyre::Result { } else { // if the directory doesn't exist, make it. If it does, then we reuse it if !pgdata.exists() { - std::fs::create_dir_all(&pgdata)?; + std::fs::create_dir_all(pgdata.parent().unwrap())?; // which is the only time we need to `initdb` it. need_initdb = true; @@ -446,25 +453,20 @@ fn initdb(postgresql_conf: Vec<&'static str>) -> eyre::Result<()> { }; command + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) .current_dir(pgdata.parent().unwrap()) .args(get_c_locale_flags()) .arg("-D") - .arg(&pgdata) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()); + .arg(&pgdata); let command_str = format!("{command:?}"); println!("{} {}", " Running".bold().green(), command_str); - let child = command.spawn().wrap_err_with(|| { - format!( - "Failed to spawn process for initializing database using command: '{command_str}': " - ) - })?; - let output = child.wait_with_output().wrap_err_with(|| { + let output = command.output().wrap_err_with(|| { format!( - "Failed waiting for spawned process attempting to initialize database using command: '{command_str}': " + "Failed to spawn process for initializing database using command: '{command_str}': " ) })?; @@ -476,6 +478,8 @@ fn initdb(postgresql_conf: Vec<&'static str>) -> eyre::Result<()> { String::from_utf8(output.stderr).unwrap() )); } + + println!("{} initializing database", " Finished".bold().green()); } modify_postgresql_conf(pgdata, postgresql_conf) @@ -485,8 +489,10 @@ fn modify_postgresql_conf(pgdata: PathBuf, postgresql_conf: Vec<&'static str>) - let mut contents = String::new(); contents.push_str("log_line_prefix='[%m] [%p] [%c]: '\n"); - contents - .push_str(&format!("unix_socket_directories = '{}'\n", pgdata.parent().unwrap().display())); + contents.push_str(&format!( + "unix_socket_directories = '{}'\n", + pgdata.parent().unwrap().display().to_string().replace("\\", "\\\\") + )); for setting in postgresql_conf { contents.push_str(&format!("{setting}\n")); } @@ -522,121 +528,160 @@ fn modify_postgresql_conf(pgdata: PathBuf, postgresql_conf: Vec<&'static str>) - fn start_pg(loglines: LogLines) -> eyre::Result { wait_for_pidfile()?; + #[cfg(target_family = "unix")] + let pipe = pipe::UnixFifo::create()?; + #[cfg(target_family = "unix")] + let make_pipe_opened_without_blocking = pipe.full()?; + #[cfg(target_os = "windows")] + let mut pipe = pipe::WindowsNamedPipe::create()?; + let pg_config = get_pg_config()?; - let postmaster_path = - pg_config.postmaster_path().wrap_err("unable to determine postmaster path")?; - - let mut command = if use_valgrind() { - let mut cmd = Command::new("valgrind"); - cmd.args([ - "--leak-check=no", - "--gen-suppressions=all", - "--time-stamp=yes", - "--error-markers=VALGRINDERROR-BEGIN,VALGRINDERROR-END", - "--trace-children=yes", - ]); - // Try to provide a suppressions file, we'll likely get false positives - // if we can't, but that might be better than nothing. + let pg_ctl = pg_config.pg_ctl_path()?; + + let postmaster_path = if use_valgrind() { + #[allow(unused_mut)] + let mut builder = tempfile::Builder::new(); + #[cfg(target_family = "unix")] + { + use std::os::unix::fs::PermissionsExt; + let permission = std::fs::Permissions::from_mode(0o700); + builder.permissions(permission); + } + let mut file = builder.tempfile()?; + file.write_all(b"#!/usr/bin/sh\n")?; + let mut command = Command::new("valgrind"); + command.arg("--leak-check=no"); + command.arg("--gen-suppressions=all"); + command.arg("--time-stamp=yes"); + command.arg("--error-markers=VALGRINDERROR-BEGIN,VALGRINDERROR-END"); + command.arg("--trace-children=yes"); if let Ok(path) = valgrind_suppressions_path(&pg_config) { - if path.exists() { - cmd.arg(format!("--suppressions={}", path.display())); + if let Ok(true) = std::fs::exists(&path) { + command.arg(format!("--suppressions={}", path.display())); } } + command.arg(pg_config.postmaster_path()?.display().to_string()); + file.write_all(format!("{command:?}").as_bytes())?; + file.write_all(b" \"$@\"\n")?; + Some(file.into_temp_path()) + } else { + None + }; - cmd.arg(postmaster_path); - cmd - } else if let Some(runas) = get_runas() { + let mut postmaster_args = Vec::new(); + postmaster_args.push("-i".into()); + postmaster_args.push("-p".into()); + postmaster_args.push(pg_config.test_port().expect("unable to determine test port").to_string()); + postmaster_args.push("-h".into()); + postmaster_args.push(pg_config.host().into()); + postmaster_args.push("-c".into()); + postmaster_args.push("log_destination=stderr".into()); + postmaster_args.push("-c".into()); + postmaster_args.push("logging_collector=off".into()); + + let mut command = if let Some(runas) = get_runas() { let mut cmd = sudo_command(runas); - cmd.arg(postmaster_path); + cmd.arg(pg_ctl); cmd } else { - Command::new(postmaster_path) + Command::new(pg_ctl) }; command + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .arg("start") + .arg("-o") + .arg(postmaster_args.join(" ")) .arg("-D") .arg(get_pgdata_path()?.to_str().unwrap()) - .arg("-h") - .arg(pg_config.host()) - .arg("-p") - .arg(pg_config.test_port().expect("unable to determine test port").to_string()) - // Redirecting logs to files can hang the test framework, override it - .args(["-c", "log_destination=stderr", "-c", "logging_collector=off"]) - .stdout(Stdio::inherit()) - .stderr(Stdio::piped()); + .arg("-l") + .arg(pipe.path()); + if let Some(postmaster_path) = postmaster_path.as_ref() { + command + .arg("-W") // pg_ctl cannot detect if postmaster starts + .arg("-p") + .arg(postmaster_path); + } + #[cfg(target_os = "windows")] + { + // on windows, created pipes are leaked, so that the command hangs + command.stdout(Stdio::inherit()).stderr(Stdio::inherit()); + } let command_str = format!("{command:?}"); - // start Postgres and monitor its stderr in the background - // also notify the main thread when it's ready to accept connections - let session_id = monitor_pg(command, command_str, loglines); - - Ok(session_id) -} - -fn valgrind_suppressions_path(pg_config: &PgConfig) -> Result { - let mut home = Pgrx::home()?; - home.push(pg_config.version()?); - home.push("src/tools/valgrind.supp"); - Ok(home) -} + #[cfg(target_family = "unix")] + let (output, mut pipe) = { + let output = command.output(); + let pipe = pipe.read().expect("failed to connect to pipe"); + drop(make_pipe_opened_without_blocking); + (output?, pipe) + }; + #[cfg(target_os = "windows")] + let (output, mut pipe) = { + let (output, pipe) = std::thread::scope(|scope| { + let thread = scope.spawn(|| { + pipe.connect().expect("failed to connect to pg_ctl"); + pipe.connect().expect("failed to connect to pipe") + }); + (command.output(), thread.join().unwrap()) + }); + (output?, pipe) + }; -fn wait_for_pidfile() -> Result<(), eyre::Report> { - const MAX_PIDFILE_RETRIES: usize = 10; + if !output.status.success() { + let log = { + use std::io::Read; + let mut buffer = vec![0u8; 4096]; + let mut result = Vec::new(); + if let Ok(n) = pipe.read(&mut buffer) { + if n > 0 { + result.extend(&buffer[..n]); + } + } + result + }; + panic!( + "problem running pg_ctl: {}\n\n{}\n\n{}", + command_str, + String::from_utf8(output.stderr).unwrap(), + String::from_utf8(log).unwrap() + ); + } - let pidfile = get_pid_file()?; + add_shutdown_hook(|| { + let pg_config = get_pg_config().unwrap(); + let pg_ctl = pg_config.pg_ctl_path().unwrap(); - let mut retries = 0; - while pidfile.exists() { - if retries > MAX_PIDFILE_RETRIES { - // break out and try to start postgres anyways, maybe it'll report a decent error about what's going on - eprintln!("`{}` has existed for ~10s. There might be some problem with the pgrx testing Postgres instance", pidfile.display()); - break; + let mut command = if let Some(runas) = get_runas() { + let mut cmd = sudo_command(runas); + cmd.arg(pg_ctl); + cmd + } else { + Command::new(pg_ctl) + }; + command + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .arg("stop") + .arg("-D") + .arg(get_pgdata_path().unwrap().to_str().unwrap()) + .arg("-m") + .arg("fast"); + let command_str = format!("{command:?}"); + let output = command.output().unwrap(); + if !output.status.success() { + panic!( + "problem running pg_ctl: {}\n\n{}", + command_str, + String::from_utf8(output.stderr).unwrap() + ); } - eprintln!("`{}` still exists. Waiting...", pidfile.display()); - std::thread::sleep(Duration::from_secs(1)); - retries += 1; - } - Ok(()) -} + }); -fn monitor_pg(mut command: Command, cmd_string: String, loglines: LogLines) -> String { let (sender, receiver) = std::sync::mpsc::channel(); - std::thread::spawn(move || { - let mut child = command.spawn().expect("postmaster didn't spawn"); - - let pid = child.id(); - // Add a shutdown hook so we can terminate it when the test framework - // exits. TODO: Consider finding a way to handle cases where we fail to - // clean up due to a SIGNAL? - add_shutdown_hook(move || unsafe { - if let Some(_runas) = get_runas() { - sudo_command("root") // NB: we must be "root" to kill the `sudo` process we spawned to start postgres - .arg("kill") - .arg("-s") - .arg("SIGTERM") - .arg(pid.to_string()) - .spawn() - .expect("failed to spawn `sudo kill`") - .wait() - .expect("`sudo kill` didn't work"); - } else { - libc::kill(pid as libc::pid_t, libc::SIGTERM); - let message_string = std::ffi::CString::new( - format!("stopping postgres (pid={pid})\n").bold().blue().to_string(), - ) - .unwrap(); - // IMPORTANT: Rust string literals are not naturally null-terminated - libc::printf("%s\0".as_ptr().cast(), message_string.as_ptr()); - } - }); - - eprintln!("{cmd}\npid={p}", cmd = cmd_string.bold().blue(), p = pid.to_string().yellow()); - eprintln!("{}", pg_sys::get_pg_version_string().bold().purple()); - - // wait for the database to say its ready to start up - let reader = BufReader::new(child.stderr.take().expect("couldn't take postmaster stderr")); - + let reader = BufReader::new(pipe); let regex = regex::Regex::new(r"\[.*?\] \[.*?\] \[(?P.*?)\]").unwrap(); let mut is_started_yet = false; let mut lines = reader.lines(); @@ -676,21 +721,37 @@ fn monitor_pg(mut command: Command, cmd_string: String, loglines: LogLines) -> S let session_lines = loglines.entry(session_id).or_default(); session_lines.push(line); } - - // wait for Postgres to really finish - match child.try_wait() { - Ok(status) => { - if let Some(_status) = status { - // we exited normally - } - } - Err(e) => panic!("was going to let Postgres finish, but errored this time:\n{e}"), - } }); // wait for Postgres to indicate it's ready to accept connection // and return its pid when it is - receiver.recv().expect("Postgres failed to start") + Ok(receiver.recv().expect("Postgres failed to start")) +} + +fn valgrind_suppressions_path(pg_config: &PgConfig) -> Result { + let mut home = Pgrx::home()?; + home.push(pg_config.version()?); + home.push("src/tools/valgrind.supp"); + Ok(home) +} + +fn wait_for_pidfile() -> Result<(), eyre::Report> { + const MAX_PIDFILE_RETRIES: usize = 10; + + let pidfile = get_pid_file()?; + + let mut retries = 0; + while pidfile.exists() { + if retries > MAX_PIDFILE_RETRIES { + // break out and try to start postgres anyways, maybe it'll report a decent error about what's going on + eprintln!("`{}` has existed for ~10s. There might be some problem with the pgrx testing Postgres instance", pidfile.display()); + break; + } + eprintln!("`{}` still exists. Waiting...", pidfile.display()); + std::thread::sleep(Duration::from_secs(1)); + retries += 1; + } + Ok(()) } fn dropdb() -> eyre::Result<()> { @@ -796,8 +857,12 @@ pub(crate) fn get_pg_dbname() -> &'static str { } pub(crate) fn get_pg_user() -> String { + #[cfg(target_family = "unix")] + let varname = "USER"; + #[cfg(target_os = "windows")] + let varname = "USERNAME"; get_runas().unwrap_or_else(|| { - std::env::var("USER") + std::env::var(varname) .unwrap_or_else(|_| panic!("USER environment var is unset or invalid UTF-8")) }) } @@ -875,7 +940,7 @@ fn get_cargo_args() -> Vec { while let Some(process) = system.process(pid) { // only if it's "cargo"... (This works for now, but just because `cargo` // is at the end of the path. How *should* this handle `CARGO`?) - if process.exe().is_some_and(|p| p.ends_with("cargo")) { + if process.exe().is_some_and(|p| p.ends_with("cargo") || p.ends_with("cargo.exe")) { // ... and only if it's "cargo test"... if process.cmd().iter().any(|arg| arg == "test") && !process.cmd().iter().any(|arg| arg == "pgrx") @@ -931,3 +996,224 @@ fn sudo_command>(user: U) -> Command { sudo.arg(user); sudo } + +pub mod pipe { + use rand::distributions::Alphanumeric; + use rand::Rng; + use std::fs::File; + use std::io::Error; + use std::path::{Path, PathBuf}; + + #[cfg(target_family = "unix")] + pub struct UnixFifo { + path: PathBuf, + } + + #[cfg(target_family = "unix")] + impl UnixFifo { + pub fn create() -> std::io::Result { + use std::ffi::CString; + let filename: String = + rand::thread_rng().sample_iter(Alphanumeric).map(char::from).take(6).collect(); + let path = format!(r"/tmp/{filename}"); + let arg = CString::new(path.clone()).unwrap(); + let mode = libc::S_IRUSR + | libc::S_IWUSR + | libc::S_IRGRP + | libc::S_IWGRP + | libc::S_IROTH + | libc::S_IWOTH; + let errno = unsafe { libc::mkfifo(arg.as_ptr(), mode) }; + if errno < 0 { + return Err(Error::last_os_error()); + } + let errno = unsafe { libc::chmod(arg.as_ptr(), mode) }; + if errno < 0 { + return Err(Error::last_os_error()); + } + Ok(UnixFifo { path: PathBuf::from(path) }) + } + pub fn path(&self) -> &Path { + &self.path + } + pub fn read(&self) -> std::io::Result { + use std::os::unix::fs::OpenOptionsExt; + let file = std::fs::OpenOptions::new() + .read(true) + .custom_flags(libc::O_NOCTTY) + .open(&self.path)?; + Ok(file) + } + pub fn full(&self) -> std::io::Result { + use std::os::unix::fs::OpenOptionsExt; + let file = std::fs::OpenOptions::new() + .read(true) + .write(true) + .custom_flags(libc::O_NOCTTY) + .open(&self.path)?; + Ok(file) + } + } + + #[cfg(target_family = "unix")] + impl Drop for UnixFifo { + fn drop(&mut self) { + let _ = std::fs::remove_file(&self.path); + } + } + + #[cfg(target_os = "windows")] + pub struct WindowsNamedPipe { + path: PathBuf, + file: File, + } + + #[cfg(target_os = "windows")] + impl WindowsNamedPipe { + pub fn create() -> std::io::Result { + let filename: String = + rand::thread_rng().sample_iter(Alphanumeric).map(char::from).take(6).collect(); + let path = format!(r"\\.\pipe\{filename}"); + let server = unsafe { + use std::os::windows::ffi::OsStrExt; + use std::os::windows::io::FromRawHandle; + let mut os_str = PathBuf::from(&path).as_os_str().to_os_string(); + os_str.push("\0"); + let arg = os_str.encode_wide().collect::>(); + let mut sd = { + let mut sd = std::mem::zeroed::(); + let success = winapi::um::securitybaseapi::InitializeSecurityDescriptor( + (&raw mut sd).cast(), + winapi::um::winnt::SECURITY_DESCRIPTOR_REVISION, + ); + if success == 0 { + return Err(Error::last_os_error()); + } + let success = winapi::um::securitybaseapi::SetSecurityDescriptorDacl( + (&raw mut sd).cast(), + 1, + std::ptr::null_mut(), + 0, + ); + if success == 0 { + return Err(Error::last_os_error()); + } + let success = winapi::um::securitybaseapi::SetSecurityDescriptorControl( + (&raw mut sd).cast(), + winapi::um::winnt::SE_DACL_PROTECTED, + winapi::um::winnt::SE_DACL_PROTECTED, + ); + if success == 0 { + return Err(Error::last_os_error()); + } + sd + }; + let mut sa = { + let mut sa = std::mem::zeroed::(); + sa.nLength = size_of::() as _; + sa.lpSecurityDescriptor = (&raw mut sd).cast(); + sa.bInheritHandle = 0; + sa + }; + let raw_handle = winapi::um::namedpipeapi::CreateNamedPipeW( + arg.as_ptr().cast(), + winapi::um::winbase::PIPE_ACCESS_DUPLEX + | winapi::um::winbase::FILE_FLAG_FIRST_PIPE_INSTANCE, + winapi::um::winbase::PIPE_TYPE_BYTE + | winapi::um::winbase::PIPE_READMODE_BYTE + | winapi::um::winbase::PIPE_WAIT, + winapi::um::winbase::PIPE_UNLIMITED_INSTANCES, + 65536, + 65536, + 0, + &raw mut sa, + ); + if raw_handle == winapi::um::handleapi::INVALID_HANDLE_VALUE { + return Err(Error::last_os_error()); + } + File::from_raw_handle(raw_handle.cast()) + }; + Ok(WindowsNamedPipe { path: PathBuf::from(path), file: server }) + } + pub fn path(&self) -> &Path { + &self.path + } + pub fn connect(&mut self) -> std::io::Result { + use std::os::windows::io::AsRawHandle; + let ret = unsafe { + winapi::um::namedpipeapi::ConnectNamedPipe( + self.file.as_raw_handle().cast(), + std::ptr::null_mut(), + ) + }; + if ret == 0 { + let last_os_error = Error::last_os_error(); + if last_os_error.raw_os_error() + != Some(winapi::shared::winerror::ERROR_PIPE_CONNECTED as _) + { + return Err(last_os_error); + } + } + let path = &self.path; + let server = unsafe { + use std::os::windows::ffi::OsStrExt; + use std::os::windows::io::FromRawHandle; + let mut os_str = PathBuf::from(&path).as_os_str().to_os_string(); + os_str.push("\0"); + let arg = os_str.encode_wide().collect::>(); + let mut sd = { + let mut sd = std::mem::zeroed::(); + let success = winapi::um::securitybaseapi::InitializeSecurityDescriptor( + (&raw mut sd).cast(), + winapi::um::winnt::SECURITY_DESCRIPTOR_REVISION, + ); + if success == 0 { + return Err(Error::last_os_error()); + } + let success = winapi::um::securitybaseapi::SetSecurityDescriptorDacl( + (&raw mut sd).cast(), + 1, + std::ptr::null_mut(), + 0, + ); + if success == 0 { + return Err(Error::last_os_error()); + } + let success = winapi::um::securitybaseapi::SetSecurityDescriptorControl( + (&raw mut sd).cast(), + winapi::um::winnt::SE_DACL_PROTECTED, + winapi::um::winnt::SE_DACL_PROTECTED, + ); + if success == 0 { + return Err(Error::last_os_error()); + } + sd + }; + let mut sa = { + let mut sa = std::mem::zeroed::(); + sa.nLength = size_of::() as _; + sa.lpSecurityDescriptor = (&raw mut sd).cast(); + sa.bInheritHandle = 0; + sa + }; + let raw_handle = winapi::um::namedpipeapi::CreateNamedPipeW( + arg.as_ptr().cast(), + winapi::um::winbase::PIPE_ACCESS_DUPLEX, + winapi::um::winbase::PIPE_TYPE_BYTE + | winapi::um::winbase::PIPE_READMODE_BYTE + | winapi::um::winbase::PIPE_WAIT, + winapi::um::winbase::PIPE_UNLIMITED_INSTANCES, + 65536, + 65536, + 0, + &raw mut sa, + ); + if raw_handle == winapi::um::handleapi::INVALID_HANDLE_VALUE { + return Err(Error::last_os_error()); + } + File::from_raw_handle(raw_handle.cast()) + }; + Ok(std::mem::replace(&mut self.file, server)) + } + } +} diff --git a/pgrx-tests/src/framework/shutdown.rs b/pgrx-tests/src/framework/shutdown.rs index cc3f91ab6d..9a439823d8 100644 --- a/pgrx-tests/src/framework/shutdown.rs +++ b/pgrx-tests/src/framework/shutdown.rs @@ -9,7 +9,7 @@ //LICENSE Use of this source code is governed by the MIT license that can be found in the LICENSE file. use std::panic::{self, AssertUnwindSafe, Location}; use std::sync::{Mutex, PoisonError}; -use std::{any, io, mem, process}; +use std::{any, mem, process}; /// Register a shutdown hook to be called when the process exits. /// @@ -114,12 +114,6 @@ fn failure_message(e: &(dyn any::Any + Send)) -> &str { /// Write to stderr, bypassing libtest's output redirection. Doesn't append `\n`. fn write_stderr(s: &str) { - loop { - let res = unsafe { libc::write(libc::STDERR_FILENO, s.as_ptr().cast(), s.len()) }; - // Handle EINTR to ensure we don't drop messages. - // `Error::last_os_error()` just reads from errno, so it's fine to use here. - if res >= 0 || io::Error::last_os_error().kind() != io::ErrorKind::Interrupted { - break; - } - } + use std::io::Write; + let _ = std::io::stderr().lock().write_all(s.as_bytes()); } diff --git a/pgrx-tests/src/tests/mod.rs b/pgrx-tests/src/tests/mod.rs index fd1658cce0..1f7db375f4 100644 --- a/pgrx-tests/src/tests/mod.rs +++ b/pgrx-tests/src/tests/mod.rs @@ -58,6 +58,7 @@ mod rel_tests; mod result_tests; mod roundtrip_tests; mod schema_tests; +#[cfg(target_family = "unix")] mod shmem_tests; mod spi_tests; mod srf_tests; diff --git a/pgrx-tests/src/tests/result_tests.rs b/pgrx-tests/src/tests/result_tests.rs index 77fe612f8f..4246cb93a1 100644 --- a/pgrx-tests/src/tests/result_tests.rs +++ b/pgrx-tests/src/tests/result_tests.rs @@ -96,10 +96,13 @@ mod tests { Err(pgrx::spi::Error::InvalidPosition) } - #[pg_test(error = "No such file or directory (os error 2)")] + #[pg_test(error = "I am a platform-independent and locale-agnostic string")] fn test_return_io_error() -> Result<(), std::io::Error> { - std::fs::read("/tmp/i-sure-hope-this-doest-exist.pgrx-tests::test_result_result") - .map(|_| ()) + use std::io::{Error, ErrorKind}; + Err(Error::new( + ErrorKind::NotFound, + "I am a platform-independent and locale-agnostic string", + )) } #[pg_test] From 162135fc630b0a9618d893d394e350685ed184be Mon Sep 17 00:00:00 2001 From: usamoi Date: Fri, 1 Nov 2024 21:02:16 +0800 Subject: [PATCH 2/5] fix shmem API on Windows --- pgrx-examples/shmem/src/lib.rs | 12 ++-- pgrx-tests/src/tests/mod.rs | 1 - pgrx-tests/src/tests/shmem_tests.rs | 4 +- pgrx/src/atomics.rs | 15 ++++- pgrx/src/lwlock.rs | 98 +++++++---------------------- pgrx/src/shmem.rs | 53 ++++++++-------- 6 files changed, 71 insertions(+), 112 deletions(-) diff --git a/pgrx-examples/shmem/src/lib.rs b/pgrx-examples/shmem/src/lib.rs index f092c03e7d..2a421f51ac 100644 --- a/pgrx-examples/shmem/src/lib.rs +++ b/pgrx-examples/shmem/src/lib.rs @@ -29,12 +29,12 @@ pub struct Pgtest { unsafe impl PGRXSharedMemory for Pgtest {} -static DEQUE: PgLwLock> = PgLwLock::new(); -static VEC: PgLwLock> = PgLwLock::new(); -static HASH: PgLwLock> = PgLwLock::new(); -static STRUCT: PgLwLock = PgLwLock::new(); -static PRIMITIVE: PgLwLock = PgLwLock::new(); -static ATOMIC: PgAtomic = PgAtomic::new(); +static DEQUE: PgLwLock> = PgLwLock::new(c"shmem_deque"); +static VEC: PgLwLock> = PgLwLock::new(c"shmem_vec"); +static HASH: PgLwLock> = PgLwLock::new(c"shmem_hash"); +static STRUCT: PgLwLock = PgLwLock::new(c"shmem_struct"); +static PRIMITIVE: PgLwLock = PgLwLock::new(c"shmem_primtive"); +static ATOMIC: PgAtomic = PgAtomic::new(c"shmem_atomic"); #[pg_guard] pub extern "C" fn _PG_init() { diff --git a/pgrx-tests/src/tests/mod.rs b/pgrx-tests/src/tests/mod.rs index 1f7db375f4..fd1658cce0 100644 --- a/pgrx-tests/src/tests/mod.rs +++ b/pgrx-tests/src/tests/mod.rs @@ -58,7 +58,6 @@ mod rel_tests; mod result_tests; mod roundtrip_tests; mod schema_tests; -#[cfg(target_family = "unix")] mod shmem_tests; mod spi_tests; mod srf_tests; diff --git a/pgrx-tests/src/tests/shmem_tests.rs b/pgrx-tests/src/tests/shmem_tests.rs index a28bc077ef..479fc1b9ee 100644 --- a/pgrx-tests/src/tests/shmem_tests.rs +++ b/pgrx-tests/src/tests/shmem_tests.rs @@ -11,8 +11,8 @@ use pgrx::prelude::*; use pgrx::{pg_shmem_init, PgAtomic, PgLwLock, PgSharedMemoryInitialization}; use std::sync::atomic::AtomicBool; -static ATOMIC: PgAtomic = PgAtomic::new(); -static LWLOCK: PgLwLock = PgLwLock::new(); +static ATOMIC: PgAtomic = PgAtomic::new(c"pgrx_tests_atomic"); +static LWLOCK: PgLwLock = PgLwLock::new(c"pgrx_tests_lwlock"); #[pg_guard] pub extern "C" fn _PG_init() { diff --git a/pgrx/src/atomics.rs b/pgrx/src/atomics.rs index 20647d7200..af722f455d 100644 --- a/pgrx/src/atomics.rs +++ b/pgrx/src/atomics.rs @@ -9,14 +9,20 @@ //LICENSE Use of this source code is governed by the MIT license that can be found in the LICENSE file. #![deny(unsafe_op_in_unsafe_fn)] use std::cell::UnsafeCell; +use std::ffi::CStr; pub struct PgAtomic { + name: &'static CStr, inner: UnsafeCell<*mut T>, } impl PgAtomic { - pub const fn new() -> Self { - Self { inner: UnsafeCell::new(std::ptr::null_mut()) } + pub const fn new(name: &'static CStr) -> Self { + Self { name, inner: UnsafeCell::new(std::ptr::null_mut()) } + } + + pub fn name(&self) -> &'static CStr { + self.name } } @@ -32,7 +38,10 @@ where } pub fn get(&self) -> &T { - unsafe { (*self.inner.get()).as_ref().expect("PgAtomic was not initialized") } + unsafe { + let shared = self.inner.get().read().as_ref().expect("PgAtomic was not initialized"); + shared + } } } diff --git a/pgrx/src/lwlock.rs b/pgrx/src/lwlock.rs index 972c8e7b48..c6e7827e5e 100644 --- a/pgrx/src/lwlock.rs +++ b/pgrx/src/lwlock.rs @@ -10,10 +10,8 @@ #![allow(clippy::needless_borrow)] use crate::pg_sys; use core::ops::{Deref, DerefMut}; -use once_cell::sync::OnceCell; use std::cell::UnsafeCell; -use std::fmt; -use uuid::Uuid; +use std::ffi::CStr; /// A Rust locking mechanism which uses a PostgreSQL LWLock to lock the data /// @@ -33,8 +31,8 @@ use uuid::Uuid; /// This lock can not be poisoned from Rust. Panic and Abort are handled by /// PostgreSQL cleanly. pub struct PgLwLock { - inner: UnsafeCell>, - name: OnceCell<&'static str>, + name: &'static CStr, + inner: UnsafeCell<*mut PgLwLockShared>, } unsafe impl Send for PgLwLock {} @@ -43,94 +41,44 @@ unsafe impl Sync for PgLwLock {} impl PgLwLock { /// Create an empty lock which can be created as a global with None as a /// sentinel value - pub const fn new() -> Self { - PgLwLock { inner: UnsafeCell::new(PgLwLockInner::empty()), name: OnceCell::new() } - } - - /// Create a new lock for T by attaching a LWLock, which is looked up by name - pub fn from_named(input_name: &'static str, value: *mut T) -> Self { - let name = OnceCell::new(); - let inner = UnsafeCell::new(PgLwLockInner::::new(input_name, value)); - name.set(input_name).unwrap(); - PgLwLock { inner, name } + pub const fn new(name: &'static CStr) -> Self { + PgLwLock { name, inner: UnsafeCell::new(std::ptr::null_mut()) } } /// Get the name of the PgLwLock - pub fn get_name(&self) -> &'static str { - match self.name.get() { - None => { - let name = Box::leak(Uuid::new_v4().to_string().into_boxed_str()); - self.name.set(name).unwrap(); - name - } - Some(name) => name, - } + pub fn name(&self) -> &'static CStr { + self.name } /// Obtain a shared lock (which comes with `&T` access) pub fn share(&self) -> PgLwLockShareGuard { - unsafe { self.inner.get().as_ref().unwrap().share() } + unsafe { + let shared = self.inner.get().read().as_ref().expect("PgLwLock was not initialized"); + pg_sys::LWLockAcquire((*shared).lock_ptr, pg_sys::LWLockMode::LW_SHARED); + PgLwLockShareGuard { data: &*(*shared).data.get(), lock: (*shared).lock_ptr } + } } /// Obtain an exclusive lock (which comes with `&mut T` access) pub fn exclusive(&self) -> PgLwLockExclusiveGuard { - unsafe { self.inner.get().as_ref().unwrap().exclusive() } + unsafe { + let shared = self.inner.get().read().as_ref().expect("PgLwLock was not initialized"); + pg_sys::LWLockAcquire((*shared).lock_ptr, pg_sys::LWLockMode::LW_EXCLUSIVE); + PgLwLockExclusiveGuard { data: &mut *(*shared).data.get(), lock: (*shared).lock_ptr } + } } /// Attach an empty PgLwLock lock to a LWLock, and wrap T /// SAFETY: Must only be called from inside the Postgres shared memory init hook - pub unsafe fn attach(&self, value: *mut T) { - *self.inner.get() = PgLwLockInner::::new(self.get_name(), value); + pub unsafe fn attach(&self, value: *mut PgLwLockShared) { + *self.inner.get() = value; } } -pub struct PgLwLockInner { - lock_ptr: *mut pg_sys::LWLock, - data: *mut T, -} - -impl fmt::Debug for PgLwLockInner { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("PgLwLockInner").finish() - } -} - -impl<'a, T> PgLwLockInner { - fn new(name: &'static str, data: *mut T) -> Self { - unsafe { - let lock = alloc::ffi::CString::new(name).expect("CString::new failed"); - PgLwLockInner { - lock_ptr: &mut (*pg_sys::GetNamedLWLockTranche(lock.as_ptr())).lock, - data, - } - } - } - - const fn empty() -> Self { - PgLwLockInner { lock_ptr: std::ptr::null_mut(), data: std::ptr::null_mut() } - } - - fn share(&self) -> PgLwLockShareGuard { - if self.lock_ptr.is_null() { - panic!("PgLwLock is not initialized"); - } - unsafe { - pg_sys::LWLockAcquire(self.lock_ptr, pg_sys::LWLockMode::LW_SHARED); - - PgLwLockShareGuard { data: self.data.as_ref().unwrap(), lock: self.lock_ptr } - } - } - - fn exclusive(&self) -> PgLwLockExclusiveGuard { - if self.lock_ptr.is_null() { - panic!("PgLwLock is not initialized"); - } - unsafe { - pg_sys::LWLockAcquire(self.lock_ptr, pg_sys::LWLockMode::LW_EXCLUSIVE); - - PgLwLockExclusiveGuard { data: self.data.as_mut().unwrap(), lock: self.lock_ptr } - } - } +#[repr(C)] +pub struct PgLwLockShared { + pub data: UnsafeCell, + pub lock_ptr: *mut pg_sys::LWLock, } pub struct PgLwLockShareGuard<'a, T> { diff --git a/pgrx/src/shmem.rs b/pgrx/src/shmem.rs index 9093f60cfe..877c4f9040 100644 --- a/pgrx/src/shmem.rs +++ b/pgrx/src/shmem.rs @@ -10,8 +10,8 @@ #![deny(unsafe_op_in_unsafe_fn)] use crate::lwlock::*; use crate::{pg_sys, PgAtomic}; +use std::cell::UnsafeCell; use std::hash::Hash; -use uuid::Uuid; /// Custom types that want to participate in shared memory must implement this marker trait pub unsafe trait PGRXSharedMemory {} @@ -38,10 +38,10 @@ pub unsafe trait PGRXSharedMemory {} /// use pgrx::{PgAtomic, PgLwLock, pg_shmem_init, PgSharedMemoryInitialization}; /// /// // primitive types must be protected behind a `PgLwLock` -/// static PRIMITIVE: PgLwLock = PgLwLock::new(); +/// static PRIMITIVE: PgLwLock = PgLwLock::new(c"primitive"); /// /// // Rust atomics can be used without locks, wrapped in a `PgAtomic` -/// static ATOMIC: PgAtomic = PgAtomic::new(); +/// static ATOMIC: PgAtomic = PgAtomic::new(c"atomic"); /// /// #[pg_guard] /// pub extern "C" fn _PG_init() { @@ -162,9 +162,8 @@ impl PgSharedMem { /// Must be run from PG_init, use for types which are guarded by a LWLock pub fn pg_init_locked(lock: &PgLwLock) { unsafe { - let lock = alloc::ffi::CString::new(lock.get_name()).expect("CString::new failed"); - pg_sys::RequestAddinShmemSpace(std::mem::size_of::()); - pg_sys::RequestNamedLWLockTranche(lock.as_ptr(), 1); + pg_sys::RequestAddinShmemSpace(std::mem::size_of::>()); + pg_sys::RequestNamedLWLockTranche(lock.name().as_ptr(), 1); } } @@ -178,18 +177,24 @@ impl PgSharedMem { /// Must be run from the shared memory init hook, use for types which are guarded by a `LWLock` /// SAFETY: Must only be called from inside the Postgres shared memory init hook pub unsafe fn shmem_init_locked(lock: &PgLwLock) { - let mut found = false; unsafe { - let shm_name = alloc::ffi::CString::new(lock.get_name()).expect("CString::new failed"); - let addin_shmem_init_lock: *mut pg_sys::LWLock = - &mut (*pg_sys::MainLWLockArray.add(21)).lock; + let shm_name = lock.name(); + let addin_shmem_init_lock = &raw mut (*pg_sys::MainLWLockArray.add(21)).lock; pg_sys::LWLockAcquire(addin_shmem_init_lock, pg_sys::LWLockMode::LW_EXCLUSIVE); - let fv_shmem = - pg_sys::ShmemInitStruct(shm_name.into_raw(), std::mem::size_of::(), &mut found) - as *mut T; - - std::ptr::write(fv_shmem, ::default()); + let mut found = false; + let fv_shmem = pg_sys::ShmemInitStruct( + shm_name.as_ptr(), + std::mem::size_of::>(), + &mut found, + ) + .cast::>(); + if !found { + fv_shmem.write(PgLwLockShared { + data: UnsafeCell::new(T::default()), + lock_ptr: &raw mut (*pg_sys::GetNamedLWLockTranche(shm_name.as_ptr())).lock, + }); + } lock.attach(fv_shmem); pg_sys::LWLockRelease(addin_shmem_init_lock); @@ -200,21 +205,19 @@ impl PgSharedMem { /// SAFETY: Must only be called from inside the Postgres shared memory init hook pub unsafe fn shmem_init_atomic(atomic: &PgAtomic) { unsafe { - let shm_name = alloc::ffi::CString::new(Uuid::new_v4().to_string()) - .expect("CString::new() failed"); - - let addin_shmem_init_lock: *mut pg_sys::LWLock = - &mut (*pg_sys::MainLWLockArray.add(21)).lock; + let shm_name = atomic.name(); + let addin_shmem_init_lock = &raw mut (*pg_sys::MainLWLockArray.add(21)).lock; + pg_sys::LWLockAcquire(addin_shmem_init_lock, pg_sys::LWLockMode::LW_EXCLUSIVE); let mut found = false; - pg_sys::LWLockAcquire(addin_shmem_init_lock, pg_sys::LWLockMode::LW_EXCLUSIVE); let fv_shmem = - pg_sys::ShmemInitStruct(shm_name.into_raw(), std::mem::size_of::(), &mut found) - as *mut T; + pg_sys::ShmemInitStruct(shm_name.as_ptr(), std::mem::size_of::(), &mut found) + .cast::(); + if !found { + fv_shmem.write(T::default()); + } atomic.attach(fv_shmem); - let atomic = T::default(); - std::ptr::copy(&atomic, fv_shmem, 1); pg_sys::LWLockRelease(addin_shmem_init_lock); } } From 1fa8a2dd393e2c1d8cc93d6a92ac56a2aea41d27 Mon Sep 17 00:00:00 2001 From: usamoi Date: Fri, 1 Nov 2024 21:06:06 +0800 Subject: [PATCH 3/5] fix runas.yml --- .github/workflows/runas.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/runas.yml b/.github/workflows/runas.yml index b927108878..5279338552 100644 --- a/.github/workflows/runas.yml +++ b/.github/workflows/runas.yml @@ -93,4 +93,6 @@ jobs: run: cargo pgrx init --pg14=$(which pg_config) - name: Test cargo pgrx test --runas - run: cd pgrx-examples/arrays && cargo pgrx test pg14 --runas postgres --pgdata=/tmp/pgdata + run: | + echo 0 | sudo tee /proc/sys/fs/protected_fifos + cd pgrx-examples/arrays && cargo pgrx test pg14 --runas postgres --pgdata=/tmp/pgdata From 55cfbfa86b3890d58599b6c23ac5c9b1a90697c2 Mon Sep 17 00:00:00 2001 From: usamoi Date: Sun, 3 Nov 2024 01:06:48 +0800 Subject: [PATCH 4/5] add Windows CI --- .github/workflows/tests.yml | 73 +++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f9e16464c2..4a030ae973 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -579,3 +579,76 @@ jobs: - name: Stop sccache server run: sccache --stop-server || true + + build_windows: + name: Windows build & test + needs: lintck + runs-on: ${{ matrix.os }} + if: "!contains(github.event.head_commit.message, 'nogha')" + env: + RUSTC_WRAPPER: sccache + SCCACHE_DIR: C:\Users\runneradmin\sccache + SCCACHE_IDLE_TIMEOUT: 0 + PG_VER: ${{ matrix.postgresql }} + + strategy: + matrix: + os: [ "windows-2022" ] + postgresql: [ 13, 17 ] + + steps: + - uses: actions/checkout@v4 + + - name: Set up prerequisites and environment + run: | + Write-Output "" + + echo "----- Install sccache -----" + Invoke-WebRequest -Uri "https://github.com/mozilla/sccache/releases/download/v0.5.4/sccache-v0.5.4-x86_64-pc-windows-msvc.tar.gz" -OutFile "sccache.tar.gz" + tar -xzvf sccache.tar.gz + Move-Item -Force "sccache-v0.5.4-x86_64-pc-windows-msvc\sccache.exe" -Destination "C:\Windows\System32" + New-Item -ItemType Directory -Force -Path $env:SCCACHE_DIR | Out-Null + sccache --version + + Write-Output "----- Outputting env -----" + Get-ChildItem Env: + Write-Output "" + + + - name: Cache sccache directory + uses: actions/cache@v4 + continue-on-error: false + with: + path: C:\Users\runneradmin\sccache + key: pgrx-sccache-${{matrix.os}}-${{ hashFiles('**/Cargo.lock', '.github/workflows/tests.yml', '.cargo/config.toml') }} + + - name: Cache cargo directory + uses: actions/cache@v4 + with: + path: | + ~/.cargo + key: pgrx-cargo-${{matrix.os}}-tests-${{ hashFiles('**/Cargo.lock', '.github/workflows/tests.yml', '.cargo/config.toml') }} + + - name: Start sccache server + run: sccache --start-server + + - name: Print sccache stats + run: sccache --show-stats + + - name: Install cargo-pgrx + run: cargo install --path cargo-pgrx/ --debug --force + + - name: Print sccache stats + run: sccache --show-stats + + - name: Run 'cargo pgrx init' + run: cargo pgrx init --pg$env:PG_VER=download + + - name: Run tests + run: cargo test --all --no-default-features --features "pg$env:PG_VER pg_test cshim proptest" --all-targets + + - name: Print sccache stats + run: sccache --show-stats + + - name: Stop sccache server + run: sccache --stop-server || true From ea251b25e73a136f9c01ce0918f19678b4a63d93 Mon Sep 17 00:00:00 2001 From: usamoi Date: Sun, 3 Nov 2024 02:23:10 +0800 Subject: [PATCH 5/5] workaround strange linker errors for PostgreSQL 14 on Windows --- pgrx-bindgen/src/build.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pgrx-bindgen/src/build.rs b/pgrx-bindgen/src/build.rs index 0069029b3f..bcfd826ec4 100644 --- a/pgrx-bindgen/src/build.rs +++ b/pgrx-bindgen/src/build.rs @@ -897,6 +897,8 @@ fn add_blocklists(bind: bindgen::Builder) -> bindgen::Builder { .blocklist_function("varsize_any") // it's defined twice on Windows, so use PGERROR instead .blocklist_item("ERROR") + // it causes strange linker errors for PostgreSQL 14 on Windows + .blocklist_function("IsQueryIdEnabled") } fn add_allowlists<'a>(