@joebigelow / wix / commits / 223b2845

Add ability to run 64-bit msbuild.

Sean Hall committed May 30, 2020 at 12:16 UTC 223b2845955d4d6604cfd014cf9aa536ca1eb0a2
7 files changed +251 -106
src/WixBuildTools.MsgGen/WixBuildTools.MsgGen.csproj
+1 -1
@@ -18,7 +18,7 @@
18 </ItemGroup>
19
20 <ItemGroup>
21 - <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta2-18618-05" PrivateAssets="All" />
21 + <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
22 <PackageReference Include="Nerdbank.GitVersioning" Version="2.1.65" PrivateAssets="All" />
23 </ItemGroup>
24
src/WixBuildTools.TestSupport/ExternalExecutable.cs new
+88
@@ -0,0 +1,88 @@
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 WixBuildTools.TestSupport
4 +{
5 + using System.Collections.Concurrent;
6 + using System.Collections.Generic;
7 + using System.Diagnostics;
8 + using System.IO;
9 + using System.Text;
10 +
11 + public abstract class ExternalExecutable
12 + {
13 + private readonly string exePath;
14 +
15 + protected ExternalExecutable(string exePath)
16 + {
17 + this.exePath = exePath;
18 + }
19 +
20 + protected ExternalExecutableResult Run(string args, bool mergeErrorIntoOutput = false, string workingDirectory = null)
21 + {
22 + var startInfo = new ProcessStartInfo(this.exePath, args)
23 + {
24 + CreateNoWindow = true,
25 + RedirectStandardError = true,
26 + RedirectStandardOutput = true,
27 + UseShellExecute = false,
28 + WorkingDirectory = workingDirectory ?? Path.GetDirectoryName(this.exePath),
29 + };
30 +
31 + using (var process = Process.Start(startInfo))
32 + {
33 + // This implementation of merging the streams does not guarantee that lines are retrieved in the same order that they were written.
34 + // If the process is simultaneously writing to both streams, this is impossible to do anyway.
35 + var standardOutput = new ConcurrentQueue<string>();
36 + var standardError = mergeErrorIntoOutput ? standardOutput : new ConcurrentQueue<string>();
37 +
38 + process.ErrorDataReceived += (s, e) => { if (e.Data != null) { standardError.Enqueue(e.Data); } };
39 + process.OutputDataReceived += (s, e) => { if (e.Data != null) { standardOutput.Enqueue(e.Data); } };
40 +
41 + process.BeginErrorReadLine();
42 + process.BeginOutputReadLine();
43 +
44 + process.WaitForExit();
45 +
46 + return new ExternalExecutableResult
47 + {
48 + ExitCode = process.ExitCode,
49 + StandardError = mergeErrorIntoOutput ? null : standardError.ToArray(),
50 + StandardOutput = standardOutput.ToArray(),
51 + StartInfo = startInfo,
52 + };
53 + }
54 + }
55 +
56 + // This is internal because it assumes backslashes aren't used as escape characters and there aren't any double quotes.
57 + internal static string CombineArguments(IEnumerable<string> arguments)
58 + {
59 + if (arguments == null)
60 + {
61 + return null;
62 + }
63 +
64 + var sb = new StringBuilder();
65 +
66 + foreach (var arg in arguments)
67 + {
68 + if (sb.Length > 0)
69 + {
70 + sb.Append(' ');
71 + }
72 +
73 + if (arg.IndexOf(' ') > -1)
74 + {
75 + sb.Append("\"");
76 + sb.Append(arg);
77 + sb.Append("\"");
78 + }
79 + else
80 + {
81 + sb.Append(arg);
82 + }
83 + }
84 +
85 + return sb.ToString();
86 + }
87 + }
88 +}
src/WixBuildTools.TestSupport/ExternalExecutableResult.cs new
+17
@@ -0,0 +1,17 @@
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 WixBuildTools.TestSupport
4 +{
5 + using System.Diagnostics;
6 +
7 + public class ExternalExecutableResult
8 + {
9 + public int ExitCode { get; set; }
10 +
11 + public string[] StandardError { get; set; }
12 +
13 + public string[] StandardOutput { get; set; }
14 +
15 + public ProcessStartInfo StartInfo { get; set; }
16 + }
17 +}
src/WixBuildTools.TestSupport/MsbuildRunner.cs
+102 -103
@@ -4,96 +4,148 @@ namespace WixBuildTools.TestSupport
4 {
5 using System;
6 using System.Collections.Generic;
7 - using System.Diagnostics;
7 using System.IO;
9 - using System.Text;
8
11 - public static class MsbuildRunner
9 + public class MsbuildRunner : ExternalExecutable
10 {
13 - private static readonly string VswhereRelativePath = @"Microsoft Visual Studio\Installer\vswhere.exe";
14 - private static readonly string[] VswhereFindArguments = new[] { "-property", "installationPath" };
11 + private static readonly string VswhereFindArguments = "-property installationPath";
12 private static readonly string Msbuild15RelativePath = @"MSBuild\15.0\Bin\MSBuild.exe";
16 - private static readonly string Msbuild16RelativePath = @"MSBuild\Current\Bin\MSBuild.exe";
13 + private static readonly string Msbuild15RelativePath64 = @"MSBuild\15.0\Bin\amd64\MSBuild.exe";
14 + private static readonly string MsbuildCurrentRelativePath = @"MSBuild\Current\Bin\MSBuild.exe";
15 + private static readonly string MsbuildCurrentRelativePath64 = @"MSBuild\Current\Bin\amd64\MSBuild.exe";
16
17 private static readonly object InitLock = new object();
18
20 - private static string Msbuild15Path;
21 - private static string Msbuild16Path;
19 + private static bool Initialized;
20 + private static MsbuildRunner Msbuild15Runner;
21 + private static MsbuildRunner Msbuild15Runner64;
22 + private static MsbuildRunner MsbuildCurrentRunner;
23 + private static MsbuildRunner MsbuildCurrentRunner64;
24
23 - public static MsbuildRunnerResult Execute(string projectPath, string[] arguments = null) => InitAndExecute(String.Empty, projectPath, arguments);
25 + public static MsbuildRunnerResult Execute(string projectPath, string[] arguments = null, bool x64 = false) =>
26 + InitAndExecute(String.Empty, projectPath, arguments, x64);
27
25 - public static MsbuildRunnerResult ExecuteWithMsbuild15(string projectPath, string[] arguments = null) => InitAndExecute("15", projectPath, arguments);
28 + public static MsbuildRunnerResult ExecuteWithMsbuild15(string projectPath, string[] arguments = null, bool x64 = false) =>
29 + InitAndExecute("15", projectPath, arguments, x64);
30
27 - public static MsbuildRunnerResult ExecuteWithMsbuild16(string projectPath, string[] arguments = null) => InitAndExecute("16", projectPath, arguments);
31 + public static MsbuildRunnerResult ExecuteWithMsbuildCurrent(string projectPath, string[] arguments = null, bool x64 = false) =>
32 + InitAndExecute("Current", projectPath, arguments, x64);
33
29 - private static MsbuildRunnerResult InitAndExecute(string msbuildVersion, string projectPath, string[] arguments)
34 + private static MsbuildRunnerResult InitAndExecute(string msbuildVersion, string projectPath, string[] arguments, bool x64)
35 {
36 lock (InitLock)
37 {
33 - if (Msbuild15Path == null && Msbuild16Path == null)
38 + if (!Initialized)
39 {
35 - var vswherePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), VswhereRelativePath);
36 - if (!File.Exists(vswherePath))
40 + Initialized = true;
41 + var vswhereResult = VswhereRunner.Execute(VswhereFindArguments, true);
42 + if (vswhereResult.ExitCode != 0)
43 {
38 - throw new InvalidOperationException($"Failed to find vswhere at: {vswherePath}");
44 + throw new InvalidOperationException($"Failed to execute vswhere.exe, exit code: {vswhereResult.ExitCode}. Output:\r\n{String.Join("\r\n", vswhereResult.StandardOutput)}");
45 }
46
41 - var result = RunProcessCaptureOutput(vswherePath, VswhereFindArguments);
42 - if (result.ExitCode != 0)
43 - {
44 - throw new InvalidOperationException($"Failed to execute vswhere.exe, exit code: {result.ExitCode}");
45 - }
46 -
47 - Msbuild15Path = String.Empty;
48 - Msbuild16Path = String.Empty;
47 + string msbuild15Path = null;
48 + string msbuild15Path64 = null;
49 + string msbuildCurrentPath = null;
50 + string msbuildCurrentPath64 = null;
51
50 - foreach (var installPath in result.Output)
52 + foreach (var installPath in vswhereResult.StandardOutput)
53 {
52 - if (String.IsNullOrEmpty(Msbuild16Path))
54 + if (msbuildCurrentPath == null)
55 + {
56 + var path = Path.Combine(installPath, MsbuildCurrentRelativePath);
57 + if (File.Exists(path))
58 + {
59 + msbuildCurrentPath = path;
60 + }
61 + }
62 +
63 + if (msbuildCurrentPath64 == null)
64 {
54 - var path = Path.Combine(installPath, Msbuild16RelativePath);
65 + var path = Path.Combine(installPath, MsbuildCurrentRelativePath64);
66 if (File.Exists(path))
67 {
57 - Msbuild16Path = path;
68 + msbuildCurrentPath64 = path;
69 }
70 }
71
61 - if (String.IsNullOrEmpty(Msbuild15Path))
72 + if (msbuild15Path == null)
73 {
74 var path = Path.Combine(installPath, Msbuild15RelativePath);
75 if (File.Exists(path))
76 {
66 - Msbuild15Path = path;
77 + msbuild15Path = path;
78 + }
79 + }
80 +
81 + if (msbuild15Path64 == null)
82 + {
83 + var path = Path.Combine(installPath, Msbuild15RelativePath64);
84 + if (File.Exists(path))
85 + {
86 + msbuild15Path64 = path;
87 }
88 }
89 }
90 +
91 + if (msbuildCurrentPath != null)
92 + {
93 + MsbuildCurrentRunner = new MsbuildRunner(msbuildCurrentPath);
94 + }
95 +
96 + if (msbuildCurrentPath64 != null)
97 + {
98 + MsbuildCurrentRunner64 = new MsbuildRunner(msbuildCurrentPath64);
99 + }
100 +
101 + if (msbuild15Path != null)
102 + {
103 + Msbuild15Runner = new MsbuildRunner(msbuild15Path);
104 + }
105 +
106 + if (msbuild15Path64 != null)
107 + {
108 + Msbuild15Runner64 = new MsbuildRunner(msbuild15Path64);
109 + }
110 }
111 }
112
73 - var msbuildPath = !String.IsNullOrEmpty(Msbuild15Path) ? Msbuild15Path : Msbuild16Path;
74 -
75 - if (msbuildVersion == "15")
113 + MsbuildRunner runner;
114 + switch (msbuildVersion)
115 {
77 - msbuildPath = Msbuild15Path;
116 + case "15":
117 + {
118 + runner = x64 ? Msbuild15Runner64 : Msbuild15Runner;
119 + break;
120 + }
121 + case "Current":
122 + {
123 + runner = x64 ? MsbuildCurrentRunner64 : MsbuildCurrentRunner;
124 + break;
125 + }
126 + default:
127 + {
128 + runner = x64 ? MsbuildCurrentRunner64 ?? Msbuild15Runner64
129 + : MsbuildCurrentRunner ?? Msbuild15Runner;
130 + break;
131 + }
132 }
79 - else if (msbuildVersion == "16")
133 +
134 + if (runner == null)
135 {
81 - msbuildPath = Msbuild16Path;
136 + throw new InvalidOperationException($"Failed to find an installed{(x64 ? " 64-bit" : String.Empty)} MSBuild{msbuildVersion}");
137 }
138
84 - return ExecuteCore(msbuildVersion, msbuildPath, projectPath, arguments);
139 + return runner.ExecuteCore(projectPath, arguments);
140 }
141
87 - private static MsbuildRunnerResult ExecuteCore(string msbuildVersion, string msbuildPath, string projectPath, string[] arguments)
88 - {
89 - if (String.IsNullOrEmpty(msbuildPath))
90 - {
91 - throw new InvalidOperationException($"Failed to find an installed MSBuild{msbuildVersion}");
92 - }
142 + private MsbuildRunner(string exePath) : base(exePath) { }
143
144 + private MsbuildRunnerResult ExecuteCore(string projectPath, string[] arguments)
145 + {
146 var total = new List<string>
147 {
96 - projectPath
148 + projectPath,
149 };
150
151 if (arguments != null)
@@ -101,69 +153,16 @@ namespace WixBuildTools.TestSupport
153 total.AddRange(arguments);
154 }
155
156 + var args = CombineArguments(total);
157 + var mergeErrorIntoOutput = true;
158 var workingFolder = Path.GetDirectoryName(projectPath);
105 - return RunProcessCaptureOutput(msbuildPath, total.ToArray(), workingFolder);
106 - }
159 + var result = this.Run(args, mergeErrorIntoOutput, workingFolder);
160
108 - private static MsbuildRunnerResult RunProcessCaptureOutput(string executablePath, string[] arguments = null, string workingFolder = null)
109 - {
110 - var startInfo = new ProcessStartInfo(executablePath)
161 + return new MsbuildRunnerResult
162 {
112 - Arguments = CombineArguments(arguments),
113 - CreateNoWindow = true,
114 - RedirectStandardError = true,
115 - RedirectStandardOutput = true,
116 - UseShellExecute = false,
117 - WorkingDirectory = workingFolder,
163 + ExitCode = result.ExitCode,
164 + Output = result.StandardOutput,
165 };
119 -
120 - var exitCode = 0;
121 - var output = new List<string>();
122 -
123 - using (var process = Process.Start(startInfo))
124 - {
125 - process.OutputDataReceived += (s, e) => { if (e.Data != null) { output.Add(e.Data); } };
126 - process.ErrorDataReceived += (s, e) => { if (e.Data != null) { output.Add(e.Data); } };
127 -
128 - process.BeginErrorReadLine();
129 - process.BeginOutputReadLine();
130 -
131 - process.WaitForExit();
132 - exitCode = process.ExitCode;
133 - }
134 -
135 - return new MsbuildRunnerResult { ExitCode = exitCode, Output = output.ToArray() };
136 - }
137 -
138 - private static string CombineArguments(string[] arguments)
139 - {
140 - if (arguments == null)
141 - {
142 - return null;
143 - }
144 -
145 - var sb = new StringBuilder();
146 -
147 - foreach (var arg in arguments)
148 - {
149 - if (sb.Length > 0)
150 - {
151 - sb.Append(' ');
152 - }
153 -
154 - if (arg.IndexOf(' ') > -1)
155 - {
156 - sb.Append("\"");
157 - sb.Append(arg);
158 - sb.Append("\"");
159 - }
160 - else
161 - {
162 - sb.Append(arg);
163 - }
164 - }
165 -
166 - return sb.ToString();
166 }
167 }
168 }
src/WixBuildTools.TestSupport/VswhereRunner.cs new
+41
@@ -0,0 +1,41 @@
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 WixBuildTools.TestSupport
4 +{
5 + using System;
6 + using System.IO;
7 +
8 + public class VswhereRunner : ExternalExecutable
9 + {
10 + private static readonly string VswhereRelativePath = @"Microsoft Visual Studio\Installer\vswhere.exe";
11 +
12 + private static readonly object InitLock = new object();
13 + private static bool Initialized;
14 + private static VswhereRunner Instance;
15 +
16 + public static ExternalExecutableResult Execute(string args, bool mergeErrorIntoOutput = false) =>
17 + InitAndExecute(args, mergeErrorIntoOutput);
18 +
19 + private static ExternalExecutableResult InitAndExecute(string args, bool mergeErrorIntoOutput)
20 + {
21 + lock (InitLock)
22 + {
23 + if (!Initialized)
24 + {
25 + Initialized = true;
26 + var vswherePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), VswhereRelativePath);
27 + if (!File.Exists(vswherePath))
28 + {
29 + throw new InvalidOperationException($"Failed to find vswhere at: {vswherePath}");
30 + }
31 +
32 + Instance = new VswhereRunner(vswherePath);
33 + }
34 + }
35 +
36 + return Instance.Run(args, mergeErrorIntoOutput);
37 + }
38 +
39 + private VswhereRunner(string exePath) : base(exePath) { }
40 + }
41 +}
src/WixBuildTools.TestSupport/WixBuildTools.TestSupport.csproj
+1 -1
@@ -18,7 +18,7 @@
18 </ItemGroup>
19
20 <ItemGroup>
21 - <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta2-18618-05 " PrivateAssets="All" />
21 + <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
22 <PackageReference Include="Nerdbank.GitVersioning" Version="2.1.65" PrivateAssets="All" />
23 </ItemGroup>
24
src/WixBuildTools.XsdGen/WixBuildTools.XsdGen.csproj
+1 -1
@@ -19,7 +19,7 @@
19 </ItemGroup>
20
21 <ItemGroup>
22 - <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta2-18618-05" PrivateAssets="All" />
22 + <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
23 <PackageReference Include="Nerdbank.GitVersioning" Version="2.1.65" PrivateAssets="All" />
24 </ItemGroup>
25