From 55deae1a0dc2e0252294f7777e049ae3d43af92e Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Sun, 14 Dec 2025 17:03:22 -0800 Subject: [PATCH 1/2] Support setting InstallerPlatform from RuntimeIdentifier Resolves 9047 --- .../MsbuildUtilities.cs | 3 +- .../CsprojWpfNetCore/CsprojWpfNetCore.csproj | 2 +- .../Package.wxs | 6 +- ...=> WixprojPackageCsprojWpfNetCore.wixproj} | 10 +- src/wix/WixToolset.BuildTasks/ILogger.cs | 13 ++ .../MSBuildLoggerAdapter.cs | 28 +++ .../ResolveInstallerPlatform.cs | 174 ++++++++++++++++++ src/wix/WixToolset.Sdk/tools/wix.targets | 56 ++++++ .../FakeMsbuildLogger.cs | 26 +++ .../ResolveInstallerPlatformTaskFixture.cs | 148 +++++++++++++++ ...ixToolsetTest.BuildTasks.v3.ncrunchproject | 4 +- .../test/WixToolsetTest.Sdk/MsbuildFixture.cs | 39 ++++ .../TestData/Wixlib/WithBadFiles/Library.wxs | 6 +- 13 files changed, 499 insertions(+), 16 deletions(-) rename src/test/wix/TestData/WixprojPackageCsprojWpfNetCore/{WixprojPackageCsprojWebApplicationNetCore.wixproj => WixprojPackageCsprojWpfNetCore.wixproj} (52%) create mode 100644 src/wix/WixToolset.BuildTasks/ILogger.cs create mode 100644 src/wix/WixToolset.BuildTasks/MSBuildLoggerAdapter.cs create mode 100644 src/wix/WixToolset.BuildTasks/ResolveInstallerPlatform.cs create mode 100644 src/wix/test/WixToolsetTest.BuildTasks/FakeMsbuildLogger.cs create mode 100644 src/wix/test/WixToolsetTest.BuildTasks/ResolveInstallerPlatformTaskFixture.cs diff --git a/src/internal/WixInternal.MSTestSupport/MsbuildUtilities.cs b/src/internal/WixInternal.MSTestSupport/MsbuildUtilities.cs index 8f3fecd9f..516143dd0 100644 --- a/src/internal/WixInternal.MSTestSupport/MsbuildUtilities.cs +++ b/src/internal/WixInternal.MSTestSupport/MsbuildUtilities.cs @@ -30,7 +30,8 @@ public static MsbuildRunnerResult BuildProject(BuildSystem buildSystem, string p if (binlog) { - MsbuildUtilities.GetQuotedSwitch(buildSystem, "bl", Path.ChangeExtension(projectPath, ".binlog")); + var binlogSwitch = MsbuildUtilities.GetQuotedSwitch(buildSystem, "bl", Path.ChangeExtension(projectPath, ".binlog")); + allArgs.Add(binlogSwitch); } if (arguments != null) diff --git a/src/test/wix/TestData/CsprojWpfNetCore/CsprojWpfNetCore.csproj b/src/test/wix/TestData/CsprojWpfNetCore/CsprojWpfNetCore.csproj index e7061e747..b1bf87815 100644 --- a/src/test/wix/TestData/CsprojWpfNetCore/CsprojWpfNetCore.csproj +++ b/src/test/wix/TestData/CsprojWpfNetCore/CsprojWpfNetCore.csproj @@ -2,7 +2,7 @@ WinExe - net5.0-windows7.0 + net8.0-windows7.0 enable true diff --git a/src/test/wix/TestData/WixprojPackageCsprojWpfNetCore/Package.wxs b/src/test/wix/TestData/WixprojPackageCsprojWpfNetCore/Package.wxs index 8bd191d8b..3671c0ca8 100644 --- a/src/test/wix/TestData/WixprojPackageCsprojWpfNetCore/Package.wxs +++ b/src/test/wix/TestData/WixprojPackageCsprojWpfNetCore/Package.wxs @@ -7,9 +7,9 @@ - - - + + + diff --git a/src/test/wix/TestData/WixprojPackageCsprojWpfNetCore/WixprojPackageCsprojWebApplicationNetCore.wixproj b/src/test/wix/TestData/WixprojPackageCsprojWpfNetCore/WixprojPackageCsprojWpfNetCore.wixproj similarity index 52% rename from src/test/wix/TestData/WixprojPackageCsprojWpfNetCore/WixprojPackageCsprojWebApplicationNetCore.wixproj rename to src/test/wix/TestData/WixprojPackageCsprojWpfNetCore/WixprojPackageCsprojWpfNetCore.wixproj index 686092b6f..df3332420 100644 --- a/src/test/wix/TestData/WixprojPackageCsprojWpfNetCore/WixprojPackageCsprojWebApplicationNetCore.wixproj +++ b/src/test/wix/TestData/WixprojPackageCsprojWpfNetCore/WixprojPackageCsprojWpfNetCore.wixproj @@ -1,17 +1,13 @@ - - - - - - + + + - diff --git a/src/wix/WixToolset.BuildTasks/ILogger.cs b/src/wix/WixToolset.BuildTasks/ILogger.cs new file mode 100644 index 000000000..16db691ea --- /dev/null +++ b/src/wix/WixToolset.BuildTasks/ILogger.cs @@ -0,0 +1,13 @@ +// 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 +{ + public interface ILogger + { + bool HasLoggedErrors { get; } + + void LogError(string message); + + void LogWarning(string message); + } +} diff --git a/src/wix/WixToolset.BuildTasks/MSBuildLoggerAdapter.cs b/src/wix/WixToolset.BuildTasks/MSBuildLoggerAdapter.cs new file mode 100644 index 000000000..5382254c6 --- /dev/null +++ b/src/wix/WixToolset.BuildTasks/MSBuildLoggerAdapter.cs @@ -0,0 +1,28 @@ +// 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 Microsoft.Build.Utilities; + + internal class MSBuildLoggerAdapter : ILogger + { + private readonly TaskLoggingHelper log; + + public MSBuildLoggerAdapter(TaskLoggingHelper log) + { + this.log = log; + } + + public bool HasLoggedErrors => this.log.HasLoggedErrors; + + public void LogError(string message) + { + this.log.LogError(message); + } + + public void LogWarning(string message) + { + this.log.LogWarning(message); + } + } +} diff --git a/src/wix/WixToolset.BuildTasks/ResolveInstallerPlatform.cs b/src/wix/WixToolset.BuildTasks/ResolveInstallerPlatform.cs new file mode 100644 index 000000000..5e551d7b1 --- /dev/null +++ b/src/wix/WixToolset.BuildTasks/ResolveInstallerPlatform.cs @@ -0,0 +1,174 @@ +// 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.Linq; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + /// + /// This task calculates the InstallerPlatform from the RuntimeIdentifier, + /// InitialInstallerPlatform and Platform properties. + /// + public class ResolveInstallerPlatform : Task + { + private readonly ILogger logger; + + /// + /// Default constructor. + /// + public ResolveInstallerPlatform() + { + this.logger = new MSBuildLoggerAdapter(this.Log); + } + + /// + /// Constructor for dependency injection of logger used by unit tests. + /// + public ResolveInstallerPlatform(ILogger logger) + { + this.logger = logger; + } + + /// + /// The optional RuntimeIdentifier property. + /// + public string RuntimeIdentifier { private get; set; } + + /// + /// The optional InitialInstallerPlatform property. + /// + public string InitialInstallerPlatform { private get; set; } + + /// + /// The InstallerPlatform property. + /// + public string InstallerPlatform { private get; set; } + + /// + /// The optional Platform property. + /// + public string Platform { private get; set; } + + /// + /// The resolved InstallerPlatform. + /// + [Output] + public string ResolvedInstallerPlatform { get; private set; } + + /// + /// The optionally resolved Platform. + /// + [Output] + public string ResolvedPlatform { get; private set; } + + /// + /// Convert the input properties into output items. + /// + /// True upon completion of the task execution. + public override bool Execute() + { + if (String.IsNullOrEmpty(this.RuntimeIdentifier)) + { + this.ResolvedInstallerPlatform = this.InstallerPlatform; + } + else if (this.ValidateWindowsRuntimeIdentifier(this.RuntimeIdentifier, out var platform)) + { + if (!String.IsNullOrEmpty(this.InitialInstallerPlatform) && !String.Equals(this.InitialInstallerPlatform, platform, StringComparison.OrdinalIgnoreCase)) + { + this.logger.LogError($"The RuntimeIdentifier '{this.RuntimeIdentifier}' resolves to platform '{platform}', which conflicts with the provided InstallerPlatform '{this.InitialInstallerPlatform}'."); + } + else + { + this.ResolvedInstallerPlatform = platform; + } + } + + // If Platform was a generic value, resolve it to the resolved installer platform. + if (String.IsNullOrEmpty(this.Platform) + || this.Platform.Equals("AnyCPU", StringComparison.OrdinalIgnoreCase) + || this.Platform.Equals("Any CPU", StringComparison.OrdinalIgnoreCase) + || this.Platform.Equals("Win32", StringComparison.OrdinalIgnoreCase)) + { + this.ResolvedPlatform = this.ResolvedInstallerPlatform; + } + else if (!this.Platform.Equals(this.ResolvedInstallerPlatform, StringComparison.OrdinalIgnoreCase)) + { + this.logger.LogWarning($"The provided Platform '{this.Platform}' does not match the resolved InstallerPlatform '{this.ResolvedInstallerPlatform}'. The output will be built using '{this.ResolvedInstallerPlatform}'."); + } + + return !this.logger.HasLoggedErrors; + } + + private bool ValidateWindowsRuntimeIdentifier(string runtimeIdentifier, out string platform) + { + platform = null; + + var ridParts = runtimeIdentifier.Split('-'); + if (ridParts.Length < 2) + { + this.logger.LogError($"The RuntimeIdentifier '{runtimeIdentifier}' is not valid."); + + return false; + } + + var os = ridParts[0]; + + if (!os.StartsWith("win", StringComparison.OrdinalIgnoreCase) || (os.Length > 3 && !Int32.TryParse(os.Substring(3), out var _))) + { + this.logger.LogError($"The RuntimeIdentifier '{runtimeIdentifier}' is not a valid Windows RuntimeIdentifier."); + + return false; + } + + // Ensure there is only one platform specified in the RID. + foreach (var part in ridParts.Skip(1)) + { + string platformPart; + switch (part.ToLowerInvariant()) + { + case "x86": + case "win32": + platformPart = "x86"; + break; + + case "x64": + case "amd64": + platformPart = "x64"; + break; + + case "arm": + case "arm32": + platformPart = "arm"; + break; + + case "arm64": + platformPart = "arm64"; + break; + + default: + continue; + } + + if (String.IsNullOrEmpty(platform)) + { + platform = platformPart; + } + else // there can be only one platform in the RID. + { + this.logger.LogError($"The RuntimeIdentifier '{runtimeIdentifier}' specifies multiple platforms which is not supported."); + } + } + + if (String.IsNullOrEmpty(platform)) + { + this.logger.LogError($"The RuntimeIdentifier '{runtimeIdentifier}' does not specify a valid platform."); + + return false; + } + + return true; + } + } +} diff --git a/src/wix/WixToolset.Sdk/tools/wix.targets b/src/wix/WixToolset.Sdk/tools/wix.targets index 4de5835fe..ac95a8d9b 100644 --- a/src/wix/WixToolset.Sdk/tools/wix.targets +++ b/src/wix/WixToolset.Sdk/tools/wix.targets @@ -40,6 +40,7 @@ + @@ -165,6 +166,7 @@ + <_InitialInstallerPlatform>$(InstallerPlatform) x86 $(Platform) @@ -178,6 +180,60 @@ + + + + ResolveInstallerPlatform; + $(PrepareForBuildDependsOn) + + + + + + + + + + + + + $(_ResolvedInstallerPlatform) + $(_ResolvedPlatform) + + + + TargetFramework=net8.0;RuntimeIdentifier=win-x64 + + + + +