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);
133 if (project.ParentEngine.BuiltTargetsOutputByName.ContainsKey (built_targets_key)) {
138 // Push a null/empty batch, effectively clearing it
139 project.PushBatch (null, null);
140 if (!ConditionParser.ParseAndEvaluate (Condition, Project)) {
141 LogMessage (MessageImportance.Low,
142 "Target {0} skipped due to false condition: {1}",
149 buildState = BuildState.Started;
150 result = BuildDependencies (GetDependencies (), out executeOnErrors);
152 if (!result && executeOnErrors)
156 // deps built fine, do main build
157 result = DoBuild (out executeOnErrors);
159 buildState = BuildState.Finished;
160 } catch (Exception e) {
161 LogError ("Error building target {0}: {1}", Name, e.ToString ());
167 project.ParentEngine.BuiltTargetsOutputByName [built_targets_key] = (ITaskItem[]) Outputs.Clone ();
168 project.BuiltTargetKeys.Add (built_targets_key);
173 List <Target> GetDependencies ()
175 List <Target> list = new List <Target> ();
177 string [] targetNames;
180 if (DependsOnTargets != String.Empty) {
181 deps = new Expression ();
182 deps.Parse (DependsOnTargets, ParseOptions.AllowItemsNoMetadataAndSplit);
183 targetNames = (string []) deps.ConvertTo (Project, typeof (string []));
184 foreach (string dep_name in targetNames) {
185 t = project.Targets [dep_name.Trim ()];
187 throw new InvalidProjectFileException (String.Format (
188 "Target '{0}', a dependency of target '{1}', not found.",
189 dep_name.Trim (), Name));
196 bool BuildDependencies (List <Target> deps, out bool executeOnErrors)
198 executeOnErrors = false;
199 foreach (Target t in deps) {
200 if (t.BuildState == BuildState.NotStarted)
201 if (!t.Build (null, out executeOnErrors))
203 if (t.BuildState == BuildState.Started)
204 throw new InvalidProjectFileException ("Cycle in target dependencies detected");
210 bool DoBuild (out bool executeOnErrors)
212 executeOnErrors = false;
215 if (BuildTasks.Count == 0)
220 result = batchingImpl.Build (this, out executeOnErrors);
221 } catch (Exception e) {
222 LogError ("Error building target {0}: {1}", Name, e.Message);
223 LogMessage (MessageImportance.Low, "Error building target {0}: {1}", Name, e.ToString ());
227 if (executeOnErrors == true)
233 void ExecuteOnErrors ()
235 foreach (XmlElement onError in onErrorElements) {
236 if (onError.GetAttribute ("ExecuteTargets") == String.Empty)
237 throw new InvalidProjectFileException ("ExecuteTargets attribute is required in OnError element.");
239 string on_error_condition = onError.GetAttribute ("Condition");
240 if (!ConditionParser.ParseAndEvaluate (on_error_condition, Project)) {
241 LogMessage (MessageImportance.Low,
242 "OnError for target {0} skipped due to false condition: {1}",
243 Name, on_error_condition);
247 string[] targetsToExecute = onError.GetAttribute ("ExecuteTargets").Split (';');
248 foreach (string t in targetsToExecute)
249 this.project.Targets [t].Build ();
253 void LogTargetSkipped ()
255 BuildMessageEventArgs bmea;
256 bmea = new BuildMessageEventArgs (String.Format (
257 "Target {0} skipped, as it has already been built.", Name),
258 null, null, MessageImportance.Low);
260 project.ParentEngine.EventSource.FireMessageRaised (this, bmea);
263 void LogError (string message, params object [] messageArgs)
266 throw new ArgumentException ("message");
268 BuildErrorEventArgs beea = new BuildErrorEventArgs (
269 null, null, null, 0, 0, 0, 0, String.Format (message, messageArgs),
271 engine.EventSource.FireErrorRaised (this, beea);
274 void LogMessage (MessageImportance importance, string message, params object [] messageArgs)
277 throw new ArgumentNullException ("message");
279 BuildMessageEventArgs bmea = new BuildMessageEventArgs (
280 String.Format (message, messageArgs), null,
282 engine.EventSource.FireMessageRaised (this, bmea);
285 public string Condition {
286 get { return targetElement.GetAttribute ("Condition"); }
287 set { targetElement.SetAttribute ("Condition", value); }
290 public string DependsOnTargets {
291 get { return targetElement.GetAttribute ("DependsOnTargets"); }
292 set { targetElement.SetAttribute ("DependsOnTargets", value); }
295 public bool IsImported {
296 get { return importedProject != null; }
303 internal Project Project {
304 get { return project; }
307 internal string TargetFile {
309 if (importedProject != null)
310 return importedProject.FullFileName;
311 return project != null ? project.FullFileName : String.Empty;
315 internal List<BuildTask> BuildTasks {
316 get { return buildTasks; }
319 internal Engine Engine {
320 get { return engine; }
323 internal BuildState BuildState {
324 get { return buildState; }
327 internal ITaskItem [] Outputs {
329 string outputs = targetElement.GetAttribute ("Outputs");
330 if (outputs == String.Empty)
331 return new ITaskItem [0];
333 Expression e = new Expression ();
334 e.Parse (outputs, ParseOptions.AllowItemsNoMetadataAndSplit);
336 return (ITaskItem []) e.ConvertTo (project, typeof (ITaskItem []));
341 internal enum BuildState {