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;
36 namespace Microsoft.Build.BuildEngine {
37 public class Target : IEnumerable {
39 BatchingImpl batchingImpl;
40 BuildState buildState;
42 ImportedProject importedProject;
45 XmlElement targetElement;
46 List <XmlElement> onErrorElements;
47 List <BuildTask> buildTasks;
49 internal Target (XmlElement targetElement, Project project, ImportedProject importedProject)
52 throw new ArgumentNullException ("project");
53 if (targetElement == null)
54 throw new ArgumentNullException ("targetElement");
56 this.targetElement = targetElement;
57 this.name = targetElement.GetAttribute ("Name");
59 this.project = project;
60 this.engine = project.ParentEngine;
61 this.importedProject = importedProject;
63 this.onErrorElements = new List <XmlElement> ();
64 this.buildState = BuildState.NotStarted;
65 this.buildTasks = new List <BuildTask> ();
66 this.batchingImpl = new BatchingImpl (project, this.targetElement);
68 bool onErrorFound = false;
69 foreach (XmlNode xn in targetElement.ChildNodes) {
70 if (xn is XmlElement) {
71 XmlElement xe = (XmlElement) xn;
72 if (xe.Name == "OnError") {
73 onErrorElements.Add (xe);
75 } else if (onErrorFound)
76 throw new InvalidProjectFileException (
77 "The element <OnError> must be last under element <Target>. Found element <Error> instead.");
79 buildTasks.Add (new BuildTask (xe, this));
85 public BuildTask AddNewTask (string taskName)
88 throw new ArgumentNullException ("taskName");
90 XmlElement task = project.XmlDocument.CreateElement (taskName, Project.XmlNamespace);
91 targetElement.AppendChild (task);
92 BuildTask bt = new BuildTask (task, this);
98 public IEnumerator GetEnumerator ()
100 foreach (BuildTask bt in buildTasks)
104 // FIXME: shouldn't we remove it from XML?
105 public void RemoveTask (BuildTask buildTask)
107 if (buildTask == null)
108 throw new ArgumentNullException ("buildTask");
109 buildTasks.Remove (buildTask);
112 // FIXME: log errors instead of throwing exceptions
113 internal bool Build ()
118 // log that target is being skipped
119 if (!ConditionParser.ParseAndEvaluate (Condition, Project))
123 buildState = BuildState.Started;
124 deps = BuildDependencies (GetDependencies ());
126 result = deps ? DoBuild () : false;
128 buildState = BuildState.Finished;
130 } catch (Exception e) {
137 List <Target> GetDependencies ()
139 List <Target> list = new List <Target> ();
141 string [] targetNames;
144 if (DependsOnTargets != String.Empty) {
145 deps = new Expression ();
146 deps.Parse (DependsOnTargets, true);
147 targetNames = (string []) deps.ConvertTo (Project, typeof (string []));
148 foreach (string name in targetNames) {
149 t = project.Targets [name.Trim ()];
151 throw new InvalidProjectFileException (String.Format ("Target '{0}' not found.", name.Trim ()));
158 bool BuildDependencies (List <Target> deps)
160 foreach (Target t in deps) {
161 if (t.BuildState == BuildState.NotStarted)
164 if (t.BuildState == BuildState.Started)
165 throw new InvalidProjectFileException ("Cycle in target dependencies detected");
173 bool executeOnErrors = false;
178 if (batchingImpl.BuildNeeded ()) {
179 foreach (BuildTask bt in buildTasks) {
180 result = batchingImpl.BatchBuildTask (bt);
182 if (!result && !bt.ContinueOnError) {
183 executeOnErrors = true;
191 LogTargetFinished (result);
193 if (executeOnErrors == true)
199 void ExecuteOnErrors ()
201 foreach (XmlElement onError in onErrorElements) {
202 // FIXME: add condition
203 if (onError.GetAttribute ("ExecuteTargets") == String.Empty)
204 throw new InvalidProjectFileException ("ExecuteTargets attribute is required in OnError element.");
205 string[] targetsToExecute = onError.GetAttribute ("ExecuteTargets").Split (';');
206 foreach (string t in targetsToExecute)
207 this.project.Targets [t].Build ();
211 void LogTargetSkipped ()
213 BuildMessageEventArgs bmea;
214 bmea = new BuildMessageEventArgs (String.Format ("Skipping target \"{0}\" because its outputs are up-to-date.",
215 name), null, "MSBuild", MessageImportance.Normal);
216 engine.EventSource.FireMessageRaised (this, bmea);
219 void LogTargetStarted ()
221 TargetStartedEventArgs tsea;
222 string projectFile = project.FullFileName;
223 tsea = new TargetStartedEventArgs ("Target " + name + " started.", null, name, projectFile, null);
224 engine.EventSource.FireTargetStarted (this, tsea);
227 void LogTargetFinished (bool succeeded)
229 TargetFinishedEventArgs tfea;
230 string projectFile = project.FullFileName;
231 tfea = new TargetFinishedEventArgs ("Target " + name + " finished.", null, name, projectFile, null, succeeded);
232 engine.EventSource.FireTargetFinished (this, tfea);
235 public string Condition {
236 get { return targetElement.GetAttribute ("Condition"); }
237 set { targetElement.SetAttribute ("Condition", value); }
240 public string DependsOnTargets {
241 get { return targetElement.GetAttribute ("DependsOnTargets"); }
242 set { targetElement.SetAttribute ("DependsOnTargets", value); }
245 public bool IsImported {
246 get { return importedProject != null; }
253 internal Project Project {
254 get { return project; }
257 internal BuildState BuildState {
258 get { return buildState; }
262 internal enum BuildState {