diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 59220319..1dd3bba2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -99,6 +99,20 @@ jobs: echo "CPATH=$(brew --prefix libpq)/include:$CPATH" >> "$GITHUB_ENV" echo "PKG_CONFIG_PATH=$(brew --prefix libpq)/lib/pkgconfig:$PKG_CONFIG_PATH" >> "$GITHUB_ENV" + - name: Set up libpq (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + $pgDir = Get-ChildItem "C:\Program Files\PostgreSQL" -Directory | + Sort-Object Name -Descending | + Select-Object -First 1 + if (-not $pgDir) { throw "PostgreSQL installation not found" } + $pg = $pgDir.FullName + "PQ_LIB_DIR=$pg\lib" >> $env:GITHUB_ENV + "LIB=$pg\lib;$env:LIB" >> $env:GITHUB_ENV + "INCLUDE=$pg\include;$env:INCLUDE" >> $env:GITHUB_ENV + "PATH=$pg\bin;$env:PATH" >> $env:GITHUB_ENV + - name: Build run: cargo build --workspace --release --verbose diff --git a/crates/gateway/Cargo.toml b/crates/gateway/Cargo.toml index 6fda9255..ddbf28f2 100644 --- a/crates/gateway/Cargo.toml +++ b/crates/gateway/Cargo.toml @@ -30,8 +30,6 @@ rand = "0.8.5" base64 = "0.21" uuid.workspace = true hyper = "1.4.1" - -[target.'cfg(not(target_os = "windows"))'.dependencies] deadpool-diesel = { version = "0.6.1", features = ["postgres"] } diesel = { version = "2.2.3", features = ["chrono", "postgres"] } diesel_migrations = "2" diff --git a/crates/gateway/src/db.rs b/crates/gateway/src/db.rs index c0d96e92..decfba75 100644 --- a/crates/gateway/src/db.rs +++ b/crates/gateway/src/db.rs @@ -1,31 +1,20 @@ use crate::errors::APIError; -use crate::models::{Request, RequestNewItem, User}; - -#[cfg(not(target_os = "windows"))] -use crate::schema; -#[cfg(not(target_os = "windows"))] +use crate::{ + models::{Request, RequestNewItem, User}, + schema, +}; use deadpool_diesel::postgres::{Manager, Pool}; -#[cfg(not(target_os = "windows"))] use diesel::prelude::*; -#[cfg(not(target_os = "windows"))] use diesel_migrations::{EmbeddedMigrations, MigrationHarness, embed_migrations}; -#[cfg(not(target_os = "windows"))] use schema::users::dsl::*; -#[cfg(not(target_os = "windows"))] pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations/"); -#[cfg(not(target_os = "windows"))] #[derive(Clone)] pub struct DB { pool: Pool, } -#[cfg(target_os = "windows")] -#[derive(Clone)] -pub struct DB; - -#[cfg(not(target_os = "windows"))] impl DB { pub async fn new(database_url: &str) -> Self { let manager = Manager::new(database_url, deadpool_diesel::Runtime::Tokio1); @@ -77,18 +66,3 @@ impl DB { } } } - -#[cfg(target_os = "windows")] -impl DB { - pub async fn new(_database_url: &str) -> Self { - unimplemented!("Postgres-backed DB is not available for Windows targets"); - } - - pub async fn insert_request(&self, _request: RequestNewItem) -> Result { - unimplemented!("Postgres-backed DB is not available for Windows targets"); - } - - pub async fn authorize_user(&self, _secret_param: String) -> Result { - unimplemented!("Postgres-backed DB is not available for Windows targets"); - } -} diff --git a/crates/gateway/src/errors.rs b/crates/gateway/src/errors.rs index 512dc979..a347d80b 100644 --- a/crates/gateway/src/errors.rs +++ b/crates/gateway/src/errors.rs @@ -27,15 +27,12 @@ pub enum APIError { #[error("Unauthorized registration access")] Unauthorized(), - #[cfg(not(target_os = "windows"))] #[error("Database connection error: {0}")] DatabaseConnection(#[from] deadpool_diesel::PoolError), - #[cfg(not(target_os = "windows"))] #[error("Database interaction error: {0}")] DatabaseInteraction(#[from] deadpool_diesel::InteractError), - #[cfg(not(target_os = "windows"))] #[error("Database query error: {0}")] DatabaseQuery(#[from] diesel::result::Error), } @@ -81,7 +78,6 @@ impl IntoResponse for APIError { details: "You are not authorized to access the registration.".to_string(), }, ), - #[cfg(not(target_os = "windows"))] APIError::DatabaseConnection(_) | APIError::DatabaseQuery(_) | APIError::DatabaseInteraction(_) => ( diff --git a/crates/gateway/src/lib.rs b/crates/gateway/src/lib.rs index 062076df..cefc66fe 100644 --- a/crates/gateway/src/lib.rs +++ b/crates/gateway/src/lib.rs @@ -3,5 +3,4 @@ pub mod errors; pub mod load_balancer; pub mod models; pub mod payload; -#[cfg(not(target_os = "windows"))] pub mod schema; diff --git a/crates/gateway/src/main.rs b/crates/gateway/src/main.rs index 3a789d38..cc9d954a 100644 --- a/crates/gateway/src/main.rs +++ b/crates/gateway/src/main.rs @@ -6,7 +6,6 @@ mod errors; mod load_balancer; mod models; mod payload; -#[cfg(not(target_os = "windows"))] mod schema; use api::{register, root}; diff --git a/crates/gateway/src/models.rs b/crates/gateway/src/models.rs index 0614f602..b2c405f9 100644 --- a/crates/gateway/src/models.rs +++ b/crates/gateway/src/models.rs @@ -1,13 +1,10 @@ use chrono::NaiveDateTime; -use serde::{Deserialize, Serialize}; - -#[cfg(not(target_os = "windows"))] use diesel::prelude::*; +use serde::{Deserialize, Serialize}; -#[derive(Deserialize, Serialize, Debug)] -#[cfg_attr(not(target_os = "windows"), derive(Queryable, Selectable, Insertable))] -#[cfg_attr(not(target_os = "windows"), diesel(table_name = crate::schema::requests))] -#[cfg_attr(not(target_os = "windows"), diesel(check_for_backend(diesel::pg::Pg)))] +#[derive(Queryable, Selectable, Insertable, Deserialize, Serialize, Debug)] +#[diesel(table_name = crate::schema::requests)] +#[diesel(check_for_backend(diesel::pg::Pg))] pub struct Request { pub id: i32, pub route: String, @@ -17,10 +14,9 @@ pub struct Request { pub reward_address: String, } -#[derive(Deserialize, Serialize, Debug)] -#[cfg_attr(not(target_os = "windows"), derive(Selectable, Insertable))] -#[cfg_attr(not(target_os = "windows"), diesel(table_name = crate::schema::requests))] -#[cfg_attr(not(target_os = "windows"), diesel(check_for_backend(diesel::pg::Pg)))] +#[derive(Selectable, Insertable, Deserialize, Serialize, Debug)] +#[diesel(table_name = crate::schema::requests)] +#[diesel(check_for_backend(diesel::pg::Pg))] pub struct RequestNewItem { pub route: String, pub user_id: i32, @@ -31,10 +27,9 @@ pub struct RequestNewItem { pub asset_name: Option, } -#[derive(Deserialize, Serialize, Debug)] -#[cfg_attr(not(target_os = "windows"), derive(Selectable, Insertable, Queryable))] -#[cfg_attr(not(target_os = "windows"), diesel(table_name = crate::schema::users))] -#[cfg_attr(not(target_os = "windows"), diesel(check_for_backend(diesel::pg::Pg)))] +#[derive(Selectable, Insertable, Queryable, Deserialize, Serialize, Debug)] +#[diesel(table_name = crate::schema::users)] +#[diesel(check_for_backend(diesel::pg::Pg))] pub struct User { pub id: i32, pub created_at: NaiveDateTime, diff --git a/flake.nix b/flake.nix index 7ff9fd87..9385f313 100644 --- a/flake.nix +++ b/flake.nix @@ -96,6 +96,7 @@ } // (lib.optionalAttrs (system == "x86_64-linux") { blockfrost-platform-x86_64-windows = inputs.self.internal.x86_64-windows.blockfrost-platform; + blockfrost-gateway-x86_64-windows = inputs.self.internal.x86_64-windows.blockfrost-gateway; }); devshells.default = import ./nix/devshells.nix {inherit inputs;}; @@ -175,7 +176,7 @@ blockfrost-platform = lib.genAttrs (config.systems ++ crossSystems) ( targetSystem: inputs.self.internal.${targetSystem}.blockfrost-platform ); - blockfrost-gateway = lib.genAttrs config.systems ( + blockfrost-gateway = lib.genAttrs (config.systems ++ crossSystems) ( targetSystem: inputs.self.internal.${targetSystem}.blockfrost-gateway ); devshell = lib.genAttrs config.systems ( diff --git a/nix/internal/windows-libpq.nix b/nix/internal/windows-libpq.nix new file mode 100644 index 00000000..3f24621c --- /dev/null +++ b/nix/internal/windows-libpq.nix @@ -0,0 +1,137 @@ +# Cross-compile just libpq (PostgreSQL client library) for Windows using MinGW. +# +# We can't use `pkgsCross.mingwW64.postgresql` because the full PostgreSQL +# package is broken for Windows in Nixpkgs. Instead, we build only the client +# library (libpq) from source using Meson cross-compilation. +# +# libpq depends on internal PostgreSQL libraries (pgcommon_shlib, pgport_shlib), +# so we build those too and merge everything into a single static archive. +{ + pkgs, + pkgsCross, +}: let + inherit (pkgs) lib; + + postgresqlSrc = pkgs.postgresql.src; + + crossPrefix = pkgsCross.stdenv.cc.targetPrefix; + crossAr = "${pkgsCross.stdenv.cc}/bin/${crossPrefix}ar"; + + crossFile = pkgs.writeText "cross-file.txt" '' + [binaries] + c = '${crossPrefix}cc' + cpp = '${crossPrefix}c++' + ar = '${crossPrefix}ar' + strip = '${crossPrefix}strip' + windres = '${crossPrefix}windres' + pkgconfig = 'pkg-config' + + [properties] + sys_root = '${pkgsCross.stdenv.cc}/${crossPrefix}' + + [host_machine] + system = 'windows' + cpu_family = 'x86_64' + cpu = 'x86_64' + endian = 'little' + ''; +in + pkgs.stdenv.mkDerivation { + pname = "libpq-windows"; + inherit (pkgs.postgresql) version; + + src = postgresqlSrc; + + nativeBuildInputs = [ + pkgs.meson + pkgs.ninja + pkgs.pkg-config + pkgs.bison + pkgs.flex + pkgs.perl + pkgsCross.stdenv.cc + ]; + + buildInputs = [ + pkgsCross.windows.pthreads + ]; + + # We use Meson's cross-compilation support. We only build the `libpq` + # sub-target so we don't need to deal with the full PostgreSQL build. + configurePhase = '' + runHook preConfigure + meson setup build \ + --cross-file ${crossFile} \ + -Dprefix=$out \ + -Ddefault_library=both \ + -Dnls=disabled \ + -Dreadline=disabled \ + -Dzlib=disabled \ + -Dssl=none \ + -Dgssapi=disabled \ + -Dldap=disabled \ + -Dpam=disabled \ + -Dselinux=disabled \ + -Dsystemd=disabled \ + -Duuid=none \ + -Dicu=disabled \ + -Dlz4=disabled \ + -Dzstd=disabled \ + -Dplperl=disabled \ + -Dplpython=disabled \ + -Dpltcl=disabled \ + -Dllvm=disabled \ + -Dlibxml=disabled \ + -Dlibxslt=disabled + runHook postConfigure + ''; + + buildPhase = '' + runHook preBuild + # Build libpq and its internal dependencies (pgcommon, pgport): + ninja -C build \ + src/interfaces/libpq/libpq.a \ + src/common/libpgcommon.a \ + src/common/libpgcommon_shlib.a \ + src/port/libpgport.a \ + src/port/libpgport_shlib.a + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + mkdir -p $out/lib $out/include + + # Merge all static archives into a single libpq.a so that the linker + # can find all symbols (pgcommon, pgport) that libpq needs: + tmpdir=$(mktemp -d) + for archive in \ + build/src/interfaces/libpq/libpq.a \ + build/src/common/libpgcommon.a \ + build/src/common/libpgcommon_shlib.a \ + build/src/port/libpgport.a \ + build/src/port/libpgport_shlib.a \ + ; do + if [ -f "$archive" ]; then + (cd "$tmpdir" && ${crossAr} x "$OLDPWD/$archive") + fi + done + ${crossAr} rcs $out/lib/libpq.a "$tmpdir"/*.obj "$tmpdir"/*.o 2>/dev/null \ + || ${crossAr} rcs $out/lib/libpq.a "$tmpdir"/* + + # Install headers: + cp src/include/postgres_ext.h $out/include/ + cp src/include/pg_config_ext.h $out/include/ 2>/dev/null || true + cp src/interfaces/libpq/libpq-fe.h $out/include/ + cp src/interfaces/libpq/libpq-events.h $out/include/ + cp build/src/include/pg_config.h $out/include/ 2>/dev/null || true + cp build/src/include/pg_config_ext.h $out/include/ 2>/dev/null || true + + runHook postInstall + ''; + + meta = { + description = "PostgreSQL client library (libpq) cross-compiled for Windows"; + license = lib.licenses.postgresql; + }; + } diff --git a/nix/internal/windows.nix b/nix/internal/windows.nix index 1ab6af3b..a43360ca 100644 --- a/nix/internal/windows.nix +++ b/nix/internal/windows.nix @@ -27,6 +27,9 @@ in rec { pkgsCross = pkgs.pkgsCross.mingwW64; + # Cross-compile libpq for Windows (pkgsCross.postgresql is broken in Nixpkgs): + libpq-windows = import ./windows-libpq.nix {inherit pkgs pkgsCross;}; + packageName = craneLib.crateNameFromCargoToml {cargoToml = src + "/crates/platform/Cargo.toml";}; commonArgs = { @@ -43,8 +46,9 @@ in rec { OPENSSL_LIB_DIR = "${pkgs.openssl.out}/lib"; OPENSSL_INCLUDE_DIR = "${pkgs.openssl.dev}/include/"; - # Unfortunately, `pkgsCross.postgresql` is broken on Windows, so making the - # `blockfrost-gateway` work there will be much more tinkering. + PQ_LIB_DIR = "${libpq-windows}/lib"; + PQ_LIB_STATIC = "1"; + depsBuildBuild = [ pkgsCross.stdenv.cc pkgsCross.windows.pthreads @@ -81,8 +85,7 @@ in rec { done find -name 'build.rs' -delete ''; - } - // (builtins.listToAttrs inputs.self.internal.x86_64-linux.hydraScriptsEnvVars)); + }); testgen-hs = let inherit (inputs.self.internal.x86_64-linux.testgen-hs) version;