diff --git a/src/Cli/dotnet/Commands/CliCommandStrings.resx b/src/Cli/dotnet/Commands/CliCommandStrings.resx index 5fb8a21e1ca5..82b2f559d0f4 100644 --- a/src/Cli/dotnet/Commands/CliCommandStrings.resx +++ b/src/Cli/dotnet/Commands/CliCommandStrings.resx @@ -2483,4 +2483,7 @@ To display a value, specify the corresponding command-line option without provid Zero tests ran - + + Recursively add projects' ReferencedProjects to solution + + \ No newline at end of file diff --git a/src/Cli/dotnet/Commands/Solution/Add/SolutionAddCommand.cs b/src/Cli/dotnet/Commands/Solution/Add/SolutionAddCommand.cs index b25ba4d85d2a..5cf0b3817fde 100644 --- a/src/Cli/dotnet/Commands/Solution/Add/SolutionAddCommand.cs +++ b/src/Cli/dotnet/Commands/Solution/Add/SolutionAddCommand.cs @@ -22,6 +22,7 @@ internal class SolutionAddCommand : CommandBase private readonly IReadOnlyCollection _projects; private readonly string? _solutionFolderPath; private string _solutionFileFullPath = string.Empty; + private bool _includeReferences; private static string GetSolutionFolderPathWithForwardSlashes(string path) { @@ -43,6 +44,7 @@ public SolutionAddCommand(ParseResult parseResult) : base(parseResult) _projects = (IReadOnlyCollection)(parseResult.GetValue(SolutionAddCommandParser.ProjectPathArgument) ?? []); _inRoot = parseResult.GetValue(SolutionAddCommandParser.InRootOption); _solutionFolderPath = parseResult.GetValue(SolutionAddCommandParser.SolutionFolderOption); + _includeReferences = parseResult.GetValue(SolutionAddCommandParser.IncludeReferencesOption); SolutionArgumentValidator.ParseAndValidateArguments(_fileOrDirectory, _projects, SolutionArgumentValidator.CommandType.Add, _inRoot, _solutionFolderPath); _solutionFileFullPath = SlnFileFactory.GetSolutionFileFullPath(_fileOrDirectory); } @@ -138,7 +140,7 @@ private async Task AddProjectsToSolutionAsync(IEnumerable projectPaths, await serializer.SaveAsync(_solutionFileFullPath, solution, cancellationToken); } - private void AddProject(SolutionModel solution, string fullProjectPath, ISolutionSerializer serializer = null) + private void AddProject(SolutionModel solution, string fullProjectPath, ISolutionSerializer serializer = null, bool showMessageOnDuplicate = true) { string solutionRelativeProjectPath = Path.GetRelativePath(Path.GetDirectoryName(_solutionFileFullPath), fullProjectPath); @@ -175,7 +177,10 @@ private void AddProject(SolutionModel solution, string fullProjectPath, ISolutio } catch (SolutionArgumentException ex) when (ex.Type == SolutionErrorType.DuplicateProjectName || solution.FindProject(solutionRelativeProjectPath) is not null) { - Reporter.Output.WriteLine(CliStrings.SolutionAlreadyContainsProject, _solutionFileFullPath, solutionRelativeProjectPath); + if (showMessageOnDuplicate) + { + Reporter.Output.WriteLine(CliStrings.SolutionAlreadyContainsProject, _solutionFileFullPath, solutionRelativeProjectPath); + } return; } @@ -205,5 +210,17 @@ private void AddProject(SolutionModel solution, string fullProjectPath, ISolutio } Reporter.Output.WriteLine(CliStrings.ProjectAddedToTheSolution, solutionRelativeProjectPath); + + // Get referencedprojects from the project instance + var referencedProjectsFullPaths = projectInstance.GetItems("ProjectReference") + .Select(item => Path.GetFullPath(item.EvaluatedInclude, Path.GetDirectoryName(fullProjectPath))); + + if (_includeReferences) + { + foreach (var referencedProjectFullPath in referencedProjectsFullPaths) + { + AddProject(solution, referencedProjectFullPath, serializer, showMessageOnDuplicate: false); + } + } } } diff --git a/src/Cli/dotnet/Commands/Solution/Add/SolutionAddCommandParser.cs b/src/Cli/dotnet/Commands/Solution/Add/SolutionAddCommandParser.cs index afd116050f83..4925520938d4 100644 --- a/src/Cli/dotnet/Commands/Solution/Add/SolutionAddCommandParser.cs +++ b/src/Cli/dotnet/Commands/Solution/Add/SolutionAddCommandParser.cs @@ -4,6 +4,7 @@ #nullable disable using System.CommandLine; +using System.CommandLine.Parsing; namespace Microsoft.DotNet.Cli.Commands.Solution.Add; @@ -26,6 +27,12 @@ public static class SolutionAddCommandParser Description = CliCommandStrings.AddProjectSolutionFolderArgumentDescription }; + public static readonly Option IncludeReferencesOption = new("--include-references") + { + Description = CliCommandStrings.SolutionAddReferencedProjectsOptionDescription, + DefaultValueFactory = (_) => true, + }; + private static readonly Command Command = ConstructCommand(); public static Command GetCommand() @@ -40,6 +47,7 @@ private static Command ConstructCommand() command.Arguments.Add(ProjectPathArgument); command.Options.Add(InRootOption); command.Options.Add(SolutionFolderOption); + command.Options.Add(IncludeReferencesOption); command.SetAction((parseResult) => new SolutionAddCommand(parseResult).Execute()); diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf index c30a1ee4f568..905e767f24ab 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf @@ -2721,6 +2721,11 @@ Your project targets multiple frameworks. Specify which framework to run using ' .slnx file {0} generated. + + Recursively add projects' ReferencedProjects to solution + Recursively add projects' ReferencedProjects to solution + + .NET modify solution file command .NET modify solution file command diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf index 8fefe9ecb9f9..3896c64b9e61 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf @@ -2721,6 +2721,11 @@ Your project targets multiple frameworks. Specify which framework to run using ' .slnx file {0} generated. + + Recursively add projects' ReferencedProjects to solution + Recursively add projects' ReferencedProjects to solution + + .NET modify solution file command .NET modify solution file command diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf index d58d43020211..20afd4520f23 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf @@ -2721,6 +2721,11 @@ Your project targets multiple frameworks. Specify which framework to run using ' .slnx file {0} generated. + + Recursively add projects' ReferencedProjects to solution + Recursively add projects' ReferencedProjects to solution + + .NET modify solution file command .NET modify solution file command diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf index 59225c3273f9..71ff36a7e759 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf @@ -2721,6 +2721,11 @@ Your project targets multiple frameworks. Specify which framework to run using ' .slnx file {0} generated. + + Recursively add projects' ReferencedProjects to solution + Recursively add projects' ReferencedProjects to solution + + .NET modify solution file command .NET modify solution file command diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf index 7a64459070aa..3a29e67f41db 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf @@ -2721,6 +2721,11 @@ Your project targets multiple frameworks. Specify which framework to run using ' .slnx file {0} generated. + + Recursively add projects' ReferencedProjects to solution + Recursively add projects' ReferencedProjects to solution + + .NET modify solution file command .NET modify solution file command diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf index c3bb747280ee..5c1579261b1a 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf @@ -2721,6 +2721,11 @@ Your project targets multiple frameworks. Specify which framework to run using ' .slnx file {0} generated. + + Recursively add projects' ReferencedProjects to solution + Recursively add projects' ReferencedProjects to solution + + .NET modify solution file command .NET modify solution file command diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf index 07fbe8c1f4c1..2b3218faac44 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf @@ -2721,6 +2721,11 @@ Your project targets multiple frameworks. Specify which framework to run using ' .slnx file {0} generated. + + Recursively add projects' ReferencedProjects to solution + Recursively add projects' ReferencedProjects to solution + + .NET modify solution file command .NET modify solution file command diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf index 9f983fdf516e..9a75d810dd44 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf @@ -2721,6 +2721,11 @@ Your project targets multiple frameworks. Specify which framework to run using ' .slnx file {0} generated. + + Recursively add projects' ReferencedProjects to solution + Recursively add projects' ReferencedProjects to solution + + .NET modify solution file command .NET modify solution file command diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf index a3a94cf6a8b0..72a2415040df 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf @@ -2721,6 +2721,11 @@ Your project targets multiple frameworks. Specify which framework to run using ' .slnx file {0} generated. + + Recursively add projects' ReferencedProjects to solution + Recursively add projects' ReferencedProjects to solution + + .NET modify solution file command .NET modify solution file command diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf index 0f760888b545..04df2e5813ca 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf @@ -2721,6 +2721,11 @@ Your project targets multiple frameworks. Specify which framework to run using ' .slnx file {0} generated. + + Recursively add projects' ReferencedProjects to solution + Recursively add projects' ReferencedProjects to solution + + .NET modify solution file command .NET modify solution file command diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf index 732e31dbf8cf..892aecbdce6b 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf @@ -2721,6 +2721,11 @@ Your project targets multiple frameworks. Specify which framework to run using ' .slnx file {0} generated. + + Recursively add projects' ReferencedProjects to solution + Recursively add projects' ReferencedProjects to solution + + .NET modify solution file command .NET modify solution file command diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf index 862bfeed422e..08f128a0eeab 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf @@ -2721,6 +2721,11 @@ Your project targets multiple frameworks. Specify which framework to run using ' .slnx file {0} generated. + + Recursively add projects' ReferencedProjects to solution + Recursively add projects' ReferencedProjects to solution + + .NET modify solution file command .NET modify solution file command diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf index d0dd5815d7d2..61319bc77d40 100644 --- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf +++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf @@ -2721,6 +2721,11 @@ Your project targets multiple frameworks. Specify which framework to run using ' .slnx file {0} generated. + + Recursively add projects' ReferencedProjects to solution + Recursively add projects' ReferencedProjects to solution + + .NET modify solution file command .NET modify solution file command diff --git a/test/TestAssets/TestProjects/SlnFileWithReferencedProjects/A/A.csproj b/test/TestAssets/TestProjects/SlnFileWithReferencedProjects/A/A.csproj new file mode 100644 index 000000000000..6b3ec4712e96 --- /dev/null +++ b/test/TestAssets/TestProjects/SlnFileWithReferencedProjects/A/A.csproj @@ -0,0 +1,12 @@ + + + + Exe + net10.0 + enable + enable + + + + + diff --git a/test/TestAssets/TestProjects/SlnFileWithReferencedProjects/App.sln b/test/TestAssets/TestProjects/SlnFileWithReferencedProjects/App.sln new file mode 100644 index 000000000000..58ea56664421 --- /dev/null +++ b/test/TestAssets/TestProjects/SlnFileWithReferencedProjects/App.sln @@ -0,0 +1,14 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/test/TestAssets/TestProjects/SlnFileWithReferencedProjects/App.slnx b/test/TestAssets/TestProjects/SlnFileWithReferencedProjects/App.slnx new file mode 100644 index 000000000000..ba788ff0d17d --- /dev/null +++ b/test/TestAssets/TestProjects/SlnFileWithReferencedProjects/App.slnx @@ -0,0 +1,2 @@ + + diff --git a/test/TestAssets/TestProjects/SlnFileWithReferencedProjects/B/B.csproj b/test/TestAssets/TestProjects/SlnFileWithReferencedProjects/B/B.csproj new file mode 100644 index 000000000000..ed9781c223ab --- /dev/null +++ b/test/TestAssets/TestProjects/SlnFileWithReferencedProjects/B/B.csproj @@ -0,0 +1,10 @@ + + + + Exe + net10.0 + enable + enable + + + diff --git a/test/dotnet.Tests/CommandTests/Solution/Add/GivenDotnetSlnAdd.cs b/test/dotnet.Tests/CommandTests/Solution/Add/GivenDotnetSlnAdd.cs index 9f9c69a6a51b..97d8f36769da 100644 --- a/test/dotnet.Tests/CommandTests/Solution/Add/GivenDotnetSlnAdd.cs +++ b/test/dotnet.Tests/CommandTests/Solution/Add/GivenDotnetSlnAdd.cs @@ -8,6 +8,7 @@ using Microsoft.VisualStudio.SolutionPersistence; using Microsoft.VisualStudio.SolutionPersistence.Model; using Microsoft.DotNet.Cli.Commands; +using System.Threading.Tasks; namespace Microsoft.DotNet.Cli.Sln.Add.Tests { @@ -36,6 +37,7 @@ dotnet solution add [...] [options] Options: --in-root Place project in root of the solution, rather than creating a solution folder. -s, --solution-folder The destination solution folder path to add the projects to. + --include-references Recursively add projects' ReferencedProjects to solution [default: True] -?, -h, --help Show command line help"; public GivenDotnetSlnAdd(ITestOutputHelper log) : base(log) @@ -1154,6 +1156,40 @@ public async Task WhenAddingProjectOutsideDirectoryItShouldNotAddSolutionFolders solution.SolutionFolders.Count.Should().Be(0); } + [Theory] + [InlineData("sln", ".sln", "--include-references=true")] + [InlineData("solution", ".sln", "--include-references=true")] + [InlineData("sln", ".slnx", "--include-references=true")] + [InlineData("solution", ".slnx", "--include-references=true")] + [InlineData("sln", ".sln", "--include-references=false")] + [InlineData("solution", ".sln", "--include-references=false")] + [InlineData("sln", ".slnx", "--include-references=false")] + [InlineData("solution", ".slnx", "--include-references=false")] + public async Task WhenSolutionIsPassedAProjectWithReferenceItAddsOtherProjectUnlessSpecified(string solutionCommand, string solutionExtension, string option) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("SlnFileWithReferencedProjects", identifier: $"GivenDotnetSlnAdd-{solutionCommand}") + .WithSource() + .Path; + var projectToAdd = Path.Combine("A", "A.csproj"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(Path.Join(projectDirectory)) + .Execute(solutionCommand, $"App{solutionExtension}", "add", projectToAdd, option); + cmd.Should().Pass(); + // Should have two projects + ISolutionSerializer serializer = SolutionSerializers.GetSerializerByMoniker(Path.Join(projectDirectory, $"App{solutionExtension}")); + SolutionModel solution = await serializer.OpenAsync(Path.Join(projectDirectory, $"App{solutionExtension}"), CancellationToken.None); + + if (option.Equals("--include-references=false")) // Option is true by default + { + solution.SolutionProjects.Count.Should().Be(1); + } + else + { + solution.SolutionProjects.Count.Should().Be(2); + } + } + private string GetExpectedSlnContents( string slnPath, string slnTemplateName, @@ -1237,6 +1273,8 @@ public void WhenSolutionIsPassedAsProjectWithSolutionFolderItPrintsSuggestionAnd { VerifySuggestionAndUsage(solutionCommand, "--solution-folder", solutionExtension); } + + private void VerifySuggestionAndUsage(string solutionCommand, string arguments, string solutionExtension) { var projectDirectory = _testAssetsManager 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 f3989e9b1bc8..6e76bbce5854 100644 --- a/test/dotnet.Tests/CompletionTests/snapshots/bash/DotnetCliSnapshotTests.VerifyCompletions.verified.sh +++ b/test/dotnet.Tests/CompletionTests/snapshots/bash/DotnetCliSnapshotTests.VerifyCompletions.verified.sh @@ -1421,7 +1421,7 @@ _testhost_solution_add() { prev="${COMP_WORDS[COMP_CWORD-1]}" COMPREPLY=() - opts="--in-root --solution-folder --help" + opts="--in-root --solution-folder --include-references --help" if [[ $COMP_CWORD == "$1" ]]; then COMPREPLY=( $(compgen -W "$opts" -- "$cur") ) @@ -1433,6 +1433,10 @@ _testhost_solution_add() { COMPREPLY=( $(compgen -W "False True" -- "$cur") ) return ;; + --include-references) + COMPREPLY=( $(compgen -W "False True" -- "$cur") ) + return + ;; esac 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 41d7d01919e3..cbac51733cdf 100644 --- a/test/dotnet.Tests/CompletionTests/snapshots/pwsh/DotnetCliSnapshotTests.VerifyCompletions.verified.ps1 +++ b/test/dotnet.Tests/CompletionTests/snapshots/pwsh/DotnetCliSnapshotTests.VerifyCompletions.verified.ps1 @@ -840,6 +840,7 @@ Register-ArgumentCompleter -Native -CommandName 'testhost' -ScriptBlock { [CompletionResult]::new('--in-root', '--in-root', [CompletionResultType]::ParameterName, "Place project in root of the solution, rather than creating a solution folder.") [CompletionResult]::new('--solution-folder', '--solution-folder', [CompletionResultType]::ParameterName, "The destination solution folder path to add the projects to.") [CompletionResult]::new('--solution-folder', '-s', [CompletionResultType]::ParameterName, "The destination solution folder path to add the projects to.") + [CompletionResult]::new('--include-references', '--include-references', [CompletionResultType]::ParameterName, "Recursively add projects`' ReferencedProjects to solution") [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 758af444236c..109551b4e1a4 100644 --- a/test/dotnet.Tests/CompletionTests/snapshots/zsh/DotnetCliSnapshotTests.VerifyCompletions.verified.zsh +++ b/test/dotnet.Tests/CompletionTests/snapshots/zsh/DotnetCliSnapshotTests.VerifyCompletions.verified.zsh @@ -888,6 +888,7 @@ _testhost() { '--in-root=[Place project in root of the solution, rather than creating a solution folder.]: :((False\:"False" True\:"True" ))' \ '--solution-folder=[The destination solution folder path to add the projects to.]: : ' \ '-s=[The destination solution folder path to add the projects to.]: : ' \ + '--include-references=[Recursively add projects'\'' ReferencedProjects to solution]: :((False\:"False" True\:"True" ))' \ '--help[Show command line help.]' \ '-h[Show command line help.]' \ '*::PROJECT_PATH -- The paths to the projects to add to the solution.: ' \