Skip to content
Merged
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 100 additions & 5 deletions src/Framework/FileUtilities.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#if NETFRAMEWORK && !TASKHOST
using Path = Microsoft.IO.Path;
#else
using System.IO;
#endif

namespace Microsoft.Build.Framework
{
Expand All @@ -14,10 +18,13 @@ namespace Microsoft.Build.Framework
/// </summary>
internal static class FrameworkFileUtilities
{
internal static readonly char[] Slashes = ['/', '\\'];
private const char UnixDirectorySeparator = '/';
private const char WindowsDirectorySeparator = '\\';

internal static readonly char[] Slashes = [UnixDirectorySeparator, WindowsDirectorySeparator];

/// <summary>
/// Indicates if the given character is a slash.
/// Indicates if the given character is a slash in current OS.
/// </summary>
/// <param name="c"></param>
/// <returns>true, if slash</returns>
Expand All @@ -38,9 +45,13 @@ internal static bool EndsWithSlash(string fileSpec)
: false;
}

/// <summary>
/// Fixes backslashes to forward slashes on Unix. This allows to recognise windows style paths on Unix.
Comment thread
AR-May marked this conversation as resolved.
/// However, this leads to incorrect path on Linux if backslash was part of the file/directory name.
/// </summary>
internal static string FixFilePath(string path)
{
return string.IsNullOrEmpty(path) || Path.DirectorySeparatorChar == '\\' ? path : path.Replace('\\', '/');
return string.IsNullOrEmpty(path) || Path.DirectorySeparatorChar == WindowsDirectorySeparator ? path : path.Replace(WindowsDirectorySeparator, UnixDirectorySeparator);
}

/// <summary>
Expand All @@ -66,7 +77,7 @@ internal static string EnsureTrailingSlash(string fileSpec)
internal static string EnsureNoTrailingSlash(string path)
{
path = FixFilePath(path);
if (EndsWithSlash(path))
if (path.Length > 0 && IsSlash(path[path.Length - 1]))
{
path = path.Substring(0, path.Length - 1);
}
Expand All @@ -75,13 +86,47 @@ internal static string EnsureNoTrailingSlash(string path)
}

#if !TASKHOST
/// <summary>
/// Checks if the path contains backslashes on Unix.
/// </summary>
private static bool HasWindowsDirectorySeparatorOnUnix(string path)
=> NativeMethods.IsUnixLike && path.IndexOf(WindowsDirectorySeparator) >= 0;

/// <summary>
/// Checks if the path contains forward slashes on Windows.
/// </summary>
private static bool HasUnixDirectorySeparatorOnWindows(string path)
=> NativeMethods.IsWindows && path.IndexOf(UnixDirectorySeparator) >= 0;

/// <summary>
/// Checks if the path contains relative segments like "." or "..".
/// </summary>
private static bool HasRelativeSegment(string path)
=> path.Contains("/.") || path.Contains("\\.");
Comment thread
AR-May marked this conversation as resolved.

/// <summary>
/// If the given path doesn't have a trailing slash then add one.
/// </summary>
/// <param name="path">The absolute path to check.</param>
/// <returns>An absolute path with a trailing slash.</returns>
/// <remarks>
/// If the path does not require modification, returns the current instance to avoid unnecessary allocations.
/// Preserves the OriginalValue of the current instance.
/// </remarks>
internal static AbsolutePath EnsureTrailingSlash(AbsolutePath path)
{
if (string.IsNullOrEmpty(path.Value))
{
return path;
}

// Check if the path already has a trailing slash and no separator fixing is needed on Unix.
// EnsureTrailingSlash should also fix the path separators on Unix.
if (IsSlash(path.Value[path.Value.Length - 1]) && !HasWindowsDirectorySeparatorOnUnix(path.Value))
{
return path;
}

return new AbsolutePath(EnsureTrailingSlash(path.Value),
original: path.OriginalValue,
ignoreRootedCheck: true);
Expand All @@ -92,8 +137,24 @@ internal static AbsolutePath EnsureTrailingSlash(AbsolutePath path)
/// </summary>
/// <param name="path">The absolute path to check.</param>
/// <returns>An absolute path without a trailing slash.</returns>
/// <remarks>
/// If the path does not require modification, returns the current instance to avoid unnecessary allocations.
/// Preserves the OriginalValue of the current instance.
/// </remarks>
internal static AbsolutePath EnsureNoTrailingSlash(AbsolutePath path)
{
if (string.IsNullOrEmpty(path.Value))
{
return path;
}

// Check if already has no trailing slash and no separator fixing needed on unix
// (EnsureNoTrailingSlash also should fix the paths on unix).
Comment thread
AR-May marked this conversation as resolved.
if (!IsSlash(path.Value[path.Value.Length - 1]) && !HasWindowsDirectorySeparatorOnUnix(path.Value))
{
return path;
}

return new AbsolutePath(EnsureNoTrailingSlash(path.Value),
original: path.OriginalValue,
ignoreRootedCheck: true);
Expand All @@ -104,26 +165,60 @@ internal static AbsolutePath EnsureNoTrailingSlash(AbsolutePath path)
/// Resolves relative segments like "." and "..". Fixes directory separators.
/// ASSUMES INPUT IS ALREADY UNESCAPED.
/// </summary>
/// <remarks>
/// If the path does not require modification, returns the current instance to avoid unnecessary allocations.
/// Preserves the OriginalValue of the current instance.
/// </remarks>
internal static AbsolutePath NormalizePath(AbsolutePath path)
{
if (string.IsNullOrEmpty(path.Value))
{
return path;
}

if (!HasRelativeSegment(path.Value) &&
!HasWindowsDirectorySeparatorOnUnix(path.Value) &&
!HasUnixDirectorySeparatorOnWindows(path.Value))
{
return path;
}

return new AbsolutePath(FixFilePath(Path.GetFullPath(path.Value)),
original: path.OriginalValue,
ignoreRootedCheck: true);
}

/// <summary>
/// Resolves relative segments like "." and "..".
/// Resolves relative segments like "." and "..". Fixes directory separators on Windows like Path.GetFullPath does.
/// ASSUMES INPUT IS ALREADY UNESCAPED.
/// </summary>
internal static AbsolutePath RemoveRelativeSegments(AbsolutePath path)
{
if (string.IsNullOrEmpty(path.Value))
{
return path;
}

if (!HasRelativeSegment(path.Value) && !HasUnixDirectorySeparatorOnWindows(path.Value))
{
return path;
}

return new AbsolutePath(Path.GetFullPath(path.Value),
original: path.OriginalValue,
ignoreRootedCheck: true);
}

/// <summary>
/// Fixes file path separators for the current platform.
/// </summary>
internal static AbsolutePath FixFilePath(AbsolutePath path)
{
if (string.IsNullOrEmpty(path.Value) || !HasWindowsDirectorySeparatorOnUnix(path.Value))
{
return path;
}

return new AbsolutePath(FixFilePath(path.Value),
original: path.OriginalValue,
ignoreRootedCheck: true);
Expand Down