d444f0caa4
Former-commit-id: a99f46acaeba3ab496c7afc02c29b839e30a0d0b
1039 lines
43 KiB
C#
1039 lines
43 KiB
C#
//
|
|
// SolutionParser.cs: Generates a project file from a solution file.
|
|
//
|
|
// Author:
|
|
// Jonathan Chambers (joncham@gmail.com)
|
|
// Ankit Jain <jankit@novell.com>
|
|
// Lluis Sanchez Gual <lluis@novell.com>
|
|
//
|
|
// (C) 2009 Jonathan Chambers
|
|
// Copyright 2008, 2009 Novell, Inc (http://www.novell.com)
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining
|
|
// a copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
|
// permit persons to whom the Software is furnished to do so, subject to
|
|
// the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be
|
|
// included in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.IO;
|
|
using Microsoft.Build.BuildEngine;
|
|
|
|
namespace Mono.XBuild.CommandLine {
|
|
class ProjectInfo {
|
|
public string Name;
|
|
public string FileName;
|
|
public Guid Guid;
|
|
|
|
public ProjectInfo (string name, string fileName)
|
|
{
|
|
Name = name;
|
|
FileName = fileName;
|
|
}
|
|
|
|
public Dictionary<TargetInfo, TargetInfo> TargetMap = new Dictionary<TargetInfo, TargetInfo> ();
|
|
public Dictionary<Guid, ProjectInfo> Dependencies = new Dictionary<Guid, ProjectInfo> ();
|
|
public Dictionary<string, ProjectSection> ProjectSections = new Dictionary<string, ProjectSection> ();
|
|
public List<string> AspNetConfigurations = new List<string> ();
|
|
}
|
|
|
|
class ProjectSection {
|
|
public string Name;
|
|
public Dictionary<string, string> Properties = new Dictionary<string, string> ();
|
|
|
|
public ProjectSection (string name)
|
|
{
|
|
Name = name;
|
|
}
|
|
}
|
|
|
|
struct TargetInfo {
|
|
public string Configuration;
|
|
public string Platform;
|
|
public bool Build;
|
|
|
|
public TargetInfo (string configuration, string platform)
|
|
: this (configuration, platform, false)
|
|
{
|
|
}
|
|
|
|
public TargetInfo (string configuration, string platform, bool build)
|
|
{
|
|
Configuration = configuration;
|
|
Platform = platform;
|
|
Build = build;
|
|
}
|
|
}
|
|
|
|
|
|
internal delegate void RaiseWarningHandler (int errorNumber, string message);
|
|
|
|
class SolutionParser {
|
|
static string[] buildTargets = new string[] { "Build", "Clean", "Rebuild", "Publish" };
|
|
|
|
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}}";
|
|
|
|
static Regex slnVersionRegex = new Regex (@"Microsoft Visual Studio Solution File, Format Version (\d?\d.\d\d)");
|
|
static Regex projectRegex = new Regex ("Project\\(\"(" + guidExpression + ")\"\\) = \"(.*?)\", \"(.*?)\", \"(" + guidExpression + ")\"(\\s*?)((\\s*?)ProjectSection\\((.*?)\\) = (.*?)EndProjectSection(\\s*?))*(\\s*?)(EndProject)?", RegexOptions.Singleline);
|
|
static Regex projectDependenciesRegex = new Regex ("ProjectSection\\((.*?)\\) = \\w*(.*?)EndProjectSection", RegexOptions.Singleline);
|
|
static Regex projectDependencyRegex = new Regex ("\\s*(" + guidExpression + ") = (" + guidExpression + ")");
|
|
static Regex projectSectionPropertiesRegex = new Regex ("\\s*(?<name>.*) = \"(?<value>.*)\"");
|
|
|
|
static Regex globalRegex = new Regex ("Global(.*)EndGlobal", RegexOptions.Singleline);
|
|
static Regex globalSectionRegex = new Regex ("GlobalSection\\((.*?)\\) = \\w*(.*?)EndGlobalSection", RegexOptions.Singleline);
|
|
|
|
static Regex solutionConfigurationRegex = new Regex ("\\s*(.*?)\\|(.*?) = (.*?)\\|(.+)");
|
|
static Regex projectConfigurationActiveCfgRegex = new Regex ("\\s*(" + guidExpression + ")\\.(.+?)\\|(.+?)\\.ActiveCfg = (.+?)\\|(.+)");
|
|
static Regex projectConfigurationBuildRegex = new Regex ("\\s*(" + guidExpression + ")\\.(.*?)\\|(.*?)\\.Build\\.0 = (.*?)\\|(.+)");
|
|
|
|
static string solutionFolderGuid = "{2150E333-8FDC-42A3-9474-1A3956D46DE8}";
|
|
static string vcprojGuid = "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}";
|
|
static string websiteProjectGuid = "{E24C65DC-7377-472B-9ABA-BC803B73C61A}";
|
|
|
|
RaiseWarningHandler RaiseWarning;
|
|
|
|
public void ParseSolution (string file, Project p, RaiseWarningHandler RaiseWarning)
|
|
{
|
|
this.RaiseWarning = RaiseWarning;
|
|
EmitBeforeImports (p, file);
|
|
|
|
AddGeneralSettings (file, p);
|
|
|
|
StreamReader reader = new StreamReader (file);
|
|
|
|
string slnVersion = GetSlnFileVersion (reader);
|
|
|
|
if (slnVersion == "12.00")
|
|
#if XBUILD_14
|
|
p.DefaultToolsVersion = "14.0";
|
|
#elif XBUILD_12
|
|
p.DefaultToolsVersion = "12.0";
|
|
#else
|
|
p.DefaultToolsVersion = "4.0";
|
|
#endif
|
|
else if (slnVersion == "11.00")
|
|
p.DefaultToolsVersion = "4.0";
|
|
else if (slnVersion == "10.00")
|
|
p.DefaultToolsVersion = "3.5";
|
|
else
|
|
p.DefaultToolsVersion = "2.0";
|
|
|
|
string line = reader.ReadToEnd ();
|
|
line = line.Replace ("\r\n", "\n");
|
|
string solutionDir = Path.GetDirectoryName (file);
|
|
|
|
List<TargetInfo> solutionTargets = new List<TargetInfo> ();
|
|
Dictionary<Guid, ProjectInfo> projectInfos = new Dictionary<Guid, ProjectInfo> ();
|
|
Dictionary<Guid, ProjectInfo> websiteProjectInfos = new Dictionary<Guid, ProjectInfo> ();
|
|
List<ProjectInfo>[] infosByLevel = null;
|
|
Dictionary<Guid, ProjectInfo> unsupportedProjectInfos = new Dictionary<Guid, ProjectInfo> ();
|
|
|
|
Match m = projectRegex.Match (line);
|
|
while (m.Success) {
|
|
ProjectInfo projectInfo = new ProjectInfo (m.Groups[2].Value,
|
|
Path.GetFullPath (Path.Combine (solutionDir,
|
|
m.Groups [3].Value.Replace ('\\', Path.DirectorySeparatorChar))));
|
|
if (String.Compare (m.Groups [1].Value, solutionFolderGuid,
|
|
StringComparison.InvariantCultureIgnoreCase) == 0) {
|
|
// Ignore solution folders
|
|
m = m.NextMatch ();
|
|
continue;
|
|
}
|
|
|
|
projectInfo.Guid = new Guid (m.Groups [4].Value);
|
|
|
|
if (String.Compare (m.Groups [1].Value, vcprojGuid,
|
|
StringComparison.InvariantCultureIgnoreCase) == 0) {
|
|
// Ignore vcproj
|
|
RaiseWarning (0, string.Format("Ignoring vcproj '{0}'.", projectInfo.Name));
|
|
|
|
unsupportedProjectInfos [projectInfo.Guid] = projectInfo;
|
|
m = m.NextMatch ();
|
|
continue;
|
|
}
|
|
|
|
if (String.Compare (m.Groups [1].Value, websiteProjectGuid,
|
|
StringComparison.InvariantCultureIgnoreCase) == 0)
|
|
websiteProjectInfos.Add (new Guid (m.Groups[4].Value), projectInfo);
|
|
else
|
|
projectInfos.Add (projectInfo.Guid, projectInfo);
|
|
|
|
Match projectSectionMatch = projectDependenciesRegex.Match (m.Groups[6].Value);
|
|
while (projectSectionMatch.Success) {
|
|
string section_name = projectSectionMatch.Groups [1].Value;
|
|
if (String.Compare (section_name, "ProjectDependencies") == 0) {
|
|
Match projectDependencyMatch = projectDependencyRegex.Match (projectSectionMatch.Value);
|
|
while (projectDependencyMatch.Success) {
|
|
// we might not have projectInfo available right now, so
|
|
// set it to null, and fill it in later
|
|
projectInfo.Dependencies [new Guid (projectDependencyMatch.Groups[1].Value)] = null;
|
|
projectDependencyMatch = projectDependencyMatch.NextMatch ();
|
|
}
|
|
} else {
|
|
ProjectSection section = new ProjectSection (section_name);
|
|
Match propertiesMatch = projectSectionPropertiesRegex.Match (
|
|
projectSectionMatch.Groups [2].Value);
|
|
while (propertiesMatch.Success) {
|
|
section.Properties [propertiesMatch.Groups ["name"].Value] =
|
|
propertiesMatch.Groups ["value"].Value;
|
|
|
|
propertiesMatch = propertiesMatch.NextMatch ();
|
|
}
|
|
|
|
projectInfo.ProjectSections [section_name] = section;
|
|
}
|
|
projectSectionMatch = projectSectionMatch.NextMatch ();
|
|
}
|
|
m = m.NextMatch ();
|
|
}
|
|
|
|
foreach (ProjectInfo projectInfo in projectInfos.Values) {
|
|
string filename = projectInfo.FileName;
|
|
string projectDir = Path.GetDirectoryName (filename);
|
|
|
|
if (!File.Exists (filename)) {
|
|
RaiseWarning (0, String.Format ("Project file {0} referenced in the solution file, " +
|
|
"not found. Ignoring.", filename));
|
|
continue;
|
|
}
|
|
|
|
Project currentProject = p.ParentEngine.CreateNewProject ();
|
|
try {
|
|
currentProject.Load (filename, ProjectLoadSettings.IgnoreMissingImports);
|
|
} catch (InvalidProjectFileException e) {
|
|
RaiseWarning (0, e.Message);
|
|
continue;
|
|
}
|
|
|
|
foreach (BuildItem bi in currentProject.GetEvaluatedItemsByName ("ProjectReference")) {
|
|
ProjectInfo info = null;
|
|
string projectReferenceGuid = bi.GetEvaluatedMetadata ("Project");
|
|
bool hasGuid = !String.IsNullOrEmpty (projectReferenceGuid);
|
|
|
|
// try to resolve the ProjectReference by GUID
|
|
// and fallback to project filename
|
|
|
|
if (hasGuid) {
|
|
Guid guid = new Guid (projectReferenceGuid);
|
|
projectInfos.TryGetValue (guid, out info);
|
|
if (info == null && unsupportedProjectInfos.TryGetValue (guid, out info)) {
|
|
RaiseWarning (0, String.Format (
|
|
"{0}: ProjectReference '{1}' is of an unsupported type. Ignoring.",
|
|
filename, bi.Include));
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (info == null || !hasGuid) {
|
|
// Project not found by guid or guid not available
|
|
// Try to find by project file
|
|
|
|
string fullpath = Path.GetFullPath (Path.Combine (projectDir, bi.Include.Replace ('\\', Path.DirectorySeparatorChar)));
|
|
info = projectInfos.Values.FirstOrDefault (pi => pi.FileName == fullpath);
|
|
|
|
if (info == null) {
|
|
if (unsupportedProjectInfos.Values.Any (pi => pi.FileName == fullpath))
|
|
RaiseWarning (0, String.Format (
|
|
"{0}: ProjectReference '{1}' is of an unsupported type. Ignoring.",
|
|
filename, bi.Include));
|
|
else
|
|
RaiseWarning (0, String.Format (
|
|
"{0}: ProjectReference '{1}' not found, neither by guid '{2}' nor by project file name '{3}'.",
|
|
filename, bi.Include, projectReferenceGuid.Replace ("{", "").Replace ("}", ""), fullpath));
|
|
}
|
|
|
|
}
|
|
|
|
if (info != null)
|
|
projectInfo.Dependencies [info.Guid] = info;
|
|
}
|
|
|
|
// unload the project after reading info from it
|
|
// it'll be reloaded with proper context when building the solution
|
|
p.ParentEngine.UnloadProject (currentProject);
|
|
}
|
|
|
|
// fill in the project info for deps found in the .sln file
|
|
foreach (ProjectInfo projectInfo in projectInfos.Values) {
|
|
List<Guid> missingInfos = new List<Guid> ();
|
|
foreach (KeyValuePair<Guid, ProjectInfo> dependency in projectInfo.Dependencies) {
|
|
if (dependency.Value == null)
|
|
missingInfos.Add (dependency.Key);
|
|
}
|
|
|
|
foreach (Guid guid in missingInfos) {
|
|
ProjectInfo info;
|
|
if (projectInfos.TryGetValue (guid, out info))
|
|
projectInfo.Dependencies [guid] = info;
|
|
else
|
|
projectInfo.Dependencies.Remove (guid);
|
|
}
|
|
}
|
|
|
|
Match globalMatch = globalRegex.Match (line);
|
|
Match globalSectionMatch = globalSectionRegex.Match (globalMatch.Groups[1].Value);
|
|
while (globalSectionMatch.Success) {
|
|
string sectionType = globalSectionMatch.Groups[1].Value;
|
|
switch (sectionType) {
|
|
case "SolutionConfigurationPlatforms":
|
|
ParseSolutionConfigurationPlatforms (globalSectionMatch.Groups[2].Value, solutionTargets);
|
|
break;
|
|
case "ProjectConfigurationPlatforms":
|
|
ParseProjectConfigurationPlatforms (globalSectionMatch.Groups[2].Value,
|
|
projectInfos, websiteProjectInfos);
|
|
break;
|
|
case "SolutionProperties":
|
|
ParseSolutionProperties (globalSectionMatch.Groups[2].Value);
|
|
break;
|
|
case "NestedProjects":
|
|
break;
|
|
case "MonoDevelopProperties":
|
|
break;
|
|
default:
|
|
RaiseWarning (0, string.Format("Don't know how to handle GlobalSection {0}, Ignoring.", sectionType));
|
|
break;
|
|
}
|
|
globalSectionMatch = globalSectionMatch.NextMatch ();
|
|
}
|
|
|
|
int num_levels = AddBuildLevels (p, solutionTargets, projectInfos, ref infosByLevel);
|
|
|
|
AddCurrentSolutionConfigurationContents (p, solutionTargets, projectInfos, websiteProjectInfos);
|
|
AddProjectReferences (p, projectInfos);
|
|
AddWebsiteProperties (p, websiteProjectInfos, projectInfos);
|
|
AddValidateSolutionConfiguration (p);
|
|
|
|
EmitAfterImports (p, file);
|
|
|
|
AddGetFrameworkPathTarget (p);
|
|
AddWebsiteTargets (p, websiteProjectInfos, projectInfos, infosByLevel, solutionTargets);
|
|
AddProjectTargets (p, solutionTargets, projectInfos);
|
|
AddSolutionTargets (p, num_levels, websiteProjectInfos.Values);
|
|
}
|
|
|
|
string GetSlnFileVersion (StreamReader reader)
|
|
{
|
|
string strInput = null;
|
|
Match match;
|
|
|
|
strInput = reader.ReadLine();
|
|
if (strInput == null)
|
|
return null;
|
|
|
|
match = slnVersionRegex.Match(strInput);
|
|
if (!match.Success) {
|
|
strInput = reader.ReadLine();
|
|
if (strInput == null)
|
|
return null;
|
|
match = slnVersionRegex.Match (strInput);
|
|
}
|
|
|
|
if (match.Success)
|
|
return match.Groups[1].Value;
|
|
|
|
return null;
|
|
}
|
|
|
|
void EmitBeforeImports (Project p, string file)
|
|
{
|
|
p.AddNewImport ("$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\SolutionFile\\ImportBefore\\*",
|
|
"'$(ImportByWildcardBeforeSolution)' != 'false' and " +
|
|
"Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\SolutionFile\\ImportBefore')");
|
|
|
|
string before_filename = Path.Combine (Path.GetDirectoryName (file), "before." + Path.GetFileName (file) + ".targets");
|
|
p.AddNewImport (before_filename, String.Format ("Exists ('{0}')", before_filename));
|
|
}
|
|
|
|
void EmitAfterImports (Project p, string file)
|
|
{
|
|
p.AddNewImport ("$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\SolutionFile\\ImportAfter\\*",
|
|
"'$(ImportByWildcardAfterSolution)' != 'false' and " +
|
|
"Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\SolutionFile\\ImportAfter')");
|
|
|
|
string after_filename = Path.Combine (Path.GetDirectoryName (file), "after." + Path.GetFileName (file) + ".targets");
|
|
p.AddNewImport (after_filename, String.Format ("Exists ('{0}')", after_filename));
|
|
}
|
|
|
|
void AddGeneralSettings (string solutionFile, Project p)
|
|
{
|
|
p.DefaultTargets = "Build";
|
|
p.InitialTargets = "ValidateSolutionConfiguration";
|
|
p.AddNewUsingTaskFromAssemblyName ("CreateTemporaryVCProject", "Microsoft.Build.Tasks, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
|
|
p.AddNewUsingTaskFromAssemblyName ("ResolveVCProjectOutput", "Microsoft.Build.Tasks, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
|
|
|
|
string solutionFilePath = Path.GetFullPath (solutionFile);
|
|
BuildPropertyGroup solutionPropertyGroup = p.AddNewPropertyGroup (true);
|
|
solutionPropertyGroup.AddNewProperty ("SolutionDir", Path.GetDirectoryName (solutionFilePath) + Path.DirectorySeparatorChar);
|
|
solutionPropertyGroup.AddNewProperty ("SolutionExt", Path.GetExtension (solutionFile));
|
|
solutionPropertyGroup.AddNewProperty ("SolutionFileName", Path.GetFileName (solutionFile));
|
|
solutionPropertyGroup.AddNewProperty ("SolutionName", Path.GetFileNameWithoutExtension (solutionFile));
|
|
solutionPropertyGroup.AddNewProperty ("SolutionPath", solutionFilePath);
|
|
}
|
|
|
|
void ParseSolutionConfigurationPlatforms (string section, List<TargetInfo> solutionTargets)
|
|
{
|
|
Match solutionConfigurationPlatform = solutionConfigurationRegex.Match (section);
|
|
while (solutionConfigurationPlatform.Success) {
|
|
string solutionConfiguration = solutionConfigurationPlatform.Groups[1].Value;
|
|
string solutionPlatform = solutionConfigurationPlatform.Groups[2].Value;
|
|
solutionTargets.Add (new TargetInfo (solutionConfiguration, solutionPlatform));
|
|
solutionConfigurationPlatform = solutionConfigurationPlatform.NextMatch ();
|
|
}
|
|
}
|
|
|
|
// ignores the website projects, in the websiteProjectInfos
|
|
void ParseProjectConfigurationPlatforms (string section, Dictionary<Guid, ProjectInfo> projectInfos,
|
|
Dictionary<Guid, ProjectInfo> websiteProjectInfos)
|
|
{
|
|
List<Guid> missingGuids = new List<Guid> ();
|
|
Match projectConfigurationPlatform = projectConfigurationActiveCfgRegex.Match (section);
|
|
while (projectConfigurationPlatform.Success) {
|
|
Guid guid = new Guid (projectConfigurationPlatform.Groups[1].Value);
|
|
ProjectInfo projectInfo;
|
|
if (!projectInfos.TryGetValue (guid, out projectInfo)) {
|
|
if (!missingGuids.Contains (guid)) {
|
|
if (!websiteProjectInfos.ContainsKey (guid))
|
|
// ignore website projects
|
|
RaiseWarning (0, string.Format("Failed to find project {0}", guid));
|
|
missingGuids.Add (guid);
|
|
}
|
|
projectConfigurationPlatform = projectConfigurationPlatform.NextMatch ();
|
|
continue;
|
|
}
|
|
string solConf = projectConfigurationPlatform.Groups[2].Value;
|
|
string solPlat = projectConfigurationPlatform.Groups[3].Value;
|
|
string projConf = projectConfigurationPlatform.Groups[4].Value;
|
|
string projPlat = projectConfigurationPlatform.Groups[5].Value;
|
|
// hack, what are they doing here?
|
|
if (projPlat == "Any CPU")
|
|
projPlat = "AnyCPU";
|
|
projectInfo.TargetMap.Add (new TargetInfo (solConf, solPlat), new TargetInfo (projConf, projPlat));
|
|
projectConfigurationPlatform = projectConfigurationPlatform.NextMatch ();
|
|
}
|
|
Match projectConfigurationPlatformBuild = projectConfigurationBuildRegex.Match (section);
|
|
while (projectConfigurationPlatformBuild.Success) {
|
|
Guid guid = new Guid (projectConfigurationPlatformBuild.Groups[1].Value);
|
|
ProjectInfo projectInfo;
|
|
if (!projectInfos.TryGetValue (guid, out projectInfo)) {
|
|
if (!missingGuids.Contains (guid)) {
|
|
RaiseWarning (0, string.Format("Failed to find project {0}", guid));
|
|
missingGuids.Add (guid);
|
|
}
|
|
projectConfigurationPlatformBuild = projectConfigurationPlatformBuild.NextMatch ();
|
|
continue;
|
|
}
|
|
string solConf = projectConfigurationPlatformBuild.Groups[2].Value;
|
|
string solPlat = projectConfigurationPlatformBuild.Groups[3].Value;
|
|
string projConf = projectConfigurationPlatformBuild.Groups[4].Value;
|
|
string projPlat = projectConfigurationPlatformBuild.Groups[5].Value;
|
|
// hack, what are they doing here?
|
|
if (projPlat == "Any CPU")
|
|
projPlat = "AnyCPU";
|
|
projectInfo.TargetMap[new TargetInfo (solConf, solPlat)] = new TargetInfo (projConf, projPlat, true);
|
|
projectConfigurationPlatformBuild = projectConfigurationPlatformBuild.NextMatch ();
|
|
}
|
|
}
|
|
|
|
void ParseSolutionProperties (string section)
|
|
{
|
|
}
|
|
|
|
void AddCurrentSolutionConfigurationContents (Project p, List<TargetInfo> solutionTargets,
|
|
Dictionary<Guid, ProjectInfo> projectInfos,
|
|
Dictionary<Guid, ProjectInfo> websiteProjectInfos)
|
|
{
|
|
TargetInfo default_target_info = new TargetInfo ("Debug", "Any CPU");
|
|
if (solutionTargets.Count > 0) {
|
|
bool found = false;
|
|
foreach (TargetInfo tinfo in solutionTargets) {
|
|
if (String.Compare (tinfo.Platform, "Mixed Platforms") == 0) {
|
|
default_target_info = tinfo;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
default_target_info = solutionTargets [0];
|
|
}
|
|
|
|
AddDefaultSolutionConfiguration (p, default_target_info);
|
|
|
|
foreach (TargetInfo solutionTarget in solutionTargets) {
|
|
BuildPropertyGroup platformPropertyGroup = p.AddNewPropertyGroup (false);
|
|
platformPropertyGroup.Condition = string.Format (
|
|
" ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ",
|
|
solutionTarget.Configuration,
|
|
solutionTarget.Platform
|
|
);
|
|
|
|
StringBuilder solutionConfigurationContents = new StringBuilder ();
|
|
solutionConfigurationContents.Append ("<SolutionConfiguration xmlns=\"\">");
|
|
foreach (KeyValuePair<Guid, ProjectInfo> projectInfo in projectInfos) {
|
|
AddProjectConfigurationItems (projectInfo.Key, projectInfo.Value, solutionTarget, solutionConfigurationContents);
|
|
}
|
|
solutionConfigurationContents.Append ("</SolutionConfiguration>");
|
|
|
|
platformPropertyGroup.AddNewProperty ("CurrentSolutionConfigurationContents",
|
|
solutionConfigurationContents.ToString ());
|
|
}
|
|
}
|
|
|
|
void AddProjectReferences (Project p, Dictionary<Guid, ProjectInfo> projectInfos)
|
|
{
|
|
BuildItemGroup big = p.AddNewItemGroup ();
|
|
foreach (KeyValuePair<Guid, ProjectInfo> pair in projectInfos)
|
|
big.AddNewItem ("ProjectReference", pair.Value.FileName);
|
|
}
|
|
|
|
void AddProjectConfigurationItems (Guid guid, ProjectInfo projectInfo, TargetInfo solutionTarget,
|
|
StringBuilder solutionConfigurationContents)
|
|
{
|
|
foreach (KeyValuePair<TargetInfo, TargetInfo> targetInfo in projectInfo.TargetMap) {
|
|
if (solutionTarget.Configuration == targetInfo.Key.Configuration &&
|
|
solutionTarget.Platform == targetInfo.Key.Platform) {
|
|
solutionConfigurationContents.AppendFormat (
|
|
"<ProjectConfiguration Project=\"{0}\" AbsolutePath=\"{1}\">{2}|{3}</ProjectConfiguration>",
|
|
guid.ToString ("B").ToUpper (), projectInfo.FileName, targetInfo.Value.Configuration, targetInfo.Value.Platform);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AddDefaultSolutionConfiguration (Project p, TargetInfo target)
|
|
{
|
|
BuildPropertyGroup configurationPropertyGroup = p.AddNewPropertyGroup (true);
|
|
configurationPropertyGroup.Condition = " '$(Configuration)' == '' ";
|
|
configurationPropertyGroup.AddNewProperty ("Configuration", target.Configuration);
|
|
|
|
BuildPropertyGroup platformPropertyGroup = p.AddNewPropertyGroup (true);
|
|
platformPropertyGroup.Condition = " '$(Platform)' == '' ";
|
|
platformPropertyGroup.AddNewProperty ("Platform", target.Platform);
|
|
|
|
// emit default for AspNetConfiguration also
|
|
BuildPropertyGroup aspNetConfigurationPropertyGroup = p.AddNewPropertyGroup (true);
|
|
aspNetConfigurationPropertyGroup.Condition = " ('$(AspNetConfiguration)' == '') ";
|
|
aspNetConfigurationPropertyGroup.AddNewProperty ("AspNetConfiguration", "$(Configuration)");
|
|
}
|
|
|
|
void AddWarningForMissingProjectConfiguration (Target target, string slnConfig, string slnPlatform, string projectName)
|
|
{
|
|
BuildTask task = target.AddNewTask ("Warning");
|
|
task.SetParameterValue ("Text",
|
|
String.Format ("The project configuration for project '{0}' corresponding " +
|
|
"to the solution configuration '{1}|{2}' was not found in the solution file.",
|
|
projectName, slnConfig, slnPlatform));
|
|
task.Condition = String.Format ("('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}')",
|
|
slnConfig, slnPlatform);
|
|
|
|
}
|
|
|
|
// Website project methods
|
|
|
|
void AddWebsiteProperties (Project p, Dictionary<Guid, ProjectInfo> websiteProjectInfos,
|
|
Dictionary<Guid, ProjectInfo> projectInfos)
|
|
{
|
|
var propertyGroupByConfig = new Dictionary<string, BuildPropertyGroup> ();
|
|
foreach (KeyValuePair<Guid, ProjectInfo> infoPair in websiteProjectInfos) {
|
|
ProjectInfo info = infoPair.Value;
|
|
string projectGuid = infoPair.Key.ToString ();
|
|
|
|
ProjectSection section;
|
|
if (!info.ProjectSections.TryGetValue ("WebsiteProperties", out section)) {
|
|
RaiseWarning (0, String.Format ("Website project '{0}' does not have the required project section: WebsiteProperties. Ignoring project.", info.Name));
|
|
return;
|
|
}
|
|
|
|
//parse project references
|
|
string [] ref_guids = null;
|
|
string references;
|
|
if (section.Properties.TryGetValue ("ProjectReferences", out references)) {
|
|
ref_guids = references.Split (new char [] {';'}, StringSplitOptions.RemoveEmptyEntries);
|
|
for (int i = 0; i < ref_guids.Length; i ++) {
|
|
// "{guid}|foo.dll"
|
|
ref_guids [i] = ref_guids [i].Split ('|') [0];
|
|
|
|
Guid r_guid = new Guid (ref_guids [i]);
|
|
ProjectInfo ref_info;
|
|
if (projectInfos.TryGetValue (r_guid, out ref_info))
|
|
// ignore if not found
|
|
info.Dependencies [r_guid] = ref_info;
|
|
}
|
|
}
|
|
|
|
foreach (KeyValuePair<string, string> pair in section.Properties) {
|
|
//looking for -- ConfigName.AspNetCompiler.PropName
|
|
string [] parts = pair.Key.Split ('.');
|
|
if (parts.Length != 3 || String.Compare (parts [1], "AspNetCompiler") != 0)
|
|
continue;
|
|
|
|
string config = parts [0];
|
|
string propertyName = parts [2];
|
|
|
|
BuildPropertyGroup bpg;
|
|
if (!propertyGroupByConfig.TryGetValue (config, out bpg)) {
|
|
bpg = p.AddNewPropertyGroup (true);
|
|
bpg.Condition = String.Format (" '$(AspNetConfiguration)' == '{0}' ", config);
|
|
propertyGroupByConfig [config] = bpg;
|
|
}
|
|
|
|
bpg.AddNewProperty (String.Format ("Project_{0}_AspNet{1}", projectGuid, propertyName),
|
|
pair.Value);
|
|
|
|
if (!info.AspNetConfigurations.Contains (config))
|
|
info.AspNetConfigurations.Add (config);
|
|
}
|
|
}
|
|
}
|
|
|
|
// For WebSite projects
|
|
// The main "Build" target:
|
|
// 1. builds all non-website projects
|
|
// 2. calls target for website project
|
|
// - gets target path for the referenced projects
|
|
// - Resolves dependencies, satellites etc for the
|
|
// referenced project assemblies, and copies them
|
|
// to bin/ folder
|
|
void AddWebsiteTargets (Project p, Dictionary<Guid, ProjectInfo> websiteProjectInfos,
|
|
Dictionary<Guid, ProjectInfo> projectInfos, List<ProjectInfo>[] infosByLevel,
|
|
List<TargetInfo> solutionTargets)
|
|
{
|
|
foreach (ProjectInfo w_info in websiteProjectInfos.Values) {
|
|
// gets a linear list of dependencies
|
|
List<ProjectInfo> depInfos = new List<ProjectInfo> ();
|
|
foreach (List<ProjectInfo> pinfos in infosByLevel) {
|
|
foreach (ProjectInfo pinfo in pinfos)
|
|
if (w_info.Dependencies.ContainsKey (pinfo.Guid))
|
|
depInfos.Add (pinfo);
|
|
}
|
|
|
|
foreach (string buildTarget in new string [] {"Build", "Rebuild"})
|
|
AddWebsiteTarget (p, w_info, projectInfos, depInfos, solutionTargets, buildTarget);
|
|
|
|
// clean/publish are not supported for website projects
|
|
foreach (string buildTarget in new string [] {"Clean", "Publish"})
|
|
AddWebsiteUnsupportedTarget (p, w_info, depInfos, buildTarget);
|
|
}
|
|
}
|
|
|
|
void AddWebsiteTarget (Project p, ProjectInfo webProjectInfo,
|
|
Dictionary<Guid, ProjectInfo> projectInfos, List<ProjectInfo> depInfos,
|
|
List<TargetInfo> solutionTargets, string buildTarget)
|
|
{
|
|
string w_guid = webProjectInfo.Guid.ToString ().ToUpper ();
|
|
|
|
Target target = p.Targets.AddNewTarget (GetTargetNameForProject (webProjectInfo.Name, buildTarget));
|
|
target.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
|
|
target.DependsOnTargets = GetWebsiteDependsOnTarget (depInfos, buildTarget);
|
|
|
|
// this item collects all the references
|
|
string final_ref_item = String.Format ("Project_{0}_References{1}", w_guid,
|
|
buildTarget != "Build" ? "_" + buildTarget : String.Empty);
|
|
|
|
foreach (TargetInfo targetInfo in solutionTargets) {
|
|
int ref_num = 0;
|
|
foreach (ProjectInfo depInfo in depInfos) {
|
|
TargetInfo projectTargetInfo;
|
|
if (!depInfo.TargetMap.TryGetValue (targetInfo, out projectTargetInfo))
|
|
// Ignore, no config, so no target path
|
|
continue;
|
|
|
|
// GetTargetPath from the referenced project
|
|
AddWebsiteMSBuildTaskForReference (target, depInfo, projectTargetInfo, targetInfo,
|
|
final_ref_item, ref_num);
|
|
ref_num ++;
|
|
}
|
|
}
|
|
|
|
// resolve the references
|
|
AddWebsiteResolveAndCopyReferencesTasks (target, webProjectInfo, final_ref_item, w_guid);
|
|
}
|
|
|
|
// emits the MSBuild task to GetTargetPath for the referenced project
|
|
void AddWebsiteMSBuildTaskForReference (Target target, ProjectInfo depInfo, TargetInfo projectTargetInfo,
|
|
TargetInfo solutionTargetInfo, string final_ref_item, int ref_num)
|
|
{
|
|
BuildTask task = target.AddNewTask ("MSBuild");
|
|
task.SetParameterValue ("Projects", depInfo.FileName);
|
|
task.SetParameterValue ("Targets", "GetTargetPath");
|
|
|
|
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));
|
|
task.Condition = string.Format (" ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ", solutionTargetInfo.Configuration, solutionTargetInfo.Platform);
|
|
|
|
string ref_item = String.Format ("{0}_{1}",
|
|
final_ref_item, ref_num);
|
|
|
|
task.AddOutputItem ("TargetOutputs", ref_item);
|
|
|
|
task = target.AddNewTask ("CreateItem");
|
|
task.SetParameterValue ("Include", String.Format ("@({0})", ref_item));
|
|
task.SetParameterValue ("AdditionalMetadata", String.Format ("Guid={{{0}}}",
|
|
depInfo.Guid.ToString ().ToUpper ()));
|
|
task.AddOutputItem ("Include", final_ref_item);
|
|
}
|
|
|
|
void AddWebsiteResolveAndCopyReferencesTasks (Target target, ProjectInfo webProjectInfo,
|
|
string final_ref_item, string w_guid)
|
|
{
|
|
BuildTask task = target.AddNewTask ("ResolveAssemblyReference");
|
|
task.SetParameterValue ("Assemblies", String.Format ("@({0}->'%(FullPath)')", final_ref_item));
|
|
task.SetParameterValue ("TargetFrameworkDirectories", "$(TargetFrameworkPath)");
|
|
task.SetParameterValue ("SearchPaths", "{RawFileName};{TargetFrameworkDirectory};{GAC}");
|
|
task.SetParameterValue ("FindDependencies", "true");
|
|
task.SetParameterValue ("FindSatellites", "true");
|
|
task.SetParameterValue ("FindRelatedFiles", "true");
|
|
task.Condition = String.Format ("Exists ('%({0}.Identity)')", final_ref_item);
|
|
|
|
string copylocal_item = String.Format ("{0}_CopyLocalFiles", final_ref_item);
|
|
task.AddOutputItem ("CopyLocalFiles", copylocal_item);
|
|
|
|
// Copy the references
|
|
task = target.AddNewTask ("Copy");
|
|
task.SetParameterValue ("SourceFiles", String.Format ("@({0})", copylocal_item));
|
|
task.SetParameterValue ("DestinationFiles", String.Format (
|
|
"@({0}->'$(Project_{1}_AspNetPhysicalPath)\\Bin\\%(DestinationSubDirectory)%(Filename)%(Extension)')",
|
|
copylocal_item, w_guid));
|
|
|
|
// AspNetConfiguration, is config for the website project, useful
|
|
// for overriding from command line
|
|
StringBuilder cond = new StringBuilder ();
|
|
foreach (string config in webProjectInfo.AspNetConfigurations) {
|
|
if (cond.Length > 0)
|
|
cond.Append (" or ");
|
|
cond.AppendFormat (" ('$(AspNetConfiguration)' == '{0}') ", config);
|
|
}
|
|
task.Condition = cond.ToString ();
|
|
|
|
task = target.AddNewTask ("Message");
|
|
cond = new StringBuilder ();
|
|
foreach (string config in webProjectInfo.AspNetConfigurations) {
|
|
if (cond.Length > 0)
|
|
cond.Append (" and ");
|
|
cond.AppendFormat (" ('$(AspNetConfiguration)' != '{0}') ", config);
|
|
}
|
|
task.Condition = cond.ToString ();
|
|
task.SetParameterValue ("Text", "Skipping as the '$(AspNetConfiguration)' configuration is " +
|
|
"not supported by this website project.");
|
|
}
|
|
|
|
void AddWebsiteUnsupportedTarget (Project p, ProjectInfo webProjectInfo, List<ProjectInfo> depInfos,
|
|
string buildTarget)
|
|
{
|
|
Target target = p.Targets.AddNewTarget (GetTargetNameForProject (webProjectInfo.Name, buildTarget));
|
|
target.DependsOnTargets = GetWebsiteDependsOnTarget (depInfos, buildTarget);
|
|
|
|
BuildTask task = target.AddNewTask ("Message");
|
|
task.SetParameterValue ("Text", String.Format (
|
|
"Target '{0}' not support for website projects", buildTarget));
|
|
}
|
|
|
|
string GetWebsiteDependsOnTarget (List<ProjectInfo> depInfos, string buildTarget)
|
|
{
|
|
StringBuilder deps = new StringBuilder ();
|
|
foreach (ProjectInfo pinfo in depInfos) {
|
|
if (deps.Length > 0)
|
|
deps.Append (";");
|
|
deps.Append (GetTargetNameForProject (pinfo.Name, buildTarget));
|
|
}
|
|
deps.Append (";GetFrameworkPath");
|
|
return deps.ToString ();
|
|
}
|
|
|
|
void AddGetFrameworkPathTarget (Project p)
|
|
{
|
|
Target t = p.Targets.AddNewTarget ("GetFrameworkPath");
|
|
BuildTask task = t.AddNewTask ("GetFrameworkPath");
|
|
task.AddOutputProperty ("Path", "TargetFrameworkPath");
|
|
}
|
|
|
|
void AddValidateSolutionConfiguration (Project p)
|
|
{
|
|
Target t = p.Targets.AddNewTarget ("ValidateSolutionConfiguration");
|
|
BuildTask task = t.AddNewTask ("Warning");
|
|
task.SetParameterValue ("Text", "On windows, an environment variable 'Platform' is set to MCD sometimes, and this overrides the Platform property" +
|
|
" for xbuild, which could be an invalid Platform for this solution file. And so you are getting the following error." +
|
|
" You could override it by either setting the environment variable to nothing, as\n" +
|
|
" set Platform=\n" +
|
|
"Or explicity specify its value on the command line, as\n" +
|
|
" xbuild Foo.sln /p:Platform=Release");
|
|
task.Condition = "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' != 'true')" +
|
|
" and '$(Platform)' == 'MCD' and '$(OS)' == 'Windows_NT'";
|
|
|
|
task = t.AddNewTask ("Error");
|
|
task.SetParameterValue ("Text", "Invalid solution configuration and platform: \"$(Configuration)|$(Platform)\".");
|
|
task.Condition = "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' != 'true')";
|
|
task = t.AddNewTask ("Warning");
|
|
task.SetParameterValue ("Text", "Invalid solution configuration and platform: \"$(Configuration)|$(Platform)\".");
|
|
task.Condition = "('$(CurrentSolutionConfigurationContents)' == '') and ('$(SkipInvalidConfigurations)' == 'true')";
|
|
task = t.AddNewTask ("Message");
|
|
task.SetParameterValue ("Text", "Building solution configuration \"$(Configuration)|$(Platform)\".");
|
|
task.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
|
|
}
|
|
|
|
void AddProjectTargets (Project p, List<TargetInfo> solutionTargets, Dictionary<Guid, ProjectInfo> projectInfos)
|
|
{
|
|
foreach (KeyValuePair<Guid, ProjectInfo> projectInfo in projectInfos) {
|
|
ProjectInfo project = projectInfo.Value;
|
|
foreach (string buildTarget in buildTargets) {
|
|
string target_name = GetTargetNameForProject (project.Name, buildTarget);
|
|
bool is_build_or_rebuild = buildTarget == "Build" || buildTarget == "Rebuild";
|
|
Target target = p.Targets.AddNewTarget (target_name);
|
|
target.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
|
|
|
|
if (is_build_or_rebuild)
|
|
target.Outputs = "@(CollectedBuildOutput)";
|
|
if (project.Dependencies.Count > 0)
|
|
target.DependsOnTargets = String.Join (";",
|
|
project.Dependencies.Values.Select (
|
|
di => GetTargetNameForProject (di.Name, buildTarget)).ToArray ());
|
|
|
|
foreach (TargetInfo targetInfo in solutionTargets) {
|
|
BuildTask task = null;
|
|
TargetInfo projectTargetInfo;
|
|
if (!project.TargetMap.TryGetValue (targetInfo, out projectTargetInfo)) {
|
|
AddWarningForMissingProjectConfiguration (target, targetInfo.Configuration,
|
|
targetInfo.Platform, project.Name);
|
|
continue;
|
|
}
|
|
if (projectTargetInfo.Build) {
|
|
task = target.AddNewTask ("MSBuild");
|
|
task.SetParameterValue ("Projects", project.FileName);
|
|
task.SetParameterValue ("ToolsVersion", "$(ProjectToolsVersion)");
|
|
if (is_build_or_rebuild)
|
|
task.AddOutputItem ("TargetOutputs", "CollectedBuildOutput");
|
|
|
|
if (buildTarget != "Build")
|
|
task.SetParameterValue ("Targets", buildTarget);
|
|
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));
|
|
} else {
|
|
task = target.AddNewTask ("Message");
|
|
task.SetParameterValue ("Text", string.Format ("Project \"{0}\" is disabled for solution configuration \"{1}|{2}\".", project.Name, targetInfo.Configuration, targetInfo.Platform));
|
|
}
|
|
task.Condition = string.Format (" ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ", targetInfo.Configuration, targetInfo.Platform);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
string GetTargetNameForProject (string projectName, string buildTarget)
|
|
{
|
|
//FIXME: hack
|
|
projectName = projectName.Replace ("\\", "/").Replace (".", "_");
|
|
string target_name = projectName +
|
|
(buildTarget == "Build" ? string.Empty : ":" + buildTarget);
|
|
|
|
if (IsBuildTargetName (projectName))
|
|
target_name = "Solution:" + target_name;
|
|
|
|
return target_name;
|
|
}
|
|
|
|
bool IsBuildTargetName (string name)
|
|
{
|
|
foreach (string tgt in buildTargets)
|
|
if (name == tgt)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
// returns number of levels
|
|
int AddBuildLevels (Project p, List<TargetInfo> solutionTargets, Dictionary<Guid, ProjectInfo> projectInfos,
|
|
ref List<ProjectInfo>[] infosByLevel)
|
|
{
|
|
infosByLevel = TopologicalSort<ProjectInfo> (projectInfos.Values);
|
|
|
|
foreach (TargetInfo targetInfo in solutionTargets) {
|
|
BuildItemGroup big = p.AddNewItemGroup ();
|
|
big.Condition = String.Format (" ('$(Configuration)' == '{0}') and ('$(Platform)' == '{1}') ",
|
|
targetInfo.Configuration, targetInfo.Platform);
|
|
|
|
//FIXME: every level has projects that can be built in parallel.
|
|
// levels are ordered on the basis of the dependency graph
|
|
|
|
for (int i = 0; i < infosByLevel.Length; i ++) {
|
|
string build_level = String.Format ("BuildLevel{0}", i);
|
|
string skip_level = String.Format ("SkipLevel{0}", i);
|
|
string missing_level = String.Format ("MissingConfigLevel{0}", i);
|
|
|
|
foreach (ProjectInfo projectInfo in infosByLevel [i]) {
|
|
TargetInfo projectTargetInfo;
|
|
if (!projectInfo.TargetMap.TryGetValue (targetInfo, out projectTargetInfo)) {
|
|
// missing project config
|
|
big.AddNewItem (missing_level, projectInfo.Name);
|
|
continue;
|
|
}
|
|
|
|
if (projectTargetInfo.Build) {
|
|
BuildItem item = big.AddNewItem (build_level, projectInfo.FileName);
|
|
item.SetMetadata ("Configuration", projectTargetInfo.Configuration);
|
|
item.SetMetadata ("Platform", projectTargetInfo.Platform);
|
|
} else {
|
|
// build disabled
|
|
big.AddNewItem (skip_level, projectInfo.Name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return infosByLevel.Length;
|
|
}
|
|
|
|
void AddSolutionTargets (Project p, int num_levels, IEnumerable<ProjectInfo> websiteProjectInfos)
|
|
{
|
|
foreach (string buildTarget in buildTargets) {
|
|
Target t = p.Targets.AddNewTarget (buildTarget);
|
|
bool is_build_or_rebuild = buildTarget == "Build" || buildTarget == "Rebuild";
|
|
|
|
t.Condition = "'$(CurrentSolutionConfigurationContents)' != ''";
|
|
if (is_build_or_rebuild)
|
|
t.Outputs = "@(CollectedBuildOutput)";
|
|
|
|
BuildTask task = null;
|
|
for (int i = 0; i < num_levels; i ++) {
|
|
string level_str = String.Format ("BuildLevel{0}", i);
|
|
task = t.AddNewTask ("MSBuild");
|
|
task.SetParameterValue ("Condition", String.Format ("'@({0})' != ''", level_str));
|
|
task.SetParameterValue ("Projects", String.Format ("@({0})", level_str));
|
|
task.SetParameterValue ("ToolsVersion", "$(ProjectToolsVersion)");
|
|
task.SetParameterValue ("Properties",
|
|
string.Format ("Configuration=%(Configuration); Platform=%(Platform); BuildingSolutionFile=true; CurrentSolutionConfigurationContents=$(CurrentSolutionConfigurationContents); SolutionDir=$(SolutionDir); SolutionExt=$(SolutionExt); SolutionFileName=$(SolutionFileName); SolutionName=$(SolutionName); SolutionPath=$(SolutionPath)"));
|
|
if (buildTarget != "Build")
|
|
task.SetParameterValue ("Targets", buildTarget);
|
|
//FIXME: change this to BuildInParallel=true, when parallel
|
|
// build support gets added
|
|
task.SetParameterValue ("RunEachTargetSeparately", "true");
|
|
if (is_build_or_rebuild)
|
|
task.AddOutputItem ("TargetOutputs", "CollectedBuildOutput");
|
|
|
|
level_str = String.Format ("SkipLevel{0}", i);
|
|
task = t.AddNewTask ("Message");
|
|
task.Condition = String.Format ("'@({0})' != ''", level_str);
|
|
task.SetParameterValue ("Text",
|
|
String.Format ("The project '%({0}.Identity)' is disabled for solution " +
|
|
"configuration '$(Configuration)|$(Platform)'.", level_str));
|
|
|
|
level_str = String.Format ("MissingConfigLevel{0}", i);
|
|
task = t.AddNewTask ("Warning");
|
|
task.Condition = String.Format ("'@({0})' != ''", level_str);
|
|
task.SetParameterValue ("Text",
|
|
String.Format ("The project configuration for project '%({0}.Identity)' " +
|
|
"corresponding to the solution configuration " +
|
|
"'$(Configuration)|$(Platform)' was not found.", level_str));
|
|
}
|
|
|
|
// "build" website projects also
|
|
StringBuilder w_targets = new StringBuilder ();
|
|
foreach (ProjectInfo info in websiteProjectInfos) {
|
|
if (w_targets.Length > 0)
|
|
w_targets.Append (";");
|
|
w_targets.Append (GetTargetNameForProject (info.Name, buildTarget));
|
|
}
|
|
|
|
task = t.AddNewTask ("CallTarget");
|
|
task.SetParameterValue ("Targets", w_targets.ToString ());
|
|
task.SetParameterValue ("RunEachTargetSeparately", "true");
|
|
}
|
|
}
|
|
|
|
// Sorts the ProjectInfo dependency graph, to obtain
|
|
// a series of build levels with projects. Projects
|
|
// in each level can be run parallel (no inter-dependency).
|
|
static List<T>[] TopologicalSort<T> (IEnumerable<T> items) where T: ProjectInfo
|
|
{
|
|
IList<T> allItems;
|
|
allItems = items as IList<T>;
|
|
if (allItems == null)
|
|
allItems = new List<T> (items);
|
|
|
|
bool[] inserted = new bool[allItems.Count];
|
|
bool[] triedToInsert = new bool[allItems.Count];
|
|
int[] levels = new int [allItems.Count];
|
|
|
|
int maxdepth = 0;
|
|
for (int i = 0; i < allItems.Count; ++i) {
|
|
int d = Insert<T> (i, allItems, levels, inserted, triedToInsert);
|
|
if (d > maxdepth)
|
|
maxdepth = d;
|
|
}
|
|
|
|
// Separate out the project infos by build level
|
|
List<T>[] infosByLevel = new List<T>[maxdepth];
|
|
for (int i = 0; i < levels.Length; i ++) {
|
|
int level = levels [i] - 1;
|
|
if (infosByLevel [level] == null)
|
|
infosByLevel [level] = new List<T> ();
|
|
|
|
infosByLevel [level].Add (allItems [i]);
|
|
}
|
|
|
|
return infosByLevel;
|
|
}
|
|
|
|
// returns level# for the project
|
|
static int Insert<T> (int index, IList<T> allItems, int[] levels, bool[] inserted, bool[] triedToInsert)
|
|
where T: ProjectInfo
|
|
{
|
|
if (inserted [index])
|
|
return levels [index];
|
|
|
|
if (triedToInsert[index])
|
|
throw new InvalidOperationException (String.Format (
|
|
"Cyclic dependency involving project {0} found in the project dependency graph",
|
|
allItems [index].Name));
|
|
|
|
triedToInsert[index] = true;
|
|
ProjectInfo insertItem = allItems[index];
|
|
|
|
int maxdepth = 0;
|
|
foreach (ProjectInfo dependency in insertItem.Dependencies.Values) {
|
|
for (int j = 0; j < allItems.Count; ++j) {
|
|
ProjectInfo checkItem = allItems [j];
|
|
if (dependency.FileName == checkItem.FileName) {
|
|
int d = Insert (j, allItems, levels, inserted, triedToInsert);
|
|
maxdepth = d > maxdepth ? d : maxdepth;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
levels [index] = maxdepth + 1;
|
|
inserted [index] = true;
|
|
|
|
return levels [index];
|
|
}
|
|
|
|
public static IEnumerable<string> GetAllProjectFileNames (string solutionFile)
|
|
{
|
|
StreamReader reader = new StreamReader (solutionFile);
|
|
string line = reader.ReadToEnd ();
|
|
line = line.Replace ("\r\n", "\n");
|
|
string soln_dir = Path.GetDirectoryName (solutionFile);
|
|
|
|
Match m = projectRegex.Match (line);
|
|
while (m.Success) {
|
|
if (String.Compare (m.Groups [1].Value, solutionFolderGuid,
|
|
StringComparison.InvariantCultureIgnoreCase) != 0)
|
|
yield return Path.Combine (soln_dir, m.Groups [3].Value).Replace ("\\", "/");
|
|
|
|
m = m.NextMatch ();
|
|
}
|
|
}
|
|
}
|
|
}
|