Skip to content

Commit 2eb7929

Browse files
bewithgauravCopilotsumitmsft
authored
FEAT: Support Python 3.15 (#817)
### Summary Adds Python 3.15 validation while keeping every existing PR-validation leg enabled. - Validates Python 3.15 GA on Windows x64 and macOS x86_64 through the hosted Python toolcache. - Keeps Linux x86_64 and Linux ARM64 on RC3 until the official `python:3.15.0-bookworm` image is published. - Uses PyArrow 25.0.1 below Python 3.15 and stable PyArrow 26.0.0 on Python 3.15+. - Handles Python 3.15 POSIX feature macros, Decimal conversion traceback privacy, and cyclic-GC finalization ordering. - Inherits the stable ABI `mssql-python-rs 0.3.0` installer contract merged in #804. ### Validation - Python 3.15.0 is marked stable in the hosted Python toolcache. - Stable PyArrow 26.0.0 publishes `cp315` wheels for every target platform. - Focused pipeline and hardening contracts: 9 passed, 6 platform skips. - Python 3.10 release locks continue to resolve PyArrow 25.0.1. ### Follow-up Move both Linux legs from RC3 to GA once the official Bookworm image is published. The native hardening changes overlap with #810; pull `main` again after #810 merges. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Sumit Sarabhai <sumitsar@microsoft.com>
1 parent 0facea3 commit 2eb7929

14 files changed

Lines changed: 234 additions & 56 deletions

‎.github/workflows/refresh-build-dependencies.yml‎

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,25 +130,43 @@ jobs:
130130
- name: Linux build
131131
artifact: linux
132132
lock: requirements-build-linux.txt
133-
versions: "3.10 3.11 3.12 3.13 3.14"
133+
versions: "3.10 3.11 3.12 3.13 3.14 3.15"
134134
platforms: "x86_64-manylinux_2_28 aarch64-manylinux_2_28 x86_64-unknown-linux-musl aarch64-unknown-linux-musl"
135135
- name: Linux test
136136
artifact: linux
137137
lock: requirements-test-linux.txt
138138
versions: "3.10 3.11 3.12 3.13 3.14"
139139
platforms: "x86_64-manylinux_2_28 aarch64-manylinux_2_28 x86_64-unknown-linux-musl aarch64-unknown-linux-musl"
140+
- name: Linux test Python 3.15
141+
artifact: linux
142+
lock: requirements-test-linux.txt
143+
versions: "3.15"
144+
platforms: "x86_64-manylinux_2_28 aarch64-manylinux_2_28 x86_64-unknown-linux-musl aarch64-unknown-linux-musl"
145+
allowSourceBuilds: true
140146
- name: macOS
141147
artifact: macos
142148
lock: requirements-build-macos.txt
143149
versions: "3.10 3.11 3.12 3.13 3.14"
144150
platforms: "x86_64-apple-darwin aarch64-apple-darwin"
151+
- name: macOS Python 3.15
152+
artifact: macos
153+
lock: requirements-build-macos.txt
154+
versions: "3.15"
155+
platforms: "x86_64-apple-darwin aarch64-apple-darwin"
156+
allowSourceBuilds: true
145157
# Windows ARM64 stages install build tools into the x64 host
146158
# interpreter before cross-compiling the target extension.
147159
- name: Windows build host
148160
artifact: windows
149161
lock: requirements-build-windows.txt
150162
versions: "3.10 3.11 3.12 3.13 3.14"
151163
platforms: "x86_64-pc-windows-msvc"
164+
- name: Windows build host Python 3.15
165+
artifact: windows
166+
lock: requirements-build-windows.txt
167+
versions: "3.15"
168+
platforms: "x86_64-pc-windows-msvc"
169+
allowSourceBuilds: true
152170
- name: ODBC
153171
artifact: odbc
154172
lock: requirements-build-odbc.txt
@@ -168,8 +186,11 @@ jobs:
168186
LOCK_FILE: generated-lock/${{ matrix.lock }}
169187
PLATFORMS: ${{ matrix.platforms }}
170188
PYTHON_VERSIONS: ${{ matrix.versions }}
189+
ALLOW_SOURCE_BUILDS: ${{ matrix.allowSourceBuilds || false }}
171190
run: |
172191
set -euo pipefail
192+
binary_args=(--only-binary :all:)
193+
if [ "$ALLOW_SOURCE_BUILDS" = true ]; then binary_args=(); fi
173194
for platform in $PLATFORMS; do
174195
for version in $PYTHON_VERSIONS; do
175196
echo "Validating $LOCK_FILE for Python $version on $platform"
@@ -179,7 +200,7 @@ jobs:
179200
--target "$RUNNER_TEMP/lock-validation" \
180201
--python-version "$version" \
181202
--python-platform "$platform" \
182-
--only-binary :all: \
203+
"${binary_args[@]}" \
183204
--require-hashes \
184205
--default-index https://pypi.org/simple \
185206
-r "$LOCK_FILE"

‎OneBranchPipelines/build-release-package-pipeline.yml‎

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
# OneBranch Release Pipeline for mssql-python
33
# =========================================================================================
44
# Builds Python wheels for all supported platforms with SDL compliance:
5-
# - Windows: Python 3.10-3.14 (x64 + ARM64)
6-
# - macOS: Python 3.10-3.14 (Universal2 = x86_64 + ARM64 in single binary)
7-
# - Linux: Python 3.10-3.14 on manylinux/musllinux (x86_64 + ARM64)
5+
# - Windows: Python 3.10-3.15 (x64 + ARM64)
6+
# - macOS: Python 3.10-3.15 (Universal2 = x86_64 + ARM64 in single binary)
7+
# - Linux: Python 3.10-3.15 on manylinux/musllinux (x86_64 + ARM64)
88
#
99
# Security Features:
1010
# - ESRP code signing (Windows .pyd files only)
@@ -82,19 +82,19 @@ parameters:
8282
# PLATFORM CONFIGURATIONS
8383
# =========================
8484
# Each platform uses different matrix strategy:
85-
# - Windows: Explicit per-version stages (9 stages for x64/ARM64 combos)
86-
# - macOS: Explicit per-version stages (5 stages for universal2 builds)
85+
# - Windows: Explicit per-version stages (11 stages for x64/ARM64 combos)
86+
# - macOS: Explicit per-version stages (6 stages for universal2 builds)
8787
# - Linux: Per-distro stages, builds ALL Python versions in loop (4 stages)
8888

8989
# Windows Configuration Matrix
9090
# Each entry creates separate stage: Win_py<pyVer>_<arch>
91-
# pyVer format: '310' = Python 3.10, '314' = Python 3.14
91+
# pyVer format: '310' = Python 3.10, '315' = Python 3.15
9292
# arch: 'x64' (Intel/AMD 64-bit) or 'arm64' (ARM64, cross-compiled on x64)
9393
# Note: ARM64 builds use x64 host with ARM64 python.lib for cross-compilation
9494
- name: windowsConfigs
9595
type: object
9696
default:
97-
# x64 builds (5 versions: 3.10-3.14)
97+
# x64 builds (6 versions: 3.10-3.15)
9898
- pyVer: '310'
9999
arch: 'x64'
100100
- pyVer: '311'
@@ -105,7 +105,9 @@ parameters:
105105
arch: 'x64'
106106
- pyVer: '314'
107107
arch: 'x64'
108-
# ARM64 builds (4 versions: 3.11-3.14)
108+
- pyVer: '315'
109+
arch: 'x64'
110+
# ARM64 builds (5 versions: 3.11-3.15)
109111
# 3.10 excluded due to limited ARM64 support
110112
- pyVer: '311'
111113
arch: 'arm64'
@@ -115,23 +117,26 @@ parameters:
115117
arch: 'arm64'
116118
- pyVer: '314'
117119
arch: 'arm64'
120+
- pyVer: '315'
121+
arch: 'arm64'
118122

119123
# macOS Configuration Matrix
120124
# Each entry creates separate stage: MacOS_py<pyVer>
121125
# All builds are Universal2 (x86_64 + ARM64 in single binary)
122-
# pyVer format: '310' = Python 3.10, '314' = Python 3.14
126+
# pyVer format: '310' = Python 3.10, '315' = Python 3.15
123127
- name: macosConfigs
124128
type: object
125129
default:
126-
# 5 versions: 3.10-3.14 (all universal2)
130+
# 6 versions: 3.10-3.15 (all universal2)
127131
- pyVer: '310'
128132
- pyVer: '311'
129133
- pyVer: '312'
130134
- pyVer: '313'
131135
- pyVer: '314'
136+
- pyVer: '315'
132137

133138
# Linux Configuration Matrix
134-
# Each entry creates ONE stage that builds ALL Python versions (3.10-3.14)
139+
# Each entry creates ONE stage that builds ALL Python versions (3.10-3.15)
135140
# tag: 'manylinux' (glibc-based, e.g., Ubuntu/CentOS) or 'musllinux' (musl-based, e.g., Alpine)
136141
# arch: CPU architecture for Docker platform
137142
# platform: Docker platform identifier for multi-arch builds
@@ -375,17 +380,17 @@ extends:
375380
# =========================
376381
# PIPELINE STAGES
377382
# =========================
378-
# mssql-python: 9 Windows + 5 macOS + 4 Linux + 1 Consolidate = 19 stages
383+
# mssql-python: 11 Windows + 6 macOS + 4 Linux + 1 Consolidate = 22 stages
379384
# mssql-python-odbc: 1 ODBC_BuildAll (single host, all 7 wheels) + 1 ConsolidateOdbc = 2 stages
380-
# Total: 21 stages, always built together. The mssql-python build stages depend on
385+
# Total: 24 stages, always built together. The mssql-python build stages depend on
381386
# ConsolidateOdbc so they install the external ODBC wheel before pytest.
382387
stages:
383388
# =========================
384389
# WINDOWS BUILD STAGES
385390
# =========================
386391
# Strategy: Explicit stage per Python version × architecture
387-
# Total: 9 stages (5 x64 + 4 ARM64)
388-
# Python versions: 3.10-3.14 (x64), 3.11-3.14 (ARM64)
392+
# Total: 11 stages (6 x64 + 5 ARM64)
393+
# Python versions: 3.10-3.15 (x64), 3.11-3.15 (ARM64)
389394
# Each stage:
390395
# 1. Installs Python (UsePythonVersion or NuGet for 3.14)
391396
# 2. Downloads ARM64 python.lib if cross-compiling
@@ -416,7 +421,7 @@ extends:
416421
# MACOS BUILD STAGES
417422
# =========================
418423
# Strategy: Explicit stage per Python version
419-
# Total: 5 stages (3.10-3.14)
424+
# Total: 6 stages (3.10-3.15)
420425
# All builds are Universal2 (x86_64 + ARM64 in single .so binary)
421426
# Each stage:
422427
# 1. Installs Python via UsePythonVersion@0
@@ -446,20 +451,20 @@ extends:
446451
# =========================
447452
# Strategy: One stage per distribution × architecture
448453
# Total: 4 stages (manylinux_2_28×2 + musllinux×2)
449-
# Each stage builds ALL Python versions (3.10-3.14) in a loop
454+
# Each stage builds ALL Python versions (3.10-3.15) in a loop
450455
# Distributions:
451456
# - manylinux_2_28: glibc 2.28+ (RHEL 8, Ubuntu 20.04+, etc.) — forward-compatible with newer glibc
452457
# - musllinux: musl-based (Alpine Linux)
453458
# Architectures: x86_64 (AMD/Intel), aarch64 (ARM64)
454459
# Each stage:
455460
# 1. Starts PyPA Docker container (manylinux_2_28 or musllinux_1_2)
456461
# 2. Starts SQL Server Docker container
457-
# 3. For each Python version (cp310-cp314):
462+
# 3. For each Python version (cp310-cp315):
458463
# a. Builds .so native extension
459464
# b. Builds wheel
460465
# c. Installs wheel in isolated directory
461466
# d. Runs pytest against SQL Server
462-
# 4. Publishes artifacts (all 5 wheels)
467+
# 4. Publishes artifacts (all 6 wheels)
463468
# 5. Component Governance + AntiMalware scanning
464469
- ${{ each config in parameters.linuxConfigs }}:
465470
- template: /OneBranchPipelines/stages/build-linux-single-stage.yml@self
@@ -479,7 +484,7 @@ extends:
479484
# CONSOLIDATE STAGE
480485
# =========================
481486
# Purpose: Collect all artifacts from platform builds into single dist/ folder
482-
# Dependencies: All 18 build stages (9 Windows + 5 macOS + 4 Linux)
487+
# Dependencies: All 21 build stages (11 Windows + 6 macOS + 4 Linux)
483488
# Stages run in parallel, Consolidate waits for ALL to complete
484489
# Outputs:
485490
# - dist/wheels/*.whl (all platform wheels)
@@ -490,22 +495,25 @@ extends:
490495
- stage: Consolidate
491496
displayName: 'Consolidate All Artifacts'
492497
dependsOn:
493-
# Windows dependencies (9 stages)
498+
# Windows dependencies (11 stages)
494499
- Win_py310_x64
495500
- Win_py311_x64
496501
- Win_py312_x64
497502
- Win_py313_x64
498503
- Win_py314_x64
504+
- Win_py315_x64
499505
- Win_py311_arm64
500506
- Win_py312_arm64
501507
- Win_py313_arm64
502508
- Win_py314_arm64
503-
# macOS dependencies (5 stages)
509+
- Win_py315_arm64
510+
# macOS dependencies (6 stages)
504511
- MacOS_py310
505512
- MacOS_py311
506513
- MacOS_py312
507514
- MacOS_py313
508515
- MacOS_py314
516+
- MacOS_py315
509517
# Linux dependencies (4 stages)
510518
- Linux_manylinux_2_28_x86_64
511519
- Linux_manylinux_2_28_aarch64

‎OneBranchPipelines/jobs/consolidate-artifacts-job.yml‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Consolidate Artifacts Job Template
22
# Downloads artifacts from all platform build stages and consolidates into single dist/ folder
3-
# Works with individual stage artifacts (18 stages total: 9 Windows + 5 macOS + 4 Linux)
4-
# Each Linux stage builds 5 Python versions (3.10-3.14), resulting in 34 total wheels
3+
# Works with individual stage artifacts (21 stages total: 11 Windows + 6 macOS + 4 Linux)
4+
# Each Linux stage builds 6 Python versions (3.10-3.15), resulting in 41 total wheels
55
parameters:
66
- name: oneBranchType
77
type: string
@@ -75,17 +75,17 @@ jobs:
7575
echo ""
7676
WHEEL_COUNT=$(ls -1 $(ob_outputDirectory)/dist/*.whl 2>/dev/null | wc -l)
7777
echo "Total wheel count: $WHEEL_COUNT"
78-
echo "Expected: 34 wheels (9 Windows + 5 macOS + 20 Linux)"
78+
echo "Expected: 41 wheels (11 Windows + 6 macOS + 24 Linux)"
7979
8080
# Hard-fail on a wheel-count mismatch (symmetric with the odbc consolidate job).
8181
# A mismatch means a build stage silently produced a partial set (e.g. a missing
8282
# Python version) or the build matrix changed without updating this count.
83-
# Update the expected 34 whenever the Windows/macOS/Linux build matrix changes.
84-
if [ "$WHEEL_COUNT" -ne 34 ]; then
85-
echo "ERROR: Expected 34 wheels but found $WHEEL_COUNT" >&2
83+
# Update the expected 41 whenever the Windows/macOS/Linux build matrix changes.
84+
if [ "$WHEEL_COUNT" -ne 41 ]; then
85+
echo "ERROR: Expected 41 wheels but found $WHEEL_COUNT" >&2
8686
exit 1
8787
else
88-
echo "SUCCESS: All 34 wheels consolidated!"
88+
echo "SUCCESS: All 41 wheels consolidated!"
8989
fi
9090
displayName: 'Consolidate wheels from all platforms'
9191

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Linux Single Configuration Stage Template
22
# Builds Python wheels for a specific Linux distribution and architecture
3-
# Builds for Python 3.10, 3.11, 3.12, 3.13, 3.14 within single job
3+
# Builds for Python 3.10, 3.11, 3.12, 3.13, 3.14, 3.15 within single job
44
# Tests each wheel after building with isolated pytest execution
55
parameters:
66
# Stage identifier (e.g., 'Linux_manylinux_2_28_x86_64')
@@ -256,14 +256,14 @@ stages:
256256
ls -1 "$(Build.SourcesDirectory)/odbc_wheels"/*.whl
257257
displayName: 'Flatten ODBC wheels for pip find-links'
258258
259-
# Build wheels for all Python versions (3.10-3.14) and test each one
259+
# Build wheels for all Python versions (3.10-3.15) and test each one
260260
- script: |
261261
set -euo pipefail
262262
if [[ "$(LINUX_TAG)" == "manylinux_2_28" ]]; then SHELL_EXE=bash; else SHELL_EXE=sh; fi
263263
docker exec build-$(LINUX_TAG)-$(ARCH) $SHELL_EXE -lc 'mkdir -p /workspace/dist'
264264
265265
# Loop through all Python versions: build wheel -> test wheel -> repeat
266-
for PYBIN in cp310 cp311 cp312 cp313 cp314; do
266+
for PYBIN in cp310 cp311 cp312 cp313 cp314 cp315; do
267267
echo ""
268268
echo "====================================================="
269269
echo "Building and testing $PYBIN on $(LINUX_TAG)/$(ARCH)"
@@ -440,7 +440,7 @@ stages:
440440
echo "====================================================="
441441
echo "✓ All Python versions built and tested successfully!"
442442
echo "====================================================="
443-
displayName: 'Build and test wheels for Python 3.10-3.14'
443+
displayName: 'Build and test wheels for Python 3.10-3.15'
444444
env:
445445
DB_PASSWORD: $(DB_PASSWORD)
446446

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ stages:
201201
"3.12" { "3.12.7" }
202202
"3.13" { "3.13.0" }
203203
"3.14" { "3.14.0" }
204+
"3.15" { "3.15.0" }
204205
}
205206
206207
$nugetUrl = "https://www.nuget.org/api/v2/package/pythonarm64/$nugetVersion"

‎eng/pipelines/pr-validation-pipeline.yml‎

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ jobs:
6666
LocalDB_Python314:
6767
sqlVersion: 'LocalDB'
6868
pythonVersion: '3.14'
69+
LocalDB_Python315:
70+
sqlVersion: 'LocalDB'
71+
pythonVersion: '3.15'
6972

7073
steps:
7174
- checkout: self
@@ -74,6 +77,7 @@ jobs:
7477
inputs:
7578
versionSpec: '$(pythonVersion)'
7679
addToPath: true
80+
allowUnstable: true
7781
githubToken: $(GITHUB_TOKEN)
7882
displayName: 'Use Python $(pythonVersion)'
7983

@@ -582,6 +586,10 @@ jobs:
582586
sqlServerImage: 'mcr.microsoft.com/mssql/server:2025-latest'
583587
sqlVersion: 'SQL2025'
584588
pythonVersion: '3.14'
589+
SQL2025_Python315:
590+
sqlServerImage: 'mcr.microsoft.com/mssql/server:2025-latest'
591+
sqlVersion: 'SQL2025'
592+
pythonVersion: '3.15'
585593

586594
steps:
587595
- checkout: self
@@ -591,6 +599,7 @@ jobs:
591599
inputs:
592600
versionSpec: '$(pythonVersion)'
593601
addToPath: true
602+
allowUnstable: true
594603
displayName: 'Use Python $(pythonVersion) on macOS'
595604

596605
- task: Cache@2
@@ -768,6 +777,11 @@ jobs:
768777
distroName: 'Debian-SQL2025'
769778
sqlServerImage: 'mcr.microsoft.com/mssql/server:2025-latest'
770779
useAzureSQL: 'false'
780+
Debian_Python315:
781+
dockerImage: 'python:3.15.0rc3-bookworm'
782+
distroName: 'Debian-Python315'
783+
sqlServerImage: 'mcr.microsoft.com/mssql/server:2025-latest'
784+
useAzureSQL: 'false'
771785

772786
steps:
773787
- checkout: self
@@ -797,7 +811,17 @@ jobs:
797811
798812
- script: |
799813
# Install dependencies in the container
800-
if [ "$(distroName)" = "Ubuntu" ]; then
814+
if [ "$(distroName)" = "Debian-Python315" ]; then
815+
docker exec test-container-$(distroName) bash -c "
816+
set -e
817+
export DEBIAN_FRONTEND=noninteractive
818+
export TZ=UTC
819+
ln -snf /usr/share/zoneinfo/\$TZ /etc/localtime && echo \$TZ > /etc/timezone
820+
apt-get update &&
821+
apt-get install -y cmake curl wget gnupg build-essential &&
822+
python3 --version
823+
"
824+
elif [ "$(distroName)" = "Ubuntu" ]; then
801825
docker exec test-container-$(distroName) bash -c "
802826
export DEBIAN_FRONTEND=noninteractive
803827
export TZ=UTC
@@ -866,6 +890,7 @@ jobs:
866890
displayName: 'Install Python dependencies in $(distroName) container'
867891
868892
- script: |
893+
set -e
869894
# Build pybind bindings in the container
870895
PROFILER_BUILD=0
871896
if [ "$(Build.Reason)" = "PullRequest" ] &&
@@ -1078,6 +1103,10 @@ jobs:
10781103
dockerImage: 'python:3.11-bookworm'
10791104
distroName: 'Debian'
10801105
archName: 'arm64'
1106+
Debian_Python315_ARM64:
1107+
dockerImage: 'python:3.15.0rc3-bookworm'
1108+
distroName: 'Debian-Python315'
1109+
archName: 'arm64'
10811110

10821111
steps:
10831112
- script: |

0 commit comments

Comments
 (0)