diff --git a/src/Cargo.UnitTests/CargoTest.cs b/src/Cargo.UnitTests/CargoTest.cs index 6d66141..668f7d9 100644 --- a/src/Cargo.UnitTests/CargoTest.cs +++ b/src/Cargo.UnitTests/CargoTest.cs @@ -190,6 +190,8 @@ public void ProjectsCanDependOnEachOtherProjects() [Theory] [InlineData("AutomaticallyUseReferenceAssemblyPackages", "true", "true")] [InlineData("AutomaticallyUseReferenceAssemblyPackages", null, "false")] + [InlineData("CargoProfile", "release-windows", "release-windows")] + [InlineData("CargoProfile", null, "")] [InlineData("DebugSymbols", "true", "false")] [InlineData("DebugSymbols", null, "false")] [InlineData("DebugType", "Full", "None")] @@ -217,6 +219,12 @@ public void ProjectsCanDependOnEachOtherProjects() [InlineData("SkipCopyFilesMarkedCopyLocal", "false", "false")] [InlineData("SkipCopyFilesMarkedCopyLocal", "true", "true")] [InlineData("SkipCopyFilesMarkedCopyLocal", null, "")] + [InlineData("SkipPublicRustUpInstall", "true", "true")] + [InlineData("SkipPublicRustUpInstall", "false", "false")] + [InlineData("SkipPublicRustUpInstall", null, "")] + [InlineData("MsRustupTargets", "aarch64-pc-windows-msvc", "aarch64-pc-windows-msvc")] + [InlineData("MsRustupTargets", "aarch64-pc-windows-msvc;x86_64-pc-windows-msvc", "aarch64-pc-windows-msvc;x86_64-pc-windows-msvc")] + [InlineData("MsRustupTargets", null, "")] public void PropertiesHaveExpectedValues(string propertyName, string value, string expectedValue) { ProjectCreator.Templates.CargoProject( diff --git a/src/Cargo/CargoTask.cs b/src/Cargo/CargoTask.cs index d5d7a8a..3309905 100644 --- a/src/Cargo/CargoTask.cs +++ b/src/Cargo/CargoTask.cs @@ -103,6 +103,29 @@ private enum ExitCode /// public string CargoOutputDir { get; set; } = string.Empty; + /// + /// Gets or sets an optional Cargo profile to pass to cargo as "--profile <value>" for MSRustup. + /// When set, this overrides the behavior of deriving the profile from Configuration. + /// + public string CargoProfile { get; set; } = string.Empty; + + /// + /// Gets or sets an optional override for whether to install the public Rust toolchain via rustup-init.exe. + /// + /// "" (default) - Skip the public install iff rust-toolchain.toml selects an "ms-" channel. + /// "true" - Always skip the public rustup-init download and install. + /// "false" - Always run the public rustup-init install, even when MSRustup is detected. + /// + /// + public string SkipPublicRustUpInstall { get; set; } = string.Empty; + + /// + /// Gets or sets an optional semicolon-separated list of additional target triples to install when running "msrustup toolchain install" + /// (e.g. "aarch64-pc-windows-msvc;x86_64-pc-windows-msvc"). Each value is passed to MSRustup as "--target <triple>". + /// Use this to enable cross-compilation. + /// + public string MsRustupTargets { get; set; } = string.Empty; + /// public override bool Execute() { @@ -178,7 +201,7 @@ private async Task ExecuteAsync() foreach (var registry in GetRegistries(Path.Combine(RepoRoot, _cargoConfigFilePath))) { - var registryName = registry.Key.Trim().ToUpper(); + var registryName = registry.Key.Trim().ToUpper().Replace('-', '_'); _cargoRegistries.Add(registryName); var tokenName = $"CARGO_REGISTRIES_{registryName}_TOKEN"; AddOrUpdateEnvVar(tokenName, $"Bearer {val}"); @@ -247,13 +270,7 @@ private async Task CargoRunCommandAsync(string command, string args) if (!string.IsNullOrEmpty(customCargo)) { - bool isDebugConfiguration = true; - if (!Configuration.Equals("debug", StringComparison.InvariantCultureIgnoreCase)) - { - isDebugConfiguration = false; - } - - return await ExecuteProcessAsync(GetCustomToolChainCargoBin() !, $"{command} {args} --offline {(isDebugConfiguration ? string.Empty : "--" + Configuration.ToLowerInvariant())} --config {Path.Combine(RepoRoot, _cargoConfigFilePath)}", ".", _envVars); + return await ExecuteProcessAsync(GetCustomToolChainCargoBin() !, $"{command} {args} --offline {GetMsRustupProfileArgument()} --config {Path.Combine(RepoRoot, _cargoConfigFilePath)}", ".", _envVars); } return ExitCode.Failed; @@ -262,16 +279,44 @@ private async Task CargoRunCommandAsync(string command, string args) return await ExecuteProcessAsync(_cargoPath, $"{command} {args}", ".", _envVars); } + /// + /// For MSRustup, determines the appropriate Cargo profile argument to pass based on the CargoProfile and Configuration properties. + /// + /// The Cargo profile argument string, if needed; else an empty string. + private string GetMsRustupProfileArgument() + { + // Explicit CargoProfile wins over the Configuration-derived value. + if (!string.IsNullOrEmpty(CargoProfile)) + { + return $"--profile {CargoProfile}"; + } + + // No flag for the default (Debug) profile. + if (string.IsNullOrEmpty(Configuration) || Configuration.Equals("debug", StringComparison.InvariantCultureIgnoreCase)) + { + return string.Empty; + } + + // Use the Configuration as a Cargo profile shorthand (e.g. "--release"). + return "--" + Configuration.ToLowerInvariant(); + } + private async Task DownloadAndInstallRust() { try { - bool downloadSuccess = await DownloadRustUpAsync(); + bool skipPublicRustUp = ShouldSkipPublicRustUp(); + if (skipPublicRustUp) + { + Log.LogMessage(MessageImportance.Normal, "Skipping public Rust toolchain install."); + } + + bool downloadSuccess = skipPublicRustUp || await DownloadRustUpAsync(); bool installSuccess = false; if (downloadSuccess) { - installSuccess = await InstallRust(); - if (installSuccess) + installSuccess = await InstallRust(skipPublicRustUp); + if (installSuccess && !skipPublicRustUp) { _shouldCleanRustPath = true; } @@ -543,16 +588,26 @@ private async Task DownloadRustUpAsync() return await VerifyInitHashAsync(); } - private async Task InstallRust() + private async Task InstallRust(bool skipPublicRustUp) { var rootToolchainPath = Path.Combine(StartupProj, _rustToolChainFileName); var useMsRustUp = File.Exists(rootToolchainPath) && IsMSToolChain(rootToolchainPath); var rustUpBinary = useMsRustUp ? _msRustUpBinary : _rustUpBinary; + bool msRustupBinaryExists = File.Exists(_msRustUpBinary); bool msRustupToolChainExists = useMsRustUp && !string.IsNullOrEmpty(GetCustomToolChainCargoBin()); bool cargoPathAndRustPathsExists = Directory.Exists(_cargoHome) && Directory.Exists(_rustUpHome); bool cargoBinaryExists = File.Exists(_cargoPath); - if ((msRustupToolChainExists && cargoPathAndRustPathsExists && useMsRustUp) || cargoPathAndRustPathsExists && cargoBinaryExists && !useMsRustUp) + // Early-return when everything we need is already installed. + if (skipPublicRustUp && useMsRustUp) + { + // MSRustup-only flow: only require MSRustup binary and the requested toolchain. + if (msRustupBinaryExists && msRustupToolChainExists) + { + return true; + } + } + else if ((msRustupToolChainExists && cargoPathAndRustPathsExists && useMsRustUp) || (cargoPathAndRustPathsExists && cargoBinaryExists && !useMsRustUp)) { return true; } @@ -592,7 +647,8 @@ private async Task InstallRust() } } - if ((!cargoBinaryExists && !useMsRustUp) || !cargoPathAndRustPathsExists) + // Public rustup-init step. Skipped when the caller asks us to skip the public toolchain entirely, or MSRustup-only is auto-detected. + if (!skipPublicRustUp && ((!cargoBinaryExists && !useMsRustUp) || !cargoPathAndRustPathsExists)) { Log.LogMessage(MessageImportance.Normal, "Installing Rust"); exitCode = await ExecuteProcessAsync(_rustUpInitBinary, "-y", ".", _envVars); @@ -605,33 +661,10 @@ private async Task InstallRust() Log.LogError("Rust failed to install successfully"); return false; } - - if (useMsRustUp) - { - string? workingDirPart = new DirectoryInfo(BuildEngine.ProjectFileOfTaskNode).Parent?.Parent?.FullName; - - if (Directory.Exists(workingDirPart)) - { - Log.LogMessage(MessageImportance.Normal, "Installing MSRustup"); - string distRootPath = Path.Combine(workingDirPart!, "content\\dist"); - var installationExitCode = await ExecuteProcessAsync("powershell.exe", $".\\msrustup.ps1 '{_msRustUpHome}'", distRootPath, _envVars); - if (installationExitCode == ExitCode.Succeeded) - { - Log.LogMessage(MessageImportance.Normal, "Installed MSRustup successfully"); - } - else - { - Log.LogError("MSRustup failed to installed successfully"); - return false; - } - } - } } if (useMsRustUp) { - Log.LogMessage(MessageImportance.Normal, "Installing custom toolchain"); - if (string.IsNullOrEmpty(_rustUpFile) || !File.Exists(_rustUpFile)) { Log.LogMessage($"MSRUSTUP_FILE environment variable is not set or the file does not exist. Assuming local build."); @@ -642,8 +675,40 @@ private async Task InstallRust() var val = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(File.ReadAllText(_rustUpFile))); AddOrUpdateEnvVar("MSRUSTUP_PAT", val); } + } + + // Install MSRustup itself (independent of the public rustup install) when needed. + if (useMsRustUp && !msRustupBinaryExists) + { + string? workingDirPart = new DirectoryInfo(BuildEngine.ProjectFileOfTaskNode).Parent?.Parent?.FullName; + + if (Directory.Exists(workingDirPart)) + { + Log.LogMessage(MessageImportance.Normal, "Installing MSRustup"); + string scriptPath = Path.Combine(workingDirPart!, "content\\dist", "msrustup.ps1"); + + // The MSRustup script installs the compiler and tools into the working directory. + // Ensure the desired install location exists, then run Powershell with that directory as the working directory. + Directory.CreateDirectory(_msRustUpHome); + + var installationExitCode = await ExecuteProcessAsync(fileName: "powershell.exe", args: $"-File \"{scriptPath}\"", workingDir: _msRustUpHome, envars: _envVars); + if (installationExitCode == ExitCode.Succeeded) + { + Log.LogMessage(MessageImportance.Normal, "Installed MSRustup successfully"); + } + else + { + Log.LogError("MSRustup failed to installed successfully"); + return false; + } + } + } - exitCodeLatest = await ExecuteProcessAsync(rustUpBinary, $"toolchain install {GetToolChainVersion()}", StartupProj, _envVars); + if (useMsRustUp) + { + Log.LogMessage(MessageImportance.Normal, "Installing custom toolchain"); + + exitCodeLatest = await ExecuteProcessAsync(rustUpBinary, $"toolchain install {GetToolChainVersion()}{GetMsRustupTargetArgs()}", StartupProj, _envVars); if (exitCodeLatest == ExitCode.Succeeded) { @@ -655,7 +720,7 @@ private async Task InstallRust() return false; } } - else + else if (!skipPublicRustUp) { exitCodeLatest = await ExecuteProcessAsync(rustUpBinary, "default stable", ".", _envVars); // ensure we have the latest stable version } @@ -663,6 +728,51 @@ private async Task InstallRust() return exitCode == 0 && exitCodeLatest == 0; } + /// + /// Determines whether to skip the public RustUp installation. + /// + /// Whether to skip the public RustUp installation. + private bool ShouldSkipPublicRustUp() + { + // Explicit override wins over auto-detection. + if (bool.TryParse(SkipPublicRustUpInstall, out bool explicitValue)) + { + return explicitValue; + } + + // Auto-detect: skip when MSRustup is detected from rust-toolchain.toml. + return _isMsRustUp; + } + + /// + /// Builds the target arguments to pass to "msrustup toolchain install" from the property. + /// + /// + /// Each target in the semicolon-separated list becomes its own "--target <triple>" argument in the returned string. + /// Returns the empty string when no targets are configured. + /// + private string GetMsRustupTargetArgs() + { + if (string.IsNullOrWhiteSpace(MsRustupTargets)) + { + return string.Empty; + } + + var sb = new System.Text.StringBuilder(); + foreach (var target in MsRustupTargets.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) + { + var trimmed = target.Trim(); + if (trimmed.Length == 0) + { + continue; + } + + sb.Append(" --target ").Append(trimmed); + } + + return sb.ToString(); + } + private async Task VerifyInitHashAsync() { using var sha256 = SHA256.Create(); diff --git a/src/Cargo/README.md b/src/Cargo/README.md index e598aec..aa9e3e9 100644 --- a/src/Cargo/README.md +++ b/src/Cargo/README.md @@ -78,8 +78,39 @@ msbuild /t:clearcargocache ### Using MSRustup (Microsoft internal use only) To enable use of MSRustup, you will need to have a rust-toolchain.toml at the root of your repo. The toml file should include a channel specifier that has "ms-" as a prefix, followed by the channel version. ```toml - [toolchain] - channel - ms- + channel = "ms-" ``` - \ No newline at end of file + +#### Optional MSRustup configuration properties + + The SDK exposes a handful of MSBuild properties for advanced scenarios. + +##### `CargoProfile` + +By default the SDK derives the Cargo profile from the MSBuild `Configuration`: `Debug` uses Cargo's default debug profile, and any other configuration is +passed as the `--` value (so `Release` becomes `--release`). + +Set `CargoProfile` to override this and pass `--profile ` to Cargo instead. This is useful when your `Cargo.toml` defines a custom profile +such as `release-windows`. + +##### `SkipPublicRustUpInstall` + +The `InstallCargo` step normally downloads `rustup-init.exe` from `static.rust-lang.org` and runs it before falling through to the MSRustup install path. + +The SDK auto-skips the public step when it detects an `ms-` channel in `rust-toolchain.toml`. You can also force the behavior explicitly via `SkipPublicRustUpInstall`: +- Unset/empty (default): Skip the public install only when an `ms-` channel is detected. +- `true`: Always skip the public rustup-init download and install. +- `false`: Always run the public rustup-init install, even when MSRustup is detected. + +##### `MsRustupTargets` + +A semicolon-separated list of target triples to install when running `msrustup toolchain install`. +Each value becomes a `--target ` argument. Use this to enable cross-compilation. + +```xml + + aarch64-pc-windows-msvc;x86_64-pc-windows-msvc + +``` + diff --git a/src/Cargo/dist/msrustup.ps1 b/src/Cargo/dist/msrustup.ps1 index 21501d7..13273ba 100644 --- a/src/Cargo/dist/msrustup.ps1 +++ b/src/Cargo/dist/msrustup.ps1 @@ -1,5 +1,5 @@ # Originally from https://aka.ms/install-msrustup.ps1 -# Version 5 +# Version 6 # This script is expected to be copied into any build system that needs to install the internal Rust toolchain, if # that system cannot use an ADO pipeline and the Rust installer pipeline task. # Updates to this script will be avoided if possible, but if it stops working in your environment, please check the above @@ -9,18 +9,8 @@ # Requires MSRUSTUP_ACCESS_TOKEN or MSRUSTUP_PAT environment variables to be set with a token. # See https://aka.ms/rust for more information. -param ( - [string]$destinationDirectory -) - $ErrorActionPreference = "Stop" - # Create directory if it doesn't exist - Write-Host $destinationDirectory - if (-Not (Test-Path $destinationDirectory)) { - New-Item -Path $destinationDirectory -ItemType Directory - } - Switch ([System.Environment]::OSVersion.Platform.ToString()) { "Win32NT" { $target_rest = 'pc-windows-msvc'; Break } "MacOSX" { $target_rest = 'apple-darwin'; Break } @@ -48,54 +38,34 @@ $package = "rust.msrustup-$target_arch-$target_rest" $feed = if (Test-Path env:MSRUSTUP_FEED_URL) { $env:MSRUSTUP_FEED_URL } else { - 'https://mscodehub.pkgs.visualstudio.com/Rust/_packaging/Rust%40Release/nuget/v3/index.json' + 'https://devdiv.pkgs.visualstudio.com/DevDiv/_packaging/Rust.Sdk%40Release/nuget/v3/index.json' } # Get authentication token $token = if (Test-Path env:MSRUSTUP_ACCESS_TOKEN) { "Bearer $env:MSRUSTUP_ACCESS_TOKEN" - } elseif (Test-Path env:MSRUSTUP_PAT) { "Basic $([System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(":$($env:MSRUSTUP_PAT)")))" -} elseif (Test-Path env:MSRUSTUP_FILE) { - $location = $env:MSRUSTUP_FILE - if (Test-Path $location) { - $contents = Get-Content $location -Raw - } - $fromBase64 = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($contents)) - "Basic $([System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(":$($fromBase64)")))" -} -elseif ((Get-Command "azureauth" -ErrorAction SilentlyContinue) -ne $null) { +} elseif ((Get-Command "azureauth" -ErrorAction SilentlyContinue) -ne $null) { azureauth ado token --output headervalue } else { - $version = '0.9.1' - $env:AZUREAUTH_VERSION = $version - $script = "${env:TEMP}\install.ps1" - $url = "https://raw.githubusercontent.com/AzureAD/microsoft-authentication-cli/$version/install/install.ps1" - [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - Invoke-WebRequest $url -OutFile $script; if ($?) { &$script | Out-Null }; if ($?) { rm $script } - - $path = "$env:LOCALAPPDATA\Programs\AzureAuth\$version\azureauth.exe" - & $path ado token --output headervalue | Out-String + Write-Error "MSRUSTUP_ACCESS_TOKEN or MSRUSTUP_PAT must be set or azureauth must be present." + exit 1 } $h = @{'Authorization' = "$token"} -try { - # Download latest NuGet package - $response = Invoke-RestMethod -Headers $h $feed - $base = ($response.resources | Where-Object { $_.'@type' -eq 'PackageBaseAddress/3.0.0' }).'@id' - $version = (Invoke-RestMethod -Headers $h "$base/$package/index.json").versions[0] - Invoke-WebRequest -Headers $h "${base}${package}/$version/$package.$version.nupkg" -OutFile 'msrustup.zip' -} catch { - Write-Error "Failed to download msrustup package. Please check your access token and feed URL." - exit 1 -} + +# Download latest NuGet package +$response = Invoke-RestMethod -Headers $h $feed +$base = ($response.resources | Where-Object { $_.'@type' -eq 'PackageBaseAddress/3.0.0' }).'@id' +$version = (Invoke-RestMethod -Headers $h "$base/$package/index.json").versions[0] +Invoke-WebRequest -Headers $h "${base}${package}/$version/$package.$version.nupkg" -OutFile 'msrustup.zip' try { # Extract archive Expand-Archive 'msrustup.zip' try { - Move-Item .\msrustup\tools\msrustup* $destinationDirectory + Move-Item .\msrustup\tools\msrustup* . } finally { Remove-Item -Recurse 'msrustup' diff --git a/src/Cargo/sdk/InstallCargo.proj b/src/Cargo/sdk/InstallCargo.proj index c7a40c8..b572fe3 100644 --- a/src/Cargo/sdk/InstallCargo.proj +++ b/src/Cargo/sdk/InstallCargo.proj @@ -3,6 +3,6 @@ - + \ No newline at end of file diff --git a/src/Cargo/sdk/Sdk.props b/src/Cargo/sdk/Sdk.props index 51ea2e6..a98b865 100644 --- a/src/Cargo/sdk/Sdk.props +++ b/src/Cargo/sdk/Sdk.props @@ -91,6 +91,25 @@ AzureAuth $(MSBuildProjectDirectory)\bin + + + + + + diff --git a/src/Cargo/sdk/Sdk.targets b/src/Cargo/sdk/Sdk.targets index e8fc3b7..b6de532 100644 --- a/src/Cargo/sdk/Sdk.targets +++ b/src/Cargo/sdk/Sdk.targets @@ -112,35 +112,35 @@ - + - + - + - + - + - + - +