Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion .vsts-dotnet-ci.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
parameters:
# Condition (compile-time constant) reused by every heavy build/test job below. It runs the leg
# UNLESS the EvaluateDocumentationOnlyChange gate succeeded and classified the change as
# documentation/.github-only. Fail-open: if the gate did not succeed we still run the leg. Kept in
# one place and referenced via ${{ parameters.runUnlessDocumentationOnly }} so the long expression
# is authored exactly once instead of copied onto each job.
- name: runUnlessDocumentationOnly
type: string
default: "or(not(succeeded('EvaluateDocumentationOnlyChange')), eq(dependencies.EvaluateDocumentationOnlyChange.outputs['CheckDocumentationOnlyChange.onlyDocChanged'], '0'))"

trigger:
branches:
include:
Expand All @@ -20,7 +30,23 @@ variables:
value: none

jobs:
# Lightweight gate: determine once whether the PR changed only documentation/.github files.
# Every heavy build/test job depends on this and is skipped entirely -- no agent is allocated --
# when it reports onlyDocChanged=1, so a docs-only PR never waits on the build pools.
- job: EvaluateDocumentationOnlyChange
displayName: "Evaluate documentation-only change"
pool:
${{ if eq(variables['System.TeamProject'], 'public') }}:
name: NetCore-Public
demands: ImageOverride -equals Build.Ubuntu.2204.Amd64.Open
${{ if ne(variables['System.TeamProject'], 'public') }}:
vmImage: 'ubuntu-latest'
steps:
- template: azure-pipelines/check-documentation-only-change.yml

- job: BootstrapMSBuildOnFullFrameworkWindows
dependsOn: EvaluateDocumentationOnlyChange
condition: ${{ parameters.runUnlessDocumentationOnly }}
variables:
coverageArtifactsDir: $(Build.SourcesDirectory)/CoverageStaging
coverageReportName: WindowsFullFrameworkCoverage
Expand Down Expand Up @@ -134,6 +160,8 @@ jobs:
ArtifactName: 'Windows-on-full Verify $(System.JobAttempt)'

- job: BootstrapMSBuildOnCoreWindows
dependsOn: EvaluateDocumentationOnlyChange
condition: ${{ parameters.runUnlessDocumentationOnly }}
displayName: "Windows Core"
variables:
coverageArtifactsDir: $(Build.SourcesDirectory)/CoverageStaging
Expand Down Expand Up @@ -222,6 +250,8 @@ jobs:
# kept as a separate Linux Core job because the analyzer is more expensive and the regular
# bootstrapped CI jobs (which all run /mt now too) do not need it.
- job: BootstrapMSBuildWithMTMode
dependsOn: EvaluateDocumentationOnlyChange
condition: ${{ parameters.runUnlessDocumentationOnly }}
displayName: "Linux Core Multithreaded Mode"
pool:
vmImage: 'ubuntu-latest'
Expand Down Expand Up @@ -252,6 +282,8 @@ jobs:
condition: always()

- job: FullReleaseOnWindows
dependsOn: EvaluateDocumentationOnlyChange
condition: ${{ parameters.runUnlessDocumentationOnly }}
displayName: "Windows Full Release (no bootstrap)"
variables:
coverageArtifactsDir: $(Build.SourcesDirectory)/CoverageStaging
Expand Down Expand Up @@ -335,6 +367,8 @@ jobs:
condition: eq(variables.onlyDocChanged, 0)

- job: CoreBootstrappedOnLinux
dependsOn: EvaluateDocumentationOnlyChange
condition: ${{ parameters.runUnlessDocumentationOnly }}
displayName: "Linux Core"
pool:
${{ if eq(variables['System.TeamProject'], 'public') }}:
Expand Down Expand Up @@ -430,6 +464,8 @@ jobs:
ArtifactName: 'Linux Verify $(System.JobAttempt)'

- job: CoreOnMac
dependsOn: EvaluateDocumentationOnlyChange
condition: ${{ parameters.runUnlessDocumentationOnly }}
displayName: "macOS Core"
pool:
vmImage: 'macOS-latest'
Expand Down Expand Up @@ -521,13 +557,15 @@ jobs:
- job: CodeCoverage
displayName: "Code Coverage"
dependsOn:
- EvaluateDocumentationOnlyChange
- BootstrapMSBuildOnFullFrameworkWindows
- BootstrapMSBuildOnCoreWindows
- FullReleaseOnWindows
- CoreBootstrappedOnLinux
- CoreOnMac
condition: and(succeeded(), ${{ parameters.runUnlessDocumentationOnly }})
variables:
onlyDocChanged: $[ dependencies.BootstrapMSBuildOnFullFrameworkWindows.outputs['CheckDocumentationOnlyChange.onlyDocChanged'] ]
onlyDocChanged: $[ dependencies.EvaluateDocumentationOnlyChange.outputs['CheckDocumentationOnlyChange.onlyDocChanged'] ]
pool:
${{ if eq(variables['System.TeamProject'], 'public') }}:
name: NetCore-Public
Expand Down Expand Up @@ -590,3 +628,11 @@ jobs:
pathToSources: $(Build.SourcesDirectory)
condition: eq(variables.onlyDocChanged, 0)
- template: /eng/common/templates/jobs/source-build.yml
parameters:
# Skip the source-build leg entirely (no agent allocated) for documentation/.github-only PRs.
defaultManagedPlatform:
name: 'Managed'
container: 'mcr.microsoft.com/dotnet-buildtools/prereqs:centos-stream-10-amd64'
jobProperties:
dependsOn: EvaluateDocumentationOnlyChange
condition: ${{ parameters.runUnlessDocumentationOnly }}
40 changes: 31 additions & 9 deletions azure-pipelines/check-documentation-only-change.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,42 @@
# Template to check if only non-build files have changed (documentation and .github)
# Sets a pipeline variable 'onlyDocChanged' to 1 if only non-build files changed, 0 otherwise
# This template should be included at the start of test jobs to determine if tests should run
#
# Diff semantics:
# For PR validation builds Azure DevOps checks out the GitHub-generated merge ref
# (refs/pull/<N>/merge). Its first parent (HEAD~1) is the tip of the PR's base branch,
# so 'git diff HEAD HEAD~1' is the FULL cumulative PR diff regardless of how many
# commits the PR contains -- not just the last commit.
#
# Fail-open:
# Detection defaults to onlyDocChanged=0 (run full CI) on any git error or an empty
# diff. This is the safe direction: a detection problem never skips CI for a code
# change, it only ever runs more than strictly necessary. Job-level conditions layer
# a second fail-open on top (they run when this gate job does not succeed).

steps:
- powershell: |
$changedFiles = git diff --name-only HEAD HEAD~1
$changedFiles
$onlyDocChanged = 1
foreach ($file in $changedFiles) {
$isNonBuildFile = ($file -match "^documentation/") -or ($file -match "^\.github/")
if(!$isNonBuildFile)
{
$onlyDocChanged = 0
break;
$onlyDocChanged = 0
try {
$changedFiles = git diff --name-only HEAD HEAD~1
if ($LASTEXITCODE -ne 0) { throw "git diff exited with code $LASTEXITCODE" }
$changedFiles
if ($changedFiles) {
$onlyDocChanged = 1
foreach ($file in $changedFiles) {
$isNonBuildFile = ($file -match "^documentation/") -or ($file -match "^\.github/")
if (!$isNonBuildFile) {
$onlyDocChanged = 0
break
}
}
}
}
catch {
Write-Host "Documentation-only detection failed; defaulting to running full CI. $_"
$onlyDocChanged = 0
}
Write-Host "onlyDocChanged=$onlyDocChanged"
Write-Host "##vso[task.setvariable variable=onlyDocChanged]$onlyDocChanged"
Write-Host "##vso[task.setvariable variable=onlyDocChanged;isoutput=true]$onlyDocChanged"
name: CheckDocumentationOnlyChange
Expand Down
27 changes: 27 additions & 0 deletions azure-pipelines/quarantine.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@
# targeting `main` (so a fix PR's effect on the quarantined tests is validated on the PR itself), and
# manual queue. Consumers that aggregate this data treat rolling and scheduled `main` runs the same
# (reasons batchedCI/individualCI/schedule), so the mix of triggers is transparent to them.
parameters:
# Condition (compile-time constant) reused by every quarantine leg. Runs the leg on any non-PR build
# (rolling/batched and scheduled re-validation on main), or -- fail-open -- if the gate did not
# succeed, or when the gate classified the PR as documentation/.github-only. Authored once here and
# referenced via ${{ parameters.runUnlessDocumentationOnly }} instead of copied onto each job.
- name: runUnlessDocumentationOnly
type: string
default: "or(ne(variables['Build.Reason'], 'PullRequest'), not(succeeded('EvaluateDocumentationOnlyChange')), eq(dependencies.EvaluateDocumentationOnlyChange.outputs['CheckDocumentationOnlyChange.onlyDocChanged'], '0'))"

trigger:
batch: true
branches:
Expand Down Expand Up @@ -57,8 +66,22 @@ variables:
value: false

jobs:
# Lightweight gate: determine once whether the PR changed only documentation/.github files.
# The quarantine legs depend on this and are skipped entirely -- no agent is allocated -- for a
# docs-only pull request. Non-PR runs (rolling/batched and scheduled re-validation on main) always
# run, so the quarantined-test signal keeps tracking main regardless of this gate.
- job: EvaluateDocumentationOnlyChange
displayName: "Evaluate documentation-only change"
pool:
Comment thread
jankratochvilcz marked this conversation as resolved.
name: NetCore-Public
demands: ImageOverride -equals Build.Ubuntu.2204.Amd64.Open
steps:
- template: azure-pipelines/check-documentation-only-change.yml

- job: RunQuarantinedTestsWindows
displayName: "Run quarantined tests (Windows)"
dependsOn: EvaluateDocumentationOnlyChange
condition: ${{ parameters.runUnlessDocumentationOnly }}
pool:
name: NetCore-Public
demands: ImageOverride -equals windows.vs2026preview.scout.amd64.open
Expand Down Expand Up @@ -112,6 +135,8 @@ jobs:

- job: RunQuarantinedTestsLinux
displayName: "Run quarantined tests (Linux)"
dependsOn: EvaluateDocumentationOnlyChange
condition: ${{ parameters.runUnlessDocumentationOnly }}
pool:
name: NetCore-Public
demands: ImageOverride -equals Build.Ubuntu.2204.Amd64.Open
Expand Down Expand Up @@ -154,6 +179,8 @@ jobs:

- job: RunQuarantinedTestsMac
displayName: "Run quarantined tests (macOS)"
dependsOn: EvaluateDocumentationOnlyChange
condition: ${{ parameters.runUnlessDocumentationOnly }}
pool:
vmImage: 'macOS-latest'
timeoutInMinutes: 120
Expand Down
Loading