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, null, out executeOnErrors);
134 bool Build (string built_targets_key, string parentTarget, out bool executeOnErrors)
137 if (parentTarget != null)
138 message = string.Format ("\"{0}\" in project \"{1}\" (\"{2}\"); \"{3}\" depends on it", Name, project.FullFileName, TargetFile, parentTarget);
140 message = string.Format ("\"{0}\" in project \"{1}\" (\"{2}\")", Name, project.FullFileName, TargetFile);
142 project.PushThisFileProperty (TargetFile);
144 LogMessage (MessageImportance.Low, "Building target {0}.", message);
145 return BuildActual (built_targets_key, out executeOnErrors);
147 LogMessage (MessageImportance.Low, "Done building target {0}.", message);
148 project.PopThisFileProperty ();
152 bool BuildActual (string built_targets_key, out bool executeOnErrors)
155 executeOnErrors = false;
157 // built targets are keyed by the particular set of global
158 // properties. So, a different set could allow a target
160 built_targets_key = project.GetKeyForTarget (Name);
161 if (project.ParentEngine.BuiltTargetsOutputByName.ContainsKey (built_targets_key)) {
166 // Push a null/empty batch, effectively clearing it
167 project.PushBatch (null, null);
168 if (!ConditionParser.ParseAndEvaluate (Condition, Project)) {
169 LogMessage (MessageImportance.Low,
170 "Target {0} skipped due to false condition: {1}",
177 buildState = BuildState.Started;
180 result = BuildDependencies (out executeOnErrors) &&
181 BuildBeforeThisTargets (out executeOnErrors) &&
182 DoBuild (out executeOnErrors) && // deps & Before targets built fine, do main build
183 BuildAfterThisTargets (out executeOnErrors);
185 result = BuildDependencies (out executeOnErrors) && DoBuild (out executeOnErrors);
188 buildState = BuildState.Finished;
189 } catch (Exception e) {
190 LogError ("Error building target {0}: {1}", Name, e.ToString ());
196 ITaskItem[] outputs = (ITaskItem[]) OutputsAsITaskItems.Clone ();
197 foreach (ITaskItem item in outputs) {
198 item.SetMetadata ("MSBuildProjectFile", TargetFile);
199 item.SetMetadata ("MSBuildTargetName", Name);
201 project.ParentEngine.BuiltTargetsOutputByName [built_targets_key] = outputs;
206 bool BuildDependencies (out bool executeOnErrors)
208 executeOnErrors = false;
210 if (String.IsNullOrEmpty (DependsOnTargets))
213 var expr = new Expression ();
214 expr.Parse (DependsOnTargets, ParseOptions.AllowItemsNoMetadataAndSplit);
215 string [] targetNames = (string []) expr.ConvertTo (Project, typeof (string []));
217 bool result = BuildOtherTargets (targetNames,
218 tname => engine.LogError ("Target '{0}', a dependency of target '{1}', not found.",
220 out executeOnErrors);
221 if (!result && executeOnErrors)
228 bool BuildBeforeThisTargets (out bool executeOnErrors)
230 executeOnErrors = false;
231 bool result = BuildOtherTargets (BeforeThisTargets, null, out executeOnErrors);
232 if (!result && executeOnErrors)
238 bool BuildAfterThisTargets (out bool executeOnErrors)
240 executeOnErrors = false;
241 //missing_target handler not required as these are picked from actual target's
242 //"Before/AfterTargets attributes!
243 bool result = BuildOtherTargets (AfterThisTargets, null, out executeOnErrors);
244 if (!result && executeOnErrors)
251 bool BuildOtherTargets (IEnumerable<string> targetNames, Action<string> missing_target, out bool executeOnErrors)
253 executeOnErrors = false;
254 if (targetNames == null)
258 foreach (string target_name in targetNames) {
259 var t = project.Targets [target_name.Trim ()];
261 if (missing_target != null)
262 missing_target (target_name);
266 if (t.BuildState == BuildState.NotStarted)
267 if (!t.Build (null, Name, out executeOnErrors))
270 if (t.BuildState == BuildState.Started)
271 throw new InvalidProjectFileException ("Cycle in target dependencies detected");
277 bool DoBuild (out bool executeOnErrors)
279 executeOnErrors = false;
282 if (BuildTasks.Count == 0)
287 result = batchingImpl.Build (this, out executeOnErrors);
288 } catch (Exception e) {
289 LogError ("Error building target {0}: {1}", Name, e.Message);
290 LogMessage (MessageImportance.Low, "Error building target {0}: {1}", Name, e.ToString ());
294 if (executeOnErrors == true)
300 void ExecuteOnErrors ()
302 foreach (XmlElement onError in onErrorElements) {
303 if (onError.GetAttribute ("ExecuteTargets") == String.Empty)
304 throw new InvalidProjectFileException ("ExecuteTargets attribute is required in OnError element.");
306 string on_error_condition = onError.GetAttribute ("Condition");
307 if (!ConditionParser.ParseAndEvaluate (on_error_condition, Project)) {
308 LogMessage (MessageImportance.Low,
309 "OnError for target {0} skipped due to false condition: {1}",
310 Name, on_error_condition);
314 string[] targetsToExecute = onError.GetAttribute ("ExecuteTargets").Split (';');
315 foreach (string t in targetsToExecute)
316 this.project.Targets [t].Build ();
320 void LogTargetSkipped ()
322 BuildMessageEventArgs bmea;
323 bmea = new BuildMessageEventArgs (String.Format (
324 "Target {0} skipped, as it has already been built.", Name),
325 null, null, MessageImportance.Low);
327 project.ParentEngine.EventSource.FireMessageRaised (this, bmea);
330 void LogError (string message, params object [] messageArgs)
333 throw new ArgumentException ("message");
335 BuildErrorEventArgs beea = new BuildErrorEventArgs (
336 null, null, null, 0, 0, 0, 0, String.Format (message, messageArgs),
338 engine.EventSource.FireErrorRaised (this, beea);
341 void LogMessage (MessageImportance importance, string message, params object [] messageArgs)
344 throw new ArgumentNullException ("message");
346 BuildMessageEventArgs bmea = new BuildMessageEventArgs (
347 String.Format (message, messageArgs), null,
349 engine.EventSource.FireMessageRaised (this, bmea);
352 public string Condition {
353 get { return targetElement.GetAttribute ("Condition"); }
354 set { targetElement.SetAttribute ("Condition", value); }
357 public string DependsOnTargets {
358 get { return targetElement.GetAttribute ("DependsOnTargets"); }
359 set { targetElement.SetAttribute ("DependsOnTargets", value); }
362 public bool IsImported {
363 get { return importedProject != null; }
370 internal Project Project {
371 get { return project; }
374 internal string TargetFile {
376 if (importedProject != null)
377 return importedProject.FullFileName;
378 return project != null ? project.FullFileName : String.Empty;
383 internal string BeforeTargets {
384 get { return targetElement.GetAttribute ("BeforeTargets"); }
387 internal string AfterTargets {
388 get { return targetElement.GetAttribute ("AfterTargets"); }
391 internal List<string> BeforeThisTargets { get; set; }
392 internal List<string> AfterThisTargets { get; set; }
395 internal List<IBuildTask> BuildTasks {
396 get { return buildTasks; }
399 internal Engine Engine {
400 get { return engine; }
403 internal BuildState BuildState {
404 get { return buildState; }
407 public string Outputs {
408 get { return targetElement.GetAttribute ("Outputs"); }
409 set { targetElement.SetAttribute ("Outputs", value); }
412 ITaskItem [] OutputsAsITaskItems {
414 var outputs = targetElement.GetAttribute ("Returns");
415 if (string.IsNullOrEmpty (outputs)) {
416 outputs = targetElement.GetAttribute ("Outputs");
417 if (string.IsNullOrEmpty (outputs))
418 return new ITaskItem [0];
421 Expression e = new Expression ();
422 e.Parse (outputs, ParseOptions.AllowItemsNoMetadataAndSplit);
424 return (ITaskItem []) e.ConvertTo (project, typeof (ITaskItem []));
429 internal enum BuildState {