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 docs/design/tools/illink/task-cache.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ internal sealed class ILLinkCache
- Purge requires an explicit cache root and UTC cutoff. Only published SHA-256 entry directories in `v1` are considered; delete entries whose last-used time is strictly older than the cutoff and retain equality. Leave staging directories and other layouts alone. Report per-entry maintenance errors and continue, then return nonzero if maintenance failed.
- For a missing, malformed, non-UTC, or unreadable marker, report the fallback and use the entry directory's creation time instead. This may purge a recently used entry or retain a stale one if restoring a cache snapshot resets directory creation times. Do not migrate or repair the marker.
- Purge must not run during any build using the cache. Do not add purge locks or change reader locking; coordination with active readers/writers remains a separate follow-up, as does protection against manual deletion.
- CI may record build start after restoring a job-private cache, run linking, then purge against that timestamp before uploading the next snapshot. Preserve tool diagnostics but do not fail the build for maintenance errors. Only purge once all users have exited; incremental skips and unreached work in failed builds do not refresh usage. See the [CI recipe](../../../tools/illink/illink-tasks.md#ci-purge-ordering). This does not enable an ILLink cache in CI automatically.
- Only the `CoreLib_Libraries_AnalyzersLinker` CI job opts into ILLink caching through `setup-build-caches.yml`, on Windows and Unix x64/arm64. Roslyn and sccache remain gated by platform. Restore a job-private Azure cache, record build start, run linking and all post-build steps, then purge against that timestamp before uploading the next snapshot. This job explicitly enables `RunILLinkInBuild` and builds the `tools.illink` subset for the maintenance tool; building `ILLink.Tasks` through a library project reference does not build it. Purge only after a successful build; skip maintenance when no versioned cache directory exists and report a missing tool. Preserve tool diagnostics but do not fail the build for maintenance errors. Only purge once all users have exited; incremental skips and unreached work in failed builds do not refresh usage. See the [CI recipe and wiring](../../../tools/illink/illink-tasks.md#ci-purge-ordering).
- Attempt best-effort cleanup of the current attempt's unpublished staging directory. Log cleanup I/O/access failures without failing a successful link; do not scan or delete other attempts' staging directories. Correctness must tolerate leftovers after crashes.

## Open follow-ups
Expand Down
29 changes: 20 additions & 9 deletions docs/tools/illink/illink-tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ or created by that build:
```sh
export ILLINK_CACHE_BUILD_START="$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
```
3. Run the build and wait for all processes using the cache to finish.
4. Run the tool from the same revision, with maintenance failures reported but nonfatal:
3. Run the build successfully and wait for all processes using the cache to finish.
4. Run the maintenance tool from the same checkout, with maintenance failures reported but nonfatal:
```sh
if ! dotnet illink-cache purge \
--cache-directory "$ILLINK_EXPERIMENTAL_CACHE_PATH" \
Expand All @@ -131,13 +131,24 @@ If the build and purge run in different CI steps, persist `ILLINK_CACHE_BUILD_ST
pipeline variable; a shell export alone does not carry it across steps. This variable is
only for the CI recipe, not another task configuration setting.

A failed build can still be followed by purge once all linking has stopped, but entries for
work that was never reached will count as unused. Likewise, incremental builds that skip
linking do not refresh cache usage. Skip purge if the cache may still be in use, including
during cancellation. Do not share a live directory with another job while purging.

This is usage guidance, not automatic CI enablement. ILLink cache restore/upload wiring
is separate from runtime's Roslyn/csc cache maintenance.
Skip purge after a failed or canceled build. Incremental builds that skip linking do not
refresh cache usage. Do not share a live directory with another job while purging.

The `CoreLib_Libraries_AnalyzersLinker` job in `global-build.yml` opts into ILLink caching
through `setup-build-caches.yml` on Windows and Unix x64/arm64 and sets `RunILLinkInBuild=true`.
The shared setup also configures Roslyn caching on Windows and Unix x64/arm64, and sccache
on Unix x64/arm64; other jobs retain those platform gates without restoring the ILLink cache.
`setup-illink-cache.yml` restores a branch-scoped Azure
Pipelines snapshot into `$(Pipeline.Workspace)/.illink-cache`, enables the experiment,
and records build start. `global-build-job.yml` runs `purge-illink-cache.yml` after all
post-build steps and before the `Cache@2` post-job save. The linker job explicitly
builds the `tools.illink` subset, which includes the purge tool; building `ILLink.Tasks`
through a library project reference alone does not build it. The purge step reports
missing prerequisites and maintenance failures as warnings, and runs only when ILLink
caching was enabled and the job succeeded. When no versioned cache directory exists,
it skips purge.

This configuration is separate from runtime's Roslyn/csc and native compiler caches.

## ILLink Task Properties

Expand Down
4 changes: 4 additions & 0 deletions eng/pipelines/common/global-build-job.yml
Original file line number Diff line number Diff line change
Expand Up @@ -277,3 +277,7 @@ jobs:
- template: /eng/pipelines/coreclr/templates/prune-roslyn-cache.yml
parameters:
osGroup: ${{ parameters.osGroup }}

- template: /eng/pipelines/coreclr/templates/purge-illink-cache.yml
parameters:
osGroup: ${{ parameters.osGroup }}
88 changes: 88 additions & 0 deletions eng/pipelines/coreclr/templates/purge-illink-cache.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
parameters:
osGroup: 'linux'

steps:
- ${{ if ne(parameters.osGroup, 'windows') }}:
- script: |
set -euo pipefail

if [[ -z "${ILLINK_EXPERIMENTAL_CACHE_PATH:-}" ||
-z "${ILLINK_CACHE_BUILD_START:-}" ]]; then
echo "##vso[task.logissue type=warning]ILLink cache configuration or build-start timestamp is unavailable; skipping purge."
exit 0
fi

if [[ ! -e "$ILLINK_EXPERIMENTAL_CACHE_PATH/v1" ]]; then
echo "No ILLink cache entries were created; skipping purge."
exit 0
fi

dotnet="$(Build.SourcesDirectory)/.dotnet/dotnet"
if [[ ! -x "$dotnet" ]]; then
echo "##vso[task.logissue type=warning]The build SDK is unavailable; skipping ILLink cache purge."
exit 0
fi

tool=""
for candidate in "$(Build.SourcesDirectory)"/artifacts/bin/ILLink.CacheTool/*/net*/dotnet-illink-cache.dll; do
if [[ -f "$candidate" ]]; then
tool="$candidate"
break
fi
done

if [[ -z "$tool" ]]; then
echo "##vso[task.logissue type=warning]The ILLink cache maintenance tool was not built; skipping purge."
exit 1
fi

if ! "$dotnet" "$tool" purge \
--cache-directory "$ILLINK_EXPERIMENTAL_CACHE_PATH" \
--before "$ILLINK_CACHE_BUILD_START"; then
echo "##vso[task.logissue type=warning]ILLink cache purge failed; see the tool diagnostics above."
fi
displayName: Purge unused ILLink cache entries
condition: and(succeeded(), eq(variables['ILLINK_EXPERIMENTAL_CACHE'], 'true'))
continueOnError: true

- ${{ if eq(parameters.osGroup, 'windows') }}:
- pwsh: |
if ([string]::IsNullOrEmpty($env:ILLINK_EXPERIMENTAL_CACHE_PATH) -or
[string]::IsNullOrEmpty($env:ILLINK_CACHE_BUILD_START)) {
Write-Host "##vso[task.logissue type=warning]ILLink cache configuration or build-start timestamp is unavailable; skipping purge."
exit 0
}

if (-not (Test-Path -LiteralPath (Join-Path $env:ILLINK_EXPERIMENTAL_CACHE_PATH "v1") -PathType Container)) {
Write-Host "No ILLink cache entries were created; skipping purge."
exit 0
}

$dotnet = Join-Path "$(Build.SourcesDirectory)" ".dotnet/dotnet.exe"
if (-not (Test-Path -LiteralPath $dotnet -PathType Leaf)) {
Write-Host "##vso[task.logissue type=warning]The build SDK is unavailable; skipping ILLink cache purge."
exit 0
}

$toolRoot = Join-Path "$(Build.SourcesDirectory)" "artifacts/bin/ILLink.CacheTool"
Comment thread
sbomer marked this conversation as resolved.
if (-not (Test-Path -LiteralPath $toolRoot -PathType Container)) {
Write-Host "##vso[task.logissue type=warning]The ILLink cache maintenance tool was not built; skipping purge."
exit 1
}

$tool = Get-ChildItem -LiteralPath $toolRoot -Filter "dotnet-illink-cache.dll" -File -Recurse |
Sort-Object FullName | Select-Object -First 1
if ($null -eq $tool) {
Write-Host "##vso[task.logissue type=warning]The ILLink cache maintenance tool was not built; skipping purge."
exit 1
}

& $dotnet $tool.FullName purge `
--cache-directory $env:ILLINK_EXPERIMENTAL_CACHE_PATH `
--before $env:ILLINK_CACHE_BUILD_START
if ($LASTEXITCODE -ne 0) {
Write-Host "##vso[task.logissue type=warning]ILLink cache purge failed; see the tool diagnostics above."
}
Comment thread
sbomer marked this conversation as resolved.
displayName: Purge unused ILLink cache entries
condition: and(succeeded(), eq(variables['ILLINK_EXPERIMENTAL_CACHE'], 'true'))
continueOnError: true
9 changes: 9 additions & 0 deletions eng/pipelines/coreclr/templates/setup-build-caches.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ parameters:
platform: ''
shouldContinueOnError: false
osSubgroup: ''
enableILLinkCache: false

# sccache version. Keep the version in sync across platforms so all jobs
# cache against the same compiler-launcher behavior.
Expand Down Expand Up @@ -51,6 +52,14 @@ steps:
Write-Host "##vso[task.setvariable variable=ROSLYN_CACHE_BUILD_START]$buildStart"
displayName: Configure Roslyn compiler cache

- ${{ if eq(parameters.enableILLinkCache, true) }}:
- template: /eng/pipelines/coreclr/templates/setup-illink-cache.yml
parameters:
osGroup: ${{ parameters.osGroup }}
osSubgroup: ${{ parameters.osSubgroup }}
archType: ${{ parameters.archType }}
nameSuffix: ${{ parameters.nameSuffix }}

# sccache native compiler cache
- ${{ if and(or(eq(parameters.osGroup, 'linux'), eq(parameters.osGroup, 'freebsd'), eq(parameters.osGroup, 'openbsd'), eq(parameters.osGroup, 'osx')), or(eq(parameters.archType, 'x64'), eq(parameters.archType, 'arm64'))) }}:
# Set up the Azure Pipeline Cache for sccache's local cache directory.
Expand Down
42 changes: 42 additions & 0 deletions eng/pipelines/coreclr/templates/setup-illink-cache.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
parameters:
archType: 'x64'
osGroup: 'linux'
nameSuffix: ''
osSubgroup: ''

steps:
- ${{ if or(eq(parameters.osGroup, 'windows'), and(in(parameters.osGroup, 'linux', 'freebsd', 'openbsd', 'osx'), in(parameters.archType, 'x64', 'arm64'))) }}:
# Use the same branch-scoped restore policy as the compiler caches. Each job
# works on its own restored copy, so post-build purge cannot affect another job.
# Keep Debug and Release snapshots separate in rolling builds.
- task: Cache@2
displayName: ILLink task cache
continueOnError: true
inputs:
key: illink | v1 | ${{ parameters.osGroup }}${{ parameters.osSubgroup }} | ${{ parameters.archType }} | ${{ parameters.nameSuffix }} | "$(buildConfigUpper)" | "$(Build.BuildId)"
path: $(Pipeline.Workspace)/.illink-cache
restoreKeys: |
illink | v1 | ${{ parameters.osGroup }}${{ parameters.osSubgroup }} | ${{ parameters.archType }} | ${{ parameters.nameSuffix }} | "$(buildConfigUpper)"

- ${{ if ne(parameters.osGroup, 'windows') }}:
- script: |
set -euo pipefail
mkdir -p "$(Pipeline.Workspace)/.illink-cache"
buildStart="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
echo "##vso[task.setvariable variable=ILLINK_EXPERIMENTAL_CACHE_PATH]$(Pipeline.Workspace)/.illink-cache"
echo "##vso[task.setvariable variable=ILLINK_CACHE_BUILD_START]$buildStart"
echo "##vso[task.setvariable variable=ILLINK_EXPERIMENTAL_CACHE]true"
displayName: Configure experimental ILLink task cache
continueOnError: true

- ${{ if eq(parameters.osGroup, 'windows') }}:
- pwsh: |
$cachePath = Join-Path "$(Pipeline.Workspace)" ".illink-cache"
New-Item -ItemType Directory -Path $cachePath -Force | Out-Null
$buildStart = [DateTime]::UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ")

Write-Host "##vso[task.setvariable variable=ILLINK_EXPERIMENTAL_CACHE_PATH]$cachePath"
Write-Host "##vso[task.setvariable variable=ILLINK_CACHE_BUILD_START]$buildStart"
Write-Host "##vso[task.setvariable variable=ILLINK_EXPERIMENTAL_CACHE]true"
displayName: Configure experimental ILLink task cache
continueOnError: true
9 changes: 8 additions & 1 deletion eng/pipelines/global-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,18 @@ extends:
- osx_arm64
jobParameters:
nameSuffix: CoreLib_Libraries_AnalyzersLinker
buildArgs: -subset clr.corelib+libs -restore -build -pack -c $(_BuildConfig) /p:RunAnalyzersInBuild=true /p:RunILLinkInBuild=true
buildArgs: -subset clr.corelib+libs+tools.illink -restore -build -pack -c $(_BuildConfig) /p:RunAnalyzersInBuild=true /p:RunILLinkInBuild=true
Comment thread
sbomer marked this conversation as resolved.
timeoutInMinutes: 120
preBuildSteps:
- template: /eng/pipelines/coreclr/templates/setup-build-caches.yml
parameters:
enableILLinkCache: true
postBuildSteps:
- template: /eng/pipelines/coreclr/templates/build-cache-stats.yml
condition:
or(
eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_libraries.containsChange'], true),
eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_tools_illink.containsChange'], true),
eq(variables['isRollingBuild'], true))

#
Expand Down
Loading