From 94f18ebadc158a7ec038e4976e758198ca2d2164 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Tue, 3 Jul 2018 23:38:11 -0500 Subject: [PATCH] Add BuildTools support for SDK-style projects in VS. (#2083) * Add BuildTools support for SDK-style projects in VS. 1. Turn off FillPartialFacadeUsingTask during design-time builds. These builds don't produced output assemblies, and the task fails if there is no output assembly. 2. The design-time builds run targets in a different order than with the legacy .csproj format. Hook our targets into different locations to make VS design-time builds work correctly. 3. Update UpdateVSConfigurations to write out .sln and .csproj files for SDK-style projects. * Fix whitespace. * Add a general AddReferencesDynamically target that allows people to hook to add new References dynamically. --- .../PackageFiles/FrameworkTargeting.targets | 25 +++- .../PackageFiles/partialfacades.task.targets | 2 +- .../UpdateVSConfigurations.cs | 140 ++++++++++-------- 3 files changed, 100 insertions(+), 67 deletions(-) diff --git a/src/Microsoft.DotNet.Build.Tasks/PackageFiles/FrameworkTargeting.targets b/src/Microsoft.DotNet.Build.Tasks/PackageFiles/FrameworkTargeting.targets index 9a461dbdaf..19630c5a45 100644 --- a/src/Microsoft.DotNet.Build.Tasks/PackageFiles/FrameworkTargeting.targets +++ b/src/Microsoft.DotNet.Build.Tasks/PackageFiles/FrameworkTargeting.targets @@ -49,6 +49,22 @@ .NETCoreApp,Version=v1.0 + + + true true @@ -61,11 +77,10 @@ Include="netstandard" /> - + + BeforeTargets="AddReferencesDynamically"> @@ -146,6 +161,10 @@ + + AddProjectReferencesDynamically; + $(PrepareProjectReferencesDependsOn); + AddProjectReferencesDynamically; $(ResolveReferencesDependsOn); diff --git a/src/Microsoft.DotNet.Build.Tasks/PackageFiles/partialfacades.task.targets b/src/Microsoft.DotNet.Build.Tasks/PackageFiles/partialfacades.task.targets index 24657ee66b..232f63751c 100644 --- a/src/Microsoft.DotNet.Build.Tasks/PackageFiles/partialfacades.task.targets +++ b/src/Microsoft.DotNet.Build.Tasks/PackageFiles/partialfacades.task.targets @@ -8,7 +8,7 @@ of the assembly compilation process. --> - + $(TargetsTriggeredByCompilation);FillPartialFacadeUsingTask diff --git a/src/Microsoft.DotNet.Build.Tasks/UpdateVSConfigurations.cs b/src/Microsoft.DotNet.Build.Tasks/UpdateVSConfigurations.cs index 0f042d275f..f3ae84079a 100644 --- a/src/Microsoft.DotNet.Build.Tasks/UpdateVSConfigurations.cs +++ b/src/Microsoft.DotNet.Build.Tasks/UpdateVSConfigurations.cs @@ -19,7 +19,7 @@ public class UpdateVSConfigurations : BuildTask private const string ConfigurationPropsFilename = "Configurations.props"; private static Regex s_configurationConditionRegex = new Regex(@"'\$\(Configuration\)\|\$\(Platform\)' ?== ?'(?.*)'"); - private static string[] s_configurationSuffixes = new [] { "Debug|AnyCPU", "Release|AnyCPU" }; + private static string[] s_configurationSuffixes = new [] { "Debug", "Release" }; public override bool Execute() { @@ -40,14 +40,14 @@ public override bool Execute() Log.LogMessage($"Updating {projectFile}"); var project = ProjectRootElement.Open(projectFile); - ICollection propertyGroups; - var actualConfigurations = GetConfigurationFromPropertyGroups(project, out propertyGroups); + ICollection propertyGroups = GetPropertyGroupsToRemove(project); + var actualConfigurations = GetConfigurationsFromProperty(project); bool addedGuid = EnsureProjectGuid(project); if (!actualConfigurations.SequenceEqual(expectedConfigurations)) { - ReplaceConfigurationPropertyGroups(project, propertyGroups, expectedConfigurations); + ReplaceConfigurationsProperty(project, propertyGroups, expectedConfigurations); } if (addedGuid || !actualConfigurations.SequenceEqual(expectedConfigurations)) @@ -92,15 +92,13 @@ private static string[] GetConfigurationStrings(string configurationProjectFile, } /// - /// Gets a sorted list of configuration strings from a project file's PropertyGroups + /// Gets a collection of a project file's configuration PropertyGroups in the legacy format. /// /// Project - /// collection that accepts the list of property groups representing configuration strings - /// Sorted list of configuration strings - private static string[] GetConfigurationFromPropertyGroups(ProjectRootElement project, out ICollection propertyGroups) + /// Collection of PropertyGroups that should be removed from the project. + private static ICollection GetPropertyGroupsToRemove(ProjectRootElement project) { - propertyGroups = new List(); - var configurations = new SortedSet(StringComparer.OrdinalIgnoreCase); + List propertyGroups = new List(); foreach (var propertyGroup in project.PropertyGroups) { @@ -108,78 +106,52 @@ private static string[] GetConfigurationFromPropertyGroups(ProjectRootElement pr if (match.Success) { - configurations.Add(match.Groups["config"].Value); propertyGroups.Add(propertyGroup); } } - return configurations.ToArray(); + return propertyGroups; + } + + private string[] GetConfigurationsFromProperty(ProjectRootElement project) + { + return project.PropertyGroups + .SelectMany(g => g.Properties) + .FirstOrDefault(p => p.Name == "Configurations")?.Value + .Split(';') + ?? Array.Empty(); } /// - /// Replaces all configuration propertygroups with empty property groups corresponding to the expected configurations. + /// Replaces the configurations property with the expected configurations. /// Doesn't attempt to preserve any content since it can all be regenerated. /// Does attempt to preserve the ordering in the project file. /// /// Project /// PropertyGroups to remove /// - private static void ReplaceConfigurationPropertyGroups(ProjectRootElement project, IEnumerable oldPropertyGroups, IEnumerable newConfigurations) + private static void ReplaceConfigurationsProperty(ProjectRootElement project, IEnumerable oldPropertyGroups, IEnumerable newConfigurations) { - ProjectElement insertAfter = null, insertBefore = null; - foreach (var oldPropertyGroup in oldPropertyGroups) { - insertBefore = oldPropertyGroup.NextSibling; project.RemoveChild(oldPropertyGroup); } - if (insertBefore == null) + string configurationsValue = string.Join(";", newConfigurations); + var configurationsProperty = project.Properties.FirstOrDefault(p => p.Name == "Configurations"); + if (configurationsProperty == null) { - // find first itemgroup after imports - var insertAt = project.Imports.FirstOrDefault()?.NextSibling; - - while (insertAt != null) + var firstPropertyGroup = project.PropertyGroups.FirstOrDefault(); + if (firstPropertyGroup == null) { - if (insertAt is ProjectItemGroupElement) - { - insertBefore = insertAt; - break; - } - - insertAt = insertAt.NextSibling; + firstPropertyGroup = project.CreatePropertyGroupElement(); } - } - - if (insertBefore == null) - { - // find last propertygroup after imports, defaulting to after imports - insertAfter = project.Imports.FirstOrDefault(); - while (insertAfter?.NextSibling != null && insertAfter.NextSibling is ProjectPropertyGroupElement) - { - insertAfter = insertAfter.NextSibling; - } + configurationsProperty = firstPropertyGroup.AddProperty("Configurations", configurationsValue); } - - foreach (var newConfiguration in newConfigurations) + else { - var newPropertyGroup = project.CreatePropertyGroupElement(); - newPropertyGroup.Condition = $"'$(Configuration)|$(Platform)' == '{newConfiguration}'"; - if (insertBefore != null) - { - project.InsertBeforeChild(newPropertyGroup, insertBefore); - } - else if (insertAfter != null) - { - project.InsertAfterChild(newPropertyGroup, insertAfter); - } - else - { - project.AppendChild(newPropertyGroup); - } - insertBefore = null; - insertAfter = newPropertyGroup; + configurationsProperty.Value = configurationsValue; } } @@ -280,10 +252,14 @@ private void UpdateSolution(ITaskItem solutionRootItem) Log.LogMessage($"Generating solution for '{solutionRootPath}'..."); + string solutionName = GetNameForSolution(solutionRootPath); + string slnFile = Path.Combine(solutionRootPath, solutionName + ".sln"); + Solution solution = new Solution(slnFile); + StringBuilder slnBuilder = new StringBuilder(); slnBuilder.AppendLine("Microsoft Visual Studio Solution File, Format Version 12.00"); - slnBuilder.AppendLine("# Visual Studio 14"); - slnBuilder.AppendLine("VisualStudioVersion = 14.0.25420.1"); + slnBuilder.AppendLine("# Visual Studio 15"); + slnBuilder.AppendLine("VisualStudioVersion = 15.0.27213.1"); slnBuilder.AppendLine("MinimumVisualStudioVersion = 10.0.40219.1"); @@ -376,10 +352,13 @@ private void UpdateSolution(ITaskItem solutionRootItem) } slnBuilder.AppendLine("\tEndGlobalSection"); + // Output the extensibility globals + slnBuilder.AppendLine("\tGlobalSection(ExtensibilityGlobals) = postSolution"); + slnBuilder.AppendLine($"\t\tSolutionGuid = {solution.Guid}"); + slnBuilder.AppendLine("\tEndGlobalSection"); + slnBuilder.AppendLine("EndGlobal"); - string solutionName = GetNameForSolution(solutionRootPath); - string slnFile = Path.Combine(solutionRootPath, solutionName + ".sln"); File.WriteAllText(slnFile, slnBuilder.ToString()); } @@ -440,6 +419,41 @@ public ProjectFolder(string basePath, string relPath, string projectId, string p } } + internal class Solution + { + public string Path { get; } + public string Guid { get; } + + public Solution(string path) + { + Path = path; + Guid = ReadSolutionGuid(path); + } + + private static string ReadSolutionGuid(string path) + { + string solutionGuid = null; + if (File.Exists(path)) + { + foreach (string line in File.ReadLines(path)) + { + if (line.StartsWith("\t\tSolutionGuid = ")) + { + solutionGuid = line.Substring("\t\tSolutionGuid = ".Length); + break; + } + } + } + + if (solutionGuid == null) + { + solutionGuid = System.Guid.NewGuid().ToString("B").ToUpper(); + } + + return solutionGuid; + } + } + internal class SolutionProject { public string ProjectPath { get; } @@ -507,10 +521,10 @@ public string SolutionGuid { //ProjectTypeGuids for different projects, pulled from the Visual Studio regkeys //TODO: Clean up or map these to actual projects, this is fragile - string slnGuid = "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"; // Windows (C#) + string slnGuid = "{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"; // Windows (C#) Managed/CPS if (ProjectPath.Contains("VisualBasic.vbproj")) { - slnGuid = "{F184B08F-C81C-45F6-A57F-5ABD9991F28F}"; //Windows (VB.NET) + slnGuid = "{778DAE3C-4631-46EA-AA77-85C1314464D9}"; //Windows (VB.NET) Managed/CPS } if (ProjectPath.Contains("TestNativeService")) //Windows (Visual C++) {