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.");
80 else if (xe.Name == "ItemGroup") {
81 var group = new BuildTaskItemGroup (xe, this);
82 buildTasks.AddRange (group.Items);
84 } else if (xe.Name == "PropertyGroup") {
85 buildTasks.Add (new BuildTaskPropertyGroup (xe, this));
89 buildTasks.Add (new BuildTask (xe, this));
95 public BuildTask AddNewTask (string taskName)
98 throw new ArgumentNullException ("taskName");
100 XmlElement task = project.XmlDocument.CreateElement (taskName, Project.XmlNamespace);
101 targetElement.AppendChild (task);
102 BuildTask bt = new BuildTask (task, this);
108 public IEnumerator GetEnumerator ()
110 return buildTasks.ToArray ().GetEnumerator ();
113 // FIXME: shouldn't we remove it from XML?
114 public void RemoveTask (BuildTask taskElement)
116 if (taskElement == null)
117 throw new ArgumentNullException ("taskElement");
118 buildTasks.Remove (taskElement);
126 internal bool Build (string built_targets_key)
128 bool executeOnErrors;
129 return Build (built_targets_key, null, out executeOnErrors);
132 bool Build (string built_targets_key, string parentTarget, out bool executeOnErrors)
135 if (parentTarget != null)
136 message = string.Format ("\"{0}\" in project \"{1}\" (\"{2}\"); \"{3}\" depends on it", Name, project.FullFileName, TargetFile, parentTarget);
138 message = string.Format ("\"{0}\" in project \"{1}\" (\"{2}\")", Name, project.FullFileName, TargetFile);
140 project.PushThisFileProperty (TargetFile);
142 LogMessage (MessageImportance.Low, "Building target {0}.", message);
143 return BuildActual (built_targets_key, out executeOnErrors);
145 LogMessage (MessageImportance.Low, "Done building target {0}.", message);
146 project.PopThisFileProperty ();
150 bool BuildActual (string built_targets_key, out bool executeOnErrors)
153 executeOnErrors = false;
155 // built targets are keyed by the particular set of global
156 // properties. So, a different set could allow a target
158 built_targets_key = project.GetKeyForTarget (Name);
159 if (project.ParentEngine.BuiltTargetsOutputByName.ContainsKey (built_targets_key)) {
164 // Push a null/empty batch, effectively clearing it
165 project.PushBatch (null, null);
166 if (!ConditionParser.ParseAndEvaluate (Condition, Project)) {
167 LogMessage (MessageImportance.Low,
168 "Target {0} skipped due to false condition: {1}",
175 buildState = BuildState.Started;
177 result = BuildDependencies (out executeOnErrors) &&
178 BuildBeforeThisTargets (out executeOnErrors) &&
179 DoBuild (out executeOnErrors) && // deps & Before targets built fine, do main build
180 BuildAfterThisTargets (out executeOnErrors);
182 buildState = BuildState.Finished;
183 } catch (Exception e) {
184 LogError ("Error building target {0}: {1}", Name, e.ToString ());
190 ITaskItem[] outputs = (ITaskItem[]) OutputsAsITaskItems.Clone ();
191 foreach (ITaskItem item in outputs) {
192 item.SetMetadata ("MSBuildProjectFile", TargetFile);
193 item.SetMetadata ("MSBuildTargetName", Name);
195 project.ParentEngine.BuiltTargetsOutputByName [built_targets_key] = outputs;
200 bool BuildDependencies (out bool executeOnErrors)
202 executeOnErrors = false;
204 if (String.IsNullOrEmpty (DependsOnTargets))
207 var expr = new Expression ();
208 expr.Parse (DependsOnTargets, ParseOptions.AllowItemsNoMetadataAndSplit);
209 string [] targetNames = (string []) expr.ConvertTo (Project, typeof (string []));
211 bool result = BuildOtherTargets (targetNames,
212 tname => engine.LogError ("Target '{0}', a dependency of target '{1}', not found.",
214 out executeOnErrors);
215 if (!result && executeOnErrors)
221 bool BuildBeforeThisTargets (out bool executeOnErrors)
223 executeOnErrors = false;
224 bool result = BuildOtherTargets (BeforeThisTargets, null, out executeOnErrors);
225 if (!result && executeOnErrors)
231 bool BuildAfterThisTargets (out bool executeOnErrors)
233 executeOnErrors = false;
234 //missing_target handler not required as these are picked from actual target's
235 //"Before/AfterTargets attributes!
236 bool result = BuildOtherTargets (AfterThisTargets, null, out executeOnErrors);
237 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, Name, 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;
374 internal string BeforeTargets {
375 get { return targetElement.GetAttribute ("BeforeTargets"); }
378 internal string AfterTargets {
379 get { return targetElement.GetAttribute ("AfterTargets"); }
382 internal List<string> BeforeThisTargets { get; set; }
383 internal List<string> AfterThisTargets { get; set; }
385 internal List<IBuildTask> BuildTasks {
386 get { return buildTasks; }
389 internal Engine Engine {
390 get { return engine; }
393 internal BuildState BuildState {
394 get { return buildState; }
397 public string Outputs {
398 get { return targetElement.GetAttribute ("Outputs"); }
399 set { targetElement.SetAttribute ("Outputs", value); }
402 ITaskItem [] OutputsAsITaskItems {
404 var outputs = targetElement.GetAttribute ("Returns");
405 if (string.IsNullOrEmpty (outputs)) {
406 outputs = targetElement.GetAttribute ("Outputs");
407 if (string.IsNullOrEmpty (outputs))
408 return new ITaskItem [0];
411 Expression e = new Expression ();
412 e.Parse (outputs, ParseOptions.AllowItemsNoMetadataAndSplit);
414 return (ITaskItem []) e.ConvertTo (project, typeof (ITaskItem []));
419 internal enum BuildState {