[xbuild] Add missing api for Target.
[mono.git] / mcs / class / Microsoft.Build.Engine / Microsoft.Build.BuildEngine / Target.cs
index 2d3457b078c81b32cabd05268650d139ab6391d7..43affc109ccacffbb3edaf0250f22de635d88a9c 100644 (file)
@@ -32,23 +32,22 @@ using System.Collections;
 using System.Collections.Generic;
 using System.Xml;
 using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
 
 namespace Microsoft.Build.BuildEngine {
        public class Target : IEnumerable {
        
-               BatchingImpl    batchingImpl;
+               TargetBatchingImpl batchingImpl;
                BuildState      buildState;
-               XmlAttribute    condition;
-               XmlAttribute    dependsOnTargets;
                Engine          engine;
-               bool            isImported;
+               ImportedProject importedProject;
                string          name;
                Project         project;
                XmlElement      targetElement;
                List <XmlElement>       onErrorElements;
                List <BuildTask>        buildTasks;
                
-               internal Target (XmlElement targetElement, Project project)
+               internal Target (XmlElement targetElement, Project project, ImportedProject importedProject)
                {
                        if (project == null)
                                throw new ArgumentNullException ("project");
@@ -57,166 +56,288 @@ namespace Microsoft.Build.BuildEngine {
 
                        this.targetElement = targetElement;
                        this.name = targetElement.GetAttribute ("Name");
-                       this.condition = targetElement.GetAttributeNode ("Condition");
-                       this.dependsOnTargets = targetElement.GetAttributeNode ("DependsOnTargets");
 
                        this.project = project;
                        this.engine = project.ParentEngine;
-                       this.isImported = false;;
+                       this.importedProject = importedProject;
 
                        this.onErrorElements  = new List <XmlElement> ();
                        this.buildState = BuildState.NotStarted;
                        this.buildTasks = new List <BuildTask> ();
-                       this.batchingImpl = new BatchingImpl (project, this.targetElement);
+                       this.batchingImpl = new TargetBatchingImpl (project, this.targetElement);
 
+                       bool onErrorFound = false;
                        foreach (XmlNode xn in targetElement.ChildNodes) {
                                if (xn is XmlElement) {
                                        XmlElement xe = (XmlElement) xn;
                                        if (xe.Name == "OnError") {
                                                onErrorElements.Add (xe);
-                                               continue;
-                                       }
-                                       buildTasks.Add (new BuildTask (xe, this));
+                                               onErrorFound = true;
+                                       } else if (onErrorFound)
+                                               throw new InvalidProjectFileException (
+                                                       "The element <OnError> must be last under element <Target>. Found element <Error> instead.");
+                                       else
+                                               buildTasks.Add (new BuildTask (xe, this));
                                }
                        }
                }
                
-               internal bool Build ()
+               [MonoTODO]
+               public BuildTask AddNewTask (string taskName)
                {
-                       bool result;
+                       if (taskName == null)
+                               throw new ArgumentNullException ("taskName");
                
-                       buildState = BuildState.Started;
-                       if (dependsOnTargets == null) {
-                               ;
-                       } else if (dependsOnTargets.Value == String.Empty) {
-                               ;
-                       } else {
-                               OldExpression dependencies = new OldExpression (Project);
-                               dependencies.ParseSource (dependsOnTargets.Value);
-                               
-                               string[] targetsToBuildFirst = (string[]) dependencies.ConvertTo (typeof (string[]));
-                               foreach (string target in targetsToBuildFirst) {
-                                       string trimmed = target.Trim ();
-                                       Target t = (Target) project.Targets [trimmed];
-                                       if (t == null)
-                                               throw new InvalidProjectFileException (String.Format ("Target {0} not found.", trimmed));
-                                       if (t.BuildState == BuildState.NotStarted) {
-                                               t.Build ();
-                                       }
-                                       if (t.BuildState == BuildState.Started)
-                                               throw new InvalidProjectFileException ("Cycle in target dependencies detected.");
-                               }
-                       }
-                       
-                       result = RealBuild ();
-                       buildState = BuildState.Finished;
+                       XmlElement task = project.XmlDocument.CreateElement (taskName, Project.XmlNamespace);
+                       targetElement.AppendChild (task);
+                       BuildTask bt = new BuildTask (task, this);
+                       buildTasks.Add (bt);
                        
+                       return bt;
+               }
+
+               public IEnumerator GetEnumerator ()
+               {
+                       foreach (BuildTask bt in buildTasks)
+                               yield return bt;
+               }
+
+               // FIXME: shouldn't we remove it from XML?
+               public void RemoveTask (BuildTask buildTask)
+               {
+                       if (buildTask == null)
+                               throw new ArgumentNullException ("buildTask");
+                       buildTasks.Remove (buildTask);
+               }
+
+               bool Build ()
+               {
+                       return Build (null);
+               }
+
+               internal bool Build (string built_targets_key)
+               {
+                       bool executeOnErrors;
+                       return Build (built_targets_key, out executeOnErrors);
+               }
+
+               bool Build (string built_targets_key, out bool executeOnErrors)
+               {
+                       project.PushThisFileProperty (TargetFile);
+                       try {
+                               return BuildActual (built_targets_key, out executeOnErrors);
+                       } finally {
+                               project.PopThisFileProperty ();
+                       }
+               }
+
+               bool BuildActual (string built_targets_key, out bool executeOnErrors)
+               {
+                       bool result = false;
+                       executeOnErrors = false;
+
+                       // built targets are keyed by the particular set of global
+                       // properties. So, a different set could allow a target
+                       // to run again
+                       built_targets_key = project.GetKeyForTarget (Name);
+                       if (project.ParentEngine.BuiltTargetsOutputByName.ContainsKey (built_targets_key)) {
+                               LogTargetSkipped ();
+                               return true;
+                       }
+
+                       // Push a null/empty batch, effectively clearing it
+                       project.PushBatch (null, null);
+                       if (!ConditionParser.ParseAndEvaluate (Condition, Project)) {
+                               LogMessage (MessageImportance.Low,
+                                               "Target {0} skipped due to false condition: {1}",
+                                               Name, Condition);
+                               project.PopBatch ();
+                               return true;
+                       }
+
+                       try {
+                               buildState = BuildState.Started;
+
+#if NET_4_0
+                               result = BuildDependencies (out executeOnErrors) &&
+                                               BuildBeforeThisTargets (out executeOnErrors) &&
+                                               DoBuild (out executeOnErrors) && // deps & Before targets built fine, do main build
+                                               BuildAfterThisTargets (out executeOnErrors);
+#else
+                               result = BuildDependencies (out executeOnErrors) && DoBuild (out executeOnErrors);
+#endif
+
+                               buildState = BuildState.Finished;
+                       } catch (Exception e) {
+                               LogError ("Error building target {0}: {1}", Name, e.ToString ());
+                               return false;
+                       } finally {
+                               project.PopBatch ();
+                       }
+
+                       project.ParentEngine.BuiltTargetsOutputByName [built_targets_key] = (ITaskItem[]) OutputsAsITaskItems.Clone ();
+
+                       return result;
+               }
+
+               bool BuildDependencies (out bool executeOnErrors)
+               {
+                       executeOnErrors = false;
+
+                       if (String.IsNullOrEmpty (DependsOnTargets))
+                               return true;
+
+                       var expr = new Expression ();
+                       expr.Parse (DependsOnTargets, ParseOptions.AllowItemsNoMetadataAndSplit);
+                       string [] targetNames = (string []) expr.ConvertTo (Project, typeof (string []));
+
+                       bool result = BuildOtherTargets (targetNames,
+                                                       tname => engine.LogError ("Target '{0}', a dependency of target '{1}', not found.",
+                                                                               tname, Name),
+                                                       out executeOnErrors);
+                       if (!result && executeOnErrors)
+                               ExecuteOnErrors ();
+
                        return result;
                }
+
+#if NET_4_0
+               bool BuildBeforeThisTargets (out bool executeOnErrors)
+               {
+                       executeOnErrors = false;
+                       bool result = BuildOtherTargets (BeforeThisTargets, null, out executeOnErrors);
+                       if (!result && executeOnErrors)
+                               ExecuteOnErrors ();
+
+                       return result;
+               }
+
+               bool BuildAfterThisTargets (out bool executeOnErrors)
+               {
+                       executeOnErrors = false;
+                       //missing_target handler not required as these are picked from actual target's
+                       //"Before/AfterTargets attributes!
+                       bool result = BuildOtherTargets (AfterThisTargets, null, out executeOnErrors);
+                       if (!result && executeOnErrors)
+                               ExecuteOnErrors ();
+
+                       return result;
+               }
+#endif
+
+               bool BuildOtherTargets (IEnumerable<string> targetNames, Action<string> missing_target, out bool executeOnErrors)
+               {
+                       executeOnErrors = false;
+                       if (targetNames == null)
+                               // nothing to build
+                               return true;
+
+                       foreach (string target_name in targetNames) {
+                               var t = project.Targets [target_name.Trim ()];
+                               if (t == null) {
+                                       if (missing_target != null)
+                                               missing_target (target_name);
+                                       return false;
+                               }
+
+                               if (t.BuildState == BuildState.NotStarted)
+                                       if (!t.Build (null, out executeOnErrors))
+                                               return false;
+
+                               if (t.BuildState == BuildState.Started)
+                                       throw new InvalidProjectFileException ("Cycle in target dependencies detected");
+                       }
+
+                       return true;
+               }
                
-               private bool RealBuild ()
+               bool DoBuild (out bool executeOnErrors)
                {
-                       bool executeOnErrors = false;
+                       executeOnErrors = false;
                        bool result = true;
+
+                       if (BuildTasks.Count == 0)
+                               // nothing to do
+                               return true;
                
-                       LogTargetStarted ();
-                       
-                       if (this.batchingImpl.BuildNeeded ()) {
-                               foreach (BuildTask bt in buildTasks) {
-                                       result = batchingImpl.BatchBuildTask (bt);
-                               
-                                       if (!result && !bt.ContinueOnError) {
-                                               executeOnErrors = true;
-                                               break;
-                                       }
-                               }
-                       } else {
-                               LogTargetSkipped ();
+                       try {
+                               result = batchingImpl.Build (this, out executeOnErrors);
+                       } catch (Exception e) {
+                               LogError ("Error building target {0}: {1}", Name, e.Message);
+                               LogMessage (MessageImportance.Low, "Error building target {0}: {1}", Name, e.ToString ());
+                               return false;
                        }
 
-                       LogTargetFinished (result);
-                       
                        if (executeOnErrors == true)
                                ExecuteOnErrors ();
                                
                        return result;
                }
                
-               private void ExecuteOnErrors ()
+               void ExecuteOnErrors ()
                {
                        foreach (XmlElement onError in onErrorElements) {
-                               // FIXME: add condition
                                if (onError.GetAttribute ("ExecuteTargets") == String.Empty)
                                        throw new InvalidProjectFileException ("ExecuteTargets attribute is required in OnError element.");
+
+                               string on_error_condition = onError.GetAttribute ("Condition");
+                               if (!ConditionParser.ParseAndEvaluate (on_error_condition, Project)) {
+                                       LogMessage (MessageImportance.Low,
+                                               "OnError for target {0} skipped due to false condition: {1}",
+                                               Name, on_error_condition);
+                                       continue;
+                               }
+
                                string[] targetsToExecute = onError.GetAttribute ("ExecuteTargets").Split (';');
                                foreach (string t in targetsToExecute)
                                        this.project.Targets [t].Build ();
                        }
                }
-               
-               private void LogTargetSkipped ()
+
+               void LogTargetSkipped ()
                {
                        BuildMessageEventArgs bmea;
-                       bmea = new BuildMessageEventArgs (String.Format ("Skipping target \"{0}\" because its outputs are up-to-date.",
-                               name), null, "MSBuild", MessageImportance.Normal);
-                       engine.EventSource.FireMessageRaised (this, bmea);
-               }
-               
-               private void LogTargetStarted ()
-               {
-                       TargetStartedEventArgs tsea;
-                       string projectFile = project.FullFileName;
-                       tsea = new TargetStartedEventArgs ("Target " + name + " started.", null, name, projectFile, null);
-                       engine.EventSource.FireTargetStarted (this, tsea);
-               }
-               
-               private void LogTargetFinished (bool succeeded)
-               {
-                       TargetFinishedEventArgs tfea;
-                       string projectFile = project.FullFileName;
-                       tfea = new TargetFinishedEventArgs ("Target " + name + " finished.", null, name, projectFile, null, succeeded);
-                       engine.EventSource.FireTargetFinished (this, tfea);
-               }
-       
-               [MonoTODO]
-               public BuildTask AddNewTask (string taskName)
-               {
-                       throw new NotImplementedException ();
+                       bmea = new BuildMessageEventArgs (String.Format (
+                                               "Target {0} skipped, as it has already been built.", Name),
+                                       null, null, MessageImportance.Low);
+
+                       project.ParentEngine.EventSource.FireMessageRaised (this, bmea);
                }
 
-               public IEnumerator GetEnumerator ()
+               void LogError (string message, params object [] messageArgs)
                {
-                       foreach (BuildTask bt in buildTasks) {
-                               yield return bt;
-                       }
+                       if (message == null)
+                               throw new ArgumentException ("message");
+
+                       BuildErrorEventArgs beea = new BuildErrorEventArgs (
+                               null, null, null, 0, 0, 0, 0, String.Format (message, messageArgs),
+                               null, null);
+                       engine.EventSource.FireErrorRaised (this, beea);
                }
 
-               public void RemoveTask (BuildTask buildTask)
+               void LogMessage (MessageImportance importance, string message, params object [] messageArgs)
                {
-                       buildTasks.Remove (buildTask);
-               }
+                       if (message == null)
+                               throw new ArgumentNullException ("message");
 
+                       BuildMessageEventArgs bmea = new BuildMessageEventArgs (
+                               String.Format (message, messageArgs), null,
+                               null, importance);
+                       engine.EventSource.FireMessageRaised (this, bmea);
+               }
+       
                public string Condition {
-                       get { return condition.Value; }
-                       set { condition.Value = value; }
+                       get { return targetElement.GetAttribute ("Condition"); }
+                       set { targetElement.SetAttribute ("Condition", value); }
                }
 
                public string DependsOnTargets {
-                       get {
-                               if (dependsOnTargets == null)
-                                       return null;
-                               else
-                                       return dependsOnTargets.Value;
-                       }
-                       set {
-                               if (dependsOnTargets != null)
-                                       dependsOnTargets.Value = value;
-                       }
+                       get { return targetElement.GetAttribute ("DependsOnTargets"); }
+                       set { targetElement.SetAttribute ("DependsOnTargets", value); }
                }
 
                public bool IsImported {
-                       get { return isImported; }
-                       internal set { isImported = value; }
+                       get { return importedProject != null; }
                }
 
                public string Name {
@@ -226,10 +347,57 @@ namespace Microsoft.Build.BuildEngine {
                internal Project Project {
                        get { return project; }
                }
+
+               internal string TargetFile {
+                       get {
+                               if (importedProject != null)
+                                       return importedProject.FullFileName;
+                               return project != null ? project.FullFileName : String.Empty;
+                       }
+               }
+
+#if NET_4_0
+               internal string BeforeTargets {
+                       get { return targetElement.GetAttribute ("BeforeTargets"); }
+               }
+
+               internal string AfterTargets {
+                       get { return targetElement.GetAttribute ("AfterTargets"); }
+               }
+
+               internal List<string> BeforeThisTargets { get; set; }
+               internal List<string> AfterThisTargets { get; set; }
+#endif
+
+               internal List<BuildTask> BuildTasks {
+                       get { return buildTasks; }
+               }
+
+               internal Engine Engine {
+                       get { return engine; }
+               }
                
                internal BuildState BuildState {
                        get { return buildState; }
                }
+
+               public string Outputs {
+                       get { return targetElement.GetAttribute ("Outputs"); }
+                       set { targetElement.SetAttribute ("Outputs", value); }
+               }
+
+               ITaskItem [] OutputsAsITaskItems {
+                       get {
+                               string outputs = targetElement.GetAttribute ("Outputs");
+                               if (outputs == String.Empty)
+                                       return new ITaskItem [0];
+
+                               Expression e = new Expression ();
+                               e.Parse (outputs, ParseOptions.AllowItemsNoMetadataAndSplit);
+
+                               return (ITaskItem []) e.ConvertTo (project, typeof (ITaskItem []));
+                       }
+               }
        }
        
        internal enum BuildState {