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  }