Add color support for errors/warnings etc.
[mcs.git] / tools / xbuild / SolutionParser.cs
blobeb62468c8b6ffc9f5818914cd7792c5fb2d8e292
1 //
2 // SolutionParser.cs: Generates a project file from a solution file.
3 //
4 // Author:
5 // Jonathan Chambers (joncham@gmail.com)
6 // Ankit Jain <jankit@novell.com>
7 // Lluis Sanchez Gual <lluis@novell.com>
8 //
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.
31 #if NET_2_0
33 using System;
34 using System.Collections.Generic;
35 using System.Text;
36 using System.Text.RegularExpressions;
37 using System.IO;
38 using Microsoft.Build.BuildEngine;
40 namespace Mono.XBuild.CommandLine {
41 class ProjectInfo {
42 public string Name;
43 public string FileName;
44 public Guid Guid;
46 public ProjectInfo (string name, string fileName)
48 Name = name;
49 FileName = fileName;
52 public Dictionary<TargetInfo, TargetInfo> TargetMap = new Dictionary<TargetInfo, TargetInfo> ();
53 public Dictionary<Guid, ProjectInfo> Dependencies = new Dictionary<Guid, ProjectInfo> ();
54 public Dictionary<string, ProjectSection> ProjectSections = new Dictionary<string, ProjectSection> ();
55 public List<string> AspNetConfigurations = new List<string> ();
58 class ProjectSection {
59 public string Name;
60 public Dictionary<string, string> Properties = new Dictionary<string, string> ();
62 public ProjectSection (string name)
64 Name = name;
68 struct TargetInfo {
69 public string Configuration;
70 public string Platform;
71 public bool Build;
73 public TargetInfo (string configuration, string platform)
74 : this (configuration, platform, false)
78 public TargetInfo (string configuration, string platform, bool build)
80 Configuration = configuration;
81 Platform = platform;
82 Build = build;
87 internal delegate void RaiseWarningHandler (int errorNumber, string message);
89 class SolutionParser {
90 static string[] buildTargets = new string[] { "Build", "Clean", "Rebuild", "Publish" };
92 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}}";
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 AddGeneralSettings (file, p);
117 StreamReader reader = new StreamReader (file);
118 string line = reader.ReadToEnd ();
119 line = line.Replace ("\r\n", "\n");
120 string solutionDir = Path.GetDirectoryName (file);
122 List<TargetInfo> solutionTargets = new List<TargetInfo> ();
123 Dictionary<Guid, ProjectInfo> projectInfos = new Dictionary<Guid, ProjectInfo> ();
124 Dictionary<Guid, ProjectInfo> websiteProjectInfos = new Dictionary<Guid, ProjectInfo> ();
125 List<ProjectInfo>[] infosByLevel = null;
127 Match m = projectRegex.Match (line);
128 while (m.Success) {
129 ProjectInfo projectInfo = new ProjectInfo (m.Groups[2].Value, m.Groups[3].Value);
130 if (String.Compare (m.Groups [1].Value, solutionFolderGuid,
131 StringComparison.InvariantCultureIgnoreCase) == 0) {
132 // Ignore solution folders
133 m = m.NextMatch ();
134 continue;
137 if (String.Compare (m.Groups [1].Value, vcprojGuid,
138 StringComparison.InvariantCultureIgnoreCase) == 0) {
139 // Ignore vcproj
140 RaiseWarning (0, string.Format("Ignoring vcproj '{0}'.", projectInfo.Name));
141 m = m.NextMatch ();
142 continue;
145 if (String.Compare (m.Groups [1].Value, websiteProjectGuid,
146 StringComparison.InvariantCultureIgnoreCase) == 0)
147 websiteProjectInfos.Add (new Guid (m.Groups[4].Value), projectInfo);
148 else
149 projectInfos.Add (new Guid (m.Groups[4].Value), projectInfo);
151 projectInfo.Guid = new Guid (m.Groups [4].Value);
153 Match projectSectionMatch = projectDependenciesRegex.Match (m.Groups[6].Value);
154 while (projectSectionMatch.Success) {
155 string section_name = projectSectionMatch.Groups [1].Value;
156 if (String.Compare (section_name, "ProjectDependencies") == 0) {
157 Match projectDependencyMatch = projectDependencyRegex.Match (projectSectionMatch.Value);
158 while (projectDependencyMatch.Success) {
159 // we might not have projectInfo available right now, so
160 // set it to null, and fill it in later
161 projectInfo.Dependencies [new Guid (projectDependencyMatch.Groups[1].Value)] = null;
162 projectDependencyMatch = projectDependencyMatch.NextMatch ();
164 } else {
165 ProjectSection section = new ProjectSection (section_name);
166 Match propertiesMatch = projectSectionPropertiesRegex.Match (
167 projectSectionMatch.Groups [2].Value);
168 while (propertiesMatch.Success) {
169 section.Properties [propertiesMatch.Groups ["name"].Value] =
170 propertiesMatch.Groups ["value"].Value;
172 propertiesMatch = propertiesMatch.NextMatch ();
175 projectInfo.ProjectSections [section_name] = section;
177 projectSectionMatch = projectSectionMatch.NextMatch ();
179 m = m.NextMatch ();
182 foreach (ProjectInfo projectInfo in projectInfos.Values) {
183 Project currentProject = p.ParentEngine.CreateNewProject ();
184 currentProject.Load (Path.Combine (solutionDir,
185 projectInfo.FileName.Replace ('\\', Path.DirectorySeparatorChar)));
187 foreach (BuildItem bi in currentProject.GetEvaluatedItemsByName ("ProjectReference")) {
188 string projectReferenceGuid = bi.GetEvaluatedMetadata ("Project");
189 Guid guid = new Guid (projectReferenceGuid);
190 ProjectInfo info;
191 if (projectInfos.TryGetValue (guid, out info))
192 // ignore if not found
193 projectInfo.Dependencies [guid] = info;
197 // fill in the project info for deps found in the .sln file
198 foreach (ProjectInfo projectInfo in projectInfos.Values) {
199 List<Guid> missingInfos = new List<Guid> ();
200 foreach (KeyValuePair<Guid, ProjectInfo> dependency in projectInfo.Dependencies) {
201 if (dependency.Value == null)
202 missingInfos.Add (dependency.Key);
205 foreach (Guid guid in missingInfos) {
206 ProjectInfo info;
207 if (projectInfos.TryGetValue (guid, out info))
208 projectInfo.Dependencies [guid] = info;
209 else
210 projectInfo.Dependencies.Remove (guid);
214 Match globalMatch = globalRegex.Match (line);
215 Match globalSectionMatch = globalSectionRegex.Match (globalMatch.Groups[1].Value);
216 while (globalSectionMatch.Success) {
217 string sectionType = globalSectionMatch.Groups[1].Value;
218 switch (sectionType) {
219 case "SolutionConfigurationPlatforms":
220 ParseSolutionConfigurationPlatforms (globalSectionMatch.Groups[2].Value, solutionTargets);
221 break;
222 case "ProjectConfigurationPlatforms":
223 ParseProjectConfigurationPlatforms (globalSectionMatch.Groups[2].Value,
224 projectInfos, websiteProjectInfos);
225 break;
226 case "SolutionProperties":
227 ParseSolutionProperties (globalSectionMatch.Groups[2].Value);
228 break;
229 case "NestedProjects":
230 break;
231 default:
232 RaiseWarning (0, string.Format("Don't know how to handle GlobalSection {0}, Ignoring.", sectionType));
233 break;
235 globalSectionMatch = globalSectionMatch.NextMatch ();
238 int num_levels = AddBuildLevels (p, solutionTargets, projectInfos, ref infosByLevel);
240 AddCurrentSolutionConfigurationContents (p, solutionTargets, projectInfos, websiteProjectInfos);
241 AddWebsiteProperties (p, websiteProjectInfos, projectInfos);
242 AddValidateSolutionConfiguration (p);
244 AddGetFrameworkPathTarget (p);
245 AddWebsiteTargets (p, websiteProjectInfos, projectInfos, infosByLevel, solutionTargets);
246 AddProjectTargets (p, solutionTargets, projectInfos);
247 AddSolutionTargets (p, num_levels, websiteProjectInfos.Values);
250 void AddGeneralSettings (string solutionFile, Project p)
252 p.DefaultTargets = "Build";
253 p.InitialTargets = "ValidateSolutionConfiguration";
254 p.AddNewUsingTaskFromAssemblyName ("CreateTemporaryVCProject", "Microsoft.Build.Tasks, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
255 p.AddNewUsingTaskFromAssemblyName ("ResolveVCProjectOutput", "Microsoft.Build.Tasks, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
257 string solutionFilePath = Path.GetFullPath (solutionFile);
258 BuildPropertyGroup solutionPropertyGroup = p.AddNewPropertyGroup (true);
259 solutionPropertyGroup.AddNewProperty ("SolutionDir", Path.GetDirectoryName (solutionFilePath) + Path.DirectorySeparatorChar);
260 solutionPropertyGroup.AddNewProperty ("SolutionExt", Path.GetExtension (solutionFile));
261 solutionPropertyGroup.AddNewProperty ("SolutionFileName", Path.GetFileName (solutionFile));
262 solutionPropertyGroup.AddNewProperty ("SolutionName", Path.GetFileNameWithoutExtension (solutionFile));
263 solutionPropertyGroup.AddNewProperty ("SolutionPath", solutionFilePath);
266 void ParseSolutionConfigurationPlatforms (string section, List<TargetInfo> solutionTargets)
268 Match solutionConfigurationPlatform = solutionConfigurationRegex.Match (section);
269 while (solutionConfigurationPlatform.Success) {
270 string solutionConfiguration = solutionConfigurationPlatform.Groups[1].Value;
271 string solutionPlatform = solutionConfigurationPlatform.Groups[2].Value;
272 solutionTargets.Add (new TargetInfo (solutionConfiguration, solutionPlatform));
273 solutionConfigurationPlatform = solutionConfigurationPlatform.NextMatch ();
277 // ignores the website projects, in the websiteProjectInfos
278 void ParseProjectConfigurationPlatforms (string section, Dictionary<Guid, ProjectInfo> projectInfos,
279 Dictionary<Guid, ProjectInfo> websiteProjectInfos)
281 List<Guid> missingGuids = new List<Guid> ();
282 Match projectConfigurationPlatform = projectConfigurationActiveCfgRegex.Match (section);
283 while (projectConfigurationPlatform.Success) {
284 Guid guid = new Guid (projectConfigurationPlatform.Groups[1].Value);
285 ProjectInfo projectInfo;
286 if (!projectInfos.TryGetValue (guid, out projectInfo)) {
287 if (!missingGuids.Contains (guid)) {
288 if (!websiteProjectInfos.ContainsKey (guid))
289 // ignore website projects
290 RaiseWarning (0, string.Format("Failed to find project {0}", guid));
291 missingGuids.Add (guid);
293 projectConfigurationPlatform = projectConfigurationPlatform.NextMatch ();
294 continue;
296 string solConf = projectConfigurationPlatform.Groups[2].Value;
297 string solPlat = projectConfigurationPlatform.Groups[3].Value;
298 string projConf = projectConfigurationPlatform.Groups[4].Value;
299 string projPlat = projectConfigurationPlatform.Groups[5].Value;
300 // hack, what are they doing here?
301 if (projPlat == "Any CPU")
302 projPlat = "AnyCPU";
303 projectInfo.TargetMap.Add (new TargetInfo (solConf, solPlat), new TargetInfo (projConf, projPlat));
304 projectConfigurationPlatform = projectConfigurationPlatform.NextMatch ();
306 Match projectConfigurationPlatformBuild = projectConfigurationBuildRegex.Match (section);
307 while (projectConfigurationPlatformBuild.Success) {
308 Guid guid = new Guid (projectConfigurationPlatformBuild.Groups[1].Value);
309 ProjectInfo projectInfo;
310 if (!projectInfos.TryGetValue (guid, out projectInfo)) {
311 if (!missingGuids.Contains (guid)) {
312 RaiseWarning (0, string.Format("Failed to find project {0}", guid));
313 missingGuids.Add (guid);
315 projectConfigurationPlatformBuild = projectConfigurationPlatformBuild.NextMatch ();
316 continue;
318 string solConf = projectConfigurationPlatformBuild.Groups[2].Value;
319 string solPlat = projectConfigurationPlatformBuild.Groups[3].Value;
320 string projConf = projectConfigurationPlatformBuild.Groups[4].Value;
321 string projPlat = projectConfigurationPlatformBuild.Groups[5].Value;
322 // hack, what are they doing here?
323 if (projPlat == "Any CPU")
324 projPlat = "AnyCPU";
325 projectInfo.TargetMap[new TargetInfo (solConf, solPlat)] = new TargetInfo (projConf, projPlat, true);
326 projectConfigurationPlatformBuild = projectConfigurationPlatformBuild.NextMatch ();
330 void ParseSolutionProperties (string section)
334 void AddCurrentSolutionConfigurationContents (Project p, List<TargetInfo> solutionTargets,
335 Dictionary<Guid, ProjectInfo> projectInfos,
336 Dictionary<Guid, ProjectInfo> websiteProjectInfos)
338 TargetInfo default_target_info = new TargetInfo ("Debug", "Any CPU");
339 if (solutionTargets.Count > 0) {
340 bool found = false;
341 foreach (TargetInfo tinfo in solutionTargets) {
342 if (String.Compare (tinfo.Platform, "Mixed Platforms") == 0) {
343 default_target_info = tinfo;
344 found = true;
345 break;
349 if (!found)
350 default_target_info = solutionTargets [0];
353 AddDefaultSolutionConfiguration (p, default_target_info);
355 foreach (TargetInfo solutionTarget in solutionTargets) {
356 BuildPropertyGroup platformPropertyGroup = p.AddNewPropertyGroup (false);
357 platformPropertyGroup.Condition = string.Format (
358 " ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ",
359 solutionTarget.Configuration,
360 solutionTarget.Platform
363 StringBuilder solutionConfigurationContents = new StringBuilder ();
364 solutionConfigurationContents.Append ("<SolutionConfiguration xmlns=\"\">");
365 foreach (KeyValuePair<Guid, ProjectInfo> projectInfo in projectInfos) {
366 AddProjectConfigurationItems (projectInfo.Key, projectInfo.Value, solutionTarget, solutionConfigurationContents);
368 solutionConfigurationContents.Append ("</SolutionConfiguration>");
370 platformPropertyGroup.AddNewProperty ("CurrentSolutionConfigurationContents",
371 solutionConfigurationContents.ToString ());
375 void AddProjectConfigurationItems (Guid guid, ProjectInfo projectInfo, TargetInfo solutionTarget,
376 StringBuilder solutionConfigurationContents)
378 foreach (KeyValuePair<TargetInfo, TargetInfo> targetInfo in projectInfo.TargetMap) {
379 if (solutionTarget.Configuration == targetInfo.Key.Configuration &&
380 solutionTarget.Platform == targetInfo.Key.Platform) {
381 solutionConfigurationContents.AppendFormat (
382 "<ProjectConfiguration Project=\"{0}\">{1}|{2}</ProjectConfiguration>",
383 guid.ToString ("B").ToUpper (), targetInfo.Value.Configuration, targetInfo.Value.Platform);
388 void AddDefaultSolutionConfiguration (Project p, TargetInfo target)
390 BuildPropertyGroup configurationPropertyGroup = p.AddNewPropertyGroup (true);
391 configurationPropertyGroup.Condition = " '$(Configuration)' == '' ";
392 configurationPropertyGroup.AddNewProperty ("Configuration", target.Configuration);
394 BuildPropertyGroup platformPropertyGroup = p.AddNewPropertyGroup (true);
395 platformPropertyGroup.Condition = " '$(Platform)' == '' ";
396 platformPropertyGroup.AddNewProperty ("Platform", target.Platform);
398 // emit default for AspNetConfiguration also
399 BuildPropertyGroup aspNetConfigurationPropertyGroup = p.AddNewPropertyGroup (true);
400 aspNetConfigurationPropertyGroup.Condition = " ('$(AspNetConfiguration)' == '') ";
401 aspNetConfigurationPropertyGroup.AddNewProperty ("AspNetConfiguration", "$(Configuration)");
404 void AddWarningForMissingProjectConfiguration (Target target, string slnConfig, string slnPlatform, string projectName)
406 BuildTask task = target.AddNewTask ("Warning");
407 task.SetParameterValue ("Text",
408 String.Format ("The project configuration for project '{0}' corresponding " +
409 "to the solution configuration '{1}|{2}' was not found in the solution file.",
410 projectName, slnConfig, slnPlatform));
411 task.Condition = String.Format ("('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}')",
412 slnConfig, slnPlatform);
416 // Website project methods
418 void AddWebsiteProperties (Project p, Dictionary<Guid, ProjectInfo> websiteProjectInfos,
419 Dictionary<Guid, ProjectInfo> projectInfos)
421 var propertyGroupByConfig = new Dictionary<string, BuildPropertyGroup> ();
422 foreach (KeyValuePair<Guid, ProjectInfo> infoPair in websiteProjectInfos) {
423 ProjectInfo info = infoPair.Value;
424 string projectGuid = infoPair.Key.ToString ();
426 ProjectSection section;
427 if (!info.ProjectSections.TryGetValue ("WebsiteProperties", out section)) {
428 RaiseWarning (0, String.Format ("Website project '{0}' does not have the required project section: WebsiteProperties. Ignoring project.", info.Name));
429 return;
432 //parse project references
433 string [] ref_guids = null;
434 string references;
435 if (section.Properties.TryGetValue ("ProjectReferences", out references)) {
436 ref_guids = references.Split (new char [] {';'}, StringSplitOptions.RemoveEmptyEntries);
437 for (int i = 0; i < ref_guids.Length; i ++) {
438 // "{guid}|foo.dll"
439 ref_guids [i] = ref_guids [i].Split ('|') [0];
441 Guid r_guid = new Guid (ref_guids [i]);
442 ProjectInfo ref_info;
443 if (projectInfos.TryGetValue (r_guid, out ref_info))
444 // ignore if not found
445 info.Dependencies [r_guid] = ref_info;
449 foreach (KeyValuePair<string, string> pair in section.Properties) {
450 //looking for -- ConfigName.AspNetCompiler.PropName
451 string [] parts = pair.Key.Split ('.');
452 if (parts.Length != 3 || String.Compare (parts [1], "AspNetCompiler") != 0)
453 continue;
455 string config = parts [0];
456 string propertyName = parts [2];
458 BuildPropertyGroup bpg;
459 if (!propertyGroupByConfig.TryGetValue (config, out bpg)) {
460 bpg = p.AddNewPropertyGroup (true);
461 bpg.Condition = String.Format (" '$(AspNetConfiguration)' == '{0}' ", config);
462 propertyGroupByConfig [config] = bpg;
465 bpg.AddNewProperty (String.Format ("Project_{0}_AspNet{1}", projectGuid, propertyName),
466 pair.Value);
468 if (!info.AspNetConfigurations.Contains (config))
469 info.AspNetConfigurations.Add (config);
474 // For WebSite projects
475 // The main "Build" target:
476 // 1. builds all non-website projects
477 // 2. calls target for website project
478 // - gets target path for the referenced projects
479 // - Resolves dependencies, satellites etc for the
480 // referenced project assemblies, and copies them
481 // to bin/ folder
482 void AddWebsiteTargets (Project p, Dictionary<Guid, ProjectInfo> websiteProjectInfos,
483 Dictionary<Guid, ProjectInfo> projectInfos, List<ProjectInfo>[] infosByLevel,
484 List<TargetInfo> solutionTargets)
486 foreach (ProjectInfo w_info in websiteProjectInfos.Values) {
487 // gets a linear list of dependencies
488 List<ProjectInfo> depInfos = new List<ProjectInfo> ();
489 foreach (List<ProjectInfo> pinfos in infosByLevel) {
490 foreach (ProjectInfo pinfo in pinfos)
491 if (w_info.Dependencies.ContainsKey (pinfo.Guid))
492 depInfos.Add (pinfo);
495 foreach (string buildTarget in new string [] {"Build", "Rebuild"})
496 AddWebsiteTarget (p, w_info, projectInfos, depInfos, solutionTargets, buildTarget);
498 // clean/publish are not supported for website projects
499 foreach (string buildTarget in new string [] {"Clean", "Publish"})
500 AddWebsiteUnsupportedTarget (p, w_info, depInfos, buildTarget);
504 void AddWebsiteTarget (Project p, ProjectInfo webProjectInfo,
505 Dictionary<Guid, ProjectInfo> projectInfos, List<ProjectInfo> depInfos,
506 List<TargetInfo> solutionTargets, string buildTarget)
508 string w_guid = webProjectInfo.Guid.ToString ().ToUpper ();
510 Target target = p.Targets.AddNewTarget (GetTargetNameForProject (webProjectInfo.Name, buildTarget));
511 target.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
512 target.DependsOnTargets = GetWebsiteDependsOnTarget (depInfos, buildTarget);
514 // this item collects all the references
515 string final_ref_item = String.Format ("Project_{0}_References{1}", w_guid,
516 buildTarget != "Build" ? "_" + buildTarget : String.Empty);
518 foreach (TargetInfo targetInfo in solutionTargets) {
519 int ref_num = 0;
520 foreach (ProjectInfo depInfo in depInfos) {
521 TargetInfo projectTargetInfo;
522 if (!depInfo.TargetMap.TryGetValue (targetInfo, out projectTargetInfo))
523 // Ignore, no config, so no target path
524 continue;
526 // GetTargetPath from the referenced project
527 AddWebsiteMSBuildTaskForReference (target, depInfo, projectTargetInfo, targetInfo,
528 final_ref_item, ref_num);
529 ref_num ++;
533 // resolve the references
534 AddWebsiteResolveAndCopyReferencesTasks (target, webProjectInfo, final_ref_item, w_guid);
537 // emits the MSBuild task to GetTargetPath for the referenced project
538 void AddWebsiteMSBuildTaskForReference (Target target, ProjectInfo depInfo, TargetInfo projectTargetInfo,
539 TargetInfo solutionTargetInfo, string final_ref_item, int ref_num)
541 BuildTask task = target.AddNewTask ("MSBuild");
542 task.SetParameterValue ("Projects", depInfo.FileName);
543 task.SetParameterValue ("Targets", "GetTargetPath");
545 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));
546 task.Condition = string.Format (" ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ", solutionTargetInfo.Configuration, solutionTargetInfo.Platform);
548 string ref_item = String.Format ("{0}_{1}",
549 final_ref_item, ref_num);
551 task.AddOutputItem ("TargetOutputs", ref_item);
553 task = target.AddNewTask ("CreateItem");
554 task.SetParameterValue ("Include", String.Format ("@({0})", ref_item));
555 task.SetParameterValue ("AdditionalMetadata", String.Format ("Guid={{{0}}}",
556 depInfo.Guid.ToString ().ToUpper ()));
557 task.AddOutputItem ("Include", final_ref_item);
560 void AddWebsiteResolveAndCopyReferencesTasks (Target target, ProjectInfo webProjectInfo,
561 string final_ref_item, string w_guid)
563 BuildTask task = target.AddNewTask ("ResolveAssemblyReference");
564 task.SetParameterValue ("Assemblies", String.Format ("@({0}->'%(FullPath)')", final_ref_item));
565 task.SetParameterValue ("TargetFrameworkDirectories", "$(TargetFrameworkPath)");
566 task.SetParameterValue ("SearchPaths", "{RawFileName};{TargetFrameworkDirectory};{GAC}");
567 task.SetParameterValue ("FindDependencies", "true");
568 task.SetParameterValue ("FindSatellites", "true");
569 task.SetParameterValue ("FindRelatedFiles", "true");
570 task.Condition = String.Format ("Exists ('%({0}.Identity)')", final_ref_item);
572 string copylocal_item = String.Format ("{0}_CopyLocalFiles", final_ref_item);
573 task.AddOutputItem ("CopyLocalFiles", copylocal_item);
575 // Copy the references
576 task = target.AddNewTask ("Copy");
577 task.SetParameterValue ("SourceFiles", String.Format ("@({0})", copylocal_item));
578 task.SetParameterValue ("DestinationFiles", String.Format (
579 "@({0}->'$(Project_{1}_AspNetPhysicalPath)\\Bin\\%(DestinationSubDirectory)%(Filename)%(Extension)')",
580 copylocal_item, w_guid));
582 // AspNetConfiguration, is config for the website project, useful
583 // for overriding from command line
584 StringBuilder cond = new StringBuilder ();
585 foreach (string config in webProjectInfo.AspNetConfigurations) {
586 if (cond.Length > 0)
587 cond.Append (" or ");
588 cond.AppendFormat (" ('$(AspNetConfiguration)' == '{0}') ", config);
590 task.Condition = cond.ToString ();
592 task = target.AddNewTask ("Message");
593 cond = new StringBuilder ();
594 foreach (string config in webProjectInfo.AspNetConfigurations) {
595 if (cond.Length > 0)
596 cond.Append (" and ");
597 cond.AppendFormat (" ('$(AspNetConfiguration)' != '{0}') ", config);
599 task.Condition = cond.ToString ();
600 task.SetParameterValue ("Text", "Skipping as the '$(AspNetConfiguration)' configuration is " +
601 "not supported by this website project.");
604 void AddWebsiteUnsupportedTarget (Project p, ProjectInfo webProjectInfo, List<ProjectInfo> depInfos,
605 string buildTarget)
607 Target target = p.Targets.AddNewTarget (GetTargetNameForProject (webProjectInfo.Name, buildTarget));
608 target.DependsOnTargets = GetWebsiteDependsOnTarget (depInfos, buildTarget);
610 BuildTask task = target.AddNewTask ("Message");
611 task.SetParameterValue ("Text", String.Format (
612 "Target '{0}' not support for website projects", buildTarget));
615 string GetWebsiteDependsOnTarget (List<ProjectInfo> depInfos, string buildTarget)
617 StringBuilder deps = new StringBuilder ();
618 foreach (ProjectInfo pinfo in depInfos) {
619 if (deps.Length > 0)
620 deps.Append (";");
621 deps.Append (GetTargetNameForProject (pinfo.Name, buildTarget));
623 deps.Append (";GetFrameworkPath");
624 return deps.ToString ();
627 void AddGetFrameworkPathTarget (Project p)
629 Target t = p.Targets.AddNewTarget ("GetFrameworkPath");
630 BuildTask task = t.AddNewTask ("GetFrameworkPath");
631 task.AddOutputProperty ("Path", "TargetFrameworkPath");
634 void AddValidateSolutionConfiguration (Project p)
636 Target t = p.Targets.AddNewTarget ("ValidateSolutionConfiguration");
637 BuildTask task = t.AddNewTask ("Error");
638 task.SetParameterValue ("Text", "Invalid solution configuration and platform: \"$(Configuration)|$(Platform)\".");
639 task.Condition = "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' != 'true')";
640 task = t.AddNewTask ("Warning");
641 task.SetParameterValue ("Text", "Invalid solution configuration and platform: \"$(Configuration)|$(Platform)\".");
642 task.Condition = "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' == 'true')";
643 task = t.AddNewTask ("Message");
644 task.SetParameterValue ("Text", "Building solution configuration \"$(Configuration)|$(Platform)\".");
645 task.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
648 void AddProjectTargets (Project p, List<TargetInfo> solutionTargets, Dictionary<Guid, ProjectInfo> projectInfos)
650 foreach (KeyValuePair<Guid, ProjectInfo> projectInfo in projectInfos) {
651 ProjectInfo project = projectInfo.Value;
652 foreach (string buildTarget in buildTargets) {
653 string target_name = GetTargetNameForProject (project.Name, buildTarget);
654 Target target = p.Targets.AddNewTarget (target_name);
655 target.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
657 if (project.Dependencies.Count > 0) {
658 StringBuilder dependencies = new StringBuilder ();
659 foreach (ProjectInfo dependentInfo in project.Dependencies.Values) {
660 if (dependencies.Length > 0)
661 dependencies.Append (";");
662 if (IsBuildTargetName (dependentInfo.Name))
663 dependencies.Append ("Solution:");
664 dependencies.Append (dependentInfo.Name);
665 if (buildTarget != "Build")
666 dependencies.Append (":" + buildTarget);
668 target.DependsOnTargets = dependencies.ToString ();
671 foreach (TargetInfo targetInfo in solutionTargets) {
672 BuildTask task = null;
673 TargetInfo projectTargetInfo;
674 if (!project.TargetMap.TryGetValue (targetInfo, out projectTargetInfo)) {
675 AddWarningForMissingProjectConfiguration (target, targetInfo.Configuration,
676 targetInfo.Platform, project.Name);
677 continue;
679 if (projectTargetInfo.Build) {
680 task = target.AddNewTask ("MSBuild");
681 task.SetParameterValue ("Projects", project.FileName);
683 if (buildTarget != "Build")
684 task.SetParameterValue ("Targets", buildTarget);
685 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));
686 } else {
687 task = target.AddNewTask ("Message");
688 task.SetParameterValue ("Text", string.Format ("Project \"{0}\" is disabled for solution configuration \"{1}|{2}\".", project.Name, targetInfo.Configuration, targetInfo.Platform));
690 task.Condition = string.Format (" ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ", targetInfo.Configuration, targetInfo.Platform);
697 string GetTargetNameForProject (string projectName, string buildTarget)
699 //FIXME: hack
700 projectName = projectName.Replace ("\\", "/").Replace (".", "_");
701 string target_name = projectName +
702 (buildTarget == "Build" ? string.Empty : ":" + buildTarget);
704 if (IsBuildTargetName (projectName))
705 target_name = "Solution:" + target_name;
707 return target_name;
710 bool IsBuildTargetName (string name)
712 foreach (string tgt in buildTargets)
713 if (name == tgt)
714 return true;
715 return false;
718 // returns number of levels
719 int AddBuildLevels (Project p, List<TargetInfo> solutionTargets, Dictionary<Guid, ProjectInfo> projectInfos,
720 ref List<ProjectInfo>[] infosByLevel)
722 infosByLevel = TopologicalSort<ProjectInfo> (projectInfos.Values);
724 foreach (TargetInfo targetInfo in solutionTargets) {
725 BuildItemGroup big = p.AddNewItemGroup ();
726 big.Condition = String.Format (" ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ",
727 targetInfo.Configuration, targetInfo.Platform);
729 //FIXME: every level has projects that can be built in parallel.
730 // levels are ordered on the basis of the dependency graph
732 for (int i = 0; i < infosByLevel.Length; i ++) {
733 string build_level = String.Format ("BuildLevel{0}", i);
734 string skip_level = String.Format ("SkipLevel{0}", i);
735 string missing_level = String.Format ("MissingConfigLevel{0}", i);
737 foreach (ProjectInfo projectInfo in infosByLevel [i]) {
738 TargetInfo projectTargetInfo;
739 if (!projectInfo.TargetMap.TryGetValue (targetInfo, out projectTargetInfo)) {
740 // missing project config
741 big.AddNewItem (missing_level, projectInfo.Name);
742 continue;
745 if (projectTargetInfo.Build) {
746 BuildItem item = big.AddNewItem (build_level, projectInfo.FileName);
747 item.SetMetadata ("Configuration", projectTargetInfo.Configuration);
748 item.SetMetadata ("Platform", projectTargetInfo.Platform);
749 } else {
750 // build disabled
751 big.AddNewItem (skip_level, projectInfo.Name);
757 return infosByLevel.Length;
760 void AddSolutionTargets (Project p, int num_levels, IEnumerable<ProjectInfo> websiteProjectInfos)
762 foreach (string buildTarget in buildTargets) {
763 Target t = p.Targets.AddNewTarget (buildTarget);
764 t.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
766 BuildTask task = null;
767 for (int i = 0; i < num_levels; i ++) {
768 string level_str = String.Format ("BuildLevel{0}", i);
769 task = t.AddNewTask ("MSBuild");
770 task.SetParameterValue ("Condition", String.Format ("'@({0})' != ''", level_str));
771 task.SetParameterValue ("Projects", String.Format ("@({0})", level_str));
772 task.SetParameterValue ("Properties",
773 string.Format ("Configuration=%(Configuration); Platform=%(Platform); BuildingSolutionFile=true; CurrentSolutionConfigurationContents=$(CurrentSolutionConfigurationContents); SolutionDir=$(SolutionDir); SolutionExt=$(SolutionExt); SolutionFileName=$(SolutionFileName); SolutionName=$(SolutionName); SolutionPath=$(SolutionPath)"));
774 if (buildTarget != "Build")
775 task.SetParameterValue ("Targets", buildTarget);
776 //FIXME: change this to BuildInParallel=true, when parallel
777 // build support gets added
778 task.SetParameterValue ("RunEachTargetSeparately", "true");
780 level_str = String.Format ("SkipLevel{0}", i);
781 task = t.AddNewTask ("Message");
782 task.Condition = String.Format ("'@({0})' != ''", level_str);
783 task.SetParameterValue ("Text",
784 String.Format ("The project '%({0}.Identity)' is disabled for solution " +
785 "configuration '$(Configuration)|$(Platform)'.", level_str));
787 level_str = String.Format ("MissingConfigLevel{0}", i);
788 task = t.AddNewTask ("Warning");
789 task.Condition = String.Format ("'@({0})' != ''", level_str);
790 task.SetParameterValue ("Text",
791 String.Format ("The project configuration for project '%({0}.Identity)' " +
792 "corresponding to the solution configuration " +
793 "'$(Configuration)|$(Platform)' was not found.", level_str));
796 // "build" website projects also
797 StringBuilder w_targets = new StringBuilder ();
798 foreach (ProjectInfo info in websiteProjectInfos) {
799 if (w_targets.Length > 0)
800 w_targets.Append (";");
801 w_targets.Append (GetTargetNameForProject (info.Name, buildTarget));
804 task = t.AddNewTask ("CallTarget");
805 task.SetParameterValue ("Targets", w_targets.ToString ());
806 task.SetParameterValue ("RunEachTargetSeparately", "true");
810 // Sorts the ProjectInfo dependency graph, to obtain
811 // a series of build levels with projects. Projects
812 // in each level can be run parallel (no inter-dependency).
813 static List<T>[] TopologicalSort<T> (IEnumerable<T> items) where T: ProjectInfo
815 IList<T> allItems;
816 allItems = items as IList<T>;
817 if (allItems == null)
818 allItems = new List<T> (items);
820 bool[] inserted = new bool[allItems.Count];
821 bool[] triedToInsert = new bool[allItems.Count];
822 int[] levels = new int [allItems.Count];
824 int maxdepth = 0;
825 for (int i = 0; i < allItems.Count; ++i) {
826 int d = Insert<T> (i, allItems, levels, inserted, triedToInsert);
827 if (d > maxdepth)
828 maxdepth = d;
831 // Separate out the project infos by build level
832 List<T>[] infosByLevel = new List<T>[maxdepth];
833 for (int i = 0; i < levels.Length; i ++) {
834 int level = levels [i] - 1;
835 if (infosByLevel [level] == null)
836 infosByLevel [level] = new List<T> ();
838 infosByLevel [level].Add (allItems [i]);
841 return infosByLevel;
844 // returns level# for the project
845 static int Insert<T> (int index, IList<T> allItems, int[] levels, bool[] inserted, bool[] triedToInsert)
846 where T: ProjectInfo
848 if (inserted [index])
849 return levels [index];
851 if (triedToInsert[index])
852 throw new InvalidOperationException (String.Format (
853 "Cyclic dependency involving project {0} found in the project dependency graph",
854 allItems [index].Name));
856 triedToInsert[index] = true;
857 ProjectInfo insertItem = allItems[index];
859 int maxdepth = 0;
860 foreach (ProjectInfo dependency in insertItem.Dependencies.Values) {
861 for (int j = 0; j < allItems.Count; ++j) {
862 ProjectInfo checkItem = allItems [j];
863 if (dependency.FileName == checkItem.FileName) {
864 int d = Insert (j, allItems, levels, inserted, triedToInsert);
865 maxdepth = d > maxdepth ? d : maxdepth;
866 break;
870 levels [index] = maxdepth + 1;
871 inserted [index] = true;
873 return levels [index];
878 #endif