Skip to content
Draft
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
30 changes: 30 additions & 0 deletions .vsts-dnup-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -345,3 +345,33 @@ extends:
oneESCompat:
templateFolderName: templates-official
publishTaskPrefix: 1ES.

############### BAR registration ###############
# Stages the per-RID standalone binaries into a single manifest + BlobArtifacts
# layout and registers the build with the Build Asset Registry. The dotnetup Daily
# default-channel mapping (in maestro-configuration) auto-promotes the build, and
# the channel's TargetChannelConfig (in arcade) routes the binaries to
# ci.dot.net/public/dotnetup/<SemVer>/ with stable aka.ms/dotnet/dotnetup/daily/...
# links. See dotnetup-publish-to-bar.yml.
- stage: publish_to_bar
displayName: 📦 Register dotnetup with BAR
dependsOn:
- executable_win_x64
- executable_win_arm64
- executable_linux_x64
- executable_linux_arm64
- executable_linux_musl_x64
- executable_linux_musl_arm64
- executable_osx_x64
- executable_osx_arm64
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
jobs:
- template: /eng/pipelines/templates/jobs/dotnetup/dotnetup-publish-to-bar.yml@self
parameters:
pool:
name: $(DncEngInternalBuildPool)
image: 1es-windows-2022
os: windows
oneESCompat:
templateFolderName: templates-official
publishTaskPrefix: 1ES.
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,10 @@ jobs:
# Copy the published (and signed) output to the artifact staging directory
$publishDir = Get-ChildItem -Path "$(Build.SourcesDirectory)\artifacts\bin\dotnetup\Release" -Directory | Select-Object -First 1
Copy-Item -Path "$($publishDir.FullName)\${{ parameters.rid }}\publish\*" -Destination "$(Build.SourcesDirectory)\artifacts\publish\dotnetup\${{ parameters.rid }}\" -Recurse -Force
# Copy just the binary to the standalone artifact staging directory
# Copy just the binary and version file to the standalone artifact staging directory
New-Item -ItemType Directory -Path "$(Build.SourcesDirectory)\artifacts\publish\dotnetup-standalone\${{ parameters.rid }}" -Force | Out-Null
Copy-Item -Path "$($publishDir.FullName)\${{ parameters.rid }}\publish\dotnetup.exe" -Destination "$(Build.SourcesDirectory)\artifacts\publish\dotnetup-standalone\${{ parameters.rid }}\"
Copy-Item -Path "$($publishDir.FullName)\${{ parameters.rid }}\publish\dotnetup.version.txt" -Destination "$(Build.SourcesDirectory)\artifacts\publish\dotnetup-standalone\${{ parameters.rid }}\"
displayName: 📦 Stage dotnetup artifacts (${{ parameters.rid }})
- powershell: |
$standaloneDir = "$(Build.SourcesDirectory)\artifacts\publish\dotnetup-standalone\${{ parameters.rid }}"
Expand Down Expand Up @@ -198,6 +199,7 @@ jobs:
cp -r "$PUBLISH_DIR/${{ parameters.rid }}/publish/"* "$(Build.SourcesDirectory)/artifacts/publish/dotnetup/${{ parameters.rid }}/"
mkdir -p "$(Build.SourcesDirectory)/artifacts/publish/dotnetup-standalone/${{ parameters.rid }}"
cp "$PUBLISH_DIR/${{ parameters.rid }}/publish/dotnetup" "$(Build.SourcesDirectory)/artifacts/publish/dotnetup-standalone/${{ parameters.rid }}/"
cp "$PUBLISH_DIR/${{ parameters.rid }}/publish/dotnetup.version.txt" "$(Build.SourcesDirectory)/artifacts/publish/dotnetup-standalone/${{ parameters.rid }}/"
displayName: 📦 Stage dotnetup artifacts (${{ parameters.rid }})
- script: |
STANDALONE_DIR="$(Build.SourcesDirectory)/artifacts/publish/dotnetup-standalone/${{ parameters.rid }}"
Expand Down
205 changes: 205 additions & 0 deletions eng/pipelines/templates/jobs/dotnetup/dotnetup-publish-to-bar.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
parameters:
# Pool to run the staging job on. The actual blob upload is performed by the shared
# Maestro Promotion Pipeline (dnceng/internal #750) which uses its own pool; we just
# need a normal Windows agent here to download artifacts and emit the manifest.
pool: {}
oneESCompat:
templateFolderName: templates
publishTaskPrefix: ''
# Maps to <Blob Id="..." /> entries in the manifest. The id is the path inside the
# target storage container; we pick a layout under dotnetup/<SemVer>/ where SemVer
# comes from the published dotnetup.version.txt file.
rids:
- rid: win-x64
isWindows: true
- rid: win-arm64
isWindows: true
- rid: linux-x64
isWindows: false
- rid: linux-arm64
isWindows: false
- rid: linux-musl-x64
isWindows: false
- rid: linux-musl-arm64
isWindows: false
- rid: osx-x64
isWindows: false
- rid: osx-arm64
isWindows: false
Comment on lines +14 to +28

jobs:
# ---------------------------------------------------------------------------
# stage_for_bar
#
# Collect all per-RID dotnetup standalone binaries (and their .sha512 siblings)
# from the executable stages, lay them out under "dotnetup/<SemVer>/<asset>",
# hand-craft an asset manifest XML in the format the Build Asset Registry expects,
# and publish two pipeline artifacts:
#
# AssetManifests/ -> read by publish-build-assets.yml to register with BAR
# BlobArtifacts/ -> read by the Maestro Promotion Pipeline (#750) to upload
#
# Both contain a copy of MergedManifest.xml; BlobArtifacts also contains the
# binaries laid out at the paths declared in the manifest.
# ---------------------------------------------------------------------------
- job: stage_for_bar
displayName: 📦 Stage dotnetup artifacts for BAR
pool: ${{ parameters.pool }}
timeoutInMinutes: 30
templateContext:
outputs:
# AssetManifests can be a pipeline artifact — only this job writes to it; the
# publish-build-assets job downloads it via DownloadPipelineArtifact and never
# writes back to the same name.
- output: pipelineArtifact
displayName: 'Publish AssetManifests'
targetPath: $(Build.StagingDirectory)/AssetManifests
artifactName: AssetManifests
publishLocation: Container

steps:
- checkout: none

- ${{ each entry in parameters.rids }}:
- task: DownloadPipelineArtifact@2
displayName: Download dotnetup-standalone-${{ entry.rid }}
inputs:
artifact: dotnetup-standalone-${{ entry.rid }}
path: $(Pipeline.Workspace)/standalone/${{ entry.rid }}
Comment on lines +67 to +68

- powershell: |
$ErrorActionPreference = 'Stop'
# Read the SemVer from dotnetup.version.txt, which the publish step emitted to each
# standalone artifact. WriteDotnetupVersionFile (in dotnetup.csproj) writes $(Version),
# which Arcade SDK has already composed as <prefix>-<label>.<BuildId> with the right
# patch/iteration numbers. Probe each downloaded RID artifact in turn so a single
# missing RID doesn't break the whole BAR registration.
$semver = $null
foreach ($rid in @('win-x64','win-arm64','linux-x64','linux-arm64','linux-musl-x64','linux-musl-arm64','osx-x64','osx-arm64')) {
$candidate = Join-Path '$(Pipeline.Workspace)' "standalone/$rid/dotnetup.version.txt"
if (Test-Path $candidate) {
$semver = (Get-Content -LiteralPath $candidate -Raw).Trim()
if (-not [string]::IsNullOrWhiteSpace($semver)) {
Write-Host "Using SemVer '$semver' (read from $candidate)"
break
}
}
}
if ([string]::IsNullOrWhiteSpace($semver)) {
throw "No usable dotnetup.version.txt found in any of the downloaded standalone RID artifacts."
}

$buildNumber = '$(Build.BuildNumber)'
$blobBase = Join-Path '$(Build.StagingDirectory)' 'BlobArtifacts'
$assetBase = Join-Path '$(Build.StagingDirectory)' 'AssetManifests'

New-Item -ItemType Directory -Path $blobBase -Force | Out-Null
New-Item -ItemType Directory -Path $assetBase -Force | Out-Null

# Map RIDs to source filenames and target filenames (RID-suffixed).
$rids = @(
@{ rid='win-x64'; sourceName='dotnetup.exe'; targetName='dotnetup-win-x64.exe' },
@{ rid='win-arm64'; sourceName='dotnetup.exe'; targetName='dotnetup-win-arm64.exe' },
@{ rid='linux-x64'; sourceName='dotnetup'; targetName='dotnetup-linux-x64' },
@{ rid='linux-arm64'; sourceName='dotnetup'; targetName='dotnetup-linux-arm64' },
@{ rid='linux-musl-x64'; sourceName='dotnetup'; targetName='dotnetup-linux-musl-x64' },
@{ rid='linux-musl-arm64';sourceName='dotnetup'; targetName='dotnetup-linux-musl-arm64' },
@{ rid='osx-x64'; sourceName='dotnetup'; targetName='dotnetup-osx-x64' },
@{ rid='osx-arm64'; sourceName='dotnetup'; targetName='dotnetup-osx-arm64' }
)

$blobIds = New-Object System.Collections.Generic.List[string]
foreach ($r in $rids) {
$srcDir = Join-Path '$(Pipeline.Workspace)' ("standalone/" + $r.rid)
$src = Join-Path $srcDir $r.sourceName
$hash = Join-Path $srcDir ($r.sourceName + '.sha512')
if (-not (Test-Path $src)) { throw "Missing binary: $src" }
if (-not (Test-Path $hash)) { throw "Missing checksum: $hash" }

# Stage files FLAT under BlobArtifacts/. Arcade's PublishArtifactsInManifest
# downloads source blobs by basename only (BlobArtifacts/<filename>), but uses
# the full Blob Id path as the upload destination (ci.dot.net/dev/<Id>).
Copy-Item -LiteralPath $src -Destination (Join-Path $blobBase $r.targetName)
Copy-Item -LiteralPath $hash -Destination (Join-Path $blobBase ($r.targetName + '.sha512'))

$blobIds.Add("dotnetup/$semver/" + $r.targetName)
$blobIds.Add("dotnetup/$semver/" + $r.targetName + '.sha512')
}

# Compose the asset manifest XML. The attribute set mirrors what Arcade's
# PushToBuildStorage task emits when authored from source; PublishBuildAssets
# only requires a subset (Name, PublishingVersion, AzureDevOps* identity).
$manifestPath = Join-Path $blobBase 'MergedManifest.xml'
$xml = [System.Xml.XmlWriterSettings]@{ Indent = $true; OmitXmlDeclaration = $false }
$writer = [System.Xml.XmlWriter]::Create($manifestPath, $xml)
try {
$writer.WriteStartDocument()
$writer.WriteStartElement('Build')
$writer.WriteAttributeString('PublishingVersion', '3')
# Name is the GitHub repository identifier — BAR keys subscriptions, default-channel
# mappings, and queries on this value as https://github.com/<Name>. dotnetup builds
# out of dotnet/sdk so we use that identifier. (BAR will associate this build with
# the dotnet/sdk repo record; eng-services queries on the SDK repo will see this
# build alongside main SDK builds, distinguished by AzureDevOpsBuildNumber.)
$writer.WriteAttributeString('Name', 'dotnet/sdk')
$writer.WriteAttributeString('BuildId', $buildNumber)
$writer.WriteAttributeString('ProductVersion', $semver)
$writer.WriteAttributeString('Branch', '$(Build.SourceBranch)')
$writer.WriteAttributeString('Commit', '$(Build.SourceVersion)')
$writer.WriteAttributeString('AzureDevOpsRepository', '$(Build.Repository.Uri)')
$writer.WriteAttributeString('AzureDevOpsBranch', '$(Build.SourceBranch)')
$writer.WriteAttributeString('AzureDevOpsBuildNumber', '$(Build.BuildNumber)')
$writer.WriteAttributeString('AzureDevOpsAccount', 'dnceng')
$writer.WriteAttributeString('AzureDevOpsProject', 'internal')
$writer.WriteAttributeString('AzureDevOpsBuildDefinitionId', '$(System.DefinitionId)')
$writer.WriteAttributeString('AzureDevOpsBuildId', '$(Build.BuildId)')
$writer.WriteAttributeString('InitialAssetsLocation', 'https://dotnetbuilds.blob.core.windows.net/public')
$writer.WriteAttributeString('IsStable', 'false')
$writer.WriteAttributeString('IsReleaseOnlyPackageVersion', 'false')
foreach ($id in $blobIds) {
$writer.WriteStartElement('Blob')
$writer.WriteAttributeString('Id', $id)
$writer.WriteEndElement()
}
$writer.WriteEndElement()
$writer.WriteEndDocument()
} finally {
$writer.Dispose()
}

# The promotion pipeline expects the manifest inside BlobArtifacts/.
# publish-build-assets.yml expects it inside AssetManifests/. Copy to both.
Copy-Item -LiteralPath $manifestPath -Destination (Join-Path $assetBase 'MergedManifest.xml') -Force

Write-Host "===== Manifest ====="
Get-Content $manifestPath
Write-Host "===== BlobArtifacts layout ====="
Get-ChildItem -Recurse $blobBase | Select-Object FullName, Length
displayName: 🛠 Generate asset manifest and lay out BlobArtifacts

# PublishBuildToMaestro (called by publish-build-assets.yml below) writes its
# MergedManifest.xml and uploads it to the "BlobArtifacts" name via the legacy
# `##vso[artifact.upload]` logging command. AzDO does not allow merging a legacy
# build artifact with a pipeline artifact under the same name, so we have to use
# the legacy 1ES.PublishBuildArtifacts@1 task here (rather than declaring this
# as a `pipelineArtifact` in `templateContext.outputs`) so Arcade's append works.
- task: 1ES.PublishBuildArtifacts@1
displayName: 'Publish BlobArtifacts (build artifact)'
inputs:
PathtoPublish: $(Build.StagingDirectory)/BlobArtifacts
ArtifactName: BlobArtifacts
PublishLocation: Container

# ---------------------------------------------------------------------------
# publish_to_bar (the standard Arcade publish-build-assets job)
# Reads AssetManifests and registers the build in BAR via Maestro. The dotnetup Daily
# default-channel mapping then triggers the Maestro Promotion Pipeline (#750) to upload
# the BlobArtifacts to ci.dot.net/public/dotnetup/<SemVer>/ and create stable
# aka.ms/dotnet/dotnetup/daily/<file> shortlinks.
# ---------------------------------------------------------------------------
- template: /eng/common/${{ parameters.oneESCompat.templateFolderName }}/job/publish-build-assets.yml@self
parameters:
publishingVersion: 3
publishAssetsImmediately: false
dependsOn: stage_for_bar
pool: ${{ parameters.pool }}
12 changes: 12 additions & 0 deletions src/Installer/dotnetup/dotnetup.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,16 @@
<EmbeddedResource Update="Strings.resx" GenerateSource="true" Namespace="Microsoft.DotNet.Tools.Bootstrapper" />
</ItemGroup>

<!--
Emit the computed SemVer to dotnetup.version.txt in the publish output so downstream
pipeline stages (e.g. the BAR-registration job that builds asset manifest paths) can
read the version without parsing csproj/props or running the AOT-published binary.
-->
<Target Name="WriteDotnetupVersionFile" AfterTargets="Publish">
<WriteLinesToFile File="$(PublishDir)dotnetup.version.txt"
Lines="$(Version)"
Overwrite="true"
WriteOnlyWhenDifferent="true" />
</Target>

</Project>
Loading