1 // Copyright (c) Microsoft. All rights reserved. 2 // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 //----------------------------------------------------------------------- 4 // </copyright> 5 // <summary> The task factory for Xaml data driven tasks. </summary> 6 //----------------------------------------------------------------------- 7 8 using System; 9 using System.Collections.Generic; 10 using Microsoft.Build.Framework; 11 using Microsoft.Build.Shared; 12 using Microsoft.Build.Utilities; 13 14 namespace Microsoft.Build.Tasks 15 { 16 #if FEATURE_XAMLTASKFACTORY 17 18 using Microsoft.Build.Tasks.Xaml; 19 using System.CodeDom.Compiler; 20 using System.CodeDom; 21 using System.IO; 22 using System.Reflection; 23 using System.Text; 24 using System.Threading; 25 using System.Xml; 26 27 /// <summary> 28 /// The task factory provider for XAML tasks. 29 /// </summary> 30 public class XamlTaskFactory : ITaskFactory 31 { 32 /// <summary> 33 /// The namespace we put the task in. 34 /// </summary> 35 private const string XamlTaskNamespace = "XamlTaskNamespace"; 36 37 /// <summary> 38 /// The compiled task assembly. 39 /// </summary> 40 private Assembly _taskAssembly; 41 42 /// <summary> 43 /// The task type. 44 /// </summary> 45 private Type _taskType; 46 47 /// <summary> 48 /// The name of the task pulled from the XAML. 49 /// </summary> 50 public string TaskName 51 { 52 get; 53 private set; 54 } 55 56 /// <summary> 57 /// The namespace of the task pulled from the XAML. 58 /// </summary> 59 public string TaskNamespace 60 { 61 get; 62 private set; 63 } 64 65 /// <summary> 66 /// The contents of the UsingTask body. 67 /// </summary> 68 public string TaskElementContents 69 { 70 get; 71 private set; 72 } 73 74 /// <summary> 75 /// The name of this factory. This factory name will be used in error messages. For example 76 /// Task "Mytask" failed to load from "FactoryName". 77 /// </summary> 78 public string FactoryName 79 { 80 get 81 { 82 return "XamlTaskFactory"; 83 } 84 } 85 86 /// <summary> 87 /// The task type object. 88 /// </summary> 89 public Type TaskType 90 { 91 get 92 { 93 if (_taskType == null) 94 { 95 _taskType = _taskAssembly.GetType(String.Concat(XamlTaskNamespace, ".", TaskName), true); 96 } 97 98 return _taskType; 99 } 100 } 101 102 /// <summary> 103 /// MSBuild engine will call this to initialize the factory. This should initialize the factory enough so that the factory can be asked 104 /// whether or not task names can be created by the factory. 105 /// </summary> Initialize(string taskName, IDictionary<string, TaskPropertyInfo> taskParameters, string taskElementContents, IBuildEngine taskFactoryLoggingHost)106 public bool Initialize(string taskName, IDictionary<string, TaskPropertyInfo> taskParameters, string taskElementContents, IBuildEngine taskFactoryLoggingHost) 107 { 108 ErrorUtilities.VerifyThrowArgumentNull(taskName, "taskName"); 109 ErrorUtilities.VerifyThrowArgumentNull(taskParameters, "taskParameters"); 110 111 TaskLoggingHelper log = new TaskLoggingHelper(taskFactoryLoggingHost, taskName); 112 log.TaskResources = AssemblyResources.PrimaryResources; 113 log.HelpKeywordPrefix = "MSBuild."; 114 115 if (taskElementContents == null) 116 { 117 log.LogErrorWithCodeFromResources("Xaml.MissingTaskBody"); 118 return false; 119 } 120 121 TaskElementContents = taskElementContents.Trim(); 122 123 // Attempt to load the task 124 TaskParser parser = new TaskParser(); 125 126 bool parseSuccessful = parser.Parse(TaskElementContents, taskName); 127 128 TaskName = parser.GeneratedTaskName; 129 TaskNamespace = parser.Namespace; 130 TaskGenerator generator = new TaskGenerator(parser); 131 132 CodeCompileUnit dom = generator.GenerateCode(); 133 134 string pathToMSBuildBinaries = ToolLocationHelper.GetPathToBuildTools(ToolLocationHelper.CurrentToolsVersion); 135 136 // create the code generator options 137 // Since we are running msbuild 12.0 these had better load. 138 CompilerParameters compilerParameters = new CompilerParameters 139 ( 140 new string[] 141 { 142 "System.dll", 143 Path.Combine(pathToMSBuildBinaries, "Microsoft.Build.Framework.dll"), 144 Path.Combine(pathToMSBuildBinaries, "Microsoft.Build.Utilities.Core.dll"), 145 Path.Combine(pathToMSBuildBinaries, "Microsoft.Build.Tasks.Core.dll") 146 } 147 148 ); 149 150 compilerParameters.GenerateInMemory = true; 151 compilerParameters.TreatWarningsAsErrors = false; 152 153 // create the code provider 154 CodeDomProvider codegenerator = CodeDomProvider.CreateProvider("cs"); 155 CompilerResults results; 156 bool debugXamlTask = Environment.GetEnvironmentVariable("MSBUILDWRITEXAMLTASK") == "1"; 157 if (debugXamlTask) 158 { 159 using (StreamWriter outputWriter = new StreamWriter(taskName + "_XamlTask.cs")) 160 { 161 CodeGeneratorOptions options = new CodeGeneratorOptions(); 162 options.BlankLinesBetweenMembers = true; 163 options.BracingStyle = "C"; 164 165 codegenerator.GenerateCodeFromCompileUnit(dom, outputWriter, options); 166 } 167 168 results = codegenerator.CompileAssemblyFromFile(compilerParameters, taskName + "_XamlTask.cs"); 169 } 170 else 171 { 172 results = codegenerator.CompileAssemblyFromDom(compilerParameters, new[] { dom }); 173 } 174 175 try 176 { 177 _taskAssembly = results.CompiledAssembly; 178 } 179 catch (FileNotFoundException) 180 { 181 // This occurs if there is a failure to compile the assembly. We just pass through because we will take care of the failure below. 182 } 183 184 if (_taskAssembly == null) 185 { 186 StringBuilder errorList = new StringBuilder(); 187 errorList.AppendLine(); 188 foreach (CompilerError error in results.Errors) 189 { 190 if (error.IsWarning) 191 { 192 continue; 193 } 194 195 if (debugXamlTask) 196 { 197 errorList.AppendLine(String.Format(Thread.CurrentThread.CurrentUICulture, "({0},{1}) {2}", error.Line, error.Column, error.ErrorText)); 198 } 199 else 200 { 201 errorList.AppendLine(error.ErrorText); 202 } 203 } 204 205 log.LogErrorWithCodeFromResources("Xaml.TaskCreationFailed", errorList.ToString()); 206 } 207 208 return !log.HasLoggedErrors; 209 } 210 211 /// <summary> 212 /// Create an instance of the task to be used. 213 /// </summary> 214 /// <param name="taskFactoryLoggingHost">The task factory logging host will log messages in the context of the task.</param> CreateTask(IBuildEngine taskFactoryLoggingHost)215 public ITask CreateTask(IBuildEngine taskFactoryLoggingHost) 216 { 217 string fullTaskName = String.Concat(TaskNamespace, ".", TaskName); 218 return (ITask)_taskAssembly.CreateInstance(fullTaskName); 219 } 220 221 /// <summary> 222 /// Cleans up any context or state that may have been built up for a given task. 223 /// </summary> 224 /// <param name="task">The task to clean up.</param> 225 /// <remarks> 226 /// For many factories, this method is a no-op. But some factories may have built up 227 /// an AppDomain as part of an individual task instance, and this is their opportunity 228 /// to shutdown the AppDomain. 229 /// </remarks> CleanupTask(ITask task)230 public void CleanupTask(ITask task) 231 { 232 ErrorUtilities.VerifyThrowArgumentNull(task, "task"); 233 } 234 235 /// <summary> 236 /// Get a list of parameters for the task. 237 /// </summary> GetTaskParameters()238 public TaskPropertyInfo[] GetTaskParameters() 239 { 240 PropertyInfo[] infos = TaskType.GetProperties(BindingFlags.Instance | BindingFlags.Public); 241 var propertyInfos = new TaskPropertyInfo[infos.Length]; 242 for (int i = 0; i < infos.Length; i++) 243 { 244 propertyInfos[i] = new TaskPropertyInfo( 245 infos[i].Name, 246 infos[i].PropertyType, 247 infos[i].GetCustomAttributes(typeof(OutputAttribute), false).Length > 0, 248 infos[i].GetCustomAttributes(typeof(RequiredAttribute), false).Length > 0); 249 } 250 251 return propertyInfos; 252 } 253 } 254 #else 255 /// <summary> 256 /// The task factory provider for XAML tasks. 257 /// </summary> 258 /// <remarks>Xaml is not supported on .NET Core so this task factory simply logs an error that it isn't supported. 259 /// 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> 260 [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)] 261 public sealed class XamlTaskFactory : ITaskFactory 262 { 263 public string FactoryName => "XamlTaskFactory"; 264 265 public Type TaskType { get; } = null; 266 267 public bool Initialize(string taskName, IDictionary<string, TaskPropertyInfo> parameterGroup, string taskBody, IBuildEngine taskFactoryLoggingHost) 268 { 269 TaskLoggingHelper log = new TaskLoggingHelper(taskFactoryLoggingHost, taskName) 270 { 271 TaskResources = AssemblyResources.PrimaryResources, 272 HelpKeywordPrefix = "MSBuild." 273 }; 274 275 log.LogErrorFromResources("TaskFactoryNotSupportedFailure", nameof(XamlTaskFactory)); 276 277 return false; 278 } 279 280 public TaskPropertyInfo[] GetTaskParameters() 281 { 282 throw new NotSupportedException(); 283 } 284 285 public ITask CreateTask(IBuildEngine taskFactoryLoggingHost) 286 { 287 throw new NotSupportedException(); 288 } 289 290 public void CleanupTask(ITask task) 291 { 292 throw new NotSupportedException(); 293 } 294 } 295 #endif 296 } 297