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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/markdownlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Run markdownlint-cli2
uses: DavidAnson/markdownlint-cli2-action@v24
uses: DavidAnson/markdownlint-cli2-action@v18
Comment thread
Evangelink marked this conversation as resolved.
with:
globs: '**/*.md'
120 changes: 91 additions & 29 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ stages:
# Seed from every trusted main merge. PR merge refs are read-only consumers, so the cache must be updated as
# soon as main changes to avoid forcing the entire 204-node graph to miss.
- pwsh: |
& ./eng/common/msbuild.ps1 -ci -warnAsError:$false ./TestFx.slnx `
& ./eng/common/msbuild.ps1 -ci -prepareMachine -warnAsError:$false ./TestFx.slnx `
/restore `
/graph `
/m `
Expand All @@ -160,7 +160,10 @@ stages:
/t:Build `
/v:minimal `
/p:Configuration=$(_BuildConfig) `
/p:FastAcceptanceTest=true `
/p:Publish=false `
"/p:RepoRoot=$(Build.SourcesDirectory)\" `
/p:Test=false `
/p:MSBuildCachePackageEnabled=true `
/p:MSBuildCacheEnabled=true `
/p:MSBuildCacheRemoteCacheIsReadOnly=false `
Expand Down Expand Up @@ -290,7 +293,7 @@ stages:
"-NoLogo",
"-NoProfile",
"-File", "./eng/common/msbuild.ps1",
"-ci",
"-prepareMachine",
"-warnAsError:`$false",
"./TestFx.slnx",
"/restore",
Expand All @@ -301,7 +304,11 @@ stages:
"/t:Build",
"/v:minimal",
"/p:Configuration=$(_BuildConfig)",
"/p:ContinuousIntegrationBuild=true",
"/p:FastAcceptanceTest=true",
"/p:Publish=false",
"/p:RepoRoot=$(Build.SourcesDirectory)\",
"/p:Test=false",
"/p:MSBuildCachePackageEnabled=true",
"/p:MSBuildCacheEnabled=true",
"/p:MSBuildCacheRemoteCacheIsReadOnly=true",
Expand All @@ -321,16 +328,68 @@ stages:

if ($exitCode -ne 0) {
Write-Host "##vso[task.logissue type=warning]MSBuildCache canary failed with exit code $exitCode; continuing with the regular Arcade build."
Write-Host "##vso[task.setvariable variable=MSBuildCacheBuildSucceeded]false"
}
else {
Write-Host "##vso[task.setvariable variable=MSBuildCacheBuildSucceeded]true"
Comment thread
Evangelink marked this conversation as resolved.
}

exit 0
displayName: Build solution graph with MSBuildCache
condition: and(succeeded(), or(and(eq(variables['Build.Reason'], 'PullRequest'), ne(variables['System.PullRequest.IsFork'], 'True')), and(ne(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.SourceBranch'], 'refs/heads/main'))))
continueOnError: true
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)

# Keep the cache canary independent from the authoritative Arcade build. Publish only the project-level
# diagnostics; CacheClient.log and the OAuth-bearing canary environment stay outside published artifacts.
# CIBuild.cmd also restores the custom toolset/workloads, signs the build outputs, and produces the packages
# consumed by the acceptance tests. Run those phases without the Build phase when the cache supplied the
# compiled outputs. Keep this non-blocking so any failure switches the regular Arcade build back on.
- pwsh: |
Write-Host "##vso[task.setvariable variable=MSBuildCacheBuildSucceeded]false"

$pwsh = Join-Path $PSHOME "pwsh.exe"
$arguments = @(
"-NoLogo",
"-NoProfile",
"-File", "./eng/common/build.ps1",
"-ci",
"-disablePipelineSetResult",
"-configuration", "$(_BuildConfig)",
"-restore",
"-sign",
"-pack",
"-prepareMachine",
"/p:ContinuousIntegrationBuild=true",
"/p:FastAcceptanceTest=true",
"/p:MSBuildCachePackageEnabled=true",
"/p:MSBuildCacheEnabled=false"
)

$previousPSNativeCommandUseErrorActionPreference = $PSNativeCommandUseErrorActionPreference
try {
$PSNativeCommandUseErrorActionPreference = $false
& $pwsh @arguments
$exitCode = $LASTEXITCODE
}
finally {
$PSNativeCommandUseErrorActionPreference = $previousPSNativeCommandUseErrorActionPreference
}

if ($exitCode -ne 0) {
Write-Host "##vso[task.logissue type=warning]Preparing cached outputs failed with exit code $exitCode; continuing with the regular Arcade build."
}
else {
Write-Host "##vso[task.setvariable variable=MSBuildCacheBuildSucceeded]true"
}

exit 0
displayName: Prepare and pack cached build outputs
condition: and(succeeded(), eq(variables['MSBuildCacheBuildSucceeded'], 'true'))
continueOnError: true

# Publish only the project-level diagnostics; CacheClient.log and the OAuth-bearing cache environment stay
# outside published artifacts. A successful cache build supplies the outputs consumed by the test steps.
# On failure, remove its partial outputs before running the regular Arcade build as a fallback.
- pwsh: |
$cacheLogSource = "$(Agent.TempDirectory)\MSBuildCache"
$cacheLogDestination = "$(Build.SourcesDirectory)\artifacts\log\$(_BuildConfig)\MSBuildCache"
Expand All @@ -347,44 +406,46 @@ stages:
Write-Host "##vso[task.logissue type=warning]Failed to preserve MSBuildCache diagnostics. $($_.Exception.Message)"
}
finally {
$cleanupErrors = @()
if ("$(MSBuildCacheBuildSucceeded)" -ne "true") {
$cleanupErrors = @()

$artifactsDirectory = "$(Build.SourcesDirectory)\artifacts"
if (Test-Path $artifactsDirectory) {
Get-ChildItem $artifactsDirectory -Force |
Where-Object Name -NotIn @("log", "msbuild-cache", "toolset") |
ForEach-Object {
$path = $_.FullName
try {
Remove-Item $path -Recurse -Force -ErrorAction Stop
}
catch {
$cleanupErrors += "${path}: $($_.Exception.Message)"
}
}
}

$artifactsDirectory = "$(Build.SourcesDirectory)\artifacts"
if (Test-Path $artifactsDirectory) {
Get-ChildItem $artifactsDirectory -Force |
Where-Object Name -NotIn @("log", "msbuild-cache", "toolset") |
@(
$cacheLogSource,
"$(Build.SourcesDirectory)\src\Package\MSTest.Sdk\Sdk\Sdk.props",
"$(Build.SourcesDirectory)\src\Package\MSTest.Sdk\Sdk\Runner\Runner.targets"
) |
Where-Object { Test-Path $_ } |
ForEach-Object {
$path = $_.FullName
$path = $_
try {
Remove-Item $path -Recurse -Force -ErrorAction Stop
}
catch {
$cleanupErrors += "${path}: $($_.Exception.Message)"
}
}
}

@(
$cacheLogSource,
"$(Build.SourcesDirectory)\src\Package\MSTest.Sdk\Sdk\Sdk.props",
"$(Build.SourcesDirectory)\src\Package\MSTest.Sdk\Sdk\Runner\Runner.targets"
) |
Where-Object { Test-Path $_ } |
ForEach-Object {
$path = $_
try {
Remove-Item $path -Recurse -Force -ErrorAction Stop
}
catch {
$cleanupErrors += "${path}: $($_.Exception.Message)"
}
if ($cleanupErrors) {
throw "Failed to clean MSBuildCache outputs before the fallback build:`n$($cleanupErrors -join "`n")"
}
Comment thread
Evangelink marked this conversation as resolved.

if ($cleanupErrors) {
throw "Failed to clean MSBuildCache canary outputs:`n$($cleanupErrors -join "`n")"
}
}
displayName: Preserve cache diagnostics and clean canary outputs
displayName: Preserve cache diagnostics and clean failed outputs
condition: and(always(), or(and(eq(variables['Build.Reason'], 'PullRequest'), ne(variables['System.PullRequest.IsFork'], 'True')), and(ne(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.SourceBranch'], 'refs/heads/main'))))

- task: PowerShell@2
Expand All @@ -407,6 +468,7 @@ stages:
/p:MSBuildCacheEnabled=false
name: Build
displayName: Build
condition: and(succeeded(), ne(variables['MSBuildCacheBuildSucceeded'], 'true'))
Comment thread
Evangelink marked this conversation as resolved.

- ${{ if eq(parameters.SkipTests, False) }}:

Expand Down
6 changes: 3 additions & 3 deletions docs/dev-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,16 +86,16 @@ For more information about all the different options available, supply the argum

### MSBuildCache

The Windows PR pipeline experimentally runs [MSBuildCache](https://github.com/microsoft/MSBuildCache) before the regular Arcade build. The cache-aware build uses Arcade's `eng/common/msbuild.ps1` launcher to invoke the solution directly because Arcade's outer `Build.proj` discovers projects dynamically and cannot expose the repository's static project graph to the cache plugin. PR builds consume the immutable Azure Pipeline cache read-only, and fork PRs skip this step because they do not receive the required token scope.
The Windows PR pipeline experimentally runs [MSBuildCache](https://github.com/microsoft/MSBuildCache). The cache-aware build uses Arcade's `eng/common/msbuild.ps1` launcher to invoke the solution directly because Arcade's outer `Build.proj` discovers projects dynamically and cannot expose the repository's static project graph to the cache plugin. PR builds consume the immutable Azure Pipeline cache read-only, and fork PRs skip this step because they do not receive the required token scope. When the cache build succeeds, the pipeline runs the remaining Arcade restore, sign, and pack phases without rebuilding, then uses the cached outputs for the test steps. When the cache build or preparation phases fail, the pipeline preserves the cache diagnostics, removes partial outputs, and runs the regular Arcade build as a fallback.

Every merge to `main` that touches product build inputs runs a dedicated, batched seed stage for both Debug and Release. This is required: the cache fingerprints project inputs, so entries become stale whenever shared build inputs such as `global.json`, `eng/Versions.props`, or Arcade change. This stage is the only remote cache publisher; PR, manual, and nightly canary builds consume the cache read-only so they cannot race to publish immutable entries. Debug and Release use separate cache universes because configuration-independent projects can otherwise race while the two configurations publish in parallel.

The cache's detached-process exclusions use fully rooted paths (for example, `$(WinDir)\**`). A drive-relative pattern such as `\Windows\**` does not match the absolute file-access paths reported by MSBuild and causes otherwise successful cache builds to fail after compilation. Cache builds also pass `-warnAsError:$false` to Arcade's launcher because MSBuildCache intentionally warns about allowlisted detached telemetry accesses. The regular Arcade build still treats warnings as errors and remains authoritative.
The cache's detached-process exclusions use fully rooted paths (for example, `$(WinDir)\**`). A drive-relative pattern such as `\Windows\**` does not match the absolute file-access paths reported by MSBuild and causes otherwise successful cache builds to fail after compilation. Cache builds also pass `-warnAsError:$false` to Arcade's launcher because MSBuildCache intentionally warns about allowlisted detached telemetry accesses. The fallback Arcade build still treats warnings as errors.

MSBuildCache currently requires Windows, the Visual Studio version pinned in `global.json`, Git on `PATH`, and a clean repository. It does not support incremental developer builds. To validate the local cache from a clean checkout, run:

```powershell
eng\common\msbuild.ps1 -msbuildEngine vs -warnAsError:$false TestFx.slnx /restore /graph /m /reportfileaccesses /t:Build /p:Configuration=Release /p:MSBuildCachePackageEnabled=true /p:MSBuildCacheEnabled=true /p:MSBuildCacheLogDirectory=artifacts\log\Release\MSBuildCache\Plugin /bl:artifacts\log\Release\MSBuildCache\Build.binlog
eng\common\msbuild.ps1 -msbuildEngine vs -warnAsError:$false TestFx.slnx /restore /graph /m /reportfileaccesses /t:Build /p:Configuration=Release /p:FastAcceptanceTest=true /p:Publish=false /p:Test=false /p:MSBuildCachePackageEnabled=true /p:MSBuildCacheEnabled=true /p:MSBuildCacheLogDirectory=artifacts\log\Release\MSBuildCache\Plugin /bl:artifacts\log\Release\MSBuildCache\Build.binlog
```

Use `MSTest.slnf` or `Microsoft.Testing.Platform.slnf` in place of `TestFx.slnx` to validate a filtered graph. Delete `artifacts\msbuild-cache` to clear the local content cache. Change the prefix of `MSBuildCacheCacheUniverse` in `Directory.Build.props`, or pass `/p:MSBuildCacheCacheUniverse=<new-value>` for one invocation, to invalidate cache entries. The default universe includes `$(Configuration)`, so use the same configuration when populating and consuming a cache.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,18 @@
SCOPE
The set is the reporter + its rendering/state types + the small platform abstractions it depends on
(IConsole/IStopwatch/IColor/System* + ILogger/LogLevel/LoggingExtensions/NopLogger +
RoslynString/ApplicationStateGuard/StackTraceHelper/TargetFrameworkParser/TestRunSummaryHelper/ZeroTestsPolicy).
RoslynString/ApplicationStateGuard/StackTraceHelper/SlowTestThresholdState/TargetFrameworkParser/
TestRunSummaryHelper/ZeroTestsPolicy).
It deliberately EXCLUDES TerminalTestReporterCommandLineOptionsProvider.cs (MTP
command-line specific - the SDK has its own CLI) and TerminalResources.cs (the hand-written [Embedded]
accessor used by in-repo IVT-linked consumers; standalone consumers compile the resx instead - see below).

The reporter + rendering + state types are [Microsoft.CodeAnalysis.Embedded], so they are NOT exported by
Microsoft.Testing.Platform; a consumer that also references the platform compiles its own (single-source)
copy and never sees a conflict. The small internal platform abstractions in this set (IConsole/IStopwatch/
SystemConsole/SystemStopwatch + RoslynString/ApplicationStateGuard/StackTraceHelper/TargetFrameworkParser/
TestRunSummaryHelper/ZeroTestsPolicy) are 'internal' and not exported either (no InternalsVisibleTo to an
external consumer), so they also never conflict.
SystemConsole/SystemStopwatch + RoslynString/ApplicationStateGuard/StackTraceHelper/SlowTestThresholdState/
TargetFrameworkParser/TestRunSummaryHelper/ZeroTestsPolicy) are 'internal' and not exported either (no
InternalsVisibleTo to an external consumer), so they also never conflict.

The exceptions are the public logging abstractions (ILogger, LogLevel, and LoggingExtensions) plus IColor and
SystemConsoleColor, which are shipped *public* API of Microsoft.Testing.Platform. A consumer that ALSO references
Expand Down Expand Up @@ -80,6 +81,7 @@
<!-- Public API of Microsoft.Testing.Platform; dropped (not compiled) when the consumer references the platform. -->
<PlatformPublicAbstraction>true</PlatformPublicAbstraction>
</TerminalReporterContractSource>
<TerminalReporterContractSource Include="$(_TerminalContractPlatform)\OutputDevice\SlowTestThresholdState.cs" />
<TerminalReporterContractSource Include="$(_TerminalContractPlatform)\OutputDevice\TargetFrameworkParser.cs" />
<TerminalReporterContractSource Include="$(_TerminalContractPlatform)\OutputDevice\TestRunSummaryHelper.cs" />
<TerminalReporterContractSource Include="$(_TerminalContractPlatform)\CommandLine\ZeroTestsPolicy.cs" />
Expand Down
Loading