Skip to content

Commit

Permalink
Merge pull request #23053 from khyperia/pathmap_fixes
Browse files Browse the repository at this point in the history
Fix PathMapping to a root directory
  • Loading branch information
khyperia authored Nov 10, 2017
2 parents a55468c + 07fd8ad commit 729708d
Show file tree
Hide file tree
Showing 10 changed files with 242 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1038,10 +1038,14 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable<string> ar
case "pathmap":
// "/pathmap:K1=V1,K2=V2..."
{
if (value == null)
unquoted = RemoveQuotesAndSlashes(value);

if (unquoted == null)
{
break;
}

pathMap = pathMap.Concat(ParsePathMap(value, diagnostics));
pathMap = pathMap.Concat(ParsePathMap(unquoted, diagnostics));
}
continue;

Expand Down
38 changes: 35 additions & 3 deletions src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9113,12 +9113,16 @@ public void PathMapParser()

parsedArgs = DefaultParse(new[] { "/pathmap:K1=V1", "a.cs" }, _baseDirectory);
parsedArgs.Errors.Verify();
Assert.Equal(KeyValuePair.Create("K1", "V1"), parsedArgs.PathMap[0]);
Assert.Equal(KeyValuePair.Create("K1\\", "V1\\"), parsedArgs.PathMap[0]);

parsedArgs = DefaultParse(new[] { "/pathmap:C:\\goo\\=/", "a.cs" }, _baseDirectory);
parsedArgs.Errors.Verify();
Assert.Equal(KeyValuePair.Create("C:\\goo\\", "/"), parsedArgs.PathMap[0]);

parsedArgs = DefaultParse(new[] { "/pathmap:K1=V1,K2=V2", "a.cs" }, _baseDirectory);
parsedArgs.Errors.Verify();
Assert.Equal(KeyValuePair.Create("K1", "V1"), parsedArgs.PathMap[0]);
Assert.Equal(KeyValuePair.Create("K2", "V2"), parsedArgs.PathMap[1]);
Assert.Equal(KeyValuePair.Create("K1\\", "V1\\"), parsedArgs.PathMap[0]);
Assert.Equal(KeyValuePair.Create("K2\\", "V2\\"), parsedArgs.PathMap[1]);

parsedArgs = DefaultParse(new[] { "/pathmap:,,,", "a.cs" }, _baseDirectory);
Assert.Equal(4, parsedArgs.Errors.Count());
Expand All @@ -9135,6 +9139,20 @@ public void PathMapParser()
parsedArgs = DefaultParse(new[] { "/pathmap:k=v=bad", "a.cs" }, _baseDirectory);
Assert.Equal(1, parsedArgs.Errors.Count());
Assert.Equal((int)ErrorCode.ERR_InvalidPathMap, parsedArgs.Errors[0].Code);

parsedArgs = DefaultParse(new[] { "/pathmap:\"supporting spaces=is hard\"", "a.cs" }, _baseDirectory);
parsedArgs.Errors.Verify();
Assert.Equal(KeyValuePair.Create("supporting spaces\\", "is hard\\"), parsedArgs.PathMap[0]);

parsedArgs = DefaultParse(new[] { "/pathmap:\"K 1=V 1\",\"K 2=V 2\"", "a.cs" }, _baseDirectory);
parsedArgs.Errors.Verify();
Assert.Equal(KeyValuePair.Create("K 1\\", "V 1\\"), parsedArgs.PathMap[0]);
Assert.Equal(KeyValuePair.Create("K 2\\", "V 2\\"), parsedArgs.PathMap[1]);

parsedArgs = DefaultParse(new[] { "/pathmap:\"K 1\"=\"V 1\",\"K 2\"=\"V 2\"", "a.cs" }, _baseDirectory);
parsedArgs.Errors.Verify();
Assert.Equal(KeyValuePair.Create("K 1\\", "V 1\\"), parsedArgs.PathMap[0]);
Assert.Equal(KeyValuePair.Create("K 2\\", "V 2\\"), parsedArgs.PathMap[1]);
}

[Fact]
Expand Down Expand Up @@ -9211,6 +9229,20 @@ void AssertPdbEmit(TempDirectory dir, string pdbPath, string pePdbPath, params s
var pdbPath = Path.Combine(dir.Path, "a.pdb");
AssertPdbEmit(dir, pdbPath, @"a.pdb", $@"/features:pdb-path-determinism");
}

// Unix path map
using (var dir = new DisposableDirectory(Temp))
{
var pdbPath = Path.Combine(dir.Path, "a.pdb");
AssertPdbEmit(dir, pdbPath, @"/a.pdb", $@"/pathmap:{dir.Path}=/");
}

// Multi-specified path map with mixed slashes
using (var dir = new DisposableDirectory(Temp))
{
var pdbPath = Path.Combine(dir.Path, "a.pdb");
AssertPdbEmit(dir, pdbPath, "/goo/a.pdb", $"/pathmap:{dir.Path}=/goo,{dir.Path}{PathUtilities.DirectorySeparatorChar}=/bar");
}
}

[CompilerTrait(CompilerFeature.Determinism)]
Expand Down
41 changes: 41 additions & 0 deletions src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.CSharp.UnitTests
Expand Down Expand Up @@ -2517,5 +2520,43 @@ public void Bug9288_keycontainer()
// get to the third case (but there's still error handling if that changes)

#endregion

#region PathMap Linux Tests
// Like the above (CoreCLR Signing Tests), these aren't actually syntax tests, but this is in one of only two assemblies tested on linux

[Theory]
[InlineData("C:\\", "/", "C:\\", "/")]
[InlineData("C:\\temp\\", "/temp/", "C:\\temp", "/temp")]
[InlineData("C:\\temp\\", "/temp/", "C:\\temp\\", "/temp/")]
[InlineData("/", "C:\\", "/", "C:\\")]
[InlineData("/temp/", "C:\\temp\\", "/temp", "C:\\temp")]
[InlineData("/temp/", "C:\\temp\\", "/temp/", "C:\\temp\\")]
public void PathMapKeepsCrossPlatformRoot(string expectedFrom, string expectedTo, string sourceFrom, string sourceTo)
{
var pathmapArg = $"/pathmap:{sourceFrom}={sourceTo}";
var parsedArgs = CSharpCommandLineParser.Default.Parse(new[] { pathmapArg, "a.cs" }, TempRoot.Root, RuntimeEnvironment.GetRuntimeDirectory(), null);
parsedArgs.Errors.Verify();
var expected = new KeyValuePair<string, string>(expectedFrom, expectedTo);
Assert.Equal(expected, parsedArgs.PathMap[0]);
}

[Fact]
public void PathMapInconsistentSlashes()
{
CSharpCommandLineArguments parse(params string[] args)
{
var parsedArgs = CSharpCommandLineParser.Default.Parse(args, TempRoot.Root, RuntimeEnvironment.GetRuntimeDirectory(), null);
parsedArgs.Errors.Verify();
return parsedArgs;
}

var sep = PathUtilities.DirectorySeparatorChar;
Assert.Equal(new KeyValuePair<string, string>("C:\\temp/goo" + sep, "/temp\\goo" + sep), parse("/pathmap:C:\\temp/goo=/temp\\goo", "a.cs").PathMap[0]);
Assert.Equal(new KeyValuePair<string, string>("noslash" + sep, "withoutslash" + sep), parse("/pathmap:noslash=withoutslash", "a.cs").PathMap[0]);
var doublemap = parse("/pathmap:/temp=/goo,/temp/=/bar", "a.cs").PathMap;
Assert.Equal(new KeyValuePair<string, string>("/temp/", "/goo/"), doublemap[0]);
Assert.Equal(new KeyValuePair<string, string>("/temp/", "/bar/"), doublemap[1]);
}
#endregion
}
}
9 changes: 0 additions & 9 deletions src/Compilers/Core/Portable/CodeAnalysisResources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 0 additions & 3 deletions src/Compilers/Core/Portable/CodeAnalysisResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -225,9 +225,6 @@
<data name="NullValueInPathMap" xml:space="preserve">
<value>A value in the pathMap is null.</value>
</data>
<data name="KeyInPathMapEndsWithSeparator" xml:space="preserve">
<value>A key in the pathMap ends with a path separator.</value>
</data>
<data name="CompilationOptionsMustNotHaveErrors" xml:space="preserve">
<value>Compilation options must not have errors.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,15 +205,17 @@ protected ImmutableArray<KeyValuePair<string, string>> ParsePathMap(string pathM
errors.Add(Diagnostic.Create(_messageProvider, _messageProvider.ERR_InvalidPathMap, kEqualsV));
continue;
}
var from = PathUtilities.TrimTrailingSeparators(kv[0]);
var to = PathUtilities.TrimTrailingSeparators(kv[1]);
var from = kv[0];
var to = kv[1];

if (from.Length == 0 || (to.Length == 0 && kv[1] != "/"))
if (from.Length == 0 || to.Length == 0)
{
errors.Add(Diagnostic.Create(_messageProvider, _messageProvider.ERR_InvalidPathMap, kEqualsV));
}
else
{
from = PathUtilities.EnsureTrailingSeparator(from);
to = PathUtilities.EnsureTrailingSeparator(to);
pathMapBuilder.Add(new KeyValuePair<string, string>(from, to));
}
}
Expand Down
38 changes: 36 additions & 2 deletions src/Compilers/Core/Portable/FileSystem/PathUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ internal static class PathUtilities
/// <summary>
/// Removes trailing directory separator characters
/// </summary>
/// <remarks>
/// This will trim the root directory separator:
/// "C:\" maps to "C:", and "/" maps to ""
/// </remarks>
public static string TrimTrailingSeparators(string s)
{
int lastSeparator = s.Length;
Expand All @@ -53,6 +57,34 @@ public static string TrimTrailingSeparators(string s)
return s;
}

/// <summary>
/// Ensures a trailing directory separator character
/// </summary>
public static string EnsureTrailingSeparator(string s)
{
if (s.Length == 0 || IsAnyDirectorySeparator(s[s.Length - 1]))
{
return s;
}

// Use the existing slashes in the path, if they're consistent
bool hasSlash = s.IndexOf('/') >= 0;
bool hasBackslash = s.IndexOf('\\') >= 0;
if (hasSlash && !hasBackslash)
{
return s + '/';
}
else if (!hasSlash && hasBackslash)
{
return s + '\\';
}
else
{
// If there are no slashes or they are inconsistent, use the current platform's slash.
return s + DirectorySeparatorChar;
}
}

public static string GetExtension(string path)
{
return FileNameUtilities.GetExtension(path);
Expand Down Expand Up @@ -625,14 +657,16 @@ public static string NormalizePathPrefix(string filePath, ImmutableArray<KeyValu
return filePath;
}

// find the first key in the path map that matches a prefix of the normalized path (followed by a path separator).
// find the first key in the path map that matches a prefix of the normalized path.
// Note that we expect the client to use consistent capitalization; we use ordinal (case-sensitive) comparisons.
foreach (var kv in pathMap)
{
var oldPrefix = kv.Key;
if (!(oldPrefix?.Length > 0)) continue;

if (filePath.StartsWith(oldPrefix, StringComparison.Ordinal) && filePath.Length > oldPrefix.Length && IsAnyDirectorySeparator(filePath[oldPrefix.Length]))
// oldPrefix always ends with a path separator, so there's no need to check if it was a partial match
// e.g. for the map /goo=/bar and filename /goooo
if (filePath.StartsWith(oldPrefix, StringComparison.Ordinal))
{
var replacementPrefix = kv.Value;

Expand Down
24 changes: 18 additions & 6 deletions src/Compilers/Core/Portable/SourceFileResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis
Expand Down Expand Up @@ -47,11 +48,16 @@ public SourceFileResolver(

_baseDirectory = baseDirectory;
_searchPaths = searchPaths;
_pathMap = pathMap.NullToEmpty();

// the keys in pathMap should not end with a path separator
// The previous public API required paths to not end with a path separator.
// This broke handling of root paths (e.g. "/" cannot be represented), so
// the new requirement is for paths to always end with a path separator.
// However, because this is a public API, both conventions must be allowed,
// so normalize the paths here (instead of enforcing end-with-sep).
if (!pathMap.IsDefaultOrEmpty)
{
var pathMapBuilder = ArrayBuilder<KeyValuePair<string, string>>.GetInstance(pathMap.Length);

foreach (var kv in pathMap)
{
var key = kv.Key;
Expand All @@ -66,11 +72,17 @@ public SourceFileResolver(
throw new ArgumentException(CodeAnalysisResources.NullValueInPathMap, nameof(pathMap));
}

if (PathUtilities.IsAnyDirectorySeparator(key[key.Length - 1]))
{
throw new ArgumentException(CodeAnalysisResources.KeyInPathMapEndsWithSeparator, nameof(pathMap));
}
var normalizedKey = PathUtilities.EnsureTrailingSeparator(key);
var normalizedValue = PathUtilities.EnsureTrailingSeparator(value);

pathMapBuilder.Add(new KeyValuePair<string, string>(normalizedKey, normalizedValue));
}

_pathMap = pathMapBuilder.ToImmutableAndFree();
}
else
{
_pathMap = ImmutableArray<KeyValuePair<string, string>>.Empty;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1068,11 +1068,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic

Case "pathmap"
' "/pathmap:K1=V1,K2=V2..."
If value = Nothing Then
Dim unquoted = RemoveQuotesAndSlashes(value)
If unquoted = Nothing Then
Exit Select
End If

pathMap = pathMap.Concat(ParsePathMap(value, diagnostics))
pathMap = pathMap.Concat(ParsePathMap(unquoted, diagnostics))
Continue For

Case "reportanalyzer"
Expand Down
Loading

0 comments on commit 729708d

Please sign in to comment.