diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index 36509bcb33..2b6c7978f9 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -7,38 +7,38 @@
-
+
https://github.com/dotnet/arcade
- a08169b890573cfd7f949ea9062c86a4db1aab1b
+ 09bc8c946f4c4ae5d031c8875b85a6b8f1876b93
-
+
https://github.com/dotnet/arcade
- a08169b890573cfd7f949ea9062c86a4db1aab1b
+ 09bc8c946f4c4ae5d031c8875b85a6b8f1876b93
-
+
https://github.com/dotnet/arcade
- a08169b890573cfd7f949ea9062c86a4db1aab1b
+ 09bc8c946f4c4ae5d031c8875b85a6b8f1876b93
-
+
https://github.com/dotnet/arcade
- a08169b890573cfd7f949ea9062c86a4db1aab1b
+ 09bc8c946f4c4ae5d031c8875b85a6b8f1876b93
-
+
https://github.com/dotnet/arcade
- a08169b890573cfd7f949ea9062c86a4db1aab1b
+ 09bc8c946f4c4ae5d031c8875b85a6b8f1876b93
-
+
https://github.com/dotnet/arcade
- a08169b890573cfd7f949ea9062c86a4db1aab1b
+ 09bc8c946f4c4ae5d031c8875b85a6b8f1876b93
-
+
https://github.com/dotnet/arcade
- a08169b890573cfd7f949ea9062c86a4db1aab1b
+ 09bc8c946f4c4ae5d031c8875b85a6b8f1876b93
diff --git a/eng/Versions.props b/eng/Versions.props
index 85feef414f..8ab869b691 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -98,7 +98,7 @@
9.0.0-beta.24212.4
5.0.0-preview.5.20278.1
8.0.0-beta.24525.2
- 11.0.0-beta.26211.1
+ 11.0.0-beta.26369.1
9.0.4
0.0.6-test
0.0.13-test
diff --git a/eng/common/AGENTS.md b/eng/common/AGENTS.md
new file mode 100644
index 0000000000..a5ed8f7292
--- /dev/null
+++ b/eng/common/AGENTS.md
@@ -0,0 +1,5 @@
+# `eng/common`
+
+Files under `eng/common` come from [Arcade](https://github.com/dotnet/arcade).
+Edits in `eng/common` will be overwritten by automation unless the changes are made directly in the Arcade repository.
+For more information, see the [Arcade documentation](https://github.com/dotnet/arcade/tree/main/Documentation).
diff --git a/eng/common/SetupNugetSources.ps1 b/eng/common/SetupNugetSources.ps1
index fc8d618014..b3bddff355 100644
--- a/eng/common/SetupNugetSources.ps1
+++ b/eng/common/SetupNugetSources.ps1
@@ -13,7 +13,11 @@
# filePath: $(System.DefaultWorkingDirectory)/eng/common/SetupNugetSources.ps1
# arguments: -ConfigFile $(System.DefaultWorkingDirectory)/NuGet.config -Password $Env:Token
# env:
-# Token: $(dn-bot-dnceng-artifact-feeds-rw)
+# Token: $(InternalFeedToken)
+#
+# Note: This logic is abstracted into enable-internal-sources.yml, which uses
+# NuGetAuthenticate or a WIF-backed service connection. Prefer that template
+# over calling this script directly.
#
# Note that the NuGetAuthenticate task should be called after SetupNugetSources.
# This ensures that:
@@ -32,6 +36,11 @@ $ErrorActionPreference = "Stop"
Set-StrictMode -Version 2.0
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+# This script only consumes helper functions from tools.ps1 to configure NuGet feeds.
+# Skip importing configure-toolset.ps1 so that repo-specific toolset setup (e.g. acquiring
+# a bootstrap SDK) is not triggered as a side effect of feed configuration.
+$disableConfigureToolsetImport = $true
+
. $PSScriptRoot\tools.ps1
# Adds or enables the package source with the given name
diff --git a/eng/common/SetupNugetSources.sh b/eng/common/SetupNugetSources.sh
index b97cc53637..67e7e0942c 100755
--- a/eng/common/SetupNugetSources.sh
+++ b/eng/common/SetupNugetSources.sh
@@ -40,6 +40,11 @@ while [[ -h "$source" ]]; do
done
scriptroot="$( cd -P "$( dirname "$source" )" && pwd )"
+# This script only consumes helper functions from tools.sh to configure NuGet feeds.
+# Skip importing configure-toolset.sh so that repo-specific toolset setup (e.g. acquiring
+# a bootstrap SDK) is not triggered as a side effect of feed configuration.
+disable_configure_toolset_import=1
+
. "$scriptroot/tools.sh"
if [ ! -f "$ConfigFile" ]; then
diff --git a/eng/common/build.ps1 b/eng/common/build.ps1
index 18397a60eb..dd84699f50 100644
--- a/eng/common/build.ps1
+++ b/eng/common/build.ps1
@@ -23,7 +23,9 @@ Param(
[switch] $clean,
[switch][Alias('pb')]$productBuild,
[switch]$fromVMR,
+ [switch]$disablePipelineSetResult,
[switch][Alias('bl')]$binaryLog,
+ [string][Alias('bln')]$binaryLogName = '',
[switch][Alias('nobl')]$excludeCIBinarylog,
[switch] $ci,
[switch] $prepareMachine,
@@ -46,6 +48,7 @@ function Print-Usage() {
Write-Host " -platform Platform configuration: 'x86', 'x64' or any valid Platform value to pass to msbuild"
Write-Host " -verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] (short: -v)"
Write-Host " -binaryLog Output binary log (short: -bl)"
+ Write-Host " -binaryLogName Binary log file name or path; implies -binaryLog (short: -bln)"
Write-Host " -help Print help and exit"
Write-Host ""
@@ -78,6 +81,7 @@ function Print-Usage() {
Write-Host " -nodeReuse 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."
@@ -102,7 +106,19 @@ function Build {
$toolsetBuildProj = InitializeToolset
InitializeCustomToolset
- $bl = if ($binaryLog) { '/bl:' + (Join-Path $LogDir 'Build.binlog') } else { '' }
+ $bl = ''
+ if ($binaryLog) {
+ $binaryLogPath = if ([string]::IsNullOrEmpty($binaryLogName)) {
+ Join-Path $LogDir 'Build.binlog'
+ } elseif ([System.IO.Path]::IsPathRooted($binaryLogName)) {
+ $binaryLogName
+ } else {
+ Join-Path $LogDir $binaryLogName
+ }
+
+ Create-Directory (Split-Path -Parent $binaryLogPath)
+ $bl = '/bl:' + $binaryLogPath
+ }
$platformArg = if ($platform) { "/p:Platform=$platform" } else { '' }
$check = if ($buildCheck) { '/check' } else { '' }
@@ -159,7 +175,15 @@ try {
if (-not $excludeCIBinarylog) {
$binaryLog = $true
}
- $nodeReuse = $false
+ # 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 ($env:MSBUILD_NODEREUSE_ENABLED -ne "1") {
+ $nodeReuse = $false
+ }
+ }
+
+ if (-not [string]::IsNullOrEmpty($binaryLogName)) {
+ $binaryLog = $true
}
if ($nativeToolsOnMachine) {
diff --git a/eng/common/build.sh b/eng/common/build.sh
index 5883e53bcf..e37edd6cff 100755
--- a/eng/common/build.sh
+++ b/eng/common/build.sh
@@ -13,6 +13,7 @@ usage()
echo " --configuration Build configuration: 'Debug' or 'Release' (short: -c)"
echo " --verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] (short: -v)"
echo " --binaryLog Create MSBuild binary log (short: -bl)"
+ echo " --binaryLogName Binary log file name or path; implies --binaryLog (short: -bln)"
echo " --help Print help and exit (short: -h)"
echo ""
@@ -39,12 +40,14 @@ usage()
echo " --projects 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 Sets nodereuse msbuild parameter ('true' or 'false')"
echo " --warnAsError Sets warnaserror msbuild parameter ('true' or 'false')"
echo " --warnNotAsError Sets a semi-colon delimited list of warning codes that should not be treated as errors"
echo " --buildCheck 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."
@@ -67,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
@@ -83,6 +87,7 @@ warn_not_as_error=''
node_reuse=true
build_check=false
binary_log=false
+binary_log_name=''
exclude_ci_binary_log=false
pipelines_log=false
@@ -115,6 +120,11 @@ while [[ $# -gt 0 ]]; do
-binarylog|-bl)
binary_log=true
;;
+ -binarylogname|-bln)
+ binary_log=true
+ binary_log_name=$2
+ shift
+ ;;
-excludecibinarylog|-nobl)
exclude_ci_binary_log=true
;;
@@ -149,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
;;
@@ -211,7 +224,11 @@ fi
if [[ "$ci" == true ]]; then
pipelines_log=true
- node_reuse=false
+ # 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
+ node_reuse=false
+ fi
if [[ "$exclude_ci_binary_log" == false ]]; then
binary_log=true
fi
@@ -237,7 +254,17 @@ function Build {
local bl=""
if [[ "$binary_log" == true ]]; then
- bl="/bl:\"$log_dir/Build.binlog\""
+ local binary_log_path=""
+ if [[ -z "$binary_log_name" ]]; then
+ binary_log_path="$log_dir/Build.binlog"
+ elif [[ "$binary_log_name" = /* ]]; then
+ binary_log_path="$binary_log_name"
+ else
+ binary_log_path="$log_dir/$binary_log_name"
+ fi
+
+ mkdir -p "$(dirname "$binary_log_path")"
+ bl="/bl:\"$binary_log_path\""
fi
local check=""
diff --git a/eng/common/core-templates/job/helix-job-monitor.yml b/eng/common/core-templates/job/helix-job-monitor.yml
new file mode 100644
index 0000000000..0da13cf69d
--- /dev/null
+++ b/eng/common/core-templates/job/helix-job-monitor.yml
@@ -0,0 +1,235 @@
+parameters:
+# Maximum run time of the monitor job in minutes. Also used for --max-wait-minutes.
+- name: timeoutInMinutes
+ type: number
+ default: 360
+
+# Owner segment of the source repository (e.g. 'dotnet' for 'dotnet/runtime') passed via --organization.
+# Defaults to the owner segment of BUILD_REPOSITORY_NAME when empty.
+- name: organization
+ type: string
+ default: ''
+
+# Name of the source repository (e.g. 'runtime' for 'dotnet/runtime') passed via --repository.
+# Defaults to the repo segment of BUILD_REPOSITORY_NAME when empty.
+- name: repository
+ type: string
+ default: ''
+
+# Optional dependency list for the generated job.
+- name: dependsOn
+ type: object
+ default: []
+
+# Optional condition for the generated job.
+- name: condition
+ type: string
+ default: ''
+
+# NuGet package id of the Helix job monitor tool.
+- name: toolPackageId
+ type: string
+ default: Microsoft.DotNet.Helix.JobMonitor
+
+# Console command exposed by the installed tool package.
+- name: toolCommand
+ type: string
+ default: dotnet-helix-job-monitor
+
+# Optional explicit tool version. Only honored when 'toolNupkgArtifactName' is set; in the
+# default code path the version is taken from the consuming repo's .config/dotnet-tools.json.
+- name: toolVersion
+ type: string
+ default: ''
+
+# Base URI for the Helix service (--helix-base-uri).
+- name: helixBaseUri
+ type: string
+ default: https://helix.dot.net/
+
+# Helix API access token forwarded to the tool via the HELIX_ACCESSTOKEN environment variable.
+- name: helixAccessToken
+ type: string
+ default: ''
+
+# Polling interval in seconds (--polling-interval-seconds).
+- name: pollingIntervalSeconds
+ type: number
+ default: 30
+
+# When 'true' (the default), Helix work items that exit 0 but have failed AzDO test results
+# are treated as failed: they count toward the monitor's exit code and are resubmitted by a
+# later invocation's retry pass. Set to 'false' to fall back to exit-code-only outcomes.
+# Forwarded as --fail-on-failed-tests.
+- name: failWorkItemsWithFailedTests
+ type: boolean
+ default: true
+
+# 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
+# primarily intended for the Arcade repository itself, where the Helix job monitor tool is
+# built in the same pipeline that runs this template.
+#
+# When this parameter is empty (the default), the consuming repository must declare the tool
+# in its .config/dotnet-tools.json manifest (alongside other local .NET tools); the template
+# will check out the repo and run 'dotnet tool restore' to install the version pinned there.
+- name: toolNupkgArtifactName
+ type: string
+ default: ''
+
+# Advanced: sub-path within the downloaded artifact where the tool nupkg is located. Defaults
+# to the standard Arcade non-shipping packages location for a Release build (relative to the
+# pipeline artifact root, which is itself the build's 'artifacts' directory).
+- name: toolNupkgArtifactSubPath
+ type: string
+ default: 'packages/Release/NonShipping'
+
+jobs:
+- job: HelixJobMonitor
+ displayName: Monitor Helix Jobs
+ timeoutInMinutes: ${{ parameters.timeoutInMinutes }}
+ ${{ if ne(length(parameters.dependsOn), 0) }}:
+ dependsOn: ${{ parameters.dependsOn }}
+ ${{ if ne(parameters.condition, '') }}:
+ condition: ${{ parameters.condition }}
+ pool:
+ ${{ if eq(variables['System.TeamProject'], 'public') }}:
+ name: $(DncEngPublicBuildPool)
+ demands: ImageOverride -equals build.azurelinux.3.amd64.open
+ ${{ else }}:
+ name: $(DncEngInternalBuildPool)
+ demands: ImageOverride -equals build.azurelinux.3.amd64
+ steps:
+ - checkout: self
+ fetchDepth: 1
+
+ - ${{ if ne(parameters.toolNupkgArtifactName, '') }}:
+ - task: DownloadPipelineArtifact@2
+ displayName: Download Helix Job Monitor artifact
+ inputs:
+ buildType: current
+ artifactName: ${{ parameters.toolNupkgArtifactName }}
+ itemPattern: '${{ parameters.toolNupkgArtifactSubPath }}/${{ parameters.toolPackageId }}.*.nupkg'
+ targetPath: $(Agent.TempDirectory)/helix-job-monitor-nupkg
+
+ - bash: |
+ set -euo pipefail
+
+ toolPath="$AGENT_TEMPDIRECTORY/helix-job-monitor-tool"
+ mkdir -p "$toolPath"
+
+ packageId='${{ parameters.toolPackageId }}'
+ toolVersion='${{ parameters.toolVersion }}'
+ nupkgArtifactSubPath='${{ parameters.toolNupkgArtifactSubPath }}'
+ nupkgDir="$AGENT_TEMPDIRECTORY/helix-job-monitor-nupkg/$nupkgArtifactSubPath"
+
+ if [ ! -d "$nupkgDir" ]; then
+ echo "Expected nupkg directory '$nupkgDir' was not produced by the artifact download." >&2
+ exit 1
+ fi
+
+ nupkg=$(find "$nupkgDir" -maxdepth 1 -type f -name "$packageId.*.nupkg" | head -n 1)
+ if [ -z "$nupkg" ]; then
+ echo "No '$packageId.*.nupkg' found in '$nupkgDir'." >&2
+ exit 1
+ fi
+
+ # Derive the version from the nupkg filename so the local package is selected
+ # deterministically instead of resolving against any other configured feed.
+ nupkgBase=$(basename "$nupkg" .nupkg)
+ derivedVersion="${nupkgBase#${packageId}.}"
+ if [ -z "$toolVersion" ]; then
+ toolVersion="$derivedVersion"
+ fi
+
+ echo "Using locally built '$packageId' version '$toolVersion' from '$nupkgDir'."
+
+ # Create a minimal NuGet.config that only references the local nupkg directory.
+ # This avoids conflicts with the repo's package source mapping which blocks --add-source.
+ toolNugetConfig="$AGENT_TEMPDIRECTORY/helix-job-monitor-nuget.config"
+ printf '\n\n \n \n \n \n\n' "$nupkgDir" > "$toolNugetConfig"
+
+ pushd "$(Build.SourcesDirectory)" > /dev/null
+ ./eng/common/dotnet.sh tool install \
+ --tool-path "$toolPath" "$packageId" \
+ --version "$toolVersion" \
+ --configfile "$toolNugetConfig"
+
+ # Locate the tool DLL so the run step can invoke it via ./eng/common/dotnet.sh exec.
+ toolDll=$(find "$toolPath/.store" -path '*/tools/*/any/*.deps.json' -type f | head -n 1)
+ toolDll="${toolDll%.deps.json}.dll"
+ if [ ! -f "$toolDll" ]; then
+ echo "Could not find tool DLL in '$toolPath/.store'." >&2
+ exit 1
+ fi
+
+ echo "Tool DLL: $toolDll"
+ echo "##vso[task.setvariable variable=HelixJobMonitorDll]$toolDll"
+ displayName: Install Helix Job Monitor
+
+ - ${{ else }}:
+ - bash: ./eng/common/dotnet.sh tool restore
+ displayName: Restore Helix Job Monitor
+
+ - bash: |
+ set -euo pipefail
+
+ toolArgs=(
+ --helix-base-uri '${{ parameters.helixBaseUri }}'
+ --polling-interval-seconds '${{ parameters.pollingIntervalSeconds }}'
+ --fail-on-failed-tests '${{ parameters.failWorkItemsWithFailedTests }}'
+ --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)'
+ )
+
+ 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.
+ if [ -z "$organization" ] || [ -z "$repository" ]; then
+ buildRepoName="${BUILD_REPOSITORY_NAME:-}"
+ if [ -n "$buildRepoName" ] && [[ "$buildRepoName" == */* ]]; then
+ repoOwner="${buildRepoName%%/*}"
+ repoName="${buildRepoName#*/}"
+ if [ -z "$organization" ]; then organization="$repoOwner"; fi
+ if [ -z "$repository" ]; then repository="$repoName"; fi
+ fi
+ fi
+
+ if [ -n "$organization" ]; then toolArgs+=( --organization "$organization" ); fi
+ if [ -n "$repository" ]; then toolArgs+=( --repository "$repository" ); fi
+
+ # Build.Reason and Build.SourceBranch are required to derive the Helix source filter
+ # the same way the Helix SDK submitter does (PR -> 'pr', internal -> 'official',
+ # otherwise -> 'ci'). Without these, manually-queued / scheduled / CI builds would
+ # be looked up under the wrong source prefix and find zero jobs.
+ toolArgs+=( --build-reason "$(Build.Reason)" )
+ toolArgs+=( --source-branch "$(Build.SourceBranch)" )
+
+ if [ -n '${{ parameters.toolNupkgArtifactName }}' ]; then
+ # Tool was installed from a local nupkg; run the DLL via the repo-local dotnet.
+ export DOTNET_ROOT="$(Build.SourcesDirectory)/.dotnet"
+ ./eng/common/dotnet.sh exec "$(HelixJobMonitorDll)" "${toolArgs[@]}"
+ else
+ # Tool was restored from the local .config/dotnet-tools.json manifest; invoke it
+ # through the manifest from the repo root.
+ pushd "$BUILD_SOURCESDIRECTORY" > /dev/null
+ trap 'popd > /dev/null' EXIT
+ ./eng/common/dotnet.sh tool run '${{ parameters.toolCommand }}' -- "${toolArgs[@]}"
+ fi
+ displayName: Monitor Helix Jobs
+ env:
+ SYSTEM_ACCESSTOKEN: $(System.AccessToken)
+ HELIX_ACCESSTOKEN: ${{ parameters.helixAccessToken }}
diff --git a/eng/common/core-templates/job/job.yml b/eng/common/core-templates/job/job.yml
index 66c7988f22..cb60f52978 100644
--- a/eng/common/core-templates/job/job.yml
+++ b/eng/common/core-templates/job/job.yml
@@ -75,6 +75,12 @@ jobs:
variables:
- name: AllowPtrToDetectTestRunRetryFiles
value: true
+ # Component Governance detection and CodeQL are not run in the public project
+ - ${{ if eq(variables['System.TeamProject'], 'public') }}:
+ - name: skipComponentGovernanceDetection
+ value: true
+ - name: Codeql.SkipTaskAutoInjection
+ value: true
- ${{ if ne(parameters.enableTelemetry, 'false') }}:
- name: DOTNET_CLI_TELEMETRY_PROFILE
value: '$(Build.Repository.Uri)'
diff --git a/eng/common/core-templates/job/onelocbuild.yml b/eng/common/core-templates/job/onelocbuild.yml
index eefed3b667..2816d2905a 100644
--- a/eng/common/core-templates/job/onelocbuild.yml
+++ b/eng/common/core-templates/job/onelocbuild.yml
@@ -8,6 +8,12 @@ parameters:
CeapexPat: $(dn-bot-ceapex-package-r) # PAT for the loc AzDO instance https://dev.azure.com/ceapex
GithubPat: $(BotAccount-dotnet-bot-repo-PAT)
+ # Service connection for WIF-based Entra authentication to ceapex feeds (replaces CeapexPat).
+ # When set, dnceng/internal builds acquire a federated Entra token instead of using a PAT.
+ # All other projects (e.g. DevDiv, public), where this dnceng-scoped service connection does not
+ # exist, and any pipeline that sets this to '' fall back to PAT-based auth via the CeapexPat parameter.
+ CeapexServiceConnection: 'dnceng-onelocbuild-ceapex'
+
SourcesDirectory: $(System.DefaultWorkingDirectory)
CreatePr: true
AutoCompletePr: false
@@ -22,6 +28,7 @@ parameters:
GitHubOrg: dotnet
MirrorRepo: ''
MirrorBranch: main
+ xLocCustomPowerShellScript: ''
condition: ''
JobNameSuffix: ''
is1ESPipeline: ''
@@ -73,6 +80,15 @@ jobs:
displayName: Generate LocProject.json
condition: ${{ parameters.condition }}
+ # Acquire an Entra token for ceapex feed access via WIF (dnceng/internal only).
+ # All other projects use PAT-based auth, since the ceapex service connection is scoped to dnceng/internal.
+ - ${{ if and(ne(parameters.CeapexServiceConnection, ''), eq(variables['System.TeamProject'], 'internal')) }}:
+ - template: /eng/common/templates/steps/get-federated-access-token.yml
+ parameters:
+ federatedServiceConnection: ${{ parameters.CeapexServiceConnection }}
+ outputVariableName: 'CeapexEntraToken'
+ condition: ${{ parameters.condition }}
+
- task: OneLocBuild@2
displayName: OneLocBuild
env:
@@ -88,7 +104,10 @@ jobs:
isUseLfLineEndingsSelected: ${{ parameters.UseLfLineEndings }}
isShouldReusePrSelected: ${{ parameters.ReusePr }}
packageSourceAuth: patAuth
- patVariable: ${{ parameters.CeapexPat }}
+ ${{ if and(ne(parameters.CeapexServiceConnection, ''), eq(variables['System.TeamProject'], 'internal')) }}:
+ patVariable: $(CeapexEntraToken)
+ ${{ if or(eq(parameters.CeapexServiceConnection, ''), ne(variables['System.TeamProject'], 'internal')) }}:
+ patVariable: ${{ parameters.CeapexPat }}
${{ if eq(parameters.RepoType, 'gitHub') }}:
repoType: ${{ parameters.RepoType }}
gitHubPatVariable: "${{ parameters.GithubPat }}"
@@ -97,6 +116,8 @@ jobs:
gitHubOrganization: ${{ parameters.GitHubOrg }}
mirrorRepo: ${{ parameters.MirrorRepo }}
mirrorBranch: ${{ parameters.MirrorBranch }}
+ ${{ if ne(parameters.xLocCustomPowerShellScript, '') }}:
+ xLocCustomPowerShellScript: ${{ parameters.xLocCustomPowerShellScript }}
condition: ${{ parameters.condition }}
# Copy the locProject.json to the root of the Loc directory, then publish a pipeline artifact
diff --git a/eng/common/core-templates/job/publish-build-assets.yml b/eng/common/core-templates/job/publish-build-assets.yml
index 700f771146..4229288d3d 100644
--- a/eng/common/core-templates/job/publish-build-assets.yml
+++ b/eng/common/core-templates/job/publish-build-assets.yml
@@ -122,9 +122,6 @@ jobs:
# Populate internal runtime variables.
- template: /eng/common/templates/steps/enable-internal-sources.yml
- ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}:
- parameters:
- legacyCredential: $(dn-bot-dnceng-artifact-feeds-rw)
- template: /eng/common/templates/steps/enable-internal-runtimes.yml
diff --git a/eng/common/core-templates/post-build/post-build.yml b/eng/common/core-templates/post-build/post-build.yml
index fcf40d1d2e..9d95135269 100644
--- a/eng/common/core-templates/post-build/post-build.yml
+++ b/eng/common/core-templates/post-build/post-build.yml
@@ -225,7 +225,7 @@ stages:
displayName: Validate
inputs:
filePath: eng\common\sdk-task.ps1
- arguments: -task SigningValidation -restore
+ arguments: -task SigningValidation -restore -msbuildEngine dotnet
/p:PackageBasePath='$(Build.ArtifactStagingDirectory)/PackageArtifacts'
/p:SignCheckExclusionsFile='$(System.DefaultWorkingDirectory)/eng/SignCheckExclusionsFile.txt'
${{ parameters.signingValidationAdditionalParameters }}
@@ -295,8 +295,6 @@ stages:
# Populate internal runtime variables.
- template: /eng/common/templates/steps/enable-internal-sources.yml
- parameters:
- legacyCredential: $(dn-bot-dnceng-artifact-feeds-rw)
- template: /eng/common/templates/steps/enable-internal-runtimes.yml
diff --git a/eng/common/core-templates/steps/enable-internal-sources.yml b/eng/common/core-templates/steps/enable-internal-sources.yml
index 4085512b69..51af9a0170 100644
--- a/eng/common/core-templates/steps/enable-internal-sources.yml
+++ b/eng/common/core-templates/steps/enable-internal-sources.yml
@@ -15,32 +15,56 @@ steps:
- ${{ if ne(variables['System.TeamProject'], 'public') }}:
- ${{ if ne(parameters.legacyCredential, '') }}:
- task: PowerShell@2
+ condition: and(succeeded(), eq(variables['Agent.Os'], 'Windows_NT'))
displayName: Setup Internal Feeds
inputs:
filePath: $(System.DefaultWorkingDirectory)/eng/common/SetupNugetSources.ps1
arguments: -ConfigFile $(System.DefaultWorkingDirectory)/NuGet.config -Password $Env:Token
env:
Token: ${{ parameters.legacyCredential }}
+ - task: Bash@3
+ condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT'))
+ displayName: Setup Internal Feeds
+ inputs:
+ targetType: inline
+ script: |
+ "$(System.DefaultWorkingDirectory)/eng/common/SetupNugetSources.sh" "$(System.DefaultWorkingDirectory)/NuGet.config" "$Token"
+ env:
+ Token: ${{ parameters.legacyCredential }}
# If running on dnceng (internal project), just use the default behavior for NuGetAuthenticate.
# If running on DevDiv, NuGetAuthenticate is not really an option. It's scoped to a single feed, and we have many feeds that
# may be added. Instead, we'll use the traditional approach (add cred to nuget.config), but use an account token.
- ${{ else }}:
- ${{ if eq(variables['System.TeamProject'], 'internal') }}:
- task: PowerShell@2
+ condition: and(succeeded(), eq(variables['Agent.Os'], 'Windows_NT'))
displayName: Setup Internal Feeds
inputs:
filePath: $(System.DefaultWorkingDirectory)/eng/common/SetupNugetSources.ps1
arguments: -ConfigFile $(System.DefaultWorkingDirectory)/NuGet.config
+ - task: Bash@3
+ condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT'))
+ displayName: Setup Internal Feeds
+ inputs:
+ filePath: $(System.DefaultWorkingDirectory)/eng/common/SetupNugetSources.sh
+ arguments: $(System.DefaultWorkingDirectory)/NuGet.config
- ${{ else }}:
- template: /eng/common/templates/steps/get-federated-access-token.yml
parameters:
federatedServiceConnection: ${{ parameters.nugetFederatedServiceConnection }}
outputVariableName: 'dnceng-artifacts-feeds-read-access-token'
- task: PowerShell@2
+ condition: and(succeeded(), eq(variables['Agent.Os'], 'Windows_NT'))
displayName: Setup Internal Feeds
inputs:
filePath: $(System.DefaultWorkingDirectory)/eng/common/SetupNugetSources.ps1
arguments: -ConfigFile $(System.DefaultWorkingDirectory)/NuGet.config -Password $(dnceng-artifacts-feeds-read-access-token)
+ - task: Bash@3
+ condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT'))
+ displayName: Setup Internal Feeds
+ inputs:
+ filePath: $(System.DefaultWorkingDirectory)/eng/common/SetupNugetSources.sh
+ arguments: $(System.DefaultWorkingDirectory)/NuGet.config $(dnceng-artifacts-feeds-read-access-token)
# This is required in certain scenarios to install the ADO credential provider.
# It installed by default in some msbuild invocations (e.g. VS msbuild), but needs to be installed for others
# (e.g. dotnet msbuild).
diff --git a/eng/common/core-templates/steps/publish-logs.yml b/eng/common/core-templates/steps/publish-logs.yml
index 84a1922c73..2731e48cce 100644
--- a/eng/common/core-templates/steps/publish-logs.yml
+++ b/eng/common/core-templates/steps/publish-logs.yml
@@ -33,10 +33,6 @@ steps:
'$(publishing-dnceng-devdiv-code-r-build-re)'
'$(dn-bot-all-orgs-artifact-feeds-rw)'
'$(akams-client-id)'
- '$(microsoft-symbol-server-pat)'
- '$(symweb-symbol-server-pat)'
- '$(dnceng-symbol-server-pat)'
- '$(dn-bot-all-orgs-build-rw-code-rw)'
'$(System.AccessToken)'
${{parameters.CustomSensitiveDataList}}
continueOnError: true
diff --git a/eng/common/core-templates/steps/send-to-helix.yml b/eng/common/core-templates/steps/send-to-helix.yml
index 68fa739c4a..ec7a200039 100644
--- a/eng/common/core-templates/steps/send-to-helix.yml
+++ b/eng/common/core-templates/steps/send-to-helix.yml
@@ -10,6 +10,7 @@ parameters:
HelixConfiguration: '' # optional -- additional property attached to a job
HelixPreCommands: '' # optional -- commands to run before Helix work item execution
HelixPostCommands: '' # optional -- commands to run after Helix work item execution
+ UseHelixMonitor: false # optional -- true will submit Helix jobs configured for the standalone Helix Job Monitor (results are reported/waited on out-of-band; this step will not wait, and WaitForWorkItemCompletion will be overridden)
WorkItemDirectory: '' # optional -- a payload directory to zip up and send to Helix; requires WorkItemCommand; incompatible with XUnitProjects
WorkItemCommand: '' # optional -- a command to execute on the payload; requires WorkItemDirectory; incompatible with XUnitProjects
WorkItemTimeout: '' # optional -- a timeout in TimeSpan.Parse-ready value (e.g. 00:02:00) for the work item command; requires WorkItemDirectory; incompatible with XUnitProjects
@@ -31,7 +32,15 @@ parameters:
continueOnError: false # optional -- determines whether to continue the build if the step errors; defaults to false
steps:
- - powershell: 'powershell "$env:BUILD_SOURCESDIRECTORY\eng\common\msbuild.ps1 $env:BUILD_SOURCESDIRECTORY/${{ parameters.HelixProjectPath }} /restore /p:TreatWarningsAsErrors=false ${{ parameters.HelixProjectArguments }} /t:Test /bl:$env:BUILD_SOURCESDIRECTORY\artifacts\log\$env:BuildConfig\SendToHelix.binlog"'
+ - powershell: >
+ $(Build.SourcesDirectory)\eng\common\msbuild.ps1
+ $(Build.SourcesDirectory)/${{ parameters.HelixProjectPath }}
+ /restore
+ /p:TreatWarningsAsErrors=false
+ /p:EnableHelixJobMonitor=${{ parameters.UseHelixMonitor }}
+ ${{ parameters.HelixProjectArguments }}
+ /t:Test
+ /bl:$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/SendToHelix.binlog
displayName: ${{ parameters.DisplayNamePrefix }} (Windows)
env:
BuildConfig: $(_BuildConfig)
@@ -61,7 +70,15 @@ steps:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
condition: and(${{ parameters.condition }}, eq(variables['Agent.Os'], 'Windows_NT'))
continueOnError: ${{ parameters.continueOnError }}
- - script: $BUILD_SOURCESDIRECTORY/eng/common/msbuild.sh $BUILD_SOURCESDIRECTORY/${{ parameters.HelixProjectPath }} /restore /p:TreatWarningsAsErrors=false ${{ parameters.HelixProjectArguments }} /t:Test /bl:$BUILD_SOURCESDIRECTORY/artifacts/log/$BuildConfig/SendToHelix.binlog
+ - script: >
+ $(Build.SourcesDirectory)/eng/common/msbuild.sh
+ $(Build.SourcesDirectory)/${{ parameters.HelixProjectPath }}
+ /restore
+ /p:TreatWarningsAsErrors=false
+ /p:EnableHelixJobMonitor=${{ parameters.UseHelixMonitor }}
+ ${{ parameters.HelixProjectArguments }}
+ /t:Test
+ /bl:$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/SendToHelix.binlog
displayName: ${{ parameters.DisplayNamePrefix }} (Unix)
env:
BuildConfig: $(_BuildConfig)
@@ -91,3 +108,4 @@ steps:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
condition: and(${{ parameters.condition }}, ne(variables['Agent.Os'], 'Windows_NT'))
continueOnError: ${{ parameters.continueOnError }}
+
diff --git a/eng/common/core-templates/steps/source-index-stage1-publish.yml b/eng/common/core-templates/steps/source-index-stage1-publish.yml
index 3ad83b8c30..fdca622357 100644
--- a/eng/common/core-templates/steps/source-index-stage1-publish.yml
+++ b/eng/common/core-templates/steps/source-index-stage1-publish.yml
@@ -1,15 +1,15 @@
parameters:
- sourceIndexUploadPackageVersion: 2.0.0-20250906.1
- sourceIndexProcessBinlogPackageVersion: 1.0.1-20250906.1
+ sourceIndexUploadPackageVersion: 2.0.0-20260521.2
+ sourceIndexProcessBinlogPackageVersion: 1.0.1-20260521.2
sourceIndexPackageSource: https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json
binlogPath: artifacts/log/Debug/Build.binlog
steps:
- task: UseDotNet@2
- displayName: "Source Index: Use .NET 9 SDK"
+ displayName: "Source Index: Use .NET 10 SDK"
inputs:
packageType: sdk
- version: 9.0.x
+ version: 10.0.x
installationPath: $(Agent.TempDirectory)/dotnet
workingDirectory: $(Agent.TempDirectory)
diff --git a/eng/common/cross/build-rootfs.sh b/eng/common/cross/build-rootfs.sh
index 314c93c575..38a3512f14 100755
--- a/eng/common/cross/build-rootfs.sh
+++ b/eng/common/cross/build-rootfs.sh
@@ -5,7 +5,7 @@ set -e
usage()
{
echo "Usage: $0 [BuildArch] [CodeName] [lldbx.y] [llvmx[.y]] [--skipunmount] --rootfsdir ]"
- echo "BuildArch can be: arm(default), arm64, armel, armv6, loongarch64, ppc64le, riscv64, s390x, x64, x86"
+ echo "BuildArch can be: arm(default), arm64, loongarch64, ppc64le, riscv64, s390x, x64, x86"
echo "CodeName - optional, Code name for Linux, can be: xenial(default), zesty, bionic, alpine"
echo " for alpine can be specified with version: alpineX.YY or alpineedge"
echo " for FreeBSD can be: freebsd13, freebsd14"
@@ -18,7 +18,10 @@ usage()
echo "--skipsigcheck - optional, will skip package signature checks (allowing untrusted packages)."
echo "--skipemulation - optional, will skip qemu and debootstrap requirement when building environment for debian based systems."
echo "--use-mirror - optional, use mirror URL to fetch resources, when available."
- echo "--jobs N - optional, restrict to N jobs."
+ echo "--ubuntu-repo - optional, override the Ubuntu apt repository base URL."
+ echo "--debian-repo - optional, override the Debian apt repository base URL."
+ echo "--alpine-repo - optional, override the Alpine Linux repository base URL."
+ echo "--jobs N (or --use-jobs N) - optional, restrict to N jobs."
exit 1
}
@@ -76,7 +79,7 @@ __AlpinePackages+=" openssl-dev"
__AlpinePackages+=" zlib-dev"
__FreeBSDBase="13.5-RELEASE"
-__FreeBSDPkg="1.21.3"
+__FreeBSDPkg="2.7.5"
__FreeBSDABI="13"
__FreeBSDPackages="libunwind"
__FreeBSDPackages+=" icu"
@@ -88,8 +91,9 @@ __FreeBSDPackages+=" terminfo-db"
__OpenBSDVersion="7.8"
__OpenBSDPackages="heimdal-libs"
__OpenBSDPackages+=" icu4c"
-__OpenBSDPackages+=" inotify-tools"
+__OpenBSDPackages+=" libinotify"
__OpenBSDPackages+=" openssl"
+__OpenBSDPackages+=" e2fsprogs"
__IllumosPackages="icu"
__IllumosPackages+=" mit-krb5"
@@ -139,11 +143,13 @@ __AlpineKeys='
616db30d:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnpUpyWDWjlUk3smlWeA0\nlIMW+oJ38t92CRLHH3IqRhyECBRW0d0aRGtq7TY8PmxjjvBZrxTNDpJT6KUk4LRm\na6A6IuAI7QnNK8SJqM0DLzlpygd7GJf8ZL9SoHSH+gFsYF67Cpooz/YDqWrlN7Vw\ntO00s0B+eXy+PCXYU7VSfuWFGK8TGEv6HfGMALLjhqMManyvfp8hz3ubN1rK3c8C\nUS/ilRh1qckdbtPvoDPhSbTDmfU1g/EfRSIEXBrIMLg9ka/XB9PvWRrekrppnQzP\nhP9YE3x/wbFc5QqQWiRCYyQl/rgIMOXvIxhkfe8H5n1Et4VAorkpEAXdsfN8KSVv\nLSMazVlLp9GYq5SUpqYX3KnxdWBgN7BJoZ4sltsTpHQ/34SXWfu3UmyUveWj7wp0\nx9hwsPirVI00EEea9AbP7NM2rAyu6ukcm4m6ATd2DZJIViq2es6m60AE6SMCmrQF\nwmk4H/kdQgeAELVfGOm2VyJ3z69fQuywz7xu27S6zTKi05Qlnohxol4wVb6OB7qG\nLPRtK9ObgzRo/OPumyXqlzAi/Yvyd1ZQk8labZps3e16bQp8+pVPiumWioMFJDWV\nGZjCmyMSU8V6MB6njbgLHoyg2LCukCAeSjbPGGGYhnKLm1AKSoJh3IpZuqcKCk5C\n8CM1S15HxV78s9dFntEqIokCAwEAAQ==
66ba20fe:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtfB12w4ZgqsXWZDfUAV/\n6Y4aHUKIu3q4SXrNZ7CXF9nXoAVYrS7NAxJdAodsY3vPCN0g5O8DFXR+390LdOuQ\n+HsGKCc1k5tX5ZXld37EZNTNSbR0k+NKhd9h6X3u6wqPOx7SIKxwAQR8qeeFq4pP\nrt9GAGlxtuYgzIIcKJPwE0dZlcBCg+GnptCUZXp/38BP1eYC+xTXSL6Muq1etYfg\nodXdb7Yl+2h1IHuOwo5rjgY5kpY7GcAs8AjGk3lDD/av60OTYccknH0NCVSmPoXK\nvrxDBOn0LQRNBLcAfnTKgHrzy0Q5h4TNkkyTgxkoQw5ObDk9nnabTxql732yy9BY\ns+hM9+dSFO1HKeVXreYSA2n1ndF18YAvAumzgyqzB7I4pMHXq1kC/8bONMJxwSkS\nYm6CoXKyavp7RqGMyeVpRC7tV+blkrrUml0BwNkxE+XnwDRB3xDV6hqgWe0XrifD\nYTfvd9ScZQP83ip0r4IKlq4GMv/R5shcCRJSkSZ6QSGshH40JYSoiwJf5FHbj9ND\n7do0UAqebWo4yNx63j/wb2ULorW3AClv0BCFSdPsIrCStiGdpgJDBR2P2NZOCob3\nG9uMj+wJD6JJg2nWqNJxkANXX37Qf8plgzssrhrgOvB0fjjS7GYhfkfmZTJ0wPOw\nA8+KzFseBh4UFGgue78KwgkCAwEAAQ==
'
-__Keyring=
__KeyringFile="/usr/share/keyrings/ubuntu-archive-keyring.gpg"
__SkipSigCheck=0
__SkipEmulation=0
__UseMirror=0
+__UbuntuRepoOverride=
+__DebianRepoOverride=
+__AlpineRepoOverride=
__UnprocessedBuildArgs=
while :; do
@@ -162,6 +168,10 @@ while :; do
__AlpineArch=armv7
__QEMUArch=arm
;;
+ armel)
+ # this is only used for tizen-build-rootfs.sh
+ __BuildArch=armel
+ ;;
arm64)
__BuildArch=arm64
__UbuntuArch=arm64
@@ -172,31 +182,6 @@ while :; do
__OpenBSDArch=arm64
__OpenBSDMachineArch=aarch64
;;
- armel)
- __BuildArch=armel
- __UbuntuArch=armel
- __UbuntuRepo="http://archive.debian.org/debian/"
- __CodeName=buster
- __KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg"
- __LLDB_Package="liblldb-6.0-dev"
- __UbuntuPackages="${__UbuntuPackages// libomp-dev/}"
- __UbuntuPackages="${__UbuntuPackages// libomp5/}"
- __UbuntuSuites=
- ;;
- armv6)
- __BuildArch=armv6
- __UbuntuArch=armhf
- __QEMUArch=arm
- __UbuntuRepo="http://raspbian.raspberrypi.org/raspbian/"
- __CodeName=buster
- __KeyringFile="/usr/share/keyrings/raspbian-archive-keyring.gpg"
- __LLDB_Package="liblldb-6.0-dev"
- __UbuntuSuites=
-
- if [[ -e "$__KeyringFile" ]]; then
- __Keyring="--keyring $__KeyringFile"
- fi
- ;;
loongarch64)
__BuildArch=loongarch64
__AlpineArch=loongarch64
@@ -204,10 +189,6 @@ while :; do
__UbuntuArch=loong64
__UbuntuSuites=unreleased
__LLDB_Package="liblldb-19-dev"
-
- if [[ "$__CodeName" == "sid" ]]; then
- __UbuntuRepo="http://ftp.ports.debian.org/debian-ports/"
- fi
;;
riscv64)
__BuildArch=riscv64
@@ -223,7 +204,7 @@ while :; do
__AlpineArch=ppc64le
__QEMUArch=ppc64le
__UbuntuArch=ppc64el
- __UbuntuRepo="http://ports.ubuntu.com/ubuntu-ports/"
+ __UbuntuRepo="https://ports.ubuntu.com/ubuntu-ports/"
__UbuntuPackages="${__UbuntuPackages// libunwind8-dev/}"
__UbuntuPackages="${__UbuntuPackages// libomp-dev/}"
__UbuntuPackages="${__UbuntuPackages// libomp5/}"
@@ -234,7 +215,7 @@ while :; do
__AlpineArch=s390x
__QEMUArch=s390x
__UbuntuArch=s390x
- __UbuntuRepo="http://ports.ubuntu.com/ubuntu-ports/"
+ __UbuntuRepo="https://ports.ubuntu.com/ubuntu-ports/"
__UbuntuPackages="${__UbuntuPackages// libunwind8-dev/}"
__UbuntuPackages="${__UbuntuPackages// libomp-dev/}"
__UbuntuPackages="${__UbuntuPackages// libomp5/}"
@@ -250,13 +231,13 @@ while :; do
__OpenBSDMachineArch=amd64
__illumosArch=x86_64
__HaikuArch=x86_64
- __UbuntuRepo="http://archive.ubuntu.com/ubuntu/"
+ __UbuntuRepo="https://archive.ubuntu.com/ubuntu/"
;;
x86)
__BuildArch=x86
__UbuntuArch=i386
__AlpineArch=x86
- __UbuntuRepo="http://archive.ubuntu.com/ubuntu/"
+ __UbuntuRepo="https://archive.ubuntu.com/ubuntu/"
;;
lldb*)
version="$(echo "$lowerI" | tr -d '[:alpha:]-=')"
@@ -316,7 +297,7 @@ while :; do
__KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg"
if [[ -z "$__UbuntuRepo" ]]; then
- __UbuntuRepo="http://ftp.debian.org/debian/"
+ __UbuntuRepo="https://archive.debian.org/debian/"
fi
;;
buster) # Debian 10
@@ -325,7 +306,7 @@ while :; do
__KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg"
if [[ -z "$__UbuntuRepo" ]]; then
- __UbuntuRepo="http://archive.debian.org/debian/"
+ __UbuntuRepo="https://archive.debian.org/debian/"
fi
;;
bullseye) # Debian 11
@@ -333,7 +314,7 @@ while :; do
__KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg"
if [[ -z "$__UbuntuRepo" ]]; then
- __UbuntuRepo="http://ftp.debian.org/debian/"
+ __UbuntuRepo="https://ftp.debian.org/debian/"
fi
;;
bookworm) # Debian 12
@@ -341,7 +322,7 @@ while :; do
__KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg"
if [[ -z "$__UbuntuRepo" ]]; then
- __UbuntuRepo="http://ftp.debian.org/debian/"
+ __UbuntuRepo="https://ftp.debian.org/debian/"
fi
;;
sid) # Debian sid
@@ -350,25 +331,21 @@ while :; do
# Debian-Ports architectures need different values
case "$__UbuntuArch" in
- amd64|arm64|armel|armhf|i386|mips64el|ppc64el|riscv64|s390x)
+ amd64|arm64|armhf|i386|mips64el|ppc64el|riscv64|s390x)
__KeyringFile="/usr/share/keyrings/debian-archive-keyring.gpg"
if [[ -z "$__UbuntuRepo" ]]; then
- __UbuntuRepo="http://ftp.debian.org/debian/"
+ __UbuntuRepo="https://ftp.debian.org/debian/"
fi
;;
*)
__KeyringFile="/usr/share/keyrings/debian-ports-archive-keyring.gpg"
if [[ -z "$__UbuntuRepo" ]]; then
- __UbuntuRepo="http://ftp.ports.debian.org/debian-ports/"
+ __UbuntuRepo="https://ftp.debian.org/debian-ports/"
fi
;;
esac
-
- if [[ -e "$__KeyringFile" ]]; then
- __Keyring="--keyring $__KeyringFile"
- fi
;;
tizen)
__CodeName=
@@ -426,6 +403,31 @@ while :; do
--use-mirror)
__UseMirror=1
;;
+ --ubuntu-repo|-ubuntu-repo)
+ shift
+ if [[ "$#" -le 0 ]]; then
+ echo "ERROR: --ubuntu-repo requires a URL argument."
+ usage
+ fi
+ __UbuntuRepoOverride="$1"
+ ;;
+ --debian-repo|-debian-repo)
+ shift
+ if [[ "$#" -le 0 ]]; then
+ echo "ERROR: --debian-repo requires a URL argument."
+ usage
+ fi
+ __DebianRepoOverride="$1"
+ ;;
+ --alpine-repo|-alpine-repo)
+ shift
+ if [[ "$#" -le 0 ]]; then
+ echo "ERROR: --alpine-repo requires a URL argument."
+ usage
+ fi
+ __AlpineRepoOverride="$1"
+ ;;
+ # Removed duplicate/invalid option handling block (was breaking case statement parsing).
--use-jobs)
shift
MAXJOBS=$1
@@ -451,9 +453,12 @@ case "$__AlpineVersion" in
elif [[ "$__AlpineArch" == "x86" ]]; then
__AlpineVersion=3.17 # minimum version that supports lldb-dev
__AlpinePackages+=" llvm15-libs"
- elif [[ "$__AlpineArch" == "riscv64" || "$__AlpineArch" == "loongarch64" ]]; then
+ elif [[ "$__AlpineArch" == "loongarch64" ]]; then
__AlpineVersion=3.21 # minimum version that supports lldb-dev
__AlpinePackages+=" llvm19-libs"
+ elif [[ "$__AlpineArch" == "riscv64" ]]; then
+ __AlpineVersion=3.22 # lldb-dev requires 3.21+, but 3.22+ provides the newer linux-headers needed for RISC-V extension probes
+ __AlpinePackages+=" llvm20-libs"
elif [[ -n "$__AlpineMajorVersion" ]]; then
# use whichever alpine version is provided and select the latest toolchain libs
__AlpineLlvmLibsLookup=1
@@ -472,7 +477,13 @@ fi
__UbuntuPackages+=" ${__LLDB_Package:-}"
if [[ -z "$__UbuntuRepo" ]]; then
- __UbuntuRepo="http://ports.ubuntu.com/"
+ __UbuntuRepo="https://ports.ubuntu.com/"
+fi
+
+if [[ -n "$__UbuntuRepoOverride" && "$__KeyringFile" == *ubuntu* ]]; then
+ __UbuntuRepo="$__UbuntuRepoOverride"
+elif [[ -n "$__DebianRepoOverride" && "$__KeyringFile" == *debian* ]]; then
+ __UbuntuRepo="$__DebianRepoOverride"
fi
if [[ -n "$__LLVM_MajorVersion" ]]; then
@@ -515,6 +526,7 @@ if [[ "$__CodeName" == "alpine" ]]; then
__ApkToolsDir="$(mktemp -d)"
__ApkKeysDir="$(mktemp -d)"
arch="$(uname -m)"
+ __AlpineRepo="${__AlpineRepoOverride:-https://dl-cdn.alpinelinux.org/alpine}"
ensureDownloadTool
@@ -559,15 +571,15 @@ if [[ "$__CodeName" == "alpine" ]]; then
# initialize DB
# shellcheck disable=SC2086
"$__ApkToolsDir/apk.static" \
- -X "http://dl-cdn.alpinelinux.org/alpine/$version/main" \
- -X "http://dl-cdn.alpinelinux.org/alpine/$version/community" \
+ -X "$__AlpineRepo/$version/main" \
+ -X "$__AlpineRepo/$version/community" \
-U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" --initdb add
if [[ "$__AlpineLlvmLibsLookup" == 1 ]]; then
# shellcheck disable=SC2086
__AlpinePackages+=" $("$__ApkToolsDir/apk.static" \
- -X "http://dl-cdn.alpinelinux.org/alpine/$version/main" \
- -X "http://dl-cdn.alpinelinux.org/alpine/$version/community" \
+ -X "$__AlpineRepo/$version/main" \
+ -X "$__AlpineRepo/$version/community" \
-U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" \
search 'llvm*-libs' | grep -E '^llvm' | sort | tail -1 | sed 's/-[^-]*//2g')"
fi
@@ -575,8 +587,8 @@ if [[ "$__CodeName" == "alpine" ]]; then
# install all packages in one go
# shellcheck disable=SC2086
"$__ApkToolsDir/apk.static" \
- -X "http://dl-cdn.alpinelinux.org/alpine/$version/main" \
- -X "http://dl-cdn.alpinelinux.org/alpine/$version/community" \
+ -X "$__AlpineRepo/$version/main" \
+ -X "$__AlpineRepo/$version/community" \
-U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" $__NoEmulationArg \
add $__AlpinePackages
@@ -593,7 +605,7 @@ elif [[ "$__CodeName" == "freebsd" ]]; then
curl -SL "https://download.freebsd.org/ftp/releases/${__FreeBSDArch}/${__FreeBSDMachineArch}/${__FreeBSDBase}/base.txz" | tar -C "$__RootfsDir" -Jxf - ./lib ./usr/lib ./usr/libdata ./usr/include ./usr/share/keys ./etc ./bin/freebsd-version
fi
echo "ABI = \"FreeBSD:${__FreeBSDABI}:${__FreeBSDMachineArch}\"; FINGERPRINTS = \"${__RootfsDir}/usr/share/keys\"; REPOS_DIR = [\"${__RootfsDir}/etc/pkg\"]; REPO_AUTOUPDATE = NO; RUN_SCRIPTS = NO;" > "${__RootfsDir}"/usr/local/etc/pkg.conf
- echo "FreeBSD: { url: \"pkg+http://pkg.FreeBSD.org/\${ABI}/quarterly\", mirror_type: \"srv\", signature_type: \"fingerprints\", fingerprints: \"/usr/share/keys/pkg\", enabled: yes }" > "${__RootfsDir}"/etc/pkg/FreeBSD.conf
+ echo "FreeBSD: { url: \"pkg+https://pkg.FreeBSD.org/\${ABI}/quarterly\", mirror_type: \"srv\", signature_type: \"fingerprints\", fingerprints: \"/usr/share/keys/pkg\", enabled: yes }" > "${__RootfsDir}"/etc/pkg/FreeBSD.conf
mkdir -p "$__RootfsDir"/tmp
# get and build package manager
if [[ "$__hasWget" == 1 ]]; then
@@ -607,7 +619,7 @@ elif [[ "$__CodeName" == "freebsd" ]]; then
./autogen.sh && ./configure --prefix="$__RootfsDir"/host && make -j "$JOBS" && make install
rm -rf "$__RootfsDir/tmp/pkg-${__FreeBSDPkg}"
# install packages we need.
- INSTALL_AS_USER=$(whoami) "$__RootfsDir"/host/sbin/pkg -r "$__RootfsDir" -C "$__RootfsDir"/usr/local/etc/pkg.conf update
+ INSTALL_AS_USER=$(whoami) IGNORE_OSVERSION=yes "$__RootfsDir"/host/sbin/pkg -r "$__RootfsDir" -C "$__RootfsDir"/usr/local/etc/pkg.conf update
# shellcheck disable=SC2086
INSTALL_AS_USER=$(whoami) "$__RootfsDir"/host/sbin/pkg -r "$__RootfsDir" -C "$__RootfsDir"/usr/local/etc/pkg.conf install --yes $__FreeBSDPackages
elif [[ "$__CodeName" == "openbsd" ]]; then
@@ -648,15 +660,15 @@ elif [[ "$__CodeName" == "openbsd" ]]; then
[[ -z "$PKG_FILE" ]] && { echo "ERROR: Package $pkg not found"; exit 1; }
if [[ "$__hasWget" == 1 ]]; then
- wget -O- "$PKG_MIRROR/$PKG_FILE" | tar -C "$__RootfsDir" -xzpf -
+ wget -O- "$PKG_MIRROR/$PKG_FILE" | tar -C "$__RootfsDir/usr/local" -xzpf -
else
- curl -SL "$PKG_MIRROR/$PKG_FILE" | tar -C "$__RootfsDir" -xzpf -
+ curl -SL "$PKG_MIRROR/$PKG_FILE" | tar -C "$__RootfsDir/usr/local" -xzpf -
fi
done
echo "Creating versionless symlinks for shared libraries..."
# Find all versioned .so files and create the base .so symlink
- for lib in "$__RootfsDir/usr/lib/libc++.so."* "$__RootfsDir/usr/lib/libc++abi.so."* "$__RootfsDir/usr/lib/libpthread.so."*; do
+ for lib in "$__RootfsDir"/usr/lib/lib*.so.*; do
if [ -f "$lib" ]; then
# Extract the filename (e.g., libc++.so.12.0)
VERSIONED_NAME=$(basename "$lib")
@@ -666,6 +678,10 @@ elif [[ "$__CodeName" == "openbsd" ]]; then
ln -sf "$VERSIONED_NAME" "$__RootfsDir/usr/lib/$BASE_NAME"
fi
done
+
+ echo "Cleaning up unnecessary paths"
+ # we don't use executables and kernel in rootfs (as we use host's compiler with -sysroot)
+ rm -rf "$__RootfsDir/usr/share" "$__RootfsDir/usr/bin"
elif [[ "$__CodeName" == "illumos" ]]; then
mkdir "$__RootfsDir/tmp"
pushd "$__RootfsDir/tmp"
@@ -830,6 +846,14 @@ elif [[ "$__CodeName" == "haiku" ]]; then
elif [[ -n "$__CodeName" ]]; then
__Suites="$__CodeName $(for suite in $__UbuntuSuites; do echo -n "$__CodeName-$suite "; done)"
+ __SigCheckArgs=
+ if [[ "$__SkipSigCheck" == "0" ]]; then
+ if [[ -e "$__KeyringFile" ]]; then
+ __SigCheckArgs="--keyring $__KeyringFile"
+ fi
+ __SigCheckArgs="$__SigCheckArgs --force-check-gpg"
+ fi
+
if [[ "$__SkipEmulation" == "1" ]]; then
if [[ -z "$AR" ]]; then
if command -v ar &>/dev/null; then
@@ -845,31 +869,23 @@ elif [[ -n "$__CodeName" ]]; then
PYTHON=${PYTHON_EXECUTABLE:-python3}
# shellcheck disable=SC2086,SC2046
- echo running "$PYTHON" "$__CrossDir/install-debs.py" --arch "$__UbuntuArch" --mirror "$__UbuntuRepo" --rootfsdir "$__RootfsDir" --artool "$AR" \
+ echo running "$PYTHON" "$__CrossDir/install-debs.py" $__SigCheckArgs --arch "$__UbuntuArch" --mirror "$__UbuntuRepo" --rootfsdir "$__RootfsDir" --artool "$AR" \
$(for suite in $__Suites; do echo -n "--suite $suite "; done) \
$__UbuntuPackages
# shellcheck disable=SC2086,SC2046
- "$PYTHON" "$__CrossDir/install-debs.py" --arch "$__UbuntuArch" --mirror "$__UbuntuRepo" --rootfsdir "$__RootfsDir" --artool "$AR" \
+ "$PYTHON" "$__CrossDir/install-debs.py" $__SigCheckArgs --arch "$__UbuntuArch" --mirror "$__UbuntuRepo" --rootfsdir "$__RootfsDir" --artool "$AR" \
$(for suite in $__Suites; do echo -n "--suite $suite "; done) \
$__UbuntuPackages
exit 0
fi
- __UpdateOptions=
- if [[ "$__SkipSigCheck" == "0" ]]; then
- __Keyring="$__Keyring --force-check-gpg"
- else
- __Keyring=
- __UpdateOptions="--allow-unauthenticated --allow-insecure-repositories"
- fi
-
# shellcheck disable=SC2086
- echo running debootstrap "--variant=minbase" $__Keyring --arch "$__UbuntuArch" "$__CodeName" "$__RootfsDir" "$__UbuntuRepo"
+ echo running debootstrap "--variant=minbase" $__SigCheckArgs --arch "$__UbuntuArch" "$__CodeName" "$__RootfsDir" "$__UbuntuRepo"
# shellcheck disable=SC2086
- if ! debootstrap "--variant=minbase" $__Keyring --arch "$__UbuntuArch" "$__CodeName" "$__RootfsDir" "$__UbuntuRepo"; then
+ if ! debootstrap "--variant=minbase" $__SigCheckArgs --arch "$__UbuntuArch" "$__CodeName" "$__RootfsDir" "$__UbuntuRepo"; then
echo "debootstrap failed! dumping debootstrap.log"
cat "$__RootfsDir/debootstrap/debootstrap.log"
exit 1
@@ -887,6 +903,11 @@ Components: main universe
Signed-By: $__KeyringFile
EOF
+ __UpdateOptions=
+ if [[ "$__SkipSigCheck" == "1" ]]; then
+ __UpdateOptions="--allow-unauthenticated --allow-insecure-repositories"
+ fi
+
# shellcheck disable=SC2086
chroot "$__RootfsDir" apt-get update $__UpdateOptions
chroot "$__RootfsDir" apt-get -f -y install
diff --git a/eng/common/cross/install-debs.py b/eng/common/cross/install-debs.py
index c81eb37e52..1d1dfabf7d 100644
--- a/eng/common/cross/install-debs.py
+++ b/eng/common/cross/install-debs.py
@@ -4,6 +4,7 @@
import asyncio
import aiohttp
import gzip
+import hashlib
import os
import re
import shutil
@@ -16,7 +17,7 @@
from collections import deque
from functools import cmp_to_key
-async def download_file(session, url, dest_path, max_retries=3, retry_delay=2, timeout=60):
+async def download_file(session, url, dest_path, max_retries=3, retry_delay=2, timeout=60, checksum=None):
"""Asynchronous file download with retries."""
attempt = 0
while attempt < max_retries:
@@ -25,19 +26,25 @@ async def download_file(session, url, dest_path, max_retries=3, retry_delay=2, t
if response.status == 200:
with open(dest_path, "wb") as f:
content = await response.read()
+
+ # verify checksum if provided
+ if checksum:
+ sha256 = hashlib.sha256(content).hexdigest()
+ if sha256 != checksum:
+ raise Exception(f"SHA256 mismatch for {url}: expected {checksum}, got {sha256}")
+
f.write(content)
print(f"Downloaded {url} at {dest_path}")
return
else:
- print(f"Failed to download {url}, Status Code: {response.status}")
- break
+ raise Exception(f"Failed to download {url}, Status Code: {response.status}")
except (asyncio.CancelledError, asyncio.TimeoutError, aiohttp.ClientError) as e:
print(f"Error downloading {url}: {type(e).__name__} - {e}. Retrying...")
attempt += 1
await asyncio.sleep(retry_delay)
- print(f"Failed to download {url} after {max_retries} attempts.")
+ raise Exception(f"Failed to download {url} after {max_retries} attempts.")
async def download_deb_files_parallel(mirror, packages, tmp_dir):
"""Download .deb files in parallel."""
@@ -51,11 +58,11 @@ async def download_deb_files_parallel(mirror, packages, tmp_dir):
if filename:
url = f"{mirror}/{filename}"
dest_path = os.path.join(tmp_dir, os.path.basename(filename))
- tasks.append(asyncio.create_task(download_file(session, url, dest_path)))
+ tasks.append(asyncio.create_task(download_file(session, url, dest_path, checksum=info.get("SHA256"))))
await asyncio.gather(*tasks)
-async def download_package_index_parallel(mirror, arch, suites):
+async def download_package_index_parallel(mirror, arch, suites, check_sig, keyring):
"""Download package index files for specified suites and components entirely in memory."""
tasks = []
timeout = aiohttp.ClientTimeout(total=60)
@@ -63,10 +70,9 @@ async def download_package_index_parallel(mirror, arch, suites):
async with aiohttp.ClientSession(timeout=timeout) as session:
for suite in suites:
for component in ["main", "universe"]:
- url = f"{mirror}/dists/{suite}/{component}/binary-{arch}/Packages.gz"
- tasks.append(fetch_and_decompress(session, url))
+ tasks.append(fetch_and_decompress(session, mirror, arch, suite, component, check_sig, keyring))
- results = await asyncio.gather(*tasks, return_exceptions=True)
+ results = await asyncio.gather(*tasks)
merged_content = ""
for result in results:
@@ -77,20 +83,75 @@ async def download_package_index_parallel(mirror, arch, suites):
return merged_content
-async def fetch_and_decompress(session, url):
+async def fetch_and_decompress(session, mirror, arch, suite, component, check_sig, keyring):
"""Fetch and decompress the Packages.gz file."""
- try:
- async with session.get(url) as response:
- if response.status == 200:
- compressed_data = await response.read()
- decompressed_data = gzip.decompress(compressed_data).decode('utf-8')
- print(f"Downloaded index: {url}")
- return decompressed_data
- else:
- print(f"Skipped index: {url} (doesn't exist)")
- return None
- except Exception as e:
- print(f"Error fetching {url}: {e}")
+
+ path = f"{component}/binary-{arch}/Packages.gz"
+ url = f"{mirror}/dists/{suite}/{path}"
+
+ async with session.get(url) as response:
+ if response.status == 200:
+ compressed_data = await response.read()
+ decompressed_data = gzip.decompress(compressed_data).decode('utf-8')
+ print(f"Downloaded index: {url}")
+
+ if check_sig:
+ # Verify the package index against the sha256 recorded in the Release file
+ release_file_content = await fetch_release_file(session, mirror, suite, keyring)
+ packages_sha = parse_release_file(release_file_content, path)
+
+ sha256 = hashlib.sha256(compressed_data).hexdigest()
+ if sha256 != packages_sha:
+ raise Exception(f"SHA256 mismatch for {path}: expected {packages_sha}, got {sha256}")
+ print(f"Checksum verified for {path}")
+
+ return decompressed_data
+ else:
+ print(f"Skipped index: {url} (doesn't exist)")
+ return None
+
+async def fetch_release_file(session, mirror, suite, keyring):
+ """Fetch Release and Release.gpg files and verify the signature."""
+
+ release_url = f"{mirror}/dists/{suite}/Release"
+ release_gpg_url = f"{mirror}/dists/{suite}/Release.gpg"
+
+ with tempfile.NamedTemporaryFile() as release_file, tempfile.NamedTemporaryFile() as release_gpg_file:
+ await download_file(session, release_url, release_file.name)
+ await download_file(session, release_gpg_url, release_gpg_file.name)
+
+ print("Verifying signature of Release with Release.gpg.")
+ # Use gpgv rather than gpg for verification. gpgv verifies a detached
+ # signature against a fixed keyring without involving gpg-agent or
+ # keyboxd, which makes it robust on hosts running GnuPG 2.4+ (e.g. Azure
+ # Linux) where "gpg --keyring" routes through keyboxd and can fail.
+ verify_command = ["gpgv"]
+ if keyring:
+ verify_command += ["--keyring", keyring]
+ verify_command += [release_gpg_file.name, release_file.name]
+ result = subprocess.run(verify_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+ if result.returncode != 0:
+ raise Exception(f"Signature verification failed: {result.stderr.decode('utf-8')}")
+
+ print("Signature verified successfully.")
+
+ with open(release_file.name) as f:
+ return f.read()
+
+def parse_release_file(content, path):
+ """Parses the Release file and returns sha256 checksum of the specified path."""
+
+ # data looks like this:
+ #
+ matches = re.findall(r'^ (\S*) +(\S*) +(\S*)$', content, re.MULTILINE)
+
+ for entry in matches:
+ # the file has both md5 and sha256 checksums, we want sha256 which has a length of 64
+ if entry[2] == path and len(entry[0]) == 64:
+ return entry[0]
+
+ raise Exception(f"Could not find checksum for {path} in Release file.")
def parse_debian_version(version):
"""Parse a Debian package version into epoch, upstream version, and revision."""
@@ -171,13 +232,15 @@ def parse_package_index(content):
filename = fields.get("Filename")
depends = fields.get("Depends")
provides = fields.get("Provides", None)
+ sha256 = fields.get("SHA256")
# Only update if package_name is not in packages or if the new version is higher
if package_name not in packages or compare_debian_versions(version, packages[package_name]["Version"]) > 0:
packages[package_name] = {
"Version": version,
"Filename": filename,
- "Depends": depends
+ "Depends": depends,
+ "SHA256": sha256
}
# Update aliases if package provides any alternatives
@@ -233,7 +296,7 @@ def extract_deb_file(deb_file, tmp_dir, extract_dir, ar_tool):
os.makedirs(extract_dir, exist_ok=True)
with tempfile.TemporaryDirectory(dir=tmp_dir) as tmp_subdir:
- result = subprocess.run(f"{ar_tool} t {os.path.abspath(deb_file)}", cwd=tmp_subdir, check=True, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ result = subprocess.run([ar_tool, "t", os.path.abspath(deb_file)], cwd=tmp_subdir, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
tar_filename = None
for line in result.stdout.decode().splitlines():
@@ -247,7 +310,8 @@ def extract_deb_file(deb_file, tmp_dir, extract_dir, ar_tool):
tar_file_path = os.path.join(tmp_subdir, tar_filename)
print(f"Extracting {tar_filename} from {deb_file}..")
- subprocess.run(f"{ar_tool} p {os.path.abspath(deb_file)} {tar_filename} > {tar_file_path}", check=True, shell=True)
+ with open(tar_file_path, "wb") as outfile:
+ subprocess.run([ar_tool, "p", os.path.abspath(deb_file), tar_filename], check=True, stdout=outfile, stderr=subprocess.PIPE)
file_extension = os.path.splitext(tar_file_path)[1].lower()
@@ -268,7 +332,18 @@ def extract_deb_file(deb_file, tmp_dir, extract_dir, ar_tool):
raise ValueError(f"Unsupported compression format: {file_extension}")
with tarfile.open(tar_file_path, mode) as tar:
- tar.extractall(path=extract_dir, filter='fully_trusted')
+ tar.extractall(path=extract_dir, filter=_rootfs_extraction_filter)
+
+def _rootfs_extraction_filter(member, dest_path):
+ """Tarfile extraction filter based on the 'data' filter that additionally
+ rewrites absolute-target symlinks/hardlinks into rootfs-relative paths.
+ """
+ if (member.issym() or member.islnk()) and os.path.isabs(member.linkname):
+ link_dir = os.path.dirname(member.name)
+ new_linkname = os.path.relpath(member.linkname.lstrip('/'),
+ start=link_dir or '.')
+ member = member.replace(linkname=new_linkname, deep=False)
+ return tarfile.data_filter(member, dest_path)
def finalize_setup(rootfsdir):
lib_dir = os.path.join(rootfsdir, 'lib')
@@ -295,24 +370,17 @@ def finalize_setup(rootfsdir):
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Generate rootfs for .NET runtime on Debian-like OS")
- parser.add_argument("--distro", required=False, help="Distro name (e.g., debian, ubuntu, etc.)")
parser.add_argument("--arch", required=True, help="Architecture (e.g., amd64, loong64, etc.)")
parser.add_argument("--rootfsdir", required=True, help="Destination directory.")
parser.add_argument('--suite', required=True, action='append', help='Specify one or more repository suites to collect index data.')
- parser.add_argument("--mirror", required=False, help="Mirror (e.g., http://ftp.debian.org/debian-ports etc.)")
+ parser.add_argument("--mirror", required=True, help="Mirror (e.g., http://ftp.debian.org/debian-ports etc.)")
parser.add_argument("--artool", required=False, default="ar", help="ar tool to extract debs (e.g., ar, llvm-ar etc.)")
+ parser.add_argument("--force-check-gpg", required=False, action='store_true', help="Verify the packages against signatures in Release file.")
+ parser.add_argument("--keyring", required=False, default='', help="Keyring file to check signature of Release file.")
parser.add_argument("packages", nargs="+", help="List of package names to be installed.")
args = parser.parse_args()
- if args.mirror is None:
- if args.distro == "ubuntu":
- args.mirror = "http://archive.ubuntu.com/ubuntu" if args.arch in ["amd64", "i386"] else "http://ports.ubuntu.com/ubuntu-ports"
- elif args.distro == "debian":
- args.mirror = "http://ftp.debian.org/debian-ports"
- else:
- raise Exception("Unsupported distro")
-
DESIRED_PACKAGES = args.packages + [ # base packages
"dpkg",
"busybox",
@@ -322,9 +390,16 @@ def finalize_setup(rootfsdir):
"debianutils"
]
- print(f"Creating rootfs. rootfsdir: {args.rootfsdir}, distro: {args.distro}, arch: {args.arch}, suites: {args.suite}, mirror: {args.mirror}")
+ print(f"Creating rootfs. rootfsdir: {args.rootfsdir}, arch: {args.arch}, suites: {args.suite}, mirror: {args.mirror}")
+
+ check_sig = args.force_check_gpg
+ if check_sig and not args.keyring:
+ print("ERROR: --force-check-gpg requires --keyring to specify a keyring file for signature verification.")
+ print("Install the appropriate keyring package (e.g., debian-ports-archive-keyring, ubuntu-archive-keyring)")
+ print("or pass --skipsigcheck to build-rootfs.sh to disable signature checking.")
+ sys.exit(1)
- package_index_content = asyncio.run(download_package_index_parallel(args.mirror, args.arch, args.suite))
+ package_index_content = asyncio.run(download_package_index_parallel(args.mirror, args.arch, args.suite, check_sig, args.keyring))
packages_info, aliases = parse_package_index(package_index_content)
diff --git a/eng/common/cross/toolchain.cmake b/eng/common/cross/toolchain.cmake
index ff2dfdb4a5..70b71395e3 100644
--- a/eng/common/cross/toolchain.cmake
+++ b/eng/common/cross/toolchain.cmake
@@ -59,9 +59,9 @@ elseif(TARGET_ARCH_NAME STREQUAL "arm64")
set(TIZEN_TOOLCHAIN "aarch64-tizen-linux-gnu")
endif()
elseif(FREEBSD)
- set(triple "aarch64-unknown-freebsd12")
+ set(TOOLCHAIN "aarch64-unknown-freebsd14")
elseif(OPENBSD)
- set(triple "aarch64-unknown-openbsd")
+ set(TOOLCHAIN "aarch64-unknown-openbsd")
endif()
elseif(TARGET_ARCH_NAME STREQUAL "armel")
set(CMAKE_SYSTEM_PROCESSOR armv7l)
@@ -87,6 +87,8 @@ elseif(TARGET_ARCH_NAME STREQUAL "ppc64le")
set(CMAKE_SYSTEM_PROCESSOR ppc64le)
if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/powerpc64le-alpine-linux-musl)
set(TOOLCHAIN "powerpc64le-alpine-linux-musl")
+ elseif(FREEBSD)
+ set(TOOLCHAIN "powerpc64le-unknown-freebsd14")
else()
set(TOOLCHAIN "powerpc64le-linux-gnu")
endif()
@@ -117,9 +119,9 @@ elseif(TARGET_ARCH_NAME STREQUAL "x64")
set(TIZEN_TOOLCHAIN "x86_64-tizen-linux-gnu")
endif()
elseif(FREEBSD)
- set(triple "x86_64-unknown-freebsd12")
+ set(TOOLCHAIN "x86_64-unknown-freebsd14")
elseif(OPENBSD)
- set(triple "x86_64-unknown-openbsd")
+ set(TOOLCHAIN "x86_64-unknown-openbsd")
elseif(ILLUMOS)
set(TOOLCHAIN "x86_64-illumos")
elseif(HAIKU)
@@ -160,8 +162,6 @@ if(TIZEN)
find_toolchain_dir("${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}")
endif()
- message(STATUS "TIZEN_TOOLCHAIN_PATH set to: ${TIZEN_TOOLCHAIN_PATH}")
-
include_directories(SYSTEM ${TIZEN_TOOLCHAIN_PATH}/include/c++)
include_directories(SYSTEM ${TIZEN_TOOLCHAIN_PATH}/include/c++/${TIZEN_TOOLCHAIN})
endif()
@@ -206,9 +206,9 @@ if(ANDROID)
include(${CROSS_ROOTFS}/../build/cmake/android.toolchain.cmake)
elseif(FREEBSD OR OPENBSD)
# we cross-compile by instructing clang
- set(CMAKE_C_COMPILER_TARGET ${triple})
- set(CMAKE_CXX_COMPILER_TARGET ${triple})
- set(CMAKE_ASM_COMPILER_TARGET ${triple})
+ set(CMAKE_C_COMPILER_TARGET ${TOOLCHAIN})
+ set(CMAKE_CXX_COMPILER_TARGET ${TOOLCHAIN})
+ set(CMAKE_ASM_COMPILER_TARGET ${TOOLCHAIN})
set(CMAKE_SYSROOT "${CROSS_ROOTFS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=lld")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fuse-ld=lld")
@@ -225,13 +225,19 @@ elseif(ILLUMOS)
locate_toolchain_exec(g++ CMAKE_CXX_COMPILER)
elseif(HAIKU)
set(CMAKE_SYSROOT "${CROSS_ROOTFS}")
- set(CMAKE_PROGRAM_PATH "${CMAKE_PROGRAM_PATH};${CROSS_ROOTFS}/cross-tools-x86_64/bin")
set(CMAKE_SYSTEM_PREFIX_PATH "${CROSS_ROOTFS}")
set(CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} -lssp")
set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lssp")
- locate_toolchain_exec(gcc CMAKE_C_COMPILER)
- locate_toolchain_exec(g++ CMAKE_CXX_COMPILER)
+ if ($ENV{CCC_CC} MATCHES ".*gcc.*")
+ set(CMAKE_PROGRAM_PATH "${CMAKE_PROGRAM_PATH};${CROSS_ROOTFS}/cross-tools-x86_64/bin")
+ locate_toolchain_exec(gcc CMAKE_C_COMPILER)
+ locate_toolchain_exec(g++ CMAKE_CXX_COMPILER)
+ else()
+ set(CMAKE_C_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/cross-tools-x86_64")
+ set(CMAKE_CXX_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/cross-tools-x86_64")
+ set(CMAKE_ASM_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/cross-tools-x86_64")
+ endif()
# let CMake set up the correct search paths
include(Platform/Haiku)
diff --git a/eng/common/dotnet-install.ps1 b/eng/common/dotnet-install.ps1
index 811f0f717f..b6d45f2bdc 100644
--- a/eng/common/dotnet-install.ps1
+++ b/eng/common/dotnet-install.ps1
@@ -4,13 +4,20 @@ Param(
[string] $architecture = '',
[string] $version = 'Latest',
[string] $runtime = 'dotnet',
+ [string] $dotnetPath = '',
[string] $RuntimeSourceFeed = '',
[string] $RuntimeSourceFeedKey = ''
)
. $PSScriptRoot\tools.ps1
-$dotnetRoot = Join-Path $RepoRoot '.dotnet'
+if (-not [string]::IsNullOrEmpty($dotnetPath)) {
+ $dotnetRoot = $dotnetPath
+} elseif (-not [string]::IsNullOrEmpty($env:DOTNET_GLOBAL_INSTALL_DIR)) {
+ $dotnetRoot = $env:DOTNET_GLOBAL_INSTALL_DIR
+} else {
+ $dotnetRoot = Join-Path $RepoRoot '.dotnet'
+}
$installdir = $dotnetRoot
try {
diff --git a/eng/common/dotnet-install.sh b/eng/common/dotnet-install.sh
index 61f302bb67..58a7e6f384 100755
--- a/eng/common/dotnet-install.sh
+++ b/eng/common/dotnet-install.sh
@@ -16,6 +16,7 @@ scriptroot="$( cd -P "$( dirname "$source" )" && pwd )"
version='Latest'
architecture=''
runtime='dotnet'
+dotnetPath=''
runtimeSourceFeed=''
runtimeSourceFeedKey=''
while [[ $# -gt 0 ]]; do
@@ -33,6 +34,10 @@ while [[ $# -gt 0 ]]; do
shift
runtime="$1"
;;
+ -dotnetpath)
+ shift
+ dotnetPath="$1"
+ ;;
-runtimesourcefeed)
shift
runtimeSourceFeed="$1"
@@ -80,7 +85,13 @@ case $cpuname in
;;
esac
-dotnetRoot="${repo_root}.dotnet"
+if [[ -n "${dotnetPath:-}" ]]; then
+ dotnetRoot="$dotnetPath"
+elif [[ -n "${DOTNET_GLOBAL_INSTALL_DIR:-}" ]]; then
+ dotnetRoot="$DOTNET_GLOBAL_INSTALL_DIR"
+else
+ dotnetRoot="${repo_root}.dotnet"
+fi
if [[ $architecture != "" ]] && [[ $architecture != $buildarch ]]; then
dotnetRoot="$dotnetRoot/$architecture"
fi
diff --git a/eng/common/dotnet.ps1 b/eng/common/dotnet.ps1
index 45e5676c9e..ce4ea40730 100644
--- a/eng/common/dotnet.ps1
+++ b/eng/common/dotnet.ps1
@@ -8,4 +8,5 @@ $dotnetRoot = InitializeDotNetCli -install:$true
if ($args.count -gt 0) {
$env:DOTNET_NOLOGO=1
& "$dotnetRoot\dotnet.exe" $args
+ ExitWithExitCode $LASTEXITCODE
}
diff --git a/eng/common/msbuild.ps1 b/eng/common/msbuild.ps1
index f041e5ddd9..495d533a90 100644
--- a/eng/common/msbuild.ps1
+++ b/eng/common/msbuild.ps1
@@ -14,7 +14,11 @@ Param(
try {
if ($ci) {
- $nodeReuse = $false
+ # 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 ($env:MSBUILD_NODEREUSE_ENABLED -ne "1") {
+ $nodeReuse = $false
+ }
}
MSBuild @extraArgs
diff --git a/eng/common/msbuild.sh b/eng/common/msbuild.sh
index 20d3dad543..333be3232f 100755
--- a/eng/common/msbuild.sh
+++ b/eng/common/msbuild.sh
@@ -51,7 +51,11 @@ done
. "$scriptroot/tools.sh"
if [[ "$ci" == true ]]; then
- node_reuse=false
+ # 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
+ node_reuse=false
+ fi
fi
MSBuild $extra_args
diff --git a/eng/common/native/LocateNativeCompiler.targets b/eng/common/native/LocateNativeCompiler.targets
new file mode 100644
index 0000000000..028b33d944
--- /dev/null
+++ b/eng/common/native/LocateNativeCompiler.targets
@@ -0,0 +1,27 @@
+
+
+
+
+ clang
+ $(ROOTFS_DIR)
+
+
+
+
+
+
+
+ $(_CC_LDFLAGS.SubString(0, $(_CC_LDFLAGS.IndexOf(';'))))
+ <_LDFLAGS>$(_CC_LDFLAGS.SubString($([MSBuild]::Add($(_CC_LDFLAGS.IndexOf(';')), 1))))
+ lld
+
+
+
diff --git a/eng/common/native/NativeAotSupported.props b/eng/common/native/NativeAotSupported.props
new file mode 100644
index 0000000000..cdff9ef036
--- /dev/null
+++ b/eng/common/native/NativeAotSupported.props
@@ -0,0 +1,28 @@
+
+
+
+
+ <_NativeAotSupportedOS Condition="
+ '$(TargetOS)' != 'browser' and
+ '$(TargetOS)' != 'haiku' and
+ '$(TargetOS)' != 'illumos' and
+ '$(TargetOS)' != 'netbsd' and
+ '$(TargetOS)' != 'solaris'
+ ">true
+
+
+ <_NativeAotSupportedArch Condition="
+ '$(TargetArchitecture)' != 'wasm' and
+ '$(TargetArchitecture)' != 's390x' and
+ '$(TargetArchitecture)' != 'ppc64le' and
+ ('$(TargetArchitecture)' != 'x86' or '$(TargetOS)' == 'windows')
+ ">true
+
+ true
+
+
+
diff --git a/eng/common/native/init-os-and-arch.sh b/eng/common/native/init-os-and-arch.sh
index 38921d4338..62d62fed52 100755
--- a/eng/common/native/init-os-and-arch.sh
+++ b/eng/common/native/init-os-and-arch.sh
@@ -27,6 +27,10 @@ if [ "$os" = "sunos" ]; then
os="solaris"
fi
CPUName=$(isainfo -n)
+elif [ "$os" = "freebsd" ]; then
+ # FreeBSD's `uname -m` is the machine class ("powerpc" for every PowerPC
+ # variant); `uname -p` gives the specific processor (e.g. powerpc64le).
+ CPUName=$(uname -p)
else
# For the rest of the operating systems, use uname(1) to determine what the CPU is.
CPUName=$(uname -m)
@@ -75,7 +79,7 @@ case "$CPUName" in
arch=s390x
;;
- ppc64le)
+ ppc64le|powerpc64le)
arch=ppc64le
;;
*)
diff --git a/eng/common/native/install-dependencies.sh b/eng/common/native/install-dependencies.sh
index 4742177a76..aff839fa09 100755
--- a/eng/common/native/install-dependencies.sh
+++ b/eng/common/native/install-dependencies.sh
@@ -24,16 +24,16 @@ case "$os" in
apt update
apt install -y build-essential gettext locales cmake llvm clang lld lldb liblldb-dev libunwind8-dev libicu-dev liblttng-ust-dev \
- libssl-dev libkrb5-dev pigz cpio ninja-build
+ libssl-dev libkrb5-dev pigz cpio ninja-build file
localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8
elif [ "$ID" = "fedora" ] || [ "$ID" = "rhel" ] || [ "$ID" = "azurelinux" ] || [ "$ID" = "centos" ]; then
pkg_mgr="$(command -v tdnf 2>/dev/null || command -v dnf)"
- $pkg_mgr install -y cmake llvm lld lldb clang python curl libicu-devel openssl-devel krb5-devel lttng-ust-devel pigz cpio ninja-build
+ $pkg_mgr install -y cmake llvm lld lldb clang python curl libicu-devel openssl-devel krb5-devel lttng-ust-devel pigz cpio ninja-build file
elif [ "$ID" = "amzn" ]; then
- dnf install -y cmake llvm lld lldb clang python libicu-devel openssl-devel krb5-devel lttng-ust-devel pigz cpio ninja-build
+ dnf install -y cmake llvm lld lldb clang python libicu-devel openssl-devel krb5-devel lttng-ust-devel pigz cpio ninja-build file
elif [ "$ID" = "alpine" ]; then
- apk add build-base cmake bash curl clang llvm llvm-dev lld lldb-dev krb5-dev lttng-ust-dev icu-dev openssl-dev pigz cpio ninja
+ apk add build-base cmake bash curl clang llvm llvm-dev lld lldb-dev krb5-dev lttng-ust-dev icu-dev openssl-dev pigz cpio ninja file
else
echo "Unsupported distro. distro: $ID"
exit 1
diff --git a/eng/common/pipeline-logging-functions.ps1 b/eng/common/pipeline-logging-functions.ps1
index 8e422c561e..9f85c29170 100644
--- a/eng/common/pipeline-logging-functions.ps1
+++ b/eng/common/pipeline-logging-functions.ps1
@@ -32,7 +32,7 @@ function Write-PipelineTelemetryError {
$PSBoundParameters.Remove('Category') | Out-Null
if ($Force -Or ((Test-Path variable:ci) -And $ci)) {
- $Message = "(NETCORE_ENGINEERING_TELEMETRY=$Category) $Message"
+ $Message = "($Category) $Message"
}
$PSBoundParameters.Remove('Message') | Out-Null
$PSBoundParameters.Add('Message', $Message)
diff --git a/eng/common/sdk-task.ps1 b/eng/common/sdk-task.ps1
index 64fd2f8abe..8d72d803dd 100644
--- a/eng/common/sdk-task.ps1
+++ b/eng/common/sdk-task.ps1
@@ -4,7 +4,9 @@ Param(
[string] $task,
[string] $verbosity = 'minimal',
[string] $msbuildEngine = $null,
- [switch] $restore,
+ # Restore defaults to on; -restore is retained only so existing consumers that pass it don't break. Use -norestore to opt out.
+ [switch] $restore = $true,
+ [switch] $norestore,
[switch] $prepareMachine,
[switch][Alias('nobl')]$excludeCIBinaryLog,
[switch]$noWarnAsError,
@@ -18,12 +20,23 @@ $ci = $true
$binaryLog = if ($excludeCIBinaryLog) { $false } else { $true }
$warnAsError = if ($noWarnAsError) { $false } else { $true }
+# Reconcile the restore state before importing tools.ps1: it reads $restore at import time to
+# decide whether toolset/SDK acquisition installs. -norestore must win so that skipping restore
+# also skips toolset initialization, not just the explicit Restore build below.
+if ($norestore) { $restore = $false }
+
+# sdk-task runs a standalone Arcade SDK task and does not need repo-specific toolset setup.
+# Skip importing configure-toolset.ps1 so its side effects (e.g. a repo's configure-toolset.ps1
+# calling exit) don't terminate this script before the task runs.
+$disableConfigureToolsetImport = $true
+
. $PSScriptRoot\tools.ps1
function Print-Usage() {
Write-Host "Common settings:"
- Write-Host " -task Name of Arcade task (name of a project in SdkTasks directory of the Arcade SDK package)"
- Write-Host " -restore Restore dependencies"
+ Write-Host " -task Name of Arcade task (name of a project in toolset directory of the Arcade SDK package)"
+ Write-Host " -restore (Legacy) Restore runs by default; retained for backward compatibility. Use -norestore to skip"
+ Write-Host " -norestore Skip restoring dependencies"
Write-Host " -verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic]"
Write-Host " -help Print help and exit"
Write-Host ""
diff --git a/eng/common/sdk-task.sh b/eng/common/sdk-task.sh
index 3270f83fa9..a7f1ba060d 100644
--- a/eng/common/sdk-task.sh
+++ b/eng/common/sdk-task.sh
@@ -2,8 +2,9 @@
show_usage() {
echo "Common settings:"
- echo " --task Name of Arcade task (name of a project in SdkTasks directory of the Arcade SDK package)"
- echo " --restore Restore dependencies"
+ echo " --task Name of Arcade task (name of a project in toolset directory of the Arcade SDK package)"
+ echo " --restore (Legacy) Restore runs by default; retained for backward compatibility. Use --norestore to skip"
+ echo " --norestore Skip restoring dependencies"
echo " --verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic]"
echo " --help Print help and exit"
echo ""
@@ -50,10 +51,11 @@ binary_log=true
configuration="Debug"
verbosity="minimal"
exclude_ci_binary_log=false
-restore=false
+# restore defaults to on; --restore is retained only so existing consumers that pass it don't break. Use --norestore to opt out.
+restore=true
help=false
properties=''
-warnAsError=true
+warn_as_error=true
while (($# > 0)); do
lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")"
@@ -63,7 +65,10 @@ while (($# > 0)); do
shift 2
;;
--restore)
- restore=true
+ shift 1
+ ;;
+ --norestore)
+ restore=false
shift 1
;;
--verbosity)
@@ -75,8 +80,8 @@ while (($# > 0)); do
exclude_ci_binary_log=true
shift 1
;;
- --noWarnAsError)
- warnAsError=false
+ --nowarnaserror)
+ warn_as_error=false
shift 1
;;
--help)
@@ -97,6 +102,11 @@ if $help; then
exit 0
fi
+# sdk-task runs a standalone Arcade SDK task and does not need repo-specific toolset setup.
+# Skip importing configure-toolset.sh so its side effects (e.g. a repo's configure-toolset.sh
+# calling exit) don't terminate this script before the task runs.
+disable_configure_toolset_import=1
+
. "$scriptroot/tools.sh"
InitializeToolset
diff --git a/eng/common/templates/job/job.yml b/eng/common/templates/job/job.yml
index 5e261f34db..85501406a5 100644
--- a/eng/common/templates/job/job.yml
+++ b/eng/common/templates/job/job.yml
@@ -21,11 +21,6 @@ jobs:
- ${{ each step in parameters.steps }}:
- ${{ step }}
- # we don't run CG in public
- - ${{ if eq(variables['System.TeamProject'], 'public') }}:
- - script: echo "##vso[task.setvariable variable=skipComponentGovernanceDetection]true"
- displayName: Set skipComponentGovernanceDetection variable
-
artifactPublishSteps:
- ${{ if ne(parameters.artifacts.publish, '') }}:
- ${{ if and(ne(parameters.artifacts.publish.artifacts, 'false'), ne(parameters.artifacts.publish.artifacts, '')) }}:
diff --git a/eng/common/templates/steps/vmr-sync.yml b/eng/common/templates/steps/vmr-sync.yml
index eb619c5026..cdc6a28ff1 100644
--- a/eng/common/templates/steps/vmr-sync.yml
+++ b/eng/common/templates/steps/vmr-sync.yml
@@ -45,11 +45,11 @@ steps:
workingDirectory: ${{ parameters.vmrPath }}
- script: |
- ./eng/common/vmr-sync.sh \
- --vmr ${{ parameters.vmrPath }} \
- --tmp $(Agent.TempDirectory) \
- --azdev-pat '$(dn-bot-all-orgs-code-r)' \
- --ci \
+ ./eng/common/vmr-sync.sh \
+ --vmr ${{ parameters.vmrPath }} \
+ --tmp $(Agent.TempDirectory) \
+ --azdev-pat '$(AzdoToken)' \
+ --ci \
--debug
if [ "$?" -ne 0 ]; then
@@ -67,11 +67,11 @@ steps:
condition: eq(variables['Agent.OS'], 'Windows_NT')
- powershell: |
- ./eng/common/vmr-sync.ps1 `
- -vmr ${{ parameters.vmrPath }} `
- -tmp $(Agent.TempDirectory) `
- -azdevPat '$(dn-bot-all-orgs-code-r)' `
- -ci `
+ ./eng/common/vmr-sync.ps1 `
+ -vmr ${{ parameters.vmrPath }} `
+ -tmp $(Agent.TempDirectory) `
+ -azdevPat '$(AzdoToken)' `
+ -ci `
-debugOutput
if ($LASTEXITCODE -ne 0) {
diff --git a/eng/common/templates/vmr-build-pr.yml b/eng/common/templates/vmr-build-pr.yml
index 2f3694fa13..d24de93524 100644
--- a/eng/common/templates/vmr-build-pr.yml
+++ b/eng/common/templates/vmr-build-pr.yml
@@ -33,7 +33,7 @@ resources:
- repository: vmr
type: github
name: dotnet/dotnet
- endpoint: dotnet
+ endpoint: public
ref: refs/heads/main # Set to whatever VMR branch the PR build should insert into
stages:
diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1
index e28db6c7c8..ebc31f7ecd 100644
--- a/eng/common/tools.ps1
+++ b/eng/common/tools.ps1
@@ -15,7 +15,7 @@
# Set to true to use the pipelines logger which will enable Azure logging output.
# https://github.com/Microsoft/azure-pipelines-tasks/blob/master/docs/authoring/commands.md
-# This flag is meant as a temporary opt-opt for the feature while validate it across
+# This flag is meant as a temporary opt-in for the feature while validating it across
# our consumers. It will be deleted in the future.
[bool]$pipelinesLog = if (Test-Path variable:pipelinesLog) { $pipelinesLog } else { $ci }
@@ -71,6 +71,8 @@ $ErrorActionPreference = 'Stop'
# True when the build is running within the VMR.
[bool]$fromVMR = if (Test-Path variable:fromVMR) { $fromVMR } else { $false }
+[bool]$disablePipelineSetResult = if (Test-Path variable:disablePipelineSetResult) { $disablePipelineSetResult } else { $false }
+
function Create-Directory ([string[]] $path) {
New-Item -Path $path -Force -ItemType 'Directory' | Out-Null
}
@@ -438,11 +440,31 @@ function InitializeVisualStudioMSBuild([object]$vsRequirements = $null) {
$msbuildVersionDir = if ([int]$vsMajorVersion -lt 16) { "$vsMajorVersion.0" } else { "Current" }
$local:BinFolder = Join-Path $vsInstallDir "MSBuild\$msbuildVersionDir\Bin"
- $local:Prefer64bit = if (Get-Member -InputObject $vsRequirements -Name 'Prefer64bit') { $vsRequirements.Prefer64bit } else { $false }
- if ($local:Prefer64bit -and (Test-Path(Join-Path $local:BinFolder "amd64"))) {
- $global:_MSBuildExe = Join-Path $local:BinFolder "amd64\msbuild.exe"
- } else {
- $global:_MSBuildExe = Join-Path $local:BinFolder "msbuild.exe"
+
+ # Use the MSBuild matching the host's process architecture (e.g. amd64 or arm64),
+ # falling back to the 32-bit MSBuild in the root Bin folder when no matching subfolder exists.
+
+ # Determine the architecture of the current process, accounting for a 32-bit process
+ # running on a 64-bit OS (PROCESSOR_ARCHITEW6432 holds the real machine architecture).
+ $local:ProcessArchitecture = $env:PROCESSOR_ARCHITECTURE
+ if (($local:ProcessArchitecture -eq 'x86') -and ($env:PROCESSOR_ARCHITEW6432)) {
+ $local:ProcessArchitecture = $env:PROCESSOR_ARCHITEW6432
+ }
+
+ # Map the architecture to the corresponding MSBuild subfolder. The 32-bit MSBuild lives in the
+ # root Bin folder, so x86 maps to an empty subfolder.
+ $local:MSBuildArchSubFolder = switch ($local:ProcessArchitecture) {
+ 'AMD64' { 'amd64' }
+ 'ARM64' { 'arm64' }
+ default { '' }
+ }
+
+ $global:_MSBuildExe = Join-Path $local:BinFolder "msbuild.exe"
+ if ($local:MSBuildArchSubFolder) {
+ $local:ArchMSBuildExe = Join-Path $local:BinFolder (Join-Path $local:MSBuildArchSubFolder "msbuild.exe")
+ if (Test-Path $local:ArchMSBuildExe) {
+ $global:_MSBuildExe = $local:ArchMSBuildExe
+ }
}
return $global:_MSBuildExe
@@ -480,7 +502,6 @@ function LocateVisualStudio([object]$vsRequirements = $null){
if (Get-Member -InputObject $GlobalJson.tools -Name 'vswhere') {
$vswhereVersion = $GlobalJson.tools.vswhere
} else {
- # keep this in sync with the VSWhereVersion in DefaultVersions.props
$vswhereVersion = '3.1.7'
}
@@ -538,6 +559,16 @@ function LocateVisualStudio([object]$vsRequirements = $null){
}
function InitializeBuildTool() {
+ # Allow a caller (e.g. a bootstrap script running out-of-proc) to inject the build tool via
+ # environment variables instead of the in-proc $global:_BuildTool variable. Only Path and
+ # Command are consumed by the MSBuild function below, so those are all that's needed.
+ if ($env:_BuildToolPath) {
+ return $global:_BuildTool = @{
+ Path = $env:_BuildToolPath
+ Command = $env:_BuildToolCommand
+ }
+ }
+
if (Test-Path variable:global:_BuildTool) {
# If the requested msbuild parameters do not match, clear the cached variables.
if($global:_BuildTool.Contains('ExcludePrereleaseVS') -and $global:_BuildTool.ExcludePrereleaseVS -ne $excludePrereleaseVS) {
@@ -565,7 +596,7 @@ function InitializeBuildTool() {
}
$dotnetPath = Join-Path $dotnetRoot (GetExecutableFileName 'dotnet')
- $buildTool = @{ Path = $dotnetPath; Command = 'msbuild'; Tool = 'dotnet'; Framework = 'net' }
+ $buildTool = @{ Path = $dotnetPath; Command = 'msbuild' }
} elseif ($msbuildEngine -eq "vs") {
try {
$msbuildPath = InitializeVisualStudioMSBuild
@@ -574,7 +605,7 @@ function InitializeBuildTool() {
ExitWithExitCode 1
}
- $buildTool = @{ Path = $msbuildPath; Command = ""; Tool = "vs"; Framework = "netframework"; ExcludePrereleaseVS = $excludePrereleaseVS }
+ $buildTool = @{ Path = $msbuildPath; Command = ""; ExcludePrereleaseVS = $excludePrereleaseVS }
} else {
Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Unexpected value of -msbuildEngine: '$msbuildEngine'."
ExitWithExitCode 1
@@ -597,16 +628,16 @@ function GetDefaultMSBuildEngine() {
ExitWithExitCode 1
}
-function GetNuGetPackageCachePath() {
+function InitializeNuGetPackageCachePath() {
if ($env:NUGET_PACKAGES -eq $null) {
# Use local cache on CI to ensure deterministic build.
- # Avoid using the http cache as workaround for https://github.com/NuGet/Home/issues/3116
# use global cache in dev builds to avoid cost of downloading packages.
# For directory normalization, see also: https://github.com/NuGet/Home/issues/7968
if ($useGlobalNuGetCache) {
- $env:NUGET_PACKAGES = Join-Path $env:UserProfile '.nuget\packages\'
+ $userProfile = if (IsWindowsPlatform) { $env:UserProfile } else { $env:HOME }
+ $env:NUGET_PACKAGES = [IO.Path]::Combine($userProfile, '.nuget', 'packages') + [IO.Path]::DirectorySeparatorChar
} else {
- $env:NUGET_PACKAGES = Join-Path $RepoRoot '.packages\'
+ $env:NUGET_PACKAGES = [IO.Path]::Combine($RepoRoot, '.packages') + [IO.Path]::DirectorySeparatorChar
}
}
@@ -615,7 +646,13 @@ function GetNuGetPackageCachePath() {
# Returns a full path to an Arcade SDK task project file.
function GetSdkTaskProject([string]$taskName) {
- return Join-Path (Split-Path (InitializeToolset) -Parent) "SdkTasks\$taskName.proj"
+ $toolsetDir = Split-Path (InitializeToolset) -Parent
+ $proj = Join-Path $toolsetDir "$taskName.proj"
+ if (Test-Path $proj) {
+ return $proj
+ }
+
+ throw "Unable to find $taskName.proj in toolset at: $toolsetDir"
}
function InitializeNativeTools() {
@@ -649,16 +686,19 @@ function InitializeToolset() {
return $global:_InitializeToolset
}
- $nugetCache = GetNuGetPackageCachePath
-
$toolsetVersion = Read-ArcadeSdkVersion
- $toolsetLocationFile = Join-Path $ToolsetDir "$toolsetVersion.txt"
+ $toolsetToolsDir = Join-Path $ToolsetDir $toolsetVersion
- if (Test-Path $toolsetLocationFile) {
- $path = Get-Content $toolsetLocationFile -TotalCount 1
- if (Test-Path $path) {
- return $global:_InitializeToolset = $path
- }
+ # Check if the toolset has already been extracted
+ $toolsetBuildProj = $null
+ $buildProjPath = Join-Path $toolsetToolsDir 'Build.proj'
+
+ if (Test-Path $buildProjPath) {
+ $toolsetBuildProj = $buildProjPath
+ }
+
+ if ($toolsetBuildProj -ne $null) {
+ return $global:_InitializeToolset = $toolsetBuildProj
}
if (-not $restore) {
@@ -666,25 +706,55 @@ function InitializeToolset() {
ExitWithExitCode 1
}
- $buildTool = InitializeBuildTool
+ $downloadArgs = @("package", "download", "Microsoft.DotNet.Arcade.Sdk@$toolsetVersion", "--verbosity", "minimal", "--prerelease", "--output", "$nugetPackageCachePath")
+ $nugetConfig = $env:NUGET_CONFIG
+ if (-not $nugetConfig) {
+ # Search for any variation of nuget.config in the RepoRoot
+ $configFile = Get-ChildItem -Path $RepoRoot -File | Where-Object { $_.Name -ieq "nuget.config" } | Select-Object -First 1
+
+ if ($configFile) {
+ $nugetConfig = $configFile.FullName
+ }
+ }
- $proj = Join-Path $ToolsetDir 'restore.proj'
- $bl = if ($binaryLog) { '/bl:' + (Join-Path $LogDir 'ToolsetRestore.binlog') } else { '' }
+ if ($nugetConfig) {
+ $downloadArgs += "--configfile"
+ $downloadArgs += $nugetConfig
+ }
- '' | Set-Content $proj
+ # 'dotnet package download' fails outright if any source in the repo's NuGet.config is
+ # unavailable (for example a transport feed that was decommissioned after a release). The
+ # Arcade SDK is always published to the public dotnet-eng feed, so if the config-driven
+ # download fails, retry once against that feed directly (which ignores the other sources)
+ # before giving up, so a single dead source doesn't block the build.
+ $downloadExitCode = DotNet -ignoreFailure @downloadArgs
+ if ($downloadExitCode) {
+ Write-Host "Restoring the Arcade SDK from the configured sources failed; retrying from the public dotnet-eng feed."
+ DotNet @downloadArgs --source "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json"
+ }
- MSBuild-Core $proj $bl /t:__WriteToolsetLocation /clp:ErrorsOnly`;NoSummary /p:__ToolsetLocationOutputFile=$toolsetLocationFile
+ $packageDir = Join-Path $nugetPackageCachePath (Join-Path 'microsoft.dotnet.arcade.sdk' $toolsetVersion)
+ $packageToolsetDir = Join-Path $packageDir 'toolset'
- $path = Get-Content $toolsetLocationFile -Encoding UTF8 -TotalCount 1
- if (!(Test-Path $path)) {
- throw "Invalid toolset path: $path"
+ if (!(Test-Path $packageToolsetDir)) {
+ Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Arcade SDK package does not contain a toolset or tools folder: $packageDir"
+ ExitWithExitCode 3
}
- return $global:_InitializeToolset = $path
+ New-Item -ItemType Directory -Path $toolsetToolsDir -Force | Out-Null
+ Copy-Item -Path "$packageToolsetDir\*" -Destination $toolsetToolsDir -Recurse -Force
+
+ if (Test-Path $buildProjPath) {
+ $toolsetBuildProj = $buildProjPath
+ } else {
+ throw "Unable to find Build.proj in toolset at: $toolsetToolsDir"
+ }
+
+ return $global:_InitializeToolset = $toolsetBuildProj
}
function ExitWithExitCode([int] $exitCode) {
- if ($ci -and $prepareMachine) {
+ if ($prepareMachine) {
Stop-Processes
}
exit $exitCode
@@ -714,55 +784,28 @@ function Stop-Processes() {
# Terminates the script if the build fails.
#
function MSBuild() {
- if ($pipelinesLog) {
- $buildTool = InitializeBuildTool
-
- if ($ci -and $buildTool.Tool -eq 'dotnet') {
- $env:NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS = 20
- $env:NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS = 20
- Write-PipelineSetVariable -Name 'NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS' -Value '20'
- Write-PipelineSetVariable -Name 'NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS' -Value '20'
- }
-
- Enable-Nuget-EnhancedRetry
-
- $toolsetBuildProject = InitializeToolset
- $basePath = Split-Path -parent $toolsetBuildProject
- $selectedPath = Join-Path $basePath (Join-Path $buildTool.Framework 'Microsoft.DotNet.ArcadeLogging.dll')
-
- if (-not $selectedPath) {
- Write-PipelineTelemetryError -Category 'Build' -Message "Unable to find arcade sdk logger assembly: $selectedPath"
- ExitWithExitCode 1
- }
-
- $args += "/logger:$selectedPath"
- }
-
- MSBuild-Core @args
-}
-
-#
-# Executes msbuild (or 'dotnet msbuild') with arguments passed to the function.
-# The arguments are automatically quoted.
-# Terminates the script if the build fails.
-#
-function MSBuild-Core() {
if ($ci) {
if (!$binaryLog -and !$excludeCIBinarylog) {
Write-PipelineTelemetryError -Category 'Build' -Message 'Binary log must be enabled in CI build, or explicitly opted-out from with the -excludeCIBinarylog switch.'
ExitWithExitCode 1
}
-
- if ($nodeReuse) {
- Write-PipelineTelemetryError -Category 'Build' -Message 'Node reuse must be disabled in CI build.'
- ExitWithExitCode 1
- }
}
- Enable-Nuget-EnhancedRetry
-
$buildTool = InitializeBuildTool
+ if ($pipelinesLog) {
+ $toolsetBuildProject = InitializeToolset
+ $basePath = Split-Path -parent $toolsetBuildProject
+ $selectedPath = Join-Path $basePath (Join-Path 'net' 'Microsoft.DotNet.ArcadeLogging.dll')
+
+ # Only inject the logger when it's present. A last-known-good Arcade used to bootstrap
+ # the build may not ship the logger yet, so its absence must not be a hard error.
+ # Specify the logger type explicitly so loading is deterministic.
+ if (Test-Path $selectedPath) {
+ $args += "/logger:Microsoft.DotNet.ArcadeLogging.PipelinesLogger,$selectedPath"
+ }
+ }
+
$cmdArgs = "$($buildTool.Command) /m /nologo /clp:Summary /v:$verbosity /nr:$nodeReuse /p:ContinuousIntegrationBuild=$ci"
# Add -mt flag for MSBuild multithreaded mode if enabled via environment variable
@@ -800,14 +843,9 @@ function MSBuild-Core() {
# The build already logged an error, that's the reason it failed. Producing an error here only adds noise.
Write-Host "Build failed with exit code $exitCode. Check errors above." -ForegroundColor Red
- $buildLog = GetMSBuildBinaryLogCommandLineArgument $args
- if ($null -ne $buildLog) {
- Write-Host "See log: $buildLog" -ForegroundColor DarkGray
- }
-
# When running on Azure Pipelines, override the returned exit code to avoid double logging.
- # Skip this when the build is a child of the VMR build.
- if ($ci -and $env:SYSTEM_TEAMPROJECT -ne $null -and !$fromVMR) {
+ # Skip this when the build is a child of the VMR build, or when -disablePipelineSetResult is set so the real exit code propagates.
+ if ($ci -and $env:SYSTEM_TEAMPROJECT -ne $null -and !$fromVMR -and !$disablePipelineSetResult) {
Write-PipelineSetResult -Result "Failed" -Message "msbuild execution failed."
# Exiting with an exit code causes the azure pipelines task to log yet another "noise" error
# The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error
@@ -818,21 +856,44 @@ function MSBuild-Core() {
}
}
-function GetMSBuildBinaryLogCommandLineArgument($arguments) {
- foreach ($argument in $arguments) {
- if ($argument -ne $null) {
- $arg = $argument.Trim()
- if ($arg.StartsWith('/bl:', "OrdinalIgnoreCase")) {
- return $arg.Substring('/bl:'.Length)
- }
+#
+# Executes a dotnet command with arguments passed to the function.
+# Terminates the script if the command fails.
+#
+function DotNet([switch]$ignoreFailure) {
+ $dotnetRoot = InitializeDotNetCli -install:$restore
+ $dotnetPath = Join-Path $dotnetRoot (GetExecutableFileName 'dotnet')
- if ($arg.StartsWith('/binaryLogger:', 'OrdinalIgnoreCase')) {
- return $arg.Substring('/binaryLogger:'.Length)
+ $cmdArgs = ""
+ foreach ($arg in $args) {
+ if ($null -ne $arg -and $arg.Trim() -ne "") {
+ if ($arg.EndsWith('\')) {
+ $arg = $arg + "\"
}
+ $cmdArgs += " `"$arg`""
}
}
- return $null
+ $env:ARCADE_BUILD_TOOL_COMMAND = "`"$dotnetPath`" $cmdArgs"
+
+ $exitCode = Exec-Process $dotnetPath $cmdArgs
+
+ if ($exitCode -ne 0) {
+ # When -ignoreFailure is set, return the exit code to the caller so it can implement
+ # its own fallback logic instead of terminating the script.
+ if ($ignoreFailure) {
+ return $exitCode
+ }
+
+ Write-Host "dotnet command failed with exit code $exitCode. Check errors above." -ForegroundColor Red
+
+ if ($ci -and $env:SYSTEM_TEAMPROJECT -ne $null -and !$fromVMR -and !$disablePipelineSetResult) {
+ Write-PipelineSetResult -Result "Failed" -Message "dotnet command execution failed."
+ ExitWithExitCode 0
+ } else {
+ ExitWithExitCode $exitCode
+ }
+ }
}
function GetExecutableFileName($baseName) {
@@ -875,6 +936,12 @@ Create-Directory $ToolsetDir
Create-Directory $TempDir
Create-Directory $LogDir
+# Direct MSBuild crash diagnostics (MSB4166 failure.txt files) to a known location
+# under artifacts/log so they are captured as build artifacts in CI.
+if (-not $env:MSBUILDDEBUGPATH) {
+ $env:MSBUILDDEBUGPATH = Join-Path $LogDir 'MsbuildDebugLogs'
+}
+
Write-PipelineSetVariable -Name 'Artifacts' -Value $ArtifactsDir
Write-PipelineSetVariable -Name 'Artifacts.Toolset' -Value $ToolsetDir
Write-PipelineSetVariable -Name 'Artifacts.Log' -Value $LogDir
@@ -896,19 +963,5 @@ if (!$disableConfigureToolsetImport) {
}
}
-#
-# If $ci flag is set, turn on (and log that we did) special environment variables for improved Nuget client retry logic.
-#
-function Enable-Nuget-EnhancedRetry() {
- if ($ci) {
- Write-Host "Setting NUGET enhanced retry environment variables"
- $env:NUGET_ENABLE_ENHANCED_HTTP_RETRY = 'true'
- $env:NUGET_ENHANCED_MAX_NETWORK_TRY_COUNT = 6
- $env:NUGET_ENHANCED_NETWORK_RETRY_DELAY_MILLISECONDS = 1000
- $env:NUGET_RETRY_HTTP_429 = 'true'
- Write-PipelineSetVariable -Name 'NUGET_ENABLE_ENHANCED_HTTP_RETRY' -Value 'true'
- Write-PipelineSetVariable -Name 'NUGET_ENHANCED_MAX_NETWORK_TRY_COUNT' -Value '6'
- Write-PipelineSetVariable -Name 'NUGET_ENHANCED_NETWORK_RETRY_DELAY_MILLISECONDS' -Value '1000'
- Write-PipelineSetVariable -Name 'NUGET_RETRY_HTTP_429' -Value 'true'
- }
-}
+# Initialize the nuget package cache vars
+$nugetPackageCachePath = InitializeNuGetPackageCachePath
diff --git a/eng/common/tools.sh b/eng/common/tools.sh
index 1e37fd95b2..cd31d8a0a0 100755
--- a/eng/common/tools.sh
+++ b/eng/common/tools.sh
@@ -10,7 +10,7 @@ source_build=${source_build:-false}
# Set to true to use the pipelines logger which will enable Azure logging output.
# https://github.com/Microsoft/azure-pipelines-tasks/blob/master/docs/authoring/commands.md
-# This flag is meant as a temporary opt-opt for the feature while validate it across
+# This flag is meant as a temporary opt-in for the feature while validating it across
# our consumers. It will be deleted in the future.
if [[ "$ci" == true ]]; then
pipelines_log=${pipelines_log:-true}
@@ -78,6 +78,8 @@ runtime_source_feed_key=${runtime_source_feed_key:-''}
# True when the build is running within the VMR.
from_vmr=${from_vmr:-false}
+disable_pipeline_set_result=${disable_pipeline_set_result:-false}
+
# Resolve any symlinks in the given path.
function ResolvePath {
local path=$1
@@ -363,6 +365,15 @@ function GetDotNetInstallScript {
}
function InitializeBuildTool {
+ # Allow a caller (e.g. a bootstrap script running out-of-proc) to inject the build tool via
+ # environment variables instead of the in-proc _InitializeBuildTool variable. Only the tool path and
+ # command are consumed by the MSBuild function below, so those are all that's needed.
+ if [[ -n "${_BuildToolPath:-}" ]]; then
+ _InitializeBuildTool="$_BuildToolPath"
+ _InitializeBuildToolCommand="$_BuildToolCommand"
+ return
+ fi
+
if [[ -n "${_InitializeBuildTool:-}" ]]; then
return
fi
@@ -374,7 +385,7 @@ function InitializeBuildTool {
_InitializeBuildToolCommand="msbuild"
}
-function GetNuGetPackageCachePath {
+function InitializeNuGetPackageCachePath {
if [[ -z ${NUGET_PACKAGES:-} ]]; then
if [[ "$use_global_nuget_cache" == true ]]; then
export NUGET_PACKAGES="$HOME/.nuget/packages/"
@@ -384,7 +395,7 @@ function GetNuGetPackageCachePath {
fi
# return value
- _GetNuGetPackageCachePath=$NUGET_PACKAGES
+ _InitializeNuGetPackageCachePath=$NUGET_PACKAGES
}
function InitializeNativeTools() {
@@ -406,20 +417,21 @@ function InitializeToolset {
return
fi
- GetNuGetPackageCachePath
-
ReadGlobalVersion "Microsoft.DotNet.Arcade.Sdk"
local toolset_version=$_ReadGlobalVersion
- local toolset_location_file="$toolset_dir/$toolset_version.txt"
+ local toolset_tools_dir="$toolset_dir/$toolset_version"
- if [[ -a "$toolset_location_file" ]]; then
- local path=`cat "$toolset_location_file"`
- if [[ -a "$path" ]]; then
- # return value
- _InitializeToolset="$path"
- return
- fi
+ # Check if the toolset has already been extracted
+ local toolset_build_proj=""
+ if [[ -a "$toolset_tools_dir/Build.proj" ]]; then
+ toolset_build_proj="$toolset_tools_dir/Build.proj"
+ fi
+
+ if [[ -n "$toolset_build_proj" ]]; then
+ # return value
+ _InitializeToolset="$toolset_build_proj"
+ return
fi
if [[ "$restore" != true ]]; then
@@ -427,20 +439,46 @@ function InitializeToolset {
ExitWithExitCode 2
fi
- local proj="$toolset_dir/restore.proj"
+ local download_args=("package" "download" "Microsoft.DotNet.Arcade.Sdk@$toolset_version" "--verbosity" "minimal" "--prerelease" "--output" "$_InitializeNuGetPackageCachePath")
+ local nuget_config="${NUGET_CONFIG:-}"
+ if [[ -z "$nuget_config" ]]; then
+ # Search for any variation of nuget.config in the RepoRoot
+ local found_config
+ found_config=$(find "$repo_root" -maxdepth 1 -type f -iname nuget.config | head -n 1)
- local bl=""
- if [[ "$binary_log" == true ]]; then
- bl="/bl:$log_dir/ToolsetRestore.binlog"
+ if [[ -n "$found_config" ]]; then
+ nuget_config="$found_config"
+ fi
fi
- echo '' > "$proj"
- MSBuild-Core "$proj" $bl /t:__WriteToolsetLocation /clp:ErrorsOnly\;NoSummary /p:__ToolsetLocationOutputFile="$toolset_location_file"
+ if [[ -n "$nuget_config" ]]; then
+ download_args+=("--configfile" "$nuget_config")
+ fi
+
+ # 'dotnet package download' fails outright if any source in the repo's NuGet.config is
+ # unavailable (for example a transport feed that was decommissioned after a release). The
+ # Arcade SDK is always published to the public dotnet-eng feed, so if the config-driven
+ # download fails, retry once against that feed directly (which ignores the other sources)
+ # before giving up, so a single dead source doesn't block the build.
+ if ! DotNet true "${download_args[@]}"; then
+ echo "Restoring the Arcade SDK from the configured sources failed; retrying from the public dotnet-eng feed."
+ DotNet "${download_args[@]}" --source "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json"
+ fi
- local toolset_build_proj=`cat "$toolset_location_file"`
+ local package_dir="$_InitializeNuGetPackageCachePath/microsoft.dotnet.arcade.sdk/$toolset_version"
- if [[ ! -a "$toolset_build_proj" ]]; then
- Write-PipelineTelemetryError -category 'Build' "Invalid toolset path: $toolset_build_proj"
+ if [[ ! -d "$package_dir/toolset" ]]; then
+ Write-PipelineTelemetryError -category 'InitializeToolset' "Arcade SDK package does not contain a toolset folder: $package_dir"
+ ExitWithExitCode 3
+ fi
+
+ mkdir -p "$toolset_tools_dir"
+ cp -r "$package_dir/toolset/." "$toolset_tools_dir"
+
+ if [[ -a "$toolset_tools_dir/Build.proj" ]]; then
+ toolset_build_proj="$toolset_tools_dir/Build.proj"
+ else
+ Write-PipelineTelemetryError -category 'Build' "Unable to find Build.proj in toolset at: $toolset_tools_dir"
ExitWithExitCode 3
fi
@@ -449,7 +487,7 @@ function InitializeToolset {
}
function ExitWithExitCode {
- if [[ "$ci" == true && "$prepare_machine" == true ]]; then
+ if [[ "$prepare_machine" == true ]]; then
StopProcesses
fi
exit $1
@@ -458,52 +496,70 @@ function ExitWithExitCode {
function StopProcesses {
echo "Killing running build processes..."
pkill -9 "dotnet" || true
- pkill -9 "vbcscompiler" || true
+ pkill -9 -i -x VBCSCompiler || true
+ pkill -9 -i -x MSBuild || true
return 0
}
-function MSBuild {
- local args=( "$@" )
- if [[ "$pipelines_log" == true ]]; then
- InitializeBuildTool
- InitializeToolset
+function DotNet {
+ # When the first argument is 'true' or 'false' it controls the exit behavior on failure:
+ # 'true' returns the dotnet exit code to the caller (so it can implement its own fallback),
+ # while the default terminates the script. Any other first argument is treated as a dotnet argument.
+ local ignore_failure=false
+ if [[ "$1" == 'true' || "$1" == 'false' ]]; then
+ ignore_failure="$1"
+ shift
+ fi
- if [[ "$ci" == true ]]; then
- export NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS=20
- export NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS=20
- Write-PipelineSetVariable -name "NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS" -value "20"
- Write-PipelineSetVariable -name "NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS" -value "20"
- fi
+ InitializeDotNetCli $restore
- local toolset_dir="${_InitializeToolset%/*}"
- local selectedPath="$toolset_dir/net/Microsoft.DotNet.ArcadeLogging.dll"
+ local dotnet_path="$_InitializeDotNetCli/dotnet"
- if [[ -z "$selectedPath" ]]; then
- Write-PipelineTelemetryError -category 'Build' "Unable to find arcade sdk logger assembly: $selectedPath"
- ExitWithExitCode 1
+ export ARCADE_BUILD_TOOL_COMMAND="$dotnet_path $@"
+
+ "$dotnet_path" "$@" || {
+ local exit_code=$?
+
+ if [[ "$ignore_failure" == true ]]; then
+ return $exit_code
fi
- args+=( "-logger:$selectedPath" )
- fi
+ echo "dotnet command failed with exit code $exit_code. Check errors above."
- MSBuild-Core "${args[@]}"
+ if [[ "$ci" == true && -n ${SYSTEM_TEAMPROJECT:-} && "$from_vmr" != true && "$disable_pipeline_set_result" != true ]]; then
+ Write-PipelineSetResult -result "Failed" -message "dotnet command execution failed."
+ ExitWithExitCode 0
+ else
+ ExitWithExitCode $exit_code
+ fi
+ }
}
-function MSBuild-Core {
+function MSBuild {
if [[ "$ci" == true ]]; then
if [[ "$binary_log" != true && "$exclude_ci_binary_log" != true ]]; then
- Write-PipelineTelemetryError -category 'Build' "Binary log must be enabled in CI build, or explicitly opted-out from with the -noBinaryLog switch."
- ExitWithExitCode 1
- fi
-
- if [[ "$node_reuse" == true ]]; then
- Write-PipelineTelemetryError -category 'Build' "Node reuse must be disabled in CI build."
+ Write-PipelineTelemetryError -category 'Build' "Binary log must be enabled in CI build, or explicitly opted-out from with the --excludeCIBinarylog switch."
ExitWithExitCode 1
fi
fi
InitializeBuildTool
+ local logger_switch=()
+ if [[ "$pipelines_log" == true ]]; then
+ InitializeToolset
+
+ local toolset_dir="${_InitializeToolset%/*}"
+ local selectedPath="$toolset_dir/net/Microsoft.DotNet.ArcadeLogging.dll"
+
+ # Only inject the logger when it's present. A last-known-good Arcade used to bootstrap
+ # the build may not ship the logger yet, so its absence must not be a hard error.
+ # Specify the logger type explicitly so loading is deterministic.
+ if [[ -f "$selectedPath" ]]; then
+ logger_switch=("-logger:Microsoft.DotNet.ArcadeLogging.PipelinesLogger,$selectedPath")
+ fi
+ fi
+
local warnaserror_switch=""
if [[ $warn_as_error == true ]]; then
warnaserror_switch="/warnaserror"
@@ -519,8 +575,8 @@ function MSBuild-Core {
echo "Build failed with exit code $exit_code. Check errors above."
# When running on Azure Pipelines, override the returned exit code to avoid double logging.
- # Skip this when the build is a child of the VMR build.
- if [[ "$ci" == true && -n ${SYSTEM_TEAMPROJECT:-} && "$from_vmr" != true ]]; then
+ # Skip this when the build is a child of the VMR build, or when -disablePipelineSetResult is set so the real exit code propagates.
+ if [[ "$ci" == true && -n ${SYSTEM_TEAMPROJECT:-} && "$from_vmr" != true && "$disable_pipeline_set_result" != true ]]; then
Write-PipelineSetResult -result "Failed" -message "msbuild execution failed."
# Exiting with an exit code causes the azure pipelines task to log yet another "noise" error
# The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error
@@ -542,7 +598,7 @@ function MSBuild-Core {
warnnotaserror_switch="/warnnotaserror:$warn_not_as_error /p:AdditionalWarningsNotAsErrors=$warn_not_as_error"
fi
- RunBuildTool "$_InitializeBuildToolCommand" /m /nologo /clp:Summary /v:$verbosity /nr:$node_reuse $warnaserror_switch $mt_switch $warnnotaserror_switch /p:TreatWarningsAsErrors=$warn_as_error /p:ContinuousIntegrationBuild=$ci "$@"
+ RunBuildTool "$_InitializeBuildToolCommand" /m /nologo /clp:Summary /v:$verbosity /nr:$node_reuse $warnaserror_switch $mt_switch $warnnotaserror_switch "${logger_switch[@]}" /p:TreatWarningsAsErrors=$warn_as_error /p:ContinuousIntegrationBuild=$ci "$@"
}
function GetDarc {
@@ -559,8 +615,17 @@ function GetDarc {
# Returns a full path to an Arcade SDK task project file.
function GetSdkTaskProject {
- taskName=$1
- echo "$(dirname $_InitializeToolset)/SdkTasks/$taskName.proj"
+ local taskName=$1
+ local toolsetDir
+ toolsetDir="$(dirname "$_InitializeToolset")"
+ local proj="$toolsetDir/$taskName.proj"
+ if [[ -a "$proj" ]]; then
+ echo "$proj"
+ return
+ fi
+
+ Write-PipelineTelemetryError -category 'Build' "Unable to find $taskName.proj in toolset at: $toolsetDir"
+ ExitWithExitCode 3
}
ResolvePath "${BASH_SOURCE[0]}"
@@ -598,6 +663,12 @@ mkdir -p "$toolset_dir"
mkdir -p "$temp_dir"
mkdir -p "$log_dir"
+# Direct MSBuild crash diagnostics (MSB4166 failure.txt files) to a known location
+# under artifacts/log so they are captured as build artifacts in CI.
+if [[ -z "${MSBUILDDEBUGPATH:-}" ]]; then
+ export MSBUILDDEBUGPATH="$log_dir/MsbuildDebugLogs"
+fi
+
Write-PipelineSetVariable -name "Artifacts" -value "$artifacts_dir"
Write-PipelineSetVariable -name "Artifacts.Toolset" -value "$toolset_dir"
Write-PipelineSetVariable -name "Artifacts.Log" -value "$log_dir"
@@ -618,3 +689,6 @@ fi
if [[ -n "${useInstalledDotNetCli:-}" ]]; then
use_installed_dotnet_cli="$useInstalledDotNetCli"
fi
+
+# Initialize the nuget package cache vars
+InitializeNuGetPackageCachePath
diff --git a/global.json b/global.json
index 6cddc5c0fc..e864b1105a 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"tools": {
- "dotnet": "11.0.100-preview.3.26170.106",
+ "dotnet": "11.0.100-preview.5.26302.115",
"runtimes": {
"dotnet": [
"$(DotNetRuntime60Version)",
@@ -13,8 +13,8 @@
}
},
"msbuild-sdks": {
- "Microsoft.DotNet.Arcade.Sdk": "11.0.0-beta.26211.1",
- "Microsoft.DotNet.Helix.Sdk": "11.0.0-beta.26211.1",
+ "Microsoft.DotNet.Arcade.Sdk": "11.0.0-beta.26369.1",
+ "Microsoft.DotNet.Helix.Sdk": "11.0.0-beta.26369.1",
"Microsoft.Build.NoTargets": "3.7.0",
"Microsoft.Build.Traversal": "3.2.0"
}