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