Merge remote-tracking branch 'aosp/upstream' into uppercase_properties
* aosp/upstream:
Fix PropertyNameForField for X86.
Support unpacking capitalized property names
Make ninjaString an interface
Fixes: 148865218
Test: m checkbuild
Change-Id: I3680cd261bf420601a7b943e21acde6837ae8619
diff --git a/context.go b/context.go
index 9a1e6cf..1ad1588 100644
--- a/context.go
+++ b/context.go
@@ -96,15 +96,15 @@
// set during PrepareBuildActions
pkgNames map[*packageContext]string
liveGlobals *liveTracker
- globalVariables map[Variable]*ninjaString
+ globalVariables map[Variable]ninjaString
globalPools map[Pool]*poolDef
globalRules map[Rule]*ruleDef
// set during PrepareBuildActions
- ninjaBuildDir *ninjaString // The builddir special Ninja variable
- requiredNinjaMajor int // For the ninja_required_version variable
- requiredNinjaMinor int // For the ninja_required_version variable
- requiredNinjaMicro int // For the ninja_required_version variable
+ ninjaBuildDir ninjaString // The builddir special Ninja variable
+ requiredNinjaMajor int // For the ninja_required_version variable
+ requiredNinjaMinor int // For the ninja_required_version variable
+ requiredNinjaMicro int // For the ninja_required_version variable
subninjas []string
@@ -2788,7 +2788,7 @@
}
}
-func (c *Context) setNinjaBuildDir(value *ninjaString) {
+func (c *Context) setNinjaBuildDir(value ninjaString) {
if c.ninjaBuildDir == nil {
c.ninjaBuildDir = value
}
@@ -2854,7 +2854,7 @@
}
func (c *Context) checkForVariableReferenceCycles(
- variables map[Variable]*ninjaString, pkgNames map[*packageContext]string) {
+ variables map[Variable]ninjaString, pkgNames map[*packageContext]string) {
visited := make(map[Variable]bool) // variables that were already checked
checking := make(map[Variable]bool) // variables actively being checked
@@ -2867,7 +2867,7 @@
defer delete(checking, v)
value := variables[v]
- for _, dep := range value.variables {
+ for _, dep := range value.Variables() {
if checking[dep] {
// This is a cycle.
return []Variable{dep, v}
@@ -3352,7 +3352,7 @@
// First visit variables on which this variable depends.
value := c.globalVariables[v]
- for _, dep := range value.variables {
+ for _, dep := range value.Variables() {
if !visited[dep] {
err := walk(dep)
if err != nil {
diff --git a/live_tracker.go b/live_tracker.go
index 5e13a87..40e1930 100644
--- a/live_tracker.go
+++ b/live_tracker.go
@@ -24,7 +24,7 @@
sync.Mutex
config interface{} // Used to evaluate variable, rule, and pool values.
- variables map[Variable]*ninjaString
+ variables map[Variable]ninjaString
pools map[Pool]*poolDef
rules map[Rule]*ruleDef
}
@@ -32,7 +32,7 @@
func newLiveTracker(config interface{}) *liveTracker {
return &liveTracker{
config: config,
- variables: make(map[Variable]*ninjaString),
+ variables: make(map[Variable]ninjaString),
pools: make(map[Pool]*poolDef),
rules: make(map[Rule]*ruleDef),
}
@@ -170,7 +170,7 @@
return nil
}
-func (l *liveTracker) addNinjaStringListDeps(list []*ninjaString) error {
+func (l *liveTracker) addNinjaStringListDeps(list []ninjaString) error {
for _, str := range list {
err := l.addNinjaStringDeps(str)
if err != nil {
@@ -180,8 +180,8 @@
return nil
}
-func (l *liveTracker) addNinjaStringDeps(str *ninjaString) error {
- for _, v := range str.variables {
+func (l *liveTracker) addNinjaStringDeps(str ninjaString) error {
+ for _, v := range str.Variables() {
err := l.addVariable(v)
if err != nil {
return err
diff --git a/ninja_defs.go b/ninja_defs.go
index 61846fe..c5d0e4b 100644
--- a/ninja_defs.go
+++ b/ninja_defs.go
@@ -128,11 +128,11 @@
// A ruleDef describes a rule definition. It does not include the name of the
// rule.
type ruleDef struct {
- CommandDeps []*ninjaString
- CommandOrderOnly []*ninjaString
+ CommandDeps []ninjaString
+ CommandOrderOnly []ninjaString
Comment string
Pool Pool
- Variables map[string]*ninjaString
+ Variables map[string]ninjaString
}
func parseRuleParams(scope scope, params *RuleParams) (*ruleDef,
@@ -141,7 +141,7 @@
r := &ruleDef{
Comment: params.Comment,
Pool: params.Pool,
- Variables: make(map[string]*ninjaString),
+ Variables: make(map[string]ninjaString),
}
if params.Command == "" {
@@ -252,13 +252,13 @@
Comment string
Rule Rule
RuleDef *ruleDef
- Outputs []*ninjaString
- ImplicitOutputs []*ninjaString
- Inputs []*ninjaString
- Implicits []*ninjaString
- OrderOnly []*ninjaString
- Args map[Variable]*ninjaString
- Variables map[string]*ninjaString
+ Outputs []ninjaString
+ ImplicitOutputs []ninjaString
+ Inputs []ninjaString
+ Implicits []ninjaString
+ OrderOnly []ninjaString
+ Args map[Variable]ninjaString
+ Variables map[string]ninjaString
Optional bool
}
@@ -273,9 +273,9 @@
Rule: rule,
}
- setVariable := func(name string, value *ninjaString) {
+ setVariable := func(name string, value ninjaString) {
if b.Variables == nil {
- b.Variables = make(map[string]*ninjaString)
+ b.Variables = make(map[string]ninjaString)
}
b.Variables[name] = value
}
@@ -339,7 +339,7 @@
argNameScope := rule.scope()
if len(params.Args) > 0 {
- b.Args = make(map[Variable]*ninjaString)
+ b.Args = make(map[Variable]ninjaString)
for name, value := range params.Args {
if !rule.isArg(name) {
return nil, fmt.Errorf("unknown argument %q", name)
@@ -419,7 +419,7 @@
return nw.BlankLine()
}
-func valueList(list []*ninjaString, pkgNames map[*packageContext]string,
+func valueList(list []ninjaString, pkgNames map[*packageContext]string,
escaper *strings.Replacer) []string {
result := make([]string, len(list))
@@ -429,7 +429,7 @@
return result
}
-func writeVariables(nw *ninjaWriter, variables map[string]*ninjaString,
+func writeVariables(nw *ninjaWriter, variables map[string]ninjaString,
pkgNames map[*packageContext]string) error {
var keys []string
for k := range variables {
diff --git a/ninja_strings.go b/ninja_strings.go
index 5b8767d..190cae9 100644
--- a/ninja_strings.go
+++ b/ninja_strings.go
@@ -34,21 +34,28 @@
":", "$:")
)
-type ninjaString struct {
+type ninjaString interface {
+ Value(pkgNames map[*packageContext]string) string
+ ValueWithEscaper(pkgNames map[*packageContext]string, escaper *strings.Replacer) string
+ Eval(variables map[Variable]ninjaString) (string, error)
+ Variables() []Variable
+}
+
+type varNinjaString struct {
strings []string
variables []Variable
}
+type literalNinjaString string
+
type scope interface {
LookupVariable(name string) (Variable, error)
IsRuleVisible(rule Rule) bool
IsPoolVisible(pool Pool) bool
}
-func simpleNinjaString(str string) *ninjaString {
- return &ninjaString{
- strings: []string{str},
- }
+func simpleNinjaString(str string) ninjaString {
+ return literalNinjaString(str)
}
type parseState struct {
@@ -57,7 +64,7 @@
pendingStr string
stringStart int
varStart int
- result *ninjaString
+ result *varNinjaString
}
func (ps *parseState) pushVariable(v Variable) {
@@ -84,10 +91,16 @@
// 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) {
+func parseNinjaString(scope scope, str string) (ninjaString, error) {
// naively pre-allocate slices by counting $ signs
n := strings.Count(str, "$")
- result := &ninjaString{
+ if n == 0 {
+ if strings.HasPrefix(str, " ") {
+ str = "$" + str
+ }
+ return literalNinjaString(str), nil
+ }
+ result := &varNinjaString{
strings: make([]string, 0, n+1),
variables: make([]Variable, 0, n),
}
@@ -253,13 +266,13 @@
}
}
-func parseNinjaStrings(scope scope, strs []string) ([]*ninjaString,
+func parseNinjaStrings(scope scope, strs []string) ([]ninjaString,
error) {
if len(strs) == 0 {
return nil, nil
}
- result := make([]*ninjaString, len(strs))
+ result := make([]ninjaString, len(strs))
for i, str := range strs {
ninjaStr, err := parseNinjaString(scope, str)
if err != nil {
@@ -270,11 +283,11 @@
return result, nil
}
-func (n *ninjaString) Value(pkgNames map[*packageContext]string) string {
+func (n varNinjaString) Value(pkgNames map[*packageContext]string) string {
return n.ValueWithEscaper(pkgNames, defaultEscaper)
}
-func (n *ninjaString) ValueWithEscaper(pkgNames map[*packageContext]string,
+func (n varNinjaString) ValueWithEscaper(pkgNames map[*packageContext]string,
escaper *strings.Replacer) string {
if len(n.strings) == 1 {
@@ -293,7 +306,7 @@
return str.String()
}
-func (n *ninjaString) Eval(variables map[Variable]*ninjaString) (string, error) {
+func (n varNinjaString) Eval(variables map[Variable]ninjaString) (string, error) {
str := n.strings[0]
for i, v := range n.variables {
variable, ok := variables[v]
@@ -309,6 +322,27 @@
return str, nil
}
+func (n varNinjaString) Variables() []Variable {
+ return n.variables
+}
+
+func (l literalNinjaString) Value(pkgNames map[*packageContext]string) string {
+ return l.ValueWithEscaper(pkgNames, defaultEscaper)
+}
+
+func (l literalNinjaString) ValueWithEscaper(pkgNames map[*packageContext]string,
+ escaper *strings.Replacer) string {
+ return escaper.Replace(string(l))
+}
+
+func (l literalNinjaString) Eval(variables map[Variable]ninjaString) (string, error) {
+ return string(l), nil
+}
+
+func (l literalNinjaString) Variables() []Variable {
+ return nil
+}
+
func validateNinjaName(name string) error {
for i, r := range name {
valid := (r >= 'a' && r <= 'z') ||
diff --git a/ninja_strings_test.go b/ninja_strings_test.go
index 0e0de64..c1e05f7 100644
--- a/ninja_strings_test.go
+++ b/ninja_strings_test.go
@@ -22,10 +22,11 @@
)
var ninjaParseTestCases = []struct {
- input string
- vars []string
- strs []string
- err string
+ input string
+ vars []string
+ strs []string
+ literal bool
+ err string
}{
{
input: "abc def $ghi jkl",
@@ -56,6 +57,7 @@
input: "foo $$ bar",
vars: nil,
strs: []string{"foo $$ bar"},
+ // this is technically a literal, but not recognized as such due to the $$
},
{
input: "$foo${bar}",
@@ -68,16 +70,22 @@
strs: []string{"", "$$"},
},
{
- input: "foo bar",
- vars: nil,
- strs: []string{"foo bar"},
+ input: "foo bar",
+ vars: nil,
+ strs: []string{"foo bar"},
+ literal: true,
},
{
- input: " foo ",
- vars: nil,
- strs: []string{"$ foo "},
+ input: " foo ",
+ vars: nil,
+ strs: []string{"$ foo "},
+ literal: true,
},
{
+ input: " $foo ",
+ vars: []string{"foo"},
+ strs: []string{"$ ", " "},
+ }, {
input: "foo $ bar",
err: "invalid character after '$' at byte offset 5",
},
@@ -114,19 +122,25 @@
expectedVars = append(expectedVars, v)
}
+ var expected ninjaString
+ if len(testCase.strs) > 0 {
+ if testCase.literal {
+ expected = literalNinjaString(testCase.strs[0])
+ } else {
+ expected = &varNinjaString{
+ strings: testCase.strs,
+ variables: expectedVars,
+ }
+ }
+ }
+
output, err := parseNinjaString(scope, testCase.input)
if err == nil {
- if !reflect.DeepEqual(output.variables, expectedVars) {
- t.Errorf("incorrect variable list:")
+ if !reflect.DeepEqual(output, expected) {
+ t.Errorf("incorrect ninja string:")
t.Errorf(" input: %q", testCase.input)
- t.Errorf(" expected: %#v", expectedVars)
- t.Errorf(" got: %#v", output.variables)
- }
- if !reflect.DeepEqual(output.strings, testCase.strs) {
- t.Errorf("incorrect string list:")
- t.Errorf(" input: %q", testCase.input)
- t.Errorf(" expected: %#v", testCase.strs)
- t.Errorf(" got: %#v", output.strings)
+ t.Errorf(" expected: %#v", expected)
+ t.Errorf(" got: %#v", output)
}
}
var errStr string
@@ -156,7 +170,7 @@
}
expect := []Variable{ImpVar}
- if !reflect.DeepEqual(output.variables, expect) {
+ if !reflect.DeepEqual(output.(*varNinjaString).variables, expect) {
t.Errorf("incorrect output:")
t.Errorf(" input: %q", input)
t.Errorf(" expected: %#v", expect)
diff --git a/package_ctx.go b/package_ctx.go
index c55152a..088239e 100644
--- a/package_ctx.go
+++ b/package_ctx.go
@@ -292,7 +292,7 @@
return packageNamespacePrefix(pkgNames[v.pctx]) + v.name_
}
-func (v *staticVariable) value(interface{}) (*ninjaString, error) {
+func (v *staticVariable) value(interface{}) (ninjaString, error) {
ninjaStr, err := parseNinjaString(v.pctx.scope, v.value_)
if err != nil {
err = fmt.Errorf("error parsing variable %s value: %s", v, err)
@@ -392,7 +392,7 @@
return packageNamespacePrefix(pkgNames[v.pctx]) + v.name_
}
-func (v *variableFunc) value(config interface{}) (*ninjaString, error) {
+func (v *variableFunc) value(config interface{}) (ninjaString, error) {
value, err := v.value_(config)
if err != nil {
return nil, err
@@ -452,7 +452,7 @@
return v.name_
}
-func (v *argVariable) value(config interface{}) (*ninjaString, error) {
+func (v *argVariable) value(config interface{}) (ninjaString, error) {
return nil, errVariableIsArg
}
diff --git a/proptools/proptools.go b/proptools/proptools.go
index b91c92a..c44a4a8 100644
--- a/proptools/proptools.go
+++ b/proptools/proptools.go
@@ -16,19 +16,34 @@
import (
"reflect"
+ "strings"
"unicode"
"unicode/utf8"
)
+// PropertyNameForField converts the name of a field in property struct to the property name that
+// might appear in a Blueprints file. Since the property struct fields must always be exported
+// to be accessed with reflection and the canonical Blueprints style is lowercased names, it
+// lower cases the first rune in the field name unless the field name contains an uppercase rune
+// after the first rune (which is always uppercase), and no lowercase runes.
func PropertyNameForField(fieldName string) string {
r, size := utf8.DecodeRuneInString(fieldName)
propertyName := string(unicode.ToLower(r))
+ if size == len(fieldName) {
+ return propertyName
+ }
+ if strings.IndexFunc(fieldName[size:], unicode.IsLower) == -1 &&
+ strings.IndexFunc(fieldName[size:], unicode.IsUpper) != -1 {
+ return fieldName
+ }
if len(fieldName) > size {
propertyName += fieldName[size:]
}
return propertyName
}
+// FieldNameForProperty converts the name of a property that might appear in a Blueprints file to
+// the name of a field in property struct by uppercasing the first rune.
func FieldNameForProperty(propertyName string) string {
r, size := utf8.DecodeRuneInString(propertyName)
fieldName := string(unicode.ToUpper(r))
diff --git a/proptools/proptools_test.go b/proptools/proptools_test.go
new file mode 100644
index 0000000..207ee1b
--- /dev/null
+++ b/proptools/proptools_test.go
@@ -0,0 +1,114 @@
+// Copyright 2020 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 proptools
+
+import "testing"
+
+func TestPropertyNameForField(t *testing.T) {
+ tests := []struct {
+ name string
+ input string
+ want string
+ }{
+ {
+ name: "short",
+ input: "S",
+ want: "s",
+ },
+ {
+ name: "long",
+ input: "String",
+ want: "string",
+ },
+ {
+ name: "uppercase",
+ input: "STRING",
+ want: "STRING",
+ },
+ {
+ name: "mixed",
+ input: "StRiNg",
+ want: "stRiNg",
+ },
+ {
+ name: "underscore",
+ input: "Under_score",
+ want: "under_score",
+ },
+ {
+ name: "uppercase underscore",
+ input: "UNDER_SCORE",
+ want: "UNDER_SCORE",
+ },
+ {
+ name: "x86",
+ input: "X86",
+ want: "x86",
+ },
+ {
+ name: "x86_64",
+ input: "X86_64",
+ want: "x86_64",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := PropertyNameForField(tt.input); got != tt.want {
+ t.Errorf("PropertyNameForField(%v) = %v, want %v", tt.input, got, tt.want)
+ }
+ })
+ }
+}
+
+func TestFieldNameForProperty(t *testing.T) {
+ tests := []struct {
+ name string
+ input string
+ want string
+ }{
+ {
+ name: "short lowercase",
+ input: "s",
+ want: "S",
+ },
+ {
+ name: "short uppercase",
+ input: "S",
+ want: "S",
+ },
+ {
+ name: "long lowercase",
+ input: "string",
+ want: "String",
+ },
+ {
+ name: "long uppercase",
+ input: "STRING",
+ want: "STRING",
+ },
+ {
+ name: "mixed",
+ input: "StRiNg",
+ want: "StRiNg",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := FieldNameForProperty(tt.input); got != tt.want {
+ t.Errorf("FieldNameForProperty(%v) = %v, want %v", tt.input, got, tt.want)
+ }
+ })
+ }
+}
diff --git a/proptools/unpack_test.go b/proptools/unpack_test.go
index 234b4e7..942dbb8 100644
--- a/proptools/unpack_test.go
+++ b/proptools/unpack_test.go
@@ -508,6 +508,21 @@
},
},
},
+ // Captitalized property
+ {
+ input: `
+ m {
+ CAPITALIZED: "foo",
+ }
+ `,
+ output: []interface{}{
+ &struct {
+ CAPITALIZED string
+ }{
+ CAPITALIZED: "foo",
+ },
+ },
+ },
}
func TestUnpackProperties(t *testing.T) {
diff --git a/scope.go b/scope.go
index 84db0cf..0a520d9 100644
--- a/scope.go
+++ b/scope.go
@@ -28,7 +28,7 @@
packageContext() *packageContext
name() string // "foo"
fullName(pkgNames map[*packageContext]string) string // "pkg.foo" or "path.to.pkg.foo"
- value(config interface{}) (*ninjaString, error)
+ value(config interface{}) (ninjaString, error)
String() string
}
@@ -351,7 +351,7 @@
type localVariable struct {
namePrefix string
name_ string
- value_ *ninjaString
+ value_ ninjaString
}
func (l *localVariable) packageContext() *packageContext {
@@ -366,7 +366,7 @@
return l.namePrefix + l.name_
}
-func (l *localVariable) value(interface{}) (*ninjaString, error) {
+func (l *localVariable) value(interface{}) (ninjaString, error) {
return l.value_, nil
}