Add ci channel for per-commit Build Cache Service framework resolution - #878
Add ci channel for per-commit Build Cache Service framework resolution#878LoopedBard3 wants to merge 14 commits into
ci channel for per-commit Build Cache Service framework resolution#878Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds a new buildcache channel to Crank’s agent/runtime resolution pipeline so the runtime binaries can be sourced per-commit from the Build Caching Service (BCS), enabling finer-grained performance regression bisection than VMR feed cadence allows.
Changes:
- Added build-cache-related properties to
Jobfor selecting commit/branch/config. - Added
BuildCacheClientfor resolving latest builds, downloading/extracting artifacts, and overlaying runtime binaries. - Updated agent startup/version resolution logic and docs to support the new
buildcachechannel and new agent CLI options.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Microsoft.Crank.Models/Job.cs | Adds job-level inputs for BCS commit/branch/config selection. |
| src/Microsoft.Crank.Agent/Startup.cs | Adds CLI options and integrates buildcache into runtime version resolution + post-publish overlay. |
| src/Microsoft.Crank.Agent/BuildCacheClient.cs | Implements BCS querying/downloading/extraction and overlay helpers. |
| docs/dotnet_versions.md | Documents buildcache channel usage and configuration knobs. |
| docs/build_cache_requirements.md | Describes required BCS-side blob layout/access for the integration. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Adds a new 'buildcache' channel that resolves .NET runtime binaries from the Build Caching Service (BCS) instead of VMR feeds. This provides per-commit granularity for performance regression bisection. Key changes: - New Job.cs properties: BuildCacheCommitSha, BuildCacheBranch, BuildCacheConfig - New BuildCacheClient.cs: HTTP client for BCS latestBuilds.json and artifact download/extraction with post-build overlay into published output - Startup.cs: 'buildcache' channel in version resolution that builds with real NuGet packages then overlays BCS runtime binaries (194 files) after publish - Agent CLI options: --build-cache-base-url, --build-cache-repo-name, --build-cache-disabled - Documentation in docs/dotnet_versions.md and docs/build_cache_requirements.md Usage: --application.channel buildcache [--application.buildCacheCommitSha <sha>] Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
99d0195 to
41394e4
Compare
Changes:
* BuildCacheClient: rewrite to drop ~250 LOC of dead code from an earlier
abandoned design (synthesizing a new shared-framework dir with a synthetic
version). The FDD overlay path is restored as a new public API.
* New OverlayDotnetHome(extractDir, dotnetHome, runtimeVersion): overlays BCS
bits into dotnetHome/shared/Microsoft.NETCore.App/{runtimeVersion}/ and
host/fxr/{runtimeVersion}/ + the dotnet host. Wired into Startup.cs after
publish so framework-dependent jobs actually run against BCS bits instead of
silently using the feed runtime.
* OverlayPublishedOutput: copy ALL managed/native runtime files
unconditionally (previously skipped any file not already in destination,
which would silently drop new DLLs introduced by the BCS commit). Also
copies hostfxr/hostpolicy/dotnet for self-contained.
* Hardening:
- URL-encode repoName/commitSha/buildCacheConfig/branch with
Uri.EscapeDataString.
- Atomic download (.partial -> rename) and Content-Length validation so
truncated archives are not reused after a failed run.
- Retry transient HTTP failures via ProcessUtil.RetryOnExceptionAsync.
- Replace shelling out to tar with System.Formats.Tar.TarFile.
- Wrap synchronous archive extraction in Task.Run.
- Per-(commit,config) SemaphoreSlim + per-call unique extract dir to avoid
races between concurrent jobs.
- Drop unused targetFramework parameter from DownloadAndExtractAsync.
- All commit-SHA Substring uses go through ShortSha (Math.Min length guard).
- ParseLatestBuilds: case-insensitive branch_name/BranchName handling and
skip non-object metadata properties safely.
* Startup.cs:
- Validate user-supplied BuildCacheCommitSha length (>= 8 chars) up front
instead of crashing later.
- Stop mutating runtimeVersion before PatchRuntimeConfig - use feed-resolved
version so runtimeconfig.json points to a really-installed shared
framework dir. Suffix +buildcache.<sha> is now applied to
job.RuntimeVersion only, after PatchRuntimeConfig has run.
- Treat 0-file overlay as fatal (job.Error + return null) so silent failures
do not produce wrong-runtime benchmarks.
- Promote overlay-failure log from Info to fatal job error.
* docs/dotnet_versions.md: add trailing newline.
* test/Microsoft.Crank.UnitTests/BuildCacheClientTests.cs: 17 new tests
covering ParseLatestBuilds (PascalCase / snake_case / mixed / missing
fields / case-insensitive lookup / non-object values), GetPlatformMoniker,
ShortSha, GetNativeLibName, OverlayPublishedOutput unconditional copy,
pdb/dbg skip, OverlayDotnetHome shared-framework precondition, and full
shared-framework + hostfxr overlay.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…verlay
Live-tested against an agent with --application.channel buildcache. The first
end-to-end run was reporting the FEED commit hash in .NET Runtime Version
because:
1. The .version file in shared/Microsoft.NETCore.App/{ver}/ was never rewritten
after the BCS overlay, so any consumer of that file (notably the agent's own
BenchmarksNetCoreAppVersion measurement metadata) reported the feed-installed
commit.
2. Even with .version rewritten, the metadata-capture block runs immediately
after the feed runtime install -- before the post-publish overlay -- so the
feed commit was captured into Job.Measurements before BCS bits were in place.
Fixes:
* OverlayDotnetHome now accepts an optional commitSha parameter and rewrites
shared/Microsoft.NETCore.App/{ver}/.version with the BCS commit so anything
reading that file gets the correct hash.
* Startup.cs splits the BCS overlay into two stages:
- dotnet-home overlay runs RIGHT AFTER feed runtime install (before the
BenchmarksNetCoreAppVersion metadata is captured), so the metadata picks up
the BCS commit. Treat 0 files as fatal.
- published-output overlay runs after publish (only path where outputFolder
exists). For self-contained, 0 files is fatal; for FDD it's expected.
* Two new tests:
- OverlayDotnetHome_WithCommitSha_RewritesVersionFile
- OverlayDotnetHome_WithoutCommitSha_LeavesVersionFileUntouched
Live test result with --application.channel buildcache --application.framework
net11.0 against a Linux agent:
| .NET Runtime Version | 11.0.0-preview.5.26256.117+603403d9cb49 |
| Requests/sec | 7,593,977 |
The +603403d9cb49 suffix is the actual BCS commit, and 212+212 files were
overlaid (dotnet home + published output).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…d apphost, and more
Round-3 fixes informed by a rubber-duck critique + a second live test.
Per-job ISOLATED dotnet home (replaces in-place overlay of shared dotnetHome)
The biggest design change: stop mutating the global dotnetHome. After feed
install, build a per-job dotnet home by copying the relevant subtrees
(shared/Microsoft.NETCore.App/{ver}, shared/Microsoft.AspNetCore.App/{ver},
host/fxr/{ver}, dotnet[.exe]) and overlay BCS bits into the copy. The global
dotnetHome stays clean. This fixes three real bugs at once:
- cross-job pollution (a later non-buildcache job would have read the BCS
commit from .version),
- concurrent buildcache jobs racing on the same shared framework dir,
- mid-overlay failures leaving a permanently-corrupted shared framework that
_installedDotnetRuntimes would continue to trust.
The per-job home is exposed via JobContext.BuildCacheDotnetHome and used by
StartProcess so framework-dependent jobs actually load BCS bits at runtime.
Cleanup happens at job-end.
Build cache fields in BuildKeyData
Job.BuildKeyData now carries BuildCacheCommitSha / BuildCacheBranch /
BuildCacheConfig so reuseBuild + NoBuild don't silently reuse a build pinned
to a different BCS commit.
SDK-bound apphost is no longer clobbered
A live test exposed this regression: the BCS archive ships the raw,
unbound apphost. Overlaying it on top of the SDK-bound published binary left
the apphost with the placeholder SHA-256 binding and the app refused to start
with "This executable is not bound to a managed DLL to execute. The binding
value is: 'c3ab8ff1...'". Fixed by NOT overlaying apphost. CoreCLR JIT, GC,
managed BCL, hostfxr, hostpolicy still all come from BCS, so the perf
relevant code is correct. (Doc comment explains how a future enhancement
could rebind a BCS apphost via Microsoft.NET.HostModel.HostWriter.)
BCS-config RID for overlay discovery
Stop using the host's GetPlatformMoniker for overlay discovery. Resolve the
RID from the buildCacheConfig instead so an explicit musl/cross-arch override
finds the right runtime pack inside the archive.
Numeric-aware managed lib dir selection
SelectHighestManagedDir parses net{major}.0 numerically so the BCS archive
could ship multiple TFMs without lexically picking net9.0 over net10.0.
Non-retryable 404
New BuildCacheNotFoundException sentinel + RetryTransientAsync helper so 404
responses fail fast instead of being retried 3 times.
Strict SHA validation
ValidateCommitSha requires 8-40 hex chars; rejects "../../../etc/passwd",
non-hex, and silly inputs early instead of letting them propagate into URLs
and temp paths.
Cleanup
CleanupExtractDir wipes per-call extraction dirs after overlay; the archive
in the parent commit dir is intentionally kept so subsequent jobs for the
same commit can skip the download. Per-job dotnet home is also deleted at
job end.
Executable bits
EnsureExecutable preserves +x on native files and the dotnet host on
Unix-like systems (File.Copy can drop the bit if the destination didn't
have it).
Tests (36 BuildCacheClient tests, all passing)
New coverage for:
- SelectHighestManagedDir picks net11.0 over net10.0 over net9.0,
- GetRidForConfig for every supported config,
- ValidateCommitSha (accepts/rejects cases incl. path-traversal-like
inputs),
- CreateBuildCacheDotnetHome: mirrors global, overlays BCS, rewrites
.version, and asserts the global home is NOT touched,
- Two concurrent CreateBuildCacheDotnetHome invocations produce isolated
homes,
- OverlayPublishedOutput preserves a pre-existing SDK-bound apphost.
Live test result
--application.framework net11.0 --application.channel buildcache against a
Linux agent:
| .NET Runtime Version | 11.0.0-preview.6.26277.104+38d408d22a64 |
| Requests/sec | 8,456,046 |
Per-job dotnet home created at:
/tmp/crank-buildcache/home-38d408d2-coreclr_x64_linux-aaa3385bb0ae4bdca01c7c6b4a7ca2bc
Global dotnetHome .version preserved: b520ee7cc01690f70d1431951f554c4e0666a69a
(the feed commit, not the BCS commit) — proving the isolation works.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Extends the buildcache channel (currently runtime/coreclr only) to also
resolve dotnet/aspnetcore builds from the Build Cache Service, selected
per-job via a new buildCacheRepo property (runtime|aspnetcore, default
runtime). The runtime path is unchanged; aspnetcore is purely additive.
- Job.cs: BuildCacheRepo property + build-key plumbing.
- BuildCacheClient.cs: BuildCacheFlavor enum + ParseFlavor/GetConfigMap/
AllConfigs; flavour-aware ResolveBuildCacheConfig/GetArtifactFile/
GetRidForConfig; CreateBuildCacheDotnetHome + OverlayPublishedOutput
overlay Microsoft.AspNetCore.App (managed-only, no host) for aspnetcore.
- Startup.cs: flavour-aware selection, version defaulting/sentinels,
aspNetCoreVersion re-resolution, Q2 single-component collision handling,
apply + reporting (+buildcache.{sha} on AspNetCoreVersion).
- Tests: aspnetcore ParseFlavor/GetRidForConfig/overlay/home tests (93 green).
- Docs: buildCacheRepo usage, aspnetcore archive layout, single-component caveat.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Switch the aspnetcore build-cache flavour from a transformed
microsoft.aspnetcore.app.runtime.{rid}/Release/... archive to the verbatim
runtime-pack nupkg. A nupkg is a zip on every OS, so it extracts with the same
ZipFile path and exposes runtimes/{rid}/... at the archive root (no Release
wrapper). This keeps the full shipped artifact (crank already filters managed/
native at consume time) instead of a lossy projection.
- PlatformToBcsConfigAspNetCore artifact files -> .nupkg
- CreateBuildCacheDotnetHome / OverlayPublishedOutput aspnetcore overlay reads
runtimes/{rid} from the extract root; runtime flavour unchanged
- ExtractArchiveAsync treats .nupkg as a zip
- Tests + docs updated for the raw-nupkg layout
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Builds on the previous commit (which switched the aspnetcore artifact to the verbatim
runtime-pack nupkg and fixed the .nupkg extension / extractor / bare runtimes/{rid}
layout). The verbatim nupkg carries the host-resolvable Microsoft.AspNetCore.App.deps.json
+ runtimeconfig.json next to the managed assemblies, so the per-job dotnet home can build
the ASP.NET Core shared framework ENTIRELY from BCS instead of overlaying onto a feed copy.
- CreateBuildCacheDotnetHome: for the aspnetcore flavour, place Microsoft.AspNetCore.App
directly from the pack (all managed incl deps.json + runtimeconfig.json + native,
synthesized .version); do not clone the feed copy of that framework. Fail loud
(BuildCacheIncompleteException) if the pack is missing managed assemblies, deps.json,
or runtimeconfig.json -- for perf runs, erroring beats silently running mixed/feed bits.
Base runtime + host stay feed-cloned. New PlaceAspNetFrameworkFromPack +
ResolveAspNetRuntimesDir (bare layout, wrapped fallback).
- OverlayPublishedOutput (SCD): unchanged behaviour (managed *.dll + native only, app's
own .deps.json governs); now resolves the pack via ResolveAspNetRuntimesDir.
- Runtime flavour: overlay path unchanged.
- Tests: aspnetcore archive helper now includes deps.json + runtimeconfig.json; FDD test
asserts pristine direct-placement (metadata present, no feed leak); +2 fail-loud
negatives. Docs updated. 95/95 unit tests green.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Refactor the buildcache channel from a single-flavour-per-job selector to
always overriding BOTH the base runtime (Microsoft.NETCore.App, dotnet/runtime)
and the ASP.NET Core shared framework (Microsoft.AspNetCore.App, dotnet/aspnetcore)
in every job, latest-by-default, with independent per-repo pinning.
- Job.cs: replace BuildCacheRepo/BuildCacheCommitSha/BuildCacheBranch/BuildCacheConfig
with per-repo BuildCacheRuntimeCommitSha/BuildCacheAspNetCoreCommitSha and
BuildCacheRuntimeBranch/BuildCacheAspNetCoreBranch (config auto-derived per repo).
- JobContext.cs: split BuildCacheExtractDir into runtime + aspnetcore extract dirs.
- BuildCacheClient.cs: rework CreateBuildCacheDotnetHome to a both-flavour signature;
factor the runtime overlay into OverlayRuntimeIntoHome; each side guarded so a
single framework can still be overlaid in isolation.
- Startup.cs: drop the --build-cache-repo-name option; resolve+download both repos
independently; overlay published output per flavour; annotate both RuntimeVersion
and AspNetCoreVersion with +buildcache.{sha}; clean up both extract dirs.
- Tests: migrate call sites to the new signature; add a both-framework overlay test.
- Docs: rewrite dotnet_versions.md and build_cache_requirements.md for the
two-SHA/two-branch always-both model.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f12e0666-adff-4aaa-b059-821e8384151d
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Suppressed comments (6)
src/Microsoft.Crank.Agent/BuildCacheClient.cs:1135
- This maps every non-ARM64 Linux host to
linux-x64, so thelinux-musl-x64configuration declared above is never selected. On a musl agent, buildcache therefore downloads glibc runtime and ASP.NET artifacts instead of failing as the documentation promises for unsupported musl ASP.NET builds, leading to incompatible binaries. Detect musl explicitly (or reject it before resolution).
return RuntimeInformation.ProcessArchitecture == Architecture.Arm64 ? "linux-arm64" : "linux-x64";
src/Microsoft.Crank.Agent/Startup.cs:3049
- This per-job extract is not registered on
JobContextuntil line 3368. If the ASP.NET download, SDK/runtime installation, cancellation check, or home creation fails first,DeleteJobAsyncsees null and leaves this potentially multi-GB directory behind. Register the extract immediately so the existing final cleanup owns it on every later return path.
runtimeBuildCacheExtractDir = await BuildCacheClient.DownloadAndExtractAsync(
_buildCacheBaseUrl, BuildCacheClient.RepoNameRuntime, runtimeBuildCacheCommitSha, runtimeBuildCacheConfigResolved, cancellationToken);
src/Microsoft.Crank.Agent/Startup.cs:3066
- Like the runtime extract, this directory is only assigned to
JobContextafter all installation and isolated-home work succeeds. Any intervening failure bypasses the end-of-job cleanup and leaks the extraction. Register it here immediately after creation.
aspNetCoreBuildCacheExtractDir = await BuildCacheClient.DownloadAndExtractAsync(
_buildCacheBaseUrl, BuildCacheClient.RepoNameAspNetCore, aspNetCoreBuildCacheCommitSha, aspNetCoreBuildCacheConfigResolved, cancellationToken);
src/Microsoft.Crank.Agent/BuildCacheClient.cs:227
- User SHAs of 8–39 characters are accepted, but
ResolveCommitAsyncreturns them unchanged and this constructs an exact blob path from that value. Static blob storage cannot expand a commit prefix, so an advertised prefix requests a prefix-named directory and returns 404 instead of resolving the full commit. Either require the exact SHA length used by BCS or resolve prefixes through an index/API before downloading.
var artifactUrl =
$"{normalizedBaseUrl}/builds/{Uri.EscapeDataString(repoName)}/buildArtifacts/" +
$"{Uri.EscapeDataString(commitSha)}/{Uri.EscapeDataString(buildCacheConfig)}/{Uri.EscapeDataString(artifactFile)}";
src/Microsoft.Crank.Models/Job.cs:83
- The PR description still advertises the controller properties
BuildCacheCommitSha,BuildCacheBranch, andBuildCacheConfig, plus a--build-cache-repo-nameagent option, but this API instead exposes four repo-specific properties and no config override or repo-name option. Update the PR wire-up and usage sections to match the implemented dual-repository contract so consumers do not use nonexistent options.
public string BuildCacheRuntimeCommitSha { get; set; } = "";
public string BuildCacheAspNetCoreCommitSha { get; set; } = "";
public string BuildCacheRuntimeBranch { get; set; } = "";
public string BuildCacheAspNetCoreBranch { get; set; } = "";
src/Microsoft.Crank.Agent/BuildCacheClient.cs:275
- Downloaded archives are intentionally retained with no size, age, or count bound. Per-commit bisection—the primary use case—adds runtime and ASP.NET archives for every tested SHA, so a long-lived agent's temp volume grows indefinitely and can eventually exhaust disk space. Add a bounded cache/eviction policy while preserving active extracts.
/// <summary>
/// Deletes a previously-extracted directory. Safe to call multiple times. Archives in the
/// parent commit dir are intentionally NOT deleted so subsequent jobs for the same commit
/// can reuse the download.
…, RID unify Fixes three Copilot review comments on the buildcache channel: - #1 (build reuse): a framework-dependent reused build previously returned early before any BCS resolve, so it ran the feed runtime instead of the resolved BCS bits. Introduce SHA-keyed persistent dotnet homes under a shared LRU root and RefreshBuildCacheForReuseAsync on the reuse path: re-resolve current shas (latest advances, pinned is idempotent), attach the persistent home on hit or re-materialize on drift/eviction (FDD), re-overlay only drifted side(s) for self-contained, re-stamp the two +buildcache.{sha} version surfaces, and fail loud on refresh failure. - #2 (overlay validation): OverlayRuntimeIntoHome now reports per-category counts (managed assemblies, native libraries, host binaries) and throws unless both managed and native files were copied. - #3 (RID unification): GetPlatformMoniker collapses Windows X86 to win-x64. Adds unit tests for the persistent-home key/cache-hit/drift behavior. Full solution builds clean; unit suite 99/99. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f12e0666-adff-4aaa-b059-821e8384151d
|
|
||
| The difference between `latest` and `edge` is that `latest` will pick runtimes and SDKs that are deemed compatible together. For instance a very recent .NET core runtime might be compatible with a less recent ASP.NET runtime. The `edge` is used to pick the absolute latest build for the select TFM. | ||
|
|
||
| The `buildcache` channel uses the Build Cache Service (BCS) from `dotnet-performance-infra` to resolve framework versions by individual commit SHA rather than from VMR feeds. This provides much finer-grained control — every cached commit is available, whereas VMR feeds may have multi-day gaps between ingested commits. On this channel crank overrides **both** the base .NET runtime (`Microsoft.NETCore.App`, from dotnet/runtime) **and** the ASP.NET Core shared framework (`Microsoft.AspNetCore.App`, from dotnet/aspnetcore); each defaults to the latest cached build and can be pinned independently. SDK and desktop versions are resolved from `latest`. |
There was a problem hiding this comment.
I would prefer to name it ci, because buildcache sounds too much like an implementation detail.
There was a problem hiding this comment.
That makes sense, especially with the other channel names. Went ahead and updated this everywhere user-facing, but kept some of the internals as they truly refer to the buildCache.
| ### Bisecting ASP.NET Core (pin aspnetcore, runtime stays latest) | ||
|
|
||
| ``` | ||
| > crank --config benchmarks.yml --scenario json --profile aspnet-perf-lin --application.channel buildcache --application.buildCacheAspNetCoreCommitSha a1b2c3d4e5f6... |
There was a problem hiding this comment.
Since we use a custom channel, which makes sense (what feed is the runtime coming from), would it be possible to reuse the exist "version" arguments instead of adding new ones? They will be shas and not actual versions.
And not setting a version would pick the latest one from the ci channel (buildcache)
There was a problem hiding this comment.
I could see a world where we could set either a version or a sha, since I assume all builds actually have a version.
There was a problem hiding this comment.
Moved over to this approach. The ci channel only supports shas because the Cache is keyed by commit and currently doesn't have any mapping or relation to the longer version strings. We would have to decide how we want to map versions to sha's as the VMR does not have a version for every ASP.NET or Runtime commit, and I am not aware of any other place that would have a version for every SHA (though maybe the mapping doesn't have to cover every SHA for general use).
| ### Different branch (per repo) | ||
|
|
||
| ``` | ||
| > crank --config benchmarks.yml --scenario json --profile aspnet-perf-lin --application.channel buildcache \ |
There was a problem hiding this comment.
Does it mean we can target a release branch and it will pick up the latest available build for this branch?
I was assuming it would only be for the main branches,
There was a problem hiding this comment.
I don't see why this would be two different variables, no way we would use different branches for aspnet/dotnet.
Another option would be to reuse the existing version arguments which already have the convention with patterns. Creating a custom format for wildcard versions.
There was a problem hiding this comment.
Yea, this was an idea of where this could be down the line with the idea that custom branches would be selectable from the cache for testing. Instead, I have removed ciBranch and this flow as a whole as we are currently only building for main so the functionality would not actually work for anything. If we find we need this in the future we can add it then, otherwise specific commits can be used from wherever they are built.
| | `buildCacheRuntimeBranch` | `main` | Branch to query for the latest runtime build. | | ||
| | `buildCacheAspNetCoreBranch` | `main` | Branch to query for the latest aspnetcore build. | | ||
|
|
||
| The BCS configuration key (e.g., `coreclr_x64_linux` for runtime, `aspnetcore_x64_linux` for aspnetcore) is auto-detected per repo from the agent platform. Platforms with no aspnetcore config (there is no macOS/musl/arm32 in v1) fail loud rather than silently skipping. |
There was a problem hiding this comment.
this is the kind of idea I am having when suggesting to use existing arguments, and base the logic on the channel.
There was a problem hiding this comment.
Landed on this IIUC.
On the buildcache channel the base shared-framework folder is always resolved to the latest feed version and the BCS build is overlaid/placed on top, so a user-supplied runtimeVersion/aspNetCoreVersion was silently ignored. Add a fail-loud guard that rejects (job.Error) any non-empty, non-"latest" value for those fields on this channel, steering users to buildCacheRuntimeCommitSha / buildCacheAspNetCoreCommitSha for pinning/bisection. Document the behavior in docs/dotnet_versions.md and the controller README. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f12e0666-adff-4aaa-b059-821e8384151d
… channel" This reverts commit 28ed602.
Rename the user-facing channel 'buildcache' to 'ci'. Reuse runtimeVersion/
aspNetCoreVersion to carry a commit SHA on the ci channel (empty = latest;
a version string is rejected with a clear, arg-named error). Collapse the
four per-repo BuildCache* commit/branch Job fields into a single ciBranch
(default main), used only for latest-lookup resolution. Change the reported
version suffix from +buildcache.{sha} to +ci.{sha}.
Runtime path behavior is unchanged; aspnetcore support remains additive.
Updates docs (dotnet_versions.md, build_cache_requirements.md), Controller
README, and adds unit coverage for IsCommitSha / TryResolveCiVersionPin.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f12e0666-adff-4aaa-b059-821e8384151d
On the ci channel, fold the "+ci.{12-char sha}" build-cache stamp into the
existing AspNetCoreVersion / NetCoreAppVersion result measurements instead of
adding separate keys. These measurements are what land in the controller
--json results and in the SQL/ES document as
application.aspNetCoreVersion / netCoreAppVersion, so BCS bisection runs are
now self-identifying in the perf database (e.g. 8.0.30+ci.8f51f037469f).
- Fresh-build path: the existing .version-file-derived measurement values gain
a "ci." marker only when useBuildCache (non-ci runs stay {ver}+{sha}).
- Reuse path: records the same AspNetCoreVersion / NetCoreAppVersion keys with
the {feedVer}+ci.{sha} value (the reuse path returns before the fresh-build
recording, so without this a reused ci run would surface no version).
- Dropped the additive AspNetCoreCiVersion / NetCoreAppCiVersion keys and the
Measurements constants; the value now lives in the existing fields.
- Internal DTO job.RuntimeVersion / job.AspNetCoreVersion keep their existing
+ci.{8-char} stamp (unchanged); surfaced measurements use 12-char to match
the sibling .version measurements.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f12e0666-adff-4aaa-b059-821e8384151d
ci channel for per-commit Build Cache Service framework resolution
The BCS pipeline only builds main, so no other branch's latest index is ever
published -- ciBranch was a non-functional knob. Remove it entirely; the
'latest' lookup (empty runtimeVersion/aspNetCoreVersion) now always targets
main. A commit SHA still pins one repo while the other stays latest, so
independent per-repo bisection is preserved.
- Job.cs: remove the CiBranch property, its GetBuildKeyData assignment, and the
BuildKeyData.CiBranch field (dropped from the build key entirely).
- Startup.cs: both resolution sites pass the literal "main" into
ResolveCommitAsync instead of a job.CiBranch-derived local. BuildCacheClient's
branch parameter is unchanged (still builds the latest/{branch}/... URL).
- docs/dotnet_versions.md + Controller README: drop the ciBranch example, table
row, CLI entry, and branch-parity note; reword "latest on ciBranch" to main.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f12e0666-adff-4aaa-b059-821e8384151d
Adds a new
cichannel that resolves .NET binaries from the Build Cache Service (BCS) instead of NuGet feeds, giving per-commit granularity for performance-regression bisection instead of feed cadence.On the
cichannel, crank always overlays BOTH shared frameworks in every job — the base runtime (Microsoft.NETCore.App, from dotnet/runtime) and the ASP.NET Core shared framework (Microsoft.AspNetCore.App, from dotnet/aspnetcore). Both default to latest, and each repo can be pinned independently to bisect one while holding the other at latest.CLI surface
Rather than add new arguments, the
cichannel reuses the existingruntimeVersion/aspNetCoreVersionarguments — on this channel they carry a commit SHA instead of a feed version:mainci; conversely the other channels accept a version string and reject a SHA)No new arguments are introduced. Latest always resolves against
main— the only branch the BCS pipeline builds — so there's no branch knob to configure. To target a specific point, pin a commit SHA; SHA lookups are branch-independent, so a commit that happens to live on a release branch still resolves. (If the pipeline starts caching release branches later, branch selection can be added back as a purely additive argument.)Usage:
Controller (
Job.cs) — reusesRuntimeVersion/AspNetCoreVersion(no new properties); on thecichannel they carry a commit SHA.Agent (
Startup.cs/BuildCacheClient.cs) — new CLI:--build-cache-base-url(default:https://pvscmdupload.z22.web.core.windows.net)--build-cache-disabledHow resolution works
The agent still builds the app with real NuGet packages (so
restore/publishbehave normally), then overlays BCS bits per publish kind:<temp>/crank-buildcache/home-…/, mirrored from the global_dotnethome, and overlaid with the BCS runtime and aspnetcore archives.StartProcessresolves from this home; the global_dotnethomeis never mutated (concurrent jobs / other channels unaffected).outputFolder; the SDK-bound apphost is preserved.The base runtime archive is raw build output, so BCS bits are overlaid onto a feed-installed runtime. The aspnetcore archive is the runtime-pack nupkg stored verbatim (carrying
deps.json+runtimeconfig.json), so that framework folder is built entirely from BCS and the job fails loudly if the pack is incomplete. If a requested commit isn't in the cache, crank fails rather than silently falling back.Version surfaces (bisection distinguishability)
Every
cirun stamps the resolved commits so bisection points never collide, and the stamp reaches the perf database:NetCoreAppVersion/AspNetCoreVersion(what lands in the controller--jsonand the SQL/ES document asapplication.netCoreAppVersion/aspNetCoreVersion) →{feedVersion}+ci.{12-char sha}on thecichannel; non-ciruns are unchanged ({feedVersion}+{12-char sha}). The+ci.marker is folded into the existing fields rather than added as new keys, so bisection runs are self-identifying without any downstream schema change.job.RuntimeVersion/job.AspNetCoreVersion(the DTO, visible on the agent job object) →{feedVersion}+ci.{8-char sha}Dependencyrecords carry the full 40-charCommitHash+ repo URL for both runtime and aspnetcoreThe
12- and8-char forms and the fullDependencyhash all encode the same underlying commit; the numeric version prefix stays feed-coherent (so runtimeconfig/roll-forward stay valid) and the+ci.{sha}suffix is the per-commit distinguisher.Cache correctness
RuntimeVersionandAspNetCoreVersionare part ofBuildKeyData, so--application.options.reuseBuildcache-busts when a pinned SHA changes. The reuse path also records the+ci.{sha}version measurements (it returns before the fresh-build recording, so without this a reusedcirun would surface no version).Backward compatibility
Fully gated on
--application.channel ci.DefaultChannelis unchanged (current). No new controller properties are added — the existingruntimeVersion/aspNetCoreVersionarguments only change meaning on thecichannel; every other channel takes the exact same path as before, so old controllers/agents interoperate unchanged. Audited diff-level againstmain: purely additive, no opt-out-path behavior change.Verification
dotnet buildclean; 119/119 unit tests pass (BuildCacheClientTests.csadds 40 methods coveringParseLatestBuilds, commit-SHA validation incl. path-traversal, the SHA-vs-version channel rules, dual-repo overlay, isolated-home concurrency,SelectHighestManagedDirnumeric ordering, cleanup, etc.).cipaths end-to-end:latest/main/latestBuilds.jsonpath per repo;aspNetCoreVersion = {ver}+ci.8f51f037469fandnetCoreAppVersion = {ver}+ci.6550ccf7827c(matching the resolved commits), while the non-ciload job carries noci.marker (gating intact);cifails fast with the documented error.