From b6c00fab49efa54a25f41497e6218824ce806d2d Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Fri, 13 Jan 2023 17:36:24 -0800 Subject: Add TargetFrameworks to ProjectReference to ease handling multi-framework projects Closes 7149 --- .../CsprojClassLibraryMultiFramework.csproj | 8 ++ .../MultiFrameworkClass.cs | 13 +++ .../CsprojConsoleMultiFramework.csproj | 13 +++ .../CsprojConsoleMultiFramework.sln | 25 ++++ .../CsprojConsoleMultiFramework/Program.cs | 15 +++ .../WixprojLibraryMultiFramework/Library.wxs | 26 +++++ .../WixprojLibraryMultiFramework.wixproj | 12 ++ src/test/wix/WixE2E/WixE2EFixture.cs | 12 +- src/wix/WixToolset.BuildTasks/ConvertReferences.cs | 89 --------------- ...eProjectReferenceDefineConstantsAndBindPaths.cs | 2 +- .../UpdateProjectReferenceMetadata.cs | 127 ++++++++++++++++++--- src/wix/WixToolset.Sdk/tools/wix.targets | 15 +-- 12 files changed, 242 insertions(+), 115 deletions(-) create mode 100644 src/test/wix/TestData/CsprojClassLibraryMultiFramework/CsprojClassLibraryMultiFramework.csproj create mode 100644 src/test/wix/TestData/CsprojClassLibraryMultiFramework/MultiFrameworkClass.cs create mode 100644 src/test/wix/TestData/CsprojConsoleMultiFramework/CsprojConsoleMultiFramework.csproj create mode 100644 src/test/wix/TestData/CsprojConsoleMultiFramework/CsprojConsoleMultiFramework.sln create mode 100644 src/test/wix/TestData/CsprojConsoleMultiFramework/Program.cs create mode 100644 src/test/wix/TestData/WixprojLibraryMultiFramework/Library.wxs create mode 100644 src/test/wix/TestData/WixprojLibraryMultiFramework/WixprojLibraryMultiFramework.wixproj delete mode 100644 src/wix/WixToolset.BuildTasks/ConvertReferences.cs diff --git a/src/test/wix/TestData/CsprojClassLibraryMultiFramework/CsprojClassLibraryMultiFramework.csproj b/src/test/wix/TestData/CsprojClassLibraryMultiFramework/CsprojClassLibraryMultiFramework.csproj new file mode 100644 index 00000000..ffc34573 --- /dev/null +++ b/src/test/wix/TestData/CsprojClassLibraryMultiFramework/CsprojClassLibraryMultiFramework.csproj @@ -0,0 +1,8 @@ + + + + net6.0;net48 + embedded + + + diff --git a/src/test/wix/TestData/CsprojClassLibraryMultiFramework/MultiFrameworkClass.cs b/src/test/wix/TestData/CsprojClassLibraryMultiFramework/MultiFrameworkClass.cs new file mode 100644 index 00000000..1fde9580 --- /dev/null +++ b/src/test/wix/TestData/CsprojClassLibraryMultiFramework/MultiFrameworkClass.cs @@ -0,0 +1,13 @@ +using System; + +namespace CsprojClassLibraryMultiFramework +{ + public class MultiFrameworkClass + { +#if NET + public string Name { get; } = ".NET v6.0 MultiFrameworkClass"; +#else + public string Name { get; } = ".NETFX v4.8 MultiFrameworkClass"; +#endif + } +} diff --git a/src/test/wix/TestData/CsprojConsoleMultiFramework/CsprojConsoleMultiFramework.csproj b/src/test/wix/TestData/CsprojConsoleMultiFramework/CsprojConsoleMultiFramework.csproj new file mode 100644 index 00000000..8b4fac23 --- /dev/null +++ b/src/test/wix/TestData/CsprojConsoleMultiFramework/CsprojConsoleMultiFramework.csproj @@ -0,0 +1,13 @@ + + + + Exe + net6.0;net48 + win-x86 + + + + + + + diff --git a/src/test/wix/TestData/CsprojConsoleMultiFramework/CsprojConsoleMultiFramework.sln b/src/test/wix/TestData/CsprojConsoleMultiFramework/CsprojConsoleMultiFramework.sln new file mode 100644 index 00000000..ec9c83db --- /dev/null +++ b/src/test/wix/TestData/CsprojConsoleMultiFramework/CsprojConsoleMultiFramework.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31911.196 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CsprojConsoleMultiFramework", "CsprojConsoleMultiFramework.csproj", "{A87A6E98-EE6A-4688-8716-E2378AC7BB75}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A87A6E98-EE6A-4688-8716-E2378AC7BB75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A87A6E98-EE6A-4688-8716-E2378AC7BB75}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A87A6E98-EE6A-4688-8716-E2378AC7BB75}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A87A6E98-EE6A-4688-8716-E2378AC7BB75}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9D5D5A46-18B1-4B8B-BC4B-DBD4EFC8215D} + EndGlobalSection +EndGlobal diff --git a/src/test/wix/TestData/CsprojConsoleMultiFramework/Program.cs b/src/test/wix/TestData/CsprojConsoleMultiFramework/Program.cs new file mode 100644 index 00000000..c5468e2f --- /dev/null +++ b/src/test/wix/TestData/CsprojConsoleMultiFramework/Program.cs @@ -0,0 +1,15 @@ +using System; +using CsprojClassLibraryMultiFramework; + +namespace CsprojConsoleNetCore +{ + class Program + { + static void Main(string[] args) + { + var mfc = new MultiFrameworkClass(); + + Console.WriteLine("Hello, {0}!", mfc.Name); + } + } +} diff --git a/src/test/wix/TestData/WixprojLibraryMultiFramework/Library.wxs b/src/test/wix/TestData/WixprojLibraryMultiFramework/Library.wxs new file mode 100644 index 00000000..00a2e558 --- /dev/null +++ b/src/test/wix/TestData/WixprojLibraryMultiFramework/Library.wxs @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/wix/TestData/WixprojLibraryMultiFramework/WixprojLibraryMultiFramework.wixproj b/src/test/wix/TestData/WixprojLibraryMultiFramework/WixprojLibraryMultiFramework.wixproj new file mode 100644 index 00000000..6b4dd129 --- /dev/null +++ b/src/test/wix/TestData/WixprojLibraryMultiFramework/WixprojLibraryMultiFramework.wixproj @@ -0,0 +1,12 @@ + + + + Library + true + + + + + + diff --git a/src/test/wix/WixE2E/WixE2EFixture.cs b/src/test/wix/WixE2E/WixE2EFixture.cs index 4a0b78d8..488d2fe9 100644 --- a/src/test/wix/WixE2E/WixE2EFixture.cs +++ b/src/test/wix/WixE2E/WixE2EFixture.cs @@ -7,12 +7,22 @@ namespace WixE2E using System.Linq; using System.Security.Cryptography; using System.Text; - using System.Threading; using WixInternal.TestSupport; using Xunit; public class WixE2EFixture { + [Fact] + public void CanBuildWixlibMultiFramework() + { + var projectPath = TestData.Get("TestData", "WixprojLibraryMultiFramework", "WixprojLibraryMultiFramework.wixproj"); + + CleanEverything(); + + var result = RestoreAndBuild(projectPath); + result.AssertSuccess(); + } + [Fact] public void CanBuildWixlibWithNativeDll() { diff --git a/src/wix/WixToolset.BuildTasks/ConvertReferences.cs b/src/wix/WixToolset.BuildTasks/ConvertReferences.cs deleted file mode 100644 index 3fdc78d1..00000000 --- a/src/wix/WixToolset.BuildTasks/ConvertReferences.cs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.BuildTasks -{ - using System; - using System.Collections.Generic; - using Microsoft.Build.Framework; - using Microsoft.Build.Utilities; - - /// - /// This task assigns Culture metadata to files based on the value of the Culture attribute on the - /// WixLocalization element inside the file. - /// - public class ConvertReferences : Task - { - private string projectOutputGroups; - private ITaskItem[] projectReferences; - private ITaskItem[] harvestItems; - - /// - /// The total list of cabs in this database - /// - [Output] - public ITaskItem[] HarvestItems - { - get { return this.harvestItems; } - } - - /// - /// The project output groups to harvest. - /// - [Required] - public string ProjectOutputGroups - { - get { return this.projectOutputGroups; } - set { this.projectOutputGroups = value; } - } - - /// - /// All the project references in the project. - /// - [Required] - public ITaskItem[] ProjectReferences - { - get { return this.projectReferences; } - set { this.projectReferences = value; } - } - - /// - /// Gets a complete list of external cabs referenced by the given installer database file. - /// - /// True upon completion of the task execution. - public override bool Execute() - { - List newItems = new List(); - - foreach(ITaskItem item in this.ProjectReferences) - { - Dictionary newItemMetadeta = new Dictionary(); - - if (!String.IsNullOrEmpty(item.GetMetadata(ToolsCommon.DoNotHarvest))) - { - continue; - } - - string refTargetDir = item.GetMetadata("RefTargetDir"); - if (!String.IsNullOrEmpty(refTargetDir)) - { - newItemMetadeta.Add("DirectoryIds", refTargetDir); - } - - string refName = item.GetMetadata("Name"); - if (!String.IsNullOrEmpty(refName)) - { - newItemMetadeta.Add("ProjectName", refName); - } - - newItemMetadeta.Add("ProjectOutputGroups", this.ProjectOutputGroups); - - ITaskItem newItem = new TaskItem(item.ItemSpec, newItemMetadeta); - newItems.Add(newItem); - } - - this.harvestItems = newItems.ToArray(); - - return true; - } - } -} diff --git a/src/wix/WixToolset.BuildTasks/CreateProjectReferenceDefineConstantsAndBindPaths.cs b/src/wix/WixToolset.BuildTasks/CreateProjectReferenceDefineConstantsAndBindPaths.cs index af4bbd0c..7ac00241 100644 --- a/src/wix/WixToolset.BuildTasks/CreateProjectReferenceDefineConstantsAndBindPaths.cs +++ b/src/wix/WixToolset.BuildTasks/CreateProjectReferenceDefineConstantsAndBindPaths.cs @@ -152,7 +152,7 @@ namespace WixToolset.BuildTasks { if (!targetPath.Equals(oldTargetPath, StringComparison.OrdinalIgnoreCase)) { - defineConstants[targetPathDefine] += ";" + targetPath; + defineConstants[targetPathDefine] += "%3B" + targetPath; } // If there was only one targetpath we need to create its culture specific define diff --git a/src/wix/WixToolset.BuildTasks/UpdateProjectReferenceMetadata.cs b/src/wix/WixToolset.BuildTasks/UpdateProjectReferenceMetadata.cs index 4de948ba..c1ef45bc 100644 --- a/src/wix/WixToolset.BuildTasks/UpdateProjectReferenceMetadata.cs +++ b/src/wix/WixToolset.BuildTasks/UpdateProjectReferenceMetadata.cs @@ -5,6 +5,7 @@ namespace WixToolset.BuildTasks using System; using System.Collections.Generic; using System.IO; + using System.Linq; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -13,6 +14,8 @@ namespace WixToolset.BuildTasks /// public class UpdateProjectReferenceMetadata : Task { + private static readonly char[] TargetFrameworksSplitter = new char[] { ',', ';' }; + /// /// The list of project references that exist. /// @@ -26,40 +29,101 @@ namespace WixToolset.BuildTasks public ITaskItem[] UpdatedProjectReferences { get; private set; } /// - /// Finds all project references requesting publishing and updates them to publish instead of build. + /// Finds all project references requesting publishing and updates them to publish instead of build and + /// sets target framework if requested. /// /// True upon completion of the task execution. public override bool Execute() { - var publishProjectReferences = new List(); + var updatedProjectReferences = new List(); var intermediateFolder = Path.GetFullPath(this.IntermediateFolder); foreach (var projectReference in this.ProjectReferences) { - var publish = projectReference.GetMetadata("Publish"); - var publishDir = projectReference.GetMetadata("PublishDir"); + var additionalProjectReferences = new List(); + + var updatedProjectReference = this.TrySetTargetFrameworksOnProjectReference(projectReference, additionalProjectReferences); - if (publish.Equals("true", StringComparison.OrdinalIgnoreCase) || - (String.IsNullOrWhiteSpace(publish) && !String.IsNullOrWhiteSpace(publishDir))) + if (this.TryAddPublishPropertiesToProjectReference(projectReference, intermediateFolder)) { - publishDir = String.IsNullOrWhiteSpace(publishDir) ? this.CalculatePublishDirFromProjectReference(projectReference, intermediateFolder) : Path.GetFullPath(publishDir); + foreach (var additionalProjectReference in additionalProjectReferences) + { + this.TryAddPublishPropertiesToProjectReference(additionalProjectReference, intermediateFolder); + } - this.AddPublishPropertiesToProjectReference(projectReference, publishDir); + updatedProjectReference = true; + } - publishProjectReferences.Add(projectReference); + if (updatedProjectReference) + { + updatedProjectReferences.Add(projectReference); } + + updatedProjectReferences.AddRange(additionalProjectReferences); } - this.UpdatedProjectReferences = publishProjectReferences.ToArray(); + this.UpdatedProjectReferences = updatedProjectReferences.ToArray(); return true; } - private string CalculatePublishDirFromProjectReference(ITaskItem projectReference, string intermediateFolder) + private bool TryAddPublishPropertiesToProjectReference(ITaskItem projectReference, string intermediateFolder) { - var publishDir = Path.Combine("publish", Path.GetFileNameWithoutExtension(projectReference.ItemSpec)); + var publish = projectReference.GetMetadata("Publish"); + var publishDir = projectReference.GetMetadata("PublishDir"); - return Path.Combine(intermediateFolder, publishDir); + if (publish.Equals("true", StringComparison.OrdinalIgnoreCase) || + (String.IsNullOrWhiteSpace(publish) && !String.IsNullOrWhiteSpace(publishDir))) + { + if (String.IsNullOrWhiteSpace(publishDir)) + { + publishDir = CalculatePublishDirFromProjectReference(projectReference, intermediateFolder); + } + + publishDir = AppendTargetFrameworkFromProjectReference(projectReference, publishDir); + + publishDir = Path.GetFullPath(publishDir); + + this.AddPublishPropertiesToProjectReference(projectReference, publishDir); + + return true; + } + + return false; + } + + private bool TrySetTargetFrameworksOnProjectReference(ITaskItem projectReference, List additionalProjectReferences) + { + var setTargetFramework = projectReference.GetMetadata("SetTargetFramework"); + var targetFrameworks = projectReference.GetMetadata("TargetFrameworks"); + var targetFrameworksToSet = targetFrameworks.Split(TargetFrameworksSplitter).Where(s => !String.IsNullOrWhiteSpace(s)).ToList(); + + if (String.IsNullOrWhiteSpace(setTargetFramework) && targetFrameworksToSet.Count > 0) + { + // First, clone the project reference so there are enough duplicates for all of the + // requested target frameworks. + for (var i = 1; i < targetFrameworksToSet.Count; ++i) + { + additionalProjectReferences.Add(new TaskItem(projectReference)); + } + + // Then set the target framework on each project reference. + for (var i = 0; i < targetFrameworksToSet.Count; ++i) + { + var reference = (i == 0) ? projectReference : additionalProjectReferences[i - 1]; + + this.SetTargetFrameworkOnProjectReference(reference, targetFrameworksToSet[i]); + } + + return true; + } + + if (!String.IsNullOrWhiteSpace(setTargetFramework) && !String.IsNullOrWhiteSpace(targetFrameworks)) + { + this.Log.LogWarning("ProjectReference {0} contains metadata for both SetTargetFramework and TargetFrameworks. SetTargetFramework takes precedent so the TargetFrameworks value '{1}' is ignored", projectReference.ItemSpec, targetFrameworks); + } + + return false; } private void AddPublishPropertiesToProjectReference(ITaskItem projectReference, string publishDir) @@ -91,5 +155,42 @@ namespace WixToolset.BuildTasks this.Log.LogMessage(MessageImportance.Low, "Adding publish metadata to project reference {0} Targets {1}, BindPath {2}, AdditionalProperties: {3}", projectReference.ItemSpec, projectReference.GetMetadata("Targets"), projectReference.GetMetadata("BindPath"), projectReference.GetMetadata("AdditionalProperties")); } + + private void SetTargetFrameworkOnProjectReference(ITaskItem projectReference, string targetFramework) + { + projectReference.SetMetadata("SetTargetFramework", $"TargetFramework={targetFramework}"); + + var bindName = projectReference.GetMetadata("BindName"); + if (String.IsNullOrWhiteSpace(bindName)) + { + bindName = Path.GetFileNameWithoutExtension(projectReference.ItemSpec); + + projectReference.SetMetadata("BindName", $"{bindName}.{targetFramework}"); + } + + this.Log.LogMessage(MessageImportance.Low, "Adding target framework metadata to project reference {0} SetTargetFramework: {1}, BindName: {2}", + projectReference.ItemSpec, projectReference.GetMetadata("SetTargetFramework"), projectReference.GetMetadata("BindName")); + } + + private static string CalculatePublishDirFromProjectReference(ITaskItem projectReference, string intermediateFolder) + { + var publishDir = Path.Combine("publish", Path.GetFileNameWithoutExtension(projectReference.ItemSpec)); + + return Path.Combine(intermediateFolder, publishDir); + } + + private static string AppendTargetFrameworkFromProjectReference(ITaskItem projectReference, string publishDir) + { + var setTargetFramework = projectReference.GetMetadata("SetTargetFramework"); + + if (setTargetFramework.StartsWith("TargetFramework=") && setTargetFramework.Length > "TargetFramework=".Length) + { + var targetFramework = setTargetFramework.Substring("TargetFramework=".Length); + + publishDir = Path.Combine(publishDir, targetFramework); + } + + return publishDir; + } } } diff --git a/src/wix/WixToolset.Sdk/tools/wix.targets b/src/wix/WixToolset.Sdk/tools/wix.targets index cdc831e6..e63cded6 100644 --- a/src/wix/WixToolset.Sdk/tools/wix.targets +++ b/src/wix/WixToolset.Sdk/tools/wix.targets @@ -245,7 +245,7 @@ @(_MSBuildProjectReferenceExistent) - References to projects that exist. [OUT] - @(_MSBuildProjectReferenceExistent) - Updated project references. + @(_MSBuildProjectReferenceExistent) - Project references updated as necessary. ================================================================================================ --> - + - <_MSBuildProjectReferenceExistent Remove='@(_WixPublishProjectReferences)' /> - <_MSBuildProjectReferenceExistent Include='@(_WixPublishProjectReferences)' /> - - - - <_MSBuildProjectReferenceExistent Condition="'%(_MSBuildProjectReferenceExistent.SetTargetFramework)' == ''"> - true - %(_MSBuildProjectReferenceExistent.UndefineProperties);TargetFramework - + <_MSBuildProjectReferenceExistent Remove='@(_WixUpdatedProjectReferences)' /> + <_MSBuildProjectReferenceExistent Include='@(_WixUpdatedProjectReferences)' /> -- cgit v1.2.3-55-g6feb