[xabuild] Use a temp file for MSBuild config (#969)

Context: https://github.com/xamarin/xamarin-android/pull/954
Context: https://github.com/xamarin/xamarin-android/pull/964
Context: https://github.com/xamarin/xamarin-android/pull/967

The `Xamarin.Android.Build.Tests` unit tests run `xabuild.exe` in
parallel, which causes all manner of "weirdness" as the multiple
`xabuild.exe` process all attempt to overwrite the *same* config file
used by the `MSBuildApp` class.

Fix this by creating *unique* per-process config files, stored in
`Path.GetTempFileName()`, and overriding the `$MSBUILD_EXE_PATH`
environment variable to refer to the per-process config file -- which
is what the [MSBuild Unit Tests use][msbuild-tests] to override the
`msbuild.exe.config` file used at runtime -- allowing each
`xabuild.exe` process to sanely provide separate config files
without conflicting with each other.

[msbuild-tests]: https://github.com/Microsoft/msbuild/blob/080ef97/src/Build.UnitTests/BuildEnvironmentHelper_Tests.cs#L47
This commit is contained in:
Jonathan Peppers 2017-10-24 12:23:03 -05:00 коммит произвёл Jonathan Pryor
Родитель 14ad71b1bf
Коммит 9324d4dea0
3 изменённых файлов: 55 добавлений и 17 удалений

Просмотреть файл

@ -1,8 +1,10 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml.Linq;
using Microsoft.Build.Framework;
using NUnit.Framework;
@ -2078,6 +2080,26 @@ namespace UnnamedProject {
StringAssert.Contains ($"warning XA4", builder.LastBuildOutput, "warning XA4212");
}
}
[Test]
public void RunXABuildInParallel ()
{
var xabuild = new ProjectBuilder ("temp/RunXABuildInParallel").XABuildExe;
var psi = new ProcessStartInfo (xabuild, "/version") {
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
};
Parallel.For (0, 10, i => {
using (var p = Process.Start (psi)) {
p.WaitForExit ();
Assert.AreEqual (0, p.ExitCode);
}
});
}
}
}

Просмотреть файл

@ -11,23 +11,32 @@ namespace Xamarin.Android.Build
static int Main ()
{
var paths = new XABuildPaths ();
if (!Directory.Exists (paths.XamarinAndroidBuildOutput)) {
Console.WriteLine ($"Unable to find Xamarin.Android build output at {paths.XamarinAndroidBuildOutput}");
return 1;
}
//Create a custom xabuild.exe.config
CreateConfig (paths);
//Create link to .NETFramework and .NETPortable directory
foreach (var dir in Directory.GetDirectories(paths.SystemProfiles)) {
var name = Path.GetFileName(dir);
if (!SymbolicLink.Create(Path.Combine(paths.FrameworksDirectory, name), dir)) {
try {
if (!Directory.Exists (paths.XamarinAndroidBuildOutput)) {
Console.WriteLine ($"Unable to find Xamarin.Android build output at {paths.XamarinAndroidBuildOutput}");
return 1;
}
}
return MSBuildApp.Main ();
//Create a custom xabuild.exe.config
CreateConfig (paths);
//Create link to .NETFramework and .NETPortable directory
foreach (var dir in Directory.GetDirectories (paths.SystemProfiles)) {
var name = Path.GetFileName (dir);
if (!SymbolicLink.Create (Path.Combine (paths.FrameworksDirectory, name), dir)) {
return 1;
}
}
return MSBuildApp.Main ();
} finally {
//NOTE: these are temporary files
foreach (var file in new [] { paths.MSBuildExeTempPath, paths.XABuildConfig }) {
if (File.Exists (file)) {
File.Delete (file);
}
}
}
}
static void CreateConfig (XABuildPaths paths)
@ -79,6 +88,8 @@ namespace Xamarin.Android.Build
}
xml.Save (paths.XABuildConfig);
Environment.SetEnvironmentVariable ("MSBUILD_EXE_PATH", paths.MSBuildExeTempPath, EnvironmentVariableTarget.Process);
}
/// <summary>

Просмотреть файл

@ -21,7 +21,7 @@ namespace Xamarin.Android.Build
public string XABuildDirectory { get; private set; }
/// <summary>
/// Path to xabuild.exe.config, on Unix it seems to use MSBuild.dll.config instead
/// Path to xabuild.exe's config file, this is now a temporary file based on MSBuildExeTempPath
/// </summary>
public string XABuildConfig { get; private set; }
@ -45,6 +45,11 @@ namespace Xamarin.Android.Build
/// </summary>
public string MSBuildBin { get; private set; }
/// <summary>
/// Temporary file used for MSBUILD_EXE_PATH
/// </summary>
public string MSBuildExeTempPath { get; private set; }
/// <summary>
/// Path to MSBuild's App.config file
/// </summary>
@ -110,7 +115,6 @@ namespace Xamarin.Android.Build
MSBuildConfig = Path.Combine (MSBuildBin, "MSBuild.exe.config");
ProjectImportSearchPaths = new [] { MSBuildPath, "$(MSBuildProgramFiles32)\\MSBuild" };
SystemProfiles = Path.Combine (programFiles, "Reference Assemblies", "Microsoft", "Framework");
XABuildConfig = Path.Combine (XABuildDirectory, "xabuild.exe.config");
SearchPathsOS = "windows";
} else {
string mono = IsMacOS ? "/Library/Frameworks/Mono.framework/Versions/Current/lib/mono" : "/usr/lib/mono";
@ -120,7 +124,6 @@ namespace Xamarin.Android.Build
MSBuildConfig = Path.Combine (MSBuildBin, "MSBuild.dll.config");
ProjectImportSearchPaths = new [] { MSBuildPath, Path.Combine (mono, "xbuild"), Path.Combine (monoExternal, "xbuild") };
SystemProfiles = Path.Combine (mono, "xbuild-frameworks");
XABuildConfig = Path.Combine (XABuildDirectory, "MSBuild.dll.config");
SearchPathsOS = IsMacOS ? "osx" : "unix";
}
@ -129,6 +132,8 @@ namespace Xamarin.Android.Build
MonoAndroidToolsDirectory = Path.Combine (prefix, "xbuild", "Xamarin", "Android");
AndroidSdkDirectory = Path.Combine (userProfile, "android-toolchain", "sdk");
AndroidNdkDirectory = Path.Combine (userProfile, "android-toolchain", "ndk");
MSBuildExeTempPath = Path.GetTempFileName ();
XABuildConfig = MSBuildExeTempPath + ".config";
}
[DllImport ("libc")]