From 892b370aa81bbbb75365ccbc20cb7438ca10e7ea Mon Sep 17 00:00:00 2001 From: Dave Cramer Date: Thu, 2 Jul 2026 12:56:18 -0400 Subject: [PATCH 1/2] Add Windows build support (MSVC) and fix test portability - Add tests/test_platform.h with cross-platform dlopen/dlsym/dlclose macros mapping to LoadLibrary/GetProcAddress/FreeLibrary on Windows - Fix test files to include windows.h before sql.h (required by MSVC) - Replace bare dlfcn.h includes with test_platform.h - Add dsn_config_read_file() for file-based DSN reading in tests; uses GetPrivateProfileStringA on Windows since SQLGetPrivateProfileString always reads from registry regardless of filename argument - Update CI workflow: install pkgconf via pip, generate native.ini to work around Meson rejecting Strawberry Perl pkg-config on Windows --- .github/workflows/ci.yml | 20 +++++--- src/dsn_config.c | 74 +++++++++++++++++++++++------- src/dsn_config.h | 14 ++++++ tests/test_catalog.c | 5 +- tests/test_column_binding.c | 5 +- tests/test_connection_attributes.c | 5 +- tests/test_connection_lifecycle.c | 5 +- tests/test_driver_load.c | 5 +- tests/test_dsn_config.c | 70 ++++++++++++++++++---------- tests/test_error_mapping.c | 5 +- tests/test_platform.h | 51 ++++++++++++++++++++ tests/test_results.c | 5 +- tests/test_statement_attributes.c | 5 +- tests/test_statement_execution.c | 5 +- tests/test_statement_lifecycle.c | 5 +- 15 files changed, 222 insertions(+), 57 deletions(-) create mode 100644 tests/test_platform.h diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b36565..7797155 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,8 +86,8 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Install meson and ninja - run: python -m pip install meson ninja + - name: Install meson, ninja, and pkgconf + run: python -m pip install meson ninja pkgconf - name: Setup MSVC uses: TheMrMilchmann/setup-msvc-dev@v3 @@ -98,16 +98,22 @@ jobs: shell: powershell run: | choco install postgresql17 --params "/Password:postgres /Port:5432" -y - # Add PostgreSQL bin to PATH for this job + # Add PostgreSQL bin to PATH for DLL loading at runtime echo "C:\Program Files\PostgreSQL\17\bin" | Out-File -FilePath $env:GITHUB_PATH -Append - name: Configure shell: powershell run: | - $env:PKG_CONFIG_PATH = "C:\Program Files\PostgreSQL\17\lib\pkgconfig" - meson setup builddir -Ddriver_manager=none - env: - PKG_CONFIG_PATH: C:\Program Files\PostgreSQL\17\lib\pkgconfig + # Create native file to use pkgconf-pypi (Strawberry Perl's pkg-config is rejected by meson) + @" + [binaries] + pkg-config = 'pkgconf-pypi' + + [built-in options] + pkg_config_path = 'C:/Program Files/PostgreSQL/17/lib/pkgconfig' + "@ | Set-Content native.ini + + meson setup builddir -Ddriver_manager=none --native-file=native.ini - name: Build run: meson compile -C builddir diff --git a/src/dsn_config.c b/src/dsn_config.c index ce78f39..758e7dd 100644 --- a/src/dsn_config.c +++ b/src/dsn_config.c @@ -15,6 +15,10 @@ #include #include +#if defined(_WIN32) || defined(_WIN64) +#include +#endif + #ifdef HAVE_ODBCINST #include @@ -23,22 +27,38 @@ #define INI_VALUE_BUFFER_SIZE 512 /* - * Read a single string value from the DSN section of odbc.ini. + * Read a single string value from the DSN section of the specified INI file. * Returns the number of characters written to the buffer (excluding null * terminator), or 0 if the key does not exist or has an empty value. */ -static int read_ini_string(const char *dsn_name, const char *key, - char *buffer, int buffer_size) +static int read_ini_string_from(const char *dsn_name, const char *key, + char *buffer, int buffer_size, + const char *ini_file) { /* SQLGetPrivateProfileString returns the number of characters written. * If the DSN section or key doesn't exist, it writes the default value - * (empty string here) and returns 0. */ + * (empty string here) and returns 0. + * + * On Windows, SQLGetPrivateProfileString always reads from the registry + * regardless of the filename argument. For file-based reads (used in + * testing), we use GetPrivateProfileStringA directly. */ +#if defined(_WIN32) || defined(_WIN64) + if (ini_file && strcmp(ini_file, ODBC_INI_FILE) != 0) { + /* File-based read — use Win32 API directly */ + return (int)GetPrivateProfileStringA( + dsn_name, key, "", buffer, (DWORD)buffer_size, ini_file); + } +#endif int chars_written = SQLGetPrivateProfileString( - dsn_name, key, "", buffer, buffer_size, ODBC_INI_FILE); + dsn_name, key, "", buffer, buffer_size, ini_file); return chars_written; } -bool dsn_config_read(const char *dsn_name, ConnectionInfo *out_info) +/* + * Internal implementation shared by dsn_config_read and dsn_config_read_file. + */ +static bool dsn_config_read_impl(const char *dsn_name, ConnectionInfo *out_info, + const char *ini_file) { if (!dsn_name || dsn_name[0] == '\0' || !out_info) { return false; @@ -49,36 +69,36 @@ bool dsn_config_read(const char *dsn_name, ConnectionInfo *out_info) /* Read server name — try "Servername" first (psqlodbc convention), * then fall back to "Server" (common alias) */ - if (read_ini_string(dsn_name, DSN_KEY_SERVERNAME, buffer, sizeof(buffer)) > 0) { + if (read_ini_string_from(dsn_name, DSN_KEY_SERVERNAME, buffer, sizeof(buffer), ini_file) > 0) { strncpy(out_info->server, buffer, sizeof(out_info->server) - 1); out_info->server[sizeof(out_info->server) - 1] = '\0'; found_any_value = true; - } else if (read_ini_string(dsn_name, DSN_KEY_SERVER, buffer, sizeof(buffer)) > 0) { + } else if (read_ini_string_from(dsn_name, DSN_KEY_SERVER, buffer, sizeof(buffer), ini_file) > 0) { strncpy(out_info->server, buffer, sizeof(out_info->server) - 1); out_info->server[sizeof(out_info->server) - 1] = '\0'; found_any_value = true; } /* Read port */ - if (read_ini_string(dsn_name, DSN_KEY_PORT, buffer, sizeof(buffer)) > 0) { + if (read_ini_string_from(dsn_name, DSN_KEY_PORT, buffer, sizeof(buffer), ini_file) > 0) { strncpy(out_info->port, buffer, sizeof(out_info->port) - 1); out_info->port[sizeof(out_info->port) - 1] = '\0'; found_any_value = true; } /* Read database name */ - if (read_ini_string(dsn_name, DSN_KEY_DATABASE, buffer, sizeof(buffer)) > 0) { + if (read_ini_string_from(dsn_name, DSN_KEY_DATABASE, buffer, sizeof(buffer), ini_file) > 0) { strncpy(out_info->database, buffer, sizeof(out_info->database) - 1); out_info->database[sizeof(out_info->database) - 1] = '\0'; found_any_value = true; } /* Read username — try "Username" first, then "UID" as alias */ - if (read_ini_string(dsn_name, DSN_KEY_USERNAME, buffer, sizeof(buffer)) > 0) { + if (read_ini_string_from(dsn_name, DSN_KEY_USERNAME, buffer, sizeof(buffer), ini_file) > 0) { strncpy(out_info->username, buffer, sizeof(out_info->username) - 1); out_info->username[sizeof(out_info->username) - 1] = '\0'; found_any_value = true; - } else if (read_ini_string(dsn_name, DSN_KEY_UID, buffer, sizeof(buffer)) > 0) { + } else if (read_ini_string_from(dsn_name, DSN_KEY_UID, buffer, sizeof(buffer), ini_file) > 0) { strncpy(out_info->username, buffer, sizeof(out_info->username) - 1); out_info->username[sizeof(out_info->username) - 1] = '\0'; found_any_value = true; @@ -86,7 +106,7 @@ bool dsn_config_read(const char *dsn_name, ConnectionInfo *out_info) /* Read password — heap-allocated for secure clearing. * Only overwrite if the INI value is non-empty. */ - if (read_ini_string(dsn_name, DSN_KEY_PASSWORD, buffer, sizeof(buffer)) > 0) { + if (read_ini_string_from(dsn_name, DSN_KEY_PASSWORD, buffer, sizeof(buffer), ini_file) > 0) { size_t password_length = strlen(buffer); char *new_password = malloc(password_length + 1); if (new_password) { @@ -101,21 +121,21 @@ bool dsn_config_read(const char *dsn_name, ConnectionInfo *out_info) } /* Read SSL mode */ - if (read_ini_string(dsn_name, DSN_KEY_SSLMODE, buffer, sizeof(buffer)) > 0) { + if (read_ini_string_from(dsn_name, DSN_KEY_SSLMODE, buffer, sizeof(buffer), ini_file) > 0) { strncpy(out_info->sslmode, buffer, sizeof(out_info->sslmode) - 1); out_info->sslmode[sizeof(out_info->sslmode) - 1] = '\0'; found_any_value = true; } /* Read application name */ - if (read_ini_string(dsn_name, DSN_KEY_APP_NAME, buffer, sizeof(buffer)) > 0) { + if (read_ini_string_from(dsn_name, DSN_KEY_APP_NAME, buffer, sizeof(buffer), ini_file) > 0) { strncpy(out_info->application_name, buffer, sizeof(out_info->application_name) - 1); out_info->application_name[sizeof(out_info->application_name) - 1] = '\0'; found_any_value = true; } /* Read connection timeout (stored as a decimal integer string) */ - if (read_ini_string(dsn_name, DSN_KEY_TIMEOUT, buffer, sizeof(buffer)) > 0) { + if (read_ini_string_from(dsn_name, DSN_KEY_TIMEOUT, buffer, sizeof(buffer), ini_file) > 0) { unsigned long timeout_value = strtoul(buffer, NULL, 10); out_info->connect_timeout = (unsigned int)timeout_value; found_any_value = true; @@ -124,6 +144,19 @@ bool dsn_config_read(const char *dsn_name, ConnectionInfo *out_info) return found_any_value; } +bool dsn_config_read(const char *dsn_name, ConnectionInfo *out_info) +{ + return dsn_config_read_impl(dsn_name, out_info, ODBC_INI_FILE); +} + +bool dsn_config_read_file(const char *dsn_name, ConnectionInfo *out_info, + const char *ini_file) +{ + if (!ini_file || ini_file[0] == '\0') + return dsn_config_read(dsn_name, out_info); + return dsn_config_read_impl(dsn_name, out_info, ini_file); +} + #else /* !HAVE_ODBCINST */ /* DSN lookup is not available without libodbcinst. @@ -136,4 +169,13 @@ bool dsn_config_read(const char *dsn_name, ConnectionInfo *out_info) return false; } +bool dsn_config_read_file(const char *dsn_name, ConnectionInfo *out_info, + const char *ini_file) +{ + (void)dsn_name; + (void)out_info; + (void)ini_file; + return false; +} + #endif /* HAVE_ODBCINST */ diff --git a/src/dsn_config.h b/src/dsn_config.h index 468724a..76060d0 100644 --- a/src/dsn_config.h +++ b/src/dsn_config.h @@ -53,4 +53,18 @@ */ bool dsn_config_read(const char *dsn_name, ConnectionInfo *out_info); +/* + * Read connection parameters for the named DSN from a specific INI file. + * + * Same behavior as dsn_config_read, but reads from the specified file path + * instead of the system ODBC configuration. On Windows, a full path causes + * SQLGetPrivateProfileString to read from the file rather than the registry. + * On Unix (unixODBC), the filename is passed directly. + * + * This is primarily useful for testing without modifying the system ODBC + * configuration. + */ +bool dsn_config_read_file(const char *dsn_name, ConnectionInfo *out_info, + const char *ini_file); + #endif /* PSQLODBC2_DSN_CONFIG_H */ diff --git a/tests/test_catalog.c b/tests/test_catalog.c index ff96ba1..d40fb78 100644 --- a/tests/test_catalog.c +++ b/tests/test_catalog.c @@ -12,10 +12,13 @@ *------------------------------------------------------------------------- */ #include -#include +#include "test_platform.h" #include #include #include +#ifdef _WIN32 +#include +#endif #include #include diff --git a/tests/test_column_binding.c b/tests/test_column_binding.c index 45f5a99..f95ff6b 100644 --- a/tests/test_column_binding.c +++ b/tests/test_column_binding.c @@ -12,11 +12,14 @@ *------------------------------------------------------------------------- */ #include -#include +#include "test_platform.h" #include #include #include #include +#ifdef _WIN32 +#include +#endif #include #include diff --git a/tests/test_connection_attributes.c b/tests/test_connection_attributes.c index 73d407d..9bb97e2 100644 --- a/tests/test_connection_attributes.c +++ b/tests/test_connection_attributes.c @@ -15,6 +15,9 @@ #include #include #include +#ifdef _WIN32 +#include +#endif #include #include #include @@ -28,7 +31,7 @@ #define LIB_HANDLE HMODULE #define LIB_ERROR_MSG() "LoadLibrary failed" #else - #include + #include "test_platform.h" #define LOAD_LIBRARY(path) dlopen(path, RTLD_NOW) #define GET_SYMBOL(lib, name) dlsym(lib, name) #define CLOSE_LIBRARY(lib) dlclose(lib) diff --git a/tests/test_connection_lifecycle.c b/tests/test_connection_lifecycle.c index 927b9fa..d56e810 100644 --- a/tests/test_connection_lifecycle.c +++ b/tests/test_connection_lifecycle.c @@ -14,6 +14,9 @@ #include #include #include +#ifdef _WIN32 +#include +#endif #include #include #include @@ -27,7 +30,7 @@ #define LIB_HANDLE HMODULE #define LIB_ERROR_MSG() "LoadLibrary failed" #else - #include + #include "test_platform.h" #define LOAD_LIBRARY(path) dlopen(path, RTLD_NOW) #define GET_SYMBOL(lib, name) dlsym(lib, name) #define CLOSE_LIBRARY(lib) dlclose(lib) diff --git a/tests/test_driver_load.c b/tests/test_driver_load.c index f252bac..e672651 100644 --- a/tests/test_driver_load.c +++ b/tests/test_driver_load.c @@ -13,6 +13,9 @@ */ #include #include +#ifdef _WIN32 +#include +#endif #include #include @@ -25,7 +28,7 @@ #define LIB_HANDLE HMODULE #define LIB_ERROR_MSG() "LoadLibrary failed" #else - #include + #include "test_platform.h" #define LOAD_LIBRARY(path) dlopen(path, RTLD_NOW) #define GET_SYMBOL(lib, name) dlsym(lib, name) #define CLOSE_LIBRARY(lib) dlclose(lib) diff --git a/tests/test_dsn_config.c b/tests/test_dsn_config.c index e0d9c47..ee789e8 100644 --- a/tests/test_dsn_config.c +++ b/tests/test_dsn_config.c @@ -15,6 +15,10 @@ #include #include +#ifdef _WIN32 +#include +#endif + #include "dsn_config.h" /* Exit code 77 tells Meson the test was skipped (not failed) */ @@ -64,8 +68,26 @@ static int tests_passed = 0; tests_passed++; \ } while (0) -/* Path for the temporary odbc.ini used by these tests */ -static const char *TEMP_ODBC_INI = "/tmp/psqlodbc2_test_odbc.ini"; +/* Path for the temporary odbc.ini used by these tests. + * On Windows we use GetTempPath; on Unix we use /tmp. */ +#ifdef _WIN32 +static char temp_odbc_ini[MAX_PATH]; +#else +static char temp_odbc_ini[512]; +#endif + +static void init_temp_path(void) +{ +#ifdef _WIN32 + char tmp[MAX_PATH]; + GetTempPathA(MAX_PATH, tmp); + snprintf(temp_odbc_ini, sizeof(temp_odbc_ini), + "%spsqlodbc2_test_odbc.ini", tmp); +#else + snprintf(temp_odbc_ini, sizeof(temp_odbc_ini), + "/tmp/psqlodbc2_test_odbc.ini"); +#endif +} /* * Write a test odbc.ini file with known DSN entries. @@ -73,9 +95,9 @@ static const char *TEMP_ODBC_INI = "/tmp/psqlodbc2_test_odbc.ini"; */ static bool create_test_ini_file(void) { - FILE *ini_file = fopen(TEMP_ODBC_INI, "w"); + FILE *ini_file = fopen(temp_odbc_ini, "w"); if (!ini_file) { - fprintf(stderr, "Failed to create temp odbc.ini at %s\n", TEMP_ODBC_INI); + fprintf(stderr, "Failed to create temp odbc.ini at %s\n", temp_odbc_ini); return false; } @@ -109,7 +131,7 @@ static bool create_test_ini_file(void) static void cleanup_test_ini_file(void) { - remove(TEMP_ODBC_INI); + remove(temp_odbc_ini); } static int test_read_full_dsn(void) @@ -119,8 +141,8 @@ static int test_read_full_dsn(void) ConnectionInfo info; memset(&info, 0, sizeof(info)); - bool result = dsn_config_read("testdsn", &info); - ASSERT_TRUE(result, "dsn_config_read should return true for existing DSN"); + bool result = dsn_config_read_file("testdsn", &info, temp_odbc_ini); + ASSERT_TRUE(result, "dsn_config_read_file should return true for existing DSN"); ASSERT_STREQ(info.server, "localhost", "Server should be 'localhost'"); ASSERT_STREQ(info.port, "5432", "Port should be '5432'"); @@ -143,8 +165,8 @@ static int test_nonexistent_dsn(void) ConnectionInfo info; memset(&info, 0, sizeof(info)); - bool result = dsn_config_read("nosuchdsn", &info); - ASSERT_FALSE(result, "dsn_config_read should return false for non-existent DSN"); + bool result = dsn_config_read_file("nosuchdsn", &info, temp_odbc_ini); + ASSERT_FALSE(result, "dsn_config_read_file should return false for non-existent DSN"); /* Verify nothing was written */ ASSERT_STREQ(info.server, "", "Server should remain empty"); @@ -161,8 +183,8 @@ static int test_partial_dsn(void) ConnectionInfo info; memset(&info, 0, sizeof(info)); - bool result = dsn_config_read("partialdsn", &info); - ASSERT_TRUE(result, "dsn_config_read should return true for partial DSN"); + bool result = dsn_config_read_file("partialdsn", &info, temp_odbc_ini); + ASSERT_TRUE(result, "dsn_config_read_file should return true for partial DSN"); ASSERT_STREQ(info.server, "dbhost.example.com", "Server should be 'dbhost.example.com'"); ASSERT_STREQ(info.database, "proddb", "Database should be 'proddb'"); @@ -182,8 +204,8 @@ static int test_uid_alias(void) ConnectionInfo info; memset(&info, 0, sizeof(info)); - bool result = dsn_config_read("uiddsn", &info); - ASSERT_TRUE(result, "dsn_config_read should return true for UID alias DSN"); + bool result = dsn_config_read_file("uiddsn", &info, temp_odbc_ini); + ASSERT_TRUE(result, "dsn_config_read_file should return true for UID alias DSN"); ASSERT_STREQ(info.server, "uidhost", "Server should be 'uidhost'"); ASSERT_STREQ(info.username, "uiduser", "Username from UID key should be 'uiduser'"); @@ -204,8 +226,8 @@ static int test_existing_values_not_overwritten_by_empty(void) /* partialdsn only has Server and Database — it should NOT overwrite * port or username since those keys are empty in the DSN. */ - bool result = dsn_config_read("partialdsn", &info); - ASSERT_TRUE(result, "dsn_config_read should return true"); + bool result = dsn_config_read_file("partialdsn", &info, temp_odbc_ini); + ASSERT_TRUE(result, "dsn_config_read_file should return true"); ASSERT_STREQ(info.server, "dbhost.example.com", "Server should be updated from DSN"); ASSERT_STREQ(info.database, "proddb", "Database should be updated from DSN"); @@ -222,9 +244,12 @@ static int test_null_and_empty_dsn_name(void) ConnectionInfo info; memset(&info, 0, sizeof(info)); - ASSERT_FALSE(dsn_config_read(NULL, &info), "NULL dsn_name should return false"); - ASSERT_FALSE(dsn_config_read("", &info), "Empty dsn_name should return false"); - ASSERT_FALSE(dsn_config_read("testdsn", NULL), "NULL out_info should return false"); + ASSERT_FALSE(dsn_config_read_file(NULL, &info, temp_odbc_ini), + "NULL dsn_name should return false"); + ASSERT_FALSE(dsn_config_read_file("", &info, temp_odbc_ini), + "Empty dsn_name should return false"); + ASSERT_FALSE(dsn_config_read_file("testdsn", NULL, temp_odbc_ini), + "NULL out_info should return false"); return TEST_PASS; } @@ -238,16 +263,14 @@ int main(void) #else printf("Running DSN config tests...\n"); - /* Set up: create temp odbc.ini and point ODBCINI to it */ + init_temp_path(); + + /* Set up: create temp odbc.ini */ if (!create_test_ini_file()) { fprintf(stderr, "Failed to create test INI file\n"); return TEST_FAIL; } - /* ODBCINI environment variable tells unixODBC where to find the user's - * odbc.ini file. We override it to use our test file. */ - setenv("ODBCINI", TEMP_ODBC_INI, 1); - int result = TEST_PASS; if (test_read_full_dsn() != TEST_PASS) result = TEST_FAIL; @@ -259,7 +282,6 @@ int main(void) /* Clean up */ cleanup_test_ini_file(); - unsetenv("ODBCINI"); printf("\n Results: %d/%d tests passed\n", tests_passed, tests_run); return result; diff --git a/tests/test_error_mapping.c b/tests/test_error_mapping.c index cf9c795..4540c16 100644 --- a/tests/test_error_mapping.c +++ b/tests/test_error_mapping.c @@ -12,10 +12,13 @@ *------------------------------------------------------------------------- */ #include -#include +#include "test_platform.h" #include #include #include +#ifdef _WIN32 +#include +#endif #include #include diff --git a/tests/test_platform.h b/tests/test_platform.h new file mode 100644 index 0000000..d1fa455 --- /dev/null +++ b/tests/test_platform.h @@ -0,0 +1,51 @@ +/* + * test_platform.h — cross-platform dynamic loading for tests + * + * On Windows, provides dlopen/dlsym/dlclose/dlerror macros that map to + * LoadLibraryA/GetProcAddress/FreeLibrary. On Unix, just includes dlfcn.h. + * + * All handles are void* for source compatibility with existing tests. + */ +#ifndef TEST_PLATFORM_H +#define TEST_PLATFORM_H + +#if defined(_WIN32) || defined(_WIN64) + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +/* RTLD_NOW is not needed on Windows but used as dlopen flag in source */ +#ifndef RTLD_NOW +#define RTLD_NOW 0 +#endif + +static inline void *platform_dlopen(const char *path, int flags) { + (void)flags; + return (void *)LoadLibraryA(path); +} + +static inline void *platform_dlsym(void *lib, const char *name) { + return (void *)(intptr_t)GetProcAddress((HMODULE)lib, name); +} + +static inline int platform_dlclose(void *lib) { + FreeLibrary((HMODULE)lib); + return 0; +} + +static inline const char *platform_dlerror(void) { + return "LoadLibrary/GetProcAddress failed"; +} + +#define dlopen(path, flags) platform_dlopen(path, flags) +#define dlsym(lib, name) platform_dlsym(lib, name) +#define dlclose(lib) platform_dlclose(lib) +#define dlerror() platform_dlerror() + +#else +#include +#endif + +#endif /* TEST_PLATFORM_H */ diff --git a/tests/test_results.c b/tests/test_results.c index 9ea764e..2f30f5a 100644 --- a/tests/test_results.c +++ b/tests/test_results.c @@ -12,11 +12,14 @@ *------------------------------------------------------------------------- */ #include -#include +#include "test_platform.h" #include #include #include #include +#ifdef _WIN32 +#include +#endif #include #include diff --git a/tests/test_statement_attributes.c b/tests/test_statement_attributes.c index 5019ebc..0f1bed2 100644 --- a/tests/test_statement_attributes.c +++ b/tests/test_statement_attributes.c @@ -15,6 +15,9 @@ #include #include #include +#ifdef _WIN32 +#include +#endif #include #include #include @@ -28,7 +31,7 @@ #define LIB_HANDLE HMODULE #define LIB_ERROR_MSG() "LoadLibrary failed" #else - #include + #include "test_platform.h" #define LOAD_LIBRARY(path) dlopen(path, RTLD_NOW) #define GET_SYMBOL(lib, name) dlsym(lib, name) #define CLOSE_LIBRARY(lib) dlclose(lib) diff --git a/tests/test_statement_execution.c b/tests/test_statement_execution.c index 9bb1096..69aaa71 100644 --- a/tests/test_statement_execution.c +++ b/tests/test_statement_execution.c @@ -12,10 +12,13 @@ *------------------------------------------------------------------------- */ #include -#include +#include "test_platform.h" #include #include #include +#ifdef _WIN32 +#include +#endif #include #include diff --git a/tests/test_statement_lifecycle.c b/tests/test_statement_lifecycle.c index 66c99ef..0518246 100644 --- a/tests/test_statement_lifecycle.c +++ b/tests/test_statement_lifecycle.c @@ -12,8 +12,11 @@ *------------------------------------------------------------------------- */ #include -#include +#include "test_platform.h" #include +#ifdef _WIN32 +#include +#endif #include #include From 46c716567d625a283a14a755ef27cd9a0dd48be0 Mon Sep 17 00:00:00 2001 From: Dave Cramer Date: Thu, 2 Jul 2026 13:14:37 -0400 Subject: [PATCH 2/2] CI: fix Windows build - rewrite libpq.pc prefix, remove Strawberry from PATH The choco PostgreSQL package ships a libpq.pc with the build machine's prefix baked in (D:/a/postgresql-packaging-foundation/...). Rewrite it to point to the actual install location. Also remove Strawberry Perl from PATH since it injects ccache (which wraps cl.exe) and a broken pkg-config. --- .github/workflows/ci.yml | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7797155..4c6ab6e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,6 +94,13 @@ jobs: with: arch: x64 + - name: Remove Strawberry Perl from PATH + shell: powershell + run: | + # Strawberry Perl injects ccache and a broken pkg-config into PATH + $cleanPath = ($env:PATH -split ';' | Where-Object { $_ -notlike '*Strawberry*' }) -join ';' + echo "PATH=$cleanPath" | Out-File -FilePath $env:GITHUB_ENV -Append + - name: Install PostgreSQL shell: powershell run: | @@ -101,10 +108,31 @@ jobs: # Add PostgreSQL bin to PATH for DLL loading at runtime echo "C:\Program Files\PostgreSQL\17\bin" | Out-File -FilePath $env:GITHUB_PATH -Append + - name: Fix libpq.pc prefix + shell: powershell + run: | + # The choco-installed PostgreSQL ships a libpq.pc with the build machine's + # prefix path baked in. Rewrite it to point to the actual install location. + $pgDir = "C:/Program Files/PostgreSQL/17" + $pcDir = "$pgDir/lib/pkgconfig" + if (Test-Path "$pcDir/libpq.pc") { + @" + prefix=$pgDir + includedir=`${prefix}/include + libdir=`${prefix}/lib + + Name: libpq + Description: PostgreSQL libpq library + Version: 17 + Libs: -L`${libdir} -lpq + Cflags: -I`${includedir} + "@ | Set-Content "$pcDir/libpq.pc" + } + - name: Configure shell: powershell run: | - # Create native file to use pkgconf-pypi (Strawberry Perl's pkg-config is rejected by meson) + # Create native file to use pkgconf-pypi (avoids broken pkg-config) @" [binaries] pkg-config = 'pkgconf-pypi'