aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2023-02-14 23:11:01 -0800
committerRob Mensching <rob@firegiant.com>2023-02-21 23:31:32 -0700
commit69f11ee0275692528ed034a3885fa9f0c1504704 (patch)
tree1595cf034e22ff8ffc670dfa0fc76db63809689e /src
parentd97ca048b45077484d068580eb9ae0b231d77009 (diff)
downloadwix-69f11ee0275692528ed034a3885fa9f0c1504704.tar.gz
wix-69f11ee0275692528ed034a3885fa9f0c1504704.tar.bz2
wix-69f11ee0275692528ed034a3885fa9f0c1504704.zip
Full support for multitargeting project references in a .wixproj
Closes 7241
Diffstat (limited to 'src')
-rw-r--r--src/wix/WixToolset.BuildTasks/CreateProjectReferenceDefineConstantsAndBindPaths.cs14
-rw-r--r--src/wix/WixToolset.BuildTasks/MetadataValue.cs68
-rw-r--r--src/wix/WixToolset.BuildTasks/MetadataValueList.cs100
-rw-r--r--src/wix/WixToolset.BuildTasks/ToolsCommon.cs (renamed from src/wix/WixToolset.BuildTasks/Common.cs)13
-rw-r--r--src/wix/WixToolset.BuildTasks/UpdateProjectReferenceMetadata.cs466
-rw-r--r--src/wix/test/WixToolsetTest.Sdk/MsbuildFixture.cs132
-rw-r--r--src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/Directory.Build.props3
-rw-r--r--src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/Directory.Build.targets3
-rw-r--r--src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/PackageReleaseAndDebug/Package.wxs36
-rw-r--r--src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/PackageReleaseAndDebug/PackageReleaseAndDebug.wixproj10
-rw-r--r--src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/PackageUsingExplicitTfmAndRids/Package.wxs26
-rw-r--r--src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/PackageUsingExplicitTfmAndRids/PackageUsingExplicitTfmAndRids.wixproj10
-rw-r--r--src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/PackageUsingRids/Package.wxs24
-rw-r--r--src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/PackageUsingRids/PackageUsingRids.wixproj10
-rw-r--r--src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/TestExe/Program.cs14
-rw-r--r--src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/TestExe/TestExe.csproj12
16 files changed, 834 insertions, 107 deletions
diff --git a/src/wix/WixToolset.BuildTasks/CreateProjectReferenceDefineConstantsAndBindPaths.cs b/src/wix/WixToolset.BuildTasks/CreateProjectReferenceDefineConstantsAndBindPaths.cs
index 7ac00241..ae9d8abe 100644
--- a/src/wix/WixToolset.BuildTasks/CreateProjectReferenceDefineConstantsAndBindPaths.cs
+++ b/src/wix/WixToolset.BuildTasks/CreateProjectReferenceDefineConstantsAndBindPaths.cs
@@ -31,7 +31,7 @@ namespace WixToolset.BuildTasks
31 public override bool Execute() 31 public override bool Execute()
32 { 32 {
33 var bindPaths = new Dictionary<string, List<ITaskItem>>(StringComparer.OrdinalIgnoreCase); 33 var bindPaths = new Dictionary<string, List<ITaskItem>>(StringComparer.OrdinalIgnoreCase);
34 var defineConstants = new Dictionary<string, string>(); 34 var defineConstants = new SortedDictionary<string, string>();
35 35
36 foreach (var resolvedReference in this.ResolvedProjectReferences) 36 foreach (var resolvedReference in this.ResolvedProjectReferences)
37 { 37 {
@@ -46,7 +46,7 @@ namespace WixToolset.BuildTasks
46 return true; 46 return true;
47 } 47 }
48 48
49 private void AddBindPathsForResolvedReference(Dictionary<string, List<ITaskItem>> bindPathByPaths, ITaskItem resolvedReference) 49 private void AddBindPathsForResolvedReference(IDictionary<string, List<ITaskItem>> bindPathByPaths, ITaskItem resolvedReference)
50 { 50 {
51 // If the BindName was not explicitly provided, try to use the source project's filename 51 // If the BindName was not explicitly provided, try to use the source project's filename
52 // as the bind name. 52 // as the bind name.
@@ -84,7 +84,7 @@ namespace WixToolset.BuildTasks
84 } 84 }
85 } 85 }
86 86
87 private void AddDefineConstantsForResolvedReference(Dictionary<string, string> defineConstants, ITaskItem resolvedReference) 87 private void AddDefineConstantsForResolvedReference(IDictionary<string, string> defineConstants, ITaskItem resolvedReference)
88 { 88 {
89 var configuration = resolvedReference.GetMetadata("Configuration"); 89 var configuration = resolvedReference.GetMetadata("Configuration");
90 var fullConfiguration = resolvedReference.GetMetadata("FullConfiguration"); 90 var fullConfiguration = resolvedReference.GetMetadata("FullConfiguration");
@@ -96,7 +96,7 @@ namespace WixToolset.BuildTasks
96 var projectFileName = Path.GetFileName(projectPath); 96 var projectFileName = Path.GetFileName(projectPath);
97 var projectName = Path.GetFileNameWithoutExtension(projectPath); 97 var projectName = Path.GetFileNameWithoutExtension(projectPath);
98 98
99 var referenceName = ToolsCommon.GetIdentifierFromName(ToolsCommon.GetMetadataOrDefault(resolvedReference, "Name", projectName)); 99 var referenceName = ToolsCommon.CreateIdentifierFromValue(ToolsCommon.GetMetadataOrDefault(resolvedReference, "Name", projectName));
100 100
101 var targetPath = resolvedReference.GetMetadata("FullPath"); 101 var targetPath = resolvedReference.GetMetadata("FullPath");
102 var targetDir = Path.GetDirectoryName(targetPath) + Path.DirectorySeparatorChar; 102 var targetDir = Path.GetDirectoryName(targetPath) + Path.DirectorySeparatorChar;
@@ -156,12 +156,12 @@ namespace WixToolset.BuildTasks
156 } 156 }
157 157
158 // If there was only one targetpath we need to create its culture specific define 158 // If there was only one targetpath we need to create its culture specific define
159 if (!oldTargetPath.Contains(";")) 159 if (!oldTargetPath.Contains("%3B"))
160 { 160 {
161 var oldSubFolder = FindSubfolder(oldTargetPath, targetDir, targetFileName); 161 var oldSubFolder = FindSubfolder(oldTargetPath, targetDir, targetFileName);
162 if (!String.IsNullOrEmpty(oldSubFolder)) 162 if (!String.IsNullOrEmpty(oldSubFolder))
163 { 163 {
164 defineConstants[referenceName + "." + oldSubFolder.Replace('\\', '_') + ".TargetPath"] = oldTargetPath; 164 defineConstants[referenceName + "." + ToolsCommon.CreateIdentifierFromValue(oldSubFolder) + ".TargetPath"] = oldTargetPath;
165 } 165 }
166 } 166 }
167 167
@@ -169,7 +169,7 @@ namespace WixToolset.BuildTasks
169 var subFolder = FindSubfolder(targetPath, targetDir, targetFileName); 169 var subFolder = FindSubfolder(targetPath, targetDir, targetFileName);
170 if (!String.IsNullOrEmpty(subFolder)) 170 if (!String.IsNullOrEmpty(subFolder))
171 { 171 {
172 defineConstants[referenceName + "." + subFolder.Replace('\\', '_') + ".TargetPath"] = targetPath; 172 defineConstants[referenceName + "." + ToolsCommon.CreateIdentifierFromValue(subFolder) + ".TargetPath"] = targetPath;
173 } 173 }
174 } 174 }
175 else 175 else
diff --git a/src/wix/WixToolset.BuildTasks/MetadataValue.cs b/src/wix/WixToolset.BuildTasks/MetadataValue.cs
new file mode 100644
index 00000000..4512ffa5
--- /dev/null
+++ b/src/wix/WixToolset.BuildTasks/MetadataValue.cs
@@ -0,0 +1,68 @@
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
3namespace WixToolset.BuildTasks
4{
5 using System;
6 using Microsoft.Build.Framework;
7
8 internal class MetadataValue
9 {
10 public MetadataValue(ITaskItem item, string name, string valuePrefix = null, string defaultValue = "")
11 {
12 this.Item = item;
13 this.Name = name;
14
15 var value = item.GetMetadata(name);
16
17 this.HadValue = !String.IsNullOrWhiteSpace(value);
18 this.OriginalValue = value;
19
20 if (!this.HadValue)
21 {
22 this.Value = defaultValue;
23 this.ValidValue = true;
24 }
25 else if (String.IsNullOrWhiteSpace(valuePrefix))
26 {
27 this.Value = value;
28 this.ValidValue = true;
29 }
30 else if (value.StartsWith(valuePrefix) && value.Length > valuePrefix.Length)
31 {
32 this.Value = value.Substring(valuePrefix.Length);
33 this.ValidValue = true;
34 }
35 }
36
37 public ITaskItem Item { get; }
38
39 public string Name { get; }
40
41 public bool HadValue { get; }
42
43 public bool Modified => !String.Equals(this.OriginalValue, this.Value, StringComparison.Ordinal);
44
45 public string OriginalValue { get; }
46
47 public string Value { get; private set; }
48
49 public bool ValidValue { get; }
50
51 public void SetValue(string value)
52 {
53 this.Value = value;
54 }
55
56 public void Apply()
57 {
58 if (String.IsNullOrWhiteSpace(this.Value))
59 {
60 this.Item.RemoveMetadata(this.Name);
61 }
62 else
63 {
64 this.Item.SetMetadata(this.Name, this.Value);
65 }
66 }
67 }
68}
diff --git a/src/wix/WixToolset.BuildTasks/MetadataValueList.cs b/src/wix/WixToolset.BuildTasks/MetadataValueList.cs
new file mode 100644
index 00000000..cf93277b
--- /dev/null
+++ b/src/wix/WixToolset.BuildTasks/MetadataValueList.cs
@@ -0,0 +1,100 @@
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
3namespace WixToolset.BuildTasks
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using Microsoft.Build.Framework;
9
10 internal class MetadataValueList
11 {
12 private static readonly char[] MetadataListSplitter = new char[] { ',', ';' };
13
14 public MetadataValueList(ITaskItem item, string name)
15 {
16 this.Item = item;
17 this.Name = name;
18
19 var value = item.GetMetadata(name);
20
21 this.HadValue = !String.IsNullOrWhiteSpace(value);
22 this.OriginalValue = value;
23
24 this.Values = value.Split(MetadataListSplitter).Where(s => !String.IsNullOrWhiteSpace(s)).ToList();
25 }
26
27 public ITaskItem Item { get; }
28
29 public string Name { get; }
30
31 public bool HadValue { get; }
32
33 public string OriginalValue { get; }
34
35 public bool Modified { get; private set; }
36
37 public List<string> Values { get; }
38
39 public void Clear()
40 {
41 if (this.Values.Count > 0)
42 {
43 this.Modified = true;
44 this.Values.Clear();
45 }
46 }
47
48 public void SetValue(string prefix, string value)
49 {
50 if (!String.IsNullOrEmpty(prefix))
51 {
52 value = String.IsNullOrWhiteSpace(value) ? null : prefix + value;
53
54 for (var i = 0; i < this.Values.Count; ++i)
55 {
56 if (this.Values[i].StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
57 {
58 if (value == null)
59 {
60 this.Values.RemoveAt(i);
61 }
62 else
63 {
64 this.Values[i] = value;
65 }
66
67 this.Modified = true;
68 return;
69 }
70 }
71 }
72
73 if (!String.IsNullOrWhiteSpace(value) && !this.Values.Contains(value))
74 {
75 this.Modified = true;
76 this.Values.Add(value);
77 }
78 }
79
80 public void AddRange(IEnumerable<string> values)
81 {
82 foreach (var value in values)
83 {
84 this.SetValue(null, value);
85 }
86 }
87
88 public void Apply()
89 {
90 if (this.Values.Count == 0)
91 {
92 this.Item.RemoveMetadata(this.Name);
93 }
94 else
95 {
96 this.Item.SetMetadata(this.Name, String.Join(";", this.Values));
97 }
98 }
99 }
100}
diff --git a/src/wix/WixToolset.BuildTasks/Common.cs b/src/wix/WixToolset.BuildTasks/ToolsCommon.cs
index 837dfb17..4857de01 100644
--- a/src/wix/WixToolset.BuildTasks/Common.cs
+++ b/src/wix/WixToolset.BuildTasks/ToolsCommon.cs
@@ -18,18 +18,17 @@ namespace WixToolset.BuildTasks
18 private static readonly Regex IllegalIdentifierCharacters = new Regex(@"[^A-Za-z0-9_\.]|\.{2,}"); // non 'words' and assorted valid characters 18 private static readonly Regex IllegalIdentifierCharacters = new Regex(@"[^A-Za-z0-9_\.]|\.{2,}"); // non 'words' and assorted valid characters
19 19
20 /// <summary> 20 /// <summary>
21 /// Return an identifier based on passed file/directory name 21 /// Return an identifier based on passed value.
22 /// </summary> 22 /// </summary>
23 /// <param name="name">File/directory name to generate identifer from</param> 23 /// <param name="value">Value to create identifer from.</param>
24 /// <returns>A version of the name that is a legal identifier.</returns> 24 /// <returns>A version of the value that is a legal identifier.</returns>
25 /// <remarks>This is duplicated from WiX's Common class.</remarks> 25 public static string CreateIdentifierFromValue(string value)
26 public static string GetIdentifierFromName(string name)
27 { 26 {
28 var result = IllegalIdentifierCharacters.Replace(name, "_"); // replace illegal characters with "_". 27 var result = IllegalIdentifierCharacters.Replace(value, "_"); // replace illegal characters with "_".
29 28
30 // MSI identifiers must begin with an alphabetic character or an 29 // MSI identifiers must begin with an alphabetic character or an
31 // underscore. Prefix all other values with an underscore. 30 // underscore. Prefix all other values with an underscore.
32 if (AddPrefix.IsMatch(name)) 31 if (AddPrefix.IsMatch(value))
33 { 32 {
34 result = String.Concat("_", result); 33 result = String.Concat("_", result);
35 } 34 }
diff --git a/src/wix/WixToolset.BuildTasks/UpdateProjectReferenceMetadata.cs b/src/wix/WixToolset.BuildTasks/UpdateProjectReferenceMetadata.cs
index c1ef45bc..d391bdb9 100644
--- a/src/wix/WixToolset.BuildTasks/UpdateProjectReferenceMetadata.cs
+++ b/src/wix/WixToolset.BuildTasks/UpdateProjectReferenceMetadata.cs
@@ -6,15 +6,16 @@ namespace WixToolset.BuildTasks
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.IO; 7 using System.IO;
8 using System.Linq; 8 using System.Linq;
9 using System.Text;
9 using Microsoft.Build.Framework; 10 using Microsoft.Build.Framework;
10 using Microsoft.Build.Utilities; 11 using Microsoft.Build.Utilities;
11 12
12 /// <summary> 13 /// <summary>
13 /// This task adds publish metadata to the appropriate project references. 14 /// This task adds mutli-targeting and publish metadata to the appropriate project references.
14 /// </summary> 15 /// </summary>
15 public class UpdateProjectReferenceMetadata : Task 16 public class UpdateProjectReferenceMetadata : Task
16 { 17 {
17 private static readonly char[] TargetFrameworksSplitter = new char[] { ',', ';' }; 18 private static readonly char[] MetadataPairSplitter = new char[] { '|' };
18 19
19 /// <summary> 20 /// <summary>
20 /// The list of project references that exist. 21 /// The list of project references that exist.
@@ -29,168 +30,449 @@ namespace WixToolset.BuildTasks
29 public ITaskItem[] UpdatedProjectReferences { get; private set; } 30 public ITaskItem[] UpdatedProjectReferences { get; private set; }
30 31
31 /// <summary> 32 /// <summary>
32 /// Finds all project references requesting publishing and updates them to publish instead of build and 33 /// Finds all project references requesting multi-targeting and publishing and updates them to publish instead of build and
33 /// sets target framework if requested. 34 /// sets target framework if requested.
34 /// </summary> 35 /// </summary>
35 /// <returns>True upon completion of the task execution.</returns> 36 /// <returns>True upon completion of the task execution.</returns>
36 public override bool Execute() 37 public override bool Execute()
37 { 38 {
38 var updatedProjectReferences = new List<ITaskItem>();
39 var intermediateFolder = Path.GetFullPath(this.IntermediateFolder); 39 var intermediateFolder = Path.GetFullPath(this.IntermediateFolder);
40 40
41 foreach (var projectReference in this.ProjectReferences) 41 // Create the project reference facades.
42 { 42 var projectReferenceFacades = this.ProjectReferences.Select(p => ProjectReferenceFacade.CreateFacade(p, this.Log, intermediateFolder));
43 var additionalProjectReferences = new List<ITaskItem>(); 43
44 // Expand the facade count by applying Configurations/Platforms.
45 projectReferenceFacades = this.ExpandProjectReferencesForConfigurationsAndPlatforms(projectReferenceFacades);
46
47 // Expand the facade count by applying TargetFrameworks/RuntimeIdentifiers.
48 projectReferenceFacades = this.ExpandProjectReferencesForTargetFrameworksAndRuntimeIdentifiers(projectReferenceFacades);
44 49
45 var updatedProjectReference = this.TrySetTargetFrameworksOnProjectReference(projectReference, additionalProjectReferences); 50 // Assign any metadata added during expansion above to the project references.
51 this.UpdatedProjectReferences = this.AssignMetadataToProjectReferences(projectReferenceFacades).ToArray();
52
53 return true;
54 }
46 55
47 if (this.TryAddPublishPropertiesToProjectReference(projectReference, intermediateFolder)) 56 private IEnumerable<ProjectReferenceFacade> ExpandProjectReferencesForConfigurationsAndPlatforms(IEnumerable<ProjectReferenceFacade> projectReferenceFacades)
57 {
58 foreach (var projectReferenceFacade in projectReferenceFacades)
59 {
60 var configurationsWithPlatforms = ExpandTerms(projectReferenceFacade.AvailableConfigurations, projectReferenceFacade.AvailablePlatforms).ToList();
61
62 if (configurationsWithPlatforms.Count == 0)
63 {
64 yield return projectReferenceFacade;
65 }
66 else
48 { 67 {
49 foreach (var additionalProjectReference in additionalProjectReferences) 68 var expand = new List<ProjectReferenceFacade>(configurationsWithPlatforms.Count)
50 { 69 {
51 this.TryAddPublishPropertiesToProjectReference(additionalProjectReference, intermediateFolder); 70 projectReferenceFacade
71 };
72
73 // First, clone the project reference so there are enough facades for all of the
74 // requested configurations/platforms.
75 for (var i = 1; i < configurationsWithPlatforms.Count; ++i)
76 {
77 expand.Add(projectReferenceFacade.Clone());
52 } 78 }
53 79
54 updatedProjectReference = true; 80 // Then set the configuration/platform on each project reference.
81 for (var i = 0; i < configurationsWithPlatforms.Count; ++i)
82 {
83 expand[i].Configuration = configurationsWithPlatforms[i].FirstTerm;
84 expand[i].Platform = configurationsWithPlatforms[i].SecondTerm;
85
86 yield return expand[i];
87 }
55 } 88 }
89 }
90 }
91
92 private IEnumerable<ProjectReferenceFacade> ExpandProjectReferencesForTargetFrameworksAndRuntimeIdentifiers(IEnumerable<ProjectReferenceFacade> projectReferenceFacades)
93 {
94 foreach (var projectReferenceFacade in projectReferenceFacades)
95 {
96 var tfmsWithRids = ExpandTerms(projectReferenceFacade.AvailableTargetFrameworks, projectReferenceFacade.AvailableRuntimeIdentifiers).ToList();
56 97
57 if (updatedProjectReference) 98 if (tfmsWithRids.Count == 0)
58 { 99 {
59 updatedProjectReferences.Add(projectReference); 100 yield return projectReferenceFacade;
60 } 101 }
102 else
103 {
104 var expand = new List<ProjectReferenceFacade>(tfmsWithRids.Count)
105 {
106 projectReferenceFacade
107 };
61 108
62 updatedProjectReferences.AddRange(additionalProjectReferences); 109 // First, clone the project reference so there are enough facades for all of the
63 } 110 // requested target frameworks/runtime identifiers.
111 for (var i = 1; i < tfmsWithRids.Count; ++i)
112 {
113 expand.Add(projectReferenceFacade.Clone());
114 }
64 115
65 this.UpdatedProjectReferences = updatedProjectReferences.ToArray(); 116 // Then set the target framework/runtime identifier on each project reference.
117 for (var i = 0; i < tfmsWithRids.Count; ++i)
118 {
119 expand[i].TargetFramework = tfmsWithRids[i].FirstTerm;
120 expand[i].RuntimeIdentifier = tfmsWithRids[i].SecondTerm;
66 121
67 return true; 122 yield return expand[i];
123 }
124 }
125 }
68 } 126 }
69 127
70 private bool TryAddPublishPropertiesToProjectReference(ITaskItem projectReference, string intermediateFolder) 128 private IEnumerable<ITaskItem> AssignMetadataToProjectReferences(IEnumerable<ProjectReferenceFacade> facades)
71 { 129 {
72 var publish = projectReference.GetMetadata("Publish"); 130 foreach (var facade in facades)
73 var publishDir = projectReference.GetMetadata("PublishDir");
74
75 if (publish.Equals("true", StringComparison.OrdinalIgnoreCase) ||
76 (String.IsNullOrWhiteSpace(publish) && !String.IsNullOrWhiteSpace(publishDir)))
77 { 131 {
78 if (String.IsNullOrWhiteSpace(publishDir)) 132 var projectReference = facade.ProjectReference;
133 var targetsValue = new MetadataValueList(projectReference, "Targets");
134
135 if (facade.Modified)
79 { 136 {
80 publishDir = CalculatePublishDirFromProjectReference(projectReference, intermediateFolder); 137 var configurationValue = new MetadataValue(projectReference, "Configuration");
138 var platformValue = new MetadataValue(projectReference, "Platform");
139 var fullConfigurationValue = new MetadataValue(projectReference, "FullConfiguration");
140 var additionalProperties = new MetadataValueList(projectReference, "AdditionalProperties");
141 var bindName = new MetadataValue(projectReference, "BindName");
142 var bindPath = new MetadataValue(projectReference, "BindPath");
143
144 var publishDir = facade.CalculatePublishDir();
145
146 additionalProperties.SetValue("PublishDir=", publishDir);
147 additionalProperties.SetValue("RuntimeIdentifier=", facade.RuntimeIdentifier);
148
149 additionalProperties.Apply();
150
151 if (!String.IsNullOrWhiteSpace(facade.Configuration))
152 {
153 projectReference.SetMetadata("SetConfiguration", $"Configuration={facade.Configuration}");
154
155 if (configurationValue.HadValue)
156 {
157 configurationValue.SetValue(facade.Configuration);
158 configurationValue.Apply();
159 }
160 }
161
162 if (!String.IsNullOrWhiteSpace(facade.Platform))
163 {
164 projectReference.SetMetadata("SetPlatform", $"Platform={facade.Platform}");
165
166 if (platformValue.HadValue)
167 {
168 platformValue.SetValue(facade.Platform);
169 platformValue.Apply();
170 }
171 }
172
173 if (fullConfigurationValue.HadValue && (configurationValue.Modified || platformValue.Modified))
174 {
175 fullConfigurationValue.SetValue($"{configurationValue.Value}|{platformValue.Value}");
176 fullConfigurationValue.Apply();
177 }
178
179 if (!String.IsNullOrWhiteSpace(facade.TargetFramework))
180 {
181 projectReference.SetMetadata("SetTargetFramework", $"TargetFramework={facade.TargetFramework}");
182 }
183
184 var bindNameSuffix = facade.CalculateBindNameSuffix();
185 if (!String.IsNullOrWhiteSpace(bindNameSuffix))
186 {
187 var bindNamePrefix = bindName.HadValue ? bindName.Value : Path.GetFileNameWithoutExtension(projectReference.ItemSpec);
188
189 bindName.SetValue(ToolsCommon.CreateIdentifierFromValue(bindNamePrefix + bindNameSuffix));
190 bindName.Apply();
191 }
192
193 if (!bindPath.HadValue)
194 {
195 bindPath.SetValue(publishDir);
196 bindPath.Apply();
197 }
81 } 198 }
82 199
83 publishDir = AppendTargetFrameworkFromProjectReference(projectReference, publishDir); 200 if (facade.Publish)
201 {
202 var publishTargets = new MetadataValueList(projectReference, "PublishTargets");
203
204 if (publishTargets.HadValue)
205 {
206 targetsValue.AddRange(publishTargets.Values);
207 }
208 else
209 {
210 targetsValue.SetValue(null, "Publish");
211 }
212
213 // GetTargetPath target always needs to be last so we can set bind paths to the output location of the project reference.
214 if (targetsValue.Values.Count == 0 || !"GetTargetPath".Equals(targetsValue.Values[targetsValue.Values.Count - 1], StringComparison.OrdinalIgnoreCase))
215 {
216 targetsValue.Values.Remove("GetTargetPath");
217 targetsValue.SetValue(null, "GetTargetPath");
218 }
219 }
84 220
85 publishDir = Path.GetFullPath(publishDir); 221 if (targetsValue.Modified)
222 {
223 targetsValue.Apply();
224 }
86 225
87 this.AddPublishPropertiesToProjectReference(projectReference, publishDir); 226 this.Log.LogMessage(MessageImportance.Low, "Adding metadata to project reference {0} Targets {1}, BindPath {2}={3}, AdditionalProperties: {4}",
227 projectReference.ItemSpec, projectReference.GetMetadata("Targets"), projectReference.GetMetadata("BindName"), projectReference.GetMetadata("BindPath"), projectReference.GetMetadata("AdditionalProperties"));
88 228
89 return true; 229 yield return projectReference;
90 } 230 }
91
92 return false;
93 } 231 }
94 232
95 private bool TrySetTargetFrameworksOnProjectReference(ITaskItem projectReference, List<ITaskItem> additionalProjectReferences) 233 private static IEnumerable<ExpansionTerms> ExpandTerms(IReadOnlyCollection<string> firstTerms, IReadOnlyCollection<string> secondTerms)
96 { 234 {
97 var setTargetFramework = projectReference.GetMetadata("SetTargetFramework"); 235 if (firstTerms.Count == 0)
98 var targetFrameworks = projectReference.GetMetadata("TargetFrameworks"); 236 {
99 var targetFrameworksToSet = targetFrameworks.Split(TargetFrameworksSplitter).Where(s => !String.IsNullOrWhiteSpace(s)).ToList(); 237 firstTerms = new[] { String.Empty };
238 }
100 239
101 if (String.IsNullOrWhiteSpace(setTargetFramework) && targetFrameworksToSet.Count > 0) 240 foreach (var firstTerm in firstTerms)
102 { 241 {
103 // First, clone the project reference so there are enough duplicates for all of the 242 var pairSplit = firstTerm.Split(MetadataPairSplitter, 2);
104 // requested target frameworks. 243
105 for (var i = 1; i < targetFrameworksToSet.Count; ++i) 244 // No pair indicator so expand the first term by the second term.
245 if (pairSplit.Length == 1)
106 { 246 {
107 additionalProjectReferences.Add(new TaskItem(projectReference)); 247 if (secondTerms.Count == 0)
248 {
249 yield return new ExpansionTerms(firstTerm, null);
250 }
251 else
252 {
253 foreach (var secondTerm in secondTerms)
254 {
255 yield return new ExpansionTerms(firstTerm, secondTerm);
256 }
257 }
108 } 258 }
109 259 else // there was a pair like "first|second" or "first|" or "|second" in the first term, so return that value as the pair.
110 // Then set the target framework on each project reference.
111 for (var i = 0; i < targetFrameworksToSet.Count; ++i)
112 { 260 {
113 var reference = (i == 0) ? projectReference : additionalProjectReferences[i - 1]; 261 yield return new ExpansionTerms(pairSplit[0], pairSplit[1]);
114
115 this.SetTargetFrameworkOnProjectReference(reference, targetFrameworksToSet[i]);
116 } 262 }
263 }
264 }
117 265
118 return true; 266 private class ProjectReferenceFacade
267 {
268 private string configuration;
269 private string platform;
270 private string targetFramework;
271 private string runtimeIdentifier;
272
273 public ProjectReferenceFacade(ITaskItem projectReference, IReadOnlyCollection<string> availableConfigurations, string configuration, IReadOnlyCollection<string> availablePlatforms, string platform, IReadOnlyCollection<string> availableTargetFrameworks, string targetFramework, IReadOnlyCollection<string> availableRuntimeIdentifiers, string runtimeIdentifier, string publishBaseDir)
274 {
275 this.ProjectReference = projectReference;
276 this.AvailableConfigurations = availableConfigurations;
277 this.configuration = configuration;
278 this.AvailablePlatforms = availablePlatforms;
279 this.platform = platform;
280 this.AvailableTargetFrameworks = availableTargetFrameworks;
281 this.targetFramework = targetFramework;
282 this.AvailableRuntimeIdentifiers = availableRuntimeIdentifiers;
283 this.runtimeIdentifier = runtimeIdentifier;
284 this.PublishBaseDir = publishBaseDir;
285 this.Modified = !String.IsNullOrWhiteSpace(configuration) || !String.IsNullOrWhiteSpace(platform) ||
286 !String.IsNullOrWhiteSpace(targetFramework) || !String.IsNullOrWhiteSpace(runtimeIdentifier) ||
287 !String.IsNullOrWhiteSpace(publishBaseDir);
119 } 288 }
120 289
121 if (!String.IsNullOrWhiteSpace(setTargetFramework) && !String.IsNullOrWhiteSpace(targetFrameworks)) 290 public ITaskItem ProjectReference { get; }
291
292 public bool Modified { get; private set; }
293
294 public IReadOnlyCollection<string> AvailableConfigurations { get; }
295
296 public IReadOnlyCollection<string> AvailablePlatforms { get; }
297
298 public IReadOnlyCollection<string> AvailableRuntimeIdentifiers { get; }
299
300 public IReadOnlyCollection<string> AvailableTargetFrameworks { get; }
301
302 public bool Publish => !String.IsNullOrEmpty(this.PublishBaseDir);
303
304 public string PublishBaseDir { get; }
305
306 public string Configuration
122 { 307 {
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); 308 get => this.configuration;
309 set => this.configuration = this.SetWithModified(value, this.configuration);
124 } 310 }
125 311
126 return false; 312 public string Platform
127 } 313 {
314 get => this.platform;
315 set => this.platform = this.SetWithModified(value, this.platform);
316 }
128 317
129 private void AddPublishPropertiesToProjectReference(ITaskItem projectReference, string publishDir) 318 public string TargetFramework
130 {
131 var additionalProperties = projectReference.GetMetadata("AdditionalProperties");
132 if (!String.IsNullOrWhiteSpace(additionalProperties))
133 { 319 {
134 additionalProperties += ";"; 320 get => this.targetFramework;
321 set => this.targetFramework = this.SetWithModified(value, this.targetFramework);
135 } 322 }
136 323
137 additionalProperties += "PublishDir=" + publishDir; 324 public string RuntimeIdentifier
325 {
326 get => this.runtimeIdentifier;
327 set => this.runtimeIdentifier = this.SetWithModified(value, this.runtimeIdentifier);
328 }
138 329
139 var bindPath = ToolsCommon.GetMetadataOrDefault(projectReference, "BindPath", publishDir); 330 public ProjectReferenceFacade Clone()
331 {
332 return new ProjectReferenceFacade(new TaskItem(this.ProjectReference), this.AvailableConfigurations, this.configuration, this.AvailablePlatforms, this.platform, this.AvailableTargetFrameworks, this.targetFramework, this.AvailableRuntimeIdentifiers, this.runtimeIdentifier, this.PublishBaseDir);
333 }
140 334
141 var publishTargets = projectReference.GetMetadata("PublishTargets"); 335 public static ProjectReferenceFacade CreateFacade(ITaskItem projectReference, TaskLoggingHelper logger, string intermediateFolder)
142 if (String.IsNullOrWhiteSpace(publishTargets))
143 { 336 {
144 publishTargets = "Publish;GetTargetPath"; 337 var configurationsValue = new MetadataValueList(projectReference, "Configurations");
338 var setConfigurationValue = new MetadataValue(projectReference, "SetConfiguration", "Configuration=");
339 var platformsValue = new MetadataValueList(projectReference, "Platforms");
340 var setPlatformValue = new MetadataValue(projectReference, "SetPlatform", "Platform=");
341 var targetFrameworksValue = new MetadataValueList(projectReference, "TargetFrameworks");
342 var setTargetFrameworkValue = new MetadataValue(projectReference, "SetTargetFramework", "TargetFramework=");
343 var runtimeIdentifiersValue = new MetadataValueList(projectReference, "RuntimeIdentifiers");
344 var publishValue = new MetadataValue(projectReference, "Publish", null);
345 var publishDirValue = new MetadataValue(projectReference, "PublishDir", null);
346
347 var configurations = GetFromListAndValidateSetValue(configurationsValue, setConfigurationValue, logger, projectReference, "Configuration=Release");
348
349 var platforms = GetFromListAndValidateSetValue(platformsValue, setPlatformValue, logger, projectReference, "Platform=x64");
350
351 var targetFrameworks = GetFromListAndValidateSetValue(targetFrameworksValue, setTargetFrameworkValue, logger, projectReference, "TargetFramework=tfm");
352
353 string publishBaseDir = null;
354
355 if (publishValue.Value.Equals("true", StringComparison.OrdinalIgnoreCase) || (!publishValue.HadValue && publishDirValue.HadValue))
356 {
357 if (publishDirValue.HadValue)
358 {
359 publishBaseDir = publishDirValue.Value;
360 }
361 else
362 {
363 publishBaseDir = Path.Combine(intermediateFolder, "publish", Path.GetFileNameWithoutExtension(projectReference.ItemSpec));
364 }
365 }
366
367 return new ProjectReferenceFacade(projectReference, configurations, null, platforms, null, targetFrameworks, null, runtimeIdentifiersValue.Values, null, publishBaseDir);
145 } 368 }
146 else if (!publishTargets.EndsWith(";GetTargetsPath", StringComparison.OrdinalIgnoreCase)) 369
370 public string CalculatePublishDir()
147 { 371 {
148 publishTargets += ";GetTargetsPath"; 372 if (!this.Publish)
373 {
374 return null;
375 }
376
377 var publishDir = this.PublishBaseDir;
378
379 if (!String.IsNullOrWhiteSpace(this.Configuration))
380 {
381 publishDir = Path.Combine(publishDir, this.Configuration);
382 }
383
384 if (!String.IsNullOrWhiteSpace(this.Platform))
385 {
386 publishDir = Path.Combine(publishDir, this.Platform);
387 }
388
389 if (!String.IsNullOrWhiteSpace(this.TargetFramework))
390 {
391 publishDir = Path.Combine(publishDir, this.TargetFramework);
392 }
393
394 if (!String.IsNullOrWhiteSpace(this.RuntimeIdentifier))
395 {
396 publishDir = Path.Combine(publishDir, this.RuntimeIdentifier);
397 }
398
399
400 return Path.GetFullPath(publishDir);
149 } 401 }
150 402
151 projectReference.SetMetadata("AdditionalProperties", additionalProperties); 403 public string CalculateBindNameSuffix()
152 projectReference.SetMetadata("BindPath", bindPath); 404 {
153 projectReference.SetMetadata("Targets", publishTargets); 405 var sb = new StringBuilder();
154 406
155 this.Log.LogMessage(MessageImportance.Low, "Adding publish metadata to project reference {0} Targets {1}, BindPath {2}, AdditionalProperties: {3}", 407 if (!String.IsNullOrWhiteSpace(this.Configuration))
156 projectReference.ItemSpec, projectReference.GetMetadata("Targets"), projectReference.GetMetadata("BindPath"), projectReference.GetMetadata("AdditionalProperties")); 408 {
157 } 409 sb.AppendFormat(".{0}", this.Configuration);
410 }
158 411
159 private void SetTargetFrameworkOnProjectReference(ITaskItem projectReference, string targetFramework) 412 if (!String.IsNullOrWhiteSpace(this.Platform))
160 { 413 {
161 projectReference.SetMetadata("SetTargetFramework", $"TargetFramework={targetFramework}"); 414 sb.AppendFormat(".{0}", this.Platform);
415 }
416
417 if (!String.IsNullOrWhiteSpace(this.TargetFramework))
418 {
419 sb.AppendFormat(".{0}", this.TargetFramework);
420 }
421
422 if (!String.IsNullOrWhiteSpace(this.RuntimeIdentifier))
423 {
424 sb.AppendFormat(".{0}", this.RuntimeIdentifier);
425 }
426
427 return sb.ToString();
428 }
162 429
163 var bindName = projectReference.GetMetadata("BindName"); 430 private string SetWithModified(string newValue, string oldValue)
164 if (String.IsNullOrWhiteSpace(bindName))
165 { 431 {
166 bindName = Path.GetFileNameWithoutExtension(projectReference.ItemSpec); 432 if (String.IsNullOrWhiteSpace(newValue) && String.IsNullOrWhiteSpace(oldValue))
433 {
434 return String.Empty;
435 }
436 else if (oldValue != newValue)
437 {
438 this.Modified = true;
439 return newValue;
440 }
167 441
168 projectReference.SetMetadata("BindName", $"{bindName}.{targetFramework}"); 442 return oldValue;
169 } 443 }
170 444
171 this.Log.LogMessage(MessageImportance.Low, "Adding target framework metadata to project reference {0} SetTargetFramework: {1}, BindName: {2}", 445 private static List<string> GetFromListAndValidateSetValue(MetadataValueList listValue, MetadataValue setValue, TaskLoggingHelper logger, ITaskItem projectReference, string setExample)
172 projectReference.ItemSpec, projectReference.GetMetadata("SetTargetFramework"), projectReference.GetMetadata("BindName")); 446 {
173 } 447 var targetFrameworks = listValue.Values;
174 448
175 private static string CalculatePublishDirFromProjectReference(ITaskItem projectReference, string intermediateFolder) 449 if (setValue.HadValue)
176 { 450 {
177 var publishDir = Path.Combine("publish", Path.GetFileNameWithoutExtension(projectReference.ItemSpec)); 451 if (listValue.HadValue)
452 {
453 logger.LogMessage("ProjectReference {0} contains metadata for both {1} and {2}. {2} takes precedent so the {1} value '{3}' will be ignored", projectReference.ItemSpec, setValue.Name, listValue.Name, setValue.OriginalValue);
454 }
455 else if (!setValue.ValidValue)
456 {
457 logger.LogError("ProjectReference {0} contains invalid {1} value '{2}'. The {1} value should look something like '{3}'.", projectReference.ItemSpec, setValue.Name, setValue.OriginalValue, setExample);
458 }
459 }
178 460
179 return Path.Combine(intermediateFolder, publishDir); 461 return targetFrameworks;
462 }
180 } 463 }
181 464
182 private static string AppendTargetFrameworkFromProjectReference(ITaskItem projectReference, string publishDir) 465 private class ExpansionTerms
183 { 466 {
184 var setTargetFramework = projectReference.GetMetadata("SetTargetFramework"); 467 public ExpansionTerms(string firstTerm, string secondTerm)
185
186 if (setTargetFramework.StartsWith("TargetFramework=") && setTargetFramework.Length > "TargetFramework=".Length)
187 { 468 {
188 var targetFramework = setTargetFramework.Substring("TargetFramework=".Length); 469 this.FirstTerm = firstTerm;
189 470 this.SecondTerm = secondTerm;
190 publishDir = Path.Combine(publishDir, targetFramework);
191 } 471 }
192 472
193 return publishDir; 473 public string FirstTerm { get; }
474
475 public string SecondTerm { get; }
194 } 476 }
195 } 477 }
196} 478}
diff --git a/src/wix/test/WixToolsetTest.Sdk/MsbuildFixture.cs b/src/wix/test/WixToolsetTest.Sdk/MsbuildFixture.cs
index 98fffbbc..6cbd445c 100644
--- a/src/wix/test/WixToolsetTest.Sdk/MsbuildFixture.cs
+++ b/src/wix/test/WixToolsetTest.Sdk/MsbuildFixture.cs
@@ -670,6 +670,114 @@ namespace WixToolsetTest.Sdk
670 } 670 }
671 } 671 }
672 672
673 [Theory]
674 [InlineData(BuildSystem.DotNetCoreSdk)]
675 [InlineData(BuildSystem.MSBuild)]
676 [InlineData(BuildSystem.MSBuild64)]
677 public void CanBuildMultiTargetingWixlibUsingRids(BuildSystem buildSystem)
678 {
679 var sourceFolder = TestData.Get(@"TestData", "MultiTargetingWixlib");
680
681 using (var fs = new TestDataFolderFileSystem())
682 {
683 fs.Initialize(sourceFolder);
684 var baseFolder = Path.Combine(fs.BaseFolder, "PackageUsingRids");
685 var binFolder = Path.Combine(baseFolder, @"bin\");
686 var filesFolder = Path.Combine(binFolder, "Release", @"PFiles\");
687 var projectPath = Path.Combine(baseFolder, "PackageUsingRids.wixproj");
688
689 var result = MsbuildUtilities.BuildProject(buildSystem, projectPath, new[] {
690 "-Restore",
691 MsbuildUtilities.GetQuotedPropertySwitch(buildSystem, "WixMSBuildProps", MsbuildFixture.WixPropsPath)
692 });
693 result.AssertSuccess();
694
695 var warnings = result.Output.Where(line => line.Contains(": warning")).ToArray();
696 WixAssert.StringCollectionEmpty(warnings);
697
698 var releaseFiles = Directory.EnumerateFiles(filesFolder, "*", SearchOption.AllDirectories);
699 var releaseFileSizes = releaseFiles.Select(p => PathAndSize(p, filesFolder)).OrderBy(s => s).ToArray();
700
701 WixAssert.CompareLineByLine(new[]
702 {
703 @"net472_x64\e_sqlite3.dll - 1601536",
704 @"net472_x86\e_sqlite3.dll - 1207296",
705 @"net6_x64\e_sqlite3.dll - 1601536",
706 @"net6_x86\e_sqlite3.dll - 1207296",
707 }, releaseFileSizes);
708 }
709 }
710
711 [Theory]
712 [InlineData(BuildSystem.DotNetCoreSdk)]
713 [InlineData(BuildSystem.MSBuild)]
714 [InlineData(BuildSystem.MSBuild64)]
715 public void CanBuildMultiTargetingWixlibUsingRidsWithReleaseAndDebug(BuildSystem buildSystem)
716 {
717 var sourceFolder = TestData.Get(@"TestData", "MultiTargetingWixlib");
718
719 using (var fs = new TestDataFolderFileSystem())
720 {
721 fs.Initialize(sourceFolder);
722 var baseFolder = Path.Combine(fs.BaseFolder, "PackageReleaseAndDebug");
723 var binFolder = Path.Combine(baseFolder, @"bin\");
724 var filesFolder = Path.Combine(binFolder, "Release", @"PFiles\");
725 var projectPath = Path.Combine(baseFolder, "PackageReleaseAndDebug.wixproj");
726
727 var result = MsbuildUtilities.BuildProject(buildSystem, projectPath, new[] {
728 "-Restore",
729 MsbuildUtilities.GetQuotedPropertySwitch(buildSystem, "WixMSBuildProps", MsbuildFixture.WixPropsPath)
730 });
731 result.AssertSuccess();
732
733 var warnings = result.Output.Where(line => line.Contains(": warning")).ToArray();
734 WixAssert.StringCollectionEmpty(warnings);
735
736 var releaseFiles = Directory.EnumerateFiles(filesFolder, "*", SearchOption.AllDirectories);
737 var releaseFileSizes = releaseFiles.Select(p => PathAndSize(p, filesFolder)).OrderBy(s => s).ToArray();
738
739 WixAssert.CompareLineByLine(new[]
740 {
741 @"debug_net472_x64\e_sqlite3.dll - 1601536",
742 @"debug_net472_x86\e_sqlite3.dll - 1207296",
743 @"debug_net6_x64\e_sqlite3.dll - 1601536",
744 @"debug_net6_x86\e_sqlite3.dll - 1207296",
745 @"release_net472_x64\e_sqlite3.dll - 1601536",
746 @"release_net472_x86\e_sqlite3.dll - 1207296",
747 @"release_net6_x64\e_sqlite3.dll - 1601536",
748 @"release_net6_x86\e_sqlite3.dll - 1207296",
749 }, releaseFileSizes);
750 }
751 }
752
753 [Theory]
754 [InlineData(BuildSystem.DotNetCoreSdk)]
755 [InlineData(BuildSystem.MSBuild)]
756 [InlineData(BuildSystem.MSBuild64)]
757 public void CannotBuildMultiTargetingWixlibUsingExplicitSubsetOfTfmAndRid(BuildSystem buildSystem)
758 {
759 var sourceFolder = TestData.Get(@"TestData", "MultiTargetingWixlib");
760
761 using (var fs = new TestDataFolderFileSystem())
762 {
763 fs.Initialize(sourceFolder);
764 var baseFolder = Path.Combine(fs.BaseFolder, "PackageUsingExplicitTfmAndRids");
765 var binFolder = Path.Combine(baseFolder, @"bin\");
766 var filesFolder = Path.Combine(binFolder, "Release", @"PFiles\");
767 var projectPath = Path.Combine(baseFolder, "PackageUsingExplicitTfmAndRids.wixproj");
768
769 var result = MsbuildUtilities.BuildProject(buildSystem, projectPath, new[] {
770 "-Restore",
771 MsbuildUtilities.GetQuotedPropertySwitch(buildSystem, "WixMSBuildProps", MsbuildFixture.WixPropsPath)
772 });
773
774 var errors = GetDistinctErrorMessages(result.Output, baseFolder);
775 WixAssert.CompareLineByLine(new[]
776 {
777 @"<basefolder>\Package.wxs(22): error WIX0103: Cannot find the File file '!(bindpath.TestExe.net472.win_x86)\e_sqlite3.dll'. The following paths were checked: !(bindpath.TestExe.net472.win_x86)\e_sqlite3.dll [<basefolder>\PackageUsingExplicitTfmAndRids.wixproj]",
778 }, errors);
779 }
780 }
673 781
674 [Theory] 782 [Theory]
675 [InlineData(BuildSystem.DotNetCoreSdk)] 783 [InlineData(BuildSystem.DotNetCoreSdk)]
@@ -773,9 +881,31 @@ namespace WixToolsetTest.Sdk
773 return ReplacePathsInMessage(message.Substring(start, end - start), baseFolder); 881 return ReplacePathsInMessage(message.Substring(start, end - start), baseFolder);
774 } 882 }
775 883
884 private static string[] GetDistinctErrorMessages(string[] output, string baseFolder)
885 {
886 return output.Where(l => l.Contains(": error ")).Select(line =>
887 {
888 var trimmed = ReplacePathsInMessage(line, baseFolder);
889
890 // If the message starts with a multi-proc build marker (like: "1>" or "2>") trim it.
891 if (trimmed[1] == '>')
892 {
893 trimmed = trimmed.Substring(2);
894 }
895
896 return trimmed;
897 }).Distinct().ToArray();
898 }
899
776 private static string ReplacePathsInMessage(string message, string baseFolder) 900 private static string ReplacePathsInMessage(string message, string baseFolder)
777 { 901 {
778 return message.Replace(baseFolder, "<basefolder>").Trim(); 902 return message.Trim().Replace(baseFolder, "<basefolder>");
903 }
904
905 private static string PathAndSize(string path, string replace)
906 {
907 var fi = new FileInfo(path);
908 return $"{fi.FullName.Replace(replace, String.Empty)} - {fi.Length}";
779 } 909 }
780 } 910 }
781} 911}
diff --git a/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/Directory.Build.props b/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/Directory.Build.props
new file mode 100644
index 00000000..16b4c2cd
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/Directory.Build.props
@@ -0,0 +1,3 @@
1<Project>
2 <!-- Reset any Directory.Build.props that might exists above this folder -->
3</Project> \ No newline at end of file
diff --git a/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/Directory.Build.targets b/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/Directory.Build.targets
new file mode 100644
index 00000000..5901760f
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/Directory.Build.targets
@@ -0,0 +1,3 @@
1<Project>
2 <!-- Reset any Directory.Build.targets that might exists above this folder -->
3</Project> \ No newline at end of file
diff --git a/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/PackageReleaseAndDebug/Package.wxs b/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/PackageReleaseAndDebug/Package.wxs
new file mode 100644
index 00000000..b6da91ba
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/PackageReleaseAndDebug/Package.wxs
@@ -0,0 +1,36 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="Test Package" Manufacturer="~Test" Version="0" Compressed="false" UpgradeCode="11111111-1111-1111-1111-111111111111">
3 <Feature Id="Main">
4 <ComponentGroupRef Id="Stuff"/>
5 </Feature>
6 </Package>
7
8 <Fragment>
9 <ComponentGroup Id="Stuff" Directory="ProgramFilesFolder">
10 <Component Subdirectory="debug_net472_x86">
11 <File Id="debug_net472_x86" Source="!(bindpath.TestExe.Debug.net472.win_x86)\e_sqlite3.dll" />
12 </Component>
13 <Component Subdirectory="debug_net472_x64">
14 <File Id="debug_net472_x64" Source="!(bindpath.TestExe.Debug.net472.win_x64)\e_sqlite3.dll" />
15 </Component>
16 <Component Subdirectory="debug_net6_x86">
17 <File Id="debug_net6_x86" Source="!(bindpath.TestExe.Debug.net6.0.win_x86)\e_sqlite3.dll" />
18 </Component>
19 <Component Subdirectory="debug_net6_x64">
20 <File Id="debug_net6_x64" Source="!(bindpath.TestExe.Debug.net6.0.win_x64)\e_sqlite3.dll" />
21 </Component>
22 <Component Subdirectory="release_net472_x86">
23 <File Id="release_net472_x86" Source="!(bindpath.TestExe.Release.net472.win_x86)\e_sqlite3.dll" />
24 </Component>
25 <Component Subdirectory="release_net472_x64">
26 <File Id="release_net472_x64" Source="!(bindpath.TestExe.Release.net472.win_x64)\e_sqlite3.dll" />
27 </Component>
28 <Component Subdirectory="release_net6_x86">
29 <File Id="release_net6_x86" Source="!(bindpath.TestExe.Release.net6.0.win_x86)\e_sqlite3.dll" />
30 </Component>
31 <Component Subdirectory="release_net6_x64">
32 <File Id="release_net6_x64" Source="!(bindpath.TestExe.Release.net6.0.win_x64)\e_sqlite3.dll" />
33 </Component>
34 </ComponentGroup>
35 </Fragment>
36</Wix>
diff --git a/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/PackageReleaseAndDebug/PackageReleaseAndDebug.wixproj b/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/PackageReleaseAndDebug/PackageReleaseAndDebug.wixproj
new file mode 100644
index 00000000..17e17d98
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/PackageReleaseAndDebug/PackageReleaseAndDebug.wixproj
@@ -0,0 +1,10 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Project>
3 <Import Project="$(WixMSBuildProps)" />
4
5 <ItemGroup>
6 <ProjectReference Include="..\TestExe\TestExe.csproj" Publish="true" Configurations="Debug,Release" TargetFrameworks="net6.0,net472" RuntimeIdentifiers="win-x86,win-x64" />
7 </ItemGroup>
8
9 <Import Project="$(WixTargetsPath)" />
10</Project>
diff --git a/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/PackageUsingExplicitTfmAndRids/Package.wxs b/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/PackageUsingExplicitTfmAndRids/Package.wxs
new file mode 100644
index 00000000..838461da
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/PackageUsingExplicitTfmAndRids/Package.wxs
@@ -0,0 +1,26 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="Test Package" Manufacturer="~Test" Version="0" Compressed="false" UpgradeCode="11111111-1111-1111-1111-111111111111">
3 <Feature Id="Main">
4 <ComponentGroupRef Id="Stuff"/>
5 </Feature>
6 </Package>
7
8 <Fragment>
9 <ComponentGroup Id="Stuff" Directory="ProgramFilesFolder">
10 <Component Subdirectory="net472_x64">
11 <File Id="net472_x64" Source="!(bindpath.TestExe.net472.win_x64)\e_sqlite3.dll" />
12 </Component>
13 <Component Subdirectory="net6_x86">
14 <File Id="net6_x86" Source="!(bindpath.TestExe.net6.0.win_x86)\e_sqlite3.dll" />
15 </Component>
16 <Component Subdirectory="net6_x64">
17 <File Id="net6_x64" Source="!(bindpath.TestExe.net6.0.win_x64)\e_sqlite3.dll" />
18 </Component>
19
20 <!-- This is explicitly not built and causes the build to fail -->
21 <Component Subdirectory="net472_x86">
22 <File Id="net472_x86" Source="!(bindpath.TestExe.net472.win_x86)\e_sqlite3.dll" />
23 </Component>
24 </ComponentGroup>
25 </Fragment>
26</Wix>
diff --git a/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/PackageUsingExplicitTfmAndRids/PackageUsingExplicitTfmAndRids.wixproj b/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/PackageUsingExplicitTfmAndRids/PackageUsingExplicitTfmAndRids.wixproj
new file mode 100644
index 00000000..42002428
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/PackageUsingExplicitTfmAndRids/PackageUsingExplicitTfmAndRids.wixproj
@@ -0,0 +1,10 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Project>
3 <Import Project="$(WixMSBuildProps)" />
4
5 <ItemGroup>
6 <ProjectReference Include="..\TestExe\TestExe.csproj" Publish="true" TargetFrameworks="net6.0,net472|win-x64" RuntimeIdentifiers="win-x86,win-x64" />
7 </ItemGroup>
8
9 <Import Project="$(WixTargetsPath)" />
10</Project>
diff --git a/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/PackageUsingRids/Package.wxs b/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/PackageUsingRids/Package.wxs
new file mode 100644
index 00000000..2fe7a289
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/PackageUsingRids/Package.wxs
@@ -0,0 +1,24 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="Test Package" Manufacturer="~Test" Version="0" Compressed="false" UpgradeCode="11111111-1111-1111-1111-111111111111">
3 <Feature Id="Main">
4 <ComponentGroupRef Id="Stuff"/>
5 </Feature>
6 </Package>
7
8 <Fragment>
9 <ComponentGroup Id="Stuff" Directory="ProgramFilesFolder">
10 <Component Subdirectory="net472_x86">
11 <File Id="net472_x86" Source="!(bindpath.TestExe.net472.win_x86)\e_sqlite3.dll" />
12 </Component>
13 <Component Subdirectory="net472_x64">
14 <File Id="net472_x64" Source="!(bindpath.TestExe.net472.win_x64)\e_sqlite3.dll" />
15 </Component>
16 <Component Subdirectory="net6_x86">
17 <File Id="net6_x86" Source="!(bindpath.TestExe.net6.0.win_x86)\e_sqlite3.dll" />
18 </Component>
19 <Component Subdirectory="net6_x64">
20 <File Id="net6_x64" Source="!(bindpath.TestExe.net6.0.win_x64)\e_sqlite3.dll" />
21 </Component>
22 </ComponentGroup>
23 </Fragment>
24</Wix>
diff --git a/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/PackageUsingRids/PackageUsingRids.wixproj b/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/PackageUsingRids/PackageUsingRids.wixproj
new file mode 100644
index 00000000..e7d834d6
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/PackageUsingRids/PackageUsingRids.wixproj
@@ -0,0 +1,10 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Project>
3 <Import Project="$(WixMSBuildProps)" />
4
5 <ItemGroup>
6 <ProjectReference Include="..\TestExe\TestExe.csproj" Publish="true" TargetFrameworks="net6.0,net472" RuntimeIdentifiers="win-x86,win-x64" />
7 </ItemGroup>
8
9 <Import Project="$(WixTargetsPath)" />
10</Project>
diff --git a/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/TestExe/Program.cs b/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/TestExe/Program.cs
new file mode 100644
index 00000000..a9c3c485
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/TestExe/Program.cs
@@ -0,0 +1,14 @@
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
3namespace WixToolsetTest.Sdk.TestData.MultiTargetingWixlib.MultiTargetingClassLib
4{
5 using System;
6
7 public class Program
8 {
9 public static void Main(string[] args)
10 {
11 Console.WriteLine("This is Test.exe");
12 }
13 }
14}
diff --git a/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/TestExe/TestExe.csproj b/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/TestExe/TestExe.csproj
new file mode 100644
index 00000000..6c72d9c2
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Sdk/TestData/MultiTargetingWixlib/TestExe/TestExe.csproj
@@ -0,0 +1,12 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Project Sdk="Microsoft.NET.Sdk">
3 <PropertyGroup>
4 <OutputType>Exe</OutputType>
5 <TargetFrameworks>net6.0;net472</TargetFrameworks>
6 <RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
7 </PropertyGroup>
8
9 <ItemGroup>
10 <PackageReference Include="Microsoft.Data.Sqlite" Version="6.0.0" />
11 </ItemGroup>
12</Project>