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