1*cc02d7e2SAndroid Build Coastguard Worker #region Copyright notice and license 2*cc02d7e2SAndroid Build Coastguard Worker 3*cc02d7e2SAndroid Build Coastguard Worker // Copyright 2018 gRPC authors. 4*cc02d7e2SAndroid Build Coastguard Worker // 5*cc02d7e2SAndroid Build Coastguard Worker // Licensed under the Apache License, Version 2.0 (the "License"); 6*cc02d7e2SAndroid Build Coastguard Worker // you may not use this file except in compliance with the License. 7*cc02d7e2SAndroid Build Coastguard Worker // You may obtain a copy of the License at 8*cc02d7e2SAndroid Build Coastguard Worker // 9*cc02d7e2SAndroid Build Coastguard Worker // http://www.apache.org/licenses/LICENSE-2.0 10*cc02d7e2SAndroid Build Coastguard Worker // 11*cc02d7e2SAndroid Build Coastguard Worker // Unless required by applicable law or agreed to in writing, software 12*cc02d7e2SAndroid Build Coastguard Worker // distributed under the License is distributed on an "AS IS" BASIS, 13*cc02d7e2SAndroid Build Coastguard Worker // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*cc02d7e2SAndroid Build Coastguard Worker // See the License for the specific language governing permissions and 15*cc02d7e2SAndroid Build Coastguard Worker // limitations under the License. 16*cc02d7e2SAndroid Build Coastguard Worker 17*cc02d7e2SAndroid Build Coastguard Worker #endregion 18*cc02d7e2SAndroid Build Coastguard Worker 19*cc02d7e2SAndroid Build Coastguard Worker using System; 20*cc02d7e2SAndroid Build Coastguard Worker using System.Collections.Generic; 21*cc02d7e2SAndroid Build Coastguard Worker using System.IO; 22*cc02d7e2SAndroid Build Coastguard Worker using System.Text; 23*cc02d7e2SAndroid Build Coastguard Worker using Microsoft.Build.Framework; 24*cc02d7e2SAndroid Build Coastguard Worker using Microsoft.Build.Utilities; 25*cc02d7e2SAndroid Build Coastguard Worker 26*cc02d7e2SAndroid Build Coastguard Worker namespace Grpc.Tools 27*cc02d7e2SAndroid Build Coastguard Worker { 28*cc02d7e2SAndroid Build Coastguard Worker internal static class DepFileUtil 29*cc02d7e2SAndroid Build Coastguard Worker { 30*cc02d7e2SAndroid Build Coastguard Worker /* 31*cc02d7e2SAndroid Build Coastguard Worker Sample dependency files. Notable features we have to deal with: 32*cc02d7e2SAndroid Build Coastguard Worker * Slash doubling, must normalize them. 33*cc02d7e2SAndroid Build Coastguard Worker * Spaces in file names. Cannot just "unwrap" the line on backslash at eof; 34*cc02d7e2SAndroid Build Coastguard Worker rather, treat every line as containing one file name except for one with 35*cc02d7e2SAndroid Build Coastguard Worker the ':' separator, as containing exactly two. 36*cc02d7e2SAndroid Build Coastguard Worker * Deal with ':' also being drive letter separator (second example). 37*cc02d7e2SAndroid Build Coastguard Worker 38*cc02d7e2SAndroid Build Coastguard Worker obj\Release\net45\/Foo.cs \ 39*cc02d7e2SAndroid Build Coastguard Worker obj\Release\net45\/FooGrpc.cs: C:/foo/include/google/protobuf/wrappers.proto\ 40*cc02d7e2SAndroid Build Coastguard Worker C:/projects/foo/src//foo.proto 41*cc02d7e2SAndroid Build Coastguard Worker 42*cc02d7e2SAndroid Build Coastguard Worker C:\projects\foo\src\./foo.grpc.pb.cc \ 43*cc02d7e2SAndroid Build Coastguard Worker C:\projects\foo\src\./foo.grpc.pb.h \ 44*cc02d7e2SAndroid Build Coastguard Worker C:\projects\foo\src\./foo.pb.cc \ 45*cc02d7e2SAndroid Build Coastguard Worker C:\projects\foo\src\./foo.pb.h: C:/foo/include/google/protobuf/wrappers.proto\ 46*cc02d7e2SAndroid Build Coastguard Worker C:/foo/include/google/protobuf/any.proto\ 47*cc02d7e2SAndroid Build Coastguard Worker C:/foo/include/google/protobuf/source_context.proto\ 48*cc02d7e2SAndroid Build Coastguard Worker C:/foo/include/google/protobuf/type.proto\ 49*cc02d7e2SAndroid Build Coastguard Worker foo.proto 50*cc02d7e2SAndroid Build Coastguard Worker */ 51*cc02d7e2SAndroid Build Coastguard Worker 52*cc02d7e2SAndroid Build Coastguard Worker /// <summary> 53*cc02d7e2SAndroid Build Coastguard Worker /// Read file names from the dependency file to the right of ':' 54*cc02d7e2SAndroid Build Coastguard Worker /// </summary> 55*cc02d7e2SAndroid Build Coastguard Worker /// <param name="protoDepDir">Relative path to the dependency cache, e. g. "out"</param> 56*cc02d7e2SAndroid Build Coastguard Worker /// <param name="proto">Relative path to the proto item, e. g. "foo/file.proto"</param> 57*cc02d7e2SAndroid Build Coastguard Worker /// <param name="log">A <see cref="TaskLoggingHelper"/> for logging</param> 58*cc02d7e2SAndroid Build Coastguard Worker /// <returns> 59*cc02d7e2SAndroid Build Coastguard Worker /// Array of the proto file <b>input</b> dependencies as written by protoc, or empty 60*cc02d7e2SAndroid Build Coastguard Worker /// array if the dependency file does not exist or cannot be parsed. 61*cc02d7e2SAndroid Build Coastguard Worker /// </returns> ReadDependencyInputs(string protoDepDir, string proto, TaskLoggingHelper log)62*cc02d7e2SAndroid Build Coastguard Worker public static string[] ReadDependencyInputs(string protoDepDir, string proto, 63*cc02d7e2SAndroid Build Coastguard Worker TaskLoggingHelper log) 64*cc02d7e2SAndroid Build Coastguard Worker { 65*cc02d7e2SAndroid Build Coastguard Worker string depFilename = GetDepFilenameForProto(protoDepDir, proto); 66*cc02d7e2SAndroid Build Coastguard Worker string[] lines = ReadDepFileLines(depFilename, false, log); 67*cc02d7e2SAndroid Build Coastguard Worker if (lines.Length == 0) 68*cc02d7e2SAndroid Build Coastguard Worker { 69*cc02d7e2SAndroid Build Coastguard Worker return lines; 70*cc02d7e2SAndroid Build Coastguard Worker } 71*cc02d7e2SAndroid Build Coastguard Worker 72*cc02d7e2SAndroid Build Coastguard Worker var result = new List<string>(); 73*cc02d7e2SAndroid Build Coastguard Worker bool skip = true; 74*cc02d7e2SAndroid Build Coastguard Worker foreach (string line in lines) 75*cc02d7e2SAndroid Build Coastguard Worker { 76*cc02d7e2SAndroid Build Coastguard Worker // Start at the only line separating dependency outputs from inputs. 77*cc02d7e2SAndroid Build Coastguard Worker int ix = skip ? FindLineSeparator(line) : -1; 78*cc02d7e2SAndroid Build Coastguard Worker skip = skip && ix < 0; 79*cc02d7e2SAndroid Build Coastguard Worker if (skip) { continue; } 80*cc02d7e2SAndroid Build Coastguard Worker string file = ExtractFilenameFromLine(line, ix + 1, line.Length); 81*cc02d7e2SAndroid Build Coastguard Worker if (file == "") 82*cc02d7e2SAndroid Build Coastguard Worker { 83*cc02d7e2SAndroid Build Coastguard Worker log.LogMessage(MessageImportance.Low, 84*cc02d7e2SAndroid Build Coastguard Worker $"Skipping unparsable dependency file {depFilename}.\nLine with error: '{line}'"); 85*cc02d7e2SAndroid Build Coastguard Worker return new string[0]; 86*cc02d7e2SAndroid Build Coastguard Worker } 87*cc02d7e2SAndroid Build Coastguard Worker 88*cc02d7e2SAndroid Build Coastguard Worker // Do not bend over backwards trying not to include a proto into its 89*cc02d7e2SAndroid Build Coastguard Worker // own list of dependencies. Since a file is not older than self, 90*cc02d7e2SAndroid Build Coastguard Worker // it is safe to add; this is purely a memory optimization. 91*cc02d7e2SAndroid Build Coastguard Worker if (file != proto) 92*cc02d7e2SAndroid Build Coastguard Worker { 93*cc02d7e2SAndroid Build Coastguard Worker result.Add(file); 94*cc02d7e2SAndroid Build Coastguard Worker } 95*cc02d7e2SAndroid Build Coastguard Worker } 96*cc02d7e2SAndroid Build Coastguard Worker return result.ToArray(); 97*cc02d7e2SAndroid Build Coastguard Worker } 98*cc02d7e2SAndroid Build Coastguard Worker 99*cc02d7e2SAndroid Build Coastguard Worker /// <summary> 100*cc02d7e2SAndroid Build Coastguard Worker /// Read file names from the dependency file to the left of ':' 101*cc02d7e2SAndroid Build Coastguard Worker /// </summary> 102*cc02d7e2SAndroid Build Coastguard Worker /// <param name="depFilename">Path to dependency file written by protoc</param> 103*cc02d7e2SAndroid Build Coastguard Worker /// <param name="log">A <see cref="TaskLoggingHelper"/> for logging</param> 104*cc02d7e2SAndroid Build Coastguard Worker /// <returns> 105*cc02d7e2SAndroid Build Coastguard Worker /// Array of the protoc-generated outputs from the given dependency file 106*cc02d7e2SAndroid Build Coastguard Worker /// written by protoc, or empty array if the file does not exist or cannot 107*cc02d7e2SAndroid Build Coastguard Worker /// be parsed. 108*cc02d7e2SAndroid Build Coastguard Worker /// </returns> 109*cc02d7e2SAndroid Build Coastguard Worker /// <remarks> 110*cc02d7e2SAndroid Build Coastguard Worker /// Since this is called after a protoc invocation, an unparsable or missing 111*cc02d7e2SAndroid Build Coastguard Worker /// file causes an error-level message to be logged. 112*cc02d7e2SAndroid Build Coastguard Worker /// </remarks> ReadDependencyOutputs(string depFilename, TaskLoggingHelper log)113*cc02d7e2SAndroid Build Coastguard Worker public static string[] ReadDependencyOutputs(string depFilename, 114*cc02d7e2SAndroid Build Coastguard Worker TaskLoggingHelper log) 115*cc02d7e2SAndroid Build Coastguard Worker { 116*cc02d7e2SAndroid Build Coastguard Worker string[] lines = ReadDepFileLines(depFilename, true, log); 117*cc02d7e2SAndroid Build Coastguard Worker if (lines.Length == 0) 118*cc02d7e2SAndroid Build Coastguard Worker { 119*cc02d7e2SAndroid Build Coastguard Worker return lines; 120*cc02d7e2SAndroid Build Coastguard Worker } 121*cc02d7e2SAndroid Build Coastguard Worker 122*cc02d7e2SAndroid Build Coastguard Worker var result = new List<string>(); 123*cc02d7e2SAndroid Build Coastguard Worker foreach (string line in lines) 124*cc02d7e2SAndroid Build Coastguard Worker { 125*cc02d7e2SAndroid Build Coastguard Worker int ix = FindLineSeparator(line); 126*cc02d7e2SAndroid Build Coastguard Worker string file = ExtractFilenameFromLine(line, 0, ix >= 0 ? ix : line.Length); 127*cc02d7e2SAndroid Build Coastguard Worker if (file == "") 128*cc02d7e2SAndroid Build Coastguard Worker { 129*cc02d7e2SAndroid Build Coastguard Worker log.LogError("Unable to parse generated dependency file {0}.\n" + 130*cc02d7e2SAndroid Build Coastguard Worker "Line with error: '{1}'", depFilename, line); 131*cc02d7e2SAndroid Build Coastguard Worker return new string[0]; 132*cc02d7e2SAndroid Build Coastguard Worker } 133*cc02d7e2SAndroid Build Coastguard Worker result.Add(file); 134*cc02d7e2SAndroid Build Coastguard Worker 135*cc02d7e2SAndroid Build Coastguard Worker // If this is the line with the separator, do not read further. 136*cc02d7e2SAndroid Build Coastguard Worker if (ix >= 0) { break; } 137*cc02d7e2SAndroid Build Coastguard Worker } 138*cc02d7e2SAndroid Build Coastguard Worker return result.ToArray(); 139*cc02d7e2SAndroid Build Coastguard Worker } 140*cc02d7e2SAndroid Build Coastguard Worker 141*cc02d7e2SAndroid Build Coastguard Worker /// <summary> 142*cc02d7e2SAndroid Build Coastguard Worker /// Construct relative dependency file name from directory hash and file name 143*cc02d7e2SAndroid Build Coastguard Worker /// </summary> 144*cc02d7e2SAndroid Build Coastguard Worker /// <param name="protoDepDir">Relative path to the dependency cache, e. g. "out"</param> 145*cc02d7e2SAndroid Build Coastguard Worker /// <param name="proto">Relative path to the proto item, e. g. "foo/file.proto"</param> 146*cc02d7e2SAndroid Build Coastguard Worker /// <returns> 147*cc02d7e2SAndroid Build Coastguard Worker /// Full relative path to the dependency file, e. g. 148*cc02d7e2SAndroid Build Coastguard Worker /// "out/deadbeef12345678_file.protodep" 149*cc02d7e2SAndroid Build Coastguard Worker /// </returns> 150*cc02d7e2SAndroid Build Coastguard Worker /// <remarks> 151*cc02d7e2SAndroid Build Coastguard Worker /// See <see cref="GetDirectoryHash"/> for notes on directory hash. 152*cc02d7e2SAndroid Build Coastguard Worker /// </remarks> GetDepFilenameForProto(string protoDepDir, string proto)153*cc02d7e2SAndroid Build Coastguard Worker public static string GetDepFilenameForProto(string protoDepDir, string proto) 154*cc02d7e2SAndroid Build Coastguard Worker { 155*cc02d7e2SAndroid Build Coastguard Worker string dirhash = GetDirectoryHash(proto); 156*cc02d7e2SAndroid Build Coastguard Worker string filename = Path.GetFileNameWithoutExtension(proto); 157*cc02d7e2SAndroid Build Coastguard Worker return Path.Combine(protoDepDir, $"{dirhash}_{filename}.protodep"); 158*cc02d7e2SAndroid Build Coastguard Worker } 159*cc02d7e2SAndroid Build Coastguard Worker 160*cc02d7e2SAndroid Build Coastguard Worker /// <summary> 161*cc02d7e2SAndroid Build Coastguard Worker /// Construct relative output directory with directory hash 162*cc02d7e2SAndroid Build Coastguard Worker /// </summary> 163*cc02d7e2SAndroid Build Coastguard Worker /// <param name="outputDir">Relative path to the output directory, e. g. "out"</param> 164*cc02d7e2SAndroid Build Coastguard Worker /// <param name="proto">Relative path to the proto item, e. g. "foo/file.proto"</param> 165*cc02d7e2SAndroid Build Coastguard Worker /// <returns> 166*cc02d7e2SAndroid Build Coastguard Worker /// Full relative path to the directory, e. g. "out/deadbeef12345678" 167*cc02d7e2SAndroid Build Coastguard Worker /// </returns> 168*cc02d7e2SAndroid Build Coastguard Worker /// <remarks> 169*cc02d7e2SAndroid Build Coastguard Worker /// See <see cref="GetDirectoryHash"/> for notes on directory hash. 170*cc02d7e2SAndroid Build Coastguard Worker /// </remarks> GetOutputDirWithHash(string outputDir, string proto)171*cc02d7e2SAndroid Build Coastguard Worker public static string GetOutputDirWithHash(string outputDir, string proto) 172*cc02d7e2SAndroid Build Coastguard Worker { 173*cc02d7e2SAndroid Build Coastguard Worker string dirhash = GetDirectoryHash(proto); 174*cc02d7e2SAndroid Build Coastguard Worker return Path.Combine(outputDir, dirhash); 175*cc02d7e2SAndroid Build Coastguard Worker } 176*cc02d7e2SAndroid Build Coastguard Worker 177*cc02d7e2SAndroid Build Coastguard Worker /// <summary> 178*cc02d7e2SAndroid Build Coastguard Worker /// Construct the directory hash from a relative file name 179*cc02d7e2SAndroid Build Coastguard Worker /// </summary> 180*cc02d7e2SAndroid Build Coastguard Worker /// <param name="proto">Relative path to the proto item, e. g. "foo/file.proto"</param> 181*cc02d7e2SAndroid Build Coastguard Worker /// <returns> 182*cc02d7e2SAndroid Build Coastguard Worker /// Directory hash based on the file name, e. g. "deadbeef12345678" 183*cc02d7e2SAndroid Build Coastguard Worker /// </returns> 184*cc02d7e2SAndroid Build Coastguard Worker /// <remarks> 185*cc02d7e2SAndroid Build Coastguard Worker /// Since a project may contain proto files with the same filename but in different 186*cc02d7e2SAndroid Build Coastguard Worker /// directories, a unique directory for the generated files is constructed based on the 187*cc02d7e2SAndroid Build Coastguard Worker /// proto file names directory. The directory path can be arbitrary, for example, 188*cc02d7e2SAndroid Build Coastguard Worker /// it can be outside of the project, or an absolute path including a drive letter, 189*cc02d7e2SAndroid Build Coastguard Worker /// or a UNC network path. A name constructed from such a path by, for example, 190*cc02d7e2SAndroid Build Coastguard Worker /// replacing disallowed name characters with an underscore, may well be over 191*cc02d7e2SAndroid Build Coastguard Worker /// filesystem's allowed path length, since it will be located under the project 192*cc02d7e2SAndroid Build Coastguard Worker /// and solution directories, which are also some level deep from the root. 193*cc02d7e2SAndroid Build Coastguard Worker /// Instead of creating long and unwieldy names for these proto sources, we cache 194*cc02d7e2SAndroid Build Coastguard Worker /// the full path of the name without the filename, as in e. g. "foo/file.proto" 195*cc02d7e2SAndroid Build Coastguard Worker /// will yield the name "deadbeef12345678", where that is a presumed hash value 196*cc02d7e2SAndroid Build Coastguard Worker /// of the string "foo". This allows the path to be short, unique (up to a hash 197*cc02d7e2SAndroid Build Coastguard Worker /// collision), and still allowing the user to guess their provenance. 198*cc02d7e2SAndroid Build Coastguard Worker /// </remarks> GetDirectoryHash(string proto)199*cc02d7e2SAndroid Build Coastguard Worker private static string GetDirectoryHash(string proto) 200*cc02d7e2SAndroid Build Coastguard Worker { 201*cc02d7e2SAndroid Build Coastguard Worker string dirname = Path.GetDirectoryName(proto); 202*cc02d7e2SAndroid Build Coastguard Worker if (Platform.IsFsCaseInsensitive) 203*cc02d7e2SAndroid Build Coastguard Worker { 204*cc02d7e2SAndroid Build Coastguard Worker dirname = dirname.ToLowerInvariant(); 205*cc02d7e2SAndroid Build Coastguard Worker } 206*cc02d7e2SAndroid Build Coastguard Worker 207*cc02d7e2SAndroid Build Coastguard Worker return HashString64Hex(dirname); 208*cc02d7e2SAndroid Build Coastguard Worker } 209*cc02d7e2SAndroid Build Coastguard Worker 210*cc02d7e2SAndroid Build Coastguard Worker // Get a 64-bit hash for a directory string. We treat it as if it were 211*cc02d7e2SAndroid Build Coastguard Worker // unique, since there are not so many distinct proto paths in a project. 212*cc02d7e2SAndroid Build Coastguard Worker // We take the first 64 bit of the string SHA1. 213*cc02d7e2SAndroid Build Coastguard Worker // Internal for tests access only. HashString64Hex(string str)214*cc02d7e2SAndroid Build Coastguard Worker internal static string HashString64Hex(string str) 215*cc02d7e2SAndroid Build Coastguard Worker { 216*cc02d7e2SAndroid Build Coastguard Worker using (var sha1 = System.Security.Cryptography.SHA1.Create()) 217*cc02d7e2SAndroid Build Coastguard Worker { 218*cc02d7e2SAndroid Build Coastguard Worker byte[] hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(str)); 219*cc02d7e2SAndroid Build Coastguard Worker var hashstr = new StringBuilder(16); 220*cc02d7e2SAndroid Build Coastguard Worker for (int i = 0; i < 8; i++) 221*cc02d7e2SAndroid Build Coastguard Worker { 222*cc02d7e2SAndroid Build Coastguard Worker hashstr.Append(hash[i].ToString("x2")); 223*cc02d7e2SAndroid Build Coastguard Worker } 224*cc02d7e2SAndroid Build Coastguard Worker return hashstr.ToString(); 225*cc02d7e2SAndroid Build Coastguard Worker } 226*cc02d7e2SAndroid Build Coastguard Worker } 227*cc02d7e2SAndroid Build Coastguard Worker 228*cc02d7e2SAndroid Build Coastguard Worker // Extract filename between 'beg' (inclusive) and 'end' (exclusive) from 229*cc02d7e2SAndroid Build Coastguard Worker // line 'line', skipping over trailing and leading whitespace, and, when 230*cc02d7e2SAndroid Build Coastguard Worker // 'end' is immediately past end of line 'line', also final '\' (used 231*cc02d7e2SAndroid Build Coastguard Worker // as a line continuation token in the dep file). 232*cc02d7e2SAndroid Build Coastguard Worker // Returns an empty string if the filename cannot be extracted. ExtractFilenameFromLine(string line, int beg, int end)233*cc02d7e2SAndroid Build Coastguard Worker static string ExtractFilenameFromLine(string line, int beg, int end) 234*cc02d7e2SAndroid Build Coastguard Worker { 235*cc02d7e2SAndroid Build Coastguard Worker while (beg < end && char.IsWhiteSpace(line[beg])) beg++; 236*cc02d7e2SAndroid Build Coastguard Worker if (beg < end && end == line.Length && line[end - 1] == '\\') end--; 237*cc02d7e2SAndroid Build Coastguard Worker while (beg < end && char.IsWhiteSpace(line[end - 1])) end--; 238*cc02d7e2SAndroid Build Coastguard Worker if (beg == end) return ""; 239*cc02d7e2SAndroid Build Coastguard Worker 240*cc02d7e2SAndroid Build Coastguard Worker string filename = line.Substring(beg, end - beg); 241*cc02d7e2SAndroid Build Coastguard Worker try 242*cc02d7e2SAndroid Build Coastguard Worker { 243*cc02d7e2SAndroid Build Coastguard Worker // Normalize file name. 244*cc02d7e2SAndroid Build Coastguard Worker return Path.Combine(Path.GetDirectoryName(filename), Path.GetFileName(filename)); 245*cc02d7e2SAndroid Build Coastguard Worker } 246*cc02d7e2SAndroid Build Coastguard Worker catch (Exception ex) when (Exceptions.IsIoRelated(ex)) 247*cc02d7e2SAndroid Build Coastguard Worker { 248*cc02d7e2SAndroid Build Coastguard Worker return ""; 249*cc02d7e2SAndroid Build Coastguard Worker } 250*cc02d7e2SAndroid Build Coastguard Worker } 251*cc02d7e2SAndroid Build Coastguard Worker 252*cc02d7e2SAndroid Build Coastguard Worker // Finds the index of the ':' separating dependency clauses in the line, 253*cc02d7e2SAndroid Build Coastguard Worker // not taking Windows drive spec into account. Returns the index of the 254*cc02d7e2SAndroid Build Coastguard Worker // separating ':', or -1 if no separator found. FindLineSeparator(string line)255*cc02d7e2SAndroid Build Coastguard Worker static int FindLineSeparator(string line) 256*cc02d7e2SAndroid Build Coastguard Worker { 257*cc02d7e2SAndroid Build Coastguard Worker // Mind this case where the first ':' is not separator: 258*cc02d7e2SAndroid Build Coastguard Worker // C:\foo\bar\.pb.h: C:/protobuf/wrappers.proto\ 259*cc02d7e2SAndroid Build Coastguard Worker int ix = line.IndexOf(':'); 260*cc02d7e2SAndroid Build Coastguard Worker if (ix <= 0 || ix == line.Length - 1 261*cc02d7e2SAndroid Build Coastguard Worker || (line[ix + 1] != '/' && line[ix + 1] != '\\') 262*cc02d7e2SAndroid Build Coastguard Worker || !char.IsLetter(line[ix - 1])) 263*cc02d7e2SAndroid Build Coastguard Worker { 264*cc02d7e2SAndroid Build Coastguard Worker return ix; // Not a windows drive: no letter before ':', or no '\' after. 265*cc02d7e2SAndroid Build Coastguard Worker } 266*cc02d7e2SAndroid Build Coastguard Worker for (int j = ix - 1; --j >= 0;) 267*cc02d7e2SAndroid Build Coastguard Worker { 268*cc02d7e2SAndroid Build Coastguard Worker if (!char.IsWhiteSpace(line[j])) 269*cc02d7e2SAndroid Build Coastguard Worker { 270*cc02d7e2SAndroid Build Coastguard Worker return ix; // Not space or BOL only before "X:/". 271*cc02d7e2SAndroid Build Coastguard Worker } 272*cc02d7e2SAndroid Build Coastguard Worker } 273*cc02d7e2SAndroid Build Coastguard Worker return line.IndexOf(':', ix + 1); 274*cc02d7e2SAndroid Build Coastguard Worker } 275*cc02d7e2SAndroid Build Coastguard Worker 276*cc02d7e2SAndroid Build Coastguard Worker // Read entire dependency file. The 'required' parameter controls error 277*cc02d7e2SAndroid Build Coastguard Worker // logging behavior in case the file not found. We require this file when 278*cc02d7e2SAndroid Build Coastguard Worker // compiling, but reading it is optional when computing dependencies. ReadDepFileLines(string filename, bool required, TaskLoggingHelper log)279*cc02d7e2SAndroid Build Coastguard Worker static string[] ReadDepFileLines(string filename, bool required, 280*cc02d7e2SAndroid Build Coastguard Worker TaskLoggingHelper log) 281*cc02d7e2SAndroid Build Coastguard Worker { 282*cc02d7e2SAndroid Build Coastguard Worker try 283*cc02d7e2SAndroid Build Coastguard Worker { 284*cc02d7e2SAndroid Build Coastguard Worker var result = File.ReadAllLines(filename); 285*cc02d7e2SAndroid Build Coastguard Worker if (!required) 286*cc02d7e2SAndroid Build Coastguard Worker { 287*cc02d7e2SAndroid Build Coastguard Worker log.LogMessage(MessageImportance.Low, $"Using dependency file {filename}"); 288*cc02d7e2SAndroid Build Coastguard Worker } 289*cc02d7e2SAndroid Build Coastguard Worker return result; 290*cc02d7e2SAndroid Build Coastguard Worker } 291*cc02d7e2SAndroid Build Coastguard Worker catch (Exception ex) when (Exceptions.IsIoRelated(ex)) 292*cc02d7e2SAndroid Build Coastguard Worker { 293*cc02d7e2SAndroid Build Coastguard Worker if (required) 294*cc02d7e2SAndroid Build Coastguard Worker { 295*cc02d7e2SAndroid Build Coastguard Worker log.LogError($"Unable to load {filename}: {ex.GetType().Name}: {ex.Message}"); 296*cc02d7e2SAndroid Build Coastguard Worker } 297*cc02d7e2SAndroid Build Coastguard Worker else 298*cc02d7e2SAndroid Build Coastguard Worker { 299*cc02d7e2SAndroid Build Coastguard Worker log.LogMessage(MessageImportance.Low, $"Skipping {filename}: {ex.Message}"); 300*cc02d7e2SAndroid Build Coastguard Worker } 301*cc02d7e2SAndroid Build Coastguard Worker return new string[0]; 302*cc02d7e2SAndroid Build Coastguard Worker } 303*cc02d7e2SAndroid Build Coastguard Worker } 304*cc02d7e2SAndroid Build Coastguard Worker }; 305*cc02d7e2SAndroid Build Coastguard Worker } 306