xref: /aosp_15_r20/external/grpc-grpc/src/csharp/Grpc.Tools/DepFileUtil.cs (revision cc02d7e222339f7a4f6ba5f422e6413f4bd931f2)
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