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