2 // Target.cs: Represents a target.
5 // Marek Sieradzki (marek.sieradzki@gmail.com)
7 // (C) 2005 Marek Sieradzki
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:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
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.
31 using System.Collections;
32 using System.Collections.Generic;
34 using Microsoft.Build.Framework;
35 using Microsoft.Build.Utilities;
37 namespace Microsoft.Build.BuildEngine {
38 public class Target : IEnumerable {
40 TargetBatchingImpl batchingImpl;
41 BuildState buildState;
43 ImportedProject importedProject;
46 XmlElement targetElement;
47 List <XmlElement> onErrorElements;
48 List <BuildTask> buildTasks;
50 internal Target (XmlElement targetElement, Project project, ImportedProject importedProject)
53 throw new ArgumentNullException ("project");
54 if (targetElement == null)
55 throw new ArgumentNullException ("targetElement");
57 this.targetElement = targetElement;
58 this.name = targetElement.GetAttribute ("Name");
60 this.project = project;
61 this.engine = project.ParentEngine;
62 this.importedProject = importedProject;
64 this.onErrorElements = new List <XmlElement> ();
65 this.buildState = BuildState.NotStarted;
66 this.buildTasks = new List <BuildTask> ();
67 this.batchingImpl = new TargetBatchingImpl (project, this.targetElement);
69 bool onErrorFound = false;
70 foreach (XmlNode xn in targetElement.ChildNodes) {
71 if (xn is XmlElement) {
72 XmlElement xe = (XmlElement) xn;
73 if (xe.Name == "OnError") {
74 onErrorElements.Add (xe);
76 } else if (onErrorFound)
77 throw new InvalidProjectFileException (
78 "The element <OnError> must be last under element <Target>. Found element <Error> instead.");
80 buildTasks.Add (new BuildTask (xe, this));
86 public BuildTask AddNewTask (string taskName)
89 throw new ArgumentNullException ("taskName");
91 XmlElement task = project.XmlDocument.CreateElement (taskName, Project.XmlNamespace);
92 targetElement.AppendChild (task);
93 BuildTask bt = new BuildTask (task, this);
99 public IEnumerator GetEnumerator ()
101 foreach (BuildTask bt in buildTasks)
105 // FIXME: shouldn't we remove it from XML?
106 public void RemoveTask (BuildTask buildTask)
108 if (buildTask == null)
109 throw new ArgumentNullException ("buildTask");
110 buildTasks.Remove (buildTask);
118 internal bool Build (string built_targets_key)
120 bool executeOnErrors;
121 return Build (built_targets_key, out executeOnErrors);
124 bool Build (string built_targets_key, out bool executeOnErrors)
127 executeOnErrors = false;
129 // built targets are keyed by the particular set of global
130 // properties. So, a different set could allow a target
132 built_targets_key = project.GetKeyForTarget (Name);
134 if (project.ParentEngine.BuiltTargetsOutputByName.ContainsKey (built_targets_key)) {
139 if (!ConditionParser.ParseAndEvaluate (Condition, Project)) {
140 LogMessage (MessageImportance.Low,
141 "Target {0} skipped due to false condition: {1}",
147 buildState = BuildState.Started;
148 result = BuildDependencies (GetDependencies (), out executeOnErrors);
150 if (!result && executeOnErrors)
154 // deps built fine, do main build
155 result = DoBuild (out executeOnErrors);
157 buildState = BuildState.Finished;
158 } catch (Exception e) {
159 LogError ("Error building target {0}: {1}", Name, e.ToString ());
163 project.ParentEngine.BuiltTargetsOutputByName [built_targets_key] = (ITaskItem[]) Outputs.Clone ();
164 project.BuiltTargetKeys.Add (built_targets_key);
169 List <Target> GetDependencies ()
171 List <Target> list = new List <Target> ();
173 string [] targetNames;
176 if (DependsOnTargets != String.Empty) {
177 deps = new Expression ();
178 deps.Parse (DependsOnTargets, ParseOptions.AllowItemsNoMetadataAndSplit);
179 targetNames = (string []) deps.ConvertTo (Project, typeof (string []));
180 foreach (string dep_name in targetNames) {
181 t = project.Targets [dep_name.Trim ()];
183 throw new InvalidProjectFileException (String.Format (
184 "Target '{0}', a dependency of target '{1}', not found.",
185 dep_name.Trim (), Name));
192 bool BuildDependencies (List <Target> deps, out bool executeOnErrors)
194 executeOnErrors = false;
195 foreach (Target t in deps) {
196 if (t.BuildState == BuildState.NotStarted)
197 if (!t.Build (null, out executeOnErrors))
199 if (t.BuildState == BuildState.Started)
200 throw new InvalidProjectFileException ("Cycle in target dependencies detected");
206 bool DoBuild (out bool executeOnErrors)
208 executeOnErrors = false;
211 if (BuildTasks.Count == 0)
216 result = batchingImpl.Build (this, out executeOnErrors);
217 } catch (Exception e) {
218 LogError ("Error building target {0}: {1}", Name, e.Message);
219 LogMessage (MessageImportance.Low, "Error building target {0}: {1}", Name, e.ToString ());
223 if (executeOnErrors == true)
229 void ExecuteOnErrors ()
231 foreach (XmlElement onError in onErrorElements) {
232 if (onError.GetAttribute ("ExecuteTargets") == String.Empty)
233 throw new InvalidProjectFileException ("ExecuteTargets attribute is required in OnError element.");
235 string on_error_condition = onError.GetAttribute ("Condition");
236 if (!ConditionParser.ParseAndEvaluate (on_error_condition, Project)) {
237 LogMessage (MessageImportance.Low,
238 "OnError for target {0} skipped due to false condition: {1}",
239 Name, on_error_condition);
243 string[] targetsToExecute = onError.GetAttribute ("ExecuteTargets").Split (';');
244 foreach (string t in targetsToExecute)
245 this.project.Targets [t].Build ();
249 void LogTargetSkipped ()
251 BuildMessageEventArgs bmea;
252 bmea = new BuildMessageEventArgs (String.Format (
253 "Target {0} skipped, as it has already been built.", Name),
254 null, null, MessageImportance.Low);
256 project.ParentEngine.EventSource.FireMessageRaised (this, bmea);
259 void LogError (string message, params object [] messageArgs)
262 throw new ArgumentException ("message");
264 BuildErrorEventArgs beea = new BuildErrorEventArgs (
265 null, null, null, 0, 0, 0, 0, String.Format (message, messageArgs),
267 engine.EventSource.FireErrorRaised (this, beea);
270 void LogMessage (MessageImportance importance, string message, params object [] messageArgs)
273 throw new ArgumentNullException ("message");
275 BuildMessageEventArgs bmea = new BuildMessageEventArgs (
276 String.Format (message, messageArgs), null,
278 engine.EventSource.FireMessageRaised (this, bmea);
281 public string Condition {
282 get { return targetElement.GetAttribute ("Condition"); }
283 set { targetElement.SetAttribute ("Condition", value); }
286 public string DependsOnTargets {
287 get { return targetElement.GetAttribute ("DependsOnTargets"); }
288 set { targetElement.SetAttribute ("DependsOnTargets", value); }
291 public bool IsImported {
292 get { return importedProject != null; }
299 internal Project Project {
300 get { return project; }
303 internal List<BuildTask> BuildTasks {
304 get { return buildTasks; }
307 internal Engine Engine {
308 get { return engine; }
311 internal BuildState BuildState {
312 get { return buildState; }
315 internal ITaskItem [] Outputs {
317 string outputs = targetElement.GetAttribute ("Outputs");
318 if (outputs == String.Empty)
319 return new ITaskItem [0];
321 Expression e = new Expression ();
322 e.Parse (outputs, ParseOptions.AllowItemsNoMetadataAndSplit);
324 return (ITaskItem []) e.ConvertTo (project, typeof (ITaskItem []));
329 internal enum BuildState {