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 buildTasks.Add (new BuildTaskItemGroup (xe, this));
84 } else if (xe.Name == "PropertyGroup") {
85 buildTasks.Add (new BuildTaskPropertyGroup (xe, this));
90 buildTasks.Add (new BuildTask (xe, this));
96 public BuildTask AddNewTask (string taskName)
99 throw new ArgumentNullException ("taskName");
101 XmlElement task = project.XmlDocument.CreateElement (taskName, Project.XmlNamespace);
102 targetElement.AppendChild (task);
103 BuildTask bt = new BuildTask (task, this);
109 public IEnumerator GetEnumerator ()
111 return buildTasks.ToArray ().GetEnumerator ();
114 // FIXME: shouldn't we remove it from XML?
115 public void RemoveTask (BuildTask buildTask)
117 if (buildTask == null)
118 throw new ArgumentNullException ("buildTask");
119 buildTasks.Remove (buildTask);
127 internal bool Build (string built_targets_key)
129 bool executeOnErrors;
130 return Build (built_targets_key, out executeOnErrors);
133 bool Build (string built_targets_key, out bool executeOnErrors)
135 project.PushThisFileProperty (TargetFile);
137 return BuildActual (built_targets_key, out executeOnErrors);
139 project.PopThisFileProperty ();
143 bool BuildActual (string built_targets_key, out bool executeOnErrors)
146 executeOnErrors = false;
148 // built targets are keyed by the particular set of global
149 // properties. So, a different set could allow a target
151 built_targets_key = project.GetKeyForTarget (Name);
152 if (project.ParentEngine.BuiltTargetsOutputByName.ContainsKey (built_targets_key)) {
157 // Push a null/empty batch, effectively clearing it
158 project.PushBatch (null, null);
159 if (!ConditionParser.ParseAndEvaluate (Condition, Project)) {
160 LogMessage (MessageImportance.Low,
161 "Target {0} skipped due to false condition: {1}",
168 buildState = BuildState.Started;
171 result = BuildDependencies (out executeOnErrors) &&
172 BuildBeforeThisTargets (out executeOnErrors) &&
173 DoBuild (out executeOnErrors) && // deps & Before targets built fine, do main build
174 BuildAfterThisTargets (out executeOnErrors);
176 result = BuildDependencies (out executeOnErrors) && DoBuild (out executeOnErrors);
179 buildState = BuildState.Finished;
180 } catch (Exception e) {
181 LogError ("Error building target {0}: {1}", Name, e.ToString ());
187 ITaskItem[] outputs = (ITaskItem[]) OutputsAsITaskItems.Clone ();
188 foreach (ITaskItem item in outputs) {
189 item.SetMetadata ("MSBuildProjectFile", TargetFile);
190 item.SetMetadata ("MSBuildTargetName", Name);
192 project.ParentEngine.BuiltTargetsOutputByName [built_targets_key] = outputs;
197 bool BuildDependencies (out bool executeOnErrors)
199 executeOnErrors = false;
201 if (String.IsNullOrEmpty (DependsOnTargets))
204 var expr = new Expression ();
205 expr.Parse (DependsOnTargets, ParseOptions.AllowItemsNoMetadataAndSplit);
206 string [] targetNames = (string []) expr.ConvertTo (Project, typeof (string []));
208 bool result = BuildOtherTargets (targetNames,
209 tname => engine.LogError ("Target '{0}', a dependency of target '{1}', not found.",
211 out executeOnErrors);
212 if (!result && executeOnErrors)
219 bool BuildBeforeThisTargets (out bool executeOnErrors)
221 executeOnErrors = false;
222 bool result = BuildOtherTargets (BeforeThisTargets, null, out executeOnErrors);
223 if (!result && executeOnErrors)
229 bool BuildAfterThisTargets (out bool executeOnErrors)
231 executeOnErrors = false;
232 //missing_target handler not required as these are picked from actual target's
233 //"Before/AfterTargets attributes!
234 bool result = BuildOtherTargets (AfterThisTargets, null, out executeOnErrors);
235 if (!result && executeOnErrors)
242 bool BuildOtherTargets (IEnumerable<string> targetNames, Action<string> missing_target, out bool executeOnErrors)
244 executeOnErrors = false;
245 if (targetNames == null)
249 foreach (string target_name in targetNames) {
250 var t = project.Targets [target_name.Trim ()];
252 if (missing_target != null)
253 missing_target (target_name);
257 if (t.BuildState == BuildState.NotStarted)
258 if (!t.Build (null, out executeOnErrors))
261 if (t.BuildState == BuildState.Started)
262 throw new InvalidProjectFileException ("Cycle in target dependencies detected");
268 bool DoBuild (out bool executeOnErrors)
270 executeOnErrors = false;
273 if (BuildTasks.Count == 0)
278 result = batchingImpl.Build (this, out executeOnErrors);
279 } catch (Exception e) {
280 LogError ("Error building target {0}: {1}", Name, e.Message);
281 LogMessage (MessageImportance.Low, "Error building target {0}: {1}", Name, e.ToString ());
285 if (executeOnErrors == true)
291 void ExecuteOnErrors ()
293 foreach (XmlElement onError in onErrorElements) {
294 if (onError.GetAttribute ("ExecuteTargets") == String.Empty)
295 throw new InvalidProjectFileException ("ExecuteTargets attribute is required in OnError element.");
297 string on_error_condition = onError.GetAttribute ("Condition");
298 if (!ConditionParser.ParseAndEvaluate (on_error_condition, Project)) {
299 LogMessage (MessageImportance.Low,
300 "OnError for target {0} skipped due to false condition: {1}",
301 Name, on_error_condition);
305 string[] targetsToExecute = onError.GetAttribute ("ExecuteTargets").Split (';');
306 foreach (string t in targetsToExecute)
307 this.project.Targets [t].Build ();
311 void LogTargetSkipped ()
313 BuildMessageEventArgs bmea;
314 bmea = new BuildMessageEventArgs (String.Format (
315 "Target {0} skipped, as it has already been built.", Name),
316 null, null, MessageImportance.Low);
318 project.ParentEngine.EventSource.FireMessageRaised (this, bmea);
321 void LogError (string message, params object [] messageArgs)
324 throw new ArgumentException ("message");
326 BuildErrorEventArgs beea = new BuildErrorEventArgs (
327 null, null, null, 0, 0, 0, 0, String.Format (message, messageArgs),
329 engine.EventSource.FireErrorRaised (this, beea);
332 void LogMessage (MessageImportance importance, string message, params object [] messageArgs)
335 throw new ArgumentNullException ("message");
337 BuildMessageEventArgs bmea = new BuildMessageEventArgs (
338 String.Format (message, messageArgs), null,
340 engine.EventSource.FireMessageRaised (this, bmea);
343 public string Condition {
344 get { return targetElement.GetAttribute ("Condition"); }
345 set { targetElement.SetAttribute ("Condition", value); }
348 public string DependsOnTargets {
349 get { return targetElement.GetAttribute ("DependsOnTargets"); }
350 set { targetElement.SetAttribute ("DependsOnTargets", value); }
353 public bool IsImported {
354 get { return importedProject != null; }
361 internal Project Project {
362 get { return project; }
365 internal string TargetFile {
367 if (importedProject != null)
368 return importedProject.FullFileName;
369 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; }
386 internal List<IBuildTask> BuildTasks {
387 get { return buildTasks; }
390 internal Engine Engine {
391 get { return engine; }
394 internal BuildState BuildState {
395 get { return buildState; }
398 public string Outputs {
399 get { return targetElement.GetAttribute ("Outputs"); }
400 set { targetElement.SetAttribute ("Outputs", value); }
403 ITaskItem [] OutputsAsITaskItems {
405 string outputs = targetElement.GetAttribute ("Outputs");
406 if (outputs == String.Empty)
407 return new ITaskItem [0];
409 Expression e = new Expression ();
410 e.Parse (outputs, ParseOptions.AllowItemsNoMetadataAndSplit);
412 return (ITaskItem []) e.ConvertTo (project, typeof (ITaskItem []));
417 internal enum BuildState {