diff --git a/README.md b/README.md index 215727e71..3ab183be4 100644 --- a/README.md +++ b/README.md @@ -242,7 +242,9 @@ We strive to deliver high-quality code and as such, we've put in place a number We pin the version of the tools we use in CI to ensure hermetic builds as much as possible. We routinely update the versions of everything to stay up to date using three scripts: -- `scripts/update_rust_toolchain.ps1` which updates the version of the Rust toolchain in the `constants.env` file. +- `scripts/update_rust_toolchain.ps1` which requires an internal `ox-sdk` checkout and updates the public + Rust toolchain versions after the internal release toolchain has been selected. + Example: `.\scripts\update_rust_toolchain.ps1 -InternalToolchainFile ..\ox-sdk\.pipelines\variables\publish.yml`. - `scripts/update_tool_versions.ps1` which updates the version of tools in the `constants.env` file. - `scripts/update_action_versions.ps1` which updates the version of GitHub actions in the various files in `.github/workflows`. diff --git a/constants.env b/constants.env index bd4b66948..31c65ff49 100644 --- a/constants.env +++ b/constants.env @@ -7,8 +7,9 @@ # used for testing, ensures the MSRV promise is kept; must match Cargo.toml [workspace.package].rust-version RUST_MSRV=1.93.1 -# used for static analysis & mutation testing -RUST_LATEST=1.96.1 +# used for static analysis & mutation testing; must match rust-toolchain.toml; its major/minor must match: +# https://o365exchange.visualstudio.com/O365%20Core/_git/ox-sdk?path=/.pipelines/variables/publish.yml +RUST_LATEST=1.95.0 # used for coverage and extended analysis; update on a regular basis RUST_NIGHTLY=nightly-2026-05-30 # used for external type exposure checks; update alongside updates to cargo-check-external-types diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 000000000..1b8b46e01 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,7 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +[toolchain] +# Must match RUST_LATEST in constants.env. Its major/minor must match MSRUSTUP_TOOLCHAIN in: +# https://o365exchange.visualstudio.com/O365%20Core/_git/ox-sdk?path=/.pipelines/variables/publish.yml +channel = "1.95.0" diff --git a/scripts/update_rust_toolchain.ps1 b/scripts/update_rust_toolchain.ps1 index d87a7f045..678338a6c 100644 --- a/scripts/update_rust_toolchain.ps1 +++ b/scripts/update_rust_toolchain.ps1 @@ -4,40 +4,55 @@ <# .SYNOPSIS - Updates Rust toolchain versions in constants.env. + Updates Rust toolchain versions in constants.env and rust-toolchain.toml. .DESCRIPTION This script automatically updates the Rust toolchain configuration to the latest versions: - - Queries the latest stable Rust version and updates RUST_LATEST in constants.env + - Reads the internal stable Rust minor, then updates RUST_LATEST and rust-toolchain.toml to its latest upstream patch - Calculates yesterday's nightly build date and updates RUST_NIGHTLY in constants.env - Fetches the latest cargo-check-external-types release to determine the tested nightly version for RUST_NIGHTLY_EXTERNAL_TYPES .PARAMETER ConstantsFile Path to the constants.env file. Defaults to ../constants.env relative to script location. +.PARAMETER ToolchainFile + Path to the rust-toolchain.toml file. Defaults to ../rust-toolchain.toml relative to script location. + +.PARAMETER InternalToolchainFile + Path to ox-sdk's .pipelines/variables/publish.yml file. Update its MSRUSTUP_TOOLCHAIN values first; + this script then aligns the public toolchain files to the latest upstream patch in that Rust minor. + .PARAMETER DryRun If specified, shows what would be updated without actually modifying the files. .EXAMPLE - .\update_rust_toolchain.ps1 + .\update_rust_toolchain.ps1 -InternalToolchainFile ..\ox-sdk\.pipelines\variables\publish.yml .EXAMPLE - .\update_rust_toolchain.ps1 -DryRun + .\update_rust_toolchain.ps1 -InternalToolchainFile ..\ox-sdk\.pipelines\variables\publish.yml -DryRun #> param( [string]$ConstantsFile = (Join-Path $PSScriptRoot ".." "constants.env"), + [string]$ToolchainFile = (Join-Path $PSScriptRoot ".." "rust-toolchain.toml"), + [Parameter(Mandatory = $true)] + [string]$InternalToolchainFile, [switch]$DryRun ) $ErrorActionPreference = "Stop" +$ToolchainChannelPattern = '(?m)^([ \t]*channel[ \t]*=[ \t]*)"([^"]*)"' function Get-LatestStableRustVersion { <# .SYNOPSIS - Retrieves the latest stable Rust version from the Rust GitHub releases. + Retrieves the latest stable Rust patch for a minor version from the Rust GitHub releases. #> - $apiUrl = "https://api.github.com/repos/rust-lang/rust/releases" + param( + [string]$MinorVersion + ) + + $apiUrl = "https://api.github.com/repos/rust-lang/rust/releases?per_page=100" try { $headers = @{ @@ -46,25 +61,20 @@ function Get-LatestStableRustVersion { } $response = Invoke-RestMethod -Uri $apiUrl -Headers $headers -ErrorAction Stop + $versionPattern = "^$([regex]::Escape($MinorVersion))\.\d+$" - # Find the latest stable release (not a beta or nightly) + # The internal production feed controls the usable minor; select its latest upstream patch. $stableRelease = $response | Where-Object { - $_.tag_name -match '^\d+\.\d+\.\d+$' -and -not $_.prerelease + $_.tag_name -match $versionPattern -and -not $_.prerelease } | Select-Object -First 1 if ($null -eq $stableRelease) { - throw "No stable release found" + throw "No stable release found for Rust $MinorVersion" } - # Keep the full x.y.z version (e.g., "1.92.0") so the pinned Cargo bugfix patch is preserved - if ($stableRelease.tag_name -match '^\d+\.\d+\.\d+$') { - return @{ - Success = $true - Version = $stableRelease.tag_name - } - } - else { - throw "Could not parse version from tag: $($stableRelease.tag_name)" + return @{ + Success = $true + Version = $stableRelease.tag_name } } catch { @@ -146,6 +156,69 @@ function Update-ConstantsEnv { return $content } +function Update-RustToolchain { + param( + [string]$FilePath, + [string]$Version + ) + + $content = Get-Content $FilePath -Raw + $replacement = '${1}"' + $Version + '"' + + return $content -replace $ToolchainChannelPattern, $replacement +} + +function Get-InternalToolchainMinorVersion { + param( + [string]$FilePath + ) + + $content = Get-Content $FilePath -Raw + $definition = [regex]::Match( + $content, + '(?ms)^[ \t]*-[ \t]+name:[ \t]*MSRUSTUP_TOOLCHAIN[ \t]*\r?\n(?.*?)(?=^[ \t]*-[ \t]+name:|\z)' + ) + + if (-not $definition.Success) { + throw "MSRUSTUP_TOOLCHAIN definition not found in '$FilePath'" + } + + $values = [regex]::Matches($definition.Groups["body"].Value, '(?m)^[ \t]*value:[ \t]*(\S+)') + + if ($values.Count -eq 0) { + throw "MSRUSTUP_TOOLCHAIN has no values in '$FilePath'" + } + + $versions = @() + + foreach ($value in $values) { + $channel = $value.Groups[1].Value + $channelMatch = [regex]::Match( + $channel, + '^(?[''"]?)ms-prod-(?\d+\.\d+)(?:@[a-z0-9_-]+)?\k$' + ) + + if (-not $channelMatch.Success) { + throw ( + "MSRUSTUP_TOOLCHAIN channel '$channel' in '$FilePath' has an invalid format. " + + "Choose a production short name such as 'ms-prod-1.95' by running " + + "'msrustup toolchain available'; append a required backend such as '@llvm' " + + "only after the short name, for example 'ms-prod-1.95@llvm'." + ) + } + + $versions += $channelMatch.Groups["minor"].Value + } + + $uniqueVersions = @($versions | Sort-Object -Unique) + + if ($uniqueVersions.Count -ne 1) { + throw "MSRUSTUP_TOOLCHAIN entries in '$FilePath' select different Rust versions: $($uniqueVersions -join ', ')" + } + + return $uniqueVersions[0] +} + # Main script execution Write-Host "Fetching latest Rust versions..." Write-Host "" @@ -156,6 +229,16 @@ if (-not (Test-Path $ConstantsFile)) { exit 1 } +if (-not (Test-Path $ToolchainFile)) { + Write-Host "Error: Rust toolchain file not found at '$ToolchainFile'" + exit 1 +} + +if (-not (Test-Path $InternalToolchainFile)) { + Write-Host "Error: Internal Rust toolchain file not found at '$InternalToolchainFile'" + exit 1 +} + # Get current versions $constantsContent = Get-Content $ConstantsFile @@ -163,11 +246,29 @@ $currentRustLatest = ($constantsContent | Select-String '^RUST_LATEST=(.+)$').Ma $currentRustNightly = ($constantsContent | Select-String '^RUST_NIGHTLY=(.+)$').Matches.Groups[1].Value $currentRustNightlyExternal = ($constantsContent | Select-String '^RUST_NIGHTLY_EXTERNAL_TYPES=(.+)$').Matches.Groups[1].Value +$toolchainContent = Get-Content $ToolchainFile -Raw +$toolchainMatch = [regex]::Match($toolchainContent, $ToolchainChannelPattern) + +if (-not $toolchainMatch.Success) { + Write-Host "Error: Rust toolchain channel not found in '$ToolchainFile'" + exit 1 +} + +$currentToolchain = $toolchainMatch.Groups[2].Value + +try { + $internalToolchainMinorVersion = Get-InternalToolchainMinorVersion -FilePath $InternalToolchainFile +} +catch { + Write-Host "Error reading internal Rust toolchain version: $($_.Exception.Message)" + exit 1 +} + # Fetch new versions -$stableResult = Get-LatestStableRustVersion +$stableResult = Get-LatestStableRustVersion -MinorVersion $internalToolchainMinorVersion if (-not $stableResult.Success) { - Write-Host "Error fetching latest stable Rust version: $($stableResult.Error)" + Write-Host "Error fetching the latest stable Rust $internalToolchainMinorVersion release: $($stableResult.Error)" exit 1 } @@ -176,13 +277,16 @@ $yesterdayNightly = Get-YesterdayNightlyVersion $externalTypesResult = Get-ExternalTypesTestedNightly Write-Host "Current versions:" +Write-Host " Internal Rust minor : $internalToolchainMinorVersion" Write-Host " RUST_LATEST : $currentRustLatest" +Write-Host " rust-toolchain.toml channel: $currentToolchain" Write-Host " RUST_NIGHTLY : $currentRustNightly" Write-Host " RUST_NIGHTLY_EXTERNAL_TYPES: $currentRustNightlyExternal" Write-Host "" Write-Host "New versions:" Write-Host " RUST_LATEST : $newStableVersion" +Write-Host " rust-toolchain.toml channel: $newStableVersion" Write-Host " RUST_NIGHTLY : $yesterdayNightly" if ($externalTypesResult.Success) { @@ -210,7 +314,9 @@ if ($externalTypesResult.Success -and $currentRustNightlyExternal -ne $externalT $constantsUpdates["RUST_NIGHTLY_EXTERNAL_TYPES"] = $externalTypesResult.Version } -if ($constantsUpdates.Count -eq 0) { +$toolchainNeedsUpdate = $currentToolchain -ne $newStableVersion + +if ($constantsUpdates.Count -eq 0 -and -not $toolchainNeedsUpdate) { exit 0 } @@ -223,3 +329,8 @@ if ($constantsUpdates.Count -gt 0) { $newConstantsContent = Update-ConstantsEnv -FilePath $ConstantsFile -Updates $constantsUpdates Set-Content -Path $ConstantsFile -Value $newConstantsContent -NoNewline } + +if ($toolchainNeedsUpdate) { + $newToolchainContent = Update-RustToolchain -FilePath $ToolchainFile -Version $newStableVersion + Set-Content -Path $ToolchainFile -Value $newToolchainContent -NoNewline +}