2 // Target.cs: Represents a target.
5 // Marek Sieradzki (marek.sieradzki@gmail.com)
7 // (C) 2005 Marek Sieradzki
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:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
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.
31 using System
.Collections
;
32 using System
.Collections
.Generic
;
34 using Microsoft
.Build
.Framework
;
35 using Microsoft
.Build
.Utilities
;
37 namespace Microsoft
.Build
.BuildEngine
{
38 public class Target
: IEnumerable
{
40 TargetBatchingImpl batchingImpl
;
41 BuildState buildState
;
43 ImportedProject importedProject
;
46 XmlElement targetElement
;
47 List
<XmlElement
> onErrorElements
;
48 List
<BuildTask
> buildTasks
;
50 internal Target (XmlElement targetElement
, Project project
, ImportedProject importedProject
)
53 throw new ArgumentNullException ("project");
54 if (targetElement
== null)
55 throw new ArgumentNullException ("targetElement");
57 this.targetElement
= targetElement
;
58 this.name
= targetElement
.GetAttribute ("Name");
60 this.project
= project
;
61 this.engine
= project
.ParentEngine
;
62 this.importedProject
= importedProject
;
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
);
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
);
76 } else if (onErrorFound
)
77 throw new InvalidProjectFileException (
78 "The element <OnError> must be last under element <Target>. Found element <Error> instead.");
80 buildTasks
.Add (new BuildTask (xe
, this));
86 public BuildTask
AddNewTask (string taskName
)
89 throw new ArgumentNullException ("taskName");
91 XmlElement task
= project
.XmlDocument
.CreateElement (taskName
, Project
.XmlNamespace
);
92 targetElement
.AppendChild (task
);
93 BuildTask bt
= new BuildTask (task
, this);
99 public IEnumerator
GetEnumerator ()
101 foreach (BuildTask bt
in buildTasks
)
105 // FIXME: shouldn't we remove it from XML?
106 public void RemoveTask (BuildTask buildTask
)
108 if (buildTask
== null)
109 throw new ArgumentNullException ("buildTask");
110 buildTasks
.Remove (buildTask
);
118 internal bool Build (string built_targets_key
)
120 bool executeOnErrors
;
121 return Build (built_targets_key
, out executeOnErrors
);
124 bool Build (string built_targets_key
, out bool executeOnErrors
)
127 executeOnErrors
= false;
129 // built targets are keyed by the particular set of global
130 // properties. So, a different set could allow a target
132 built_targets_key
= project
.GetKeyForTarget (Name
);
134 if (project
.ParentEngine
.BuiltTargetsOutputByName
.ContainsKey (built_targets_key
)) {
139 if (!ConditionParser
.ParseAndEvaluate (Condition
, Project
)) {
140 LogMessage (MessageImportance
.Low
,
141 "Target {0} skipped due to false condition: {1}",
147 buildState
= BuildState
.Started
;
148 result
= BuildDependencies (GetDependencies (), out executeOnErrors
);
150 if (!result
&& executeOnErrors
)
154 // deps built fine, do main build
155 result
= DoBuild (out executeOnErrors
);
157 buildState
= BuildState
.Finished
;
158 } catch (Exception e
) {
159 LogError ("Error building target {0}: {1}", Name
, e
.ToString ());
163 project
.ParentEngine
.BuiltTargetsOutputByName
[built_targets_key
] = (ITaskItem
[]) Outputs
.Clone ();
164 project
.BuiltTargetKeys
.Add (built_targets_key
);
169 List
<Target
> GetDependencies ()
171 List
<Target
> list
= new List
<Target
> ();
173 string [] targetNames
;
176 if (DependsOnTargets
!= String
.Empty
) {
177 deps
= new Expression ();
178 deps
.Parse (DependsOnTargets
, ParseOptions
.AllowItemsNoMetadataAndSplit
);
179 targetNames
= (string []) deps
.ConvertTo (Project
, typeof (string []));
180 foreach (string dep_name
in targetNames
) {
181 t
= project
.Targets
[dep_name
.Trim ()];
183 throw new InvalidProjectFileException (String
.Format (
184 "Target '{0}', a dependency of target '{1}', not found.",
185 dep_name
.Trim (), Name
));
192 bool BuildDependencies (List
<Target
> deps
, out bool executeOnErrors
)
194 executeOnErrors
= false;
195 foreach (Target t
in deps
) {
196 if (t
.BuildState
== BuildState
.NotStarted
)
197 if (!t
.Build (null, out executeOnErrors
))
199 if (t
.BuildState
== BuildState
.Started
)
200 throw new InvalidProjectFileException ("Cycle in target dependencies detected");
206 bool DoBuild (out bool executeOnErrors
)
208 executeOnErrors
= false;
211 if (BuildTasks
.Count
== 0)
216 result
= batchingImpl
.Build (this, out executeOnErrors
);
217 } catch (Exception e
) {
218 LogError ("Error building target {0}: {1}", Name
, e
.Message
);
219 LogMessage (MessageImportance
.Low
, "Error building target {0}: {1}", Name
, e
.ToString ());
223 if (executeOnErrors
== true)
229 void ExecuteOnErrors ()
231 foreach (XmlElement onError
in onErrorElements
) {
232 if (onError
.GetAttribute ("ExecuteTargets") == String
.Empty
)
233 throw new InvalidProjectFileException ("ExecuteTargets attribute is required in OnError element.");
235 string on_error_condition
= onError
.GetAttribute ("Condition");
236 if (!ConditionParser
.ParseAndEvaluate (on_error_condition
, Project
)) {
237 LogMessage (MessageImportance
.Low
,
238 "OnError for target {0} skipped due to false condition: {1}",
239 Name
, on_error_condition
);
243 string[] targetsToExecute
= onError
.GetAttribute ("ExecuteTargets").Split (';');
244 foreach (string t
in targetsToExecute
)
245 this.project
.Targets
[t
].Build ();
249 void LogTargetSkipped ()
251 BuildMessageEventArgs bmea
;
252 bmea
= new BuildMessageEventArgs (String
.Format (
253 "Target {0} skipped, as it has already been built.", Name
),
254 null, null, MessageImportance
.Low
);
256 project
.ParentEngine
.EventSource
.FireMessageRaised (this, bmea
);
259 void LogError (string message
, params object [] messageArgs
)
262 throw new ArgumentException ("message");
264 BuildErrorEventArgs beea
= new BuildErrorEventArgs (
265 null, null, null, 0, 0, 0, 0, String
.Format (message
, messageArgs
),
267 engine
.EventSource
.FireErrorRaised (this, beea
);
270 void LogMessage (MessageImportance importance
, string message
, params object [] messageArgs
)
273 throw new ArgumentNullException ("message");
275 BuildMessageEventArgs bmea
= new BuildMessageEventArgs (
276 String
.Format (message
, messageArgs
), null,
278 engine
.EventSource
.FireMessageRaised (this, bmea
);
281 public string Condition
{
282 get { return targetElement.GetAttribute ("Condition"); }
283 set { targetElement.SetAttribute ("Condition", value); }
286 public string DependsOnTargets
{
287 get { return targetElement.GetAttribute ("DependsOnTargets"); }
288 set { targetElement.SetAttribute ("DependsOnTargets", value); }
291 public bool IsImported
{
292 get { return importedProject != null; }
299 internal Project Project
{
300 get { return project; }
303 internal string TargetFile
{
305 if (importedProject
!= null)
306 return importedProject
.FullFileName
;
307 return project
!= null ? project
.FullFileName
: String
.Empty
;
311 internal List
<BuildTask
> BuildTasks
{
312 get { return buildTasks; }
315 internal Engine Engine
{
316 get { return engine; }
319 internal BuildState BuildState
{
320 get { return buildState; }
323 internal ITaskItem
[] Outputs
{
325 string outputs
= targetElement
.GetAttribute ("Outputs");
326 if (outputs
== String
.Empty
)
327 return new ITaskItem
[0];
329 Expression e
= new Expression ();
330 e
.Parse (outputs
, ParseOptions
.AllowItemsNoMetadataAndSplit
);
332 return (ITaskItem
[]) e
.ConvertTo (project
, typeof (ITaskItem
[]));
337 internal enum BuildState
{