Merge remote-tracking branch 'upstream/master'
[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 using System;
29 using System.Text;
30 using System.Collections;
31 using System.Collections.Generic;
32 using System.Linq;
33 using System.Xml;
34 using Microsoft.Build.Framework;
35 using Microsoft.Build.Utilities;
36 using Mono.XBuild.Utilities;
37
38 namespace Microsoft.Build.BuildEngine {
39         public class Target : IEnumerable {
40         
41                 TargetBatchingImpl batchingImpl;
42                 BuildState      buildState;
43                 Engine          engine;
44                 ImportedProject importedProject;
45                 string          name;
46                 Project         project;
47                 XmlElement      targetElement;
48                 List <XmlElement>       onErrorElements;
49                 List <IBuildTask>       buildTasks;
50                 
51                 internal Target (XmlElement targetElement, Project project, ImportedProject importedProject)
52                 {
53                         if (project == null)
54                                 throw new ArgumentNullException ("project");
55                         if (targetElement == null)
56                                 throw new ArgumentNullException ("targetElement");
57
58                         this.targetElement = targetElement;
59                         this.name = targetElement.GetAttribute ("Name");
60
61                         this.project = project;
62                         this.engine = project.ParentEngine;
63                         this.importedProject = importedProject;
64
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);
69
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);
76                                                 onErrorFound = true;
77                                         } else if (onErrorFound)
78                                                 throw new InvalidProjectFileException (
79                                                         "The element <OnError> must be last under element <Target>. Found element <Error> instead.");
80 #if NET_3_5
81                                         else if (xe.Name == "ItemGroup") {
82                                                 buildTasks.Add (new BuildTaskItemGroup (xe, this));
83                                                 continue;
84                                         } else if (xe.Name == "PropertyGroup") {
85                                                 buildTasks.Add (new BuildTaskPropertyGroup (xe, this));
86                                                 continue;
87                                         }
88 #endif
89                                         else
90                                                 buildTasks.Add (new BuildTask (xe, this));
91                                 }
92                         }
93                 }
94
95                 [MonoTODO]
96                 public BuildTask AddNewTask (string taskName)
97                 {
98                         if (taskName == null)
99                                 throw new ArgumentNullException ("taskName");
100                 
101                         XmlElement task = project.XmlDocument.CreateElement (taskName, Project.XmlNamespace);
102                         targetElement.AppendChild (task);
103                         BuildTask bt = new BuildTask (task, this);
104                         buildTasks.Add (bt);
105                         
106                         return bt;
107                 }
108
109                 public IEnumerator GetEnumerator ()
110                 {
111                         return buildTasks.ToArray ().GetEnumerator ();
112                 }
113
114                 // FIXME: shouldn't we remove it from XML?
115                 public void RemoveTask (BuildTask buildTask)
116                 {
117                         if (buildTask == null)
118                                 throw new ArgumentNullException ("buildTask");
119                         buildTasks.Remove (buildTask);
120                 }
121
122                 bool Build ()
123                 {
124                         return Build (null);
125                 }
126
127                 internal bool Build (string built_targets_key)
128                 {
129                         bool executeOnErrors;
130                         return Build (built_targets_key, out executeOnErrors);
131                 }
132
133                 bool Build (string built_targets_key, out bool executeOnErrors)
134                 {
135                         project.PushThisFileProperty (TargetFile);
136                         try {
137                                 return BuildActual (built_targets_key, out executeOnErrors);
138                         } finally {
139                                 project.PopThisFileProperty ();
140                         }
141                 }
142
143                 bool BuildActual (string built_targets_key, out bool executeOnErrors)
144                 {
145                         bool result = false;
146                         executeOnErrors = false;
147
148                         // built targets are keyed by the particular set of global
149                         // properties. So, a different set could allow a target
150                         // to run again
151                         built_targets_key = project.GetKeyForTarget (Name);
152                         if (project.ParentEngine.BuiltTargetsOutputByName.ContainsKey (built_targets_key)) {
153                                 LogTargetSkipped ();
154                                 return true;
155                         }
156
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}",
162                                                 Name, Condition);
163                                 project.PopBatch ();
164                                 return true;
165                         }
166
167                         try {
168                                 buildState = BuildState.Started;
169
170 #if NET_4_0
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);
175 #else
176                                 result = BuildDependencies (out executeOnErrors) && DoBuild (out executeOnErrors);
177 #endif
178
179                                 buildState = BuildState.Finished;
180                         } catch (Exception e) {
181                                 LogError ("Error building target {0}: {1}", Name, e.ToString ());
182                                 return false;
183                         } finally {
184                                 project.PopBatch ();
185                         }
186
187                         ITaskItem[] outputs = (ITaskItem[]) OutputsAsITaskItems.Clone ();
188                         foreach (ITaskItem item in outputs) {
189                                 item.SetMetadata ("MSBuildProjectFile", TargetFile);
190                                 item.SetMetadata ("MSBuildTargetName", Name);
191                         }
192                         project.ParentEngine.BuiltTargetsOutputByName [built_targets_key] = outputs;
193
194                         return result;
195                 }
196
197                 bool BuildDependencies (out bool executeOnErrors)
198                 {
199                         executeOnErrors = false;
200
201                         if (String.IsNullOrEmpty (DependsOnTargets))
202                                 return true;
203
204                         var expr = new Expression ();
205                         expr.Parse (DependsOnTargets, ParseOptions.AllowItemsNoMetadataAndSplit);
206                         string [] targetNames = (string []) expr.ConvertTo (Project, typeof (string []));
207
208                         bool result = BuildOtherTargets (targetNames,
209                                                         tname => engine.LogError ("Target '{0}', a dependency of target '{1}', not found.",
210                                                                                 tname, Name),
211                                                         out executeOnErrors);
212                         if (!result && executeOnErrors)
213                                 ExecuteOnErrors ();
214
215                         return result;
216                 }
217
218 #if NET_4_0
219                 bool BuildBeforeThisTargets (out bool executeOnErrors)
220                 {
221                         executeOnErrors = false;
222                         bool result = BuildOtherTargets (BeforeThisTargets, null, out executeOnErrors);
223                         if (!result && executeOnErrors)
224                                 ExecuteOnErrors ();
225
226                         return result;
227                 }
228
229                 bool BuildAfterThisTargets (out bool executeOnErrors)
230                 {
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)
236                                 ExecuteOnErrors ();
237
238                         return result;
239                 }
240 #endif
241
242                 bool BuildOtherTargets (IEnumerable<string> targetNames, Action<string> missing_target, out bool executeOnErrors)
243                 {
244                         executeOnErrors = false;
245                         if (targetNames == null)
246                                 // nothing to build
247                                 return true;
248
249                         foreach (string target_name in targetNames) {
250                                 var t = project.Targets [target_name.Trim ()];
251                                 if (t == null) {
252                                         if (missing_target != null)
253                                                 missing_target (target_name);
254                                         return false;
255                                 }
256
257                                 if (t.BuildState == BuildState.NotStarted)
258                                         if (!t.Build (null, out executeOnErrors))
259                                                 return false;
260
261                                 if (t.BuildState == BuildState.Started)
262                                         throw new InvalidProjectFileException ("Cycle in target dependencies detected");
263                         }
264
265                         return true;
266                 }
267                 
268                 bool DoBuild (out bool executeOnErrors)
269                 {
270                         executeOnErrors = false;
271                         bool result = true;
272
273                         if (BuildTasks.Count == 0)
274                                 // nothing to do
275                                 return true;
276                 
277                         try {
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 ());
282                                 return false;
283                         }
284
285                         if (executeOnErrors == true)
286                                 ExecuteOnErrors ();
287                                 
288                         return result;
289                 }
290                 
291                 void ExecuteOnErrors ()
292                 {
293                         foreach (XmlElement onError in onErrorElements) {
294                                 if (onError.GetAttribute ("ExecuteTargets") == String.Empty)
295                                         throw new InvalidProjectFileException ("ExecuteTargets attribute is required in OnError element.");
296
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);
302                                         continue;
303                                 }
304
305                                 string[] targetsToExecute = onError.GetAttribute ("ExecuteTargets").Split (';');
306                                 foreach (string t in targetsToExecute)
307                                         this.project.Targets [t].Build ();
308                         }
309                 }
310
311                 void LogTargetSkipped ()
312                 {
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);
317
318                         project.ParentEngine.EventSource.FireMessageRaised (this, bmea);
319                 }
320
321                 void LogError (string message, params object [] messageArgs)
322                 {
323                         if (message == null)
324                                 throw new ArgumentException ("message");
325
326                         BuildErrorEventArgs beea = new BuildErrorEventArgs (
327                                 null, null, null, 0, 0, 0, 0, String.Format (message, messageArgs),
328                                 null, null);
329                         engine.EventSource.FireErrorRaised (this, beea);
330                 }
331
332                 void LogMessage (MessageImportance importance, string message, params object [] messageArgs)
333                 {
334                         if (message == null)
335                                 throw new ArgumentNullException ("message");
336
337                         BuildMessageEventArgs bmea = new BuildMessageEventArgs (
338                                 String.Format (message, messageArgs), null,
339                                 null, importance);
340                         engine.EventSource.FireMessageRaised (this, bmea);
341                 }
342         
343                 public string Condition {
344                         get { return targetElement.GetAttribute ("Condition"); }
345                         set { targetElement.SetAttribute ("Condition", value); }
346                 }
347
348                 public string DependsOnTargets {
349                         get { return targetElement.GetAttribute ("DependsOnTargets"); }
350                         set { targetElement.SetAttribute ("DependsOnTargets", value); }
351                 }
352
353                 public bool IsImported {
354                         get { return importedProject != null; }
355                 }
356
357                 public string Name {
358                         get { return name; }
359                 }
360                 
361                 internal Project Project {
362                         get { return project; }
363                 }
364
365                 internal string TargetFile {
366                         get {
367                                 if (importedProject != null)
368                                         return importedProject.FullFileName;
369                                 return project != null ? project.FullFileName : String.Empty;
370                         }
371                 }
372
373 #if NET_4_0
374                 internal string BeforeTargets {
375                         get { return targetElement.GetAttribute ("BeforeTargets"); }
376                 }
377
378                 internal string AfterTargets {
379                         get { return targetElement.GetAttribute ("AfterTargets"); }
380                 }
381
382                 internal List<string> BeforeThisTargets { get; set; }
383                 internal List<string> AfterThisTargets { get; set; }
384 #endif
385
386                 internal List<IBuildTask> BuildTasks {
387                         get { return buildTasks; }
388                 }
389
390                 internal Engine Engine {
391                         get { return engine; }
392                 }
393                 
394                 internal BuildState BuildState {
395                         get { return buildState; }
396                 }
397
398                 public string Outputs {
399                         get { return targetElement.GetAttribute ("Outputs"); }
400                         set { targetElement.SetAttribute ("Outputs", value); }
401                 }
402
403                 ITaskItem [] OutputsAsITaskItems {
404                         get {
405                                 string outputs = targetElement.GetAttribute ("Outputs");
406                                 if (outputs == String.Empty)
407                                         return new ITaskItem [0];
408
409                                 Expression e = new Expression ();
410                                 e.Parse (outputs, ParseOptions.AllowItemsNoMetadataAndSplit);
411
412                                 return (ITaskItem []) e.ConvertTo (project, typeof (ITaskItem []));
413                         }
414                 }
415         }
416         
417         internal enum BuildState {
418                 NotStarted,
419                 Started,
420                 Finished,
421                 Skipped
422         }
423 }