[xbuild] Add support for Before/AfterTargets.
[mono.git] / mcs / class / Microsoft.Build.Engine / Microsoft.Build.BuildEngine / Target.cs
1 //
2 // Target.cs: Represents a target.
3 //
4 // Author:
5 //   Marek Sieradzki (marek.sieradzki@gmail.com)
6 //
7 // (C) 2005 Marek Sieradzki
8 //
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:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
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.
27
28 #if NET_2_0
29
30 using System;
31 using System.Collections;
32 using System.Collections.Generic;
33 using System.Xml;
34 using Microsoft.Build.Framework;
35 using Microsoft.Build.Utilities;
36
37 namespace Microsoft.Build.BuildEngine {
38         public class Target : IEnumerable {
39         
40                 TargetBatchingImpl batchingImpl;
41                 BuildState      buildState;
42                 Engine          engine;
43                 ImportedProject importedProject;
44                 string          name;
45                 Project         project;
46                 XmlElement      targetElement;
47                 List <XmlElement>       onErrorElements;
48                 List <BuildTask>        buildTasks;
49                 
50                 internal Target (XmlElement targetElement, Project project, ImportedProject importedProject)
51                 {
52                         if (project == null)
53                                 throw new ArgumentNullException ("project");
54                         if (targetElement == null)
55                                 throw new ArgumentNullException ("targetElement");
56
57                         this.targetElement = targetElement;
58                         this.name = targetElement.GetAttribute ("Name");
59
60                         this.project = project;
61                         this.engine = project.ParentEngine;
62                         this.importedProject = importedProject;
63
64                         this.onErrorElements  = new List <XmlElement> ();
65                         this.buildState = BuildState.NotStarted;
66                         this.buildTasks = new List <BuildTask> ();
67                         this.batchingImpl = new TargetBatchingImpl (project, this.targetElement);
68
69                         bool onErrorFound = false;
70                         foreach (XmlNode xn in targetElement.ChildNodes) {
71                                 if (xn is XmlElement) {
72                                         XmlElement xe = (XmlElement) xn;
73                                         if (xe.Name == "OnError") {
74                                                 onErrorElements.Add (xe);
75                                                 onErrorFound = true;
76                                         } else if (onErrorFound)
77                                                 throw new InvalidProjectFileException (
78                                                         "The element <OnError> must be last under element <Target>. Found element <Error> instead.");
79                                         else
80                                                 buildTasks.Add (new BuildTask (xe, this));
81                                 }
82                         }
83                 }
84                 
85                 [MonoTODO]
86                 public BuildTask AddNewTask (string taskName)
87                 {
88                         if (taskName == null)
89                                 throw new ArgumentNullException ("taskName");
90                 
91                         XmlElement task = project.XmlDocument.CreateElement (taskName, Project.XmlNamespace);
92                         targetElement.AppendChild (task);
93                         BuildTask bt = new BuildTask (task, this);
94                         buildTasks.Add (bt);
95                         
96                         return bt;
97                 }
98
99                 public IEnumerator GetEnumerator ()
100                 {
101                         foreach (BuildTask bt in buildTasks)
102                                 yield return bt;
103                 }
104
105                 // FIXME: shouldn't we remove it from XML?
106                 public void RemoveTask (BuildTask buildTask)
107                 {
108                         if (buildTask == null)
109                                 throw new ArgumentNullException ("buildTask");
110                         buildTasks.Remove (buildTask);
111                 }
112
113                 bool Build ()
114                 {
115                         return Build (null);
116                 }
117
118                 internal bool Build (string built_targets_key)
119                 {
120                         bool executeOnErrors;
121                         return Build (built_targets_key, out executeOnErrors);
122                 }
123
124                 bool Build (string built_targets_key, out bool executeOnErrors)
125                 {
126                         project.PushThisFileProperty (TargetFile);
127                         try {
128                                 return BuildActual (built_targets_key, out executeOnErrors);
129                         } finally {
130                                 project.PopThisFileProperty ();
131                         }
132                 }
133
134                 bool BuildActual (string built_targets_key, out bool executeOnErrors)
135                 {
136                         bool result = false;
137                         executeOnErrors = false;
138
139                         // built targets are keyed by the particular set of global
140                         // properties. So, a different set could allow a target
141                         // to run again
142                         built_targets_key = project.GetKeyForTarget (Name);
143                         if (project.ParentEngine.BuiltTargetsOutputByName.ContainsKey (built_targets_key)) {
144                                 LogTargetSkipped ();
145                                 return true;
146                         }
147
148                         // Push a null/empty batch, effectively clearing it
149                         project.PushBatch (null, null);
150                         if (!ConditionParser.ParseAndEvaluate (Condition, Project)) {
151                                 LogMessage (MessageImportance.Low,
152                                                 "Target {0} skipped due to false condition: {1}",
153                                                 Name, Condition);
154                                 project.PopBatch ();
155                                 return true;
156                         }
157
158                         try {
159                                 buildState = BuildState.Started;
160
161 #if NET_4_0
162                                 result = BuildDependencies (out executeOnErrors) &&
163                                                 BuildBeforeThisTargets (out executeOnErrors) &&
164                                                 DoBuild (out executeOnErrors) && // deps & Before targets built fine, do main build
165                                                 BuildAfterThisTargets (out executeOnErrors);
166 #else
167                                 result = BuildDependencies (out executeOnErrors) && DoBuild (out executeOnErrors);
168 #endif
169
170                                 buildState = BuildState.Finished;
171                         } catch (Exception e) {
172                                 LogError ("Error building target {0}: {1}", Name, e.ToString ());
173                                 return false;
174                         } finally {
175                                 project.PopBatch ();
176                         }
177
178                         project.ParentEngine.BuiltTargetsOutputByName [built_targets_key] = (ITaskItem[]) Outputs.Clone ();
179
180                         return result;
181                 }
182
183                 bool BuildDependencies (out bool executeOnErrors)
184                 {
185                         executeOnErrors = false;
186
187                         if (String.IsNullOrEmpty (DependsOnTargets))
188                                 return true;
189
190                         var expr = new Expression ();
191                         expr.Parse (DependsOnTargets, ParseOptions.AllowItemsNoMetadataAndSplit);
192                         string [] targetNames = (string []) expr.ConvertTo (Project, typeof (string []));
193
194                         bool result = BuildOtherTargets (targetNames,
195                                                         tname => engine.LogError ("Target '{0}', a dependency of target '{1}', not found.",
196                                                                                 tname, Name),
197                                                         out executeOnErrors);
198                         if (!result && executeOnErrors)
199                                 ExecuteOnErrors ();
200
201                         return result;
202                 }
203
204 #if NET_4_0
205                 bool BuildBeforeThisTargets (out bool executeOnErrors)
206                 {
207                         executeOnErrors = false;
208                         bool result = BuildOtherTargets (BeforeThisTargets, null, out executeOnErrors);
209                         if (!result && executeOnErrors)
210                                 ExecuteOnErrors ();
211
212                         return result;
213                 }
214
215                 bool BuildAfterThisTargets (out bool executeOnErrors)
216                 {
217                         executeOnErrors = false;
218                         //missing_target handler not required as these are picked from actual target's
219                         //"Before/AfterTargets attributes!
220                         bool result = BuildOtherTargets (AfterThisTargets, null, out executeOnErrors);
221                         if (!result && executeOnErrors)
222                                 ExecuteOnErrors ();
223
224                         return result;
225                 }
226 #endif
227
228                 bool BuildOtherTargets (IEnumerable<string> targetNames, Action<string> missing_target, out bool executeOnErrors)
229                 {
230                         executeOnErrors = false;
231                         if (targetNames == null)
232                                 // nothing to build
233                                 return true;
234
235                         foreach (string target_name in targetNames) {
236                                 var t = project.Targets [target_name.Trim ()];
237                                 if (t == null) {
238                                         if (missing_target != null)
239                                                 missing_target (target_name);
240                                         return false;
241                                 }
242
243                                 if (t.BuildState == BuildState.NotStarted)
244                                         if (!t.Build (null, out executeOnErrors))
245                                                 return false;
246
247                                 if (t.BuildState == BuildState.Started)
248                                         throw new InvalidProjectFileException ("Cycle in target dependencies detected");
249                         }
250
251                         return true;
252                 }
253                 
254                 bool DoBuild (out bool executeOnErrors)
255                 {
256                         executeOnErrors = false;
257                         bool result = true;
258
259                         if (BuildTasks.Count == 0)
260                                 // nothing to do
261                                 return true;
262                 
263                         try {
264                                 result = batchingImpl.Build (this, out executeOnErrors);
265                         } catch (Exception e) {
266                                 LogError ("Error building target {0}: {1}", Name, e.Message);
267                                 LogMessage (MessageImportance.Low, "Error building target {0}: {1}", Name, e.ToString ());
268                                 return false;
269                         }
270
271                         if (executeOnErrors == true)
272                                 ExecuteOnErrors ();
273                                 
274                         return result;
275                 }
276                 
277                 void ExecuteOnErrors ()
278                 {
279                         foreach (XmlElement onError in onErrorElements) {
280                                 if (onError.GetAttribute ("ExecuteTargets") == String.Empty)
281                                         throw new InvalidProjectFileException ("ExecuteTargets attribute is required in OnError element.");
282
283                                 string on_error_condition = onError.GetAttribute ("Condition");
284                                 if (!ConditionParser.ParseAndEvaluate (on_error_condition, Project)) {
285                                         LogMessage (MessageImportance.Low,
286                                                 "OnError for target {0} skipped due to false condition: {1}",
287                                                 Name, on_error_condition);
288                                         continue;
289                                 }
290
291                                 string[] targetsToExecute = onError.GetAttribute ("ExecuteTargets").Split (';');
292                                 foreach (string t in targetsToExecute)
293                                         this.project.Targets [t].Build ();
294                         }
295                 }
296
297                 void LogTargetSkipped ()
298                 {
299                         BuildMessageEventArgs bmea;
300                         bmea = new BuildMessageEventArgs (String.Format (
301                                                 "Target {0} skipped, as it has already been built.", Name),
302                                         null, null, MessageImportance.Low);
303
304                         project.ParentEngine.EventSource.FireMessageRaised (this, bmea);
305                 }
306
307                 void LogError (string message, params object [] messageArgs)
308                 {
309                         if (message == null)
310                                 throw new ArgumentException ("message");
311
312                         BuildErrorEventArgs beea = new BuildErrorEventArgs (
313                                 null, null, null, 0, 0, 0, 0, String.Format (message, messageArgs),
314                                 null, null);
315                         engine.EventSource.FireErrorRaised (this, beea);
316                 }
317
318                 void LogMessage (MessageImportance importance, string message, params object [] messageArgs)
319                 {
320                         if (message == null)
321                                 throw new ArgumentNullException ("message");
322
323                         BuildMessageEventArgs bmea = new BuildMessageEventArgs (
324                                 String.Format (message, messageArgs), null,
325                                 null, importance);
326                         engine.EventSource.FireMessageRaised (this, bmea);
327                 }
328         
329                 public string Condition {
330                         get { return targetElement.GetAttribute ("Condition"); }
331                         set { targetElement.SetAttribute ("Condition", value); }
332                 }
333
334                 public string DependsOnTargets {
335                         get { return targetElement.GetAttribute ("DependsOnTargets"); }
336                         set { targetElement.SetAttribute ("DependsOnTargets", value); }
337                 }
338
339                 public bool IsImported {
340                         get { return importedProject != null; }
341                 }
342
343                 public string Name {
344                         get { return name; }
345                 }
346                 
347                 internal Project Project {
348                         get { return project; }
349                 }
350
351                 internal string TargetFile {
352                         get {
353                                 if (importedProject != null)
354                                         return importedProject.FullFileName;
355                                 return project != null ? project.FullFileName : String.Empty;
356                         }
357                 }
358
359 #if NET_4_0
360                 internal string BeforeTargets {
361                         get { return targetElement.GetAttribute ("BeforeTargets"); }
362                 }
363
364                 internal string AfterTargets {
365                         get { return targetElement.GetAttribute ("AfterTargets"); }
366                 }
367
368                 internal List<string> BeforeThisTargets { get; set; }
369                 internal List<string> AfterThisTargets { get; set; }
370 #endif
371
372                 internal List<BuildTask> BuildTasks {
373                         get { return buildTasks; }
374                 }
375
376                 internal Engine Engine {
377                         get { return engine; }
378                 }
379                 
380                 internal BuildState BuildState {
381                         get { return buildState; }
382                 }
383
384                 internal ITaskItem [] Outputs {
385                         get {
386                                 string outputs = targetElement.GetAttribute ("Outputs");
387                                 if (outputs == String.Empty)
388                                         return new ITaskItem [0];
389
390                                 Expression e = new Expression ();
391                                 e.Parse (outputs, ParseOptions.AllowItemsNoMetadataAndSplit);
392
393                                 return (ITaskItem []) e.ConvertTo (project, typeof (ITaskItem []));
394                         }
395                 }
396         }
397         
398         internal enum BuildState {
399                 NotStarted,
400                 Started,
401                 Finished,
402                 Skipped
403         }
404 }
405
406 #endif