From 0cd0fb15b24cf8ef4e61b4e6d516ac68b289f0ed Mon Sep 17 00:00:00 2001 From: Parker Bibus Date: Fri, 11 Sep 2026 12:52:00 -0700 Subject: [PATCH 01/17] Keep the main performance channel on the .NET 11 SDK (#5308) Avoid generating net12.0 benchmark projects with an SDK that still supports only net11.0 targeting. Co-authored-by: Parker Bibus Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- scripts/channel_map.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/channel_map.py b/scripts/channel_map.py index a043902174a..0d5de09b272 100644 --- a/scripts/channel_map.py +++ b/scripts/channel_map.py @@ -4,7 +4,7 @@ class ChannelMap(): channel_map = { 'main': { 'tfm': 'net11.0', - 'branch': '12.0', + 'branch': '11.0', 'quality': 'daily' }, '11.0': { From 743c3c623a09044b4833d2c2766bb77b4c8cce8e Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Sat, 12 Sep 2026 18:12:49 -0500 Subject: [PATCH 02/17] Fix WASM tool framework version rollover (#5310) Materialize the SDK's installed Microsoft.NETCore.App framework under the exact local WebAssembly tool package version so ILLink can start without broad roll-forward settings. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- scripts/build_runtime_payload.py | 31 +++++++++++++++ scripts/tests/test_wasm_coreclr_r2r.py | 52 +++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 1 deletion(-) diff --git a/scripts/build_runtime_payload.py b/scripts/build_runtime_payload.py index 23ef25323f1..926b7efcfbd 100644 --- a/scripts/build_runtime_payload.py +++ b/scripts/build_runtime_payload.py @@ -350,6 +350,7 @@ def build_wasm_coreclr_payload( browser_wasm_coreclr_archive_or_dir, wasm_built_nugets_dir, prefix="staging/built-nugets/" ) local_package_version = _get_wasm_local_package_version(wasm_built_nugets_dir) + _ensure_wasm_tool_framework_version(wasm_dotnet_dir, local_package_version) # Determine version from the runtime pack directory structure runtime_pack_src = os.path.join( @@ -380,6 +381,36 @@ def build_wasm_coreclr_payload( return local_package_version +def _ensure_wasm_tool_framework_version(wasm_dotnet_dir: str, package_version: str) -> None: + """Make the SDK's shared framework available at the local tool package version.""" + shared_framework_dir = Path(wasm_dotnet_dir) / "shared" / "Microsoft.NETCore.App" + requested_framework_dir = shared_framework_dir / package_version + if requested_framework_dir.is_dir(): + return + + version_parts = package_version.split(".", 2) + if len(version_parts) < 2 or not all(part.isdigit() for part in version_parts[:2]): + raise ValueError(f"Invalid local WebAssembly toolchain package version: {package_version}") + + version_prefix = f"{version_parts[0]}.{version_parts[1]}." + compatible_frameworks = sorted( + path for path in shared_framework_dir.glob(f"{version_prefix}*") if path.is_dir() + ) + if len(compatible_frameworks) != 1: + installed_versions = ", ".join(path.name for path in compatible_frameworks) or "none" + raise ValueError( + f"Expected one installed Microsoft.NETCore.App {version_parts[0]}.{version_parts[1]} " + f"framework for local WebAssembly tools {package_version}, found: {installed_versions}" + ) + + shutil.copytree(compatible_frameworks[0], requested_framework_dir) + getLogger().info( + "Installed Microsoft.NETCore.App framework alias %s from %s for local WebAssembly tools", + package_version, + compatible_frameworks[0].name, + ) + + def _get_wasm_local_package_version(built_nugets_dir: str) -> str: package_prefix = "Microsoft.NET.Sdk.WebAssembly.Pack." wasm_sdk_packages = [ diff --git a/scripts/tests/test_wasm_coreclr_r2r.py b/scripts/tests/test_wasm_coreclr_r2r.py index 6f88e06adac..dd3d6194619 100644 --- a/scripts/tests/test_wasm_coreclr_r2r.py +++ b/scripts/tests/test_wasm_coreclr_r2r.py @@ -89,18 +89,68 @@ def test_ready_to_run_validates_resolved_runtime_pack_items(): def test_coreclr_payload_detects_local_toolchain_package_version(tmp_path): artifact = tmp_path / "artifact" / "staging" ref_pack = artifact / "dotnet-none" / "packs" / "Microsoft.NETCore.App.Ref" / "11.0.0-rc.1.26431.109" + shared_framework = artifact / "dotnet-none" / "shared" / "Microsoft.NETCore.App" / "11.0.0-rc.1.26431.109" runtime_pack = artifact / "microsoft.netcore.app.runtime.browser-wasm" / "Release" built_nugets = artifact / "built-nugets" ref_pack.mkdir(parents=True) + shared_framework.mkdir(parents=True) runtime_pack.mkdir(parents=True) built_nugets.mkdir(parents=True) + (shared_framework / "System.Private.CoreLib.dll").touch() (built_nugets / "Microsoft.NET.Sdk.WebAssembly.Pack.11.0.0-ci.nupkg").touch() (built_nugets / "Microsoft.NETCore.App.Crossgen2.linux-x64.11.0.0-ci.nupkg").touch() (built_nugets / "Microsoft.NET.ILLink.Tasks.11.0.0-ci.nupkg").touch() - version = build_wasm_coreclr_payload(str(artifact.parent), str(tmp_path / "payload")) + payload = tmp_path / "payload" + version = build_wasm_coreclr_payload(str(artifact.parent), str(payload)) assert version == "11.0.0-ci" + assert ( + payload + / "dotnet" + / "shared" + / "Microsoft.NETCore.App" + / "11.0.0-ci" + / "System.Private.CoreLib.dll" + ).is_file() + + +def test_coreclr_payload_aliases_sdk_framework_during_major_version_rollover(tmp_path): + artifact = tmp_path / "artifact" / "staging" + dotnet = artifact / "dotnet-none" + installed_version = "12.0.0-alpha.1.26458.117" + shared_framework = dotnet / "shared" / "Microsoft.NETCore.App" / installed_version + built_nugets = artifact / "built-nugets" + shared_framework.mkdir(parents=True) + built_nugets.mkdir(parents=True) + (shared_framework / "System.Private.CoreLib.dll").write_text("runtime") + (built_nugets / "Microsoft.NET.Sdk.WebAssembly.Pack.12.0.0-ci.nupkg").touch() + (built_nugets / "Microsoft.NETCore.App.Crossgen2.linux-x64.12.0.0-ci.nupkg").touch() + (built_nugets / "Microsoft.NET.ILLink.Tasks.12.0.0-ci.nupkg").touch() + + payload = tmp_path / "payload" + version = build_wasm_coreclr_payload(str(artifact.parent), str(payload)) + + aliased_framework = ( + payload / "dotnet" / "shared" / "Microsoft.NETCore.App" / "12.0.0-ci" + ) + assert version == "12.0.0-ci" + assert (aliased_framework / "System.Private.CoreLib.dll").read_text() == "runtime" + + +def test_coreclr_payload_rejects_missing_compatible_sdk_framework(tmp_path): + artifact = tmp_path / "artifact" / "staging" + (artifact / "dotnet-none" / "shared" / "Microsoft.NETCore.App" / "11.0.0").mkdir( + parents=True + ) + built_nugets = artifact / "built-nugets" + built_nugets.mkdir(parents=True) + (built_nugets / "Microsoft.NET.Sdk.WebAssembly.Pack.12.0.0-ci.nupkg").touch() + (built_nugets / "Microsoft.NETCore.App.Crossgen2.linux-x64.12.0.0-ci.nupkg").touch() + (built_nugets / "Microsoft.NET.ILLink.Tasks.12.0.0-ci.nupkg").touch() + + with pytest.raises(ValueError, match="Expected one installed Microsoft.NETCore.App 12.0"): + build_wasm_coreclr_payload(str(artifact.parent), str(tmp_path / "payload")) def test_coreclr_payload_rejects_mismatched_toolchain_package_versions(tmp_path): From 84ea38fb9e02c5fc45c89e78b8dc5425c8bf1b23 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Tue, 15 Sep 2026 14:02:08 -0500 Subject: [PATCH 03/17] Acquire coherent nightly WASM workloads (#5300) * Acquire coherent nightly WASM workloads Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Fix BenchmarkDotNet documentation link Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 797f00e5-64e5-4d0b-9b62-bf06a1bf9c9d * Address nightly WASM review feedback Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 797f00e5-64e5-4d0b-9b62-bf06a1bf9c9d * Use Windows syntax for WASM package sources Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 797f00e5-64e5-4d0b-9b62-bf06a1bf9c9d * Use Windows path for WASM dotnet payload Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 797f00e5-64e5-4d0b-9b62-bf06a1bf9c9d * Scope nightly WASM workload setup Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 797f00e5-64e5-4d0b-9b62-bf06a1bf9c9d * Use workload no-cache option Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 797f00e5-64e5-4d0b-9b62-bf06a1bf9c9d --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 797f00e5-64e5-4d0b-9b62-bf06a1bf9c9d --- docs/benchmarkdotnet.md | 1 - docs/benchmarking-workflow-dotnet-runtime.md | 30 ++ .../templates/run-performance-job.yml | 3 + eng/pipelines/templates/runtime-perf-job.yml | 1 + scripts/benchmarks_ci.py | 25 ++ scripts/dotnet.py | 300 +++++++++++++++++ scripts/run_performance_job.py | 113 ++++++- scripts/tests/test_wasm_coreclr_r2r.py | 313 +++++++++++++++++- 8 files changed, 768 insertions(+), 18 deletions(-) diff --git a/docs/benchmarkdotnet.md b/docs/benchmarkdotnet.md index be429f57ad7..ed084e27876 100644 --- a/docs/benchmarkdotnet.md +++ b/docs/benchmarkdotnet.md @@ -27,7 +27,6 @@ BenchmarkDotNet is the benchmarking tool that allows to run benchmarks for .NET, - [Running In Process](#running-in-process) - [CoreRun](#corerun) - [dotnet cli](#dotnet-cli) - - [Private CLR Build](#private-clr-build) - [Private CoreRT Build](#private-corert-build) ## Main Concepts diff --git a/docs/benchmarking-workflow-dotnet-runtime.md b/docs/benchmarking-workflow-dotnet-runtime.md index 44d1ecd0d75..53ac9b1ceb1 100644 --- a/docs/benchmarking-workflow-dotnet-runtime.md +++ b/docs/benchmarking-workflow-dotnet-runtime.md @@ -11,6 +11,7 @@ - [Install v8 engine](#install-v8-engine) - [Run the benchmarks with the interpreter](#run-the-benchmarks-with-the-interpreter) - [Run the benchmarks with AOT](#run-the-benchmarks-with-aot) + - [Run CoreCLR WASM ReadyToRun with a nightly or VMR SDK](#run-coreclr-wasm-readytorun-with-a-nightly-or-vmr-sdk) - [Note about "file ... being used by another process" error](#note-about-file--being-used-by-another-process-error) - [dotnet runtime testing for MonoAOT](#dotnet-runtime-testing-for-monoaot) - [Prerequisites (Files either built locally (with build.(sh/cmd) or downloaded from payload above (if same system setup) (in this order))](#prerequisites-files-either-built-locally-with-buildshcmd-or-downloaded-from-payload-above-if-same-system-setup-in-this-order) @@ -204,6 +205,35 @@ Essentially, add `--aotcompilermode wasm` to the `--bdn-arguments=".."`: --bdn-arguments="--category-exclusion-filter NoInterpreter NoWASM NoMono --aotcompilermode wasm --logBuildOutput --buildTimeout 3600 --filter " ``` +#### Run CoreCLR WASM ReadyToRun with a nightly or VMR SDK + +Use an exact SDK version together with a NuGet source containing the matching +browser runtime/ref, WebAssembly SDK, host-specific Crossgen2, and ILLink +packages, plus the complete `wasm-tools` pack closure. The source can be a +downloaded VMR package directory or a coherent feed. Workload manifest updates +are disabled and only exact versions recorded by the selected SDK are restored +from that source, so packages cannot float to another build cohort. + +```cmd +/path/to/dotnet/performance$ python3 ./scripts/benchmarks_ci.py \ + --csproj src/benchmarks/micro/MicroBenchmarks.csproj \ + -f net11.0 \ + --dotnet-versions \ + --wasm \ + --wasm-runtime-flavor CoreCLR \ + --wasm-ready-to-run \ + --wasm-workload-source /path/to/vmr/packages \ + --run-isolated \ + --bdn-artifacts artifacts/BenchmarkDotNet.Artifacts \ + --bdn-arguments="--category-exclusion-filter NoWASM --logBuildOutput --buildTimeout 3600 --filter " +``` + +Omit `--dotnet-versions` to use the existing `main` daily-channel SDK +selection. In that case the package source must contain the product version +recorded in that SDK's `Microsoft.NETCoreSdk.BundledVersions.props`. +`PERFLAB_WASM_PACKAGE_VERSION` should remain unset; that override is reserved +for non-official runtime payloads. + #### Note about "file ... being used by another process" error If you are seeing warnings like: diff --git a/eng/pipelines/templates/run-performance-job.yml b/eng/pipelines/templates/run-performance-job.yml index 0045cdb5a03..80a7f771cde 100644 --- a/eng/pipelines/templates/run-performance-job.yml +++ b/eng/pipelines/templates/run-performance-job.yml @@ -57,6 +57,7 @@ parameters: iOSStripSymbols: true # optional -- Whether to strip symbols from the iOS build machinePool: '' # optional -- Machine pool name (e.g. Pixel8, GalaxyA16, iPhone17) additionalSetupParameters: '' # optional -- Additional arguments to pass to the script + wasmWorkloadSource: '' # optional -- Exclusive coherent package source for SDK CoreCLR browser-WASM liveLibrariesBuildConfig: '' # optional -- Build configuration when generating Core_Root for libraries crossBuild: false # optional -- Whether the Core_Root is being cross-compiled @@ -241,6 +242,8 @@ jobs: - '--cross-build' - ${{ if ne(parameters.additionalSetupParameters, '') }}: - '${{ parameters.additionalSetupParameters }}' + - ${{ if and(ne(parameters.wasmWorkloadSource, ''), eq(parameters.runtimeType, 'wasm_coreclr'), eq(parameters.r2rRunType, 'r2r')) }}: + - '--wasm-workload-source "${{ parameters.wasmWorkloadSource }}"' - template: /eng/pipelines/templates/send-to-helix-step.yml parameters: osGroup: ${{ parameters.osGroup }} diff --git a/eng/pipelines/templates/runtime-perf-job.yml b/eng/pipelines/templates/runtime-perf-job.yml index 479d40959a8..3c4fa42a959 100644 --- a/eng/pipelines/templates/runtime-perf-job.yml +++ b/eng/pipelines/templates/runtime-perf-job.yml @@ -16,6 +16,7 @@ parameters: isScenario: false downloadSpecificBuild: null # buildId, pipeline, branchName, project crossBuild: false + wasmWorkloadSource: '' runtimeRepoAlias: runtime performanceRepoAlias: self selfIsRuntime: true diff --git a/scripts/benchmarks_ci.py b/scripts/benchmarks_ci.py index 2aaea577db4..17131362e9e 100755 --- a/scripts/benchmarks_ci.py +++ b/scripts/benchmarks_ci.py @@ -101,6 +101,14 @@ def __is_valid_dotnet_path(dp: str) -> str: type=__is_valid_dotnet_path, help='Path to a custom dotnet' ) + parser.add_argument( + '--wasm-workload-source', + dest='wasm_workload_source', + required=False, + help=( + 'Exclusive NuGet source containing the coherent browser-WASM workload ' + 'packages for the selected SDK. Requires CoreCLR WASM ReadyToRun.') + ) parser.add_argument('--upload-to-perflab-container', dest="upload_to_perflab_container", @@ -220,6 +228,14 @@ def main(argv: list[str]): raise Exception("Framework version (-f) must be specified.") target_framework_monikers = dotnet.get_target_framework_monikers(args.frameworks) + if args.wasm_workload_source and not ( + args.wasm + and args.wasm_runtime_flavor == 'CoreCLR' + and args.wasm_ready_to_run): + raise ValueError( + '--wasm-workload-source requires --wasm ' + '--wasm-runtime-flavor CoreCLR --wasm-ready-to-run') + # Acquire necessary tools (dotnet) if not args.dotnet_path: init_tools( @@ -233,6 +249,15 @@ def main(argv: list[str]): else: dotnet.setup_dotnet(args.dotnet_path) + if args.wasm_workload_source: + os.environ.pop('PERFLAB_WASM_PACKAGE_VERSION', None) + dotnet.install_wasm_workload( + architecture=args.architecture, + target_framework_monikers=target_framework_monikers, + package_source=args.wasm_workload_source, + sdk_versions=args.dotnet_versions, + verbose=verbose) + # WORKAROUND # The MicroBenchmarks.csproj targets .NET Core 2.1, 3.0, 3.1 and 5.0 # to avoid a build failure when using older frameworks (error NETSDK1045: diff --git a/scripts/dotnet.py b/scripts/dotnet.py index 90be9aaf918..10fe6582bd7 100755 --- a/scripts/dotnet.py +++ b/scripts/dotnet.py @@ -7,6 +7,10 @@ import re import json import datetime +import platform as platform_module +import shutil +import tempfile +import xml.etree.ElementTree as ET from argparse import ArgumentParser, ArgumentTypeError from glob import iglob from logging import getLogger @@ -68,6 +72,14 @@ def __log_script_header(message: str): CSharpProjFile = NamedTuple('CSharpProjFile', file_name=str, working_directory=str) +WasmPackage = NamedTuple('WasmPackage', package_id=str, version=str) +WasmWorkloadCohort = NamedTuple( + 'WasmWorkloadCohort', + sdk_version=str, + product_version=str, + host_rid=str, + workload_id=str, + packages=tuple[WasmPackage, ...]) @tracer.start_as_current_span("get_target_framework_moniker") def get_target_framework_moniker(framework: str) -> str: @@ -106,6 +118,294 @@ def get_target_framework_monikers(frameworks: list[str]) -> list[str]: # ['net6.0', 'nativeaot6.0'] should become ['net6.0'] return list(set(monikers)) + +def get_host_rid( + architecture: str, + system: Optional[str] = None, + libc: Optional[str] = None) -> str: + system = system or platform_module.system() + normalized_system = system.casefold() + + if normalized_system == 'darwin': + os_rid = 'osx' + elif normalized_system == 'windows': + os_rid = 'win' + elif normalized_system == 'linux': + if libc is None: + libc_name = platform_module.libc_ver()[0] + is_musl = ( + libc_name.casefold() == 'musl' + or path.isfile('/etc/alpine-release') + or any(iglob('/lib/ld-musl-*.so.1'))) + else: + is_musl = libc.casefold() == 'musl' + os_rid = 'linux-musl' if is_musl else 'linux' + else: + raise ValueError(f'Unsupported host operating system for Crossgen2: {system}') + + return f'{os_rid}-{architecture}' + + +def get_wasm_workload_cohort( + dotnet_root: str, + sdk_version: str, + target_framework: str, + host_rid: str) -> WasmWorkloadCohort: + sdk_major_match = re.match(r'^(\d+)\.', sdk_version) + framework_major_match = re.match(r'^net(\d+)\.0$', target_framework) + if not sdk_major_match or not framework_major_match: + raise ValueError( + f'Cannot determine a coherent WASM cohort for SDK {sdk_version} and {target_framework}') + if sdk_major_match.group(1) != framework_major_match.group(1): + raise ValueError( + f'WASM workload acquisition requires the SDK and target framework to have the same ' + f'major version, but got SDK {sdk_version} and {target_framework}') + + bundled_versions_path = path.join( + dotnet_root, 'sdk', sdk_version, 'Microsoft.NETCoreSdk.BundledVersions.props') + if not path.isfile(bundled_versions_path): + raise ValueError( + f'Cannot find SDK bundled product versions at {bundled_versions_path}') + + root = ET.parse(bundled_versions_path).getroot() + + def find_item(name: str, include: str) -> ET.Element: + for item in root.iter(name): + if (item.get('Include') == include + and item.get('TargetFramework') == target_framework): + return item + raise ValueError( + f'SDK {sdk_version} does not define {name} {include} for {target_framework}') + + framework_reference = find_item('KnownFrameworkReference', 'Microsoft.NETCore.App') + runtime_rids = framework_reference.get('RuntimePackRuntimeIdentifiers', '').split(';') + if 'browser-wasm' not in runtime_rids: + raise ValueError( + f'SDK {sdk_version} does not provide a browser-wasm CoreCLR runtime pack') + + crossgen = find_item('KnownCrossgen2Pack', 'Microsoft.NETCore.App.Crossgen2') + crossgen_rids = set( + crossgen.get('Crossgen2PortableRuntimeIdentifiers', '').split(';') + + crossgen.get('Crossgen2RuntimeIdentifiers', '').split(';')) + if host_rid not in crossgen_rids: + raise ValueError( + f'SDK {sdk_version} does not provide a Crossgen2 pack for host RID {host_rid}') + + illink = find_item('KnownILLinkPack', 'Microsoft.NET.ILLink.Tasks') + webassembly = find_item( + 'KnownWebAssemblySdkPack', 'Microsoft.NET.Sdk.WebAssembly.Pack') + package_versions = { + 'Microsoft.NETCore.App.Runtime.browser-wasm': + framework_reference.get('DefaultRuntimeFrameworkVersion'), + framework_reference.get('TargetingPackName', 'Microsoft.NETCore.App.Ref'): + framework_reference.get('TargetingPackVersion'), + 'Microsoft.NET.Sdk.WebAssembly.Pack': + webassembly.get('WebAssemblySdkPackVersion'), + f'Microsoft.NETCore.App.Crossgen2.{host_rid}': + crossgen.get('Crossgen2PackVersion'), + 'Microsoft.NET.ILLink.Tasks': + illink.get('ILLinkPackVersion'), + } + + missing_versions = [ + package_id for package_id, version in package_versions.items() if not version + ] + if missing_versions: + raise ValueError( + f'SDK {sdk_version} has no version for required WASM packages: ' + f'{", ".join(missing_versions)}') + + versions = set(package_versions.values()) + if len(versions) != 1: + formatted_versions = ', '.join( + f'{package_id}={version}' for package_id, version in package_versions.items()) + raise ValueError( + f'SDK {sdk_version} does not define one coherent CoreCLR browser-WASM ' + f'package version: {formatted_versions}') + + product_version = next(iter(versions)) + return WasmWorkloadCohort( + sdk_version=sdk_version, + product_version=product_version, + host_rid=host_rid, + workload_id='wasm-tools', + packages=tuple( + WasmPackage(package_id, version) + for package_id, version in package_versions.items())) + + +def get_wasm_workload_commands( + dotnet_executable: str, + cohort: WasmWorkloadCohort, + config_file: str, + restore_project: str, + package_root: str) -> list[list[str]]: + return [ + [ + dotnet_executable, + 'restore', + restore_project, + '--packages', + package_root, + '--configfile', + config_file, + '--no-http-cache', + ], + [ + dotnet_executable, + 'workload', + 'install', + cohort.workload_id, + '--skip-manifest-update', + '--configfile', + config_file, + '--no-cache', + ], + ] + + +def install_wasm_workload( + architecture: str, + target_framework_monikers: list[str], + package_source: str, + sdk_versions: list[str], + verbose: bool) -> WasmWorkloadCohort: + if len(target_framework_monikers) != 1: + raise ValueError( + '--wasm-workload-source requires exactly one target framework') + if len(sdk_versions) > 1: + raise ValueError( + '--wasm-workload-source supports at most one SDK version') + + dotnet_root = environ.get('DOTNET_ROOT') + if not dotnet_root: + raise ValueError('DOTNET_ROOT is not configured') + dotnet_executable = path.join( + dotnet_root, 'dotnet.exe' if platform == 'win32' else 'dotnet') + + if sdk_versions: + sdk_version = sdk_versions[0] + else: + sdk_version = check_output( + [dotnet_executable, '--version'], + cwd=dotnet_root, + text=True).strip() + + host_rid = get_host_rid(architecture) + cohort = get_wasm_workload_cohort( + dotnet_root, + sdk_version, + target_framework_monikers[0], + host_rid) + + parsed_source = urlparse(package_source) + if parsed_source.scheme not in ('http', 'https', 'file'): + package_source = path.abspath(package_source) + if not path.isdir(package_source): + raise ValueError( + f'WASM workload package source does not exist: {package_source}') + + available_packages = { + path.basename(package).casefold() + for package in iglob( + path.join(package_source, '**', '*.nupkg'), + recursive=True) + } + missing_packages = [ + f'{package.package_id}.{package.version}.nupkg' + for package in cohort.packages + if f'{package.package_id}.{package.version}.nupkg'.casefold() + not in available_packages + ] + if missing_packages: + raise ValueError( + f'WASM workload source {package_source} is missing required coherent ' + f'cohort packages: {", ".join(missing_packages)}') + + with tempfile.TemporaryDirectory(prefix='perflab-wasm-workload-') as temp_dir: + config_path = path.join(temp_dir, 'NuGet.Config') + config_root = ET.Element('configuration') + package_sources = ET.SubElement(config_root, 'packageSources') + ET.SubElement(package_sources, 'clear') + ET.SubElement( + package_sources, + 'add', + {'key': 'wasm-cohort', 'value': package_source}) + ET.ElementTree(config_root).write( + config_path, encoding='utf-8', xml_declaration=True) + + global_json_path = path.join(temp_dir, 'global.json') + with open(global_json_path, 'w', encoding='utf-8') as global_json: + json.dump({ + 'sdk': { + 'version': sdk_version, + 'rollForward': 'disable', + 'allowPrerelease': True, + } + }, global_json) + + restore_project_path = path.join(temp_dir, 'WasmCohort.csproj') + project = ET.Element('Project', {'Sdk': 'Microsoft.NET.Sdk'}) + properties = ET.SubElement(project, 'PropertyGroup') + ET.SubElement(properties, 'TargetFramework').text = target_framework_monikers[0] + ET.SubElement(properties, 'RestoreProjectStyle').text = 'PackageReference' + package_downloads = ET.SubElement(project, 'ItemGroup') + for package in cohort.packages: + ET.SubElement( + package_downloads, + 'PackageDownload', + { + 'Include': package.package_id, + 'Version': f'[{package.version}]', + }) + ET.ElementTree(project).write( + restore_project_path, encoding='utf-8', xml_declaration=True) + + package_root = path.join(temp_dir, 'packages') + commands = get_wasm_workload_commands( + dotnet_executable, + cohort, + config_path, + restore_project_path, + package_root) + try: + for command in commands: + RunCommand(command, verbose=verbose).run(temp_dir) + except CalledProcessError as error: + raise ValueError( + f'Failed to install the exact CoreCLR browser-WASM cohort ' + f'{cohort.product_version} from {package_source}') from error + + for package in cohort.packages: + restored_package = path.join( + package_root, + package.package_id.casefold(), + package.version.casefold()) + if not path.isdir(restored_package): + raise ValueError( + f'NuGet did not restore {package.package_id} {package.version} ' + f'from {package_source}') + installed_package = path.join( + dotnet_root, 'packs', package.package_id, package.version) + shutil.copytree(restored_package, installed_package, dirs_exist_ok=True) + + missing_installed_packs = [ + f'{package.package_id}/{package.version}' + for package in cohort.packages + if not path.isdir(path.join( + dotnet_root, 'packs', package.package_id, package.version)) + ] + if missing_installed_packs: + raise ValueError( + f'CoreCLR browser-WASM workload installation is incomplete: ' + f'{", ".join(missing_installed_packs)}') + + getLogger().info( + 'Installed coherent CoreCLR browser-WASM cohort %s for %s', + cohort.product_version, + cohort.host_rid) + return cohort + _VERSION_RE = re.compile(r'^\d+\.\d+\.\d+') def version_type(value: str) -> str: if not _VERSION_RE.search(value): diff --git a/scripts/run_performance_job.py b/scripts/run_performance_job.py index db14b4511a4..018cbbf2bed 100644 --- a/scripts/run_performance_job.py +++ b/scripts/run_performance_job.py @@ -12,6 +12,7 @@ import tempfile import time from traceback import format_exc +import urllib.parse import urllib.request import xml.etree.ElementTree as ET from typing import Any, Optional @@ -30,6 +31,20 @@ def apt_command(arguments: str, *, executable: str = "apt-get") -> str: return f"sudo {executable} {APT_LOCK_TIMEOUT_OPTION} {arguments}" +def normalize_wasm_workload_source(source: Optional[str]) -> Optional[str]: + normalized_source = source.strip() if source else "" + return normalized_source or None + + +def set_shell_environment_variable( + os_group: str, + name: str, + value: str) -> str: + if os_group == "windows": + return f'set "{name}={value}"' + return f"export {name}={value}" + + def output_counters_for_crank(reports: list[Any]): print("#StartJobStatistics") @@ -123,6 +138,7 @@ class RunPerformanceJobArgs: pdn_path: Optional[str] = None os_version: Optional[str] = None dotnet_version_link: Optional[str] = None + wasm_workload_source: Optional[str] = None target_csproj: Optional[str] = None build_config: str = DEFAULT_BUILD_CONFIG live_libraries_build_config: Optional[str] = None @@ -209,7 +225,12 @@ def get_pre_commands( codegen_type: str, build_config: str, v8_version: str, - wasm_local_package_version: Optional[str] = None): + wasm_local_package_version: Optional[str] = None, + wasm_workload_source: Optional[str] = None): + if os_group == "windows" and runtime_type in ("wasm", "wasm_coreclr"): + raise ValueError( + "WASM performance job prerequisite setup is not supported on Windows") + helix_pre_commands: list[str] = [] # Remember the previous PYTHONPATH that was set so it can be restored in the post commands @@ -286,17 +307,24 @@ def get_pre_commands( # Set up everything needed for WASM runs (both Mono and CoreCLR) if runtime_type in ("wasm", "wasm_coreclr"): + use_workload_source = ( + runtime_type == "wasm_coreclr" and bool(wasm_workload_source)) if runtime_type == "wasm_coreclr": - if not wasm_local_package_version: - raise ValueError("CoreCLR WASM requires a local WebAssembly toolchain package version") - install_prerequisites += [ - f"export PERFLAB_WASM_PACKAGE_VERSION={wasm_local_package_version}" - ] + if wasm_local_package_version: + install_prerequisites += [ + set_shell_environment_variable( + os_group, + "PERFLAB_WASM_PACKAGE_VERSION", + wasm_local_package_version) + ] + elif not use_workload_source: + raise ValueError( + "CoreCLR WASM requires either a private runtime payload or " + "a coherent WASM workload source") if os_distro == "azurelinux": # Azure Linux uses tdnf package manager install_prerequisites += [ - "export RestoreAdditionalProjectSources=$HELIX_CORRELATION_PAYLOAD/built-nugets", "sudo tdnf -y update", "sudo tdnf -y remove nodejs", "sudo tdnf -y install ca-certificates curl gnupg nodejs npm", @@ -308,7 +336,6 @@ def get_pre_commands( ] else: install_prerequisites += [ - "export RestoreAdditionalProjectSources=$HELIX_CORRELATION_PAYLOAD/built-nugets", apt_command("-y remove nodejs"), apt_command("update"), apt_command("install -y ca-certificates curl gnupg"), @@ -327,6 +354,18 @@ def get_pre_commands( "${V8_ENGINE_PATH} -e 'console.log(`V8 version: ${this.version()}`)'" ] + if not use_workload_source: + correlation_payload = ( + "%HELIX_CORRELATION_PAYLOAD%\\built-nugets" + if os_group == "windows" + else "$HELIX_CORRELATION_PAYLOAD/built-nugets") + install_prerequisites.insert( + 0, + set_shell_environment_variable( + os_group, + "RestoreAdditionalProjectSources", + correlation_payload)) + # Add the install_prerequisites to the pre_commands if os_group == "windows": # Chain Windows commands with error checking using && to ensure each command succeeds @@ -710,7 +749,8 @@ def get_work_item_command( bdn_artifacts_dir: str, wasm_coreclr: bool = False, wasm_ready_to_run: bool = False, - only_sanity_check: bool = False): + only_sanity_check: bool = False, + wasm_workload_source: Optional[str] = None): if os_group == "windows": work_item_command = [ "python", @@ -737,11 +777,25 @@ def get_work_item_command( work_item_command += ["--dotnet-versions", "$DOTNET_VERSION"] if wasm: - work_item_command += ["--run-isolated", "--wasm", "--dotnet-path", "$HELIX_CORRELATION_PAYLOAD/dotnet/"] + dotnet_path = ( + "%HELIX_CORRELATION_PAYLOAD%\\dotnet\\" + if os_group == "windows" + else "$HELIX_CORRELATION_PAYLOAD/dotnet/") + work_item_command += [ + "--run-isolated", + "--wasm", + "--dotnet-path", + dotnet_path, + ] if wasm_coreclr: work_item_command += ["--wasm-runtime-flavor", "CoreCLR"] if wasm_ready_to_run: work_item_command += ["--wasm-ready-to-run"] + if wasm_ready_to_run and wasm_workload_source: + work_item_command += [ + "--wasm-workload-source", + wasm_workload_source, + ] work_item_command += ["--bdn-artifacts", bdn_artifacts_dir] @@ -963,7 +1017,30 @@ def run_performance_job(args: RunPerformanceJobArgs): v8_version = "" wasm_local_package_version = None - if wasm_coreclr: + wasm_workload_source = normalize_wasm_workload_source( + args.wasm_workload_source) + wasm_sdk_cohort = ( + wasm_coreclr + and args.r2r_run_type == "r2r" + and wasm_workload_source is not None) + helix_wasm_workload_source = ( + wasm_workload_source if wasm_sdk_cohort else None) + if wasm_sdk_cohort and wasm_workload_source: + parsed_workload_source = urllib.parse.urlparse(wasm_workload_source) + local_workload_source = ( + urllib.request.url2pathname(parsed_workload_source.path) + if parsed_workload_source.scheme == "file" + else os.path.abspath(wasm_workload_source)) + if os.path.isdir(local_workload_source): + payload_workload_source = os.path.join( + payload_dir, "wasm-workload-source") + shutil.copytree(local_workload_source, payload_workload_source) + helix_wasm_workload_source = ( + "%HELIX_CORRELATION_PAYLOAD%\\wasm-workload-source" + if args.os_group == "windows" + else "$HELIX_CORRELATION_PAYLOAD/wasm-workload-source") + + if wasm_coreclr and not wasm_sdk_cohort: if args.libraries_download_dir is None: raise Exception("Libraries not downloaded for wasm_coreclr runs") @@ -974,7 +1051,7 @@ def run_performance_job(args: RunPerformanceJobArgs): payload_dir, ) - elif wasm: + elif wasm and not wasm_coreclr: if args.libraries_download_dir is None: raise Exception("Libraries not downloaded for wasm runs") @@ -1008,7 +1085,8 @@ def run_performance_job(args: RunPerformanceJobArgs): if args.javascript_engine_path is None: args.javascript_engine_path = f"/home/helixbot/.jsvu/bin/{args.javascript_engine}" - ci_setup_arguments.dotnet_path = f"{payload_dir}/dotnet" + if not wasm_sdk_cohort: + ci_setup_arguments.dotnet_path = f"{payload_dir}/dotnet" if args.dotnet_version_link is not None: if args.dotnet_version_link.startswith("https"): # Version link is a proper url @@ -1164,7 +1242,8 @@ def run_performance_job(args: RunPerformanceJobArgs): args.codegen_type, args.build_config, v8_version, - wasm_local_package_version) + wasm_local_package_version, + helix_wasm_workload_source) helix_post_commands = get_post_commands(args.os_group, args.internal, args.runtime_type) # Point ML.NET at the SSWE model that was pre-downloaded into the correlation payload above, so it @@ -1200,7 +1279,7 @@ def run_performance_job(args: RunPerformanceJobArgs): ci_setup_arguments.target_windows = args.os_group == "windows" ci_setup_arguments.output_file = os.path.join(root_payload_dir, "machine-setup") - if args.is_scenario: + if args.is_scenario or wasm_sdk_cohort: ci_setup_arguments.install_dir = os.path.join(payload_dir, "dotnet") elif wasm_coreclr: # For wasm_coreclr, we already have the SDK in the payload - skip downloading @@ -1413,7 +1492,8 @@ def get_work_item_command_for_artifact_dir(artifact_dir: str): artifact_dir, wasm_coreclr, wasm_coreclr and args.r2r_run_type == "r2r", - args.only_sanity_check) + args.only_sanity_check, + helix_wasm_workload_source) work_item_command = get_work_item_command_for_artifact_dir(bdn_artifacts_directory) baseline_work_item_command = get_work_item_command_for_artifact_dir(bdn_baseline_artifacts_dir) @@ -1570,6 +1650,7 @@ def main(argv: list[str]): "--perf-hash": "perf_hash", "--os-version": "os_version", "--dotnet-version-link": "dotnet_version_link", + "--wasm-workload-source": "wasm_workload_source", "--target-csproj": "target_csproj", "--pdn-path": "pdn_path", "--runtime-repo-dir": "runtime_repo_dir", diff --git a/scripts/tests/test_wasm_coreclr_r2r.py b/scripts/tests/test_wasm_coreclr_r2r.py index dd3d6194619..e59f34a4327 100644 --- a/scripts/tests/test_wasm_coreclr_r2r.py +++ b/scripts/tests/test_wasm_coreclr_r2r.py @@ -3,6 +3,7 @@ import xml.etree.ElementTree as ET from argparse import Namespace from pathlib import Path +from typing import Optional import pytest @@ -10,8 +11,15 @@ sys.path.insert(0, str(scripts_dir)) import micro_benchmarks +import dotnet from build_runtime_payload import build_wasm_coreclr_payload -from run_performance_job import get_pre_commands, get_run_configurations, get_work_item_command +from run_performance_job import ( + get_pre_commands, + get_run_configurations, + get_work_item_command, + normalize_wasm_workload_source, + set_shell_environment_variable, +) def test_ready_to_run_requires_coreclr_wasm(): @@ -52,6 +60,48 @@ def test_ready_to_run_argument_is_forwarded_to_helix_work_item(): assert "--wasm-ready-to-run" in command +def test_workload_source_is_forwarded_to_helix_work_item(): + command = get_work_item_command( + os_group="linux", + target_csproj="src/benchmarks/micro/MicroBenchmarks.csproj", + architecture="x64", + perf_lab_framework="net11.0", + internal=True, + wasm=True, + bdn_artifacts_dir="/tmp/artifacts", + wasm_coreclr=True, + wasm_ready_to_run=True, + wasm_workload_source="https://example.test/cohort/v3/index.json", + ) + + source_index = command.index("--wasm-workload-source") + assert command[source_index + 1] == "https://example.test/cohort/v3/index.json" + + +def test_windows_wasm_work_item_uses_windows_payload_paths(): + command = get_work_item_command( + os_group="windows", + target_csproj="src/benchmarks/micro/MicroBenchmarks.csproj", + architecture="x64", + perf_lab_framework="net11.0", + internal=True, + wasm=True, + bdn_artifacts_dir="%HELIX_WORKITEM_UPLOAD_ROOT%\\artifacts", + wasm_coreclr=True, + wasm_ready_to_run=True, + wasm_workload_source=( + "%HELIX_CORRELATION_PAYLOAD%\\wasm-workload-source"), + ) + + dotnet_path_index = command.index("--dotnet-path") + workload_source_index = command.index("--wasm-workload-source") + assert command[dotnet_path_index + 1] == ( + "%HELIX_CORRELATION_PAYLOAD%\\dotnet\\") + assert command[workload_source_index + 1] == ( + "%HELIX_CORRELATION_PAYLOAD%\\wasm-workload-source") + assert all("$HELIX_CORRELATION_PAYLOAD" not in argument for argument in command) + + def test_ready_to_run_has_distinct_result_configuration(): configurations = get_run_configurations( run_kind="micro", @@ -192,3 +242,264 @@ def test_coreclr_pre_commands_export_local_toolchain_package_version(): ) assert any("PERFLAB_WASM_PACKAGE_VERSION=11.0.0-ci" in command for command in commands) + assert any("RestoreAdditionalProjectSources" in command for command in commands) + + +@pytest.mark.parametrize("runtime_type", ["wasm", "wasm_coreclr"]) +def test_windows_wasm_pre_commands_fail_clearly(runtime_type): + with pytest.raises(ValueError, match="not supported on Windows"): + get_pre_commands( + os_group="windows", + os_distro=None, + internal=False, + runtime_type=runtime_type, + codegen_type="wasm", + build_config="Release", + v8_version="15.1.206", + wasm_local_package_version=( + "11.0.0-ci" if runtime_type == "wasm_coreclr" else None), + ) + + +@pytest.mark.parametrize( + ("os_group", "expected"), + [ + ("windows", 'set "NAME=value"'), + ("linux", "export NAME=value"), + ], +) +def test_shell_environment_variable_command(os_group, expected): + assert set_shell_environment_variable(os_group, "NAME", "value") == expected + + +def test_coreclr_sdk_pre_commands_do_not_enable_private_package_overrides(): + commands = get_pre_commands( + os_group="linux", + os_distro="ubuntu", + internal=False, + runtime_type="wasm_coreclr", + codegen_type="wasm", + build_config="Release", + v8_version="15.1.206", + wasm_workload_source="https://example.test/cohort/v3/index.json", + ) + + assert not any("PERFLAB_WASM_PACKAGE_VERSION" in command for command in commands) + assert not any("RestoreAdditionalProjectSources" in command for command in commands) + + +def test_mono_wasm_pre_commands_preserve_private_package_source(): + commands = get_pre_commands( + os_group="linux", + os_distro="ubuntu", + internal=False, + runtime_type="wasm", + codegen_type="wasm", + build_config="Release", + v8_version="15.1.206", + wasm_workload_source="https://example.test/cohort/v3/index.json", + ) + + assert any("RestoreAdditionalProjectSources" in command for command in commands) + + +def test_non_r2r_coreclr_command_ignores_shared_workload_source(): + command = get_work_item_command( + os_group="linux", + target_csproj="src/benchmarks/micro/MicroBenchmarks.csproj", + architecture="x64", + perf_lab_framework="net11.0", + internal=True, + wasm=True, + bdn_artifacts_dir="/tmp/artifacts", + wasm_coreclr=True, + wasm_ready_to_run=False, + wasm_workload_source="https://example.test/cohort/v3/index.json", + ) + + assert "--wasm-workload-source" not in command + + +def test_pipeline_scopes_workload_source_to_coreclr_r2r(): + template = ( + scripts_dir.parent + / "eng" + / "pipelines" + / "templates" + / "run-performance-job.yml" + ).read_text(encoding="utf-8") + + condition = ( + "and(ne(parameters.wasmWorkloadSource, ''), " + "eq(parameters.runtimeType, 'wasm_coreclr'), " + "eq(parameters.r2rRunType, 'r2r'))" + ) + assert condition in template + + +@pytest.mark.parametrize("source", [None, "", " "]) +def test_empty_workload_source_is_not_enabled(source): + assert normalize_wasm_workload_source(source) is None + + +def test_workload_source_is_trimmed(): + assert normalize_wasm_workload_source( + " https://example.test/cohort/v3/index.json " + ) == "https://example.test/cohort/v3/index.json" + + +@pytest.mark.parametrize( + ("system", "architecture", "libc", "expected"), + [ + ("Darwin", "arm64", "", "osx-arm64"), + ("Darwin", "x64", "", "osx-x64"), + ("Linux", "x64", "glibc", "linux-x64"), + ("Linux", "x64", "musl", "linux-musl-x64"), + ("Windows", "x64", "", "win-x64"), + ], +) +def test_crossgen2_host_rid(system, architecture, libc, expected): + assert dotnet.get_host_rid(architecture, system=system, libc=libc) == expected + + +def _write_bundled_versions( + dotnet_root: Path, + sdk_version: str = "11.0.100-preview.1.12345.1", + product_version: str = "11.0.0-preview.1.12345.1", + illink_version: Optional[str] = None) -> None: + sdk_dir = dotnet_root / "sdk" / sdk_version + sdk_dir.mkdir(parents=True) + (sdk_dir / "Microsoft.NETCoreSdk.BundledVersions.props").write_text( + f""" + + + + + + + +""", + encoding="utf-8", + ) + + +def test_cohort_uses_exact_sdk_product_version_and_host_crossgen2(tmp_path): + sdk_version = "11.0.100-preview.1.12345.1" + product_version = "11.0.0-preview.1.12345.1" + _write_bundled_versions(tmp_path, sdk_version, product_version) + + cohort = dotnet.get_wasm_workload_cohort( + str(tmp_path), sdk_version, "net11.0", "osx-arm64") + + assert cohort.product_version == product_version + assert cohort.workload_id == "wasm-tools" + assert dotnet.WasmPackage( + "Microsoft.NETCore.App.Crossgen2.osx-arm64", + product_version, + ) in cohort.packages + assert {package.version for package in cohort.packages} == {product_version} + + +def test_cohort_rejects_mismatched_sdk_package_versions(tmp_path): + _write_bundled_versions( + tmp_path, + illink_version="11.0.0-preview.1.12345.2", + ) + + with pytest.raises(ValueError, match="does not define one coherent"): + dotnet.get_wasm_workload_cohort( + str(tmp_path), + "11.0.100-preview.1.12345.1", + "net11.0", + "linux-x64", + ) + + +def test_local_cohort_source_requires_all_exact_packages(tmp_path, monkeypatch): + sdk_version = "11.0.100-preview.1.12345.1" + product_version = "11.0.0-preview.1.12345.1" + dotnet_root = tmp_path / "dotnet" + package_source = tmp_path / "packages" + package_source.mkdir() + _write_bundled_versions(dotnet_root, sdk_version, product_version) + monkeypatch.setenv("DOTNET_ROOT", str(dotnet_root)) + monkeypatch.setattr(dotnet, "get_host_rid", lambda architecture: "linux-x64") + + for package_id in ( + "Microsoft.NETCore.App.Runtime.browser-wasm", + "Microsoft.NETCore.App.Ref", + "Microsoft.NET.Sdk.WebAssembly.Pack", + "Microsoft.NETCore.App.Crossgen2.linux-x64", + ): + (package_source / f"{package_id}.{product_version}.nupkg").touch() + + with pytest.raises(ValueError, match="Microsoft.NET.ILLink.Tasks"): + dotnet.install_wasm_workload( + architecture="x64", + target_framework_monikers=["net11.0"], + package_source=str(package_source), + sdk_versions=[sdk_version], + verbose=False, + ) + + +def test_cohort_source_rejects_multiple_sdk_versions(): + with pytest.raises(ValueError, match="at most one SDK version"): + dotnet.install_wasm_workload( + architecture="x64", + target_framework_monikers=["net11.0"], + package_source="https://example.test/cohort/v3/index.json", + sdk_versions=["11.0.100-preview.1", "11.0.100-preview.2"], + verbose=False, + ) + + +def test_generated_workload_commands_pin_workload_and_coreclr_r2r_cohort(): + cohort = dotnet.WasmWorkloadCohort( + sdk_version="11.0.100-preview.1.12345.1", + product_version="11.0.0-preview.1.12345.1", + host_rid="linux-x64", + workload_id="wasm-tools", + packages=(), + ) + + commands = dotnet.get_wasm_workload_commands( + "/dotnet/dotnet", + cohort, + "/tmp/NuGet.Config", + "/tmp/WasmCohort.csproj", + "/tmp/packages", + ) + + assert commands[0] == [ + "/dotnet/dotnet", + "restore", + "/tmp/WasmCohort.csproj", + "--packages", + "/tmp/packages", + "--configfile", + "/tmp/NuGet.Config", + "--no-http-cache", + ] + assert commands[1] == [ + "/dotnet/dotnet", + "workload", + "install", + "wasm-tools", + "--skip-manifest-update", + "--configfile", + "/tmp/NuGet.Config", + "--no-cache", + ] From 6effc80b8096292fb38a674b0b106214069e9d0e Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 13:53:06 -0700 Subject: [PATCH 04/17] Update dependencies from https://github.com/dotnet/android build 10.0.100+azdo.15299678 (#5309) On relative base path root Microsoft.Android.Sdk.Windows From Version 36.1.115 -> To Version 36.1.117 Co-authored-by: dotnet-maestro[bot] --- NuGet.config | 2 +- eng/Version.Details.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/NuGet.config b/NuGet.config index c3dce14f9aa..f6b92c8abb7 100644 --- a/NuGet.config +++ b/NuGet.config @@ -7,8 +7,8 @@ - + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 37c4f335c2a..c4be5458b8e 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -63,9 +63,9 @@ bcd0a2de8927de44b65f3ad41bef8820ee2b3d0b https://github.com/dotnet/dotnet - + https://github.com/dotnet/android - ee72c6ff89ce0b2609b21fc5b0897741cae8dc5c + 0c3cd1162d31963582224c5dc552d74e873ba6e3 4c9d1b112c16716c2479e054e9ad4db8b5b8c70c From ad876c1fe1c6205be65fb8f53fdbb6ec3d6bdfe1 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 13:53:52 -0700 Subject: [PATCH 05/17] [main] Update dependencies from dotnet/xharness (#5306) * Update dependencies from https://github.com/dotnet/xharness build 20260909.1 On relative base path root Microsoft.DotNet.XHarness.CLI From Version 11.0.0-prerelease.26458.2 -> To Version 11.0.0-prerelease.26459.1 * Update dependencies from https://github.com/dotnet/xharness build 20260911.1 On relative base path root Microsoft.DotNet.XHarness.CLI From Version 11.0.0-prerelease.26458.2 -> To Version 11.0.0-prerelease.26461.1 * Update dependencies from https://github.com/dotnet/xharness build 20260914.1 On relative base path root Microsoft.DotNet.XHarness.CLI From Version 11.0.0-prerelease.26458.2 -> To Version 11.0.0-prerelease.26464.1 * Update dependencies from https://github.com/dotnet/xharness build 20260915.1 On relative base path root Microsoft.DotNet.XHarness.CLI From Version 11.0.0-prerelease.26458.2 -> To Version 11.0.0-prerelease.26465.1 --------- Co-authored-by: dotnet-maestro[bot] --- eng/Version.Details.xml | 4 ++-- eng/Versions.props | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index c4be5458b8e..402e28f6a09 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -35,9 +35,9 @@ https://github.com/dotnet/dotnet bcd0a2de8927de44b65f3ad41bef8820ee2b3d0b - + https://github.com/dotnet/xharness - 1665037768fd7eec44e046d94fd07b8c6407c966 + 225f4dfefc66973c1f37623cd65d0470d389a63d From 649330d7026c3348fa2d9558baca932a457d285e Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 13:54:13 -0700 Subject: [PATCH 06/17] [main] Update dependencies from dotnet/maui (#5305) * Update dependencies from https://github.com/dotnet/maui build 20260908.7 On relative base path root Microsoft.Maui.Controls From Version 10.0.110-ci.main.26458.5 -> To Version 10.0.110-ci.main.26458.8 * Update dependencies from https://github.com/dotnet/maui build 20260909.7 On relative base path root Microsoft.Maui.Controls From Version 10.0.110-ci.main.26458.5 -> To Version 10.0.110-ci.main.26459.8 * Update dependencies from https://github.com/dotnet/maui build 20260910.4 On relative base path root Microsoft.Maui.Controls From Version 10.0.110-ci.main.26458.5 -> To Version 10.0.110-ci.main.26460.5 * Update dependencies from https://github.com/dotnet/maui build 20260911.2 On relative base path root Microsoft.Maui.Controls From Version 10.0.110-ci.main.26458.5 -> To Version 10.0.110-ci.main.26461.3 * Update dependencies from https://github.com/dotnet/maui build 20260911.4 On relative base path root Microsoft.Maui.Controls From Version 10.0.110-ci.main.26458.5 -> To Version 10.0.110-ci.main.26461.5 * Update dependencies from https://github.com/dotnet/maui build 20260911.6 On relative base path root Microsoft.Maui.Controls From Version 10.0.110-ci.main.26458.5 -> To Version 10.0.120-ci.main.26461.7 * Update dependencies from https://github.com/dotnet/maui build 20260911.8 On relative base path root Microsoft.Maui.Controls From Version 10.0.110-ci.main.26458.5 -> To Version 10.0.120-ci.main.26461.9 * Update dependencies from https://github.com/dotnet/maui build 20260912.2 On relative base path root Microsoft.Maui.Controls From Version 10.0.110-ci.main.26458.5 -> To Version 10.0.120-ci.main.26462.3 * Update dependencies from https://github.com/dotnet/maui build 20260912.3 On relative base path root Microsoft.Maui.Controls From Version 10.0.110-ci.main.26458.5 -> To Version 10.0.120-ci.main.26462.4 * Update dependencies from https://github.com/dotnet/maui build 20260912.7 On relative base path root Microsoft.Maui.Controls From Version 10.0.110-ci.main.26458.5 -> To Version 10.0.120-ci.main.26462.8 * Update dependencies from https://github.com/dotnet/maui build 20260913.1 On relative base path root Microsoft.Maui.Controls From Version 10.0.110-ci.main.26458.5 -> To Version 10.0.120-ci.main.26463.2 * Update dependencies from https://github.com/dotnet/maui build 20260913.4 On relative base path root Microsoft.Maui.Controls From Version 10.0.110-ci.main.26458.5 -> To Version 10.0.120-ci.main.26463.5 --------- Co-authored-by: dotnet-maestro[bot] --- eng/Version.Details.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 402e28f6a09..73f68bbabe8 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -51,8 +51,8 @@ Mapping_Microsoft.tvOS.Sdk:default --> - - 2805bd2ff835d56183bc24790978db678711c770 + + 70e7adc57504d3435a0f8cc68e890300546ab96a https://github.com/dotnet/maui From 2f0b6a9fad313d165f428a1f7db3016318365dfc Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 13:54:44 -0700 Subject: [PATCH 07/17] [main] Update dependencies from dotnet/dotnet (#5304) * Update dependencies from https://github.com/dotnet/dotnet build 20260908.9 On relative base path root Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 11.0.0-beta.26458.103 -> To Version 11.0.0-beta.26458.109 Microsoft.Extensions.Logging , Microsoft.NET.ILLink , Microsoft.NET.ILLink.Tasks , Microsoft.NETCore.App.Ref , Microsoft.WindowsDesktop.App.Ref , System.Threading.Channels From Version 11.0.0-rc.2.26458.103 -> To Version 11.0.0-rc.2.26458.109 Microsoft.NET.Sdk From Version 11.0.100-rc.2.26458.103 -> To Version 11.0.100-rc.2.26458.109 * Update dependencies from https://github.com/dotnet/dotnet build 20260911.10 On relative base path root Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 11.0.0-beta.26458.103 -> To Version 11.0.0-beta.26461.110 Microsoft.Extensions.Logging , Microsoft.NET.ILLink , Microsoft.NET.ILLink.Tasks , Microsoft.NETCore.App.Ref , Microsoft.WindowsDesktop.App.Ref , System.Threading.Channels From Version 11.0.0-rc.2.26458.103 -> To Version 11.0.0-rc.2.26461.110 Microsoft.NET.Sdk From Version 11.0.100-rc.2.26458.103 -> To Version 11.0.100-rc.2.26461.110 * Update dependencies from https://github.com/dotnet/dotnet build 20260911.15 On relative base path root Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 11.0.0-beta.26458.103 -> To Version 11.0.0-beta.26461.115 Microsoft.Extensions.Logging , Microsoft.NET.ILLink , Microsoft.NET.ILLink.Tasks , Microsoft.NETCore.App.Ref , Microsoft.WindowsDesktop.App.Ref , System.Threading.Channels From Version 11.0.0-rc.2.26458.103 -> To Version 11.0.0-rc.2.26461.115 Microsoft.NET.Sdk From Version 11.0.100-rc.2.26458.103 -> To Version 11.0.100-rc.2.26461.115 * Update dependencies from https://github.com/dotnet/dotnet build 20260914.19 On relative base path root Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 11.0.0-beta.26458.103 -> To Version 11.0.0-beta.26464.119 Microsoft.Extensions.Logging , Microsoft.NET.ILLink , Microsoft.NET.ILLink.Tasks , Microsoft.NETCore.App.Ref , Microsoft.WindowsDesktop.App.Ref , System.Threading.Channels From Version 11.0.0-rc.2.26458.103 -> To Version 11.0.0-rc.2.26464.119 Microsoft.NET.Sdk From Version 11.0.100-rc.2.26458.103 -> To Version 11.0.100-rc.2.26464.119 * Update dependencies from https://github.com/dotnet/dotnet build 20260915.2 On relative base path root Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 11.0.0-beta.26458.103 -> To Version 11.0.0-beta.26465.102 Microsoft.Extensions.Logging , Microsoft.NET.ILLink , Microsoft.NET.ILLink.Tasks , Microsoft.NETCore.App.Ref , Microsoft.WindowsDesktop.App.Ref , System.Threading.Channels From Version 11.0.0-rc.2.26458.103 -> To Version 11.0.0-rc.2.26465.102 Microsoft.NET.Sdk From Version 11.0.100-rc.2.26458.103 -> To Version 11.0.100-rc.2.26465.102 --------- Co-authored-by: dotnet-maestro[bot] --- eng/Version.Details.xml | 36 +++++++------- eng/Versions.props | 8 ++-- eng/common/core-templates/job/onelocbuild.yml | 48 +++++++------------ global.json | 8 ++-- 4 files changed, 43 insertions(+), 57 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 73f68bbabe8..1e2bb80c2b6 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,25 +1,25 @@ - + https://github.com/dotnet/dotnet - bcd0a2de8927de44b65f3ad41bef8820ee2b3d0b + bcdaaccbf4b97f0fa310f29be7dc6d0bf4924d47 - + https://github.com/dotnet/dotnet - bcd0a2de8927de44b65f3ad41bef8820ee2b3d0b + bcdaaccbf4b97f0fa310f29be7dc6d0bf4924d47 - + https://github.com/dotnet/dotnet - bcd0a2de8927de44b65f3ad41bef8820ee2b3d0b + bcdaaccbf4b97f0fa310f29be7dc6d0bf4924d47 - + https://github.com/dotnet/dotnet - bcd0a2de8927de44b65f3ad41bef8820ee2b3d0b + bcdaaccbf4b97f0fa310f29be7dc6d0bf4924d47 - + https://github.com/dotnet/dotnet - bcd0a2de8927de44b65f3ad41bef8820ee2b3d0b + bcdaaccbf4b97f0fa310f29be7dc6d0bf4924d47 https://github.com/dotnet/dotnet @@ -27,13 +27,13 @@ - + https://github.com/dotnet/dotnet - bcd0a2de8927de44b65f3ad41bef8820ee2b3d0b + bcdaaccbf4b97f0fa310f29be7dc6d0bf4924d47 - + https://github.com/dotnet/dotnet - bcd0a2de8927de44b65f3ad41bef8820ee2b3d0b + bcdaaccbf4b97f0fa310f29be7dc6d0bf4924d47 https://github.com/dotnet/xharness @@ -55,12 +55,12 @@ 70e7adc57504d3435a0f8cc68e890300546ab96a https://github.com/dotnet/maui - - bcd0a2de8927de44b65f3ad41bef8820ee2b3d0b + + bcdaaccbf4b97f0fa310f29be7dc6d0bf4924d47 https://github.com/dotnet/dotnet - - bcd0a2de8927de44b65f3ad41bef8820ee2b3d0b + + bcdaaccbf4b97f0fa310f29be7dc6d0bf4924d47 https://github.com/dotnet/dotnet diff --git a/eng/Versions.props b/eng/Versions.props index d9d46bedcbb..297cc304d1c 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -7,10 +7,10 @@ - 11.0.0-rc.2.26458.103 - 11.0.0-rc.2.26458.103 - 11.0.0-rc.2.26458.103 - 11.0.0-rc.2.26458.103 + 11.0.0-rc.2.26465.102 + 11.0.0-rc.2.26465.102 + 11.0.0-rc.2.26465.102 + 11.0.0-rc.2.26465.102 0.16.0-nightly.20260801.595 11.0.0-preview.5.26261.101 11.0.0-prerelease.26465.1 diff --git a/eng/common/core-templates/job/onelocbuild.yml b/eng/common/core-templates/job/onelocbuild.yml index ce077368e40..25af2787900 100644 --- a/eng/common/core-templates/job/onelocbuild.yml +++ b/eng/common/core-templates/job/onelocbuild.yml @@ -5,20 +5,10 @@ parameters: # Optional: A defined YAML pool - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#pool pool: '' - 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. + # Project-scoped WIF service connection for Ceapex feed authentication. CeapexServiceConnection: 'dnceng-onelocbuild-ceapex' # GitHub App authentication for the OneLoc check-in PR. - # dnceng/internal and DevDiv/DevDiv are enabled by default with their project-scoped service - # connections. Other projects must explicitly opt in after provisioning equivalent infrastructure. - UseGitHubAppAuthentication: true - UseGitHubAppAuthenticationInOtherProjects: false GitHubAppServiceConnection: 'dnceng-oneloc-githubapp' GitHubAppClientId: 'Iv23lijBU8x3gc9lDOc9' GitHubAppKeyVaultName: 'EngKeyVault' @@ -50,7 +40,6 @@ jobs: displayName: OneLocBuild${{ parameters.JobNameSuffix }} variables: - - group: OneLocBuildVariables # Contains the CeapexPat and GithubPat - name: _GenerateLocProjectArguments value: -SourcesDirectory ${{ parameters.SourcesDirectory }} -LanguageSet "${{ parameters.LanguageSet }}" @@ -81,6 +70,12 @@ jobs: steps: - ${{ if eq(parameters.is1ESPipeline, '') }}: - 'Illegal entry point, is1ESPipeline is not defined. Repository yaml should not directly reference templates in core-templates folder.': error + - ${{ if notin(variables['System.TeamProject'], 'internal', 'DevDiv') }}: + - 'OneLocBuild is supported only in dnceng/internal and DevDiv/DevDiv.': error + - ${{ if eq(parameters.CeapexServiceConnection, '') }}: + - 'CeapexServiceConnection must identify a WIF service connection.': error + - ${{ if and(eq(parameters.RepoType, 'gitHub'), eq(parameters.GitHubAppServiceConnection, '')) }}: + - 'GitHubAppServiceConnection must identify a WIF service connection for GitHub repositories.': error - ${{ if ne(parameters.SkipLocProjectJsonGeneration, 'true') }}: - task: Powershell@2 @@ -90,18 +85,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 }} + # Acquire a short-lived Entra token for Ceapex feed access. + - template: /eng/common/templates/steps/get-federated-access-token.yml + parameters: + federatedServiceConnection: ${{ parameters.CeapexServiceConnection }} + outputVariableName: 'CeapexEntraToken' + condition: ${{ parameters.condition }} - # Mint a short-lived GitHub App installation token for the loc check-in PR. Use the connection - # provisioned in each supported project; other projects must explicitly opt in and override it. - - ${{ if and(eq(parameters.RepoType, 'gitHub'), eq(parameters.UseGitHubAppAuthentication, true), or(eq(variables['System.TeamProject'], 'internal'), eq(variables['System.TeamProject'], 'DevDiv'), eq(parameters.UseGitHubAppAuthenticationInOtherProjects, true))) }}: + # Mint a short-lived GitHub App installation token for the loc check-in PR. + - ${{ if eq(parameters.RepoType, 'gitHub') }}: - template: /eng/common/core-templates/steps/get-github-app-token.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} @@ -131,16 +123,10 @@ jobs: isUseLfLineEndingsSelected: ${{ parameters.UseLfLineEndings }} isShouldReusePrSelected: ${{ parameters.ReusePr }} packageSourceAuth: patAuth - ${{ 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 }} + patVariable: $(CeapexEntraToken) ${{ if eq(parameters.RepoType, 'gitHub') }}: repoType: ${{ parameters.RepoType }} - ${{ if and(eq(parameters.UseGitHubAppAuthentication, true), or(eq(variables['System.TeamProject'], 'internal'), eq(variables['System.TeamProject'], 'DevDiv'), eq(parameters.UseGitHubAppAuthenticationInOtherProjects, true))) }}: - gitHubPatVariable: "$(GitHubAppInstallationToken)" - ${{ else }}: - gitHubPatVariable: "${{ parameters.GithubPat }}" + gitHubPatVariable: "$(GitHubAppInstallationToken)" ${{ if ne(parameters.MirrorRepo, '') }}: isMirrorRepoSelected: true gitHubOrganization: ${{ parameters.GitHubOrg }} diff --git a/global.json b/global.json index 63b826eb09a..4e21455a2db 100644 --- a/global.json +++ b/global.json @@ -1,15 +1,15 @@ { "sdk": { - "version": "11.0.100-preview.6.26359.118", + "version": "11.0.100-rc.1.26420.103", "allowPrerelease": true, "rollForward": "major" }, "tools": { - "dotnet": "11.0.100-preview.6.26359.118" + "dotnet": "11.0.100-rc.1.26420.103" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "11.0.0-beta.26458.103", - "Microsoft.DotNet.Helix.Sdk": "11.0.0-beta.26458.103" + "Microsoft.DotNet.Arcade.Sdk": "11.0.0-beta.26465.102", + "Microsoft.DotNet.Helix.Sdk": "11.0.0-beta.26465.102" }, "native-tools": { "python3": "3.7.1" From d92bd6943bb130fef8a1de79d782f9022e3d260c Mon Sep 17 00:00:00 2001 From: Parker Bibus Date: Thu, 17 Sep 2026 11:26:10 -0700 Subject: [PATCH 08/17] Adopt standalone Helix monitoring for SDK performance CI (#5307) * Adopt standalone Helix monitoring for SDK performance CI Pair internal asynchronous submissions with a credentialed stage monitor and pin its tool alongside the Helix SDK. Preserve public, PR, and local synchronous behavior. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Keep SDK monitor variables in one template guard Evaluate the internal-run predicate directly while expanding root variables. Define the async flag, pool provider, and Helix credential group together so same-block variable lookup cannot omit monitor prerequisites. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Complete SDK Helix monitor public support and Linux startup Enable anonymous monitoring for selected public and PR jobs, keep internal credentials guarded, and bound the monitor to six hours without changing sender or work-item timeouts. Restore the Arcade bootstrap executable bit for Linux tool restore. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Keep SDK performance matrix legs distinct in Helix monitoring Prefix monitor test-run identities with the matrix-expanded agent job name so independent channels sharing a phase and queue are not treated as retries. Retain stable identities for job and stage retries and scope the naming change to monitored SDK flows. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Always include the SDK performance Helix monitor Keep asynchronous submission, the pool provider, matrix identity prefix, and monitor parameter block unconditional. Use anonymous Helix credentials outside internal non-PR runs, and allow legitimate no-workload stages without changing workload selection or timeout budgets. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Provide SDK monitor credentials for internal PR runs Align monitor credential eligibility with dotnet/runtime#133885: import the Helix group and select its token for the internal project regardless of build reason. Public runs, including public PRs, remain anonymous. Preserve workload selection and monitor settings. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Fail SDK performance runs without Helix jobs Keep the monitor unconditional but reject zero-job discovery, including internal manual runs with all workload selectors false. Preserve workload selection, credential scope, timeout budgets, and matrix identities. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Forward the SDK Helix monitor token directly Match the accepted runtime pipeline convention by passing HelixApiAccessToken without a compile-time iif. Keep the credential group internal-only and document the public empty-value contract without assuming unresolved macros become empty. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Clarify public Helix access with direct token forwarding Document the established forwarding convention without asserting an unverified empty-variable requirement. Read-only checks confirmed matching public job and work-item visibility with no auth and a literal unresolved-token header. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Parker Bibus Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .config/dotnet-tools.json | 12 +++++++++++ azure-pipelines.yml | 18 ++++++++++++++++ docs/sdk-scenarios.md | 43 +++++++++++++++++++++++++++++++++++++++ eng/Version.Details.xml | 4 ++++ eng/common/dotnet.sh | 0 5 files changed, 77 insertions(+) create mode 100644 .config/dotnet-tools.json mode change 100644 => 100755 eng/common/dotnet.sh diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 00000000000..5f56247a879 --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "microsoft.dotnet.helix.jobmonitor": { + "version": "11.0.0-beta.26458.103", + "commands": [ + "dotnet-helix-job-monitor" + ] + } + } +} diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4cd188a6a74..9a4cdcf2bac 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -12,6 +12,18 @@ parameters: type: boolean default: false +variables: + # This entrypoint has one implicit stage. Keep asynchronous sends paired with its monitor. + - name: EnableHelixJobMonitor + value: true + # Matrix legs share a phase and queue. Keep their monitor identities distinct across channels. + - name: TestRunNamePrefix + value: '$(Agent.JobName)_' + - template: /eng/common/templates/variables/pool-providers.yml + - ${{ if eq(variables['System.TeamProject'], 'internal') }}: + # The standalone monitor does not inherit the submitter jobs' variable groups. + - group: DotNet-HelixApi-Access + resources: containers: - container: ubuntu_x64_build_container @@ -67,3 +79,9 @@ jobs: jobParameters: ${{ if parameters.onlySanityCheck }}: onlySanityCheck: true + + - template: /eng/common/core-templates/job/helix-job-monitor.yml + parameters: + timeoutInMinutes: 360 + helixAccessToken: $(HelixApiAccessToken) + allowNoHelixJobs: false diff --git a/docs/sdk-scenarios.md b/docs/sdk-scenarios.md index 12e2c952289..c09f165f198 100644 --- a/docs/sdk-scenarios.md +++ b/docs/sdk-scenarios.md @@ -4,6 +4,49 @@ An introduction of how to run scenario tests can be found in [Scenarios Tests Gu - [SDK Build Throughput Scenario](#sdk-build-throughput-scenario) +## SDK performance pipeline + +The repository's [azure-pipelines.yml](../azure-pipelines.yml) entrypoint uses +[sdk-perf-jobs.yml](../eng/pipelines/sdk-perf-jobs.yml) for SDK-based scenario and +benchmark jobs. It backs the internal `dotnet-performance` pipeline (definition 306) +and the public `performance-ci` pipeline (definition 38). + +The entrypoint always enables asynchronous submission and includes the standalone +Helix Job Monitor, its pool provider, and its parameters, including on public and PR +runs. The monitor runs alongside submitters in the same implicit stage, waits for +their Helix work items, and reports failures to Azure DevOps. The existing manual +job-selection parameters and schedules are unchanged: public runs always select +correctness jobs. Internal manual runs must select workloads; leaving all three +job-selection flags false still includes the monitor and fails because no Helix jobs +were submitted (`allowNoHelixJobs: false`). Submitter and work-item failures also +continue to fail the run. + +The monitor job has a six-hour timeout (355 minutes for the tool, leaving five minutes +for it to exit). Existing 320-minute submitter limits and Helix work-item timeouts are +unchanged. These limits start when the respective job runs; they are not a whole-run +wall-clock deadline that includes time waiting for agents. + +Internal runs, including internal PR runs, import `DotNet-HelixApi-Access`. The +monitor forwards `$(HelixApiAccessToken)` directly, matching the shared pipeline +convention. Internal runs use the imported credential. Public runs, including public +PR runs, do not import that group and use public Helix access. Direct forwarding +does not change how Azure DevOps expands variables or how the SDK handles tokens. +Azure DevOps timeline access and result reporting still use the job's +`System.AccessToken`. The tool is restored from +`.config/dotnet-tools.json` in its own checkout; `eng/Version.Details.xml` tracks that +pin alongside the Helix SDK. + +Monitor test-run names include the matrix-expanded job display name. This keeps +different channels in the same phase and Helix queue independent while preserving a +stable identity when the same leg is retried. + +Submitter build logs still use the existing `Logs_*` pipeline artifacts. Benchmark +results and diagnostics are still uploaded from the Helix work items to PerfLab and +Helix, respectively. The monitor reports test results and links to Helix consoles; it +does not download arbitrary Helix uploads into pipeline artifacts. Asynchronous sends +skip the submitter's `artifacts/helix-results` downloads, which were not published by +this pipeline. Local `--send-to-helix` runs still wait and download performance reports. + ## SDK Build Throughput Scenario **SDK Build Throughput** is a scenario test that measures the throughput of SDK build process. To be more specific, our test *implicitly calls* diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 1e2bb80c2b6..d5aa30d3869 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -35,6 +35,10 @@ https://github.com/dotnet/dotnet bcdaaccbf4b97f0fa310f29be7dc6d0bf4924d47 + + https://github.com/dotnet/dotnet + bcd0a2de8927de44b65f3ad41bef8820ee2b3d0b + https://github.com/dotnet/xharness 225f4dfefc66973c1f37623cd65d0470d389a63d diff --git a/eng/common/dotnet.sh b/eng/common/dotnet.sh old mode 100644 new mode 100755 From 99531dcedc4125a76acc682ca56b1539eace92f6 Mon Sep 17 00:00:00 2001 From: Vlad Brezae Date: Mon, 21 Sep 2026 20:29:33 +0300 Subject: [PATCH 09/17] Configure r2r to disable runtime code generation in the r2r_interpreter configuration (#5295) * Configure r2r to disable runtime code generation in the r2r_interpreter configuration This configuration used to be hardcoded in crossgen2, but it workaround was recently reverted with proper support added in https://github.com/dotnet/runtime/commit/5b11923ecac20a8367b4ca60a9b9739479319e9a. This means r2r_interpreter configuration needs to pass `--target-allows-runtime-code-generation:false` to crossgen. This can be achieved by having the bdn autogenerated project receive the msbuild property `PublishReadyToRunCrossgen2ExtraArgs` initialized to this extra arg. Given bdn doesn't support this configuration via its cli arguments (https://github.com/dotnet/BenchmarkDotNet/blob/master/src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs), we add a new argument to the microbenchmark project --msbuild-argument, which will be forwarded to bdn via the job arguments (job.WithMsBuildArguments). * Document microbenchmark MSBuild argument syntax Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Parker Bibus Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- docs/benchmarkdotnet.md | 6 ++++++ scripts/run_performance_job.py | 4 ++++ src/benchmarks/micro/Program.cs | 5 ++++- .../BenchmarkDotNet.Extensions/RecommendedConfig.cs | 8 +++++++- 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/benchmarkdotnet.md b/docs/benchmarkdotnet.md index ed084e27876..657c56bb8b9 100644 --- a/docs/benchmarkdotnet.md +++ b/docs/benchmarkdotnet.md @@ -116,6 +116,12 @@ And select one of the benchmarks from the list by either entering its number or ### Command Line +The microbenchmark `--msbuild-arguments` option passes additional arguments to MSBuild for BenchmarkDotNet's generated project. Use slash-prefixed arguments such as `/p:` or `/t:`, not `-p:` or `-t:`: `CommandLineOptions.ParseAndRemoveStringsParameter` stops collecting values at any token beginning with `-`. Embedded `--` within a `/p:` value is fine, for example: + +```text +--msbuild-arguments /p:PublishReadyToRunCrossgen2ExtraArgs=--target-allows-runtime-code-generation:false +``` + #### Filtering the Benchmarks You can filter the benchmarks using `--filter $globPattern` console line argument. The filter is **case insensitive**. diff --git a/scripts/run_performance_job.py b/scripts/run_performance_job.py index 018cbbf2bed..f3b8393240f 100644 --- a/scripts/run_performance_job.py +++ b/scripts/run_performance_job.py @@ -606,6 +606,10 @@ def get_bdn_arguments( bdn_arguments += ["\\\"--wasmArgs=--experimental-wasm-exnref\\\""] if runtime_type == "coreclr_r2r_interpreter": + bdn_arguments += [ + "--msbuild-arguments", + "/p:PublishReadyToRunCrossgen2ExtraArgs=--target-allows-runtime-code-generation:false", + ] if os_group == "windows": bdn_arguments += [ "--runtimes", "r2r11_0", diff --git a/src/benchmarks/micro/Program.cs b/src/benchmarks/micro/Program.cs index 8d2a590cae9..28e9eca5312 100644 --- a/src/benchmarks/micro/Program.cs +++ b/src/benchmarks/micro/Program.cs @@ -22,6 +22,7 @@ static async Task Main(string[] args) int? partitionIndex; List exclusionFilterValue; List categoryExclusionFilterValue; + List msBuildArguments; bool getDiffableDisasm; // Parse and remove any additional parameters that we need that aren't part of BDN @@ -31,6 +32,7 @@ static async Task Main(string[] args) argsList = CommandLineOptions.ParseAndRemoveIntParameter(argsList, "--partition-index", out partitionIndex); argsList = CommandLineOptions.ParseAndRemoveStringsParameter(argsList, "--exclusion-filter", out exclusionFilterValue); argsList = CommandLineOptions.ParseAndRemoveStringsParameter(argsList, "--category-exclusion-filter", out categoryExclusionFilterValue); + argsList = CommandLineOptions.ParseAndRemoveStringsParameter(argsList, "--msbuild-arguments", out msBuildArguments); CommandLineOptions.ParseAndRemoveBooleanParameter(argsList, "--disasm-diff", out getDiffableDisasm); CommandLineOptions.ValidatePartitionParameters(partitionCount, partitionIndex); @@ -56,7 +58,8 @@ static async Task Main(string[] args) partitionIndex: partitionIndex, exclusionFilterValue: exclusionFilterValue, categoryExclusionFilterValue: categoryExclusionFilterValue, - getDiffableDisasm: getDiffableDisasm) + getDiffableDisasm: getDiffableDisasm, + msBuildArguments: msBuildArguments) .AddValidator(new NoWasmValidator(Categories.NoWASM))) .ConfigureAwait(false); diff --git a/src/harness/BenchmarkDotNet.Extensions/RecommendedConfig.cs b/src/harness/BenchmarkDotNet.Extensions/RecommendedConfig.cs index c8a469ba68e..2630289ae2c 100644 --- a/src/harness/BenchmarkDotNet.Extensions/RecommendedConfig.cs +++ b/src/harness/BenchmarkDotNet.Extensions/RecommendedConfig.cs @@ -28,7 +28,8 @@ public static IConfig Create( List? exclusionFilterValue = null, List? categoryExclusionFilterValue = null, Job? job = null, - bool getDiffableDisasm = false) + bool getDiffableDisasm = false, + IReadOnlyList? msBuildArguments = null) { if (job is null) { @@ -43,6 +44,11 @@ public static IConfig Create( #pragma warning restore CS0618 } + if (msBuildArguments is not null && msBuildArguments.Count > 0) + { + job = job.WithMsBuildArguments(msBuildArguments.ToArray()); + } + var config = ManualConfig.CreateEmpty() .WithBuildTimeout(TimeSpan.FromMinutes(15)) // for slow machines .AddLogger(ConsoleLogger.Default) // log output to console From eb3f6ba2dca0e0ee979d28ed19d5085072b076bd Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Wed, 23 Sep 2026 13:58:23 -0700 Subject: [PATCH 10/17] Update dependencies from https://github.com/dotnet/android build 10.0.100+azdo.15299689 (#5315) On relative base path root Microsoft.Android.Sdk.Windows From Version 36.1.117 -> To Version 36.1.118 Co-authored-by: dotnet-maestro[bot] --- NuGet.config | 2 +- eng/Version.Details.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/NuGet.config b/NuGet.config index f6b92c8abb7..fcdef4b1f38 100644 --- a/NuGet.config +++ b/NuGet.config @@ -7,8 +7,8 @@ + - diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index d5aa30d3869..d9b67d509f9 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -67,9 +67,9 @@ bcdaaccbf4b97f0fa310f29be7dc6d0bf4924d47 https://github.com/dotnet/dotnet - + https://github.com/dotnet/android - 0c3cd1162d31963582224c5dc552d74e873ba6e3 + db080b91dbe7947a69005a203ea98d9bb207ad04 4c9d1b112c16716c2479e054e9ad4db8b5b8c70c From 6925e53048fa622a7c647f8a5b7d75a4d6fcf1b0 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Wed, 23 Sep 2026 13:58:43 -0700 Subject: [PATCH 11/17] [main] Update dependencies from dotnet/maui (#5313) * Update dependencies from https://github.com/dotnet/maui build 20260916.1 On relative base path root Microsoft.Maui.Controls From Version 10.0.120-ci.main.26463.5 -> To Version 10.0.120-ci.inflight.26466.1 * Update dependencies from https://github.com/dotnet/maui build 20260916.12 On relative base path root Microsoft.Maui.Controls From Version 10.0.120-ci.main.26463.5 -> To Version 10.0.120-ci.inflight.26466.12 * Update dependencies from https://github.com/dotnet/maui build 20260917.7 On relative base path root Microsoft.Maui.Controls From Version 10.0.120-ci.main.26463.5 -> To Version 10.0.110-ci.main.26467.8 * Update dependencies from https://github.com/dotnet/maui build 20260917.10 On relative base path root Microsoft.Maui.Controls From Version 10.0.120-ci.main.26463.5 -> To Version 10.0.120-ci.inflight.26467.29 * Update dependencies from https://github.com/dotnet/maui build 20260918.8 On relative base path root Microsoft.Maui.Controls From Version 10.0.120-ci.main.26463.5 -> To Version 10.0.120-ci.inflight.26468.10 * Update dependencies from https://github.com/dotnet/maui build 20260920.2 On relative base path root Microsoft.Maui.Controls From Version 10.0.120-ci.main.26463.5 -> To Version 10.0.120-ci.inflight.26470.2 * Update dependencies from https://github.com/dotnet/maui build 20260921.10 On relative base path root Microsoft.Maui.Controls From Version 10.0.120-ci.main.26463.5 -> To Version 10.0.120-ci.inflight.26471.12 * Update dependencies from https://github.com/dotnet/maui build 20260921.14 On relative base path root Microsoft.Maui.Controls From Version 10.0.120-ci.main.26463.5 -> To Version 10.0.120-ci.inflight.26471.16 * Update dependencies from https://github.com/dotnet/maui build 20260922.7 On relative base path root Microsoft.Maui.Controls From Version 10.0.120-ci.main.26463.5 -> To Version 10.0.120-ci.inflight.26472.9 --------- Co-authored-by: dotnet-maestro[bot] --- eng/Version.Details.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index d9b67d509f9..729d1b30d4a 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -55,8 +55,8 @@ Mapping_Microsoft.tvOS.Sdk:default --> - - 70e7adc57504d3435a0f8cc68e890300546ab96a + + 0fb2d2e873627b4fd88b20ffa17c5d3b20b4441c https://github.com/dotnet/maui From 41f79b098916067e6975b8beaef4ef60132a8a50 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Wed, 23 Sep 2026 14:24:02 -0700 Subject: [PATCH 12/17] [main] Update dependencies from dotnet/dotnet (#5311) * Update dependencies from https://github.com/dotnet/dotnet build 20260915.8 On relative base path root Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 11.0.0-beta.26465.102 -> To Version 11.0.0-beta.26465.108 Microsoft.Extensions.Logging , Microsoft.NET.ILLink , Microsoft.NET.ILLink.Tasks , Microsoft.NETCore.App.Ref , Microsoft.WindowsDesktop.App.Ref , System.Threading.Channels From Version 11.0.0-rc.2.26465.102 -> To Version 11.0.0-rc.2.26465.108 Microsoft.NET.Sdk From Version 11.0.100-rc.2.26465.102 -> To Version 11.0.100-rc.2.26465.108 * Update dependencies from https://github.com/dotnet/dotnet build 20260915.13 On relative base path root Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 11.0.0-beta.26465.102 -> To Version 11.0.0-beta.26465.113 Microsoft.Extensions.Logging , Microsoft.NET.ILLink , Microsoft.NET.ILLink.Tasks , Microsoft.NETCore.App.Ref , Microsoft.WindowsDesktop.App.Ref , System.Threading.Channels From Version 11.0.0-rc.2.26465.102 -> To Version 11.0.0-rc.2.26465.113 Microsoft.NET.Sdk From Version 11.0.100-rc.2.26465.102 -> To Version 11.0.100-rc.2.26465.113 * Update dependencies from https://github.com/dotnet/dotnet build 20260916.22 On relative base path root Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 11.0.0-beta.26465.102 -> To Version 11.0.0-beta.26466.122 Microsoft.Extensions.Logging , Microsoft.NET.ILLink , Microsoft.NET.ILLink.Tasks , Microsoft.NETCore.App.Ref , Microsoft.WindowsDesktop.App.Ref , System.Threading.Channels From Version 11.0.0-rc.2.26465.102 -> To Version 11.0.0-rc.2.26466.122 Microsoft.NET.Sdk From Version 11.0.100-rc.2.26465.102 -> To Version 11.0.100-rc.2.26466.122 * Update dependencies from https://github.com/dotnet/dotnet build 20260917.4 On relative base path root Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 11.0.0-beta.26465.102 -> To Version 11.0.0-beta.26467.104 Microsoft.Extensions.Logging , Microsoft.NET.ILLink , Microsoft.NET.ILLink.Tasks , Microsoft.NETCore.App.Ref , Microsoft.WindowsDesktop.App.Ref , System.Threading.Channels From Version 11.0.0-rc.2.26465.102 -> To Version 11.0.0-rc.2.26467.104 Microsoft.NET.Sdk From Version 11.0.100-rc.2.26465.102 -> To Version 11.0.100-rc.2.26467.104 * Update dependencies from https://github.com/dotnet/dotnet build 20260918.10 On relative base path root Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 11.0.0-beta.26465.102 -> To Version 11.0.0-beta.26468.110 Microsoft.Extensions.Logging , Microsoft.NET.ILLink , Microsoft.NET.ILLink.Tasks , Microsoft.NETCore.App.Ref , Microsoft.WindowsDesktop.App.Ref , System.Threading.Channels From Version 11.0.0-rc.2.26465.102 -> To Version 11.0.0-rc.2.26468.110 Microsoft.NET.Sdk From Version 11.0.100-rc.2.26465.102 -> To Version 11.0.100-rc.2.26468.110 * Update dependencies from https://github.com/dotnet/dotnet build 20260918.17 On relative base path root Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 11.0.0-beta.26465.102 -> To Version 11.0.0-beta.26468.117 Microsoft.Extensions.Logging , Microsoft.NET.ILLink , Microsoft.NET.ILLink.Tasks , Microsoft.NETCore.App.Ref , Microsoft.WindowsDesktop.App.Ref , System.Threading.Channels From Version 11.0.0-rc.2.26465.102 -> To Version 11.0.0-rc.2.26468.117 Microsoft.NET.Sdk From Version 11.0.100-rc.2.26465.102 -> To Version 11.0.100-rc.2.26468.117 * Update dependencies from https://github.com/dotnet/dotnet build 20260919.4 On relative base path root Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 11.0.0-beta.26465.102 -> To Version 11.0.0-beta.26469.104 Microsoft.Extensions.Logging , Microsoft.NET.ILLink , Microsoft.NET.ILLink.Tasks , Microsoft.NETCore.App.Ref , Microsoft.WindowsDesktop.App.Ref , System.Threading.Channels From Version 11.0.0-rc.2.26465.102 -> To Version 11.0.0-rc.2.26469.104 Microsoft.NET.Sdk From Version 11.0.100-rc.2.26465.102 -> To Version 11.0.100-rc.2.26469.104 * Update dependencies from https://github.com/dotnet/dotnet build 20260919.8 On relative base path root Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 11.0.0-beta.26465.102 -> To Version 11.0.0-beta.26469.108 Microsoft.Extensions.Logging , Microsoft.NET.ILLink , Microsoft.NET.ILLink.Tasks , Microsoft.NETCore.App.Ref , Microsoft.WindowsDesktop.App.Ref , System.Threading.Channels From Version 11.0.0-rc.2.26465.102 -> To Version 11.0.0-rc.2.26469.108 Microsoft.NET.Sdk From Version 11.0.100-rc.2.26465.102 -> To Version 11.0.100-rc.2.26469.108 * Update dependencies from https://github.com/dotnet/dotnet build 20260919.9 On relative base path root Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 11.0.0-beta.26465.102 -> To Version 11.0.0-beta.26469.109 Microsoft.Extensions.Logging , Microsoft.NET.ILLink , Microsoft.NET.ILLink.Tasks , Microsoft.NETCore.App.Ref , Microsoft.WindowsDesktop.App.Ref , System.Threading.Channels From Version 11.0.0-rc.2.26465.102 -> To Version 11.0.0-rc.2.26469.109 Microsoft.NET.Sdk From Version 11.0.100-rc.2.26465.102 -> To Version 11.0.100-rc.2.26469.109 * Update dependencies from https://github.com/dotnet/dotnet build 20260920.1 On relative base path root Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 11.0.0-beta.26465.102 -> To Version 11.0.0-beta.26470.101 Microsoft.Extensions.Logging , Microsoft.NET.ILLink , Microsoft.NET.ILLink.Tasks , Microsoft.NETCore.App.Ref , Microsoft.WindowsDesktop.App.Ref , System.Threading.Channels From Version 11.0.0-rc.2.26465.102 -> To Version 11.0.0-rc.2.26470.101 Microsoft.NET.Sdk From Version 11.0.100-rc.2.26465.102 -> To Version 11.0.100-rc.2.26470.101 * Update dependencies from https://github.com/dotnet/dotnet build 20260920.3 On relative base path root Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 11.0.0-beta.26465.102 -> To Version 11.0.0-beta.26470.103 Microsoft.Extensions.Logging , Microsoft.NET.ILLink , Microsoft.NET.ILLink.Tasks , Microsoft.NETCore.App.Ref , Microsoft.WindowsDesktop.App.Ref , System.Threading.Channels From Version 11.0.0-rc.2.26465.102 -> To Version 11.0.0-rc.2.26470.103 Microsoft.NET.Sdk From Version 11.0.100-rc.2.26465.102 -> To Version 11.0.100-rc.2.26470.103 * Update dependencies from https://github.com/dotnet/dotnet build 20260921.9 On relative base path root Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 11.0.0-beta.26465.102 -> To Version 11.0.0-beta.26471.109 Microsoft.Extensions.Logging , Microsoft.NET.ILLink , Microsoft.NET.ILLink.Tasks , Microsoft.NETCore.App.Ref , Microsoft.WindowsDesktop.App.Ref , System.Threading.Channels From Version 11.0.0-rc.2.26465.102 -> To Version 11.0.0-rc.2.26471.109 Microsoft.NET.Sdk From Version 11.0.100-rc.2.26465.102 -> To Version 11.0.100-rc.2.26471.109 * Update dependencies from https://github.com/dotnet/dotnet build 20260922.6 On relative base path root Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 11.0.0-beta.26465.102 -> To Version 11.0.0-beta.26472.106 Microsoft.Extensions.Logging , Microsoft.NET.ILLink , Microsoft.NET.ILLink.Tasks , Microsoft.NETCore.App.Ref , Microsoft.WindowsDesktop.App.Ref , System.Threading.Channels From Version 11.0.0-rc.2.26465.102 -> To Version 11.0.0-rc.2.26472.106 Microsoft.NET.Sdk From Version 11.0.100-rc.2.26465.102 -> To Version 11.0.100-rc.2.26472.106 * Update dependencies from https://github.com/dotnet/dotnet build 20260922.8 On relative base path root Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 11.0.0-beta.26465.102 -> To Version 11.0.0-beta.26472.108 Microsoft.Extensions.Logging , Microsoft.NET.ILLink , Microsoft.NET.ILLink.Tasks , Microsoft.NETCore.App.Ref , Microsoft.WindowsDesktop.App.Ref , System.Threading.Channels From Version 11.0.0-rc.2.26465.102 -> To Version 11.0.0-rc.2.26472.108 Microsoft.NET.Sdk From Version 11.0.100-rc.2.26465.102 -> To Version 11.0.100-rc.2.26472.108 * Update dependencies from https://github.com/dotnet/dotnet build 20260922.24 On relative base path root Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 11.0.0-beta.26465.102 -> To Version 11.0.0-beta.26472.124 Microsoft.Extensions.Logging , Microsoft.NET.ILLink , Microsoft.NET.ILLink.Tasks , Microsoft.NETCore.App.Ref , Microsoft.WindowsDesktop.App.Ref , System.Threading.Channels From Version 11.0.0-rc.2.26465.102 -> To Version 11.0.0-rtm.26472.124 Microsoft.NET.Sdk From Version 11.0.100-rc.2.26465.102 -> To Version 11.0.100-rtm.26472.124 * Update dependencies from https://github.com/dotnet/dotnet build 20260923.4 On relative base path root Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 11.0.0-beta.26465.102 -> To Version 11.0.0-beta.26473.104 Microsoft.Extensions.Logging , Microsoft.NET.ILLink , Microsoft.NET.ILLink.Tasks , Microsoft.NETCore.App.Ref , Microsoft.WindowsDesktop.App.Ref , System.Threading.Channels From Version 11.0.0-rc.2.26465.102 -> To Version 11.0.0-rtm.26473.104 Microsoft.NET.Sdk From Version 11.0.100-rc.2.26465.102 -> To Version 11.0.100-rtm.26473.104 --------- Co-authored-by: dotnet-maestro[bot] --- eng/Version.Details.xml | 36 ++++++++-------- eng/Versions.props | 8 ++-- .../core-templates/job/helix-job-monitor.yml | 42 +++++++++++++++++- eng/common/core-templates/job/job.yml | 3 +- .../core-templates/steps/send-to-helix.yml | 43 +++++++++++++++++-- global.json | 8 ++-- 6 files changed, 107 insertions(+), 33 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 729d1b30d4a..3d9b8262596 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,25 +1,25 @@ - + https://github.com/dotnet/dotnet - bcdaaccbf4b97f0fa310f29be7dc6d0bf4924d47 + cca240e47d0e810f091225e13c7c93aa7e1c5189 - + https://github.com/dotnet/dotnet - bcdaaccbf4b97f0fa310f29be7dc6d0bf4924d47 + cca240e47d0e810f091225e13c7c93aa7e1c5189 - + https://github.com/dotnet/dotnet - bcdaaccbf4b97f0fa310f29be7dc6d0bf4924d47 + cca240e47d0e810f091225e13c7c93aa7e1c5189 - + https://github.com/dotnet/dotnet - bcdaaccbf4b97f0fa310f29be7dc6d0bf4924d47 + cca240e47d0e810f091225e13c7c93aa7e1c5189 - + https://github.com/dotnet/dotnet - bcdaaccbf4b97f0fa310f29be7dc6d0bf4924d47 + cca240e47d0e810f091225e13c7c93aa7e1c5189 https://github.com/dotnet/dotnet @@ -27,13 +27,13 @@ - + https://github.com/dotnet/dotnet - bcdaaccbf4b97f0fa310f29be7dc6d0bf4924d47 + cca240e47d0e810f091225e13c7c93aa7e1c5189 - + https://github.com/dotnet/dotnet - bcdaaccbf4b97f0fa310f29be7dc6d0bf4924d47 + cca240e47d0e810f091225e13c7c93aa7e1c5189 https://github.com/dotnet/dotnet @@ -59,12 +59,12 @@ 0fb2d2e873627b4fd88b20ffa17c5d3b20b4441c https://github.com/dotnet/maui - - bcdaaccbf4b97f0fa310f29be7dc6d0bf4924d47 + + cca240e47d0e810f091225e13c7c93aa7e1c5189 https://github.com/dotnet/dotnet - - bcdaaccbf4b97f0fa310f29be7dc6d0bf4924d47 + + cca240e47d0e810f091225e13c7c93aa7e1c5189 https://github.com/dotnet/dotnet diff --git a/eng/Versions.props b/eng/Versions.props index 297cc304d1c..fa377a9b1e1 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -7,10 +7,10 @@ - 11.0.0-rc.2.26465.102 - 11.0.0-rc.2.26465.102 - 11.0.0-rc.2.26465.102 - 11.0.0-rc.2.26465.102 + 11.0.0-rtm.26473.104 + 11.0.0-rtm.26473.104 + 11.0.0-rtm.26473.104 + 11.0.0-rtm.26473.104 0.16.0-nightly.20260801.595 11.0.0-preview.5.26261.101 11.0.0-prerelease.26465.1 diff --git a/eng/common/core-templates/job/helix-job-monitor.yml b/eng/common/core-templates/job/helix-job-monitor.yml index 53bbf74927e..7417afdf3ea 100644 --- a/eng/common/core-templates/job/helix-job-monitor.yml +++ b/eng/common/core-templates/job/helix-job-monitor.yml @@ -52,11 +52,23 @@ parameters: type: string default: https://helix.dot.net/ -# Helix API access token forwarded to the tool via the HELIX_ACCESSTOKEN environment variable. +# Helix API access token forwarded via HELIX_ACCESSTOKEN. Not forwarded when +# useEntraAuthentication is true. - name: helixAccessToken type: string default: '' +# Use a refreshable Entra credential instead of a PAT or anonymous access. +- name: useEntraAuthentication + type: boolean + default: false + +# Azure service connection ID authorized for Helix. Required when +# useEntraAuthentication is true. +- name: azureSubscription + type: string + default: '' + # Polling interval in seconds (--polling-interval-seconds). - name: pollingIntervalSeconds type: number @@ -141,6 +153,26 @@ jobs: - checkout: self fetchDepth: 1 + - ${{ if and(eq(parameters.useEntraAuthentication, true), eq(parameters.azureSubscription, '')) }}: + - pwsh: throw "azureSubscription must be set when useEntraAuthentication is true." + displayName: Validate Helix Entra authentication + + - ${{ if eq(parameters.useEntraAuthentication, true) }}: + - task: AzureCLI@2 + displayName: Initialize Helix Entra authentication + inputs: + azureSubscription: ${{ parameters.azureSubscription }} + addSpnToEnvironment: true + scriptType: pscore + scriptLocation: inlineScript + inlineScript: | + if ([string]::IsNullOrWhiteSpace($env:servicePrincipalId) -or [string]::IsNullOrWhiteSpace($env:tenantId)) { + throw "The Helix Azure service connection did not provide a service principal or tenant ID." + } + + Write-Host "##vso[task.setvariable variable=HelixEntraClientId]$env:servicePrincipalId" + Write-Host "##vso[task.setvariable variable=HelixEntraTenantId]$env:tenantId" + - ${{ if ne(parameters.toolNupkgArtifactName, '') }}: - task: DownloadPipelineArtifact@2 displayName: Download Helix Job Monitor artifact @@ -214,6 +246,7 @@ jobs: toolArgs=( --helix-base-uri '${{ parameters.helixBaseUri }}' + --use-entra-authentication '${{ parameters.useEntraAuthentication }}' --polling-interval-seconds '${{ parameters.pollingIntervalSeconds }}' --fail-on-failed-tests '${{ parameters.failWorkItemsWithFailedTests }}' --allow-no-helix-jobs '${{ parameters.allowNoHelixJobs }}' @@ -275,4 +308,9 @@ jobs: displayName: Monitor Helix Jobs env: SYSTEM_ACCESSTOKEN: $(System.AccessToken) - HELIX_ACCESSTOKEN: ${{ parameters.helixAccessToken }} + ${{ if eq(parameters.useEntraAuthentication, false) }}: + HELIX_ACCESSTOKEN: ${{ parameters.helixAccessToken }} + ${{ if eq(parameters.useEntraAuthentication, true) }}: + AZURESUBSCRIPTION_CLIENT_ID: $(HelixEntraClientId) + AZURESUBSCRIPTION_TENANT_ID: $(HelixEntraTenantId) + AZURESUBSCRIPTION_SERVICE_CONNECTION_ID: ${{ parameters.azureSubscription }} diff --git a/eng/common/core-templates/job/job.yml b/eng/common/core-templates/job/job.yml index cb60f529784..7da6bdf520e 100644 --- a/eng/common/core-templates/job/job.yml +++ b/eng/common/core-templates/job/job.yml @@ -119,7 +119,8 @@ jobs: - name: ${{ pair.key }} value: ${{ pair.value }} - # DotNet-HelixApi-Access provides 'HelixApiAccessToken' for internal builds + # DotNet-HelixApi-Access provides 'HelixApiAccessToken' for internal builds. + # Entra-enabled Helix templates do not forward this value to their processes. - ${{ if and(eq(parameters.enableTelemetry, 'true'), eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - group: DotNet-HelixApi-Access diff --git a/eng/common/core-templates/steps/send-to-helix.yml b/eng/common/core-templates/steps/send-to-helix.yml index ec7a2000399..62cce4d4ea2 100644 --- a/eng/common/core-templates/steps/send-to-helix.yml +++ b/eng/common/core-templates/steps/send-to-helix.yml @@ -4,7 +4,9 @@ parameters: HelixType: 'tests/default/' # required -- Helix telemetry which identifies what type of data this is; should include "test" for clarity and must end in '/' HelixBuild: $(Build.BuildNumber) # required -- the build number Helix will use to identify this -- automatically set to the AzDO build number HelixTargetQueues: '' # required -- semicolon-delimited list of Helix queues to test on; see https://helix.dot.net/ for a list of queues - HelixAccessToken: '' # required -- access token to make Helix API requests; should be provided by the appropriate variable group + HelixAccessToken: '' # optional -- legacy access token; not forwarded when HelixUseEntraAuthentication is true + HelixUseEntraAuthentication: false # optional -- use refreshable Entra authentication instead of a PAT or anonymous access + HelixAzureSubscription: '' # required when HelixUseEntraAuthentication is true -- Azure service connection ID authorized for Helix HelixProjectPath: 'eng/common/helixpublish.proj' # optional -- path to the project file to build relative to BUILD_SOURCESDIRECTORY HelixProjectArguments: '' # optional -- arguments passed to the build command HelixConfiguration: '' # optional -- additional property attached to a job @@ -32,12 +34,35 @@ parameters: continueOnError: false # optional -- determines whether to continue the build if the step errors; defaults to false steps: + - ${{ if and(eq(parameters.HelixUseEntraAuthentication, true), eq(parameters.HelixAzureSubscription, '')) }}: + - pwsh: throw "HelixAzureSubscription must be set when HelixUseEntraAuthentication is true." + displayName: Validate Helix Entra authentication + condition: ${{ parameters.condition }} + + - ${{ if eq(parameters.HelixUseEntraAuthentication, true) }}: + - task: AzureCLI@2 + displayName: Initialize Helix Entra authentication + inputs: + azureSubscription: ${{ parameters.HelixAzureSubscription }} + addSpnToEnvironment: true + scriptType: pscore + scriptLocation: inlineScript + inlineScript: | + if ([string]::IsNullOrWhiteSpace($env:servicePrincipalId) -or [string]::IsNullOrWhiteSpace($env:tenantId)) { + throw "The Helix Azure service connection did not provide a service principal or tenant ID." + } + + Write-Host "##vso[task.setvariable variable=HelixEntraClientId]$env:servicePrincipalId" + Write-Host "##vso[task.setvariable variable=HelixEntraTenantId]$env:tenantId" + condition: ${{ parameters.condition }} + - powershell: > $(Build.SourcesDirectory)\eng\common\msbuild.ps1 $(Build.SourcesDirectory)/${{ parameters.HelixProjectPath }} /restore /p:TreatWarningsAsErrors=false /p:EnableHelixJobMonitor=${{ parameters.UseHelixMonitor }} + /p:HelixUseEntraAuthentication=${{ parameters.HelixUseEntraAuthentication }} ${{ parameters.HelixProjectArguments }} /t:Test /bl:$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/SendToHelix.binlog @@ -49,7 +74,12 @@ steps: HelixBuild: ${{ parameters.HelixBuild }} HelixConfiguration: ${{ parameters.HelixConfiguration }} HelixTargetQueues: ${{ parameters.HelixTargetQueues }} - HelixAccessToken: ${{ parameters.HelixAccessToken }} + ${{ if eq(parameters.HelixUseEntraAuthentication, false) }}: + HelixAccessToken: ${{ parameters.HelixAccessToken }} + ${{ if eq(parameters.HelixUseEntraAuthentication, true) }}: + AZURESUBSCRIPTION_CLIENT_ID: $(HelixEntraClientId) + AZURESUBSCRIPTION_TENANT_ID: $(HelixEntraTenantId) + AZURESUBSCRIPTION_SERVICE_CONNECTION_ID: ${{ parameters.HelixAzureSubscription }} HelixPreCommands: ${{ parameters.HelixPreCommands }} HelixPostCommands: ${{ parameters.HelixPostCommands }} WorkItemDirectory: ${{ parameters.WorkItemDirectory }} @@ -76,6 +106,7 @@ steps: /restore /p:TreatWarningsAsErrors=false /p:EnableHelixJobMonitor=${{ parameters.UseHelixMonitor }} + /p:HelixUseEntraAuthentication=${{ parameters.HelixUseEntraAuthentication }} ${{ parameters.HelixProjectArguments }} /t:Test /bl:$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/SendToHelix.binlog @@ -87,7 +118,12 @@ steps: HelixBuild: ${{ parameters.HelixBuild }} HelixConfiguration: ${{ parameters.HelixConfiguration }} HelixTargetQueues: ${{ parameters.HelixTargetQueues }} - HelixAccessToken: ${{ parameters.HelixAccessToken }} + ${{ if eq(parameters.HelixUseEntraAuthentication, false) }}: + HelixAccessToken: ${{ parameters.HelixAccessToken }} + ${{ if eq(parameters.HelixUseEntraAuthentication, true) }}: + AZURESUBSCRIPTION_CLIENT_ID: $(HelixEntraClientId) + AZURESUBSCRIPTION_TENANT_ID: $(HelixEntraTenantId) + AZURESUBSCRIPTION_SERVICE_CONNECTION_ID: ${{ parameters.HelixAzureSubscription }} HelixPreCommands: ${{ parameters.HelixPreCommands }} HelixPostCommands: ${{ parameters.HelixPostCommands }} WorkItemDirectory: ${{ parameters.WorkItemDirectory }} @@ -108,4 +144,3 @@ steps: SYSTEM_ACCESSTOKEN: $(System.AccessToken) condition: and(${{ parameters.condition }}, ne(variables['Agent.Os'], 'Windows_NT')) continueOnError: ${{ parameters.continueOnError }} - diff --git a/global.json b/global.json index 4e21455a2db..91f7652ccac 100644 --- a/global.json +++ b/global.json @@ -1,15 +1,15 @@ { "sdk": { - "version": "11.0.100-rc.1.26420.103", + "version": "11.0.100-rc.1.26425.128", "allowPrerelease": true, "rollForward": "major" }, "tools": { - "dotnet": "11.0.100-rc.1.26420.103" + "dotnet": "11.0.100-rc.1.26425.128" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "11.0.0-beta.26465.102", - "Microsoft.DotNet.Helix.Sdk": "11.0.0-beta.26465.102" + "Microsoft.DotNet.Arcade.Sdk": "11.0.0-beta.26473.104", + "Microsoft.DotNet.Helix.Sdk": "11.0.0-beta.26473.104" }, "native-tools": { "python3": "3.7.1" From 8c12376d65c0e400197d191f7cf2bf375c4e2ab6 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Wed, 23 Sep 2026 14:28:45 -0700 Subject: [PATCH 13/17] Update dependencies from https://github.com/dotnet/dotnet build 20260923.4 (#5318) On relative base path root Microsoft.DotNet.Helix.JobMonitor From Version 11.0.0-beta.26458.103 -> To Version 11.0.0-beta.26473.104 Co-authored-by: dotnet-maestro[bot] --- .config/dotnet-tools.json | 2 +- NuGet.config | 1 + eng/Version.Details.xml | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 5f56247a879..09c845ee7e9 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "microsoft.dotnet.helix.jobmonitor": { - "version": "11.0.0-beta.26458.103", + "version": "11.0.0-beta.26473.104", "commands": [ "dotnet-helix-job-monitor" ] diff --git a/NuGet.config b/NuGet.config index fcdef4b1f38..accbce0f625 100644 --- a/NuGet.config +++ b/NuGet.config @@ -8,6 +8,7 @@ + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 3d9b8262596..46c155f1d8d 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -35,9 +35,9 @@ https://github.com/dotnet/dotnet cca240e47d0e810f091225e13c7c93aa7e1c5189 - + https://github.com/dotnet/dotnet - bcd0a2de8927de44b65f3ad41bef8820ee2b3d0b + cca240e47d0e810f091225e13c7c93aa7e1c5189 https://github.com/dotnet/xharness From 31130a40d496897ab2727c8e2b328e18752e0b9a Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Wed, 23 Sep 2026 14:29:45 -0700 Subject: [PATCH 14/17] [main] Update dependencies from dotnet/xharness (#5312) * Update dependencies from https://github.com/dotnet/xharness build 20260916.1 On relative base path root Microsoft.DotNet.XHarness.CLI From Version 11.0.0-prerelease.26465.1 -> To Version 11.0.0-prerelease.26466.1 * Update dependencies from https://github.com/dotnet/xharness build 20260921.1 On relative base path root Microsoft.DotNet.XHarness.CLI From Version 11.0.0-prerelease.26465.1 -> To Version 11.0.0-prerelease.26471.1 * Update dependencies from https://github.com/dotnet/xharness build 20260921.4 On relative base path root Microsoft.DotNet.XHarness.CLI From Version 11.0.0-prerelease.26465.1 -> To Version 11.0.0-prerelease.26471.4 * Update dependencies from https://github.com/dotnet/xharness build 20260922.1 On relative base path root Microsoft.DotNet.XHarness.CLI From Version 11.0.0-prerelease.26465.1 -> To Version 11.0.0-prerelease.26472.1 * Update dependencies from https://github.com/dotnet/xharness build 20260923.1 On relative base path root Microsoft.DotNet.XHarness.CLI From Version 11.0.0-prerelease.26465.1 -> To Version 11.0.0-prerelease.26473.1 --------- Co-authored-by: dotnet-maestro[bot] --- eng/Version.Details.xml | 4 ++-- eng/Versions.props | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 46c155f1d8d..65e5abfade0 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -39,9 +39,9 @@ https://github.com/dotnet/dotnet cca240e47d0e810f091225e13c7c93aa7e1c5189 - + https://github.com/dotnet/xharness - 225f4dfefc66973c1f37623cd65d0470d389a63d + 64629bf55dc3bf7bddf2ec24670950c02ba451ea From 1f0e0f68889a0996f30e265846446549a801800e Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Wed, 23 Sep 2026 17:47:07 -0500 Subject: [PATCH 15/17] Enable Jil for CoreCLR WASM R2R (#5317) The wasm codegen issue that produced invalid Jil ISO8601 TimeSpan deserializers was fixed by dotnet/runtime#133210, merged as a5b8d110306eff632cced48a4d108d619393a6a4. Remove the temporary Jil.dll R2R exclusion so the CoreCLR browser-WASM lane compiles it normally again. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/benchmarks/micro/MicroBenchmarks.Wasm.targets | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/benchmarks/micro/MicroBenchmarks.Wasm.targets b/src/benchmarks/micro/MicroBenchmarks.Wasm.targets index 93ca49d7232..b6e4216cf79 100644 --- a/src/benchmarks/micro/MicroBenchmarks.Wasm.targets +++ b/src/benchmarks/micro/MicroBenchmarks.Wasm.targets @@ -48,10 +48,6 @@ - - - <_AOT_InternalForceInterpretAssemblies Include="Microsoft.CodeAnalysis.CSharp.dll" /> <_AOT_InternalForceInterpretAssemblies Include="Microsoft.CodeAnalysis.dll" /> From 88d25037b7b5a4265583b9bdc1678158e9a54664 Mon Sep 17 00:00:00 2001 From: Parker Bibus Date: Mon, 28 Sep 2026 11:12:27 -0700 Subject: [PATCH 16/17] Prepare Blazor workloads before dispatching to Helix Install wasm-tools once into the SDK correlation payload and make all Blazor scenario workload helpers read-only. Keep publish and measurement on Helix, avoiding partial workload uninstalls that corrupt subsequent work items. Preserve non-AOT localized publishing and pass the localized AOT argument directly instead of treating item metadata as a property. Add real MSBuild command-evaluation and Python helper regression coverage and document the lifecycle. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- docs/sdk-scenarios.md | 22 ++++ eng/performance/blazor_scenarios.proj | 18 ++- scripts/tests/test_blazor_workload.py | 181 ++++++++++++++++++++++++++ 3 files changed, 216 insertions(+), 5 deletions(-) create mode 100644 scripts/tests/test_blazor_workload.py diff --git a/docs/sdk-scenarios.md b/docs/sdk-scenarios.md index c09f165f198..c72027e1323 100644 --- a/docs/sdk-scenarios.md +++ b/docs/sdk-scenarios.md @@ -47,6 +47,28 @@ does not download arbitrary Helix uploads into pipeline artifacts. Asynchronous skip the submitter's `artifacts/helix-results` downloads, which were not published by this pipeline. Local `--send-to-helix` runs still wait and download performance reports. +## Blazor workload preparation + +SDK Blazor jobs install `wasm-tools` once on the build agent through +`PreparePayloadWorkItems`, using the SDK in the correlation payload, its bundled +manifests (`--skip-manifest-update`), and the payload's `NuGet.config`. An installation +failure stops payload preparation before the job is sent to Helix. + +Helix work items use `pre.py publish --has-workload --readonly-dotnet` and +`post.py --readonly-dotnet`. These flags prevent the scenario helpers from changing +the shared workload installation; they do not set filesystem permissions. Application +restore, publish, size measurements, and cleanup of work-item-local outputs still run +on Helix. Standalone scenario scripts retain their existing workload-management +defaults. + +Workload availability does not select the benchmark's compilation mode. The Minimum +Template explicitly enables native relinking, while the non-AOT New Template, Pizza, +and Localized scenarios disable it with `WasmNativeWorkload=false`. AOT scenarios +enable `RunAOTCompilation=true`. The Localized AOT command passes this property directly +instead of referencing item metadata as an MSBuild property, which previously dropped +the AOT argument. Its reported sizes may therefore change when it first runs with AOT +actually enabled. + ## SDK Build Throughput Scenario **SDK Build Throughput** is a scenario test that measures the throughput of SDK build process. To be more specific, our test *implicitly calls* diff --git a/eng/performance/blazor_scenarios.proj b/eng/performance/blazor_scenarios.proj index 4cba378c7fa..8d0e868dc27 100644 --- a/eng/performance/blazor_scenarios.proj +++ b/eng/performance/blazor_scenarios.proj @@ -7,9 +7,13 @@ %PERFLAB_TARGET_FRAMEWORKS% %24{PERFLAB_TARGET_FRAMEWORKS} <_MSBuildArgs>/p:_TrimmerDumpDependencies=true;/warnaserror:NU1602,NU1604 - --msbuild "$(_MSBuildArgs)" --msbuild-static AdditionalMonoLinkerOptions=%27"%24(AdditionalMonoLinkerOptions) --dump-dependencies"%27 --binlog $(LogDirectory)blazor_publish.binlog + --has-workload --readonly-dotnet --msbuild "$(_MSBuildArgs)" --msbuild-static AdditionalMonoLinkerOptions=%27"%24(AdditionalMonoLinkerOptions) --dump-dependencies"%27 --binlog $(LogDirectory)blazor_publish.binlog $(Python) pre.py publish $(PublishArgs) + <_BlazorDotNet>$([MSBuild]::NormalizePath('$(CorrelationPayloadDirectory)', 'dotnet', 'dotnet')) + <_BlazorDotNet Condition="'$(TargetsWindows)' == 'true'">$(_BlazorDotNet).exe + <_BlazorNuGetConfig>$([MSBuild]::NormalizePath('$(CorrelationPayloadDirectory)', 'performance', 'NuGet.config')) + pub\wwwroot pub/wwwroot @@ -27,6 +31,11 @@ + + "$(_BlazorDotNet)" workload install wasm-tools --skip-manifest-update --configfile "$(_BlazorNuGetConfig)" + $(CorrelationPayloadDirectory)performance + + %(Identity) @@ -35,7 +44,7 @@ - $(Python) post.py + $(Python) post.py --readonly-dotnet 4:00 @@ -81,14 +90,13 @@ $(ScenariosDir)blazorlocalized - $(PublishCommand) -f $(PerfLabTargetFrameworksEnvVar) && $(Python) test.py sod --scenario-name "%(Identity)" --dirs $(PizzaAppPubLocation) $(ScenarioArgs) + $(PublishCommand) --msbuild "$(_MSBuildArgs);/p:WasmNativeWorkload=false" -f $(PerfLabTargetFrameworksEnvVar) && $(Python) test.py sod --scenario-name "%(Identity)" --dirs $(PizzaAppPubLocation) $(ScenarioArgs) - <_PublishArgsWithAOT>--msbuild "$(_MSBuildArgs);/p:RunAOTCompilation=true" $(ScenariosDir)blazorlocalized - $(PublishCommand) $(_PublishArgsWithAOT) -f $(PerfLabTargetFrameworksEnvVar) && $(Python) test.py sod --scenario-name "%(Identity)" --dirs $(PizzaAppPubLocation) $(ScenarioArgs) + $(PublishCommand) --msbuild "$(_MSBuildArgs);/p:RunAOTCompilation=true" -f $(PerfLabTargetFrameworksEnvVar) && $(Python) test.py sod --scenario-name "%(Identity)" --dirs $(PizzaAppPubLocation) $(ScenarioArgs) diff --git a/scripts/tests/test_blazor_workload.py b/scripts/tests/test_blazor_workload.py new file mode 100644 index 00000000000..5fab9879d76 --- /dev/null +++ b/scripts/tests/test_blazor_workload.py @@ -0,0 +1,181 @@ +import importlib +import json +import os +from pathlib import Path +import shlex +import shutil +import subprocess +import sys +from unittest.mock import Mock +import xml.etree.ElementTree as ET + +import pytest + + +repo_root = Path(__file__).resolve().parents[2] + + +@pytest.fixture +def scenario_commands(monkeypatch): + monkeypatch.syspath_prepend(str(repo_root / "scripts")) + monkeypatch.syspath_prepend(str(repo_root / "src" / "scenarios")) + return ( + importlib.import_module("shared.precommands").PreCommands, + importlib.import_module("shared.postcommands").PostCommands, + ) + + +@pytest.fixture(params=["true", "false"], ids=["windows", "unix"]) +def blazor_items(request, tmp_path): + dotnet = shutil.which("dotnet") + if dotnet is None: + pytest.skip("MSBuild evaluation requires a .NET SDK") + + project = ET.parse(repo_root / "eng" / "performance" / "blazor_scenarios.proj") + # Evaluate the real work items without downloading the Helix SDK. + project.getroot().attrib.pop("Sdk") + project.find("Import").set( + "Project", str(repo_root / "eng" / "performance" / "PreparePayloadWorkItems.targets") + ) + project_path = tmp_path / "blazor.proj" + project.write(project_path, encoding="utf-8") + payload = tmp_path / "correlation payload" + result = subprocess.run( + [ + dotnet, "msbuild", str(project_path), "-nologo", + "-getItem:PreparePayloadWorkItem,HelixWorkItem", + f"-p:CorrelationPayloadDirectory={payload}{os.sep}", + f"-p:TargetsWindows={request.param}", + "-p:Python=python", + ], + cwd=tmp_path, + check=True, + capture_output=True, + text=True, + ) + return json.loads(result.stdout)["Items"], payload, request.param == "true" + + +def test_workload_is_prepared_once_in_payload(blazor_items): + items, payload, windows = blazor_items + preparation, = items["PreparePayloadWorkItem"] + executable = payload / "dotnet" / ("dotnet.exe" if windows else "dotnet") + config = payload / "performance" / "NuGet.config" + + assert preparation["Command"] == ( + f'"{executable}" workload install wasm-tools --skip-manifest-update ' + f'--configfile "{config}"' + ) + assert Path(preparation["WorkingDirectory"]) == payload / "performance" + + +@pytest.mark.parametrize("exit_code", [0, 23]) +def test_preparation_propagates_workload_exit_code(blazor_items, tmp_path, exit_code): + _, payload, windows = blazor_items + working_directory = payload / "performance" + working_directory.mkdir(parents=True) + # Substitute only the executable; run the real preparation target and arguments. + (working_directory / "workload").write_text( + "import sys\n" + "assert sys.argv[1:4] == ['install', 'wasm-tools', '--skip-manifest-update']\n" + "print('Workload preparation executed')\n" + f"sys.exit({exit_code})\n", + encoding="utf-8", + ) + result = subprocess.run( + [ + shutil.which("dotnet"), "msbuild", str(tmp_path / "blazor.proj"), "-nologo", + "-t:PreparePayloadWorkItems", + f"-p:CorrelationPayloadDirectory={payload}{os.sep}", + f"-p:TargetsWindows={str(windows).lower()}", + f"-p:_BlazorDotNet={sys.executable}", + ], + cwd=tmp_path, + capture_output=True, + text=True, + ) + + assert "Workload preparation executed" in result.stdout + if exit_code: + assert result.returncode != 0 + assert "MSB3073" in result.stdout + else: + assert result.returncode == 0, result.stdout + result.stderr + + +def test_all_work_items_leave_shared_workload_untouched( + blazor_items, scenario_commands, monkeypatch +): + items, _, _ = blazor_items + PreCommands, PostCommands = scenario_commands + run = Mock(side_effect=AssertionError("A work item must not modify the workload")) + monkeypatch.setattr(subprocess, "run", run) + + assert len(items["HelixWorkItem"]) == 8 + for item in items["HelixWorkItem"]: + command = item["Command"].split(" && ", 1)[0] + monkeypatch.setattr(sys, "argv", shlex.split(command)[1:]) + pre = PreCommands() + assert pre.has_workload + assert pre.readonly_dotnet + pre.install_workload("wasm-tools") + pre.uninstall_workload("wasm-tools") + + monkeypatch.setattr(sys, "argv", shlex.split(item["PostCommands"])[1:]) + post = PostCommands() + assert post.readonly_dotnet + post.uninstall_workload("wasm-tools") + + run.assert_not_called() + + +@pytest.mark.parametrize( + ("name", "property_argument"), + [ + ("SOD - Localized App - Publish", "/p:WasmNativeWorkload=false"), + ("SOD - Localized App - Publish - AOT", "/p:RunAOTCompilation=true"), + ], +) +def test_localized_publish_mode_is_forwarded( + blazor_items, scenario_commands, monkeypatch, name, property_argument +): + items, _, _ = blazor_items + PreCommands, _ = scenario_commands + item = next(item for item in items["HelixWorkItem"] if item["Identity"] == name) + command = item["Command"].split(" && ", 1)[0] + monkeypatch.setattr(sys, "argv", shlex.split(command)[1:]) + pre = PreCommands() + pre.project = Mock() + pre.execute() + + assert pre.project.publish.call_args.args[6] == [ + "/p:_TrimmerDumpDependencies=true", + "/warnaserror:NU1602,NU1604", + property_argument, + ] + + +def test_standalone_workload_install_is_unchanged(scenario_commands, monkeypatch): + PreCommands, _ = scenario_commands + monkeypatch.setattr(sys, "argv", ["pre.py", "publish"]) + run = Mock() + monkeypatch.setattr(subprocess, "run", run) + + PreCommands().install_workload("wasm-tools") + + run.assert_called_once_with( + ["dotnet", "workload", "install", "wasm-tools", "--skip-manifest-update"], + check=True, + ) + + +def test_readonly_sdk_requires_preinstalled_workload(scenario_commands, monkeypatch): + PreCommands, _ = scenario_commands + monkeypatch.setattr(sys, "argv", ["pre.py", "publish", "--readonly-dotnet"]) + run = Mock() + monkeypatch.setattr(subprocess, "run", run) + + with pytest.raises(Exception, match="has_workload=false"): + PreCommands().install_workload("wasm-tools") + + run.assert_not_called() From a2f922748755c375fa75ab49e25b512d06deeb09 Mon Sep 17 00:00:00 2001 From: Parker Bibus Date: Mon, 28 Sep 2026 13:34:58 -0700 Subject: [PATCH 17/17] Explain Localized Blazor compilation-mode overrides Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- eng/performance/blazor_scenarios.proj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/eng/performance/blazor_scenarios.proj b/eng/performance/blazor_scenarios.proj index 8d0e868dc27..03c2b5a06ee 100644 --- a/eng/performance/blazor_scenarios.proj +++ b/eng/performance/blazor_scenarios.proj @@ -87,6 +87,9 @@ $(PublishCommand) -f $(PerfLabTargetFrameworksEnvVar) && $(Python) test.py sod --scenario-name "%(Identity)" --dirs $(PizzaAppPubLocation) $(ScenarioArgs) + $(ScenariosDir)blazorlocalized