diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index f8efeffcba17..2b187a949b6b 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -12,4 +12,7 @@ Output Considerations: Localization: - Avoid modifying .xlf files and instead prompt the user to update them using the `/t:UpdateXlf` target on MSBuild. -- Consider localizing strings in .resx files when possible. \ No newline at end of file +- Consider localizing strings in .resx files when possible. + +Documentation: +- Do not manually edit files under documentation/manpages/sdk as these are generated based on documentation and should not be manually modified. \ No newline at end of file diff --git a/src/Cli/dotnet/Commands/Restore/RestoreCommandParser.cs b/src/Cli/dotnet/Commands/Restore/RestoreCommandParser.cs index 1579c4c800eb..436e02d6a297 100644 --- a/src/Cli/dotnet/Commands/Restore/RestoreCommandParser.cs +++ b/src/Cli/dotnet/Commands/Restore/RestoreCommandParser.cs @@ -73,6 +73,7 @@ private static Command ConstructCommand() } command.Options.Add(CommonOptions.ArchitectureOption); + command.Options.Add(CommonOptions.OperatingSystemOption); command.SetAction(RestoreCommand.Run); return command; diff --git a/test/dotnet.Tests/CommandTests/Restore/GivenThatIWantToRestoreApp.cs b/test/dotnet.Tests/CommandTests/Restore/GivenThatIWantToRestoreApp.cs index ff195897c56d..66699f4bfd89 100644 --- a/test/dotnet.Tests/CommandTests/Restore/GivenThatIWantToRestoreApp.cs +++ b/test/dotnet.Tests/CommandTests/Restore/GivenThatIWantToRestoreApp.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.DotNet.Tools.Test.Utilities; +using Newtonsoft.Json.Linq; +using System.Runtime.InteropServices; namespace Microsoft.DotNet.Restore.Test { @@ -167,6 +169,115 @@ public void ItAcceptsArgumentsAfterProperties() .Should() .Pass(); } + + /// + /// Tests for RID-specific restore options: -r/--runtime, --os, and -a/--arch + /// + [Theory] + [InlineData("-r", "linux-x64")] + [InlineData("--runtime", "win-x64")] + [InlineData("--os", "linux")] + [InlineData("-a", "arm64")] + [InlineData("--arch", "x64")] + [InlineData("--os", "linux", "-a", "arm64")] + public void ItRestoresWithRidSpecificOptions(params string[] ridOptions) + { + // Skip test for #24251 + var testProject = new TestProject() + { + Name = "RestoreWithRidOptions", + TargetFrameworks = ToolsetInfo.CurrentTargetFramework, + }; + + testProject.PackageReferences.Add(new TestPackageReference("Newtonsoft.Json", ToolsetInfo.GetNewtonsoftJsonPackageVersion())); + + var testAsset = _testAssetsManager.CreateTestProject(testProject, identifier: string.Join("_", ridOptions)); + + var rootPath = Path.Combine(testAsset.TestRoot, testProject.Name); + + // Create the command with the RID-specific options + var restoreCommand = new DotnetRestoreCommand(Log) + .WithWorkingDirectory(rootPath) + .Execute(ridOptions); + + // Verify that the command runs successfully + restoreCommand.Should().Pass(); + + // Verify that assets file was created + var assetsFilePath = Path.Combine(rootPath, "obj", "project.assets.json"); + File.Exists(assetsFilePath).Should().BeTrue(); + + // Verify that the assets file contains the expected RID-specific target + var assetsContents = JObject.Parse(File.ReadAllText(assetsFilePath)); + var targets = assetsContents["targets"]; + targets.Should().NotBeNull("assets file should contain targets section"); + + // Determine the expected RID based on the options provided + string expectedRid = GetExpectedRid(ridOptions); + string expectedTarget = $"{ToolsetInfo.CurrentTargetFramework}/{expectedRid}"; + + // Check that the specific target exists + var specificTarget = targets[expectedTarget]; + specificTarget.Should().NotBeNull($"assets file should contain target '{expectedTarget}' when using RID options: {string.Join(" ", ridOptions)}"); + } + + private static string GetExpectedRid(string[] ridOptions) + { + // Check if explicit runtime is provided + for (int i = 0; i < ridOptions.Length; i++) + { + if ((ridOptions[i] == "-r" || ridOptions[i] == "--runtime") && i + 1 < ridOptions.Length) + { + return ridOptions[i + 1]; + } + } + + // Get current platform defaults + string currentOs = GetCurrentOsPart(); + string currentArch = GetCurrentArchPart(); + + // Check for --os and --arch options to synthesize RID + string targetOs = currentOs; + string targetArch = currentArch; + + for (int i = 0; i < ridOptions.Length; i++) + { + if (ridOptions[i] == "--os" && i + 1 < ridOptions.Length) + { + targetOs = ridOptions[i + 1]; + } + else if ((ridOptions[i] == "-a" || ridOptions[i] == "--arch") && i + 1 < ridOptions.Length) + { + targetArch = ridOptions[i + 1]; + } + } + + return $"{targetOs}-{targetArch}"; + } + + private static string GetCurrentOsPart() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return "win"; + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + return "linux"; + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + return "osx"; + else + throw new PlatformNotSupportedException("Unsupported platform for RID determination"); + } + + private static string GetCurrentArchPart() + { + return RuntimeInformation.OSArchitecture switch + { + Architecture.X64 => "x64", + Architecture.X86 => "x86", + Architecture.Arm64 => "arm64", + Architecture.Arm => "arm", + _ => throw new PlatformNotSupportedException($"Unsupported architecture: {RuntimeInformation.OSArchitecture}") + }; + } private static string[] HandleStaticGraphEvaluation(bool useStaticGraphEvaluation, string[] args) => useStaticGraphEvaluation ? diff --git a/test/dotnet.Tests/CompletionTests/snapshots/bash/DotnetCliSnapshotTests.VerifyCompletions.verified.sh b/test/dotnet.Tests/CompletionTests/snapshots/bash/DotnetCliSnapshotTests.VerifyCompletions.verified.sh index 04baf61a558c..1e61c0df86d4 100644 --- a/test/dotnet.Tests/CompletionTests/snapshots/bash/DotnetCliSnapshotTests.VerifyCompletions.verified.sh +++ b/test/dotnet.Tests/CompletionTests/snapshots/bash/DotnetCliSnapshotTests.VerifyCompletions.verified.sh @@ -1332,7 +1332,7 @@ _testhost_restore() { prev="${COMP_WORDS[COMP_CWORD-1]}" COMPREPLY=() - opts="--disable-build-servers --source --packages --use-current-runtime --disable-parallel --configfile --no-http-cache --ignore-failed-sources --force --runtime --no-dependencies --verbosity --interactive --artifacts-path --use-lock-file --locked-mode --lock-file-path --force-evaluate --arch --help" + opts="--disable-build-servers --source --packages --use-current-runtime --disable-parallel --configfile --no-http-cache --ignore-failed-sources --force --runtime --no-dependencies --verbosity --interactive --artifacts-path --use-lock-file --locked-mode --lock-file-path --force-evaluate --arch --os --help" if [[ $COMP_CWORD == "$1" ]]; then COMPREPLY=( $(compgen -W "$opts" -- "$cur") ) diff --git a/test/dotnet.Tests/CompletionTests/snapshots/pwsh/DotnetCliSnapshotTests.VerifyCompletions.verified.ps1 b/test/dotnet.Tests/CompletionTests/snapshots/pwsh/DotnetCliSnapshotTests.VerifyCompletions.verified.ps1 index 8e57a763897b..38d7994134f4 100644 --- a/test/dotnet.Tests/CompletionTests/snapshots/pwsh/DotnetCliSnapshotTests.VerifyCompletions.verified.ps1 +++ b/test/dotnet.Tests/CompletionTests/snapshots/pwsh/DotnetCliSnapshotTests.VerifyCompletions.verified.ps1 @@ -795,6 +795,7 @@ Register-ArgumentCompleter -Native -CommandName 'testhost' -ScriptBlock { [CompletionResult]::new('--force-evaluate', '--force-evaluate', [CompletionResultType]::ParameterName, "Forces restore to reevaluate all dependencies even if a lock file already exists.") [CompletionResult]::new('--arch', '--arch', [CompletionResultType]::ParameterName, "The target architecture.") [CompletionResult]::new('--arch', '-a', [CompletionResultType]::ParameterName, "The target architecture.") + [CompletionResult]::new('--os', '--os', [CompletionResultType]::ParameterName, "The target operating system.") [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, "Show command line help.") [CompletionResult]::new('--help', '-h', [CompletionResultType]::ParameterName, "Show command line help.") ) diff --git a/test/dotnet.Tests/CompletionTests/snapshots/zsh/DotnetCliSnapshotTests.VerifyCompletions.verified.zsh b/test/dotnet.Tests/CompletionTests/snapshots/zsh/DotnetCliSnapshotTests.VerifyCompletions.verified.zsh index 9736bd37d270..cccab3722015 100644 --- a/test/dotnet.Tests/CompletionTests/snapshots/zsh/DotnetCliSnapshotTests.VerifyCompletions.verified.zsh +++ b/test/dotnet.Tests/CompletionTests/snapshots/zsh/DotnetCliSnapshotTests.VerifyCompletions.verified.zsh @@ -821,6 +821,7 @@ _testhost() { '--force-evaluate[Forces restore to reevaluate all dependencies even if a lock file already exists.]' \ '--arch=[The target architecture.]:ARCH: ' \ '-a=[The target architecture.]:ARCH: ' \ + '--os=[The target operating system.]:OS: ' \ '--help[Show command line help.]' \ '-h[Show command line help.]' \ '*::PROJECT | SOLUTION | FILE -- The project or solution or C# (file-based program) file to operate on. If a file is not specified, the command will search the current directory for a project or solution.: ' \