@joebigelow / wix-1 / commits / b6c00fab

Add TargetFrameworks to ProjectReference to ease handling multi-framework projects

Closes 7149

Rob Mensching committed Jan 13, 2023 at 17:36 UTC b6c00fab49efa54a25f41497e6218824ce806d2d
12 files changed +242 -115
src/test/wix/TestData/CsprojClassLibraryMultiFramework/CsprojClassLibraryMultiFramework.csproj new
+8
@@ -0,0 +1,8 @@
1 +<Project Sdk="Microsoft.NET.Sdk">
2 +
3 + <PropertyGroup>
4 + <TargetFrameworks>net6.0;net48</TargetFrameworks>
5 + <DebugType>embedded</DebugType>
6 + </PropertyGroup>
7 +
8 +</Project>
src/test/wix/TestData/CsprojClassLibraryMultiFramework/MultiFrameworkClass.cs new
+13
@@ -0,0 +1,13 @@
1 +using System;
2 +
3 +namespace CsprojClassLibraryMultiFramework
4 +{
5 + public class MultiFrameworkClass
6 + {
7 +#if NET
8 + public string Name { get; } = ".NET v6.0 MultiFrameworkClass";
9 +#else
10 + public string Name { get; } = ".NETFX v4.8 MultiFrameworkClass";
11 +#endif
12 + }
13 +}
src/test/wix/TestData/CsprojConsoleMultiFramework/CsprojConsoleMultiFramework.csproj new
+13
@@ -0,0 +1,13 @@
1 +<Project Sdk="Microsoft.NET.Sdk">
2 +
3 + <PropertyGroup>
4 + <OutputType>Exe</OutputType>
5 + <TargetFrameworks>net6.0;net48</TargetFrameworks>
6 + <RuntimeIdentifiers>win-x86</RuntimeIdentifiers>
7 + </PropertyGroup>
8 +
9 + <ItemGroup>
10 + <ProjectReference Include="..\CsprojClassLibraryMultiFramework\CsprojClassLibraryMultiFramework.csproj" />
11 + </ItemGroup>
12 +
13 +</Project>
src/test/wix/TestData/CsprojConsoleMultiFramework/CsprojConsoleMultiFramework.sln new
+25
@@ -0,0 +1,25 @@
1 +
2 +Microsoft Visual Studio Solution File, Format Version 12.00
3 +# Visual Studio Version 16
4 +VisualStudioVersion = 16.0.31911.196
5 +MinimumVisualStudioVersion = 10.0.40219.1
6 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CsprojConsoleMultiFramework", "CsprojConsoleMultiFramework.csproj", "{A87A6E98-EE6A-4688-8716-E2378AC7BB75}"
7 +EndProject
8 +Global
9 + GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 + Debug|Any CPU = Debug|Any CPU
11 + Release|Any CPU = Release|Any CPU
12 + EndGlobalSection
13 + GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 + {A87A6E98-EE6A-4688-8716-E2378AC7BB75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 + {A87A6E98-EE6A-4688-8716-E2378AC7BB75}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 + {A87A6E98-EE6A-4688-8716-E2378AC7BB75}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 + {A87A6E98-EE6A-4688-8716-E2378AC7BB75}.Release|Any CPU.Build.0 = Release|Any CPU
18 + EndGlobalSection
19 + GlobalSection(SolutionProperties) = preSolution
20 + HideSolutionNode = FALSE
21 + EndGlobalSection
22 + GlobalSection(ExtensibilityGlobals) = postSolution
23 + SolutionGuid = {9D5D5A46-18B1-4B8B-BC4B-DBD4EFC8215D}
24 + EndGlobalSection
25 +EndGlobal
src/test/wix/TestData/CsprojConsoleMultiFramework/Program.cs new
+15
@@ -0,0 +1,15 @@
1 +using System;
2 +using CsprojClassLibraryMultiFramework;
3 +
4 +namespace CsprojConsoleNetCore
5 +{
6 + class Program
7 + {
8 + static void Main(string[] args)
9 + {
10 + var mfc = new MultiFrameworkClass();
11 +
12 + Console.WriteLine("Hello, {0}!", mfc.Name);
13 + }
14 + }
15 +}
src/test/wix/TestData/WixprojLibraryMultiFramework/Library.wxs new
+26
@@ -0,0 +1,26 @@
1 +<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui">
2 + <Fragment>
3 + <ComponentGroup Id='CsprojClassLibraryMultiFrameworkNet60' Directory='ApplicationFolder' Subdirectory="net6.0">
4 + <Component>
5 + <File Id="Net60" Source='!(bindpath.CsprojConsoleMultiFramework.net6.0)CsprojConsoleMultiFramework.exe' />
6 + <File Source='!(bindpath.CsprojConsoleMultiFramework.net6.0)CsprojConsoleMultiFramework.deps.json' />
7 + <File Source='!(bindpath.CsprojConsoleMultiFramework.net6.0)CsprojConsoleMultiFramework.runtimeconfig.json' />
8 + </Component>
9 + <Component>
10 + <File Source='!(bindpath.CsprojConsoleMultiFramework.net6.0)CsprojClassLibraryMultiFramework.dll' />
11 + </Component>
12 + </ComponentGroup>
13 + </Fragment>
14 +
15 + <Fragment>
16 + <ComponentGroup Id='CsprojClassLibraryMultiFrameworkNet48' Directory='ApplicationFolder' Subdirectory="net48">
17 + <Component>
18 + <File Id="Net48" Source='!(bindpath.CsprojConsoleMultiFramework.net48)CsprojConsoleMultiFramework.exe' />
19 + <File Source='!(bindpath.CsprojConsoleMultiFramework.net48)CsprojConsoleMultiFramework.exe.config' />
20 + </Component>
21 + <Component>
22 + <File Source='!(bindpath.CsprojConsoleMultiFramework.net48)CsprojClassLibraryMultiFramework.dll' />
23 + </Component>
24 + </ComponentGroup>
25 + </Fragment>
26 +</Wix>
src/test/wix/TestData/WixprojLibraryMultiFramework/WixprojLibraryMultiFramework.wixproj new
+12
@@ -0,0 +1,12 @@
1 +<!-- 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. -->
2 +<Project Sdk='WixToolset.Sdk'>
3 + <PropertyGroup>
4 + <OutputType>Library</OutputType>
5 + <BindFiles>true</BindFiles>
6 + </PropertyGroup>
7 +
8 + <ItemGroup>
9 + <ProjectReference Include="..\CsprojConsoleMultiFramework\CsprojConsoleMultiFramework.csproj"
10 + TargetFrameworks="net48;net6.0" Publish="true" />
11 + </ItemGroup>
12 +</Project>
src/test/wix/WixE2E/WixE2EFixture.cs
+11 -1
@@ -7,12 +7,22 @@ namespace WixE2E
7 using System.Linq;
8 using System.Security.Cryptography;
9 using System.Text;
10 - using System.Threading;
10 using WixInternal.TestSupport;
11 using Xunit;
12
13 public class WixE2EFixture
14 {
15 + [Fact]
16 + public void CanBuildWixlibMultiFramework()
17 + {
18 + var projectPath = TestData.Get("TestData", "WixprojLibraryMultiFramework", "WixprojLibraryMultiFramework.wixproj");
19 +
20 + CleanEverything();
21 +
22 + var result = RestoreAndBuild(projectPath);
23 + result.AssertSuccess();
24 + }
25 +
26 [Fact]
27 public void CanBuildWixlibWithNativeDll()
28 {
src/wix/WixToolset.BuildTasks/ConvertReferences.cs deleted
-89
@@ -1,89 +0,0 @@
1 -// 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.
2 -
3 -namespace WixToolset.BuildTasks
4 -{
5 - using System;
6 - using System.Collections.Generic;
7 - using Microsoft.Build.Framework;
8 - using Microsoft.Build.Utilities;
9 -
10 - /// <summary>
11 - /// This task assigns Culture metadata to files based on the value of the Culture attribute on the
12 - /// WixLocalization element inside the file.
13 - /// </summary>
14 - public class ConvertReferences : Task
15 - {
16 - private string projectOutputGroups;
17 - private ITaskItem[] projectReferences;
18 - private ITaskItem[] harvestItems;
19 -
20 - /// <summary>
21 - /// The total list of cabs in this database
22 - /// </summary>
23 - [Output]
24 - public ITaskItem[] HarvestItems
25 - {
26 - get { return this.harvestItems; }
27 - }
28 -
29 - /// <summary>
30 - /// The project output groups to harvest.
31 - /// </summary>
32 - [Required]
33 - public string ProjectOutputGroups
34 - {
35 - get { return this.projectOutputGroups; }
36 - set { this.projectOutputGroups = value; }
37 - }
38 -
39 - /// <summary>
40 - /// All the project references in the project.
41 - /// </summary>
42 - [Required]
43 - public ITaskItem[] ProjectReferences
44 - {
45 - get { return this.projectReferences; }
46 - set { this.projectReferences = value; }
47 - }
48 -
49 - /// <summary>
50 - /// Gets a complete list of external cabs referenced by the given installer database file.
51 - /// </summary>
52 - /// <returns>True upon completion of the task execution.</returns>
53 - public override bool Execute()
54 - {
55 - List<ITaskItem> newItems = new List<ITaskItem>();
56 -
57 - foreach(ITaskItem item in this.ProjectReferences)
58 - {
59 - Dictionary<string, string> newItemMetadeta = new Dictionary<string, string>();
60 -
61 - if (!String.IsNullOrEmpty(item.GetMetadata(ToolsCommon.DoNotHarvest)))
62 - {
63 - continue;
64 - }
65 -
66 - string refTargetDir = item.GetMetadata("RefTargetDir");
67 - if (!String.IsNullOrEmpty(refTargetDir))
68 - {
69 - newItemMetadeta.Add("DirectoryIds", refTargetDir);
70 - }
71 -
72 - string refName = item.GetMetadata("Name");
73 - if (!String.IsNullOrEmpty(refName))
74 - {
75 - newItemMetadeta.Add("ProjectName", refName);
76 - }
77 -
78 - newItemMetadeta.Add("ProjectOutputGroups", this.ProjectOutputGroups);
79 -
80 - ITaskItem newItem = new TaskItem(item.ItemSpec, newItemMetadeta);
81 - newItems.Add(newItem);
82 - }
83 -
84 - this.harvestItems = newItems.ToArray();
85 -
86 - return true;
87 - }
88 - }
89 -}
src/wix/WixToolset.BuildTasks/CreateProjectReferenceDefineConstantsAndBindPaths.cs
+1 -1
@@ -152,7 +152,7 @@ namespace WixToolset.BuildTasks
152 {
153 if (!targetPath.Equals(oldTargetPath, StringComparison.OrdinalIgnoreCase))
154 {
155 - defineConstants[targetPathDefine] += ";" + targetPath;
155 + defineConstants[targetPathDefine] += "%3B" + targetPath;
156 }
157
158 // If there was only one targetpath we need to create its culture specific define
src/wix/WixToolset.BuildTasks/UpdateProjectReferenceMetadata.cs
+114 -13
@@ -5,6 +5,7 @@ namespace WixToolset.BuildTasks
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 + using System.Linq;
9 using Microsoft.Build.Framework;
10 using Microsoft.Build.Utilities;
11
@@ -13,6 +14,8 @@ namespace WixToolset.BuildTasks
14 /// </summary>
15 public class UpdateProjectReferenceMetadata : Task
16 {
17 + private static readonly char[] TargetFrameworksSplitter = new char[] { ',', ';' };
18 +
19 /// <summary>
20 /// The list of project references that exist.
21 /// </summary>
@@ -26,40 +29,101 @@ namespace WixToolset.BuildTasks
29 public ITaskItem[] UpdatedProjectReferences { get; private set; }
30
31 /// <summary>
29 - /// Finds all project references requesting publishing and updates them to publish instead of build.
32 + /// Finds all project references requesting publishing and updates them to publish instead of build and
33 + /// sets target framework if requested.
34 /// </summary>
35 /// <returns>True upon completion of the task execution.</returns>
36 public override bool Execute()
37 {
34 - var publishProjectReferences = new List<ITaskItem>();
38 + var updatedProjectReferences = new List<ITaskItem>();
39 var intermediateFolder = Path.GetFullPath(this.IntermediateFolder);
40
41 foreach (var projectReference in this.ProjectReferences)
42 {
39 - var publish = projectReference.GetMetadata("Publish");
40 - var publishDir = projectReference.GetMetadata("PublishDir");
43 + var additionalProjectReferences = new List<ITaskItem>();
44 +
45 + var updatedProjectReference = this.TrySetTargetFrameworksOnProjectReference(projectReference, additionalProjectReferences);
46
42 - if (publish.Equals("true", StringComparison.OrdinalIgnoreCase) ||
43 - (String.IsNullOrWhiteSpace(publish) && !String.IsNullOrWhiteSpace(publishDir)))
47 + if (this.TryAddPublishPropertiesToProjectReference(projectReference, intermediateFolder))
48 {
45 - publishDir = String.IsNullOrWhiteSpace(publishDir) ? this.CalculatePublishDirFromProjectReference(projectReference, intermediateFolder) : Path.GetFullPath(publishDir);
49 + foreach (var additionalProjectReference in additionalProjectReferences)
50 + {
51 + this.TryAddPublishPropertiesToProjectReference(additionalProjectReference, intermediateFolder);
52 + }
53
47 - this.AddPublishPropertiesToProjectReference(projectReference, publishDir);
54 + updatedProjectReference = true;
55 + }
56
49 - publishProjectReferences.Add(projectReference);
57 + if (updatedProjectReference)
58 + {
59 + updatedProjectReferences.Add(projectReference);
60 }
61 +
62 + updatedProjectReferences.AddRange(additionalProjectReferences);
63 }
64
53 - this.UpdatedProjectReferences = publishProjectReferences.ToArray();
65 + this.UpdatedProjectReferences = updatedProjectReferences.ToArray();
66
67 return true;
68 }
69
58 - private string CalculatePublishDirFromProjectReference(ITaskItem projectReference, string intermediateFolder)
70 + private bool TryAddPublishPropertiesToProjectReference(ITaskItem projectReference, string intermediateFolder)
71 {
60 - var publishDir = Path.Combine("publish", Path.GetFileNameWithoutExtension(projectReference.ItemSpec));
72 + var publish = projectReference.GetMetadata("Publish");
73 + var publishDir = projectReference.GetMetadata("PublishDir");
74
62 - return Path.Combine(intermediateFolder, publishDir);
75 + if (publish.Equals("true", StringComparison.OrdinalIgnoreCase) ||
76 + (String.IsNullOrWhiteSpace(publish) && !String.IsNullOrWhiteSpace(publishDir)))
77 + {
78 + if (String.IsNullOrWhiteSpace(publishDir))
79 + {
80 + publishDir = CalculatePublishDirFromProjectReference(projectReference, intermediateFolder);
81 + }
82 +
83 + publishDir = AppendTargetFrameworkFromProjectReference(projectReference, publishDir);
84 +
85 + publishDir = Path.GetFullPath(publishDir);
86 +
87 + this.AddPublishPropertiesToProjectReference(projectReference, publishDir);
88 +
89 + return true;
90 + }
91 +
92 + return false;
93 + }
94 +
95 + private bool TrySetTargetFrameworksOnProjectReference(ITaskItem projectReference, List<ITaskItem> additionalProjectReferences)
96 + {
97 + var setTargetFramework = projectReference.GetMetadata("SetTargetFramework");
98 + var targetFrameworks = projectReference.GetMetadata("TargetFrameworks");
99 + var targetFrameworksToSet = targetFrameworks.Split(TargetFrameworksSplitter).Where(s => !String.IsNullOrWhiteSpace(s)).ToList();
100 +
101 + if (String.IsNullOrWhiteSpace(setTargetFramework) && targetFrameworksToSet.Count > 0)
102 + {
103 + // First, clone the project reference so there are enough duplicates for all of the
104 + // requested target frameworks.
105 + for (var i = 1; i < targetFrameworksToSet.Count; ++i)
106 + {
107 + additionalProjectReferences.Add(new TaskItem(projectReference));
108 + }
109 +
110 + // Then set the target framework on each project reference.
111 + for (var i = 0; i < targetFrameworksToSet.Count; ++i)
112 + {
113 + var reference = (i == 0) ? projectReference : additionalProjectReferences[i - 1];
114 +
115 + this.SetTargetFrameworkOnProjectReference(reference, targetFrameworksToSet[i]);
116 + }
117 +
118 + return true;
119 + }
120 +
121 + if (!String.IsNullOrWhiteSpace(setTargetFramework) && !String.IsNullOrWhiteSpace(targetFrameworks))
122 + {
123 + 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);
124 + }
125 +
126 + return false;
127 }
128
129 private void AddPublishPropertiesToProjectReference(ITaskItem projectReference, string publishDir)
@@ -91,5 +155,42 @@ namespace WixToolset.BuildTasks
155 this.Log.LogMessage(MessageImportance.Low, "Adding publish metadata to project reference {0} Targets {1}, BindPath {2}, AdditionalProperties: {3}",
156 projectReference.ItemSpec, projectReference.GetMetadata("Targets"), projectReference.GetMetadata("BindPath"), projectReference.GetMetadata("AdditionalProperties"));
157 }
158 +
159 + private void SetTargetFrameworkOnProjectReference(ITaskItem projectReference, string targetFramework)
160 + {
161 + projectReference.SetMetadata("SetTargetFramework", $"TargetFramework={targetFramework}");
162 +
163 + var bindName = projectReference.GetMetadata("BindName");
164 + if (String.IsNullOrWhiteSpace(bindName))
165 + {
166 + bindName = Path.GetFileNameWithoutExtension(projectReference.ItemSpec);
167 +
168 + projectReference.SetMetadata("BindName", $"{bindName}.{targetFramework}");
169 + }
170 +
171 + this.Log.LogMessage(MessageImportance.Low, "Adding target framework metadata to project reference {0} SetTargetFramework: {1}, BindName: {2}",
172 + projectReference.ItemSpec, projectReference.GetMetadata("SetTargetFramework"), projectReference.GetMetadata("BindName"));
173 + }
174 +
175 + private static string CalculatePublishDirFromProjectReference(ITaskItem projectReference, string intermediateFolder)
176 + {
177 + var publishDir = Path.Combine("publish", Path.GetFileNameWithoutExtension(projectReference.ItemSpec));
178 +
179 + return Path.Combine(intermediateFolder, publishDir);
180 + }
181 +
182 + private static string AppendTargetFrameworkFromProjectReference(ITaskItem projectReference, string publishDir)
183 + {
184 + var setTargetFramework = projectReference.GetMetadata("SetTargetFramework");
185 +
186 + if (setTargetFramework.StartsWith("TargetFramework=") && setTargetFramework.Length > "TargetFramework=".Length)
187 + {
188 + var targetFramework = setTargetFramework.Substring("TargetFramework=".Length);
189 +
190 + publishDir = Path.Combine(publishDir, targetFramework);
191 + }
192 +
193 + return publishDir;
194 + }
195 }
196 }
src/wix/WixToolset.Sdk/tools/wix.targets
+4 -11
@@ -245,7 +245,7 @@
245 @(_MSBuildProjectReferenceExistent) - References to projects that exist.
246
247 [OUT]
248 - @(_MSBuildProjectReferenceExistent) - Updated project references.
248 + @(_MSBuildProjectReferenceExistent) - Project references updated as necessary.
249 ================================================================================================
250 -->
251 <Target
@@ -256,19 +256,12 @@
256 <UpdateProjectReferenceMetadata
257 ProjectReferences="@(_MSBuildProjectReferenceExistent)"
258 IntermediateFolder="$(IntermediateOutputPath)">
259 - <Output TaskParameter="UpdatedProjectReferences" ItemName="_WixPublishProjectReferences" />
259 + <Output TaskParameter="UpdatedProjectReferences" ItemName="_WixUpdatedProjectReferences" />
260 </UpdateProjectReferenceMetadata>
261
262 <ItemGroup>
263 - <_MSBuildProjectReferenceExistent Remove='@(_WixPublishProjectReferences)' />
264 - <_MSBuildProjectReferenceExistent Include='@(_WixPublishProjectReferences)' />
265 - </ItemGroup>
266 -
267 - <ItemGroup>
268 - <_MSBuildProjectReferenceExistent Condition="'%(_MSBuildProjectReferenceExistent.SetTargetFramework)' == ''">
269 - <SkipGetTargetFrameworkProperties Condition="'$(EnableDynamicPlatformResolution)' != 'true'">true</SkipGetTargetFrameworkProperties>
270 - <UndefineProperties>%(_MSBuildProjectReferenceExistent.UndefineProperties);TargetFramework</UndefineProperties>
271 - </_MSBuildProjectReferenceExistent>
263 + <_MSBuildProjectReferenceExistent Remove='@(_WixUpdatedProjectReferences)' />
264 + <_MSBuildProjectReferenceExistent Include='@(_WixUpdatedProjectReferences)' />
265 </ItemGroup>
266 </Target>
267