github.com/maruel/nin@v0.0.0-20220112143044-f35891e3ce7e/clparser.go (about) 1 // Copyright 2015 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package nin 16 17 import ( 18 "strings" 19 ) 20 21 // CLParser parses Visual Studio's cl.exe dependency output. 22 // 23 // It requires some massaging to work with Ninja; for example, it emits include 24 // information on stderr in a funny format when building with /showIncludes. 25 // This class parses this output. 26 type CLParser struct { 27 includes map[string]struct{} 28 } 29 30 // NewCLParser returns an initialized CLParser. 31 func NewCLParser() CLParser { 32 return CLParser{includes: map[string]struct{}{}} 33 } 34 35 // Parse a line of cl.exe output and extract /showIncludes info. 36 // If a dependency is extracted, returns a nonempty string. 37 // Exposed for testing. 38 func filterShowIncludes(line string, depsPrefix string) string { 39 const depsPrefixEnglish = "Note: including file: " 40 if depsPrefix == "" { 41 depsPrefix = depsPrefixEnglish 42 } 43 if strings.HasPrefix(line, depsPrefix) { 44 return strings.TrimLeft(line[len(depsPrefix):], " ") 45 } 46 return "" 47 } 48 49 // Return true if a mentioned include file is a system path. 50 // Filtering these out reduces dependency information considerably. 51 func isSystemInclude(path string) bool { 52 // TODO(maruel): The C++ code does it only for ASCII. 53 path = strings.ToLower(path) 54 // TODO: this is a heuristic, perhaps there's a better way? 55 return strings.Contains(path, "program files") || strings.Contains(path, "microsoft visual studio") 56 } 57 58 // Parse a line of cl.exe output and return true if it looks like 59 // it's printing an input filename. This is a heuristic but it appears 60 // to be the best we can do. 61 // Exposed for testing. 62 func filterInputFilename(line string) bool { 63 // TODO(maruel): The C++ code does it only for ASCII. 64 line = strings.ToLower(line) 65 // TODO: other extensions, like .asm? 66 return strings.HasSuffix(line, ".c") || 67 strings.HasSuffix(line, ".cc") || 68 strings.HasSuffix(line, ".cxx") || 69 strings.HasSuffix(line, ".cpp") 70 } 71 72 // Parse the full output of cl, filling filteredOutput with the text that 73 // should be printed (if any). Returns true on success, or false with err 74 // filled. output must not be the same object as filteredObject. 75 func (c *CLParser) Parse(output, depsPrefix string, filteredOutput *string) error { 76 defer metricRecord("CLParser::Parse")() 77 // Loop over all lines in the output to process them. 78 start := 0 79 seenShowIncludes := false 80 normalizer, err := newIncludesNormalize(".") 81 if err != nil { 82 return err 83 } 84 for start < len(output) { 85 end := strings.IndexAny(output[start:], "\r\n") 86 if end == -1 { 87 end = len(output) 88 } else { 89 end += start 90 } 91 line := output[start:end] 92 93 include := filterShowIncludes(line, depsPrefix) 94 if len(include) != 0 { 95 seenShowIncludes = true 96 normalized, err := normalizer.Normalize(include) 97 if err != nil { 98 return err 99 } 100 if !isSystemInclude(normalized) { 101 c.includes[normalized] = struct{}{} 102 } 103 } else if !seenShowIncludes && filterInputFilename(line) { 104 // Drop it. 105 // TODO: if we support compiling multiple output files in a single 106 // cl.exe invocation, we should stash the filename. 107 } else { 108 *filteredOutput += line 109 *filteredOutput += "\n" 110 } 111 112 if end < len(output) && output[end] == '\r' { 113 end++ 114 } 115 if end < len(output) && output[end] == '\n' { 116 end++ 117 } 118 start = end 119 } 120 return nil 121 }