2 // SolutionParser.cs: Generates a project file from a solution file.
5 // Jonathan Chambers (joncham@gmail.com)
6 // Ankit Jain <jankit@novell.com>
7 // Lluis Sanchez Gual <lluis@novell.com>
9 // (C) 2009 Jonathan Chambers
10 // Copyright 2008, 2009 Novell, Inc (http://www.novell.com)
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 using System
.Collections
.Generic
;
35 using System
.Text
.RegularExpressions
;
37 using Microsoft
.Build
.BuildEngine
;
39 namespace Mono
.XBuild
.CommandLine
{
42 public string FileName
;
45 public ProjectInfo (string name
, string fileName
)
51 public Dictionary
<TargetInfo
, TargetInfo
> TargetMap
= new Dictionary
<TargetInfo
, TargetInfo
> ();
52 public Dictionary
<Guid
, ProjectInfo
> Dependencies
= new Dictionary
<Guid
, ProjectInfo
> ();
53 public Dictionary
<string, ProjectSection
> ProjectSections
= new Dictionary
<string, ProjectSection
> ();
54 public List
<string> AspNetConfigurations
= new List
<string> ();
57 class ProjectSection
{
59 public Dictionary
<string, string> Properties
= new Dictionary
<string, string> ();
61 public ProjectSection (string name
)
68 public string Configuration
;
69 public string Platform
;
72 public TargetInfo (string configuration
, string platform
)
73 : this (configuration
, platform
, false)
77 public TargetInfo (string configuration
, string platform
, bool build
)
79 Configuration
= configuration
;
86 internal delegate void RaiseWarningHandler (int errorNumber
, string message
);
88 class SolutionParser
{
89 static string[] buildTargets
= new string[] { "Build", "Clean", "Rebuild", "Publish" }
;
91 static string guidExpression
= "{[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}}";
93 static Regex slnVersionRegex
= new Regex (@"Microsoft Visual Studio Solution File, Format Version (\d?\d.\d\d)");
94 static Regex projectRegex
= new Regex ("Project\\(\"(" + guidExpression
+ ")\"\\) = \"(.*?)\", \"(.*?)\", \"(" + guidExpression
+ ")\"(\\s*?)((\\s*?)ProjectSection\\((.*?)\\) = (.*?)EndProjectSection(\\s*?))*(\\s*?)(EndProject)?", RegexOptions
.Singleline
);
95 static Regex projectDependenciesRegex
= new Regex ("ProjectSection\\((.*?)\\) = \\w*(.*?)EndProjectSection", RegexOptions
.Singleline
);
96 static Regex projectDependencyRegex
= new Regex ("\\s*(" + guidExpression
+ ") = (" + guidExpression
+ ")");
97 static Regex projectSectionPropertiesRegex
= new Regex ("\\s*(?<name>.*) = \"(?<value>.*)\"");
99 static Regex globalRegex
= new Regex ("Global(.*)EndGlobal", RegexOptions
.Singleline
);
100 static Regex globalSectionRegex
= new Regex ("GlobalSection\\((.*?)\\) = \\w*(.*?)EndGlobalSection", RegexOptions
.Singleline
);
102 static Regex solutionConfigurationRegex
= new Regex ("\\s*(.*?)\\|(.*?) = (.*?)\\|(.+)");
103 static Regex projectConfigurationActiveCfgRegex
= new Regex ("\\s*(" + guidExpression
+ ")\\.(.+?)\\|(.+?)\\.ActiveCfg = (.+?)\\|(.+)");
104 static Regex projectConfigurationBuildRegex
= new Regex ("\\s*(" + guidExpression
+ ")\\.(.*?)\\|(.*?)\\.Build\\.0 = (.*?)\\|(.+)");
106 static string solutionFolderGuid
= "{2150E333-8FDC-42A3-9474-1A3956D46DE8}";
107 static string vcprojGuid
= "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}";
108 static string websiteProjectGuid
= "{E24C65DC-7377-472B-9ABA-BC803B73C61A}";
110 RaiseWarningHandler RaiseWarning
;
112 public void ParseSolution (string file
, Project p
, RaiseWarningHandler RaiseWarning
)
114 this.RaiseWarning
= RaiseWarning
;
115 EmitBeforeImports (p
, file
);
117 AddGeneralSettings (file
, p
);
119 StreamReader reader
= new StreamReader (file
);
121 string slnVersion
= GetSlnFileVersion (reader
);
123 if (slnVersion
== "12.00")
125 p
.DefaultToolsVersion
= "14.0";
127 p
.DefaultToolsVersion
= "12.0";
129 p
.DefaultToolsVersion
= "4.0";
131 else if (slnVersion
== "11.00")
132 p
.DefaultToolsVersion
= "4.0";
133 else if (slnVersion
== "10.00")
134 p
.DefaultToolsVersion
= "3.5";
136 p
.DefaultToolsVersion
= "2.0";
138 string line
= reader
.ReadToEnd ();
139 line
= line
.Replace ("\r\n", "\n");
140 string solutionDir
= Path
.GetDirectoryName (file
);
142 List
<TargetInfo
> solutionTargets
= new List
<TargetInfo
> ();
143 Dictionary
<Guid
, ProjectInfo
> projectInfos
= new Dictionary
<Guid
, ProjectInfo
> ();
144 Dictionary
<Guid
, ProjectInfo
> websiteProjectInfos
= new Dictionary
<Guid
, ProjectInfo
> ();
145 List
<ProjectInfo
>[] infosByLevel
= null;
146 Dictionary
<Guid
, ProjectInfo
> unsupportedProjectInfos
= new Dictionary
<Guid
, ProjectInfo
> ();
148 Match m
= projectRegex
.Match (line
);
150 ProjectInfo projectInfo
= new ProjectInfo (m
.Groups
[2].Value
,
151 Path
.GetFullPath (Path
.Combine (solutionDir
,
152 m
.Groups
[3].Value
.Replace ('\\', Path
.DirectorySeparatorChar
))));
153 if (String
.Compare (m
.Groups
[1].Value
, solutionFolderGuid
,
154 StringComparison
.InvariantCultureIgnoreCase
) == 0) {
155 // Ignore solution folders
160 projectInfo
.Guid
= new Guid (m
.Groups
[4].Value
);
162 if (String
.Compare (m
.Groups
[1].Value
, vcprojGuid
,
163 StringComparison
.InvariantCultureIgnoreCase
) == 0) {
165 RaiseWarning (0, string.Format("Ignoring vcproj '{0}'.", projectInfo
.Name
));
167 unsupportedProjectInfos
[projectInfo
.Guid
] = projectInfo
;
172 if (String
.Compare (m
.Groups
[1].Value
, websiteProjectGuid
,
173 StringComparison
.InvariantCultureIgnoreCase
) == 0)
174 websiteProjectInfos
.Add (new Guid (m
.Groups
[4].Value
), projectInfo
);
176 projectInfos
.Add (projectInfo
.Guid
, projectInfo
);
178 Match projectSectionMatch
= projectDependenciesRegex
.Match (m
.Groups
[6].Value
);
179 while (projectSectionMatch
.Success
) {
180 string section_name
= projectSectionMatch
.Groups
[1].Value
;
181 if (String
.Compare (section_name
, "ProjectDependencies") == 0) {
182 Match projectDependencyMatch
= projectDependencyRegex
.Match (projectSectionMatch
.Value
);
183 while (projectDependencyMatch
.Success
) {
184 // we might not have projectInfo available right now, so
185 // set it to null, and fill it in later
186 projectInfo
.Dependencies
[new Guid (projectDependencyMatch
.Groups
[1].Value
)] = null;
187 projectDependencyMatch
= projectDependencyMatch
.NextMatch ();
190 ProjectSection section
= new ProjectSection (section_name
);
191 Match propertiesMatch
= projectSectionPropertiesRegex
.Match (
192 projectSectionMatch
.Groups
[2].Value
);
193 while (propertiesMatch
.Success
) {
194 section
.Properties
[propertiesMatch
.Groups
["name"].Value
] =
195 propertiesMatch
.Groups
["value"].Value
;
197 propertiesMatch
= propertiesMatch
.NextMatch ();
200 projectInfo
.ProjectSections
[section_name
] = section
;
202 projectSectionMatch
= projectSectionMatch
.NextMatch ();
207 foreach (ProjectInfo projectInfo
in projectInfos
.Values
) {
208 string filename
= projectInfo
.FileName
;
209 string projectDir
= Path
.GetDirectoryName (filename
);
211 if (!File
.Exists (filename
)) {
212 RaiseWarning (0, String
.Format ("Project file {0} referenced in the solution file, " +
213 "not found. Ignoring.", filename
));
217 Project currentProject
= p
.ParentEngine
.CreateNewProject ();
219 currentProject
.Load (filename
, ProjectLoadSettings
.IgnoreMissingImports
);
220 } catch (InvalidProjectFileException e
) {
221 RaiseWarning (0, e
.Message
);
225 foreach (BuildItem bi
in currentProject
.GetEvaluatedItemsByName ("ProjectReference")) {
226 ProjectInfo info
= null;
227 string projectReferenceGuid
= bi
.GetEvaluatedMetadata ("Project");
228 bool hasGuid
= !String
.IsNullOrEmpty (projectReferenceGuid
);
230 // try to resolve the ProjectReference by GUID
231 // and fallback to project filename
234 Guid guid
= new Guid (projectReferenceGuid
);
235 projectInfos
.TryGetValue (guid
, out info
);
236 if (info
== null && unsupportedProjectInfos
.TryGetValue (guid
, out info
)) {
237 RaiseWarning (0, String
.Format (
238 "{0}: ProjectReference '{1}' is of an unsupported type. Ignoring.",
239 filename
, bi
.Include
));
244 if (info
== null || !hasGuid
) {
245 // Project not found by guid or guid not available
246 // Try to find by project file
248 string fullpath
= Path
.GetFullPath (Path
.Combine (projectDir
, bi
.Include
.Replace ('\\', Path
.DirectorySeparatorChar
)));
249 info
= projectInfos
.Values
.FirstOrDefault (pi
=> pi
.FileName
== fullpath
);
252 if (unsupportedProjectInfos
.Values
.Any (pi
=> pi
.FileName
== fullpath
))
253 RaiseWarning (0, String
.Format (
254 "{0}: ProjectReference '{1}' is of an unsupported type. Ignoring.",
255 filename
, bi
.Include
));
257 RaiseWarning (0, String
.Format (
258 "{0}: ProjectReference '{1}' not found, neither by guid '{2}' nor by project file name '{3}'.",
259 filename
, bi
.Include
, projectReferenceGuid
.Replace ("{", "").Replace ("}", ""), fullpath
));
265 projectInfo
.Dependencies
[info
.Guid
] = info
;
268 // unload the project after reading info from it
269 // it'll be reloaded with proper context when building the solution
270 p
.ParentEngine
.UnloadProject (currentProject
);
273 // fill in the project info for deps found in the .sln file
274 foreach (ProjectInfo projectInfo
in projectInfos
.Values
) {
275 List
<Guid
> missingInfos
= new List
<Guid
> ();
276 foreach (KeyValuePair
<Guid
, ProjectInfo
> dependency
in projectInfo
.Dependencies
) {
277 if (dependency
.Value
== null)
278 missingInfos
.Add (dependency
.Key
);
281 foreach (Guid guid
in missingInfos
) {
283 if (projectInfos
.TryGetValue (guid
, out info
))
284 projectInfo
.Dependencies
[guid
] = info
;
286 projectInfo
.Dependencies
.Remove (guid
);
290 Match globalMatch
= globalRegex
.Match (line
);
291 Match globalSectionMatch
= globalSectionRegex
.Match (globalMatch
.Groups
[1].Value
);
292 while (globalSectionMatch
.Success
) {
293 string sectionType
= globalSectionMatch
.Groups
[1].Value
;
294 switch (sectionType
) {
295 case "SolutionConfigurationPlatforms":
296 ParseSolutionConfigurationPlatforms (globalSectionMatch
.Groups
[2].Value
, solutionTargets
);
298 case "ProjectConfigurationPlatforms":
299 ParseProjectConfigurationPlatforms (globalSectionMatch
.Groups
[2].Value
,
300 projectInfos
, websiteProjectInfos
);
302 case "SolutionProperties":
303 ParseSolutionProperties (globalSectionMatch
.Groups
[2].Value
);
305 case "NestedProjects":
307 case "MonoDevelopProperties":
310 RaiseWarning (0, string.Format("Don't know how to handle GlobalSection {0}, Ignoring.", sectionType
));
313 globalSectionMatch
= globalSectionMatch
.NextMatch ();
316 int num_levels
= AddBuildLevels (p
, solutionTargets
, projectInfos
, ref infosByLevel
);
318 AddCurrentSolutionConfigurationContents (p
, solutionTargets
, projectInfos
, websiteProjectInfos
);
319 AddProjectReferences (p
, projectInfos
);
320 AddWebsiteProperties (p
, websiteProjectInfos
, projectInfos
);
321 AddValidateSolutionConfiguration (p
);
323 EmitAfterImports (p
, file
);
325 AddGetFrameworkPathTarget (p
);
326 AddWebsiteTargets (p
, websiteProjectInfos
, projectInfos
, infosByLevel
, solutionTargets
);
327 AddProjectTargets (p
, solutionTargets
, projectInfos
);
328 AddSolutionTargets (p
, num_levels
, websiteProjectInfos
.Values
);
331 string GetSlnFileVersion (StreamReader reader
)
333 string strInput
= null;
336 strInput
= reader
.ReadLine();
337 if (strInput
== null)
340 match
= slnVersionRegex
.Match(strInput
);
341 if (!match
.Success
) {
342 strInput
= reader
.ReadLine();
343 if (strInput
== null)
345 match
= slnVersionRegex
.Match (strInput
);
349 return match
.Groups
[1].Value
;
354 void EmitBeforeImports (Project p
, string file
)
356 p
.AddNewImport ("$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\SolutionFile\\ImportBefore\\*",
357 "'$(ImportByWildcardBeforeSolution)' != 'false' and " +
358 "Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\SolutionFile\\ImportBefore')");
360 string before_filename
= Path
.Combine (Path
.GetDirectoryName (file
), "before." + Path
.GetFileName (file
) + ".targets");
361 p
.AddNewImport (before_filename
, String
.Format ("Exists ('{0}')", before_filename
));
364 void EmitAfterImports (Project p
, string file
)
366 p
.AddNewImport ("$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\SolutionFile\\ImportAfter\\*",
367 "'$(ImportByWildcardAfterSolution)' != 'false' and " +
368 "Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\SolutionFile\\ImportAfter')");
370 string after_filename
= Path
.Combine (Path
.GetDirectoryName (file
), "after." + Path
.GetFileName (file
) + ".targets");
371 p
.AddNewImport (after_filename
, String
.Format ("Exists ('{0}')", after_filename
));
374 void AddGeneralSettings (string solutionFile
, Project p
)
376 p
.DefaultTargets
= "Build";
377 p
.InitialTargets
= "ValidateSolutionConfiguration";
378 p
.AddNewUsingTaskFromAssemblyName ("CreateTemporaryVCProject", "Microsoft.Build.Tasks, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
379 p
.AddNewUsingTaskFromAssemblyName ("ResolveVCProjectOutput", "Microsoft.Build.Tasks, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
381 string solutionFilePath
= Path
.GetFullPath (solutionFile
);
382 BuildPropertyGroup solutionPropertyGroup
= p
.AddNewPropertyGroup (true);
383 solutionPropertyGroup
.AddNewProperty ("SolutionDir", Path
.GetDirectoryName (solutionFilePath
) + Path
.DirectorySeparatorChar
);
384 solutionPropertyGroup
.AddNewProperty ("SolutionExt", Path
.GetExtension (solutionFile
));
385 solutionPropertyGroup
.AddNewProperty ("SolutionFileName", Path
.GetFileName (solutionFile
));
386 solutionPropertyGroup
.AddNewProperty ("SolutionName", Path
.GetFileNameWithoutExtension (solutionFile
));
387 solutionPropertyGroup
.AddNewProperty ("SolutionPath", solutionFilePath
);
390 void ParseSolutionConfigurationPlatforms (string section
, List
<TargetInfo
> solutionTargets
)
392 Match solutionConfigurationPlatform
= solutionConfigurationRegex
.Match (section
);
393 while (solutionConfigurationPlatform
.Success
) {
394 string solutionConfiguration
= solutionConfigurationPlatform
.Groups
[1].Value
;
395 string solutionPlatform
= solutionConfigurationPlatform
.Groups
[2].Value
;
396 solutionTargets
.Add (new TargetInfo (solutionConfiguration
, solutionPlatform
));
397 solutionConfigurationPlatform
= solutionConfigurationPlatform
.NextMatch ();
401 // ignores the website projects, in the websiteProjectInfos
402 void ParseProjectConfigurationPlatforms (string section
, Dictionary
<Guid
, ProjectInfo
> projectInfos
,
403 Dictionary
<Guid
, ProjectInfo
> websiteProjectInfos
)
405 List
<Guid
> missingGuids
= new List
<Guid
> ();
406 Match projectConfigurationPlatform
= projectConfigurationActiveCfgRegex
.Match (section
);
407 while (projectConfigurationPlatform
.Success
) {
408 Guid guid
= new Guid (projectConfigurationPlatform
.Groups
[1].Value
);
409 ProjectInfo projectInfo
;
410 if (!projectInfos
.TryGetValue (guid
, out projectInfo
)) {
411 if (!missingGuids
.Contains (guid
)) {
412 if (!websiteProjectInfos
.ContainsKey (guid
))
413 // ignore website projects
414 RaiseWarning (0, string.Format("Failed to find project {0}", guid
));
415 missingGuids
.Add (guid
);
417 projectConfigurationPlatform
= projectConfigurationPlatform
.NextMatch ();
420 string solConf
= projectConfigurationPlatform
.Groups
[2].Value
;
421 string solPlat
= projectConfigurationPlatform
.Groups
[3].Value
;
422 string projConf
= projectConfigurationPlatform
.Groups
[4].Value
;
423 string projPlat
= projectConfigurationPlatform
.Groups
[5].Value
;
424 // hack, what are they doing here?
425 if (projPlat
== "Any CPU")
427 projectInfo
.TargetMap
.Add (new TargetInfo (solConf
, solPlat
), new TargetInfo (projConf
, projPlat
));
428 projectConfigurationPlatform
= projectConfigurationPlatform
.NextMatch ();
430 Match projectConfigurationPlatformBuild
= projectConfigurationBuildRegex
.Match (section
);
431 while (projectConfigurationPlatformBuild
.Success
) {
432 Guid guid
= new Guid (projectConfigurationPlatformBuild
.Groups
[1].Value
);
433 ProjectInfo projectInfo
;
434 if (!projectInfos
.TryGetValue (guid
, out projectInfo
)) {
435 if (!missingGuids
.Contains (guid
)) {
436 RaiseWarning (0, string.Format("Failed to find project {0}", guid
));
437 missingGuids
.Add (guid
);
439 projectConfigurationPlatformBuild
= projectConfigurationPlatformBuild
.NextMatch ();
442 string solConf
= projectConfigurationPlatformBuild
.Groups
[2].Value
;
443 string solPlat
= projectConfigurationPlatformBuild
.Groups
[3].Value
;
444 string projConf
= projectConfigurationPlatformBuild
.Groups
[4].Value
;
445 string projPlat
= projectConfigurationPlatformBuild
.Groups
[5].Value
;
446 // hack, what are they doing here?
447 if (projPlat
== "Any CPU")
449 projectInfo
.TargetMap
[new TargetInfo (solConf
, solPlat
)] = new TargetInfo (projConf
, projPlat
, true);
450 projectConfigurationPlatformBuild
= projectConfigurationPlatformBuild
.NextMatch ();
454 void ParseSolutionProperties (string section
)
458 void AddCurrentSolutionConfigurationContents (Project p
, List
<TargetInfo
> solutionTargets
,
459 Dictionary
<Guid
, ProjectInfo
> projectInfos
,
460 Dictionary
<Guid
, ProjectInfo
> websiteProjectInfos
)
462 TargetInfo default_target_info
= new TargetInfo ("Debug", "Any CPU");
463 if (solutionTargets
.Count
> 0) {
465 foreach (TargetInfo tinfo
in solutionTargets
) {
466 if (String
.Compare (tinfo
.Platform
, "Mixed Platforms") == 0) {
467 default_target_info
= tinfo
;
474 default_target_info
= solutionTargets
[0];
477 AddDefaultSolutionConfiguration (p
, default_target_info
);
479 foreach (TargetInfo solutionTarget
in solutionTargets
) {
480 BuildPropertyGroup platformPropertyGroup
= p
.AddNewPropertyGroup (false);
481 platformPropertyGroup
.Condition
= string.Format (
482 " ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ",
483 solutionTarget
.Configuration
,
484 solutionTarget
.Platform
487 StringBuilder solutionConfigurationContents
= new StringBuilder ();
488 solutionConfigurationContents
.Append ("<SolutionConfiguration xmlns=\"\">");
489 foreach (KeyValuePair
<Guid
, ProjectInfo
> projectInfo
in projectInfos
) {
490 AddProjectConfigurationItems (projectInfo
.Key
, projectInfo
.Value
, solutionTarget
, solutionConfigurationContents
);
492 solutionConfigurationContents
.Append ("</SolutionConfiguration>");
494 platformPropertyGroup
.AddNewProperty ("CurrentSolutionConfigurationContents",
495 solutionConfigurationContents
.ToString ());
499 void AddProjectReferences (Project p
, Dictionary
<Guid
, ProjectInfo
> projectInfos
)
501 BuildItemGroup big
= p
.AddNewItemGroup ();
502 foreach (KeyValuePair
<Guid
, ProjectInfo
> pair
in projectInfos
)
503 big
.AddNewItem ("ProjectReference", pair
.Value
.FileName
);
506 void AddProjectConfigurationItems (Guid guid
, ProjectInfo projectInfo
, TargetInfo solutionTarget
,
507 StringBuilder solutionConfigurationContents
)
509 foreach (KeyValuePair
<TargetInfo
, TargetInfo
> targetInfo
in projectInfo
.TargetMap
) {
510 if (solutionTarget
.Configuration
== targetInfo
.Key
.Configuration
&&
511 solutionTarget
.Platform
== targetInfo
.Key
.Platform
) {
512 solutionConfigurationContents
.AppendFormat (
513 "<ProjectConfiguration Project=\"{0}\" AbsolutePath=\"{1}\">{2}|{3}</ProjectConfiguration>",
514 guid
.ToString ("B").ToUpper (), projectInfo
.FileName
, targetInfo
.Value
.Configuration
, targetInfo
.Value
.Platform
);
519 void AddDefaultSolutionConfiguration (Project p
, TargetInfo target
)
521 BuildPropertyGroup configurationPropertyGroup
= p
.AddNewPropertyGroup (true);
522 configurationPropertyGroup
.Condition
= " '$(Configuration)' == '' ";
523 configurationPropertyGroup
.AddNewProperty ("Configuration", target
.Configuration
);
525 BuildPropertyGroup platformPropertyGroup
= p
.AddNewPropertyGroup (true);
526 platformPropertyGroup
.Condition
= " '$(Platform)' == '' ";
527 platformPropertyGroup
.AddNewProperty ("Platform", target
.Platform
);
529 // emit default for AspNetConfiguration also
530 BuildPropertyGroup aspNetConfigurationPropertyGroup
= p
.AddNewPropertyGroup (true);
531 aspNetConfigurationPropertyGroup
.Condition
= " ('$(AspNetConfiguration)' == '') ";
532 aspNetConfigurationPropertyGroup
.AddNewProperty ("AspNetConfiguration", "$(Configuration)");
535 void AddWarningForMissingProjectConfiguration (Target target
, string slnConfig
, string slnPlatform
, string projectName
)
537 BuildTask task
= target
.AddNewTask ("Warning");
538 task
.SetParameterValue ("Text",
539 String
.Format ("The project configuration for project '{0}' corresponding " +
540 "to the solution configuration '{1}|{2}' was not found in the solution file.",
541 projectName
, slnConfig
, slnPlatform
));
542 task
.Condition
= String
.Format ("('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}')",
543 slnConfig
, slnPlatform
);
547 // Website project methods
549 void AddWebsiteProperties (Project p
, Dictionary
<Guid
, ProjectInfo
> websiteProjectInfos
,
550 Dictionary
<Guid
, ProjectInfo
> projectInfos
)
552 var propertyGroupByConfig
= new Dictionary
<string, BuildPropertyGroup
> ();
553 foreach (KeyValuePair
<Guid
, ProjectInfo
> infoPair
in websiteProjectInfos
) {
554 ProjectInfo info
= infoPair
.Value
;
555 string projectGuid
= infoPair
.Key
.ToString ();
557 ProjectSection section
;
558 if (!info
.ProjectSections
.TryGetValue ("WebsiteProperties", out section
)) {
559 RaiseWarning (0, String
.Format ("Website project '{0}' does not have the required project section: WebsiteProperties. Ignoring project.", info
.Name
));
563 //parse project references
564 string [] ref_guids
= null;
566 if (section
.Properties
.TryGetValue ("ProjectReferences", out references
)) {
567 ref_guids
= references
.Split (new char [] {';'}
, StringSplitOptions
.RemoveEmptyEntries
);
568 for (int i
= 0; i
< ref_guids
.Length
; i
++) {
570 ref_guids
[i
] = ref_guids
[i
].Split ('|') [0];
572 Guid r_guid
= new Guid (ref_guids
[i
]);
573 ProjectInfo ref_info
;
574 if (projectInfos
.TryGetValue (r_guid
, out ref_info
))
575 // ignore if not found
576 info
.Dependencies
[r_guid
] = ref_info
;
580 foreach (KeyValuePair
<string, string> pair
in section
.Properties
) {
581 //looking for -- ConfigName.AspNetCompiler.PropName
582 string [] parts
= pair
.Key
.Split ('.');
583 if (parts
.Length
!= 3 || String
.Compare (parts
[1], "AspNetCompiler") != 0)
586 string config
= parts
[0];
587 string propertyName
= parts
[2];
589 BuildPropertyGroup bpg
;
590 if (!propertyGroupByConfig
.TryGetValue (config
, out bpg
)) {
591 bpg
= p
.AddNewPropertyGroup (true);
592 bpg
.Condition
= String
.Format (" '$(AspNetConfiguration)' == '{0}' ", config
);
593 propertyGroupByConfig
[config
] = bpg
;
596 bpg
.AddNewProperty (String
.Format ("Project_{0}_AspNet{1}", projectGuid
, propertyName
),
599 if (!info
.AspNetConfigurations
.Contains (config
))
600 info
.AspNetConfigurations
.Add (config
);
605 // For WebSite projects
606 // The main "Build" target:
607 // 1. builds all non-website projects
608 // 2. calls target for website project
609 // - gets target path for the referenced projects
610 // - Resolves dependencies, satellites etc for the
611 // referenced project assemblies, and copies them
613 void AddWebsiteTargets (Project p
, Dictionary
<Guid
, ProjectInfo
> websiteProjectInfos
,
614 Dictionary
<Guid
, ProjectInfo
> projectInfos
, List
<ProjectInfo
>[] infosByLevel
,
615 List
<TargetInfo
> solutionTargets
)
617 foreach (ProjectInfo w_info
in websiteProjectInfos
.Values
) {
618 // gets a linear list of dependencies
619 List
<ProjectInfo
> depInfos
= new List
<ProjectInfo
> ();
620 foreach (List
<ProjectInfo
> pinfos
in infosByLevel
) {
621 foreach (ProjectInfo pinfo
in pinfos
)
622 if (w_info
.Dependencies
.ContainsKey (pinfo
.Guid
))
623 depInfos
.Add (pinfo
);
626 foreach (string buildTarget
in new string [] {"Build", "Rebuild"}
)
627 AddWebsiteTarget (p
, w_info
, projectInfos
, depInfos
, solutionTargets
, buildTarget
);
629 // clean/publish are not supported for website projects
630 foreach (string buildTarget
in new string [] {"Clean", "Publish"}
)
631 AddWebsiteUnsupportedTarget (p
, w_info
, depInfos
, buildTarget
);
635 void AddWebsiteTarget (Project p
, ProjectInfo webProjectInfo
,
636 Dictionary
<Guid
, ProjectInfo
> projectInfos
, List
<ProjectInfo
> depInfos
,
637 List
<TargetInfo
> solutionTargets
, string buildTarget
)
639 string w_guid
= webProjectInfo
.Guid
.ToString ().ToUpper ();
641 Target target
= p
.Targets
.AddNewTarget (GetTargetNameForProject (webProjectInfo
.Name
, buildTarget
));
642 target
.Condition
= "'$(CurrentSolutionConfigurationContents)' != ''";
643 target
.DependsOnTargets
= GetWebsiteDependsOnTarget (depInfos
, buildTarget
);
645 // this item collects all the references
646 string final_ref_item
= String
.Format ("Project_{0}_References{1}", w_guid
,
647 buildTarget
!= "Build" ? "_" + buildTarget
: String
.Empty
);
649 foreach (TargetInfo targetInfo
in solutionTargets
) {
651 foreach (ProjectInfo depInfo
in depInfos
) {
652 TargetInfo projectTargetInfo
;
653 if (!depInfo
.TargetMap
.TryGetValue (targetInfo
, out projectTargetInfo
))
654 // Ignore, no config, so no target path
657 // GetTargetPath from the referenced project
658 AddWebsiteMSBuildTaskForReference (target
, depInfo
, projectTargetInfo
, targetInfo
,
659 final_ref_item
, ref_num
);
664 // resolve the references
665 AddWebsiteResolveAndCopyReferencesTasks (target
, webProjectInfo
, final_ref_item
, w_guid
);
668 // emits the MSBuild task to GetTargetPath for the referenced project
669 void AddWebsiteMSBuildTaskForReference (Target target
, ProjectInfo depInfo
, TargetInfo projectTargetInfo
,
670 TargetInfo solutionTargetInfo
, string final_ref_item
, int ref_num
)
672 BuildTask task
= target
.AddNewTask ("MSBuild");
673 task
.SetParameterValue ("Projects", depInfo
.FileName
);
674 task
.SetParameterValue ("Targets", "GetTargetPath");
676 task
.SetParameterValue ("Properties", string.Format ("Configuration={0}; Platform={1}; BuildingSolutionFile=true; CurrentSolutionConfigurationContents=$(CurrentSolutionConfigurationContents); SolutionDir=$(SolutionDir); SolutionExt=$(SolutionExt); SolutionFileName=$(SolutionFileName); SolutionName=$(SolutionName); SolutionPath=$(SolutionPath)", projectTargetInfo
.Configuration
, projectTargetInfo
.Platform
));
677 task
.Condition
= string.Format (" ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ", solutionTargetInfo
.Configuration
, solutionTargetInfo
.Platform
);
679 string ref_item
= String
.Format ("{0}_{1}",
680 final_ref_item
, ref_num
);
682 task
.AddOutputItem ("TargetOutputs", ref_item
);
684 task
= target
.AddNewTask ("CreateItem");
685 task
.SetParameterValue ("Include", String
.Format ("@({0})", ref_item
));
686 task
.SetParameterValue ("AdditionalMetadata", String
.Format ("Guid={{{0}}}",
687 depInfo
.Guid
.ToString ().ToUpper ()));
688 task
.AddOutputItem ("Include", final_ref_item
);
691 void AddWebsiteResolveAndCopyReferencesTasks (Target target
, ProjectInfo webProjectInfo
,
692 string final_ref_item
, string w_guid
)
694 BuildTask task
= target
.AddNewTask ("ResolveAssemblyReference");
695 task
.SetParameterValue ("Assemblies", String
.Format ("@({0}->'%(FullPath)')", final_ref_item
));
696 task
.SetParameterValue ("TargetFrameworkDirectories", "$(TargetFrameworkPath)");
697 task
.SetParameterValue ("SearchPaths", "{RawFileName};{TargetFrameworkDirectory};{GAC}");
698 task
.SetParameterValue ("FindDependencies", "true");
699 task
.SetParameterValue ("FindSatellites", "true");
700 task
.SetParameterValue ("FindRelatedFiles", "true");
701 task
.Condition
= String
.Format ("Exists ('%({0}.Identity)')", final_ref_item
);
703 string copylocal_item
= String
.Format ("{0}_CopyLocalFiles", final_ref_item
);
704 task
.AddOutputItem ("CopyLocalFiles", copylocal_item
);
706 // Copy the references
707 task
= target
.AddNewTask ("Copy");
708 task
.SetParameterValue ("SourceFiles", String
.Format ("@({0})", copylocal_item
));
709 task
.SetParameterValue ("DestinationFiles", String
.Format (
710 "@({0}->'$(Project_{1}_AspNetPhysicalPath)\\Bin\\%(DestinationSubDirectory)%(Filename)%(Extension)')",
711 copylocal_item
, w_guid
));
713 // AspNetConfiguration, is config for the website project, useful
714 // for overriding from command line
715 StringBuilder cond
= new StringBuilder ();
716 foreach (string config
in webProjectInfo
.AspNetConfigurations
) {
718 cond
.Append (" or ");
719 cond
.AppendFormat (" ('$(AspNetConfiguration)' == '{0}') ", config
);
721 task
.Condition
= cond
.ToString ();
723 task
= target
.AddNewTask ("Message");
724 cond
= new StringBuilder ();
725 foreach (string config
in webProjectInfo
.AspNetConfigurations
) {
727 cond
.Append (" and ");
728 cond
.AppendFormat (" ('$(AspNetConfiguration)' != '{0}') ", config
);
730 task
.Condition
= cond
.ToString ();
731 task
.SetParameterValue ("Text", "Skipping as the '$(AspNetConfiguration)' configuration is " +
732 "not supported by this website project.");
735 void AddWebsiteUnsupportedTarget (Project p
, ProjectInfo webProjectInfo
, List
<ProjectInfo
> depInfos
,
738 Target target
= p
.Targets
.AddNewTarget (GetTargetNameForProject (webProjectInfo
.Name
, buildTarget
));
739 target
.DependsOnTargets
= GetWebsiteDependsOnTarget (depInfos
, buildTarget
);
741 BuildTask task
= target
.AddNewTask ("Message");
742 task
.SetParameterValue ("Text", String
.Format (
743 "Target '{0}' not support for website projects", buildTarget
));
746 string GetWebsiteDependsOnTarget (List
<ProjectInfo
> depInfos
, string buildTarget
)
748 StringBuilder deps
= new StringBuilder ();
749 foreach (ProjectInfo pinfo
in depInfos
) {
752 deps
.Append (GetTargetNameForProject (pinfo
.Name
, buildTarget
));
754 deps
.Append (";GetFrameworkPath");
755 return deps
.ToString ();
758 void AddGetFrameworkPathTarget (Project p
)
760 Target t
= p
.Targets
.AddNewTarget ("GetFrameworkPath");
761 BuildTask task
= t
.AddNewTask ("GetFrameworkPath");
762 task
.AddOutputProperty ("Path", "TargetFrameworkPath");
765 void AddValidateSolutionConfiguration (Project p
)
767 Target t
= p
.Targets
.AddNewTarget ("ValidateSolutionConfiguration");
768 BuildTask task
= t
.AddNewTask ("Warning");
769 task
.SetParameterValue ("Text", "On windows, an environment variable 'Platform' is set to MCD sometimes, and this overrides the Platform property" +
770 " for xbuild, which could be an invalid Platform for this solution file. And so you are getting the following error." +
771 " You could override it by either setting the environment variable to nothing, as\n" +
773 "Or explicity specify its value on the command line, as\n" +
774 " xbuild Foo.sln /p:Platform=Release");
775 task
.Condition
= "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' != 'true')" +
776 " and '$(Platform)' == 'MCD' and '$(OS)' == 'Windows_NT'";
778 task
= t
.AddNewTask ("Error");
779 task
.SetParameterValue ("Text", "Invalid solution configuration and platform: \"$(Configuration)|$(Platform)\".");
780 task
.Condition
= "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' != 'true')";
781 task
= t
.AddNewTask ("Warning");
782 task
.SetParameterValue ("Text", "Invalid solution configuration and platform: \"$(Configuration)|$(Platform)\".");
783 task
.Condition
= "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' == 'true')";
784 task
= t
.AddNewTask ("Message");
785 task
.SetParameterValue ("Text", "Building solution configuration \"$(Configuration)|$(Platform)\".");
786 task
.Condition
= "'$(CurrentSolutionConfigurationContents)' != ''";
789 void AddProjectTargets (Project p
, List
<TargetInfo
> solutionTargets
, Dictionary
<Guid
, ProjectInfo
> projectInfos
)
791 foreach (KeyValuePair
<Guid
, ProjectInfo
> projectInfo
in projectInfos
) {
792 ProjectInfo project
= projectInfo
.Value
;
793 foreach (string buildTarget
in buildTargets
) {
794 string target_name
= GetTargetNameForProject (project
.Name
, buildTarget
);
795 bool is_build_or_rebuild
= buildTarget
== "Build" || buildTarget
== "Rebuild";
796 Target target
= p
.Targets
.AddNewTarget (target_name
);
797 target
.Condition
= "'$(CurrentSolutionConfigurationContents)' != ''";
799 if (is_build_or_rebuild
)
800 target
.Outputs
= "@(CollectedBuildOutput)";
801 if (project
.Dependencies
.Count
> 0)
802 target
.DependsOnTargets
= String
.Join (";",
803 project
.Dependencies
.Values
.Select (
804 di
=> GetTargetNameForProject (di
.Name
, buildTarget
)).ToArray ());
806 foreach (TargetInfo targetInfo
in solutionTargets
) {
807 BuildTask task
= null;
808 TargetInfo projectTargetInfo
;
809 if (!project
.TargetMap
.TryGetValue (targetInfo
, out projectTargetInfo
)) {
810 AddWarningForMissingProjectConfiguration (target
, targetInfo
.Configuration
,
811 targetInfo
.Platform
, project
.Name
);
814 if (projectTargetInfo
.Build
) {
815 task
= target
.AddNewTask ("MSBuild");
816 task
.SetParameterValue ("Projects", project
.FileName
);
817 task
.SetParameterValue ("ToolsVersion", "$(ProjectToolsVersion)");
818 if (is_build_or_rebuild
)
819 task
.AddOutputItem ("TargetOutputs", "CollectedBuildOutput");
821 if (buildTarget
!= "Build")
822 task
.SetParameterValue ("Targets", buildTarget
);
823 task
.SetParameterValue ("Properties", string.Format ("Configuration={0}; Platform={1}; BuildingSolutionFile=true; CurrentSolutionConfigurationContents=$(CurrentSolutionConfigurationContents); SolutionDir=$(SolutionDir); SolutionExt=$(SolutionExt); SolutionFileName=$(SolutionFileName); SolutionName=$(SolutionName); SolutionPath=$(SolutionPath)", projectTargetInfo
.Configuration
, projectTargetInfo
.Platform
));
825 task
= target
.AddNewTask ("Message");
826 task
.SetParameterValue ("Text", string.Format ("Project \"{0}\" is disabled for solution configuration \"{1}|{2}\".", project
.Name
, targetInfo
.Configuration
, targetInfo
.Platform
));
828 task
.Condition
= string.Format (" ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ", targetInfo
.Configuration
, targetInfo
.Platform
);
835 string GetTargetNameForProject (string projectName
, string buildTarget
)
838 projectName
= projectName
.Replace ("\\", "/").Replace (".", "_");
839 string target_name
= projectName
+
840 (buildTarget
== "Build" ? string.Empty
: ":" + buildTarget
);
842 if (IsBuildTargetName (projectName
))
843 target_name
= "Solution:" + target_name
;
848 bool IsBuildTargetName (string name
)
850 foreach (string tgt
in buildTargets
)
856 // returns number of levels
857 int AddBuildLevels (Project p
, List
<TargetInfo
> solutionTargets
, Dictionary
<Guid
, ProjectInfo
> projectInfos
,
858 ref List
<ProjectInfo
>[] infosByLevel
)
860 infosByLevel
= TopologicalSort
<ProjectInfo
> (projectInfos
.Values
);
862 foreach (TargetInfo targetInfo
in solutionTargets
) {
863 BuildItemGroup big
= p
.AddNewItemGroup ();
864 big
.Condition
= String
.Format (" ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ",
865 targetInfo
.Configuration
, targetInfo
.Platform
);
867 //FIXME: every level has projects that can be built in parallel.
868 // levels are ordered on the basis of the dependency graph
870 for (int i
= 0; i
< infosByLevel
.Length
; i
++) {
871 string build_level
= String
.Format ("BuildLevel{0}", i
);
872 string skip_level
= String
.Format ("SkipLevel{0}", i
);
873 string missing_level
= String
.Format ("MissingConfigLevel{0}", i
);
875 foreach (ProjectInfo projectInfo
in infosByLevel
[i
]) {
876 TargetInfo projectTargetInfo
;
877 if (!projectInfo
.TargetMap
.TryGetValue (targetInfo
, out projectTargetInfo
)) {
878 // missing project config
879 big
.AddNewItem (missing_level
, projectInfo
.Name
);
883 if (projectTargetInfo
.Build
) {
884 BuildItem item
= big
.AddNewItem (build_level
, projectInfo
.FileName
);
885 item
.SetMetadata ("Configuration", projectTargetInfo
.Configuration
);
886 item
.SetMetadata ("Platform", projectTargetInfo
.Platform
);
889 big
.AddNewItem (skip_level
, projectInfo
.Name
);
895 return infosByLevel
.Length
;
898 void AddSolutionTargets (Project p
, int num_levels
, IEnumerable
<ProjectInfo
> websiteProjectInfos
)
900 foreach (string buildTarget
in buildTargets
) {
901 Target t
= p
.Targets
.AddNewTarget (buildTarget
);
902 bool is_build_or_rebuild
= buildTarget
== "Build" || buildTarget
== "Rebuild";
904 t
.Condition
= "'$(CurrentSolutionConfigurationContents)' != ''";
905 if (is_build_or_rebuild
)
906 t
.Outputs
= "@(CollectedBuildOutput)";
908 BuildTask task
= null;
909 for (int i
= 0; i
< num_levels
; i
++) {
910 string level_str
= String
.Format ("BuildLevel{0}", i
);
911 task
= t
.AddNewTask ("MSBuild");
912 task
.SetParameterValue ("Condition", String
.Format ("'@({0})' != ''", level_str
));
913 task
.SetParameterValue ("Projects", String
.Format ("@({0})", level_str
));
914 task
.SetParameterValue ("ToolsVersion", "$(ProjectToolsVersion)");
915 task
.SetParameterValue ("Properties",
916 string.Format ("Configuration=%(Configuration); Platform=%(Platform); BuildingSolutionFile=true; CurrentSolutionConfigurationContents=$(CurrentSolutionConfigurationContents); SolutionDir=$(SolutionDir); SolutionExt=$(SolutionExt); SolutionFileName=$(SolutionFileName); SolutionName=$(SolutionName); SolutionPath=$(SolutionPath)"));
917 if (buildTarget
!= "Build")
918 task
.SetParameterValue ("Targets", buildTarget
);
919 //FIXME: change this to BuildInParallel=true, when parallel
920 // build support gets added
921 task
.SetParameterValue ("RunEachTargetSeparately", "true");
922 if (is_build_or_rebuild
)
923 task
.AddOutputItem ("TargetOutputs", "CollectedBuildOutput");
925 level_str
= String
.Format ("SkipLevel{0}", i
);
926 task
= t
.AddNewTask ("Message");
927 task
.Condition
= String
.Format ("'@({0})' != ''", level_str
);
928 task
.SetParameterValue ("Text",
929 String
.Format ("The project '%({0}.Identity)' is disabled for solution " +
930 "configuration '$(Configuration)|$(Platform)'.", level_str
));
932 level_str
= String
.Format ("MissingConfigLevel{0}", i
);
933 task
= t
.AddNewTask ("Warning");
934 task
.Condition
= String
.Format ("'@({0})' != ''", level_str
);
935 task
.SetParameterValue ("Text",
936 String
.Format ("The project configuration for project '%({0}.Identity)' " +
937 "corresponding to the solution configuration " +
938 "'$(Configuration)|$(Platform)' was not found.", level_str
));
941 // "build" website projects also
942 StringBuilder w_targets
= new StringBuilder ();
943 foreach (ProjectInfo info
in websiteProjectInfos
) {
944 if (w_targets
.Length
> 0)
945 w_targets
.Append (";");
946 w_targets
.Append (GetTargetNameForProject (info
.Name
, buildTarget
));
949 task
= t
.AddNewTask ("CallTarget");
950 task
.SetParameterValue ("Targets", w_targets
.ToString ());
951 task
.SetParameterValue ("RunEachTargetSeparately", "true");
955 // Sorts the ProjectInfo dependency graph, to obtain
956 // a series of build levels with projects. Projects
957 // in each level can be run parallel (no inter-dependency).
958 static List
<T
>[] TopologicalSort
<T
> (IEnumerable
<T
> items
) where T
: ProjectInfo
961 allItems
= items
as IList
<T
>;
962 if (allItems
== null)
963 allItems
= new List
<T
> (items
);
965 bool[] inserted
= new bool[allItems
.Count
];
966 bool[] triedToInsert
= new bool[allItems
.Count
];
967 int[] levels
= new int [allItems
.Count
];
970 for (int i
= 0; i
< allItems
.Count
; ++i
) {
971 int d
= Insert
<T
> (i
, allItems
, levels
, inserted
, triedToInsert
);
976 // Separate out the project infos by build level
977 List
<T
>[] infosByLevel
= new List
<T
>[maxdepth
];
978 for (int i
= 0; i
< levels
.Length
; i
++) {
979 int level
= levels
[i
] - 1;
980 if (infosByLevel
[level
] == null)
981 infosByLevel
[level
] = new List
<T
> ();
983 infosByLevel
[level
].Add (allItems
[i
]);
989 // returns level# for the project
990 static int Insert
<T
> (int index
, IList
<T
> allItems
, int[] levels
, bool[] inserted
, bool[] triedToInsert
)
993 if (inserted
[index
])
994 return levels
[index
];
996 if (triedToInsert
[index
])
997 throw new InvalidOperationException (String
.Format (
998 "Cyclic dependency involving project {0} found in the project dependency graph",
999 allItems
[index
].Name
));
1001 triedToInsert
[index
] = true;
1002 ProjectInfo insertItem
= allItems
[index
];
1005 foreach (ProjectInfo dependency
in insertItem
.Dependencies
.Values
) {
1006 for (int j
= 0; j
< allItems
.Count
; ++j
) {
1007 ProjectInfo checkItem
= allItems
[j
];
1008 if (dependency
.FileName
== checkItem
.FileName
) {
1009 int d
= Insert (j
, allItems
, levels
, inserted
, triedToInsert
);
1010 maxdepth
= d
> maxdepth
? d
: maxdepth
;
1015 levels
[index
] = maxdepth
+ 1;
1016 inserted
[index
] = true;
1018 return levels
[index
];
1021 public static IEnumerable
<string> GetAllProjectFileNames (string solutionFile
)
1023 StreamReader reader
= new StreamReader (solutionFile
);
1024 string line
= reader
.ReadToEnd ();
1025 line
= line
.Replace ("\r\n", "\n");
1026 string soln_dir
= Path
.GetDirectoryName (solutionFile
);
1028 Match m
= projectRegex
.Match (line
);
1030 if (String
.Compare (m
.Groups
[1].Value
, solutionFolderGuid
,
1031 StringComparison
.InvariantCultureIgnoreCase
) != 0)
1032 yield return Path
.Combine (soln_dir
, m
.Groups
[3].Value
).Replace ("\\", "/");