-
Notifications
You must be signed in to change notification settings - Fork 927
Expand file tree
/
Copy pathazure-pipelines.yml
More file actions
1021 lines (964 loc) · 49.1 KB
/
Copy pathazure-pipelines.yml
File metadata and controls
1021 lines (964 loc) · 49.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
parameters:
- name: packageVSCodeExtensionAsPreRelease
displayName: 'Package VS Code Extension as Pre-Release'
type: boolean
default: false
# Operator-controlled override for the AspireCliChannel baked into native CLI
# binaries by build_sign_native.yml. The default 'auto' lets the pipeline pick
# stable / staging / daily / pr-<N> from Build.Reason and Build.SourceBranch
# (where release/* and internal/release/* branches always resolve to 'staging'
# so stabilizing dogfood builds aren't mis-baked as 'stable' — see
# https://github.com/microsoft/aspire/issues/17527). Set this to 'stable' when
# kicking off the official GA ship build from a release/* branch so the
# distributed binary identifies as stable and `aspire init` writes a
# nuget.org-only nuget.config matching the promoted package set.
- name: aspireCliChannelOverride
displayName: 'Aspire CLI channel override (auto = derive from branch; set to stable for the GA ship build)'
type: string
default: 'auto'
values:
- auto
- stable
- staging
- daily
# When true, the notify_failure and notify_success stages run in dry-run
# mode: they log the `gh` CLI commands they would run to file/update/close
# the ci-broken issue, but do not actually mutate anything on
# microsoft/aspire. Use this when validating pipeline plumbing changes
# without spamming issues.
- name: notifyOnFailureDryRun
displayName: 'Notify on failure: dry-run (log gh commands, do not mutate)'
type: boolean
default: false
trigger:
batch: true
branches:
include:
- main*
- release/*
- internal/release/*
paths:
include:
- '*'
exclude:
- '**.md'
- eng/Version.Details.xml
- .github/*
- docs/*
- LICENSE.TXT
- PATENTS.TXT
- THIRD-PARTY-NOTICES.TXT
pr:
branches:
include:
- main*
- release/*
- feature/*
- internal/release/*
paths:
include:
- '*'
exclude:
- '**.md'
- eng/Version.Details.xml
- .github/*
- docs/*
- LICENSE.TXT
- PATENTS.TXT
- THIRD-PARTY-NOTICES.TXT
variables:
- template: /eng/pipelines/common-variables.yml@self
- template: /eng/common/templates-official/variables/pool-providers.yml@self
# aspire-bot-app-id + aspire-bot-private-key for notify_failure / notify_success.
# Gate mirrors the notify stage conditions: non-PR + main/release/* only.
# A manual run on a feature branch never needs these, so don't pay the
# variable-group auth check at queue time.
- ${{ if and(notin(variables['Build.Reason'], 'PullRequest'), or(eq(variables['Build.SourceBranch'], 'refs/heads/main'), startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'))) }}:
- group: Aspire-Release-Secrets
- name: _BuildConfig
value: Release
- name: Build.Arcade.ArtifactsPath
value: $(Build.SourcesDirectory)/artifacts/
- name: Build.Arcade.LogsPath
value: $(Build.Arcade.ArtifactsPath)log/$(_BuildConfig)/
- name: Build.Arcade.TestResultsPath
value: $(Build.Arcade.ArtifactsPath)TestResults/$(_BuildConfig)/
# Produce test-signed build for PR and Public builds
- ${{ if or(eq(variables['_RunAsPublic'], 'true'), eq(variables['Build.Reason'], 'PullRequest')) }}:
# needed for darc (dependency flow) publishing
- name: _PublishArgs
value: ''
- name: _OfficialBuildIdArgs
value: ''
# needed for signing
- name: _SignType
value: test
- name: _SignArgs
value: ''
- name: _Sign
value: false
# Set up non-PR build from internal project
- ${{ if and(ne(variables['_RunAsPublic'], 'true'), ne(variables['Build.Reason'], 'PullRequest')) }}:
# needed for darc (dependency flow) publishing
- name: _PublishArgs
value: >-
/p:DotNetPublishUsingPipelines=true
- name: _OfficialBuildIdArgs
value: /p:OfficialBuildId=$(BUILD.BUILDNUMBER)
# needed for signing
- name: _SignType
value: real
- name: _SignArgs
value: /p:DotNetSignType=$(_SignType) /p:TeamName=$(_TeamName) /p:Sign=$(_Sign) /p:DotNetPublishUsingPipelines=true
- name: _Sign
value: true
resources:
repositories:
- repository: 1ESPipelineTemplates
type: git
name: 1ESPipelineTemplates/1ESPipelineTemplates
ref: refs/tags/release
extends:
template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates
parameters:
settings:
networkIsolationPolicy: Permissive,CFSClean,CFSClean2
featureFlags:
autoEnablePREfastWithNewRuleset: false
autoEnableRoslynWithNewRuleset: false
sdl:
componentgovernance:
# Exclude .packages folder from CG scanning. This folder contains older versions of our own
# packages restored for API compatibility validation, which may have since-fixed vulnerabilities.
ignoreDirectories: $(Build.SourcesDirectory)/.packages
policheck:
enabled: true
exclusionsFile: $(Build.SourcesDirectory)\.config\PoliCheckExclusions.xml
eslint:
enabled: false
justificationForDisabling: 'see https://portal.microsofticm.com/imp/v3/incidents/incident/482258316/summary'
sourceAnalysisPool:
name: NetCore1ESPool-Internal
image: windows.vs2026preview.scout.amd64
os: windows
tsa:
enabled: true
customBuildTags:
- ES365AIMigrationTooling
containers:
linux_x64:
image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-amd64
env:
SysRoot: /crossrootfs/x64
LinkerFlavor: lld
linux_arm64:
image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-arm64
env:
SysRoot: /crossrootfs/arm64
LinkerFlavor: lld
linux_musl_x64:
image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-amd64-musl
env:
SysRoot: /crossrootfs/x64
LinkerFlavor: lld
stages:
- stage: build_sign_native
displayName: Build+Sign native packages
jobs:
- template: /eng/pipelines/templates/build_sign_native.yml@self
parameters:
agentOs: macos
targetRidsForSameOS:
- osx-arm64
- osx-x64
codeSign: true
teamName: $(_TeamName)
aspireCliChannelOverride: ${{ parameters.aspireCliChannelOverride }}
extraBuildArgs: >-
/p:Configuration=$(_BuildConfig)
$(_SignArgs)
$(_OfficialBuildIdArgs)
- template: /eng/pipelines/templates/build_sign_native.yml@self
parameters:
agentOs: linux
targetRidsForSameOS:
- linux-x64
- linux-arm64
- linux-musl-x64
# no need to sign ELF binaries on linux
codeSign: false
# Have the linux-x64 native job compute installer-pipeline vars
# (aspireVersion / aspireArtifactVersion / installerChannel) at the
# end of its build. Lets prepare_installers depend on
# build_sign_native instead of assemble — see the ComputeVars step
# comment in build_sign_native.yml for the full rationale.
computeVarsRid: linux-x64
teamName: $(_TeamName)
aspireCliChannelOverride: ${{ parameters.aspireCliChannelOverride }}
extraBuildArgs: >-
/p:Configuration=$(_BuildConfig)
$(_OfficialBuildIdArgs)
- template: /eng/pipelines/templates/build_sign_native.yml@self
parameters:
agentOs: windows
targetRidsForSameOS:
- win-x64
- win-arm64
codeSign: true
teamName: $(_TeamName)
aspireCliChannelOverride: ${{ parameters.aspireCliChannelOverride }}
extraBuildArgs: >-
/p:Configuration=$(_BuildConfig)
$(_SignArgs)
$(_OfficialBuildIdArgs)
# ----------------------------------------------------------------
# Builds the VS Code extension VSIX in parallel with `build` and
# `build_sign_native`. Extracted from the managed Windows `build` job
# because the extension's only inputs are extension/ (Node.js / yarn /
# vsce) and signVsix.proj; it does not consume any managed nupkg or
# native CLI archive. See eng/pipelines/templates/build_extension.yml
# for the full job. Assemble pulls the resulting aspire-vscode-extension
# pipeline artifact, same as before.
# ----------------------------------------------------------------
- stage: build_extension
displayName: Build VS Code Extension
dependsOn: []
jobs:
- template: /eng/pipelines/templates/build_extension.yml@self
parameters:
packageVSCodeExtensionAsPreRelease: ${{ parameters.packageVSCodeExtensionAsPreRelease }}
# ----------------------------------------------------------------
# This stage performs the managed build, sign, pack, and template
# tests. It runs IN PARALLEL with build_sign_native and
# build_extension — the managed compile/sign/pack does not consume
# any native CLI artifacts or VS Code extension artifacts (those are
# stitched into the unified BAR manifest by the assemble stage at
# publish time).
# ----------------------------------------------------------------
- stage: build
displayName: Build
dependsOn: []
jobs:
- template: /eng/common/templates-official/jobs/jobs.yml@self
parameters:
enableMicrobuild: true
# Publishing (AssetManifest emit, PublishBuildAssets, darc) runs in the
# downstream `assemble` stage once both the managed build (this stage)
# and the native CLI archives (build_sign_native) are available together.
enablePublishUsingPipelines: false
enablePublishBuildAssets: false
publishAssetsImmediately: false
enableTelemetry: true
enableSourceIndex: ${{ eq(variables['Build.SourceBranch'], 'refs/heads/main') }}
# Publish build logs
enablePublishBuildArtifacts: true
# Test results publishing is owned by the template_tests stage —
# the Windows job here no longer runs tests inline, so leaving
# this on would emit a "no test results found" warning on every
# build. Cosmetic, but worth keeping the log clean.
enablePublishTestResults: false
workspace:
clean: all
jobs:
- job: Windows
${{ if or(startswith(variables['Build.SourceBranch'], 'refs/heads/release/'), startswith(variables['Build.SourceBranch'], 'refs/heads/internal/release/'), eq(variables['Build.Reason'], 'Manual')) }}:
# If the build is getting signed, then the timeout should be increased.
timeoutInMinutes: 120
${{ else }}:
# timeout accounts for wait times for helix agents up to 30mins
timeoutInMinutes: 90
pool:
name: NetCore1ESPool-Internal
image: windows.vs2026preview.scout.amd64
os: windows
variables:
- name: _buildScript
value: $(Build.SourcesDirectory)/build.cmd -ci
preSteps:
- checkout: self
fetchDepth: 1
clean: true
steps:
# NOTE: Native CLI archive + per-RID nupkg downloads (Aspire.Cli.*)
# were here, but moved to the assemble stage. The managed Windows
# build does NOT consume native artifacts during compile/sign/pack;
# native artifacts only matter at -publish time (Publishing.props
# _PublishBlobItems globs the Shipping dir). Splitting the native
# download + -publish into a downstream `assemble` stage lets this
# job start in parallel with build_sign_native. The
# download_native_symbols template (aspire.pdb + .symbols.nupkg)
# likewise runs in assemble alongside download-native-archives.ps1
# so the -publish call there routes symbols to MSDL/SymWeb.
#
# Node.js / npm / vsce / corepack setup that used to live here
# was extension-build scaffolding. It moved to the parallel
# `build_extension` stage along with the extension build / sign
# / verify / publish steps (see buildExtension: false below).
# validateNpmPackageSignatures is also off for this job, so this
# path has no remaining npm tooling dependency.
- template: /eng/pipelines/templates/BuildAndTest.yml
parameters:
dotnetScript: $(Build.SourcesDirectory)/dotnet.cmd
buildScript: $(_buildScript)
buildConfig: $(_BuildConfig)
repoArtifactsPath: $(Build.Arcade.ArtifactsPath)
repoLogPath: $(Build.Arcade.LogsPath)
repoTestResultsPath: $(Build.Arcade.TestResultsPath)
isWindows: true
packageVSCodeExtensionAsPreRelease: ${{ parameters.packageVSCodeExtensionAsPreRelease }}
# Split C moves native pack + npm-tgz staging to the downstream
# Assemble stage, so no microsoft-aspire-cli*.tgz files exist
# under this job's Shipping directory at signature-validation
# time. The Assemble stage owns the equivalent check after it
# stages the signed tarballs from the per-RID native artifacts.
validateNpmPackageSignatures: false
# VS Code extension build / sign / verify / publish moved to
# the parallel `build_extension` stage so it does not sit on
# the Windows managed-build critical path through Assemble.
buildExtension: false
# Upload the packed managed Shipping artifacts so the downstream
# assemble stage can stage them next to the native CLI archives
# and run a single unified -publish that emits the BAR manifest
# covering everything.
# IMPORTANT: This artifact is ALSO consumed by the `template_tests`
# stage. It must contain every Aspire.* nupkg the templates
# transitively reference — including per-RID arch-specific
# Aspire.Hosting.Orchestration.<rid> and Aspire.Dashboard.Sdk.<rid>
# packages produced by the managed build. If a future refactor
# moves any of those out of Shipping/, template_tests will start
# failing with restore errors.
- task: 1ES.PublishPipelineArtifact@1
displayName: 🟣Publish managed packages for assemble
inputs:
path: '$(Build.SourcesDirectory)/artifacts/packages/$(_BuildConfig)/Shipping'
artifactName: managed_packages_shipping
# Dashboard runtime zips ship from artifacts/DashboardArtifacts/<config>/<rid>/
# (per eng/dashboardpack/Common.projitems) and are picked up by
# Publishing.props _DashboardFilesToPublish. They sit outside packages/,
# so they need their own pipeline-artifact handoff to assemble.
- task: 1ES.PublishPipelineArtifact@1
displayName: 🟣Publish dashboard artifacts for assemble
inputs:
path: '$(Build.SourcesDirectory)/artifacts/DashboardArtifacts/$(_BuildConfig)'
artifactName: managed_dashboard_artifacts
# OneLocBuild is temporarily disabled while we work with the loc team
# to reconfigure it after the repo migration from dotnet/aspire to microsoft/aspire.
# - ${{ if and(notin(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.SourceBranch'], 'refs/heads/main')) }}:
# - template: /eng/common/templates-official/job/onelocbuild.yml@self
# parameters:
# LclSource: lclFilesfromPackage
# LclPackageId: 'LCL-JUNO-PROD-ASPIRE'
# GitHubOrg: microsoft
# MirrorRepo: aspire
# MirrorBranch: main
# ----------------------------------------------------------------
# Template tests: validate that the packed Aspire.* nupkgs produce
# working `aspire new` projects. These are a quality gate, not a
# shipping dependency — assemble + prepare_installers do NOT wait
# for this stage. If templates fail, the overall build is reported
# failed (so the release pipeline won't promote), but the artifacts
# can still be inspected from BAR.
# ----------------------------------------------------------------
- stage: template_tests
displayName: Template Tests
dependsOn:
- build
jobs:
- template: /eng/common/templates-official/jobs/jobs.yml@self
parameters:
enableMicrobuild: false
enablePublishUsingPipelines: false
enablePublishBuildAssets: false
publishAssetsImmediately: false
enableTelemetry: true
enablePublishBuildArtifacts: true
enablePublishTestResults: true
workspace:
clean: all
jobs:
- job: TemplateTests
displayName: Template Tests
timeoutInMinutes: 30
pool:
name: NetCore1ESPool-Internal
image: windows.vs2026preview.scout.amd64
os: windows
variables:
- name: _buildScript
value: $(Build.SourcesDirectory)/build.cmd -ci
preSteps:
- checkout: self
fetchDepth: 1
clean: true
steps:
# The packed Aspire.* nupkgs were uploaded as managed_packages_shipping
# by the build stage's Windows job. Drop them back into the canonical
# Shipping path so the Templates test's nuget.config (which points at
# this directory as a local feed) can resolve Aspire.* references.
- task: DownloadPipelineArtifact@2
displayName: 🟣Download managed packages
inputs:
artifact: managed_packages_shipping
targetPath: '$(Build.SourcesDirectory)/artifacts/packages/$(_BuildConfig)/Shipping'
- script: $(Build.SourcesDirectory)/dotnet.cmd
build
tests/workloads.proj
/p:SkipPackageCheckForTemplatesTesting=true
displayName: 🟣Prepare sdks for templates testing
- script: $(_buildScript)
-build
-restore
-test
-configuration $(_BuildConfig)
/bl:$(Build.Arcade.LogsPath)/BuildTemplatesTests.binlog
$(_OfficialBuildIdArgs)
$(_InternalBuildArgs)
/p:SkipTests=false
-projects $(Build.SourcesDirectory)\tests\Aspire.Templates.Tests\Aspire.Templates.Tests.csproj
env:
RunOnlyBasicBuildTemplateTests: true
DEV_TEMP: $(Build.SourcesDirectory)\..
DOTNET_ROOT: $(Build.SourcesDirectory)\.dotnet
TEST_LOG_PATH: $(Build.SourcesDirectory)\artifacts\log\$(_BuildConfig)\Aspire.Templates.Tests
displayName: 🟣Run Template tests
# ----------------------------------------------------------------
# Assemble: stage native CLI archives + managed packages together,
# run the unified -publish (emits the BAR AssetManifest covering
# everything), and the auto-injected PublishBuildAssets job pushes
# the manifest to BAR/darc.
# NOTE: This stage does NOT depend on template_tests. Templates is a
# validation signal, not a shipping dependency — running it in
# parallel keeps the critical path off it.
# ----------------------------------------------------------------
- stage: assemble
displayName: Assemble + Publish
dependsOn:
- build_sign_native
- build
- build_extension
jobs:
- template: /eng/common/templates-official/jobs/jobs.yml@self
parameters:
# Enable MicroBuild signing on the Assemble pool so we can sign the
# npm tgz files staged from per-RID native artifacts (see "Sign npm
# package tarballs" step below). The per-RID native jobs cannot sign
# them because PackDotnetTool (eng/AfterSigning.targets) runs AFTER
# Arcade's Sign target — by name and design, the npm tgz files only
# exist after Sign has already completed. Upstream signs them in the
# Windows BuildAndTest job which runs `-sign` again after staging;
# Split C moves that second sign pass here so the parallelism
# between `build` and `build_sign_native` is preserved. The Assemble
# job runs on Windows because Authenticode signing of aspire.js
# (MicrosoftDotNet500 cert in eng/Signing.props) is rejected by
# Linux ESRP with "This file format cannot be signed because it is
# not recognized."
enableMicrobuild: true
enablePublishUsingPipelines: true
# Asset_Registry_Publish (BAR publish) needs MaestroAccessToken from
# the Publish-Build-Assets variable group, which is only loaded on
# main / release / internal-release branches because of the AzDO
# ACL on that group. Skip the publish job entirely on contributor
# branches so jobs.yml does not inject Asset_Registry_Publish and
# fail at runtime for a missing token (and so the assemble stage
# does not trip an additional 1ES Branch control checkpoint).
${{ if or(eq(variables['Build.SourceBranch'], 'refs/heads/main'), startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), startsWith(variables['Build.SourceBranch'], 'refs/heads/internal/release/')) }}:
enablePublishBuildAssets: true
publishAssetsImmediately: true
${{ else }}:
enablePublishBuildAssets: false
publishAssetsImmediately: false
enableTelemetry: true
enablePublishBuildArtifacts: true
workspace:
clean: all
jobs:
- job: Assemble
timeoutInMinutes: 60
# Windows agent: required for MicroBuild signing of aspire.js via
# MicrosoftDotNet500 (Authenticode). Linux ESRP rejects that cert
# for JS, so we cannot run the sign step on Linux even though the
# surrounding download/stage/publish work is cross-platform.
pool:
name: NetCore1ESPool-Internal
image: windows.vs2026preview.scout.amd64
os: windows
variables:
- name: _buildScript
value: $(Build.SourcesDirectory)/build.cmd -ci
preSteps:
- checkout: self
fetchDepth: 1
clean: true
steps:
# Parallelize the 7 native_archives_<rid> downloads. AzDO runs
# steps serially within a job and DownloadPipelineArtifact@2 with
# itemPattern: downloads each artifact ONCE PER SLICE (both
# *.zip|*.tar.gz and Aspire.Cli*.nupkg slices walk the same
# artifact independently), so the previous two-task design
# serialized to ~156s wall-clock.
#
# See eng/scripts/download-native-archives.ps1 for the
# extraction logic, error handling, and debug-info contract.
# Scope is only Container-typed artifacts (BuildArtifacts
# published via 1ES.PublishBuildArtifacts@1); the three
# PipelineArtifact downloads below stay on
# DownloadPipelineArtifact@2 because that API uses a different
# protocol that Bearer + Invoke-WebRequest does not handle.
- pwsh: |
& "$(Build.SourcesDirectory)/eng/scripts/download-native-archives.ps1" `
-CollectionUri '$(System.CollectionUri)' `
-Project '$(System.TeamProject)' `
-BuildId '$(Build.BuildId)' `
-ArchivesTargetDir "$(Build.SourcesDirectory)/artifacts/signed-archives/$(_BuildConfig)" `
-NupkgsTargetDir "$(Build.SourcesDirectory)/artifacts/native-cli-packages/$(_BuildConfig)"
displayName: 🟣Download native archives (parallel)
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
# Stage and sign the npm tgz files BEFORE downloading managed
# packages. SignToolTask walks every container under
# $(ArtifactsShippingPackagesDir) looking for nested signable
# items, and Arcade's default `<ItemsToSign Include="...**\*.nupkg" />`
# would re-sign every managed Aspire.Hosting.*.nupkg already
# signed upstream — including
# Aspire.Hosting.Orchestration.<rid>.nupkg whose nested
# manifest.cat would be re-submitted to ESRP. Keeping Shipping
# limited to native CLI staged content at sign time avoids that.
- pwsh: |
$ErrorActionPreference = 'Stop'
& "$(Build.SourcesDirectory)/eng/scripts/stage-native-cli-tool-packages.ps1" `
-DownloadRoot "$(Build.SourcesDirectory)/artifacts/native-cli-packages/$(_BuildConfig)" `
-ShippingDir "$(Build.SourcesDirectory)/artifacts/packages/$(_BuildConfig)/Shipping" `
-RequireNpmPackages
displayName: 🟣Stage Native CLI Packages
# Sign the npm tgz files now that they are staged in Shipping.
# PackDotnetTool runs in eng/AfterSigning.targets — after Arcade's
# Sign target — so the per-RID native jobs cannot sign the tgz
# files themselves. Upstream signs them on the Windows
# BuildAndTest job which runs a second `-sign` after staging;
# Split C does the same here so Sign.proj picks up the tgz files
# via ItemsToSign in eng/Signing.props (LinuxSign500180PGP cert,
# detached PGP sidecar). `-restore` is required for Arcade's
# Toolset.proj; we scope per-project Restore to a single small
# csproj for the same reason as the publish call below.
- script: $(_buildScript)
-restore
-sign $(_SignArgs)
-configuration $(_BuildConfig)
/bl:$(Build.Arcade.LogsPath)/AssembleSign.binlog
$(_OfficialBuildIdArgs)
$(_InternalBuildArgs)
/p:SkipNativeBuild=true
/p:SkipManagedBuild=true
/p:SkipTestProjects=true
-projects $(Build.SourcesDirectory)/src/Aspire.Hosting/Aspire.Hosting.csproj
displayName: 🟣Sign npm package tarballs
condition: and(succeeded(), eq(variables['_SignType'], 'real'))
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken) # Needed for the signing task
# Verify each staged microsoft-aspire-cli*.tgz has a sibling .sig
# file with a plausible PGP signature. Signing happens in the
# step above; this check is the catch-net for the silent failure
# mode where Arcade/ESRP emits an empty or malformed sidecar.
# Only meaningful when real signing happened.
- pwsh: |
& "$(Build.SourcesDirectory)/eng/scripts/validate-npm-package-signatures.ps1" `
-ShippingDir "$(Build.SourcesDirectory)/artifacts/packages/$(_BuildConfig)/Shipping"
displayName: 🟣Validate npm package signatures
condition: and(succeeded(), eq(variables['_SignType'], 'real'))
# Managed-side artifacts use PipelineArtifact (1ES.PublishPipelineArtifact@1)
# which DownloadPipelineArtifact@2 handles natively. Combined size is
# small (~30s sequential) so parallelizing them isn't worth the
# complexity of branching on resource.type in the pwsh script above.
# Downloaded AFTER the npm sign step above so SignToolTask doesn't
# re-process the already-signed managed Aspire.Hosting.*.nupkg
# files (see comment on staging step). The native AOT symbols
# template stages .symbols.nupkg into Shipping for the same reason
# — kept after sign even though current ItemsToSign rules in
# eng/Signing.props don't match symbols.nupkg, the convention is
# "only what we want signed lives in Shipping at sign time".
- template: /eng/pipelines/templates/download_native_symbols.yml@self
- task: DownloadPipelineArtifact@2
displayName: 🟣Download managed packages
inputs:
artifact: managed_packages_shipping
targetPath: '$(Build.SourcesDirectory)/artifacts/packages/$(_BuildConfig)/Shipping'
# VS Code extension VSIX + manifest live at packages/<config>/vscode/
# (not Shipping). The aspire-vscode-extension artifact is uploaded
# earlier by BuildAndTest.yml after signing — pull it back into the
# same path here so _PublishBlobItems' VSIX-count validation passes.
- task: DownloadPipelineArtifact@2
displayName: 🟣Download VS Code extension
inputs:
artifact: aspire-vscode-extension
targetPath: '$(Build.SourcesDirectory)/artifacts/packages/$(_BuildConfig)/vscode'
# Dashboard runtime zips originate at artifacts/DashboardArtifacts/<config>/
# and are scanned by _DashboardFilesToPublish at publish time.
- task: DownloadPipelineArtifact@2
displayName: 🟣Download dashboard artifacts
inputs:
artifact: managed_dashboard_artifacts
targetPath: '$(Build.SourcesDirectory)/artifacts/DashboardArtifacts/$(_BuildConfig)'
- pwsh: |
Get-ChildItem -Path "$(Build.SourcesDirectory)/artifacts/packages" -File -Recurse |
Select-Object FullName, @{Name="Size(MB)";Expression={[math]::Round($_.Length/1MB,2)}} |
Format-Table -AutoSize
displayName: 🟣List staged Shipping contents
# Run only Arcade's Publish target. -build/-pack/-sign are off so we
# don't recompile or re-sign anything (the inputs already arrived
# signed from upstream stages).
#
# "Unified" = one AssetManifest covering every shipping output of
# this build: managed nupkgs (from the build stage), native CLI
# archives (from build_sign_native), dashboard runtime zips, and
# the signed VS Code extension. Publishing.props's
# _PublishBlobItems scans ArtifactsShippingPackagesDir +
# _SignedArchivesDir + DashboardPublishedArtifactsOutputDir +
# the staged VSIX folder, and writes one manifest.xml.
#
# The Publish Assets job that jobs.yml auto-injects after this
# stage uploads that manifest to BAR (Build Asset Registry /
# Maestro), which is what release-publish-nuget.yml and downstream
# promotion pipelines read to know what NuGet packages, native
# archives, and other blobs this build shipped.
#
# `-restore` is required for Arcade's Toolset.proj (dropping it
# causes "Toolset version 10.0.0-beta... has not been restored"
# in InitializeToolset). But we narrow the per-project Restore
# traversal to a single small csproj via `-projects` instead of
# letting it walk the full ProjectToBuild (~393 projects / 120s).
# Arcade restores Tools.proj independently inside
# InitializeCustomToolset on top of the -projects scope.
- script: $(_buildScript)
-restore
-publish $(_PublishArgs)
-configuration $(_BuildConfig)
/bl:$(Build.Arcade.LogsPath)/AssemblePublish.binlog
$(_OfficialBuildIdArgs)
$(_InternalBuildArgs)
/p:SkipNativeBuild=true
/p:SkipManagedBuild=true
/p:SkipTestProjects=true
-projects $(Build.SourcesDirectory)/src/Aspire.Hosting/Aspire.Hosting.csproj
displayName: 🟣Publish unified asset manifest
# Sanity check: with -restore dropped from the publish call above,
# there's a real failure mode where Arcade's Publish target silently
# doesn't fire (or fires but emits an empty AssetManifest), the
# downstream PublishBuildAssets job pushes an empty manifest to BAR,
# and the build goes green having shipped nothing. Catch that here
# with a hard error. If this check ever fires after a working baseline,
# restoring `-restore -projects <small-csproj>` is the safe fallback.
- pwsh: |
$ErrorActionPreference = 'Stop'
$manifestRoot = "$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/AssetManifest"
if (-not (Test-Path $manifestRoot)) {
throw "AssetManifest directory '$manifestRoot' does not exist — Arcade Publish target did not fire."
}
$manifests = @(Get-ChildItem -Path $manifestRoot -Filter '*.xml' -File -Recurse -ErrorAction SilentlyContinue)
if ($manifests.Count -eq 0) {
throw "No AssetManifest .xml files found under '$manifestRoot'."
}
$totalItems = 0
foreach ($m in $manifests) {
[xml]$xml = Get-Content -LiteralPath $m.FullName -Raw
$packages = @($xml.SelectNodes('//Package'))
$blobs = @($xml.SelectNodes('//Blob'))
$count = $packages.Count + $blobs.Count
$totalItems += $count
Write-Host " $($m.Name): packages=$($packages.Count) blobs=$($blobs.Count)"
}
if ($totalItems -lt 50) {
throw "AssetManifest has only $totalItems items across $($manifests.Count) file(s) — expected >= 50 (full Aspire build). Publish target may have run with an empty input set. Restore the '-restore' flag on the publish call as a fallback."
}
Write-Host "AssetManifest sanity check OK: $totalItems items across $($manifests.Count) manifest file(s)."
displayName: 🟣Verify AssetManifest is non-trivial
# ----------------------------------------------------------------
# Generate installer packages (WinGet + Homebrew) and validate npm install.
# Artifacts are consumed by release-publish-nuget.yml for stable
# publishing. No publishing happens from this pipeline.
#
# Depends on build_sign_native — NOT assemble — so this work can run in
# parallel with the assemble stage. The three installer-pipeline vars
# (aspireVersion / aspireArtifactVersion / installerChannel) are produced
# by the ComputeVars step on the linux-x64 native job (see
# build_sign_native.yml's `computeVarsRid` parameter). The per-RID
# native_archives_* artifacts already contain the npm tgz / nupkg /
# archive files this stage's jobs consume; nothing here needs assemble's
# BlobArtifacts.
# ----------------------------------------------------------------
- stage: prepare_installers
displayName: Prepare Installers
dependsOn:
- build_sign_native
variables:
aspireVersion: $[ stageDependencies.build_sign_native.BuildNative_linux_x64.outputs['ComputeVars.aspireVersion'] ]
aspireArtifactVersion: $[ stageDependencies.build_sign_native.BuildNative_linux_x64.outputs['ComputeVars.aspireArtifactVersion'] ]
installerChannel: $[ stageDependencies.build_sign_native.BuildNative_linux_x64.outputs['ComputeVars.installerChannel'] ]
condition: |
and(
succeeded(),
or(
eq(variables['Build.Reason'], 'PullRequest'),
or(
ne(dependencies.build_sign_native.outputs['BuildNative_linux_x64.ComputeVars.installerChannel'], 'stable'),
or(
eq(variables['Build.SourceBranch'], 'refs/heads/main'),
startsWith(variables['Build.SourceBranch'], 'refs/heads/release/')
)
)
)
)
jobs:
- template: /eng/common/templates-official/jobs/jobs.yml@self
parameters:
enableMicrobuild: false
enablePublishUsingPipelines: false
enablePublishBuildAssets: false
# WinGet and Homebrew jobs only consume previously-built CLI archives;
# they don't run the repo build, so artifacts/log/$(_BuildConfig) is
# never created. Leaving this 'true' makes the 1ES Publish Logs output
# fail because its targetPath doesn't exist.
enablePublishBuildArtifacts: false
enableTelemetry: true
workspace:
clean: all
jobs:
- job: WinGet
displayName: WinGet Manifest
timeoutInMinutes: 30
pool:
name: NetCore1ESPool-Internal
image: 1es-windows-2022
os: windows
steps:
- checkout: self
fetchDepth: 1
- task: DownloadPipelineArtifact@2
displayName: 🟣Download CLI archives (win-x64)
inputs:
artifact: native_archives_win_x64
targetPath: '$(Pipeline.Workspace)/native-archives/native_archives_win_x64'
- task: DownloadPipelineArtifact@2
displayName: 🟣Download CLI archives (win-arm64)
inputs:
artifact: native_archives_win_arm64
targetPath: '$(Pipeline.Workspace)/native-archives/native_archives_win_arm64'
- template: /eng/pipelines/templates/prepare-winget-manifest.yml@self
parameters:
version: $(aspireVersion)
artifactVersion: $(aspireArtifactVersion)
channel: $(installerChannel)
archiveRoot: $(Pipeline.Workspace)/native-archives
- job: Homebrew
displayName: Homebrew Cask
timeoutInMinutes: 30
pool:
name: Azure Pipelines
vmImage: macOS-latest-internal
os: macOS
steps:
- checkout: self
fetchDepth: 1
- task: DownloadPipelineArtifact@2
displayName: 🟣Download CLI archives (osx-x64)
inputs:
artifact: native_archives_osx_x64
targetPath: '$(Pipeline.Workspace)/native-archives/native_archives_osx_x64'
- task: DownloadPipelineArtifact@2
displayName: 🟣Download CLI archives (osx-arm64)
inputs:
artifact: native_archives_osx_arm64
targetPath: '$(Pipeline.Workspace)/native-archives/native_archives_osx_arm64'
- template: /eng/pipelines/templates/prepare-homebrew-cask.yml@self
parameters:
version: $(aspireVersion)
channel: $(installerChannel)
archiveRoot: $(Pipeline.Workspace)/native-archives
- job: NpmInstall_Windows_x64
displayName: npm install validation (win-x64)
timeoutInMinutes: 30
pool:
name: NetCore1ESPool-Internal
image: 1es-windows-2022
os: windows
steps:
- template: /eng/pipelines/templates/npm-cli-install-validation-steps.yml@self
parameters:
rid: win-x64
nativeArchiveArtifactName: native_archives_win_x64
validationSummaryArtifactName: $(NPM_VALIDATION_SUMMARY_WIN_X64_ARTIFACT)
- job: NpmInstall_Linux_x64
displayName: npm install validation (linux-x64)
timeoutInMinutes: 30
pool:
name: NetCore1ESPool-Internal
image: 1es-ubuntu-2204
os: linux
steps:
- template: /eng/pipelines/templates/npm-cli-install-validation-steps.yml@self
parameters:
rid: linux-x64
nativeArchiveArtifactName: native_archives_linux_x64
validationSummaryArtifactName: $(NPM_VALIDATION_SUMMARY_LINUX_X64_ARTIFACT)
- job: NpmInstall_macOS
displayName: npm install validation (macOS native RID)
timeoutInMinutes: 30
pool:
name: Azure Pipelines
vmImage: macOS-latest-internal
os: macOS
steps:
- template: /eng/pipelines/templates/npm-cli-install-validation-steps.yml@self
parameters:
rid: $(NpmValidationRid)
validationSummaryArtifactName: $(NPM_VALIDATION_SUMMARY_OSX_ARTIFACT)
# The macOS native_archives_* artifact name is computed at
# runtime in the preStep (the agent's architecture decides
# osx-arm64 vs osx-x64) and threaded in as the underscored
# form. AzDO ${{ }} template substitution runs at compile
# time and cannot see runtime variables, so the default
# `native_archives_${{ replace(rid, '-', '_') }}` would
# resolve to the literal `native_archives_$(NpmValidationRid)`
# and fail the artifact download.
nativeArchiveArtifactName: $(NpmValidationArtifactName)
preSteps:
- pwsh: |
$ErrorActionPreference = 'Stop'
$rid = switch ('$(Agent.OSArchitecture)') {
'ARM64' { 'osx-arm64' }
'X64' { 'osx-x64' }
default { throw "Unsupported macOS agent architecture '$(Agent.OSArchitecture)'." }
}
Write-Host "Resolved macOS npm validation RID: $rid"
Write-Host "##vso[task.setvariable variable=NpmValidationRid]$rid"
# Pre-compute the underscored artifact-name form
# for DownloadPipelineArtifact@2 (it reads the
# value at task runtime, which is fine).
$artifactName = "native_archives_" + $rid.Replace('-', '_')
Write-Host "Resolved macOS npm validation artifact: $artifactName"
Write-Host "##vso[task.setvariable variable=NpmValidationArtifactName]$artifactName"
displayName: 🟣Resolve native macOS RID
# ----------------------------------------------------------------
# On every non-PR build of main / release/*, file or update a GitHub
# issue on microsoft/aspire when the build fails, and close any open
# ci-broken issue for the branch when the build succeeds.
#
# Split into TWO stages so we can gate at the STAGE level — only
# stage conditions can reference dependencies.<stage>.result; from a
# JOB condition in a different stage, the only available form is
# stageDependencies.<stage>.<job>.result (no stage-aggregate form),
# which would force us to enumerate specific job names from upstream
# stages.
#
# See docs/ci/internal-build-failure-notifications.md for the full
# contract (labels, marker, dedupe behavior, dry-run).
# ----------------------------------------------------------------
- stage: notify_failure
displayName: Notify Build Failure
dependsOn:
- build_sign_native
- build_extension
- build
- template_tests
- assemble
- prepare_installers
# Files an issue when at least one watched stage Failed. We depend on
# EVERY non-notify stage so a break anywhere — including the publish
# stage `assemble`, the VSIX build `build_extension`, or `template_tests`
# — files an issue. A stage whose dependency failed is reported as
# 'Skipped' (not 'Failed'), so each stage must be watched directly
# rather than relying on one downstream stage to roll failures up.
# Canceled stage results match neither 'Failed' nor 'Succeeded', so
# operator/timeout cancellations produce no notification.
condition: |
and(
ne(variables['Build.Reason'], 'PullRequest'),
eq(variables['_IsNotificationBranch'], 'true'),
or(
in(dependencies.build_sign_native.result, 'Failed'),
in(dependencies.build_extension.result, 'Failed'),
in(dependencies.build.result, 'Failed'),
in(dependencies.template_tests.result, 'Failed'),
in(dependencies.assemble.result, 'Failed'),
in(dependencies.prepare_installers.result, 'Failed')
)
)
variables:
# Stage results exposed as runtime variables so the shared notify
# template can compose a "failed stages" list for the notification.
- name: BuildSignNativeStageResult
value: $[ dependencies.build_sign_native.result ]
- name: BuildExtensionStageResult
value: $[ dependencies.build_extension.result ]
- name: BuildStageResult
value: $[ dependencies.build.result ]
- name: TemplateTestsStageResult
value: $[ dependencies.template_tests.result ]
- name: AssembleStageResult
value: $[ dependencies.assemble.result ]
- name: PrepareInstallersStageResult
value: $[ dependencies.prepare_installers.result ]
jobs:
- template: /eng/pipelines/templates/notify-build-result.yml@self
parameters:
mode: Failure
jobName: NotifyOnFailure
jobDisplayName: 'File / update ci-broken issue'
dryRun: ${{ parameters.notifyOnFailureDryRun }}
- stage: notify_success
displayName: Notify Build Success
dependsOn:
- build_sign_native
- build_extension
- build
- template_tests
- assemble
- prepare_installers
# Only closes the issue when EVERY watched stage is Succeeded /
# SucceededWithIssues (the same stage set as notify_failure). Requiring
# the full set means a build that fixes one stage but breaks another
# (e.g. green build/build_sign_native but a failed `assemble`) does NOT
# falsely close the open ci-broken issue.
#
# prepare_installers also permits 'Skipped': on a notifiable branch
# (main / release/*) it runs whenever build_sign_native succeeded, so a
# Skip there implies build_sign_native did not succeed — a failure path
# already covered by notify_failure. (The stable-channel skip in its own
# condition only takes effect on non-notifiable branches.) Allowing
# 'Skipped' is therefore defensive and cannot cause a false close.
#
# Canceled stage results don't match either branch and produce no
# notification.
condition: |
and(
ne(variables['Build.Reason'], 'PullRequest'),
eq(variables['_IsNotificationBranch'], 'true'),
in(dependencies.build_sign_native.result, 'Succeeded', 'SucceededWithIssues'),
in(dependencies.build_extension.result, 'Succeeded', 'SucceededWithIssues'),
in(dependencies.build.result, 'Succeeded', 'SucceededWithIssues'),
in(dependencies.template_tests.result, 'Succeeded', 'SucceededWithIssues'),
in(dependencies.assemble.result, 'Succeeded', 'SucceededWithIssues'),
in(dependencies.prepare_installers.result, 'Succeeded', 'SucceededWithIssues', 'Skipped')
)
variables:
# Stage results exposed as runtime variables so the shared notify