Improve error reporting.
[mcs.git] / class / Microsoft.Build.Engine / Microsoft.Build.BuildEngine / Target.cs
blobbfdd2768356bbe6cc94ef7d3ce8410559a185d22
1 //
2 // Target.cs: Represents a target.
3 //
4 // Author:
5 // Marek Sieradzki (marek.sieradzki@gmail.com)
6 //
7 // (C) 2005 Marek Sieradzki
8 //
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.
28 #if NET_2_0
30 using System;
31 using System.Collections;
32 using System.Collections.Generic;
33 using System.Xml;
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;
42 Engine engine;
43 ImportedProject importedProject;
44 string name;
45 Project project;
46 XmlElement targetElement;
47 List <XmlElement> onErrorElements;
48 List <BuildTask> buildTasks;
50 internal Target (XmlElement targetElement, Project project, ImportedProject importedProject)
52 if (project == null)
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);
75 onErrorFound = true;
76 } else if (onErrorFound)
77 throw new InvalidProjectFileException (
78 "The element <OnError> must be last under element <Target>. Found element <Error> instead.");
79 else
80 buildTasks.Add (new BuildTask (xe, this));
85 [MonoTODO]
86 public BuildTask AddNewTask (string taskName)
88 if (taskName == null)
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);
94 buildTasks.Add (bt);
96 return bt;
99 public IEnumerator GetEnumerator ()
101 foreach (BuildTask bt in buildTasks)
102 yield return bt;
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);
113 bool Build ()
115 return Build (null);
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)
126 bool result = false;
127 executeOnErrors = false;
129 // built targets are keyed by the particular set of global
130 // properties. So, a different set could allow a target
131 // to run again
132 built_targets_key = project.GetKeyForTarget (Name);
133 ITaskItem[] outputs;
134 if (project.ParentEngine.BuiltTargetsOutputByName.ContainsKey (built_targets_key)) {
135 LogTargetSkipped ();
136 return true;
139 if (!ConditionParser.ParseAndEvaluate (Condition, Project)) {
140 LogMessage (MessageImportance.Low,
141 "Target {0} skipped due to false condition: {1}",
142 Name, Condition);
143 return true;
146 try {
147 buildState = BuildState.Started;
148 result = BuildDependencies (GetDependencies (), out executeOnErrors);
150 if (!result && executeOnErrors)
151 ExecuteOnErrors ();
153 if (result)
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 ());
160 return false;
163 project.ParentEngine.BuiltTargetsOutputByName [built_targets_key] = (ITaskItem[]) Outputs.Clone ();
164 project.BuiltTargetKeys.Add (built_targets_key);
166 return result;
169 List <Target> GetDependencies ()
171 List <Target> list = new List <Target> ();
172 Target t;
173 string [] targetNames;
174 Expression deps;
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 ()];
182 if (t == null)
183 throw new InvalidProjectFileException (String.Format (
184 "Target '{0}', a dependency of target '{1}', not found.",
185 dep_name.Trim (), Name));
186 list.Add (t);
189 return list;
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))
198 return false;
199 if (t.BuildState == BuildState.Started)
200 throw new InvalidProjectFileException ("Cycle in target dependencies detected");
203 return true;
206 bool DoBuild (out bool executeOnErrors)
208 executeOnErrors = false;
209 bool result = true;
211 if (BuildTasks.Count == 0)
212 // nothing to do
213 return true;
215 try {
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 ());
220 return false;
223 if (executeOnErrors == true)
224 ExecuteOnErrors ();
226 return result;
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);
240 continue;
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)
261 if (message == null)
262 throw new ArgumentException ("message");
264 BuildErrorEventArgs beea = new BuildErrorEventArgs (
265 null, null, null, 0, 0, 0, 0, String.Format (message, messageArgs),
266 null, null);
267 engine.EventSource.FireErrorRaised (this, beea);
270 void LogMessage (MessageImportance importance, string message, params object [] messageArgs)
272 if (message == null)
273 throw new ArgumentNullException ("message");
275 BuildMessageEventArgs bmea = new BuildMessageEventArgs (
276 String.Format (message, messageArgs), null,
277 null, importance);
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; }
295 public string Name {
296 get { return name; }
299 internal Project Project {
300 get { return project; }
303 internal string TargetFile {
304 get {
305 if (importedProject != null)
306 return importedProject.FullFileName;
307 return project != null ? project.FullFileName : String.Empty;
311 internal List<BuildTask> BuildTasks {
312 get { return buildTasks; }
315 internal Engine Engine {
316 get { return engine; }
319 internal BuildState BuildState {
320 get { return buildState; }
323 internal ITaskItem [] Outputs {
324 get {
325 string outputs = targetElement.GetAttribute ("Outputs");
326 if (outputs == String.Empty)
327 return new ITaskItem [0];
329 Expression e = new Expression ();
330 e.Parse (outputs, ParseOptions.AllowItemsNoMetadataAndSplit);
332 return (ITaskItem []) e.ConvertTo (project, typeof (ITaskItem []));
337 internal enum BuildState {
338 NotStarted,
339 Started,
340 Finished,
341 Skipped
345 #endif