/// <summary> /// Determines the full path for the given file-spec. /// </summary> /// <owner>SumedhK</owner> /// <param name="fileSpec">The file spec to get the full path of.</param> /// <param name="currentDirectory"></param> /// <returns>full path</returns> private static string GetFullPath(string fileSpec, string currentDirectory) { // Sending data out of the engine into the filesystem, so time to unescape. fileSpec = EscapingUtilities.UnescapeAll(fileSpec); // Data coming back from the filesystem into the engine, so time to escape it back. string fullPath = EscapingUtilities.Escape(Path.GetFullPath(Path.Combine(currentDirectory, fileSpec))); if (!EndsWithSlash(fullPath)) { Match drive = FileUtilitiesRegex.DrivePattern.Match(fileSpec); Match UNCShare = FileUtilitiesRegex.UNCPattern.Match(fullPath); if ((drive.Success && (drive.Length == fileSpec.Length)) || (UNCShare.Success && (UNCShare.Length == fullPath.Length))) { // append trailing slash if Path.GetFullPath failed to (this happens with drive-specs and UNC shares) fullPath += Path.DirectorySeparatorChar; } } return(fullPath); }
/// <summary> /// Performs path manipulations on the given item-spec as directed. /// /// Supported modifiers: /// %(FullPath) = full path of item /// %(RootDir) = root directory of item /// %(Filename) = item filename without extension /// %(Extension) = item filename extension /// %(RelativeDir) = item directory as given in item-spec /// %(Directory) = full path of item directory relative to root /// %(RecursiveDir) = portion of item path that matched a recursive wildcard /// %(Identity) = item-spec as given /// %(ModifiedTime) = last write time of item /// %(CreatedTime) = creation time of item /// %(AccessedTime) = last access time of item /// /// NOTES: /// 1) This method always returns an empty string for the %(RecursiveDir) modifier because it does not have enough /// information to compute it -- only the BuildItem class can compute this modifier. /// 2) The %(ModifiedTime), %(CreatedTime) and %(AccessedTime) modifiers are never cached because they are not constants. /// </summary> /// <remarks> /// Methods of the Path class "normalize" slashes and periods. For example: /// 1) successive slashes are combined into 1 slash /// 2) trailing periods are discarded /// 3) forward slashes are changed to back-slashes /// /// As a result, we cannot rely on any file-spec that has passed through a Path method to remain the same. We will /// therefore not bother preserving slashes and periods when file-specs are transformed. /// </remarks> /// <owner>SumedhK</owner> /// <param name="currentDirectory">The root directory for relative item-specs. When called on the Engine thread, this is the project directory. When called as part of building a task, it is null, indicating that the current directory should be used.</param> /// <param name="itemSpec">The item-spec to modify.</param> /// <param name="modifier">The modifier to apply to the item-spec.</param> /// <param name="cachedModifiers">Cache of previously computed modifiers (if null, this method will create it unless the modifier cannot be cached).</param> /// <returns>The modified item-spec (can be empty string, but will never be null).</returns> /// <exception cref="InvalidOperationException">Thrown when the item-spec is not a path.</exception> internal static string GetItemSpecModifier(string currentDirectory, string itemSpec, string modifier, ref Hashtable cachedModifiers) { ErrorUtilities.VerifyThrow(itemSpec != null, "Need item-spec to modify."); ErrorUtilities.VerifyThrow(modifier != null, "Need modifier to apply to item-spec."); string modifiedItemSpec = null; // check if we have computed this modifier before if (cachedModifiers != null) { ErrorUtilities.VerifyThrow((string)cachedModifiers[String.Empty] == itemSpec, "The cache of modifiers is only valid for one item-spec. If the item-spec changes, the cache must be nulled out, or a different cache passed in."); modifiedItemSpec = (string)cachedModifiers[modifier]; } if (modifiedItemSpec == null) { // certain properties can't be cached -- this will be turned to true in those cases bool isVolatile = false; try { if (String.Compare(modifier, ItemSpecModifiers.FullPath, StringComparison.OrdinalIgnoreCase) == 0) { if (currentDirectory == null) { currentDirectory = String.Empty; } modifiedItemSpec = GetFullPath(itemSpec, currentDirectory); } else if (String.Compare(modifier, ItemSpecModifiers.RootDir, StringComparison.OrdinalIgnoreCase) == 0) { if (currentDirectory == null) { currentDirectory = String.Empty; } string fullPath = Path.GetFullPath(Path.Combine(currentDirectory, itemSpec)); modifiedItemSpec = Path.GetPathRoot(fullPath); if (!EndsWithSlash(modifiedItemSpec)) { Debug.Assert(FileUtilitiesRegex.UNCPattern.IsMatch(modifiedItemSpec), "Only UNC shares should be missing trailing slashes."); // restore/append trailing slash if Path.GetPathRoot() has either removed it, or failed to add it // (this happens with UNC shares) modifiedItemSpec += Path.DirectorySeparatorChar; } } else if (String.Compare(modifier, ItemSpecModifiers.Filename, StringComparison.OrdinalIgnoreCase) == 0) { // if the item-spec is a root directory, it can have no filename if (Path.GetDirectoryName(itemSpec) == null) { // NOTE: this is to prevent Path.GetFileNameWithoutExtension() from treating server and share elements // in a UNC file-spec as filenames e.g. \\server, \\server\share modifiedItemSpec = String.Empty; } else { modifiedItemSpec = Path.GetFileNameWithoutExtension(itemSpec); } } else if (String.Compare(modifier, ItemSpecModifiers.Extension, StringComparison.OrdinalIgnoreCase) == 0) { // if the item-spec is a root directory, it can have no extension if (Path.GetDirectoryName(itemSpec) == null) { // NOTE: this is to prevent Path.GetExtension() from treating server and share elements in a UNC // file-spec as filenames e.g. \\server.ext, \\server\share.ext modifiedItemSpec = String.Empty; } else { modifiedItemSpec = Path.GetExtension(itemSpec); } } else if (String.Compare(modifier, ItemSpecModifiers.RelativeDir, StringComparison.OrdinalIgnoreCase) == 0) { modifiedItemSpec = GetDirectory(itemSpec); } else if (String.Compare(modifier, ItemSpecModifiers.Directory, StringComparison.OrdinalIgnoreCase) == 0) { if (currentDirectory == null) { currentDirectory = String.Empty; } modifiedItemSpec = GetDirectory(GetFullPath(itemSpec, currentDirectory)); Match root = FileUtilitiesRegex.DrivePattern.Match(modifiedItemSpec); if (!root.Success) { root = FileUtilitiesRegex.UNCPattern.Match(modifiedItemSpec); } if (root.Success) { ErrorUtilities.VerifyThrow((modifiedItemSpec.Length > root.Length) && IsSlash(modifiedItemSpec[root.Length]), "Root directory must have a trailing slash."); modifiedItemSpec = modifiedItemSpec.Substring(root.Length + 1); } } else if (String.Compare(modifier, ItemSpecModifiers.RecursiveDir, StringComparison.OrdinalIgnoreCase) == 0) { // only the BuildItem class can compute this modifier -- so leave empty modifiedItemSpec = String.Empty; } else if (String.Compare(modifier, ItemSpecModifiers.Identity, StringComparison.OrdinalIgnoreCase) == 0) { modifiedItemSpec = itemSpec; } else if (String.Compare(modifier, ItemSpecModifiers.ModifiedTime, StringComparison.OrdinalIgnoreCase) == 0) { isVolatile = true; // About to go out to the filesystem. This means data is leaving the engine, so need // to unescape first. string unescapedItemSpec = EscapingUtilities.UnescapeAll(itemSpec); FileInfo info = FileUtilities.GetFileInfoNoThrow(unescapedItemSpec); if (info != null) { modifiedItemSpec = info.LastWriteTime.ToString(FileTimeFormat, null); } else { // File does not exist, or path is a directory modifiedItemSpec = String.Empty; } } else if (String.Compare(modifier, ItemSpecModifiers.CreatedTime, StringComparison.OrdinalIgnoreCase) == 0) { isVolatile = true; // About to go out to the filesystem. This means data is leaving the engine, so need // to unescape first. string unescapedItemSpec = EscapingUtilities.UnescapeAll(itemSpec); if (File.Exists(unescapedItemSpec)) { modifiedItemSpec = File.GetCreationTime(unescapedItemSpec).ToString(FileTimeFormat, null); } else { // File does not exist, or path is a directory modifiedItemSpec = String.Empty; } } else if (String.Compare(modifier, ItemSpecModifiers.AccessedTime, StringComparison.OrdinalIgnoreCase) == 0) { isVolatile = true; // About to go out to the filesystem. This means data is leaving the engine, so need // to unescape first. string unescapedItemSpec = EscapingUtilities.UnescapeAll(itemSpec); if (File.Exists(unescapedItemSpec)) { modifiedItemSpec = File.GetLastAccessTime(unescapedItemSpec).ToString(FileTimeFormat, null); } else { // File does not exist, or path is a directory modifiedItemSpec = String.Empty; } } else { ErrorUtilities.VerifyThrow(false, "\"{0}\" is not a valid item-spec modifier.", modifier); } } catch (Exception e) // Catching Exception, but rethrowing unless it's a well-known exception. { if (ExceptionHandling.NotExpectedException(e)) { throw; } ErrorUtilities.VerifyThrowInvalidOperation(false, "Shared.InvalidFilespecForTransform", modifier, itemSpec, e.Message); } ErrorUtilities.VerifyThrow(modifiedItemSpec != null, "The item-spec modifier \"{0}\" was not evaluated.", modifier); // cache the modifier if (!isVolatile) { if (cachedModifiers == null) { cachedModifiers = new Hashtable(StringComparer.OrdinalIgnoreCase); // mark the cache to indicate the item-spec for which it was created // NOTE: we've intentionally picked a key here that will never conflict with any modifier name -- if we // use the item-spec as the key, it's possible for it to conflict with the name of a modifier cachedModifiers[String.Empty] = itemSpec; } cachedModifiers[modifier] = modifiedItemSpec; } } return(modifiedItemSpec); }