|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Microsoft.Build.Utilities;
#nullable disable
namespace Microsoft.Build.Tasks
{
#if FEATURE_XAMLTASKFACTORY
using System.CodeDom;
using System.CodeDom.Compiler;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading;
using Microsoft.Build.Tasks.Xaml;
/// <summary>
/// The task factory provider for XAML tasks.
/// </summary>
public class XamlTaskFactory : ITaskFactory
{
/// <summary>
/// The namespace we put the task in.
/// </summary>
private const string XamlTaskNamespace = "XamlTaskNamespace";
/// <summary>
/// The compiled task assembly.
/// </summary>
private Assembly _taskAssembly;
/// <summary>
/// The task type.
/// </summary>
private Type _taskType;
/// <summary>
/// The name of the task pulled from the XAML.
/// </summary>
public string TaskName { get; private set; }
/// <summary>
/// The namespace of the task pulled from the XAML.
/// </summary>
public string TaskNamespace { get; private set; }
/// <summary>
/// The contents of the UsingTask body.
/// </summary>
public string TaskElementContents { get; private set; }
/// <summary>
/// The name of this factory. This factory name will be used in error messages. For example
/// Task "Mytask" failed to load from "FactoryName".
/// </summary>
public string FactoryName { get; } = "XamlTaskFactory";
/// <summary>
/// The task type object.
/// </summary>
public Type TaskType
{
get
{
if (_taskType == null)
{
_taskType = _taskAssembly.GetType(String.Concat(XamlTaskNamespace, ".", TaskName), true);
}
return _taskType;
}
}
/// <summary>
/// MSBuild engine will call this to initialize the factory. This should initialize the factory enough so that the factory can be asked
/// whether or not task names can be created by the factory.
/// </summary>
public bool Initialize(string taskName, IDictionary<string, TaskPropertyInfo> taskParameters, string taskElementContents, IBuildEngine taskFactoryLoggingHost)
{
ErrorUtilities.VerifyThrowArgumentNull(taskName);
ErrorUtilities.VerifyThrowArgumentNull(taskParameters);
var log = new TaskLoggingHelper(taskFactoryLoggingHost, taskName)
{
TaskResources = AssemblyResources.PrimaryResources,
HelpKeywordPrefix = "MSBuild."
};
if (taskElementContents == null)
{
log.LogErrorWithCodeFromResources("Xaml.MissingTaskBody");
return false;
}
TaskElementContents = taskElementContents.Trim();
// Attempt to load the task
TaskParser parser = new TaskParser();
bool parseSuccessful = parser.Parse(TaskElementContents, taskName);
TaskName = parser.GeneratedTaskName;
TaskNamespace = parser.Namespace;
var generator = new TaskGenerator(parser);
CodeCompileUnit dom = generator.GenerateCode();
// MSBuildToolsDirectoryRoot is the canonical location for MSBuild dll's.
string pathToMSBuildBinaries = BuildEnvironmentHelper.Instance.MSBuildToolsDirectoryRoot;
// create the code generator options
// Since we are running msbuild 12.0 these had better load.
var compilerParameters = new CompilerParameters(
[
"System.dll",
Path.Combine(pathToMSBuildBinaries, "Microsoft.Build.Framework.dll"),
Path.Combine(pathToMSBuildBinaries, "Microsoft.Build.Utilities.Core.dll"),
Path.Combine(pathToMSBuildBinaries, "Microsoft.Build.Tasks.Core.dll")
])
{
GenerateInMemory = true,
TreatWarningsAsErrors = false
};
// create the code provider
using var codegenerator = CodeDomProvider.CreateProvider("cs");
CompilerResults results;
bool debugXamlTask = Environment.GetEnvironmentVariable("MSBUILDWRITEXAMLTASK") == "1";
if (debugXamlTask)
{
using (var outputWriter = new StreamWriter(taskName + "_XamlTask.cs"))
{
var options = new CodeGeneratorOptions
{
BlankLinesBetweenMembers = true,
BracingStyle = "C"
};
codegenerator.GenerateCodeFromCompileUnit(dom, outputWriter, options);
}
results = codegenerator.CompileAssemblyFromFile(compilerParameters, taskName + "_XamlTask.cs");
}
else
{
results = codegenerator.CompileAssemblyFromDom(compilerParameters, dom);
}
try
{
_taskAssembly = results.CompiledAssembly;
}
catch (FileNotFoundException)
{
// This occurs if there is a failure to compile the assembly. We just pass through because we will take care of the failure below.
}
if (_taskAssembly == null)
{
var errorList = new StringBuilder();
errorList.AppendLine();
foreach (CompilerError error in results.Errors)
{
if (error.IsWarning)
{
continue;
}
if (debugXamlTask)
{
errorList.AppendFormat(Thread.CurrentThread.CurrentUICulture, "({0},{1}) {2}", error.Line, error.Column, error.ErrorText).AppendLine();
}
else
{
errorList.AppendLine(error.ErrorText);
}
}
log.LogErrorWithCodeFromResources("Xaml.TaskCreationFailed", errorList.ToString());
}
return !log.HasLoggedErrors;
}
/// <summary>
/// Create an instance of the task to be used.
/// </summary>
/// <param name="taskFactoryLoggingHost">The task factory logging host will log messages in the context of the task.</param>
public ITask CreateTask(IBuildEngine taskFactoryLoggingHost)
{
string fullTaskName = String.Concat(TaskNamespace, ".", TaskName);
return (ITask)_taskAssembly.CreateInstance(fullTaskName);
}
/// <summary>
/// Cleans up any context or state that may have been built up for a given task.
/// </summary>
/// <param name="task">The task to clean up.</param>
/// <remarks>
/// For many factories, this method is a no-op. But some factories may have built up
/// an AppDomain as part of an individual task instance, and this is their opportunity
/// to shutdown the AppDomain.
/// </remarks>
public void CleanupTask(ITask task)
{
ErrorUtilities.VerifyThrowArgumentNull(task);
}
/// <summary>
/// Get a list of parameters for the task.
/// </summary>
public TaskPropertyInfo[] GetTaskParameters()
{
PropertyInfo[] infos = TaskType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
var propertyInfos = new TaskPropertyInfo[infos.Length];
for (int i = 0; i < infos.Length; i++)
{
propertyInfos[i] = new TaskPropertyInfo(
infos[i].Name,
infos[i].PropertyType,
infos[i].GetCustomAttributes(typeof(OutputAttribute), false).Length > 0,
infos[i].GetCustomAttributes(typeof(RequiredAttribute), false).Length > 0);
}
return propertyInfos;
}
}
#else
/// <summary>
/// The task factory provider for XAML tasks.
/// </summary>
/// <remarks>Xaml is not supported on .NET Core so this task factory simply logs an error that it isn't supported.
/// If we don't compile this class, then the user will get an error that the class doesn't exist which is a bad experience.</remarks>
[Obsolete("The XamlTaskFactory is not supported on .NET Core. This class is included so that users receive run-time errors and should not be used for any other purpose.", error: true)]
public sealed class XamlTaskFactory : ITaskFactory
{
public string FactoryName => "XamlTaskFactory";
public Type TaskType { get; } = null;
public bool Initialize(string taskName, IDictionary<string, TaskPropertyInfo> parameterGroup, string taskBody, IBuildEngine taskFactoryLoggingHost)
{
TaskLoggingHelper log = new TaskLoggingHelper(taskFactoryLoggingHost, taskName)
{
TaskResources = AssemblyResources.PrimaryResources,
HelpKeywordPrefix = "MSBuild."
};
log.LogErrorWithCodeFromResources("TaskFactoryNotSupportedFailure", nameof(XamlTaskFactory));
return false;
}
public TaskPropertyInfo[] GetTaskParameters()
{
throw new NotSupportedException();
}
public ITask CreateTask(IBuildEngine taskFactoryLoggingHost)
{
throw new NotSupportedException();
}
public void CleanupTask(ITask task)
{
throw new NotSupportedException();
}
}
#endif
}
|