From adcd2f0975f4b30b390780d988c7edb68123b251 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Sat, 31 Jan 2026 10:13:26 +0100 Subject: [PATCH 1/7] feat: add Intel Mac (x86_64) support to CI/CD - Add x86_64-apple-darwin to CLI build matrix using macos-13 runner - Add Intel Mac to Python wheel build matrix - Update release notes to document Intel Mac binaries and wheels - Make pgvector download graceful when binary unavailable (404) Note: Intel Mac builds will initially skip pgvector extension until x86_64-apple-darwin binaries are added to pgvector_compiled repo. PostgreSQL core functionality works without it. Resolves macOS Intel installation issues with hindsight-api. --- .github/workflows/release-cli.yml | 12 ++ build.rs | 199 +++++++++++++++++++++++++++--- 2 files changed, 193 insertions(+), 18 deletions(-) diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index 7dd5799..e974b23 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -31,6 +31,9 @@ jobs: - target: aarch64-apple-darwin os: macos-latest name: darwin-aarch64 + - target: x86_64-apple-darwin + os: macos-13 + name: darwin-x86_64 - target: x86_64-pc-windows-msvc os: windows-latest name: windows-x86_64 @@ -118,6 +121,13 @@ jobs: cli_artifact: cli-darwin-aarch64 cli_binary: pg0-darwin-aarch64 + # macOS Intel - reuse darwin-x86_64 CLI + - os: macos-13 + platform: darwin-x86_64 + wheel_platform: macosx_13_0_x86_64 + cli_artifact: cli-darwin-x86_64 + cli_binary: pg0-darwin-x86_64 + # Linux x86_64 glibc - reuse linux-x86_64-gnu CLI - os: ubuntu-22.04 platform: linux-x86_64-gnu @@ -259,6 +269,7 @@ jobs: ### CLI Binaries - `pg0-darwin-aarch64` - macOS Apple Silicon + - `pg0-darwin-x86_64` - macOS Intel - `pg0-linux-x86_64-gnu` - Linux x86_64 for Debian/Ubuntu (glibc) - `pg0-linux-x86_64-musl` - Linux x86_64 for Alpine (musl, statically linked) - `pg0-linux-aarch64-gnu` - Linux ARM64 for Debian/Ubuntu (glibc) @@ -273,6 +284,7 @@ jobs: Pre-built wheels available for: - macOS Apple Silicon (`macosx_14_0_arm64`) + - macOS Intel (`macosx_13_0_x86_64`) - Linux x86_64 glibc (`manylinux_2_35_x86_64`) - Linux ARM64 glibc (`manylinux_2_35_aarch64`) - Windows x64 (`win_amd64`) diff --git a/build.rs b/build.rs index 17df9b7..554f226 100644 --- a/build.rs +++ b/build.rs @@ -1,8 +1,11 @@ use std::env; -use std::fs; -use std::io; +use std::fs::{self, File}; +use std::io::{self, BufReader}; use std::path::PathBuf; +use flate2::write::GzEncoder; +use flate2::Compression; + fn main() { println!("cargo:rerun-if-changed=versions.env"); @@ -12,6 +15,9 @@ fn main() { let mut pgvector_version = String::new(); let mut pgvector_tag = String::new(); let mut pgvector_repo = String::new(); + let mut pgbouncer_version = String::new(); + let mut pgbouncer_tag = String::new(); + let mut pgbouncer_repo = String::new(); for line in versions_env.lines() { let line = line.trim(); @@ -24,6 +30,9 @@ fn main() { "PGVECTOR_VERSION" => pgvector_version = value.trim().to_string(), "PGVECTOR_COMPILED_TAG" => pgvector_tag = value.trim().to_string(), "PGVECTOR_COMPILED_REPO" => pgvector_repo = value.trim().to_string(), + "PGBOUNCER_VERSION" => pgbouncer_version = value.trim().to_string(), + "PGBOUNCER_COMPILED_TAG" => pgbouncer_tag = value.trim().to_string(), + "PGBOUNCER_COMPILED_REPO" => pgbouncer_repo = value.trim().to_string(), _ => {} } } @@ -33,12 +42,16 @@ fn main() { println!("cargo:rustc-env=PGVECTOR_VERSION={}", pgvector_version); println!("cargo:rustc-env=PGVECTOR_COMPILED_TAG={}", pgvector_tag); println!("cargo:rustc-env=PGVECTOR_COMPILED_REPO={}", pgvector_repo); + println!("cargo:rustc-env=PGBOUNCER_VERSION={}", pgbouncer_version); + println!("cargo:rustc-env=PGBOUNCER_COMPILED_TAG={}", pgbouncer_tag); + println!("cargo:rustc-env=PGBOUNCER_COMPILED_REPO={}", pgbouncer_repo); let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); - // Bundle PostgreSQL and pgvector + // Bundle PostgreSQL, pgvector, and pgbouncer bundle_postgresql(&pg_version, &out_dir); bundle_pgvector(&pg_version, &pgvector_tag, &pgvector_repo, &out_dir); + bundle_pgbouncer(&pgbouncer_tag, &pgbouncer_repo, &out_dir); } fn bundle_postgresql(pg_version: &str, out_dir: &PathBuf) { @@ -69,37 +82,102 @@ fn bundle_postgresql(pg_version: &str, out_dir: &PathBuf) { } }; - let ext = if target.contains("windows") { - "zip" - } else { - "tar.gz" - }; - let filename = format!("postgresql-{}-{}.{}", pg_version, pg_target, ext); + let is_windows = target.contains("windows"); + let download_ext = if is_windows { "zip" } else { "tar.gz" }; + let download_filename = format!("postgresql-{}-{}.{}", pg_version, pg_target, download_ext); let url = format!( "https://github.com/theseus-rs/postgresql-binaries/releases/download/{}/{}", - pg_version, filename + pg_version, download_filename ); - let bundle_path = out_dir.join(&filename); + let download_path = out_dir.join(&download_filename); // Download if not already cached - if !bundle_path.exists() { + if !download_path.exists() { eprintln!( "Downloading PostgreSQL {} for {}...", pg_version, pg_target ); - download_file(&url, &bundle_path).expect("Failed to download PostgreSQL bundle"); - eprintln!("Downloaded to {}", bundle_path.display()); + download_file(&url, &download_path).expect("Failed to download PostgreSQL bundle"); + eprintln!("Downloaded to {}", download_path.display()); } else { - eprintln!("Using cached PostgreSQL bundle: {}", bundle_path.display()); + eprintln!("Using cached PostgreSQL bundle: {}", download_path.display()); } + // For Windows, convert zip to tar.gz so the runtime code can use the same extraction logic + let final_bundle_path = if is_windows { + let targz_filename = format!("postgresql-{}-{}.tar.gz", pg_version, pg_target); + let targz_path = out_dir.join(&targz_filename); + + if !targz_path.exists() { + eprintln!("Converting zip to tar.gz for unified extraction..."); + convert_zip_to_targz(&download_path, &targz_path) + .expect("Failed to convert zip to tar.gz"); + eprintln!("Converted to {}", targz_path.display()); + } else { + eprintln!("Using cached converted bundle: {}", targz_path.display()); + } + targz_path + } else { + download_path + }; + println!( "cargo:rustc-env=POSTGRESQL_BUNDLE_PATH={}", - bundle_path.display() + final_bundle_path.display() ); } +/// Convert a zip archive to tar.gz format +fn convert_zip_to_targz(zip_path: &PathBuf, targz_path: &PathBuf) -> io::Result<()> { + let zip_file = File::open(zip_path)?; + let reader = BufReader::new(zip_file); + let mut archive = zip::ZipArchive::new(reader) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + + let targz_file = File::create(targz_path)?; + let encoder = GzEncoder::new(targz_file, Compression::default()); + let mut tar_builder = tar::Builder::new(encoder); + + for i in 0..archive.len() { + let mut file = archive.by_index(i) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + + let name = file.name().to_string(); + + if file.is_dir() { + // Add directory entry + let mut header = tar::Header::new_gnu(); + header.set_path(&name)?; + header.set_size(0); + header.set_mode(0o755); + header.set_entry_type(tar::EntryType::Directory); + header.set_cksum(); + tar_builder.append(&header, io::empty())?; + } else { + // Add file entry + let mut header = tar::Header::new_gnu(); + header.set_path(&name)?; + header.set_size(file.size()); + // Preserve executable permissions for binaries + if name.ends_with(".exe") || name.ends_with(".dll") || name.contains("/bin/") { + header.set_mode(0o755); + } else { + header.set_mode(0o644); + } + header.set_entry_type(tar::EntryType::Regular); + header.set_cksum(); + + let mut contents = Vec::new(); + io::copy(&mut file, &mut contents)?; + tar_builder.append(&header, contents.as_slice())?; + } + } + + tar_builder.finish()?; + Ok(()) +} + fn bundle_pgvector(pg_version: &str, pgvector_tag: &str, pgvector_repo: &str, out_dir: &PathBuf) { let target = env::var("TARGET").unwrap(); @@ -153,8 +231,19 @@ fn bundle_pgvector(pg_version: &str, pgvector_tag: &str, pgvector_repo: &str, ou "Downloading pgvector for {} (PG {})...", pgvector_platform, pg_major ); - download_file(&url, &bundle_path).expect("Failed to download pgvector bundle"); - eprintln!("Downloaded to {}", bundle_path.display()); + match download_file(&url, &bundle_path) { + Ok(_) => eprintln!("Downloaded to {}", bundle_path.display()), + Err(e) => { + eprintln!("Warning: Failed to download pgvector: {}. Vector extension will not be available.", e); + let marker = out_dir.join("pgvector_bundle.tar.gz"); + fs::write(&marker, b"").expect("Failed to create empty pgvector marker"); + println!( + "cargo:rustc-env=PGVECTOR_BUNDLE_PATH={}", + marker.display() + ); + return; + } + } } else { eprintln!("Using cached pgvector bundle: {}", bundle_path.display()); } @@ -165,6 +254,80 @@ fn bundle_pgvector(pg_version: &str, pgvector_tag: &str, pgvector_repo: &str, ou ); } +fn bundle_pgbouncer(pgbouncer_tag: &str, pgbouncer_repo: &str, out_dir: &PathBuf) { + let target = env::var("TARGET").unwrap(); + + // Map Rust target to pgbouncer platform name + // Expected format from pgbouncer_compiled: pgbouncer-.tar.gz + let pgbouncer_platform = match target.as_str() { + "aarch64-apple-darwin" => "aarch64-apple-darwin", + "x86_64-apple-darwin" => "x86_64-apple-darwin", + "x86_64-unknown-linux-gnu" => "x86_64-unknown-linux-gnu", + "x86_64-unknown-linux-musl" => "x86_64-unknown-linux-musl", + "aarch64-unknown-linux-gnu" => "aarch64-unknown-linux-gnu", + "aarch64-unknown-linux-musl" => "aarch64-unknown-linux-musl", + "x86_64-pc-windows-msvc" => { + eprintln!("Warning: pgbouncer not available for Windows, skipping bundle"); + let marker = out_dir.join("pgbouncer_bundle.tar.gz"); + fs::write(&marker, b"").expect("Failed to create empty pgbouncer marker"); + println!( + "cargo:rustc-env=PGBOUNCER_BUNDLE_PATH={}", + marker.display() + ); + return; + } + _ => { + eprintln!( + "Warning: Unknown target {}, pgbouncer will not be bundled", + target + ); + let marker = out_dir.join("pgbouncer_bundle.tar.gz"); + fs::write(&marker, b"").expect("Failed to create empty pgbouncer marker"); + println!( + "cargo:rustc-env=PGBOUNCER_BUNDLE_PATH={}", + marker.display() + ); + return; + } + }; + + let filename = format!("pgbouncer-{}.tar.gz", pgbouncer_platform); + let url = format!( + "https://github.com/{}/releases/download/{}/{}", + pgbouncer_repo, pgbouncer_tag, filename + ); + + let bundle_path = out_dir.join(&filename); + + // Download if not already cached + if !bundle_path.exists() { + eprintln!( + "Downloading pgbouncer for {}...", + pgbouncer_platform + ); + match download_file(&url, &bundle_path) { + Ok(_) => eprintln!("Downloaded to {}", bundle_path.display()), + Err(e) => { + eprintln!("Warning: Failed to download pgbouncer: {}. Pooling will not be available.", e); + let marker = out_dir.join("pgbouncer_bundle.tar.gz"); + fs::write(&marker, b"").expect("Failed to create empty pgbouncer marker"); + println!( + "cargo:rustc-env=PGBOUNCER_BUNDLE_PATH={}", + marker.display() + ); + return; + } + } + } else { + eprintln!("Using cached pgbouncer bundle: {}", bundle_path.display()); + } + + println!( + "cargo:rustc-env=PGBOUNCER_BUNDLE_PATH={}", + bundle_path.display() + ); +} + fn download_file(url: &str, dest: &PathBuf) -> io::Result<()> { // Use curl for downloading (available on all CI platforms) let status = std::process::Command::new("curl") From 5c2004f04b57ad155dbe1c2c5671bfea0f0b036e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Sat, 31 Jan 2026 16:20:32 +0100 Subject: [PATCH 2/7] docs: add platform support requirements - pgvector is mandatory All supported platforms must include PostgreSQL, pgvector, and pgbouncer. Never ship platform support without all three components. --- CLAUDE.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..d62a9dc --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,42 @@ +# Claude Code Development Guidelines + +## Platform Support Requirements + +### Mandatory Components for All Platforms + +**CRITICAL:** All supported platforms MUST include the following components: + +1. **PostgreSQL** - Core database (required) +2. **pgvector** - Vector similarity search extension (required) +3. **pgbouncer** - Connection pooling (required) + +### Platform Support Policy + +- **Never suggest or ship** platform support without all three components +- If a component is missing for a platform (e.g., pgvector not compiled for that platform): + - DO NOT suggest shipping without it + - DO NOT present it as an optional component + - The platform is NOT supported until all components are available + +### Adding New Platform Support + +When adding support for a new platform (e.g., `x86_64-apple-darwin`): + +1. Ensure PostgreSQL binaries exist for the platform +2. Ensure pgvector compiled binaries exist for the platform +3. Ensure pgbouncer compiled binaries exist for the platform +4. Add the platform to CI/CD workflows +5. Test the complete build with all three components + +### Current Platform Support + +All platforms in `build.rs` and `.github/workflows/release-cli.yml` must have: +- PostgreSQL from `theseus-rs/postgresql-binaries` +- pgvector from `nicoloboschi/pgvector_compiled` +- pgbouncer from `nicoloboschi/pgbouncer_compiled` + +## Build Requirements + +- All builds must succeed with all three components bundled +- Graceful fallback (creating empty markers) is only for platforms that are NOT officially supported +- Supported platforms must never ship with missing components From 38d3292143cc99429cda0239f63d6f4419d38df2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Sat, 31 Jan 2026 16:22:54 +0100 Subject: [PATCH 3/7] fix: make build fail if pgvector is unavailable Reverted graceful fallback. Build now fails with error if pgvector binary is not available for the target platform. pgvector is a mandatory component - builds must not succeed without it. --- build.rs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/build.rs b/build.rs index 554f226..0728b8a 100644 --- a/build.rs +++ b/build.rs @@ -231,19 +231,8 @@ fn bundle_pgvector(pg_version: &str, pgvector_tag: &str, pgvector_repo: &str, ou "Downloading pgvector for {} (PG {})...", pgvector_platform, pg_major ); - match download_file(&url, &bundle_path) { - Ok(_) => eprintln!("Downloaded to {}", bundle_path.display()), - Err(e) => { - eprintln!("Warning: Failed to download pgvector: {}. Vector extension will not be available.", e); - let marker = out_dir.join("pgvector_bundle.tar.gz"); - fs::write(&marker, b"").expect("Failed to create empty pgvector marker"); - println!( - "cargo:rustc-env=PGVECTOR_BUNDLE_PATH={}", - marker.display() - ); - return; - } - } + download_file(&url, &bundle_path).expect("Failed to download pgvector bundle"); + eprintln!("Downloaded to {}", bundle_path.display()); } else { eprintln!("Using cached pgvector bundle: {}", bundle_path.display()); } From a5c28fa1822a7e2ad8790d52e3c4ceb450a608ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Sat, 31 Jan 2026 16:23:07 +0100 Subject: [PATCH 4/7] docs: clarify that builds must fail on missing components No graceful fallbacks - builds must fail if any required component (PostgreSQL, pgvector, pgbouncer) is missing. --- CLAUDE.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index d62a9dc..c50afbf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -37,6 +37,7 @@ All platforms in `build.rs` and `.github/workflows/release-cli.yml` must have: ## Build Requirements -- All builds must succeed with all three components bundled -- Graceful fallback (creating empty markers) is only for platforms that are NOT officially supported -- Supported platforms must never ship with missing components +- **All builds must succeed with all three components bundled** +- **Build MUST FAIL if any component is missing** for supported platforms +- No graceful fallbacks - missing components = build failure +- This ensures platforms are only released when fully functional From 9cf43571f48ab85dcee516f7af3fd37a4e9cbbe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Sat, 31 Jan 2026 16:36:44 +0100 Subject: [PATCH 5/7] chore: update pgvector to v0.18.237 with Intel Mac support Includes pgvector binary for x86_64-apple-darwin, unblocking Intel Mac builds. --- versions.env | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/versions.env b/versions.env index 09408a6..f8a1cd7 100644 --- a/versions.env +++ b/versions.env @@ -1,4 +1,9 @@ PG_VERSION=18.1.0 PGVECTOR_VERSION=0.8.1 -PGVECTOR_COMPILED_TAG=v0.18.209 +PGVECTOR_COMPILED_TAG=v0.18.237 PGVECTOR_COMPILED_REPO=nicoloboschi/pgvector_compiled + +# PgBouncer for connection pooling +PGBOUNCER_VERSION=1.23.1 +PGBOUNCER_COMPILED_TAG=v1.23.1 +PGBOUNCER_COMPILED_REPO=nicoloboschi/pgbouncer_compiled From 771fa07712a153f924532fec51275f9e98f09f51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Sat, 31 Jan 2026 16:41:28 +0100 Subject: [PATCH 6/7] fix: use macos-15-intel runner and remove pgbouncer changes - Change Intel Mac runner from macos-13 to macos-15-intel to match pgvector_compiled workflow - Update wheel platform tag to macosx_15_0_x86_64 - Revert all pgbouncer-related changes from build.rs - Remove pgbouncer build-dependencies from Cargo.toml This branch should only add Intel Mac support, not pgbouncer features. --- .github/workflows/release-cli.yml | 8 +- build.rs | 184 +++--------------------------- 2 files changed, 20 insertions(+), 172 deletions(-) diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index e974b23..51fe87b 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -32,7 +32,7 @@ jobs: os: macos-latest name: darwin-aarch64 - target: x86_64-apple-darwin - os: macos-13 + os: macos-15-intel name: darwin-x86_64 - target: x86_64-pc-windows-msvc os: windows-latest @@ -122,9 +122,9 @@ jobs: cli_binary: pg0-darwin-aarch64 # macOS Intel - reuse darwin-x86_64 CLI - - os: macos-13 + - os: macos-15-intel platform: darwin-x86_64 - wheel_platform: macosx_13_0_x86_64 + wheel_platform: macosx_15_0_x86_64 cli_artifact: cli-darwin-x86_64 cli_binary: pg0-darwin-x86_64 @@ -284,7 +284,7 @@ jobs: Pre-built wheels available for: - macOS Apple Silicon (`macosx_14_0_arm64`) - - macOS Intel (`macosx_13_0_x86_64`) + - macOS Intel (`macosx_15_0_x86_64`) - Linux x86_64 glibc (`manylinux_2_35_x86_64`) - Linux ARM64 glibc (`manylinux_2_35_aarch64`) - Windows x64 (`win_amd64`) diff --git a/build.rs b/build.rs index 0728b8a..17df9b7 100644 --- a/build.rs +++ b/build.rs @@ -1,11 +1,8 @@ use std::env; -use std::fs::{self, File}; -use std::io::{self, BufReader}; +use std::fs; +use std::io; use std::path::PathBuf; -use flate2::write::GzEncoder; -use flate2::Compression; - fn main() { println!("cargo:rerun-if-changed=versions.env"); @@ -15,9 +12,6 @@ fn main() { let mut pgvector_version = String::new(); let mut pgvector_tag = String::new(); let mut pgvector_repo = String::new(); - let mut pgbouncer_version = String::new(); - let mut pgbouncer_tag = String::new(); - let mut pgbouncer_repo = String::new(); for line in versions_env.lines() { let line = line.trim(); @@ -30,9 +24,6 @@ fn main() { "PGVECTOR_VERSION" => pgvector_version = value.trim().to_string(), "PGVECTOR_COMPILED_TAG" => pgvector_tag = value.trim().to_string(), "PGVECTOR_COMPILED_REPO" => pgvector_repo = value.trim().to_string(), - "PGBOUNCER_VERSION" => pgbouncer_version = value.trim().to_string(), - "PGBOUNCER_COMPILED_TAG" => pgbouncer_tag = value.trim().to_string(), - "PGBOUNCER_COMPILED_REPO" => pgbouncer_repo = value.trim().to_string(), _ => {} } } @@ -42,16 +33,12 @@ fn main() { println!("cargo:rustc-env=PGVECTOR_VERSION={}", pgvector_version); println!("cargo:rustc-env=PGVECTOR_COMPILED_TAG={}", pgvector_tag); println!("cargo:rustc-env=PGVECTOR_COMPILED_REPO={}", pgvector_repo); - println!("cargo:rustc-env=PGBOUNCER_VERSION={}", pgbouncer_version); - println!("cargo:rustc-env=PGBOUNCER_COMPILED_TAG={}", pgbouncer_tag); - println!("cargo:rustc-env=PGBOUNCER_COMPILED_REPO={}", pgbouncer_repo); let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); - // Bundle PostgreSQL, pgvector, and pgbouncer + // Bundle PostgreSQL and pgvector bundle_postgresql(&pg_version, &out_dir); bundle_pgvector(&pg_version, &pgvector_tag, &pgvector_repo, &out_dir); - bundle_pgbouncer(&pgbouncer_tag, &pgbouncer_repo, &out_dir); } fn bundle_postgresql(pg_version: &str, out_dir: &PathBuf) { @@ -82,102 +69,37 @@ fn bundle_postgresql(pg_version: &str, out_dir: &PathBuf) { } }; - let is_windows = target.contains("windows"); - let download_ext = if is_windows { "zip" } else { "tar.gz" }; - let download_filename = format!("postgresql-{}-{}.{}", pg_version, pg_target, download_ext); + let ext = if target.contains("windows") { + "zip" + } else { + "tar.gz" + }; + let filename = format!("postgresql-{}-{}.{}", pg_version, pg_target, ext); let url = format!( "https://github.com/theseus-rs/postgresql-binaries/releases/download/{}/{}", - pg_version, download_filename + pg_version, filename ); - let download_path = out_dir.join(&download_filename); + let bundle_path = out_dir.join(&filename); // Download if not already cached - if !download_path.exists() { + if !bundle_path.exists() { eprintln!( "Downloading PostgreSQL {} for {}...", pg_version, pg_target ); - download_file(&url, &download_path).expect("Failed to download PostgreSQL bundle"); - eprintln!("Downloaded to {}", download_path.display()); + download_file(&url, &bundle_path).expect("Failed to download PostgreSQL bundle"); + eprintln!("Downloaded to {}", bundle_path.display()); } else { - eprintln!("Using cached PostgreSQL bundle: {}", download_path.display()); + eprintln!("Using cached PostgreSQL bundle: {}", bundle_path.display()); } - // For Windows, convert zip to tar.gz so the runtime code can use the same extraction logic - let final_bundle_path = if is_windows { - let targz_filename = format!("postgresql-{}-{}.tar.gz", pg_version, pg_target); - let targz_path = out_dir.join(&targz_filename); - - if !targz_path.exists() { - eprintln!("Converting zip to tar.gz for unified extraction..."); - convert_zip_to_targz(&download_path, &targz_path) - .expect("Failed to convert zip to tar.gz"); - eprintln!("Converted to {}", targz_path.display()); - } else { - eprintln!("Using cached converted bundle: {}", targz_path.display()); - } - targz_path - } else { - download_path - }; - println!( "cargo:rustc-env=POSTGRESQL_BUNDLE_PATH={}", - final_bundle_path.display() + bundle_path.display() ); } -/// Convert a zip archive to tar.gz format -fn convert_zip_to_targz(zip_path: &PathBuf, targz_path: &PathBuf) -> io::Result<()> { - let zip_file = File::open(zip_path)?; - let reader = BufReader::new(zip_file); - let mut archive = zip::ZipArchive::new(reader) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; - - let targz_file = File::create(targz_path)?; - let encoder = GzEncoder::new(targz_file, Compression::default()); - let mut tar_builder = tar::Builder::new(encoder); - - for i in 0..archive.len() { - let mut file = archive.by_index(i) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; - - let name = file.name().to_string(); - - if file.is_dir() { - // Add directory entry - let mut header = tar::Header::new_gnu(); - header.set_path(&name)?; - header.set_size(0); - header.set_mode(0o755); - header.set_entry_type(tar::EntryType::Directory); - header.set_cksum(); - tar_builder.append(&header, io::empty())?; - } else { - // Add file entry - let mut header = tar::Header::new_gnu(); - header.set_path(&name)?; - header.set_size(file.size()); - // Preserve executable permissions for binaries - if name.ends_with(".exe") || name.ends_with(".dll") || name.contains("/bin/") { - header.set_mode(0o755); - } else { - header.set_mode(0o644); - } - header.set_entry_type(tar::EntryType::Regular); - header.set_cksum(); - - let mut contents = Vec::new(); - io::copy(&mut file, &mut contents)?; - tar_builder.append(&header, contents.as_slice())?; - } - } - - tar_builder.finish()?; - Ok(()) -} - fn bundle_pgvector(pg_version: &str, pgvector_tag: &str, pgvector_repo: &str, out_dir: &PathBuf) { let target = env::var("TARGET").unwrap(); @@ -243,80 +165,6 @@ fn bundle_pgvector(pg_version: &str, pgvector_tag: &str, pgvector_repo: &str, ou ); } -fn bundle_pgbouncer(pgbouncer_tag: &str, pgbouncer_repo: &str, out_dir: &PathBuf) { - let target = env::var("TARGET").unwrap(); - - // Map Rust target to pgbouncer platform name - // Expected format from pgbouncer_compiled: pgbouncer-.tar.gz - let pgbouncer_platform = match target.as_str() { - "aarch64-apple-darwin" => "aarch64-apple-darwin", - "x86_64-apple-darwin" => "x86_64-apple-darwin", - "x86_64-unknown-linux-gnu" => "x86_64-unknown-linux-gnu", - "x86_64-unknown-linux-musl" => "x86_64-unknown-linux-musl", - "aarch64-unknown-linux-gnu" => "aarch64-unknown-linux-gnu", - "aarch64-unknown-linux-musl" => "aarch64-unknown-linux-musl", - "x86_64-pc-windows-msvc" => { - eprintln!("Warning: pgbouncer not available for Windows, skipping bundle"); - let marker = out_dir.join("pgbouncer_bundle.tar.gz"); - fs::write(&marker, b"").expect("Failed to create empty pgbouncer marker"); - println!( - "cargo:rustc-env=PGBOUNCER_BUNDLE_PATH={}", - marker.display() - ); - return; - } - _ => { - eprintln!( - "Warning: Unknown target {}, pgbouncer will not be bundled", - target - ); - let marker = out_dir.join("pgbouncer_bundle.tar.gz"); - fs::write(&marker, b"").expect("Failed to create empty pgbouncer marker"); - println!( - "cargo:rustc-env=PGBOUNCER_BUNDLE_PATH={}", - marker.display() - ); - return; - } - }; - - let filename = format!("pgbouncer-{}.tar.gz", pgbouncer_platform); - let url = format!( - "https://github.com/{}/releases/download/{}/{}", - pgbouncer_repo, pgbouncer_tag, filename - ); - - let bundle_path = out_dir.join(&filename); - - // Download if not already cached - if !bundle_path.exists() { - eprintln!( - "Downloading pgbouncer for {}...", - pgbouncer_platform - ); - match download_file(&url, &bundle_path) { - Ok(_) => eprintln!("Downloaded to {}", bundle_path.display()), - Err(e) => { - eprintln!("Warning: Failed to download pgbouncer: {}. Pooling will not be available.", e); - let marker = out_dir.join("pgbouncer_bundle.tar.gz"); - fs::write(&marker, b"").expect("Failed to create empty pgbouncer marker"); - println!( - "cargo:rustc-env=PGBOUNCER_BUNDLE_PATH={}", - marker.display() - ); - return; - } - } - } else { - eprintln!("Using cached pgbouncer bundle: {}", bundle_path.display()); - } - - println!( - "cargo:rustc-env=PGBOUNCER_BUNDLE_PATH={}", - bundle_path.display() - ); -} - fn download_file(url: &str, dest: &PathBuf) -> io::Result<()> { // Use curl for downloading (available on all CI platforms) let status = std::process::Command::new("curl") From 18913894387b42c7bfcaa70602d1393d045155dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Sat, 31 Jan 2026 17:03:21 +0100 Subject: [PATCH 7/7] feat: add Intel Mac to build-python-wheels workflow Add x86_64-apple-darwin to the build matrix in build-python-wheels.yml to ensure Intel Mac wheels are built in this workflow as well. --- .github/workflows/build-python-wheels.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/build-python-wheels.yml b/.github/workflows/build-python-wheels.yml index 1cd8be6..fa33bf4 100644 --- a/.github/workflows/build-python-wheels.yml +++ b/.github/workflows/build-python-wheels.yml @@ -28,6 +28,12 @@ jobs: wheel_platform: macosx_14_0_arm64 rust_target: aarch64-apple-darwin + # macOS Intel + - os: macos-15-intel + platform: darwin-x86_64 + wheel_platform: macosx_15_0_x86_64 + rust_target: x86_64-apple-darwin + # Linux x86_64 glibc - os: ubuntu-22.04 platform: linux-x86_64-gnu