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 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+