1 //
2 // ProjectCollection.cs
3 //
4 // Author:
5 //   Leszek Ciesielski (skolima@gmail.com)
6 //   Rolf Bjarne Kvinge (rolf@xamarin.com)
7 //   Atsushi Enomoto (atsushi@xamarin.com)
8 //
9 // (C) 2011 Leszek Ciesielski
10 // Copyright (C) 2011,2013 Xamarin Inc.
11 //
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:
19 //
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 //
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.
30 //
31 
32 using Microsoft.Build.Construction;
33 using Microsoft.Build.Execution;
34 using Microsoft.Build.Framework;
35 using Microsoft.Build.Logging;
36 using Microsoft.Build.Utilities;
37 using System;
38 using System.Collections.Generic;
39 using System.Collections.ObjectModel;
40 using System.IO;
41 using System.Linq;
42 using System.Xml;
43 using System.Reflection;
44 using System.Globalization;
45 using Mono.XBuild.Utilities;
46 using Microsoft.Build.Internal;
47 
48 namespace Microsoft.Build.Evaluation
49 {
50 	public class ProjectCollection : IDisposable
51 	{
ProjectAddedEventHandler(object sender, ProjectAddedToProjectCollectionEventArgs e)52 		public delegate void ProjectAddedEventHandler (object sender, ProjectAddedToProjectCollectionEventArgs e);
53 
54 		public class ProjectAddedToProjectCollectionEventArgs : EventArgs
55 		{
ProjectAddedToProjectCollectionEventArgs(ProjectRootElement element)56 			public ProjectAddedToProjectCollectionEventArgs (ProjectRootElement element)
57 			{
58 				if (element == null)
59 					throw new ArgumentNullException ("project");
60 				ProjectRootElement = element;
61 			}
62 
63 			public ProjectRootElement ProjectRootElement { get; private set; }
64 		}
65 
66 		// static members
67 
68 		static readonly ProjectCollection global_project_collection;
69 
ProjectCollection()70 		static ProjectCollection ()
71 		{
72 			global_project_collection = new ProjectCollection (new ReadOnlyDictionary<string, string> (new Dictionary<string, string> ()));
73 		}
74 
Escape(string unescapedString)75 		public static string Escape (string unescapedString)
76 		{
77 			return Mono.XBuild.Utilities.MSBuildUtils.Escape (unescapedString);
78 		}
79 
Unescape(string escapedString)80 		public static string Unescape (string escapedString)
81 		{
82 			return Mono.XBuild.Utilities.MSBuildUtils.Unescape (escapedString);
83 		}
84 
85 		public static ProjectCollection GlobalProjectCollection {
86 			get { return global_project_collection; }
87 		}
88 
89 		// semantic model part
90 
ProjectCollection()91 		public ProjectCollection ()
92 			: this (null)
93 		{
94 		}
95 
ProjectCollection(IDictionary<string, string> globalProperties)96 		public ProjectCollection (IDictionary<string, string> globalProperties)
97         	: this (globalProperties, null, ToolsetDefinitionLocations.Registry | ToolsetDefinitionLocations.ConfigurationFile)
98 		{
99 		}
100 
ProjectCollection(ToolsetDefinitionLocations toolsetLocations)101 		public ProjectCollection (ToolsetDefinitionLocations toolsetLocations)
102         	: this (null, null, toolsetLocations)
103 		{
104 		}
105 
ProjectCollection(IDictionary<string, string> globalProperties, IEnumerable<ILogger> loggers, ToolsetDefinitionLocations toolsetDefinitionLocations)106 		public ProjectCollection (IDictionary<string, string> globalProperties, IEnumerable<ILogger> loggers,
107 				ToolsetDefinitionLocations toolsetDefinitionLocations)
108 			: this (globalProperties, loggers, null, toolsetDefinitionLocations, 1, false)
109 		{
110 		}
111 
ProjectCollection(IDictionary<string, string> globalProperties, IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers, ToolsetDefinitionLocations toolsetDefinitionLocations, int maxNodeCount, bool onlyLogCriticalEvents)112 		public ProjectCollection (IDictionary<string, string> globalProperties,
113 				IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers,
114 				ToolsetDefinitionLocations toolsetDefinitionLocations,
115 				int maxNodeCount, bool onlyLogCriticalEvents)
116 		{
117 			global_properties = globalProperties ?? new Dictionary<string, string> ();
118 			this.loggers = loggers != null ? loggers.ToList () : new List<ILogger> ();
119 			toolset_locations = toolsetDefinitionLocations;
120 			MaxNodeCount = maxNodeCount;
121 			OnlyLogCriticalEvents = onlyLogCriticalEvents;
122 
123 			LoadDefaultToolsets ();
124 		}
125 
126 		[MonoTODO ("not fired yet")]
127 		public event ProjectAddedEventHandler ProjectAdded;
128 		[MonoTODO ("not fired yet")]
129 		public event EventHandler<ProjectChangedEventArgs> ProjectChanged;
130 		[MonoTODO ("not fired yet")]
131 		public event EventHandler<ProjectCollectionChangedEventArgs> ProjectCollectionChanged;
132 		[MonoTODO ("not fired yet")]
133 		public event EventHandler<ProjectXmlChangedEventArgs> ProjectXmlChanged;
134 
AddProject(Project project)135 		public void AddProject (Project project)
136 		{
137 			this.loaded_projects.Add (project);
138 			if (ProjectAdded != null)
139 				ProjectAdded (this, new ProjectAddedToProjectCollectionEventArgs (project.Xml));
140 		}
141 
142 		public int Count {
143 			get { return loaded_projects.Count; }
144 		}
145 
146 		string default_tools_version;
147 		public string DefaultToolsVersion {
148 			get { return default_tools_version; }
149 			set {
150 				if (GetToolset (value) == null)
151 					throw new InvalidOperationException (string.Format ("Toolset '{0}' does not exist", value));
152 				default_tools_version = value;
153 			}
154 		}
155 
Dispose()156 		public void Dispose ()
157 		{
158 			Dispose (true);
159 			GC.SuppressFinalize (this);
160 		}
161 
Dispose(bool disposing)162 		protected virtual void Dispose (bool disposing)
163 		{
164 			if (disposing) {
165 			}
166 		}
167 
GetLoadedProjects(string fullPath)168 		public ICollection<Project> GetLoadedProjects (string fullPath)
169 		{
170 			return LoadedProjects.Where (p => p.FullPath != null && Path.GetFullPath (p.FullPath) == Path.GetFullPath (fullPath)).ToList ();
171 		}
172 
173 		readonly IDictionary<string, string> global_properties;
174 
175 		public IDictionary<string, string> GlobalProperties {
176 			get { return global_properties; }
177 		}
178 
179 		readonly List<Project> loaded_projects = new List<Project> ();
180 
LoadProject(string fileName)181 		public Project LoadProject (string fileName)
182 		{
183 			return LoadProject (fileName, DefaultToolsVersion);
184 		}
185 
LoadProject(string fileName, string toolsVersion)186 		public Project LoadProject (string fileName, string toolsVersion)
187 		{
188 			return LoadProject (fileName, null, toolsVersion);
189 		}
190 
LoadProject(string fileName, IDictionary<string,string> globalProperties, string toolsVersion)191 		public Project LoadProject (string fileName, IDictionary<string,string> globalProperties, string toolsVersion)
192 		{
193 			var ret = new Project (fileName, globalProperties, toolsVersion);
194 			loaded_projects.Add (ret);
195 			return ret;
196 		}
197 
198 		// These methods somehow don't add the project to ProjectCollection...
LoadProject(XmlReader xmlReader)199 		public Project LoadProject (XmlReader xmlReader)
200 		{
201 			return LoadProject (xmlReader, DefaultToolsVersion);
202 		}
203 
LoadProject(XmlReader xmlReader, string toolsVersion)204 		public Project LoadProject (XmlReader xmlReader, string toolsVersion)
205 		{
206 			return LoadProject (xmlReader, null, toolsVersion);
207 		}
208 
LoadProject(XmlReader xmlReader, IDictionary<string,string> globalProperties, string toolsVersion)209 		public Project LoadProject (XmlReader xmlReader, IDictionary<string,string> globalProperties, string toolsVersion)
210 		{
211 			return new Project (xmlReader, globalProperties, toolsVersion);
212 		}
213 
214 		public ICollection<Project> LoadedProjects {
215 			get { return loaded_projects; }
216 		}
217 
218 		readonly List<ILogger> loggers = new List<ILogger> ();
219 
220 		public ICollection<ILogger> Loggers {
221 			get { return loggers; }
222 		}
223 
224 		[MonoTODO]
225 		public bool OnlyLogCriticalEvents { get; set; }
226 
227 		[MonoTODO]
228 		public bool SkipEvaluation { get; set; }
229 
230 		readonly ToolsetDefinitionLocations toolset_locations;
231 		public ToolsetDefinitionLocations ToolsetLocations {
232 			get { return toolset_locations; }
233 		}
234 
235 		readonly List<Toolset> toolsets = new List<Toolset> ();
236 		// so what should we do without ToolLocationHelper in Microsoft.Build.Utilities.dll? There is no reference to it in this dll.
237 		public ICollection<Toolset> Toolsets {
238 			// For ConfigurationFile and None, they cannot be added externally.
239 			get { return (ToolsetLocations & ToolsetDefinitionLocations.Registry) != 0 ? toolsets : toolsets.ToList (); }
240 		}
241 
GetToolset(string toolsVersion)242 		public Toolset GetToolset (string toolsVersion)
243 		{
244 			return Toolsets.FirstOrDefault (t => t.ToolsVersion == toolsVersion);
245 		}
246 
247 		//FIXME: should also support config file, depending on ToolsetLocations
LoadDefaultToolsets()248 		void LoadDefaultToolsets ()
249 		{
250 			AddToolset (new Toolset ("4.0",
251 				ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version40), this, null));
252 #if XBUILD_12
253 			AddToolset (new Toolset ("12.0", ToolLocationHelper.GetPathToBuildTools ("12.0"), this, null));
254 #endif
255 #if XBUILD_14
256 			AddToolset (new Toolset ("14.0", ToolLocationHelper.GetPathToBuildTools ("14.0"), this, null));
257 #endif
258 
259 			// We don't support these anymore
260 			AddToolset (new Toolset ("2.0",
261 				ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version20), this, null));
262 			AddToolset (new Toolset ("3.0",
263 				ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version30), this, null));
264 			AddToolset (new Toolset ("3.5",
265 				ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version35), this, null));
266 
267 			default_tools_version = toolsets [0].ToolsVersion;
268 		}
269 
270 		[MonoTODO ("not verified at all")]
AddToolset(Toolset toolset)271 		public void AddToolset (Toolset toolset)
272 		{
273 			toolsets.Add (toolset);
274 		}
275 
276 		[MonoTODO ("not verified at all")]
RemoveAllToolsets()277 		public void RemoveAllToolsets ()
278 		{
279 			toolsets.Clear ();
280 		}
281 
RegisterLogger(ILogger logger)282 		public void RegisterLogger (ILogger logger)
283 		{
284 			loggers.Add (logger);
285 		}
286 
RegisterLoggers(IEnumerable<ILogger> loggers)287 		public void RegisterLoggers (IEnumerable<ILogger> loggers)
288 		{
289 			foreach (var logger in loggers)
290 				this.loggers.Add (logger);
291 		}
292 
UnloadAllProjects()293 		public void UnloadAllProjects ()
294 		{
295 			throw new NotImplementedException ();
296 		}
297 
298 		[MonoTODO ("Not verified at all")]
UnloadProject(Project project)299 		public void UnloadProject (Project project)
300 		{
301 			this.loaded_projects.Remove (project);
302 		}
303 
304 		[MonoTODO ("Not verified at all")]
UnloadProject(ProjectRootElement projectRootElement)305 		public void UnloadProject (ProjectRootElement projectRootElement)
306 		{
307 			foreach (var proj in loaded_projects.Where (p => p.Xml == projectRootElement).ToArray ())
308 				UnloadProject (proj);
309 		}
310 
311 		public static Version Version {
312 			get { throw new NotImplementedException (); }
313 		}
314 
315 		// Execution part
316 
317 		[MonoTODO]
318 		public bool DisableMarkDirty { get; set; }
319 
320 		[MonoTODO]
321 		public HostServices HostServices { get; set; }
322 
323 		[MonoTODO]
324 		public bool IsBuildEnabled { get; set; }
325 
326 		internal string BuildStartupDirectory { get; set; }
327 
328 		internal int MaxNodeCount { get; private set; }
329 
330 		Stack<string> ongoing_imports = new Stack<string> ();
331 
332 		internal Stack<string> OngoingImports {
333 			get { return ongoing_imports; }
334 		}
335 
336 		// common part
GetWellKnownProperties(Project project)337 		internal static IEnumerable<EnvironmentProjectProperty> GetWellKnownProperties (Project project)
338 		{
339 			Func<string,string,EnvironmentProjectProperty> create = (name, value) => new EnvironmentProjectProperty (project, name, value, true);
340 			return GetWellKnownProperties (create);
341 		}
342 
GetWellKnownProperties(ProjectInstance project)343 		internal static IEnumerable<ProjectPropertyInstance> GetWellKnownProperties (ProjectInstance project)
344 		{
345 			Func<string,string,ProjectPropertyInstance> create = (name, value) => new ProjectPropertyInstance (name, true, value);
346 			return GetWellKnownProperties (create);
347 		}
348 
GetWellKnownProperties(Func<string,string,T> create)349 		static IEnumerable<T> GetWellKnownProperties<T> (Func<string,string,T> create)
350 		{
351 			yield return create ("OS", OS);
352 			var ext = Environment.GetEnvironmentVariable ("MSBuildExtensionsPath") ?? DefaultExtensionsPath;
353 			yield return create ("MSBuildExtensionsPath", ext);
354 			var ext32 = Environment.GetEnvironmentVariable ("MSBuildExtensionsPath32") ?? ext;
355 			yield return create ("MSBuildExtensionsPath32", ext32);
356 			var ext64 = Environment.GetEnvironmentVariable ("MSBuildExtensionsPath64") ?? ext;
357 			yield return create ("MSBuildExtensionsPath64", ext64);
358 		}
359 
360 		static string OS {
361 			get {
362 				PlatformID pid = Environment.OSVersion.Platform;
363 				switch ((int) pid) {
364 				case 128:
365 				case 4:
366 					return "Unix";
367 				case 6:
368 					return "OSX";
369 				default:
370 					return "Windows_NT";
371 				}
372 			}
373 		}
374 
375 		#region Extension Paths resolution
376 
377 		static string extensions_path;
378 		internal static string DefaultExtensionsPath {
379 			get {
380 				if (extensions_path == null) {
381 					// NOTE: code from mcs/tools/gacutil/driver.cs
382 					PropertyInfo gac = typeof (System.Environment).GetProperty (
383 							"GacPath", BindingFlags.Static | BindingFlags.NonPublic);
384 
385 					if (gac != null) {
386 						MethodInfo get_gac = gac.GetGetMethod (true);
387 						string gac_path = (string) get_gac.Invoke (null, null);
388 						extensions_path = Path.GetFullPath (Path.Combine (
389 									gac_path, Path.Combine ("..", "xbuild")));
390 					}
391 				}
392 				return extensions_path;
393 			}
394 		}
395 
396 		static string DotConfigExtensionsPath = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData),
397 			Path.Combine ("xbuild", "tasks"));
398 		const string MacOSXExternalXBuildDir = "/Library/Frameworks/Mono.framework/External/xbuild";
399 		static string PathSeparatorAsString = Path.PathSeparator.ToString ();
400 
401 		// Gives a list of extensions paths to try for $(MSBuildExtensionsPath),
402 		// *in-order*
GetApplicableExtensionsPaths(Action<string> logMessage)403 		internal static IEnumerable<string> GetApplicableExtensionsPaths (Action<string> logMessage)
404 		{
405 			string envvar = String.Join (PathSeparatorAsString, new string [] {
406 				// For mac osx, look in the 'External' dir on macosx,
407 				// see bug #663180
408 				MSBuildUtils.RunningOnMac ? MacOSXExternalXBuildDir : String.Empty,
409 				DotConfigExtensionsPath,
410 				DefaultExtensionsPath});
411 
412 			var pathsTable = new Dictionary<string, string> ();
413 			foreach (string extn_path in envvar.Split (new char [] {Path.PathSeparator}, StringSplitOptions.RemoveEmptyEntries)) {
414 				if (pathsTable.ContainsKey (extn_path))
415 					continue;
416 
417 				if (!Directory.Exists (extn_path)) {
418 					logMessage (string.Format ("Extension path '{0}' not found, ignoring.", extn_path));
419 					continue;
420 				}
421 
422 				pathsTable [extn_path] = extn_path;
423 				yield return extn_path;
424 			}
425 		}
426 
FindFileInSeveralExtensionsPath(ref string extensionsPathOverride, Func<string,string> expandString, string file, Action<string> logMessage)427 		internal static string FindFileInSeveralExtensionsPath (ref string extensionsPathOverride, Func<string,string> expandString, string file, Action<string> logMessage)
428 		{
429 			string ret = null;
430 			string ex = extensionsPathOverride;
431 			Func<bool> action = () => {
432 				string path = WindowsCompatibilityExtensions.FindMatchingPath (expandString (file));
433 				if (File.Exists (path))
434 					ret = path;
435 				else
436 					return false;
437 				return true;
438 			};
439 
440 			try {
441 				if (!action ()) {
442 					foreach (var s in ProjectCollection.GetApplicableExtensionsPaths (logMessage)) {
443 						extensionsPathOverride = s;
444 						ex = s;
445 						if (action ())
446 							break;
447 					}
448 				}
449 			} finally {
450 				extensionsPathOverride = null;
451 			}
452 
453 			return ret ?? WindowsCompatibilityExtensions.FindMatchingPath (expandString (file));
454 		}
455 
456 		#endregion
457 
GetReservedProperties(Toolset toolset, Project project)458 		internal IEnumerable<ReservedProjectProperty> GetReservedProperties (Toolset toolset, Project project)
459 		{
460 			Func<string,Func<string>,ReservedProjectProperty> create = (name, value) => new ReservedProjectProperty (project, name, value);
461 			return GetReservedProperties<ReservedProjectProperty> (toolset, project.Xml, create, () => project.FullPath);
462 		}
463 
GetReservedProperties(Toolset toolset, ProjectInstance project, ProjectRootElement xml)464 		internal IEnumerable<ProjectPropertyInstance> GetReservedProperties (Toolset toolset, ProjectInstance project, ProjectRootElement xml)
465 		{
466 			Func<string,Func<string>,ProjectPropertyInstance> create = (name, value) => new ProjectPropertyInstance (name, true, null, value);
467 			return GetReservedProperties<ProjectPropertyInstance> (toolset, xml, create, () => project.FullPath);
468 		}
469 
470 		// seealso http://msdn.microsoft.com/en-us/library/ms164309.aspx
GetReservedProperties(Toolset toolset, ProjectRootElement project, Func<string,Func<string>,T> create, Func<string> projectFullPath)471 		IEnumerable<T> GetReservedProperties<T> (Toolset toolset, ProjectRootElement project, Func<string,Func<string>,T> create, Func<string> projectFullPath)
472 		{
473 			yield return create ("MSBuildBinPath", () => toolset.ToolsPath);
474 			// FIXME: add MSBuildLastTaskResult
475 			// FIXME: add MSBuildNodeCount
476 			// FIXME: add MSBuildProgramFiles32
477 			yield return create ("MSBuildProjectDefaultTargets", () => project.DefaultTargets);
478 			yield return create ("MSBuildProjectDirectory", () => project.DirectoryPath + Path.DirectorySeparatorChar);
479 			yield return create ("MSBuildProjectDirectoryNoRoot", () => project.DirectoryPath.Substring (Path.GetPathRoot (project.DirectoryPath).Length));
480 			yield return create ("MSBuildProjectExtension", () => Path.GetExtension (project.FullPath));
481 			yield return create ("MSBuildProjectFile", () => Path.GetFileName (project.FullPath));
482 			yield return create ("MSBuildProjectFullPath", () => project.FullPath);
483 			yield return create ("MSBuildProjectName", () => Path.GetFileNameWithoutExtension (project.FullPath));
484 			yield return create ("MSBuildStartupDirectory", () => BuildStartupDirectory);
485 			yield return create ("MSBuildThisFile", () => Path.GetFileName (GetEvaluationTimeThisFile (projectFullPath)));
486 			yield return create ("MSBuildThisFileFullPath", () => GetEvaluationTimeThisFile (projectFullPath));
487 			yield return create ("MSBuildThisFileName", () => Path.GetFileNameWithoutExtension (GetEvaluationTimeThisFile (projectFullPath)));
488 			yield return create ("MSBuildThisFileExtension", () => Path.GetExtension (GetEvaluationTimeThisFile (projectFullPath)));
489 
490 			yield return create ("MSBuildThisFileDirectory", () => Path.GetDirectoryName (GetEvaluationTimeThisFileDirectory (projectFullPath)));
491 			yield return create ("MSBuildThisFileDirectoryNoRoot", () => {
492 				string dir = GetEvaluationTimeThisFileDirectory (projectFullPath) + Path.DirectorySeparatorChar;
493 				return dir.Substring (Path.GetPathRoot (dir).Length);
494 				});
495 			yield return create ("MSBuildToolsPath", () => toolset.ToolsPath);
496 			yield return create ("MSBuildToolsVersion", () => toolset.ToolsVersion);
497 
498 			// This is an implementation specific special property for this Microsoft.Build.dll to differentiate
499 			// the build from Microsoft.Build.Engine.dll. It is significantly used in some *.targets file we share
500 			// between old and new build engine.
501 			yield return create ("MonoUseMicrosoftBuildDll", () => "True");
502 		}
503 
504 		// These are required for reserved property, represents dynamically changing property values.
505 		// This should resolve to either the project file path or that of the imported file.
GetEvaluationTimeThisFileDirectory(Func<string> nonImportingTimeFullPath)506 		internal string GetEvaluationTimeThisFileDirectory (Func<string> nonImportingTimeFullPath)
507 		{
508 			var file = GetEvaluationTimeThisFile (nonImportingTimeFullPath);
509 			var dir = Path.IsPathRooted (file) ? Path.GetDirectoryName (file) : Directory.GetCurrentDirectory ();
510 			return dir + Path.DirectorySeparatorChar;
511 		}
512 
GetEvaluationTimeThisFile(Func<string> nonImportingTimeFullPath)513 		internal string GetEvaluationTimeThisFile (Func<string> nonImportingTimeFullPath)
514 		{
515 			return OngoingImports.Count > 0 ? OngoingImports.Peek () : (nonImportingTimeFullPath () ?? string.Empty);
516 		}
517 
518 		static readonly char [] item_target_sep = {';'};
519 
GetAllItems(Func<string,string> expandString, string include, string exclude, Func<string,T> creator, Func<string,ITaskItem> taskItemCreator, string directory, Action<T,string> assignRecurse, Func<ITaskItem,bool> isDuplicate)520 		internal static IEnumerable<T> GetAllItems<T> (Func<string,string> expandString, string include, string exclude, Func<string,T> creator, Func<string,ITaskItem> taskItemCreator, string directory, Action<T,string> assignRecurse, Func<ITaskItem,bool> isDuplicate)
521 		{
522 			var includes = expandString (include).Trim ().Split (item_target_sep, StringSplitOptions.RemoveEmptyEntries);
523 			var excludes = expandString (exclude).Trim ().Split (item_target_sep, StringSplitOptions.RemoveEmptyEntries);
524 
525 			if (includes.Length == 0)
526 				yield break;
527 			if (includes.Length == 1 && includes [0].IndexOf ('*') < 0 && excludes.Length == 0) {
528 				// for most case - shortcut.
529 				var item = creator (includes [0]);
530 				yield return item;
531 			} else {
532 				var ds = new Microsoft.Build.BuildEngine.DirectoryScanner () {
533 					BaseDirectory = new DirectoryInfo (directory),
534 					Includes = includes.Where (s => !string.IsNullOrWhiteSpace (s)).Select (i => taskItemCreator (i)).ToArray (),
535 					Excludes = excludes.Where (s => !string.IsNullOrWhiteSpace (s)).Select (e => taskItemCreator (e)).ToArray (),
536 				};
537 				ds.Scan ();
538 				foreach (var taskItem in ds.MatchedItems) {
539 					if (isDuplicate (taskItem))
540 						continue; // skip duplicate
541 					var item = creator (taskItem.ItemSpec);
542 					string recurse = taskItem.GetMetadata ("RecursiveDir");
543 					assignRecurse (item, recurse);
544 					yield return item;
545 				}
546 			}
547 		}
548 
549 		static readonly char [] path_sep = {Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar};
550 
GetWellKnownMetadata(string name, string file, Func<string,string> getFullPath, string recursiveDir)551 		internal static string GetWellKnownMetadata (string name, string file, Func<string,string> getFullPath, string recursiveDir)
552 		{
553 			switch (name.ToLower (CultureInfo.InvariantCulture)) {
554 			case "fullpath":
555 				return getFullPath (file);
556 			case "rootdir":
557 				return Path.GetPathRoot (getFullPath (file));
558 			case "filename":
559 				return Path.GetFileNameWithoutExtension (file);
560 			case "extension":
561 				return Path.GetExtension (file);
562 			case "relativedir":
563 					var idx = file.LastIndexOfAny (path_sep);
564 					return idx < 0 ? string.Empty : file.Substring (0, idx + 1);
565 			case "directory":
566 					var fp = getFullPath (file);
567 					return Path.GetDirectoryName (fp).Substring (Path.GetPathRoot (fp).Length);
568 			case "recursivedir":
569 				return recursiveDir;
570 			case "identity":
571 				return file;
572 			case "modifiedtime":
573 				return new FileInfo (getFullPath (file)).LastWriteTime.ToString ("yyyy-MM-dd HH:mm:ss.fffffff");
574 			case "createdtime":
575 				return new FileInfo (getFullPath (file)).CreationTime.ToString ("yyyy-MM-dd HH:mm:ss.fffffff");
576 			case "accessedtime":
577 				return new FileInfo (getFullPath (file)).LastAccessTime.ToString ("yyyy-MM-dd HH:mm:ss.fffffff");
578 			}
579 			return null;
580 		}
581 	}
582 }
583