Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sln-remove: Support for slnx #45160

Merged
merged 12 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Added edge cases
  • Loading branch information
edvilme committed Jan 6, 2025
commit 40e0a9fd26b82b3d8d27e8496a7c6de65a7f05a4
49 changes: 36 additions & 13 deletions src/Cli/dotnet/commands/dotnet-sln/remove/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Microsoft.Extensions.EnvironmentAbstractions;
using Microsoft.VisualStudio.SolutionPersistence;
using Microsoft.VisualStudio.SolutionPersistence.Model;
using Microsoft.VisualStudio.SolutionPersistence.Serializer.SlnV12;

namespace Microsoft.DotNet.Tools.Sln.Remove
{
Expand All @@ -36,19 +37,26 @@ public override int Execute()
throw new GracefulException(CommonLocalizableStrings.SpecifyAtLeastOneProjectToRemove);
}

IEnumerable<string> fullProjectPaths = _projects.Select(project =>
{
var fullPath = Path.GetFullPath(project);
return Directory.Exists(fullPath) ? MsbuildProject.GetProjectFileFromDirectory(fullPath).FullName : fullPath;
});

try
{
RemoveProjectsAsync(solutionFileFullPath, fullProjectPaths, CancellationToken.None).Wait();
var relativeProjectPaths = _projects.Select(p =>
{
var fullPath = Path.GetFullPath(p);
return Path.GetRelativePath(
Path.GetDirectoryName(solutionFileFullPath),
Directory.Exists(fullPath)
? MsbuildProject.GetProjectFileFromDirectory(fullPath).FullName
: fullPath);
});
RemoveProjectsAsync(solutionFileFullPath, relativeProjectPaths, CancellationToken.None).Wait();
return 0;
}
catch (Exception ex) when (ex is not GracefulException)
{
if (ex is SolutionException || ex.InnerException is SolutionException)
{
throw new GracefulException(CommonLocalizableStrings.InvalidSolutionFormatString, solutionFileFullPath, ex.Message);
}
throw new GracefulException(ex.Message, ex);
}
}
Expand All @@ -58,17 +66,32 @@ private async Task RemoveProjectsAsync(string solutionFileFullPath, IEnumerable<
ISolutionSerializer serializer = SlnCommandParser.GetSolutionSerializer(solutionFileFullPath);
SolutionModel solution = await serializer.OpenAsync(solutionFileFullPath, cancellationToken);

// set UTF8 BOM encoding for .sln
if (serializer is ISolutionSerializer<SlnV12SerializerSettings> v12Serializer)
{
solution.SerializerExtension = v12Serializer.CreateModelExtension(new()
{
Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: true)
});
}

foreach (var projectPath in projectPaths)
{
// Open project instance to see if it is a valid project
ProjectRootElement projectRootElement = ProjectRootElement.Open(projectPath);
ProjectInstance projectInstance = new ProjectInstance(projectRootElement);
string projectInstanceId = projectInstance.GetProjectId();
var project = solution.FindProject(projectPath);
if (project != null)
{
solution.RemoveProject(project);

SolutionProjectModel? projectModel = (SolutionProjectModel?) solution.FindItemById(new Guid(projectInstanceId));
solution.RemoveProject(projectModel);
Reporter.Output.WriteLine(CommonLocalizableStrings.ProjectRemovedFromTheSolution, projectPath);
}
else
{
Reporter.Output.WriteLine(CommonLocalizableStrings.ProjectNotFoundInTheSolution, projectPath);
}
}

// TODO: Remove empty solution folders

await serializer.SaveAsync(solutionFileFullPath, solution, cancellationToken);
}
}
Expand Down
47 changes: 27 additions & 20 deletions test/dotnet-sln.Tests/GivenDotnetSlnRemove.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ dotnet solution <SLN_FILE> remove [<PROJECT_PATH>...] [options]
{7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.ActiveCfg = Release|x86
{7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
";

Expand Down Expand Up @@ -314,27 +317,31 @@ public void WhenInvalidSolutionIsPassedItPrintsErrorAndUsage(string solutionComm
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, "InvalidSolution.sln", "remove", projectToRemove);
cmd.Should().Fail();
cmd.StdErr.Should().Be(string.Format(CommonLocalizableStrings.InvalidSolutionFormatString, "InvalidSolution.sln", LocalizableStrings.FileHeaderMissingError));
cmd.StdErr.Should().Match(string.Format(CommonLocalizableStrings.InvalidSolutionFormatString, "InvalidSolution.sln", "*"));
cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized("");
}

[Theory]
[InlineData("sln")]
[InlineData("solution")]
public void WhenInvalidSolutionIsFoundRemovePrintsErrorAndUsage(string solutionCommand)
[InlineData("sln", ".sln")]
[InlineData("solution", ".sln")]
public void WhenInvalidSolutionIsFoundRemovePrintsErrorAndUsage(string solutionCommand, string solutionExtension)
{
var projectDirectory = _testAssetsManager
var projectDirectoryRoot = _testAssetsManager
.CopyTestAsset("InvalidSolution", identifier: $"{solutionCommand}")
.WithSource()
.Path;

var projectDirectory = solutionExtension == ".sln"
? Path.Join(projectDirectoryRoot, "Sln")
: Path.Join(projectDirectoryRoot, "Slnx");

var solutionPath = Path.Combine(projectDirectory, "InvalidSolution.sln");
var projectToRemove = Path.Combine("Lib", "Lib.csproj");
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, "remove", projectToRemove);
cmd.Should().Fail();
cmd.StdErr.Should().Be(string.Format(CommonLocalizableStrings.InvalidSolutionFormatString, solutionPath, LocalizableStrings.FileHeaderMissingError));
cmd.StdErr.Should().Match(string.Format(CommonLocalizableStrings.InvalidSolutionFormatString, solutionPath, "*"));
cmd.StdOut.Should().BeVisuallyEquivalentToIfNotLocalized("");
}

Expand Down Expand Up @@ -408,7 +415,7 @@ public void WhenPassedAReferenceNotInSlnItPrintsStatus(string solutionCommand)
var contentBefore = File.ReadAllText(solutionPath);
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, "remove", "referenceDoesNotExistInSln.csproj");
.Execute(solutionCommand, "App.sln", "remove", "referenceDoesNotExistInSln.csproj");
cmd.Should().Pass();
cmd.StdOut.Should().Be(string.Format(CommonLocalizableStrings.ProjectNotFoundInTheSolution, "referenceDoesNotExistInSln.csproj"));
File.ReadAllText(solutionPath)
Expand All @@ -432,7 +439,7 @@ public void WhenPassedAReferenceItRemovesTheReferenceButNotOtherReferences(strin
var projectToRemove = Path.Combine("Lib", "Lib.csproj");
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, "remove", projectToRemove);
.Execute(solutionCommand, "App.sln", "remove", projectToRemove);
cmd.Should().Pass();
cmd.StdOut.Should().Be(string.Format(CommonLocalizableStrings.ProjectRemovedFromTheSolution, projectToRemove));

Expand All @@ -457,7 +464,7 @@ public void WhenSolutionItemsExistInFolderParentFoldersAreNotRemoved(string solu
var projectToRemove = Path.Combine("ConsoleApp1", "ConsoleApp1.csproj");
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, "remove", projectToRemove);
.Execute(solutionCommand, "App.sln", "remove", projectToRemove);
cmd.Should().Pass();
cmd.StdOut.Should().Be(string.Format(CommonLocalizableStrings.ProjectRemovedFromTheSolution, projectToRemove));

Expand All @@ -484,7 +491,7 @@ public void WhenDuplicateReferencesArePresentItRemovesThemAll(string solutionCom
var projectToRemove = Path.Combine("Lib", "Lib.csproj");
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, "remove", projectToRemove);
.Execute(solutionCommand, "App.sln", "remove", projectToRemove);
cmd.Should().Pass();

string outputText = string.Format(CommonLocalizableStrings.ProjectRemovedFromTheSolution, projectToRemove);
Expand Down Expand Up @@ -513,7 +520,7 @@ public void WhenPassedMultipleReferencesAndOneOfThemDoesNotExistItRemovesTheOneT
var projectToRemove = Path.Combine("Lib", "Lib.csproj");
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, "remove", "idontexist.csproj", projectToRemove, "idontexisteither.csproj");
.Execute(solutionCommand, "App.sln", "remove", "idontexist.csproj", projectToRemove, "idontexisteither.csproj");
cmd.Should().Pass();

string outputText = $@"{string.Format(CommonLocalizableStrings.ProjectNotFoundInTheSolution, "idontexist.csproj")}
Expand Down Expand Up @@ -544,7 +551,7 @@ public void WhenReferenceIsRemovedBuildConfigsAreAlsoRemoved(string solutionComm
var projectToRemove = Path.Combine("Lib", "Lib.csproj");
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, "remove", projectToRemove);
.Execute(solutionCommand, "App.sln", "remove", projectToRemove);
cmd.Should().Pass();

File.ReadAllText(solutionPath)
Expand All @@ -567,7 +574,7 @@ public void WhenDirectoryContainingProjectIsGivenProjectIsRemoved(string solutio

var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, "remove", "Lib");
.Execute(solutionCommand, "App.sln", "remove", "Lib");
cmd.Should().Pass();

File.ReadAllText(solutionPath)
Expand All @@ -587,7 +594,7 @@ public void WhenDirectoryContainsNoProjectsItCancelsWholeOperation(string soluti

var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, "remove", directoryToRemove);
.Execute(solutionCommand, "App.sln", "remove", directoryToRemove);
cmd.Should().Fail();
cmd.StdErr.Should().Be(
string.Format(
Expand All @@ -609,7 +616,7 @@ public void WhenDirectoryContainsMultipleProjectsItCancelsWholeOperation(string

var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, "remove", directoryToRemove);
.Execute(solutionCommand, "App.sln", "remove", directoryToRemove);
cmd.Should().Fail();
cmd.StdErr.Should().Be(
string.Format(
Expand All @@ -635,7 +642,7 @@ public void WhenReferenceIsRemovedSlnBuilds(string solutionCommand)
var projectToRemove = Path.Combine("Lib", "Lib.csproj");
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, "remove", projectToRemove);
.Execute(solutionCommand, "App.sln", "remove", projectToRemove);
cmd.Should().Pass();

new DotnetCommand(Log)
Expand Down Expand Up @@ -705,7 +712,7 @@ public void WhenFinalReferenceIsRemovedEmptySectionsAreRemoved(string solutionCo
var libPath = Path.Combine("Lib", "Lib.csproj");
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, "remove", libPath, appPath);
.Execute(solutionCommand, "App.sln", "remove", libPath, appPath);
cmd.Should().Pass();

var solutionContents = File.ReadAllText(solutionPath);
Expand All @@ -728,7 +735,7 @@ public void WhenNestedProjectIsRemovedItsSolutionFoldersAreRemoved(string soluti
var projectToRemove = Path.Combine("src", "NotLastProjInSrc", "NotLastProjInSrc.csproj");
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, "remove", projectToRemove);
.Execute(solutionCommand, "App.sln", "remove", projectToRemove);
cmd.Should().Pass();

File.ReadAllText(solutionPath)
Expand All @@ -750,7 +757,7 @@ public void WhenFinalNestedProjectIsRemovedSolutionFoldersAreRemoved(string solu
var projectToRemove = Path.Combine("src", "Lib", "Lib.csproj");
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, "remove", projectToRemove);
.Execute(solutionCommand, "App.sln", "remove", projectToRemove);
cmd.Should().Pass();

File.ReadAllText(solutionPath)
Expand All @@ -772,7 +779,7 @@ public void WhenProjectIsRemovedThenDependenciesOnProjectAreAlsoRemoved(string s
var projectToRemove = Path.Combine("Second", "Second.csproj");
var cmd = new DotnetCommand(Log)
.WithWorkingDirectory(projectDirectory)
.Execute(solutionCommand, "remove", projectToRemove);
.Execute(solutionCommand, "App.sln", "remove", projectToRemove);
cmd.Should().Pass();

File.ReadAllText(solutionPath)
Expand Down