diff --git a/tools/runfiles/BUILD.bazel b/tools/runfiles/BUILD.bazel index a715e113..14929c3c 100644 --- a/tools/runfiles/BUILD.bazel +++ b/tools/runfiles/BUILD.bazel @@ -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 = [ ], diff --git a/tools/runfiles/Runfiles.cs b/tools/runfiles/Runfiles.cs index 0190e3a6..a9bc716e 100644 --- a/tools/runfiles/Runfiles.cs +++ b/tools/runfiles/Runfiles.cs @@ -15,7 +15,7 @@ namespace Bazel /// USAGE: /// /// 1. Depend on this runfiles library from your build rule: - /// + /// /// /// csharp_binary( /// name = "my_binary", @@ -23,7 +23,7 @@ namespace Bazel /// deps = ["@bazel_tools//tools/java/runfiles"], /// ) /// - /// + /// /// 2. Import the runfiles library. /// /// @@ -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(); /// @@ -83,10 +83,10 @@ public static Runfiles Create() /// If 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 . /// - /// If contains RUNFILES_DIR=SOME_DIRECTORY or JAVA_RUNFILES=SOME_DIRECTORY, + /// If 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. @@ -153,7 +153,7 @@ public static Runfiles Create(string argv0, IDictionary env) /// /// 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. /// @@ -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; } @@ -193,6 +193,93 @@ public string Rlocation(string path) /// public abstract IDictionary GetEnvVars(); + /// + /// 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. + /// + /// Path to check. + /// + /// Handles paths that use the alternate directory separator. It is a frequent mistake to + /// assume that rooted paths 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). + /// + /// + /// Thrown if is null. + /// + private static bool IsPathFullyQualified(string path) + { + if (path == null) + { + throw new ArgumentNullException(nameof(path)); + } + + return !IsPathPartiallyQualified(path); + } + + /// + /// 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). + /// + /// + /// 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). + /// + 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])); + } + } + + /// + /// True if the given character is a directory separator. + /// + private static bool IsDirectorySeparator(char character) + { + return character == Path.DirectorySeparatorChar || character == Path.AltDirectorySeparatorChar; + } + + /// + /// Returns true if the given character is a valid drive letter + /// + private static bool IsValidDriveChar(char value) + { + return (uint)((value | 0x20) - 'a') <= (uint)('z' - 'a'); + } + private static Boolean isManifestOnly(IDictionary env) { env.TryGetValue("RUNFILES_MANIFEST_ONLY", out var value); diff --git a/tools/runfiles/Runfiles.csproj b/tools/runfiles/Runfiles.csproj index 83c3bbf5..83b7ca42 100644 --- a/tools/runfiles/Runfiles.csproj +++ b/tools/runfiles/Runfiles.csproj @@ -1,7 +1,7 @@ - netstandard2.1 + netstandard2.0 false