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