diff --git a/src/Shared/FileUtilities.cs b/src/Shared/FileUtilities.cs index cf40546ec51..707657933d1 100644 --- a/src/Shared/FileUtilities.cs +++ b/src/Shared/FileUtilities.cs @@ -1031,27 +1031,55 @@ internal static string MakeRelative(string basePath, string path) ErrorUtilities.VerifyThrowArgumentNull(basePath, nameof(basePath)); ErrorUtilities.VerifyThrowArgumentLength(path, nameof(path)); - if (basePath.Length == 0) - { - return path; - } + string fullBase = Path.GetFullPath(basePath); + string fullPath = Path.GetFullPath(path); + + string[] splitBase = fullBase.Split(MSBuildConstants.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries); + string[] splitPath = fullPath.Split(MSBuildConstants.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries); - Uri baseUri = new Uri(EnsureTrailingSlash(basePath), UriKind.Absolute); // May throw UriFormatException + ErrorUtilities.VerifyThrow(splitPath.Length > 0, "Cannot call MakeRelative on a path of only slashes."); - Uri pathUri = CreateUriFromPath(path); + // On a mac, the path could start with any number of slashes and still be valid. We have to check them all. + int indexOfFirstNonSlashChar = 0; + while (path[indexOfFirstNonSlashChar] == Path.DirectorySeparatorChar) + { + indexOfFirstNonSlashChar++; + } + if (path.IndexOf(splitPath[0]) != indexOfFirstNonSlashChar) + { + // path was already relative so just return it + return FixFilePath(path); + } - if (!pathUri.IsAbsoluteUri) + int index = 0; + while (index < splitBase.Length && index < splitPath.Length && splitBase[index].Equals(splitPath[index], PathComparison)) { - // the path is already a relative url, we will just normalize it... - pathUri = new Uri(baseUri, pathUri); + index++; } - Uri relativeUri = baseUri.MakeRelativeUri(pathUri); - string relativePath = Uri.UnescapeDataString(relativeUri.IsAbsoluteUri ? relativeUri.LocalPath : relativeUri.ToString()); + if (index == splitBase.Length && index == splitPath.Length) + { + return "."; + } + + // If the paths have no component in common, the only valid relative path is the full path. + if (index == 0) + { + return fullPath; + } - string result = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + StringBuilder sb = StringBuilderCache.Acquire(); - return result; + for (int i = index; i < splitBase.Length; i++) + { + sb.Append("..").Append(Path.DirectorySeparatorChar); + } + for (int i = index; i < splitPath.Length; i++) + { + sb.Append(splitPath[i]).Append(Path.DirectorySeparatorChar); + } + sb.Length--; + return StringBuilderCache.GetStringAndRelease(sb); } /// diff --git a/src/Shared/UnitTests/FileUtilities_Tests.cs b/src/Shared/UnitTests/FileUtilities_Tests.cs index 636c41df025..7080a2e7ccc 100644 --- a/src/Shared/UnitTests/FileUtilities_Tests.cs +++ b/src/Shared/UnitTests/FileUtilities_Tests.cs @@ -83,9 +83,6 @@ private static void TestGetItemSpecModifier(string currentDirectory) } [Fact] - [Trait("Category", "mono-osx-failing")] - [Trait("Category", "netcore-osx-failing")] - [Trait("Category", "netcore-linux-failing")] public void MakeRelativeTests() { if (NativeMethodsShared.IsWindows) @@ -97,20 +94,17 @@ public void MakeRelativeTests() Assert.Equal(@"e:\abc\def\foo.cpp", FileUtilities.MakeRelative(@"c:\abc\def", @"e:\abc\def\foo.cpp")); Assert.Equal(@"foo.cpp", FileUtilities.MakeRelative(@"\\aaa\abc\def", @"\\aaa\abc\def\foo.cpp")); Assert.Equal(@"foo.cpp", FileUtilities.MakeRelative(@"c:\abc\def", @"foo.cpp")); - Assert.Equal(@"foo.cpp", FileUtilities.MakeRelative(@"c:\abc\def", @"..\def\foo.cpp")); Assert.Equal(@"\\host\path\file", FileUtilities.MakeRelative(@"c:\abc\def", @"\\host\path\file")); Assert.Equal(@"\\host\d$\file", FileUtilities.MakeRelative(@"c:\abc\def", @"\\host\d$\file")); Assert.Equal(@"..\fff\ggg.hh", FileUtilities.MakeRelative(@"c:\foo\bar\..\abc\cde", @"c:\foo\bar\..\abc\fff\ggg.hh")); } else { - Assert.Equal(@"foo.cpp", FileUtilities.MakeRelative(@"/abc/def", @"/abc/def/foo.cpp")); + Assert.Equal(@"bar.cpp", FileUtilities.MakeRelative(@"/abc/def", @"/abc/def/bar.cpp")); Assert.Equal(@"def/foo.cpp", FileUtilities.MakeRelative(@"/abc/", @"/abc/def/foo.cpp")); - Assert.Equal(@"..\foo.cpp", FileUtilities.MakeRelative(@"/abc/def/xyz", @"/abc/def/foo.cpp")); - Assert.Equal(@"..\ttt\foo.cpp", FileUtilities.MakeRelative(@"/abc/def/xyz/", @"/abc/def/ttt/foo.cpp")); - Assert.Equal(@"/abc/def/foo.cpp", FileUtilities.MakeRelative(@"/abc/def", @"/abc/def/foo.cpp")); + Assert.Equal(@"../foo.cpp", FileUtilities.MakeRelative(@"/abc/def/xyz", @"/abc/def/foo.cpp")); + Assert.Equal(@"../ttt/foo.cpp", FileUtilities.MakeRelative(@"/abc/def/xyz/", @"/abc/def/ttt/foo.cpp")); Assert.Equal(@"foo.cpp", FileUtilities.MakeRelative(@"/abc/def", @"foo.cpp")); - Assert.Equal(@"foo.cpp", FileUtilities.MakeRelative(@"/abc/def", @"../def/foo.cpp")); Assert.Equal(@"../fff/ggg.hh", FileUtilities.MakeRelative(@"/foo/bar/../abc/cde", @"/foo/bar/../abc/fff/ggg.hh")); } }