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.
29 using System.Collections;
30 using System.Collections.Generic;
33 using Microsoft.Build.Framework;
34 using Microsoft.Build.Utilities;
36 namespace Microsoft.Build.BuildEngine {
37 public class Target : IEnumerable {
39 TargetBatchingImpl batchingImpl;
40 BuildState buildState;
42 ImportedProject importedProject;
45 XmlElement targetElement;
46 List <XmlElement> onErrorElements;
47 List <BuildTask> buildTasks;
49 internal Target (XmlElement targetElement, Project project, ImportedProject importedProject)
52 throw new ArgumentNullException ("project");
53 if (targetElement == null)
54 throw new ArgumentNullException ("targetElement");
56 this.targetElement = targetElement;
57 this.name = targetElement.GetAttribute ("Name");
59 this.project = project;
60 this.engine = project.ParentEngine;
61 this.importedProject = importedProject;
63 this.onErrorElements = new List <XmlElement> ();
64 this.buildState = BuildState.NotStarted;
65 this.buildTasks = new List <BuildTask> ();
66 this.batchingImpl = new TargetBatchingImpl (project, this.targetElement);
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);
75 } else if (onErrorFound)
76 throw new InvalidProjectFileException (
77 "The element <OnError> must be last under element <Target>. Found element <Error> instead.");
79 else if (xe.Name == "ItemGroup") {
80 //don't blow up for ItemGroups inside Targets in >= 3.5
81 // TODO: evaluate them (see https://bugzilla.xamarin.com/show_bug.cgi?id=1862 and test in TargetTest.cs )
86 buildTasks.Add (new BuildTask (xe, this));
92 public BuildTask AddNewTask (string taskName)
95 throw new ArgumentNullException ("taskName");
97 XmlElement task = project.XmlDocument.CreateElement (taskName, Project.XmlNamespace);
98 targetElement.AppendChild (task);
99 BuildTask bt = new BuildTask (task, this);
105 public IEnumerator GetEnumerator ()
107 return buildTasks.ToArray ().GetEnumerator ();
110 // FIXME: shouldn't we remove it from XML?
111 public void RemoveTask (BuildTask buildTask)
113 if (buildTask == null)
114 throw new ArgumentNullException ("buildTask");
115 buildTasks.Remove (buildTask);
123 internal bool Build (string built_targets_key)
125 bool executeOnErrors;
126 return Build (built_targets_key, out executeOnErrors);
129 bool Build (string built_targets_key, out bool executeOnErrors)
131 project.PushThisFileProperty (TargetFile);
133 return BuildActual (built_targets_key, out executeOnErrors);
135 project.PopThisFileProperty ();
139 bool BuildActual (string built_targets_key, out bool executeOnErrors)
142 executeOnErrors = false;
144 // built targets are keyed by the particular set of global
145 // properties. So, a different set could allow a target
147 built_targets_key = project.GetKeyForTarget (Name);
148 if (project.ParentEngine.BuiltTargetsOutputByName.ContainsKey (built_targets_key)) {
153 // Push a null/empty batch, effectively clearing it
154 project.PushBatch (null, null);
155 if (!ConditionParser.ParseAndEvaluate (Condition, Project)) {
156 LogMessage (MessageImportance.Low,
157 "Target {0} skipped due to false condition: {1}",
164 buildState = BuildState.Started;
167 result = BuildDependencies (out executeOnErrors) &&
168 BuildBeforeThisTargets (out executeOnErrors) &&
169 DoBuild (out executeOnErrors) && // deps & Before targets built fine, do main build
170 BuildAfterThisTargets (out executeOnErrors);
172 result = BuildDependencies (out executeOnErrors) && DoBuild (out executeOnErrors);
175 buildState = BuildState.Finished;
176 } catch (Exception e) {
177 LogError ("Error building target {0}: {1}", Name, e.ToString ());
183 ITaskItem[] outputs = (ITaskItem[]) OutputsAsITaskItems.Clone ();
184 foreach (ITaskItem item in outputs) {
185 item.SetMetadata ("MSBuildProjectFile", TargetFile);
186 item.SetMetadata ("MSBuildTargetName", Name);
188 project.ParentEngine.BuiltTargetsOutputByName [built_targets_key] = outputs;
193 bool BuildDependencies (out bool executeOnErrors)
195 executeOnErrors = false;
197 if (String.IsNullOrEmpty (DependsOnTargets))
200 var expr = new Expression ();
201 expr.Parse (DependsOnTargets, ParseOptions.AllowItemsNoMetadataAndSplit);
202 string [] targetNames = (string []) expr.ConvertTo (Project, typeof (string []));
204 bool result = BuildOtherTargets (targetNames,
205 tname => engine.LogError ("Target '{0}', a dependency of target '{1}', not found.",
207 out executeOnErrors);
208 if (!result && executeOnErrors)
215 bool BuildBeforeThisTargets (out bool executeOnErrors)
217 executeOnErrors = false;
218 bool result = BuildOtherTargets (BeforeThisTargets, null, out executeOnErrors);
219 if (!result && executeOnErrors)
225 bool BuildAfterThisTargets (out bool executeOnErrors)
227 executeOnErrors = false;
228 //missing_target handler not required as these are picked from actual target's
229 //"Before/AfterTargets attributes!
230 bool result = BuildOtherTargets (AfterThisTargets, null, out executeOnErrors);
231 if (!result && executeOnErrors)
238 bool BuildOtherTargets (IEnumerable<string> targetNames, Action<string> missing_target, out bool executeOnErrors)
240 executeOnErrors = false;
241 if (targetNames == null)
245 foreach (string target_name in targetNames) {
246 var t = project.Targets [target_name.Trim ()];
248 if (missing_target != null)
249 missing_target (target_name);
253 if (t.BuildState == BuildState.NotStarted)
254 if (!t.Build (null, out executeOnErrors))
257 if (t.BuildState == BuildState.Started)
258 throw new InvalidProjectFileException ("Cycle in target dependencies detected");
264 bool DoBuild (out bool executeOnErrors)
266 executeOnErrors = false;
269 if (BuildTasks.Count == 0)
274 result = batchingImpl.Build (this, out executeOnErrors);
275 } catch (Exception e) {
276 LogError ("Error building target {0}: {1}", Name, e.Message);
277 LogMessage (MessageImportance.Low, "Error building target {0}: {1}", Name, e.ToString ());
281 if (executeOnErrors == true)
287 void ExecuteOnErrors ()
289 foreach (XmlElement onError in onErrorElements) {
290 if (onError.GetAttribute ("ExecuteTargets") == String.Empty)
291 throw new InvalidProjectFileException ("ExecuteTargets attribute is required in OnError element.");
293 string on_error_condition = onError.GetAttribute ("Condition");
294 if (!ConditionParser.ParseAndEvaluate (on_error_condition, Project)) {
295 LogMessage (MessageImportance.Low,
296 "OnError for target {0} skipped due to false condition: {1}",
297 Name, on_error_condition);
301 string[] targetsToExecute = onError.GetAttribute ("ExecuteTargets").Split (';');
302 foreach (string t in targetsToExecute)
303 this.project.Targets [t].Build ();
307 void LogTargetSkipped ()
309 BuildMessageEventArgs bmea;
310 bmea = new BuildMessageEventArgs (String.Format (
311 "Target {0} skipped, as it has already been built.", Name),
312 null, null, MessageImportance.Low);
314 project.ParentEngine.EventSource.FireMessageRaised (this, bmea);
317 void LogError (string message, params object [] messageArgs)
320 throw new ArgumentException ("message");
322 BuildErrorEventArgs beea = new BuildErrorEventArgs (
323 null, null, null, 0, 0, 0, 0, String.Format (message, messageArgs),
325 engine.EventSource.FireErrorRaised (this, beea);
328 void LogMessage (MessageImportance importance, string message, params object [] messageArgs)
331 throw new ArgumentNullException ("message");
333 BuildMessageEventArgs bmea = new BuildMessageEventArgs (
334 String.Format (message, messageArgs), null,
336 engine.EventSource.FireMessageRaised (this, bmea);
339 public string Condition {
340 get { return targetElement.GetAttribute ("Condition"); }
341 set { targetElement.SetAttribute ("Condition", value); }
344 public string DependsOnTargets {
345 get { return targetElement.GetAttribute ("DependsOnTargets"); }
346 set { targetElement.SetAttribute ("DependsOnTargets", value); }
349 public bool IsImported {
350 get { return importedProject != null; }
357 internal Project Project {
358 get { return project; }
361 internal string TargetFile {
363 if (importedProject != null)
364 return importedProject.FullFileName;
365 return project != null ? project.FullFileName : String.Empty;
370 internal string BeforeTargets {
371 get { return targetElement.GetAttribute ("BeforeTargets"); }
374 internal string AfterTargets {
375 get { return targetElement.GetAttribute ("AfterTargets"); }
378 internal List<string> BeforeThisTargets { get; set; }
379 internal List<string> AfterThisTargets { get; set; }
382 internal List<BuildTask> BuildTasks {
383 get { return buildTasks; }
386 internal Engine Engine {
387 get { return engine; }
390 internal BuildState BuildState {
391 get { return buildState; }
394 public string Outputs {
395 get { return targetElement.GetAttribute ("Outputs"); }
396 set { targetElement.SetAttribute ("Outputs", value); }
399 ITaskItem [] OutputsAsITaskItems {
401 string outputs = targetElement.GetAttribute ("Outputs");
402 if (outputs == String.Empty)
403 return new ITaskItem [0];
405 Expression e = new Expression ();
406 e.Parse (outputs, ParseOptions.AllowItemsNoMetadataAndSplit);
408 return (ITaskItem []) e.ConvertTo (project, typeof (ITaskItem []));
413 internal enum BuildState {