github.com/maruel/nin@v0.0.0-20220112143044-f35891e3ce7e/dyndep_parser.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 "fmt"
    18  
    19  // dyndepParser parses dyndep files.
    20  type dyndepParser struct {
    21  	// Mutable.
    22  	lexer      lexer
    23  	state      *State
    24  	dyndepFile DyndepFile
    25  	env        *BindingEnv
    26  }
    27  
    28  // ParseDyndep parses a dyndep file provided as an input with null terminated
    29  // string.
    30  //
    31  // It updates state and dyndepFile.
    32  func ParseDyndep(state *State, dyndepFile DyndepFile, filename string, input []byte) error {
    33  	d := dyndepParser{
    34  		state:      state,
    35  		dyndepFile: dyndepFile,
    36  	}
    37  	return d.parse(filename, input)
    38  }
    39  
    40  // If the next token is not \a expected, produce an error string
    41  // saying "expected foo, got bar".
    42  func (d *dyndepParser) expectToken(expected Token) error {
    43  	if token := d.lexer.ReadToken(); token != expected {
    44  		return d.lexer.Error("expected " + expected.String() + ", got " + token.String() + expected.errorHint())
    45  	}
    46  	return nil
    47  }
    48  
    49  // Parse a file, given its contents as a string.
    50  func (d *dyndepParser) parse(filename string, input []byte) error {
    51  	defer metricRecord(".ninja parse")()
    52  	if err := d.lexer.Start(filename, input); err != nil {
    53  		return err
    54  	}
    55  
    56  	// Require a supported ninjaDyndepVersion value immediately so
    57  	// we can exit before encountering any syntactic surprises.
    58  	haveDyndepVersion := false
    59  
    60  	for {
    61  		token := d.lexer.ReadToken()
    62  		switch token {
    63  		case BUILD:
    64  			if !haveDyndepVersion {
    65  				return d.lexer.Error("expected 'ninja_dyndep_version = ...'")
    66  			}
    67  			if err := d.parseEdge(); err != nil {
    68  				return err
    69  			}
    70  		case IDENT:
    71  			d.lexer.UnreadToken()
    72  			if haveDyndepVersion {
    73  				return d.lexer.Error("unexpected " + token.String())
    74  			}
    75  			if err := d.parseDyndepVersion(); err != nil {
    76  				return err
    77  			}
    78  			haveDyndepVersion = true
    79  		case ERROR:
    80  			return d.lexer.Error(d.lexer.DescribeLastError())
    81  		case TEOF:
    82  			if !haveDyndepVersion {
    83  				return d.lexer.Error("expected 'ninja_dyndep_version = ...'")
    84  			}
    85  			return nil
    86  		case NEWLINE:
    87  		default:
    88  			return d.lexer.Error("unexpected " + token.String())
    89  		}
    90  	}
    91  }
    92  
    93  func (d *dyndepParser) parseDyndepVersion() error {
    94  	name, letValue, err := d.parseLet()
    95  	if err != nil {
    96  		return err
    97  	}
    98  	if name != "ninja_dyndep_version" {
    99  		return d.lexer.Error("expected 'ninja_dyndep_version = ...'")
   100  	}
   101  	version := letValue.Evaluate(d.env)
   102  	major, minor := parseVersion(version)
   103  	if major != 1 || minor != 0 {
   104  		return d.lexer.Error("unsupported 'ninja_dyndep_version = " + version + "'")
   105  	}
   106  	return nil
   107  }
   108  
   109  func (d *dyndepParser) parseLet() (string, EvalString, error) {
   110  	key := d.lexer.readIdent()
   111  	eval := EvalString{}
   112  	var err error
   113  	if key == "" {
   114  		err = d.lexer.Error("expected variable name")
   115  	} else if err = d.expectToken(EQUALS); err == nil {
   116  		eval, err = d.lexer.readEvalString(false)
   117  	}
   118  	return key, eval, err
   119  }
   120  
   121  func (d *dyndepParser) parseEdge() error {
   122  	// Parse one explicit output.  We expect it to already have an edge.
   123  	// We will record its dynamically-discovered dependency information.
   124  	var dyndeps *Dyndeps
   125  	eval, err := d.lexer.readEvalString(true)
   126  	if err != nil {
   127  		return err
   128  	} else if len(eval.Parsed) == 0 {
   129  		return d.lexer.Error("expected path")
   130  	}
   131  
   132  	path := eval.Evaluate(d.env)
   133  	if len(path) == 0 {
   134  		return d.lexer.Error("empty path")
   135  	}
   136  	path = CanonicalizePath(path)
   137  	node := d.state.Paths[path]
   138  	if node == nil || node.InEdge == nil {
   139  		// TODO(maruel): Use %q for real quoting.
   140  		return d.lexer.Error(fmt.Sprintf("no build statement exists for '%s'", path))
   141  	}
   142  	edge := node.InEdge
   143  	if _, ok := d.dyndepFile[edge]; ok {
   144  		// TODO(maruel): Use %q for real quoting.
   145  		return d.lexer.Error(fmt.Sprintf("multiple statements for '%s'", path))
   146  	}
   147  	dyndeps = &Dyndeps{}
   148  	d.dyndepFile[edge] = dyndeps
   149  
   150  	// Disallow explicit outputs.
   151  	eval, err = d.lexer.readEvalString(true)
   152  	if err != nil {
   153  		return err
   154  	} else if len(eval.Parsed) != 0 {
   155  		return d.lexer.Error("explicit outputs not supported")
   156  	}
   157  
   158  	// Parse implicit outputs, if any.
   159  	var outs []EvalString
   160  	if d.lexer.PeekToken(PIPE) {
   161  		for {
   162  			eval, err = d.lexer.readEvalString(true)
   163  			if err != nil {
   164  				// TODO(maruel): Bug upstream.
   165  				return err
   166  			}
   167  			if len(eval.Parsed) == 0 {
   168  				break
   169  			}
   170  			outs = append(outs, eval)
   171  		}
   172  	}
   173  
   174  	if err = d.expectToken(COLON); err != nil {
   175  		return err
   176  	}
   177  
   178  	if ruleName := d.lexer.readIdent(); ruleName == "" || ruleName != "dyndep" {
   179  		return d.lexer.Error("expected build command name 'dyndep'")
   180  	}
   181  
   182  	// Disallow explicit inputs.
   183  	eval, err = d.lexer.readEvalString(true)
   184  	if err != nil {
   185  		return err
   186  	} else if len(eval.Parsed) != 0 {
   187  		return d.lexer.Error("explicit inputs not supported")
   188  	}
   189  
   190  	// Parse implicit inputs, if any.
   191  	var ins []EvalString
   192  	if d.lexer.PeekToken(PIPE) {
   193  		for {
   194  			eval, err = d.lexer.readEvalString(true)
   195  			if err != nil {
   196  				// TODO(maruel): Bug upstream.
   197  				return err
   198  			}
   199  			if len(eval.Parsed) == 0 {
   200  				break
   201  			}
   202  			ins = append(ins, eval)
   203  		}
   204  	}
   205  
   206  	// Disallow order-only inputs.
   207  	if d.lexer.PeekToken(PIPE2) {
   208  		return d.lexer.Error("order-only inputs not supported")
   209  	}
   210  
   211  	if err = d.expectToken(NEWLINE); err != nil {
   212  		return err
   213  	}
   214  
   215  	if d.lexer.PeekToken(INDENT) {
   216  		key, val, err := d.parseLet()
   217  		if err != nil {
   218  			return err
   219  		}
   220  		if key != "restat" {
   221  			return d.lexer.Error("binding is not 'restat'")
   222  		}
   223  		value := val.Evaluate(d.env)
   224  		dyndeps.restat = value != ""
   225  	}
   226  
   227  	dyndeps.implicitInputs = make([]*Node, 0, len(ins))
   228  	for _, i := range ins {
   229  		path := i.Evaluate(d.env)
   230  		if len(path) == 0 {
   231  			return d.lexer.Error("empty path")
   232  		}
   233  		n := d.state.GetNode(CanonicalizePathBits(path))
   234  		dyndeps.implicitInputs = append(dyndeps.implicitInputs, n)
   235  	}
   236  
   237  	dyndeps.implicitOutputs = make([]*Node, 0, len(outs))
   238  	for _, i := range outs {
   239  		path := i.Evaluate(d.env)
   240  		if len(path) == 0 {
   241  			return d.lexer.Error("empty path")
   242  		}
   243  		n := d.state.GetNode(CanonicalizePathBits(path))
   244  		dyndeps.implicitOutputs = append(dyndeps.implicitOutputs, n)
   245  	}
   246  	return nil
   247  }