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