X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=blobdiff_plain;f=mcs%2Ftools%2Fxbuild%2FSolutionParser.cs;h=67bce1c6afb0519ab3f4d138f1cbbc1f32b20a8d;hb=9aba3ef7eb4949d46ee4f5f720a834e0e2e70e59;hp=040a71bc69f1e636a200a09563742fb4fed4fd07;hpb=328db53daa8d5e305203248e8c70730b9cab61b5;p=mono.git diff --git a/mcs/tools/xbuild/SolutionParser.cs b/mcs/tools/xbuild/SolutionParser.cs index 040a71bc69f..67bce1c6afb 100644 --- a/mcs/tools/xbuild/SolutionParser.cs +++ b/mcs/tools/xbuild/SolutionParser.cs @@ -32,6 +32,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.IO; @@ -41,6 +42,7 @@ namespace Mono.XBuild.CommandLine { class ProjectInfo { public string Name; public string FileName; + public Guid Guid; public ProjectInfo (string name, string fileName) { @@ -50,6 +52,18 @@ namespace Mono.XBuild.CommandLine { public Dictionary TargetMap = new Dictionary (); public Dictionary Dependencies = new Dictionary (); + public Dictionary ProjectSections = new Dictionary (); + public List AspNetConfigurations = new List (); + } + + class ProjectSection { + public string Name; + public Dictionary Properties = new Dictionary (); + + public ProjectSection (string name) + { + Name = name; + } } struct TargetInfo { @@ -71,14 +85,18 @@ namespace Mono.XBuild.CommandLine { } + 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 projectRegex = new Regex ("Project\\(\"(" + guidExpression + ")\"\\) = \"(.*?)\", \"(.*?)\", \"(" + guidExpression + ")\"(\\s*?)((\\s*?)ProjectSection\\((.*?)\\) = (.*?)EndProjectSection(\\s*?))*(\\s*?)EndProject?", RegexOptions.Singleline); + 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*(?.*) = \"(?.*)\""); static Regex globalRegex = new Regex ("Global(.*)EndGlobal", RegexOptions.Singleline); static Regex globalSectionRegex = new Regex ("GlobalSection\\((.*?)\\) = \\w*(.*?)EndGlobalSection", RegexOptions.Singleline); @@ -89,44 +107,89 @@ namespace Mono.XBuild.CommandLine { static string solutionFolderGuid = "{2150E333-8FDC-42A3-9474-1A3956D46DE8}"; static string vcprojGuid = "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}"; + static string websiteProjectGuid = "{E24C65DC-7377-472B-9ABA-BC803B73C61A}"; - public void ParseSolution (string file, Project p) + 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 == "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 solutionTargets = new List (); Dictionary projectInfos = new Dictionary (); + Dictionary websiteProjectInfos = new Dictionary (); + List[] infosByLevel = null; + Dictionary unsupportedProjectInfos = new Dictionary (); Match m = projectRegex.Match (line); while (m.Success) { - ProjectInfo projectInfo = new ProjectInfo (m.Groups[2].Value, m.Groups[3].Value); + 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 - ErrorUtilities.ReportWarning (0, string.Format("Ignoring vcproj '{0}'.", projectInfo.Name)); + RaiseWarning (0, string.Format("Ignoring vcproj '{0}'.", projectInfo.Name)); + + unsupportedProjectInfos [projectInfo.Guid] = projectInfo; m = m.NextMatch (); continue; } - projectInfos.Add (new Guid (m.Groups[4].Value), projectInfo); + 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) { - Match projectDependencyMatch = projectDependencyRegex.Match (projectSectionMatch.Value); - while (projectDependencyMatch.Success) { - projectInfo.Dependencies [new Guid (projectDependencyMatch.Groups[1].Value)] = null; - projectDependencyMatch = projectDependencyMatch.NextMatch (); + 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 (); } @@ -134,14 +197,81 @@ namespace Mono.XBuild.CommandLine { } 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 (); - currentProject.Load (Path.Combine (solutionDir, - projectInfo.FileName.Replace ('\\', Path.DirectorySeparatorChar))); + 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"); - Guid guid = new Guid (projectReferenceGuid); - projectInfo.Dependencies [guid] = projectInfos [guid]; + 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; + } + } + + // fill in the project info for deps found in the .sln file + foreach (ProjectInfo projectInfo in projectInfos.Values) { + List missingInfos = new List (); + foreach (KeyValuePair 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); } } @@ -154,26 +284,82 @@ namespace Mono.XBuild.CommandLine { ParseSolutionConfigurationPlatforms (globalSectionMatch.Groups[2].Value, solutionTargets); break; case "ProjectConfigurationPlatforms": - ParseProjectConfigurationPlatforms (globalSectionMatch.Groups[2].Value, projectInfos); + ParseProjectConfigurationPlatforms (globalSectionMatch.Groups[2].Value, + projectInfos, websiteProjectInfos); break; case "SolutionProperties": ParseSolutionProperties (globalSectionMatch.Groups[2].Value); break; case "NestedProjects": break; + case "MonoDevelopProperties": + break; default: - ErrorUtilities.ReportWarning (0, string.Format("Don't know how to handle GlobalSection {0}, Ignoring.", sectionType)); + 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); + int num_levels = AddBuildLevels (p, solutionTargets, projectInfos, ref infosByLevel); - AddCurrentSolutionConfigurationContents (p, solutionTargets, projectInfos); + AddCurrentSolutionConfigurationContents (p, solutionTargets, projectInfos, websiteProjectInfos); + 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); + 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) + { +#if NET_4_0 + p.AddNewImport ("$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\SolutionFile\\ImportBefore\\*", + "'$(ImportByWildcardBeforeSolution)' != 'false' and " + + "Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\SolutionFile\\ImportBefore')"); +#endif + + 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) + { +#if NET_4_0 + p.AddNewImport ("$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\SolutionFile\\ImportAfter\\*", + "'$(ImportByWildcardAfterSolution)' != 'false' and " + + "Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\SolutionFile\\ImportAfter')"); +#endif + + 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) @@ -183,10 +369,6 @@ namespace Mono.XBuild.CommandLine { 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"); - BuildPropertyGroup aspNetConfigurationPropertyGroup = p.AddNewPropertyGroup (true); - aspNetConfigurationPropertyGroup.Condition = " ('$(AspNetConfiguration)' == '') "; - aspNetConfigurationPropertyGroup.AddNewProperty ("AspNetConfiguration", "$(Configuration)"); - string solutionFilePath = Path.GetFullPath (solutionFile); BuildPropertyGroup solutionPropertyGroup = p.AddNewPropertyGroup (true); solutionPropertyGroup.AddNewProperty ("SolutionDir", Path.GetDirectoryName (solutionFilePath) + Path.DirectorySeparatorChar); @@ -207,7 +389,9 @@ namespace Mono.XBuild.CommandLine { } } - void ParseProjectConfigurationPlatforms (string section, Dictionary projectInfos) + // ignores the website projects, in the websiteProjectInfos + void ParseProjectConfigurationPlatforms (string section, Dictionary projectInfos, + Dictionary websiteProjectInfos) { List missingGuids = new List (); Match projectConfigurationPlatform = projectConfigurationActiveCfgRegex.Match (section); @@ -216,7 +400,9 @@ namespace Mono.XBuild.CommandLine { ProjectInfo projectInfo; if (!projectInfos.TryGetValue (guid, out projectInfo)) { if (!missingGuids.Contains (guid)) { - ErrorUtilities.ReportWarning (0, string.Format("Failed to find project {0}", guid)); + if (!websiteProjectInfos.ContainsKey (guid)) + // ignore website projects + RaiseWarning (0, string.Format("Failed to find project {0}", guid)); missingGuids.Add (guid); } projectConfigurationPlatform = projectConfigurationPlatform.NextMatch (); @@ -238,7 +424,7 @@ namespace Mono.XBuild.CommandLine { ProjectInfo projectInfo; if (!projectInfos.TryGetValue (guid, out projectInfo)) { if (!missingGuids.Contains (guid)) { - ErrorUtilities.ReportWarning (0, string.Format("Failed to find project {0}", guid)); + RaiseWarning (0, string.Format("Failed to find project {0}", guid)); missingGuids.Add (guid); } projectConfigurationPlatformBuild = projectConfigurationPlatformBuild.NextMatch (); @@ -260,12 +446,26 @@ namespace Mono.XBuild.CommandLine { { } - void AddCurrentSolutionConfigurationContents (Project p, List solutionTargets, Dictionary projectInfos) + void AddCurrentSolutionConfigurationContents (Project p, List solutionTargets, + Dictionary projectInfos, + Dictionary websiteProjectInfos) { - AddDefaultSolutionConfiguration (p, - solutionTargets.Count > 0 ? - solutionTargets [0] : - new TargetInfo ("Debug", "Any CPU")); + 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); @@ -275,18 +475,28 @@ namespace Mono.XBuild.CommandLine { solutionTarget.Platform ); - string solutionConfigurationContents = ""; + StringBuilder solutionConfigurationContents = new StringBuilder (); + solutionConfigurationContents.Append (""); foreach (KeyValuePair projectInfo in projectInfos) { - foreach (KeyValuePair targetInfo in projectInfo.Value.TargetMap) { - if (solutionTarget.Configuration == targetInfo.Key.Configuration && solutionTarget.Platform == targetInfo.Key.Platform) { - solutionConfigurationContents += string.Format ("{1}|{2}", - projectInfo.Key.ToString ("B").ToUpper (), targetInfo.Value.Configuration, targetInfo.Value.Platform); - } - } + AddProjectConfigurationItems (projectInfo.Key, projectInfo.Value, solutionTarget, solutionConfigurationContents); } - solutionConfigurationContents += ""; + solutionConfigurationContents.Append (""); - platformPropertyGroup.AddNewProperty ("CurrentSolutionConfigurationContents", solutionConfigurationContents); + platformPropertyGroup.AddNewProperty ("CurrentSolutionConfigurationContents", + solutionConfigurationContents.ToString ()); + } + } + + void AddProjectConfigurationItems (Guid guid, ProjectInfo projectInfo, TargetInfo solutionTarget, + StringBuilder solutionConfigurationContents) + { + foreach (KeyValuePair targetInfo in projectInfo.TargetMap) { + if (solutionTarget.Configuration == targetInfo.Key.Configuration && + solutionTarget.Platform == targetInfo.Key.Platform) { + solutionConfigurationContents.AppendFormat ( + "{1}|{2}", + guid.ToString ("B").ToUpper (), targetInfo.Value.Configuration, targetInfo.Value.Platform); + } } } @@ -299,6 +509,11 @@ namespace Mono.XBuild.CommandLine { 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) @@ -313,10 +528,238 @@ namespace Mono.XBuild.CommandLine { } + // Website project methods + + void AddWebsiteProperties (Project p, Dictionary websiteProjectInfos, + Dictionary projectInfos) + { + var propertyGroupByConfig = new Dictionary (); + foreach (KeyValuePair 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 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 websiteProjectInfos, + Dictionary projectInfos, List[] infosByLevel, + List solutionTargets) + { + foreach (ProjectInfo w_info in websiteProjectInfos.Values) { + // gets a linear list of dependencies + List depInfos = new List (); + foreach (List 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 projectInfos, List depInfos, + List 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 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 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 ("Error"); + 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"); @@ -332,19 +775,13 @@ namespace Mono.XBuild.CommandLine { foreach (KeyValuePair projectInfo in projectInfos) { ProjectInfo project = projectInfo.Value; foreach (string buildTarget in buildTargets) { - Target target = p.Targets.AddNewTarget (project.Name + (buildTarget == "Build" ? string.Empty : ":" + buildTarget)); + string target_name = GetTargetNameForProject (project.Name, buildTarget); + Target target = p.Targets.AddNewTarget (target_name); target.Condition = "'$(CurrentSolutionConfigurationContents)' != ''"; - - string dependencies = string.Empty; - foreach (ProjectInfo dependentInfo in project.Dependencies.Values) { - if (dependencies.Length > 0) - dependencies += ";"; - dependencies += dependentInfo.Name; - if (buildTarget != "Build") - dependencies += ":" + buildTarget; - } - if (dependencies != string.Empty) - target.DependsOnTargets = dependencies; + 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; @@ -357,6 +794,7 @@ namespace Mono.XBuild.CommandLine { if (projectTargetInfo.Build) { task = target.AddNewTask ("MSBuild"); task.SetParameterValue ("Projects", project.FileName); + task.SetParameterValue ("ToolsVersion", "$(ProjectToolsVersion)"); if (buildTarget != "Build") task.SetParameterValue ("Targets", buildTarget); @@ -371,10 +809,33 @@ namespace Mono.XBuild.CommandLine { } } + + 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 solutionTargets, Dictionary projectInfos) + int AddBuildLevels (Project p, List solutionTargets, Dictionary projectInfos, + ref List[] infosByLevel) { - List[] infosByLevel = TopologicalSort (projectInfos.Values); + infosByLevel = TopologicalSort (projectInfos.Values); foreach (TargetInfo targetInfo in solutionTargets) { BuildItemGroup big = p.AddNewItemGroup (); @@ -412,17 +873,19 @@ namespace Mono.XBuild.CommandLine { return infosByLevel.Length; } - void AddSolutionTargets (Project p, int num_levels) + void AddSolutionTargets (Project p, int num_levels, IEnumerable websiteProjectInfos) { foreach (string buildTarget in buildTargets) { Target t = p.Targets.AddNewTarget (buildTarget); t.Condition = "'$(CurrentSolutionConfigurationContents)' != ''"; + BuildTask task = null; for (int i = 0; i < num_levels; i ++) { string level_str = String.Format ("BuildLevel{0}", i); - BuildTask task = t.AddNewTask ("MSBuild"); + 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") @@ -446,6 +909,18 @@ namespace Mono.XBuild.CommandLine { "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"); } } @@ -491,7 +966,9 @@ namespace Mono.XBuild.CommandLine { return levels [index]; if (triedToInsert[index]) - throw new InvalidOperationException ("Cyclic dependency found in the project dependency graph"); + 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]; @@ -512,6 +989,23 @@ namespace Mono.XBuild.CommandLine { return levels [index]; } + + public static IEnumerable 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 (); + } + } } }