diff --git a/build_projects/dotnet-cli-build/MakeRelative.cs b/build_projects/dotnet-cli-build/MakeRelative.cs index e8f04b159..6361f341c 100644 --- a/build_projects/dotnet-cli-build/MakeRelative.cs +++ b/build_projects/dotnet-cli-build/MakeRelative.cs @@ -17,21 +17,12 @@ namespace Microsoft.DotNet.Cli.Build [Required] public string Path2 { get; set; } - public char SeparatorChar { get; set; } - [Output] public ITaskItem RelativePath { get; set; } public override bool Execute() { - if (SeparatorChar == default(char)) - { - SeparatorChar = Path.DirectorySeparatorChar; - } - - var relativePath = GetRelativePath(Path1, Path2, SeparatorChar); - - RelativePath = ToTaskItem(Path1, Path2, relativePath); + RelativePath = ToTaskItem(Path1, Path2, Path.GetRelativePath(Path1, Path2)); return true; } @@ -47,75 +38,5 @@ namespace Microsoft.DotNet.Cli.Build return framework; } - - private static string GetRelativePath(string path1, string path2, char separator = default(char)) - { - - StringComparison compare; - if (CurrentPlatform.IsWindows) - { - compare = StringComparison.OrdinalIgnoreCase; - // check if paths are on the same volume - if (!string.Equals(Path.GetPathRoot(path1), Path.GetPathRoot(path2))) - { - // on different volumes, "relative" path is just Path2 - return path2; - } - } - else - { - compare = StringComparison.Ordinal; - } - - var index = 0; - var path1Segments = path1.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); - var path2Segments = path2.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); - // if path1 does not end with / it is assumed the end is not a directory - // we will assume that is isn't a directory by ignoring the last split - var len1 = path1Segments.Length - 1; - var len2 = path2Segments.Length; - - // find largest common absolute path between both paths - var min = Math.Min(len1, len2); - while (min > index) - { - if (!string.Equals(path1Segments[index], path2Segments[index], compare)) - { - break; - } - // Handle scenarios where folder and file have same name (only if os supports same name for file and directory) - // e.g. /file/name /file/name/app - else if ((len1 == index && len2 > index + 1) || (len1 > index && len2 == index + 1)) - { - break; - } - ++index; - } - - var path = ""; - - // check if path2 ends with a non-directory separator and if path1 has the same non-directory at the end - if (len1 + 1 == len2 && !string.IsNullOrEmpty(path1Segments[index]) && - string.Equals(path1Segments[index], path2Segments[index], compare)) - { - return path; - } - - for (var i = index; len1 > i; ++i) - { - path += ".." + separator; - } - for (var i = index; len2 - 1 > i; ++i) - { - path += path2Segments[i] + separator; - } - // if path2 doesn't end with an empty string it means it ended with a non-directory name, so we add it back - if (!string.IsNullOrEmpty(path2Segments[len2 - 1])) - { - path += path2Segments[len2 - 1]; - } - - return path; - } } } diff --git a/build_projects/dotnet-cli-build/dotnet-cli-build.csproj b/build_projects/dotnet-cli-build/dotnet-cli-build.csproj index 17aab1107..677e26bce 100644 --- a/build_projects/dotnet-cli-build/dotnet-cli-build.csproj +++ b/build_projects/dotnet-cli-build/dotnet-cli-build.csproj @@ -14,6 +14,7 @@ + diff --git a/src/Microsoft.DotNet.Cli.Utils/PathUtility.cs b/src/Microsoft.DotNet.Cli.Utils/PathUtility.cs index cbbc0d401..e0ad71840 100644 --- a/src/Microsoft.DotNet.Cli.Utils/PathUtility.cs +++ b/src/Microsoft.DotNet.Cli.Utils/PathUtility.cs @@ -151,7 +151,7 @@ namespace Microsoft.DotNet.Tools.Common { compare = StringComparison.OrdinalIgnoreCase; // check if paths are on the same volume - if (!string.Equals(Path.GetPathRoot(path1), Path.GetPathRoot(path2))) + if (!string.Equals(Path.GetPathRoot(path1), Path.GetPathRoot(path2), compare)) { // on different volumes, "relative" path is just path2 return path2; @@ -273,7 +273,22 @@ namespace Microsoft.DotNet.Tools.Common foreach (var component in components) { - if (!string.IsNullOrEmpty(component)) + if (string.IsNullOrEmpty(component)) + { + continue; + } + + if (string.IsNullOrEmpty(result)) + { + result = component; + + // On Windows, manually append a separator for drive references because Path.Combine won't do so + if (result.EndsWith(":") && RuntimeEnvironment.OperatingSystemPlatform == Platform.Windows) + { + result += Path.DirectorySeparatorChar; + } + } + else { result = Path.Combine(result, component); } diff --git a/src/dotnet/MsbuildProject.cs b/src/dotnet/MsbuildProject.cs index 860d38799..34055cacc 100644 --- a/src/dotnet/MsbuildProject.cs +++ b/src/dotnet/MsbuildProject.cs @@ -256,7 +256,7 @@ namespace Microsoft.DotNet.Tools string fullPath = Path.GetFullPath(reference); ret.Add(fullPath); - ret.Add(PathUtility.GetRelativePath(ProjectDirectory, fullPath)); + ret.Add(Path.GetRelativePath(ProjectDirectory, fullPath)); return ret; } diff --git a/src/dotnet/SlnFileExtensions.cs b/src/dotnet/SlnFileExtensions.cs index 24848dffa..16dce1b31 100644 --- a/src/dotnet/SlnFileExtensions.cs +++ b/src/dotnet/SlnFileExtensions.cs @@ -23,7 +23,7 @@ namespace Microsoft.DotNet.Tools.Common throw new ArgumentException(); } - var relativeProjectPath = PathUtility.GetRelativePath( + var relativeProjectPath = Path.GetRelativePath( PathUtility.EnsureTrailingSlash(slnFile.BaseDirectory), fullProjectPath); diff --git a/src/dotnet/commands/dotnet-add/dotnet-add-reference/Program.cs b/src/dotnet/commands/dotnet-add/dotnet-add-reference/Program.cs index 72498b37f..e79ebfc0c 100644 --- a/src/dotnet/commands/dotnet-add/dotnet-add-reference/Program.cs +++ b/src/dotnet/commands/dotnet-add/dotnet-add-reference/Program.cs @@ -91,7 +91,7 @@ namespace Microsoft.DotNet.Tools.Add.ProjectToProjectReference } var relativePathReferences = _appliedCommand.Arguments.Select((r) => - PathUtility.GetRelativePath(msbuildProj.ProjectDirectory, Path.GetFullPath(r))) + Path.GetRelativePath(msbuildProj.ProjectDirectory, Path.GetFullPath(r))) .ToList(); int numberOfAddedReferences = msbuildProj.AddProjectToProjectReferences( diff --git a/src/dotnet/commands/dotnet-sln/remove/Program.cs b/src/dotnet/commands/dotnet-sln/remove/Program.cs index ce2b88703..973294c6e 100644 --- a/src/dotnet/commands/dotnet-sln/remove/Program.cs +++ b/src/dotnet/commands/dotnet-sln/remove/Program.cs @@ -41,7 +41,7 @@ namespace Microsoft.DotNet.Tools.Sln.Remove SlnFile slnFile = SlnFileFactory.CreateFromFileOrDirectory(_fileOrDirectory); var relativeProjectPaths = _appliedCommand.Arguments.Select(p => - PathUtility.GetRelativePath( + Path.GetRelativePath( PathUtility.EnsureTrailingSlash(slnFile.BaseDirectory), Path.GetFullPath(p))) .ToList(); diff --git a/test/Microsoft.DotNet.Cli.Utils.Tests/PathUtilityTests.cs b/test/Microsoft.DotNet.Cli.Utils.Tests/PathUtilityTests.cs new file mode 100644 index 000000000..70e7768c7 --- /dev/null +++ b/test/Microsoft.DotNet.Cli.Utils.Tests/PathUtilityTests.cs @@ -0,0 +1,34 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.DotNet.Tools.Common; +using Microsoft.DotNet.Tools.Test.Utilities; +using Xunit; + +namespace Microsoft.DotNet.Cli.Utils +{ + public class PathUtilityTests : TestBase + { + /// + /// Tests that PathUtility.GetRelativePath treats drive references as case insensitive on Windows. + /// + [WindowsOnlyFact] + public void GetRelativePathWithCaseInsensitiveDrives() + { + Assert.Equal(@"bar\", PathUtility.GetRelativePath(@"C:\foo\", @"C:\foo\bar\")); + Assert.Equal(@"Bar\Baz\", PathUtility.GetRelativePath(@"c:\foo\", @"C:\Foo\Bar\Baz\")); + Assert.Equal(@"baz\Qux\", PathUtility.GetRelativePath(@"C:\fOO\bar\", @"c:\foo\BAR\baz\Qux\")); + Assert.Equal(@"d:\foo\", PathUtility.GetRelativePath(@"C:\foo\", @"d:\foo\")); + } + + /// + /// Tests that PathUtility.RemoveExtraPathSeparators works correctly with drive references on Windows. + /// + [WindowsOnlyFact] + public void RemoveExtraPathSeparatorsWithDrives() + { + Assert.Equal(@"c:\foo\bar\baz\", PathUtility.RemoveExtraPathSeparators(@"c:\\\foo\\\\bar\baz\\")); + Assert.Equal(@"D:\QUX\", PathUtility.RemoveExtraPathSeparators(@"D:\\\\\QUX\")); + } + } +}