Skip to content

Commit da81496

Browse files
radicaleerhardt
andauthored
Aspire cli: Add AOT builds for Linux, and macOS (#10275)
Add AOT builds for Linux, and macOS ## What? This PR adds AOT builds for Linux, and macOS on the internal builds. On macOS the binaries are appropriately signed, and notarized making them usable after downloading. ## How? This is done by having separate OS-specific jobs just to build the AOTed aspire cli. And these upload the archives for use by a "joining" Windows job which builds windows binaries, and publishes all the archives. Checksum (`sha512`) files are also generated for the archives which can be used to verify the archive when downloading. This is the current supported matrix: | Runtime Identifier | AOTed | |-------------------|-------------| | `win-x64` | ✅ | | `win-arm64` | ✅ | | `win-x86` | ❌ | | `linux-x64` | ✅ | | `linux-arm64` | ❌ | | `linux-musl-x64` | ❌ | | `osx-x64` | ✅ | | `osx-arm64` | ✅ | - `win-x86` - Native AOT is not supported for this - `linux-arm64`, `linux-musl-x64` - will be added in a follow up PR For the non-aot cases a self-contained executable is generated. ## Tests To catch missing archives due to any reason on the CI build -- * Addresses review feedback from @ eerhardt * Addresses review feedback from @ joperezr Co-authored-by: Eric Erhardt <eric.erhardt@microsoft.com>
1 parent 49dde32 commit da81496

File tree

8 files changed

+221
-45
lines changed

8 files changed

+221
-45
lines changed

docs/contributing.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ Or, if you are using Visual Studio:
3131

3232
Make sure you [build the repo](#build-the-repo) from command line at least once. Then use `./start-code.sh` (macOS and Linux) or `.\start-code.cmd` to start VS Code.
3333

34+
## Native build
35+
36+
The default build includes native builds for `Aspire.Cli` which produces Native AOT binaries for some platforms. These projects are in `eng/clipack/Aspire.Cli.*`.
37+
38+
By default it builds the cli native project for the current Runtime Identifier. A specific RIDs can be specified too by setting `$(TargetRids)` to a colon separated list like `/p:TargetRids=osx-x64:osx-arm64`.
39+
40+
Native build can be disabled with `/p:SkipNativeBuild=true`. And to only the native bits use `/p:SkipManagedBuild=true`.
41+
3442
## View Dashboard
3543

3644
When you start the sample app in Visual Studio, it will automatically open your browser to show the dashboard.

eng/Build.props

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
1-
<Project>
2-
<ItemGroup Condition="'$(DotNetBuildFromSource)' != 'true' and '$(DotNetBuild)' != 'true'">
1+
<Project TreatAsLocalProperty="TargetRids">
2+
<PropertyGroup>
3+
<BuildRid>$([System.Runtime.InteropServices.RuntimeInformation]::RuntimeIdentifier)</BuildRid>
4+
5+
<!-- Use a colon separate list of RIDs here, like osx-x64:osx-arm64 .
6+
It is helpful to allow building the non-aot cli builds on a single CI job -->
7+
<TargetRids Condition="'$(TargetRids)' == ''">$(BuildRid)</TargetRids>
8+
</PropertyGroup>
9+
10+
<ItemGroup Condition="'$(SkipManagedBuild)' != 'true'">
311
<ProjectToBuild Include="$(RepoRoot)src\**\*.csproj" Exclude="$(RepoRoot)src\Aspire.ProjectTemplates\templates\**\*.csproj" />
412
<ProjectToBuild Include="$(RepoRoot)eng\dcppack\**\*.csproj" />
513
<ProjectToBuild Include="$(RepoRoot)eng\dashboardpack\**\*.csproj" />
6-
<ProjectToBuild Include="$(RepoRoot)eng\clipack\**\*.csproj" />
14+
715
<ProjectToBuild Include="$(RepoRoot)playground\**\*.csproj" />
816

917
<!-- `$(SkipTestProjects)` allows skipping test projects from being
@@ -12,6 +20,20 @@
1220
<ProjectToBuild Include="$(RepoRoot)tests\**\*.csproj" Condition="'$(SkipTestProjects)' != 'true'" />
1321
</ItemGroup>
1422

23+
<!-- Native build only -->
24+
<ItemGroup Condition="'$(SkipNativeBuild)' != 'true'">
25+
<!-- Add Aspire.Cli project here for native-only builds so it gets picked
26+
up Restore, because Aspire.Cli.$(RID).csproj uses MSBuild task to build
27+
instead of a ProjectReference -->
28+
<ProjectToBuild Condition="'$(SkipManagedBuild)' == 'true'" Include="$(RepoRoot)src\Aspire.Cli\Aspire.Cli.csproj" />
29+
30+
<!-- Skip any unknown target rids.
31+
TODO: Map unknown rids to the available native projects -->
32+
<_TargetRidItem Include="$(TargetRids.Split(':'))" />
33+
<_NativeProjectToBuild Include="@(_TargetRidItem -> '$(RepoRoot)eng\clipack\Aspire.Cli.%(Identity).csproj')" />
34+
<ProjectToBuild Include="@(_NativeProjectToBuild->Exists())" />
35+
</ItemGroup>
36+
1537
<!-- When building from source, we want to use the live repo contents as opposed to cloning the repo. -->
1638
<PropertyGroup Condition="'$(ArcadeBuildFromSource)' == 'true' or '$(DotNetBuildRepo)' == 'true'">
1739
<CopySrcInsteadOfClone>true</CopySrcInsteadOfClone>

eng/Publishing.props

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,28 @@
2323
<_InstallersToPublish Include="$(ArtifactsDir)**\*.wixpack.zip" Condition="'$(PostBuildSign)' == 'true'" />
2424
<_InstallerManifestFilesToPublish Include="$(ArtifactsDir)VSSetup\$(Configuration)\Insertion\**\*.zip" />
2525
<_DashboardFilesToPublish Include="$(DashboardPublishedArtifactsOutputDir)\**\*.zip" />
26-
<_CliFilesToPublish Include="$(ArtifactsShippingPackagesDir)\aspire-cli-*" />
2726
</ItemGroup>
2827

2928
<Target Name="_PublishBlobItems">
29+
<!-- Validate list of aspire-cli packages on disk vs expected ones.
30+
And do this first to fail early -->
31+
<ItemGroup>
32+
<_ArchiveFiles Include="$(ArtifactsPackagesDir)\**\aspire-cli-*.zip" />
33+
<_ArchiveFiles Include="$(ArtifactsPackagesDir)\**\aspire-cli-*.tar.gz" />
34+
35+
<_CliPackProjects Include="$(RepoRoot)eng\clipack\Aspire.Cli.*.csproj" />
36+
<_ExpectedRids Include="@(_CliPackProjects->'%(Filename)'->Replace('Aspire.Cli.', ''))" />
37+
38+
<!-- Extract the rid from filenames like aspire-cli-linux-x64-9.0-dev.tar.gz and aspire-cli-linux-arm64-9.4.0-preview.1.25358.11.zip -->
39+
<_FoundRidInCliArchiveFile Include="$([System.Text.RegularExpressions.Regex]::Match(%(_ArchiveFiles.FileName), 'aspire-cli-(.*)-\d+.*').Groups[1].Value)" />
40+
41+
<_MissingRids Include="@(_ExpectedRids)" Exclude="@(_FoundRidInCliArchiveFile)" />
42+
<_UnexpectedRids Include="@(_FoundRidInCliArchiveFile)" Exclude="@(_ExpectedRids)" />
43+
</ItemGroup>
44+
45+
<Warning Condition="@(_UnexpectedRids->Count()) > 0" Text="Found unexpected CLI archives for @(_UnexpectedRids, ',') . These are all the cli archives found - @(_ArchiveFiles, ', ')" />
46+
<Error Condition="@(_MissingRids->Count()) > 0" Text="Missing CLI archive(s) for runtime identifiers: @(_MissingRids, ', '). These are all the cli archives found - @(_ArchiveFiles, ', ')" />
47+
3048
<!--
3149
For blob items for the Dashboard, we want to make sure that the version we get back is not stable, even when the repo is producing stable versions.
3250
This is because we want to be able to re-spin the build if necessary without hitting issues of blob items clashing with each other. For this reason,
@@ -39,6 +57,17 @@
3957
<Output TaskParameter="TargetOutputs" PropertyName="_PackageVersion" />
4058
</MSBuild>
4159

60+
<!-- Generate checksums for aspire-cli packages -->
61+
<ItemGroup>
62+
<_CliFileToPublish Include="@(_ArchiveFiles)" />
63+
<GenerateChecksumItems Include="@(_CliFileToPublish)" DestinationPath="%(FullPath).sha512" />
64+
</ItemGroup>
65+
66+
<GenerateChecksums Items="@(GenerateChecksumItems)" />
67+
<ItemGroup>
68+
<_CliFileToPublish Include="@(GenerateChecksumItems->'%(DestinationPath)')" />
69+
</ItemGroup>
70+
4271
<ItemGroup>
4372
<ItemsToPushToBlobFeed Include="@(_InstallersToPublish)">
4473
<IsShipping>true</IsShipping>
@@ -55,7 +84,7 @@
5584
<PublishFlatContainer>true</PublishFlatContainer>
5685
<RelativeBlobPath>$(_UploadPathRoot)/$(_PackageVersion)/%(Filename)%(Extension)</RelativeBlobPath>
5786
</ItemsToPushToBlobFeed>
58-
<ItemsToPushToBlobFeed Include="@(_CliFilesToPublish)">
87+
<ItemsToPushToBlobFeed Include="@(_CliFileToPublish)">
5988
<IsShipping>false</IsShipping>
6089
<PublishFlatContainer>true</PublishFlatContainer>
6190
<RelativeBlobPath>$(_UploadPathRoot)/$(_PackageVersion)/%(Filename)%(Extension)</RelativeBlobPath>

eng/Signing.props

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@
3434
<FileSignInfo Include="OpenTelemetry.Exporter.OpenTelemetryProtocol.dll" CertificateName="3PartySHA2" />
3535
<FileSignInfo Include="OpenTelemetry.Extensions.Hosting.dll" CertificateName="3PartySHA2" />
3636
<FileSignInfo Include="Semver.dll" CertificateName="3PartySHA2" />
37+
38+
<FileSignInfo Condition="$([System.OperatingSystem]::IsWindows())" Include="aspire.exe" CertificateName="MicrosoftDotNet500" />
39+
<FileSignInfo Condition="$([System.OperatingSystem]::IsLinux())" Include="aspire" CertificateName="MicrosoftDotNet500" />
40+
<FileSignInfo Condition="$([System.OperatingSystem]::IsMacOS())" Include="aspire" CertificateName="MacDeveloperHardenWithNotarization" />
3741
</ItemGroup>
3842

3943
<PropertyGroup>
@@ -45,6 +49,8 @@
4549
<ItemsToSign Include="$(ArtifactsPackagesDir)**\*.wixpack.zip" Condition="'$(PostBuildSign)' != 'true'" />
4650
<ItemsToSignPostBuild Include="$(VisualStudioSetupInsertionPath)\**\*.msi" Condition="'$(PostBuildSign)' == 'true'" />
4751
<ItemsToSign Include="$(VisualStudioSetupInsertionPath)\**\*.zip" Condition="'$(PostBuildSign)' != 'true'" />
52+
<ItemsToSign Include="$(ArtifactsPackagesDir)**\aspire-cli-*.zip" />
53+
<ItemsToSign Include="$(ArtifactsPackagesDir)**\aspire-cli-*.tar.gz" />
4854
<ItemsToSignPostBuild Include="$(VisualStudioSetupInsertionPath)\**\*.zip" Condition="'$(PostBuildSign)' == 'true'" />
4955
</ItemGroup>
5056
</Project>

eng/clipack/Common.projitems

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
<TargetFramework>$(DefaultTargetFramework)</TargetFramework>
44

55
<ArchiveName>aspire-cli-$(CliRuntime)</ArchiveName>
6-
<ArchiveFormat Condition="$(CliRuntime.StartsWith('linux-'))">tar.gz</ArchiveFormat>
7-
<ArchiveFormat Condition="!$(CliRuntime.StartsWith('linux-'))">zip</ArchiveFormat>
6+
<ArchiveFormat Condition="'$(ArchiveFormat)' == '' and $(CliRuntime.StartsWith('win-'))">zip</ArchiveFormat>
7+
<ArchiveFormat Condition="'$(ArchiveFormat)' == ''">tar.gz</ArchiveFormat>
88

9-
<!-- Publish native AOT if running on the target platfrom -->
9+
<!-- PublishNativeAot is explicitly set to false in the projects for cases where we don't want to AOT at all.
10+
For the rest, publish native AOT if running on the target platfrom -->
1011
<PublishNativeAot Condition="'$(PublishNativeAot)' == '' and $([System.OperatingSystem]::IsWindows()) and $(CliRuntime.StartsWith('win-'))">true</PublishNativeAot>
1112
<PublishNativeAot Condition="'$(PublishNativeAot)' == '' and $([System.OperatingSystem]::IsLinux()) and $(CliRuntime.StartsWith('linux-'))">true</PublishNativeAot>
1213
<PublishNativeAot Condition="'$(PublishNativeAot)' == '' and $([System.OperatingSystem]::IsMacOS()) and $(CliRuntime.StartsWith('osx-'))">true</PublishNativeAot>
@@ -48,6 +49,10 @@
4849
Properties="@(AdditionalProperties)"
4950
RemoveProperties="OutputPath;TargetFramework" />
5051

52+
<PropertyGroup>
53+
<_OutputBinaryPath>$(OutputPath)/aspire</_OutputBinaryPath>
54+
</PropertyGroup>
55+
5156
<!-- TODO: avoid generating the file instead of manually deleting it here -->
5257
<Delete Files="$(OutputPath)\aspire.xml" Condition="Exists('$(OutputPath)\aspire.xml')" />
5358

eng/pipelines/azure-pipelines.yml

Lines changed: 55 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,43 @@ extends:
112112

113113
stages:
114114

115+
- stage: build_sign_native
116+
displayName: Build+Sign native packages
117+
118+
jobs:
119+
- template: /eng/pipelines/templates/build_sign_native.yml@self
120+
parameters:
121+
agentOs: macos
122+
targetRidsForSameOS:
123+
- osx-arm64
124+
- osx-x64
125+
codeSign: true
126+
teamName: $(_TeamName)
127+
extraBuildArgs: >-
128+
/p:Configuration=$(_BuildConfig)
129+
$(_SignArgs)
130+
$(_OfficialBuildIdArgs)
131+
132+
- template: /eng/pipelines/templates/build_sign_native.yml@self
133+
parameters:
134+
agentOs: linux
135+
targetRidsForSameOS:
136+
- linux-x64
137+
# no need to sign ELF binaries on linux
138+
codeSign: false
139+
teamName: $(_TeamName)
140+
extraBuildArgs: >-
141+
/p:Configuration=$(_BuildConfig)
142+
$(_SignArgs)
143+
$(_OfficialBuildIdArgs)
144+
115145
# ----------------------------------------------------------------
116146
# This stage performs build, test, packaging
117147
# ----------------------------------------------------------------
118148
- stage: build
119149
displayName: Build
150+
dependsOn:
151+
- build_sign_native
120152
jobs:
121153
- template: /eng/common/templates-official/jobs/jobs.yml@self
122154
parameters:
@@ -160,6 +192,21 @@ extends:
160192
clean: true
161193

162194
steps:
195+
- task: DownloadPipelineArtifact@2
196+
displayName: 🟣Download All Native Archives
197+
inputs:
198+
itemPattern: |
199+
**/aspire-cli-*.zip
200+
**/aspire-cli-*.tar.gz
201+
targetPath: '$(Build.SourcesDirectory)/artifacts/packages/$(_BuildConfig)'
202+
203+
- task: PowerShell@2
204+
displayName: 🟣List artifacts packages contents
205+
inputs:
206+
targetType: 'inline'
207+
script: |
208+
Get-ChildItem -Path "$(Build.SourcesDirectory)\artifacts\packages" -File -Recurse | Select-Object FullName, @{Name="Size(MB)";Expression={[math]::Round($_.Length/1MB,2)}} | Format-Table -AutoSize
209+
163210
- template: /eng/pipelines/templates/BuildAndTest.yml
164211
parameters:
165212
dotnetScript: $(Build.SourcesDirectory)/dotnet.cmd
@@ -169,40 +216,14 @@ extends:
169216
repoLogPath: $(Build.Arcade.LogsPath)
170217
repoTestResultsPath: $(Build.Arcade.TestResultsPath)
171218
isWindows: true
172-
173-
- ${{ if eq(variables._RunAsPublic, True) }}:
174-
- job: Linux
175-
${{ if or(startswith(variables['Build.SourceBranch'], 'refs/heads/release/'), startswith(variables['Build.SourceBranch'], 'refs/heads/internal/release/'), eq(variables['Build.Reason'], 'Manual')) }}:
176-
# If the build is getting signed, then the timeout should be increased.
177-
timeoutInMinutes: 120
178-
${{ else }}:
179-
# timeout accounts for wait times for helix agents up to 30mins
180-
timeoutInMinutes: 90
181-
182-
pool:
183-
name: NetCore1ESPool-Internal
184-
image: 1es-mariner-2
185-
os: linux
186-
187-
variables:
188-
- name: _buildScript
189-
value: $(Build.SourcesDirectory)/build.sh --ci
190-
191-
preSteps:
192-
- checkout: self
193-
fetchDepth: 1
194-
clean: true
195-
196-
steps:
197-
- template: /eng/pipelines/templates/BuildAndTest.yml
198-
parameters:
199-
dotnetScript: $(Build.SourcesDirectory)/dotnet.sh
200-
buildScript: $(_buildScript)
201-
buildConfig: $(_BuildConfig)
202-
repoArtifactsPath: $(Build.Arcade.ArtifactsPath)
203-
repoLogPath: $(Build.Arcade.LogsPath)
204-
repoTestResultsPath: $(Build.Arcade.TestResultsPath)
205-
isWindows: false
219+
targetRids:
220+
# aot
221+
- win-x64
222+
- win-arm64
223+
# non-aot - single file builds
224+
- win-x86
225+
- linux-arm64
226+
- linux-musl-x64
206227

207228
- ${{ if and(notin(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.SourceBranch'], 'refs/heads/main')) }}:
208229
- template: /eng/common/templates-official/job/onelocbuild.yml@self

eng/pipelines/templates/BuildAndTest.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ parameters:
1616
type: string
1717
- name: dotnetScript
1818
type: string
19+
- name: targetRids
20+
type: object
21+
default: ''
1922
- name: runHelixTests
2023
type: boolean
2124
default: false
@@ -38,14 +41,15 @@ steps:
3841
/bl:${{ parameters.repoLogPath }}/build.binlog
3942
$(_OfficialBuildIdArgs)
4043
$(_InternalBuildArgs)
44+
/p:TargetRids=${{ join(':', parameters.targetRids) }}
4145
/p:SkipTestProjects=true
42-
displayName: Build
46+
displayName: 🟣Build
4347

4448
- script: ${{ parameters.dotnetScript }}
4549
build
4650
tests/workloads.proj
4751
/p:SkipPackageCheckForTemplatesTesting=true
48-
displayName: Prepare sdks for templates testing
52+
displayName: 🟣Prepare sdks for templates testing
4953

5054
- script: ${{ parameters.buildScript }}
5155
-build
@@ -63,7 +67,7 @@ steps:
6367
DEV_TEMP: $(Build.SourcesDirectory)\..
6468
DOTNET_ROOT: $(Build.SourcesDirectory)\.dotnet
6569
TEST_LOG_PATH: $(Build.SourcesDirectory)\artifacts\log\$(_BuildConfig)\Aspire.Templates.Tests
66-
displayName: Run Template tests
70+
displayName: 🟣Run Template tests
6771

6872
# Public pipeline - helix tests
6973
- ${{ if eq(parameters.runAsPublic, 'true') }}:
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
parameters:
2+
# values: windows/mac/linux
3+
agentOs: 'windows'
4+
targetRidsForSameOS:
5+
- linux-x64
6+
extraBuildArgs: ''
7+
codeSign: false
8+
teamName: ''
9+
10+
jobs:
11+
12+
- ${{ each targetRid in parameters.targetRidsForSameOS }}:
13+
- template: /eng/common/templates-official/jobs/jobs.yml@self
14+
parameters:
15+
enableMicrobuild: ${{ eq(parameters.codeSign, true) }}
16+
enableMicrobuildForMacAndLinux: ${{ and(eq(parameters.codeSign, true), ne(parameters.agentOs, 'windows')) }}
17+
enableTelemetry: true
18+
# Publish build logs
19+
enablePublishBuildArtifacts: true
20+
21+
jobs:
22+
- job: BuildNative_${{ replace(targetRid, '-', '_') }}
23+
displayName: ${{ replace(targetRid, '-', '_') }}
24+
timeoutInMinutes: 40
25+
26+
variables:
27+
- TeamName: ${{ parameters.teamName }}
28+
- ${{ if eq(parameters.codeSign, true) }}:
29+
- _buildArgs: '--sign'
30+
- ${{ else }}:
31+
- _buildArgs: ''
32+
33+
- ${{ if eq(parameters.agentOs, 'windows') }}:
34+
- scriptName: build.cmd
35+
- ${{ else }}:
36+
- scriptName: build.sh
37+
38+
pool:
39+
${{ if eq(parameters.agentOs, 'windows') }}:
40+
name: NetCore1ESPool-Internal
41+
image: windows.vs2022preview.amd64
42+
os: windows
43+
${{ if eq(parameters.agentOs, 'linux') }}:
44+
name: NetCore1ESPool-Internal
45+
image: 1es-mariner-2
46+
os: linux
47+
${{ if eq(parameters.agentOs, 'macos') }}:
48+
name: Azure Pipelines
49+
vmImage: macOS-latest-internal
50+
os: macOS
51+
52+
preSteps:
53+
- checkout: self
54+
fetchDepth: 1
55+
clean: true
56+
57+
# Installing Microbuild plugin fails due to https://github.com/dotnet/arcade/issues/15946#issuecomment-3045780552
58+
# because of the preview sdk. To fix that `restore` from `global.json` so the above step
59+
# does not have to install anything.
60+
- script: $(Build.SourcesDirectory)/$(scriptName) -restore /p:Configuration=$(_BuildConfig)
61+
displayName: 🟣Restore
62+
63+
steps:
64+
- script: >-
65+
$(Build.SourcesDirectory)/$(scriptName)
66+
--ci
67+
--build
68+
--restore
69+
/p:SkipManagedBuild=true
70+
/p:TargetRids=${{ targetRid }}
71+
$(_buildArgs)
72+
${{ parameters.extraBuildArgs }}
73+
/bl:$(Build.Arcade.LogsPath)Build.binlog
74+
displayName: 🟣Build native packages
75+
76+
- task: 1ES.PublishBuildArtifacts@1
77+
displayName: 🟣Publish Artifacts
78+
condition: always()
79+
inputs:
80+
PathtoPublish: '$(Build.Arcade.ArtifactsPath)packages/'
81+
ArtifactName: native_archives_${{ replace(targetRid, '-', '_') }}

0 commit comments

Comments
 (0)