aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/csharp/Grpc.Tools/DepFileUtil.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/csharp/Grpc.Tools/DepFileUtil.cs')
-rw-r--r--src/csharp/Grpc.Tools/DepFileUtil.cs450
1 files changed, 241 insertions, 209 deletions
diff --git a/src/csharp/Grpc.Tools/DepFileUtil.cs b/src/csharp/Grpc.Tools/DepFileUtil.cs
index e635ad1e85..440d3d535c 100644
--- a/src/csharp/Grpc.Tools/DepFileUtil.cs
+++ b/src/csharp/Grpc.Tools/DepFileUtil.cs
@@ -23,219 +23,251 @@ using System.Text;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
-namespace Grpc.Tools {
- internal static class DepFileUtil {
- /*
- Sample dependency files. Notable features we have to deal with:
- * Slash doubling, must normalize them.
- * Spaces in file names. Cannot just "unwrap" the line on backslash at eof;
- rather, treat every line as containing one file name except for one with
- the ':' separator, as containing exactly two.
- * Deal with ':' also being drive letter separator (second example).
-
- obj\Release\net45\/Foo.cs \
- obj\Release\net45\/FooGrpc.cs: C:/foo/include/google/protobuf/wrappers.proto\
- C:/projects/foo/src//foo.proto
-
- C:\projects\foo\src\./foo.grpc.pb.cc \
- C:\projects\foo\src\./foo.grpc.pb.h \
- C:\projects\foo\src\./foo.pb.cc \
- C:\projects\foo\src\./foo.pb.h: C:/foo/include/google/protobuf/wrappers.proto\
- C:/foo/include/google/protobuf/any.proto\
- C:/foo/include/google/protobuf/source_context.proto\
- C:/foo/include/google/protobuf/type.proto\
- foo.proto
- */
-
- /// <summary>
- /// Read file names from the dependency file to the right of ':'
- /// </summary>
- /// <param name="protoDepDir">Relative path to the dependency cache, e. g. "out"</param>
- /// <param name="proto">Relative path to the proto item, e. g. "foo/file.proto"</param>
- /// <param name="log">A <see cref="TaskLoggingHelper"/> for logging</param>
- /// <returns>
- /// Array of the proto file <b>input</b> dependencies as written by protoc, or empty
- /// array if the dependency file does not exist or cannot be parsed.
- /// </returns>
- public static string[] ReadDependencyInputs(string protoDepDir, string proto,
- TaskLoggingHelper log) {
- string depFilename = GetDepFilenameForProto(protoDepDir, proto);
- string[] lines = ReadDepFileLines(depFilename, false, log);
- if (lines.Length == 0) {
- return lines;
- }
-
- var result = new List<string>();
- bool skip = true;
- foreach (string line in lines) {
- // Start at the only line separating dependency outputs from inputs.
- int ix = skip ? FindLineSeparator(line) : -1;
- skip = skip && ix < 0;
- if (skip) continue;
- string file = ExtractFilenameFromLine(line, ix + 1, line.Length);
- if (file == "") {
- log.LogMessage(MessageImportance.Low,
- $"Skipping unparsable dependency file {depFilename}.\nLine with error: '{line}'");
- return new string[0];
+namespace Grpc.Tools
+{
+ internal static class DepFileUtil
+ {
+ /*
+ Sample dependency files. Notable features we have to deal with:
+ * Slash doubling, must normalize them.
+ * Spaces in file names. Cannot just "unwrap" the line on backslash at eof;
+ rather, treat every line as containing one file name except for one with
+ the ':' separator, as containing exactly two.
+ * Deal with ':' also being drive letter separator (second example).
+
+ obj\Release\net45\/Foo.cs \
+ obj\Release\net45\/FooGrpc.cs: C:/foo/include/google/protobuf/wrappers.proto\
+ C:/projects/foo/src//foo.proto
+
+ C:\projects\foo\src\./foo.grpc.pb.cc \
+ C:\projects\foo\src\./foo.grpc.pb.h \
+ C:\projects\foo\src\./foo.pb.cc \
+ C:\projects\foo\src\./foo.pb.h: C:/foo/include/google/protobuf/wrappers.proto\
+ C:/foo/include/google/protobuf/any.proto\
+ C:/foo/include/google/protobuf/source_context.proto\
+ C:/foo/include/google/protobuf/type.proto\
+ foo.proto
+ */
+
+ /// <summary>
+ /// Read file names from the dependency file to the right of ':'
+ /// </summary>
+ /// <param name="protoDepDir">Relative path to the dependency cache, e. g. "out"</param>
+ /// <param name="proto">Relative path to the proto item, e. g. "foo/file.proto"</param>
+ /// <param name="log">A <see cref="TaskLoggingHelper"/> for logging</param>
+ /// <returns>
+ /// Array of the proto file <b>input</b> dependencies as written by protoc, or empty
+ /// array if the dependency file does not exist or cannot be parsed.
+ /// </returns>
+ public static string[] ReadDependencyInputs(string protoDepDir, string proto,
+ TaskLoggingHelper log)
+ {
+ string depFilename = GetDepFilenameForProto(protoDepDir, proto);
+ string[] lines = ReadDepFileLines(depFilename, false, log);
+ if (lines.Length == 0)
+ {
+ return lines;
+ }
+
+ var result = new List<string>();
+ bool skip = true;
+ foreach (string line in lines)
+ {
+ // Start at the only line separating dependency outputs from inputs.
+ int ix = skip ? FindLineSeparator(line) : -1;
+ skip = skip && ix < 0;
+ if (skip) { continue; }
+ string file = ExtractFilenameFromLine(line, ix + 1, line.Length);
+ if (file == "")
+ {
+ log.LogMessage(MessageImportance.Low,
+ $"Skipping unparsable dependency file {depFilename}.\nLine with error: '{line}'");
+ return new string[0];
+ }
+
+ // Do not bend over backwards trying not to include a proto into its
+ // own list of dependencies. Since a file is not older than self,
+ // it is safe to add; this is purely a memory optimization.
+ if (file != proto)
+ {
+ result.Add(file);
+ }
+ }
+ return result.ToArray();
}
- // Do not bend over backwards trying not to include a proto into its
- // own list of dependencies. Since a file is not older than self,
- // it is safe to add; this is purely a memory optimization.
- if (file != proto) {
- result.Add(file);
+ /// <summary>
+ /// Read file names from the dependency file to the left of ':'
+ /// </summary>
+ /// <param name="depFilename">Path to dependency file written by protoc</param>
+ /// <param name="log">A <see cref="TaskLoggingHelper"/> for logging</param>
+ /// <returns>
+ /// Array of the protoc-generated outputs from the given dependency file
+ /// written by protoc, or empty array if the file does not exist or cannot
+ /// be parsed.
+ /// </returns>
+ /// <remarks>
+ /// Since this is called after a protoc invocation, an unparsable or missing
+ /// file causes an error-level message to be logged.
+ /// </remarks>
+ public static string[] ReadDependencyOutputs(string depFilename,
+ TaskLoggingHelper log)
+ {
+ string[] lines = ReadDepFileLines(depFilename, true, log);
+ if (lines.Length == 0)
+ {
+ return lines;
+ }
+
+ var result = new List<string>();
+ foreach (string line in lines)
+ {
+ int ix = FindLineSeparator(line);
+ string file = ExtractFilenameFromLine(line, 0, ix >= 0 ? ix : line.Length);
+ if (file == "")
+ {
+ log.LogError("Unable to parse generated dependency file {0}.\n" +
+ "Line with error: '{1}'", depFilename, line);
+ return new string[0];
+ }
+ result.Add(file);
+
+ // If this is the line with the separator, do not read further.
+ if (ix >= 0) { break; }
+ }
+ return result.ToArray();
}
- }
- return result.ToArray();
- }
-
- /// <summary>
- /// Read file names from the dependency file to the left of ':'
- /// </summary>
- /// <param name="depFilename">Path to dependency file written by protoc</param>
- /// <param name="log">A <see cref="TaskLoggingHelper"/> for logging</param>
- /// <returns>
- /// Array of the protoc-generated outputs from the given dependency file
- /// written by protoc, or empty array if the file does not exist or cannot
- /// be parsed.
- /// </returns>
- /// <remarks>
- /// Since this is called after a protoc invocation, an unparsable or missing
- /// file causes an error-level message to be logged.
- /// </remarks>
- public static string[] ReadDependencyOutputs(string depFilename,
- TaskLoggingHelper log) {
- string[] lines = ReadDepFileLines(depFilename, true, log);
- if (lines.Length == 0) {
- return lines;
- }
-
- var result = new List<string>();
- foreach (string line in lines) {
- int ix = FindLineSeparator(line);
- string file = ExtractFilenameFromLine(line, 0, ix >= 0 ? ix : line.Length);
- if (file == "") {
- log.LogError("Unable to parse generated dependency file {0}.\n" +
- "Line with error: '{1}'", depFilename, line);
- return new string[0];
+
+ /// <summary>
+ /// Construct relative dependency file name from directory hash and file name
+ /// </summary>
+ /// <param name="protoDepDir">Relative path to the dependency cache, e. g. "out"</param>
+ /// <param name="proto">Relative path to the proto item, e. g. "foo/file.proto"</param>
+ /// <returns>
+ /// Full relative path to the dependency file, e. g.
+ /// "out/deadbeef12345678_file.protodep"
+ /// </returns>
+ /// <remarks>
+ /// Since a project may contain proto files with the same filename but in different
+ /// directories, a unique filename for the dependency file is constructed based on the
+ /// proto file name both name and directory. The directory path can be arbitrary,
+ /// for example, it can be outside of the project, or an absolute path including
+ /// a drive letter, or a UNC network path. A name constructed from such a path by,
+ /// for example, replacing disallowed name characters with an underscore, may well
+ /// be over filesystem's allowed path length, since it will be located under the
+ /// project and solution directories, which are also some level deep from the root.
+ /// Instead of creating long and unwieldy names for these proto sources, we cache
+ /// the full path of the name without the filename, and append the filename to it,
+ /// as in e. g. "foo/file.proto" will yield the name "deadbeef12345678_file", where
+ /// "deadbeef12345678" is a presumed hash value of the string "foo/". This allows
+ /// the file names be short, unique (up to a hash collision), and still allowing
+ /// the user to guess their provenance.
+ /// </remarks>
+ public static string GetDepFilenameForProto(string protoDepDir, string proto)
+ {
+ string dirname = Path.GetDirectoryName(proto);
+ if (Platform.IsFsCaseInsensitive)
+ {
+ dirname = dirname.ToLowerInvariant();
+ }
+ string dirhash = HashString64Hex(dirname);
+ string filename = Path.GetFileNameWithoutExtension(proto);
+ return Path.Combine(protoDepDir, $"{dirhash}_{filename}.protodep");
}
- result.Add(file);
-
- // If this is the line with the separator, do not read further.
- if (ix >= 0)
- break;
- }
- return result.ToArray();
- }
-
- /// <summary>
- /// Construct relative dependency file name from directory hash and file name
- /// </summary>
- /// <param name="protoDepDir">Relative path to the dependency cache, e. g. "out"</param>
- /// <param name="proto">Relative path to the proto item, e. g. "foo/file.proto"</param>
- /// <returns>
- /// Full relative path to the dependency file, e. g.
- /// "out/deadbeef12345678_file.protodep"
- /// </returns>
- /// <remarks>
- /// Since a project may contain proto files with the same filename but in different
- /// directories, a unique filename for the dependency file is constructed based on the
- /// proto file name both name and directory. The directory path can be arbitrary,
- /// for example, it can be outside of the project, or an absolute path including
- /// a drive letter, or a UNC network path. A name constructed from such a path by,
- /// for example, replacing disallowed name characters with an underscore, may well
- /// be over filesystem's allowed path length, since it will be located under the
- /// project and solution directories, which are also some level deep from the root.
- /// Instead of creating long and unwieldy names for these proto sources, we cache
- /// the full path of the name without the filename, and append the filename to it,
- /// as in e. g. "foo/file.proto" will yield the name "deadbeef12345678_file", where
- /// "deadbeef12345678" is a presumed hash value of the string "foo/". This allows
- /// the file names be short, unique (up to a hash collision), and still allowing
- /// the user to guess their provenance.
- /// </remarks>
- public static string GetDepFilenameForProto(string protoDepDir, string proto) {
- string dirname = Path.GetDirectoryName(proto);
- if (Platform.IsFsCaseInsensitive) {
- dirname = dirname.ToLowerInvariant();
- }
- string dirhash = HashString64Hex(dirname);
- string filename = Path.GetFileNameWithoutExtension(proto);
- return Path.Combine(protoDepDir, $"{dirhash}_{filename}.protodep");
- }
-
- // Get a 64-bit hash for a directory string. We treat it as if it were
- // unique, since there are not so many distinct proto paths in a project.
- // We take the first 64 bit of the string SHA1.
- // Internal for tests access only.
- internal static string HashString64Hex(string str) {
- using (var sha1 = System.Security.Cryptography.SHA1.Create()) {
- byte[] hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(str));
- var hashstr = new StringBuilder(16);
- for (int i = 0; i < 8; i++) {
- hashstr.Append(hash[i].ToString("x2"));
+
+ // Get a 64-bit hash for a directory string. We treat it as if it were
+ // unique, since there are not so many distinct proto paths in a project.
+ // We take the first 64 bit of the string SHA1.
+ // Internal for tests access only.
+ internal static string HashString64Hex(string str)
+ {
+ using (var sha1 = System.Security.Cryptography.SHA1.Create())
+ {
+ byte[] hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(str));
+ var hashstr = new StringBuilder(16);
+ for (int i = 0; i < 8; i++)
+ {
+ hashstr.Append(hash[i].ToString("x2"));
+ }
+ return hashstr.ToString();
+ }
+ }
+
+ // Extract filename between 'beg' (inclusive) and 'end' (exclusive) from
+ // line 'line', skipping over trailing and leading whitespace, and, when
+ // 'end' is immediately past end of line 'line', also final '\' (used
+ // as a line continuation token in the dep file).
+ // Returns an empty string if the filename cannot be extracted.
+ static string ExtractFilenameFromLine(string line, int beg, int end)
+ {
+ while (beg < end && char.IsWhiteSpace(line[beg])) beg++;
+ if (beg < end && end == line.Length && line[end - 1] == '\\') end--;
+ while (beg < end && char.IsWhiteSpace(line[end - 1])) end--;
+ if (beg == end) return "";
+
+ string filename = line.Substring(beg, end - beg);
+ try
+ {
+ // Normalize file name.
+ return Path.Combine(Path.GetDirectoryName(filename), Path.GetFileName(filename));
+ }
+ catch (Exception ex) when (Exceptions.IsIoRelated(ex))
+ {
+ return "";
+ }
}
- return hashstr.ToString();
- }
- }
-
- // Extract filename between 'beg' (inclusive) and 'end' (exclusive) from
- // line 'line', skipping over trailing and leading whitespace, and, when
- // 'end' is immediately past end of line 'line', also final '\' (used
- // as a line continuation token in the dep file).
- // Returns an empty string if the filename cannot be extracted.
- static string ExtractFilenameFromLine(string line, int beg, int end) {
- while (beg < end && char.IsWhiteSpace(line[beg])) beg++;
- if (beg < end && end == line.Length && line[end - 1] == '\\') end--;
- while (beg < end && char.IsWhiteSpace(line[end - 1])) end--;
- if (beg == end) return "";
-
- string filename = line.Substring(beg, end - beg);
- try {
- // Normalize file name.
- return Path.Combine(
- Path.GetDirectoryName(filename),
- Path.GetFileName(filename));
- } catch (Exception ex) when (Exceptions.IsIoRelated(ex)) {
- return "";
- }
- }
-
- // Finds the index of the ':' separating dependency clauses in the line,
- // not taking Windows drive spec into account. Returns the index of the
- // separating ':', or -1 if no separator found.
- static int FindLineSeparator(string line) {
- // Mind this case where the first ':' is not separator:
- // C:\foo\bar\.pb.h: C:/protobuf/wrappers.proto\
- int ix = line.IndexOf(':');
- if (ix <= 0 || ix == line.Length - 1
- || (line[ix + 1] != '/' && line[ix + 1] != '\\')
- || !char.IsLetter(line[ix - 1]))
- return ix; // Not a windows drive: no letter before ':', or no '\' after.
- for (int j = ix - 1; --j >= 0;) {
- if (!char.IsWhiteSpace(line[j]))
- return ix; // Not space or BOL only before "X:/".
- }
- return line.IndexOf(':', ix + 1);
- }
-
- // Read entire dependency file. The 'required' parameter controls error
- // logging behavior in case the file not found. We require this file when
- // compiling, but reading it is optional when computing dependencies.
- static string[] ReadDepFileLines(string filename, bool required,
- TaskLoggingHelper log) {
- try {
- var result = File.ReadAllLines(filename);
- if (!required)
- log.LogMessage(MessageImportance.Low, $"Using dependency file {filename}");
- return result;
- } catch (Exception ex) when (Exceptions.IsIoRelated(ex)) {
- if (required) {
- log.LogError($"Unable to load {filename}: {ex.GetType().Name}: {ex.Message}");
- } else {
- log.LogMessage(MessageImportance.Low, $"Skipping {filename}: {ex.Message}");
+
+ // Finds the index of the ':' separating dependency clauses in the line,
+ // not taking Windows drive spec into account. Returns the index of the
+ // separating ':', or -1 if no separator found.
+ static int FindLineSeparator(string line)
+ {
+ // Mind this case where the first ':' is not separator:
+ // C:\foo\bar\.pb.h: C:/protobuf/wrappers.proto\
+ int ix = line.IndexOf(':');
+ if (ix <= 0 || ix == line.Length - 1
+ || (line[ix + 1] != '/' && line[ix + 1] != '\\')
+ || !char.IsLetter(line[ix - 1]))
+ {
+ return ix; // Not a windows drive: no letter before ':', or no '\' after.
+ }
+ for (int j = ix - 1; --j >= 0;)
+ {
+ if (!char.IsWhiteSpace(line[j]))
+ {
+ return ix; // Not space or BOL only before "X:/".
+ }
+ }
+ return line.IndexOf(':', ix + 1);
+ }
+
+ // Read entire dependency file. The 'required' parameter controls error
+ // logging behavior in case the file not found. We require this file when
+ // compiling, but reading it is optional when computing dependencies.
+ static string[] ReadDepFileLines(string filename, bool required,
+ TaskLoggingHelper log)
+ {
+ try
+ {
+ var result = File.ReadAllLines(filename);
+ if (!required)
+ {
+ log.LogMessage(MessageImportance.Low, $"Using dependency file {filename}");
+ }
+ return result;
+ }
+ catch (Exception ex) when (Exceptions.IsIoRelated(ex))
+ {
+ if (required)
+ {
+ log.LogError($"Unable to load {filename}: {ex.GetType().Name}: {ex.Message}");
+ }
+ else
+ {
+ log.LogMessage(MessageImportance.Low, $"Skipping {filename}: {ex.Message}");
+ }
+ return new string[0];
+ }
}
- return new string[0];
- }
- }
- };
+ };
}