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
2 changes: 1 addition & 1 deletion .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"microsoft.dotnet.helix.jobmonitor": {
"version": "11.0.0-beta.26365.101",
"version": "11.0.0-beta.26404.101",
"commands": [
"dotnet-helix-job-monitor"
]
Expand Down
12 changes: 0 additions & 12 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -96,24 +96,12 @@

<!-- https://github.com/dotnet/source-build/issues/4115. -->
<PublishWindowsPdb>false</PublishWindowsPdb>

<!-- Enable NuGet Audit to check for security vulnerabilities -->
<NuGetAudit>true</NuGetAudit>
<NuGetAuditLevel>low</NuGetAuditLevel>
<NuGetAuditMode>all</NuGetAuditMode>
</PropertyGroup>

<PropertyGroup Condition="'$(IsTestProject)' == 'true'">
<GenerateProgramFile>false</GenerateProgramFile>
</PropertyGroup>

<!-- Disable NuGet audit in official builds. MSBuild does not correctly handle
WarningsNotAsErrors for NuGet warnings (https://github.com/dotnet/msbuild/issues/10801),
so audit findings become errors via TreatWarningsAsErrors. -->
<PropertyGroup Condition="'$(OfficialBuild)' == 'true'">
<NuGetAudit>false</NuGetAudit>
</PropertyGroup>

<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
Expand Down
250 changes: 125 additions & 125 deletions eng/Version.Details.props

Large diffs are not rendered by default.

502 changes: 251 additions & 251 deletions eng/Version.Details.xml

Large diffs are not rendered by default.

18 changes: 9 additions & 9 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
<VersionSDKMinorPatch>0</VersionSDKMinorPatch>
<VersionFeature>$([System.String]::Copy('$(VersionSDKMinorPatch)').PadLeft(2, '0'))</VersionFeature>
<VersionPrefix>$(VersionMajor).$(VersionMinor).$(VersionSDKMinor)$(VersionFeature)</VersionPrefix>
<PreReleaseVersionLabel>preview</PreReleaseVersionLabel>
<PreReleaseVersionIteration>7</PreReleaseVersionIteration>
<PreReleaseVersionLabel>rc</PreReleaseVersionLabel>
<PreReleaseVersionIteration>1</PreReleaseVersionIteration>
<!-- Allowed values: '', 'prerelease', 'release'. Set to 'release' when stabilizing. -->
<DotNetFinalVersionKind></DotNetFinalVersionKind>

Expand Down Expand Up @@ -149,19 +149,19 @@
</PropertyGroup>

<PropertyGroup Label="Workload manifest package versions">
<MauiFeatureBand>11.0.100-preview.1</MauiFeatureBand>
<MauiFeatureBand>11.0.100-preview.6</MauiFeatureBand>

<!--
Updates to any of these versions will require the updating of that package version in the
source-build-assets repo as a text-only package.
See https://github.com/dotnet/source-build-assets?tab=readme-ov-file#text-only
-->
<MauiWorkloadManifestVersion>11.0.0-preview.1.26102.3</MauiWorkloadManifestVersion>
<XamarinAndroidWorkloadManifestVersion>36.1.99-preview.1.119</XamarinAndroidWorkloadManifestVersion>
<XamarinIOSWorkloadManifestVersion>26.2.11310-net11-p1</XamarinIOSWorkloadManifestVersion>
<XamarinMacCatalystWorkloadManifestVersion>26.2.11310-net11-p1</XamarinMacCatalystWorkloadManifestVersion>
<XamarinMacOSWorkloadManifestVersion>26.2.11310-net11-p1</XamarinMacOSWorkloadManifestVersion>
<XamarinTvOSWorkloadManifestVersion>26.2.11310-net11-p1</XamarinTvOSWorkloadManifestVersion>
<MauiWorkloadManifestVersion>11.0.0-preview.6.26360.8</MauiWorkloadManifestVersion>
<XamarinAndroidWorkloadManifestVersion>37.0.0-preview.6.59</XamarinAndroidWorkloadManifestVersion>
<XamarinIOSWorkloadManifestVersion>26.5.11720-net11-p6</XamarinIOSWorkloadManifestVersion>
<XamarinMacCatalystWorkloadManifestVersion>26.5.11720-net11-p6</XamarinMacCatalystWorkloadManifestVersion>
<XamarinMacOSWorkloadManifestVersion>26.5.11720-net11-p6</XamarinMacOSWorkloadManifestVersion>
<XamarinTvOSWorkloadManifestVersion>26.5.11720-net11-p6</XamarinTvOSWorkloadManifestVersion>
</PropertyGroup>

<PropertyGroup Label="Pinned dependency">
Expand Down
139 changes: 139 additions & 0 deletions eng/common/Get-GitHubAppToken.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Mints a short-lived GitHub App installation access token by signing a JWT
# with a private key stored in Azure Key Vault (RSA, RS256). The signed JWT is
# exchanged with the GitHub API for a token scoped to a single installation.
#
# Requirements:
# - A GitHub App whose private key has been uploaded into Key Vault as an RSA
# key (the PEM converted to a Key Vault *key*, NOT stored as a secret).
# - The caller (the federated Azure service connection used to run this script)
# must have the `Key Vault Crypto User` role (or at minimum the `Sign`
# action) on that key.
# - The App must be installed on the target organization/account
# (`InstallationOwner`) with the permissions/repositories it needs.
#
# Installation tokens (ghs_*) are exempt from the enterprise classic-PAT
# lifetime policy, which is why this replaces the long-lived PAT.

[CmdletBinding()]
param(
# Name of the Key Vault that holds the GitHub App's RSA signing key.
[Parameter(Mandatory = $true)]
[string] $KeyVaultName,

# Name of the RSA key inside the Key Vault (the App's private key).
[Parameter(Mandatory = $true)]
[string] $KeyName,

# The GitHub App's Client ID (the value to put in the `iss` JWT claim).
[Parameter(Mandatory = $true)]
[string] $AppClientId,

# Login of the organization or user account whose installation we should
# mint the token for (e.g. `dotnet`, `microsoft`).
[Parameter(Mandatory = $true)]
[string] $InstallationOwner,

# Optional Azure DevOps pipeline variable name to set with the installation
# token (marked as a secret). When not specified, the token is written to
# stdout instead.
[Parameter(Mandatory = $false)]
[string] $OutputVariableName
)

$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true

. $PSScriptRoot\pipeline-logging-functions.ps1

function ConvertTo-Base64Url([byte[]] $bytes) {
return [Convert]::ToBase64String($bytes).TrimEnd('=').Replace('+', '-').Replace('/', '_')
}

# Build JWT header and payload. Use [ordered] hashtables so JSON
# serialization is deterministic.
$jwtHeader = [ordered]@{
alg = 'RS256'
typ = 'JWT'
}
$now = [System.DateTimeOffset]::UtcNow
$jwtPayload = [ordered]@{
iat = $now.AddMinutes(-1).ToUnixTimeSeconds()
exp = $now.AddMinutes(5).ToUnixTimeSeconds()
iss = $AppClientId
}

$headerEncoded = ConvertTo-Base64Url ([System.Text.Encoding]::UTF8.GetBytes(($jwtHeader | ConvertTo-Json -Compress)))
$payloadEncoded = ConvertTo-Base64Url ([System.Text.Encoding]::UTF8.GetBytes(($jwtPayload | ConvertTo-Json -Compress)))
$signingInput = "$headerEncoded.$payloadEncoded"

# Key Vault `sign` expects the *digest* (base64), not the raw bytes.
$sha256 = [System.Security.Cryptography.SHA256]::Create()
$digestBytes = $sha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($signingInput))
$digestBase64 = [Convert]::ToBase64String($digestBytes)

Write-Host "Signing JWT with key '$KeyName' in vault '$KeyVaultName'..."
try {
$signResponseJson = az keyvault key sign `
--vault-name $KeyVaultName `
--name $KeyName `
--algorithm RS256 `
--digest $digestBase64
}
catch {
Write-PipelineTelemetryError -Category 'Build' -Message "Failed to sign the JWT via Key Vault (key '$KeyName', vault '$KeyVaultName'): $_. Verify the service connection identity has the 'Key Vault Crypto User' role (Sign action) on the key."
exit 1
}
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($signResponseJson)) {
Write-PipelineTelemetryError -Category 'Build' -Message "'az keyvault key sign' exited with code $LASTEXITCODE for key '$KeyName' in vault '$KeyVaultName'. Verify the service connection identity has the 'Key Vault Crypto User' role (Sign action) on the key."
exit 1
}
$signResponse = $signResponseJson | ConvertFrom-Json
if ([string]::IsNullOrEmpty($signResponse.signature)) {
Write-PipelineTelemetryError -Category 'Build' -Message "Key Vault returned an empty signature for key '$KeyName' in vault '$KeyVaultName'."
exit 1
}
$signatureUrl = $signResponse.signature.TrimEnd('=').Replace('+', '-').Replace('/', '_')
$jwt = "$signingInput.$signatureUrl"

$headers = @{
Authorization = "Bearer $jwt"
'X-GitHub-Api-Version' = '2022-11-28'
Accept = 'application/vnd.github+json'
'User-Agent' = 'dotnet-arcade-onelocbuild'
}

Write-Host "Looking up installation for '$InstallationOwner'..."
try {
$installations = Invoke-RestMethod -Uri 'https://api.github.com/app/installations' -Headers $headers -Method Get
}
catch {
Write-PipelineTelemetryError -Category 'Build' -Message "Failed to list GitHub App installations: $_. The signed JWT may be invalid or the App's Client ID ('$AppClientId') may be incorrect."
exit 1
}
$installation = $installations | Where-Object { $_.account.login -eq $InstallationOwner } | Select-Object -First 1
if (-not $installation) {
$found = ($installations | ForEach-Object { $_.account.login }) -join ', '
Write-PipelineTelemetryError -Category 'Build' -Message "No installation found for '$InstallationOwner'. App is installed on: $found"
exit 1
}

try {
$tokenResponse = Invoke-RestMethod `
-Uri "https://api.github.com/app/installations/$($installation.id)/access_tokens" `
-Headers $headers `
-Method Post `
-ContentType 'application/json'
}
catch {
Write-PipelineTelemetryError -Category 'Build' -Message "Failed to mint an installation access token for '$InstallationOwner' (installation $($installation.id)): $_"
exit 1
}

Write-Host "Got installation token for '$InstallationOwner' (expires $($tokenResponse.expires_at))."
if ($OutputVariableName) {
Write-Host "Setting pipeline variable '$OutputVariableName'."
Write-Host "##vso[task.setvariable variable=$OutputVariableName;issecret=true]$($tokenResponse.token)"
}
else {
Write-Host $tokenResponse.token -ForegroundColor Green
}
2 changes: 2 additions & 0 deletions eng/common/build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Param(
[switch] $clean,
[switch][Alias('pb')]$productBuild,
[switch]$fromVMR,
[switch]$disablePipelineSetResult,
[switch][Alias('bl')]$binaryLog,
[string][Alias('bln')]$binaryLogName = '',
[switch][Alias('nobl')]$excludeCIBinarylog,
Expand Down Expand Up @@ -80,6 +81,7 @@ function Print-Usage() {
Write-Host " -nodeReuse <value> Sets nodereuse msbuild parameter ('true' or 'false')"
Write-Host " -buildCheck Sets /check msbuild parameter"
Write-Host " -fromVMR Set when building from within the VMR"
Write-Host " -disablePipelineSetResult Set to disable masking the actual exit code in the pipeline when the build fails"
Write-Host ""

Write-Host "Command line arguments not listed above are passed thru to msbuild."
Expand Down
11 changes: 11 additions & 0 deletions eng/common/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,14 @@ usage()
echo " --projects <value> Project or solution file(s) to build"
echo " --ci Set when running on CI server"
echo " --excludeCIBinarylog Don't output binary log (short: -nobl)"
echo " --pipelinesLog Promote msbuild errors/warnings to Azure Pipelines timeline issues; defaults to on in CI (short: -pl)"
echo " --prepareMachine Prepare machine for CI run, clean up processes after build"
echo " --nodeReuse <value> Sets nodereuse msbuild parameter ('true' or 'false')"
echo " --warnAsError <value> Sets warnaserror msbuild parameter ('true' or 'false')"
echo " --warnNotAsError <value> Sets a semi-colon delimited list of warning codes that should not be treated as errors"
echo " --buildCheck <value> Sets /check msbuild parameter"
echo " --fromVMR Set when building from within the VMR"
echo " --disablePipelineSetResult Set to disable masking the actual exit code in the pipeline when the build fails"
echo ""
echo "Command line arguments not listed above are passed thru to msbuild."
echo "Arguments can also be passed in with a single hyphen."
Expand All @@ -68,6 +70,7 @@ build=false
source_build=false
product_build=false
from_vmr=false
disable_pipeline_set_result=false
rebuild=false
test=false
integration_test=false
Expand All @@ -86,6 +89,7 @@ build_check=false
binary_log=false
binary_log_name=''
exclude_ci_binary_log=false
pipelines_log=false

projects=''
configuration=''
Expand Down Expand Up @@ -124,6 +128,9 @@ while [[ $# -gt 0 ]]; do
-excludecibinarylog|-nobl)
exclude_ci_binary_log=true
;;
-pipelineslog|-pl)
pipelines_log=true
;;
-restore|-r)
restore=true
;;
Expand Down Expand Up @@ -152,6 +159,9 @@ while [[ $# -gt 0 ]]; do
-fromvmr|-from-vmr)
from_vmr=true
;;
-disablepipelinesetresult|-disable-pipeline-set-result)
disable_pipeline_set_result=true
;;
-test|-t)
test=true
;;
Expand Down Expand Up @@ -213,6 +223,7 @@ if [[ -z "$configuration" ]]; then
fi

if [[ "$ci" == true ]]; then
pipelines_log=true
# Disable node reuse on CI unless explicitly opted in via MSBUILD_NODEREUSE_ENABLED.
# Internal testing only; this env var will be replaced with a switch (https://github.com/dotnet/arcade/issues/17013) and must not be depended on.
if [[ "${MSBUILD_NODEREUSE_ENABLED:-}" != "1" ]]; then
Expand Down
35 changes: 29 additions & 6 deletions eng/common/core-templates/job/helix-job-monitor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,20 @@ parameters:
type: boolean
default: true

# When true, allow the monitor to succeed when this stage produces no Helix jobs in any attempt.
# Forwarded as --allow-no-helix-jobs.
- name: allowNoHelixJobs
type: boolean
default: false

# When true, test results are reported to Azure DevOps using the fully qualified test name
# (Namespace.Type.Method) as the stable automatedTestName and the visible title is qualified as
# well (--use-fully-qualified-test-name). Opt-in because it changes AzDO test identity and display;
# primarily useful for frameworks like MSTest whose display name is only the method name.
- name: useFullyQualifiedTestName
type: boolean
default: false

# Advanced: optional pipeline artifact (produced earlier in this run) that contains the tool
# nupkg. When set, the artifact is downloaded and the tool is installed from the nupkg into
# a local tool-path; this bypasses the repo's .config/dotnet-tools.json manifest and is
Expand Down Expand Up @@ -178,24 +192,33 @@ jobs:
set -euo pipefail

toolArgs=(
--helix-base-uri '${{ parameters.helixBaseUri }}'
--polling-interval-seconds '${{ parameters.pollingIntervalSeconds }}'
--fail-on-failed-tests '${{ parameters.failWorkItemsWithFailedTests }}'
--max-wait-minutes "$((${{ parameters.timeoutInMinutes }} - 5))" # Set the tool's timeout slightly lower than the Azure DevOps job timeout to allow it to exit gracefully.
--stage-name '$(System.StageName)'
--helix-base-uri '${{ parameters.helixBaseUri }}'
--polling-interval-seconds '${{ parameters.pollingIntervalSeconds }}'
--fail-on-failed-tests '${{ parameters.failWorkItemsWithFailedTests }}'
--allow-no-helix-jobs '${{ parameters.allowNoHelixJobs }}'
--use-fully-qualified-test-name '${{ parameters.useFullyQualifiedTestName }}'
--max-wait-minutes "$((${{ parameters.timeoutInMinutes }} - 5))" # Set the tool's timeout slightly lower than the Azure DevOps job timeout to allow it to exit gracefully.
--stage-name '$(System.StageName)'
--stage-attempt '$(System.StageAttempt)'
)

organization='${{ parameters.organization }}'
repository='${{ parameters.repository }}'

# Fall back to Azure DevOps-provided environment variables when the caller did not
# supply organization / repository explicitly. BUILD_REPOSITORY_NAME is typically
# 'owner/repo' for GitHub-backed builds.
# 'owner/repo' for GitHub-backed builds and 'owner-repo' for internal builds.
if [ -z "$organization" ] || [ -z "$repository" ]; then
buildRepoName="${BUILD_REPOSITORY_NAME:-}"
if [ -n "$buildRepoName" ] && [[ "$buildRepoName" == */* ]]; then
repoOwner="${buildRepoName%%/*}"
repoName="${buildRepoName#*/}"
elif [ -n "$buildRepoName" ] && [[ "$buildRepoName" == *-* ]]; then
repoOwner="${buildRepoName%%-*}"
repoName="${buildRepoName#*-}"
fi

if [ -n "${repoOwner:-}" ] && [ -n "${repoName:-}" ]; then
if [ -z "$organization" ]; then organization="$repoOwner"; fi
if [ -z "$repository" ]; then repository="$repoName"; fi
fi
Expand Down
Loading
Loading