[Harness] Generalize MSBuildTask and DotnetBuildTask. (#8306)

Move all the logic outside of the Jenkins namespace. Rework a little the
inheritance to make it nicer in the constructors.

Co-authored-by: Rolf Bjarne Kvinge <rolf@xamarin.com>
Co-authored-by: Přemek Vysoký <premek.vysoky@microsoft.com>
This commit is contained in:
Manuel de la Pena 2020-04-10 16:50:50 -04:00 коммит произвёл GitHub
Родитель 8d3987b84a
Коммит a2f57b38d6
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
10 изменённых файлов: 309 добавлений и 188 удалений

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

@ -13,9 +13,11 @@ using Microsoft.DotNet.XHarness.iOS.Shared.Utilities;
using Microsoft.DotNet.XHarness.iOS.Shared;
using Microsoft.DotNet.XHarness.iOS.Shared.Hardware;
using Xharness.TestTasks;
using MSBuildTask = Xharness.Jenkins.TestTasks.MSBuildTask;
using DotNetBuildTask = Xharness.Jenkins.TestTasks.DotNetBuildTask;
namespace Xharness.Jenkins {
public class Jenkins : IResourceManager
public class Jenkins : IResourceManager, IErrorKnowledgeBase
{
readonly ISimulatorLoader simulators;
readonly IHardwareDeviceLoader devices;
@ -1756,7 +1758,7 @@ namespace Xharness.Jenkins {
}
}
public bool IsHE0038Error (ILog log) {
bool IsHE0038Error (ILog log) {
if (log == null)
return false;
if (File.Exists (log.FullPath) && new FileInfo (log.FullPath).Length > 0) {
@ -1773,7 +1775,7 @@ namespace Xharness.Jenkins {
return false;
}
public bool IsMonoMulti3Issue (ILog log) {
bool IsMonoMulti3Issue (ILog log) {
if (log == null)
return false;
if (File.Exists (log.FullPath) && new FileInfo (log.FullPath).Length > 0) {
@ -1788,6 +1790,27 @@ namespace Xharness.Jenkins {
}
return false;
}
public bool IsKnownBuildIssue (ILog buildLog, out string knownFailureMessage)
{
knownFailureMessage = null;
if (IsMonoMulti3Issue (buildLog)) {
knownFailureMessage = $"<a href='https://github.com/mono/mono/issues/18560'>Undefined symbol ___multi3 on Release Mode</a>";
return true;
}
return false;
}
public bool IsKnownTestIssue (ILog runLog, out string knownFailureMessage)
{
knownFailureMessage = null;
if (IsHE0038Error (runLog)) {
knownFailureMessage = $"<a href='https://github.com/xamarin/maccore/issues/581'>HE0038</a>";
return true;
}
return false;
}
string previous_test_runs;
void GenerateReportImpl (Stream stream, StreamWriter markdown_summary = null)

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

@ -7,36 +7,26 @@ using Xharness.TestTasks;
namespace Xharness.Jenkins.TestTasks {
abstract class BuildProjectTask : BuildToolTask
{
Xharness.TestTasks.BuildProjectTask BuildProject => buildToolTask as Xharness.TestTasks.BuildProjectTask;
public string SolutionPath {
get => buildProjectTask.SolutionPath;
set => buildProjectTask.SolutionPath = value;
get => BuildProject.SolutionPath;
set => BuildProject.SolutionPath = value;
}
Xharness.TestTasks.BuildProjectTask buildProjectTask;
public override TestProject TestProject {
get => base.TestProject;
set {
base.TestProject = value;
buildProjectTask.TestProject = value;
}
}
protected BuildProjectTask (Jenkins jenkins, TestProject testProject, IProcessManager processManager) : base (jenkins, processManager)
{
buildProjectTask = new Xharness.TestTasks.BuildProjectTask (processManager, Jenkins, this, this);
TestProject = testProject ?? throw new ArgumentNullException (nameof (testProject));
}
=> TestProject = testProject ?? throw new ArgumentNullException (nameof (testProject));
public virtual bool RestoreNugets => buildProjectTask.RestoreNugets;
public virtual bool RestoreNugets => BuildProject.RestoreNugets;
public override bool SupportsParallelExecution => buildProjectTask.SupportsParallelExecution;
public override bool SupportsParallelExecution => BuildProject.SupportsParallelExecution;
protected override void InitializeTool ()
=> buildToolTask = new Xharness.TestTasks.BuildProjectTask (ProcessManager, Jenkins, this, this);
// This method must be called with the desktop resource acquired
// (which is why it takes an IAcquiredResources as a parameter without using it in the function itself).
protected async Task RestoreNugetsAsync (ILog log, IAcquiredResource resource, bool useXIBuild = false) =>
ExecutionResult = await buildProjectTask.RestoreNugetsAsync (log, resource, useXIBuild);
ExecutionResult = await BuildProject.RestoreNugetsAsync (log, resource, useXIBuild);
}
}

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

@ -1,56 +1,60 @@
using System.Threading.Tasks;
using Microsoft.DotNet.XHarness.iOS.Shared.Execution;
namespace Xharness.Jenkins.TestTasks
{
public abstract class BuildToolTask : AppleTestTask
{
readonly Xharness.TestTasks.BuildToolTask buildToolTask;
public IProcessManager ProcessManager => buildToolTask.ProcessManager;
public override string TestName {
get => base.TestName;
set {
base.TestName = value;
buildToolTask.TestName = value;
}
}
public bool SpecifyPlatform {
get => buildToolTask.SpecifyPlatform;
set => buildToolTask.SpecifyPlatform = value;
}
public bool SpecifyConfiguration {
get => buildToolTask.SpecifyConfiguration;
set => buildToolTask.SpecifyConfiguration = value;
}
public override TestProject TestProject {
get => base.TestProject;
set {
base.TestProject = value;
buildToolTask.TestProject = value;
}
}
protected BuildToolTask (Jenkins jenkins, IProcessManager processManager) : base (jenkins)
=> buildToolTask = new Xharness.TestTasks.BuildToolTask (processManager);
public override TestPlatform Platform {
get => base.Platform;
set {
base.Platform = value;
buildToolTask.Platform = value;
}
}
public override string Mode {
get => buildToolTask.Mode;
set => buildToolTask.Mode = value;
}
public virtual Task CleanAsync () => buildToolTask.CleanAsync ();
}
}
using System;
using System.Threading.Tasks;
using Microsoft.DotNet.XHarness.iOS.Shared.Execution;
namespace Xharness.Jenkins.TestTasks
{
public abstract class BuildToolTask : AppleTestTask
{
protected Xharness.TestTasks.BuildToolTask buildToolTask;
public IProcessManager ProcessManager { get; }
public override string TestName {
get => base.TestName;
set {
base.TestName = value;
buildToolTask.TestName = value;
}
}
public bool SpecifyPlatform {
get => buildToolTask.SpecifyPlatform;
set => buildToolTask.SpecifyPlatform = value;
}
public bool SpecifyConfiguration {
get => buildToolTask.SpecifyConfiguration;
set => buildToolTask.SpecifyConfiguration = value;
}
public override TestProject TestProject {
get => base.TestProject;
set {
base.TestProject = value;
buildToolTask.TestProject = value;
}
}
protected BuildToolTask (Jenkins jenkins, IProcessManager processManager) : base (jenkins) {
ProcessManager = processManager ?? throw new ArgumentNullException (nameof (processManager));
InitializeTool ();
}
public override TestPlatform Platform {
get => base.Platform;
set {
base.Platform = value;
buildToolTask.Platform = value;
}
}
public override string Mode {
get => buildToolTask.Mode;
set => buildToolTask.Mode = value;
}
protected virtual void InitializeTool () => buildToolTask = new Xharness.TestTasks.BuildToolTask (ProcessManager);
public virtual Task CleanAsync () => buildToolTask.CleanAsync ();
}
}

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

@ -1,30 +1,35 @@
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.DotNet.XHarness.iOS.Shared.Execution;
namespace Xharness.Jenkins.TestTasks {
class DotNetBuildTask : MSBuildTask {
public DotNetBuildTask (Jenkins jenkins, TestProject testProject, IProcessManager processManager) : base (jenkins, testProject, processManager)
public DotNetBuildTask (Jenkins jenkins, TestProject testProject, IProcessManager processManager)
: base (jenkins, testProject, processManager) { }
protected override string ToolName => Harness.DOTNET;
public override void SetEnvironmentVariables (Process process)
{
SetDotNetEnvironmentVariables (Environment);
}
protected override string ToolName {
get { return Harness.DOTNET; }
}
public override bool RestoreNugets => false; // 'dotnet build' will restore
protected override List<string> ToolArguments {
get {
var args = base.ToolArguments;
// 'dotnet build' takes almost the same arguments as 'msbuild', so just massage a little bit.
args.Remove ("--");
args.Insert (0, "build");
return args;
}
base.SetEnvironmentVariables (process);
// modify those env vars that we do care about
process.StartInfo.EnvironmentVariables.Remove ("MSBUILD_EXE_PATH");
process.StartInfo.EnvironmentVariables.Remove ("MSBuildExtensionsPathFallbackPathsOverride");
process.StartInfo.EnvironmentVariables.Remove ("MSBuildSDKsPath");
process.StartInfo.EnvironmentVariables.Remove ("TargetFrameworkFallbackSearchPaths");
process.StartInfo.EnvironmentVariables.Remove ("MSBuildExtensionsPathFallbackPathsOverride");
}
protected override void InitializeTool () =>
buildToolTask = new Xharness.TestTasks.DotNetBuildTask (
msbuildPath: ToolName,
processManager: ProcessManager,
resourceManager: Jenkins,
eventLogger: this,
envManager: this,
errorKnowledgeBase: Jenkins);
public static void SetDotNetEnvironmentVariables (Dictionary<string, string> environment)
{

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

@ -1,12 +1,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Xml;
using Microsoft.DotNet.XHarness.iOS.Shared;
using Microsoft.DotNet.XHarness.iOS.Shared.Execution;
using Microsoft.DotNet.XHarness.iOS.Shared.Logging;
using Microsoft.DotNet.XHarness.iOS.Shared.Utilities;
namespace Xharness.Jenkins.TestTasks {
class MSBuildTask : BuildProjectTask
@ -15,98 +11,45 @@ namespace Xharness.Jenkins.TestTasks {
protected virtual string ToolName => Harness.XIBuildPath;
protected virtual List<string> ToolArguments {
get {
var binlogPath = BuildLog.FullPath.Replace (".txt", ".binlog");
protected virtual List<string> ToolArguments =>
MSBuild.GetToolArguments (ProjectPlatform, ProjectConfiguration, ProjectFile, BuildLog);
var args = new List<string> ();
args.Add ("--");
args.Add ("/verbosity:diagnostic");
args.Add ($"/bl:{binlogPath}");
if (SpecifyPlatform)
args.Add ($"/p:Platform={ProjectPlatform}");
if (SpecifyConfiguration)
args.Add ($"/p:Configuration={ProjectConfiguration}");
args.Add (ProjectFile);
return args;
}
}
Xharness.TestTasks.MSBuildTask MSBuild => buildToolTask as Xharness.TestTasks.MSBuildTask;
public MSBuildTask (Jenkins jenkins, TestProject testProject, IProcessManager processManager) : base (jenkins, testProject, processManager)
{
}
public MSBuildTask (Jenkins jenkins, TestProject testProject, IProcessManager processManager)
: base (jenkins, testProject, processManager) { }
protected override void InitializeTool () =>
buildToolTask = new Xharness.TestTasks.MSBuildTask (
msbuildPath: ToolName,
processManager: ProcessManager,
resourceManager: Jenkins,
eventLogger: this,
envManager: this,
errorKnowledgeBase: Jenkins);
protected override async Task ExecuteAsync ()
{
using (var resource = await NotifyAndAcquireDesktopResourceAsync ()) {
BuildLog = Logs.Create ($"build-{Platform}-{Timestamp}.txt", LogType.BuildLog.ToString ());
using var resource = await NotifyAndAcquireDesktopResourceAsync ();
BuildLog = Logs.Create ($"build-{Platform}-{Timestamp}.txt", LogType.BuildLog.ToString ());
(ExecutionResult, KnownFailure) = await MSBuild.ExecuteAsync (
projectPlatform: ProjectPlatform,
projectConfiguration: ProjectConfiguration,
projectFile: ProjectFile,
resource: resource,
dryRun: Harness.DryRun,
buildLog: BuildLog,
mainLog: Jenkins.MainLog);
await RestoreNugetsAsync (BuildLog, resource, useXIBuild: true);
using (var xbuild = new Process ()) {
xbuild.StartInfo.FileName = ToolName;
xbuild.StartInfo.Arguments = StringUtils.FormatArguments (ToolArguments);
SetEnvironmentVariables (xbuild);
xbuild.StartInfo.EnvironmentVariables.Remove ("MSBuildExtensionsPath");
LogEvent (BuildLog, "Building {0} ({1})", TestName, Mode);
if (!Harness.DryRun) {
var timeout = TimeSpan.FromMinutes (60);
var result = await ProcessManager.RunAsync (xbuild, BuildLog, timeout);
if (result.TimedOut) {
ExecutionResult = TestExecutingResult.TimedOut;
BuildLog.WriteLine ("Build timed out after {0} seconds.", timeout.TotalSeconds);
} else if (result.Succeeded) {
ExecutionResult = TestExecutingResult.Succeeded;
} else {
ExecutionResult = TestExecutingResult.Failed;
if (Jenkins.IsMonoMulti3Issue (BuildLog)) {
KnownFailure = $"<a href='https://github.com/mono/mono/issues/18560'>Undefined symbol ___multi3 on Release Mode</a>";
}
}
}
Jenkins.MainLog.WriteLine ("Built {0} ({1})", TestName, Mode);
}
BuildLog.Dispose ();
}
BuildLog.Dispose ();
}
async Task CleanProjectAsync (ILog log, string project_file, string project_platform, string project_configuration)
{
// Don't require the desktop resource here, this shouldn't be that resource sensitive
using (var xbuild = new Process ()) {
xbuild.StartInfo.FileName = Harness.XIBuildPath;
var args = new List<string> ();
args.Add ("--");
args.Add ("/verbosity:diagnostic");
if (project_platform != null)
args.Add ($"/p:Platform={project_platform}");
if (project_configuration != null)
args.Add ($"/p:Configuration={project_configuration}");
args.Add (project_file);
args.Add ("/t:Clean");
xbuild.StartInfo.Arguments = StringUtils.FormatArguments (args);
SetEnvironmentVariables (xbuild);
LogEvent (log, "Cleaning {0} ({1}) - {2}", TestName, Mode, project_file);
var timeout = TimeSpan.FromMinutes (1);
await ProcessManager.RunAsync (xbuild, log, timeout);
log.WriteLine ("Clean timed out after {0} seconds.", timeout.TotalSeconds);
Jenkins.MainLog.WriteLine ("Cleaned {0} ({1})", TestName, Mode);
}
}
public async override Task CleanAsync ()
{
var log = Logs.Create ($"clean-{Platform}-{Timestamp}.txt", "Clean log");
await CleanProjectAsync (log, ProjectFile, SpecifyPlatform ? ProjectPlatform : null, SpecifyConfiguration ? ProjectConfiguration : null);
// Iterate over all the project references as well.
var doc = new XmlDocument ();
doc.LoadWithoutNetworkAccess (ProjectFile);
foreach (var pr in doc.GetProjectReferences ()) {
var path = pr.Replace ('\\', '/');
await CleanProjectAsync (log, path, SpecifyPlatform ? ProjectPlatform : null, SpecifyConfiguration ? ProjectConfiguration : null);
}
}
public override Task CleanAsync () =>
MSBuild.CleanAsync (
projectPlatform: ProjectPlatform,
projectConfiguration: ProjectConfiguration,
projectFile: ProjectFile,
cleanLog: Logs.Create ($"clean-{Platform}-{Timestamp}.txt", "Clean log"),
mainLog: Jenkins.MainLog);
}
}

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

@ -127,8 +127,8 @@ namespace Xharness.Jenkins.TestTasks {
ExecutionResult = runner.Result;
KnownFailure = null;
if (Jenkins.IsHE0038Error (runner.MainLog))
KnownFailure = $"<a href='https://github.com/xamarin/maccore/issues/581'>HE0038</a>";
if (Jenkins.IsKnownTestIssue (runner.MainLog, out KnownFailure))
Jenkins.MainLog.WriteLine ($"Test run has a known failure: '{KnownFailure}'");
}
protected override string XIMode {

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

@ -0,0 +1,24 @@
using System.Collections.Generic;
using Microsoft.DotNet.XHarness.iOS.Shared.Execution;
using Microsoft.DotNet.XHarness.iOS.Shared.Logging;
namespace Xharness.TestTasks {
public class DotNetBuildTask : MSBuildTask {
public DotNetBuildTask (string msbuildPath,
IProcessManager processManager,
IResourceManager resourceManager,
IEventLogger eventLogger,
IEnvManager envManager,
IErrorKnowledgeBase errorKnowledgeBase)
: base (msbuildPath, processManager, resourceManager, eventLogger, envManager, errorKnowledgeBase) { }
public override List<string> GetToolArguments (string projectPlatform, string projectConfiguration, string projectFile, ILog buildLog)
{
var args = base.GetToolArguments (projectPlatform, projectConfiguration, projectFile, buildLog);
args.Remove ("--");
args.Insert (0, "build");
return args;
}
}
}

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

@ -0,0 +1,13 @@
using Microsoft.DotNet.XHarness.iOS.Shared.Logging;
namespace Xharness.TestTasks {
/// <summary>
/// Interface to be implemented by those classes that know about common errors that will be reporter to the
/// harness runner. This allows to store certain problems that we know are common and that we can skip, helping
/// those that are monitoring the result.
/// </summary>
public interface IErrorKnowledgeBase {
bool IsKnownBuildIssue (ILog buildLog, out string knownFailureMessage);
bool IsKnownTestIssue (ILog runLog, out string knownFailureMessage);
}
}

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

@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Xml;
using Microsoft.DotNet.XHarness.iOS.Shared;
using Microsoft.DotNet.XHarness.iOS.Shared.Execution;
using Microsoft.DotNet.XHarness.iOS.Shared.Logging;
using Microsoft.DotNet.XHarness.iOS.Shared.Utilities;
namespace Xharness.TestTasks {
public class MSBuildTask : BuildProjectTask {
readonly IErrorKnowledgeBase errorKnowledgeBase;
readonly string msbuildPath;
public virtual List<string> GetToolArguments (string projectPlatform, string projectConfiguration, string projectFile, ILog buildLog) {
var binlogPath = buildLog.FullPath.Replace (".txt", ".binlog");
var args = new List<string> ();
args.Add ("--");
args.Add ("/verbosity:diagnostic");
args.Add ($"/bl:{binlogPath}");
if (SpecifyPlatform)
args.Add ($"/p:Platform={projectPlatform}");
if (SpecifyConfiguration)
args.Add ($"/p:Configuration={projectConfiguration}");
args.Add (projectFile);
return args;
}
public MSBuildTask (string msbuildPath,
IProcessManager processManager,
IResourceManager resourceManager,
IEventLogger eventLogger,
IEnvManager envManager,
IErrorKnowledgeBase errorKnowledgeBase) : base (processManager, resourceManager, eventLogger, envManager)
{
this.msbuildPath = msbuildPath ?? throw new ArgumentNullException (nameof (msbuildPath));
this.errorKnowledgeBase = errorKnowledgeBase ?? throw new ArgumentNullException (nameof (errorKnowledgeBase));
}
public async Task<(TestExecutingResult ExecutionResult, string KnownFailure)> ExecuteAsync (string projectPlatform,
string projectConfiguration,
string projectFile,
IAcquiredResource resource,
bool dryRun,
ILog buildLog,
ILog mainLog)
{
(TestExecutingResult ExecutionResult, string KnownFailure) result = (TestExecutingResult.NotStarted, (string) null);
await RestoreNugetsAsync (buildLog, resource, useXIBuild: true);
using (var xbuild = new Process ()) {
xbuild.StartInfo.FileName = msbuildPath;
xbuild.StartInfo.Arguments = StringUtils.FormatArguments (GetToolArguments (projectPlatform, projectConfiguration, projectFile, buildLog));
EnviromentManager.SetEnvironmentVariables (xbuild);
xbuild.StartInfo.EnvironmentVariables ["MSBuildExtensionsPath"] = null;
EventLogger.LogEvent (buildLog, "Building {0} ({1})", TestName, Mode);
if (!dryRun) {
var timeout = TimeSpan.FromMinutes (60);
var processResult = await ProcessManager.RunAsync (xbuild, buildLog, timeout);
if (processResult.TimedOut) {
result.ExecutionResult = TestExecutingResult.TimedOut;
buildLog.WriteLine ("Build timed out after {0} seconds.", timeout.TotalSeconds);
} else if (processResult.Succeeded) {
result.ExecutionResult = TestExecutingResult.Succeeded;
} else {
result.ExecutionResult = TestExecutingResult.Failed;
if (errorKnowledgeBase.IsKnownBuildIssue (buildLog, out result.KnownFailure))
buildLog.WriteLine ($"Build has a known failure: '{result.KnownFailure}'");
}
}
mainLog.WriteLine ("Built {0} ({1})", TestName, Mode);
}
return result;
}
async Task CleanProjectAsync (string project_file, string project_platform, string project_configuration, ILog log, ILog mainLog)
{
// Don't require the desktop resource here, this shouldn't be that resource sensitive
using (var xbuild = new Process ()) {
xbuild.StartInfo.FileName = Harness.XIBuildPath;
var args = new List<string> ();
args.Add ("--");
args.Add ("/verbosity:diagnostic");
if (project_platform != null)
args.Add ($"/p:Platform={project_platform}");
if (project_configuration != null)
args.Add ($"/p:Configuration={project_configuration}");
args.Add (project_file);
args.Add ("/t:Clean");
xbuild.StartInfo.Arguments = StringUtils.FormatArguments (args);
EnviromentManager.SetEnvironmentVariables (xbuild);
EventLogger.LogEvent (log, "Cleaning {0} ({1}) - {2}", TestName, Mode, project_file);
var timeout = TimeSpan.FromMinutes (1);
await ProcessManager.RunAsync (xbuild, log, timeout);
log.WriteLine ("Clean timed out after {0} seconds.", timeout.TotalSeconds);
mainLog.WriteLine ("Cleaned {0} ({1})", TestName, Mode);
}
}
public async Task CleanAsync (string projectPlatform, string projectConfiguration, string projectFile, ILog cleanLog, ILog mainLog)
{
await CleanProjectAsync (projectFile, SpecifyPlatform ? projectPlatform : null, SpecifyConfiguration ? projectConfiguration : null, cleanLog, mainLog);
// Iterate over all the project references as well.
var doc = new XmlDocument ();
doc.LoadWithoutNetworkAccess (projectFile);
foreach (var pr in doc.GetProjectReferences ()) {
var path = pr.Replace ('\\', '/');
await CleanProjectAsync (path, SpecifyPlatform ? projectPlatform : null, SpecifyConfiguration ? projectConfiguration : null, cleanLog, mainLog);
}
}
}
}

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

@ -124,6 +124,9 @@
<Compile Include="TestTasks\BuildProjectTask.cs" />
<Compile Include="TestTasks\IResourceManager.cs" />
<Compile Include="TestTasks\IEnvManager.cs" />
<Compile Include="TestTasks\MSBuildTask.cs" />
<Compile Include="TestTasks\IErrorKnowledgeBase.cs" />
<Compile Include="TestTasks\DotNetBuildTask.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\tools\common\SdkVersions.cs">