2 // TaskEngine.cs: Class that executes each task.
5 // Marek Sieradzki (marek.sieradzki@gmail.com)
6 // Ankit Jain (jankit@novell.com)
8 // (C) 2005 Marek Sieradzki
9 // Copyright 2009 Novell, Inc (http://www.novell.com)
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:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
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.
31 using System.Collections.Generic;
32 using System.Collections.Specialized;
33 using System.Reflection;
35 using Microsoft.Build.Framework;
36 using Microsoft.Build.Utilities;
38 namespace Microsoft.Build.BuildEngine {
39 internal class TaskEngine {
42 XmlElement taskElement;
44 Project parentProject;
46 static Type requiredAttribute;
47 static Type outputAttribute;
51 requiredAttribute = typeof (Microsoft.Build.Framework.RequiredAttribute);
52 outputAttribute = typeof (Microsoft.Build.Framework.OutputAttribute);
55 public TaskEngine (Project project)
57 parentProject = project;
60 // Rules (inferred) for property values incase of empty data
62 // Prop Type Argument Final Value Required
63 // string empty string null Yes/no
64 // string only whitespace @arg Yes/no
67 // ITaskItem[] empty/whitespace empty array Yes
70 // ITaskItem[] empty/whitespace null No
72 public void Prepare (ITask task, XmlElement taskElement,
73 IDictionary <string, string> parameters, Type taskType)
75 Dictionary <string, object> values;
76 PropertyInfo currentProperty;
77 PropertyInfo[] properties;
81 this.taskElement = taskElement;
82 this.taskType = taskType;
83 values = new Dictionary <string, object> (StringComparer.OrdinalIgnoreCase);
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",
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);
102 properties = taskType.GetProperties ();
103 foreach (PropertyInfo pi in properties) {
104 bool is_required = pi.IsDefined (requiredAttribute, false);
106 if (is_required && values.ContainsKey (pi.Name) == false)
107 throw new InvalidProjectFileException (String.Format ("Required property '{0}' not set.",
110 if (!values.ContainsKey (pi.Name))
113 Type prop_type = pi.PropertyType;
114 if (prop_type.IsArray)
115 prop_type = prop_type.GetElementType ();
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.",
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];
131 InitializeParameter (pi, val);
135 public bool Execute ()
137 return task.Execute ();
140 public void PublishOutput ()
142 XmlElement xmlElement;
143 PropertyInfo propertyInfo;
145 string taskParameter;
149 foreach (XmlNode xmlNode in taskElement.ChildNodes) {
150 if (!(xmlNode is XmlElement))
153 xmlElement = (XmlElement) xmlNode;
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.");
162 if (!ConditionParser.ParseAndEvaluate (xmlElement.GetAttribute ("Condition"), parentProject))
165 taskParameter = xmlElement.GetAttribute ("TaskParameter");
166 itemName = xmlElement.GetAttribute ("ItemName");
167 propertyName = xmlElement.GetAttribute ("PropertyName");
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.");
177 o = propertyInfo.GetValue (task, null);
178 if (itemName != String.Empty) {
179 PublishItemGroup (propertyInfo, o, itemName);
181 PublishProperty (propertyInfo, o, propertyName);
186 void InitializeParameter (PropertyInfo propertyInfo, object value)
188 propertyInfo.SetValue (task, value, null);
191 void PublishProperty (PropertyInfo propertyInfo,
196 parentProject.EvaluatedProperties.RemoveProperty (propertyName);
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),
208 parentProject.EvaluatedProperties.AddProperty (bp);
211 // FIXME: cleanup + test
212 void PublishItemGroup (PropertyInfo propertyInfo,
219 BuildItemGroup newItems;
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),
228 newItems.ParentProject = parentProject;
230 if (parentProject.EvaluatedItemsByName.ContainsKey (itemName)) {
231 BuildItemGroup big = parentProject.EvaluatedItemsByName [itemName];
232 foreach (BuildItem item in newItems)
235 parentProject.EvaluatedItemsByName.Add (itemName, newItems);
237 foreach (BuildItem bi in newItems)
238 parentProject.EvaluatedItems.AddItem (bi);
241 // returns true, if the @result should be included in the values list
242 bool TryGetObjectFromString (string raw, Type type, out object result)
247 e = new Expression ();
248 e.Parse (raw, ParseOptions.AllowItemsMetadataAndSplit);
250 // See rules in comment for 'Prepare'
251 string str = (string) e.ConvertTo (parentProject, typeof (string));
252 if (!type.IsArray && str == String.Empty)
255 if (str.Trim ().Length == 0 && type.IsArray &&
256 (type.GetElementType () == typeof (string) || type.GetElementType () == typeof (ITaskItem)))
259 result = e.ConvertTo (parentProject, type, ExpressionOptions.ExpandItemRefs);