Jo Shields 8b9b85e7f5 Imported Upstream version 3.10.0
Former-commit-id: 172c8e3c300b39d5785c7a3e8dfb08ebdbc1a99b
2014-10-04 11:27:48 +01:00

289 lines
8.9 KiB
C#

//
// TargetBatchingImpl.cs: Class that implements Target Batching Algorithm from the wiki.
//
// Author:
// Marek Sieradzki (marek.sieradzki@gmail.com)
// Ankit Jain (jankit@novell.com)
//
// (C) 2005 Marek Sieradzki
// Copyright 2008 Novell, Inc (http://www.novell.com)
// Copyright 2009 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using System;
using System.IO;
using System.Collections.Generic;
using System.Xml;
using Microsoft.Build.Framework;
namespace Microsoft.Build.BuildEngine {
internal class TargetBatchingImpl : BatchingImplBase
{
string inputs;
string outputs;
string name;
public TargetBatchingImpl (Project project, XmlElement targetElement)
: base (project)
{
if (targetElement == null)
throw new ArgumentNullException ("targetElement");
inputs = targetElement.GetAttribute ("Inputs");
outputs = targetElement.GetAttribute ("Outputs");
name = targetElement.GetAttribute ("Name");
}
public bool Build (Target target, out bool executeOnErrors)
{
executeOnErrors = false;
try {
Init ();
ParseTargetAttributes (target);
BatchAndPrepareBuckets ();
return Run (target, out executeOnErrors);
} finally {
consumedItemsByName = null;
consumedMetadataReferences = null;
consumedQMetadataReferences = null;
consumedUQMetadataReferences = null;
batchedItemsByName = null;
commonItemsByName = null;
}
}
bool Run (Target target, out bool executeOnErrors)
{
executeOnErrors = false;
if (buckets.Count > 0) {
foreach (Dictionary<string, BuildItemGroup> bucket in buckets)
if (!RunTargetWithBucket (bucket, target, out executeOnErrors))
return false;
return true;
} else {
return RunTargetWithBucket (null, target, out executeOnErrors);
}
}
bool RunTargetWithBucket (Dictionary<string, BuildItemGroup> bucket, Target target, out bool executeOnErrors)
{
bool target_result = true;
executeOnErrors = false;
LogTargetStarted (target);
if (bucket != null)
project.PushBatch (bucket, commonItemsByName);
try {
TaskExecutionMode taskExecutionMode;
string reason;
bool skip_completely;
if (!BuildTargetNeeded (out reason, out skip_completely)) {
LogTargetSkipped (target, reason);
if (skip_completely)
return true;
taskExecutionMode = TaskExecutionMode.SkipAndSetOutput;
} else {
taskExecutionMode = TaskExecutionMode.Complete;
if (!String.IsNullOrEmpty (reason))
target.Engine.LogMessage (MessageImportance.Low, reason);
}
for (int i = 0; i < target.BuildTasks.Count; i ++) {
//FIXME: parsing attributes repeatedly
IBuildTask bt = target.BuildTasks [i];
// HACK: need some form of cross references checks
var tem = taskExecutionMode;
if (tem == TaskExecutionMode.SkipAndSetOutput) {
var bti = bt as BuildTask;
//
// BuildTargetNeeded checks only files timestamps but ignores any metadata dependencies
// that way we can end up in the situation when output metadata are populated but from
// incomplete dependencies.
//
// E.g.
// <CreateItem Include="$(IntermediateOutputPath)%(_PngImage.LogicalName)" AdditionalMetadata="LogicalName=%(_PngImage.LogicalName)">
// <Output TaskParameter="Include" />
// </CreateItem>
//
if (bti != null && bti.Name == "CreateItem")
tem = TaskExecutionMode.Complete;
}
TaskBatchingImpl batchingImpl = new TaskBatchingImpl (project);
bool task_result = batchingImpl.Build (bt, tem, out executeOnErrors);
if (task_result)
continue;
// task failed, if ContinueOnError,
// ignore failed state for target
target_result = bt.ContinueOnError;
if (!bt.ContinueOnError) {
executeOnErrors = true;
return false;
}
}
} finally {
if (bucket != null)
project.PopBatch ();
LogTargetFinished (target, target_result);
}
return target_result;
}
// Parse target's Input and Output attributes to get list of referenced
// metadata and items to determine batching
void ParseTargetAttributes (Target target)
{
if (!String.IsNullOrEmpty (inputs))
ParseAttribute (inputs);
if (!String.IsNullOrEmpty (outputs))
ParseAttribute (outputs);
}
bool BuildTargetNeeded (out string reason, out bool skipCompletely)
{
reason = String.Empty;
ITaskItem [] inputFiles;
ITaskItem [] outputFiles;
DateTime youngestInput, oldestOutput;
skipCompletely = false;
if (String.IsNullOrEmpty (inputs.Trim ())) {
return true;
}
if (String.IsNullOrEmpty (outputs.Trim ())) {
project.ParentEngine.LogError ("Target {0} has inputs but no outputs specified.", name);
return true;
}
Expression e = new Expression ();
e.Parse (inputs, ParseOptions.AllowItemsMetadataAndSplit);
inputFiles = (ITaskItem[]) e.ConvertTo (project, typeof (ITaskItem[]), ExpressionOptions.ExpandItemRefs);
e = new Expression ();
e.Parse (outputs, ParseOptions.AllowItemsMetadataAndSplit);
outputFiles = (ITaskItem[]) e.ConvertTo (project, typeof (ITaskItem[]), ExpressionOptions.ExpandItemRefs);
if (outputFiles == null || outputFiles.Length == 0) {
reason = String.Format ("No output files were specified for target {0}, skipping.", name);
return false;
}
if (inputFiles == null || inputFiles.Length == 0) {
skipCompletely = true;
reason = String.Format ("No input files were specified for target {0}, skipping.", name);
return false;
}
youngestInput = DateTime.MinValue;
oldestOutput = DateTime.MaxValue;
string youngestInputFile, oldestOutputFile;
youngestInputFile = oldestOutputFile = String.Empty;
foreach (ITaskItem item in inputFiles) {
string file = item.ItemSpec.Trim ();
if (file.Length == 0)
continue;
if (!File.Exists (file)) {
reason = String.Format ("Target {0} needs to be built as input file '{1}' does not exist.", name, file);
return true;
}
DateTime lastWriteTime = File.GetLastWriteTime (file);
if (lastWriteTime > youngestInput) {
youngestInput = lastWriteTime;
youngestInputFile = file;
}
}
foreach (ITaskItem item in outputFiles) {
string file = item.ItemSpec.Trim ();
if (file.Length == 0)
continue;
if (!File.Exists (file)) {
reason = String.Format ("Target {0} needs to be built as output file '{1}' does not exist.", name, file);
return true;
}
DateTime lastWriteTime = File.GetLastWriteTime (file);
if (lastWriteTime < oldestOutput) {
oldestOutput = lastWriteTime;
oldestOutputFile = file;
}
}
if (youngestInput > oldestOutput) {
reason = String.Format ("Target {0} needs to be built as input file '{1}' is newer than output file '{2}'",
name, youngestInputFile, oldestOutputFile);
return true;
}
return false;
}
void LogTargetSkipped (Target target, string reason)
{
BuildMessageEventArgs bmea;
bmea = new BuildMessageEventArgs (String.IsNullOrEmpty (reason)
? String.Format ("Skipping target \"{0}\" because its outputs are up-to-date.", target.Name)
: reason,
null, "MSBuild", MessageImportance.Normal);
target.Engine.EventSource.FireMessageRaised (this, bmea);
}
void LogTargetStarted (Target target)
{
TargetStartedEventArgs tsea;
string projectFile = project.FullFileName;
tsea = new TargetStartedEventArgs (String.Format ("Target {0} started.", target.Name), null,
target.Name, projectFile, target.TargetFile);
target.Engine.EventSource.FireTargetStarted (this, tsea);
}
void LogTargetFinished (Target target, bool succeeded)
{
TargetFinishedEventArgs tfea;
string projectFile = project.FullFileName;
tfea = new TargetFinishedEventArgs (String.Format ("Target {0} finished.", target.Name), null,
target.Name, projectFile, target.TargetFile, succeeded);
target.Engine.EventSource.FireTargetFinished (this, tfea);
}
}
}