X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=blobdiff_plain;f=mcs%2Fclass%2FMicrosoft.Build.Engine%2FMicrosoft.Build.BuildEngine%2FTarget.cs;h=43affc109ccacffbb3edaf0250f22de635d88a9c;hb=f29e8e450199e224c77bc08316040ebaeae0c650;hp=e95d8dfde82e2bdc773c9016ac32cd54f45da34f;hpb=5bbfa8860b090e465a3aa45edeb9c94481ef1a22;p=mono.git diff --git a/mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/Target.cs b/mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/Target.cs index e95d8dfde82..43affc109cc 100644 --- a/mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/Target.cs +++ b/mcs/class/Microsoft.Build.Engine/Microsoft.Build.BuildEngine/Target.cs @@ -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 onErrorElements; List 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 (); this.buildState = BuildState.NotStarted; this.buildTasks = new List (); - 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 must be last under element . Found element 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 == "") { - ; - } 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 targetNames, Action 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) { - if (this.batchingImpl.BatchBuildTask (bt) == false && bt.ContinueOnError == false) { - executeOnErrors = true; - result = false; - 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) { - //taskElements.Remove (taskElement); - 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 BeforeThisTargets { get; set; } + internal List AfterThisTargets { get; set; } +#endif + + internal List 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 {