Merge branch 'master' of github.com:mono/mono
[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 #if NET_2_0
31
32 using System;
33 using System.Collections.Generic;
34 using System.Collections.Specialized;
35 using System.Reflection;
36 using System.Xml;
37 using Microsoft.Build.Framework;
38 using Microsoft.Build.Utilities;
39
40 namespace Microsoft.Build.BuildEngine {
41         internal class TaskEngine {
42                 
43                 ITask           task;
44                 XmlElement      taskElement;
45                 Type            taskType;
46                 Project         parentProject;
47                 
48                 static Type     requiredAttribute;
49                 static Type     outputAttribute;
50                 
51                 static TaskEngine ()
52                 {
53                         requiredAttribute = typeof (Microsoft.Build.Framework.RequiredAttribute);
54                         outputAttribute = typeof (Microsoft.Build.Framework.OutputAttribute);
55                 }
56
57                 public TaskEngine (Project project)
58                 {
59                         parentProject = project;
60                 }
61
62                 // Rules (inferred) for property values incase of empty data
63                 //
64                 // Prop Type         Argument         Final Value     Required
65                 // string            empty string       null           Yes/no
66                 // string            only whitespace    @arg           Yes/no
67                 //
68                 // string/
69                 //   ITaskItem[]     empty/whitespace   empty array     Yes
70                 //
71                 // string/
72                 //   ITaskItem[]     empty/whitespace   null            No
73                 
74                 public void Prepare (ITask task, XmlElement taskElement,
75                                      IDictionary <string, string> parameters, Type taskType)
76                 {
77                         Dictionary <string, object>     values;
78                         PropertyInfo    currentProperty;
79                         PropertyInfo[]  properties;
80                         object          value;
81                         
82                         this.task = task;
83                         this.taskElement = taskElement;
84                         this.taskType = taskType;
85                         values = new Dictionary <string, object> (StringComparer.InvariantCultureIgnoreCase);
86                         
87                         foreach (KeyValuePair <string, string> de in parameters) {
88                                 currentProperty = taskType.GetProperty (de.Key, BindingFlags.Public | BindingFlags.Instance
89                                                 | BindingFlags.IgnoreCase);
90                                 if (currentProperty == null)
91                                         throw new InvalidProjectFileException (String.Format ("Task does not have property \"{0}\" defined",
92                                                 de.Key));
93                                 
94                                 try {
95                                         if (TryGetObjectFromString (de.Value, currentProperty.PropertyType, out value))
96                                                 values.Add (de.Key, value);
97                                 } catch (Exception e) {
98                                         throw new InvalidProjectFileException (String.Format (
99                                                         "Error converting Property named '{0}' with value '{1}' to type {2}: {3}",
100                                                         de.Key, de.Value, currentProperty.PropertyType, e.Message), e);
101                                 }
102                         }
103                         
104                         properties = taskType.GetProperties ();
105                         foreach (PropertyInfo pi in properties) {
106                                 bool is_required = pi.IsDefined (requiredAttribute, false);
107
108                                 if (is_required && values.ContainsKey (pi.Name) == false)
109                                         throw new InvalidProjectFileException (String.Format ("Required property '{0}' not set.",
110                                                 pi.Name));
111
112                                 if (!values.ContainsKey (pi.Name))
113                                         continue;
114
115                                 Type prop_type = pi.PropertyType;
116                                 if (prop_type.IsArray)
117                                         prop_type = prop_type.GetElementType ();
118
119                                 // Valid data types: primitive types, DateTime, string and ITaskItem, and their arrays
120                                 if (!prop_type.IsPrimitive && prop_type != typeof (string) && prop_type != typeof (ITaskItem))
121                                         throw new InvalidProjectFileException (String.Format (
122                                                         "{0} is not a supported type for properties for msbuild tasks.",
123                                                         pi.PropertyType));
124
125                                 object val = values [pi.Name];
126                                 if (val == null && pi.PropertyType.IsArray && is_required) {
127                                         if (pi.PropertyType == typeof (ITaskItem[]))
128                                                 val = new ITaskItem [0];
129                                         else if (pi.PropertyType == typeof (string[]))
130                                                 val = new string [0];
131                                 }
132
133                                 InitializeParameter (pi, val);
134                         }
135                 }
136                 
137                 public bool Execute ()
138                 {
139                         return task.Execute ();
140                 }
141                 
142                 public void PublishOutput ()
143                 {
144                         XmlElement      xmlElement;
145                         PropertyInfo    propertyInfo;
146                         string          propertyName;
147                         string          taskParameter;
148                         string          itemName;
149                         object          o;
150                 
151                         foreach (XmlNode xmlNode in taskElement.ChildNodes) {
152                                 if (!(xmlNode is XmlElement))
153                                         continue;
154                         
155                                 xmlElement = (XmlElement) xmlNode;
156                                 
157                                 if (xmlElement.Name != "Output")
158                                         throw new InvalidProjectFileException ("Only Output elements can be Task's child nodes.");
159                                 if (xmlElement.GetAttribute ("ItemName") != String.Empty && xmlElement.GetAttribute ("PropertyName") != String.Empty)
160                                         throw new InvalidProjectFileException ("Only one of ItemName and PropertyName attributes can be specified.");
161                                 if (xmlElement.GetAttribute ("TaskParameter") == String.Empty)
162                                         throw new InvalidProjectFileException ("TaskParameter attribute must be specified.");
163
164                                 if (!ConditionParser.ParseAndEvaluate (xmlElement.GetAttribute ("Condition"), parentProject))
165                                         continue;
166                                         
167                                 taskParameter = xmlElement.GetAttribute ("TaskParameter");
168                                 itemName = xmlElement.GetAttribute ("ItemName");
169                                 propertyName = xmlElement.GetAttribute ("PropertyName");
170                                 
171                                 propertyInfo = taskType.GetProperty (taskParameter, BindingFlags.Public | BindingFlags.Instance |
172                                                         BindingFlags.IgnoreCase);
173                                 if (propertyInfo == null)
174                                         throw new InvalidProjectFileException (String.Format (
175                                                 "The parameter '{0}' was not found for the '{1}' task.", taskParameter, taskElement.Name));
176                                 if (!propertyInfo.IsDefined (outputAttribute, false))
177                                         throw new InvalidProjectFileException ("This is not output property.");
178                                 
179                                 o = propertyInfo.GetValue (task, null);
180                                 // FIXME: maybe we should throw an exception here?
181                                 if (o == null)
182                                         continue;
183                                 
184                                 if (itemName != String.Empty) {
185                                         PublishItemGroup (propertyInfo, o, itemName);
186                                 } else {
187                                         PublishProperty (propertyInfo, o, propertyName);
188                                 }
189                         }
190                 }
191                 
192                 void InitializeParameter (PropertyInfo propertyInfo, object value)
193                 {
194                         propertyInfo.SetValue (task, value, null);
195                 }
196
197                 void PublishProperty (PropertyInfo propertyInfo,
198                                               object o,
199                                               string propertyName)
200                 {
201                         BuildProperty bp;
202                         try {
203                                 bp = ChangeType.ToBuildProperty (o, propertyInfo.PropertyType, propertyName);
204                         } catch (Exception e) {
205                                 throw new Exception (String.Format ("Error publishing Output from task property '{0} {1}' to property named '{2}' : {3}",
206                                                         propertyInfo.PropertyType, propertyInfo.Name, propertyName, e.Message),
207                                                         e);
208                         }
209                         parentProject.EvaluatedProperties.AddProperty (bp);
210                 }
211
212                 // FIXME: cleanup + test
213                 void PublishItemGroup (PropertyInfo propertyInfo,
214                                                object o,
215                                                string itemName)
216                 {
217                         BuildItemGroup newItems;
218                         try {
219                                 newItems = ChangeType.ToBuildItemGroup (o, propertyInfo.PropertyType, itemName);
220                         } catch (Exception e) {
221                                 throw new Exception (String.Format ("Error publishing Output from task property '{0} {1}' to item named '{2}' : {3}",
222                                                         propertyInfo.PropertyType, propertyInfo.Name, itemName, e.Message),
223                                                         e);
224                         }
225
226                         newItems.ParentProject = parentProject;
227                         
228                         if (parentProject.EvaluatedItemsByName.ContainsKey (itemName)) {
229                                 BuildItemGroup big = parentProject.EvaluatedItemsByName [itemName];
230                                 foreach (BuildItem item in newItems)
231                                         big.AddItem (item);
232                         } else {
233                                 parentProject.EvaluatedItemsByName.Add (itemName, newItems);
234                         }
235                         foreach (BuildItem bi in newItems)
236                                 parentProject.EvaluatedItems.AddItem (bi);
237                 }
238
239                 // returns true, if the @result should be included in the values list
240                 bool TryGetObjectFromString (string raw, Type type, out object result)
241                 {
242                         Expression e;
243                         result = null;
244                         
245                         e = new Expression ();
246                         e.Parse (raw, ParseOptions.AllowItemsMetadataAndSplit);
247
248                         // See rules in comment for 'Prepare'
249                         string str = (string) e.ConvertTo (parentProject, typeof (string));
250                         if (!type.IsArray && str == String.Empty)
251                                 return false;
252
253                         if (str.Trim ().Length == 0 && type.IsArray &&
254                                 (type.GetElementType () == typeof (string) || type.GetElementType () == typeof (ITaskItem)))
255                                 return true;
256
257                         result = e.ConvertTo (parentProject, type, ExpressionOptions.ExpandItemRefs);
258                         
259                         return true;
260                 }
261         }
262 }
263
264 #endif