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.
30 using System.Collections;
31 using System.Collections.Generic;
34 using Microsoft.Build.Framework;
35 using Microsoft.Build.Utilities;
36 using Mono.XBuild.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 <IBuildTask> 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 <IBuildTask> ();
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 else if (xe.Name == "ItemGroup") {
82 var group = new BuildTaskItemGroup (xe, this);
83 buildTasks.AddRange (group.Items);
85 } else if (xe.Name == "PropertyGroup") {
86 buildTasks.Add (new BuildTaskPropertyGroup (xe, this));
91 buildTasks.Add (new BuildTask (xe, this));
97 public BuildTask AddNewTask (string taskName)
100 throw new ArgumentNullException ("taskName");
102 XmlElement task = project.XmlDocument.CreateElement (taskName, Project.XmlNamespace);
103 targetElement.AppendChild (task);
104 BuildTask bt = new BuildTask (task, this);
110 public IEnumerator GetEnumerator ()
112 return buildTasks.ToArray ().GetEnumerator ();
115 // FIXME: shouldn't we remove it from XML?
116 public void RemoveTask (BuildTask buildTask)
118 if (buildTask == null)
119 throw new ArgumentNullException ("buildTask");
120 buildTasks.Remove (buildTask);
128 internal bool Build (string built_targets_key)
130 bool executeOnErrors;
131 return Build (built_targets_key, out executeOnErrors);
134 bool Build (string built_targets_key, out bool executeOnErrors)
136 project.PushThisFileProperty (TargetFile);
138 return BuildActual (built_targets_key, out executeOnErrors);
140 project.PopThisFileProperty ();
144 bool BuildActual (string built_targets_key, out bool executeOnErrors)
147 executeOnErrors = false;
149 // built targets are keyed by the particular set of global
150 // properties. So, a different set could allow a target
152 built_targets_key = project.GetKeyForTarget (Name);
153 if (project.ParentEngine.BuiltTargetsOutputByName.ContainsKey (built_targets_key)) {
158 // Push a null/empty batch, effectively clearing it
159 project.PushBatch (null, null);
160 if (!ConditionParser.ParseAndEvaluate (Condition, Project)) {
161 LogMessage (MessageImportance.Low,
162 "Target {0} skipped due to false condition: {1}",
169 buildState = BuildState.Started;
172 result = BuildDependencies (out executeOnErrors) &&
173 BuildBeforeThisTargets (out executeOnErrors) &&
174 DoBuild (out executeOnErrors) && // deps & Before targets built fine, do main build
175 BuildAfterThisTargets (out executeOnErrors);
177 result = BuildDependencies (out executeOnErrors) && DoBuild (out executeOnErrors);
180 buildState = BuildState.Finished;
181 } catch (Exception e) {
182 LogError ("Error building target {0}: {1}", Name, e.ToString ());
188 ITaskItem[] outputs = (ITaskItem[]) OutputsAsITaskItems.Clone ();
189 foreach (ITaskItem item in outputs) {
190 item.SetMetadata ("MSBuildProjectFile", TargetFile);
191 item.SetMetadata ("MSBuildTargetName", Name);
193 project.ParentEngine.BuiltTargetsOutputByName [built_targets_key] = outputs;
198 bool BuildDependencies (out bool executeOnErrors)
200 executeOnErrors = false;
202 if (String.IsNullOrEmpty (DependsOnTargets))
205 var expr = new Expression ();
206 expr.Parse (DependsOnTargets, ParseOptions.AllowItemsNoMetadataAndSplit);
207 string [] targetNames = (string []) expr.ConvertTo (Project, typeof (string []));
209 bool result = BuildOtherTargets (targetNames,
210 tname => engine.LogError ("Target '{0}', a dependency of target '{1}', not found.",
212 out executeOnErrors);
213 if (!result && executeOnErrors)
220 bool BuildBeforeThisTargets (out bool executeOnErrors)
222 executeOnErrors = false;
223 bool result = BuildOtherTargets (BeforeThisTargets, null, out executeOnErrors);
224 if (!result && executeOnErrors)
230 bool BuildAfterThisTargets (out bool executeOnErrors)
232 executeOnErrors = false;
233 //missing_target handler not required as these are picked from actual target's
234 //"Before/AfterTargets attributes!
235 bool result = BuildOtherTargets (AfterThisTargets, null, out executeOnErrors);
236 if (!result && executeOnErrors)
243 bool BuildOtherTargets (IEnumerable<string> targetNames, Action<string> missing_target, out bool executeOnErrors)
245 executeOnErrors = false;
246 if (targetNames == null)
250 foreach (string target_name in targetNames) {
251 var t = project.Targets [target_name.Trim ()];
253 if (missing_target != null)
254 missing_target (target_name);
258 if (t.BuildState == BuildState.NotStarted)
259 if (!t.Build (null, out executeOnErrors))
262 if (t.BuildState == BuildState.Started)
263 throw new InvalidProjectFileException ("Cycle in target dependencies detected");
269 bool DoBuild (out bool executeOnErrors)
271 executeOnErrors = false;
274 if (BuildTasks.Count == 0)
279 result = batchingImpl.Build (this, out executeOnErrors);
280 } catch (Exception e) {
281 LogError ("Error building target {0}: {1}", Name, e.Message);
282 LogMessage (MessageImportance.Low, "Error building target {0}: {1}", Name, e.ToString ());
286 if (executeOnErrors == true)
292 void ExecuteOnErrors ()
294 foreach (XmlElement onError in onErrorElements) {
295 if (onError.GetAttribute ("ExecuteTargets") == String.Empty)
296 throw new InvalidProjectFileException ("ExecuteTargets attribute is required in OnError element.");
298 string on_error_condition = onError.GetAttribute ("Condition");
299 if (!ConditionParser.ParseAndEvaluate (on_error_condition, Project)) {
300 LogMessage (MessageImportance.Low,
301 "OnError for target {0} skipped due to false condition: {1}",
302 Name, on_error_condition);
306 string[] targetsToExecute = onError.GetAttribute ("ExecuteTargets").Split (';');
307 foreach (string t in targetsToExecute)
308 this.project.Targets [t].Build ();
312 void LogTargetSkipped ()
314 BuildMessageEventArgs bmea;
315 bmea = new BuildMessageEventArgs (String.Format (
316 "Target {0} skipped, as it has already been built.", Name),
317 null, null, MessageImportance.Low);
319 project.ParentEngine.EventSource.FireMessageRaised (this, bmea);
322 void LogError (string message, params object [] messageArgs)
325 throw new ArgumentException ("message");
327 BuildErrorEventArgs beea = new BuildErrorEventArgs (
328 null, null, null, 0, 0, 0, 0, String.Format (message, messageArgs),
330 engine.EventSource.FireErrorRaised (this, beea);
333 void LogMessage (MessageImportance importance, string message, params object [] messageArgs)
336 throw new ArgumentNullException ("message");
338 BuildMessageEventArgs bmea = new BuildMessageEventArgs (
339 String.Format (message, messageArgs), null,
341 engine.EventSource.FireMessageRaised (this, bmea);
344 public string Condition {
345 get { return targetElement.GetAttribute ("Condition"); }
346 set { targetElement.SetAttribute ("Condition", value); }
349 public string DependsOnTargets {
350 get { return targetElement.GetAttribute ("DependsOnTargets"); }
351 set { targetElement.SetAttribute ("DependsOnTargets", value); }
354 public bool IsImported {
355 get { return importedProject != null; }
362 internal Project Project {
363 get { return project; }
366 internal string TargetFile {
368 if (importedProject != null)
369 return importedProject.FullFileName;
370 return project != null ? project.FullFileName : String.Empty;
375 internal string BeforeTargets {
376 get { return targetElement.GetAttribute ("BeforeTargets"); }
379 internal string AfterTargets {
380 get { return targetElement.GetAttribute ("AfterTargets"); }
383 internal List<string> BeforeThisTargets { get; set; }
384 internal List<string> AfterThisTargets { get; set; }
387 internal List<IBuildTask> BuildTasks {
388 get { return buildTasks; }
391 internal Engine Engine {
392 get { return engine; }
395 internal BuildState BuildState {
396 get { return buildState; }
399 public string Outputs {
400 get { return targetElement.GetAttribute ("Outputs"); }
401 set { targetElement.SetAttribute ("Outputs", value); }
404 ITaskItem [] OutputsAsITaskItems {
406 string outputs = targetElement.GetAttribute ("Outputs");
407 if (outputs == String.Empty)
408 return new ITaskItem [0];
410 Expression e = new Expression ();
411 e.Parse (outputs, ParseOptions.AllowItemsNoMetadataAndSplit);
413 return (ITaskItem []) e.ConvertTo (project, typeof (ITaskItem []));
418 internal enum BuildState {