Skip to content

Commit 415982c

Browse files
authored
FIX: vendor the correct-arch mssql_py_core into Windows arm64 wheels (#737)
### Work Item / Issue Reference > [AB#47315](https://sqlclientdrivers.visualstudio.com/c6d89619-62de-46a0-8b46-70b92a84d85e/_workitems/edit/47315) ### Summary Windows **arm64** wheels shipped a **wrong-architecture** `mssql_py_core`, breaking bulk copy on arm64. **Root cause.** [`eng/scripts/install-mssql-py-core.ps1`](eng/scripts/install-mssql-py-core.ps1) picked the core wheel from the **host** arch (`platform.machine()`). Windows arm64 wheels are *cross-built on an x64 host*, so the installer always fetched the `win_amd64` core and `setup.py bdist_wheel` packaged it into the arm64 wheel. `validate_mssql_py_core()` only checks that a `.pyd` *exists*, not its architecture, so nothing caught it — the arm64 wheel silently shipped an x64 bulk-copy binary. **Fix (2 files):** - **`install-mssql-py-core.ps1`** — new `-TargetArch` parameter. When set, the core wheel is selected by the **target** arch; `aarch64` maps to `arm64` in the Windows wheel-platform tag so it resolves `win_arm64` (not `win_aarch64`); and the `import mssql_py_core` self-check is skipped on cross-arch builds (an arm64 `.pyd` cannot load in the x64 build Python). Empty `-TargetArch` keeps host behaviour, so x64 and local/native builds are unchanged. - **`OneBranchPipelines/stages/build-windows-single-stage.yml`** — the install step now passes `-TargetArch $(targetArch)`. (`$env:ARCHITECTURE` isn't usable here — it's only `set` inline at `bdist_wheel`, *after* the installer runs.) **Why this is complete and safe:** - The pinned core feed (`mssql-py-core-wheels` **0.1.9**, from `eng/versions/mssql-py-core.version`) contains `win_arm64` wheels for **cp311–cp314**, which exactly matches the Windows arm64 build matrix (`3.11–3.14`; py3.10 arm64 is **not** built) — so every arm64 stage resolves a matching core. No `setup.py` change is needed. - x64 stages: `target == host` → verify still runs, no behaviour change. **Validation.** PowerShell AST parse is clean; the arch-selection was unit-checked on an x64 host — `arm64 → win_arm64` (cross-arch, verify skipped), `x64`/empty `→ win_amd64` (verify runs) — all present in the 0.1.9 feed. Final proof is the arm64 Windows pipeline leg producing a wheel whose vendored `mssql_py_core*.pyd` is ARM64. **Follow-up (separate PR).** The conda `bld.bat` `mssql_py_core` strip can be dropped once these arm64 wheels flow through (the "restore path").
1 parent 0085fd5 commit 415982c

2 files changed

Lines changed: 44 additions & 11 deletions

File tree

‎OneBranchPipelines/stages/build-windows-single-stage.yml‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,10 @@ stages:
299299
inputs:
300300
targetType: 'filePath'
301301
filePath: '$(Build.SourcesDirectory)\eng\scripts\install-mssql-py-core.ps1'
302+
# Pass the TARGET arch so a cross-compiled arm64 wheel vendors the
303+
# win_arm64 core, not the x64 host's. The installer skips its import
304+
# self-check for cross-arch; arm64 bulk copy is exercised on native agents.
305+
arguments: '-TargetArch $(targetArch)'
302306

303307
# =========================
304308
# PHASE 2: EXTERNAL ODBC DRIVER PACKAGE

‎eng/scripts/install-mssql-py-core.ps1‎

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,21 @@
1010
.PARAMETER OutputDir
1111
Temporary directory for downloaded artifacts. Cleaned up after extraction.
1212
Defaults to $env:TEMP\mssql-py-core-wheels.
13+
14+
.PARAMETER TargetArch
15+
Target CPU architecture ('x64' or 'arm64') for cross-compilation builds.
16+
When set, the matching-arch mssql_py_core wheel is selected instead of the
17+
host arch reported by platform.machine() -- required because Windows arm64
18+
wheels are cross-built on an x64 host, where an unset value would vendor the
19+
x64 core into the arm64 wheel. The import self-check is also skipped when the
20+
target differs from the host (a cross-arch .pyd cannot load here). Defaults to
21+
empty (use host arch), which is correct for native/local builds.
1322
#>
1423

1524
param(
1625
[string]$FeedUrl = "https://pkgs.dev.azure.com/sqlclientdrivers/public/_packaging/mssql-rs_Public/nuget/v3/index.json",
17-
[string]$OutputDir = "$env:TEMP\mssql-py-core-wheels"
26+
[string]$OutputDir = "$env:TEMP\mssql-py-core-wheels",
27+
[string]$TargetArch = ""
1828
)
1929

2030
$ErrorActionPreference = 'Stop'
@@ -34,26 +44,37 @@ function Read-PackageVersion {
3444
}
3545

3646
function Get-PlatformInfo {
37-
# Single python call to get version, platform, and arch
47+
# Single python call to get version, platform, and HOST arch
3848
$info = & python -c "import sys, platform; v = sys.version_info; print(f'cp{v.major}{v.minor} {platform.system().lower()} {platform.machine().lower()}')"
3949
if ($LASTEXITCODE -ne 0) { throw "Failed to detect Python platform info" }
4050

4151
$parts = $info -split ' '
4252
$script:PyVersion = $parts[0]
4353
$script:Platform = $parts[1]
44-
$script:Arch = $parts[2]
54+
$hostArch = $parts[2]
55+
56+
# Normalize an arch string (platform.machine() or -TargetArch) to a wheel tag.
57+
function ConvertTo-ArchTag($a) {
58+
switch -Regex ($a) {
59+
'amd64|x86_64|x64' { return 'x86_64' }
60+
'arm64|aarch64' { return 'aarch64' }
61+
default { throw "Unsupported architecture: $a" }
62+
}
63+
}
4564

46-
Write-Host "Python: $script:PyVersion | Platform: $script:Platform | Arch: $script:Arch"
65+
# Select the wheel by TARGET arch, not host arch. Windows arm64 wheels are
66+
# cross-built on an x64 host, where the running Python is x64 and reports the
67+
# HOST via platform.machine(); without this override the installer would vendor
68+
# the x64 core into the arm64 wheel. Empty -TargetArch (native/local) -> host.
69+
$hostArchTag = ConvertTo-ArchTag $hostArch
70+
$archTag = if ($TargetArch) { ConvertTo-ArchTag $TargetArch } else { $hostArchTag }
71+
$script:IsCrossArch = ($archTag -ne $hostArchTag)
4772

48-
# Normalize arch tag
49-
$archTag = switch -Regex ($script:Arch) {
50-
'amd64|x86_64' { 'x86_64' }
51-
'arm64|aarch64' { 'aarch64' }
52-
default { throw "Unsupported architecture: $script:Arch" }
53-
}
73+
Write-Host "Python: $script:PyVersion | Platform: $script:Platform | Arch: $archTag (host: $hostArchTag, target: $(if ($TargetArch) { $TargetArch } else { '<host>' }))"
5474

5575
$script:WheelPlatform = switch ($script:Platform) {
56-
'windows' { "win_$($archTag -replace 'x86_64','amd64')" }
76+
# aarch64 -> arm64 so a Windows arm64 target resolves win_arm64, not win_aarch64.
77+
'windows' { "win_$($archTag -replace 'x86_64','amd64' -replace 'aarch64','arm64')" }
5778
'linux' { "linux_$archTag" }
5879
'darwin' { 'macosx_15_0_universal2' }
5980
default { throw "Unsupported platform: $script:Platform" }
@@ -123,6 +144,14 @@ function Install-AndVerify {
123144
& python "$ScriptDir\extract_wheel.py" $script:MatchingWheel.FullName $RepoRoot
124145
if ($LASTEXITCODE -ne 0) { throw "Failed to extract mssql_py_core from wheel" }
125146

147+
# Skip the import self-check on cross-arch builds: an arm64 .pyd cannot be
148+
# loaded by the x64 Python running on the build host. The wheel is still
149+
# vendored correctly and is exercised by tests on native-arch agents.
150+
if ($script:IsCrossArch) {
151+
Write-Host "Skipping import verification (cross-arch build: target != host)"
152+
return
153+
}
154+
126155
Write-Host "Verifying import..."
127156
Push-Location $RepoRoot
128157
try {

0 commit comments

Comments
 (0)