From 223b2845955d4d6604cfd014cf9aa536ca1eb0a2 Mon Sep 17 00:00:00 2001
From: Sean Hall <r.sean.hall@gmail.com>
Date: Sat, 30 May 2020 12:16:27 +1000
Subject: Add ability to run 64-bit msbuild.

---
 .../WixBuildTools.MsgGen.csproj                    |   2 +-
 .../ExternalExecutable.cs                          |  88 +++++++++
 .../ExternalExecutableResult.cs                    |  17 ++
 src/WixBuildTools.TestSupport/MsbuildRunner.cs     | 205 ++++++++++-----------
 src/WixBuildTools.TestSupport/VswhereRunner.cs     |  41 +++++
 .../WixBuildTools.TestSupport.csproj               |   2 +-
 .../WixBuildTools.XsdGen.csproj                    |   2 +-
 7 files changed, 251 insertions(+), 106 deletions(-)
 create mode 100644 src/WixBuildTools.TestSupport/ExternalExecutable.cs
 create mode 100644 src/WixBuildTools.TestSupport/ExternalExecutableResult.cs
 create mode 100644 src/WixBuildTools.TestSupport/VswhereRunner.cs

(limited to 'src')

diff --git a/src/WixBuildTools.MsgGen/WixBuildTools.MsgGen.csproj b/src/WixBuildTools.MsgGen/WixBuildTools.MsgGen.csproj
index 80d6b0d6..ccbb92c2 100644
--- a/src/WixBuildTools.MsgGen/WixBuildTools.MsgGen.csproj
+++ b/src/WixBuildTools.MsgGen/WixBuildTools.MsgGen.csproj
@@ -18,7 +18,7 @@
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta2-18618-05" PrivateAssets="All" />
+    <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
     <PackageReference Include="Nerdbank.GitVersioning" Version="2.1.65" PrivateAssets="All" />
   </ItemGroup>
 
diff --git a/src/WixBuildTools.TestSupport/ExternalExecutable.cs b/src/WixBuildTools.TestSupport/ExternalExecutable.cs
new file mode 100644
index 00000000..eb07aa13
--- /dev/null
+++ b/src/WixBuildTools.TestSupport/ExternalExecutable.cs
@@ -0,0 +1,88 @@
+// 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 WixBuildTools.TestSupport
+{
+    using System.Collections.Concurrent;
+    using System.Collections.Generic;
+    using System.Diagnostics;
+    using System.IO;
+    using System.Text;
+
+    public abstract class ExternalExecutable
+    {
+        private readonly string exePath;
+
+        protected ExternalExecutable(string exePath)
+        {
+            this.exePath = exePath;
+        }
+
+        protected ExternalExecutableResult Run(string args, bool mergeErrorIntoOutput = false, string workingDirectory = null)
+        {
+            var startInfo = new ProcessStartInfo(this.exePath, args)
+            {
+                CreateNoWindow = true,
+                RedirectStandardError = true,
+                RedirectStandardOutput = true,
+                UseShellExecute = false,
+                WorkingDirectory = workingDirectory ?? Path.GetDirectoryName(this.exePath),
+            };
+
+            using (var process = Process.Start(startInfo))
+            {
+                // This implementation of merging the streams does not guarantee that lines are retrieved in the same order that they were written.
+                // If the process is simultaneously writing to both streams, this is impossible to do anyway.
+                var standardOutput = new ConcurrentQueue<string>();
+                var standardError = mergeErrorIntoOutput ? standardOutput : new ConcurrentQueue<string>();
+
+                process.ErrorDataReceived += (s, e) => { if (e.Data != null) { standardError.Enqueue(e.Data); } };
+                process.OutputDataReceived += (s, e) => { if (e.Data != null) { standardOutput.Enqueue(e.Data); } };
+
+                process.BeginErrorReadLine();
+                process.BeginOutputReadLine();
+
+                process.WaitForExit();
+
+                return new ExternalExecutableResult
+                {
+                    ExitCode = process.ExitCode,
+                    StandardError = mergeErrorIntoOutput ? null : standardError.ToArray(),
+                    StandardOutput = standardOutput.ToArray(),
+                    StartInfo = startInfo,
+                };
+            }
+        }
+
+        // This is internal because it assumes backslashes aren't used as escape characters and there aren't any double quotes.
+        internal static string CombineArguments(IEnumerable<string> arguments)
+        {
+            if (arguments == null)
+            {
+                return null;
+            }
+
+            var sb = new StringBuilder();
+
+            foreach (var arg in arguments)
+            {
+                if (sb.Length > 0)
+                {
+                    sb.Append(' ');
+                }
+
+                if (arg.IndexOf(' ') > -1)
+                {
+                    sb.Append("\"");
+                    sb.Append(arg);
+                    sb.Append("\"");
+                }
+                else
+                {
+                    sb.Append(arg);
+                }
+            }
+
+            return sb.ToString();
+        }
+    }
+}
diff --git a/src/WixBuildTools.TestSupport/ExternalExecutableResult.cs b/src/WixBuildTools.TestSupport/ExternalExecutableResult.cs
new file mode 100644
index 00000000..19b5183b
--- /dev/null
+++ b/src/WixBuildTools.TestSupport/ExternalExecutableResult.cs
@@ -0,0 +1,17 @@
+// 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 WixBuildTools.TestSupport
+{
+    using System.Diagnostics;
+
+    public class ExternalExecutableResult
+    {
+        public int ExitCode { get; set; }
+
+        public string[] StandardError { get; set; }
+
+        public string[] StandardOutput { get; set; }
+
+        public ProcessStartInfo StartInfo { get; set; }
+    }
+}
diff --git a/src/WixBuildTools.TestSupport/MsbuildRunner.cs b/src/WixBuildTools.TestSupport/MsbuildRunner.cs
index b38387a9..35e53de6 100644
--- a/src/WixBuildTools.TestSupport/MsbuildRunner.cs
+++ b/src/WixBuildTools.TestSupport/MsbuildRunner.cs
@@ -4,96 +4,148 @@ namespace WixBuildTools.TestSupport
 {
     using System;
     using System.Collections.Generic;
-    using System.Diagnostics;
     using System.IO;
-    using System.Text;
 
-    public static class MsbuildRunner
+    public class MsbuildRunner : ExternalExecutable
     {
-        private static readonly string VswhereRelativePath = @"Microsoft Visual Studio\Installer\vswhere.exe";
-        private static readonly string[] VswhereFindArguments = new[] { "-property", "installationPath" };
+        private static readonly string VswhereFindArguments = "-property installationPath";
         private static readonly string Msbuild15RelativePath = @"MSBuild\15.0\Bin\MSBuild.exe";
-        private static readonly string Msbuild16RelativePath = @"MSBuild\Current\Bin\MSBuild.exe";
+        private static readonly string Msbuild15RelativePath64 = @"MSBuild\15.0\Bin\amd64\MSBuild.exe";
+        private static readonly string MsbuildCurrentRelativePath = @"MSBuild\Current\Bin\MSBuild.exe";
+        private static readonly string MsbuildCurrentRelativePath64 = @"MSBuild\Current\Bin\amd64\MSBuild.exe";
 
         private static readonly object InitLock = new object();
 
-        private static string Msbuild15Path;
-        private static string Msbuild16Path;
+        private static bool Initialized;
+        private static MsbuildRunner Msbuild15Runner;
+        private static MsbuildRunner Msbuild15Runner64;
+        private static MsbuildRunner MsbuildCurrentRunner;
+        private static MsbuildRunner MsbuildCurrentRunner64;
 
-        public static MsbuildRunnerResult Execute(string projectPath, string[] arguments = null) => InitAndExecute(String.Empty, projectPath, arguments);
+        public static MsbuildRunnerResult Execute(string projectPath, string[] arguments = null, bool x64 = false) =>
+            InitAndExecute(String.Empty, projectPath, arguments, x64);
 
-        public static MsbuildRunnerResult ExecuteWithMsbuild15(string projectPath, string[] arguments = null) => InitAndExecute("15", projectPath, arguments);
+        public static MsbuildRunnerResult ExecuteWithMsbuild15(string projectPath, string[] arguments = null, bool x64 = false) =>
+            InitAndExecute("15", projectPath, arguments, x64);
 
-        public static MsbuildRunnerResult ExecuteWithMsbuild16(string projectPath, string[] arguments = null) => InitAndExecute("16", projectPath, arguments);
+        public static MsbuildRunnerResult ExecuteWithMsbuildCurrent(string projectPath, string[] arguments = null, bool x64 = false) =>
+            InitAndExecute("Current", projectPath, arguments, x64);
 
-        private static MsbuildRunnerResult InitAndExecute(string msbuildVersion, string projectPath, string[] arguments)
+        private static MsbuildRunnerResult InitAndExecute(string msbuildVersion, string projectPath, string[] arguments, bool x64)
         {
             lock (InitLock)
             {
-                if (Msbuild15Path == null && Msbuild16Path == null)
+                if (!Initialized)
                 {
-                    var vswherePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), VswhereRelativePath);
-                    if (!File.Exists(vswherePath))
+                    Initialized = true;
+                    var vswhereResult = VswhereRunner.Execute(VswhereFindArguments, true);
+                    if (vswhereResult.ExitCode != 0)
                     {
-                        throw new InvalidOperationException($"Failed to find vswhere at: {vswherePath}");
+                        throw new InvalidOperationException($"Failed to execute vswhere.exe, exit code: {vswhereResult.ExitCode}. Output:\r\n{String.Join("\r\n", vswhereResult.StandardOutput)}");
                     }
 
-                    var result = RunProcessCaptureOutput(vswherePath, VswhereFindArguments);
-                    if (result.ExitCode != 0)
-                    {
-                        throw new InvalidOperationException($"Failed to execute vswhere.exe, exit code: {result.ExitCode}");
-                    }
-
-                    Msbuild15Path = String.Empty;
-                    Msbuild16Path = String.Empty;
+                    string msbuild15Path = null;
+                    string msbuild15Path64 = null;
+                    string msbuildCurrentPath = null;
+                    string msbuildCurrentPath64 = null;
 
-                    foreach (var installPath in result.Output)
+                    foreach (var installPath in vswhereResult.StandardOutput)
                     {
-                        if (String.IsNullOrEmpty(Msbuild16Path))
+                        if (msbuildCurrentPath == null)
+                        {
+                            var path = Path.Combine(installPath, MsbuildCurrentRelativePath);
+                            if (File.Exists(path))
+                            {
+                                msbuildCurrentPath = path;
+                            }
+                        }
+
+                        if (msbuildCurrentPath64 == null)
                         {
-                            var path = Path.Combine(installPath, Msbuild16RelativePath);
+                            var path = Path.Combine(installPath, MsbuildCurrentRelativePath64);
                             if (File.Exists(path))
                             {
-                                Msbuild16Path = path;
+                                msbuildCurrentPath64 = path;
                             }
                         }
 
-                        if (String.IsNullOrEmpty(Msbuild15Path))
+                        if (msbuild15Path == null)
                         {
                             var path = Path.Combine(installPath, Msbuild15RelativePath);
                             if (File.Exists(path))
                             {
-                                Msbuild15Path = path;
+                                msbuild15Path = path;
+                            }
+                        }
+
+                        if (msbuild15Path64 == null)
+                        {
+                            var path = Path.Combine(installPath, Msbuild15RelativePath64);
+                            if (File.Exists(path))
+                            {
+                                msbuild15Path64 = path;
                             }
                         }
                     }
+
+                    if (msbuildCurrentPath != null)
+                    {
+                        MsbuildCurrentRunner = new MsbuildRunner(msbuildCurrentPath);
+                    }
+
+                    if (msbuildCurrentPath64 != null)
+                    {
+                        MsbuildCurrentRunner64 = new MsbuildRunner(msbuildCurrentPath64);
+                    }
+
+                    if (msbuild15Path != null)
+                    {
+                        Msbuild15Runner = new MsbuildRunner(msbuild15Path);
+                    }
+
+                    if (msbuild15Path64 != null)
+                    {
+                        Msbuild15Runner64 = new MsbuildRunner(msbuild15Path64);
+                    }
                 }
             }
 
-            var msbuildPath = !String.IsNullOrEmpty(Msbuild15Path) ? Msbuild15Path : Msbuild16Path;
-
-            if (msbuildVersion == "15")
+            MsbuildRunner runner;
+            switch (msbuildVersion)
             {
-                msbuildPath = Msbuild15Path;
+                case "15":
+                    {
+                        runner = x64 ? Msbuild15Runner64 : Msbuild15Runner;
+                        break;
+                    }
+                case "Current":
+                    {
+                        runner = x64 ? MsbuildCurrentRunner64 : MsbuildCurrentRunner;
+                        break;
+                    }
+                default:
+                    {
+                        runner = x64 ? MsbuildCurrentRunner64 ?? Msbuild15Runner64
+                                     : MsbuildCurrentRunner ?? Msbuild15Runner;
+                        break;
+                    }
             }
-            else if (msbuildVersion == "16")
+
+            if (runner == null)
             {
-                msbuildPath = Msbuild16Path;
+                throw new InvalidOperationException($"Failed to find an installed{(x64 ? " 64-bit" : String.Empty)} MSBuild{msbuildVersion}");
             }
 
-            return ExecuteCore(msbuildVersion, msbuildPath, projectPath, arguments);
+            return runner.ExecuteCore(projectPath, arguments);
         }
 
-        private static MsbuildRunnerResult ExecuteCore(string msbuildVersion, string msbuildPath, string projectPath, string[] arguments)
-        {
-            if (String.IsNullOrEmpty(msbuildPath))
-            {
-                throw new InvalidOperationException($"Failed to find an installed MSBuild{msbuildVersion}");
-            }
+        private MsbuildRunner(string exePath) : base(exePath) { }
 
+        private MsbuildRunnerResult ExecuteCore(string projectPath, string[] arguments)
+        {
             var total = new List<string>
             {
-                projectPath
+                projectPath,
             };
 
             if (arguments != null)
@@ -101,69 +153,16 @@ namespace WixBuildTools.TestSupport
                 total.AddRange(arguments);
             }
 
+            var args = CombineArguments(total);
+            var mergeErrorIntoOutput = true;
             var workingFolder = Path.GetDirectoryName(projectPath);
-            return RunProcessCaptureOutput(msbuildPath, total.ToArray(), workingFolder);
-        }
+            var result = this.Run(args, mergeErrorIntoOutput, workingFolder);
 
-        private static MsbuildRunnerResult RunProcessCaptureOutput(string executablePath, string[] arguments = null, string workingFolder = null)
-        {
-            var startInfo = new ProcessStartInfo(executablePath)
+            return new MsbuildRunnerResult
             {
-                Arguments = CombineArguments(arguments),
-                CreateNoWindow = true,
-                RedirectStandardError = true,
-                RedirectStandardOutput = true,
-                UseShellExecute = false,
-                WorkingDirectory = workingFolder,
+                ExitCode = result.ExitCode,
+                Output = result.StandardOutput,
             };
-
-            var exitCode = 0;
-            var output = new List<string>();
-
-            using (var process = Process.Start(startInfo))
-            {
-                process.OutputDataReceived += (s, e) => { if (e.Data != null) { output.Add(e.Data); } };
-                process.ErrorDataReceived += (s, e) => { if (e.Data != null) { output.Add(e.Data); } };
-
-                process.BeginErrorReadLine();
-                process.BeginOutputReadLine();
-
-                process.WaitForExit();
-                exitCode = process.ExitCode;
-            }
-
-            return new MsbuildRunnerResult { ExitCode = exitCode, Output = output.ToArray() };
-        }
-
-        private static string CombineArguments(string[] arguments)
-        {
-            if (arguments == null)
-            {
-                return null;
-            }
-
-            var sb = new StringBuilder();
-
-            foreach (var arg in arguments)
-            {
-                if (sb.Length > 0)
-                {
-                    sb.Append(' ');
-                }
-
-                if (arg.IndexOf(' ') > -1)
-                {
-                    sb.Append("\"");
-                    sb.Append(arg);
-                    sb.Append("\"");
-                }
-                else
-                {
-                    sb.Append(arg);
-                }
-            }
-
-            return sb.ToString();
         }
     }
 }
diff --git a/src/WixBuildTools.TestSupport/VswhereRunner.cs b/src/WixBuildTools.TestSupport/VswhereRunner.cs
new file mode 100644
index 00000000..0197e125
--- /dev/null
+++ b/src/WixBuildTools.TestSupport/VswhereRunner.cs
@@ -0,0 +1,41 @@
+// 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 WixBuildTools.TestSupport
+{
+    using System;
+    using System.IO;
+
+    public class VswhereRunner : ExternalExecutable
+    {
+        private static readonly string VswhereRelativePath = @"Microsoft Visual Studio\Installer\vswhere.exe";
+
+        private static readonly object InitLock = new object();
+        private static bool Initialized;
+        private static VswhereRunner Instance;
+
+        public static ExternalExecutableResult Execute(string args, bool mergeErrorIntoOutput = false) =>
+            InitAndExecute(args, mergeErrorIntoOutput);
+
+        private static ExternalExecutableResult InitAndExecute(string args, bool mergeErrorIntoOutput)
+        {
+            lock (InitLock)
+            {
+                if (!Initialized)
+                {
+                    Initialized = true;
+                    var vswherePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), VswhereRelativePath);
+                    if (!File.Exists(vswherePath))
+                    {
+                        throw new InvalidOperationException($"Failed to find vswhere at: {vswherePath}");
+                    }
+
+                    Instance = new VswhereRunner(vswherePath);
+                }
+            }
+
+            return Instance.Run(args, mergeErrorIntoOutput);
+        }
+
+        private VswhereRunner(string exePath) : base(exePath) { }
+    }
+}
diff --git a/src/WixBuildTools.TestSupport/WixBuildTools.TestSupport.csproj b/src/WixBuildTools.TestSupport/WixBuildTools.TestSupport.csproj
index 31bdf033..e6cdddef 100644
--- a/src/WixBuildTools.TestSupport/WixBuildTools.TestSupport.csproj
+++ b/src/WixBuildTools.TestSupport/WixBuildTools.TestSupport.csproj
@@ -18,7 +18,7 @@
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta2-18618-05 " PrivateAssets="All" />
+    <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
     <PackageReference Include="Nerdbank.GitVersioning" Version="2.1.65" PrivateAssets="All" />
   </ItemGroup>
 
diff --git a/src/WixBuildTools.XsdGen/WixBuildTools.XsdGen.csproj b/src/WixBuildTools.XsdGen/WixBuildTools.XsdGen.csproj
index ef24420e..bf9d957f 100644
--- a/src/WixBuildTools.XsdGen/WixBuildTools.XsdGen.csproj
+++ b/src/WixBuildTools.XsdGen/WixBuildTools.XsdGen.csproj
@@ -19,7 +19,7 @@
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta2-18618-05" PrivateAssets="All" />
+    <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
     <PackageReference Include="Nerdbank.GitVersioning" Version="2.1.65" PrivateAssets="All" />
   </ItemGroup>
 
-- 
cgit v1.2.3-55-g6feb