629a6a3bc3
Bug: http://b/259130368 Test: built android, saw tags set in perfetto trace Change-Id: I09618114832b143a10a2f84baccfaf3aa0fd5bd8
419 lines
11 KiB
Go
419 lines
11 KiB
Go
// Copyright 2014 Google Inc. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package blueprint
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
)
|
|
|
|
const eof = -1
|
|
|
|
var (
|
|
defaultEscaper = strings.NewReplacer(
|
|
"\n", "$\n")
|
|
inputEscaper = strings.NewReplacer(
|
|
"\n", "$\n",
|
|
" ", "$ ")
|
|
outputEscaper = strings.NewReplacer(
|
|
"\n", "$\n",
|
|
" ", "$ ",
|
|
":", "$:")
|
|
)
|
|
|
|
// ninjaString contains the parsed result of a string that can contain references to variables (e.g. $cflags) that will
|
|
// be propagated to the build.ninja file. For literal strings with no variable references, the variables field will be
|
|
// nil. For strings with variable references str contains the original, unparsed string, and variables contains a
|
|
// pointer to a list of references, each with a span of bytes they should replace and a Variable interface.
|
|
type ninjaString struct {
|
|
str string
|
|
variables *[]variableReference
|
|
}
|
|
|
|
// variableReference contains information about a single reference to a variable (e.g. $cflags) inside a parsed
|
|
// ninjaString. start and end are int32 to reduce memory usage. A nil variable is a special case of an inserted '$'
|
|
// at the beginning of the string to handle leading whitespace that must not be stripped by ninja.
|
|
type variableReference struct {
|
|
// start is the offset of the '$' character from the beginning of the unparsed string.
|
|
start int32
|
|
|
|
// end is the offset of the character _after_ the final character of the variable name (or '}' if using the
|
|
//'${}' syntax)
|
|
end int32
|
|
|
|
variable Variable
|
|
}
|
|
|
|
type scope interface {
|
|
LookupVariable(name string) (Variable, error)
|
|
IsRuleVisible(rule Rule) bool
|
|
IsPoolVisible(pool Pool) bool
|
|
}
|
|
|
|
func simpleNinjaString(str string) *ninjaString {
|
|
return &ninjaString{str: str}
|
|
}
|
|
|
|
type parseState struct {
|
|
scope scope
|
|
str string
|
|
varStart int
|
|
varNameStart int
|
|
result *ninjaString
|
|
}
|
|
|
|
func (ps *parseState) pushVariable(start, end int, v Variable) {
|
|
if ps.result.variables == nil {
|
|
ps.result.variables = &[]variableReference{{start: int32(start), end: int32(end), variable: v}}
|
|
} else {
|
|
*ps.result.variables = append(*ps.result.variables, variableReference{start: int32(start), end: int32(end), variable: v})
|
|
}
|
|
}
|
|
|
|
type stateFunc func(*parseState, int, rune) (stateFunc, error)
|
|
|
|
// parseNinjaString parses an unescaped ninja string (i.e. all $<something>
|
|
// occurrences are expected to be variables or $$) and returns a list of the
|
|
// variable names that the string references.
|
|
func parseNinjaString(scope scope, str string) (*ninjaString, error) {
|
|
// naively pre-allocate slice by counting $ signs
|
|
n := strings.Count(str, "$")
|
|
if n == 0 {
|
|
if len(str) > 0 && str[0] == ' ' {
|
|
str = "$" + str
|
|
}
|
|
return simpleNinjaString(str), nil
|
|
}
|
|
variableReferences := make([]variableReference, 0, n)
|
|
result := &ninjaString{
|
|
str: str,
|
|
variables: &variableReferences,
|
|
}
|
|
|
|
parseState := &parseState{
|
|
scope: scope,
|
|
str: str,
|
|
result: result,
|
|
}
|
|
|
|
state := parseFirstRuneState
|
|
var err error
|
|
for i := 0; i < len(str); i++ {
|
|
r := rune(str[i])
|
|
state, err = state(parseState, i, r)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing ninja string %q: %s", str, err)
|
|
}
|
|
}
|
|
|
|
_, err = state(parseState, len(parseState.str), eof)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// All the '$' characters counted initially could have been "$$" escapes, leaving no
|
|
// variable references. Deallocate the variables slice if so.
|
|
if len(*result.variables) == 0 {
|
|
result.variables = nil
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func parseFirstRuneState(state *parseState, i int, r rune) (stateFunc, error) {
|
|
if r == ' ' {
|
|
state.pushVariable(0, 1, nil)
|
|
}
|
|
return parseStringState(state, i, r)
|
|
}
|
|
|
|
func parseStringState(state *parseState, i int, r rune) (stateFunc, error) {
|
|
switch {
|
|
case r == '$':
|
|
state.varStart = i
|
|
return parseDollarStartState, nil
|
|
|
|
case r == eof:
|
|
return nil, nil
|
|
|
|
default:
|
|
return parseStringState, nil
|
|
}
|
|
}
|
|
|
|
func parseDollarStartState(state *parseState, i int, r rune) (stateFunc, error) {
|
|
switch {
|
|
case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z',
|
|
r >= '0' && r <= '9', r == '_', r == '-':
|
|
// The beginning of a of the variable name.
|
|
state.varNameStart = i
|
|
return parseDollarState, nil
|
|
|
|
case r == '$':
|
|
// Just a "$$". Go back to parseStringState.
|
|
return parseStringState, nil
|
|
|
|
case r == '{':
|
|
// This is a bracketted variable name (e.g. "${blah.blah}").
|
|
state.varNameStart = i + 1
|
|
return parseBracketsState, nil
|
|
|
|
case r == eof:
|
|
return nil, fmt.Errorf("unexpected end of string after '$'")
|
|
|
|
default:
|
|
// This was some arbitrary character following a dollar sign,
|
|
// which is not allowed.
|
|
return nil, fmt.Errorf("invalid character after '$' at byte "+
|
|
"offset %d", i)
|
|
}
|
|
}
|
|
|
|
func parseDollarState(state *parseState, i int, r rune) (stateFunc, error) {
|
|
switch {
|
|
case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z',
|
|
r >= '0' && r <= '9', r == '_', r == '-':
|
|
// A part of the variable name. Keep going.
|
|
return parseDollarState, nil
|
|
}
|
|
|
|
// The variable name has ended, output what we have.
|
|
v, err := state.scope.LookupVariable(state.str[state.varNameStart:i])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
state.pushVariable(state.varStart, i, v)
|
|
|
|
switch {
|
|
case r == '$':
|
|
// A dollar after the variable name (e.g. "$blah$"). Start a new one.
|
|
state.varStart = i
|
|
return parseDollarStartState, nil
|
|
|
|
case r == eof:
|
|
return nil, nil
|
|
|
|
default:
|
|
return parseStringState, nil
|
|
}
|
|
}
|
|
|
|
func parseBracketsState(state *parseState, i int, r rune) (stateFunc, error) {
|
|
switch {
|
|
case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z',
|
|
r >= '0' && r <= '9', r == '_', r == '-', r == '.':
|
|
// A part of the variable name. Keep going.
|
|
return parseBracketsState, nil
|
|
|
|
case r == '}':
|
|
if state.varNameStart == i {
|
|
// The brackets were immediately closed. That's no good.
|
|
return nil, fmt.Errorf("empty variable name at byte offset %d",
|
|
i)
|
|
}
|
|
|
|
// This is the end of the variable name.
|
|
v, err := state.scope.LookupVariable(state.str[state.varNameStart:i])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
state.pushVariable(state.varStart, i+1, v)
|
|
return parseStringState, nil
|
|
|
|
case r == eof:
|
|
return nil, fmt.Errorf("unexpected end of string in variable name")
|
|
|
|
default:
|
|
// This character isn't allowed in a variable name.
|
|
return nil, fmt.Errorf("invalid character in variable name at "+
|
|
"byte offset %d", i)
|
|
}
|
|
}
|
|
|
|
func parseNinjaStrings(scope scope, strs []string) ([]*ninjaString,
|
|
error) {
|
|
|
|
if len(strs) == 0 {
|
|
return nil, nil
|
|
}
|
|
result := make([]*ninjaString, len(strs))
|
|
for i, str := range strs {
|
|
ninjaStr, err := parseNinjaString(scope, str)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing element %d: %s", i, err)
|
|
}
|
|
result[i] = ninjaStr
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (n *ninjaString) Value(pkgNames map[*packageContext]string) string {
|
|
if n.variables == nil || len(*n.variables) == 0 {
|
|
return defaultEscaper.Replace(n.str)
|
|
}
|
|
str := &strings.Builder{}
|
|
n.ValueWithEscaper(str, pkgNames, defaultEscaper)
|
|
return str.String()
|
|
}
|
|
|
|
func (n *ninjaString) ValueWithEscaper(w io.StringWriter, pkgNames map[*packageContext]string,
|
|
escaper *strings.Replacer) {
|
|
|
|
if n.variables == nil || len(*n.variables) == 0 {
|
|
w.WriteString(escaper.Replace(n.str))
|
|
return
|
|
}
|
|
|
|
i := 0
|
|
for _, v := range *n.variables {
|
|
w.WriteString(escaper.Replace(n.str[i:v.start]))
|
|
if v.variable == nil {
|
|
w.WriteString("$ ")
|
|
} else {
|
|
w.WriteString("${")
|
|
w.WriteString(v.variable.fullName(pkgNames))
|
|
w.WriteString("}")
|
|
}
|
|
i = int(v.end)
|
|
}
|
|
w.WriteString(escaper.Replace(n.str[i:len(n.str)]))
|
|
}
|
|
|
|
func (n *ninjaString) Eval(variables map[Variable]*ninjaString) (string, error) {
|
|
if n.variables == nil || len(*n.variables) == 0 {
|
|
return n.str, nil
|
|
}
|
|
|
|
w := &strings.Builder{}
|
|
i := 0
|
|
for _, v := range *n.variables {
|
|
w.WriteString(n.str[i:v.start])
|
|
if v.variable == nil {
|
|
w.WriteString(" ")
|
|
} else {
|
|
variable, ok := variables[v.variable]
|
|
if !ok {
|
|
return "", fmt.Errorf("no such global variable: %s", v.variable)
|
|
}
|
|
value, err := variable.Eval(variables)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
w.WriteString(value)
|
|
}
|
|
i = int(v.end)
|
|
}
|
|
w.WriteString(n.str[i:len(n.str)])
|
|
return w.String(), nil
|
|
}
|
|
|
|
func (n *ninjaString) Variables() []Variable {
|
|
if n.variables == nil || len(*n.variables) == 0 {
|
|
return nil
|
|
}
|
|
|
|
variables := make([]Variable, 0, len(*n.variables))
|
|
for _, v := range *n.variables {
|
|
if v.variable != nil {
|
|
variables = append(variables, v.variable)
|
|
}
|
|
}
|
|
return variables
|
|
}
|
|
|
|
func validateNinjaName(name string) error {
|
|
for i, r := range name {
|
|
valid := (r >= 'a' && r <= 'z') ||
|
|
(r >= 'A' && r <= 'Z') ||
|
|
(r >= '0' && r <= '9') ||
|
|
(r == '_') ||
|
|
(r == '-') ||
|
|
(r == '.')
|
|
if !valid {
|
|
|
|
return fmt.Errorf("%q contains an invalid Ninja name character "+
|
|
"%q at byte offset %d", name, r, i)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func toNinjaName(name string) string {
|
|
ret := bytes.Buffer{}
|
|
ret.Grow(len(name))
|
|
for _, r := range name {
|
|
valid := (r >= 'a' && r <= 'z') ||
|
|
(r >= 'A' && r <= 'Z') ||
|
|
(r >= '0' && r <= '9') ||
|
|
(r == '_') ||
|
|
(r == '-') ||
|
|
(r == '.')
|
|
if valid {
|
|
ret.WriteRune(r)
|
|
} else {
|
|
// TODO(jeffrygaston): do escaping so that toNinjaName won't ever output duplicate
|
|
// names for two different input names
|
|
ret.WriteRune('_')
|
|
}
|
|
}
|
|
|
|
return ret.String()
|
|
}
|
|
|
|
var builtinRuleArgs = []string{"out", "in"}
|
|
|
|
func validateArgName(argName string) error {
|
|
err := validateNinjaName(argName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// We only allow globals within the rule's package to be used as rule
|
|
// arguments. A global in another package can always be mirrored into
|
|
// the rule's package by defining a new variable, so this doesn't limit
|
|
// what's possible. This limitation prevents situations where a Build
|
|
// invocation in another package must use the rule-defining package's
|
|
// import name for a 3rd package in order to set the rule's arguments.
|
|
if strings.ContainsRune(argName, '.') {
|
|
return fmt.Errorf("%q contains a '.' character", argName)
|
|
}
|
|
|
|
if argName == "tags" {
|
|
return fmt.Errorf("\"tags\" is a reserved argument name")
|
|
}
|
|
|
|
for _, builtin := range builtinRuleArgs {
|
|
if argName == builtin {
|
|
return fmt.Errorf("%q conflicts with Ninja built-in", argName)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func validateArgNames(argNames []string) error {
|
|
for _, argName := range argNames {
|
|
err := validateArgName(argName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|