From 1894f53001d942c66ed9cd9a9de13ee9a3984a8b Mon Sep 17 00:00:00 2001 From: Jahnvi Thakkar Date: Mon, 27 Apr 2026 14:37:58 +0530 Subject: [PATCH 1/5] fix: produce universal2 wheel for Python 3.10 on macOS (#496) - setup.py: set plat_name BEFORE parent finalize_options() so plat_name_supplied=True and get_tag() uses our explicit macosx_15_0_universal2 tag instead of inspecting Mach-O binaries - OneBranch pipeline: install universal2 Python 3.10.11 from python.org for macOS builds (UsePythonVersion@0 only provides darwin-x64 for 3.10) - Dev pipeline: same conditional install for py310 macOS matrix entry --- .../stages/build-macos-single-stage.yml | 36 ++++++++++++++----- eng/pipelines/build-whl-pipeline.yml | 21 ++++++++++- setup.py | 11 +++--- 3 files changed, 55 insertions(+), 13 deletions(-) diff --git a/OneBranchPipelines/stages/build-macos-single-stage.yml b/OneBranchPipelines/stages/build-macos-single-stage.yml index 352d6863b..1ba4ec5d1 100644 --- a/OneBranchPipelines/stages/build-macos-single-stage.yml +++ b/OneBranchPipelines/stages/build-macos-single-stage.yml @@ -68,14 +68,34 @@ stages: # ========================= # PYTHON INSTALLATION # ========================= - # UsePythonVersion@0 supports Python 3.10-3.14 on macOS - # No need for NuGet download like Windows (3.14 is in Azure Pipelines registry) - - task: UsePythonVersion@0 - inputs: - versionSpec: '${{ parameters.pythonVersion }}' - addToPath: true - displayName: 'Use Python ${{ parameters.pythonVersion }} (Universal2)' - continueOnError: false + # For Python 3.11+, UsePythonVersion@0 provides a universal2 interpreter. + # For Python 3.10, UsePythonVersion@0 only has darwin-x64 in the + # actions/python-versions manifest, producing x86_64-only wheels. + # We install universal2 Python 3.10 from python.org instead. + - ${{ if ne(parameters.shortPyVer, '310') }}: + - task: UsePythonVersion@0 + inputs: + versionSpec: '${{ parameters.pythonVersion }}' + addToPath: true + displayName: 'Use Python ${{ parameters.pythonVersion }} (Universal2)' + continueOnError: false + - ${{ if eq(parameters.shortPyVer, '310') }}: + - script: | + echo "Installing universal2 Python 3.10 from python.org..." + curl -fsSL https://www.python.org/ftp/python/3.10.11/python-3.10.11-macos11.pkg -o /tmp/python310.pkg + sudo installer -pkg /tmp/python310.pkg -target / + # python.org installs to /Library/Frameworks/Python.framework/Versions/3.10/bin + PY310_BIN="/Library/Frameworks/Python.framework/Versions/3.10/bin" + echo "##vso[task.prependpath]${PY310_BIN}" + # Create 'python' symlink so build scripts find it + if [ ! -f "${PY310_BIN}/python" ]; then + ln -s "${PY310_BIN}/python3" "${PY310_BIN}/python" + fi + ${PY310_BIN}/python3 --version + echo "Interpreter architectures:" + file ${PY310_BIN}/python3 + displayName: 'Install Python 3.10 Universal2 (python.org)' + continueOnError: false # ========================= # BUILD TOOLS diff --git a/eng/pipelines/build-whl-pipeline.yml b/eng/pipelines/build-whl-pipeline.yml index a6540c8aa..b51ce48b9 100644 --- a/eng/pipelines/build-whl-pipeline.yml +++ b/eng/pipelines/build-whl-pipeline.yml @@ -248,12 +248,31 @@ jobs: targetArch: 'universal2' steps: - # Use correct Python version and architecture for the current job + # For Python 3.11+, UsePythonVersion@0 provides a universal2 interpreter. + # For Python 3.10, UsePythonVersion@0 only has darwin-x64 in the + # actions/python-versions manifest, producing x86_64-only wheels. + # We install universal2 Python 3.10 from python.org instead. - task: UsePythonVersion@0 inputs: versionSpec: '$(pythonVersion)' addToPath: true displayName: 'Use Python $(pythonVersion) (Universal2)' + condition: ne(variables['shortPyVer'], '310') + + - script: | + echo "Installing universal2 Python 3.10 from python.org..." + curl -fsSL https://www.python.org/ftp/python/3.10.11/python-3.10.11-macos11.pkg -o /tmp/python310.pkg + sudo installer -pkg /tmp/python310.pkg -target / + PY310_BIN="/Library/Frameworks/Python.framework/Versions/3.10/bin" + echo "##vso[task.prependpath]${PY310_BIN}" + if [ ! -f "${PY310_BIN}/python" ]; then + ln -s "${PY310_BIN}/python3" "${PY310_BIN}/python" + fi + ${PY310_BIN}/python3 --version + echo "Interpreter architectures:" + file ${PY310_BIN}/python3 + displayName: 'Install Python 3.10 Universal2 (python.org)' + condition: eq(variables['shortPyVer'], '310') # Install CMake on macOS - script: | diff --git a/setup.py b/setup.py index 0e719c48b..ed5dafbb3 100644 --- a/setup.py +++ b/setup.py @@ -102,12 +102,15 @@ def validate_mssql_py_core(): class CustomBdistWheel(bdist_wheel): def finalize_options(self): - # Call the original finalize_options first to initialize self.bdist_dir - bdist_wheel.finalize_options(self) - - # Get platform info using consolidated function + # Set plat_name BEFORE calling parent finalize_options() so that + # plat_name_supplied = bool(self.plat_name) evaluates to True. + # Without this, get_tag() ignores our plat_name on macOS and falls + # back to inspecting Mach-O binaries, which can downgrade + # universal2 → x86_64 if any bundled .so is single-arch. arch, platform_tag = get_platform_info() self.plat_name = platform_tag + + bdist_wheel.finalize_options(self) print(f"Setting wheel platform tag to: {self.plat_name} (arch: {arch})") def run(self): From 96d97bbe7758726cac299dd58aba7823224fdbb9 Mon Sep 17 00:00:00 2001 From: Jahnvi Thakkar Date: Mon, 27 Apr 2026 14:47:56 +0530 Subject: [PATCH 2/5] fix: address PR review - signature verification, sudo symlinks, ensurepip - Verify .pkg signature via pkgutil before installing (supply-chain safety) - Use sudo ln -sf for symlinks under /Library/Frameworks - Add ensurepip + pip symlink so pip commands resolve correctly - Document why 3.10.11 is pinned (last version with macOS binary installers per PEP 619) --- .../stages/build-macos-single-stage.yml | 44 ++++++++++++++++--- eng/pipelines/build-whl-pipeline.yml | 43 +++++++++++++++--- 2 files changed, 74 insertions(+), 13 deletions(-) diff --git a/OneBranchPipelines/stages/build-macos-single-stage.yml b/OneBranchPipelines/stages/build-macos-single-stage.yml index 1ba4ec5d1..3d8f56ed3 100644 --- a/OneBranchPipelines/stages/build-macos-single-stage.yml +++ b/OneBranchPipelines/stages/build-macos-single-stage.yml @@ -82,18 +82,48 @@ stages: - ${{ if eq(parameters.shortPyVer, '310') }}: - script: | echo "Installing universal2 Python 3.10 from python.org..." - curl -fsSL https://www.python.org/ftp/python/3.10.11/python-3.10.11-macos11.pkg -o /tmp/python310.pkg - sudo installer -pkg /tmp/python310.pkg -target / + # Pinned to 3.10.11: last release with macOS binary installers (PEP 619). + # 3.10.12+ are source-only. Update this version only if a new + # 3.10.x release ships macOS installers (unlikely per PEP 619). + PKG_URL="https://www.python.org/ftp/python/3.10.11/python-3.10.11-macos11.pkg" + PKG_PATH="/tmp/python310.pkg" + curl -fsSL "${PKG_URL}" -o "${PKG_PATH}" + + # Verify package signature before installing (supply-chain safety) + echo "Verifying installer package signature..." + PKG_SIGNATURE="$(pkgutil --check-signature "${PKG_PATH}")" + echo "${PKG_SIGNATURE}" + echo "${PKG_SIGNATURE}" | grep -F "Status: signed by a certificate trusted by macOS" >/dev/null || { + echo "ERROR: Package signature is not trusted by macOS" + exit 1 + } + echo "${PKG_SIGNATURE}" | grep -F "Python Software Foundation" >/dev/null || { + echo "ERROR: Package signer did not match expected publisher (Python Software Foundation)" + exit 1 + } + + sudo installer -pkg "${PKG_PATH}" -target / + # python.org installs to /Library/Frameworks/Python.framework/Versions/3.10/bin PY310_BIN="/Library/Frameworks/Python.framework/Versions/3.10/bin" echo "##vso[task.prependpath]${PY310_BIN}" - # Create 'python' symlink so build scripts find it - if [ ! -f "${PY310_BIN}/python" ]; then - ln -s "${PY310_BIN}/python3" "${PY310_BIN}/python" + + # Create 'python' symlink so build scripts find the intended interpreter + # /Library/Frameworks requires sudo for symlink creation + sudo ln -sf "${PY310_BIN}/python3" "${PY310_BIN}/python" + + # Ensure pip is available for dependency installation + if [ ! -f "${PY310_BIN}/pip3" ]; then + "${PY310_BIN}/python3" -m ensurepip --upgrade + fi + if [ ! -f "${PY310_BIN}/pip" ]; then + sudo ln -sf "${PY310_BIN}/pip3" "${PY310_BIN}/pip" fi - ${PY310_BIN}/python3 --version + + "${PY310_BIN}/python3" --version + "${PY310_BIN}/python3" -m pip --version echo "Interpreter architectures:" - file ${PY310_BIN}/python3 + file "${PY310_BIN}/python3" displayName: 'Install Python 3.10 Universal2 (python.org)' continueOnError: false diff --git a/eng/pipelines/build-whl-pipeline.yml b/eng/pipelines/build-whl-pipeline.yml index b51ce48b9..9625f02c3 100644 --- a/eng/pipelines/build-whl-pipeline.yml +++ b/eng/pipelines/build-whl-pipeline.yml @@ -261,16 +261,47 @@ jobs: - script: | echo "Installing universal2 Python 3.10 from python.org..." - curl -fsSL https://www.python.org/ftp/python/3.10.11/python-3.10.11-macos11.pkg -o /tmp/python310.pkg - sudo installer -pkg /tmp/python310.pkg -target / + # Pinned to 3.10.11: last release with macOS binary installers (PEP 619). + # 3.10.12+ are source-only. Update this version only if a new + # 3.10.x release ships macOS installers (unlikely per PEP 619). + PKG_URL="https://www.python.org/ftp/python/3.10.11/python-3.10.11-macos11.pkg" + PKG_PATH="/tmp/python310.pkg" + curl -fsSL "${PKG_URL}" -o "${PKG_PATH}" + + # Verify package signature before installing (supply-chain safety) + echo "Verifying installer package signature..." + PKG_SIGNATURE="$(pkgutil --check-signature "${PKG_PATH}")" + echo "${PKG_SIGNATURE}" + echo "${PKG_SIGNATURE}" | grep -F "Status: signed by a certificate trusted by macOS" >/dev/null || { + echo "ERROR: Package signature is not trusted by macOS" + exit 1 + } + echo "${PKG_SIGNATURE}" | grep -F "Python Software Foundation" >/dev/null || { + echo "ERROR: Package signer did not match expected publisher (Python Software Foundation)" + exit 1 + } + + sudo installer -pkg "${PKG_PATH}" -target / + PY310_BIN="/Library/Frameworks/Python.framework/Versions/3.10/bin" echo "##vso[task.prependpath]${PY310_BIN}" - if [ ! -f "${PY310_BIN}/python" ]; then - ln -s "${PY310_BIN}/python3" "${PY310_BIN}/python" + + # Create 'python' symlink so build scripts find the intended interpreter + # /Library/Frameworks requires sudo for symlink creation + sudo ln -sf "${PY310_BIN}/python3" "${PY310_BIN}/python" + + # Ensure pip is available for dependency installation + if [ ! -f "${PY310_BIN}/pip3" ]; then + "${PY310_BIN}/python3" -m ensurepip --upgrade fi - ${PY310_BIN}/python3 --version + if [ ! -f "${PY310_BIN}/pip" ]; then + sudo ln -sf "${PY310_BIN}/pip3" "${PY310_BIN}/pip" + fi + + "${PY310_BIN}/python3" --version + "${PY310_BIN}/python3" -m pip --version echo "Interpreter architectures:" - file ${PY310_BIN}/python3 + file "${PY310_BIN}/python3" displayName: 'Install Python 3.10 Universal2 (python.org)' condition: eq(variables['shortPyVer'], '310') From 77b3e036889f75437ef88cd64571797c8f17432e Mon Sep 17 00:00:00 2001 From: Jahnvi Thakkar Date: Wed, 29 Apr 2026 14:54:57 +0530 Subject: [PATCH 3/5] fix: download Python 3.10 installer from NuGet feed instead of python.org - Add install-python-310-macos.sh that downloads from mssql-rs_Public feed - Add eng/versions/python-310-macos.version (pinned to 1.0.0) - Verify SHA-256 + Apple code signature (Ned Deily) before install - Fix SSL certificates for python.org macOS installer - Update both pipelines to call the script for py3.10 --- .../stages/build-macos-single-stage.yml | 47 +---- eng/pipelines/build-whl-pipeline.yml | 46 +---- eng/scripts/install-python-310-macos.sh | 183 ++++++++++++++++++ eng/versions/python-310-macos.version | 1 + 4 files changed, 190 insertions(+), 87 deletions(-) create mode 100644 eng/scripts/install-python-310-macos.sh create mode 100644 eng/versions/python-310-macos.version diff --git a/OneBranchPipelines/stages/build-macos-single-stage.yml b/OneBranchPipelines/stages/build-macos-single-stage.yml index 3d8f56ed3..d10f6c5a8 100644 --- a/OneBranchPipelines/stages/build-macos-single-stage.yml +++ b/OneBranchPipelines/stages/build-macos-single-stage.yml @@ -81,50 +81,9 @@ stages: continueOnError: false - ${{ if eq(parameters.shortPyVer, '310') }}: - script: | - echo "Installing universal2 Python 3.10 from python.org..." - # Pinned to 3.10.11: last release with macOS binary installers (PEP 619). - # 3.10.12+ are source-only. Update this version only if a new - # 3.10.x release ships macOS installers (unlikely per PEP 619). - PKG_URL="https://www.python.org/ftp/python/3.10.11/python-3.10.11-macos11.pkg" - PKG_PATH="/tmp/python310.pkg" - curl -fsSL "${PKG_URL}" -o "${PKG_PATH}" - - # Verify package signature before installing (supply-chain safety) - echo "Verifying installer package signature..." - PKG_SIGNATURE="$(pkgutil --check-signature "${PKG_PATH}")" - echo "${PKG_SIGNATURE}" - echo "${PKG_SIGNATURE}" | grep -F "Status: signed by a certificate trusted by macOS" >/dev/null || { - echo "ERROR: Package signature is not trusted by macOS" - exit 1 - } - echo "${PKG_SIGNATURE}" | grep -F "Python Software Foundation" >/dev/null || { - echo "ERROR: Package signer did not match expected publisher (Python Software Foundation)" - exit 1 - } - - sudo installer -pkg "${PKG_PATH}" -target / - - # python.org installs to /Library/Frameworks/Python.framework/Versions/3.10/bin - PY310_BIN="/Library/Frameworks/Python.framework/Versions/3.10/bin" - echo "##vso[task.prependpath]${PY310_BIN}" - - # Create 'python' symlink so build scripts find the intended interpreter - # /Library/Frameworks requires sudo for symlink creation - sudo ln -sf "${PY310_BIN}/python3" "${PY310_BIN}/python" - - # Ensure pip is available for dependency installation - if [ ! -f "${PY310_BIN}/pip3" ]; then - "${PY310_BIN}/python3" -m ensurepip --upgrade - fi - if [ ! -f "${PY310_BIN}/pip" ]; then - sudo ln -sf "${PY310_BIN}/pip3" "${PY310_BIN}/pip" - fi - - "${PY310_BIN}/python3" --version - "${PY310_BIN}/python3" -m pip --version - echo "Interpreter architectures:" - file "${PY310_BIN}/python3" - displayName: 'Install Python 3.10 Universal2 (python.org)' + chmod +x "$(Build.SourcesDirectory)/eng/scripts/install-python-310-macos.sh" + "$(Build.SourcesDirectory)/eng/scripts/install-python-310-macos.sh" + displayName: 'Install Python 3.10 Universal2 (NuGet feed)' continueOnError: false # ========================= diff --git a/eng/pipelines/build-whl-pipeline.yml b/eng/pipelines/build-whl-pipeline.yml index 9625f02c3..67b271c71 100644 --- a/eng/pipelines/build-whl-pipeline.yml +++ b/eng/pipelines/build-whl-pipeline.yml @@ -260,49 +260,9 @@ jobs: condition: ne(variables['shortPyVer'], '310') - script: | - echo "Installing universal2 Python 3.10 from python.org..." - # Pinned to 3.10.11: last release with macOS binary installers (PEP 619). - # 3.10.12+ are source-only. Update this version only if a new - # 3.10.x release ships macOS installers (unlikely per PEP 619). - PKG_URL="https://www.python.org/ftp/python/3.10.11/python-3.10.11-macos11.pkg" - PKG_PATH="/tmp/python310.pkg" - curl -fsSL "${PKG_URL}" -o "${PKG_PATH}" - - # Verify package signature before installing (supply-chain safety) - echo "Verifying installer package signature..." - PKG_SIGNATURE="$(pkgutil --check-signature "${PKG_PATH}")" - echo "${PKG_SIGNATURE}" - echo "${PKG_SIGNATURE}" | grep -F "Status: signed by a certificate trusted by macOS" >/dev/null || { - echo "ERROR: Package signature is not trusted by macOS" - exit 1 - } - echo "${PKG_SIGNATURE}" | grep -F "Python Software Foundation" >/dev/null || { - echo "ERROR: Package signer did not match expected publisher (Python Software Foundation)" - exit 1 - } - - sudo installer -pkg "${PKG_PATH}" -target / - - PY310_BIN="/Library/Frameworks/Python.framework/Versions/3.10/bin" - echo "##vso[task.prependpath]${PY310_BIN}" - - # Create 'python' symlink so build scripts find the intended interpreter - # /Library/Frameworks requires sudo for symlink creation - sudo ln -sf "${PY310_BIN}/python3" "${PY310_BIN}/python" - - # Ensure pip is available for dependency installation - if [ ! -f "${PY310_BIN}/pip3" ]; then - "${PY310_BIN}/python3" -m ensurepip --upgrade - fi - if [ ! -f "${PY310_BIN}/pip" ]; then - sudo ln -sf "${PY310_BIN}/pip3" "${PY310_BIN}/pip" - fi - - "${PY310_BIN}/python3" --version - "${PY310_BIN}/python3" -m pip --version - echo "Interpreter architectures:" - file "${PY310_BIN}/python3" - displayName: 'Install Python 3.10 Universal2 (python.org)' + chmod +x "$(Build.SourcesDirectory)/eng/scripts/install-python-310-macos.sh" + "$(Build.SourcesDirectory)/eng/scripts/install-python-310-macos.sh" + displayName: 'Install Python 3.10 Universal2 (NuGet feed)' condition: eq(variables['shortPyVer'], '310') # Install CMake on macOS diff --git a/eng/scripts/install-python-310-macos.sh b/eng/scripts/install-python-310-macos.sh new file mode 100644 index 000000000..85c028bab --- /dev/null +++ b/eng/scripts/install-python-310-macos.sh @@ -0,0 +1,183 @@ +#!/usr/bin/env bash +# Downloads the python-310-macos-installer NuGet package from an Azure Artifacts +# feed and extracts the Python 3.10.11 universal2 macOS .pkg installer. +# +# This avoids a direct download from python.org in the pipeline, addressing +# supply-chain concerns (URL availability, content integrity). The .pkg is +# hosted in our own NuGet feed and its Apple code signature is verified after +# extraction. +# +# The package version is read from eng/versions/python-310-macos.version. +# +# Usage: +# ./install-python-310-macos.sh [--feed-url URL] +# +# Outputs: +# Installs Python 3.10 universal2 to /Library/Frameworks/Python.framework/Versions/3.10 +# Prepends the bin directory to PATH via ##vso[task.prependpath] +# Creates python/pip symlinks for build script compatibility + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +# Use system Python (pre-installed on macOS) for resolve_nuget_feed.py +# since we're installing Python 3.10 and it may not be available yet. +PYTHON="${PYTHON:-$(command -v python3 || command -v python)}" + +FEED_URL="${FEED_URL:-https://pkgs.dev.azure.com/sqlclientdrivers/public/_packaging/mssql-rs_Public/nuget/v3/index.json}" +PACKAGE_ID="python-310-macos-installer" + +while [[ $# -gt 0 ]]; do + case "$1" in + --feed-url) FEED_URL="$2"; shift 2 ;; + *) echo "Unknown argument: $1"; exit 1 ;; + esac +done + +# --------------------------------------------------------------------------- +# Read package version +# --------------------------------------------------------------------------- +VERSION_FILE="$REPO_ROOT/eng/versions/python-310-macos.version" +if [ ! -f "$VERSION_FILE" ]; then + echo "ERROR: Version file not found: $VERSION_FILE" + exit 1 +fi +PACKAGE_VERSION=$(tr -d '[:space:]' < "$VERSION_FILE") +if [ -z "$PACKAGE_VERSION" ]; then + echo "ERROR: Version file is empty: $VERSION_FILE" + exit 1 +fi +echo "Package version: $PACKAGE_VERSION" + +# --------------------------------------------------------------------------- +# Download .nupkg from Azure Artifacts feed +# --------------------------------------------------------------------------- +echo "Resolving feed: $FEED_URL" +PACKAGE_BASE_URL=$("$PYTHON" "$SCRIPT_DIR/resolve_nuget_feed.py" "$FEED_URL") +if [ -z "$PACKAGE_BASE_URL" ]; then + echo "ERROR: Could not resolve PackageBaseAddress from feed" + exit 1 +fi + +VERSION_LOWER=$(echo "$PACKAGE_VERSION" | tr '[:upper:]' '[:lower:]') +NUPKG_URL="${PACKAGE_BASE_URL}${PACKAGE_ID}/${VERSION_LOWER}/${PACKAGE_ID}.${VERSION_LOWER}.nupkg" +WORK_DIR="${TMPDIR:-/tmp}/python-310-installer" +NUPKG_PATH="$WORK_DIR/${PACKAGE_ID}.${VERSION_LOWER}.nupkg" + +rm -rf "$WORK_DIR" +mkdir -p "$WORK_DIR" + +echo "Downloading: $NUPKG_URL" +curl -sSL -o "$NUPKG_PATH" "$NUPKG_URL" + +FILESIZE=$(wc -c < "$NUPKG_PATH") +echo "Downloaded: $NUPKG_PATH ($FILESIZE bytes)" +if [ "$FILESIZE" -eq 0 ]; then + echo "ERROR: Downloaded file is empty" + exit 1 +fi + +# --------------------------------------------------------------------------- +# Extract .pkg from .nupkg (which is a ZIP) +# --------------------------------------------------------------------------- +EXTRACT_DIR="$WORK_DIR/extracted" +mkdir -p "$EXTRACT_DIR" +if command -v unzip &>/dev/null; then + unzip -q "$NUPKG_PATH" -d "$EXTRACT_DIR" +else + "$PYTHON" -c "import zipfile, sys; zipfile.ZipFile(sys.argv[1]).extractall(sys.argv[2])" "$NUPKG_PATH" "$EXTRACT_DIR" +fi + +PKG_PATH=$(find "$EXTRACT_DIR" -name "*.pkg" | head -1) +if [ -z "$PKG_PATH" ]; then + echo "ERROR: No .pkg file found in NuGet package" + ls -laR "$EXTRACT_DIR" + exit 1 +fi +echo "Found installer: $(basename "$PKG_PATH")" + +# --------------------------------------------------------------------------- +# Verify SHA-256 hash (integrity check) +# --------------------------------------------------------------------------- +# SHA-256 of the official python-3.10.11-macos11.pkg from python.org +EXPECTED_SHA256="767ed35ad688d28ea4494081ae96408a0318d0d5bb9ca0139d74d6247b231cfc" +ACTUAL_SHA256="$(shasum -a 256 "${PKG_PATH}" | awk '{print $1}')" +echo "Expected SHA-256: ${EXPECTED_SHA256}" +echo "Actual SHA-256: ${ACTUAL_SHA256}" +if [ "${ACTUAL_SHA256}" != "${EXPECTED_SHA256}" ]; then + echo "ERROR: SHA-256 mismatch — installer may be corrupted or tampered with" + exit 1 +fi +echo "SHA-256 verified." + +# --------------------------------------------------------------------------- +# Verify Apple code signature (supply-chain safety) +# --------------------------------------------------------------------------- +# The python.org macOS installers are signed by Ned Deily (DJ3H93M7VJ), +# who is the CPython macOS release manager, using a Developer ID Installer +# certificate issued by Apple. +echo "Verifying installer package signature..." +PKG_SIGNATURE="$(pkgutil --check-signature "${PKG_PATH}")" +echo "${PKG_SIGNATURE}" +echo "${PKG_SIGNATURE}" | grep -F "signed by a developer certificate issued by Apple" >/dev/null || { + echo "ERROR: Package is not signed by an Apple-issued developer certificate" + exit 1 +} +echo "${PKG_SIGNATURE}" | grep -F "Ned Deily" >/dev/null || { + echo "ERROR: Package signer did not match expected publisher (Ned Deily - CPython macOS release manager)" + exit 1 +} + +# --------------------------------------------------------------------------- +# Install Python 3.10 universal2 +# --------------------------------------------------------------------------- +echo "Installing Python 3.10 universal2..." +sudo installer -pkg "${PKG_PATH}" -target / + +PY310_BIN="/Library/Frameworks/Python.framework/Versions/3.10/bin" +echo "##vso[task.prependpath]${PY310_BIN}" + +# Create 'python' symlink so build scripts find the intended interpreter +# /Library/Frameworks requires sudo for symlink creation +sudo ln -sf "${PY310_BIN}/python3" "${PY310_BIN}/python" + +# Ensure pip is available for dependency installation +if [ ! -f "${PY310_BIN}/pip3" ]; then + "${PY310_BIN}/python3" -m ensurepip --upgrade +fi +if [ ! -f "${PY310_BIN}/pip" ]; then + sudo ln -sf "${PY310_BIN}/pip3" "${PY310_BIN}/pip" +fi + +# Fix SSL certificates — python.org macOS installers don't include root CA +# certificates. Without this, urllib/requests fail with SSL: CERTIFICATE_VERIFY_FAILED. +# The "Install Certificates.command" ships with the installer and installs +# certifi's CA bundle into Python's OpenSSL directory. +echo "Installing SSL root certificates..." +"${PY310_BIN}/python3" -m pip install --upgrade certifi +CERT_SCRIPT="/Applications/Python 3.10/Install Certificates.command" +if [ -f "$CERT_SCRIPT" ]; then + bash "$CERT_SCRIPT" +else + # Fallback: manually link certifi's CA bundle + CERT_PATH=$("${PY310_BIN}/python3" -c "import certifi; print(certifi.where())") + SSL_DIR="/Library/Frameworks/Python.framework/Versions/3.10/etc/openssl" + sudo mkdir -p "$SSL_DIR" + sudo ln -sf "$CERT_PATH" "$SSL_DIR/cert.pem" + echo "Linked certifi CA bundle to $SSL_DIR/cert.pem" +fi + +echo "Python version:" +"${PY310_BIN}/python3" --version +echo "pip version:" +"${PY310_BIN}/python3" -m pip --version +echo "Interpreter architectures:" +file "${PY310_BIN}/python3" + +# --------------------------------------------------------------------------- +# Cleanup +# --------------------------------------------------------------------------- +rm -rf "$WORK_DIR" +echo "=== Python 3.10 universal2 installed successfully ===" diff --git a/eng/versions/python-310-macos.version b/eng/versions/python-310-macos.version new file mode 100644 index 000000000..3eefcb9dd --- /dev/null +++ b/eng/versions/python-310-macos.version @@ -0,0 +1 @@ +1.0.0 From 24fcf9f5f1cff33e5742028c4a6c058af6d1abcf Mon Sep 17 00:00:00 2001 From: Jahnvi Thakkar Date: Wed, 29 Apr 2026 15:58:22 +0530 Subject: [PATCH 4/5] refactor: use wheel retag instead of custom Python install - Revert setup.py to original (plat_name after finalize_options) - Revert build-whl-pipeline.yml to unconditional UsePythonVersion - Revert OneBranch stage to unconditional UsePythonVersion - Add 'wheel tags --platform-tag macosx_15_0_universal2' retag step - Remove install-python-310-macos.sh and python-310-macos.version - Same approach used in mssql-tds Rust pipeline --- .../stages/build-macos-single-stage.yml | 36 ++-- eng/pipelines/build-whl-pipeline.yml | 13 +- eng/scripts/install-python-310-macos.sh | 183 ------------------ eng/versions/python-310-macos.version | 1 - setup.py | 11 +- 5 files changed, 24 insertions(+), 220 deletions(-) delete mode 100644 eng/scripts/install-python-310-macos.sh delete mode 100644 eng/versions/python-310-macos.version diff --git a/OneBranchPipelines/stages/build-macos-single-stage.yml b/OneBranchPipelines/stages/build-macos-single-stage.yml index d10f6c5a8..a0b5f8273 100644 --- a/OneBranchPipelines/stages/build-macos-single-stage.yml +++ b/OneBranchPipelines/stages/build-macos-single-stage.yml @@ -68,23 +68,12 @@ stages: # ========================= # PYTHON INSTALLATION # ========================= - # For Python 3.11+, UsePythonVersion@0 provides a universal2 interpreter. - # For Python 3.10, UsePythonVersion@0 only has darwin-x64 in the - # actions/python-versions manifest, producing x86_64-only wheels. - # We install universal2 Python 3.10 from python.org instead. - - ${{ if ne(parameters.shortPyVer, '310') }}: - - task: UsePythonVersion@0 - inputs: - versionSpec: '${{ parameters.pythonVersion }}' - addToPath: true - displayName: 'Use Python ${{ parameters.pythonVersion }} (Universal2)' - continueOnError: false - - ${{ if eq(parameters.shortPyVer, '310') }}: - - script: | - chmod +x "$(Build.SourcesDirectory)/eng/scripts/install-python-310-macos.sh" - "$(Build.SourcesDirectory)/eng/scripts/install-python-310-macos.sh" - displayName: 'Install Python 3.10 Universal2 (NuGet feed)' - continueOnError: false + - task: UsePythonVersion@0 + inputs: + versionSpec: '${{ parameters.pythonVersion }}' + addToPath: true + displayName: 'Use Python ${{ parameters.pythonVersion }}' + continueOnError: false # ========================= # BUILD TOOLS @@ -213,6 +202,19 @@ stages: python setup.py bdist_wheel displayName: 'Build wheel package' + # Retag wheel to universal2 — the .so binary is already universal2 (CMake cross-compiles + # both arm64 + x86_64), but bdist_wheel may tag as x86_64 when the Python interpreter + # lacks universal2 support (e.g. Python 3.10 from UsePythonVersion@0 is x86_64-only). + # Same approach used in mssql-tds Rust pipeline (build-python-wheels-template.yml). + - script: | + for whl in dist/*.whl; do + echo "Retagging: $(basename $whl)" + wheel tags --platform-tag macosx_15_0_universal2 --remove "$whl" + done + echo "Wheels after retag:" + ls -lh dist/ + displayName: 'Ensure universal2 platform tag' + # ========================= # ARTIFACT PUBLISHING # ========================= diff --git a/eng/pipelines/build-whl-pipeline.yml b/eng/pipelines/build-whl-pipeline.yml index 67b271c71..1c8d8d5ab 100644 --- a/eng/pipelines/build-whl-pipeline.yml +++ b/eng/pipelines/build-whl-pipeline.yml @@ -248,22 +248,11 @@ jobs: targetArch: 'universal2' steps: - # For Python 3.11+, UsePythonVersion@0 provides a universal2 interpreter. - # For Python 3.10, UsePythonVersion@0 only has darwin-x64 in the - # actions/python-versions manifest, producing x86_64-only wheels. - # We install universal2 Python 3.10 from python.org instead. - task: UsePythonVersion@0 inputs: versionSpec: '$(pythonVersion)' addToPath: true - displayName: 'Use Python $(pythonVersion) (Universal2)' - condition: ne(variables['shortPyVer'], '310') - - - script: | - chmod +x "$(Build.SourcesDirectory)/eng/scripts/install-python-310-macos.sh" - "$(Build.SourcesDirectory)/eng/scripts/install-python-310-macos.sh" - displayName: 'Install Python 3.10 Universal2 (NuGet feed)' - condition: eq(variables['shortPyVer'], '310') + displayName: 'Use Python $(pythonVersion)' # Install CMake on macOS - script: | diff --git a/eng/scripts/install-python-310-macos.sh b/eng/scripts/install-python-310-macos.sh deleted file mode 100644 index 85c028bab..000000000 --- a/eng/scripts/install-python-310-macos.sh +++ /dev/null @@ -1,183 +0,0 @@ -#!/usr/bin/env bash -# Downloads the python-310-macos-installer NuGet package from an Azure Artifacts -# feed and extracts the Python 3.10.11 universal2 macOS .pkg installer. -# -# This avoids a direct download from python.org in the pipeline, addressing -# supply-chain concerns (URL availability, content integrity). The .pkg is -# hosted in our own NuGet feed and its Apple code signature is verified after -# extraction. -# -# The package version is read from eng/versions/python-310-macos.version. -# -# Usage: -# ./install-python-310-macos.sh [--feed-url URL] -# -# Outputs: -# Installs Python 3.10 universal2 to /Library/Frameworks/Python.framework/Versions/3.10 -# Prepends the bin directory to PATH via ##vso[task.prependpath] -# Creates python/pip symlinks for build script compatibility - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" - -# Use system Python (pre-installed on macOS) for resolve_nuget_feed.py -# since we're installing Python 3.10 and it may not be available yet. -PYTHON="${PYTHON:-$(command -v python3 || command -v python)}" - -FEED_URL="${FEED_URL:-https://pkgs.dev.azure.com/sqlclientdrivers/public/_packaging/mssql-rs_Public/nuget/v3/index.json}" -PACKAGE_ID="python-310-macos-installer" - -while [[ $# -gt 0 ]]; do - case "$1" in - --feed-url) FEED_URL="$2"; shift 2 ;; - *) echo "Unknown argument: $1"; exit 1 ;; - esac -done - -# --------------------------------------------------------------------------- -# Read package version -# --------------------------------------------------------------------------- -VERSION_FILE="$REPO_ROOT/eng/versions/python-310-macos.version" -if [ ! -f "$VERSION_FILE" ]; then - echo "ERROR: Version file not found: $VERSION_FILE" - exit 1 -fi -PACKAGE_VERSION=$(tr -d '[:space:]' < "$VERSION_FILE") -if [ -z "$PACKAGE_VERSION" ]; then - echo "ERROR: Version file is empty: $VERSION_FILE" - exit 1 -fi -echo "Package version: $PACKAGE_VERSION" - -# --------------------------------------------------------------------------- -# Download .nupkg from Azure Artifacts feed -# --------------------------------------------------------------------------- -echo "Resolving feed: $FEED_URL" -PACKAGE_BASE_URL=$("$PYTHON" "$SCRIPT_DIR/resolve_nuget_feed.py" "$FEED_URL") -if [ -z "$PACKAGE_BASE_URL" ]; then - echo "ERROR: Could not resolve PackageBaseAddress from feed" - exit 1 -fi - -VERSION_LOWER=$(echo "$PACKAGE_VERSION" | tr '[:upper:]' '[:lower:]') -NUPKG_URL="${PACKAGE_BASE_URL}${PACKAGE_ID}/${VERSION_LOWER}/${PACKAGE_ID}.${VERSION_LOWER}.nupkg" -WORK_DIR="${TMPDIR:-/tmp}/python-310-installer" -NUPKG_PATH="$WORK_DIR/${PACKAGE_ID}.${VERSION_LOWER}.nupkg" - -rm -rf "$WORK_DIR" -mkdir -p "$WORK_DIR" - -echo "Downloading: $NUPKG_URL" -curl -sSL -o "$NUPKG_PATH" "$NUPKG_URL" - -FILESIZE=$(wc -c < "$NUPKG_PATH") -echo "Downloaded: $NUPKG_PATH ($FILESIZE bytes)" -if [ "$FILESIZE" -eq 0 ]; then - echo "ERROR: Downloaded file is empty" - exit 1 -fi - -# --------------------------------------------------------------------------- -# Extract .pkg from .nupkg (which is a ZIP) -# --------------------------------------------------------------------------- -EXTRACT_DIR="$WORK_DIR/extracted" -mkdir -p "$EXTRACT_DIR" -if command -v unzip &>/dev/null; then - unzip -q "$NUPKG_PATH" -d "$EXTRACT_DIR" -else - "$PYTHON" -c "import zipfile, sys; zipfile.ZipFile(sys.argv[1]).extractall(sys.argv[2])" "$NUPKG_PATH" "$EXTRACT_DIR" -fi - -PKG_PATH=$(find "$EXTRACT_DIR" -name "*.pkg" | head -1) -if [ -z "$PKG_PATH" ]; then - echo "ERROR: No .pkg file found in NuGet package" - ls -laR "$EXTRACT_DIR" - exit 1 -fi -echo "Found installer: $(basename "$PKG_PATH")" - -# --------------------------------------------------------------------------- -# Verify SHA-256 hash (integrity check) -# --------------------------------------------------------------------------- -# SHA-256 of the official python-3.10.11-macos11.pkg from python.org -EXPECTED_SHA256="767ed35ad688d28ea4494081ae96408a0318d0d5bb9ca0139d74d6247b231cfc" -ACTUAL_SHA256="$(shasum -a 256 "${PKG_PATH}" | awk '{print $1}')" -echo "Expected SHA-256: ${EXPECTED_SHA256}" -echo "Actual SHA-256: ${ACTUAL_SHA256}" -if [ "${ACTUAL_SHA256}" != "${EXPECTED_SHA256}" ]; then - echo "ERROR: SHA-256 mismatch — installer may be corrupted or tampered with" - exit 1 -fi -echo "SHA-256 verified." - -# --------------------------------------------------------------------------- -# Verify Apple code signature (supply-chain safety) -# --------------------------------------------------------------------------- -# The python.org macOS installers are signed by Ned Deily (DJ3H93M7VJ), -# who is the CPython macOS release manager, using a Developer ID Installer -# certificate issued by Apple. -echo "Verifying installer package signature..." -PKG_SIGNATURE="$(pkgutil --check-signature "${PKG_PATH}")" -echo "${PKG_SIGNATURE}" -echo "${PKG_SIGNATURE}" | grep -F "signed by a developer certificate issued by Apple" >/dev/null || { - echo "ERROR: Package is not signed by an Apple-issued developer certificate" - exit 1 -} -echo "${PKG_SIGNATURE}" | grep -F "Ned Deily" >/dev/null || { - echo "ERROR: Package signer did not match expected publisher (Ned Deily - CPython macOS release manager)" - exit 1 -} - -# --------------------------------------------------------------------------- -# Install Python 3.10 universal2 -# --------------------------------------------------------------------------- -echo "Installing Python 3.10 universal2..." -sudo installer -pkg "${PKG_PATH}" -target / - -PY310_BIN="/Library/Frameworks/Python.framework/Versions/3.10/bin" -echo "##vso[task.prependpath]${PY310_BIN}" - -# Create 'python' symlink so build scripts find the intended interpreter -# /Library/Frameworks requires sudo for symlink creation -sudo ln -sf "${PY310_BIN}/python3" "${PY310_BIN}/python" - -# Ensure pip is available for dependency installation -if [ ! -f "${PY310_BIN}/pip3" ]; then - "${PY310_BIN}/python3" -m ensurepip --upgrade -fi -if [ ! -f "${PY310_BIN}/pip" ]; then - sudo ln -sf "${PY310_BIN}/pip3" "${PY310_BIN}/pip" -fi - -# Fix SSL certificates — python.org macOS installers don't include root CA -# certificates. Without this, urllib/requests fail with SSL: CERTIFICATE_VERIFY_FAILED. -# The "Install Certificates.command" ships with the installer and installs -# certifi's CA bundle into Python's OpenSSL directory. -echo "Installing SSL root certificates..." -"${PY310_BIN}/python3" -m pip install --upgrade certifi -CERT_SCRIPT="/Applications/Python 3.10/Install Certificates.command" -if [ -f "$CERT_SCRIPT" ]; then - bash "$CERT_SCRIPT" -else - # Fallback: manually link certifi's CA bundle - CERT_PATH=$("${PY310_BIN}/python3" -c "import certifi; print(certifi.where())") - SSL_DIR="/Library/Frameworks/Python.framework/Versions/3.10/etc/openssl" - sudo mkdir -p "$SSL_DIR" - sudo ln -sf "$CERT_PATH" "$SSL_DIR/cert.pem" - echo "Linked certifi CA bundle to $SSL_DIR/cert.pem" -fi - -echo "Python version:" -"${PY310_BIN}/python3" --version -echo "pip version:" -"${PY310_BIN}/python3" -m pip --version -echo "Interpreter architectures:" -file "${PY310_BIN}/python3" - -# --------------------------------------------------------------------------- -# Cleanup -# --------------------------------------------------------------------------- -rm -rf "$WORK_DIR" -echo "=== Python 3.10 universal2 installed successfully ===" diff --git a/eng/versions/python-310-macos.version b/eng/versions/python-310-macos.version deleted file mode 100644 index 3eefcb9dd..000000000 --- a/eng/versions/python-310-macos.version +++ /dev/null @@ -1 +0,0 @@ -1.0.0 diff --git a/setup.py b/setup.py index ed5dafbb3..0e719c48b 100644 --- a/setup.py +++ b/setup.py @@ -102,15 +102,12 @@ def validate_mssql_py_core(): class CustomBdistWheel(bdist_wheel): def finalize_options(self): - # Set plat_name BEFORE calling parent finalize_options() so that - # plat_name_supplied = bool(self.plat_name) evaluates to True. - # Without this, get_tag() ignores our plat_name on macOS and falls - # back to inspecting Mach-O binaries, which can downgrade - # universal2 → x86_64 if any bundled .so is single-arch. + # Call the original finalize_options first to initialize self.bdist_dir + bdist_wheel.finalize_options(self) + + # Get platform info using consolidated function arch, platform_tag = get_platform_info() self.plat_name = platform_tag - - bdist_wheel.finalize_options(self) print(f"Setting wheel platform tag to: {self.plat_name} (arch: {arch})") def run(self): From 91c42f9b96d40783913c041ed0d8857d2aff2976 Mon Sep 17 00:00:00 2001 From: Jahnvi Thakkar Date: Wed, 29 Apr 2026 16:11:12 +0530 Subject: [PATCH 5/5] revert: restore eng/pipelines/build-whl-pipeline.yml to main (outdated folder) --- eng/pipelines/build-whl-pipeline.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eng/pipelines/build-whl-pipeline.yml b/eng/pipelines/build-whl-pipeline.yml index 1c8d8d5ab..a6540c8aa 100644 --- a/eng/pipelines/build-whl-pipeline.yml +++ b/eng/pipelines/build-whl-pipeline.yml @@ -248,11 +248,12 @@ jobs: targetArch: 'universal2' steps: + # Use correct Python version and architecture for the current job - task: UsePythonVersion@0 inputs: versionSpec: '$(pythonVersion)' addToPath: true - displayName: 'Use Python $(pythonVersion)' + displayName: 'Use Python $(pythonVersion) (Universal2)' # Install CMake on macOS - script: |