Skip to content

Commit 5eb45b8

Browse files
committed
Refactor generate intermediate solution folders
1 parent 734c901 commit 5eb45b8

File tree

1 file changed

+60
-33
lines changed

1 file changed

+60
-33
lines changed

src/Cli/dotnet/commands/dotnet-sln/add/Program.cs

+60-33
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ internal class AddProjectToSolutionCommand : CommandBase
2323
private readonly bool _inRoot;
2424
private readonly IReadOnlyCollection<string> _projects;
2525
private readonly string? _solutionFolderPath;
26+
private string _solutionFileFullPath = string.Empty;
2627

2728
private static string GetSolutionFolderPathWithForwardSlashes(string path)
2829
{
@@ -31,9 +32,9 @@ private static string GetSolutionFolderPathWithForwardSlashes(string path)
3132
return "/" + string.Join("/", PathUtility.GetPathWithDirectorySeparator(path).Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries)) + "/";
3233
}
3334

34-
private static bool DoesSolutionFolderPathContainInvalidChars(string pathFragment)
35+
private static bool IsValidSolutionFolderPath(string pathFragment)
3536
{
36-
return pathFragment
37+
return !string.IsNullOrEmpty(pathFragment) && !pathFragment
3738
.Split(Path.DirectorySeparatorChar)
3839
.Any(pathFragment => pathFragment == ".." || pathFragment == "." || Path.GetInvalidFileNameChars().Any(c => pathFragment.Contains(c)));
3940
}
@@ -45,6 +46,7 @@ public AddProjectToSolutionCommand(ParseResult parseResult) : base(parseResult)
4546
_inRoot = parseResult.GetValue(SlnAddParser.InRootOption);
4647
_solutionFolderPath = parseResult.GetValue(SlnAddParser.SolutionFolderOption);
4748
SlnArgumentValidator.ParseAndValidateArguments(_fileOrDirectory, _projects, SlnArgumentValidator.CommandType.Add, _inRoot, _solutionFolderPath);
49+
_solutionFileFullPath = SlnFileFactory.GetSolutionFileFullPath(_fileOrDirectory);
4850
}
4951

5052
public override int Execute()
@@ -53,7 +55,6 @@ public override int Execute()
5355
{
5456
throw new GracefulException(CommonLocalizableStrings.SpecifyAtLeastOneProjectToAdd);
5557
}
56-
string solutionFileFullPath = SlnFileFactory.GetSolutionFileFullPath(_fileOrDirectory);
5758

5859
try
5960
{
@@ -63,24 +64,24 @@ public override int Execute()
6364
var fullPath = Path.GetFullPath(project);
6465
return Directory.Exists(fullPath) ? MsbuildProject.GetProjectFileFromDirectory(fullPath).FullName : fullPath;
6566
});
66-
AddProjectsToSolutionAsync(solutionFileFullPath, fullProjectPaths, CancellationToken.None).GetAwaiter().GetResult();
67+
AddProjectsToSolutionAsync(fullProjectPaths, CancellationToken.None).GetAwaiter().GetResult();
6768
return 0;
6869
}
6970
catch (Exception ex) when (ex is not GracefulException)
7071
{
7172
{
7273
if (ex is SolutionException || ex.InnerException is SolutionException)
7374
{
74-
throw new GracefulException(CommonLocalizableStrings.InvalidSolutionFormatString, solutionFileFullPath, ex.Message);
75+
throw new GracefulException(CommonLocalizableStrings.InvalidSolutionFormatString, _solutionFileFullPath, ex.Message);
7576
}
7677
throw new GracefulException(ex.Message, ex);
7778
}
7879
}
7980
}
8081

81-
private async Task AddProjectsToSolutionAsync(string solutionFileFullPath, IEnumerable<string> projectPaths, CancellationToken cancellationToken)
82+
private async Task AddProjectsToSolutionAsync(IEnumerable<string> projectPaths, CancellationToken cancellationToken)
8283
{
83-
SolutionModel solution = SlnFileFactory.CreateFromFileOrDirectory(solutionFileFullPath);
84+
SolutionModel solution = SlnFileFactory.CreateFromFileOrDirectory(_solutionFileFullPath);
8485
ISolutionSerializer serializer = solution.SerializerExtension.Serializer;
8586
// set UTF8 BOM encoding for .sln
8687
if (serializer is ISolutionSerializer<SlnV12SerializerSettings> v12Serializer)
@@ -95,51 +96,74 @@ private async Task AddProjectsToSolutionAsync(string solutionFileFullPath, IEnum
9596
_defaultBuildTypes.ToList().ForEach(solution.AddBuildType);
9697
}
9798

98-
SolutionFolderModel? solutionFolder = (!_inRoot && !string.IsNullOrEmpty(_solutionFolderPath))
99-
? solution.AddFolder(GetSolutionFolderPathWithForwardSlashes(_solutionFolderPath))
100-
: null;
101-
99+
102100
foreach (var projectPath in projectPaths)
103101
{
104-
string relativePath = Path.GetRelativePath(Path.GetDirectoryName(solutionFileFullPath), projectPath);
105-
106-
// Add fallback solution folder if relative path does not contain invalid characters
107-
string relativeSolutionFolder = DoesSolutionFolderPathContainInvalidChars(relativePath) ? string.Empty : Path.GetDirectoryName(relativePath);
108-
109-
if (!_inRoot && solutionFolder is null && !string.IsNullOrEmpty(relativeSolutionFolder))
110-
{
111-
if (relativeSolutionFolder.Split(Path.DirectorySeparatorChar).LastOrDefault() == Path.GetFileNameWithoutExtension(relativePath))
112-
{
113-
relativeSolutionFolder = Path.Combine(relativeSolutionFolder.Split(Path.DirectorySeparatorChar).SkipLast(1).ToArray());
114-
}
115-
if (!string.IsNullOrEmpty(relativeSolutionFolder))
116-
{
117-
solutionFolder = solution.AddFolder(GetSolutionFolderPathWithForwardSlashes(relativeSolutionFolder));
118-
}
119-
}
102+
string relativePath = Path.GetRelativePath(Path.GetDirectoryName(_solutionFileFullPath), projectPath);
120103

121104
try
122105
{
123-
AddProject(solution, relativePath, projectPath, solutionFolder, serializer);
106+
AddProject(solution, relativePath, projectPath, serializer);
124107
}
125108
catch (InvalidProjectFileException ex)
126109
{
127110
Reporter.Error.WriteLine(string.Format(CommonLocalizableStrings.InvalidProjectWithExceptionMessage, projectPath, ex.Message));
128111
}
129112
catch (SolutionArgumentException ex) when (solution.FindProject(relativePath) != null || ex.Type == SolutionErrorType.DuplicateProjectName)
130113
{
131-
Reporter.Output.WriteLine(CommonLocalizableStrings.SolutionAlreadyContainsProject, solutionFileFullPath, relativePath);
114+
Reporter.Output.WriteLine(CommonLocalizableStrings.SolutionAlreadyContainsProject, _solutionFileFullPath, relativePath);
115+
}
116+
}
117+
await serializer.SaveAsync(_solutionFileFullPath, solution, cancellationToken);
118+
}
119+
120+
private SolutionFolderModel? GenerateIntermediateSolutionFoldersForProjectPath(SolutionModel solution, string relativeProjectPath)
121+
{
122+
if (_inRoot)
123+
{
124+
return null;
125+
}
126+
127+
string relativeSolutionFolderPath = string.Empty;
128+
129+
if (string.IsNullOrEmpty(_solutionFolderPath))
130+
{
131+
// Generate the solution folder path based on the project path
132+
relativeSolutionFolderPath = Path.GetDirectoryName(relativeProjectPath);
133+
134+
// If the project is in a folder with the same name as the project, we need to go up one level
135+
if (relativeSolutionFolderPath.Split(Path.DirectorySeparatorChar).LastOrDefault() == Path.GetFileNameWithoutExtension(relativeProjectPath))
136+
{
137+
relativeSolutionFolderPath = Path.Combine([.. relativeSolutionFolderPath.Split(Path.DirectorySeparatorChar).SkipLast(1)]);
138+
}
139+
140+
// If the generated path is invalid, make it empty
141+
if (!IsValidSolutionFolderPath(relativeSolutionFolderPath))
142+
{
143+
relativeSolutionFolderPath = string.Empty;
132144
}
133145
}
134-
await serializer.SaveAsync(solutionFileFullPath, solution, cancellationToken);
146+
else
147+
{
148+
// Use the provided solution folder path
149+
relativeSolutionFolderPath = _solutionFolderPath;
150+
}
151+
152+
return string.IsNullOrEmpty(relativeSolutionFolderPath)
153+
? null
154+
: solution.AddFolder(GetSolutionFolderPathWithForwardSlashes(relativeSolutionFolderPath));
135155
}
136156

137-
private void AddProject(SolutionModel solution, string solutionRelativeProjectPath, string fullPath, SolutionFolderModel? solutionFolder, ISolutionSerializer serializer = null)
157+
private void AddProject(SolutionModel solution, string solutionRelativeProjectPath, string fullProjectPath, ISolutionSerializer serializer = null)
138158
{
139159
// Open project instance to see if it is a valid project
140-
ProjectRootElement projectRootElement = ProjectRootElement.Open(fullPath);
160+
ProjectRootElement projectRootElement = ProjectRootElement.Open(fullProjectPath);
141161
ProjectInstance projectInstance = new ProjectInstance(projectRootElement);
142162
SolutionProjectModel project;
163+
164+
// Generate the solution folder path based on the project path
165+
SolutionFolderModel? solutionFolder = GenerateIntermediateSolutionFoldersForProjectPath(solution, solutionRelativeProjectPath);
166+
143167
try
144168
{
145169
project = solution.AddProject(solutionRelativeProjectPath, null, solutionFolder);
@@ -150,13 +174,15 @@ private void AddProject(SolutionModel solution, string solutionRelativeProjectPa
150174
var guid = projectRootElement.GetProjectTypeGuid() ?? projectInstance.GetDefaultProjectTypeGuid();
151175
if (string.IsNullOrEmpty(guid))
152176
{
153-
Reporter.Error.WriteLine(CommonLocalizableStrings.UnsupportedProjectType, fullPath);
177+
Reporter.Error.WriteLine(CommonLocalizableStrings.UnsupportedProjectType, fullProjectPath);
154178
return;
155179
}
156180
project = solution.AddProject(solutionRelativeProjectPath, guid, solutionFolder);
157181
}
182+
158183
// Add settings based on existing project instance
159184
string projectInstanceId = projectInstance.GetProjectId();
185+
160186
if (!string.IsNullOrEmpty(projectInstanceId) && serializer is ISolutionSerializer<SlnV12SerializerSettings>)
161187
{
162188
project.Id = new Guid(projectInstanceId);
@@ -178,6 +204,7 @@ private void AddProject(SolutionModel solution, string solutionRelativeProjectPa
178204
buildType => buildType.Replace(" ", string.Empty) == solutionBuildType.Replace(" ", string.Empty), projectInstanceBuildTypes.FirstOrDefault());
179205
project.AddProjectConfigurationRule(new ConfigurationRule(BuildDimension.BuildType, solutionBuildType, "*", projectBuildType));
180206
}
207+
181208
Reporter.Output.WriteLine(CommonLocalizableStrings.ProjectAddedToTheSolution, solutionRelativeProjectPath);
182209
}
183210
}

0 commit comments

Comments
 (0)