diff --git a/src/Cli/dotnet/commands/dotnet-restore/RestoreCommandParser.cs b/src/Cli/dotnet/commands/dotnet-restore/RestoreCommandParser.cs index 81f69185a799..145c9c7f9117 100644 --- a/src/Cli/dotnet/commands/dotnet-restore/RestoreCommandParser.cs +++ b/src/Cli/dotnet/commands/dotnet-restore/RestoreCommandParser.cs @@ -27,9 +27,10 @@ internal static class RestoreCommandParser private static IEnumerable FullRestoreOptions() => ImplicitRestoreOptions(true, true, true, true).Concat( - new CliOption[] { + [ CommonOptions.VerbosityOption, CommonOptions.InteractiveMsBuildForwardOption, + CommonOptions.ArtifactsPathOption, new ForwardedOption("--use-lock-file") { Description = LocalizableStrings.CmdUseLockFileOptionDescription, @@ -46,7 +47,8 @@ private static IEnumerable FullRestoreOptions() => new ForwardedOption("--force-evaluate") { Description = LocalizableStrings.CmdReevaluateOptionDescription - }.ForwardAs("-property:RestoreForceEvaluate=true") }); + }.ForwardAs("-property:RestoreForceEvaluate=true"), + ]); private static readonly CliCommand Command = ConstructCommand(); diff --git a/src/Cli/dotnet/commands/dotnet-run/RunCommandParser.cs b/src/Cli/dotnet/commands/dotnet-run/RunCommandParser.cs index d1ae11ed5aee..137599163bf9 100644 --- a/src/Cli/dotnet/commands/dotnet-run/RunCommandParser.cs +++ b/src/Cli/dotnet/commands/dotnet-run/RunCommandParser.cs @@ -83,6 +83,7 @@ private static CliCommand ConstructCommand() command.Options.Add(CommonOptions.ArchitectureOption); command.Options.Add(CommonOptions.OperatingSystemOption); command.Options.Add(CommonOptions.DisableBuildServersOption); + command.Options.Add(CommonOptions.ArtifactsPathOption); command.Arguments.Add(ApplicationArguments); diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/DependencyContextBuilder.cs b/src/Tasks/Microsoft.NET.Build.Tasks/DependencyContextBuilder.cs index d5bc50cfc4b3..463270e87c14 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/DependencyContextBuilder.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks/DependencyContextBuilder.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; using Microsoft.Build.Framework; using Microsoft.Extensions.DependencyModel; using NuGet.Packaging; @@ -253,7 +254,7 @@ public DependencyContextBuilder WithPackagesThatWereFiltered(Dictionary !l.ExcludeFromRuntime && l.Type != "runtimepack")) { - var runtimeLibrary = GetRuntimeLibrary(library); + var runtimeLibrary = GetRuntimeLibrary(library, userRuntimeAssemblies); if (runtimeLibrary != null) { runtimeLibraries.Add(runtimeLibrary); @@ -505,7 +506,7 @@ private IEnumerable GetRuntimePackLibraries() }); } - private RuntimeLibrary GetRuntimeLibrary(DependencyLibrary library) + private RuntimeLibrary GetRuntimeLibrary(DependencyLibrary library, string[] userRuntimeAssemblies) { GetCommonLibraryProperties(library, out string hash, @@ -527,7 +528,14 @@ private RuntimeLibrary GetRuntimeLibrary(DependencyLibrary library) if (library.Type == "project" && !(referenceProjectInfo is UnreferencedProjectInfo)) { - runtimeAssemblyGroups.Add(new RuntimeAssetGroup(string.Empty, referenceProjectInfo.OutputName)); + var fileName = Path.GetFileNameWithoutExtension(library.Path); + var assemblyPath = userRuntimeAssemblies?.FirstOrDefault(p => Path.GetFileNameWithoutExtension(p).Equals(fileName)); + runtimeAssemblyGroups.Add(new RuntimeAssetGroup(string.Empty, + [ new RuntimeFile( + referenceProjectInfo.OutputName, + library.Version.ToString(), + assemblyPath is null || !File.Exists(assemblyPath) ? string.Empty : FileVersionInfo.GetVersionInfo(assemblyPath).FileVersion) + ])); resourceAssemblies.AddRange(referenceProjectInfo.ResourceAssemblies .Select(r => new ResourceAssembly(r.RelativePath, r.Culture))); diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/GenerateDepsFile.cs b/src/Tasks/Microsoft.NET.Build.Tasks/GenerateDepsFile.cs index fc4cc2a2d429..816b94b2b580 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/GenerateDepsFile.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks/GenerateDepsFile.cs @@ -246,7 +246,7 @@ bool ShouldIncludeRuntimeAsset(ITaskItem item) .Concat(ResolvedRuntimeTargetsFiles.Select(f => new ResolvedFile(f, true))); builder = builder.WithResolvedNuGetFiles(resolvedNuGetFiles); - DependencyContext dependencyContext = builder.Build(); + DependencyContext dependencyContext = builder.Build(UserRuntimeAssemblies); var writer = new DependencyContextWriter(); using (var fileStream = File.Create(depsFilePath)) diff --git a/test/dotnet-MsiInstallation.Tests/Framework/VMTestBase.cs b/test/dotnet-MsiInstallation.Tests/Framework/VMTestBase.cs index d2cebfc0f54a..87b4b13a8577 100644 --- a/test/dotnet-MsiInstallation.Tests/Framework/VMTestBase.cs +++ b/test/dotnet-MsiInstallation.Tests/Framework/VMTestBase.cs @@ -168,9 +168,10 @@ protected CommandResult InstallWorkload(string workloadName) return result; } - protected WorkloadSet GetRollback() + protected WorkloadSet GetRollback(string directory = null) { var result = VM.CreateRunCommand("dotnet", "workload", "update", "--print-rollback") + .WithWorkingDirectory(directory) .WithIsReadOnly(true) .Execute(); diff --git a/test/dotnet-MsiInstallation.Tests/WorkloadSetTests.cs b/test/dotnet-MsiInstallation.Tests/WorkloadSetTests.cs index f3f8d60161e9..e7157a0b7da5 100644 --- a/test/dotnet-MsiInstallation.Tests/WorkloadSetTests.cs +++ b/test/dotnet-MsiInstallation.Tests/WorkloadSetTests.cs @@ -213,6 +213,51 @@ public void UpdateToWorkloadSetVersionWithManifestsNotAvailable() GetWorkloadVersion().Should().Be(workloadVersionBeforeUpdate); } + [Fact] + public void UpdateWorkloadSetViaGlobalJson() + { + InstallSdk(); + + var versionToUpdateTo = "8.0.300-preview.0.24217.2"; + var directory = "C:\\SdkTesting"; + + string originalVersion = GetWorkloadVersion(); + + var rollback = GetRollback(directory); + + VM.WriteFile("C:\\SdkTesting\\global.json", @$"{{""sdk"":{{""workloadVersion"":""{versionToUpdateTo}""}}}}").Execute().Should().Pass(); + + GetWorkloadVersion(directory).Should().Be(versionToUpdateTo); + + // The version should have changed but not yet the manifests. Since we expect both, getting the rollback should fail. + var result = VM.CreateRunCommand("dotnet", "workload", "update", "--print-rollback") + .WithWorkingDirectory(directory) + .WithIsReadOnly(true) + .Execute(); + + result.Should().Fail(); + result.StdErr.Should().Contain("FileNotFoundException"); + result.StdErr.Should().Contain(versionToUpdateTo); + + AddNuGetSource(@"C:\SdkTesting\workloadsets", directory); + + VM.CreateRunCommand("dotnet", "workload", "update").WithWorkingDirectory(directory).Execute().Should().Pass(); + + GetRollback(directory).Should().NotBe(rollback); + } + + string GetWorkloadVersion(string workingDirectory = null) + { + var result = VM.CreateRunCommand("dotnet", "workload", "--version") + .WithWorkingDirectory(workingDirectory) + .WithIsReadOnly(true) + .Execute(); + + result.Should().Pass(); + + return result.StdOut; + } + string GetUpdateMode() { var result = VM.CreateRunCommand("dotnet", "workload", "config", "--update-mode") @@ -223,5 +268,15 @@ string GetUpdateMode() return result.StdOut; } + + void AddNuGetSource(string source, string directory = null) + { + VM.CreateRunCommand("dotnet", "nuget", "add", "source", source) + .WithWorkingDirectory(directory) + .WithDescription($"Add {source} to NuGet.config") + .Execute() + .Should() + .Pass(); + } } } diff --git a/test/dotnet.Tests/ParserTests/BuildRelatedCommandParserTests.cs b/test/dotnet.Tests/ParserTests/BuildRelatedCommandParserTests.cs new file mode 100644 index 000000000000..b3a4042afaac --- /dev/null +++ b/test/dotnet.Tests/ParserTests/BuildRelatedCommandParserTests.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.CommandLine; +using Microsoft.DotNet.Cli; +using Microsoft.DotNet.Cli.CommandLineValidation; +using Microsoft.DotNet.Tools.Common; +using Parser = Microsoft.DotNet.Cli.Parser; + +namespace Microsoft.DotNet.Tests.ParserTests +{ + public class BuildRelatedCommandParserTests + { + + /// + /// These commands all implicitly use MSBuild under the covers and generally should expose + /// the same set of property- and behavior-impacting options. + /// + private static string[] BuildRelatedCommands = [ + "build", + "clean", + "pack", + "publish", + "restore", + "run", + "test" + ]; + + private static string[] OptionsToVerify = [ + "--artifacts-path" + ]; + + public static TheoryData BuildRelatedCommandsAndOptions() + { + var data = new TheoryData(); + foreach (var cmd in BuildRelatedCommands) + { + foreach (var opt in OptionsToVerify) + { + data.Add(cmd, opt); + } + } + return data; + } + + [MemberData(nameof(BuildRelatedCommandsAndOptions))] + [Theory] + public void Build(string command, string option) + { + var cliCommand = Parser.Instance.RootCommand.Children.OfType().FirstOrDefault(c => c.Name == command); + if (cliCommand is null) + { + throw new ArgumentException($"Command {command} not found in the dotnet CLI"); + } + var cliOption = cliCommand.Children.OfType().FirstOrDefault(o => o.Name == option || o.Aliases.Contains(option)); + if (cliOption is null) + { + throw new ArgumentException($"Option {option} not found in the {command} command"); + } + } + } +}