Skip to content

Commit a12d5de

Browse files
FEAT: Linux Support - Build and C++ (#113)
### ADO Work Item Reference <!-- Insert your ADO Work Item ID below (e.g. AB#37452) --> > [AB#34987](https://sqlclientdrivers.visualstudio.com/c6d89619-62de-46a0-8b46-70b92a84d85e/_workitems/edit/34987) ------------------------------------------------------------------- ### Summary <!-- Insert your Copilot Generated Summary below --> This pull request introduces cross-platform compatibility improvements for macOS and Linux in the MSSQL Python bindings, updates build scripts to support multiple operating systems, and refines driver handling logic. The most significant changes include adding Linux-specific handling alongside macOS, enhancing build scripts for OS detection, and integrating Python for dynamic driver path resolution. ### Cross-Platform Compatibility Enhancements: * [`mssql_python/pybind/ddbc_bindings.cpp`](diffhunk://#diff-dde2297345718ec449a14e7dff91b7bb2342b008ecc071f562233646d71144a1L261-R262): Updated numerous code sections to include Linux-specific handling, ensuring proper encoding and buffer management for SQLWCHAR data. This includes changes in functions like `BindParameters`, `SQLExecDirect_wrap`, and `FetchBatchData`. [[1]](diffhunk://#diff-dde2297345718ec449a14e7dff91b7bb2342b008ecc071f562233646d71144a1L261-R262) [[2]](diffhunk://#diff-dde2297345718ec449a14e7dff91b7bb2342b008ecc071f562233646d71144a1L828-R831) [[3]](diffhunk://#diff-dde2297345718ec449a14e7dff91b7bb2342b008ecc071f562233646d71144a1L1609-R1613) * [`mssql_python/pybind/ddbc_bindings.h`](diffhunk://#diff-85167a2d59779df18704284ab7ce46220c3619408fbf22c631ffdf29f794d635L35-R35): Replaced macOS-specific headers with Unix-specific headers to accommodate both macOS and Linux environments. [[1]](diffhunk://#diff-85167a2d59779df18704284ab7ce46220c3619408fbf22c631ffdf29f794d635L35-R35) [[2]](diffhunk://#diff-85167a2d59779df18704284ab7ce46220c3619408fbf22c631ffdf29f794d635L65-R67) ### Build Script Updates: * [`mssql_python/pybind/build.sh`](diffhunk://#diff-7cfcd9b5499a176c28ca4d028e9b2b060238ec79c4bea5b062d4b39929f9fb04L2-R22): Enhanced the script to detect the operating system dynamically and configure builds accordingly for macOS and Linux. Added OS-specific diagnostic messages and build configurations. [[1]](diffhunk://#diff-7cfcd9b5499a176c28ca4d028e9b2b060238ec79c4bea5b062d4b39929f9fb04L2-R22) [[2]](diffhunk://#diff-7cfcd9b5499a176c28ca4d028e9b2b060238ec79c4bea5b062d4b39929f9fb04L30-R50) [[3]](diffhunk://#diff-7cfcd9b5499a176c28ca4d028e9b2b060238ec79c4bea5b062d4b39929f9fb04L48-R81) * [`mssql_python/pybind/build.bat`](diffhunk://#diff-a08055320153458138a68ccff086b540cb080819b76f921664caa782603a4c2eL166-R166): Updated VCREDIST path to reflect platform-specific directory structures. ### Driver Handling Improvements: * [`mssql_python/pybind/ddbc_bindings.cpp`](diffhunk://#diff-dde2297345718ec449a14e7dff91b7bb2342b008ecc071f562233646d71144a1L621-R659): Integrated Python to dynamically determine the driver path based on the module directory and system architecture. Added a new function `GetDriverPathFromPython` for this purpose. [[1]](diffhunk://#diff-dde2297345718ec449a14e7dff91b7bb2342b008ecc071f562233646d71144a1L621-R659) [[2]](diffhunk://#diff-dde2297345718ec449a14e7dff91b7bb2342b008ecc071f562233646d71144a1L650-R673) * [`mssql_python/pybind/ddbc_bindings.cpp`](diffhunk://#diff-dde2297345718ec449a14e7dff91b7bb2342b008ecc071f562233646d71144a1R2069): Exposed the `get_driver_path` function to Python for external usage. ### Documentation Updates: * [`mssql_python/pybind/README.md`](diffhunk://#diff-1429930017ef4a5b84d9331206c1ac47e546427b914986e531e3f04e45f4bb31L62-R62): Corrected the file name for compilation instructions, replacing `ddbc_bindings_mac.cpp` with `ddbc_bindings.cpp`. ### Miscellaneous Improvements: * [`mssql_python/pybind/CMakeLists.txt`](diffhunk://#diff-dbb5892fbbb28149d1639664797cf3adb48ced28ec11aba95f2e2b338ca46badR15-R16): Added a TODO comment suggesting a unified module for platform detection across Python and C++. <!-- ### PR Title Guide > For feature requests FEAT: (short-description) > For non-feature requests like test case updates, config updates , dependency updates etc CHORE: (short-description) > For Fix requests FIX: (short-description) > For doc update requests DOC: (short-description) > For Formatting, indentation, or styling update STYLE: (short-description) > For Refactor, without any feature changes REFACTOR: (short-description) > For release related changes, without any feature changes RELEASE: #<RELEASE_VERSION> (short-description) --> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 71a9777 commit a12d5de

30 files changed

+266
-103
lines changed

mssql_python/ddbc_bindings.py

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,77 @@
33
import sys
44
import platform
55

6+
def normalize_architecture(platform_name, architecture):
7+
"""
8+
Normalize architecture names for the given platform.
9+
10+
Args:
11+
platform_name (str): Platform name ('windows', 'darwin', 'linux')
12+
architecture (str): Architecture string to normalize
13+
14+
Returns:
15+
str: Normalized architecture name
16+
17+
Raises:
18+
ImportError: If architecture is not supported for the given platform
19+
OSError: If platform is not supported
20+
"""
21+
arch_lower = architecture.lower()
22+
23+
if platform_name == "windows":
24+
arch_map = {
25+
"win64": "x64", "amd64": "x64", "x64": "x64",
26+
"win32": "x86", "x86": "x86",
27+
"arm64": "arm64"
28+
}
29+
if arch_lower in arch_map:
30+
return arch_map[arch_lower]
31+
else:
32+
supported = list(set(arch_map.keys()))
33+
raise ImportError(f"Unsupported architecture '{architecture}' for platform '{platform_name}'; expected one of {supported}")
34+
35+
elif platform_name == "darwin":
36+
# For macOS, return runtime architecture
37+
return platform.machine().lower()
38+
39+
elif platform_name == "linux":
40+
arch_map = {
41+
"x64": "x86_64", "amd64": "x86_64",
42+
"arm64": "arm64", "aarch64": "arm64"
43+
}
44+
if arch_lower in arch_map:
45+
return arch_map[arch_lower]
46+
else:
47+
supported = list(set(arch_map.keys()))
48+
raise ImportError(f"Unsupported architecture '{architecture}' for platform '{platform_name}'; expected one of {supported}")
49+
50+
else:
51+
supported_platforms = ["windows", "darwin", "linux"]
52+
raise OSError(f"Unsupported platform '{platform_name}'; expected one of {supported_platforms}")
53+
654
# Get current Python version and architecture
755
python_version = f"cp{sys.version_info.major}{sys.version_info.minor}"
856

9-
platform_name = sys.platform.lower()
10-
architecture = platform.machine().lower()
57+
platform_name = platform.system().lower()
58+
raw_architecture = platform.machine().lower()
1159

12-
# On macOS, prioritize universal2 binary regardless of the local architecture
60+
# Special handling for macOS universal2 binaries
1361
if platform_name == 'darwin':
1462
architecture = "universal2"
15-
elif platform_name == 'win32':
16-
if architecture in ('amd64', 'x86_64', 'x64'):
17-
architecture = "amd64" if platform_name == 'win32' else "x86_64"
18-
elif architecture in ('arm64', 'aarch64'):
19-
architecture = "arm64"
20-
else:
21-
raise ImportError(f"Unsupported architecture for mssql-python: {platform_name}-{architecture}")
2263
else:
23-
raise ImportError(f"Unsupported architecture for mssql-python: {platform_name}-{architecture}")
64+
architecture = normalize_architecture(platform_name, raw_architecture)
65+
66+
# Handle Windows-specific naming for binary files
67+
if platform_name == 'windows' and architecture == 'x64':
68+
architecture = "amd64"
69+
70+
# Validate supported platforms
71+
if platform_name not in ['windows', 'darwin', 'linux']:
72+
supported_platforms = ['windows', 'darwin', 'linux']
73+
raise ImportError(f"Unsupported platform '{platform_name}' for mssql-python; expected one of {supported_platforms}")
2474

2575
# Determine extension based on platform
26-
if platform_name == 'win32':
76+
if platform_name == 'windows':
2777
extension = '.pyd'
2878
else: # macOS or Linux
2979
extension = '.so'

mssql_python/helpers.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
from mssql_python import ddbc_bindings
88
from mssql_python.exceptions import raise_exception
99
from mssql_python.logging_config import get_logger, ENABLE_LOGGING
10+
import platform
11+
from pathlib import Path
12+
from mssql_python.ddbc_bindings import normalize_architecture
1013

1114
logger = get_logger()
1215

@@ -109,3 +112,75 @@ def add_driver_name_to_app_parameter(connection_string):
109112

110113
# Join the parameters back into a connection string
111114
return ";".join(modified_parameters) + ";"
115+
116+
117+
def detect_linux_distro():
118+
"""
119+
Detect Linux distribution for driver path selection.
120+
121+
Returns:
122+
str: Distribution name ('debian_ubuntu', 'rhel', 'alpine', etc.)
123+
"""
124+
import os
125+
126+
distro_name = "debian_ubuntu" # default
127+
128+
try:
129+
if os.path.exists("/etc/os-release"):
130+
with open("/etc/os-release", "r") as f:
131+
content = f.read()
132+
for line in content.split("\n"):
133+
if line.startswith("ID="):
134+
distro_id = line.split("=", 1)[1].strip('"\'')
135+
if distro_id in ["ubuntu", "debian"]:
136+
distro_name = "debian_ubuntu"
137+
elif distro_id in ["rhel", "centos", "fedora"]:
138+
distro_name = "rhel"
139+
elif distro_id == "alpine":
140+
distro_name = "alpine"
141+
else:
142+
distro_name = distro_id # use as-is
143+
break
144+
except Exception:
145+
pass # use default
146+
147+
return distro_name
148+
149+
def get_driver_path(module_dir, architecture):
150+
"""
151+
Get the platform-specific ODBC driver path.
152+
153+
Args:
154+
module_dir (str): Base module directory
155+
architecture (str): Target architecture (x64, arm64, x86, etc.)
156+
157+
Returns:
158+
str: Full path to the ODBC driver file
159+
160+
Raises:
161+
RuntimeError: If driver not found or unsupported platform
162+
"""
163+
164+
platform_name = platform.system().lower()
165+
normalized_arch = normalize_architecture(platform_name, architecture)
166+
167+
if platform_name == "windows":
168+
driver_path = Path(module_dir) / "libs" / "windows" / normalized_arch / "msodbcsql18.dll"
169+
170+
elif platform_name == "darwin":
171+
driver_path = Path(module_dir) / "libs" / "macos" / normalized_arch / "lib" / "libmsodbcsql.18.dylib"
172+
173+
elif platform_name == "linux":
174+
distro_name = detect_linux_distro()
175+
driver_path = Path(module_dir) / "libs" / "linux" / distro_name / normalized_arch / "lib" / "libmsodbcsql-18.5.so.1.1"
176+
177+
else:
178+
raise RuntimeError(f"Unsupported platform: {platform_name}")
179+
180+
driver_path_str = str(driver_path)
181+
182+
# Check if file exists
183+
if not driver_path.exists():
184+
raise RuntimeError(f"ODBC driver not found at: {driver_path_str}")
185+
186+
return driver_path_str
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

mssql_python/libs/x64/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt renamed to mssql_python/libs/windows/x64/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt

File renamed without changes.

mssql_python/libs/x64/MICROSOFT_VISUAL_STUDIO_LICENSE.txt renamed to mssql_python/libs/windows/x64/MICROSOFT_VISUAL_STUDIO_LICENSE.txt

File renamed without changes.

0 commit comments

Comments
 (0)