2007-02-20 Marek Sieradzki <marek.sieradzki@gmail.com>
[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
36 namespace Microsoft.Build.BuildEngine {
37         public class Target : IEnumerable {
38         
39                 BatchingImpl    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 BatchingImpl (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                                         else
79                                                 buildTasks.Add (new BuildTask (xe, this));
80                                 }
81                         }
82                 }
83                 
84                 [MonoTODO]
85                 public BuildTask AddNewTask (string taskName)
86                 {
87                         if (taskName == null)
88                                 throw new ArgumentNullException ("taskName");
89                 
90                         XmlElement task = project.XmlDocument.CreateElement (taskName, Project.XmlNamespace);
91                         targetElement.AppendChild (task);
92                         BuildTask bt = new BuildTask (task, this);
93                         buildTasks.Add (bt);
94                         
95                         return bt;
96                 }
97
98                 public IEnumerator GetEnumerator ()
99                 {
100                         foreach (BuildTask bt in buildTasks)
101                                 yield return bt;
102                 }
103
104                 // FIXME: shouldn't we remove it from XML?
105                 public void RemoveTask (BuildTask buildTask)
106                 {
107                         if (buildTask == null)
108                                 throw new ArgumentNullException ("buildTask");
109                         buildTasks.Remove (buildTask);
110                 }
111                 
112                 // FIXME: log errors instead of throwing exceptions
113                 internal bool Build ()
114                 {
115                         bool deps;
116                         bool result;
117
118                         // log that target is being skipped
119                         if (!ConditionParser.ParseAndEvaluate (Condition, Project))
120                                 return true;
121
122                         try {
123                                 buildState = BuildState.Started;
124                                 deps = BuildDependencies (GetDependencies ());
125
126                                 result = deps ? DoBuild () : false;
127
128                                 buildState = BuildState.Finished;
129                         // FIXME: log it 
130                         } catch (Exception e) {
131                                 return false;
132                         }
133
134                         return result;
135                 }
136
137                 List <Target> GetDependencies ()
138                 {
139                         List <Target> list = new List <Target> ();
140                         Target t;
141                         string [] targetNames;
142                         Expression deps;
143
144                         if (DependsOnTargets != String.Empty) {
145                                 deps = new Expression ();
146                                 deps.Parse (DependsOnTargets, true);
147                                 targetNames = (string []) deps.ConvertTo (Project, typeof (string []));
148                                 foreach (string name in targetNames) {
149                                         t = project.Targets [name.Trim ()];
150                                         if (t == null)
151                                                 throw new InvalidProjectFileException (String.Format ("Target '{0}' not found.", name.Trim ()));
152                                         list.Add (t);
153                                 }
154                         }
155                         return list;
156                 }
157
158                 bool BuildDependencies (List <Target> deps)
159                 {
160                         foreach (Target t in deps) {
161                                 if (t.BuildState == BuildState.NotStarted)
162                                         if (!t.Build ())
163                                                 return false;
164                                 if (t.BuildState == BuildState.Started)
165                                         throw new InvalidProjectFileException ("Cycle in target dependencies detected");
166                         }
167
168                         return true;
169                 }
170                 
171                 bool DoBuild ()
172                 {
173                         bool executeOnErrors = false;
174                         bool result = true;
175                 
176                         LogTargetStarted ();
177                         
178                         if (batchingImpl.BuildNeeded ()) {
179                                 foreach (BuildTask bt in buildTasks) {
180                                         result = batchingImpl.BatchBuildTask (bt);
181                                 
182                                         if (!result && !bt.ContinueOnError) {
183                                                 executeOnErrors = true;
184                                                 break;
185                                         }
186                                 }
187                         } else {
188                                 LogTargetSkipped ();
189                         }
190
191                         LogTargetFinished (result);
192                         
193                         if (executeOnErrors == true)
194                                 ExecuteOnErrors ();
195                                 
196                         return result;
197                 }
198                 
199                 void ExecuteOnErrors ()
200                 {
201                         foreach (XmlElement onError in onErrorElements) {
202                                 // FIXME: add condition
203                                 if (onError.GetAttribute ("ExecuteTargets") == String.Empty)
204                                         throw new InvalidProjectFileException ("ExecuteTargets attribute is required in OnError element.");
205                                 string[] targetsToExecute = onError.GetAttribute ("ExecuteTargets").Split (';');
206                                 foreach (string t in targetsToExecute)
207                                         this.project.Targets [t].Build ();
208                         }
209                 }
210
211                 void LogTargetSkipped ()
212                 {
213                         BuildMessageEventArgs bmea;
214                         bmea = new BuildMessageEventArgs (String.Format ("Skipping target \"{0}\" because its outputs are up-to-date.",
215                                 name), null, "MSBuild", MessageImportance.Normal);
216                         engine.EventSource.FireMessageRaised (this, bmea);
217                 }
218                 
219                 void LogTargetStarted ()
220                 {
221                         TargetStartedEventArgs tsea;
222                         string projectFile = project.FullFileName;
223                         tsea = new TargetStartedEventArgs ("Target " + name + " started.", null, name, projectFile, null);
224                         engine.EventSource.FireTargetStarted (this, tsea);
225                 }
226                 
227                 void LogTargetFinished (bool succeeded)
228                 {
229                         TargetFinishedEventArgs tfea;
230                         string projectFile = project.FullFileName;
231                         tfea = new TargetFinishedEventArgs ("Target " + name + " finished.", null, name, projectFile, null, succeeded);
232                         engine.EventSource.FireTargetFinished (this, tfea);
233                 }
234         
235                 public string Condition {
236                         get { return targetElement.GetAttribute ("Condition"); }
237                         set { targetElement.SetAttribute ("Condition", value); }
238                 }
239
240                 public string DependsOnTargets {
241                         get { return targetElement.GetAttribute ("DependsOnTargets"); }
242                         set { targetElement.SetAttribute ("DependsOnTargets", value); }
243                 }
244
245                 public bool IsImported {
246                         get { return importedProject != null; }
247                 }
248
249                 public string Name {
250                         get { return name; }
251                 }
252                 
253                 internal Project Project {
254                         get { return project; }
255                 }
256                 
257                 internal BuildState BuildState {
258                         get { return buildState; }
259                 }
260         }
261         
262         internal enum BuildState {
263                 NotStarted,
264                 Started,
265                 Finished,
266                 Skipped
267         }
268 }
269
270 #endif