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;
35 using Microsoft.Build.Framework;
36 using Microsoft.Build.Utilities;
38 namespace Microsoft.Build.BuildEngine {
39 public class Target : IEnumerable {
41 TargetBatchingImpl batchingImpl;
42 BuildState buildState;
44 ImportedProject importedProject;
47 XmlElement targetElement;
48 List <XmlElement> onErrorElements;
49 List <BuildTask> buildTasks;
51 internal Target (XmlElement targetElement, Project project, ImportedProject importedProject)
54 throw new ArgumentNullException ("project");
55 if (targetElement == null)
56 throw new ArgumentNullException ("targetElement");
58 this.targetElement = targetElement;
59 this.name = targetElement.GetAttribute ("Name");
61 this.project = project;
62 this.engine = project.ParentEngine;
63 this.importedProject = importedProject;
65 this.onErrorElements = new List <XmlElement> ();
66 this.buildState = BuildState.NotStarted;
67 this.buildTasks = new List <BuildTask> ();
68 this.batchingImpl = new TargetBatchingImpl (project, this.targetElement);
70 bool onErrorFound = false;
71 foreach (XmlNode xn in targetElement.ChildNodes) {
72 if (xn is XmlElement) {
73 XmlElement xe = (XmlElement) xn;
74 if (xe.Name == "OnError") {
75 onErrorElements.Add (xe);
77 } else if (onErrorFound)
78 throw new InvalidProjectFileException (
79 "The element <OnError> must be last under element <Target>. Found element <Error> instead.");
81 buildTasks.Add (new BuildTask (xe, this));
87 public BuildTask AddNewTask (string taskName)
90 throw new ArgumentNullException ("taskName");
92 XmlElement task = project.XmlDocument.CreateElement (taskName, Project.XmlNamespace);
93 targetElement.AppendChild (task);
94 BuildTask bt = new BuildTask (task, this);
100 public IEnumerator GetEnumerator ()
102 foreach (BuildTask bt in buildTasks)
106 // FIXME: shouldn't we remove it from XML?
107 public void RemoveTask (BuildTask buildTask)
109 if (buildTask == null)
110 throw new ArgumentNullException ("buildTask");
111 buildTasks.Remove (buildTask);
119 internal bool Build (string built_targets_key)
121 bool executeOnErrors;
122 return Build (built_targets_key, out executeOnErrors);
125 bool Build (string built_targets_key, out bool executeOnErrors)
127 project.PushThisFileProperty (TargetFile);
129 return BuildActual (built_targets_key, out executeOnErrors);
131 project.PopThisFileProperty ();
135 bool BuildActual (string built_targets_key, out bool executeOnErrors)
138 executeOnErrors = false;
140 // built targets are keyed by the particular set of global
141 // properties. So, a different set could allow a target
143 built_targets_key = project.GetKeyForTarget (Name);
144 if (project.ParentEngine.BuiltTargetsOutputByName.ContainsKey (built_targets_key)) {
149 // Push a null/empty batch, effectively clearing it
150 project.PushBatch (null, null);
151 if (!ConditionParser.ParseAndEvaluate (Condition, Project)) {
152 LogMessage (MessageImportance.Low,
153 "Target {0} skipped due to false condition: {1}",
160 buildState = BuildState.Started;
163 result = BuildDependencies (out executeOnErrors) &&
164 BuildBeforeThisTargets (out executeOnErrors) &&
165 DoBuild (out executeOnErrors) && // deps & Before targets built fine, do main build
166 BuildAfterThisTargets (out executeOnErrors);
168 result = BuildDependencies (out executeOnErrors) && DoBuild (out executeOnErrors);
171 buildState = BuildState.Finished;
172 } catch (Exception e) {
173 LogError ("Error building target {0}: {1}", Name, e.ToString ());
179 ITaskItem[] outputs = (ITaskItem[]) OutputsAsITaskItems.Clone ();
180 foreach (ITaskItem item in outputs) {
181 item.SetMetadata ("MSBuildProjectFile", TargetFile);
182 item.SetMetadata ("MSBuildTargetName", Name);
184 project.ParentEngine.BuiltTargetsOutputByName [built_targets_key] = outputs;
189 bool BuildDependencies (out bool executeOnErrors)
191 executeOnErrors = false;
193 if (String.IsNullOrEmpty (DependsOnTargets))
196 var expr = new Expression ();
197 expr.Parse (DependsOnTargets, ParseOptions.AllowItemsNoMetadataAndSplit);
198 string [] targetNames = (string []) expr.ConvertTo (Project, typeof (string []));
200 bool result = BuildOtherTargets (targetNames,
201 tname => engine.LogError ("Target '{0}', a dependency of target '{1}', not found.",
203 out executeOnErrors);
204 if (!result && executeOnErrors)
211 bool BuildBeforeThisTargets (out bool executeOnErrors)
213 executeOnErrors = false;
214 bool result = BuildOtherTargets (BeforeThisTargets, null, out executeOnErrors);
215 if (!result && executeOnErrors)
221 bool BuildAfterThisTargets (out bool executeOnErrors)
223 executeOnErrors = false;
224 //missing_target handler not required as these are picked from actual target's
225 //"Before/AfterTargets attributes!
226 bool result = BuildOtherTargets (AfterThisTargets, null, out executeOnErrors);
227 if (!result && executeOnErrors)
234 bool BuildOtherTargets (IEnumerable<string> targetNames, Action<string> missing_target, out bool executeOnErrors)
236 executeOnErrors = false;
237 if (targetNames == null)
241 foreach (string target_name in targetNames) {
242 var t = project.Targets [target_name.Trim ()];
244 if (missing_target != null)
245 missing_target (target_name);
249 if (t.BuildState == BuildState.NotStarted)
250 if (!t.Build (null, out executeOnErrors))
253 if (t.BuildState == BuildState.Started)
254 throw new InvalidProjectFileException ("Cycle in target dependencies detected");
260 bool DoBuild (out bool executeOnErrors)
262 executeOnErrors = false;
265 if (BuildTasks.Count == 0)
270 result = batchingImpl.Build (this, out executeOnErrors);
271 } catch (Exception e) {
272 LogError ("Error building target {0}: {1}", Name, e.Message);
273 LogMessage (MessageImportance.Low, "Error building target {0}: {1}", Name, e.ToString ());
277 if (executeOnErrors == true)
283 void ExecuteOnErrors ()
285 foreach (XmlElement onError in onErrorElements) {
286 if (onError.GetAttribute ("ExecuteTargets") == String.Empty)
287 throw new InvalidProjectFileException ("ExecuteTargets attribute is required in OnError element.");
289 string on_error_condition = onError.GetAttribute ("Condition");
290 if (!ConditionParser.ParseAndEvaluate (on_error_condition, Project)) {
291 LogMessage (MessageImportance.Low,
292 "OnError for target {0} skipped due to false condition: {1}",
293 Name, on_error_condition);
297 string[] targetsToExecute = onError.GetAttribute ("ExecuteTargets").Split (';');
298 foreach (string t in targetsToExecute)
299 this.project.Targets [t].Build ();
303 void LogTargetSkipped ()
305 BuildMessageEventArgs bmea;
306 bmea = new BuildMessageEventArgs (String.Format (
307 "Target {0} skipped, as it has already been built.", Name),
308 null, null, MessageImportance.Low);
310 project.ParentEngine.EventSource.FireMessageRaised (this, bmea);
313 void LogError (string message, params object [] messageArgs)
316 throw new ArgumentException ("message");
318 BuildErrorEventArgs beea = new BuildErrorEventArgs (
319 null, null, null, 0, 0, 0, 0, String.Format (message, messageArgs),
321 engine.EventSource.FireErrorRaised (this, beea);
324 void LogMessage (MessageImportance importance, string message, params object [] messageArgs)
327 throw new ArgumentNullException ("message");
329 BuildMessageEventArgs bmea = new BuildMessageEventArgs (
330 String.Format (message, messageArgs), null,
332 engine.EventSource.FireMessageRaised (this, bmea);
335 public string Condition {
336 get { return targetElement.GetAttribute ("Condition"); }
337 set { targetElement.SetAttribute ("Condition", value); }
340 public string DependsOnTargets {
341 get { return targetElement.GetAttribute ("DependsOnTargets"); }
342 set { targetElement.SetAttribute ("DependsOnTargets", value); }
345 public bool IsImported {
346 get { return importedProject != null; }
353 internal Project Project {
354 get { return project; }
357 internal string TargetFile {
359 if (importedProject != null)
360 return importedProject.FullFileName;
361 return project != null ? project.FullFileName : String.Empty;
366 internal string BeforeTargets {
367 get { return targetElement.GetAttribute ("BeforeTargets"); }
370 internal string AfterTargets {
371 get { return targetElement.GetAttribute ("AfterTargets"); }
374 internal List<string> BeforeThisTargets { get; set; }
375 internal List<string> AfterThisTargets { get; set; }
378 internal List<BuildTask> BuildTasks {
379 get { return buildTasks; }
382 internal Engine Engine {
383 get { return engine; }
386 internal BuildState BuildState {
387 get { return buildState; }
390 public string Outputs {
391 get { return targetElement.GetAttribute ("Outputs"); }
392 set { targetElement.SetAttribute ("Outputs", value); }
395 ITaskItem [] OutputsAsITaskItems {
397 string outputs = targetElement.GetAttribute ("Outputs");
398 if (outputs == String.Empty)
399 return new ITaskItem [0];
401 Expression e = new Expression ();
402 e.Parse (outputs, ParseOptions.AllowItemsNoMetadataAndSplit);
404 return (ITaskItem []) e.ConvertTo (project, typeof (ITaskItem []));
409 internal enum BuildState {