Skip to content

Commit

Permalink
Make Runfiles library compatible with .NET Standard 2.0 (#383)
Browse files Browse the repository at this point in the history
This commit attemps to make the Runfiles library compatible with .NET
Standard 2.0 instead of .NET Standard 2.1. The requirement for 2.1 is
caused by the use of Path.IsPathFullyQualified(), which only exists for
.NET Core 2.0 and later. The implementation of Path.IsPathFullyQualified()
has been paraphrased from the code in the .NET runtime source repo.

There may be other ways to accomplish this, including changing which
references are included, but this seems the least intrusive mechanism.
  • Loading branch information
jimevans authored Sep 19, 2023
1 parent 6da67eb commit ab7e043
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 10 deletions.
4 changes: 2 additions & 2 deletions tools/runfiles/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ csharp_library(
name = "runfiles",
srcs = ["Runfiles.cs"],
private_deps = [
"@rules_dotnet_nuget_packages//netstandard.library.ref",
"@rules_dotnet_nuget_packages//netstandard.library",
],
target_frameworks = ["netstandard2.1"],
target_frameworks = ["netstandard2.0"],
visibility = ["//visibility:public"],
deps = [
],
Expand Down
101 changes: 94 additions & 7 deletions tools/runfiles/Runfiles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ namespace Bazel
/// USAGE:
///
/// 1. Depend on this runfiles library from your build rule:
///
///
/// <code>
/// csharp_binary(
/// name = "my_binary",
/// ...
/// deps = ["@bazel_tools//tools/java/runfiles"],
/// )
/// </code>
///
///
/// 2. Import the runfiles library.
///
/// <code>
Expand All @@ -47,7 +47,7 @@ namespace Bazel
/// var path = runfiles.Rlocation("path/to/binary");
/// var process = new System.Diagnostics.Process();
/// process.StartInfo.FileName = path;
/// process.StartInfo.Environment = Runfiles.GetEnvVars();
/// process.StartInfo.Environment = Runfiles.GetEnvVars();
/// ...
/// process.Start();
/// </code>
Expand Down Expand Up @@ -83,10 +83,10 @@ public static Runfiles Create()
/// If <paramref name="env"/> contains RUNFILES_MANIFEST_ONLY=1, this method returns a manifest-based implementation.
/// The manifest's path is defined by the RUNFILES_MANIFEST_FILE key's value in <paramref name="env"/>.
///
/// If <paramref name="env"/> contains RUNFILES_DIR=SOME_DIRECTORY or JAVA_RUNFILES=SOME_DIRECTORY,
/// If <paramref name="env"/> contains RUNFILES_DIR=SOME_DIRECTORY or JAVA_RUNFILES=SOME_DIRECTORY,
/// this method returns a directory-based implementation.
///
/// Otherwise this method tries to find a the manifest file based on the argv0
/// Otherwise this method tries to find a the manifest file based on the argv0
/// If argv0 + ".runfiles/MANFIEST" exists RUNFILES_MANIFEST_FILE will be set to to that path
/// else if argv0 + ".runfiles_manifest" exists RUNFILES_MANIFEST_FILE will be set to to that path.
/// If argv0 + ".runfiles" exists RUNFILES_DIR will be set to to that path.
Expand Down Expand Up @@ -153,7 +153,7 @@ public static Runfiles Create(string argv0, IDictionary<string, string> env)

/// <summary>
/// Returns the runtime path of a runfile (a Bazel-built binary's/test's data-dependency).
///
///
/// The returned path may not be valid. The caller should check the path's validity and that the
/// path exists.
/// </summary>
Expand All @@ -178,7 +178,7 @@ public string Rlocation(string path)
throw new ArgumentException($"path is absolute without a drive letter: \"{path}\"");
}

if (Path.IsPathFullyQualified(path))
if (IsPathFullyQualified(path))
{
return path;
}
Expand All @@ -193,6 +193,93 @@ public string Rlocation(string path)
/// </summary>
public abstract IDictionary<string, string> GetEnvVars();

/// <summary>
/// Returns true if the path is fixed to a specific drive or UNC path. This method does no
/// validation of the path (URIs will be returned as relative as a result).
/// Returns false if the path specified is relative to the current drive or working directory.
/// </summary>
/// <param name="path">Path to check.</param>
/// <remarks>
/// Handles paths that use the alternate directory separator. It is a frequent mistake to
/// assume that rooted paths <see cref="System.IO.Path.IsPathRooted(string)"/> are not relative. This isn't the case.
/// "C:a" is drive relative- meaning that it will be resolved against the current directory
/// for C: (rooted, but relative). "C:\a" is rooted and not relative (the current directory
/// will not be used to modify the path).
/// </remarks>
/// <exception cref="ArgumentNullException">
/// Thrown if <paramref name="path"/> is null.
/// </exception>
private static bool IsPathFullyQualified(string path)
{
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}

return !IsPathPartiallyQualified(path);
}

/// <summary>
/// Returns true if the path specified is relative to the current drive or working directory.
/// Returns false if the path is fixed to a specific drive or UNC path. This method does no
/// validation of the path (URIs will be returned as relative as a result).
/// </summary>
/// <remarks>
/// Handles paths that use the alternate directory separator. It is a frequent mistake to
/// assume that rooted paths (Path.IsPathRooted) are not relative. This isn't the case.
/// "C:a" is drive relative- meaning that it will be resolved against the current directory
/// for C: (rooted, but relative). "C:\a" is rooted and not relative (the current directory
/// will not be used to modify the path).
/// </remarks>
private static bool IsPathPartiallyQualified(string path)
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return !Path.IsPathRooted(path);
}
else
{
if (path.Length < 2)
{
// It isn't fixed, it must be relative. There is no way to specify a fixed
// path with one character (or less).
return false;
}

if (IsDirectorySeparator(path[0]))
{
// There is no valid way to specify a relative path with two initial slashes or
// \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\
return !(path[1] == '?' || IsDirectorySeparator(path[1]));
}

// The only way to specify a fixed path that doesn't begin with two slashes
// is the drive, colon, slash format- i.e. C:\
return !((path.Length >= 3)
&& (path[1] == Path.VolumeSeparatorChar)
&& IsDirectorySeparator(path[2])
// To match old behavior we'll check the drive character for validity as the path is technically
// not qualified if you don't have a valid drive. "=:\" is the "=" file's default data stream.
&& IsValidDriveChar(path[0]));
}
}

/// <summary>
/// True if the given character is a directory separator.
/// </summary>
private static bool IsDirectorySeparator(char character)
{
return character == Path.DirectorySeparatorChar || character == Path.AltDirectorySeparatorChar;
}

/// <summary>
/// Returns true if the given character is a valid drive letter
/// </summary>
private static bool IsValidDriveChar(char value)
{
return (uint)((value | 0x20) - 'a') <= (uint)('z' - 'a');
}

private static Boolean isManifestOnly(IDictionary<string, string> env)
{
env.TryGetValue("RUNFILES_MANIFEST_ONLY", out var value);
Expand Down
2 changes: 1 addition & 1 deletion tools/runfiles/Runfiles.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<EnableDefaultItems>false</EnableDefaultItems>
</PropertyGroup>
<ItemGroup>
Expand Down

0 comments on commit ab7e043

Please sign in to comment.