diff --git a/eng/pipelines/pr-validation-pipeline.yml b/eng/pipelines/pr-validation-pipeline.yml index 14e07885..1fc10024 100644 --- a/eng/pipelines/pr-validation-pipeline.yml +++ b/eng/pipelines/pr-validation-pipeline.yml @@ -6,1936 +6,631 @@ trigger: include: - main -jobs: -- job: CodeQLAnalysis - displayName: 'CodeQL Security Analysis' - pool: - vmImage: 'ubuntu-latest' - - steps: - - script: | - sudo apt-get update - sudo apt-get install -y build-essential cmake curl git python3 python3-pip python3-dev python3-venv unixodbc-dev - displayName: 'Install build dependencies for CodeQL' - - - task: UsePythonVersion@0 - inputs: - versionSpec: '3.13' - addToPath: true - displayName: 'Use Python 3.13 for CodeQL' - - - script: | - python -m pip install --upgrade pip - pip install -r requirements.txt - displayName: 'Install Python dependencies for CodeQL' - - - task: CodeQL3000Init@0 - inputs: - Enabled: true - displayName: 'Initialize CodeQL' - - # Build the C++ extension for CodeQL analysis - - script: | - cd mssql_python/pybind - chmod +x build.sh - ./build.sh - displayName: 'Build C++ extension for CodeQL analysis' +pr: + branches: + include: + - main - - task: CodeQL3000Finalize@0 - condition: always() - displayName: 'Finalize CodeQL' +jobs: +# ===== PYTHON 3.14 TESTING ONLY ===== -- job: PytestOnWindows - displayName: 'Windows x64' +- job: BuildWindowsWheels_Python314 pool: vmImage: 'windows-latest' - + displayName: 'Build Windows Python 3.14 -' strategy: matrix: - LocalDB: - sqlVersion: 'LocalDB' - pythonVersion: '3.13' - SQLServer2022: - sqlVersion: 'SQL2022' - pythonVersion: '3.13' - LocalDB_Python314: - sqlVersion: 'LocalDB' + py314_x64: + pythonVersion: '3.14' + shortPyVer: '314' + architecture: 'x64' + targetArch: 'x64' + py314_arm64: pythonVersion: '3.14' + shortPyVer: '314' + architecture: 'x64' + targetArch: 'arm64' steps: - - task: UsePythonVersion@0 - inputs: - versionSpec: '$(pythonVersion)' - addToPath: true - githubToken: $(GITHUB_TOKEN) - displayName: 'Use Python $(pythonVersion)' - - - script: | - python -m pip install --upgrade pip - pip install -r requirements.txt - displayName: 'Install dependencies' - - # Start LocalDB instance (for LocalDB matrix) - - powershell: | - sqllocaldb create MSSQLLocalDB - sqllocaldb start MSSQLLocalDB - displayName: 'Start LocalDB instance' - condition: eq(variables['sqlVersion'], 'LocalDB') - - # Create database and user for LocalDB - - powershell: | - sqlcmd -S "(localdb)\MSSQLLocalDB" -Q "CREATE DATABASE TestDB" - sqlcmd -S "(localdb)\MSSQLLocalDB" -Q "CREATE LOGIN testuser WITH PASSWORD = '$(DB_PASSWORD)'" - sqlcmd -S "(localdb)\MSSQLLocalDB" -d TestDB -Q "CREATE USER testuser FOR LOGIN testuser" - sqlcmd -S "(localdb)\MSSQLLocalDB" -d TestDB -Q "ALTER ROLE db_owner ADD MEMBER testuser" - displayName: 'Setup database and user for LocalDB' - condition: eq(variables['sqlVersion'], 'LocalDB') - env: - DB_PASSWORD: $(DB_PASSWORD) - - # Install SQL Server 2022 (for SQL2022 matrix) - - powershell: | - Write-Host "Downloading SQL Server 2022 Express..." - # Download SQL Server 2022 Express installer - $ProgressPreference = 'SilentlyContinue' - Invoke-WebRequest -Uri "https://go.microsoft.com/fwlink/p/?linkid=2216019" -OutFile "SQL2022-SSEI-Expr.exe" - - Write-Host "Installing SQL Server 2022 Express..." - # Install SQL Server 2022 Express with basic features - Start-Process -FilePath "SQL2022-SSEI-Expr.exe" -ArgumentList "/Action=Download","/MediaPath=$env:TEMP","/MediaType=Core","/Quiet" -Wait - - # Find the downloaded setup file - $setupFile = Get-ChildItem -Path $env:TEMP -Filter "SQLEXPR_x64_ENU.exe" -Recurse | Select-Object -First 1 - - if ($setupFile) { - Write-Host "Extracting SQL Server setup files..." - Start-Process -FilePath $setupFile.FullName -ArgumentList "/x:$env:TEMP\SQLSetup","/u" -Wait - - Write-Host "Running SQL Server setup..." - Start-Process -FilePath "$env:TEMP\SQLSetup\setup.exe" -ArgumentList "/Q","/ACTION=Install","/FEATURES=SQLEngine","/INSTANCENAME=MSSQLSERVER","/SQLSVCACCOUNT=`"NT AUTHORITY\SYSTEM`"","/SQLSYSADMINACCOUNTS=`"BUILTIN\Administrators`"","/TCPENABLED=1","/SECURITYMODE=SQL","/SAPWD=$(DB_PASSWORD)","/IACCEPTSQLSERVERLICENSETERMS" -Wait - } else { - Write-Error "Failed to download SQL Server setup file" - exit 1 - } - - Write-Host "SQL Server 2022 installation completed" - displayName: 'Install SQL Server 2022 Express' - condition: eq(variables['sqlVersion'], 'SQL2022') - env: - DB_PASSWORD: $(DB_PASSWORD) - - # Create database for SQL Server 2022 - - powershell: | - # Wait for SQL Server to start - $maxAttempts = 30 - $attempt = 0 - $connected = $false - - Write-Host "Waiting for SQL Server 2022 to start..." - while (-not $connected -and $attempt -lt $maxAttempts) { - try { - sqlcmd -S "localhost" -U "sa" -P "$(DB_PASSWORD)" -Q "SELECT 1" -C - $connected = $true - Write-Host "SQL Server is ready!" - } catch { - $attempt++ - Write-Host "Waiting... ($attempt/$maxAttempts)" - Start-Sleep -Seconds 2 - } - } - - if (-not $connected) { - Write-Error "Failed to connect to SQL Server after $maxAttempts attempts" - exit 1 - } - - # Create database and user - sqlcmd -S "localhost" -U "sa" -P "$(DB_PASSWORD)" -Q "CREATE DATABASE TestDB" -C - sqlcmd -S "localhost" -U "sa" -P "$(DB_PASSWORD)" -Q "CREATE LOGIN testuser WITH PASSWORD = '$(DB_PASSWORD)'" -C - sqlcmd -S "localhost" -U "sa" -P "$(DB_PASSWORD)" -d TestDB -Q "CREATE USER testuser FOR LOGIN testuser" -C - sqlcmd -S "localhost" -U "sa" -P "$(DB_PASSWORD)" -d TestDB -Q "ALTER ROLE db_owner ADD MEMBER testuser" -C - displayName: 'Setup database and user for SQL Server 2022' - condition: eq(variables['sqlVersion'], 'SQL2022') - env: - DB_PASSWORD: $(DB_PASSWORD) - - - script: | - cd mssql_python\pybind - build.bat x64 - displayName: 'Build .pyd file' - - # Run tests for LocalDB - - script: | - python -m pytest -v --junitxml=test-results-localdb.xml --cov=. --cov-report=xml:coverage-localdb.xml --capture=tee-sys --cache-clear - displayName: 'Run tests with coverage on LocalDB' - condition: eq(variables['sqlVersion'], 'LocalDB') - env: - DB_CONNECTION_STRING: 'Server=(localdb)\MSSQLLocalDB;Database=TestDB;Uid=testuser;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes' - - # Run tests for SQL Server 2022 - - script: | - python -m pytest -v --junitxml=test-results-sql2022.xml --cov=. --cov-report=xml:coverage-sql2022.xml --capture=tee-sys --cache-clear - displayName: 'Run tests with coverage on SQL Server 2022' - condition: eq(variables['sqlVersion'], 'SQL2022') - env: - DB_CONNECTION_STRING: 'Server=localhost;Database=TestDB;Uid=testuser;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes' - - # Download and restore AdventureWorks2022 database for benchmarking - - powershell: | - Write-Host "Downloading AdventureWorks2022.bak..." - $ProgressPreference = 'SilentlyContinue' - Invoke-WebRequest -Uri "https://github.com/Microsoft/sql-server-samples/releases/download/adventureworks/AdventureWorks2022.bak" -OutFile "$env:TEMP\AdventureWorks2022.bak" - - Write-Host "Restoring AdventureWorks2022 database..." - # Get the default data and log paths - $dataPath = sqlcmd -S "localhost" -U "sa" -P "$(DB_PASSWORD)" -Q "SET NOCOUNT ON; SELECT SERVERPROPERTY('InstanceDefaultDataPath') AS DataPath" -h -1 -C | Out-String - $logPath = sqlcmd -S "localhost" -U "sa" -P "$(DB_PASSWORD)" -Q "SET NOCOUNT ON; SELECT SERVERPROPERTY('InstanceDefaultLogPath') AS LogPath" -h -1 -C | Out-String - - $dataPath = $dataPath.Trim() - $logPath = $logPath.Trim() - - Write-Host "Data path: $dataPath" - Write-Host "Log path: $logPath" - - # Restore the database - sqlcmd -S "localhost" -U "sa" -P "$(DB_PASSWORD)" -C -Q @" - RESTORE DATABASE AdventureWorks2022 - FROM DISK = '$env:TEMP\AdventureWorks2022.bak' - WITH - MOVE 'AdventureWorks2022' TO '${dataPath}AdventureWorks2022.mdf', - MOVE 'AdventureWorks2022_log' TO '${logPath}AdventureWorks2022_log.ldf', - REPLACE - "@ - - if ($LASTEXITCODE -eq 0) { - Write-Host "AdventureWorks2022 database restored successfully" - } else { - Write-Error "Failed to restore AdventureWorks2022 database" - exit 1 - } - displayName: 'Download and restore AdventureWorks2022 database' - condition: eq(variables['sqlVersion'], 'SQL2022') - env: - DB_PASSWORD: $(DB_PASSWORD) - - # Run performance benchmarks on SQL Server 2022 - - powershell: | - Write-Host "Checking and installing ODBC Driver 18 for SQL Server..." - - # Check if ODBC Driver 18 is registered in Windows registry - $odbcDriverKey = "HKLM:\SOFTWARE\ODBC\ODBCINST.INI\ODBC Driver 18 for SQL Server" - $driverExists = Test-Path $odbcDriverKey - - if ($driverExists) { - Write-Host "✓ ODBC Driver 18 for SQL Server is already installed and registered" - $driverPath = (Get-ItemProperty -Path $odbcDriverKey -Name "Driver" -ErrorAction SilentlyContinue).Driver - if ($driverPath) { - Write-Host " Driver location: $driverPath" + - powershell: | + # Download Python 3.14 x64 from NuGet (no installation needed) + $nugetUrl = "https://www.nuget.org/api/v2/package/python/3.14.0-a2" + $nugetFile = "$(Build.SourcesDirectory)\python-x64.nupkg" + $zipFile = "$(Build.SourcesDirectory)\python-x64.zip" + $extractPath = "C:\Python314-NuGet" + + Write-Host "Downloading Python 3.14 x64 NuGet package from: $nugetUrl" + Invoke-WebRequest -Uri $nugetUrl -OutFile $nugetFile -UseBasicParsing + + Write-Host "Renaming .nupkg to .zip for extraction..." + Move-Item -Path $nugetFile -Destination $zipFile -Force + + Write-Host "Extracting NuGet package to: $extractPath" + Expand-Archive -Path $zipFile -DestinationPath $extractPath -Force + + # Python executable is in tools directory + $pythonDir = "$extractPath\tools" + + Write-Host "`nVerifying Python installation:" + & "$pythonDir\python.exe" --version + & "$pythonDir\python.exe" -c "import sys; print('Python executable:', sys.executable)" + + # Set up for subsequent steps + Write-Host "`nPython is ready at: $pythonDir" + + # Create a simple redirect script at C:\Python314 + New-Item -ItemType Directory -Force -Path "C:\Python314" | Out-Null + Copy-Item -Path "$pythonDir\*" -Destination "C:\Python314" -Recurse -Force + Write-Host "Copied Python to C:\Python314 for consistent paths" + + Remove-Item -Path $zipFile -Force + displayName: 'Download Python 3.14 x64 from NuGet' + + - powershell: | + Write-Host "Installing dependencies with Python 3.14..." + Write-Host "Python version:" + C:\Python314\python.exe --version + Write-Host "Python executable:" + C:\Python314\python.exe -c "import sys; print(sys.executable)" + + C:\Python314\python.exe -m pip install --upgrade pip + C:\Python314\python.exe -m pip install -r requirements.txt + C:\Python314\python.exe -m pip install cmake pybind11 + + Write-Host "`nVerifying pytest installation:" + C:\Python314\python.exe -m pytest --version + displayName: 'Install dependencies' + + - script: | + choco install sqlserver-odbcdriver -y + displayName: 'Install SQL Server ODBC Driver' + + - powershell: | + sqllocaldb create MSSQLLocalDB + sqllocaldb start MSSQLLocalDB + displayName: 'Start LocalDB instance' + + - powershell: | + sqlcmd -S "(localdb)\MSSQLLocalDB" -Q "CREATE DATABASE TestDB" + sqlcmd -S "(localdb)\MSSQLLocalDB" -Q "CREATE LOGIN testuser WITH PASSWORD = '$(DB_PASSWORD)'" + sqlcmd -S "(localdb)\MSSQLLocalDB" -d TestDB -Q "CREATE USER testuser FOR LOGIN testuser" + sqlcmd -S "(localdb)\MSSQLLocalDB" -d TestDB -Q "ALTER ROLE db_owner ADD MEMBER testuser" + displayName: 'Setup database and user' + env: + DB_PASSWORD: $(DB_PASSWORD) + + - powershell: | + # Download Python 3.14 ARM64 from NuGet (contains libs directory) + $nugetUrl = "https://www.nuget.org/api/v2/package/pythonarm64/3.14.0-a2" + $nugetFile = "$(Build.SourcesDirectory)\pythonarm64.nupkg" + $zipFile = "$(Build.SourcesDirectory)\pythonarm64.zip" + $extractPath = "$(Build.SourcesDirectory)\pythonarm64-nuget" + $destPath = "$(Build.SourcesDirectory)\mssql_python\pybind\python_libs\arm64" + + Write-Host "Downloading Python 3.14 ARM64 NuGet package from: $nugetUrl" + Invoke-WebRequest -Uri $nugetUrl -OutFile $nugetFile -UseBasicParsing + + Write-Host "Renaming .nupkg to .zip for extraction..." + Move-Item -Path $nugetFile -Destination $zipFile -Force + + Write-Host "Extracting NuGet package..." + Expand-Archive -Path $zipFile -DestinationPath $extractPath -Force + + Write-Host "`nSearching for libs directory..." + $libsDir = Get-ChildItem -Path $extractPath -Recurse -Directory -Filter "libs" | Select-Object -First 1 + + if ($libsDir) { + Write-Host "Found libs at: $($libsDir.FullName)" + New-Item -ItemType Directory -Force -Path $destPath | Out-Null + Copy-Item -Path "$($libsDir.FullName)\*" -Destination $destPath -Recurse -Force + Write-Host "✓ Copied .lib files from NuGet package" + } else { + Write-Host "libs directory not found, searching for .lib files..." + $libFiles = Get-ChildItem -Path $extractPath -Recurse -Filter "*.lib" + New-Item -ItemType Directory -Force -Path $destPath | Out-Null + foreach ($lib in $libFiles) { + Write-Host " Copying $($lib.Name)" + Copy-Item -Path $lib.FullName -Destination $destPath -Force + } } - } else { - Write-Host "ODBC Driver 18 for SQL Server not found, installing..." - - # Download ODBC Driver 18.5.2.1 (x64) from official Microsoft link - $ProgressPreference = 'SilentlyContinue' - $installerUrl = "https://go.microsoft.com/fwlink/?linkid=2335671" - $installerPath = "$env:TEMP\msodbcsql_18.5.2.1_x64.msi" - Write-Host "Downloading ODBC Driver 18 (x64) from Microsoft..." - Write-Host " URL: $installerUrl" - try { - Invoke-WebRequest -Uri $installerUrl -OutFile $installerPath -UseBasicParsing - Write-Host "✓ Download completed: $installerPath" - } catch { - Write-Error "Failed to download ODBC driver: $_" - exit 1 + # Also copy DLLs if available + $dllFiles = Get-ChildItem -Path $extractPath -Recurse -Filter "*.dll" | Where-Object { $_.Name -like "python*" -or $_.Name -like "vcruntime*" -or $_.Name -like "libcrypto*" -or $_.Name -like "libssl*" -or $_.Name -like "libffi*" } + foreach ($dll in $dllFiles) { + Write-Host " Copying $($dll.Name)" + Copy-Item -Path $dll.FullName -Destination $destPath -Force } - Write-Host "Installing ODBC Driver 18..." - $installArgs = @( - "/i" - "`"$installerPath`"" - "/quiet" - "/qn" - "/norestart" - "IACCEPTMSODBCSQLLICENSETERMS=YES" - "/l*v" - "`"$env:TEMP\odbc_install.log`"" - ) - - $installCmd = "msiexec.exe $($installArgs -join ' ')" - Write-Host " Command: $installCmd" - - $process = Start-Process msiexec.exe -ArgumentList $installArgs -Wait -PassThru -NoNewWindow + Write-Host "`nContents of $destPath :" + Get-ChildItem $destPath | ForEach-Object { Write-Host " - $($_.Name)" } - if ($process.ExitCode -eq 0) { - Write-Host "✓ ODBC Driver 18 installation completed successfully" - } elseif ($process.ExitCode -eq 3010) { - Write-Host "✓ ODBC Driver 18 installed (reboot recommended but not required)" + if (Test-Path "$destPath\python314.lib") { + Write-Host "`n✓ python314.lib found" } else { - Write-Error "ODBC Driver 18 installation failed with exit code: $($process.ExitCode)" - Write-Host "Check installation log: $env:TEMP\odbc_install.log" - Get-Content "$env:TEMP\odbc_install.log" -Tail 50 -ErrorAction SilentlyContinue + Write-Error "python314.lib not found in NuGet package!" exit 1 } - # Wait for registry update - Start-Sleep -Seconds 2 - - # Clean up installer - Remove-Item $installerPath -ErrorAction SilentlyContinue - } - - # Final verification using registry - Write-Host "`nVerifying ODBC Driver 18 installation..." - $verifyKey = Test-Path "HKLM:\SOFTWARE\ODBC\ODBCINST.INI\ODBC Driver 18 for SQL Server" - - if ($verifyKey) { - $driverInfo = Get-ItemProperty -Path "HKLM:\SOFTWARE\ODBC\ODBCINST.INI\ODBC Driver 18 for SQL Server" -ErrorAction SilentlyContinue - Write-Host "✓ SUCCESS: ODBC Driver 18 for SQL Server is registered" - Write-Host " Driver: $($driverInfo.Driver)" - Write-Host " Setup: $($driverInfo.Setup)" - } else { - Write-Error "ODBC Driver 18 for SQL Server is not registered in ODBC" - Write-Host "`nListing all installed ODBC drivers from registry:" - Get-ChildItem "HKLM:\SOFTWARE\ODBC\ODBCINST.INI" -ErrorAction SilentlyContinue | ForEach-Object { Write-Host " - $($_.PSChildName)" } - exit 1 - } - - Write-Host "`nInstalling pyodbc..." - pip install pyodbc - - Write-Host "`nRunning performance benchmarks..." - python benchmarks/perf-benchmarking.py - displayName: 'Run performance benchmarks on SQL Server 2022' - condition: eq(variables['sqlVersion'], 'SQL2022') - continueOnError: true - env: - DB_CONNECTION_STRING: 'Server=localhost;Database=AdventureWorks2022;Uid=sa;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes' - - - task: CopyFiles@2 - inputs: - SourceFolder: 'mssql_python' - Contents: 'ddbc_bindings.cp*-amd64.pyd' - TargetFolder: '$(Build.ArtifactStagingDirectory)' - displayName: 'Copy pyd file to staging' - - - task: CopyFiles@2 - inputs: - SourceFolder: 'mssql_python' - Contents: 'ddbc_bindings.cp*-amd64.pdb' - TargetFolder: '$(Build.ArtifactStagingDirectory)' - displayName: 'Copy pdb file to staging' - - - task: PublishBuildArtifacts@1 - inputs: - PathtoPublish: '$(Build.ArtifactStagingDirectory)' - ArtifactName: 'ddbc_bindings' - publishLocation: 'Container' - displayName: 'Publish build artifacts' - - - task: PublishTestResults@2 - condition: succeededOrFailed() - inputs: - testResultsFiles: '**/test-results-*.xml' - testRunTitle: 'Publish test results for Windows $(sqlVersion)' - - # - task: PublishCodeCoverageResults@1 - # inputs: - # codeCoverageTool: 'Cobertura' - # summaryFileLocation: 'coverage.xml' - # displayName: 'Publish code coverage results' - -- job: PytestOnMacOS - displayName: 'macOS x86_64' - pool: - vmImage: 'macos-latest' - - steps: - - task: UsePythonVersion@0 - inputs: - versionSpec: '3.13' - addToPath: true - displayName: 'Use Python 3.13 on macOS' - - - script: | - brew update - # Uninstall existing CMake to avoid tap conflicts - brew uninstall cmake --ignore-dependencies || echo "CMake not installed or already removed" - # Install CMake from homebrew/core - brew install cmake - displayName: 'Install CMake' - - - script: | - brew update - brew install docker colima - - # Start Colima with extra resources - colima start --cpu 4 --memory 8 --disk 50 - - # Optional: set Docker context (usually automatic) - docker context use colima >/dev/null || true - - # Confirm Docker is operational - docker version - docker ps - displayName: 'Install and start Colima-based Docker' - - - script: | - # Pull and run SQL Server container - docker pull mcr.microsoft.com/mssql/server:2022-latest - docker run \ - --name sqlserver \ - -e ACCEPT_EULA=Y \ - -e MSSQL_SA_PASSWORD="${DB_PASSWORD}" \ - -p 1433:1433 \ - -d mcr.microsoft.com/mssql/server:2022-latest - - # Starting SQL Server container… - for i in {1..30}; do - docker exec sqlserver \ - /opt/mssql-tools18/bin/sqlcmd \ - -S localhost \ - -U SA \ - -P "$DB_PASSWORD" \ - -C -Q "SELECT 1" && break - sleep 2 - done - displayName: 'Pull & start SQL Server (Docker)' - env: - DB_PASSWORD: $(DB_PASSWORD) - - - script: | - python -m pip install --upgrade pip - pip install -r requirements.txt - displayName: 'Install Python dependencies' - - - script: | - cd mssql_python/pybind - ./build.sh - displayName: 'Build pybind bindings (.so)' - - - script: | - echo "Build successful, running tests now" - python -m pytest -v --junitxml=test-results.xml --cov=. --cov-report=xml --capture=tee-sys --cache-clear - displayName: 'Run pytest with coverage' - env: - DB_CONNECTION_STRING: 'Server=tcp:127.0.0.1,1433;Database=master;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes' - DB_PASSWORD: $(DB_PASSWORD) - - - task: PublishTestResults@2 - condition: succeededOrFailed() - inputs: - testResultsFiles: '**/test-results.xml' - testRunTitle: 'Publish pytest results on macOS' - -- job: PytestOnLinux - displayName: 'Linux x86_64' - pool: - vmImage: 'ubuntu-latest' - - strategy: - matrix: - Ubuntu: - dockerImage: 'ubuntu:22.04' - distroName: 'Ubuntu' - sqlServerImage: 'mcr.microsoft.com/mssql/server:2022-latest' - useAzureSQL: 'false' - Ubuntu_SQL2025: - dockerImage: 'ubuntu:22.04' - distroName: 'Ubuntu-SQL2025' - sqlServerImage: 'mcr.microsoft.com/mssql/server:2025-latest' - useAzureSQL: 'false' - ${{ if ne(variables['AZURE_CONNECTION_STRING'], '') }}: - Ubuntu_AzureSQL: - dockerImage: 'ubuntu:22.04' - distroName: 'Ubuntu-AzureSQL' - sqlServerImage: '' - useAzureSQL: 'true' - Debian: - dockerImage: 'debian:12' - distroName: 'Debian' - sqlServerImage: 'mcr.microsoft.com/mssql/server:2022-latest' - useAzureSQL: 'false' - - steps: - - script: | - # Create a Docker container for testing - docker run -d --name test-container-$(distroName) \ - -v $(Build.SourcesDirectory):/workspace \ - -w /workspace \ - --network bridge \ - $(dockerImage) \ - tail -f /dev/null - displayName: 'Create $(distroName) container' - - - script: | - # Start SQL Server container - docker run -d --name sqlserver-$(distroName) \ - -e ACCEPT_EULA=Y \ - -e MSSQL_SA_PASSWORD="$(DB_PASSWORD)" \ - -p 1433:1433 \ - $(sqlServerImage) - - # Wait for SQL Server to be ready - echo "Waiting for SQL Server to start..." - for i in {1..60}; do - if docker exec sqlserver-$(distroName) \ - /opt/mssql-tools18/bin/sqlcmd \ - -S localhost \ - -U SA \ - -P "$(DB_PASSWORD)" \ - -C -Q "SELECT 1" >/dev/null 2>&1; then - echo "SQL Server is ready!" - break - fi - echo "Waiting... ($i/60)" - sleep 2 - done - - # Create test database - docker exec sqlserver-$(distroName) \ - /opt/mssql-tools18/bin/sqlcmd \ - -S localhost \ - -U SA \ - -P "$(DB_PASSWORD)" \ - -C -Q "CREATE DATABASE TestDB" - displayName: 'Start SQL Server container for $(distroName)' - condition: eq(variables['useAzureSQL'], 'false') - env: - DB_PASSWORD: $(DB_PASSWORD) - - - script: | - # Install dependencies in the container - if [ "$(distroName)" = "Ubuntu" ]; then - docker exec test-container-$(distroName) bash -c " - export DEBIAN_FRONTEND=noninteractive - export TZ=UTC - ln -snf /usr/share/zoneinfo/\$TZ /etc/localtime && echo \$TZ > /etc/timezone - apt-get update && - apt-get install -y python3 python3-pip python3-venv python3-full cmake curl wget gnupg software-properties-common build-essential python3-dev pybind11-dev - " - else - # Debian - docker exec test-container-$(distroName) bash -c " - export DEBIAN_FRONTEND=noninteractive - export TZ=UTC - ln -snf /usr/share/zoneinfo/\$TZ /etc/localtime && echo \$TZ > /etc/timezone - apt-get update && - apt-get install -y python3 python3-pip python3-venv python3-full cmake curl wget gnupg software-properties-common build-essential python3-dev pybind11-dev - " - fi - displayName: 'Install basic dependencies in $(distroName) container' - - - script: | - # Install ODBC driver in the container - docker exec test-container-$(distroName) bash -c " - export DEBIAN_FRONTEND=noninteractive - - # Download the package to configure the Microsoft repo - if [ '$(distroName)' = 'Ubuntu' ]; then - curl -sSL -O https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb - else - # Debian 12 - curl -sSL -O https://packages.microsoft.com/config/debian/12/packages-microsoft-prod.deb - fi - - # Install the package - dpkg -i packages-microsoft-prod.deb || true - rm packages-microsoft-prod.deb - - # Update package list - apt-get update - - # Install the driver - ACCEPT_EULA=Y apt-get install -y msodbcsql18 - # optional: for bcp and sqlcmd - ACCEPT_EULA=Y apt-get install -y mssql-tools18 - # optional: for unixODBC development headers - apt-get install -y unixodbc-dev - " - displayName: 'Install ODBC Driver in $(distroName) container' - - - script: | - # Install Python dependencies in the container using virtual environment - docker exec test-container-$(distroName) bash -c " - # Create a virtual environment - python3 -m venv /opt/venv - source /opt/venv/bin/activate - - # Install dependencies in the virtual environment - python -m pip install --upgrade pip - python -m pip install -r requirements.txt - - # Make the virtual environment globally available - echo 'source /opt/venv/bin/activate' >> ~/.bashrc - " - displayName: 'Install Python dependencies in $(distroName) container' - - - script: | - # Build pybind bindings in the container - docker exec test-container-$(distroName) bash -c " - source /opt/venv/bin/activate - cd mssql_python/pybind - chmod +x build.sh - ./build.sh - " - displayName: 'Build pybind bindings (.so) in $(distroName) container' - - - script: | - # Uninstall ODBC Driver before running tests - docker exec test-container-$(distroName) bash -c " - export DEBIAN_FRONTEND=noninteractive - apt-get remove --purge -y msodbcsql18 mssql-tools18 unixodbc-dev - rm -f /usr/bin/sqlcmd - rm -f /usr/bin/bcp - rm -rf /opt/microsoft/msodbcsql - rm -f /lib/x86_64-linux-gnu/libodbcinst.so.2 - odbcinst -u -d -n 'ODBC Driver 18 for SQL Server' || true - echo 'Uninstalled ODBC Driver and cleaned up libraries' - echo 'Verifying x86_64 debian_ubuntu driver library signatures:' - ldd mssql_python/libs/linux/debian_ubuntu/x86_64/lib/libmsodbcsql-18.5.so.1.1 - " - displayName: 'Uninstall ODBC Driver before running tests in $(distroName) container' - - - script: | - # Run tests in the container - if [ "$(useAzureSQL)" = "true" ]; then - # Azure SQL Database testing - echo "Testing against Azure SQL Database" - - docker exec \ - -e DB_CONNECTION_STRING="$(AZURE_CONNECTION_STRING)" \ - test-container-$(distroName) bash -c " - source /opt/venv/bin/activate - echo 'Build successful, running tests now on $(distroName) with Azure SQL' - echo 'Using Azure SQL connection string' - python -m pytest -v --junitxml=test-results-$(distroName).xml --cov=. --cov-report=xml:coverage-$(distroName).xml --capture=tee-sys --cache-clear - " - else - # Local SQL Server testing - SQLSERVER_IP=$(docker inspect sqlserver-$(distroName) --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}') - echo "SQL Server IP: $SQLSERVER_IP" - - docker exec \ - -e DB_CONNECTION_STRING="Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes" \ - -e DB_PASSWORD="$(DB_PASSWORD)" \ - test-container-$(distroName) bash -c " - source /opt/venv/bin/activate - echo 'Build successful, running tests now on $(distroName)' - echo 'Using connection string: Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=***;TrustServerCertificate=yes' - python -m pytest -v --junitxml=test-results-$(distroName).xml --cov=. --cov-report=xml:coverage-$(distroName).xml --capture=tee-sys --cache-clear - " - fi - displayName: 'Run pytest with coverage in $(distroName) container' - condition: or(eq(variables['useAzureSQL'], 'false'), and(eq(variables['useAzureSQL'], 'true'), ne(variables['AZURE_CONNECTION_STRING'], ''))) - env: - DB_PASSWORD: $(DB_PASSWORD) + # Cleanup + Remove-Item -Path $zipFile -Force -ErrorAction SilentlyContinue + Remove-Item -Path $extractPath -Recurse -Force -ErrorAction SilentlyContinue + condition: eq(variables['targetArch'], 'arm64') + displayName: 'Download Python 3.14 ARM64 libs from NuGet' - - script: | - # Download and restore AdventureWorks2022 database for benchmarking on Ubuntu only - if [ "$(distroName)" = "Ubuntu" ] && [ "$(useAzureSQL)" = "false" ]; then - echo "Downloading AdventureWorks2022.bak..." - wget -q https://github.com/Microsoft/sql-server-samples/releases/download/adventureworks/AdventureWorks2022.bak -O /tmp/AdventureWorks2022.bak + - script: | + set PATH=C:\Python314;C:\Python314\Scripts;%PATH% - echo "Copying backup file into SQL Server container..." - docker cp /tmp/AdventureWorks2022.bak sqlserver-$(distroName):/tmp/AdventureWorks2022.bak + echo Python Version: $(pythonVersion) + echo Short Tag: $(shortPyVer) + echo Architecture: Host=$(architecture), Target=$(targetArch) - echo "Restoring AdventureWorks2022 database..." - docker exec sqlserver-$(distroName) /opt/mssql-tools18/bin/sqlcmd \ - -S localhost \ - -U SA \ - -P "$(DB_PASSWORD)" \ - -C \ - -Q "RESTORE DATABASE AdventureWorks2022 FROM DISK = '/tmp/AdventureWorks2022.bak' WITH MOVE 'AdventureWorks2022' TO '/var/opt/mssql/data/AdventureWorks2022.mdf', MOVE 'AdventureWorks2022_log' TO '/var/opt/mssql/data/AdventureWorks2022_log.ldf', REPLACE" - - if [ $? -eq 0 ]; then - echo "AdventureWorks2022 database restored successfully" - else - echo "Failed to restore AdventureWorks2022 database" - fi - - # Clean up (ignore errors if files are locked) - rm -f /tmp/AdventureWorks2022.bak || true - docker exec sqlserver-$(distroName) rm -f /tmp/AdventureWorks2022.bak || true - fi - displayName: 'Download and restore AdventureWorks2022 database in $(distroName)' - condition: and(eq(variables['distroName'], 'Ubuntu'), eq(variables['useAzureSQL'], 'false')) - continueOnError: true - env: - DB_PASSWORD: $(DB_PASSWORD) - - - script: | - # Run performance benchmarks on Ubuntu with SQL Server 2022 only - if [ "$(distroName)" = "Ubuntu" ] && [ "$(useAzureSQL)" = "false" ]; then - SQLSERVER_IP=$(docker inspect sqlserver-$(distroName) --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}') - echo "Running performance benchmarks on Ubuntu with SQL Server IP: $SQLSERVER_IP" - - docker exec \ - -e DB_CONNECTION_STRING="Server=$SQLSERVER_IP;Database=AdventureWorks2022;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes" \ - test-container-$(distroName) bash -c " - source /opt/venv/bin/activate - - echo 'Reinstalling ODBC Driver for benchmarking...' - export DEBIAN_FRONTEND=noninteractive - - # Remove duplicate repository sources if they exist - rm -f /etc/apt/sources.list.d/microsoft-prod.list - - # Add Microsoft repository - curl -sSL https://packages.microsoft.com/keys/microsoft.asc | apt-key add - - curl -sSL https://packages.microsoft.com/config/ubuntu/22.04/prod.list > /etc/apt/sources.list.d/mssql-release.list - - # Update package lists - apt-get update -qq - - # Install unixodbc and its dependencies first (provides libodbcinst.so.2 needed by msodbcsql18) - echo 'Installing unixODBC dependencies...' - apt-get install -y --no-install-recommends unixodbc unixodbc-dev libodbc1 odbcinst odbcinst1debian2 - - # Verify libodbcinst.so.2 is available - ldconfig - ls -la /usr/lib/x86_64-linux-gnu/libodbcinst.so.2 || echo 'Warning: libodbcinst.so.2 not found' - - # Install ODBC Driver 18 - echo 'Installing msodbcsql18...' - ACCEPT_EULA=Y apt-get install -y msodbcsql18 - - # Verify ODBC driver installation - odbcinst -q -d -n 'ODBC Driver 18 for SQL Server' || echo 'Warning: ODBC Driver 18 not registered' - - echo 'Installing pyodbc for benchmarking...' - pip install pyodbc - echo 'Running performance benchmarks on $(distroName)' - python benchmarks/perf-benchmarking.py || echo 'Performance benchmark failed or database not available' - " - else - echo "Skipping performance benchmarks on $(distroName) (only runs on Ubuntu with local SQL Server)" - fi - displayName: 'Run performance benchmarks in $(distroName) container' - condition: and(eq(variables['distroName'], 'Ubuntu'), eq(variables['useAzureSQL'], 'false')) - continueOnError: true - env: - DB_PASSWORD: $(DB_PASSWORD) - - - script: | - # Copy test results from container to host - docker cp test-container-$(distroName):/workspace/test-results-$(distroName).xml $(Build.SourcesDirectory)/ - docker cp test-container-$(distroName):/workspace/coverage-$(distroName).xml $(Build.SourcesDirectory)/ - displayName: 'Copy test results from $(distroName) container' - condition: always() - - - script: | - # Clean up containers - docker stop test-container-$(distroName) || true - docker rm test-container-$(distroName) || true - if [ "$(useAzureSQL)" = "false" ]; then - docker stop sqlserver-$(distroName) || true - docker rm sqlserver-$(distroName) || true - fi - displayName: 'Clean up $(distroName) containers' - condition: always() - - - task: PublishTestResults@2 - condition: succeededOrFailed() - inputs: - testResultsFiles: '**/test-results-$(distroName).xml' - testRunTitle: 'Publish pytest results on $(distroName)' - -- job: PytestOnLinux_ARM64 - displayName: 'Linux ARM64' - pool: - vmImage: 'ubuntu-latest' - - strategy: - matrix: - Ubuntu_ARM64: - dockerImage: 'ubuntu:22.04' - distroName: 'Ubuntu' - archName: 'arm64' - Debian_ARM64: - dockerImage: 'debian:12' - distroName: 'Debian' - archName: 'arm64' + echo. + echo Verifying Python before build: + python --version + python -c "import sys; print('Python:', sys.executable); print('Version:', sys.version_info)" - steps: - - script: | - # Set up Docker buildx for multi-architecture support - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - docker buildx create --name multiarch --driver docker-container --use - docker buildx inspect --bootstrap - displayName: 'Setup Docker buildx for ARM64 emulation' + cd "$(Build.SourcesDirectory)\mssql_python\pybind" - - script: | - # Create a Docker container for testing on ARM64 - docker run -d --name test-container-$(distroName)-$(archName) \ - --platform linux/arm64 \ - -v $(Build.SourcesDirectory):/workspace \ - -w /workspace \ - --network bridge \ - $(dockerImage) \ - tail -f /dev/null - displayName: 'Create $(distroName) ARM64 container' + if "$(targetArch)"=="arm64" ( + echo Using arm64-specific Python library... + set CUSTOM_PYTHON_LIB_DIR=$(Build.SourcesDirectory)\mssql_python\pybind\python_libs\arm64 + ) - - script: | - # Start SQL Server container (x86_64 - SQL Server doesn't support ARM64) - docker run -d --name sqlserver-$(distroName)-$(archName) \ - --platform linux/amd64 \ - -e ACCEPT_EULA=Y \ - -e MSSQL_SA_PASSWORD="$(DB_PASSWORD)" \ - -p 1433:1433 \ - mcr.microsoft.com/mssql/server:2022-latest - - # Wait for SQL Server to be ready - echo "Waiting for SQL Server to start..." - for i in {1..60}; do - if docker exec sqlserver-$(distroName)-$(archName) \ - /opt/mssql-tools18/bin/sqlcmd \ - -S localhost \ - -U SA \ - -P "$(DB_PASSWORD)" \ - -C -Q "SELECT 1" >/dev/null 2>&1; then - echo "SQL Server is ready!" - break - fi - echo "Waiting... ($i/60)" - sleep 2 - done - - # Create test database - docker exec sqlserver-$(distroName)-$(archName) \ - /opt/mssql-tools18/bin/sqlcmd \ - -S localhost \ - -U SA \ - -P "$(DB_PASSWORD)" \ - -C -Q "CREATE DATABASE TestDB" - displayName: 'Start SQL Server container for $(distroName) ARM64' - env: - DB_PASSWORD: $(DB_PASSWORD) + call build.bat $(targetArch) + call keep_single_arch.bat $(targetArch) - - script: | - # Install dependencies in the ARM64 container - if [ "$(distroName)" = "Ubuntu" ]; then - docker exec test-container-$(distroName)-$(archName) bash -c " - export DEBIAN_FRONTEND=noninteractive - export TZ=UTC - ln -snf /usr/share/zoneinfo/\$TZ /etc/localtime && echo \$TZ > /etc/timezone - apt-get update && - apt-get install -y python3 python3-pip python3-venv python3-full cmake curl wget gnupg software-properties-common build-essential python3-dev pybind11-dev - # Verify architecture - uname -m - dpkg --print-architecture - " - else - # Debian ARM64 - docker exec test-container-$(distroName)-$(archName) bash -c " - export DEBIAN_FRONTEND=noninteractive - export TZ=UTC - ln -snf /usr/share/zoneinfo/\$TZ /etc/localtime && echo \$TZ > /etc/timezone - apt-get update && - apt-get install -y python3 python3-pip python3-venv python3-full cmake curl wget gnupg software-properties-common build-essential python3-dev pybind11-dev - # Verify architecture - uname -m - dpkg --print-architecture - " - fi - displayName: 'Install basic dependencies in $(distroName) ARM64 container' + cd ..\.. + displayName: 'Build PYD for $(targetArch)' + continueOnError: false - - script: | - # Install ODBC driver in the ARM64 container - docker exec test-container-$(distroName)-$(archName) bash -c " - export DEBIAN_FRONTEND=noninteractive - - # Download the package to configure the Microsoft repo - if [ '$(distroName)' = 'Ubuntu' ]; then - curl -sSL -O https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb - else - # Debian 12 - curl -sSL -O https://packages.microsoft.com/config/debian/12/packages-microsoft-prod.deb - fi - - # Install the package - dpkg -i packages-microsoft-prod.deb || true - rm packages-microsoft-prod.deb + - powershell: | + Write-Host "Running pytests with Python 3.14 to validate bindings" - # Update package list - apt-get update + if ("$(targetArch)" -eq "arm64") { + Write-Host "Skipping tests for ARM64 (cannot run ARM64 binaries on x64 host)" + exit 0 + } - # Install the driver (ARM64 version) - ACCEPT_EULA=Y apt-get install -y msodbcsql18 - # optional: for bcp and sqlcmd - ACCEPT_EULA=Y apt-get install -y mssql-tools18 - # optional: for unixODBC development headers - apt-get install -y unixodbc-dev - " - displayName: 'Install ODBC Driver in $(distroName) ARM64 container' - - - script: | - # Install Python dependencies in the ARM64 container using virtual environment - docker exec test-container-$(distroName)-$(archName) bash -c " - # Create a virtual environment - python3 -m venv /opt/venv - source /opt/venv/bin/activate + Write-Host "`nPython version being used for tests:" + & C:\Python314\python.exe --version + & C:\Python314\python.exe -c "import sys; print('Executable:', sys.executable)" - # Install dependencies in the virtual environment - python -m pip install --upgrade pip - python -m pip install -r requirements.txt + Write-Host "`nVerifying pytest is available:" + & C:\Python314\python.exe -m pytest --version - # Make the virtual environment globally available - echo 'source /opt/venv/bin/activate' >> ~/.bashrc - " - displayName: 'Install Python dependencies in $(distroName) ARM64 container' - - - script: | - # Build pybind bindings in the ARM64 container - docker exec test-container-$(distroName)-$(archName) bash -c " - source /opt/venv/bin/activate - cd mssql_python/pybind - chmod +x build.sh - ./build.sh - " - displayName: 'Build pybind bindings (.so) in $(distroName) ARM64 container' - - - script: | - # Uninstall ODBC Driver before running tests - docker exec test-container-$(distroName)-$(archName) bash -c " - export DEBIAN_FRONTEND=noninteractive - apt-get remove --purge -y msodbcsql18 mssql-tools18 unixodbc-dev - rm -f /usr/bin/sqlcmd - rm -f /usr/bin/bcp - rm -rf /opt/microsoft/msodbcsql - rm -f /lib/aarch64-linux-gnu/libodbcinst.so.2 - odbcinst -u -d -n 'ODBC Driver 11 for SQL Server' || true - echo 'Uninstalled ODBC Driver and cleaned up libraries' - echo 'Verifying arm64 debian_ubuntu driver library signatures:' - ldd mssql_python/libs/linux/debian_ubuntu/arm64/lib/libmsodbcsql-18.5.so.1.1 - " - displayName: 'Uninstall ODBC Driver before running tests in $(distroName) ARM64 container' - - - script: | - # Run tests in the ARM64 container - # Get SQL Server container IP - SQLSERVER_IP=$(docker inspect sqlserver-$(distroName)-$(archName) --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}') - echo "SQL Server IP: $SQLSERVER_IP" - - docker exec \ - -e DB_CONNECTION_STRING="Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes" \ - -e DB_PASSWORD="$(DB_PASSWORD)" \ - test-container-$(distroName)-$(archName) bash -c " - source /opt/venv/bin/activate - echo 'Build successful, running tests now on $(distroName) ARM64' - echo 'Architecture:' \$(uname -m) - echo 'Using connection string: Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=***;TrustServerCertificate=yes' - python main.py - python -m pytest -v --junitxml=test-results-$(distroName)-$(archName).xml --cov=. --cov-report=xml:coverage-$(distroName)-$(archName).xml --capture=tee-sys --cache-clear - " - displayName: 'Run pytest with coverage in $(distroName) ARM64 container' - env: - DB_PASSWORD: $(DB_PASSWORD) - - - script: | - # Copy test results from container to host - docker cp test-container-$(distroName)-$(archName):/workspace/test-results-$(distroName)-$(archName).xml $(Build.SourcesDirectory)/ - docker cp test-container-$(distroName)-$(archName):/workspace/coverage-$(distroName)-$(archName).xml $(Build.SourcesDirectory)/ - displayName: 'Copy test results from $(distroName) ARM64 container' - condition: always() - - - script: | - # Clean up containers - docker stop test-container-$(distroName)-$(archName) || true - docker rm test-container-$(distroName)-$(archName) || true - docker stop sqlserver-$(distroName)-$(archName) || true - docker rm sqlserver-$(distroName)-$(archName) || true - displayName: 'Clean up $(distroName) ARM64 containers' - condition: always() - - - task: PublishTestResults@2 - condition: succeededOrFailed() - inputs: - testResultsFiles: '**/test-results-$(distroName)-$(archName).xml' - testRunTitle: 'Publish pytest results on $(distroName) ARM64' - -- job: PytestOnLinux_RHEL9 - displayName: 'Linux RedHat x86_64' - pool: - vmImage: 'ubuntu-latest' - - steps: - - script: | - # Create a Docker container for testing - docker run -d --name test-container-rhel9 \ - -v $(Build.SourcesDirectory):/workspace \ - -w /workspace \ - --network bridge \ - redhat/ubi9:latest \ - tail -f /dev/null - displayName: 'Create RHEL 9 container' - - - script: | - # Start SQL Server container - docker run -d --name sqlserver-rhel9 \ - -e ACCEPT_EULA=Y \ - -e MSSQL_SA_PASSWORD="$(DB_PASSWORD)" \ - -p 1433:1433 \ - mcr.microsoft.com/mssql/server:2022-latest - - # Wait for SQL Server to be ready - echo "Waiting for SQL Server to start..." - for i in {1..60}; do - if docker exec sqlserver-rhel9 \ - /opt/mssql-tools18/bin/sqlcmd \ - -S localhost \ - -U SA \ - -P "$(DB_PASSWORD)" \ - -C -Q "SELECT 1" >/dev/null 2>&1; then - echo "SQL Server is ready!" - break - fi - echo "Waiting... ($i/60)" - sleep 2 - done - - # Create test database - docker exec sqlserver-rhel9 \ - /opt/mssql-tools18/bin/sqlcmd \ - -S localhost \ - -U SA \ - -P "$(DB_PASSWORD)" \ - -C -Q "CREATE DATABASE TestDB" - displayName: 'Start SQL Server container for RHEL 9' - env: - DB_PASSWORD: $(DB_PASSWORD) - - - script: | - # Install dependencies in the RHEL 9 container - docker exec test-container-rhel9 bash -c " - # Enable CodeReady Builder repository for additional packages - dnf update -y - dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm - subscription-manager repos --enable codeready-builder-for-rhel-9-$(arch)-rpms || dnf config-manager --set-enabled ubi-9-codeready-builder - - # Install Python 3.9 (available in RHEL 9 UBI) and development tools - dnf install -y python3 python3-pip python3-devel cmake curl wget gnupg2 glibc-devel kernel-headers - dnf install -y python3-libs python3-debug - dnf install -y gcc gcc-c++ make binutils - dnf install -y cmake - # If that doesn't work, try installing from different repositories - if ! which gcc; then - echo 'Trying alternative gcc installation...' - dnf --enablerepo=ubi-9-codeready-builder install -y gcc gcc-c++ - fi - # Verify installation - python3 --version - which gcc && which g++ - gcc --version - g++ --version - " - displayName: 'Install basic dependencies in RHEL 9 container' - - - script: | - # Verify compiler installation and set environment for RHEL 9 - docker exec test-container-rhel9 bash -c " - # Verify compilers are available - which gcc || echo 'GCC not found' - which g++ || echo 'G++ not found' - gcc --version || echo 'GCC version check failed' - g++ --version || echo 'G++ version check failed' + Write-Host "`nVerifying DB_CONNECTION_STRING environment variable:" + if ($env:DB_CONNECTION_STRING) { + Write-Host "✓ DB_CONNECTION_STRING is set" + } else { + Write-Error "DB_CONNECTION_STRING is not set!" + exit 1 + } - # Set compiler environment variables - export CC=/usr/bin/gcc - export CXX=/usr/bin/g++ - echo 'CC set to:' \$CC - echo 'CXX set to:' \$CXX + Write-Host "`nRunning pytest..." + & C:\Python314\python.exe -m pytest tests/ -v --maxfail=5 - # Create a wrapper script to preserve environment - cat > /workspace/setup_env.sh << 'EOF' - #!/bin/bash - export CC=/usr/bin/gcc - export CXX=/usr/bin/g++ - export PATH=/usr/bin:\$PATH - exec \"\$@\" - EOF - chmod +x /workspace/setup_env.sh - " - displayName: 'Verify and configure compilers in RHEL 9 container' - - - script: | - # Install ODBC driver in the RHEL 9 container - docker exec test-container-rhel9 bash -c " - # Add Microsoft repository for RHEL 9 - curl -sSL -o /etc/yum.repos.d/msprod.repo https://packages.microsoft.com/config/rhel/9/prod.repo + if ($LASTEXITCODE -ne 0) { + Write-Error "Tests failed with exit code $LASTEXITCODE" + exit $LASTEXITCODE + } - # Install the driver - ACCEPT_EULA=Y dnf install -y msodbcsql18 - # optional: for bcp and sqlcmd - ACCEPT_EULA=Y dnf install -y mssql-tools18 - # optional: for unixODBC development headers - dnf install -y unixODBC-devel - " - displayName: 'Install ODBC Driver in RHEL 9 container' + Write-Host "`n✓ All tests passed!" + displayName: 'Run pytests' + env: + DB_CONNECTION_STRING: 'Server=(localdb)\MSSQLLocalDB;Database=TestDB;Uid=testuser;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes' - - script: | - # Install Python dependencies in the container using virtual environment - docker exec test-container-rhel9 bash -c " - # Create a virtual environment with Python 3.9 - python3 -m venv myvenv - source myvenv/bin/activate + - task: CopyFiles@2 + inputs: + SourceFolder: '$(Build.SourcesDirectory)\mssql_python' + Contents: 'ddbc_bindings.cp$(shortPyVer)-*.pyd' + TargetFolder: '$(Build.ArtifactStagingDirectory)\ddbc-bindings\windows' + displayName: 'Place PYD file into artifacts directory' - # Install dependencies in the virtual environment + - script: | + set PATH=C:\Python314;C:\Python314\Scripts;%PATH% python -m pip install --upgrade pip - python -m pip install -r requirements.txt - - # Make the virtual environment globally available - echo 'source myvenv/bin/activate' >> ~/.bashrc - " - displayName: 'Install Python dependencies in RHEL 9 container' - - - script: | - # Build pybind bindings in the container - docker exec test-container-rhel9 bash -c " - source myvenv/bin/activate - ls /usr/include/python3.9 - # Set compiler environment variables - export CC=/usr/bin/gcc - export CXX=/usr/bin/g++ - - cd mssql_python/pybind - chmod +x build.sh - ./build.sh - " - displayName: 'Build pybind bindings (.so) in RHEL 9 container' - - - script: | - # Uninstall ODBC Driver before running tests - docker exec test-container-rhel9 bash -c " - dnf remove -y msodbcsql18 mssql-tools18 unixODBC-devel - rm -f /usr/bin/sqlcmd - rm -f /usr/bin/bcp - rm -rf /opt/microsoft/msodbcsql - rm -f /lib64/libodbcinst.so.2 - odbcinst -u -d -n 'ODBC Driver 11 for SQL Server' || true - echo 'Uninstalled ODBC Driver and cleaned up libraries' - echo 'Verifying x86_64 rhel driver library signatures:' - ldd mssql_python/libs/linux/rhel/x86_64/lib/libmsodbcsql-18.5.so.1.1 - " - displayName: 'Uninstall ODBC Driver before running tests in RHEL 9 container' - - - script: | - # Run tests in the container - # Get SQL Server container IP - SQLSERVER_IP=$(docker inspect sqlserver-rhel9 --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}') - echo "SQL Server IP: $SQLSERVER_IP" - - docker exec \ - -e DB_CONNECTION_STRING="Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes" \ - -e DB_PASSWORD="$(DB_PASSWORD)" \ - test-container-rhel9 bash -c " - source myvenv/bin/activate - echo 'Build successful, running tests now on RHEL 9' - echo 'Using connection string: Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=***;TrustServerCertificate=yes' - python main.py - python -m pytest -v --junitxml=test-results-rhel9.xml --cov=. --cov-report=xml:coverage-rhel9.xml --capture=tee-sys --cache-clear - " - displayName: 'Run pytest with coverage in RHEL 9 container' - env: - DB_PASSWORD: $(DB_PASSWORD) - - - script: | - # Copy test results from container to host - docker cp test-container-rhel9:/workspace/test-results-rhel9.xml $(Build.SourcesDirectory)/ - docker cp test-container-rhel9:/workspace/coverage-rhel9.xml $(Build.SourcesDirectory)/ - displayName: 'Copy test results from RHEL 9 container' - condition: always() - - - script: | - # Clean up containers - docker stop test-container-rhel9 || true - docker rm test-container-rhel9 || true - docker stop sqlserver-rhel9 || true - docker rm sqlserver-rhel9 || true - displayName: 'Clean up RHEL 9 containers' - condition: always() - - - task: PublishTestResults@2 - condition: succeededOrFailed() - inputs: - testResultsFiles: '**/test-results-rhel9.xml' - testRunTitle: 'Publish pytest results on RHEL 9' - -- job: PytestOnLinux_RHEL9_ARM64 - displayName: 'Linux RedHat ARM64' + pip install wheel setuptools + set ARCHITECTURE=$(targetArch) + python setup.py bdist_wheel + displayName: 'Build wheel package for Python $(pythonVersion) ($(targetArch))' + + - task: CopyFiles@2 + inputs: + SourceFolder: '$(Build.SourcesDirectory)\dist' + Contents: '*.whl' + TargetFolder: '$(Build.ArtifactStagingDirectory)\dist' + displayName: 'Collect wheel package' + + - task: PublishBuildArtifacts@1 + condition: succeededOrFailed() + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)\ddbc-bindings' + ArtifactName: 'mssql-python-ddbc-bindings-py314' + publishLocation: 'Container' + displayName: 'Publish PYDs as artifacts' + + - task: PublishBuildArtifacts@1 + condition: eq(variables['targetArch'], 'arm64') + inputs: + PathtoPublish: '$(Build.SourcesDirectory)\mssql_python\pybind\python_libs\arm64' + ArtifactName: 'mssql-python-arm64-libs-py314' + publishLocation: 'Container' + displayName: 'Publish arm64 libs as artifacts' + + - task: PublishBuildArtifacts@1 + condition: succeededOrFailed() + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)\dist' + ArtifactName: 'mssql-python-wheels-dist-py314' + publishLocation: 'Container' + displayName: 'Publish wheels as artifacts' + +- job: BuildMacOSWheels_Python314 pool: - vmImage: 'ubuntu-latest' + vmImage: 'macos-latest' + displayName: 'Build macOS Python 3.14 - Universal2' steps: - - script: | - # Set up Docker buildx for multi-architecture support - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - docker buildx create --name multiarch --driver docker-container --use - docker buildx inspect --bootstrap - displayName: 'Setup Docker buildx for ARM64 emulation' - - - script: | - # Create a Docker container for testing on ARM64 - docker run -d --name test-container-rhel9-arm64 \ - --platform linux/arm64 \ - -v $(Build.SourcesDirectory):/workspace \ - -w /workspace \ - --network bridge \ - redhat/ubi9:latest \ - tail -f /dev/null - displayName: 'Create RHEL 9 ARM64 container' - - - script: | - # Start SQL Server container (x86_64 - SQL Server doesn't support ARM64) - docker run -d --name sqlserver-rhel9-arm64 \ - --platform linux/amd64 \ - -e ACCEPT_EULA=Y \ - -e MSSQL_SA_PASSWORD="$(DB_PASSWORD)" \ - -p 1433:1433 \ - mcr.microsoft.com/mssql/server:2022-latest - - # Wait for SQL Server to be ready - echo "Waiting for SQL Server to start..." - for i in {1..60}; do - if docker exec sqlserver-rhel9-arm64 \ - /opt/mssql-tools18/bin/sqlcmd \ - -S localhost \ - -U SA \ - -P "$(DB_PASSWORD)" \ - -C -Q "SELECT 1" >/dev/null 2>&1; then - echo "SQL Server is ready!" - break - fi - echo "Waiting... ($i/60)" - sleep 2 - done - - # Create test database - docker exec sqlserver-rhel9-arm64 \ - /opt/mssql-tools18/bin/sqlcmd \ - -S localhost \ - -U SA \ - -P "$(DB_PASSWORD)" \ - -C -Q "CREATE DATABASE TestDB" - displayName: 'Start SQL Server container for RHEL 9 ARM64' - env: - DB_PASSWORD: $(DB_PASSWORD) - - - script: | - # Install dependencies in the RHEL 9 ARM64 container - docker exec test-container-rhel9-arm64 bash -c " - # Enable CodeReady Builder repository for additional packages - dnf update -y - dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm - subscription-manager repos --enable codeready-builder-for-rhel-9-$(arch)-rpms || dnf config-manager --set-enabled ubi-9-codeready-builder - - # Install Python 3.9 (available in RHEL 9 UBI) and development tools - dnf install -y python3 python3-pip python3-devel cmake curl wget gnupg2 glibc-devel kernel-headers - dnf install -y python3-libs python3-debug - dnf install -y gcc gcc-c++ make binutils - dnf install -y cmake - # If that doesn't work, try installing from different repositories - if ! which gcc; then - echo 'Trying alternative gcc installation...' - dnf --enablerepo=ubi-9-codeready-builder install -y gcc gcc-c++ - fi - # Verify installation and architecture - python3 --version - which gcc && which g++ - gcc --version - g++ --version - uname -m - echo 'Architecture:' \$(uname -m) - " - displayName: 'Install basic dependencies in RHEL 9 ARM64 container' - - - script: | - # Verify compiler installation and set environment for RHEL 9 ARM64 - docker exec test-container-rhel9-arm64 bash -c " - # Verify compilers are available - which gcc || echo 'GCC not found' - which g++ || echo 'G++ not found' - gcc --version || echo 'GCC version check failed' - g++ --version || echo 'G++ version check failed' - - # Set compiler environment variables - export CC=/usr/bin/gcc - export CXX=/usr/bin/g++ - echo 'CC set to:' \$CC - echo 'CXX set to:' \$CXX - echo 'Running on architecture:' \$(uname -m) - - # Create a wrapper script to preserve environment - cat > /workspace/setup_env.sh << 'EOF' - #!/bin/bash - export CC=/usr/bin/gcc - export CXX=/usr/bin/g++ - export PATH=/usr/bin:\$PATH - exec \"\$@\" - EOF - chmod +x /workspace/setup_env.sh - " - displayName: 'Verify and configure compilers in RHEL 9 ARM64 container' - - - script: | - # Install ODBC driver in the RHEL 9 ARM64 container - docker exec test-container-rhel9-arm64 bash -c " - # Add Microsoft repository for RHEL 9 - curl -sSL -o /etc/yum.repos.d/msprod.repo https://packages.microsoft.com/config/rhel/9/prod.repo - - # Install the driver (ARM64 version) - ACCEPT_EULA=Y dnf install -y msodbcsql18 - # optional: for bcp and sqlcmd - ACCEPT_EULA=Y dnf install -y mssql-tools18 - # optional: for unixODBC development headers - dnf install -y unixODBC-devel - " - displayName: 'Install ODBC Driver in RHEL 9 ARM64 container' - - - script: | - # Install Python dependencies in the container using virtual environment - docker exec test-container-rhel9-arm64 bash -c " - # Create a virtual environment with Python 3.9 - python3 -m venv myvenv - source myvenv/bin/activate - - # Install dependencies in the virtual environment + - task: UsePythonVersion@0 + inputs: + versionSpec: '3.14' + addToPath: true + githubToken: $(GITHUB_TOKEN) + displayName: 'Use Python 3.14 (Universal2)' + + - script: | + brew update + brew uninstall cmake --ignore-dependencies || echo "CMake not installed or already removed" + brew install cmake + displayName: 'Install CMake' + + - script: | python -m pip install --upgrade pip - python -m pip install -r requirements.txt - - # Make the virtual environment globally available - echo 'source myvenv/bin/activate' >> ~/.bashrc - " - displayName: 'Install Python dependencies in RHEL 9 ARM64 container' - - - script: | - # Build pybind bindings in the ARM64 container - docker exec test-container-rhel9-arm64 bash -c " - source myvenv/bin/activate - ls /usr/include/python3.9 - # Set compiler environment variables - export CC=/usr/bin/gcc - export CXX=/usr/bin/g++ - - cd mssql_python/pybind - chmod +x build.sh + pip install -r requirements.txt + pip install cmake pybind11 + displayName: 'Install dependencies' + + - script: | + echo "Python Version: 3.14" + echo "Building Universal2 Binary" + cd "$(Build.SourcesDirectory)/mssql_python/pybind" ./build.sh - " - displayName: 'Build pybind bindings (.so) in RHEL 9 ARM64 container' - - - script: | - # Uninstall ODBC Driver before running tests - docker exec test-container-rhel9-arm64 bash -c " - dnf remove -y msodbcsql18 mssql-tools18 unixODBC-devel - rm -f /usr/bin/sqlcmd - rm -f /usr/bin/bcp - rm -rf /opt/microsoft/msodbcsql - rm -f /lib64/libodbcinst.so.2 - odbcinst -u -d -n 'ODBC Driver 18 for SQL Server' || true - echo 'Uninstalled ODBC Driver and cleaned up libraries' - echo 'Verifying arm64 rhel driver library signatures:' - ldd mssql_python/libs/linux/rhel/arm64/lib/libmsodbcsql-18.5.so.1.1 - " - displayName: 'Uninstall ODBC Driver before running tests in RHEL 9 ARM64 container' - - - script: | - # Run tests in the ARM64 container - # Get SQL Server container IP - SQLSERVER_IP=$(docker inspect sqlserver-rhel9-arm64 --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}') - echo "SQL Server IP: $SQLSERVER_IP" - - docker exec \ - -e DB_CONNECTION_STRING="Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes" \ - -e DB_PASSWORD="$(DB_PASSWORD)" \ - test-container-rhel9-arm64 bash -c " - source myvenv/bin/activate - echo 'Build successful, running tests now on RHEL 9 ARM64' - echo 'Architecture:' \$(uname -m) - echo 'Using connection string: Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=***;TrustServerCertificate=yes' - python -m pytest -v --junitxml=test-results-rhel9-arm64.xml --cov=. --cov-report=xml:coverage-rhel9-arm64.xml --capture=tee-sys --cache-clear - " - displayName: 'Run pytest with coverage in RHEL 9 ARM64 container' - env: - DB_PASSWORD: $(DB_PASSWORD) - - - script: | - # Copy test results from container to host - docker cp test-container-rhel9-arm64:/workspace/test-results-rhel9-arm64.xml $(Build.SourcesDirectory)/ - docker cp test-container-rhel9-arm64:/workspace/coverage-rhel9-arm64.xml $(Build.SourcesDirectory)/ - displayName: 'Copy test results from RHEL 9 ARM64 container' - condition: always() - - - script: | - # Clean up containers - docker stop test-container-rhel9-arm64 || true - docker rm test-container-rhel9-arm64 || true - docker stop sqlserver-rhel9-arm64 || true - docker rm sqlserver-rhel9-arm64 || true - displayName: 'Clean up RHEL 9 ARM64 containers' - condition: always() - - - task: PublishTestResults@2 - condition: succeededOrFailed() - inputs: - testResultsFiles: '**/test-results-rhel9-arm64.xml' - testRunTitle: 'Publish pytest results on RHEL 9 ARM64' - -- job: PytestOnLinux_Alpine - displayName: 'Linux Alpine x86_64' - pool: - vmImage: 'ubuntu-latest' - - steps: - - script: | - # Set up Docker buildx for multi-architecture support - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - docker buildx create --name multiarch --driver docker-container --use - docker buildx inspect --bootstrap - displayName: 'Setup Docker buildx for multi-architecture support' - - - script: | - # Create a Docker container for testing on x86_64 - docker run -d --name test-container-alpine \ - --platform linux/amd64 \ - -v $(Build.SourcesDirectory):/workspace \ - -w /workspace \ - --network bridge \ - alpine:latest \ - tail -f /dev/null - displayName: 'Create Alpine x86_64 container' - - - script: | - # Start SQL Server container (x86_64) - docker run -d --name sqlserver-alpine \ - --platform linux/amd64 \ - -e ACCEPT_EULA=Y \ - -e MSSQL_SA_PASSWORD="$(DB_PASSWORD)" \ - -p 1433:1433 \ - mcr.microsoft.com/mssql/server:2022-latest - - # Wait for SQL Server to be ready - echo "Waiting for SQL Server to start..." - for i in {1..60}; do - if docker exec sqlserver-alpine \ - /opt/mssql-tools18/bin/sqlcmd \ - -S localhost \ - -U SA \ - -P "$(DB_PASSWORD)" \ - -C -Q "SELECT 1" >/dev/null 2>&1; then - echo "SQL Server is ready!" - break - fi - echo "Waiting... ($i/60)" - sleep 2 - done - - # Create test database - docker exec sqlserver-alpine \ - /opt/mssql-tools18/bin/sqlcmd \ - -S localhost \ - -U SA \ - -P "$(DB_PASSWORD)" \ - -C -Q "CREATE DATABASE TestDB" - displayName: 'Start SQL Server container for Alpine x86_64' - env: - DB_PASSWORD: $(DB_PASSWORD) - - - script: | - # Install dependencies in the Alpine x86_64 container - docker exec test-container-alpine sh -c " - # Update package index - apk update - - # Install build tools and system dependencies - apk add --no-cache \ - build-base \ - cmake \ - clang \ - git \ - bash \ - wget \ - curl \ - gnupg \ - unixodbc \ - unixodbc-dev \ - libffi-dev \ - openssl-dev \ - zlib-dev \ - py3-pip \ - python3-dev \ - patchelf - - # Create symlinks for Python compatibility - ln -sf python3 /usr/bin/python || true - ln -sf pip3 /usr/bin/pip || true - - # Verify installation and architecture - uname -m - python --version - which cmake - " - displayName: 'Install basic dependencies in Alpine x86_64 container' - - - script: | - # Install ODBC driver in the Alpine x86_64 container - docker exec test-container-alpine bash -c " - # Detect architecture for ODBC driver download - case \$(uname -m) in - x86_64) architecture='amd64' ;; - arm64|aarch64) architecture='arm64' ;; - *) architecture='unsupported' ;; - esac - - if [[ 'unsupported' == '\$architecture' ]]; then - echo 'Alpine architecture \$(uname -m) is not currently supported.' - exit 1 - fi - - echo 'Detected architecture: '\$architecture - - # Download the packages - curl -O https://download.microsoft.com/download/fae28b9a-d880-42fd-9b98-d779f0fdd77f/msodbcsql18_18.5.1.1-1_\$architecture.apk - curl -O https://download.microsoft.com/download/7/6d/76de322a-d860-4894-9945-f0cc5d6a45f8/mssql-tools18_18.4.1.1-1_\$architecture.apk - - # Download signatures for verification - curl -O https://download.microsoft.com/download/fae28b9a-d880-42fd-9b98-d779f0fdd77f/msodbcsql18_18.5.1.1-1_\$architecture.sig - curl -O https://download.microsoft.com/download/7/6d/76de322a-d860-4894-9945-f0cc5d6a45f8/mssql-tools18_18.4.1.1-1_\$architecture.sig - - # Import Microsoft GPG key and verify packages - curl https://packages.microsoft.com/keys/microsoft.asc | gpg --import - - gpg --verify msodbcsql18_18.5.1.1-1_\$architecture.sig msodbcsql18_18.5.1.1-1_\$architecture.apk - gpg --verify mssql-tools18_18.4.1.1-1_\$architecture.sig mssql-tools18_18.4.1.1-1_\$architecture.apk - - # Install the packages - apk add --allow-untrusted msodbcsql18_18.5.1.1-1_\$architecture.apk - apk add --allow-untrusted mssql-tools18_18.4.1.1-1_\$architecture.apk - - # Cleanup - rm -f msodbcsql18_18.5.1.1-1_\$architecture.* mssql-tools18_18.4.1.1-1_\$architecture.* - - # Add mssql-tools to PATH - export PATH=\"\$PATH:/opt/mssql-tools18/bin\" - echo 'export PATH=\"\$PATH:/opt/mssql-tools18/bin\"' >> ~/.bashrc - " - displayName: 'Install ODBC Driver in Alpine x86_64 container' - - - script: | - # Install Python dependencies in the Alpine x86_64 container using virtual environment - docker exec test-container-alpine bash -c " - # Create virtual environment - python -m venv /workspace/venv - - # Activate virtual environment and install dependencies - source /workspace/venv/bin/activate - - # Upgrade pip and install dependencies + displayName: 'Build .so file' + continueOnError: false + + - task: CopyFiles@2 + inputs: + SourceFolder: '$(Build.SourcesDirectory)/mssql_python' + Contents: '*.so' + TargetFolder: '$(Build.ArtifactStagingDirectory)/ddbc-bindings/macOS' + displayName: 'Place .so file into artifacts directory' + + - script: | + brew update + brew install docker colima + + colima start --cpu 3 --memory 10 --disk 30 --vm-type=vz || \ + colima start --cpu 3 --memory 10 --disk 30 --vm-type=qemu + + sleep 30 + docker context use colima >/dev/null || true + docker version + docker ps + displayName: 'Install and start Colima-based Docker' + timeoutInMinutes: 15 + + - script: | + docker pull mcr.microsoft.com/mssql/server:2022-latest + docker run \ + --name sqlserver \ + -e ACCEPT_EULA=Y \ + -e MSSQL_SA_PASSWORD="${DB_PASSWORD}" \ + -p 1433:1433 \ + -d mcr.microsoft.com/mssql/server:2022-latest + + for i in {1..30}; do + docker exec sqlserver \ + /opt/mssql-tools18/bin/sqlcmd \ + -S localhost \ + -U SA \ + -P "$DB_PASSWORD" \ + -C -Q "SELECT 1" && break + sleep 2 + done + displayName: 'Pull & start SQL Server (Docker)' + env: + DB_PASSWORD: $(DB_PASSWORD) + + - script: | + python -m pytest -v + displayName: 'Run Pytest to validate bindings' + env: + DB_CONNECTION_STRING: 'Server=tcp:127.0.0.1,1433;Database=master;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes' + + - script: | python -m pip install --upgrade pip - python -m pip install -r requirements.txt - - # Verify virtual environment is active - which python - which pip - " - displayName: 'Install Python dependencies in Alpine x86_64 container' - - - script: | - # Build pybind bindings in the Alpine x86_64 container - docker exec test-container-alpine bash -c " - # Activate virtual environment - source /workspace/venv/bin/activate - - cd mssql_python/pybind - chmod +x build.sh - ./build.sh - " - displayName: 'Build pybind bindings (.so) in Alpine x86_64 container' + pip install wheel setuptools + python setup.py bdist_wheel + displayName: 'Build 3.14 universal2 whl' + + - task: CopyFiles@2 + inputs: + SourceFolder: '$(Build.SourcesDirectory)/dist' + Contents: '*.whl' + TargetFolder: '$(Build.ArtifactStagingDirectory)/dist' + displayName: 'Collect wheel package' + + - task: PublishBuildArtifacts@1 + condition: succeededOrFailed() + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)/ddbc-bindings' + ArtifactName: 'mssql-python-ddbc-bindings-py314' + publishLocation: 'Container' + displayName: 'Publish .so files as artifacts' + + - task: PublishBuildArtifacts@1 + condition: succeededOrFailed() + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)/dist' + ArtifactName: 'mssql-python-wheels-dist-py314' + publishLocation: 'Container' + displayName: 'Publish wheels as artifacts' + +- job: BuildLinuxWheels_Python314 + displayName: 'Build Linux Python 3.14 -' + pool: { vmImage: 'ubuntu-latest' } + timeoutInMinutes: 120 - - script: | - # Uninstall ODBC Driver before running tests to use bundled libraries - docker exec test-container-alpine bash -c " - # Remove system ODBC installation - apk del msodbcsql18 mssql-tools18 unixodbc-dev || echo 'ODBC packages not installed via apk' - rm -f /usr/bin/sqlcmd - rm -f /usr/bin/bcp - rm -rf /opt/microsoft/msodbcsql18 - rm -f /usr/lib/libodbcinst.so.2 - odbcinst -u -d -n 'ODBC Driver 18 for SQL Server' || true - echo 'Uninstalled system ODBC Driver and cleaned up libraries' - echo 'Verifying x86_64 alpine driver library signatures:' - ldd mssql_python/libs/linux/alpine/x86_64/lib/libmsodbcsql-18.5.so.1.1 || echo 'Driver library not found' - " - displayName: 'Uninstall system ODBC Driver before running tests in Alpine x86_64 container' - - - script: | - # Run tests in the Alpine x86_64 container - # Get SQL Server container IP - SQLSERVER_IP=$(docker inspect sqlserver-alpine --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}') - echo "SQL Server IP: $SQLSERVER_IP" - - docker exec \ - -e DB_CONNECTION_STRING="Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes" \ - -e DB_PASSWORD="$(DB_PASSWORD)" \ - test-container-alpine bash -c " - echo 'Build successful, running tests now on Alpine x86_64' - echo 'Architecture:' \$(uname -m) - echo 'Alpine version:' \$(cat /etc/alpine-release) - echo 'Using connection string: Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=***;TrustServerCertificate=yes' - - # Activate virtual environment - source /workspace/venv/bin/activate - - # Test basic Python import first - python -c 'import mssql_python; print(\"mssql_python imported successfully\")' - - # Run main.py if it exists - if [ -f main.py ]; then - echo 'Running main.py...' - python main.py - fi - - # Run pytest - python -m pytest -v --junitxml=test-results-alpine.xml --cov=. --cov-report=xml:coverage-alpine.xml --capture=tee-sys --cache-clear - " - displayName: 'Run pytest with coverage in Alpine x86_64 container' - env: - DB_PASSWORD: $(DB_PASSWORD) - - - script: | - # Copy test results from container to host - docker cp test-container-alpine:/workspace/test-results-alpine.xml $(Build.SourcesDirectory)/ || echo 'Failed to copy test results' - docker cp test-container-alpine:/workspace/coverage-alpine.xml $(Build.SourcesDirectory)/ || echo 'Failed to copy coverage results' - displayName: 'Copy test results from Alpine x86_64 container' - condition: always() - - - script: | - # Clean up containers - docker stop test-container-alpine || true - docker rm test-container-alpine || true - docker stop sqlserver-alpine || true - docker rm sqlserver-alpine || true - displayName: 'Clean up Alpine x86_64 containers' - condition: always() - - - task: PublishTestResults@2 - condition: succeededOrFailed() - inputs: - testResultsFiles: '**/test-results-alpine.xml' - testRunTitle: 'Publish pytest results on Alpine x86_64' - -- job: PytestOnLinux_Alpine_ARM64 - displayName: 'Linux Alpine ARM64' - pool: - vmImage: 'ubuntu-latest' + strategy: + matrix: + manylinux_x86_64: + LINUX_TAG: 'manylinux' + ARCH: 'x86_64' + DOCKER_PLATFORM: 'linux/amd64' + IMAGE: 'quay.io/pypa/manylinux_2_28_x86_64' + manylinux_aarch64: + LINUX_TAG: 'manylinux' + ARCH: 'aarch64' + DOCKER_PLATFORM: 'linux/arm64' + IMAGE: 'quay.io/pypa/manylinux_2_28_aarch64' + musllinux_x86_64: + LINUX_TAG: 'musllinux' + ARCH: 'x86_64' + DOCKER_PLATFORM: 'linux/amd64' + IMAGE: 'quay.io/pypa/musllinux_1_2_x86_64' + musllinux_aarch64: + LINUX_TAG: 'musllinux' + ARCH: 'aarch64' + DOCKER_PLATFORM: 'linux/arm64' + IMAGE: 'quay.io/pypa/musllinux_1_2_aarch64' steps: - - script: | - # Set up Docker buildx for multi-architecture support - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - docker buildx create --name multiarch --driver docker-container --use - docker buildx inspect --bootstrap - displayName: 'Setup Docker buildx for ARM64 emulation' - - - script: | - # Create a Docker container for testing on ARM64 - docker run -d --name test-container-alpine-arm64 \ - --platform linux/arm64 \ - -v $(Build.SourcesDirectory):/workspace \ - -w /workspace \ - --network bridge \ - alpine:latest \ - tail -f /dev/null - displayName: 'Create Alpine ARM64 container' - - - script: | - # Start SQL Server container (x86_64 - SQL Server doesn't support ARM64) - docker run -d --name sqlserver-alpine-arm64 \ - --platform linux/amd64 \ - -e ACCEPT_EULA=Y \ - -e MSSQL_SA_PASSWORD="$(DB_PASSWORD)" \ - -p 1433:1433 \ - mcr.microsoft.com/mssql/server:2022-latest - - # Wait for SQL Server to be ready - echo "Waiting for SQL Server to start..." - for i in {1..60}; do - if docker exec sqlserver-alpine-arm64 \ - /opt/mssql-tools18/bin/sqlcmd \ - -S localhost \ - -U SA \ - -P "$(DB_PASSWORD)" \ - -C -Q "SELECT 1" >/dev/null 2>&1; then - echo "SQL Server is ready!" - break + - checkout: self + fetchDepth: 0 + + - script: | + sudo docker run --rm --privileged tonistiigi/binfmt --install all + displayName: 'Enable QEMU (for aarch64)' + + - script: | + rm -rf $(Build.ArtifactStagingDirectory)/dist $(Build.ArtifactStagingDirectory)/ddbc-bindings + mkdir -p $(Build.ArtifactStagingDirectory)/dist + mkdir -p $(Build.ArtifactStagingDirectory)/ddbc-bindings/$(LINUX_TAG)-$(ARCH) + displayName: 'Prepare artifact directories' + + - script: | + docker run -d --name build-$(LINUX_TAG)-$(ARCH) \ + --platform $(DOCKER_PLATFORM) \ + -v $(Build.SourcesDirectory):/workspace \ + -w /workspace \ + $(IMAGE) \ + tail -f /dev/null + displayName: 'Start $(LINUX_TAG) $(ARCH) container' + + - script: | + set -euxo pipefail + if [[ "$(LINUX_TAG)" == "manylinux" ]]; then + docker exec build-$(LINUX_TAG)-$(ARCH) bash -lc ' + set -euxo pipefail + if command -v dnf >/dev/null 2>&1; then + dnf -y update || true + dnf -y install gcc gcc-c++ make cmake unixODBC-devel krb5-libs keyutils-libs ccache || true + elif command -v yum >/dev/null 2>&1; then + yum -y update || true + yum -y install gcc gcc-c++ make cmake unixODBC-devel krb5-libs keyutils-libs ccache || true + else + echo "No dnf/yum found in manylinux image" >&2 + fi + + echo "---- tool versions ----" + gcc --version || true + cmake --version || true + ' + else + docker exec build-$(LINUX_TAG)-$(ARCH) sh -lc ' + set -euxo pipefail + apk update || true + apk add --no-cache bash build-base cmake unixodbc-dev krb5-libs keyutils-libs ccache || true + + echo "---- tool versions ----" + gcc --version || true + cmake --version || true + ' fi - echo "Waiting... ($i/60)" - sleep 2 - done - - # Create test database - docker exec sqlserver-alpine-arm64 \ - /opt/mssql-tools18/bin/sqlcmd \ - -S localhost \ - -U SA \ - -P "$(DB_PASSWORD)" \ - -C -Q "CREATE DATABASE TestDB" - displayName: 'Start SQL Server container for Alpine ARM64' - env: - DB_PASSWORD: $(DB_PASSWORD) - - - script: | - # Install dependencies in the Alpine ARM64 container - docker exec test-container-alpine-arm64 sh -c " - # Update package index - apk update - - # Install build tools and system dependencies - apk add --no-cache \ - build-base \ - cmake \ - clang \ - git \ - bash \ - wget \ - curl \ - gnupg \ - unixodbc \ - unixodbc-dev \ - libffi-dev \ - openssl-dev \ - zlib-dev \ - py3-pip \ - python3-dev \ - patchelf - - # Create symlinks for Python compatibility - ln -sf python3 /usr/bin/python || true - ln -sf pip3 /usr/bin/pip || true - - # Verify installation and architecture - uname -m - python --version - which cmake - " - displayName: 'Install basic dependencies in Alpine ARM64 container' - - - script: | - # Install ODBC driver in the Alpine ARM64 container - docker exec test-container-alpine-arm64 bash -c " - # Detect architecture for ODBC driver download - case \$(uname -m) in - x86_64) architecture='amd64' ;; - arm64|aarch64) architecture='arm64' ;; - *) architecture='unsupported' ;; - esac - - if [[ 'unsupported' == '\$architecture' ]]; then - echo 'Alpine architecture \$(uname -m) is not currently supported.' - exit 1 + displayName: 'Install system build dependencies' + + - script: | + set -euxo pipefail + if [[ "$(LINUX_TAG)" == "manylinux" ]]; then SHELL_EXE=bash; else SHELL_EXE=sh; fi + + docker exec build-$(LINUX_TAG)-$(ARCH) $SHELL_EXE -lc 'mkdir -p /workspace/dist' + + # Build only for Python 3.14 (cp314) + PYBIN=cp314 + echo "=== Building for $PYBIN on $(LINUX_TAG)/$(ARCH) ===" + if [[ "$(LINUX_TAG)" == "manylinux" ]]; then + docker exec build-$(LINUX_TAG)-$(ARCH) bash -lc " + set -euxo pipefail; + PY=/opt/python/${PYBIN}-${PYBIN}/bin/python; + test -x \$PY || { echo 'Python \$PY missing'; exit 0; } + ln -sf \$PY /usr/local/bin/python; + python -m pip install -U pip setuptools wheel pybind11; + echo 'python:' \$(python -V); which python; + cd /workspace/mssql_python/pybind; + bash build.sh; + + cd /workspace; + python setup.py bdist_wheel; + " + else + docker exec build-$(LINUX_TAG)-$(ARCH) sh -lc " + set -euxo pipefail; + PY=/opt/python/${PYBIN}-${PYBIN}/bin/python; + test -x \$PY || { echo 'Python \$PY missing'; exit 0; } + ln -sf \$PY /usr/local/bin/python; + python -m pip install -U pip setuptools wheel pybind11; + echo 'python:' \$(python -V); which python; + cd /workspace/mssql_python/pybind; + bash build.sh; + + cd /workspace; + python setup.py bdist_wheel; + " fi - - echo 'Detected architecture: '\$architecture - - # Download the packages - curl -O https://download.microsoft.com/download/fae28b9a-d880-42fd-9b98-d779f0fdd77f/msodbcsql18_18.5.1.1-1_\$architecture.apk - curl -O https://download.microsoft.com/download/7/6d/76de322a-d860-4894-9945-f0cc5d6a45f8/mssql-tools18_18.4.1.1-1_\$architecture.apk - - # Download signatures for verification - curl -O https://download.microsoft.com/download/fae28b9a-d880-42fd-9b98-d779f0fdd77f/msodbcsql18_18.5.1.1-1_\$architecture.sig - curl -O https://download.microsoft.com/download/7/6d/76de322a-d860-4894-9945-f0cc5d6a45f8/mssql-tools18_18.4.1.1-1_\$architecture.sig - - # Import Microsoft GPG key and verify packages - curl https://packages.microsoft.com/keys/microsoft.asc | gpg --import - - gpg --verify msodbcsql18_18.5.1.1-1_\$architecture.sig msodbcsql18_18.5.1.1-1_\$architecture.apk - gpg --verify mssql-tools18_18.4.1.1-1_\$architecture.sig mssql-tools18_18.4.1.1-1_\$architecture.apk - - # Install the packages - apk add --allow-untrusted msodbcsql18_18.5.1.1-1_\$architecture.apk - apk add --allow-untrusted mssql-tools18_18.4.1.1-1_\$architecture.apk - - # Cleanup - rm -f msodbcsql18_18.5.1.1-1_\$architecture.* mssql-tools18_18.4.1.1-1_\$architecture.* - - # Add mssql-tools to PATH - export PATH=\"\$PATH:/opt/mssql-tools18/bin\" - echo 'export PATH=\"\$PATH:/opt/mssql-tools18/bin\"' >> ~/.bashrc - " - displayName: 'Install ODBC Driver in Alpine ARM64 container' - - - script: | - # Install Python dependencies in the Alpine ARM64 container using virtual environment - docker exec test-container-alpine-arm64 bash -c " - # Create virtual environment - python -m venv /workspace/venv - - # Activate virtual environment and install dependencies - source /workspace/venv/bin/activate - - # Upgrade pip and install dependencies - python -m pip install --upgrade pip - python -m pip install -r requirements.txt - - # Verify virtual environment is active - which python - which pip - " - displayName: 'Install Python dependencies in Alpine ARM64 container' - - - script: | - # Build pybind bindings in the Alpine ARM64 container - docker exec test-container-alpine-arm64 bash -c " - # Activate virtual environment - source /workspace/venv/bin/activate - - cd mssql_python/pybind - chmod +x build.sh - ./build.sh - " - displayName: 'Build pybind bindings (.so) in Alpine ARM64 container' - - - script: | - # Uninstall ODBC Driver before running tests to use bundled libraries - docker exec test-container-alpine-arm64 bash -c " - # Remove system ODBC installation - apk del msodbcsql18 mssql-tools18 unixodbc-dev || echo 'ODBC packages not installed via apk' - rm -f /usr/bin/sqlcmd - rm -f /usr/bin/bcp - rm -rf /opt/microsoft/msodbcsql18 - rm -f /usr/lib/libodbcinst.so.2 - odbcinst -u -d -n 'ODBC Driver 18 for SQL Server' || true - echo 'Uninstalled system ODBC Driver and cleaned up libraries' - echo 'Verifying arm64 alpine driver library signatures:' - ldd mssql_python/libs/linux/alpine/arm64/lib/libmsodbcsql-18.5.so.1.1 || echo 'Driver library not found' - " - displayName: 'Uninstall system ODBC Driver before running tests in Alpine ARM64 container' - - - script: | - # Run tests in the Alpine ARM64 container - # Get SQL Server container IP - SQLSERVER_IP=$(docker inspect sqlserver-alpine-arm64 --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}') - echo "SQL Server IP: $SQLSERVER_IP" - - docker exec \ - -e DB_CONNECTION_STRING="Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes" \ - -e DB_PASSWORD="$(DB_PASSWORD)" \ - test-container-alpine-arm64 bash -c " - echo 'Build successful, running tests now on Alpine ARM64' - echo 'Architecture:' \$(uname -m) - echo 'Alpine version:' \$(cat /etc/alpine-release) - echo 'Using connection string: Server=$SQLSERVER_IP;Database=TestDB;Uid=SA;Pwd=***;TrustServerCertificate=yes' - - # Activate virtual environment - source /workspace/venv/bin/activate - - # Test basic Python import first - python -c 'import mssql_python; print(\"mssql_python imported successfully\")' - - # Run main.py if it exists - if [ -f main.py ]; then - echo 'Running main.py...' - python main.py + displayName: 'Run build.sh and build wheels for Python 3.14' + + - script: | + set -euxo pipefail + docker cp build-$(LINUX_TAG)-$(ARCH):/workspace/dist/. "$(Build.ArtifactStagingDirectory)/dist/" || echo "No wheels to copy" + + mkdir -p "$(Build.ArtifactStagingDirectory)/ddbc-bindings/$(LINUX_TAG)-$(ARCH)" + + docker exec build-$(LINUX_TAG)-$(ARCH) $([[ "$(LINUX_TAG)" == "manylinux" ]] && echo bash -lc || echo sh -lc) ' + set -euxo pipefail; + echo "Listing package dirs for sanity:"; + ls -la /workspace/mssql_python || true; + ls -la /workspace/mssql_python/pybind || true; + + OUT="/tmp/ddbc-out-$(LINUX_TAG)-$(ARCH)"; + rm -rf "$OUT"; mkdir -p "$OUT"; + + find /workspace/mssql_python -maxdepth 1 -type f -name "*.so" -exec cp -v {} "$OUT"/ \; || true + + echo "Top-level .so collected in $OUT:"; + ls -la "$OUT" || true + ' + + docker cp "build-$(LINUX_TAG)-$(ARCH):/tmp/ddbc-out-$(LINUX_TAG)-$(ARCH)/." \ + "$(Build.ArtifactStagingDirectory)/ddbc-bindings/$(LINUX_TAG)-$(ARCH)/" \ + || echo "No top-level .so files to copy" + + find "$(Build.ArtifactStagingDirectory)/ddbc-bindings/$(LINUX_TAG)-$(ARCH)" -maxdepth 1 -type f ! -name "*.so" -delete || true + displayName: 'Copy wheels and .so back to host' + + - script: | + set -euxo pipefail + + # Start SQL Server on host + docker run -d --name sqlserver-$(LINUX_TAG)-$(ARCH) \ + --platform linux/amd64 \ + -e ACCEPT_EULA=Y \ + -e MSSQL_SA_PASSWORD="$(DB_PASSWORD)" \ + -p 1433:1433 \ + mcr.microsoft.com/mssql/server:2022-latest + + # Wait for SQL Server to be ready + for i in {1..30}; do + if docker exec sqlserver-$(LINUX_TAG)-$(ARCH) /opt/mssql-tools18/bin/sqlcmd \ + -S localhost -U SA -P "$(DB_PASSWORD)" -C -Q "SELECT 1" >/dev/null 2>&1; then + echo "SQL Server is ready!" + break + fi + sleep 2 + done + + # Get SQL Server container IP + SQL_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' sqlserver-$(LINUX_TAG)-$(ARCH)) + echo "SQL Server IP: $SQL_IP" + + # Install wheel and run tests in build container + if [[ "$(LINUX_TAG)" == "manylinux" ]]; then + docker exec build-$(LINUX_TAG)-$(ARCH) bash -lc " + set -ex + PY=/opt/python/cp314-cp314/bin/python + + # Install the built wheel in isolated directory + mkdir -p /test_isolated + cd /test_isolated + \$PY -m pip install /workspace/dist/*.whl + + # Verify installation + \$PY -c 'import mssql_python; print(\"Package imported successfully from:\", mssql_python.__file__)' + + # Install pytest + \$PY -m pip install pytest + + # Copy tests from workspace + cp -r /workspace/tests /test_isolated/ || echo 'No tests directory' + cp /workspace/pytest.ini /test_isolated/ || echo 'No pytest.ini found' + cp /workspace/requirements.txt /test_isolated/ || true + \$PY -m pip install -r /test_isolated/requirements.txt || true + + # Run pytest + if [ -d /test_isolated/tests ]; then + DB_CONNECTION_STRING='Server=$SQL_IP;Database=master;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes' \$PY -m pytest /test_isolated/tests -v || echo 'Some tests may have failed' + fi + " + else + docker exec build-$(LINUX_TAG)-$(ARCH) sh -lc " + set -ex + PY=/opt/python/cp314-cp314/bin/python + + # Install the built wheel in isolated directory + mkdir -p /test_isolated + cd /test_isolated + \$PY -m pip install /workspace/dist/*.whl + + # Verify installation + \$PY -c 'import mssql_python; print(\"Package imported successfully from:\", mssql_python.__file__)' + + # Install pytest + \$PY -m pip install pytest + + # Copy tests from workspace + cp -r /workspace/tests /test_isolated/ || echo 'No tests directory' + cp /workspace/pytest.ini /test_isolated/ || echo 'No pytest.ini found' + cp /workspace/requirements.txt /test_isolated/ || true + \$PY -m pip install -r /test_isolated/requirements.txt || true + + # Run pytest + if [ -d /test_isolated/tests ]; then + DB_CONNECTION_STRING='Server=$SQL_IP;Database=master;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes' \$PY -m pytest /test_isolated/tests -v || echo 'Some tests may have failed' + fi + " fi - - # Run pytest - python -m pytest -v --junitxml=test-results-alpine-arm64.xml --cov=. --cov-report=xml:coverage-alpine-arm64.xml --capture=tee-sys --cache-clear - " - displayName: 'Run pytest with coverage in Alpine ARM64 container' - env: - DB_PASSWORD: $(DB_PASSWORD) - - - script: | - # Copy test results from container to host - docker cp test-container-alpine-arm64:/workspace/test-results-alpine-arm64.xml $(Build.SourcesDirectory)/ || echo 'Failed to copy test results' - docker cp test-container-alpine-arm64:/workspace/coverage-alpine-arm64.xml $(Build.SourcesDirectory)/ || echo 'Failed to copy coverage results' - displayName: 'Copy test results from Alpine ARM64 container' - condition: always() - - - script: | - # Clean up containers - docker stop test-container-alpine-arm64 || true - docker rm test-container-alpine-arm64 || true - docker stop sqlserver-alpine-arm64 || true - docker rm sqlserver-alpine-arm64 || true - displayName: 'Clean up Alpine ARM64 containers' - condition: always() - - - task: PublishTestResults@2 - condition: succeededOrFailed() - inputs: - testResultsFiles: '**/test-results-alpine-arm64.xml' - testRunTitle: 'Publish pytest results on Alpine ARM64' - -- job: CodeCoverageReport - displayName: 'Full Code Coverage Report in Ubuntu x86_64' - pool: - vmImage: 'ubuntu-latest' - - steps: - - script: | - # Install build dependencies - sudo apt-get update - sudo apt-get install -y cmake gcc g++ lcov unixodbc-dev llvm clang - displayName: 'Install build dependencies' - - - script: | - # Start SQL Server container - docker pull mcr.microsoft.com/mssql/server:2022-latest - docker run \ - --name sqlserver \ - -e ACCEPT_EULA=Y \ - -e MSSQL_SA_PASSWORD="$(DB_PASSWORD)" \ - -p 1433:1433 \ - -d mcr.microsoft.com/mssql/server:2022-latest - - # Wait until SQL Server is ready - for i in {1..30}; do - docker exec sqlserver \ - /opt/mssql-tools18/bin/sqlcmd \ - -S localhost \ - -U SA \ - -P "$(DB_PASSWORD)" \ - -C -Q "SELECT 1" && break - sleep 2 - done - displayName: 'Start SQL Server container' - env: - DB_PASSWORD: $(DB_PASSWORD) - - - script: | - # Install Python dependencies - python -m pip install --upgrade pip - pip install -r requirements.txt - pip install coverage-lcov lcov-cobertura - displayName: 'Install Python dependencies' - - - script: | - # Build pybind bindings with coverage instrumentation - cd mssql_python/pybind - ./build.sh codecov - displayName: 'Build pybind bindings with coverage' - - - script: | - # Generate unified coverage (Python + C++) - chmod +x ./generate_codecov.sh - ./generate_codecov.sh - - # Convert unified LCOV to Cobertura XML for ADO reporting - lcov_cobertura total.info --output unified-coverage/coverage.xml - displayName: 'Generate unified coverage (Python + C++)' - env: - DB_CONNECTION_STRING: 'Server=tcp:127.0.0.1,1433;Database=master;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes' - DB_PASSWORD: $(DB_PASSWORD) - - - task: PublishTestResults@2 - condition: succeededOrFailed() - inputs: - testResultsFiles: '**/test-results.xml' - testRunTitle: 'Publish pytest results with unified coverage' - - - task: PublishCodeCoverageResults@2 - condition: succeededOrFailed() - inputs: - codeCoverageTool: Cobertura - summaryFileLocation: 'unified-coverage/coverage.xml' - reportDirectory: 'unified-coverage' - failIfCoverageEmpty: true - displayName: 'Publish unified code coverage results' + displayName: 'Install wheel and run pytests' + env: + DB_PASSWORD: $(DB_PASSWORD) + continueOnError: true + + - script: | + docker stop build-$(LINUX_TAG)-$(ARCH) sqlserver-$(LINUX_TAG)-$(ARCH) || true + docker rm build-$(LINUX_TAG)-$(ARCH) sqlserver-$(LINUX_TAG)-$(ARCH) || true + displayName: 'Clean up containers' + condition: always() + + - task: PublishBuildArtifacts@1 + condition: succeededOrFailed() + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)/dist' + ArtifactName: 'mssql-python-wheels-dist-py314' + publishLocation: 'Container' + displayName: 'Publish wheels as artifacts' + + - task: PublishBuildArtifacts@1 + condition: succeededOrFailed() + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)/ddbc-bindings' + ArtifactName: 'mssql-python-ddbc-bindings-py314' + publishLocation: 'Container' + displayName: 'Publish .so files as artifacts'