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