Merge remote-tracking branch 'joncham/sgen-msvc2'
[mono.git] / mcs / class / Microsoft.Build.Engine / Microsoft.Build.BuildEngine / TaskEngine.cs
1 //
2 // TaskEngine.cs: Class that executes each task.
3 //
4 // Author:
5 //   Marek Sieradzki (marek.sieradzki@gmail.com)
6 //   Ankit Jain (jankit@novell.com)
7 // 
8 // (C) 2005 Marek Sieradzki
9 // Copyright 2009 Novell, Inc (http://www.novell.com)
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 //
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 //
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29
30 using System;
31 using System.Collections.Generic;
32 using System.Collections.Specialized;
33 using System.Reflection;
34 using System.Xml;
35 using Microsoft.Build.Framework;
36 using Microsoft.Build.Utilities;
37
38 namespace Microsoft.Build.BuildEngine {
39         internal class TaskEngine {
40                 
41                 ITask           task;
42                 XmlElement      taskElement;
43                 Type            taskType;
44                 Project         parentProject;
45                 
46                 static Type     requiredAttribute;
47                 static Type     outputAttribute;
48                 
49                 static TaskEngine ()
50                 {
51                         requiredAttribute = typeof (Microsoft.Build.Framework.RequiredAttribute);
52                         outputAttribute = typeof (Microsoft.Build.Framework.OutputAttribute);
53                 }
54
55                 public TaskEngine (Project project)
56                 {
57                         parentProject = project;
58                 }
59
60                 // Rules (inferred) for property values incase of empty data
61                 //
62                 // Prop Type         Argument         Final Value     Required
63                 // string            empty string       null           Yes/no
64                 // string            only whitespace    @arg           Yes/no
65                 //
66                 // string/
67                 //   ITaskItem[]     empty/whitespace   empty array     Yes
68                 //
69                 // string/
70                 //   ITaskItem[]     empty/whitespace   null            No
71                 
72                 public void Prepare (ITask task, XmlElement taskElement,
73                                      IDictionary <string, string> parameters, Type taskType)
74                 {
75                         Dictionary <string, object>     values;
76                         PropertyInfo    currentProperty;
77                         PropertyInfo[]  properties;
78                         object          value;
79                         
80                         this.task = task;
81                         this.taskElement = taskElement;
82                         this.taskType = taskType;
83                         values = new Dictionary <string, object> (StringComparer.OrdinalIgnoreCase);
84                         
85                         foreach (KeyValuePair <string, string> de in parameters) {
86                                 currentProperty = taskType.GetProperty (de.Key, BindingFlags.Public | BindingFlags.Instance
87                                                 | BindingFlags.IgnoreCase);
88                                 if (currentProperty == null)
89                                         throw new InvalidProjectFileException (String.Format ("Task does not have property \"{0}\" defined",
90                                                 de.Key));
91                                 
92                                 try {
93                                         if (TryGetObjectFromString (de.Value, currentProperty.PropertyType, out value))
94                                                 values.Add (de.Key, value);
95                                 } catch (Exception e) {
96                                         throw new InvalidProjectFileException (String.Format (
97                                                         "Error converting Property named '{0}' with value '{1}' to type {2}: {3}",
98                                                         de.Key, de.Value, currentProperty.PropertyType, e.Message), e);
99                                 }
100                         }
101                         
102                         properties = taskType.GetProperties ();
103                         foreach (PropertyInfo pi in properties) {
104                                 bool is_required = pi.IsDefined (requiredAttribute, false);
105
106                                 if (is_required && values.ContainsKey (pi.Name) == false)
107                                         throw new InvalidProjectFileException (String.Format ("Required property '{0}' not set.",
108                                                 pi.Name));
109
110                                 if (!values.ContainsKey (pi.Name))
111                                         continue;
112
113                                 Type prop_type = pi.PropertyType;
114                                 if (prop_type.IsArray)
115                                         prop_type = prop_type.GetElementType ();
116
117                                 // Valid data types: primitive types, DateTime, string and ITaskItem, and their arrays
118                                 if (!prop_type.IsPrimitive && prop_type != typeof (string) && prop_type != typeof (ITaskItem))
119                                         throw new InvalidProjectFileException (String.Format (
120                                                         "{0} is not a supported type for properties for msbuild tasks.",
121                                                         pi.PropertyType));
122
123                                 object val = values [pi.Name];
124                                 if (val == null && pi.PropertyType.IsArray && is_required) {
125                                         if (pi.PropertyType == typeof (ITaskItem[]))
126                                                 val = new ITaskItem [0];
127                                         else if (pi.PropertyType == typeof (string[]))
128                                                 val = new string [0];
129                                 }
130
131                                 InitializeParameter (pi, val);
132                         }
133                 }
134                 
135                 public bool Execute ()
136                 {
137                         return task.Execute ();
138                 }
139                 
140                 public void PublishOutput ()
141                 {
142                         XmlElement      xmlElement;
143                         PropertyInfo    propertyInfo;
144                         string          propertyName;
145                         string          taskParameter;
146                         string          itemName;
147                         object          o;
148                 
149                         foreach (XmlNode xmlNode in taskElement.ChildNodes) {
150                                 if (!(xmlNode is XmlElement))
151                                         continue;
152                         
153                                 xmlElement = (XmlElement) xmlNode;
154                                 
155                                 if (xmlElement.Name != "Output")
156                                         throw new InvalidProjectFileException ("Only Output elements can be Task's child nodes.");
157                                 if (xmlElement.GetAttribute ("ItemName") != String.Empty && xmlElement.GetAttribute ("PropertyName") != String.Empty)
158                                         throw new InvalidProjectFileException ("Only one of ItemName and PropertyName attributes can be specified.");
159                                 if (xmlElement.GetAttribute ("TaskParameter") == String.Empty)
160                                         throw new InvalidProjectFileException ("TaskParameter attribute must be specified.");
161
162                                 if (!ConditionParser.ParseAndEvaluate (xmlElement.GetAttribute ("Condition"), parentProject))
163                                         continue;
164                                         
165                                 taskParameter = xmlElement.GetAttribute ("TaskParameter");
166                                 itemName = xmlElement.GetAttribute ("ItemName");
167                                 propertyName = xmlElement.GetAttribute ("PropertyName");
168                                 
169                                 propertyInfo = taskType.GetProperty (taskParameter, BindingFlags.Public | BindingFlags.Instance |
170                                                         BindingFlags.IgnoreCase);
171                                 if (propertyInfo == null)
172                                         throw new InvalidProjectFileException (String.Format (
173                                                 "The parameter '{0}' was not found for the '{1}' task.", taskParameter, taskElement.Name));
174                                 if (!propertyInfo.IsDefined (outputAttribute, false))
175                                         throw new InvalidProjectFileException ("This is not output property.");
176                                 
177                                 o = propertyInfo.GetValue (task, null);
178                                 if (itemName != String.Empty) {
179                                         PublishItemGroup (propertyInfo, o, itemName);
180                                 } else {
181                                         PublishProperty (propertyInfo, o, propertyName);
182                                 }
183                         }
184                 }
185                 
186                 void InitializeParameter (PropertyInfo propertyInfo, object value)
187                 {
188                         propertyInfo.SetValue (task, value, null);
189                 }
190
191                 void PublishProperty (PropertyInfo propertyInfo,
192                                               object o,
193                                               string propertyName)
194                 {
195                         if (o == null) {
196                                 parentProject.EvaluatedProperties.RemoveProperty (propertyName);
197                                 return;
198                         }
199
200                         BuildProperty bp;
201                         try {
202                                 bp = ChangeType.ToBuildProperty (o, propertyInfo.PropertyType, propertyName);
203                         } catch (Exception e) {
204                                 throw new Exception (String.Format ("Error publishing Output from task property '{0} {1}' to property named '{2}' : {3}",
205                                                         propertyInfo.PropertyType, propertyInfo.Name, propertyName, e.Message),
206                                                         e);
207                         }
208                         parentProject.EvaluatedProperties.AddProperty (bp);
209                 }
210
211                 // FIXME: cleanup + test
212                 void PublishItemGroup (PropertyInfo propertyInfo,
213                                                object o,
214                                                string itemName)
215                 {
216                         if (o == null)
217                                 return;
218
219                         BuildItemGroup newItems;
220                         try {
221                                 newItems = ChangeType.ToBuildItemGroup (o, propertyInfo.PropertyType, itemName);
222                         } catch (Exception e) {
223                                 throw new Exception (String.Format ("Error publishing Output from task property '{0} {1}' to item named '{2}' : {3}",
224                                                         propertyInfo.PropertyType, propertyInfo.Name, itemName, e.Message),
225                                                         e);
226                         }
227
228                         newItems.ParentProject = parentProject;
229                         
230                         if (parentProject.EvaluatedItemsByName.ContainsKey (itemName)) {
231                                 BuildItemGroup big = parentProject.EvaluatedItemsByName [itemName];
232                                 foreach (BuildItem item in newItems)
233                                         big.AddItem (item);
234                         } else {
235                                 parentProject.EvaluatedItemsByName.Add (itemName, newItems);
236                         }
237                         foreach (BuildItem bi in newItems)
238                                 parentProject.EvaluatedItems.AddItem (bi);
239                 }
240
241                 // returns true, if the @result should be included in the values list
242                 bool TryGetObjectFromString (string raw, Type type, out object result)
243                 {
244                         Expression e;
245                         result = null;
246                         
247                         e = new Expression ();
248                         e.Parse (raw, ParseOptions.AllowItemsMetadataAndSplit);
249
250                         // See rules in comment for 'Prepare'
251                         string str = (string) e.ConvertTo (parentProject, typeof (string));
252                         if (!type.IsArray && str == String.Empty)
253                                 return false;
254
255                         if (str.Trim ().Length == 0 && type.IsArray &&
256                                 (type.GetElementType () == typeof (string) || type.GetElementType () == typeof (ITaskItem)))
257                                 return true;
258
259                         result = e.ConvertTo (parentProject, type, ExpressionOptions.ExpandItemRefs);
260                         
261                         return true;
262                 }
263         }
264 }