* Target.cs (Build): Log a message if a target is skipped.
[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.Xml;
34 using Microsoft.Build.Framework;
35 using Microsoft.Build.Utilities;
36
37 namespace Microsoft.Build.BuildEngine {
38         public class Target : IEnumerable {
39         
40                 TargetBatchingImpl batchingImpl;
41                 BuildState      buildState;
42                 Engine          engine;
43                 ImportedProject importedProject;
44                 string          name;
45                 Project         project;
46                 XmlElement      targetElement;
47                 List <XmlElement>       onErrorElements;
48                 List <BuildTask>        buildTasks;
49                 
50                 internal Target (XmlElement targetElement, Project project, ImportedProject importedProject)
51                 {
52                         if (project == null)
53                                 throw new ArgumentNullException ("project");
54                         if (targetElement == null)
55                                 throw new ArgumentNullException ("targetElement");
56
57                         this.targetElement = targetElement;
58                         this.name = targetElement.GetAttribute ("Name");
59
60                         this.project = project;
61                         this.engine = project.ParentEngine;
62                         this.importedProject = importedProject;
63
64                         this.onErrorElements  = new List <XmlElement> ();
65                         this.buildState = BuildState.NotStarted;
66                         this.buildTasks = new List <BuildTask> ();
67                         this.batchingImpl = new TargetBatchingImpl (project, this.targetElement);
68
69                         bool onErrorFound = false;
70                         foreach (XmlNode xn in targetElement.ChildNodes) {
71                                 if (xn is XmlElement) {
72                                         XmlElement xe = (XmlElement) xn;
73                                         if (xe.Name == "OnError") {
74                                                 onErrorElements.Add (xe);
75                                                 onErrorFound = true;
76                                         } else if (onErrorFound)
77                                                 throw new InvalidProjectFileException (
78                                                         "The element <OnError> must be last under element <Target>. Found element <Error> instead.");
79                                         else
80                                                 buildTasks.Add (new BuildTask (xe, this));
81                                 }
82                         }
83                 }
84                 
85                 [MonoTODO]
86                 public BuildTask AddNewTask (string taskName)
87                 {
88                         if (taskName == null)
89                                 throw new ArgumentNullException ("taskName");
90                 
91                         XmlElement task = project.XmlDocument.CreateElement (taskName, Project.XmlNamespace);
92                         targetElement.AppendChild (task);
93                         BuildTask bt = new BuildTask (task, this);
94                         buildTasks.Add (bt);
95                         
96                         return bt;
97                 }
98
99                 public IEnumerator GetEnumerator ()
100                 {
101                         foreach (BuildTask bt in buildTasks)
102                                 yield return bt;
103                 }
104
105                 // FIXME: shouldn't we remove it from XML?
106                 public void RemoveTask (BuildTask buildTask)
107                 {
108                         if (buildTask == null)
109                                 throw new ArgumentNullException ("buildTask");
110                         buildTasks.Remove (buildTask);
111                 }
112                 
113                 internal bool Build ()
114                 {
115                         bool deps;
116                         bool result;
117
118                         if (!ConditionParser.ParseAndEvaluate (Condition, Project)) {
119                                 LogMessage (MessageImportance.Low,
120                                                 "Target {0} skipped due to false condition: {1}",
121                                                 Name, Condition);
122                                 return true;
123                         }
124
125                         try {
126                                 buildState = BuildState.Started;
127                                 deps = BuildDependencies (GetDependencies ());
128
129                                 result = deps ? DoBuild () : false;
130
131                                 buildState = BuildState.Finished;
132                         } catch (Exception e) {
133                                 LogError ("Error building target {0}: {1}", Name, e.ToString ());
134                                 return false;
135                         }
136
137                         return result;
138                 }
139
140                 List <Target> GetDependencies ()
141                 {
142                         List <Target> list = new List <Target> ();
143                         Target t;
144                         string [] targetNames;
145                         Expression deps;
146
147                         if (DependsOnTargets != String.Empty) {
148                                 deps = new Expression ();
149                                 deps.Parse (DependsOnTargets, true);
150                                 targetNames = (string []) deps.ConvertTo (Project, typeof (string []));
151                                 foreach (string name in targetNames) {
152                                         t = project.Targets [name.Trim ()];
153                                         if (t == null)
154                                                 throw new InvalidProjectFileException (String.Format ("Target '{0}' not found.", name.Trim ()));
155                                         list.Add (t);
156                                 }
157                         }
158                         return list;
159                 }
160
161                 bool BuildDependencies (List <Target> deps)
162                 {
163                         foreach (Target t in deps) {
164                                 if (t.BuildState == BuildState.NotStarted)
165                                         if (!t.Build ())
166                                                 return false;
167                                 if (t.BuildState == BuildState.Started)
168                                         throw new InvalidProjectFileException ("Cycle in target dependencies detected");
169                         }
170
171                         return true;
172                 }
173                 
174                 bool DoBuild ()
175                 {
176                         bool executeOnErrors;
177                         bool result = true;
178
179                         if (BuildTasks.Count == 0)
180                                 // nothing to do
181                                 return true;
182                 
183                         try {
184                                 result = batchingImpl.Build (this, out executeOnErrors);
185                         } catch (Exception e) {
186                                 LogError ("Error building target {0}: {1}", Name, e.ToString ());
187                                 throw;
188                         }
189
190                         if (executeOnErrors == true)
191                                 ExecuteOnErrors ();
192                                 
193                         return result;
194                 }
195                 
196                 void ExecuteOnErrors ()
197                 {
198                         foreach (XmlElement onError in onErrorElements) {
199                                 // FIXME: add condition
200                                 if (onError.GetAttribute ("ExecuteTargets") == String.Empty)
201                                         throw new InvalidProjectFileException ("ExecuteTargets attribute is required in OnError element.");
202                                 string[] targetsToExecute = onError.GetAttribute ("ExecuteTargets").Split (';');
203                                 foreach (string t in targetsToExecute)
204                                         this.project.Targets [t].Build ();
205                         }
206                 }
207
208                 void LogError (string message, params object [] messageArgs)
209                 {
210                         if (message == null)
211                                 throw new ArgumentException ("message");
212
213                         BuildErrorEventArgs beea = new BuildErrorEventArgs (
214                                 null, null, null, 0, 0, 0, 0, String.Format (message, messageArgs),
215                                 null, null);
216                         engine.EventSource.FireErrorRaised (this, beea);
217                 }
218
219                 void LogMessage (MessageImportance importance, string message, params object [] messageArgs)
220                 {
221                         if (message == null)
222                                 throw new ArgumentNullException ("message");
223
224                         BuildMessageEventArgs bmea = new BuildMessageEventArgs (
225                                 String.Format (message, messageArgs), null,
226                                 null, importance);
227                         engine.EventSource.FireMessageRaised (this, bmea);
228                 }
229         
230                 public string Condition {
231                         get { return targetElement.GetAttribute ("Condition"); }
232                         set { targetElement.SetAttribute ("Condition", value); }
233                 }
234
235                 public string DependsOnTargets {
236                         get { return targetElement.GetAttribute ("DependsOnTargets"); }
237                         set { targetElement.SetAttribute ("DependsOnTargets", value); }
238                 }
239
240                 public bool IsImported {
241                         get { return importedProject != null; }
242                 }
243
244                 public string Name {
245                         get { return name; }
246                 }
247                 
248                 internal Project Project {
249                         get { return project; }
250                 }
251
252                 internal List<BuildTask> BuildTasks {
253                         get { return buildTasks; }
254                 }
255
256                 internal Engine Engine {
257                         get { return engine; }
258                 }
259                 
260                 internal BuildState BuildState {
261                         get { return buildState; }
262                 }
263
264                 internal ITaskItem [] Outputs {
265                         get {
266                                 string outputs = targetElement.GetAttribute ("Outputs");
267                                 if (outputs == String.Empty)
268                                         return new ITaskItem [0];
269
270                                 Expression e = new Expression ();
271                                 e.Parse (outputs, true);
272
273                                 return (ITaskItem []) e.ConvertTo (project, typeof (ITaskItem []));
274                         }
275                 }
276         }
277         
278         internal enum BuildState {
279                 NotStarted,
280                 Started,
281                 Finished,
282                 Skipped
283         }
284 }
285
286 #endif