Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ mssql_python.egg-info/
# The release pipeline still overwrites them per-platform for the full matrix.
mssql_python_odbc.egg-info/

# Rust ODBC provider package (mssql-python-rust-odbc).
# The driver binaries under mssql_python_rust_odbc/libs/ are the committed
# source of truth, synced via eng/scripts/sync-mssql-odbc-native-libs.py.
mssql_python_rust_odbc.egg-info/

# Python bytecode
__pycache__/
*.py[cod]
Expand All @@ -41,6 +46,8 @@ build/

# C extensions
*.so
# ...except the committed Rust ODBC provider driver binaries (see note above).
!mssql_python_rust_odbc/libs/**/*.so
*.pyd
*.pdb

Expand Down
43 changes: 42 additions & 1 deletion OneBranchPipelines/build-release-package-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -377,8 +377,12 @@ extends:
# =========================
# mssql-python: 9 Windows + 5 macOS + 4 Linux + 1 Consolidate = 19 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
# mssql-python-rust-odbc: 1 RustOdbc_BuildAll (single host, all 7 wheels) + 1 ConsolidateRustOdbc = 2 stages
# Total: 23 stages, always built together. The mssql-python build stages depend on
# ConsolidateOdbc so they install the external ODBC wheel before pytest.
# mssql-python-rust-odbc is not installed before pytest: it is an optional,
# runtime-selected provider (mssql_python.native_provider), not required to
# import mssql_python or run the default (classic-provider) test suite.
stages:
# =========================
# WINDOWS BUILD STAGES
Expand Down Expand Up @@ -566,3 +570,40 @@ extends:
# 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.

# =========================================================================
# RUST ODBC PACKAGE BUILD STAGE (mssql-python-rust-odbc) — SINGLE HOST
# =========================================================================
# ONE Windows stage (RustOdbc_BuildAll) produces ALL 7 data-only wheels
# (2 Windows + 1 macOS universal2 + 4 Linux) — no native compile, no pytest.
# Mirrors ODBC_BuildAll/ConsolidateOdbc above; the package is pure driver
# data (py3-none-<platform>), so a single host cross-produces every
# platform's wheel from the committed mssql_python_rust_odbc/libs tree via
# setup_rust_odbc.py's RUST_ODBC_TARGET_* overrides.
#
# Unlike ODBC_BuildAll, the mssql-python build stages do NOT depend on
# ConsolidateRustOdbc: the rust-odbc provider is optional and selected at
# runtime (mssql_python.native_provider), so it is not required to import
# mssql_python or run the default (classic-provider) pytest suite.
- template: /OneBranchPipelines/stages/build-rust-odbc-all-stage.yml@self
parameters:
stageName: RustOdbc_BuildAll
jobName: BuildWheel
oneBranchType: '${{ variables.effectiveOneBranchType }}'

# =========================================================================
# CONSOLIDATE RUST ODBC STAGE
# =========================================================================
# Collects the 7 mssql-python-rust-odbc wheels into a single dist/ folder and
# publishes them as `drop_ConsolidateRustOdbc_ConsolidateArtifacts`. The
# release pipeline selects this artifact when its `releasePackage`
# parameter targets mssql-python-rust-odbc.
- stage: ConsolidateRustOdbc
displayName: 'Consolidate All Rust ODBC Artifacts'
dependsOn:
- RustOdbc_BuildAll
jobs:
- template: /OneBranchPipelines/jobs/consolidate-rust-odbc-artifacts-job.yml@self
parameters:
oneBranchType: '${{ variables.effectiveOneBranchType }}'
expectedWheelCount: 7
66 changes: 62 additions & 4 deletions OneBranchPipelines/dummy-release-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ pr: none

# Parameters for DUMMY release pipeline
parameters:
# Which package to test-release. Both are produced by the same build pipeline
# Which package to test-release. All three are produced by the same build pipeline
# (definition 2199); this switches the consolidated artifact and messaging.
- name: releasePackage
displayName: '[TEST] Package to Release'
type: string
values:
- 'mssql-python'
- 'mssql-python-odbc'
- 'mssql-python-rust-odbc'
default: 'mssql-python'

- name: publishSymbols
Expand All @@ -43,12 +44,15 @@ variables:
- group: 'Symbols Publishing' # Contains SymbolServer, SymbolTokenUri variables

# Select which consolidated artifact to download based on the target package.
# Both are produced by the same build pipeline (definition 2199):
# mssql-python -> drop_Consolidate_ConsolidateArtifacts
# mssql-python-odbc -> drop_ConsolidateOdbc_ConsolidateArtifacts
# All three are produced by the same build pipeline (definition 2199):
# mssql-python -> drop_Consolidate_ConsolidateArtifacts
# mssql-python-odbc -> drop_ConsolidateOdbc_ConsolidateArtifacts
# mssql-python-rust-odbc -> drop_ConsolidateRustOdbc_ConsolidateArtifacts
- name: consolidatedArtifactName
${{ if eq(parameters.releasePackage, 'mssql-python-odbc') }}:
value: 'drop_ConsolidateOdbc_ConsolidateArtifacts'
${{ elseif eq(parameters.releasePackage, 'mssql-python-rust-odbc') }}:
value: 'drop_ConsolidateRustOdbc_ConsolidateArtifacts'
${{ else }}:
value: 'drop_Consolidate_ConsolidateArtifacts'

Expand Down Expand Up @@ -268,6 +272,60 @@ extends:
Write-Host "OK: wheel depends on mssql-python-odbc==$expectedVersion"
}

# Guard: same check as above, for the mssql-python-rust-odbc pin.
# $expectedVersion is derived from mssql_python_rust_odbc/__init__.py
# (single source of truth), so it never needs a manual bump.
- task: PowerShell@2
displayName: '[TEST] Validate mssql-python-rust-odbc pin in wheel metadata'
inputs:
targetType: 'inline'
script: |
$ErrorActionPreference = 'Stop'
$initFile = "$(Build.SourcesDirectory)/mssql_python_rust_odbc/__init__.py"
$verMatch = Select-String -Path $initFile -Pattern '__version__\s*=\s*["'']([^"'']+)["'']' | Select-Object -First 1
if (-not $verMatch) {
Write-Error "Could not parse __version__ from $initFile"
exit 1
}
$expectedVersion = $verMatch.Matches[0].Groups[1].Value
Write-Host "Expected mssql-python-rust-odbc pin (from mssql_python_rust_odbc/__init__.py): $expectedVersion"
$wheels = Get-ChildItem -Path "$(Build.SourcesDirectory)/artifacts/dist" -Filter "mssql_python-*.whl"
if ($wheels.Count -eq 0) {
Write-Error "No mssql_python-*.whl found in artifacts/dist to validate the rust-odbc pin."
exit 1
}
Add-Type -AssemblyName System.IO.Compression.FileSystem
$wheel = $wheels[0].FullName
Write-Host "Inspecting wheel metadata: $wheel"
$zip = [System.IO.Compression.ZipFile]::OpenRead($wheel)
$metaEntry = $zip.Entries | Where-Object { $_.FullName -match '\.dist-info/METADATA$' } | Select-Object -First 1
if (-not $metaEntry) {
$zip.Dispose()
Write-Error "METADATA not found inside $wheel"
exit 1
}
$reader = New-Object System.IO.StreamReader($metaEntry.Open())
$metadata = $reader.ReadToEnd()
$reader.Dispose()
$zip.Dispose()
$rustOdbcLines = ($metadata -split "`n") | Where-Object { $_ -match 'Requires-Dist:\s*mssql[-_]python[-_]rust[-_]odbc' }
if (-not $rustOdbcLines) {
Write-Warning "Wheel declares no mssql-python-rust-odbc dependency (pre-pin build). Skipping rust-odbc pin enforcement - expected until setup.py adds install_requires=mssql-python-rust-odbc==$expectedVersion."
} else {
Write-Host "rust-odbc dependency in metadata:"
$rustOdbcLines | ForEach-Object { Write-Host " $($_.Trim())" }
# Anchor with a negative lookahead so 0.1.0.1 does not
# satisfy a 0.1.0 pin, while still allowing a trailing
# space, ';' environment marker, or end of line.
$pinRegex = 'mssql[-_]python[-_]rust[-_]odbc\s*==\s*' + [regex]::Escape($expectedVersion) + '(?![\d.])'
$pinned = $rustOdbcLines | Where-Object { $_ -match $pinRegex }
if (-not $pinned) {
Write-Error "mssql-python wheel declares an mssql-python-rust-odbc dependency but not the exact pin ==$expectedVersion. Release from the POST-pin build with the correct rust-odbc version."
exit 1
}
Write-Host "OK: wheel depends on mssql-python-rust-odbc==$expectedVersion"
}

# Step 4: Verify wheel integrity
- task: PowerShell@2
displayName: '[TEST] Verify Wheel Integrity'
Expand Down
75 changes: 75 additions & 0 deletions OneBranchPipelines/jobs/consolidate-rust-odbc-artifacts-job.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Consolidate Rust ODBC Artifacts Job Template
# Downloads the per-platform `mssql-python-rust-odbc` wheels from the
# RustOdbc_BuildAll stage and consolidates them into a single dist/ folder for
# the release pipeline to publish. Expected: 7 wheels (2 Windows + 1 macOS
# universal2 + 4 Linux).
parameters:
- name: oneBranchType
type: string
default: 'Official'
- name: expectedWheelCount
type: number
default: 7

jobs:
- job: ConsolidateArtifacts
displayName: 'Consolidate All Rust ODBC Platform Artifacts'
condition: succeeded()

pool:
type: linux
isCustom: true
name: Azure Pipelines
vmImage: 'ubuntu-latest'

variables:
# Consolidation only moves files; no binaries to scan.
- name: ob_sdl_binskim_enabled
value: false
- name: ob_outputDirectory
value: '$(Build.ArtifactStagingDirectory)'

steps:
- checkout: self
fetchDepth: 1

# Download only the mssql-python-rust-odbc platform-stage artifacts
# (drop_RustOdbc_*) from the current build. itemPattern scopes this
# download so only the 6 rust-odbc wheels are consolidated here.
- task: DownloadPipelineArtifact@2
displayName: 'Download All Rust ODBC Platform Artifacts'
inputs:
buildType: 'current'
itemPattern: 'drop_RustOdbc_*/**'
targetPath: '$(Pipeline.Workspace)/all-artifacts'

- bash: |
set -e
mkdir -p $(ob_outputDirectory)/dist

echo "Finding all .whl files..."
find $(Pipeline.Workspace)/all-artifacts -name "*.whl" -exec ls -lh {} \;

echo "Copying all wheels to consolidated dist/..."
find $(Pipeline.Workspace)/all-artifacts -name "*.whl" -exec cp -v {} $(ob_outputDirectory)/dist/ \;

echo "Consolidated wheels:"
ls -lh $(ob_outputDirectory)/dist/
WHEEL_COUNT=$(ls -1 $(ob_outputDirectory)/dist/*.whl 2>/dev/null | wc -l)
echo "Total wheel count: $WHEEL_COUNT (expected ${{ parameters.expectedWheelCount }})"
if [ "$WHEEL_COUNT" -ne "${{ parameters.expectedWheelCount }}" ]; then
echo "ERROR: expected ${{ parameters.expectedWheelCount }} wheels but found $WHEEL_COUNT" >&2
exit 1
fi
echo "SUCCESS: all ${{ parameters.expectedWheelCount }} rust-odbc wheels consolidated."
displayName: 'Consolidate rust-odbc wheels'

- task: PublishPipelineArtifact@1
displayName: 'Publish Consolidated Rust ODBC Artifacts'
inputs:
targetPath: '$(ob_outputDirectory)'
# Distinct name so it does not collide with the mssql-python or
# mssql-python-odbc consolidate artifacts in the same build run.
# Matches the OneBranch auto-name for a stage named `ConsolidateRustOdbc`.
artifact: 'drop_ConsolidateRustOdbc_ConsolidateArtifacts'
publishLocation: 'pipeline'
66 changes: 62 additions & 4 deletions OneBranchPipelines/official-release-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ pr: none

# Parameters for release pipeline
parameters:
# Which package to release. Both are produced by the same build pipeline
# Which package to release. All three are produced by the same build pipeline
# (definition 2199); this switches the consolidated artifact and messaging.
- name: releasePackage
displayName: 'Package to Release'
type: string
values:
- 'mssql-python'
- 'mssql-python-odbc'
- 'mssql-python-rust-odbc'
default: 'mssql-python'

- name: publishSymbols
Expand All @@ -43,12 +44,15 @@ variables:
- group: 'Symbols Publishing' # Contains SymbolServer, SymbolTokenUri variables

# Select which consolidated artifact to download/publish based on the target
# package. Both are produced by the same build pipeline (definition 2199):
# mssql-python -> drop_Consolidate_ConsolidateArtifacts
# mssql-python-odbc -> drop_ConsolidateOdbc_ConsolidateArtifacts
# package. All three are produced by the same build pipeline (definition 2199):
# mssql-python -> drop_Consolidate_ConsolidateArtifacts
# mssql-python-odbc -> drop_ConsolidateOdbc_ConsolidateArtifacts
# mssql-python-rust-odbc -> drop_ConsolidateRustOdbc_ConsolidateArtifacts
- name: consolidatedArtifactName
${{ if eq(parameters.releasePackage, 'mssql-python-odbc') }}:
value: 'drop_ConsolidateOdbc_ConsolidateArtifacts'
${{ elseif eq(parameters.releasePackage, 'mssql-python-rust-odbc') }}:
value: 'drop_ConsolidateRustOdbc_ConsolidateArtifacts'
${{ else }}:
value: 'drop_Consolidate_ConsolidateArtifacts'

Expand Down Expand Up @@ -268,6 +272,60 @@ extends:
Write-Host "OK: wheel depends on mssql-python-odbc==$expectedVersion"
}

# Guard: same check as above, for the mssql-python-rust-odbc pin.
# $expectedVersion is derived from mssql_python_rust_odbc/__init__.py
# (single source of truth), so it never needs a manual bump.
- task: PowerShell@2
displayName: 'Validate mssql-python-rust-odbc pin in wheel metadata'
inputs:
targetType: 'inline'
script: |
$ErrorActionPreference = 'Stop'
$initFile = "$(Build.SourcesDirectory)/mssql_python_rust_odbc/__init__.py"
$verMatch = Select-String -Path $initFile -Pattern '__version__\s*=\s*["'']([^"'']+)["'']' | Select-Object -First 1
if (-not $verMatch) {
Write-Error "Could not parse __version__ from $initFile"
exit 1
}
$expectedVersion = $verMatch.Matches[0].Groups[1].Value
Write-Host "Expected mssql-python-rust-odbc pin (from mssql_python_rust_odbc/__init__.py): $expectedVersion"
$wheels = Get-ChildItem -Path "$(Build.SourcesDirectory)/artifacts/dist" -Filter "mssql_python-*.whl"
if ($wheels.Count -eq 0) {
Write-Error "No mssql_python-*.whl found in artifacts/dist to validate the rust-odbc pin."
exit 1
}
Add-Type -AssemblyName System.IO.Compression.FileSystem
$wheel = $wheels[0].FullName
Write-Host "Inspecting wheel metadata: $wheel"
$zip = [System.IO.Compression.ZipFile]::OpenRead($wheel)
$metaEntry = $zip.Entries | Where-Object { $_.FullName -match '\.dist-info/METADATA$' } | Select-Object -First 1
if (-not $metaEntry) {
$zip.Dispose()
Write-Error "METADATA not found inside $wheel"
exit 1
}
$reader = New-Object System.IO.StreamReader($metaEntry.Open())
$metadata = $reader.ReadToEnd()
$reader.Dispose()
$zip.Dispose()
$rustOdbcLines = ($metadata -split "`n") | Where-Object { $_ -match 'Requires-Dist:\s*mssql[-_]python[-_]rust[-_]odbc' }
if (-not $rustOdbcLines) {
Write-Warning "Wheel declares no mssql-python-rust-odbc dependency (pre-pin build). Skipping rust-odbc pin enforcement - expected until setup.py adds install_requires=mssql-python-rust-odbc==$expectedVersion."
} else {
Write-Host "rust-odbc dependency in metadata:"
$rustOdbcLines | ForEach-Object { Write-Host " $($_.Trim())" }
# Anchor with a negative lookahead so 0.1.0.1 does not
# satisfy a 0.1.0 pin, while still allowing a trailing
# space, ';' environment marker, or end of line.
$pinRegex = 'mssql[-_]python[-_]rust[-_]odbc\s*==\s*' + [regex]::Escape($expectedVersion) + '(?![\d.])'
$pinned = $rustOdbcLines | Where-Object { $_ -match $pinRegex }
if (-not $pinned) {
Write-Error "mssql-python wheel declares an mssql-python-rust-odbc dependency but not the exact pin ==$expectedVersion. Release from the POST-pin build with the correct rust-odbc version."
exit 1
}
Write-Host "OK: wheel depends on mssql-python-rust-odbc==$expectedVersion"
}

# Step 4: Verify wheel integrity
- task: PowerShell@2
displayName: 'Verify Wheel Integrity'
Expand Down
Loading
Loading