From b1ab47cd36cd6c51f0d5127d45ef4124d43c493c Mon Sep 17 00:00:00 2001 From: Jahnvi Thakkar Date: Thu, 30 Jul 2026 11:59:37 +0530 Subject: [PATCH 01/11] REFACTOR: Remove bundled ODBC libs from mssql-python; require mssql-python-odbc Phase 2 of the ODBC package split. Move the driver binaries from mssql_python/libs to mssql_python_odbc/libs so only the mssql-python-odbc distribution ships them. Remove the C++ bundled-libs fallback in GetOdbcLibsBaseDir(): a missing or incomplete mssql_python_odbc now raises a clear error instead of silently using bundled libs. Drop libs packaging from setup.py, retire sync_libs() in setup_odbc.py, and repoint build.bat vcredist source. AB#46586 --- .gitignore | 9 ++- mssql_python/pybind/build.bat | 6 +- mssql_python/pybind/ddbc_bindings.cpp | 61 +++++++++--------- .../libs/LICENSING | 0 ...OFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt | 0 .../alpine/arm64/lib/libmsodbcsql-18.6.so.2.1 | Bin .../linux/alpine/arm64/lib/libodbcinst.so.2 | Bin .../share/resources/en_US/msodbcsqlr18.rll | Bin ...OFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt | 0 .../x86_64/lib/libmsodbcsql-18.6.so.2.1 | Bin .../linux/alpine/x86_64/lib/libodbcinst.so.2 | Bin .../share/resources/en_US/msodbcsqlr18.rll | Bin ...OFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt | 0 .../arm64/lib/libmsodbcsql-18.6.so.2.1 | Bin .../debian_ubuntu/arm64/lib/libodbcinst.so.2 | Bin .../share/resources/en_US/msodbcsqlr18.rll | Bin ...OFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt | 0 .../x86_64/lib/libmsodbcsql-18.6.so.2.1 | Bin .../debian_ubuntu/x86_64/lib/libodbcinst.so.2 | Bin .../share/resources/en_US/msodbcsqlr18.rll | Bin ...OFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt | 0 .../rhel/arm64/lib/libmsodbcsql-18.6.so.2.1 | Bin .../linux/rhel/arm64/lib/libodbcinst.so.2 | Bin .../share/resources/en_US/msodbcsqlr18.rll | Bin ...OFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt | 0 .../rhel/x86_64/lib/libmsodbcsql-18.6.so.2.1 | Bin .../linux/rhel/x86_64/lib/libodbcinst.so.2 | Bin .../share/resources/en_US/msodbcsqlr18.rll | Bin ...OFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt | 0 .../suse/x86_64/lib/libmsodbcsql-18.6.so.2.1 | Bin .../linux/suse/x86_64/lib/libodbcinst.so.2 | Bin .../share/resources/en_US/msodbcsqlr18.rll | Bin ...OFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt | 0 .../libs/macos/arm64/lib/libltdl.7.dylib | Bin .../macos/arm64/lib/libmsodbcsql.18.dylib | Bin .../libs/macos/arm64/lib/libodbc.2.dylib | Bin .../libs/macos/arm64/lib/libodbcinst.2.dylib | Bin .../arm64/share/doc/msodbcsql18/LICENSE.txt | 0 .../arm64/share/doc/msodbcsql18/RELEASE_NOTES | 0 .../resources/en_US/msodbcsqlr18.rll | Bin ...OFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt | 0 .../libs/macos/x86_64/lib/libltdl.7.dylib | Bin .../macos/x86_64/lib/libmsodbcsql.18.dylib | Bin .../libs/macos/x86_64/lib/libodbc.2.dylib | Bin .../libs/macos/x86_64/lib/libodbcinst.2.dylib | Bin .../x86_64/share/doc/msodbcsql18/LICENSE.txt | 0 .../share/doc/msodbcsql18/RELEASE_NOTES | 0 .../resources/en_US/msodbcsqlr18.rll | Bin .../libs/windows/arm64/1033/msodbcsqlr18.rll | Bin ...OFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt | 0 .../libs/windows/arm64/msodbcdiag18.dll | Bin .../libs/windows/arm64/msodbcsql18.dll | Bin .../libs/windows/arm64/mssql-auth.dll | Bin .../MICROSOFT_VISUAL_STUDIO_LICENSE.txt | 0 .../libs/windows/arm64/vcredist/msvcp140.dll | Bin .../libs/windows/x64/1033/msodbcsqlr18.rll | Bin ...OFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt | 0 .../x64/MICROSOFT_VISUAL_STUDIO_LICENSE.txt | 0 .../libs/windows/x64/msodbcdiag18.dll | Bin .../libs/windows/x64/msodbcsql18.dll | Bin .../libs/windows/x64/mssql-auth.dll | Bin .../MICROSOFT_VISUAL_STUDIO_LICENSE.txt | 0 .../libs/windows/x64/vcredist/msvcp140.dll | Bin .../libs/windows/x86/1033/msodbcsqlr18.rll | Bin ...OFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt | 0 .../libs/windows/x86/msodbcdiag18.dll | Bin .../libs/windows/x86/msodbcsql18.dll | Bin .../libs/windows/x86/mssql-auth.dll | Bin .../MICROSOFT_VISUAL_STUDIO_LICENSE.txt | 0 .../libs/windows/x86/vcredist/msvcp140.dll | Bin setup.py | 31 +-------- setup_odbc.py | 15 ++--- 72 files changed, 47 insertions(+), 75 deletions(-) rename {mssql_python => mssql_python_odbc}/libs/LICENSING (100%) rename {mssql_python => mssql_python_odbc}/libs/linux/alpine/arm64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt (100%) rename {mssql_python => mssql_python_odbc}/libs/linux/alpine/arm64/lib/libmsodbcsql-18.6.so.2.1 (100%) rename {mssql_python => mssql_python_odbc}/libs/linux/alpine/arm64/lib/libodbcinst.so.2 (100%) rename {mssql_python => mssql_python_odbc}/libs/linux/alpine/arm64/share/resources/en_US/msodbcsqlr18.rll (100%) rename {mssql_python => mssql_python_odbc}/libs/linux/alpine/x86_64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt (100%) rename {mssql_python => mssql_python_odbc}/libs/linux/alpine/x86_64/lib/libmsodbcsql-18.6.so.2.1 (100%) rename {mssql_python => mssql_python_odbc}/libs/linux/alpine/x86_64/lib/libodbcinst.so.2 (100%) rename {mssql_python => mssql_python_odbc}/libs/linux/alpine/x86_64/share/resources/en_US/msodbcsqlr18.rll (100%) rename {mssql_python => mssql_python_odbc}/libs/linux/debian_ubuntu/arm64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt (100%) rename {mssql_python => mssql_python_odbc}/libs/linux/debian_ubuntu/arm64/lib/libmsodbcsql-18.6.so.2.1 (100%) rename {mssql_python => mssql_python_odbc}/libs/linux/debian_ubuntu/arm64/lib/libodbcinst.so.2 (100%) rename {mssql_python => mssql_python_odbc}/libs/linux/debian_ubuntu/arm64/share/resources/en_US/msodbcsqlr18.rll (100%) rename {mssql_python => mssql_python_odbc}/libs/linux/debian_ubuntu/x86_64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt (100%) rename {mssql_python => mssql_python_odbc}/libs/linux/debian_ubuntu/x86_64/lib/libmsodbcsql-18.6.so.2.1 (100%) rename {mssql_python => mssql_python_odbc}/libs/linux/debian_ubuntu/x86_64/lib/libodbcinst.so.2 (100%) rename {mssql_python => mssql_python_odbc}/libs/linux/debian_ubuntu/x86_64/share/resources/en_US/msodbcsqlr18.rll (100%) rename {mssql_python => mssql_python_odbc}/libs/linux/rhel/arm64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt (100%) rename {mssql_python => mssql_python_odbc}/libs/linux/rhel/arm64/lib/libmsodbcsql-18.6.so.2.1 (100%) rename {mssql_python => mssql_python_odbc}/libs/linux/rhel/arm64/lib/libodbcinst.so.2 (100%) rename {mssql_python => mssql_python_odbc}/libs/linux/rhel/arm64/share/resources/en_US/msodbcsqlr18.rll (100%) rename {mssql_python => mssql_python_odbc}/libs/linux/rhel/x86_64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt (100%) rename {mssql_python => mssql_python_odbc}/libs/linux/rhel/x86_64/lib/libmsodbcsql-18.6.so.2.1 (100%) rename {mssql_python => mssql_python_odbc}/libs/linux/rhel/x86_64/lib/libodbcinst.so.2 (100%) rename {mssql_python => mssql_python_odbc}/libs/linux/rhel/x86_64/share/resources/en_US/msodbcsqlr18.rll (100%) rename {mssql_python => mssql_python_odbc}/libs/linux/suse/x86_64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt (100%) rename {mssql_python => mssql_python_odbc}/libs/linux/suse/x86_64/lib/libmsodbcsql-18.6.so.2.1 (100%) rename {mssql_python => mssql_python_odbc}/libs/linux/suse/x86_64/lib/libodbcinst.so.2 (100%) rename {mssql_python => mssql_python_odbc}/libs/linux/suse/x86_64/share/resources/en_US/msodbcsqlr18.rll (100%) rename {mssql_python => mssql_python_odbc}/libs/macos/arm64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt (100%) rename {mssql_python => mssql_python_odbc}/libs/macos/arm64/lib/libltdl.7.dylib (100%) rename {mssql_python => mssql_python_odbc}/libs/macos/arm64/lib/libmsodbcsql.18.dylib (100%) rename {mssql_python => mssql_python_odbc}/libs/macos/arm64/lib/libodbc.2.dylib (100%) rename {mssql_python => mssql_python_odbc}/libs/macos/arm64/lib/libodbcinst.2.dylib (100%) rename {mssql_python => mssql_python_odbc}/libs/macos/arm64/share/doc/msodbcsql18/LICENSE.txt (100%) rename {mssql_python => mssql_python_odbc}/libs/macos/arm64/share/doc/msodbcsql18/RELEASE_NOTES (100%) rename {mssql_python => mssql_python_odbc}/libs/macos/arm64/share/msodbcsql18/resources/en_US/msodbcsqlr18.rll (100%) rename {mssql_python => mssql_python_odbc}/libs/macos/x86_64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt (100%) rename {mssql_python => mssql_python_odbc}/libs/macos/x86_64/lib/libltdl.7.dylib (100%) rename {mssql_python => mssql_python_odbc}/libs/macos/x86_64/lib/libmsodbcsql.18.dylib (100%) rename {mssql_python => mssql_python_odbc}/libs/macos/x86_64/lib/libodbc.2.dylib (100%) rename {mssql_python => mssql_python_odbc}/libs/macos/x86_64/lib/libodbcinst.2.dylib (100%) rename {mssql_python => mssql_python_odbc}/libs/macos/x86_64/share/doc/msodbcsql18/LICENSE.txt (100%) rename {mssql_python => mssql_python_odbc}/libs/macos/x86_64/share/doc/msodbcsql18/RELEASE_NOTES (100%) rename {mssql_python => mssql_python_odbc}/libs/macos/x86_64/share/msodbcsql18/resources/en_US/msodbcsqlr18.rll (100%) rename {mssql_python => mssql_python_odbc}/libs/windows/arm64/1033/msodbcsqlr18.rll (100%) rename {mssql_python => mssql_python_odbc}/libs/windows/arm64/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt (100%) rename {mssql_python => mssql_python_odbc}/libs/windows/arm64/msodbcdiag18.dll (100%) rename {mssql_python => mssql_python_odbc}/libs/windows/arm64/msodbcsql18.dll (100%) rename {mssql_python => mssql_python_odbc}/libs/windows/arm64/mssql-auth.dll (100%) rename {mssql_python => mssql_python_odbc}/libs/windows/arm64/vcredist/MICROSOFT_VISUAL_STUDIO_LICENSE.txt (100%) rename {mssql_python => mssql_python_odbc}/libs/windows/arm64/vcredist/msvcp140.dll (100%) rename {mssql_python => mssql_python_odbc}/libs/windows/x64/1033/msodbcsqlr18.rll (100%) rename {mssql_python => mssql_python_odbc}/libs/windows/x64/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt (100%) rename {mssql_python => mssql_python_odbc}/libs/windows/x64/MICROSOFT_VISUAL_STUDIO_LICENSE.txt (100%) rename {mssql_python => mssql_python_odbc}/libs/windows/x64/msodbcdiag18.dll (100%) rename {mssql_python => mssql_python_odbc}/libs/windows/x64/msodbcsql18.dll (100%) rename {mssql_python => mssql_python_odbc}/libs/windows/x64/mssql-auth.dll (100%) rename {mssql_python => mssql_python_odbc}/libs/windows/x64/vcredist/MICROSOFT_VISUAL_STUDIO_LICENSE.txt (100%) rename {mssql_python => mssql_python_odbc}/libs/windows/x64/vcredist/msvcp140.dll (100%) rename {mssql_python => mssql_python_odbc}/libs/windows/x86/1033/msodbcsqlr18.rll (100%) rename {mssql_python => mssql_python_odbc}/libs/windows/x86/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt (100%) rename {mssql_python => mssql_python_odbc}/libs/windows/x86/msodbcdiag18.dll (100%) rename {mssql_python => mssql_python_odbc}/libs/windows/x86/msodbcsql18.dll (100%) rename {mssql_python => mssql_python_odbc}/libs/windows/x86/mssql-auth.dll (100%) rename {mssql_python => mssql_python_odbc}/libs/windows/x86/vcredist/MICROSOFT_VISUAL_STUDIO_LICENSE.txt (100%) rename {mssql_python => mssql_python_odbc}/libs/windows/x86/vcredist/msvcp140.dll (100%) diff --git a/.gitignore b/.gitignore index cd64adb9b..7d54653d3 100644 --- a/.gitignore +++ b/.gitignore @@ -23,11 +23,10 @@ build/ **/build/ mssql_python.egg-info/ -# ODBC package (mssql-python-odbc): transition-period build artifacts. -# During Phase 2, setup_odbc.py copies the current platform's driver binaries -# from mssql_python/libs into this tree so a wheel can be built locally. The -# release pipeline populates these per-platform; do not commit the copies. -mssql_python_odbc/libs/ +# ODBC package (mssql-python-odbc). +# The driver binaries under mssql_python_odbc/libs/ are the committed source of +# truth for the standalone package (moved here from mssql_python/libs in Phase 2). +# The release pipeline still overwrites them per-platform for the full matrix. mssql_python_odbc.egg-info/ # Python bytecode diff --git a/mssql_python/pybind/build.bat b/mssql_python/pybind/build.bat index 90241c05d..d6b8b5c8a 100644 --- a/mssql_python/pybind/build.bat +++ b/mssql_python/pybind/build.bat @@ -163,7 +163,11 @@ if exist "%OUTPUT_DIR%\%PYD_NAME%" ( ) echo [DIAGNOSTIC] Parent is: !PARENT_DIR! - set VCREDIST_DLL_PATH=!PARENT_DIR!\libs\windows\!ARCH!\vcredist\msvcp140.dll + REM The VC++ runtime (msvcp140.dll) now ships with the driver binaries in the + REM standalone mssql-python-odbc package (mssql_python_odbc\libs), not under + REM mssql_python\libs. We still copy it next to the built .pyd because the + REM extension is built /MD and loads msvcp140.dll from its own directory. + set VCREDIST_DLL_PATH=!PARENT_DIR!\..\mssql_python_odbc\libs\windows\!ARCH!\vcredist\msvcp140.dll echo [DIAGNOSTIC] Looking for msvcp140.dll at "!VCREDIST_DLL_PATH!" if exist "!VCREDIST_DLL_PATH!" ( diff --git a/mssql_python/pybind/ddbc_bindings.cpp b/mssql_python/pybind/ddbc_bindings.cpp index 2ba176d7c..573bc32f6 100644 --- a/mssql_python/pybind/ddbc_bindings.cpp +++ b/mssql_python/pybind/ddbc_bindings.cpp @@ -1132,12 +1132,12 @@ std::string GetModuleDirectory() { // its directory as the base that `GetDriverPathCpp` (and the Windows // `mssql-auth.dll` lookup) append `libs` to. // -// During the Phase-2 transition we fall back to the bundled `mssql_python` -// directory when the external package is not installed, so a wheel that still -// bundles `libs/` keeps working. Importing `mssql_python_odbc` here is -// Alpine/musl-safe precisely because it is a separate pure package: it cannot -// trigger the partially-initialized-module circular import that motivated -// resolving these paths in C++ in the first place. +// Post-split the standalone package is REQUIRED: if it is missing or does not +// ship this platform's driver binaries we raise a clear, actionable error +// instead of silently falling back to bundled libs (there are none). Importing +// `mssql_python_odbc` here is Alpine/musl-safe precisely because it is a +// separate pure package: it cannot trigger the partially-initialized-module +// circular import that motivated resolving these paths in C++ in the first place. // // (`GetDriverPathCpp` is defined further below; forward-declared here so we can // verify the external package actually ships this platform's driver binary.) @@ -1159,19 +1159,17 @@ std::string GetOdbcLibsBaseDir() { fs::path parentDir = fs::path(module_file).parent_path(); - // Only treat the external package as authoritative if it actually ships - // a COMPLETE set of this platform's driver binaries. In a source/dev + // The external package is authoritative and REQUIRED: it must ship a + // COMPLETE set of this platform's driver binaries. In a source/dev // checkout (and in CI) the package is importable from the repo root but // its `libs/` tree is gitignored and either absent or only partially - // populated; in that case fall back to the bundled `mssql_python` libs - // so driver resolution points at a real, complete installation. + // populated; there is no bundled fallback anymore, so fail hard rather + // than resolve to a directory that has no usable driver. // // "Complete" means the ODBC driver itself and, on Windows, the // co-located `mssql-auth.dll` that LoadDriverOrThrowException loads // unconditionally. Verifying both here keeps this resolver's notion of a - // usable base dir consistent with what the loader below actually needs, - // so we never select a directory that would later make the loader throw - // (e.g. a dir that has msodbcsql18.dll but is missing mssql-auth.dll). + // usable base dir consistent with what the loader below actually needs. std::error_code ec; fs::path externalDriver(GetDriverPathCpp(parentDir.string())); bool externalComplete = fs::exists(externalDriver, ec); @@ -1181,24 +1179,27 @@ std::string GetOdbcLibsBaseDir() { externalComplete = fs::exists(externalAuthDll, ec); } #endif - if (externalComplete) { - LOG("GetOdbcLibsBaseDir: Using external mssql_python_odbc package - directory='%s'", + if (!externalComplete) { + LOG("GetOdbcLibsBaseDir: mssql_python_odbc present at '%s' but its ODBC driver " + "binaries are missing or incomplete for this platform", parentDir.string().c_str()); - return parentDir.string(); + ThrowStdException( + "The 'mssql-python-odbc' package is installed but its ODBC driver binaries " + "are missing or incomplete for this platform. Reinstall it with: " + "pip install --force-reinstall mssql-python-odbc"); } - LOG("GetOdbcLibsBaseDir: mssql_python_odbc present at '%s' but its libs are missing or " - "incomplete; falling back to bundled libs in mssql_python", + LOG("GetOdbcLibsBaseDir: Using external mssql_python_odbc package - directory='%s'", parentDir.string().c_str()); - return GetModuleDirectory(); + return parentDir.string(); } catch (const py::error_already_set& e) { if (e.matches(PyExc_ModuleNotFoundError)) { - // Expected in Phase 2 when the standalone package is not installed. - // pybind11 has already fetched and cleared the CPython error - // indicator, so re-importing `mssql_python` below is safe. - LOG("GetOdbcLibsBaseDir: mssql_python_odbc not installed (%s); " - "falling back to bundled libs in mssql_python", + // Phase 2: the standalone package is required. Turn the missing + // dependency into a clear, actionable error instead of a fallback. + LOG("GetOdbcLibsBaseDir: required package mssql_python_odbc is not installed (%s)", e.what()); - return GetModuleDirectory(); + ThrowStdException( + "The required 'mssql-python-odbc' package (which ships the ODBC driver " + "binaries) is not installed. Install it with: pip install mssql-python-odbc"); } // A different import-time error means the package is installed but // broken; surface it instead of silently masking the real problem. @@ -1311,8 +1312,8 @@ std::string GetDriverPathCpp(const std::string& moduleDir) { // CMakeLists.txt. This keeps the native resolver and the Python package // version from ever drifting: GetOdbcLibsBaseDir() calls fs::exists() on this // exact path to decide whether the external package is "complete", so a stale - // name would silently fall back to the bundled libs. (The ".so.2.1" suffix is - // the driver's ELF soname, which is independent of the product version.) + // name would fail that completeness check and raise a hard error. (The + // ".so.2.1" suffix is the driver's ELF soname, independent of the product version.) fs::path driverPath = basePath / "libs" / "linux" / platform / arch / "lib" / ("libmsodbcsql-" MSODBCSQL_VERSION_MAJOR_MINOR ".so.2.1"); return driverPath.string(); @@ -1341,9 +1342,9 @@ DriverHandle LoadDriverOrThrowException() { namespace fs = std::filesystem; // Resolve the base dir from the standalone `mssql_python_odbc` package - // (falls back to the bundled `mssql_python` libs during the transition). - // Both the driver path and the Windows `mssql-auth.dll` path below are - // derived from this directory. + // (required post-split; raises if missing or incomplete). Both the driver + // path and the Windows `mssql-auth.dll` path below are derived from this + // directory. std::string moduleDir = GetOdbcLibsBaseDir(); LOG("LoadDriverOrThrowException: ODBC libs base directory resolved to '%s'", moduleDir.c_str()); diff --git a/mssql_python/libs/LICENSING b/mssql_python_odbc/libs/LICENSING similarity index 100% rename from mssql_python/libs/LICENSING rename to mssql_python_odbc/libs/LICENSING diff --git a/mssql_python/libs/linux/alpine/arm64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt b/mssql_python_odbc/libs/linux/alpine/arm64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt similarity index 100% rename from mssql_python/libs/linux/alpine/arm64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt rename to mssql_python_odbc/libs/linux/alpine/arm64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt diff --git a/mssql_python/libs/linux/alpine/arm64/lib/libmsodbcsql-18.6.so.2.1 b/mssql_python_odbc/libs/linux/alpine/arm64/lib/libmsodbcsql-18.6.so.2.1 similarity index 100% rename from mssql_python/libs/linux/alpine/arm64/lib/libmsodbcsql-18.6.so.2.1 rename to mssql_python_odbc/libs/linux/alpine/arm64/lib/libmsodbcsql-18.6.so.2.1 diff --git a/mssql_python/libs/linux/alpine/arm64/lib/libodbcinst.so.2 b/mssql_python_odbc/libs/linux/alpine/arm64/lib/libodbcinst.so.2 similarity index 100% rename from mssql_python/libs/linux/alpine/arm64/lib/libodbcinst.so.2 rename to mssql_python_odbc/libs/linux/alpine/arm64/lib/libodbcinst.so.2 diff --git a/mssql_python/libs/linux/alpine/arm64/share/resources/en_US/msodbcsqlr18.rll b/mssql_python_odbc/libs/linux/alpine/arm64/share/resources/en_US/msodbcsqlr18.rll similarity index 100% rename from mssql_python/libs/linux/alpine/arm64/share/resources/en_US/msodbcsqlr18.rll rename to mssql_python_odbc/libs/linux/alpine/arm64/share/resources/en_US/msodbcsqlr18.rll diff --git a/mssql_python/libs/linux/alpine/x86_64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt b/mssql_python_odbc/libs/linux/alpine/x86_64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt similarity index 100% rename from mssql_python/libs/linux/alpine/x86_64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt rename to mssql_python_odbc/libs/linux/alpine/x86_64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt diff --git a/mssql_python/libs/linux/alpine/x86_64/lib/libmsodbcsql-18.6.so.2.1 b/mssql_python_odbc/libs/linux/alpine/x86_64/lib/libmsodbcsql-18.6.so.2.1 similarity index 100% rename from mssql_python/libs/linux/alpine/x86_64/lib/libmsodbcsql-18.6.so.2.1 rename to mssql_python_odbc/libs/linux/alpine/x86_64/lib/libmsodbcsql-18.6.so.2.1 diff --git a/mssql_python/libs/linux/alpine/x86_64/lib/libodbcinst.so.2 b/mssql_python_odbc/libs/linux/alpine/x86_64/lib/libodbcinst.so.2 similarity index 100% rename from mssql_python/libs/linux/alpine/x86_64/lib/libodbcinst.so.2 rename to mssql_python_odbc/libs/linux/alpine/x86_64/lib/libodbcinst.so.2 diff --git a/mssql_python/libs/linux/alpine/x86_64/share/resources/en_US/msodbcsqlr18.rll b/mssql_python_odbc/libs/linux/alpine/x86_64/share/resources/en_US/msodbcsqlr18.rll similarity index 100% rename from mssql_python/libs/linux/alpine/x86_64/share/resources/en_US/msodbcsqlr18.rll rename to mssql_python_odbc/libs/linux/alpine/x86_64/share/resources/en_US/msodbcsqlr18.rll diff --git a/mssql_python/libs/linux/debian_ubuntu/arm64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt b/mssql_python_odbc/libs/linux/debian_ubuntu/arm64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt similarity index 100% rename from mssql_python/libs/linux/debian_ubuntu/arm64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt rename to mssql_python_odbc/libs/linux/debian_ubuntu/arm64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt diff --git a/mssql_python/libs/linux/debian_ubuntu/arm64/lib/libmsodbcsql-18.6.so.2.1 b/mssql_python_odbc/libs/linux/debian_ubuntu/arm64/lib/libmsodbcsql-18.6.so.2.1 similarity index 100% rename from mssql_python/libs/linux/debian_ubuntu/arm64/lib/libmsodbcsql-18.6.so.2.1 rename to mssql_python_odbc/libs/linux/debian_ubuntu/arm64/lib/libmsodbcsql-18.6.so.2.1 diff --git a/mssql_python/libs/linux/debian_ubuntu/arm64/lib/libodbcinst.so.2 b/mssql_python_odbc/libs/linux/debian_ubuntu/arm64/lib/libodbcinst.so.2 similarity index 100% rename from mssql_python/libs/linux/debian_ubuntu/arm64/lib/libodbcinst.so.2 rename to mssql_python_odbc/libs/linux/debian_ubuntu/arm64/lib/libodbcinst.so.2 diff --git a/mssql_python/libs/linux/debian_ubuntu/arm64/share/resources/en_US/msodbcsqlr18.rll b/mssql_python_odbc/libs/linux/debian_ubuntu/arm64/share/resources/en_US/msodbcsqlr18.rll similarity index 100% rename from mssql_python/libs/linux/debian_ubuntu/arm64/share/resources/en_US/msodbcsqlr18.rll rename to mssql_python_odbc/libs/linux/debian_ubuntu/arm64/share/resources/en_US/msodbcsqlr18.rll diff --git a/mssql_python/libs/linux/debian_ubuntu/x86_64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt b/mssql_python_odbc/libs/linux/debian_ubuntu/x86_64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt similarity index 100% rename from mssql_python/libs/linux/debian_ubuntu/x86_64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt rename to mssql_python_odbc/libs/linux/debian_ubuntu/x86_64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt diff --git a/mssql_python/libs/linux/debian_ubuntu/x86_64/lib/libmsodbcsql-18.6.so.2.1 b/mssql_python_odbc/libs/linux/debian_ubuntu/x86_64/lib/libmsodbcsql-18.6.so.2.1 similarity index 100% rename from mssql_python/libs/linux/debian_ubuntu/x86_64/lib/libmsodbcsql-18.6.so.2.1 rename to mssql_python_odbc/libs/linux/debian_ubuntu/x86_64/lib/libmsodbcsql-18.6.so.2.1 diff --git a/mssql_python/libs/linux/debian_ubuntu/x86_64/lib/libodbcinst.so.2 b/mssql_python_odbc/libs/linux/debian_ubuntu/x86_64/lib/libodbcinst.so.2 similarity index 100% rename from mssql_python/libs/linux/debian_ubuntu/x86_64/lib/libodbcinst.so.2 rename to mssql_python_odbc/libs/linux/debian_ubuntu/x86_64/lib/libodbcinst.so.2 diff --git a/mssql_python/libs/linux/debian_ubuntu/x86_64/share/resources/en_US/msodbcsqlr18.rll b/mssql_python_odbc/libs/linux/debian_ubuntu/x86_64/share/resources/en_US/msodbcsqlr18.rll similarity index 100% rename from mssql_python/libs/linux/debian_ubuntu/x86_64/share/resources/en_US/msodbcsqlr18.rll rename to mssql_python_odbc/libs/linux/debian_ubuntu/x86_64/share/resources/en_US/msodbcsqlr18.rll diff --git a/mssql_python/libs/linux/rhel/arm64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt b/mssql_python_odbc/libs/linux/rhel/arm64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt similarity index 100% rename from mssql_python/libs/linux/rhel/arm64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt rename to mssql_python_odbc/libs/linux/rhel/arm64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt diff --git a/mssql_python/libs/linux/rhel/arm64/lib/libmsodbcsql-18.6.so.2.1 b/mssql_python_odbc/libs/linux/rhel/arm64/lib/libmsodbcsql-18.6.so.2.1 similarity index 100% rename from mssql_python/libs/linux/rhel/arm64/lib/libmsodbcsql-18.6.so.2.1 rename to mssql_python_odbc/libs/linux/rhel/arm64/lib/libmsodbcsql-18.6.so.2.1 diff --git a/mssql_python/libs/linux/rhel/arm64/lib/libodbcinst.so.2 b/mssql_python_odbc/libs/linux/rhel/arm64/lib/libodbcinst.so.2 similarity index 100% rename from mssql_python/libs/linux/rhel/arm64/lib/libodbcinst.so.2 rename to mssql_python_odbc/libs/linux/rhel/arm64/lib/libodbcinst.so.2 diff --git a/mssql_python/libs/linux/rhel/arm64/share/resources/en_US/msodbcsqlr18.rll b/mssql_python_odbc/libs/linux/rhel/arm64/share/resources/en_US/msodbcsqlr18.rll similarity index 100% rename from mssql_python/libs/linux/rhel/arm64/share/resources/en_US/msodbcsqlr18.rll rename to mssql_python_odbc/libs/linux/rhel/arm64/share/resources/en_US/msodbcsqlr18.rll diff --git a/mssql_python/libs/linux/rhel/x86_64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt b/mssql_python_odbc/libs/linux/rhel/x86_64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt similarity index 100% rename from mssql_python/libs/linux/rhel/x86_64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt rename to mssql_python_odbc/libs/linux/rhel/x86_64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt diff --git a/mssql_python/libs/linux/rhel/x86_64/lib/libmsodbcsql-18.6.so.2.1 b/mssql_python_odbc/libs/linux/rhel/x86_64/lib/libmsodbcsql-18.6.so.2.1 similarity index 100% rename from mssql_python/libs/linux/rhel/x86_64/lib/libmsodbcsql-18.6.so.2.1 rename to mssql_python_odbc/libs/linux/rhel/x86_64/lib/libmsodbcsql-18.6.so.2.1 diff --git a/mssql_python/libs/linux/rhel/x86_64/lib/libodbcinst.so.2 b/mssql_python_odbc/libs/linux/rhel/x86_64/lib/libodbcinst.so.2 similarity index 100% rename from mssql_python/libs/linux/rhel/x86_64/lib/libodbcinst.so.2 rename to mssql_python_odbc/libs/linux/rhel/x86_64/lib/libodbcinst.so.2 diff --git a/mssql_python/libs/linux/rhel/x86_64/share/resources/en_US/msodbcsqlr18.rll b/mssql_python_odbc/libs/linux/rhel/x86_64/share/resources/en_US/msodbcsqlr18.rll similarity index 100% rename from mssql_python/libs/linux/rhel/x86_64/share/resources/en_US/msodbcsqlr18.rll rename to mssql_python_odbc/libs/linux/rhel/x86_64/share/resources/en_US/msodbcsqlr18.rll diff --git a/mssql_python/libs/linux/suse/x86_64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt b/mssql_python_odbc/libs/linux/suse/x86_64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt similarity index 100% rename from mssql_python/libs/linux/suse/x86_64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt rename to mssql_python_odbc/libs/linux/suse/x86_64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt diff --git a/mssql_python/libs/linux/suse/x86_64/lib/libmsodbcsql-18.6.so.2.1 b/mssql_python_odbc/libs/linux/suse/x86_64/lib/libmsodbcsql-18.6.so.2.1 similarity index 100% rename from mssql_python/libs/linux/suse/x86_64/lib/libmsodbcsql-18.6.so.2.1 rename to mssql_python_odbc/libs/linux/suse/x86_64/lib/libmsodbcsql-18.6.so.2.1 diff --git a/mssql_python/libs/linux/suse/x86_64/lib/libodbcinst.so.2 b/mssql_python_odbc/libs/linux/suse/x86_64/lib/libodbcinst.so.2 similarity index 100% rename from mssql_python/libs/linux/suse/x86_64/lib/libodbcinst.so.2 rename to mssql_python_odbc/libs/linux/suse/x86_64/lib/libodbcinst.so.2 diff --git a/mssql_python/libs/linux/suse/x86_64/share/resources/en_US/msodbcsqlr18.rll b/mssql_python_odbc/libs/linux/suse/x86_64/share/resources/en_US/msodbcsqlr18.rll similarity index 100% rename from mssql_python/libs/linux/suse/x86_64/share/resources/en_US/msodbcsqlr18.rll rename to mssql_python_odbc/libs/linux/suse/x86_64/share/resources/en_US/msodbcsqlr18.rll diff --git a/mssql_python/libs/macos/arm64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt b/mssql_python_odbc/libs/macos/arm64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt similarity index 100% rename from mssql_python/libs/macos/arm64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt rename to mssql_python_odbc/libs/macos/arm64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt diff --git a/mssql_python/libs/macos/arm64/lib/libltdl.7.dylib b/mssql_python_odbc/libs/macos/arm64/lib/libltdl.7.dylib similarity index 100% rename from mssql_python/libs/macos/arm64/lib/libltdl.7.dylib rename to mssql_python_odbc/libs/macos/arm64/lib/libltdl.7.dylib diff --git a/mssql_python/libs/macos/arm64/lib/libmsodbcsql.18.dylib b/mssql_python_odbc/libs/macos/arm64/lib/libmsodbcsql.18.dylib similarity index 100% rename from mssql_python/libs/macos/arm64/lib/libmsodbcsql.18.dylib rename to mssql_python_odbc/libs/macos/arm64/lib/libmsodbcsql.18.dylib diff --git a/mssql_python/libs/macos/arm64/lib/libodbc.2.dylib b/mssql_python_odbc/libs/macos/arm64/lib/libodbc.2.dylib similarity index 100% rename from mssql_python/libs/macos/arm64/lib/libodbc.2.dylib rename to mssql_python_odbc/libs/macos/arm64/lib/libodbc.2.dylib diff --git a/mssql_python/libs/macos/arm64/lib/libodbcinst.2.dylib b/mssql_python_odbc/libs/macos/arm64/lib/libodbcinst.2.dylib similarity index 100% rename from mssql_python/libs/macos/arm64/lib/libodbcinst.2.dylib rename to mssql_python_odbc/libs/macos/arm64/lib/libodbcinst.2.dylib diff --git a/mssql_python/libs/macos/arm64/share/doc/msodbcsql18/LICENSE.txt b/mssql_python_odbc/libs/macos/arm64/share/doc/msodbcsql18/LICENSE.txt similarity index 100% rename from mssql_python/libs/macos/arm64/share/doc/msodbcsql18/LICENSE.txt rename to mssql_python_odbc/libs/macos/arm64/share/doc/msodbcsql18/LICENSE.txt diff --git a/mssql_python/libs/macos/arm64/share/doc/msodbcsql18/RELEASE_NOTES b/mssql_python_odbc/libs/macos/arm64/share/doc/msodbcsql18/RELEASE_NOTES similarity index 100% rename from mssql_python/libs/macos/arm64/share/doc/msodbcsql18/RELEASE_NOTES rename to mssql_python_odbc/libs/macos/arm64/share/doc/msodbcsql18/RELEASE_NOTES diff --git a/mssql_python/libs/macos/arm64/share/msodbcsql18/resources/en_US/msodbcsqlr18.rll b/mssql_python_odbc/libs/macos/arm64/share/msodbcsql18/resources/en_US/msodbcsqlr18.rll similarity index 100% rename from mssql_python/libs/macos/arm64/share/msodbcsql18/resources/en_US/msodbcsqlr18.rll rename to mssql_python_odbc/libs/macos/arm64/share/msodbcsql18/resources/en_US/msodbcsqlr18.rll diff --git a/mssql_python/libs/macos/x86_64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt b/mssql_python_odbc/libs/macos/x86_64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt similarity index 100% rename from mssql_python/libs/macos/x86_64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt rename to mssql_python_odbc/libs/macos/x86_64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt diff --git a/mssql_python/libs/macos/x86_64/lib/libltdl.7.dylib b/mssql_python_odbc/libs/macos/x86_64/lib/libltdl.7.dylib similarity index 100% rename from mssql_python/libs/macos/x86_64/lib/libltdl.7.dylib rename to mssql_python_odbc/libs/macos/x86_64/lib/libltdl.7.dylib diff --git a/mssql_python/libs/macos/x86_64/lib/libmsodbcsql.18.dylib b/mssql_python_odbc/libs/macos/x86_64/lib/libmsodbcsql.18.dylib similarity index 100% rename from mssql_python/libs/macos/x86_64/lib/libmsodbcsql.18.dylib rename to mssql_python_odbc/libs/macos/x86_64/lib/libmsodbcsql.18.dylib diff --git a/mssql_python/libs/macos/x86_64/lib/libodbc.2.dylib b/mssql_python_odbc/libs/macos/x86_64/lib/libodbc.2.dylib similarity index 100% rename from mssql_python/libs/macos/x86_64/lib/libodbc.2.dylib rename to mssql_python_odbc/libs/macos/x86_64/lib/libodbc.2.dylib diff --git a/mssql_python/libs/macos/x86_64/lib/libodbcinst.2.dylib b/mssql_python_odbc/libs/macos/x86_64/lib/libodbcinst.2.dylib similarity index 100% rename from mssql_python/libs/macos/x86_64/lib/libodbcinst.2.dylib rename to mssql_python_odbc/libs/macos/x86_64/lib/libodbcinst.2.dylib diff --git a/mssql_python/libs/macos/x86_64/share/doc/msodbcsql18/LICENSE.txt b/mssql_python_odbc/libs/macos/x86_64/share/doc/msodbcsql18/LICENSE.txt similarity index 100% rename from mssql_python/libs/macos/x86_64/share/doc/msodbcsql18/LICENSE.txt rename to mssql_python_odbc/libs/macos/x86_64/share/doc/msodbcsql18/LICENSE.txt diff --git a/mssql_python/libs/macos/x86_64/share/doc/msodbcsql18/RELEASE_NOTES b/mssql_python_odbc/libs/macos/x86_64/share/doc/msodbcsql18/RELEASE_NOTES similarity index 100% rename from mssql_python/libs/macos/x86_64/share/doc/msodbcsql18/RELEASE_NOTES rename to mssql_python_odbc/libs/macos/x86_64/share/doc/msodbcsql18/RELEASE_NOTES diff --git a/mssql_python/libs/macos/x86_64/share/msodbcsql18/resources/en_US/msodbcsqlr18.rll b/mssql_python_odbc/libs/macos/x86_64/share/msodbcsql18/resources/en_US/msodbcsqlr18.rll similarity index 100% rename from mssql_python/libs/macos/x86_64/share/msodbcsql18/resources/en_US/msodbcsqlr18.rll rename to mssql_python_odbc/libs/macos/x86_64/share/msodbcsql18/resources/en_US/msodbcsqlr18.rll diff --git a/mssql_python/libs/windows/arm64/1033/msodbcsqlr18.rll b/mssql_python_odbc/libs/windows/arm64/1033/msodbcsqlr18.rll similarity index 100% rename from mssql_python/libs/windows/arm64/1033/msodbcsqlr18.rll rename to mssql_python_odbc/libs/windows/arm64/1033/msodbcsqlr18.rll diff --git a/mssql_python/libs/windows/arm64/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt b/mssql_python_odbc/libs/windows/arm64/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt similarity index 100% rename from mssql_python/libs/windows/arm64/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt rename to mssql_python_odbc/libs/windows/arm64/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt diff --git a/mssql_python/libs/windows/arm64/msodbcdiag18.dll b/mssql_python_odbc/libs/windows/arm64/msodbcdiag18.dll similarity index 100% rename from mssql_python/libs/windows/arm64/msodbcdiag18.dll rename to mssql_python_odbc/libs/windows/arm64/msodbcdiag18.dll diff --git a/mssql_python/libs/windows/arm64/msodbcsql18.dll b/mssql_python_odbc/libs/windows/arm64/msodbcsql18.dll similarity index 100% rename from mssql_python/libs/windows/arm64/msodbcsql18.dll rename to mssql_python_odbc/libs/windows/arm64/msodbcsql18.dll diff --git a/mssql_python/libs/windows/arm64/mssql-auth.dll b/mssql_python_odbc/libs/windows/arm64/mssql-auth.dll similarity index 100% rename from mssql_python/libs/windows/arm64/mssql-auth.dll rename to mssql_python_odbc/libs/windows/arm64/mssql-auth.dll diff --git a/mssql_python/libs/windows/arm64/vcredist/MICROSOFT_VISUAL_STUDIO_LICENSE.txt b/mssql_python_odbc/libs/windows/arm64/vcredist/MICROSOFT_VISUAL_STUDIO_LICENSE.txt similarity index 100% rename from mssql_python/libs/windows/arm64/vcredist/MICROSOFT_VISUAL_STUDIO_LICENSE.txt rename to mssql_python_odbc/libs/windows/arm64/vcredist/MICROSOFT_VISUAL_STUDIO_LICENSE.txt diff --git a/mssql_python/libs/windows/arm64/vcredist/msvcp140.dll b/mssql_python_odbc/libs/windows/arm64/vcredist/msvcp140.dll similarity index 100% rename from mssql_python/libs/windows/arm64/vcredist/msvcp140.dll rename to mssql_python_odbc/libs/windows/arm64/vcredist/msvcp140.dll diff --git a/mssql_python/libs/windows/x64/1033/msodbcsqlr18.rll b/mssql_python_odbc/libs/windows/x64/1033/msodbcsqlr18.rll similarity index 100% rename from mssql_python/libs/windows/x64/1033/msodbcsqlr18.rll rename to mssql_python_odbc/libs/windows/x64/1033/msodbcsqlr18.rll diff --git a/mssql_python/libs/windows/x64/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt b/mssql_python_odbc/libs/windows/x64/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt similarity index 100% rename from mssql_python/libs/windows/x64/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt rename to mssql_python_odbc/libs/windows/x64/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt diff --git a/mssql_python/libs/windows/x64/MICROSOFT_VISUAL_STUDIO_LICENSE.txt b/mssql_python_odbc/libs/windows/x64/MICROSOFT_VISUAL_STUDIO_LICENSE.txt similarity index 100% rename from mssql_python/libs/windows/x64/MICROSOFT_VISUAL_STUDIO_LICENSE.txt rename to mssql_python_odbc/libs/windows/x64/MICROSOFT_VISUAL_STUDIO_LICENSE.txt diff --git a/mssql_python/libs/windows/x64/msodbcdiag18.dll b/mssql_python_odbc/libs/windows/x64/msodbcdiag18.dll similarity index 100% rename from mssql_python/libs/windows/x64/msodbcdiag18.dll rename to mssql_python_odbc/libs/windows/x64/msodbcdiag18.dll diff --git a/mssql_python/libs/windows/x64/msodbcsql18.dll b/mssql_python_odbc/libs/windows/x64/msodbcsql18.dll similarity index 100% rename from mssql_python/libs/windows/x64/msodbcsql18.dll rename to mssql_python_odbc/libs/windows/x64/msodbcsql18.dll diff --git a/mssql_python/libs/windows/x64/mssql-auth.dll b/mssql_python_odbc/libs/windows/x64/mssql-auth.dll similarity index 100% rename from mssql_python/libs/windows/x64/mssql-auth.dll rename to mssql_python_odbc/libs/windows/x64/mssql-auth.dll diff --git a/mssql_python/libs/windows/x64/vcredist/MICROSOFT_VISUAL_STUDIO_LICENSE.txt b/mssql_python_odbc/libs/windows/x64/vcredist/MICROSOFT_VISUAL_STUDIO_LICENSE.txt similarity index 100% rename from mssql_python/libs/windows/x64/vcredist/MICROSOFT_VISUAL_STUDIO_LICENSE.txt rename to mssql_python_odbc/libs/windows/x64/vcredist/MICROSOFT_VISUAL_STUDIO_LICENSE.txt diff --git a/mssql_python/libs/windows/x64/vcredist/msvcp140.dll b/mssql_python_odbc/libs/windows/x64/vcredist/msvcp140.dll similarity index 100% rename from mssql_python/libs/windows/x64/vcredist/msvcp140.dll rename to mssql_python_odbc/libs/windows/x64/vcredist/msvcp140.dll diff --git a/mssql_python/libs/windows/x86/1033/msodbcsqlr18.rll b/mssql_python_odbc/libs/windows/x86/1033/msodbcsqlr18.rll similarity index 100% rename from mssql_python/libs/windows/x86/1033/msodbcsqlr18.rll rename to mssql_python_odbc/libs/windows/x86/1033/msodbcsqlr18.rll diff --git a/mssql_python/libs/windows/x86/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt b/mssql_python_odbc/libs/windows/x86/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt similarity index 100% rename from mssql_python/libs/windows/x86/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt rename to mssql_python_odbc/libs/windows/x86/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt diff --git a/mssql_python/libs/windows/x86/msodbcdiag18.dll b/mssql_python_odbc/libs/windows/x86/msodbcdiag18.dll similarity index 100% rename from mssql_python/libs/windows/x86/msodbcdiag18.dll rename to mssql_python_odbc/libs/windows/x86/msodbcdiag18.dll diff --git a/mssql_python/libs/windows/x86/msodbcsql18.dll b/mssql_python_odbc/libs/windows/x86/msodbcsql18.dll similarity index 100% rename from mssql_python/libs/windows/x86/msodbcsql18.dll rename to mssql_python_odbc/libs/windows/x86/msodbcsql18.dll diff --git a/mssql_python/libs/windows/x86/mssql-auth.dll b/mssql_python_odbc/libs/windows/x86/mssql-auth.dll similarity index 100% rename from mssql_python/libs/windows/x86/mssql-auth.dll rename to mssql_python_odbc/libs/windows/x86/mssql-auth.dll diff --git a/mssql_python/libs/windows/x86/vcredist/MICROSOFT_VISUAL_STUDIO_LICENSE.txt b/mssql_python_odbc/libs/windows/x86/vcredist/MICROSOFT_VISUAL_STUDIO_LICENSE.txt similarity index 100% rename from mssql_python/libs/windows/x86/vcredist/MICROSOFT_VISUAL_STUDIO_LICENSE.txt rename to mssql_python_odbc/libs/windows/x86/vcredist/MICROSOFT_VISUAL_STUDIO_LICENSE.txt diff --git a/mssql_python/libs/windows/x86/vcredist/msvcp140.dll b/mssql_python_odbc/libs/windows/x86/vcredist/msvcp140.dll similarity index 100% rename from mssql_python/libs/windows/x86/vcredist/msvcp140.dll rename to mssql_python_odbc/libs/windows/x86/vcredist/msvcp140.dll diff --git a/setup.py b/setup.py index 4e56229fa..50197a779 100644 --- a/setup.py +++ b/setup.py @@ -139,28 +139,6 @@ def run(self): if (PROJECT_ROOT / "mssql_py_core").is_dir(): packages.append("mssql_py_core") -# Add platform-specific packages -if sys.platform.startswith("win"): - packages.extend( - [ - f"mssql_python.libs.windows.{arch}", - f"mssql_python.libs.windows.{arch}.1033", - f"mssql_python.libs.windows.{arch}.vcredist", - ] - ) -elif sys.platform.startswith("darwin"): - packages.extend( - [ - f"mssql_python.libs.macos", - ] - ) -elif sys.platform.startswith("linux"): - packages.extend( - [ - f"mssql_python.libs.linux", - ] - ) - # --------------------------------------------------------------------------- # package_data – binaries to include in the wheel # --------------------------------------------------------------------------- @@ -169,8 +147,9 @@ def run(self): "py.typed", "ddbc_bindings.cp*.pyd", "ddbc_bindings.cp*.so", - "libs/*", - "libs/**/*", + # msvcp140.dll (VC++ runtime) is copied next to the compiled extension by + # build.bat; the ODBC driver binaries themselves ship only in the + # standalone mssql-python-odbc package (see setup_odbc.py). "*.dll", ], "mssql_py_core": [ @@ -211,10 +190,6 @@ def run(self): distclass=BinaryDistribution, exclude_package_data={ "": ["*.yml", "*.yaml"], # Exclude YML files - "mssql_python": [ - "libs/*/vcredist/*", - "libs/*/vcredist/**/*", # Exclude vcredist directories, added here since `'libs/*' is already included` - ], }, # Register custom commands cmdclass={ diff --git a/setup_odbc.py b/setup_odbc.py index 7e87cc737..c0b642105 100644 --- a/setup_odbc.py +++ b/setup_odbc.py @@ -10,16 +10,14 @@ python setup_odbc.py bdist_wheel -During the transition period the driver binaries still live under -``mssql_python/libs/``. This script copies the current platform's subtree into -``mssql_python_odbc/libs/`` so a wheel can be produced locally. The release -pipeline populates ``libs/`` per-platform and is the source of truth for the +The driver binaries live under ``mssql_python_odbc/libs/`` (the committed source +of truth, moved here from ``mssql_python/libs/`` in Phase 2). The release +pipeline overwrites that subtree per-platform and is the source of truth for the full release matrix. """ import os import re -import shutil import sys from pathlib import Path @@ -31,7 +29,6 @@ PROJECT_ROOT = Path(__file__).resolve().parent PACKAGE_NAME = "mssql_python_odbc" PACKAGE_DIR = PROJECT_ROOT / PACKAGE_NAME -BUNDLED_LIBS_ROOT = PROJECT_ROOT / "mssql_python" / "libs" # The driver binaries are packaged via the recursive ``libs/**/*`` glob in # ``package_data`` below, which enumerates files from the copied @@ -179,7 +176,7 @@ def sync_libs() -> None: class CustomBdistWheel(bdist_wheel): - """Force a platform-specific but Python-agnostic tag and sync libs. + """Force a platform-specific but Python-agnostic tag. The package ships only pre-built ODBC driver binaries (data), not a compiled Python extension, so one ``py3-none-`` wheel serves every supported @@ -204,10 +201,6 @@ def get_tag(self): _python, _abi, plat = bdist_wheel.get_tag(self) return "py3", "none", plat - def run(self): - sync_libs() - bdist_wheel.run(self) - _require_min_setuptools() From 5e2d22b2cd11dc9d4b814c112321027a80db7cc0 Mon Sep 17 00:00:00 2001 From: Jahnvi Thakkar Date: Thu, 30 Jul 2026 13:04:03 +0530 Subject: [PATCH 02/11] REFACTOR: single-host ODBC cross-build + pipeline reorder for Phase 2 Piece A: setup_odbc.py builds all 7 py3-none- ODBC data wheels from one host via ODBC_TARGET_* overrides; per-target package_data (include_package_data=False) so each wheel ships only its own driver + LICENSING; plat_name_supplied=True fixes macOS wheel tagging. Piece B: one unconditional ODBC_BuildAll + ConsolidateOdbc stage runs first; Windows/macOS/Linux mssql-python build stages dependsOn ConsolidateOdbc and install the external mssql-python-odbc wheel before pytest (Linux via PIP_FIND_LINKS into the bind-mounted repo dir). --- .../build-release-package-pipeline.yml | 103 ++++----- .../stages/build-linux-single-stage.yml | 41 +++- .../stages/build-macos-single-stage.yml | 33 +++ .../stages/build-odbc-all-stage.yml | 198 ++++++++++++++++++ .../stages/build-windows-single-stage.yml | 40 ++++ setup_odbc.py | 126 ++++++----- 6 files changed, 425 insertions(+), 116 deletions(-) create mode 100644 OneBranchPipelines/stages/build-odbc-all-stage.yml diff --git a/OneBranchPipelines/build-release-package-pipeline.yml b/OneBranchPipelines/build-release-package-pipeline.yml index 2c39429ef..146904bbe 100644 --- a/OneBranchPipelines/build-release-package-pipeline.yml +++ b/OneBranchPipelines/build-release-package-pipeline.yml @@ -427,6 +427,10 @@ extends: shortPyVer: ${{ config.pyVer }} architecture: ${{ config.arch }} oneBranchType: '${{ variables.effectiveOneBranchType }}' + # Phase 2: install the external ODBC wheel before pytest (bundled driver removed). + odbcDependsOn: + - ConsolidateOdbc + installOdbcWheel: true # ========================= # MACOS BUILD STAGES @@ -453,6 +457,10 @@ extends: pythonVersion: ${{ format('{0}.{1}', substring(config.pyVer, 0, 1), substring(config.pyVer, 1, 2)) }} shortPyVer: ${{ config.pyVer }} oneBranchType: '${{ variables.effectiveOneBranchType }}' + # Phase 2: install the external ODBC wheel before pytest (bundled driver removed). + odbcDependsOn: + - ConsolidateOdbc + installOdbcWheel: true # ========================= # LINUX BUILD STAGES @@ -484,6 +492,10 @@ extends: arch: ${{ config.arch }} dockerPlatform: ${{ config.platform }} oneBranchType: '${{ variables.effectiveOneBranchType }}' + # Phase 2: install the external ODBC wheel before pytest (bundled driver removed). + odbcDependsOn: + - ConsolidateOdbc + installOdbcWheel: true # ========================= # CONSOLIDATE STAGE @@ -533,70 +545,47 @@ extends: # PDB files uploaded to Microsoft Symbol Server for debugging # ========================================================================= - # ODBC PACKAGE BUILD STAGES (mssql-python-odbc) + # ODBC PACKAGE BUILD STAGE (mssql-python-odbc) — SINGLE HOST # ========================================================================= - # 7 data-only wheels (no native compile, no pytest) built from setup_odbc.py. - # They run in parallel with the mssql-python stages and are consolidated by a - # SEPARATE ConsolidateOdbc stage so the release pipeline can ship each package - # independently. SDL "just works": these wheels contain the SAME ODBC driver - # binaries already scanned in mssql_python/libs by the main build — no new - # first-party native code is introduced. + # ONE Windows stage (ODBC_BuildAll) produces ALL 7 data-only wheels + # (2 Windows + 1 macOS universal2 + 4 Linux) — no native compile, no pytest. + # The package is pure driver data (py3-none-), so a single host can + # cross-produce every platform's wheel from the committed mssql_python_odbc/libs + # tree via setup_odbc.py's ODBC_TARGET_* overrides. SDL "just works": these + # wheels contain the SAME ODBC driver binaries already scanned by the build — + # no new first-party native code is introduced. # - # ODBC Windows stages (2): ODBC_Win_x64, ODBC_Win_arm64 - # Gate: build mssql-python-odbc stages unless an mssql-python-only build was requested - - ${{ if ne(variables.effectiveBuildPackage, 'mssql-python') }}: - - ${{ each config in parameters.odbcWindowsConfigs }}: - - template: /OneBranchPipelines/stages/build-odbc-windows-stage.yml@self - parameters: - stageName: ODBC_Win_${{ config.arch }} - jobName: BuildWheel - architecture: ${{ config.arch }} - oneBranchType: '${{ variables.effectiveOneBranchType }}' - - # ODBC macOS stage (1): ODBC_MacOS_universal2 - - ${{ if ne(variables.effectiveBuildPackage, 'mssql-python') }}: - - ${{ each config in parameters.odbcMacosConfigs }}: - - template: /OneBranchPipelines/stages/build-odbc-macos-stage.yml@self - parameters: - stageName: ODBC_MacOS_${{ config.name }} - jobName: BuildWheel - oneBranchType: '${{ variables.effectiveOneBranchType }}' - - # ODBC Linux stages (4): manylinux_2_28 x86_64/aarch64, musllinux x86_64/aarch64 - - ${{ if ne(variables.effectiveBuildPackage, 'mssql-python') }}: - - ${{ each config in parameters.odbcLinuxConfigs }}: - - template: /OneBranchPipelines/stages/build-odbc-linux-stage.yml@self - parameters: - stageName: ODBC_Linux_${{ config.tag }}_${{ config.arch }} - jobName: BuildWheel - linuxTag: ${{ config.tag }} - arch: ${{ config.arch }} - dockerPlatform: ${{ config.platform }} - oneBranchType: '${{ variables.effectiveOneBranchType }}' + # UNCONDITIONAL: every buildPackage value needs the ODBC wheels FIRST — + # * 'mssql-python-odbc' ships them, + # * 'mssql-python' installs the matching wheel before its pytest (Phase 2 + # removed the bundled driver, so the mssql-python tests now require the + # external package), + # * 'both' needs both. + # It therefore runs ahead of the mssql-python build stages, which depend on + # ConsolidateOdbc (below) to obtain the wheel. + - template: /OneBranchPipelines/stages/build-odbc-all-stage.yml@self + parameters: + stageName: ODBC_BuildAll + jobName: BuildWheel + oneBranchType: '${{ variables.effectiveOneBranchType }}' # ========================================================================= # CONSOLIDATE ODBC STAGE # ========================================================================= # Collects the 7 mssql-python-odbc wheels into a single dist/ folder and - # publishes them as `drop_ConsolidateOdbc_ConsolidateArtifacts` (distinct from - # the mssql-python `drop_Consolidate_ConsolidateArtifacts`). The release - # pipeline selects one artifact based on its `releasePackage` parameter. - - ${{ if ne(variables.effectiveBuildPackage, 'mssql-python') }}: - - stage: ConsolidateOdbc - displayName: 'Consolidate All ODBC Artifacts' - dependsOn: - - ODBC_Win_x64 - - ODBC_Win_arm64 - - ODBC_MacOS_universal2 - - ODBC_Linux_manylinux_2_28_x86_64 - - ODBC_Linux_manylinux_2_28_aarch64 - - ODBC_Linux_musllinux_x86_64 - - ODBC_Linux_musllinux_aarch64 - jobs: - - template: /OneBranchPipelines/jobs/consolidate-odbc-artifacts-job.yml@self - parameters: - oneBranchType: '${{ variables.effectiveOneBranchType }}' - expectedWheelCount: 7 + # publishes them as `drop_ConsolidateOdbc_ConsolidateArtifacts`. Runs + # UNCONDITIONALLY because the mssql-python build stages depend on it to install + # the ODBC wheel before pytest. The release pipeline selects this artifact when + # its `releasePackage` parameter targets mssql-python-odbc. + - stage: ConsolidateOdbc + displayName: 'Consolidate All ODBC Artifacts' + dependsOn: + - ODBC_BuildAll + jobs: + - template: /OneBranchPipelines/jobs/consolidate-odbc-artifacts-job.yml@self + parameters: + oneBranchType: '${{ variables.effectiveOneBranchType }}' + expectedWheelCount: 7 # ========================================================================= # WHEEL INSTALLATION TEST STAGE(S) diff --git a/OneBranchPipelines/stages/build-linux-single-stage.yml b/OneBranchPipelines/stages/build-linux-single-stage.yml index 6d82f5a6f..535e0fb97 100644 --- a/OneBranchPipelines/stages/build-linux-single-stage.yml +++ b/OneBranchPipelines/stages/build-linux-single-stage.yml @@ -23,10 +23,20 @@ parameters: - name: oneBranchType type: string default: 'Official' + # Phase 2: stages this build depends on (e.g. [ConsolidateOdbc]) so the external + # mssql-python-odbc wheel exists before pytest. Default [] = no dependency. + - name: odbcDependsOn + type: object + default: [] + # Phase 2: install the external ODBC wheel (from ConsolidateOdbc) before pytest. + - name: installOdbcWheel + type: boolean + default: false stages: - stage: ${{ parameters.stageName }} displayName: 'Linux ${{ parameters.linuxTag }} ${{ parameters.arch }}' + dependsOn: ${{ parameters.odbcDependsOn }} jobs: - job: ${{ parameters.jobName }} displayName: 'Build Wheels - ${{ parameters.linuxTag }} ${{ parameters.arch }}' @@ -191,6 +201,33 @@ stages: env: DB_PASSWORD: $(DB_PASSWORD) + # ========================= + # PHASE 2: EXTERNAL ODBC DRIVER PACKAGE + # ========================= + # Phase 2 removed the bundled ODBC driver from the mssql-python wheel, so the + # isolated-install pytest below must obtain the driver from the external + # mssql-python-odbc package. Download the wheels built earlier this run + # (ConsolidateOdbc) into the repo dir (bind-mounted at /workspace in the build + # container) and flatten them so PIP_FIND_LINKS=/workspace/odbc_wheels lets the + # in-container `pip install ` resolve the pinned + # mssql-python-odbc==18.6.2 dependency locally (no PyPI needed for it). + - ${{ if parameters.installOdbcWheel }}: + - task: DownloadPipelineArtifact@2 + displayName: 'Download consolidated ODBC wheels' + inputs: + buildType: 'current' + artifactName: 'drop_ConsolidateOdbc_ConsolidateArtifacts' + itemPattern: '**/*.whl' + targetPath: '$(Build.SourcesDirectory)/odbc_wheels' + - bash: | + set -euo pipefail + # Flatten all downloaded wheels into the find-links root so pip (via + # PIP_FIND_LINKS) sees them without recursion. + find "$(Build.SourcesDirectory)/odbc_wheels" -name '*.whl' -exec mv -f {} "$(Build.SourcesDirectory)/odbc_wheels/" \; || true + echo "ODBC wheels available for find-links:" + ls -1 "$(Build.SourcesDirectory)/odbc_wheels"/*.whl + displayName: 'Flatten ODBC wheels for pip find-links' + # Build wheels for all Python versions (3.10-3.14) and test each one - script: | set -euxo pipefail @@ -206,7 +243,7 @@ stages: if [[ "$(LINUX_TAG)" == "manylinux_2_28" ]]; then # Manylinux (glibc-based) - use bash - docker exec -e PYBIN=$PYBIN -e SQL_IP=$(SQL_IP) -e DB_PASSWORD="$(DB_PASSWORD)" -e MANYLINUX_TAG="$(LINUX_TAG)" build-$(LINUX_TAG)-$(ARCH) bash -lc ' + docker exec -e PYBIN=$PYBIN -e SQL_IP=$(SQL_IP) -e DB_PASSWORD="$(DB_PASSWORD)" -e MANYLINUX_TAG="$(LINUX_TAG)" -e PIP_FIND_LINKS="/workspace/odbc_wheels" build-$(LINUX_TAG)-$(ARCH) bash -lc ' set -euxo pipefail; # Step 1: Setup Python environment @@ -274,7 +311,7 @@ stages: ' else # Musllinux (musl libc-based) - use sh - docker exec -e PYBIN=$PYBIN -e SQL_IP=$(SQL_IP) -e DB_PASSWORD="$(DB_PASSWORD)" -e MANYLINUX_TAG="$(LINUX_TAG)" build-$(LINUX_TAG)-$(ARCH) sh -lc ' + docker exec -e PYBIN=$PYBIN -e SQL_IP=$(SQL_IP) -e DB_PASSWORD="$(DB_PASSWORD)" -e MANYLINUX_TAG="$(LINUX_TAG)" -e PIP_FIND_LINKS="/workspace/odbc_wheels" build-$(LINUX_TAG)-$(ARCH) sh -lc ' set -euxo pipefail; # Step 1: Setup Python environment diff --git a/OneBranchPipelines/stages/build-macos-single-stage.yml b/OneBranchPipelines/stages/build-macos-single-stage.yml index a0b5f8273..c2c2ed790 100644 --- a/OneBranchPipelines/stages/build-macos-single-stage.yml +++ b/OneBranchPipelines/stages/build-macos-single-stage.yml @@ -20,10 +20,20 @@ parameters: - name: oneBranchType type: string default: 'Official' + # Phase 2: stages this build depends on (e.g. [ConsolidateOdbc]) so the external + # mssql-python-odbc wheel exists before pytest. Default [] = no dependency. + - name: odbcDependsOn + type: object + default: [] + # Phase 2: install the external ODBC wheel (from ConsolidateOdbc) before pytest. + - name: installOdbcWheel + type: boolean + default: false stages: - stage: ${{ parameters.stageName }} displayName: 'macOS Py${{ parameters.pythonVersion }} Universal2' + dependsOn: ${{ parameters.odbcDependsOn }} jobs: - job: ${{ parameters.jobName }} displayName: 'Build Wheel - Py${{ parameters.pythonVersion }} Universal2' @@ -176,6 +186,29 @@ stages: bash $(Build.SourcesDirectory)/eng/scripts/install-mssql-py-core.sh displayName: 'Install mssql_py_core from NuGet' + # ========================= + # PHASE 2: EXTERNAL ODBC DRIVER PACKAGE + # ========================= + # Phase 2 removed the bundled ODBC driver from the mssql-python wheel, so the + # driver is resolved from the separate mssql-python-odbc package. Install the + # matching wheel built earlier in this run (ConsolidateOdbc) before pytest. + - ${{ if parameters.installOdbcWheel }}: + - task: DownloadPipelineArtifact@2 + displayName: 'Download consolidated ODBC wheels' + inputs: + buildType: 'current' + artifactName: 'drop_ConsolidateOdbc_ConsolidateArtifacts' + itemPattern: '**/*.whl' + targetPath: '$(Pipeline.Workspace)/odbc_wheels' + - script: | + set -e + LINKS=$(dirname "$(find "$(Pipeline.Workspace)/odbc_wheels" -name '*.whl' | head -1)") + if [ -z "$LINKS" ]; then echo "No ODBC wheel found in downloaded artifact" >&2; exit 1; fi + echo "Installing mssql-python-odbc from $LINKS" + python -m pip install --no-index --find-links "$LINKS" mssql-python-odbc + python -c "import mssql_python_odbc, os; print('mssql_python_odbc at', os.path.dirname(mssql_python_odbc.__file__))" + displayName: 'Install external ODBC wheel (mssql-python-odbc)' + # ========================= # TESTING # ========================= diff --git a/OneBranchPipelines/stages/build-odbc-all-stage.yml b/OneBranchPipelines/stages/build-odbc-all-stage.yml new file mode 100644 index 000000000..42722ca78 --- /dev/null +++ b/OneBranchPipelines/stages/build-odbc-all-stage.yml @@ -0,0 +1,198 @@ +# ODBC "Build All" Single-Host Stage Template +# ============================================================================ +# Builds ALL platform-specific `mssql-python-odbc` wheels (Windows x64/arm64, +# macOS universal2, Linux manylinux/musllinux x86_64/aarch64 = 7 wheels) on ONE +# Windows agent. +# +# Why this is safe to do on a single host: the `mssql-python-odbc` package ships +# ONLY pre-built ODBC driver binaries (pure data) — there is NO compiled Python +# extension and NO per-Python-version matrix. Every platform's driver binaries +# are committed under `mssql_python_odbc/libs/`, so the build host is irrelevant +# to the wheel CONTENTS: only the wheel's platform TAG and the selected `libs/` +# subtree matter. `setup_odbc.py` honours the `ODBC_TARGET_PLATFORM_TAG` / +# `ODBC_TARGET_ARCH` environment overrides to cross-produce each target's wheel +# (see `get_platform_info` / `_target_libs_globs`), and marks the tag as +# explicitly supplied so `wheel` does not re-derive a macOS tag from the host. +# +# This replaces the previous 7 separate ODBC stages (2 Windows + 1 macOS + 4 +# Linux) and their macOS/Linux agents, collapsing the ODBC build to a single fast +# stage that emits all 7 wheels as one artifact (`drop__`), +# which `ConsolidateOdbc` then picks up via its `drop_ODBC_*` item pattern. +parameters: + # Stage identifier (e.g., 'ODBC_BuildAll'). + - name: stageName + type: string + default: 'ODBC_BuildAll' + # Job identifier within the stage. + - name: jobName + type: string + default: 'BuildWheel' + # OneBranch build type: 'Official' (production) or 'NonOfficial' (dev/test). + - name: oneBranchType + type: string + default: 'Official' + # Any supported interpreter can produce the Python-agnostic wheels. + - name: pythonVersion + type: string + default: '3.12' + # The full release matrix of ODBC wheels to produce from this one host. + # Each entry drives ODBC_TARGET_PLATFORM_TAG (wheel tag) + ODBC_TARGET_ARCH + # (libs/ subtree selector) for one `setup_odbc.py bdist_wheel` invocation. + - name: odbcTargets + type: object + default: + - { tag: 'win_amd64', arch: 'x64' } + - { tag: 'win_arm64', arch: 'arm64' } + - { tag: 'macosx_15_0_universal2', arch: 'universal2' } + - { tag: 'manylinux_2_28_x86_64', arch: 'x86_64' } + - { tag: 'manylinux_2_28_aarch64', arch: 'aarch64' } + - { tag: 'musllinux_1_2_x86_64', arch: 'x86_64' } + - { tag: 'musllinux_1_2_aarch64', arch: 'aarch64' } + +stages: + - stage: ${{ parameters.stageName }} + displayName: 'ODBC Build All Wheels (single host)' + jobs: + - job: ${{ parameters.jobName }} + displayName: 'Build all 7 ODBC wheels on one Windows host' + # Reuse the same custom 1ES Windows pool as the mssql-python build. + pool: + type: windows + isCustom: true + name: Python-1ES-pool + demands: + - imageOverride -equals PYTHON-1ES-MMS2022 + timeoutInMinutes: 60 + + variables: + # OneBranch output directory for artifacts. + ob_outputDirectory: '$(Build.ArtifactStagingDirectory)' + # OneBranch-required variable (unused on Windows stages). + LinuxContainerImage: 'onebranch.azurecr.io/linux/ubuntu-2204:latest' + + steps: + # Driver binaries are committed under mssql_python_odbc/libs; shallow checkout is enough. + - checkout: self + fetchDepth: 1 + + - task: UsePythonVersion@0 + inputs: + versionSpec: '${{ parameters.pythonVersion }}' + architecture: 'x64' + addToPath: true + displayName: 'Use Python ${{ parameters.pythonVersion }}' + + - powershell: | + $ErrorActionPreference = "Stop" + python -m pip install --upgrade pip + python -m pip install setuptools wheel build twine + displayName: 'Install build tooling' + + # Build every target wheel by driving setup_odbc.py's ODBC_TARGET_* overrides. + # One step per target (compile-time expanded from odbcTargets). build/ + + # egg-info are wiped before each build so a previous target's staged libs/ + # can never leak into the next wheel; all wheels accumulate in dist/. + - ${{ each t in parameters.odbcTargets }}: + - powershell: | + $ErrorActionPreference = "Stop" + Remove-Item -Recurse -Force build, mssql_python_odbc.egg-info -ErrorAction SilentlyContinue + $env:ODBC_TARGET_PLATFORM_TAG = "${{ t.tag }}" + $env:ODBC_TARGET_ARCH = "${{ t.arch }}" + python setup_odbc.py bdist_wheel + if ($LASTEXITCODE -ne 0) { Write-Error "ODBC wheel build failed for ${{ t.tag }}"; exit 1 } + displayName: 'Build ODBC wheel: ${{ t.tag }}' + + - powershell: | + $ErrorActionPreference = "Stop" + Write-Host "Produced wheels:" + Get-ChildItem dist\*.whl | ForEach-Object { Write-Host " - $($_.Name)" } + python -m twine check dist\*.whl + displayName: 'List + twine check wheels' + + - task: CopyFiles@2 + inputs: + SourceFolder: '$(Build.SourcesDirectory)\dist' + Contents: '*.whl' + TargetFolder: '$(ob_outputDirectory)\wheels' + displayName: 'Stage wheel artifacts' + + # Assert EVERY wheel ships ONLY its own platform's driver binaries — a data + # wheel that packaged the wrong (or an incomplete) subtree would still pass + # `twine check`. This is the single-host cross-build's safety net: it proves + # the ODBC_TARGET_* overrides selected the right libs/ subtree per tag and + # that no foreign-platform binaries leaked in. + - powershell: | + $ErrorActionPreference = "Stop" + Add-Type -AssemblyName System.IO.Compression.FileSystem + $wheelDir = "$(ob_outputDirectory)\wheels" + + # tag -> @{ Must = ; Forbid = } + $expect = @{ + 'win_amd64' = @{ Must = @('libs/windows/x64/', 'msodbcsql', 'mssql-auth.dll', 'libs/LICENSING'); Forbid = @('libs/linux/', 'libs/macos/', 'libs/windows/arm64/') } + 'win_arm64' = @{ Must = @('libs/windows/arm64/', 'msodbcsql', 'mssql-auth.dll', 'libs/LICENSING'); Forbid = @('libs/linux/', 'libs/macos/', 'libs/windows/x64/') } + 'macosx_15_0_universal2' = @{ Must = @('libs/macos/arm64/', 'libs/macos/x86_64/', '.dylib', 'libs/LICENSING'); Forbid = @('libs/windows/', 'libs/linux/') } + 'manylinux_2_28_x86_64' = @{ Must = @('libs/linux/debian_ubuntu/x86_64/', '.so', 'libs/LICENSING'); Forbid = @('libs/windows/', 'libs/macos/', '/arm64/') } + 'manylinux_2_28_aarch64' = @{ Must = @('libs/linux/debian_ubuntu/arm64/', '.so', 'libs/LICENSING'); Forbid = @('libs/windows/', 'libs/macos/', '/x86_64/') } + 'musllinux_1_2_x86_64' = @{ Must = @('libs/linux/alpine/x86_64/', '.so', 'libs/LICENSING'); Forbid = @('libs/windows/', 'libs/macos/', '/arm64/') } + 'musllinux_1_2_aarch64' = @{ Must = @('libs/linux/alpine/arm64/', '.so', 'libs/LICENSING'); Forbid = @('libs/windows/', 'libs/macos/', '/x86_64/') } + } + + $failed = $false + foreach ($tag in $expect.Keys) { + $whl = Get-ChildItem -Path $wheelDir -Filter "*$tag.whl" | Select-Object -First 1 + if (-not $whl) { + Write-Error "Missing expected ODBC wheel for tag '$tag'" + $failed = $true + continue + } + $zip = [System.IO.Compression.ZipFile]::OpenRead($whl.FullName) + $names = $zip.Entries | ForEach-Object { $_.FullName.Replace('\', '/') } + $zip.Dispose() + foreach ($m in $expect[$tag].Must) { + if (-not ($names | Where-Object { $_ -like "*$m*" })) { + Write-Error "$($whl.Name): MISSING required content '$m'" + $failed = $true + } + } + foreach ($f in $expect[$tag].Forbid) { + if ($names | Where-Object { $_ -like "*$f*" }) { + Write-Error "$($whl.Name): contains FOREIGN content '$f' (wrong-platform binaries leaked)" + $failed = $true + } + } + if (-not $failed) { Write-Host "OK: $($whl.Name) ships only its own platform's libs" } + } + if ($failed) { exit 1 } + Write-Host "All 7 ODBC wheels verified: each ships only its own platform's driver." + displayName: 'Assert each ODBC wheel is platform-correct' + + # OneBranch requires artifact naming: drop__. + # ConsolidateOdbc picks this up via its 'drop_ODBC_*' item pattern. + - task: PublishPipelineArtifact@1 + displayName: 'Publish ODBC Wheels Artifact' + inputs: + targetPath: '$(ob_outputDirectory)' + artifact: 'drop_${{ parameters.stageName }}_${{ parameters.jobName }}' + publishLocation: 'pipeline' + + # Component Governance + OneBranch AntiMalware notification. + - template: /OneBranchPipelines/steps/malware-scanning-step.yml@self + parameters: + scanPath: '$(ob_outputDirectory)' + artifactType: 'pkg' + + # Scan the redistributed driver binaries + wheels for malware (Official only). + - ${{ if eq(parameters.oneBranchType, 'Official') }}: + - task: EsrpMalwareScanning@5 + displayName: 'ESRP MalwareScanning - ODBC Wheels (Official)' + inputs: + ConnectedServiceName: '$(SigningEsrpConnectedServiceName)' + AppRegistrationClientId: '$(SigningAppRegistrationClientId)' + AppRegistrationTenantId: '$(SigningAppRegistrationTenantId)' + EsrpClientId: '$(SigningEsrpClientId)' + UseMSIAuthentication: true + FolderPath: '$(ob_outputDirectory)/wheels' + Pattern: '*.whl' + SessionTimeout: 60 + CleanupTempStorage: 1 + VerboseLogin: 1 diff --git a/OneBranchPipelines/stages/build-windows-single-stage.yml b/OneBranchPipelines/stages/build-windows-single-stage.yml index d4a89218f..fcad9260b 100644 --- a/OneBranchPipelines/stages/build-windows-single-stage.yml +++ b/OneBranchPipelines/stages/build-windows-single-stage.yml @@ -27,10 +27,20 @@ parameters: - name: publishSymbols type: boolean default: true + # Phase 2: stages this build depends on (e.g. [ConsolidateOdbc]) so the external + # mssql-python-odbc wheel exists before pytest. Default [] = no dependency. + - name: odbcDependsOn + type: object + default: [] + # Phase 2: install the external ODBC wheel (from ConsolidateOdbc) before pytest. + - name: installOdbcWheel + type: boolean + default: false stages: - stage: ${{ parameters.stageName }} displayName: 'Windows Py${{ parameters.pythonVersion }} ${{ parameters.architecture }}' + dependsOn: ${{ parameters.odbcDependsOn }} jobs: - job: ${{ parameters.jobName }} displayName: 'Build Wheel - Py${{ parameters.pythonVersion }} ${{ parameters.architecture }}' @@ -249,6 +259,36 @@ stages: targetType: 'filePath' filePath: '$(Build.SourcesDirectory)\eng\scripts\install-mssql-py-core.ps1' + # ========================= + # PHASE 2: EXTERNAL ODBC DRIVER PACKAGE + # ========================= + # The bundled ODBC driver was removed from the mssql-python wheel in Phase 2, + # so `import mssql_python` now resolves the driver from the separate + # mssql-python-odbc package. Install the matching wheel built earlier in this + # run (ConsolidateOdbc) so pytest exercises the real external-package path. + # Skipped on ARM64 (pytest does not run on the x64 host for ARM64 targets). + - ${{ if parameters.installOdbcWheel }}: + - task: DownloadPipelineArtifact@2 + displayName: 'Download consolidated ODBC wheels' + condition: ne(variables['targetArch'], 'arm64') + inputs: + buildType: 'current' + artifactName: 'drop_ConsolidateOdbc_ConsolidateArtifacts' + itemPattern: '**/*.whl' + targetPath: '$(Pipeline.Workspace)/odbc_wheels' + - powershell: | + $ErrorActionPreference = "Stop" + # find-links points at the folder holding the .whl files (consolidate + # publishes them under dist/). pip picks the wheel matching THIS host. + $links = Get-ChildItem -Path "$(Pipeline.Workspace)/odbc_wheels" -Recurse -Filter *.whl | + Select-Object -First 1 -ExpandProperty DirectoryName + if (-not $links) { Write-Error "No ODBC wheel found in downloaded artifact"; exit 1 } + Write-Host "Installing mssql-python-odbc from $links" + python -m pip install --no-index --find-links "$links" mssql-python-odbc + python -c "import mssql_python_odbc, os; print('mssql_python_odbc at', os.path.dirname(mssql_python_odbc.__file__))" + displayName: 'Install external ODBC wheel (mssql-python-odbc)' + condition: ne(variables['targetArch'], 'arm64') + # ========================= # TESTING # ========================= diff --git a/setup_odbc.py b/setup_odbc.py index c0b642105..c02503dcf 100644 --- a/setup_odbc.py +++ b/setup_odbc.py @@ -14,6 +14,12 @@ of truth, moved here from ``mssql_python/libs/`` in Phase 2). The release pipeline overwrites that subtree per-platform and is the source of truth for the full release matrix. + +Each wheel ships ONLY its own platform's ``libs/`` subtree (see +``_target_libs_globs``). Because the package contains no compiled extension, a +single build host can produce EVERY platform's wheel by setting +``ODBC_TARGET_PLATFORM_TAG`` / ``ODBC_TARGET_ARCH`` (see ``get_platform_info``), +e.g. build all 7 release wheels on one Windows agent. """ import os @@ -83,6 +89,16 @@ def get_platform_info(): Kept in sync with ``setup.py`` so the ODBC wheel carries the same platform tags as the main ``mssql-python`` wheel. """ + # Explicit target override for single-host cross-building: produce ANY + # platform's wheel from ONE build agent. The ODBC package ships only pre-built + # driver data (no compiled extension), so the build host is irrelevant to the + # wheel contents -- only the platform TAG and the selected libs subtree matter. + # When ODBC_TARGET_PLATFORM_TAG is set, honor it verbatim (with the libs-dir + # arch from ODBC_TARGET_ARCH) instead of inferring from the build host. + explicit_tag = os.environ.get("ODBC_TARGET_PLATFORM_TAG") + if explicit_tag: + return os.environ.get("ODBC_TARGET_ARCH", ""), explicit_tag + if sys.platform.startswith("win"): arch = os.environ.get("ARCHITECTURE", "x64") if isinstance(arch, str): @@ -118,61 +134,44 @@ def get_platform_info(): raise OSError(f"Unsupported platform: {sys.platform!r}") -def _libs_arch(build_arch: str) -> str: - """Map the build arch from ``get_platform_info`` to the ``libs/`` dir name. - - ``libs/`` uses ``x64``/``x86``/``arm64`` on Windows and - ``x86_64``/``arm64`` on Linux/macOS. - """ - if sys.platform.startswith("win"): - return build_arch # already x64 / x86 / arm64 - if build_arch in ("x86_64", "amd64"): - return "x86_64" - if build_arch in ("aarch64", "arm64"): - return "arm64" - return build_arch - - -def _copytree(src: Path, dst: Path) -> None: - if src.is_dir(): - shutil.copytree(src, dst, dirs_exist_ok=True) - print(f" Copied {src} -> {dst}") - +def _target_libs_globs(platform_tag: str, arch: str) -> list: + """Return the ``package_data`` globs for exactly ONE target platform's libs. -def sync_libs() -> None: - """Copy the current platform's ODBC libs into ``mssql_python_odbc/libs/``. - - Convenience for local/transition builds while ``mssql_python`` still bundles - the binaries. If ``mssql_python/libs`` is absent (e.g. the pipeline places - binaries directly into the package), this is a no-op. + Post-Phase-2 the committed ``mssql_python_odbc/libs/`` tree contains EVERY + platform's driver binaries. A wheel must ship only its own platform's subtree, + so we translate the (``platform_tag``, ``arch``) of the wheel being built into + the minimal set of ``libs/`` globs. Combined with ``include_package_data=False`` + this guarantees a Windows wheel never carries Linux/macOS binaries (and vice + versa), whether the build runs on the native OS or is cross-built on a single + host via the ``ODBC_TARGET_*`` overrides. """ - target_root = PACKAGE_DIR / "libs" - if not BUNDLED_LIBS_ROOT.is_dir(): - print(f"sync_libs: bundled libs not found at {BUNDLED_LIBS_ROOT}; skipping copy") - return - - build_arch, _ = get_platform_info() - arch = _libs_arch(build_arch) - - # Always carry the licensing files. - _copytree(BUNDLED_LIBS_ROOT / "LICENSING", target_root / "LICENSING") - - if sys.platform.startswith("win"): - _copytree(BUNDLED_LIBS_ROOT / "windows" / arch, target_root / "windows" / arch) - - elif sys.platform.startswith("darwin"): - # universal2 wheel serves both architectures. - for mac_arch in ("arm64", "x86_64"): - _copytree(BUNDLED_LIBS_ROOT / "macos" / mac_arch, target_root / "macos" / mac_arch) - - elif sys.platform.startswith("linux"): - # A single Linux wheel serves all distro families for its libc/arch; - # the driver is selected at runtime via /etc/*-release detection. + # LICENSING travels with every wheel (it is a file at the libs/ root). + globs = ["libs/LICENSING"] + tag = platform_tag.lower() + + def _subtree(root: str) -> None: + globs.append(f"{root}/*") + globs.append(f"{root}/**/*") + + if tag.startswith("win"): + # arch is already the libs dir name on Windows: x64 / arm64 / x86. + _subtree(f"libs/windows/{arch}") + elif tag.startswith("macos"): + # The universal2 wheel serves both slices (arm64 + x86_64). + _subtree("libs/macos") + elif "linux" in tag: + libs_arch = ( + "x86_64" + if arch in ("x86_64", "amd64") + else "arm64" if arch in ("aarch64", "arm64") else arch + ) + # A single Linux wheel serves all distro families for its arch; the driver + # is selected at runtime via /etc/*-release detection. for distro in ("alpine", "debian_ubuntu", "rhel", "suse"): - _copytree( - BUNDLED_LIBS_ROOT / "linux" / distro / arch, - target_root / "linux" / distro / arch, - ) + _subtree(f"libs/linux/{distro}/{libs_arch}") + else: + raise OSError(f"Cannot determine libs subtree for platform tag {platform_tag!r}") + return globs class CustomBdistWheel(bdist_wheel): @@ -191,6 +190,12 @@ def finalize_options(self): bdist_wheel.finalize_options(self) arch, platform_tag = get_platform_info() self.plat_name = platform_tag + # Treat the tag as if it were passed via ``--plat-name`` so ``wheel`` + # trusts it VERBATIM. Otherwise wheel's ``get_tag`` special-cases any + # ``macosx*`` tag and re-derives it from the *build host*, which silently + # mislabels a cross-built macOS wheel as (e.g.) ``win_amd64`` -- fatal to + # single-host cross-building of the macOS wheel. + self.plat_name_supplied = True # Platform-specific (ships native binaries) but not tied to a CPython ABI. self.root_is_pure = False print(f"Setting wheel platform tag to: {self.plat_name} (arch: {arch})") @@ -204,6 +209,13 @@ def get_tag(self): _require_min_setuptools() +# Resolve the ONE target this invocation builds (host-inferred, or overridden via +# ODBC_TARGET_* for single-host cross-building) and the minimal libs globs for it. +_TARGET_ARCH, _TARGET_PLATFORM_TAG = get_platform_info() +_LIBS_GLOBS = _target_libs_globs(_TARGET_PLATFORM_TAG, _TARGET_ARCH) +print(f"ODBC wheel target: tag={_TARGET_PLATFORM_TAG} arch={_TARGET_ARCH!r}") +print(f"ODBC libs globs: {_LIBS_GLOBS}") + setup( name="mssql-python-odbc", version=_read_odbc_version(), @@ -243,12 +255,12 @@ def get_tag(self): # is completeness + attribution, which is also why GetOdbcLibsBaseDir()'s # completeness check does not need to verify vcredist. package_data={ - PACKAGE_NAME: [ - "libs/*", - "libs/**/*", - ], + PACKAGE_NAME: _LIBS_GLOBS, }, - include_package_data=True, + # include_package_data MUST stay False: the committed libs/ tree holds EVERY + # platform, so SCM-based inclusion would sweep them all into every wheel. We + # rely solely on the target-specific _LIBS_GLOBS above to ship one platform. + include_package_data=False, python_requires=">=3.10", classifiers=[ "License :: OSI Approved :: MIT License", From 39fab4e9640ed7151775496407ebe307222e5a6a Mon Sep 17 00:00:00 2001 From: Jahnvi Thakkar Date: Thu, 30 Jul 2026 13:12:20 +0530 Subject: [PATCH 03/11] REFACTOR: drop redundant post-consolidation TestBothWheels stages The mssql-python build stages now install the external mssql-python-odbc wheel and run the full pytest suite, so the separate post-consolidation wheel-installation test stages are redundant. --- .../build-release-package-pipeline.yml | 50 ++----------------- 1 file changed, 5 insertions(+), 45 deletions(-) diff --git a/OneBranchPipelines/build-release-package-pipeline.yml b/OneBranchPipelines/build-release-package-pipeline.yml index 146904bbe..4c6944b94 100644 --- a/OneBranchPipelines/build-release-package-pipeline.yml +++ b/OneBranchPipelines/build-release-package-pipeline.yml @@ -587,48 +587,8 @@ extends: oneBranchType: '${{ variables.effectiveOneBranchType }}' expectedWheelCount: 7 - # ========================================================================= - # WHEEL INSTALLATION TEST STAGE(S) - # ========================================================================= - # Post-consolidation validation of the package split from an end user's - # perspective: install the ACTUAL published mssql-python + mssql-python-odbc - # wheels together, remove the bundled ODBC driver so the external package is - # the ONLY driver source, then run the full pytest suite. Runs only when BOTH - # packages are built in the same run (buildPackage = 'both'), because it needs - # both consolidated artifacts (drop_Consolidate_* and drop_ConsolidateOdbc_*). - - ${{ if eq(variables.effectiveBuildPackage, 'both') }}: - - template: /OneBranchPipelines/stages/wheel-installation-test-stage.yml@self - parameters: - stageName: TestBothWheels_manylinux_2_28_x86_64 - jobName: InstallAndTest - linuxTag: manylinux_2_28 - arch: x86_64 - dockerPlatform: linux/amd64 - oneBranchType: '${{ variables.effectiveOneBranchType }}' - - template: /OneBranchPipelines/stages/wheel-installation-test-stage.yml@self - parameters: - stageName: TestBothWheels_musllinux_x86_64 - jobName: InstallAndTest - linuxTag: musllinux - arch: x86_64 - dockerPlatform: linux/amd64 - oneBranchType: '${{ variables.effectiveOneBranchType }}' - # Windows + macOS counterparts run natively (no Docker) on the same pools the - # build stages use. They prove the external-package resolution end-to-end on all - # three OSes - Windows additionally covers mssql-auth.dll resolution, macOS covers - # the universal2 dylib @loader_path/rpath resolution. - - template: /OneBranchPipelines/stages/wheel-installation-test-windows-stage.yml@self - parameters: - stageName: TestBothWheels_Windows_x64 - jobName: InstallAndTest - pythonVersion: '3.12' - shortPyVer: '312' - architecture: x64 - oneBranchType: '${{ variables.effectiveOneBranchType }}' - - template: /OneBranchPipelines/stages/wheel-installation-test-macos-stage.yml@self - parameters: - stageName: TestBothWheels_macOS_universal2 - jobName: InstallAndTest - pythonVersion: '3.12' - shortPyVer: '312' - oneBranchType: '${{ variables.effectiveOneBranchType }}' + # NOTE: The former post-consolidation "TestBothWheels_*" stages were removed. + # Phase 2 removed the bundled ODBC driver from the mssql-python wheel, and the + # mssql-python build stages now install the external mssql-python-odbc wheel + # (from ConsolidateOdbc) and run the full pytest suite against it — so the + # external-package resolution is already validated end-to-end during the build. From 5789500d6d80b4777b064a7b5e5ce563495217fb Mon Sep 17 00:00:00 2001 From: Jahnvi Thakkar Date: Thu, 30 Jul 2026 14:38:30 +0530 Subject: [PATCH 04/11] FIX: tag macOS ODBC wheel macosx_11_0_universal2 so it installs on macOS-14 agent The pure-data ODBC wheel was tagged macosx_15_0_universal2, which pip rejects on the macOS-14 build/test agent (from versions: none). Lower it to the canonical universal2 floor (macosx_11_0); the driver dylibs run on macOS 11+ and the tag stays compatible everywhere the mssql-python wheel installs. --- OneBranchPipelines/stages/build-odbc-all-stage.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/OneBranchPipelines/stages/build-odbc-all-stage.yml b/OneBranchPipelines/stages/build-odbc-all-stage.yml index 42722ca78..68dc5aaf2 100644 --- a/OneBranchPipelines/stages/build-odbc-all-stage.yml +++ b/OneBranchPipelines/stages/build-odbc-all-stage.yml @@ -43,7 +43,12 @@ parameters: default: - { tag: 'win_amd64', arch: 'x64' } - { tag: 'win_arm64', arch: 'arm64' } - - { tag: 'macosx_15_0_universal2', arch: 'universal2' } + # macOS: universal2 data-only wheel. macosx_11_0 is the canonical universal2 + # floor (Big Sur — first arm64 macOS) and the driver dylibs run on macOS 11+ + # (proven by pre-split connection tests on the macOS-14 agent), so this is + # pip-installable on every macOS the mssql-python wheel targets, including the + # macOS-14 build/test agent (a macosx_15_0 tag is rejected there). + - { tag: 'macosx_11_0_universal2', arch: 'universal2' } - { tag: 'manylinux_2_28_x86_64', arch: 'x86_64' } - { tag: 'manylinux_2_28_aarch64', arch: 'aarch64' } - { tag: 'musllinux_1_2_x86_64', arch: 'x86_64' } @@ -130,7 +135,7 @@ stages: $expect = @{ 'win_amd64' = @{ Must = @('libs/windows/x64/', 'msodbcsql', 'mssql-auth.dll', 'libs/LICENSING'); Forbid = @('libs/linux/', 'libs/macos/', 'libs/windows/arm64/') } 'win_arm64' = @{ Must = @('libs/windows/arm64/', 'msodbcsql', 'mssql-auth.dll', 'libs/LICENSING'); Forbid = @('libs/linux/', 'libs/macos/', 'libs/windows/x64/') } - 'macosx_15_0_universal2' = @{ Must = @('libs/macos/arm64/', 'libs/macos/x86_64/', '.dylib', 'libs/LICENSING'); Forbid = @('libs/windows/', 'libs/linux/') } + 'macosx_11_0_universal2' = @{ Must = @('libs/macos/arm64/', 'libs/macos/x86_64/', '.dylib', 'libs/LICENSING'); Forbid = @('libs/windows/', 'libs/linux/') } 'manylinux_2_28_x86_64' = @{ Must = @('libs/linux/debian_ubuntu/x86_64/', '.so', 'libs/LICENSING'); Forbid = @('libs/windows/', 'libs/macos/', '/arm64/') } 'manylinux_2_28_aarch64' = @{ Must = @('libs/linux/debian_ubuntu/arm64/', '.so', 'libs/LICENSING'); Forbid = @('libs/windows/', 'libs/macos/', '/x86_64/') } 'musllinux_1_2_x86_64' = @{ Must = @('libs/linux/alpine/x86_64/', '.so', 'libs/LICENSING'); Forbid = @('libs/windows/', 'libs/macos/', '/arm64/') } From 8dd4a62b62b4fc09dfbb3226f4d8bb670f81dfb7 Mon Sep 17 00:00:00 2001 From: Jahnvi Thakkar Date: Thu, 30 Jul 2026 15:11:03 +0530 Subject: [PATCH 05/11] FIX: resolve ODBC libs dir from mssql_python_odbc in test_015 Phase 2 moved the bundled ODBC driver libs from mssql_python into the external mssql_python_odbc package. test_libs_directory_exists and test_auth_dll_exists_if_libs_present now resolve the libs/ base dir via a helper that prefers mssql_python_odbc (falling back to mssql_python for the pre-split layout). --- tests/test_015_utf8_path_handling.py | 34 ++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/tests/test_015_utf8_path_handling.py b/tests/test_015_utf8_path_handling.py index 542b3b163..9c8ea7d74 100644 --- a/tests/test_015_utf8_path_handling.py +++ b/tests/test_015_utf8_path_handling.py @@ -159,12 +159,33 @@ def test_module_loads_on_windows(self): # - mssql-auth.dll (if exists) assert mssql_python.ddbc_bindings is not None - def test_libs_directory_exists(self): - """Verify the libs/windows directory structure exists.""" + @staticmethod + def _driver_libs_base_dir(): + """Base directory containing the ODBC driver's ``libs/`` payload. + + Phase 2 moved the bundled ODBC driver out of the mssql-python wheel into the + external mssql-python-odbc package, so the driver's ``libs/`` tree now lives + under ``mssql_python_odbc``. Fall back to ``mssql_python``'s own directory for + the pre-split single-wheel layout. + """ from pathlib import Path - module_dir = Path(mssql_python.__file__).parent - libs_dir = module_dir / "libs" / "windows" + try: + import mssql_python_odbc + + external = Path(mssql_python_odbc.__file__).parent + if (external / "libs").exists(): + return external + except ImportError: + pass + + import mssql_python + + return Path(mssql_python.__file__).parent + + def test_libs_directory_exists(self): + """Verify the ODBC driver libs/windows directory structure exists.""" + libs_dir = self._driver_libs_base_dir() / "libs" / "windows" # Check that at least one architecture directory exists arch_dirs = ["x64", "x86", "arm64"] @@ -172,11 +193,10 @@ def test_libs_directory_exists(self): assert found_arch, f"No architecture directory found in {libs_dir}" def test_auth_dll_exists_if_libs_present(self): - """Verify mssql-auth.dll exists in the libs directory.""" - from pathlib import Path + """Verify mssql-auth.dll exists in the ODBC driver libs directory.""" import struct - module_dir = Path(mssql_python.__file__).parent + module_dir = self._driver_libs_base_dir() # Determine architecture arch = "x64" if struct.calcsize("P") * 8 == 64 else "x86" From 578cf7d5ae1c0f671f4585544d75d87afc6e8c9a Mon Sep 17 00:00:00 2001 From: Jahnvi Thakkar Date: Fri, 31 Jul 2026 13:34:20 +0530 Subject: [PATCH 06/11] CHORE: always build both packages; remove buildPackage option --- .../build-release-package-pipeline.yml | 199 +++++++----------- 1 file changed, 81 insertions(+), 118 deletions(-) diff --git a/OneBranchPipelines/build-release-package-pipeline.yml b/OneBranchPipelines/build-release-package-pipeline.yml index 4c6944b94..d945237bc 100644 --- a/OneBranchPipelines/build-release-package-pipeline.yml +++ b/OneBranchPipelines/build-release-package-pipeline.yml @@ -70,23 +70,6 @@ parameters: type: boolean default: true - # Which package to build. Both are produced from this same pipeline but are - # fully independent stage groups, so building only one halves the run time and - # lets you run two builds (one per package) concurrently. - # mssql-python -> Windows/macOS/Linux wheels + Consolidate - # mssql-python-odbc -> ODBC data wheels + ConsolidateOdbc - # both -> everything (previous default behaviour) - # Scheduled (daily) builds ignore this and always build 'both' (see - # effectiveBuildPackage below) so the nightly keeps producing every artifact. - - name: buildPackage - displayName: 'Package to Build' - type: string - values: - - 'mssql-python' - - 'mssql-python-odbc' - - 'both' - default: 'mssql-python' - # ========================= # PLATFORM CONFIGURATIONS # ========================= @@ -194,14 +177,6 @@ variables: ${{ else }}: value: '${{ parameters.oneBranchType }}' - # Determine which package(s) to build: scheduled (nightly) builds always build - # 'both' so every artifact keeps refreshing; manual/PR builds honour the parameter. - - name: effectiveBuildPackage - ${{ if eq(variables['Build.Reason'], 'Schedule') }}: - value: 'both' - ${{ else }}: - value: '${{ parameters.buildPackage }}' - # Variable template imports # Each file provides specific variable groups: # - common-variables: Shared across all builds (paths, flags) @@ -378,13 +353,9 @@ extends: # Version automatically detected from wheel metadata (setup.py) sbom: enabled: ${{ parameters.runSdlTasks }} - # Name the SBOM after the package actually being built so an odbc-only build - # does not mislabel the driver wheels as 'mssql-python'. Combined ('both') and - # mssql-python builds keep the primary package name. - ${{ if eq(variables.effectiveBuildPackage, 'mssql-python-odbc') }}: - packageName: 'mssql-python-odbc' - ${{ else }}: - packageName: 'mssql-python' + # This pipeline always builds both packages; name the SBOM after the primary + # mssql-python wheel set. + packageName: 'mssql-python' # TSA - Threat and Security Assessment # Uploads scan results to Microsoft's TSA tool for tracking @@ -397,9 +368,9 @@ extends: # PIPELINE STAGES # ========================= # mssql-python: 9 Windows + 5 macOS + 4 Linux + 1 Consolidate = 19 stages - # mssql-python-odbc: 2 Windows + 1 macOS + 4 Linux + 1 ConsolidateOdbc = 8 stages - # Total: 27 stages. All build stages run in parallel; each Consolidate* stage - # waits only on its own package's build stages. + # mssql-python-odbc: 1 ODBC_BuildAll (single host, all 7 wheels) + 1 ConsolidateOdbc = 2 stages + # Total: 21 stages, always built together. The mssql-python build stages depend on + # ConsolidateOdbc so they install the external ODBC wheel before pytest. stages: # ========================= # WINDOWS BUILD STAGES @@ -415,22 +386,20 @@ extends: # 5. Builds wheel # 6. Publishes artifacts (wheels + PYD + PDB) # 7. ESRP malware scanning - # Gate: build mssql-python stages unless an odbc-only build was requested - - ${{ if ne(variables.effectiveBuildPackage, 'mssql-python-odbc') }}: - - ${{ each config in parameters.windowsConfigs }}: - - template: /OneBranchPipelines/stages/build-windows-single-stage.yml@self - parameters: - stageName: Win_py${{ config.pyVer }}_${{ config.arch }} - jobName: BuildWheel - # Convert pyVer '310' → pythonVersion '3.10' - pythonVersion: ${{ format('{0}.{1}', substring(config.pyVer, 0, 1), substring(config.pyVer, 1, 2)) }} - shortPyVer: ${{ config.pyVer }} - architecture: ${{ config.arch }} - oneBranchType: '${{ variables.effectiveOneBranchType }}' - # Phase 2: install the external ODBC wheel before pytest (bundled driver removed). - odbcDependsOn: - - ConsolidateOdbc - installOdbcWheel: true + - ${{ each config in parameters.windowsConfigs }}: + - template: /OneBranchPipelines/stages/build-windows-single-stage.yml@self + parameters: + stageName: Win_py${{ config.pyVer }}_${{ config.arch }} + jobName: BuildWheel + # Convert pyVer '310' → pythonVersion '3.10' + pythonVersion: ${{ format('{0}.{1}', substring(config.pyVer, 0, 1), substring(config.pyVer, 1, 2)) }} + shortPyVer: ${{ config.pyVer }} + architecture: ${{ config.arch }} + oneBranchType: '${{ variables.effectiveOneBranchType }}' + # Phase 2: install the external ODBC wheel before pytest (bundled driver removed). + odbcDependsOn: + - ConsolidateOdbc + installOdbcWheel: true # ========================= # MACOS BUILD STAGES @@ -447,20 +416,19 @@ extends: # 6. Builds wheel # 7. Publishes artifacts (wheels + .so) # 8. ESRP malware scanning - - ${{ if ne(variables.effectiveBuildPackage, 'mssql-python-odbc') }}: - - ${{ each config in parameters.macosConfigs }}: - - template: /OneBranchPipelines/stages/build-macos-single-stage.yml@self - parameters: - stageName: MacOS_py${{ config.pyVer }} - jobName: BuildWheel - # Convert pyVer '310' → pythonVersion '3.10' - pythonVersion: ${{ format('{0}.{1}', substring(config.pyVer, 0, 1), substring(config.pyVer, 1, 2)) }} - shortPyVer: ${{ config.pyVer }} - oneBranchType: '${{ variables.effectiveOneBranchType }}' - # Phase 2: install the external ODBC wheel before pytest (bundled driver removed). - odbcDependsOn: - - ConsolidateOdbc - installOdbcWheel: true + - ${{ each config in parameters.macosConfigs }}: + - template: /OneBranchPipelines/stages/build-macos-single-stage.yml@self + parameters: + stageName: MacOS_py${{ config.pyVer }} + jobName: BuildWheel + # Convert pyVer '310' → pythonVersion '3.10' + pythonVersion: ${{ format('{0}.{1}', substring(config.pyVer, 0, 1), substring(config.pyVer, 1, 2)) }} + shortPyVer: ${{ config.pyVer }} + oneBranchType: '${{ variables.effectiveOneBranchType }}' + # Phase 2: install the external ODBC wheel before pytest (bundled driver removed). + odbcDependsOn: + - ConsolidateOdbc + installOdbcWheel: true # ========================= # LINUX BUILD STAGES @@ -482,20 +450,19 @@ extends: # d. Runs pytest against SQL Server # 4. Publishes artifacts (all 5 wheels) # 5. Component Governance + AntiMalware scanning - - ${{ if ne(variables.effectiveBuildPackage, 'mssql-python-odbc') }}: - - ${{ each config in parameters.linuxConfigs }}: - - template: /OneBranchPipelines/stages/build-linux-single-stage.yml@self - parameters: - stageName: Linux_${{ config.tag }}_${{ config.arch }} - jobName: BuildWheels - linuxTag: ${{ config.tag }} - arch: ${{ config.arch }} - dockerPlatform: ${{ config.platform }} - oneBranchType: '${{ variables.effectiveOneBranchType }}' - # Phase 2: install the external ODBC wheel before pytest (bundled driver removed). - odbcDependsOn: - - ConsolidateOdbc - installOdbcWheel: true + - ${{ each config in parameters.linuxConfigs }}: + - template: /OneBranchPipelines/stages/build-linux-single-stage.yml@self + parameters: + stageName: Linux_${{ config.tag }}_${{ config.arch }} + jobName: BuildWheels + linuxTag: ${{ config.tag }} + arch: ${{ config.arch }} + dockerPlatform: ${{ config.platform }} + oneBranchType: '${{ variables.effectiveOneBranchType }}' + # Phase 2: install the external ODBC wheel before pytest (bundled driver removed). + odbcDependsOn: + - ConsolidateOdbc + installOdbcWheel: true # ========================= # CONSOLIDATE STAGE @@ -509,37 +476,36 @@ extends: # - dist/bindings/macOS/*.so (macOS universal2 binaries) # - dist/bindings/Linux/*.so (Linux native extensions) # This stage also runs final BinSkim scan on all binaries - - ${{ if ne(variables.effectiveBuildPackage, 'mssql-python-odbc') }}: - - stage: Consolidate - displayName: 'Consolidate All Artifacts' - dependsOn: - # Windows dependencies (9 stages) - - Win_py310_x64 - - Win_py311_x64 - - Win_py312_x64 - - Win_py313_x64 - - Win_py314_x64 - - Win_py311_arm64 - - Win_py312_arm64 - - Win_py313_arm64 - - Win_py314_arm64 - # macOS dependencies (5 stages) - - MacOS_py310 - - MacOS_py311 - - MacOS_py312 - - MacOS_py313 - - MacOS_py314 - # Linux dependencies (4 stages) - - Linux_manylinux_2_28_x86_64 - - Linux_manylinux_2_28_aarch64 - - Linux_musllinux_x86_64 - - Linux_musllinux_aarch64 - jobs: - - template: /OneBranchPipelines/jobs/consolidate-artifacts-job.yml@self - parameters: - # CRITICAL: Use effectiveOneBranchType to ensure scheduled builds run as 'Official' - # Using parameters.oneBranchType would break scheduled builds (they'd run as 'NonOfficial') - oneBranchType: '${{ variables.effectiveOneBranchType }}' + - stage: Consolidate + displayName: 'Consolidate All Artifacts' + dependsOn: + # Windows dependencies (9 stages) + - Win_py310_x64 + - Win_py311_x64 + - Win_py312_x64 + - Win_py313_x64 + - Win_py314_x64 + - Win_py311_arm64 + - Win_py312_arm64 + - Win_py313_arm64 + - Win_py314_arm64 + # macOS dependencies (5 stages) + - MacOS_py310 + - MacOS_py311 + - MacOS_py312 + - MacOS_py313 + - MacOS_py314 + # Linux dependencies (4 stages) + - Linux_manylinux_2_28_x86_64 + - Linux_manylinux_2_28_aarch64 + - Linux_musllinux_x86_64 + - Linux_musllinux_aarch64 + jobs: + - template: /OneBranchPipelines/jobs/consolidate-artifacts-job.yml@self + parameters: + # CRITICAL: Use effectiveOneBranchType to ensure scheduled builds run as 'Official' + # Using parameters.oneBranchType would break scheduled builds (they'd run as 'NonOfficial') + oneBranchType: '${{ variables.effectiveOneBranchType }}' # Note: Symbol publishing handled directly in Windows build stages # PDB files uploaded to Microsoft Symbol Server for debugging @@ -555,14 +521,11 @@ extends: # wheels contain the SAME ODBC driver binaries already scanned by the build — # no new first-party native code is introduced. # - # UNCONDITIONAL: every buildPackage value needs the ODBC wheels FIRST — - # * 'mssql-python-odbc' ships them, - # * 'mssql-python' installs the matching wheel before its pytest (Phase 2 - # removed the bundled driver, so the mssql-python tests now require the - # external package), - # * 'both' needs both. - # It therefore runs ahead of the mssql-python build stages, which depend on - # ConsolidateOdbc (below) to obtain the wheel. + # ALWAYS BUILT: this pipeline builds both packages, and the mssql-python build + # stages install the matching ODBC wheel before their pytest (Phase 2 removed the + # bundled driver, so the mssql-python tests now require the external package). + # The ODBC wheels are therefore produced FIRST, ahead of the mssql-python build + # stages, which depend on ConsolidateOdbc (below) to obtain the wheel. - template: /OneBranchPipelines/stages/build-odbc-all-stage.yml@self parameters: stageName: ODBC_BuildAll From 897020a714dba838351c318cb462cf8ad0328776 Mon Sep 17 00:00:00 2001 From: Jahnvi Thakkar Date: Fri, 31 Jul 2026 13:50:52 +0530 Subject: [PATCH 07/11] CHORE: remove unused ODBC/wheel-installation-test stage templates --- .../stages/build-odbc-linux-stage.yml | 197 ---------- .../stages/build-odbc-macos-stage.yml | 134 ------- .../stages/build-odbc-windows-stage.yml | 153 -------- .../wheel-installation-test-macos-stage.yml | 211 ----------- .../stages/wheel-installation-test-stage.yml | 342 ------------------ .../wheel-installation-test-windows-stage.yml | 196 ---------- 6 files changed, 1233 deletions(-) delete mode 100644 OneBranchPipelines/stages/build-odbc-linux-stage.yml delete mode 100644 OneBranchPipelines/stages/build-odbc-macos-stage.yml delete mode 100644 OneBranchPipelines/stages/build-odbc-windows-stage.yml delete mode 100644 OneBranchPipelines/stages/wheel-installation-test-macos-stage.yml delete mode 100644 OneBranchPipelines/stages/wheel-installation-test-stage.yml delete mode 100644 OneBranchPipelines/stages/wheel-installation-test-windows-stage.yml diff --git a/OneBranchPipelines/stages/build-odbc-linux-stage.yml b/OneBranchPipelines/stages/build-odbc-linux-stage.yml deleted file mode 100644 index 51da9144b..000000000 --- a/OneBranchPipelines/stages/build-odbc-linux-stage.yml +++ /dev/null @@ -1,197 +0,0 @@ -# ODBC Linux Single Configuration Stage Template -# Builds the platform-specific `mssql-python-odbc` wheel for ONE libc/arch combo -# (manylinux_2_28 or musllinux_1_2 x x86_64 or aarch64). -# -# Data-only package: no native compilation, no per-Python matrix, no pytest. -# A PyPA build container is still used so `platform.libc_ver()` reports the right -# libc and the wheel is tagged manylinux_2_28_* or musllinux_1_2_* correctly. -# aarch64 wheels are produced under QEMU emulation. Driver binaries come from the -# committed mssql_python/libs/ tree that is bind-mounted into the container. -parameters: - # Stage identifier (e.g., 'ODBC_Linux_manylinux_2_28_x86_64'). - - name: stageName - type: string - - name: jobName - type: string - default: 'BuildWheel' - # Distribution type: 'manylinux_2_28' (glibc 2.28+) or 'musllinux' (musl libc). - - name: linuxTag - type: string - # CPU architecture: 'x86_64' (AMD64) or 'aarch64' (ARM64). - - name: arch - type: string - # Docker platform for QEMU emulation: 'linux/amd64' or 'linux/arm64'. - - name: dockerPlatform - type: string - - name: oneBranchType - type: string - default: 'Official' - -stages: - - stage: ${{ parameters.stageName }} - displayName: 'ODBC Linux ${{ parameters.linuxTag }} ${{ parameters.arch }}' - jobs: - - job: ${{ parameters.jobName }} - displayName: 'Build ODBC Wheel - ${{ parameters.linuxTag }} ${{ parameters.arch }}' - # Reuse the same custom 1ES Linux pool as the mssql-python build. - pool: - type: linux - isCustom: true - name: Python-1ES-pool - demands: - - imageOverride -equals PYTHON-1ES-UB2404 - timeoutInMinutes: 60 - - variables: - # BinSkim needs ICU libs not present in manylinux/musllinux containers. - - name: ob_sdl_binskim_enabled - value: false - - name: ob_outputDirectory - value: '$(Build.ArtifactStagingDirectory)' - - name: LinuxContainerImage - value: 'onebranch.azurecr.io/linux/ubuntu-2204:latest' - - name: LINUX_TAG - value: ${{ parameters.linuxTag }} - - name: ARCH - value: ${{ parameters.arch }} - - name: DOCKER_PLATFORM - value: ${{ parameters.dockerPlatform }} - - steps: - - checkout: self - fetchDepth: 1 - - - bash: | - set -e - if ! docker info > /dev/null 2>&1; then - echo "Starting Docker daemon..." - sudo dockerd > docker.log 2>&1 & - sleep 10 - fi - docker --version - displayName: 'Setup and start Docker daemon' - - - script: | - sudo apt-get install -y qemu-user-static - displayName: 'Enable QEMU (for aarch64)' - - - script: | - rm -rf $(ob_outputDirectory)/wheels - mkdir -p $(ob_outputDirectory)/wheels - displayName: 'Prepare artifact directories' - - - task: AzureCLI@2 - displayName: 'Login to ACR (tdslibrs)' - inputs: - azureSubscription: 'Magnitude Test-mssql-rs-mssql-python' - scriptType: 'bash' - scriptLocation: 'inlineScript' - inlineScript: | - az acr login --name tdslibrs - - - script: | - set -euxo pipefail - # Same PyPA images as the mssql-python Linux build (mirrored into ACR). - if [[ "$(LINUX_TAG)" == "manylinux_2_28" ]]; then - IMAGE="tdslibrs.azurecr.io/import/python-build/manylinux_2_28_$(ARCH):latest" - elif [[ "$(LINUX_TAG)" == "musllinux" ]]; then - IMAGE="tdslibrs.azurecr.io/import/python-build/musllinux_1_2_$(ARCH):latest" - else - echo "ERROR: Unsupported LINUX_TAG '$(LINUX_TAG)'. Expected: manylinux_2_28 or musllinux" >&2 - exit 1 - fi - docker run -d --name build-$(LINUX_TAG)-$(ARCH) \ - --platform $(DOCKER_PLATFORM) \ - -v $(Build.SourcesDirectory):/workspace \ - -w /workspace \ - $IMAGE tail -f /dev/null - displayName: 'Start $(LINUX_TAG) $(ARCH) container' - - # Build the single py3-none wheel inside the container. Any CPython works; no - # compilation runs. setup_odbc.py's get_platform_info() auto-detects musl via - # platform.libc_ver(): on musllinux images it ALWAYS emits musllinux_1_2_ - # and ignores MANYLINUX_TAG. MANYLINUX_TAG is only consulted on glibc images, so - # hardcoding "manylinux_2_28" below is correct for both image families. - - script: | - set -euxo pipefail - if [[ "$(LINUX_TAG)" == "manylinux_2_28" ]]; then SHELL_EXE=bash; else SHELL_EXE=sh; fi - docker exec \ - -e targetArch="$(ARCH)" \ - -e MANYLINUX_TAG="manylinux_2_28" \ - build-$(LINUX_TAG)-$(ARCH) $SHELL_EXE -lc ' - set -eux - # Pick any available CPython from the PyPA image. - PY=$(ls -d /opt/python/cp312-cp312/bin/python 2>/dev/null || ls -d /opt/python/cp3*/bin/python | head -1) - echo "Using: $($PY --version)" - $PY -m pip install -q -U pip setuptools wheel twine - cd /workspace - $PY setup_odbc.py bdist_wheel - $PY -m twine check /workspace/dist/*.whl - echo "Produced wheels:"; ls -lh /workspace/dist/*.whl - ' - displayName: 'Build ODBC wheel in container' - - - script: | - set -euxo pipefail - cp -v $(Build.SourcesDirectory)/dist/*.whl $(ob_outputDirectory)/wheels/ - displayName: 'Stage wheel artifact' - - # Guard against the .gitignore '*.so' trap or a silent sync_libs() skip - # shipping a data wheel with NO driver binary (twine check would still pass). - # The driver filename contains 'msodbcsql' on every platform. - - script: | - set -eu - wheel_dir="$(ob_outputDirectory)/wheels" - matches=$(ls "$wheel_dir"/*.whl 2>/dev/null || true) - if [ -z "$matches" ]; then - echo "ERROR: no wheels found to verify in $wheel_dir" >&2 - exit 1 - fi - for whl in $matches; do - echo "Verifying ODBC driver binary present in: $whl" - entries=$(python3 -m zipfile -l "$whl") - if ! echo "$entries" | grep -qi 'msodbcsql'; then - echo "ERROR: ODBC driver binary (msodbcsql*) missing from $whl" >&2 - exit 1 - fi - # The msodbcsql driver loads the ODBC installer lib at runtime; assert - # the companion is packaged too, not just the driver itself. - if ! echo "$entries" | grep -qi 'libodbcinst'; then - echo "ERROR: ODBC installer lib (libodbcinst*) missing from $whl" >&2 - exit 1 - fi - echo "OK: $whl contains the ODBC driver binary and libodbcinst" - done - displayName: 'Assert wheel contains ODBC driver binary' - - - script: | - docker rm -f build-$(LINUX_TAG)-$(ARCH) || true - displayName: 'Stop and remove build container' - condition: always() - - - task: PublishPipelineArtifact@1 - displayName: 'Publish ODBC Linux Artifact' - inputs: - targetPath: '$(ob_outputDirectory)' - artifact: 'drop_${{ parameters.stageName }}_${{ parameters.jobName }}' - publishLocation: 'pipeline' - - - template: /OneBranchPipelines/steps/malware-scanning-step.yml@self - parameters: - scanPath: '$(ob_outputDirectory)' - artifactType: 'pkg' - - - ${{ if eq(parameters.oneBranchType, 'Official') }}: - - task: EsrpMalwareScanning@5 - displayName: 'ESRP MalwareScanning - ODBC Wheel (Official)' - inputs: - ConnectedServiceName: '$(SigningEsrpConnectedServiceName)' - AppRegistrationClientId: '$(SigningAppRegistrationClientId)' - AppRegistrationTenantId: '$(SigningAppRegistrationTenantId)' - EsrpClientId: '$(SigningEsrpClientId)' - UseMSIAuthentication: true - FolderPath: '$(ob_outputDirectory)/wheels' - Pattern: '*.whl' - SessionTimeout: 60 - CleanupTempStorage: 1 - VerboseLogin: 1 diff --git a/OneBranchPipelines/stages/build-odbc-macos-stage.yml b/OneBranchPipelines/stages/build-odbc-macos-stage.yml deleted file mode 100644 index 7378bc868..000000000 --- a/OneBranchPipelines/stages/build-odbc-macos-stage.yml +++ /dev/null @@ -1,134 +0,0 @@ -# ODBC macOS Single Configuration Stage Template -# Builds the universal2 `mssql-python-odbc` wheel (arm64 + x86_64 driver binaries in -# one wheel). -# -# Data-only package: no native compilation, no per-Python matrix, no pytest. -# `setup_odbc.py` returns the macosx_15_0_universal2 platform tag and copies both -# macOS arch subtrees from mssql_python/libs, so a single build serves both slices. -parameters: - # Stage identifier (e.g., 'ODBC_MacOS_universal2'). - - name: stageName - type: string - - name: jobName - type: string - default: 'BuildWheel' - - name: oneBranchType - type: string - default: 'Official' - - name: pythonVersion - type: string - default: '3.12' - -stages: - - stage: ${{ parameters.stageName }} - displayName: 'ODBC macOS Universal2' - jobs: - - job: ${{ parameters.jobName }} - displayName: 'Build ODBC Wheel - macOS Universal2' - # macOS pools declare as 'linux' type (Azure Pipelines quirk). - pool: - type: linux - isCustom: true - name: Azure Pipelines - vmImage: 'macOS-14' - timeoutInMinutes: 60 - - variables: - # BinSkim targets PE binaries; macOS uses Mach-O, so disable it here. - - name: ob_sdl_binskim_enabled - value: false - - name: ob_outputDirectory - value: '$(Build.ArtifactStagingDirectory)' - # OneBranch-required variable (unused on macOS stages). - - name: LinuxContainerImage - value: 'onebranch.azurecr.io/linux/ubuntu-2204:latest' - - steps: - - checkout: self - fetchDepth: 1 - - - task: UsePythonVersion@0 - inputs: - versionSpec: '${{ parameters.pythonVersion }}' - addToPath: true - displayName: 'Use Python ${{ parameters.pythonVersion }}' - - - script: | - set -e - python -m pip install --upgrade pip - python -m pip install setuptools wheel build twine - displayName: 'Install build tooling' - - # get_platform_info() forces the macosx_15_0_universal2 tag; sync_libs() copies - # both arm64 + x86_64 driver subtrees, so no post-build retag is required. - - script: | - set -e - python setup_odbc.py bdist_wheel - echo "Produced wheels:"; ls -lh dist/*.whl - displayName: 'Build ODBC wheel (universal2)' - - - script: | - set -e - python -m twine check dist/*.whl - displayName: 'twine check wheel' - - - task: CopyFiles@2 - inputs: - SourceFolder: '$(Build.SourcesDirectory)/dist' - Contents: '*.whl' - TargetFolder: '$(ob_outputDirectory)/wheels' - displayName: 'Stage wheel artifact' - - # Guard against a data wheel that packaged no driver binary (twine check - # would still pass). The driver filename contains 'msodbcsql' on every platform. - - script: | - set -eu - wheel_dir="$(ob_outputDirectory)/wheels" - matches=$(ls "$wheel_dir"/*.whl 2>/dev/null || true) - if [ -z "$matches" ]; then - echo "ERROR: no wheels found to verify in $wheel_dir" >&2 - exit 1 - fi - for whl in $matches; do - echo "Verifying ODBC driver binary present in: $whl" - entries=$(python -m zipfile -l "$whl") - if ! echo "$entries" | grep -qi 'msodbcsql'; then - echo "ERROR: ODBC driver binary (msodbcsql*) missing from $whl" >&2 - exit 1 - fi - # The msodbcsql driver loads the ODBC installer lib at runtime; assert - # the companion is packaged too, not just the driver itself. - if ! echo "$entries" | grep -qi 'libodbcinst'; then - echo "ERROR: ODBC installer lib (libodbcinst*) missing from $whl" >&2 - exit 1 - fi - echo "OK: $whl contains the ODBC driver binary and libodbcinst" - done - displayName: 'Assert wheel contains ODBC driver binary' - - - task: PublishPipelineArtifact@1 - displayName: 'Publish ODBC macOS Artifact' - inputs: - targetPath: '$(ob_outputDirectory)' - artifact: 'drop_${{ parameters.stageName }}_${{ parameters.jobName }}' - publishLocation: 'pipeline' - - - template: /OneBranchPipelines/steps/malware-scanning-step.yml@self - parameters: - scanPath: '$(ob_outputDirectory)' - artifactType: 'pkg' - - - ${{ if eq(parameters.oneBranchType, 'Official') }}: - - task: EsrpMalwareScanning@5 - displayName: 'ESRP MalwareScanning - ODBC Wheel (Official)' - inputs: - ConnectedServiceName: '$(SigningEsrpConnectedServiceName)' - AppRegistrationClientId: '$(SigningAppRegistrationClientId)' - AppRegistrationTenantId: '$(SigningAppRegistrationTenantId)' - EsrpClientId: '$(SigningEsrpClientId)' - UseMSIAuthentication: true - FolderPath: '$(ob_outputDirectory)/wheels' - Pattern: '*.whl' - SessionTimeout: 60 - CleanupTempStorage: 1 - VerboseLogin: 1 diff --git a/OneBranchPipelines/stages/build-odbc-windows-stage.yml b/OneBranchPipelines/stages/build-odbc-windows-stage.yml deleted file mode 100644 index 3fc9903b8..000000000 --- a/OneBranchPipelines/stages/build-odbc-windows-stage.yml +++ /dev/null @@ -1,153 +0,0 @@ -# ODBC Windows Single Configuration Stage Template -# Builds the platform-specific `mssql-python-odbc` wheel for ONE Windows architecture. -# -# Unlike the mssql-python build, this package ships ONLY pre-built ODBC driver -# binaries (data) — there is NO compiled Python extension. A single -# `py3-none-` wheel therefore serves every supported Python version -# (3.10+), so this stage does NOT use a per-Python-version matrix and does NOT -# run pytest against SQL Server. The driver binaries are already committed under -# `mssql_python/libs/`, so `checkout: self` is all that is needed; `setup_odbc.py` -# packages the current platform's subtree. -parameters: - # Stage identifier (e.g., 'ODBC_Win_x64'). - - name: stageName - type: string - # Job identifier within the stage. - - name: jobName - type: string - default: 'BuildWheel' - # Target architecture: 'x64' (AMD64) or 'arm64' (ARM64). - - name: architecture - type: string - # OneBranch build type: 'Official' (production) or 'NonOfficial' (dev/test). - - name: oneBranchType - type: string - default: 'Official' - # Any supported interpreter can produce the Python-agnostic wheel. - - name: pythonVersion - type: string - default: '3.12' - -stages: - - stage: ${{ parameters.stageName }} - displayName: 'ODBC Windows ${{ parameters.architecture }}' - jobs: - - job: ${{ parameters.jobName }} - displayName: 'Build ODBC Wheel - Windows ${{ parameters.architecture }}' - # Reuse the same custom 1ES Windows pool as the mssql-python build. - pool: - type: windows - isCustom: true - name: Python-1ES-pool - demands: - - imageOverride -equals PYTHON-1ES-MMS2022 - timeoutInMinutes: 60 - - variables: - # OneBranch output directory for artifacts. - ob_outputDirectory: '$(Build.ArtifactStagingDirectory)' - # OneBranch-required variable (unused on Windows stages). - LinuxContainerImage: 'onebranch.azurecr.io/linux/ubuntu-2204:latest' - - steps: - # Driver binaries live in mssql_python/libs (committed); shallow checkout is enough. - - checkout: self - fetchDepth: 1 - - # The ARM64 wheel is pure data, so it can be produced on an x64 host — there is - # no native compilation and hence no ARM64 python.lib cross-compile machinery. - - task: UsePythonVersion@0 - inputs: - versionSpec: '${{ parameters.pythonVersion }}' - architecture: 'x64' - addToPath: true - displayName: 'Use Python ${{ parameters.pythonVersion }}' - - - powershell: | - $ErrorActionPreference = "Stop" - python -m pip install --upgrade pip - python -m pip install setuptools wheel build twine - displayName: 'Install build tooling' - - # Build the platform-specific, Python-agnostic wheel. ARCHITECTURE controls the - # platform tag (win_amd64 / win_arm64). No native compilation happens here — - # setup_odbc.py only packages the driver binaries from mssql_python/libs. - - powershell: | - $ErrorActionPreference = "Stop" - $env:ARCHITECTURE = "${{ parameters.architecture }}" - python setup_odbc.py bdist_wheel - Write-Host "Produced wheels:" - Get-ChildItem dist\*.whl | ForEach-Object { Write-Host " - $($_.Name)" } - displayName: 'Build ODBC wheel (ARCHITECTURE=${{ parameters.architecture }})' - - - powershell: | - $ErrorActionPreference = "Stop" - python -m twine check dist\*.whl - displayName: 'twine check wheel' - - - task: CopyFiles@2 - inputs: - SourceFolder: '$(Build.SourcesDirectory)\dist' - Contents: '*.whl' - TargetFolder: '$(ob_outputDirectory)\wheels' - displayName: 'Stage wheel artifact' - - # Guard against a data wheel that packaged an incomplete driver set (twine - # check would still pass). Assert BOTH the driver DLL (msodbcsql*) and the - # Windows auth component (mssql-auth.dll): the native loader treats the - # external package as COMPLETE on Windows only when mssql-auth.dll is - # co-located with the driver - without it the loader silently falls back to - # bundled libs (or fails), which the Linux-only install test cannot catch. - - powershell: | - $ErrorActionPreference = "Stop" - $wheels = Get-ChildItem -Path "$(ob_outputDirectory)\wheels" -Filter "*.whl" - if ($wheels.Count -eq 0) { - Write-Error "No wheels found to verify in $(ob_outputDirectory)\wheels" - exit 1 - } - Add-Type -AssemblyName System.IO.Compression.FileSystem - foreach ($whl in $wheels) { - $zip = [System.IO.Compression.ZipFile]::OpenRead($whl.FullName) - $names = $zip.Entries | ForEach-Object { $_.FullName } - $zip.Dispose() - if (-not ($names | Where-Object { $_ -match 'msodbcsql' })) { - Write-Error "ODBC driver binary (msodbcsql*) missing from $($whl.Name)" - exit 1 - } - if (-not ($names | Where-Object { $_ -match 'mssql-auth\.dll' })) { - Write-Error "Windows auth component (mssql-auth.dll) missing from $($whl.Name) - loader would not treat the external package as complete" - exit 1 - } - Write-Host "OK: $($whl.Name) contains the ODBC driver binary and mssql-auth.dll" - } - displayName: 'Assert wheel contains ODBC driver binary' - - # OneBranch requires artifact naming: drop__. - - task: PublishPipelineArtifact@1 - displayName: 'Publish ODBC Windows Artifact' - inputs: - targetPath: '$(ob_outputDirectory)' - artifact: 'drop_${{ parameters.stageName }}_${{ parameters.jobName }}' - publishLocation: 'pipeline' - - # Component Governance + OneBranch AntiMalware notification. - - template: /OneBranchPipelines/steps/malware-scanning-step.yml@self - parameters: - scanPath: '$(ob_outputDirectory)' - artifactType: 'pkg' - - # Scan the redistributed driver binaries + wheel for malware (Official only). - - ${{ if eq(parameters.oneBranchType, 'Official') }}: - - task: EsrpMalwareScanning@5 - displayName: 'ESRP MalwareScanning - ODBC Wheel (Official)' - inputs: - ConnectedServiceName: '$(SigningEsrpConnectedServiceName)' - AppRegistrationClientId: '$(SigningAppRegistrationClientId)' - AppRegistrationTenantId: '$(SigningAppRegistrationTenantId)' - EsrpClientId: '$(SigningEsrpClientId)' - UseMSIAuthentication: true - FolderPath: '$(ob_outputDirectory)/wheels' - Pattern: '*.whl' - SessionTimeout: 60 - CleanupTempStorage: 1 - VerboseLogin: 1 diff --git a/OneBranchPipelines/stages/wheel-installation-test-macos-stage.yml b/OneBranchPipelines/stages/wheel-installation-test-macos-stage.yml deleted file mode 100644 index 0c324df23..000000000 --- a/OneBranchPipelines/stages/wheel-installation-test-macos-stage.yml +++ /dev/null @@ -1,211 +0,0 @@ -# ========================================================================================= -# End-User "Both Wheels Together" Installation Test - macOS -# ========================================================================================= -# macOS counterpart of wheel-installation-test-stage.yml (which is Docker/Linux-only). -# Installs the ACTUAL published mssql-python (universal2) + mssql-python-odbc (universal2) -# wheels into a fresh venv, runs the full pytest suite (bundled libs intact, external -# package already preferred by GetOdbcLibsBaseDir), then DELETES the bundled -# mssql_python/libs and connects once more in a fresh process. With the bundled fallback -# gone, a successful live query can ONLY be using the driver from the separately installed -# mssql_python_odbc package. This also exercises the universal2 dylib @loader_path/rpath -# resolution from the external package. -# -# Runs natively on a macOS-15 + Colima/Docker SQL Server agent. macOS 15 is required -# because the universal2 wheels are retagged to a macosx_15_0 minimum deployment target. -# A single representative Python version is sufficient: the external package RESOLUTION -# path (GetOdbcLibsBaseDir in ddbc_bindings.cpp) is Python-version independent; per-version -# native builds are already covered by the macOS build stages. -# ========================================================================================= -parameters: - # Stage identifier (e.g., 'TestBothWheels_macOS_universal2') - - name: stageName - type: string - # Job identifier within the stage - - name: jobName - type: string - default: 'InstallAndTest' - # Python version in X.Y format used to select the mssql-python wheel - - name: pythonVersion - type: string - default: '3.12' - # Python version as 3-digit string (e.g., '312') matching the cp wheel tag - - name: shortPyVer - type: string - default: '312' - # OneBranch build type: 'Official' or 'NonOfficial' - - name: oneBranchType - type: string - default: 'Official' - -stages: - - stage: ${{ parameters.stageName }} - displayName: 'Test Both Wheels macOS universal2' - # Only meaningful when both consolidated artifacts exist in this run. - dependsOn: - - Consolidate - - ConsolidateOdbc - jobs: - - job: ${{ parameters.jobName }} - displayName: 'Install both wheels + pytest - macOS universal2' - - # macOS-15 (Sequoia). type:linux is an Azure Pipelines quirk - macOS pools - # declare as 'linux' type. The build stage retags the universal2 wheels to - # macosx_15_0 (min deployment target), so pip only accepts them on macOS 15+. - # The build stage itself runs on macOS-14 because it tests the freshly built - # .so directly (never pip-installs the retagged wheel); this install test does - # pip-install it, so it MUST run on macOS 15+ or pip rejects the wheel with - # "not a supported wheel on this platform". - pool: - type: linux - isCustom: true - name: Azure Pipelines - vmImage: 'macOS-15' - timeoutInMinutes: 120 - - variables: - # BinSkim is Windows-PE focused; macOS uses Mach-O. - - name: ob_sdl_binskim_enabled - value: false - - name: ob_outputDirectory - value: '$(Build.ArtifactStagingDirectory)' - # OneBranch-required variable (unused on macOS agents). - - name: LinuxContainerImage - value: 'onebranch.azurecr.io/linux/ubuntu-2204:latest' - - steps: - - checkout: self - fetchDepth: 1 - - - task: UsePythonVersion@0 - inputs: - versionSpec: '${{ parameters.pythonVersion }}' - addToPath: true - displayName: 'Use Python ${{ parameters.pythonVersion }}' - - # Download the two CONSOLIDATED wheel artifacts produced earlier in this run. - - task: DownloadPipelineArtifact@2 - displayName: 'Download consolidated mssql-python wheels' - inputs: - buildType: 'current' - artifactName: 'drop_Consolidate_ConsolidateArtifacts' - targetPath: '$(Pipeline.Workspace)/mssql-python-wheels' - - - task: DownloadPipelineArtifact@2 - displayName: 'Download consolidated mssql-python-odbc wheels' - inputs: - buildType: 'current' - artifactName: 'drop_ConsolidateOdbc_ConsolidateArtifacts' - targetPath: '$(Pipeline.Workspace)/mssql-python-odbc-wheels' - - # Install Docker CLI + Colima (macOS Docker runtime) and start a SQL Server - # 2022 container on the host loopback (localhost:1433), same as the build stage. - - script: | - brew update - brew install docker colima - colima start --vm-type vz --cpu 4 --memory 8 || { - echo "vz VM failed, trying qemu..." - colima start --vm-type qemu --cpu 4 --memory 8 - } - sleep 30 - docker context use colima >/dev/null || true - docker version - displayName: 'Install and start Docker (Colima)' - timeoutInMinutes: 15 - - - script: | - set -euxo pipefail - docker pull mcr.microsoft.com/mssql/server:2022-latest - docker run --name sqlserver \ - -e ACCEPT_EULA=Y \ - -e MSSQL_SA_PASSWORD="${DB_PASSWORD}" \ - -p 1433:1433 -d \ - mcr.microsoft.com/mssql/server:2022-latest - echo "Waiting for SQL Server to be ready..." - for i in {1..30}; do - # sqlcmd runs INSIDE the mssql container; localhost is the container's own - # loopback (a readiness probe), not debug code reaching a dev box. - if docker exec sqlserver /opt/mssql-tools18/bin/sqlcmd \ - -S localhost -U SA -P "$DB_PASSWORD" -C -Q "SELECT 1" >/dev/null 2>&1; then # DevSkim: ignore DS162092 - echo "SQL Server is ready"; break - fi - sleep 2 - done - displayName: 'Start SQL Server (Docker)' - env: - DB_PASSWORD: $(DB_PASSWORD) - - - script: | - set -euo pipefail - - PYTAG="cp${{ parameters.shortPyVer }}" - - # Select the universal2 wheels matching THIS Python version (mssql-python) - # and the data-only odbc package (py3-none-macosx-universal2). - MP_WHEEL=$(ls "$(Pipeline.Workspace)"/mssql-python-wheels/dist/mssql_python-*${PYTAG}-*macosx*universal2*.whl 2>/dev/null | head -1) - if [ -z "$MP_WHEEL" ]; then echo "ERROR: no mssql-python wheel for ${PYTAG}/macosx universal2"; exit 1; fi - ODBC_WHEEL=$(ls "$(Pipeline.Workspace)"/mssql-python-odbc-wheels/dist/mssql_python_odbc-*macosx*universal2*.whl 2>/dev/null | head -1) - if [ -z "$ODBC_WHEEL" ]; then echo "ERROR: no mssql-python-odbc wheel for macosx universal2"; exit 1; fi - - # Fresh isolated venv = clean end-user machine. - TEST_DIR="$(Agent.TempDirectory)/enduser_${PYTAG}" - rm -rf "$TEST_DIR" - python3 -m venv "$TEST_DIR" - VENV_PY="$TEST_DIR/bin/python" - - # PYTHONSAFEPATH so the repo checkout can never shadow the installed wheel. - export PYTHONSAFEPATH=1 - - "$VENV_PY" -m pip install -q -U pip - - # Install the odbc wheel FIRST so mssql-python's pinned mssql-python-odbc==X - # requirement is satisfied from this LOCAL wheel (pip never reaches PyPI). - echo "Installing mssql-python-odbc: $ODBC_WHEEL" - "$VENV_PY" -m pip install -q "$ODBC_WHEEL" - echo "Installing mssql-python: $MP_WHEEL" - "$VENV_PY" -m pip install -q "$MP_WHEEL" - - # Show where the external ODBC package lives (the split target). - "$VENV_PY" -c "import mssql_python_odbc, os; print('mssql_python_odbc libs:', mssql_python_odbc.get_libs_dir()); assert os.path.isdir(mssql_python_odbc.get_libs_dir())" - - # POSITIVE assertion: prove the native loader WILL resolve the driver from - # the external mssql_python_odbc package (mirrors GetOdbcLibsBaseDir). - "$VENV_PY" -c "import mssql_python.ddbc_bindings as d, mssql_python_odbc, os; base=os.path.dirname(mssql_python_odbc.__file__); drv=d.GetDriverPathCpp(base); print('external driver path:', drv); assert os.path.exists(drv), 'external ODBC driver not found - loader would NOT select external package: '+drv; print('POSITIVE: loader will resolve the driver from the external mssql_python_odbc package')" - - # PHASE 1: run the FULL existing suite with bundled libs INTACT. Copy the - # suite OUT of the source tree so collection imports the INSTALLED package. - cp -r "$(Build.SourcesDirectory)/tests" "$TEST_DIR/tests" - cp "$(Build.SourcesDirectory)/pytest.ini" "$TEST_DIR/pytest.ini" - "$VENV_PY" -m pip install -q pytest - if [ -f "$(Build.SourcesDirectory)/requirements.txt" ]; then - "$VENV_PY" -m pip install -q -r "$(Build.SourcesDirectory)/requirements.txt" - fi - cd "$TEST_DIR" - # 127.0.0.1 targets the CI SQL Server 2022 container on the agent loopback (a - # test fixture), not debug code reaching a real host. - export DB_CONNECTION_STRING="Server=tcp:127.0.0.1,1433;Database=master;Uid=SA;Pwd=$DB_PASSWORD;TrustServerCertificate=yes" # DevSkim: ignore DS162092 - "$VENV_PY" -m pytest "$TEST_DIR/tests" -v --maxfail=1 -m "not stress" - echo "Full suite passed for ${PYTAG} with both wheels installed (external driver preferred)" - - # PHASE 2 - DECISIVE negative proof: remove the ODBC driver that - # mssql_python still bundles, then connect again in a FRESH process. - MP_LIBS=$("$VENV_PY" -c "import mssql_python, os; print(os.path.join(os.path.dirname(mssql_python.__file__), 'libs'))") - case "$MP_LIBS" in - "$(Build.SourcesDirectory)"/*) - echo "ERROR: resolved mssql_python to the source tree ($MP_LIBS), not the installed wheel"; exit 1 ;; - esac - if [ -d "$MP_LIBS" ]; then - echo "Removing bundled mssql_python/libs to force use of external ODBC package: $MP_LIBS" - rm -rf "$MP_LIBS" - else - echo "Note: mssql_python has no bundled libs/ (already split at build time)" - fi - - "$VENV_PY" -c "import mssql_python; print('mssql_python', mssql_python.__version__, 'imported OK (bundled libs removed)')" - # 127.0.0.1 targets the CI SQL Server 2022 container on the agent loopback (a - # test fixture), not debug code reaching a real host. - export DB_CONNECTION_STRING="Server=tcp:127.0.0.1,1433;Database=master;Uid=SA;Pwd=$DB_PASSWORD;TrustServerCertificate=yes" # DevSkim: ignore DS162092 - "$VENV_PY" -c "import mssql_python, os; c=mssql_python.connect(os.environ['DB_CONNECTION_STRING']); cur=c.cursor(); cur.execute('SELECT 1'); print('connection OK via EXTERNAL driver:', cur.fetchone()); c.close()" - echo "DECISIVE: connected with bundled libs removed - driver resolved from external mssql_python_odbc package" - displayName: 'Install both wheels + full pytest + external-only proof' - env: - DB_PASSWORD: $(DB_PASSWORD) diff --git a/OneBranchPipelines/stages/wheel-installation-test-stage.yml b/OneBranchPipelines/stages/wheel-installation-test-stage.yml deleted file mode 100644 index 1fe0c1a62..000000000 --- a/OneBranchPipelines/stages/wheel-installation-test-stage.yml +++ /dev/null @@ -1,342 +0,0 @@ -# ========================================================================================= -# End-User "Both Wheels Together" Installation Test Stage -# ========================================================================================= -# Validates the mssql-python / mssql-python-odbc PACKAGE SPLIT from an end-user's -# perspective, using the ACTUAL published wheel artifacts (not raw build binaries): -# -# pip install mssql_python---.whl -# pip install mssql_python_odbc--py3-none-.whl -# -# The proof is both POSITIVE and NEGATIVE: -# * POSITIVE: before touching anything we assert that the native loader WILL pick -# the external package - GetDriverPathCpp(dir(mssql_python_odbc)) points at an -# existing driver file, which is exactly the condition GetOdbcLibsBaseDir uses -# to treat the external package as authoritative. -# * NEGATIVE: we then DELETE the ODBC driver binaries that mssql-python still -# bundles under `mssql_python/libs/`. With the bundled fallback removed, a -# successful connection + passing test suite proves the driver was resolved -# from the separately-installed `mssql_python_odbc` package. -# (see GetOdbcLibsBaseDir in ddbc_bindings.cpp, which prefers the external package -# and falls back to bundled libs). Without the deletion the bundled fallback would -# mask a broken split and the test would pass even if the ODBC package were ignored. -# -# Scope: Linux manylinux_2_28 + musllinux (x86_64), all supported Python versions. -# This stage tests the INSTALL/RESOLUTION path, not per-arch compilation (already -# covered by the build stages). It runs only when BOTH packages are built in the -# same run (buildPackage = 'both'), because it needs both consolidated artifacts. -# ========================================================================================= -parameters: - # Stage identifier (e.g., 'TestBothWheels_manylinux_2_28_x86_64') - - name: stageName - type: string - # Job identifier within the stage - - name: jobName - type: string - default: 'InstallAndTest' - # Linux distribution type: 'manylinux_2_28' (glibc 2.28+) or 'musllinux' (musl libc) - - name: linuxTag - type: string - # CPU architecture (test stage targets x86_64 only) - - name: arch - type: string - default: 'x86_64' - # Docker platform for the build/test container - - name: dockerPlatform - type: string - default: 'linux/amd64' - # OneBranch build type: 'Official' or 'NonOfficial' - - name: oneBranchType - type: string - default: 'Official' - -stages: - - stage: ${{ parameters.stageName }} - displayName: 'Test Both Wheels ${{ parameters.linuxTag }} ${{ parameters.arch }}' - # Only meaningful when both consolidated artifacts exist in this run. - dependsOn: - - Consolidate - - ConsolidateOdbc - jobs: - - job: ${{ parameters.jobName }} - displayName: 'Install both wheels + pytest - ${{ parameters.linuxTag }} ${{ parameters.arch }}' - - pool: - type: linux - isCustom: true - name: Python-1ES-pool - demands: - - imageOverride -equals PYTHON-1ES-UB2404 - timeoutInMinutes: 90 - - variables: - - name: ob_sdl_binskim_enabled - value: false - - name: ob_outputDirectory - value: '$(Build.ArtifactStagingDirectory)' - - name: LinuxContainerImage - value: 'onebranch.azurecr.io/linux/ubuntu-2204:latest' - - name: LINUX_TAG - value: ${{ parameters.linuxTag }} - - name: ARCH - value: ${{ parameters.arch }} - - name: DOCKER_PLATFORM - value: ${{ parameters.dockerPlatform }} - - steps: - - checkout: self - fetchDepth: 1 - - # Download the two CONSOLIDATED wheel artifacts produced earlier in this run: - # drop_Consolidate_ConsolidateArtifacts -> mssql-python wheels (all platforms/pyvers) - # drop_ConsolidateOdbc_ConsolidateArtifacts -> mssql-python-odbc wheels (7 platforms) - - task: DownloadPipelineArtifact@2 - displayName: 'Download consolidated mssql-python wheels' - inputs: - buildType: 'current' - artifactName: 'drop_Consolidate_ConsolidateArtifacts' - targetPath: '$(Pipeline.Workspace)/mssql-python-wheels' - - - task: DownloadPipelineArtifact@2 - displayName: 'Download consolidated mssql-python-odbc wheels' - inputs: - buildType: 'current' - artifactName: 'drop_ConsolidateOdbc_ConsolidateArtifacts' - targetPath: '$(Pipeline.Workspace)/mssql-python-odbc-wheels' - - - bash: | - set -euxo pipefail - if ! docker info > /dev/null 2>&1; then - echo "Docker daemon not running, starting it..." - sudo dockerd > docker.log 2>&1 & - sleep 10 - fi - docker --version - displayName: 'Ensure Docker daemon is running' - - - task: AzureCLI@2 - displayName: 'Login to ACR (tdslibrs)' - inputs: - azureSubscription: 'Magnitude Test-mssql-rs-mssql-python' - scriptType: 'bash' - scriptLocation: 'inlineScript' - inlineScript: | - az acr login --name tdslibrs - - - script: | - set -euxo pipefail - if [[ "$(LINUX_TAG)" == "manylinux_2_28" ]]; then - IMAGE="tdslibrs.azurecr.io/import/python-build/manylinux_2_28_$(ARCH):latest" - elif [[ "$(LINUX_TAG)" == "musllinux" ]]; then - IMAGE="tdslibrs.azurecr.io/import/python-build/musllinux_1_2_$(ARCH):latest" - else - echo "ERROR: Unsupported LINUX_TAG '$(LINUX_TAG)'" >&2 - exit 1 - fi - - # Mount the repo and downloaded wheels read-only. Read-only /workspace is - # deliberate: this stage must exercise the INSTALLED wheels, never the - # source checkout, so nothing here should ever write into the repo tree. - docker run -d --name test-$(LINUX_TAG)-$(ARCH) \ - --platform $(DOCKER_PLATFORM) \ - -v $(Build.SourcesDirectory):/workspace:ro \ - -v $(Pipeline.Workspace)/mssql-python-wheels:/wheels/mssql-python:ro \ - -v $(Pipeline.Workspace)/mssql-python-odbc-wheels:/wheels/mssql-python-odbc:ro \ - -w /workspace \ - $IMAGE \ - tail -f /dev/null - displayName: 'Start $(LINUX_TAG) $(ARCH) test container' - - # Install the ODBC driver's runtime SYSTEM dependencies INTO the test - # container. msodbcsql18 links MIT Kerberos (libkrb5.so.3 / - # libgssapi_krb5.so.2) as a system dependency - these are never bundled in - # the wheel (the driver's RUNPATH is only $ORIGIN, which resolves the - # co-located libodbcinst.so.2 but NOT krb5). The manylinux_2_28 base image - # ships krb5-libs by default, but the minimal musllinux/Alpine image does - # NOT, so without this the driver's dlopen fails with - # "Error loading shared library libkrb5.so.3: No such file or directory" and, - # because that throw happens inside std::call_once on musl, the process - # aborts (core dump) at import instead of raising a clean ImportError. This - # mirrors the krb5-libs install already performed by - # build-linux-single-stage.yml and reflects the real runtime requirement for - # Alpine end users. - - script: | - set -euxo pipefail - if [[ "$(LINUX_TAG)" == "musllinux" ]]; then - docker exec test-$(LINUX_TAG)-$(ARCH) sh -lc 'apk add --no-cache krb5-libs keyutils-libs' - else - docker exec test-$(LINUX_TAG)-$(ARCH) bash -lc 'dnf -y install krb5-libs keyutils-libs || yum -y install krb5-libs keyutils-libs || true' - fi - displayName: 'Install ODBC driver runtime deps (krb5) in $(LINUX_TAG) container' - - # Start a SQL Server 2022 container on the host so pytest has a live server. - - script: | - set -euxo pipefail - docker run -d --name sqlserver-$(LINUX_TAG)-$(ARCH) \ - --platform linux/amd64 \ - -e ACCEPT_EULA=Y \ - -e MSSQL_SA_PASSWORD="$(DB_PASSWORD)" \ - -p 1433:1433 \ - mcr.microsoft.com/mssql/server:2022-latest - - echo "Waiting for SQL Server to be ready..." - for i in {1..30}; do - if docker exec sqlserver-$(LINUX_TAG)-$(ARCH) /opt/mssql-tools18/bin/sqlcmd \ - -S localhost -U SA -P "$(DB_PASSWORD)" -C -Q "SELECT 1" >/dev/null 2>&1; then # DevSkim: ignore DS162092 - echo "SQL Server is ready" - break - fi - sleep 2 - done - - SQL_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' sqlserver-$(LINUX_TAG)-$(ARCH)) - echo "SQL Server IP: $SQL_IP" - echo "##vso[task.setvariable variable=SQL_IP]$SQL_IP" - displayName: 'Start SQL Server container for testing' - env: - DB_PASSWORD: $(DB_PASSWORD) - - # For each Python version: install BOTH wheels, remove the bundled ODBC - # driver from the installed mssql_python package, then run pytest. A pass - # proves the driver was loaded from the separate mssql_python_odbc package. - - script: | - set -euxo pipefail - if [[ "$(LINUX_TAG)" == "manylinux_2_28" ]]; then SHELL_EXE=bash; else SHELL_EXE=sh; fi - - # Count how many Python versions actually ran the full test so the stage - # cannot go green having silently skipped every version (e.g. if none of - # the /opt/python/ interpreters exist in the image). - TESTED=0 - # Inner script uses exit code 3 to signal "interpreter absent - skipped". - SKIP_RC=3 - - for PYBIN in cp310 cp311 cp312 cp313 cp314; do - echo "" - echo "=====================================================" - echo "End-user both-wheels test: $PYBIN on $(LINUX_TAG)/$(ARCH)" - echo "=====================================================" - - set +e - docker exec -e PYBIN=$PYBIN -e LINUX_TAG="$(LINUX_TAG)" -e SQL_IP=$(SQL_IP) -e DB_PASSWORD="$(DB_PASSWORD)" \ - test-$(LINUX_TAG)-$(ARCH) $SHELL_EXE -lc ' - set -euo pipefail - - PY=/opt/python/${PYBIN}-${PYBIN}/bin/python - test -x "$PY" || { echo "Python $PY missing - skipping ${PYBIN}"; exit 3; } - echo "Using: $($PY --version)" - - # Fresh isolated venv = clean end-user machine - TEST_DIR="/enduser_${PYBIN}" - rm -rf "$TEST_DIR" - $PY -m venv "$TEST_DIR" - VENV_PY="$TEST_DIR/bin/python" - - # Run all Python invocations from the venv dir (never /workspace) and - # with PYTHONSAFEPATH so the repo checkout at /workspace/mssql_python - # can NEVER shadow the freshly installed wheel. Without this, cwd - # (/workspace) lands on sys.path and "import mssql_python" would resolve - # to the source tree instead of the package under test. - export PYTHONSAFEPATH=1 - cd "$TEST_DIR" - - $VENV_PY -m pip install -q -U pip - - # Platform wheel tag: manylinux_2_28 vs musllinux_1_2, x86_64 - if [ "$LINUX_TAG" = "manylinux_2_28" ]; then PLAT="manylinux"; else PLAT="musllinux"; fi - - # Select the mssql-python wheel matching THIS Python version + libc - MP_WHEEL=$(ls /wheels/mssql-python/dist/mssql_python-*${PYBIN}-*${PLAT}*x86_64*.whl 2>/dev/null | head -1) - if [ -z "$MP_WHEEL" ]; then - echo "ERROR: no mssql-python wheel for ${PYBIN}/${PLAT}"; exit 1 - fi - - # ODBC wheel is data-only (py3-none-), one per platform - ODBC_WHEEL=$(ls /wheels/mssql-python-odbc/dist/mssql_python_odbc-*${PLAT}*x86_64*.whl 2>/dev/null | head -1) - if [ -z "$ODBC_WHEEL" ]; then - echo "ERROR: no mssql-python-odbc wheel for ${PLAT}"; exit 1 - fi - - # Install the odbc wheel FIRST so that once mssql-python pins - # mssql-python-odbc==X (setup.py install_requires), the requirement - # is already satisfied from this LOCAL wheel and pip never reaches - # PyPI for a version that may not be published yet. - echo "Installing mssql-python-odbc: $ODBC_WHEEL" - $VENV_PY -m pip install -q "$ODBC_WHEEL" - echo "Installing mssql-python: $MP_WHEEL" - $VENV_PY -m pip install -q "$MP_WHEEL" - - # Show where the external ODBC package lives (the split target) - $VENV_PY -c "import mssql_python_odbc, os; print(\"mssql_python_odbc libs:\", mssql_python_odbc.get_libs_dir()); assert os.path.isdir(mssql_python_odbc.get_libs_dir())" - - # POSITIVE assertion: prove the native loader WILL resolve the driver - # from the external mssql_python_odbc package. This mirrors exactly what - # GetOdbcLibsBaseDir() does in ddbc_bindings.cpp: it takes the parent dir - # of mssql_python_odbc.__file__ as the base and treats the external - # package as authoritative iff GetDriverPathCpp(base) exists. If this - # driver file is present, the loader is guaranteed to pick the external - # package (non-Windows only checks the driver; Windows also needs - # mssql-auth.dll, which this platform test does not cover). - $VENV_PY -c "import mssql_python.ddbc_bindings as d, mssql_python_odbc, os; base=os.path.dirname(mssql_python_odbc.__file__); drv=d.GetDriverPathCpp(base); print(\"external driver path:\", drv); assert os.path.exists(drv), \"external ODBC driver not found - loader would NOT select external package: \"+drv; print(\"POSITIVE: loader will resolve the driver from the external mssql_python_odbc package\")" - - # DECISIVE (negative) STEP: remove the ODBC driver bundled inside - # mssql_python. With the fallback gone, any successful connection must - # be using the driver from the separate mssql_python_odbc package. - MP_LIBS=$($VENV_PY -c "import mssql_python, os; print(os.path.join(os.path.dirname(mssql_python.__file__), \"libs\"))") - case "$MP_LIBS" in - /workspace/*) - echo "ERROR: resolved mssql_python to the source tree ($MP_LIBS), not the installed wheel"; exit 1 ;; - esac - if [ -d "$MP_LIBS" ]; then - echo "Removing bundled mssql_python/libs to force use of external ODBC package: $MP_LIBS" - rm -rf "$MP_LIBS" - else - echo "Note: mssql_python has no bundled libs/ (already split at build time)" - fi - - # Smoke test: import + live connection using ONLY the external driver - $VENV_PY -c "import mssql_python; print(\"mssql_python\", mssql_python.__version__, \"imported OK\")" - DB_CONNECTION_STRING="Server=$SQL_IP;Database=master;Uid=SA;Pwd=$DB_PASSWORD;TrustServerCertificate=yes" \ - $VENV_PY -c "import mssql_python, os; c=mssql_python.connect(os.environ[\"DB_CONNECTION_STRING\"]); cur=c.cursor(); cur.execute(\"SELECT 1\"); print(\"connection OK:\", cur.fetchone()); c.close()" - - # Copy the suite OUT of /workspace so pytest collection cannot import - # the source-tree package either, then run against the installed wheels. - cp -r /workspace/tests "$TEST_DIR/tests" - cp /workspace/pytest.ini "$TEST_DIR/pytest.ini" 2>/dev/null || true - $VENV_PY -m pip install -q pytest - if [ -f /workspace/requirements.txt ]; then - $VENV_PY -m pip install -q -r /workspace/requirements.txt - fi - DB_CONNECTION_STRING="Server=$SQL_IP;Database=master;Uid=SA;Pwd=$DB_PASSWORD;TrustServerCertificate=yes" \ - $VENV_PY -m pytest "$TEST_DIR/tests" -v --maxfail=1 - echo "All tests passed for ${PYBIN} using the EXTERNAL mssql_python_odbc driver" - ' - rc=$? - set -e - - if [ "$rc" -eq "$SKIP_RC" ]; then - echo "Skipped $PYBIN (interpreter not present in image)" - elif [ "$rc" -ne 0 ]; then - echo "ERROR: both-wheels test failed for $PYBIN (exit $rc)" - exit "$rc" - else - TESTED=$((TESTED + 1)) - echo "Both-wheels test complete for $PYBIN" - fi - done - - if [ "$TESTED" -eq 0 ]; then - echo "ERROR: no Python versions were tested - every interpreter was missing. Failing the stage." - exit 1 - fi - - echo "=====================================================" - echo "All available Python versions ($TESTED) passed the both-wheels end-user test" - echo "=====================================================" - displayName: 'Install both wheels + pytest (Python 3.10-3.14)' - env: - DB_PASSWORD: $(DB_PASSWORD) - - - script: | - docker stop test-$(LINUX_TAG)-$(ARCH) sqlserver-$(LINUX_TAG)-$(ARCH) || true - docker rm test-$(LINUX_TAG)-$(ARCH) sqlserver-$(LINUX_TAG)-$(ARCH) || true - displayName: 'Cleanup containers' - condition: always() diff --git a/OneBranchPipelines/stages/wheel-installation-test-windows-stage.yml b/OneBranchPipelines/stages/wheel-installation-test-windows-stage.yml deleted file mode 100644 index 8a45e39b7..000000000 --- a/OneBranchPipelines/stages/wheel-installation-test-windows-stage.yml +++ /dev/null @@ -1,196 +0,0 @@ -# ========================================================================================= -# End-User "Both Wheels Together" Installation Test - WINDOWS -# ========================================================================================= -# Windows counterpart of wheel-installation-test-stage.yml (which is Docker/Linux-only). -# Installs the ACTUAL published mssql-python + mssql-python-odbc Windows wheels into a -# fresh venv, runs the full pytest suite (bundled libs intact, external package already -# preferred by GetOdbcLibsBaseDir), then DELETES the bundled mssql_python/libs and -# connects once more in a fresh process. With the bundled fallback gone, a successful -# live query can ONLY be using the driver from the separately installed -# mssql_python_odbc package. -# -# Windows adds coverage the Linux stage explicitly cannot: on Windows the loader treats -# the external package as authoritative only when BOTH the ODBC driver AND mssql-auth.dll -# are present, so this proves the auth DLL is resolved from the external package too. -# -# Runs natively on the same 1ES Windows Server + SQL Server LocalDB pool as the build -# stage (no Docker). A single representative Python version is sufficient: the external -# package RESOLUTION path (GetOdbcLibsBaseDir in ddbc_bindings.cpp) is Python-version -# independent; per-version native builds are already covered by the Windows build stages. -# ========================================================================================= -parameters: - # Stage identifier (e.g., 'TestBothWheels_Windows_x64') - - name: stageName - type: string - # Job identifier within the stage - - name: jobName - type: string - default: 'InstallAndTest' - # Python version in X.Y format used to select the mssql-python wheel - - name: pythonVersion - type: string - default: '3.12' - # Python version as 3-digit string (e.g., '312') matching the cp wheel tag - - name: shortPyVer - type: string - default: '312' - # Target architecture (test stage targets x64 only; ARM64 binaries can't run on x64 host) - - name: architecture - type: string - default: 'x64' - # OneBranch build type: 'Official' or 'NonOfficial' - - name: oneBranchType - type: string - default: 'Official' - -stages: - - stage: ${{ parameters.stageName }} - displayName: 'Test Both Wheels Windows ${{ parameters.architecture }}' - # Only meaningful when both consolidated artifacts exist in this run. - dependsOn: - - Consolidate - - ConsolidateOdbc - jobs: - - job: ${{ parameters.jobName }} - displayName: 'Install both wheels + pytest - Windows ${{ parameters.architecture }}' - - # Same custom 1ES pool the Windows build stage uses: Windows Server 2022 with - # SQL Server LocalDB pre-installed, so pytest has a live server without Docker. - pool: - type: windows - isCustom: true - name: Python-1ES-pool - demands: - - imageOverride -equals PYTHON-1ES-MMS2022 - timeoutInMinutes: 120 - - variables: - ob_outputDirectory: '$(Build.ArtifactStagingDirectory)' - # OneBranch-required variable (unused on Windows agents). - LinuxContainerImage: 'onebranch.azurecr.io/linux/ubuntu-2204:latest' - - steps: - - checkout: self - fetchDepth: 1 - - - task: UsePythonVersion@0 - inputs: - versionSpec: '${{ parameters.pythonVersion }}' - architecture: 'x64' - addToPath: true - displayName: 'Use Python ${{ parameters.pythonVersion }}' - - # Download the two CONSOLIDATED wheel artifacts produced earlier in this run. - - task: DownloadPipelineArtifact@2 - displayName: 'Download consolidated mssql-python wheels' - inputs: - buildType: 'current' - artifactName: 'drop_Consolidate_ConsolidateArtifacts' - targetPath: '$(Pipeline.Workspace)/mssql-python-wheels' - - - task: DownloadPipelineArtifact@2 - displayName: 'Download consolidated mssql-python-odbc wheels' - inputs: - buildType: 'current' - artifactName: 'drop_ConsolidateOdbc_ConsolidateArtifacts' - targetPath: '$(Pipeline.Workspace)/mssql-python-odbc-wheels' - - # Start SQL Server LocalDB (pre-installed on the 1ES Windows image). - - powershell: | - sqllocaldb create MSSQLLocalDB - sqllocaldb start MSSQLLocalDB - displayName: 'Start LocalDB instance' - - - powershell: | - sqlcmd -S "(localdb)\MSSQLLocalDB" -Q "CREATE DATABASE TestDB" - sqlcmd -S "(localdb)\MSSQLLocalDB" -Q "CREATE LOGIN testuser WITH PASSWORD = '$(DB_PASSWORD)'" - sqlcmd -S "(localdb)\MSSQLLocalDB" -d TestDB -Q "CREATE USER testuser FOR LOGIN testuser" - sqlcmd -S "(localdb)\MSSQLLocalDB" -d TestDB -Q "ALTER ROLE db_owner ADD MEMBER testuser" - displayName: 'Setup database and user' - env: - DB_PASSWORD: $(DB_PASSWORD) - - - powershell: | - $ErrorActionPreference = 'Stop' - - # Run every venv-python invocation through this helper so a non-zero exit - # (native commands do NOT trip $ErrorActionPreference) fails the step loudly. - # No param() block: PowerShell then routes ALL tokens (including -m, -c, -q) - # into the automatic $args instead of trying to bind them as parameters. - $script:VenvPy = $null - function Invoke-Py { - & $script:VenvPy @args - if ($LASTEXITCODE -ne 0) { throw "python exited ${LASTEXITCODE}: $($args -join ' ')" } - } - - $PYTAG = 'cp${{ parameters.shortPyVer }}' - $PLATTAG = 'win_amd64' - - # Select the mssql-python wheel matching THIS Python version + platform. - $mpWheel = Get-ChildItem "$(Pipeline.Workspace)\mssql-python-wheels\dist\mssql_python-*$PYTAG-*$PLATTAG*.whl" -ErrorAction SilentlyContinue | Select-Object -First 1 - if (-not $mpWheel) { throw "ERROR: no mssql-python wheel for $PYTAG/$PLATTAG" } - # The odbc wheel is data-only (py3-none-), one per platform. - $odbcWheel = Get-ChildItem "$(Pipeline.Workspace)\mssql-python-odbc-wheels\dist\mssql_python_odbc-*$PLATTAG*.whl" -ErrorAction SilentlyContinue | Select-Object -First 1 - if (-not $odbcWheel) { throw "ERROR: no mssql-python-odbc wheel for $PLATTAG" } - - # Fresh isolated venv = clean end-user machine. - $testDir = Join-Path $env:TEMP "enduser_$PYTAG" - if (Test-Path $testDir) { Remove-Item -Recurse -Force $testDir } - python -m venv $testDir - if ($LASTEXITCODE -ne 0) { throw "venv creation failed" } - $script:VenvPy = Join-Path $testDir 'Scripts\python.exe' - - # PYTHONSAFEPATH so the repo checkout can never shadow the installed wheel. - $env:PYTHONSAFEPATH = '1' - - Invoke-Py -m pip install -q -U pip - - # Install the odbc wheel FIRST so mssql-python's pinned mssql-python-odbc==X - # requirement is satisfied from this LOCAL wheel (pip never reaches PyPI). - Write-Host "Installing mssql-python-odbc: $($odbcWheel.Name)" - Invoke-Py -m pip install -q "$($odbcWheel.FullName)" - Write-Host "Installing mssql-python: $($mpWheel.Name)" - Invoke-Py -m pip install -q "$($mpWheel.FullName)" - - # Show where the external ODBC package lives (the split target). - Invoke-Py -c "import mssql_python_odbc, os; d=mssql_python_odbc.get_libs_dir(); print('mssql_python_odbc libs:', d); assert os.path.isdir(d)" - - # POSITIVE assertion: prove the native loader WILL resolve the driver from - # the external mssql_python_odbc package (mirrors GetOdbcLibsBaseDir). - Invoke-Py -c "import mssql_python.ddbc_bindings as d, mssql_python_odbc, os; base=os.path.dirname(mssql_python_odbc.__file__); drv=d.GetDriverPathCpp(base); print('external driver path:', drv); assert os.path.exists(drv), 'external ODBC driver not found - loader would NOT select external package: '+drv; print('POSITIVE: loader will resolve the driver from the external mssql_python_odbc package')" - - # PHASE 1: run the FULL existing suite with bundled libs INTACT. Copy the - # suite OUT of the source tree so collection imports the INSTALLED package. - Copy-Item -Recurse "$(Build.SourcesDirectory)\tests" (Join-Path $testDir 'tests') - Copy-Item "$(Build.SourcesDirectory)\pytest.ini" (Join-Path $testDir 'pytest.ini') - Invoke-Py -m pip install -q pytest - if (Test-Path "$(Build.SourcesDirectory)\requirements.txt") { - Invoke-Py -m pip install -q -r "$(Build.SourcesDirectory)\requirements.txt" - } - $env:DB_CONNECTION_STRING = "Server=(localdb)\MSSQLLocalDB;Database=TestDB;Uid=testuser;Pwd=$($env:DB_PASSWORD);TrustServerCertificate=yes" - Push-Location $testDir - try { - Invoke-Py -m pytest (Join-Path $testDir 'tests') -v --maxfail=1 -m "not stress" - } finally { - Pop-Location - } - Write-Host "Full suite passed for $PYTAG with both wheels installed (external driver preferred)" - - # PHASE 2 - DECISIVE negative proof: remove the ODBC driver that - # mssql_python still bundles, then connect again in a FRESH process. - $mpLibs = & $script:VenvPy -c "import mssql_python, os; print(os.path.join(os.path.dirname(mssql_python.__file__), 'libs'))" - if ($LASTEXITCODE -ne 0) { throw "failed to resolve mssql_python libs dir" } - if ($mpLibs -like "$(Build.SourcesDirectory)*") { throw "ERROR: resolved mssql_python to the source tree ($mpLibs), not the installed wheel" } - if (Test-Path $mpLibs) { - Write-Host "Removing bundled mssql_python/libs to force use of external ODBC package: $mpLibs" - Remove-Item -Recurse -Force $mpLibs - } else { - Write-Host "Note: mssql_python has no bundled libs/ (already split at build time)" - } - - Invoke-Py -c "import mssql_python; print('mssql_python', mssql_python.__version__, 'imported OK (bundled libs removed)')" - Invoke-Py -c "import mssql_python, os; c=mssql_python.connect(os.environ['DB_CONNECTION_STRING']); cur=c.cursor(); cur.execute('SELECT 1'); print('connection OK via EXTERNAL driver:', cur.fetchone()); c.close()" - Write-Host "DECISIVE: connected with bundled libs removed - driver + mssql-auth.dll resolved from external mssql_python_odbc package" - displayName: 'Install both wheels + full pytest + external-only proof' - env: - DB_PASSWORD: $(DB_PASSWORD) From ca2c3c09efcf4d784e4c53cde7d401097a438b90 Mon Sep 17 00:00:00 2001 From: Jahnvi Thakkar Date: Fri, 31 Jul 2026 14:20:30 +0530 Subject: [PATCH 08/11] FIX: repoint PR-validation driver-verify paths to mssql_python_odbc/libs after Phase 2 move --- eng/pipelines/pr-validation-pipeline.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/eng/pipelines/pr-validation-pipeline.yml b/eng/pipelines/pr-validation-pipeline.yml index a97a47bc1..f0323fb0b 100644 --- a/eng/pipelines/pr-validation-pipeline.yml +++ b/eng/pipelines/pr-validation-pipeline.yml @@ -569,7 +569,7 @@ jobs: fi echo "Verifying the bundled x86_64 driver load chain is intact:" - otool -L mssql_python/libs/macos/x86_64/lib/libmsodbcsql.18.dylib \ + otool -L mssql_python_odbc/libs/macos/x86_64/lib/libmsodbcsql.18.dylib \ || echo " bundled driver not found" displayName: 'Uninstall system ODBC Driver before running tests on macOS' @@ -849,7 +849,7 @@ jobs: odbcinst -u -d -n 'ODBC Driver 18 for SQL Server' || true echo 'Uninstalled ODBC Driver and cleaned up libraries' echo 'Verifying x86_64 debian_ubuntu driver library signatures:' - ldd mssql_python/libs/linux/debian_ubuntu/x86_64/lib/libmsodbcsql-${DRIVER_MAJOR_MINOR}.so.2.1 + ldd mssql_python_odbc/libs/linux/debian_ubuntu/x86_64/lib/libmsodbcsql-${DRIVER_MAJOR_MINOR}.so.2.1 " displayName: 'Uninstall ODBC Driver before running tests in $(distroName) container' @@ -1198,7 +1198,7 @@ jobs: odbcinst -u -d -n 'ODBC Driver 11 for SQL Server' || true echo 'Uninstalled ODBC Driver and cleaned up libraries' echo 'Verifying arm64 debian_ubuntu driver library signatures:' - ldd mssql_python/libs/linux/debian_ubuntu/arm64/lib/libmsodbcsql-${DRIVER_MAJOR_MINOR}.so.2.1 + ldd mssql_python_odbc/libs/linux/debian_ubuntu/arm64/lib/libmsodbcsql-${DRIVER_MAJOR_MINOR}.so.2.1 " displayName: 'Uninstall ODBC Driver before running tests in $(distroName) ARM64 container' @@ -1417,7 +1417,7 @@ jobs: odbcinst -u -d -n 'ODBC Driver 11 for SQL Server' || true echo 'Uninstalled ODBC Driver and cleaned up libraries' echo 'Verifying x86_64 rhel driver library signatures:' - ldd mssql_python/libs/linux/rhel/x86_64/lib/libmsodbcsql-${DRIVER_MAJOR_MINOR}.so.2.1 + ldd mssql_python_odbc/libs/linux/rhel/x86_64/lib/libmsodbcsql-${DRIVER_MAJOR_MINOR}.so.2.1 " displayName: 'Uninstall ODBC Driver before running tests in RHEL 9 container' @@ -1648,7 +1648,7 @@ jobs: odbcinst -u -d -n 'ODBC Driver 18 for SQL Server' || true echo 'Uninstalled ODBC Driver and cleaned up libraries' echo 'Verifying arm64 rhel driver library signatures:' - ldd mssql_python/libs/linux/rhel/arm64/lib/libmsodbcsql-${DRIVER_MAJOR_MINOR}.so.2.1 + ldd mssql_python_odbc/libs/linux/rhel/arm64/lib/libmsodbcsql-${DRIVER_MAJOR_MINOR}.so.2.1 " displayName: 'Uninstall ODBC Driver before running tests in RHEL 9 ARM64 container' @@ -1888,7 +1888,7 @@ jobs: odbcinst -u -d -n 'ODBC Driver 18 for SQL Server' || true echo 'Uninstalled system ODBC Driver and cleaned up libraries' echo 'Verifying x86_64 alpine driver library signatures:' - ldd mssql_python/libs/linux/alpine/x86_64/lib/libmsodbcsql-${DRIVER_MAJOR_MINOR}.so.2.1 || echo 'Driver library not found' + ldd mssql_python_odbc/libs/linux/alpine/x86_64/lib/libmsodbcsql-${DRIVER_MAJOR_MINOR}.so.2.1 || echo 'Driver library not found' " displayName: 'Uninstall system ODBC Driver before running tests in Alpine x86_64 container' @@ -2145,7 +2145,7 @@ jobs: odbcinst -u -d -n 'ODBC Driver 18 for SQL Server' || true echo 'Uninstalled system ODBC Driver and cleaned up libraries' echo 'Verifying arm64 alpine driver library signatures:' - ldd mssql_python/libs/linux/alpine/arm64/lib/libmsodbcsql-${DRIVER_MAJOR_MINOR}.so.2.1 || echo 'Driver library not found' + ldd mssql_python_odbc/libs/linux/alpine/arm64/lib/libmsodbcsql-${DRIVER_MAJOR_MINOR}.so.2.1 || echo 'Driver library not found' " displayName: 'Uninstall system ODBC Driver before running tests in Alpine ARM64 container' From 334c4f6b38acd608753372f0aebbe09a50b2b462 Mon Sep 17 00:00:00 2001 From: Jahnvi Thakkar Date: Fri, 31 Jul 2026 14:53:34 +0530 Subject: [PATCH 09/11] CHORE: remove dead GetModuleDirectory(); fix stale mssql_python/libs doc references --- .github/copilot-instructions.md | 2 +- .github/prompts/create-pr.prompt.md | 6 +++--- README.md | 2 +- mssql_python/pybind/ddbc_bindings.cpp | 23 ----------------------- 4 files changed, 5 insertions(+), 28 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 60b9a4dba..870bd8477 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -15,7 +15,7 @@ Paths you will touch: - `mssql_python/pybind/CMakeLists.txt` — all platform/architecture build conditionals live here. - `mssql_python/pybind/build.sh` / `build.bat` — build entry points; `configure_dylibs.sh` fixes macOS dylib paths. -- `mssql_python/libs/{windows,macos,linux}/...` — prebuilt ODBC binaries. **NEVER hand-edit these.** +- `mssql_python_odbc/libs/{windows,macos,linux}/...` — prebuilt ODBC binaries (moved out of the mssql-python wheel into the standalone `mssql-python-odbc` package in Phase 2). **NEVER hand-edit these.** - `setup.py` / `pyproject.toml` — packaging and wheel/platform tagging. - `mssql_python/mssql_python.pyi` + `mssql_python/py.typed` — PEP 561 type stubs. Update stubs when the public API changes. - `tests/` — mostly-numbered `test_NNN_*.py` files (a live-SQL-Server integration suite plus no-DB dependency checks). diff --git a/.github/prompts/create-pr.prompt.md b/.github/prompts/create-pr.prompt.md index d17c3ae94..c3921e6a4 100644 --- a/.github/prompts/create-pr.prompt.md +++ b/.github/prompts/create-pr.prompt.md @@ -146,15 +146,15 @@ git status > ⚠️ **AVOID:** `git add .` stages everything including binary files. Always stage specific files. **Files to typically EXCLUDE from commits:** -- `mssql_python/libs/**/*.dylib` - macOS libraries -- `mssql_python/libs/**/*.so` - Linux libraries +- `mssql_python_odbc/libs/**/*.dylib` - macOS libraries +- `mssql_python_odbc/libs/**/*.so` - Linux libraries - `mssql_python/*.so` or `*.pyd` - Built extensions - `*.dll` - Windows libraries - Virtual environments (`myvenv/`, `testenv/`, etc.) **To unstage accidentally added binary files:** ```bash -git restore --staged mssql_python/libs/ +git restore --staged mssql_python_odbc/libs/ git restore --staged "*.dylib" "*.so" "*.pyd" ``` diff --git a/README.md b/README.md index d82939e5f..00b6bacc5 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,7 @@ For more information see the [Code of Conduct FAQ](https://opensource.microsoft. contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. ## License -The mssql-python driver for SQL Server is licensed under the MIT license, except the dynamic-link libraries (DLLs) in the [libs](https://github.com/microsoft/mssql-python/tree/alphaChanges/mssql_python/libs) folder +The mssql-python driver for SQL Server is licensed under the MIT license, except the dynamic-link libraries (DLLs) in the [libs](https://github.com/microsoft/mssql-python/tree/main/mssql_python_odbc/libs) folder that are licensed under MICROSOFT SOFTWARE LICENSE TERMS. Please review the [LICENSE](LICENSE) file for more details. diff --git a/mssql_python/pybind/ddbc_bindings.cpp b/mssql_python/pybind/ddbc_bindings.cpp index 573bc32f6..af5da9607 100644 --- a/mssql_python/pybind/ddbc_bindings.cpp +++ b/mssql_python/pybind/ddbc_bindings.cpp @@ -1102,29 +1102,6 @@ void ThrowStdException(const std::string& message) { } std::string GetLastErrorMessage(); -// TODO: Move this to Python -std::string GetModuleDirectory() { - namespace fs = std::filesystem; - py::object module = py::module::import("mssql_python"); - py::object module_path = module.attr("__file__"); - std::string module_file = module_path.cast(); - - // Use std::filesystem::path for cross-platform path handling - // This properly handles UTF-8 encoded paths on all platforms - fs::path modulePath(module_file); - fs::path parentDir = modulePath.parent_path(); - - // Log path extraction for observability - LOG("GetModuleDirectory: Extracted directory - " - "original_path='%s', directory='%s'", - module_file.c_str(), parentDir.string().c_str()); - - // Return UTF-8 encoded string for consistent handling - // If parentDir is empty or invalid, subsequent operations (like LoadDriverLibrary) - // will fail naturally with clear error messages - return parentDir.string(); -} - // Resolve the base directory that contains the ODBC driver `libs/` tree. // // Post-split, the driver binaries ship in the standalone `mssql_python_odbc` From af727cdea41d8dc5476b3324a0835dbc62bbb410 Mon Sep 17 00:00:00 2001 From: Jahnvi Thakkar Date: Fri, 31 Jul 2026 14:54:00 +0530 Subject: [PATCH 10/11] FIX: break OneBranch release stage cycle (ODBC_BuildAll dependsOn: []); validate installed mssql-python-odbc wheel on Win/macOS pytest; repoint configure_dylibs to mssql_python_odbc/libs --- OneBranchPipelines/stages/build-macos-single-stage.yml | 10 +++++++++- OneBranchPipelines/stages/build-odbc-all-stage.yml | 9 +++++++++ .../stages/build-windows-single-stage.yml | 10 +++++++++- mssql_python/pybind/configure_dylibs.sh | 5 ++++- 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/OneBranchPipelines/stages/build-macos-single-stage.yml b/OneBranchPipelines/stages/build-macos-single-stage.yml index c2c2ed790..2e359a3bf 100644 --- a/OneBranchPipelines/stages/build-macos-single-stage.yml +++ b/OneBranchPipelines/stages/build-macos-single-stage.yml @@ -206,7 +206,15 @@ stages: if [ -z "$LINKS" ]; then echo "No ODBC wheel found in downloaded artifact" >&2; exit 1; fi echo "Installing mssql-python-odbc from $LINKS" python -m pip install --no-index --find-links "$LINKS" mssql-python-odbc - python -c "import mssql_python_odbc, os; print('mssql_python_odbc at', os.path.dirname(mssql_python_odbc.__file__))" + # The repo checkout ships a committed mssql_python_odbc/ dir whose libs/ + # would SHADOW the just-installed site-packages wheel when pytest runs from + # the checkout root. Remove it so pytest resolves the driver from the + # EXTERNAL wheel (mirrors the Linux isolated-install job); otherwise a + # broken/no-op install would go undetected. setup.py excludes + # mssql_python_odbc, so the later mssql-python bdist_wheel is unaffected. + rm -rf "$(Build.SourcesDirectory)/mssql_python_odbc" + # Assert the driver package now resolves to site-packages, NOT the checkout. + python -c "import os,mssql_python_odbc as m; p=os.path.realpath(os.path.dirname(m.__file__)); s=os.path.realpath(os.environ['BUILD_SOURCESDIRECTORY']); assert not p.startswith(s), 'mssql_python_odbc resolved inside checkout: '+p; print('mssql_python_odbc resolved from installed wheel:', p)" displayName: 'Install external ODBC wheel (mssql-python-odbc)' # ========================= diff --git a/OneBranchPipelines/stages/build-odbc-all-stage.yml b/OneBranchPipelines/stages/build-odbc-all-stage.yml index 68dc5aaf2..d93b4184f 100644 --- a/OneBranchPipelines/stages/build-odbc-all-stage.yml +++ b/OneBranchPipelines/stages/build-odbc-all-stage.yml @@ -57,6 +57,15 @@ parameters: stages: - stage: ${{ parameters.stageName }} displayName: 'ODBC Build All Wheels (single host)' + # This stage MUST run first: the mssql-python build stages depend on + # ConsolidateOdbc, which depends on this stage. It is declared LAST in the + # orchestrator YAML (right after Consolidate), so WITHOUT an explicit empty + # dependsOn, Azure's implicit "depends on the previous stage" rule would chain it + # to Consolidate and form a cycle: + # ODBC_BuildAll -> Consolidate -> (18 platform builds) -> ConsolidateOdbc -> ODBC_BuildAll + # which fails the pipeline at compile time. An empty dependsOn breaks that implicit + # link so ODBC_BuildAll has no dependencies and runs first. + dependsOn: [] jobs: - job: ${{ parameters.jobName }} displayName: 'Build all 7 ODBC wheels on one Windows host' diff --git a/OneBranchPipelines/stages/build-windows-single-stage.yml b/OneBranchPipelines/stages/build-windows-single-stage.yml index fcad9260b..c49eac4db 100644 --- a/OneBranchPipelines/stages/build-windows-single-stage.yml +++ b/OneBranchPipelines/stages/build-windows-single-stage.yml @@ -285,7 +285,15 @@ stages: if (-not $links) { Write-Error "No ODBC wheel found in downloaded artifact"; exit 1 } Write-Host "Installing mssql-python-odbc from $links" python -m pip install --no-index --find-links "$links" mssql-python-odbc - python -c "import mssql_python_odbc, os; print('mssql_python_odbc at', os.path.dirname(mssql_python_odbc.__file__))" + # The repo checkout ships a committed mssql_python_odbc\ dir whose libs\ + # would SHADOW the just-installed site-packages wheel when pytest runs from + # the checkout root. Remove it so pytest resolves the driver from the + # EXTERNAL wheel (mirrors the Linux isolated-install job); otherwise a + # broken/no-op install would go undetected. setup.py excludes + # mssql_python_odbc, so the later mssql-python bdist_wheel is unaffected. + Remove-Item -Recurse -Force "$(Build.SourcesDirectory)\mssql_python_odbc" -ErrorAction SilentlyContinue + # Assert the driver package now resolves to site-packages, NOT the checkout. + python -c "import os,mssql_python_odbc as m; p=os.path.realpath(os.path.dirname(m.__file__)); s=os.path.realpath(os.environ['BUILD_SOURCESDIRECTORY']); assert not p.startswith(s), 'mssql_python_odbc resolved inside checkout: '+p; print('mssql_python_odbc resolved from installed wheel:', p)" displayName: 'Install external ODBC wheel (mssql-python-odbc)' condition: ne(variables['targetArch'], 'arm64') diff --git a/mssql_python/pybind/configure_dylibs.sh b/mssql_python/pybind/configure_dylibs.sh index 6022d9771..523da3a69 100755 --- a/mssql_python/pybind/configure_dylibs.sh +++ b/mssql_python/pybind/configure_dylibs.sh @@ -25,7 +25,10 @@ DRIVER_DYLIB_NAME="libmsodbcsql.${DRIVER_MAJOR}.dylib" # install_name_tool and codesign both work cross-arch. Fixing only the host arch # is what shipped the broken arm64 driver in issue #656. for ARCH in arm64 x86_64; do -LIB_DIR="$PROJECT_DIR/libs/macos/$ARCH/lib" +# Post-split the driver dylibs ship in the standalone mssql_python_odbc package +# (a pure-data sibling of mssql_python), so configure them there. Mirrors the +# ODBC_VERSION_FILE path above. +LIB_DIR="$PROJECT_DIR/../mssql_python_odbc/libs/macos/$ARCH/lib" LIBMSODBCSQL_PATH="$LIB_DIR/$DRIVER_DYLIB_NAME" LIBMSODBCSQL_NAME="$DRIVER_DYLIB_NAME" LIBODBCINST_PATH="$LIB_DIR/libodbcinst.2.dylib" From 3394b32141199425237601029fa1ee92aa9aaf2d Mon Sep 17 00:00:00 2001 From: Jahnvi Thakkar Date: Fri, 31 Jul 2026 15:56:20 +0530 Subject: [PATCH 11/11] FIX: reject empty ODBC_TARGET_ARCH; tighten ODBC-wheel leak-check to exact driver filenames - setup_odbc.get_platform_info now raises OSError if ODBC_TARGET_PLATFORM_TAG is set but ODBC_TARGET_ARCH is empty; an empty arch expanded the libs/ package_data globs to every architecture and could leak foreign-platform driver binaries into the wheel. - build-odbc-all-stage leak-check Must lists now assert the exact driver filenames (msodbcsql18.dll / libmsodbcsql.18.dylib / libmsodbcsql-18) instead of loose substrings that also matched support files. - test_015 prose updated to reference GetOdbcLibsBaseDir (the successor to the removed GetModuleDirectory). --- .../stages/build-odbc-all-stage.yml | 19 ++++++++++++------- setup_odbc.py | 10 +++++++++- tests/test_015_utf8_path_handling.py | 8 ++++---- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/OneBranchPipelines/stages/build-odbc-all-stage.yml b/OneBranchPipelines/stages/build-odbc-all-stage.yml index d93b4184f..5dd7fbef3 100644 --- a/OneBranchPipelines/stages/build-odbc-all-stage.yml +++ b/OneBranchPipelines/stages/build-odbc-all-stage.yml @@ -141,14 +141,19 @@ stages: $wheelDir = "$(ob_outputDirectory)\wheels" # tag -> @{ Must = ; Forbid = } + # Must uses the EXACT driver filenames (not loose 'msodbcsql'/'.so'/'.dylib', + # which also match support files like 1033/msodbcsqlr18.rll or libodbcinst.so.2), + # so the check proves the actual ODBC DRIVER binary is packaged, not just a + # sibling. Linux uses the version-major prefix so a driver MINOR bump won't + # false-fail this gate. $expect = @{ - 'win_amd64' = @{ Must = @('libs/windows/x64/', 'msodbcsql', 'mssql-auth.dll', 'libs/LICENSING'); Forbid = @('libs/linux/', 'libs/macos/', 'libs/windows/arm64/') } - 'win_arm64' = @{ Must = @('libs/windows/arm64/', 'msodbcsql', 'mssql-auth.dll', 'libs/LICENSING'); Forbid = @('libs/linux/', 'libs/macos/', 'libs/windows/x64/') } - 'macosx_11_0_universal2' = @{ Must = @('libs/macos/arm64/', 'libs/macos/x86_64/', '.dylib', 'libs/LICENSING'); Forbid = @('libs/windows/', 'libs/linux/') } - 'manylinux_2_28_x86_64' = @{ Must = @('libs/linux/debian_ubuntu/x86_64/', '.so', 'libs/LICENSING'); Forbid = @('libs/windows/', 'libs/macos/', '/arm64/') } - 'manylinux_2_28_aarch64' = @{ Must = @('libs/linux/debian_ubuntu/arm64/', '.so', 'libs/LICENSING'); Forbid = @('libs/windows/', 'libs/macos/', '/x86_64/') } - 'musllinux_1_2_x86_64' = @{ Must = @('libs/linux/alpine/x86_64/', '.so', 'libs/LICENSING'); Forbid = @('libs/windows/', 'libs/macos/', '/arm64/') } - 'musllinux_1_2_aarch64' = @{ Must = @('libs/linux/alpine/arm64/', '.so', 'libs/LICENSING'); Forbid = @('libs/windows/', 'libs/macos/', '/x86_64/') } + 'win_amd64' = @{ Must = @('libs/windows/x64/', 'msodbcsql18.dll', 'mssql-auth.dll', 'libs/LICENSING'); Forbid = @('libs/linux/', 'libs/macos/', 'libs/windows/arm64/') } + 'win_arm64' = @{ Must = @('libs/windows/arm64/', 'msodbcsql18.dll', 'mssql-auth.dll', 'libs/LICENSING'); Forbid = @('libs/linux/', 'libs/macos/', 'libs/windows/x64/') } + 'macosx_11_0_universal2' = @{ Must = @('libs/macos/arm64/', 'libs/macos/x86_64/', 'libmsodbcsql.18.dylib', 'libs/LICENSING'); Forbid = @('libs/windows/', 'libs/linux/') } + 'manylinux_2_28_x86_64' = @{ Must = @('libs/linux/debian_ubuntu/x86_64/', 'libmsodbcsql-18', 'libs/LICENSING'); Forbid = @('libs/windows/', 'libs/macos/', '/arm64/') } + 'manylinux_2_28_aarch64' = @{ Must = @('libs/linux/debian_ubuntu/arm64/', 'libmsodbcsql-18', 'libs/LICENSING'); Forbid = @('libs/windows/', 'libs/macos/', '/x86_64/') } + 'musllinux_1_2_x86_64' = @{ Must = @('libs/linux/alpine/x86_64/', 'libmsodbcsql-18', 'libs/LICENSING'); Forbid = @('libs/windows/', 'libs/macos/', '/arm64/') } + 'musllinux_1_2_aarch64' = @{ Must = @('libs/linux/alpine/arm64/', 'libmsodbcsql-18', 'libs/LICENSING'); Forbid = @('libs/windows/', 'libs/macos/', '/x86_64/') } } $failed = $false diff --git a/setup_odbc.py b/setup_odbc.py index c02503dcf..edf471356 100644 --- a/setup_odbc.py +++ b/setup_odbc.py @@ -97,7 +97,15 @@ def get_platform_info(): # arch from ODBC_TARGET_ARCH) instead of inferring from the build host. explicit_tag = os.environ.get("ODBC_TARGET_PLATFORM_TAG") if explicit_tag: - return os.environ.get("ODBC_TARGET_ARCH", ""), explicit_tag + target_arch = os.environ.get("ODBC_TARGET_ARCH", "").strip() + if not target_arch: + raise OSError( + "ODBC_TARGET_ARCH must be set (non-empty) when " + "ODBC_TARGET_PLATFORM_TAG is provided: an empty arch would expand the " + "libs/ package_data globs to EVERY architecture's subtree and leak " + "foreign-platform driver binaries into the wheel." + ) + return target_arch, explicit_tag if sys.platform.startswith("win"): arch = os.environ.get("ARCHITECTURE", "x64") diff --git a/tests/test_015_utf8_path_handling.py b/tests/test_015_utf8_path_handling.py index 9c8ea7d74..486edc7e8 100644 --- a/tests/test_015_utf8_path_handling.py +++ b/tests/test_015_utf8_path_handling.py @@ -5,7 +5,7 @@ characters on Windows (e.g., usernames like 'Thalén', folders like 'café'). Bug Summary: -- GetModuleDirectory() used ANSI APIs (PathRemoveFileSpecA) which corrupted UTF-8 paths +- The module-directory resolver used ANSI APIs (PathRemoveFileSpecA) which corrupted UTF-8 paths - LoadDriverLibrary() used broken UTF-8→UTF-16 conversion: std::wstring(path.begin(), path.end()) - LoadDriverOrThrowException() used same broken pattern for mssql-auth.dll @@ -28,15 +28,15 @@ class TestPathHandlingCodePaths: Test that path handling code paths are exercised correctly. These tests run by DEFAULT and verify the fixed C++ functions - (GetModuleDirectory, LoadDriverLibrary) are working. + (GetOdbcLibsBaseDir, LoadDriverLibrary) are working. """ def test_module_import_exercises_path_handling(self): """ - Verify module import succeeds - this exercises GetModuleDirectory(). + Verify module import succeeds - this exercises GetOdbcLibsBaseDir(). When mssql_python imports, it calls: - 1. GetModuleDirectory() - to find module location + 1. GetOdbcLibsBaseDir() - to resolve the ODBC driver libs directory 2. LoadDriverLibrary() - to load ODBC driver 3. LoadLibraryW() for mssql-auth.dll on Windows