Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
41a0f33
CHORE: validate Python 3.15 RC2 across PR CI
bewithgaurav Sep 21, 2026
bf20afd
FIX: enforce POSIX native binary hardening
sumitmsft Sep 23, 2026
dd1b21c
FIX: isolate concurrent auth connection mocks
sumitmsft Sep 24, 2026
d3f01a3
Merge branch 'main' into sumitsar/fix-cf041-native-hardening
bewithgaurav Sep 24, 2026
8cf2999
CHORE: validate Python 3.15 with abi3 Rust wheels
bewithgaurav Sep 24, 2026
b9a2b2e
FIX: enforce POSIX native binary hardening
sumitmsft Sep 23, 2026
16729ab
TEST: focus Python 3.15 preview validation
bewithgaurav Sep 24, 2026
5740776
FIX: unblock Python 3.15 validation
bewithgaurav Sep 24, 2026
5d9e7db
FIX: complete Python 3.15 test compatibility
bewithgaurav Sep 24, 2026
78c794f
FIX: adapt error and GC handling for Python 3.15
bewithgaurav Sep 24, 2026
6cf3549
TEST: restore the full PR validation matrix
bewithgaurav Sep 25, 2026
bf75844
Merge PR 810 hardening branch
bewithgaurav Sep 25, 2026
edac63d
Merge branch 'main' into bewithgaurav/python-3.15-validation
bewithgaurav Sep 25, 2026
c7ff914
Merge remote-tracking branch 'origin/main' into bewithgaurav/python-3…
bewithgaurav Oct 1, 2026
8084f02
CHORE: sync Python 3.15 validation with main
bewithgaurav Oct 1, 2026
6942f4a
CHORE: advance hosted Python 3.15 validation to RC3
bewithgaurav Oct 5, 2026
39a06fc
Merge remote-tracking branch 'origin/main' into bewithgaurav/python-3…
bewithgaurav Oct 12, 2026
aab35f4
CHORE: advance Python 3.15 validation to GA
bewithgaurav Oct 12, 2026
242b059
FIX: address Python 3.15 review findings
bewithgaurav Oct 12, 2026
79243f2
FIX: preserve Python 3.10 dependency resolution
bewithgaurav Oct 12, 2026
e062c6d
Merge branch 'main' into bewithgaurav/python-3.15-validation
bewithgaurav Oct 12, 2026
5a106b8
REFACTOR: keep Python 3.15 support scoped
bewithgaurav Oct 12, 2026
2666338
CHORE: refresh universal locks for Python 3.15
bewithgaurav Oct 12, 2026
1c897dc
FIX: keep Python 3.15 review fixes behavioral
bewithgaurav Oct 12, 2026
661e485
FEAT: Build Python 3.15 release wheels (#838)
bewithgaurav Oct 12, 2026
9b89361
FIX: discard Decimal conversion exception context
bewithgaurav Oct 12, 2026
18e1ab9
FEAT: Add Python 3.15 Conda support
jahnvi480 Oct 12, 2026
dac63f0
FIX: Pin the published Conda PR smoke baseline
jahnvi480 Oct 12, 2026
eb49645
Merge branch 'main' into jahnvi/conda-python-3-15
jahnvi480 Oct 12, 2026
db5a336
FIX: Select Conda smoke inputs from PyPI
jahnvi480 Oct 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions .github/workflows/conda-audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@
#
# The OneBranch conda-build pipeline is `trigger: none` / `pr: none`, so its blocking
# audit never runs on a PR. This lightweight GitHub Actions job builds ONE real
# linux-64 conda package from the SHIPPED PyPI wheels and runs
# linux-64 conda package from the current published PyPI wheels and runs
# python -m eng.conda_tools elf on it, so a regression in the $ORIGIN climb,
# the declared conda deps (krb5/libtool/openssl), or the expected DT_NEEDED set fails
# the PR automatically -- the full-agent runtime masking cannot hide it.
#
# build.sh ALWAYS stamps the relative $ORIGIN climb onto the vendored Linux ODBC .so
# (they are not code-signed, so the patch breaks no signature; an already-baked climb
# is a byte-for-byte no-op). This gate builds one real linux-64 conda package from the
# shipped PyPI wheels and audits the resulting climb (see conda/mssql-python/build.sh).
# published wheels and audits the resulting climb (see conda/mssql-python/build.sh).
# It is a published CPython 3.11 packaging control, not current-source RS/3.15
# qualification; source-only tests above it remain current-checkout and blocking.
#
# PR triggers are conda-specific: general package/version changes and shared wheel
# build infrastructure do not run this gate unless the PR also changes a path below.
Expand Down Expand Up @@ -72,15 +74,15 @@ jobs:
tests/test_038_conda_archive_limits.py \
tests/test_027_conda_release_metadata.py -q

- name: Fetch the shipped linux wheels from PyPI
- name: Fetch the published CPython 3.11 smoke wheels from PyPI
id: public-wheels
working-directory: ${{ github.workspace }}
run: |
set -euo pipefail
# Exact maintained release versions and PyPI digests, never an older/latest fallback.
# Fetch RS only if binding METADATA requires it, using its distribution (not NuGet) pin.
# Embedded-core public wheels are historical controls, not current-source RS proof.
# Select PyPI's current binding and its exact ODBC/RS dependencies, not source pins.
# Verify PyPI hashes and actual METADATA/WHEEL/RECORD; no older-release fallback.
python -m eng.conda_tools fetch-wheels \
--published-smoke \
--wheel-dir "$PWD/wheels" --requirements-file "$PWD/wheel-inputs.txt" \
--python-tag cp311 --conda-subdir linux-64
echo "Downloaded:"; ls -1 wheels
Expand All @@ -91,10 +93,9 @@ jobs:
RS_REQUIRED: ${{ steps.public-wheels.outputs.rsRequired }}
run: |
set -euo pipefail
# The verified public dependency/ownership facts select the actual input set.
rsArgs=()
if [[ "$RS_REQUIRED" == "true" ]]; then
rsArgs=(--rs-wheel-dir "$PWD/wheels" --rs-version-file "$PWD/eng/versions/mssql-python-rs.version")
rsArgs=(--rs-wheel-dir "$PWD/wheels")
elif [[ "$RS_REQUIRED" != "false" ]]; then
echo "Missing verified public wheel profile"; exit 1
fi
Expand Down
9 changes: 4 additions & 5 deletions OneBranchPipelines/conda-build-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,11 @@ parameters:
- 'Official'
- 'NonOfficial'
default: 'NonOfficial'
# Python versions to build conda packages for (Windows loops these; the POSIX legs
# auto-detect the same set from the downloaded wheels).
# Ordinary CPython versions passed explicitly to the Windows and POSIX build legs.
- name: pythonVersions
displayName: 'Python versions (comma-separated)'
type: string
default: '3.10,3.11,3.12,3.13,3.14'
default: '3.10,3.11,3.12,3.13,3.14,3.15'
# H1: enable the minimal-base ldd reachability gate (CONDA_ASSERT_PREFIX_REACHABLE).
# It fails CLOSED if the driver binds a system (or absent) krb5/gssapi/libltdl, so it
# is only valid on a leg with NO system copies of those libs -- set true ONLY when
Expand Down Expand Up @@ -198,14 +197,14 @@ extends:
# internally (so a broken/wrong-arch package is never staged), AND win-arm64 is in
# the release gate's requiredSubdirs -- so a MISSING win-arm64 blocks the RELEASE.
# Net: a win-arm64 failure isolates to win-arm64 (win-64 still ships) yet still
# blocks the publish via the gate. DELIBERATELY a FIXED set (3.12-3.14), INDEPENDENT
# blocks the publish via the gate. DELIBERATELY a FIXED set (3.12-3.15), INDEPENDENT
# of the pythonVersions parameter: neither defaults nor conda-forge ships a
# win-arm64 python for 3.10/3.11, so following the param would queue an unbuildable
# 3.10/3.11 arm leg. Its own bld tree + subdir-scoped verify env keep it from
# colliding with win-64 on this shared agent.
- template: /OneBranchPipelines/steps/conda-build-validate-step.yml@self
parameters:
pythonVersion: '3.12,3.13,3.14'
pythonVersion: '3.12,3.13,3.14,3.15'
condaSubdir: 'win-arm64'
condaTargetSubdir: 'win-arm64'
odbcWheelDir: '$(Pipeline.Workspace)/odbc_wheels'
Expand Down
18 changes: 9 additions & 9 deletions OneBranchPipelines/jobs/consolidate-conda-artifacts-job.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@
# Expected packages (validated conda subdirs). The self-contained mssql-python
# package (which vendors the ODBC payload) is emitted per-Python by each build leg;
# there is NO separate companion package:
# win-64 : 5 py x mssql-python = 5
# win-arm64 : 3 py x mssql-python (py3.12-3.14, cross-built on x64) = 3
# osx-64 : 5 py x mssql-python (Intel Mac, native) = 5
# osx-arm64 : 5 py x mssql-python (Intel Mac, cross-built) = 5
# linux-64 : 5 py x mssql-python (glibc x86_64 host, native) = 5
# linux-aarch64 : 5 py x mssql-python (x86_64 host + QEMU, best-effort) = 5
# win-64 : 6 py x mssql-python (py3.10-3.15) = 6
# win-arm64 : 4 py x mssql-python (py3.12-3.15, cross-built on x64) = 4
# osx-64 : 6 py x mssql-python (Intel Mac, native) = 6
# osx-arm64 : 6 py x mssql-python (Intel Mac, cross-built) = 6
# linux-64 : 6 py x mssql-python (glibc x86_64 host, native) = 6
# linux-aarch64 : 6 py x mssql-python (x86_64 host + QEMU, best-effort) = 6
# ------------------------------------------------------------------------------
# TOTAL (PyPI parity minus musllinux) = 28
# win-arm64 is cross-built for py3.12-3.14 only (py3.10/3.11 have no win-arm64 deps
# TOTAL (ordinary CPython, no musllinux or free-threaded variants) = 34
# win-arm64 is cross-built for py3.12-3.15 only (py3.10/3.11 have no win-arm64 deps
# on Anaconda defaults); its runtime import is skipped on the x64 host, so its arch
# is enforced by the PE audit (eng.conda_tools pe). musllinux (no conda
# musl subdir) is intentionally NOT conda-built. This job is BEST-EFFORT and never
Expand Down Expand Up @@ -117,7 +117,7 @@ jobs:
n=$(find "$d" -type f \( -name '*.conda' -o -name '*.tar.bz2' \) | wc -l)
printf ' %-14s %s\n' "$sub" "$n"
done
echo "Total conda package count: $PKG_COUNT (full PyPI-parity set = 28)"
echo "Total conda package count: $PKG_COUNT (full ordinary-CPython set = 34)"

# BEST-EFFORT: never exit non-zero — an empty set emits a warning; a partial set
# remains visible in the counts above. A conda hiccup must not block the wheel
Expand Down
2 changes: 1 addition & 1 deletion OneBranchPipelines/steps/conda-build-validate-step.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
parameters:
# Python version(s) to build, comma-separated X.Y (e.g. '3.13' or '3.12,3.13,3.14'); passed
# straight to the orchestrator's --python-versions. win-64 follows the pythonVersions
# parameter; win-arm64 uses a fixed 3.12-3.14 set (the singular name is kept for callers).
# parameter; win-arm64 uses a fixed 3.12-3.15 set (the singular name is kept for callers).
- name: pythonVersion
type: string
# Conda subdir for this leg's package metadata + staging: 'win-64' (native) or 'win-arm64'
Expand Down
2 changes: 1 addition & 1 deletion OneBranchPipelines/steps/conda-publish-step.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ parameters:
# against this job's independently downloaded artifact before upload.
- name: pythonVersions
type: string
default: '3.10,3.11,3.12,3.13,3.14'
default: '3.10,3.11,3.12,3.13,3.14,3.15'
# The publisher hash lock targets CPython 3.12 / Windows x64; other runtimes reject.
- name: pythonVersion
type: string
Expand Down
4 changes: 2 additions & 2 deletions OneBranchPipelines/steps/conda-release-step.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
# dependency solve PLUS a PE machine-type assert on the vendored arm64 .pyd/.dll, so a
# broken or wrong-arch package is never STAGED. That build leg is continueOnError (so a
# win-arm64 hiccup can't nuke the shared win-64 publish), and THIS release gate is where
# win-arm64 is enforced: a missing win-arm64 fails the gate. Its matrix is 3.12-3.14 (the
# win-arm64 is enforced: a missing win-arm64 fails the gate. Its matrix is 3.12-3.15 (the
# gate's per-subdir override), since Anaconda `defaults` has no win-arm64 python for
# 3.10/3.11. musllinux has no conda subdir at all (conda Linux is glibc-only), so absent.
#
Expand Down Expand Up @@ -64,7 +64,7 @@ parameters:
# subdir. The gate reads each binding's pyXY build tag from info/index.json.
- name: pythonVersions
type: string
default: '3.10,3.11,3.12,3.13,3.14'
default: '3.10,3.11,3.12,3.13,3.14,3.15'
# Optional EXACT expected versions. When set, the gate asserts every package's
# info/index.json version matches; when empty it still enforces one-version-per-
# package consistency plus the subdir / matrix / pairing checks.
Expand Down
43 changes: 36 additions & 7 deletions conda/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,15 @@ The `conda-audit` PR gate triggers only for `conda/`, `eng/conda_tools/`, Conda-
OneBranch pipelines/steps/jobs, their native/probe/archive/provenance and release tests,
and the workflow itself. General package metadata/version changes and shared wheel-build
infrastructure do not trigger it on their own; mixed PRs with a Conda change still do.
This scope does not change the unit-test runner, source-version wheel selection, or
failure handling when a required wheel is unpublished.
Source-only tests remain current-checkout and blocking. The real Linux CPython 3.11
build/audit uses `fetch-wheels --published-smoke`: PyPI's current published binding
and its exact ODBC dependency (plus RS when required). No smoke versions or hashes
are hardcoded; downloads are verified against PyPI's SHA256 values and actual wheel
metadata/ownership. Source release bumps do not select unpublished smoke inputs.
The selected versions are logged and hash-required requirements are generated per run.
Missing wheels, metadata/dependency or hash mismatches and build/audit failures still
fail the gate; there is no skip or older-release fallback. This published packaging
control does not qualify current-source RS or Python 3.15.

Run the source-only trigger regressions without a native build, SQL Server, or PyYAML:

Expand Down Expand Up @@ -112,8 +119,9 @@ macOS retains its external Homebrew/MacPorts OpenSSL prerequisite for encryption
Conda OpenSSL alone does not satisfy the driver's system-path lookup.

The release goal is the matching PyPI release's public API and supported feature
behavior on all 28 ordinary CPython variants: 3.10-3.14 on win-64, linux-64,
linux-aarch64, osx-64 and osx-arm64; 3.12-3.14 on win-arm64. No silent removal of
behavior on all 34 ordinary CPython variants: 3.10-3.15 on win-64, linux-64,
linux-aarch64, osx-64 and osx-arm64; 3.12-3.15 on win-arm64. Free-threaded CPython
(including `cp315t`) and musllinux are not Conda targets. No silent removal of
required native functionality is acceptable. Optional features require their
corresponding dependencies; Windows ARM64 PyArrow availability remains a blocker
to qualifying those features, not permission to drop them.
Expand All @@ -125,7 +133,7 @@ load do not establish those results. Applicable OS, certificate and authenticati
configuration remain external prerequisites.

Use only organizationally approved channels and handle applicable terms separately;
the Windows ARM64 dependency profile includes Anaconda `defaults`. Before unattended
the Windows ARM64 3.12-3.14 dependency profile includes Anaconda `defaults`. Before unattended
Windows ARM64 builds, agent owners must use approved provisioning to put a disposable
Conda installation on `PATH`, with applicable channel terms handled for the job's
execution identity. The orchestrator's automatic Miniforge installation does not
Expand All @@ -134,6 +142,25 @@ or verification solve stops with Conda's diagnostic; provision the prerequisite
running again. Automatic acceptance, including an inherited
`CONDA_PLUGINS_AUTO_ACCEPT_TOS` opt-in, remains disabled.

Windows ARM64 3.15 uses `microsoft`, then `conda-forge`, for both build and verification
(after the local verification channel), without `defaults`. Older targets retain their
existing channel policies and the ARM64 3.12 manual ABI pin. The blocking full dependency
solve remains; every 3.15 package must pass the `python_abi 3.15.* *_cp315` native audit.

### Python 3.15 packaging

Conda-forge's Windows Python 3.15 declares `lib/python/site-packages` instead of
`Lib/site-packages` ([layout change](https://conda-forge.org/blog/2026/09/03/python-315/)).
The Windows recipe uses the target's `SP_DIR` for binding/RS/ODBC extraction, with
matching native and installed-metadata checks. Windows legs containing 3.15 use
**conda-build 26.9.1** for all requested variants: its
[26.9.0 fix](https://github.com/conda/conda-build/releases/tag/26.9.0) reads
`python_site_packages_path` from the target Python record, including cross builds.
25.x computes the old Windows path even for 3.15. Other legs retain `conda-build<26`;
the publishing toolchain is unchanged. The existing RS 0.3.0 stable-ABI selection and
ODBC `py3-none` wheels need no version change. PyArrow stays optional; no speculative
`libpython` dependency or change to the Unix OS floors is introduced.

Run this existing build workflow only in a disposable isolated installation: shared-environment
ownership hardening is outside this change. Publication/provenance tooling is included
alongside this native/tooling foundation; see the
Expand Down Expand Up @@ -262,17 +289,19 @@ members. This detects unowned or cross-owned additions; it cannot recover file o
after an overwrite of an already owned path, which is why the raw pre-extraction gate
is required.

`fetch-wheels` uses exact current maintained versions, hash-required binary-only PyPI
By default, `fetch-wheels` uses exact current maintained versions, hash-required binary-only PyPI
downloads, and actual wheel metadata/ownership. Pip selects for its executing interpreter;
the requested Python/platform must match the binding, ODBC and selected RS wheel tags
before the command accepts the inputs. These arguments validate the download, rather
than enabling cross-target pip resolution. An authentic published embedded-core
binding is explicitly reported as a **historical published packaging control, not
current-source RS qualification**. An RS-dependent published binding requires its exact
provider and source pin; unavailable releases, malformed declarations, or mismatches
provider and selected pin; unavailable releases, malformed declarations, or mismatches
fail with no older-version or TLS fallback. Public availability does not gate the
recorded ADO artifact path. Neither a public input check nor static archive checks
establish installed SQL/runtime qualification.
Only the explicit `--published-smoke` option selects the published inputs above;
production, provenance, preflight and release/publish version selection are unchanged.

The driver-load probe's sole implementation is
`eng/conda_tools/driver_load_probe.py`. It separates native connection execution,
Expand Down
8 changes: 7 additions & 1 deletion conda/mssql-python/bld.bat
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ REM vendor the ODBC Driver 18 payload inside it. conda-build exports PKG_NAME /
REM PKG_VERSION / CONDA_PY; the pipeline exports WHEELS_DIR + MSSQL_ODBC_VERSION.
setlocal enabledelayedexpansion

set "SP=%PREFIX%\Lib\site-packages"
REM conda-build 26.9+ reads the target Python's python_site_packages_path (CFEP-27).
REM Python 3.15 uses lib\python\site-packages; older Windows targets use Lib\site-packages.
if not defined SP_DIR (
echo ERROR: conda-build must supply the target Python site-packages in SP_DIR.
exit /b 1
)
set "SP=%SP_DIR%"
if not exist "%SP%" mkdir "%SP%"
set "PKG_UNDERSCORE=%PKG_NAME:-=_%"

Expand Down
2 changes: 1 addition & 1 deletion conda/mssql-python/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ requirements:
# --python matrix here, so each build emits the matching py3XX package. A range such
# as `python >=3.10` collapses ALL --python builds into ONE cp<newest> package (build
# string "py>=310") whose python_abi is pinned to 3.14, so `conda create python=3.10
# mssql-python` can't solve. The >=3.10 floor is enforced implicitly: only py310-py314
# mssql-python` can't solve. The >=3.10 floor is enforced implicitly: only py310-py315
# are built and each package's python_abi pins its exact minor.
- python
- pip
Expand Down
8 changes: 4 additions & 4 deletions eng/conda_tools/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,10 +302,10 @@ def build_packages(
croot = os.path.join(os.path.dirname(os.path.abspath(bld)), "croot")
if os.path.isdir(croot):
shutil.rmtree(croot)
channels = ["microsoft", "conda-forge"]
if target_subdir == "win-arm64":
channels.insert(0, "defaults")
for py in pyvers:
channels = ["microsoft", "conda-forge"]
if target_subdir == "win-arm64" and py != "3.15":
channels.insert(0, "defaults")
environment._log(
f"=== [py {py}] build mssql-python (self-contained: vendors the ODBC payload) ==="
)
Expand Down Expand Up @@ -471,8 +471,8 @@ def execute(args: argparse.Namespace) -> int:
conda = environment.find_or_install_conda(output_dir)
environment._log(f"Using conda: {conda}")
environment.run([conda, "--version"], what="conda --version")
builder = environment.create_builder_env(conda)
pyvers = detect_pythons(links, python_versions)
builder = environment.create_builder_env(conda, target, pyvers)
cross_build = bool(args.conda_target_subdir)
env = environment.build_env(mssql_ver, odbc_ver, links, args.conda_target_subdir, rs_ver)

Expand Down
15 changes: 13 additions & 2 deletions eng/conda_tools/contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,18 @@ def validate_rs_ownership(
return errors


def site_packages_root(python_tag: str, subdir: str) -> str:
"""Canonical archive root for the supported ordinary CPython target, not the host."""
if not re.fullmatch(r"cp3\d+", python_tag) or subdir not in _RS_PLATFORMS:
raise ValueError(f"Unsupported Conda Python/platform target: {python_tag} {subdir}")
minor = int(python_tag[3:])
if subdir.startswith("win-"):
prefix = "lib/python" if minor >= 15 else "Lib"
else:
prefix = f"lib/python3.{minor}"
return f"{prefix}/site-packages/"


def validate_native_contract(names: Iterable[str], index: dict[str, Any]) -> list[str]:
"""Require a target binding, core extension and initializer; platform audits check headers.

Expand All @@ -436,8 +448,7 @@ def validate_native_contract(names: Iterable[str], index: dict[str, Any]) -> lis
subdir = index["subdir"]
windows = subdir.startswith("win-")
suffix = "pyd" if windows else "so"
prefix = "Lib" if windows else f"lib/python{abi[1]}"
root = f"{prefix}/site-packages/"
root = site_packages_root(f"cp{abi[2]}", subdir)
names = [name.replace("\\", "/") for name in names]
bindings = [
name
Expand Down
Loading
Loading