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.
33 using System.Collections.Generic;
34 using System.Collections.Specialized;
35 using System.Reflection;
37 using Microsoft.Build.Framework;
38 using Microsoft.Build.Utilities;
40 namespace Microsoft.Build.BuildEngine {
41 internal class TaskEngine {
44 XmlElement taskElement;
46 Project parentProject;
48 static Type requiredAttribute;
49 static Type outputAttribute;
53 requiredAttribute = typeof (Microsoft.Build.Framework.RequiredAttribute);
54 outputAttribute = typeof (Microsoft.Build.Framework.OutputAttribute);
57 public TaskEngine (Project project)
59 parentProject = project;
62 // Rules (inferred) for property values incase of empty data
64 // Prop Type Argument Final Value Required
65 // string empty string null Yes/no
66 // string only whitespace @arg Yes/no
69 // ITaskItem[] empty/whitespace empty array Yes
72 // ITaskItem[] empty/whitespace null No
74 public void Prepare (ITask task, XmlElement taskElement,
75 IDictionary <string, string> parameters, Type taskType)
77 Dictionary <string, object> values;
78 PropertyInfo currentProperty;
79 PropertyInfo[] properties;
83 this.taskElement = taskElement;
84 this.taskType = taskType;
85 values = new Dictionary <string, object> (StringComparer.OrdinalIgnoreCase);
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",
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);
104 properties = taskType.GetProperties ();
105 foreach (PropertyInfo pi in properties) {
106 bool is_required = pi.IsDefined (requiredAttribute, false);
108 if (is_required && values.ContainsKey (pi.Name) == false)
109 throw new InvalidProjectFileException (String.Format ("Required property '{0}' not set.",
112 if (!values.ContainsKey (pi.Name))
115 Type prop_type = pi.PropertyType;
116 if (prop_type.IsArray)
117 prop_type = prop_type.GetElementType ();
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.",
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];
133 InitializeParameter (pi, val);
137 public bool Execute ()
139 return task.Execute ();
142 public void PublishOutput ()
144 XmlElement xmlElement;
145 PropertyInfo propertyInfo;
147 string taskParameter;
151 foreach (XmlNode xmlNode in taskElement.ChildNodes) {
152 if (!(xmlNode is XmlElement))
155 xmlElement = (XmlElement) xmlNode;
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.");
164 if (!ConditionParser.ParseAndEvaluate (xmlElement.GetAttribute ("Condition"), parentProject))
167 taskParameter = xmlElement.GetAttribute ("TaskParameter");
168 itemName = xmlElement.GetAttribute ("ItemName");
169 propertyName = xmlElement.GetAttribute ("PropertyName");
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.");
179 o = propertyInfo.GetValue (task, null);
180 if (itemName != String.Empty) {
181 PublishItemGroup (propertyInfo, o, itemName);
183 PublishProperty (propertyInfo, o, propertyName);
188 void InitializeParameter (PropertyInfo propertyInfo, object value)
190 propertyInfo.SetValue (task, value, null);
193 void PublishProperty (PropertyInfo propertyInfo,
198 parentProject.EvaluatedProperties.RemoveProperty (propertyName);
204 bp = ChangeType.ToBuildProperty (o, propertyInfo.PropertyType, propertyName);
205 } catch (Exception e) {
206 throw new Exception (String.Format ("Error publishing Output from task property '{0} {1}' to property named '{2}' : {3}",
207 propertyInfo.PropertyType, propertyInfo.Name, propertyName, e.Message),
210 parentProject.EvaluatedProperties.AddProperty (bp);
213 // FIXME: cleanup + test
214 void PublishItemGroup (PropertyInfo propertyInfo,
221 BuildItemGroup newItems;
223 newItems = ChangeType.ToBuildItemGroup (o, propertyInfo.PropertyType, itemName);
224 } catch (Exception e) {
225 throw new Exception (String.Format ("Error publishing Output from task property '{0} {1}' to item named '{2}' : {3}",
226 propertyInfo.PropertyType, propertyInfo.Name, itemName, e.Message),
230 newItems.ParentProject = parentProject;
232 if (parentProject.EvaluatedItemsByName.ContainsKey (itemName)) {
233 BuildItemGroup big = parentProject.EvaluatedItemsByName [itemName];
234 foreach (BuildItem item in newItems)
237 parentProject.EvaluatedItemsByName.Add (itemName, newItems);
239 foreach (BuildItem bi in newItems)
240 parentProject.EvaluatedItems.AddItem (bi);
243 // returns true, if the @result should be included in the values list
244 bool TryGetObjectFromString (string raw, Type type, out object result)
249 e = new Expression ();
250 e.Parse (raw, ParseOptions.AllowItemsMetadataAndSplit);
252 // See rules in comment for 'Prepare'
253 string str = (string) e.ConvertTo (parentProject, typeof (string));
254 if (!type.IsArray && str == String.Empty)
257 if (str.Trim ().Length == 0 && type.IsArray &&
258 (type.GetElementType () == typeof (string) || type.GetElementType () == typeof (ITaskItem)))
261 result = e.ConvertTo (parentProject, type, ExpressionOptions.ExpandItemRefs);