2 // TargetBatchingImpl.cs: Class that implements Target Batching Algorithm from the wiki.
5 // Marek Sieradzki (marek.sieradzki@gmail.com)
6 // Ankit Jain (jankit@novell.com)
8 // (C) 2005 Marek Sieradzki
9 // Copyright 2008 Novell, Inc (http://www.novell.com)
10 // Copyright 2009 Novell, Inc (http://www.novell.com)
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 using System.Collections.Generic;
36 using Microsoft.Build.Framework;
38 namespace Microsoft.Build.BuildEngine {
40 internal class TargetBatchingImpl : BatchingImplBase
46 public TargetBatchingImpl (Project project, XmlElement targetElement)
49 if (targetElement == null)
50 throw new ArgumentNullException ("targetElement");
52 inputs = targetElement.GetAttribute ("Inputs");
53 outputs = targetElement.GetAttribute ("Outputs");
54 name = targetElement.GetAttribute ("Name");
57 public bool Build (Target target, out bool executeOnErrors)
59 executeOnErrors = false;
62 if (!BuildTargetNeeded (out reason)) {
63 LogTargetStarted (target);
64 LogTargetSkipped (target, reason);
65 LogTargetFinished (target, true);
69 if (!String.IsNullOrEmpty (reason))
70 target.Engine.LogMessage (MessageImportance.Low, reason);
74 ParseTargetAttributes (target);
75 BatchAndPrepareBuckets ();
76 return Run (target, out executeOnErrors);
78 consumedItemsByName = null;
79 consumedMetadataReferences = null;
80 consumedQMetadataReferences = null;
81 consumedUQMetadataReferences = null;
82 batchedItemsByName = null;
83 commonItemsByName = null;
87 bool Run (Target target, out bool executeOnErrors)
89 executeOnErrors = false;
90 if (buckets.Count > 0) {
91 foreach (Dictionary<string, BuildItemGroup> bucket in buckets)
92 if (!RunTargetWithBucket (bucket, target, out executeOnErrors))
97 return RunTargetWithBucket (null, target, out executeOnErrors);
101 bool RunTargetWithBucket (Dictionary<string, BuildItemGroup> bucket, Target target, out bool executeOnErrors)
103 bool target_result = true;
104 executeOnErrors = false;
106 LogTargetStarted (target);
108 project.PushBatch (bucket, commonItemsByName);
111 if (!BuildTargetNeeded (out reason)) {
112 LogTargetSkipped (target, reason);
116 if (!String.IsNullOrEmpty (reason))
117 target.Engine.LogMessage (MessageImportance.Low, reason);
119 for (int i = 0; i < target.BuildTasks.Count; i ++) {
120 //FIXME: parsing attributes repeatedly
121 BuildTask bt = target.BuildTasks [i];
123 TaskBatchingImpl batchingImpl = new TaskBatchingImpl (project);
124 bool task_result = batchingImpl.Build (bt, out executeOnErrors);
128 // task failed, if ContinueOnError,
129 // ignore failed state for target
130 target_result = bt.ContinueOnError;
132 if (!bt.ContinueOnError) {
133 executeOnErrors = true;
141 LogTargetFinished (target, target_result);
144 return target_result;
147 // Parse target's Input and Output attributes to get list of referenced
148 // metadata and items to determine batching
149 void ParseTargetAttributes (Target target)
151 if (!String.IsNullOrEmpty (inputs))
152 ParseAttribute (inputs);
154 if (!String.IsNullOrEmpty (outputs))
155 ParseAttribute (outputs);
158 bool BuildTargetNeeded (out string reason)
160 reason = String.Empty;
161 ITaskItem [] inputFiles;
162 ITaskItem [] outputFiles;
163 DateTime youngestInput, oldestOutput;
165 if (String.IsNullOrEmpty (inputs.Trim ()))
168 if (String.IsNullOrEmpty (outputs.Trim ())) {
169 project.ParentEngine.LogError ("Target {0} has inputs but no outputs specified.", name);
173 Expression e = new Expression ();
174 e.Parse (inputs, ParseOptions.AllowItemsMetadataAndSplit);
175 inputFiles = (ITaskItem[]) e.ConvertTo (project, typeof (ITaskItem[]), ExpressionOptions.ExpandItemRefs);
177 e = new Expression ();
178 e.Parse (outputs, ParseOptions.AllowItemsMetadataAndSplit);
179 outputFiles = (ITaskItem[]) e.ConvertTo (project, typeof (ITaskItem[]), ExpressionOptions.ExpandItemRefs);
181 if (outputFiles == null || outputFiles.Length == 0) {
182 reason = String.Format ("No output files were specified for target {0}, skipping.", name);
186 if (inputFiles == null || inputFiles.Length == 0) {
187 reason = String.Format ("No input files were specified for target {0}, skipping.", name);
191 youngestInput = DateTime.MinValue;
192 oldestOutput = DateTime.MaxValue;
194 string youngestInputFile, oldestOutputFile;
195 youngestInputFile = oldestOutputFile = String.Empty;
196 foreach (ITaskItem item in inputFiles) {
197 string file = item.ItemSpec.Trim ();
198 if (file.Length == 0)
201 if (!File.Exists (file)) {
202 reason = String.Format ("Target {0} needs to be built as input file '{1}' does not exist.", name, file);
206 DateTime lastWriteTime = File.GetLastWriteTime (file);
207 if (lastWriteTime > youngestInput) {
208 youngestInput = lastWriteTime;
209 youngestInputFile = file;
213 foreach (ITaskItem item in outputFiles) {
214 string file = item.ItemSpec.Trim ();
215 if (file.Length == 0)
218 if (!File.Exists (file)) {
219 reason = String.Format ("Target {0} needs to be built as output file '{1}' does not exist.", name, file);
223 DateTime lastWriteTime = File.GetLastWriteTime (file);
224 if (lastWriteTime < oldestOutput) {
225 oldestOutput = lastWriteTime;
226 oldestOutputFile = file;
230 if (youngestInput > oldestOutput) {
231 reason = String.Format ("Target {0} needs to be built as input file '{1}' is newer than output file '{2}'",
232 name, youngestInputFile, oldestOutputFile);
239 void LogTargetSkipped (Target target, string reason)
241 BuildMessageEventArgs bmea;
242 bmea = new BuildMessageEventArgs (String.IsNullOrEmpty (reason)
243 ? String.Format ("Skipping target \"{0}\" because its outputs are up-to-date.", target.Name)
245 null, "MSBuild", MessageImportance.Normal);
246 target.Engine.EventSource.FireMessageRaised (this, bmea);
249 void LogTargetStarted (Target target)
251 TargetStartedEventArgs tsea;
252 string projectFile = project.FullFileName;
253 tsea = new TargetStartedEventArgs (String.Format ("Target {0} started.", target.Name), null,
254 target.Name, projectFile, target.TargetFile);
255 target.Engine.EventSource.FireTargetStarted (this, tsea);
258 void LogTargetFinished (Target target, bool succeeded)
260 TargetFinishedEventArgs tfea;
261 string projectFile = project.FullFileName;
262 tfea = new TargetFinishedEventArgs (String.Format ("Target {0} finished.", target.Name), null,
263 target.Name, projectFile, target.TargetFile, succeeded);
264 target.Engine.EventSource.FireTargetFinished (this, tfea);