Merge pull request #890 from xoofx/master
[mono.git] / mcs / class / Microsoft.Build.Tasks / Microsoft.Build.Tasks / CodeTaskFactory.cs
1 //
2 // CodeTaskFactory.cs
3 //
4 // Author:
5 //   Atsushi Enomoto <atsushi@xamarin.com>
6 //
7 // Copyright (C) 2014 Xamarin Inc.
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28 #if NET_4_0
29 using System;
30 using Microsoft.Build.Framework;
31 using System.Collections.Generic;
32 using System.Xml;
33 using System.IO;
34 using Microsoft.Build.BuildEngine;
35 using System.CodeDom;
36 using Microsoft.CSharp;
37 using System.CodeDom.Compiler;
38 using System.Linq;
39 using System.Reflection;
40 using System.Collections.Specialized;
41
42 namespace Microsoft.Build.Tasks
43 {
44         public class CodeTaskFactory : ITaskFactory2
45         {
46                 public CodeTaskFactory ()
47                 {
48                 }
49
50                 #region ITaskFactory implementation
51                 public void CleanupTask (ITask task)
52                 {
53                 }
54                 public ITask CreateTask (IBuildEngine taskFactoryLoggingHost)
55                 {
56                         return CreateTask (taskFactoryLoggingHost, null);
57                 }
58                 public ITask CreateTask (IBuildEngine taskFactoryLoggingHost, IDictionary<string, string> taskIdentityParameters)
59                 {
60                         if (assembly == null)
61                                 return null;
62                         return (ITask) Activator.CreateInstance (assembly.GetType (task_name));
63                 }
64
65                 public TaskPropertyInfo [] GetTaskParameters ()
66                 {
67                         return parameter_group != null ? parameter_group.Values.ToArray () : new TaskPropertyInfo [0];
68                 }
69                 public bool Initialize (string taskName, IDictionary<string, TaskPropertyInfo> parameterGroup, string taskBody, IBuildEngine taskFactoryLoggingHost)
70                 {
71                         return Initialize (taskName, null, parameterGroup, taskBody, taskFactoryLoggingHost);
72                 }
73                 public bool Initialize (string taskName, IDictionary<string, string> factoryIdentityParameters, IDictionary<string, TaskPropertyInfo> parameterGroup, string taskBody, IBuildEngine taskFactoryLoggingHost)
74                 {
75                         task_name = taskName;
76                         if (parameterGroup != null)
77                                 parameter_group = new Dictionary<string, TaskPropertyInfo> (parameterGroup);
78                         
79                         List<string> references = new List<string> ();
80                         List<string> namespace_uses = new List<string> ();
81                         namespace_uses.Add ("Microsoft.Build.Framework");
82                         string type = null, language = null, code = null;
83
84                         var xml = XmlReader.Create (new StringReader (taskBody), new XmlReaderSettings () { ConformanceLevel = ConformanceLevel.Fragment });
85                         for (xml.MoveToContent (); !xml.EOF; xml.MoveToContent ()) {
86                                 switch (xml.NodeType) {
87                                 case XmlNodeType.Element:
88                                         switch (xml.LocalName) {
89                                         case "Reference":
90                                                 references.Add (xml.GetAttribute ("Include"));
91                                                 xml.Skip ();
92                                                 break;
93                                         case "Using":
94                                                 namespace_uses.Add (xml.GetAttribute ("Namespace"));
95                                                 xml.Skip ();
96                                                 break;
97                                         case "Code":
98                                                 // MSB3757: Multiple Code elements have been found, this is not allowed.
99                                                 if (code != null)
100                                                         throw new InvalidProjectFileException (null, "Multiple Code elements are not allowed", "MSB", "3757", null);
101                                                 type = xml.GetAttribute ("Type");
102                                                 language = xml.GetAttribute ("Language");
103                                                 code = xml.ReadElementContentAsString ();
104                                                 break;
105                                         }
106                                         break;
107                                 default:
108                                         xml.Skip ();
109                                         break;
110                                 }
111                         }
112                         if (language == "vb")
113                                 throw new NotImplementedException (string.Format ("{0} is not supported language for inline task", language));
114                         if (language != "cs")
115                                 throw new InvalidProjectFileException (null, string.Format ("{0} is not supported language for inline task", language), "MSB", "4175", null);
116                         string gen = null;
117                         // The documentation says "ITask", but the very first example shows "Log" which is not in ITask! It is likely that the generated code uses Task or TaskExtension.
118                         string classTemplate = @"public class " + taskName + " : Microsoft.Build.Utilities.Task { @@EXECUTE@@ }";
119                         foreach (var ns in namespace_uses)
120                                 gen += "using " + ns + ";\n";
121                         switch (type) {
122                         case "Class":
123                                 gen += code;
124                                 break;
125                         case "Method":
126                                 gen += classTemplate.Replace ("@@EXECUTE@@", code);
127                                 break;
128                         case "Fragment":
129                                 gen += classTemplate.Replace ("@@EXECUTE@@", "public override bool Execute () { " + code + " return true; }");
130                                 break;
131                         }
132
133                         var cscParams = new CompilerParameters ();
134                         cscParams.ReferencedAssemblies.Add ("Microsoft.Build.Framework.dll");
135                         cscParams.ReferencedAssemblies.Add ("Microsoft.Build.Utilities.v4.0.dll"); // since we use Task, it depends on this dll.
136                         var results = new CSharpCodeProvider ().CompileAssemblyFromSource (cscParams, gen);
137                         var errors = new CompilerError [results.Errors.Count];
138                         results.Errors.CopyTo (errors, 0);
139                         if (errors.Any (e => !e.IsWarning)) {
140                                 string msg = string.Format ("Invalid '{0}' source code of '{1}' type: {2}", language, type, string.Join (" ", errors.Where (e => !e.IsWarning).Select (e => e.ToString ())));
141                                 throw new InvalidProjectFileException (null, msg, "MSB", "3758", null);
142                         }
143                         assembly = results.CompiledAssembly;
144                         return true;
145                 }
146                 public string FactoryName {
147                         get { return "Code Task Factory"; }
148                 }
149                 public Type TaskType {
150                         get { return null; }
151                 }
152
153                 string task_name;
154                 Assembly assembly;
155                 Dictionary<string, TaskPropertyInfo> parameter_group;
156
157                 #endregion
158         }
159 }
160
161 #endif