Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 184 additions & 0 deletions .azure-pipelines/macos-standalone-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
# Azure CLI - macOS Release Pipeline (Build → Sign → Test → Publish)
#
# Purpose: Complete end-to-end macOS release pipeline
# Architecture: Chains 4 job templates in sequence
#
# Pipeline Flow:
# 1. macos-build-jobs.yml → Build unsigned tarballs (ARM64 + Intel)
# 2. macos-sign-notarize-jobs.yml → Sign and notarize via ESRP
# 3. macos-test-jobs.yml → Test cask (local file://) + offline install
# 4. macos-publish-jobs.yml → GitHub release + Homebrew cask update
#
# Output Artifacts:
# - cli-build-unsigned-arm64, cli-build-unsigned-x86_64 (intermediate)
# - cli-signed-notarized-arm64, cli-signed-notarized-x86_64 (final)

trigger: none

parameters:
# Build parameters
- name: PythonVersion
displayName: 'Python Version (Homebrew)'
type: string
default: '3.13'

# Sign/notarize parameters
- name: BundleId
displayName: 'Bundle ID for notarization'
type: string
default: 'com.microsoft.azure.cli'

# Publish parameters
- name: PublishToGitHub
displayName: 'Publish to GitHub Release'
type: boolean
default: true

- name: GitHubRepo
displayName: 'GitHub Repository (owner/repo)'
type: string
default: 'Azure/homebrew-azure-cli'

- name: UpdateHomebrew
displayName: 'Update Homebrew cask after release'
type: boolean
default: true

- name: HomebrewTapRepo
displayName: 'Homebrew Tap Repository'
type: string
default: 'Azure/homebrew-azure-cli'

- name: GitHubServiceConnection
displayName: 'GitHub Service Connection'
type: string
default: 'Azure'

- name: ESRPServiceConnection
displayName: 'ESRP Service Connection'
type: string
default: 'ame_esrp_connection'

- name: Debug
displayName: 'Enable debug diagnostics'
type: boolean
default: false

resources:
repositories:
- repository: homebrewtap
type: github
endpoint: 'Azure'
name: Azure/homebrew-azure-cli
ref: main

variables:
- template: templates/variables.yml
- group: 'AME ESRP Variable Group'

- name: GitHubRepo
value: ${{ parameters.GitHubRepo }}
- name: HomebrewTapRepo
value: ${{ parameters.HomebrewTapRepo }}

# Disable auto-injection tasks
- name: Codeql.Enabled
value: false
- name: Codeql.SkipTaskAutoInjection
value: true
- name: CodeQL.enabled
value: false
- name: runCodesignValidationInjection
value: false
- name: DOTNET_CLI_TELEMETRY_OPTOUT
value: 1
- name: DOTNET_NOLOGO
value: 1
- name: NugetSecurityAnalysisWarningLevel
value: none
- name: skipNugetSecurityAnalysis
value: true

name: macos-release-$(Build.BuildId)

# ============================================================================
# JOBS: End-to-end macOS release flow
# ============================================================================
jobs:
# ============================================================================
# PHASE 1: BUILD (unsigned tarballs)
# ============================================================================
- template: templates/macos/macos-build-jobs.yml
parameters:
PythonVersion: ${{ parameters.PythonVersion }}
MacosArm64Image: ${{ variables.macos_arm64_pool }}
MacosIntelImage: ${{ variables.macos_intel_pool }}

# Jobs included:
# - BuildMacOSCli (matrix: ARM64 + Intel)
# - VerifyMacOSCli (matrix: ARM64 + Intel)
# Artifacts: cli-build-unsigned-arm64, cli-build-unsigned-x86_64

# ============================================================================
# PHASE 2: SIGN & NOTARIZE (via ESRP)
# ============================================================================
- template: templates/macos/macos-sign-notarize-jobs.yml
parameters:
BundleId: ${{ parameters.BundleId }}
PythonVersion: ${{ parameters.PythonVersion }}
MacosArm64Image: ${{ variables.macos_arm64_pool }}
MacosIntelImage: ${{ variables.macos_intel_pool }}
ESRPServiceConnection: ${{ parameters.ESRPServiceConnection }}
UseCurrentPipelineArtifacts: true
dependsOn:
- VerifyMacOSCli

# Jobs included:
# - DownloadAnalyze (matrix: ARM64 + Intel)
# - SignBinaries (matrix: ARM64 + Intel)
# - CreateNotarizeBundle (matrix: ARM64 + Intel)
# - Notarize (matrix: ARM64 + Intel)
# - CreateFinalTarball (matrix: ARM64 + Intel)
# Artifacts: cli-signed-notarized-arm64, cli-signed-notarized-x86_64

# ============================================================================
# PHASE 3a: TEST (local file:// cask + offline install)
# ============================================================================
- template: templates/macos/macos-cask-generation-and-tests.yml
parameters:
MacosArm64Image: ${{ variables.macos_arm64_pool }}
MacosIntelImage: ${{ variables.macos_intel_pool }}
PythonVersion: ${{ parameters.PythonVersion }}
GitHubRepo: $(GitHubRepo)
Debug: ${{ parameters.Debug }}
dependsOn:
- CreateFinalTarball

# Jobs included:
# - TestTempTapCask (matrix: ARM64 + Intel) - tests cask with local file:// URLs
# - TestOfflineInstall (matrix: ARM64 + Intel) - tests direct tarball install

# ============================================================================
# PHASE 3b: PUBLISH (GitHub + Homebrew tap)
# ============================================================================
- template: templates/macos/macos-publish-jobs.yml
parameters:
PublishToGitHub: ${{ parameters.PublishToGitHub }}
UpdateHomebrew: ${{ parameters.UpdateHomebrew }}
TestAfterPublish: false
GitHubRepo: $(GitHubRepo)
GitHubServiceConnection: ${{ parameters.GitHubServiceConnection }}
HomebrewTapRepo: ${{ parameters.HomebrewTapRepo }}
MacosArm64Image: ${{ variables.macos_arm64_pool }}
MacosIntelImage: ${{ variables.macos_intel_pool }}
PythonVersion: ${{ parameters.PythonVersion }}
Debug: ${{ parameters.Debug }}
dependsOn:
- TestTempTapCask
- TestOfflineInstall

# Jobs included:
# - CreateGitHubRelease (conditional)
# - UpdateHomebrewCask (conditional)
# - PrintSummary

186 changes: 186 additions & 0 deletions .azure-pipelines/templates/macos/macos-build-jobs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# macOS Build Jobs Template
#
# Purpose: Build Azure CLI tar.gz for macOS (ARM64 + Intel)
# Usage: Can be called from main pipeline or standalone wrapper
#
# Parameters:
# - PythonVersion: Homebrew Python version (default: 3.13)
# - MacosArm64Image: VM image for ARM64 builds
# - MacosIntelImage: VM image for Intel builds
# - condition: Job execution condition
# - dependsOn: Jobs this depends on
#
# Artifacts Published:
# - macos-cli-build-unsigned-arm64
# - macos-cli-build-unsigned-x86_64

parameters:
- name: PythonVersion
type: string
default: '3.13'
- name: MacosArm64Image
type: string
default: 'macos-15-arm64'
- name: MacosIntelImage
type: string
default: 'macos-15'
- name: condition
type: string
default: 'succeeded()'
- name: dependsOn
type: object
default: []

jobs:
# ============================================================================
# JOB: BUILD MACOS CLI (ARM64 + Intel via Matrix)
# ============================================================================
- job: BuildMacOSCli
displayName: 'macOS | Build CLI'
condition: ${{ parameters.condition }}
dependsOn: ${{ parameters.dependsOn }}
strategy:
matrix:
ARM64:
Architecture: 'arm64'
vmImageName: ${{ parameters.MacosArm64Image }}
Intel:
Architecture: 'x86_64'
vmImageName: ${{ parameters.MacosIntelImage }}
pool:
vmImage: $(vmImageName)
timeoutInMinutes: 60

steps:
- checkout: self
fetchDepth: 1

- bash: |
set -e
echo "=== Installing Homebrew Python ${{ parameters.PythonVersion }} ==="
echo "Architecture: $(Architecture)"
brew install python@${{ parameters.PythonVersion }}
PYTHON_PATH=$(brew --prefix python@${{ parameters.PythonVersion }})/libexec/bin/python3
echo "Python path: $PYTHON_PATH"
$PYTHON_PATH --version
echo "##vso[task.setvariable variable=PythonPath]$PYTHON_PATH"
displayName: 'Install Homebrew Python'
- bash: |
set -e
PYTHON="$(PythonPath)"
ARCH="$(Architecture)"
echo "=== Building Azure CLI with Homebrew Python ==="
echo "Python: $PYTHON"
echo "Architecture: $ARCH"
$PYTHON --version
$PYTHON scripts/release/macos/build_binary_tar_gz.py \
--platform-tag macos-$ARCH \
--output-dir dist/binary_tar_gz
echo ""
echo "=== Build Output ==="
ls -lh dist/binary_tar_gz/
mkdir -p $(Build.ArtifactStagingDirectory)/cli-build
cp dist/binary_tar_gz/*.tar.gz $(Build.ArtifactStagingDirectory)/cli-build/
cp dist/binary_tar_gz/*.sha256 $(Build.ArtifactStagingDirectory)/cli-build/
displayName: 'Build Azure CLI Tarball'
env:
PYTHON_MAJOR_MINOR: ${{ parameters.PythonVersion }}
- task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0
displayName: 'Generate SBOM'
inputs:
BuildDropPath: $(Build.ArtifactStagingDirectory)/cli-build

- publish: $(Build.ArtifactStagingDirectory)/cli-build
artifact: 'macos-cli-build-unsigned-$(Architecture)'
displayName: 'Publish Unsigned CLI Build ($(Architecture))'

# ============================================================================
# JOB: VERIFY MACOS CLI (ARM64 + Intel via Matrix)
# ============================================================================
- job: VerifyMacOSCli
displayName: 'macOS | Verify CLI'
dependsOn: BuildMacOSCli
condition: succeeded()
strategy:
matrix:
ARM64:
Architecture: 'arm64'
vmImageName: ${{ parameters.MacosArm64Image }}
Intel:
Architecture: 'x86_64'
vmImageName: ${{ parameters.MacosIntelImage }}
pool:
vmImage: $(vmImageName)

steps:
- checkout: none

- download: current
artifact: 'macos-cli-build-unsigned-$(Architecture)'
displayName: 'Download CLI Build ($(Architecture))'

- bash: |
set -e
ARCH="$(Architecture)"
TARBALL=$(find $(Pipeline.Workspace)/macos-cli-build-unsigned-$ARCH -name "azure-cli-*-macos-$ARCH-nopython.tar.gz" | head -1)
EXTRACT_DIR=$(mktemp -d)
echo "=== Tarball Analysis ==="
echo "Architecture: $ARCH"
echo "Tarball: $(basename "$TARBALL")"
TARBALL_SIZE=$(du -h "$TARBALL" | cut -f1)
echo "Size: $TARBALL_SIZE"
tar -xzf "$TARBALL" -C "$EXTRACT_DIR"
echo ""
echo "=== Native Extensions (.so files) ==="
find "$EXTRACT_DIR" -name "*.so" -type f | wc -l
echo ""
echo "=== Tarball Contents Summary ==="
echo "Extracted size: $(du -sh "$EXTRACT_DIR" | cut -f1)"
echo "File count: $(find "$EXTRACT_DIR" -type f | wc -l)"
rm -rf "$EXTRACT_DIR"
displayName: 'Analyze Tarball ($(Architecture))'
- bash: |
set -e
ARCH="$(Architecture)"
TARBALL=$(find $(Pipeline.Workspace)/macos-cli-build-unsigned-$ARCH -name "azure-cli-*-macos-$ARCH-nopython.tar.gz" | head -1)
EXTRACT_DIR=$(mktemp -d)
echo "=== Verifying Azure CLI on $ARCH ==="
tar -xzf "$TARBALL" -C "$EXTRACT_DIR"
if ! brew list python@${{ parameters.PythonVersion }} &>/dev/null; then
brew install python@${{ parameters.PythonVersion }}
fi
AZ_PYTHON="$(brew --prefix python@${{ parameters.PythonVersion }})/libexec/bin/python3"
export AZ_PYTHON
echo "System architecture: $(uname -m)"
export AZ_DEBUG=1
"$EXTRACT_DIR/bin/az" version
echo ""
echo "Azure CLI works correctly on $ARCH"
rm -rf "$EXTRACT_DIR"
displayName: 'Verify CLI Works ($(Architecture))'
Loading