Merge branch 'master' into msbuilddll2
[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                                                 var group = new BuildTaskItemGroup (xe, this);
83                                                 buildTasks.AddRange (group.Items);
84                                                 continue;
85                                         } else if (xe.Name == "PropertyGroup") {
86                                                 buildTasks.Add (new BuildTaskPropertyGroup (xe, this));
87                                                 continue;
88                                         }
89 #endif
90                                         else
91                                                 buildTasks.Add (new BuildTask (xe, this));
92                                 }
93                         }
94                 }
95
96                 [MonoTODO]
97                 public BuildTask AddNewTask (string taskName)
98                 {
99                         if (taskName == null)
100                                 throw new ArgumentNullException ("taskName");
101                 
102                         XmlElement task = project.XmlDocument.CreateElement (taskName, Project.XmlNamespace);
103                         targetElement.AppendChild (task);
104                         BuildTask bt = new BuildTask (task, this);
105                         buildTasks.Add (bt);
106                         
107                         return bt;
108                 }
109
110                 public IEnumerator GetEnumerator ()
111                 {
112                         return buildTasks.ToArray ().GetEnumerator ();
113                 }
114
115                 // FIXME: shouldn't we remove it from XML?
116                 public void RemoveTask (BuildTask buildTask)
117                 {
118                         if (buildTask == null)
119                                 throw new ArgumentNullException ("buildTask");
120                         buildTasks.Remove (buildTask);
121                 }
122
123                 bool Build ()
124                 {
125                         return Build (null);
126                 }
127
128                 internal bool Build (string built_targets_key)
129                 {
130                         bool executeOnErrors;
131                         return Build (built_targets_key, null, out executeOnErrors);
132                 }
133
134                 bool Build (string built_targets_key, string parentTarget, out bool executeOnErrors)
135                 {
136                         string message;
137                         if (parentTarget != null)
138                                 message = string.Format ("\"{0}\" in project \"{1}\" (\"{2}\"); \"{3}\" depends on it", Name, project.FullFileName, TargetFile, parentTarget);
139                         else
140                                 message = string.Format ("\"{0}\" in project \"{1}\" (\"{2}\")", Name, project.FullFileName, TargetFile);
141
142                         project.PushThisFileProperty (TargetFile);
143                         try {
144                                 LogMessage (MessageImportance.Low, "Building target {0}.", message);
145                                 return BuildActual (built_targets_key, out executeOnErrors);
146                         } finally {
147                                 LogMessage (MessageImportance.Low, "Done building target {0}.", message);
148                                 project.PopThisFileProperty ();
149                         }
150                 }
151
152                 bool BuildActual (string built_targets_key, out bool executeOnErrors)
153                 {
154                         bool result = false;
155                         executeOnErrors = false;
156
157                         // built targets are keyed by the particular set of global
158                         // properties. So, a different set could allow a target
159                         // to run again
160                         built_targets_key = project.GetKeyForTarget (Name);
161                         if (project.ParentEngine.BuiltTargetsOutputByName.ContainsKey (built_targets_key)) {
162                                 LogTargetSkipped ();
163                                 return true;
164                         }
165
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}",
171                                                 Name, Condition);
172                                 project.PopBatch ();
173                                 return true;
174                         }
175
176                         try {
177                                 buildState = BuildState.Started;
178
179 #if NET_4_0
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);
184 #else
185                                 result = BuildDependencies (out executeOnErrors) && DoBuild (out executeOnErrors);
186 #endif
187
188                                 buildState = BuildState.Finished;
189                         } catch (Exception e) {
190                                 LogError ("Error building target {0}: {1}", Name, e.ToString ());
191                                 return false;
192                         } finally {
193                                 project.PopBatch ();
194                         }
195
196                         ITaskItem[] outputs = (ITaskItem[]) OutputsAsITaskItems.Clone ();
197                         foreach (ITaskItem item in outputs) {
198                                 item.SetMetadata ("MSBuildProjectFile", TargetFile);
199                                 item.SetMetadata ("MSBuildTargetName", Name);
200                         }
201                         project.ParentEngine.BuiltTargetsOutputByName [built_targets_key] = outputs;
202
203                         return result;
204                 }
205
206                 bool BuildDependencies (out bool executeOnErrors)
207                 {
208                         executeOnErrors = false;
209
210                         if (String.IsNullOrEmpty (DependsOnTargets))
211                                 return true;
212
213                         var expr = new Expression ();
214                         expr.Parse (DependsOnTargets, ParseOptions.AllowItemsNoMetadataAndSplit);
215                         string [] targetNames = (string []) expr.ConvertTo (Project, typeof (string []));
216
217                         bool result = BuildOtherTargets (targetNames,
218                                                         tname => engine.LogError ("Target '{0}', a dependency of target '{1}', not found.",
219                                                                                 tname, Name),
220                                                         out executeOnErrors);
221                         if (!result && executeOnErrors)
222                                 ExecuteOnErrors ();
223
224                         return result;
225                 }
226
227 #if NET_4_0
228                 bool BuildBeforeThisTargets (out bool executeOnErrors)
229                 {
230                         executeOnErrors = false;
231                         bool result = BuildOtherTargets (BeforeThisTargets, null, out executeOnErrors);
232                         if (!result && executeOnErrors)
233                                 ExecuteOnErrors ();
234
235                         return result;
236                 }
237
238                 bool BuildAfterThisTargets (out bool executeOnErrors)
239                 {
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)
245                                 ExecuteOnErrors ();
246
247                         return result;
248                 }
249 #endif
250
251                 bool BuildOtherTargets (IEnumerable<string> targetNames, Action<string> missing_target, out bool executeOnErrors)
252                 {
253                         executeOnErrors = false;
254                         if (targetNames == null)
255                                 // nothing to build
256                                 return true;
257
258                         foreach (string target_name in targetNames) {
259                                 var t = project.Targets [target_name.Trim ()];
260                                 if (t == null) {
261                                         if (missing_target != null)
262                                                 missing_target (target_name);
263                                         return false;
264                                 }
265
266                                 if (t.BuildState == BuildState.NotStarted)
267                                         if (!t.Build (null, Name, out executeOnErrors))
268                                                 return false;
269
270                                 if (t.BuildState == BuildState.Started)
271                                         throw new InvalidProjectFileException ("Cycle in target dependencies detected");
272                         }
273
274                         return true;
275                 }
276                 
277                 bool DoBuild (out bool executeOnErrors)
278                 {
279                         executeOnErrors = false;
280                         bool result = true;
281
282                         if (BuildTasks.Count == 0)
283                                 // nothing to do
284                                 return true;
285                 
286                         try {
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 ());
291                                 return false;
292                         }
293
294                         if (executeOnErrors == true)
295                                 ExecuteOnErrors ();
296                                 
297                         return result;
298                 }
299                 
300                 void ExecuteOnErrors ()
301                 {
302                         foreach (XmlElement onError in onErrorElements) {
303                                 if (onError.GetAttribute ("ExecuteTargets") == String.Empty)
304                                         throw new InvalidProjectFileException ("ExecuteTargets attribute is required in OnError element.");
305
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);
311                                         continue;
312                                 }
313
314                                 string[] targetsToExecute = onError.GetAttribute ("ExecuteTargets").Split (';');
315                                 foreach (string t in targetsToExecute)
316                                         this.project.Targets [t].Build ();
317                         }
318                 }
319
320                 void LogTargetSkipped ()
321                 {
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);
326
327                         project.ParentEngine.EventSource.FireMessageRaised (this, bmea);
328                 }
329
330                 void LogError (string message, params object [] messageArgs)
331                 {
332                         if (message == null)
333                                 throw new ArgumentException ("message");
334
335                         BuildErrorEventArgs beea = new BuildErrorEventArgs (
336                                 null, null, null, 0, 0, 0, 0, String.Format (message, messageArgs),
337                                 null, null);
338                         engine.EventSource.FireErrorRaised (this, beea);
339                 }
340
341                 void LogMessage (MessageImportance importance, string message, params object [] messageArgs)
342                 {
343                         if (message == null)
344                                 throw new ArgumentNullException ("message");
345
346                         BuildMessageEventArgs bmea = new BuildMessageEventArgs (
347                                 String.Format (message, messageArgs), null,
348                                 null, importance);
349                         engine.EventSource.FireMessageRaised (this, bmea);
350                 }
351         
352                 public string Condition {
353                         get { return targetElement.GetAttribute ("Condition"); }
354                         set { targetElement.SetAttribute ("Condition", value); }
355                 }
356
357                 public string DependsOnTargets {
358                         get { return targetElement.GetAttribute ("DependsOnTargets"); }
359                         set { targetElement.SetAttribute ("DependsOnTargets", value); }
360                 }
361
362                 public bool IsImported {
363                         get { return importedProject != null; }
364                 }
365
366                 public string Name {
367                         get { return name; }
368                 }
369                 
370                 internal Project Project {
371                         get { return project; }
372                 }
373
374                 internal string TargetFile {
375                         get {
376                                 if (importedProject != null)
377                                         return importedProject.FullFileName;
378                                 return project != null ? project.FullFileName : String.Empty;
379                         }
380                 }
381
382 #if NET_4_0
383                 internal string BeforeTargets {
384                         get { return targetElement.GetAttribute ("BeforeTargets"); }
385                 }
386
387                 internal string AfterTargets {
388                         get { return targetElement.GetAttribute ("AfterTargets"); }
389                 }
390
391                 internal List<string> BeforeThisTargets { get; set; }
392                 internal List<string> AfterThisTargets { get; set; }
393 #endif
394
395                 internal List<IBuildTask> BuildTasks {
396                         get { return buildTasks; }
397                 }
398
399                 internal Engine Engine {
400                         get { return engine; }
401                 }
402                 
403                 internal BuildState BuildState {
404                         get { return buildState; }
405                 }
406
407                 public string Outputs {
408                         get { return targetElement.GetAttribute ("Outputs"); }
409                         set { targetElement.SetAttribute ("Outputs", value); }
410                 }
411
412                 ITaskItem [] OutputsAsITaskItems {
413                         get {
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];
419                                 }
420
421                                 Expression e = new Expression ();
422                                 e.Parse (outputs, ParseOptions.AllowItemsNoMetadataAndSplit);
423
424                                 return (ITaskItem []) e.ConvertTo (project, typeof (ITaskItem []));
425                         }
426                 }
427         }
428         
429         internal enum BuildState {
430                 NotStarted,
431                 Started,
432                 Finished,
433                 Skipped
434         }
435 }