Skip to content

Commit 1e4334f

Browse files
naga-nandyalanddq
authored andcommitted
[Packaging] | Add new ways (preview) to install azure-cli on macOS (Azure#32880)
Introducing two new ways to install azure-cli on macOS. 1. homebrew-cask 2. offline tarball.
1 parent 4810c63 commit 1e4334f

15 files changed

Lines changed: 6400 additions & 1 deletion

File tree

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# Azure CLI - macOS Release Pipeline (Build → Sign → Test → Publish)
2+
#
3+
# Purpose: Complete end-to-end macOS release pipeline
4+
# Architecture: Chains 4 job templates in sequence
5+
#
6+
# Pipeline Flow:
7+
# 1. macos-build-jobs.yml → Build unsigned tarballs (ARM64 + Intel)
8+
# 2. macos-sign-notarize-jobs.yml → Sign and notarize via ESRP
9+
# 3. macos-test-jobs.yml → Test cask (local file://) + offline install
10+
# 4. macos-publish-jobs.yml → GitHub release + Homebrew cask update
11+
#
12+
# Output Artifacts:
13+
# - cli-build-unsigned-arm64, cli-build-unsigned-x86_64 (intermediate)
14+
# - cli-signed-notarized-arm64, cli-signed-notarized-x86_64 (final)
15+
16+
trigger: none
17+
18+
parameters:
19+
# Build parameters
20+
- name: PythonVersion
21+
displayName: 'Python Version (Homebrew)'
22+
type: string
23+
default: '3.13'
24+
25+
# Sign/notarize parameters
26+
- name: BundleId
27+
displayName: 'Bundle ID for notarization'
28+
type: string
29+
default: 'com.microsoft.azure.cli'
30+
31+
# Publish parameters
32+
- name: PublishToGitHub
33+
displayName: 'Publish to GitHub Release'
34+
type: boolean
35+
default: true
36+
37+
- name: GitHubRepo
38+
displayName: 'GitHub Repository (owner/repo)'
39+
type: string
40+
default: 'Azure/homebrew-azure-cli'
41+
42+
- name: UpdateHomebrew
43+
displayName: 'Update Homebrew cask after release'
44+
type: boolean
45+
default: true
46+
47+
- name: HomebrewTapRepo
48+
displayName: 'Homebrew Tap Repository'
49+
type: string
50+
default: 'Azure/homebrew-azure-cli'
51+
52+
- name: GitHubServiceConnection
53+
displayName: 'GitHub Service Connection'
54+
type: string
55+
default: 'Azure'
56+
57+
- name: ESRPServiceConnection
58+
displayName: 'ESRP Service Connection'
59+
type: string
60+
default: 'ame_esrp_connection'
61+
62+
- name: Debug
63+
displayName: 'Enable debug diagnostics'
64+
type: boolean
65+
default: false
66+
67+
resources:
68+
repositories:
69+
- repository: homebrewtap
70+
type: github
71+
endpoint: 'Azure'
72+
name: Azure/homebrew-azure-cli
73+
ref: main
74+
75+
variables:
76+
- template: templates/variables.yml
77+
- ${{ if eq(variables['System.TeamProject'], 'release') }}:
78+
- group: 'AME ESRP Variable Group'
79+
80+
- name: GitHubRepo
81+
value: ${{ parameters.GitHubRepo }}
82+
- name: HomebrewTapRepo
83+
value: ${{ parameters.HomebrewTapRepo }}
84+
85+
# Disable auto-injection tasks
86+
- name: Codeql.Enabled
87+
value: false
88+
- name: Codeql.SkipTaskAutoInjection
89+
value: true
90+
- name: CodeQL.enabled
91+
value: false
92+
- name: runCodesignValidationInjection
93+
value: false
94+
- name: DOTNET_CLI_TELEMETRY_OPTOUT
95+
value: 1
96+
- name: DOTNET_NOLOGO
97+
value: 1
98+
- name: NugetSecurityAnalysisWarningLevel
99+
value: none
100+
- name: skipNugetSecurityAnalysis
101+
value: true
102+
103+
name: macos-release-$(Build.BuildId)
104+
105+
# ============================================================================
106+
# JOBS: End-to-end macOS release flow
107+
# ============================================================================
108+
jobs:
109+
# ============================================================================
110+
# PHASE 1: BUILD (unsigned tarballs)
111+
# ============================================================================
112+
- template: templates/macos/macos-build-jobs.yml
113+
parameters:
114+
PythonVersion: ${{ parameters.PythonVersion }}
115+
MacosArm64Image: ${{ variables.macos_arm64_pool }}
116+
MacosIntelImage: ${{ variables.macos_intel_pool }}
117+
118+
# Jobs included:
119+
# - BuildMacOSCli (matrix: ARM64 + Intel)
120+
# - VerifyMacOSCli (matrix: ARM64 + Intel)
121+
# Artifacts: cli-build-unsigned-arm64, cli-build-unsigned-x86_64
122+
123+
# ============================================================================
124+
# PHASE 2: SIGN & NOTARIZE (via ESRP)
125+
# ============================================================================
126+
- ${{ if eq(variables['System.TeamProject'], 'release') }}:
127+
- template: templates/macos/macos-sign-notarize-jobs.yml
128+
parameters:
129+
BundleId: ${{ parameters.BundleId }}
130+
PythonVersion: ${{ parameters.PythonVersion }}
131+
MacosArm64Image: ${{ variables.macos_arm64_pool }}
132+
MacosIntelImage: ${{ variables.macos_intel_pool }}
133+
ESRPServiceConnection: ${{ parameters.ESRPServiceConnection }}
134+
UseCurrentPipelineArtifacts: true
135+
dependsOn:
136+
- VerifyMacOSCli
137+
138+
# Jobs included:
139+
# - DownloadAnalyze (matrix: ARM64 + Intel)
140+
# - SignBinaries (matrix: ARM64 + Intel)
141+
# - CreateNotarizeBundle (matrix: ARM64 + Intel)
142+
# - Notarize (matrix: ARM64 + Intel)
143+
# - CreateFinalTarball (matrix: ARM64 + Intel)
144+
# Artifacts: cli-signed-notarized-arm64, cli-signed-notarized-x86_64
145+
146+
# ============================================================================
147+
# PHASE 3a: TEST (local file:// cask + offline install)
148+
# ============================================================================
149+
- ${{ if eq(variables['System.TeamProject'], 'release') }}:
150+
- template: templates/macos/macos-cask-generation-and-tests.yml
151+
parameters:
152+
MacosArm64Image: ${{ variables.macos_arm64_pool }}
153+
MacosIntelImage: ${{ variables.macos_intel_pool }}
154+
PythonVersion: ${{ parameters.PythonVersion }}
155+
GitHubRepo: $(GitHubRepo)
156+
Debug: ${{ parameters.Debug }}
157+
dependsOn:
158+
- CreateFinalTarball
159+
160+
# Jobs included:
161+
# - TestTempTapCask (matrix: ARM64 + Intel) - tests cask with local file:// URLs
162+
# - TestOfflineInstall (matrix: ARM64 + Intel) - tests direct tarball install
163+
164+
# ============================================================================
165+
# PHASE 3b: PUBLISH (GitHub + Homebrew tap)
166+
# ============================================================================
167+
- ${{ if eq(variables['System.TeamProject'], 'release') }}:
168+
- template: templates/macos/macos-publish-jobs.yml
169+
parameters:
170+
PublishToGitHub: ${{ parameters.PublishToGitHub }}
171+
UpdateHomebrew: ${{ parameters.UpdateHomebrew }}
172+
TestAfterPublish: false
173+
GitHubRepo: $(GitHubRepo)
174+
GitHubServiceConnection: ${{ parameters.GitHubServiceConnection }}
175+
HomebrewTapRepo: ${{ parameters.HomebrewTapRepo }}
176+
MacosArm64Image: ${{ variables.macos_arm64_pool }}
177+
MacosIntelImage: ${{ variables.macos_intel_pool }}
178+
PythonVersion: ${{ parameters.PythonVersion }}
179+
Debug: ${{ parameters.Debug }}
180+
dependsOn:
181+
- TestTempTapCask
182+
- TestOfflineInstall
183+
184+
# Jobs included:
185+
# - CreateGitHubRelease (conditional)
186+
# - UpdateHomebrewCask (conditional)
187+
# - PrintSummary
188+
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
# macOS Build Jobs Template
2+
#
3+
# Purpose: Build Azure CLI tar.gz for macOS (ARM64 + Intel)
4+
# Usage: Can be called from main pipeline or standalone wrapper
5+
#
6+
# Parameters:
7+
# - PythonVersion: Homebrew Python version (default: 3.13)
8+
# - MacosArm64Image: VM image for ARM64 builds
9+
# - MacosIntelImage: VM image for Intel builds
10+
# - condition: Job execution condition
11+
# - dependsOn: Jobs this depends on
12+
#
13+
# Artifacts Published:
14+
# - macos-cli-build-unsigned-arm64
15+
# - macos-cli-build-unsigned-x86_64
16+
17+
parameters:
18+
- name: PythonVersion
19+
type: string
20+
default: '3.13'
21+
- name: MacosArm64Image
22+
type: string
23+
default: 'macos-15-arm64'
24+
- name: MacosIntelImage
25+
type: string
26+
default: 'macos-15'
27+
- name: condition
28+
type: string
29+
default: 'succeeded()'
30+
- name: dependsOn
31+
type: object
32+
default: []
33+
34+
jobs:
35+
# ============================================================================
36+
# JOB: BUILD MACOS CLI (ARM64 + Intel via Matrix)
37+
# ============================================================================
38+
- job: BuildMacOSCli
39+
displayName: 'macOS | Build CLI'
40+
condition: ${{ parameters.condition }}
41+
dependsOn: ${{ parameters.dependsOn }}
42+
strategy:
43+
matrix:
44+
ARM64:
45+
Architecture: 'arm64'
46+
vmImageName: ${{ parameters.MacosArm64Image }}
47+
Intel:
48+
Architecture: 'x86_64'
49+
vmImageName: ${{ parameters.MacosIntelImage }}
50+
pool:
51+
vmImage: $(vmImageName)
52+
timeoutInMinutes: 60
53+
54+
steps:
55+
- checkout: self
56+
fetchDepth: 1
57+
58+
- bash: |
59+
set -e
60+
61+
echo "=== Installing Homebrew Python ${{ parameters.PythonVersion }} ==="
62+
echo "Architecture: $(Architecture)"
63+
brew install python@${{ parameters.PythonVersion }}
64+
65+
PYTHON_PATH=$(brew --prefix python@${{ parameters.PythonVersion }})/libexec/bin/python3
66+
echo "Python path: $PYTHON_PATH"
67+
$PYTHON_PATH --version
68+
69+
echo "##vso[task.setvariable variable=PythonPath]$PYTHON_PATH"
70+
displayName: 'Install Homebrew Python'
71+
72+
- bash: |
73+
set -e
74+
75+
PYTHON="$(PythonPath)"
76+
ARCH="$(Architecture)"
77+
78+
echo "=== Building Azure CLI with Homebrew Python ==="
79+
echo "Python: $PYTHON"
80+
echo "Architecture: $ARCH"
81+
$PYTHON --version
82+
83+
$PYTHON scripts/release/macos/build_binary_tar_gz.py \
84+
--platform-tag macos-$ARCH \
85+
--output-dir dist/binary_tar_gz
86+
87+
echo ""
88+
echo "=== Build Output ==="
89+
ls -lh dist/binary_tar_gz/
90+
91+
mkdir -p $(Build.ArtifactStagingDirectory)/cli-build
92+
cp dist/binary_tar_gz/*.tar.gz $(Build.ArtifactStagingDirectory)/cli-build/
93+
cp dist/binary_tar_gz/*.sha256 $(Build.ArtifactStagingDirectory)/cli-build/
94+
95+
displayName: 'Build Azure CLI Tarball'
96+
env:
97+
PYTHON_MAJOR_MINOR: ${{ parameters.PythonVersion }}
98+
99+
- task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0
100+
displayName: 'Generate SBOM'
101+
inputs:
102+
BuildDropPath: $(Build.ArtifactStagingDirectory)/cli-build
103+
104+
- publish: $(Build.ArtifactStagingDirectory)/cli-build
105+
artifact: 'macos-cli-build-unsigned-$(Architecture)'
106+
displayName: 'Publish Unsigned CLI Build ($(Architecture))'
107+
108+
# ============================================================================
109+
# JOB: VERIFY MACOS CLI (ARM64 + Intel via Matrix)
110+
# ============================================================================
111+
- job: VerifyMacOSCli
112+
displayName: 'macOS | Verify CLI'
113+
dependsOn: BuildMacOSCli
114+
condition: succeeded()
115+
strategy:
116+
matrix:
117+
ARM64:
118+
Architecture: 'arm64'
119+
vmImageName: ${{ parameters.MacosArm64Image }}
120+
Intel:
121+
Architecture: 'x86_64'
122+
vmImageName: ${{ parameters.MacosIntelImage }}
123+
pool:
124+
vmImage: $(vmImageName)
125+
126+
steps:
127+
- checkout: none
128+
129+
- download: current
130+
artifact: 'macos-cli-build-unsigned-$(Architecture)'
131+
displayName: 'Download CLI Build ($(Architecture))'
132+
133+
- bash: |
134+
set -e
135+
136+
ARCH="$(Architecture)"
137+
TARBALL=$(find $(Pipeline.Workspace)/macos-cli-build-unsigned-$ARCH -name "azure-cli-*-macos-$ARCH-nopython.tar.gz" | head -1)
138+
EXTRACT_DIR=$(mktemp -d)
139+
140+
echo "=== Tarball Analysis ==="
141+
echo "Architecture: $ARCH"
142+
echo "Tarball: $(basename "$TARBALL")"
143+
TARBALL_SIZE=$(du -h "$TARBALL" | cut -f1)
144+
echo "Size: $TARBALL_SIZE"
145+
146+
tar -xzf "$TARBALL" -C "$EXTRACT_DIR"
147+
148+
echo ""
149+
echo "=== Native Extensions (.so files) ==="
150+
find "$EXTRACT_DIR" -name "*.so" -type f | wc -l
151+
152+
echo ""
153+
echo "=== Tarball Contents Summary ==="
154+
echo "Extracted size: $(du -sh "$EXTRACT_DIR" | cut -f1)"
155+
echo "File count: $(find "$EXTRACT_DIR" -type f | wc -l)"
156+
157+
rm -rf "$EXTRACT_DIR"
158+
displayName: 'Analyze Tarball ($(Architecture))'
159+
160+
- bash: |
161+
set -e
162+
163+
ARCH="$(Architecture)"
164+
TARBALL=$(find $(Pipeline.Workspace)/macos-cli-build-unsigned-$ARCH -name "azure-cli-*-macos-$ARCH-nopython.tar.gz" | head -1)
165+
EXTRACT_DIR=$(mktemp -d)
166+
167+
echo "=== Verifying Azure CLI on $ARCH ==="
168+
tar -xzf "$TARBALL" -C "$EXTRACT_DIR"
169+
170+
if ! brew list python@${{ parameters.PythonVersion }} &>/dev/null; then
171+
brew install python@${{ parameters.PythonVersion }}
172+
fi
173+
174+
AZ_PYTHON="$(brew --prefix python@${{ parameters.PythonVersion }})/libexec/bin/python3"
175+
export AZ_PYTHON
176+
177+
echo "System architecture: $(uname -m)"
178+
179+
export AZ_DEBUG=1
180+
"$EXTRACT_DIR/bin/az" version
181+
182+
echo ""
183+
echo "Azure CLI works correctly on $ARCH"
184+
185+
rm -rf "$EXTRACT_DIR"
186+
displayName: 'Verify CLI Works ($(Architecture))'

0 commit comments

Comments
 (0)