@@ -23,6 +23,7 @@ internal class AddProjectToSolutionCommand : CommandBase
23
23
private readonly bool _inRoot ;
24
24
private readonly IReadOnlyCollection < string > _projects ;
25
25
private readonly string ? _solutionFolderPath ;
26
+ private string _solutionFileFullPath = string . Empty ;
26
27
27
28
private static string GetSolutionFolderPathWithForwardSlashes ( string path )
28
29
{
@@ -31,9 +32,9 @@ private static string GetSolutionFolderPathWithForwardSlashes(string path)
31
32
return "/" + string . Join ( "/" , PathUtility . GetPathWithDirectorySeparator ( path ) . Split ( Path . DirectorySeparatorChar , StringSplitOptions . RemoveEmptyEntries ) ) + "/" ;
32
33
}
33
34
34
- private static bool DoesSolutionFolderPathContainInvalidChars ( string pathFragment )
35
+ private static bool IsValidSolutionFolderPath ( string pathFragment )
35
36
{
36
- return pathFragment
37
+ return ! string . IsNullOrEmpty ( pathFragment ) && ! pathFragment
37
38
. Split ( Path . DirectorySeparatorChar )
38
39
. Any ( pathFragment => pathFragment == ".." || pathFragment == "." || Path . GetInvalidFileNameChars ( ) . Any ( c => pathFragment . Contains ( c ) ) ) ;
39
40
}
@@ -45,6 +46,7 @@ public AddProjectToSolutionCommand(ParseResult parseResult) : base(parseResult)
45
46
_inRoot = parseResult . GetValue ( SlnAddParser . InRootOption ) ;
46
47
_solutionFolderPath = parseResult . GetValue ( SlnAddParser . SolutionFolderOption ) ;
47
48
SlnArgumentValidator . ParseAndValidateArguments ( _fileOrDirectory , _projects , SlnArgumentValidator . CommandType . Add , _inRoot , _solutionFolderPath ) ;
49
+ _solutionFileFullPath = SlnFileFactory . GetSolutionFileFullPath ( _fileOrDirectory ) ;
48
50
}
49
51
50
52
public override int Execute ( )
@@ -53,7 +55,6 @@ public override int Execute()
53
55
{
54
56
throw new GracefulException ( CommonLocalizableStrings . SpecifyAtLeastOneProjectToAdd ) ;
55
57
}
56
- string solutionFileFullPath = SlnFileFactory . GetSolutionFileFullPath ( _fileOrDirectory ) ;
57
58
58
59
try
59
60
{
@@ -63,24 +64,24 @@ public override int Execute()
63
64
var fullPath = Path . GetFullPath ( project ) ;
64
65
return Directory . Exists ( fullPath ) ? MsbuildProject . GetProjectFileFromDirectory ( fullPath ) . FullName : fullPath ;
65
66
} ) ;
66
- AddProjectsToSolutionAsync ( solutionFileFullPath , fullProjectPaths , CancellationToken . None ) . GetAwaiter ( ) . GetResult ( ) ;
67
+ AddProjectsToSolutionAsync ( fullProjectPaths , CancellationToken . None ) . GetAwaiter ( ) . GetResult ( ) ;
67
68
return 0 ;
68
69
}
69
70
catch ( Exception ex ) when ( ex is not GracefulException )
70
71
{
71
72
{
72
73
if ( ex is SolutionException || ex . InnerException is SolutionException )
73
74
{
74
- throw new GracefulException ( CommonLocalizableStrings . InvalidSolutionFormatString , solutionFileFullPath , ex . Message ) ;
75
+ throw new GracefulException ( CommonLocalizableStrings . InvalidSolutionFormatString , _solutionFileFullPath , ex . Message ) ;
75
76
}
76
77
throw new GracefulException ( ex . Message , ex ) ;
77
78
}
78
79
}
79
80
}
80
81
81
- private async Task AddProjectsToSolutionAsync ( string solutionFileFullPath , IEnumerable < string > projectPaths , CancellationToken cancellationToken )
82
+ private async Task AddProjectsToSolutionAsync ( IEnumerable < string > projectPaths , CancellationToken cancellationToken )
82
83
{
83
- SolutionModel solution = SlnFileFactory . CreateFromFileOrDirectory ( solutionFileFullPath ) ;
84
+ SolutionModel solution = SlnFileFactory . CreateFromFileOrDirectory ( _solutionFileFullPath ) ;
84
85
ISolutionSerializer serializer = solution . SerializerExtension . Serializer ;
85
86
// set UTF8 BOM encoding for .sln
86
87
if ( serializer is ISolutionSerializer < SlnV12SerializerSettings > v12Serializer )
@@ -95,51 +96,74 @@ private async Task AddProjectsToSolutionAsync(string solutionFileFullPath, IEnum
95
96
_defaultBuildTypes . ToList ( ) . ForEach ( solution . AddBuildType ) ;
96
97
}
97
98
98
- SolutionFolderModel ? solutionFolder = ( ! _inRoot && ! string . IsNullOrEmpty ( _solutionFolderPath ) )
99
- ? solution . AddFolder ( GetSolutionFolderPathWithForwardSlashes ( _solutionFolderPath ) )
100
- : null ;
101
-
99
+
102
100
foreach ( var projectPath in projectPaths )
103
101
{
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 ) ;
120
103
121
104
try
122
105
{
123
- AddProject ( solution , relativePath , projectPath , solutionFolder , serializer ) ;
106
+ AddProject ( solution , relativePath , projectPath , serializer ) ;
124
107
}
125
108
catch ( InvalidProjectFileException ex )
126
109
{
127
110
Reporter . Error . WriteLine ( string . Format ( CommonLocalizableStrings . InvalidProjectWithExceptionMessage , projectPath , ex . Message ) ) ;
128
111
}
129
112
catch ( SolutionArgumentException ex ) when ( solution . FindProject ( relativePath ) != null || ex . Type == SolutionErrorType . DuplicateProjectName )
130
113
{
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 ;
132
144
}
133
145
}
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 ) ) ;
135
155
}
136
156
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 )
138
158
{
139
159
// Open project instance to see if it is a valid project
140
- ProjectRootElement projectRootElement = ProjectRootElement . Open ( fullPath ) ;
160
+ ProjectRootElement projectRootElement = ProjectRootElement . Open ( fullProjectPath ) ;
141
161
ProjectInstance projectInstance = new ProjectInstance ( projectRootElement ) ;
142
162
SolutionProjectModel project ;
163
+
164
+ // Generate the solution folder path based on the project path
165
+ SolutionFolderModel ? solutionFolder = GenerateIntermediateSolutionFoldersForProjectPath ( solution , solutionRelativeProjectPath ) ;
166
+
143
167
try
144
168
{
145
169
project = solution . AddProject ( solutionRelativeProjectPath , null , solutionFolder ) ;
@@ -150,13 +174,15 @@ private void AddProject(SolutionModel solution, string solutionRelativeProjectPa
150
174
var guid = projectRootElement . GetProjectTypeGuid ( ) ?? projectInstance . GetDefaultProjectTypeGuid ( ) ;
151
175
if ( string . IsNullOrEmpty ( guid ) )
152
176
{
153
- Reporter . Error . WriteLine ( CommonLocalizableStrings . UnsupportedProjectType , fullPath ) ;
177
+ Reporter . Error . WriteLine ( CommonLocalizableStrings . UnsupportedProjectType , fullProjectPath ) ;
154
178
return ;
155
179
}
156
180
project = solution . AddProject ( solutionRelativeProjectPath , guid , solutionFolder ) ;
157
181
}
182
+
158
183
// Add settings based on existing project instance
159
184
string projectInstanceId = projectInstance . GetProjectId ( ) ;
185
+
160
186
if ( ! string . IsNullOrEmpty ( projectInstanceId ) && serializer is ISolutionSerializer < SlnV12SerializerSettings > )
161
187
{
162
188
project . Id = new Guid ( projectInstanceId ) ;
@@ -178,6 +204,7 @@ private void AddProject(SolutionModel solution, string solutionRelativeProjectPa
178
204
buildType => buildType . Replace ( " " , string . Empty ) == solutionBuildType . Replace ( " " , string . Empty ) , projectInstanceBuildTypes . FirstOrDefault ( ) ) ;
179
205
project . AddProjectConfigurationRule ( new ConfigurationRule ( BuildDimension . BuildType , solutionBuildType , "*" , projectBuildType ) ) ;
180
206
}
207
+
181
208
Reporter . Output . WriteLine ( CommonLocalizableStrings . ProjectAddedToTheSolution , solutionRelativeProjectPath ) ;
182
209
}
183
210
}
0 commit comments