platform_build_soong/mk2rbc/mk2rbc.go
Cole Faust 8e15f69709 Remove ?= assignements to product variables
In make, all product variables are assigned to an empty string before
including the product config makefiles. In starlark, we don't do that
just so the memory usage is smaller. This means that in make, using
?= to assign a product config variable has no effect. Replicate this
behavior in starlark by not emitting any code for ?= assignments of
product variables.

Fixes: 304324003
Test: go test
Change-Id: Id31531506ac9e372a1bea92ce9ae8e17ae0ca70c
2023-10-09 12:26:21 -07:00

2260 lines
74 KiB
Go

// Copyright 2021 Google LLC
//
// 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.
// Convert makefile containing device configuration to Starlark file
// The conversion can handle the following constructs in a makefile:
// - comments
// - simple variable assignments
// - $(call init-product,<file>)
// - $(call inherit-product-if-exists
// - if directives
//
// All other constructs are carried over to the output starlark file as comments.
package mk2rbc
import (
"bytes"
"fmt"
"io"
"io/fs"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"text/scanner"
mkparser "android/soong/androidmk/parser"
)
const (
annotationCommentPrefix = "RBC#"
baseUri = "//build/make/core:product_config.rbc"
// The name of the struct exported by the product_config.rbc
// that contains the functions and variables available to
// product configuration Starlark files.
baseName = "rblf"
soongNsPrefix = "SOONG_CONFIG_"
// And here are the functions and variables:
cfnGetCfg = baseName + ".cfg"
cfnMain = baseName + ".product_configuration"
cfnBoardMain = baseName + ".board_configuration"
cfnPrintVars = baseName + ".printvars"
cfnInherit = baseName + ".inherit"
cfnSetListDefault = baseName + ".setdefault"
)
const (
soongConfigAppend = "soong_config_append"
soongConfigAssign = "soong_config_set"
)
var knownFunctions = map[string]interface {
parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr
}{
"abspath": &simpleCallParser{name: baseName + ".abspath", returnType: starlarkTypeString},
"add-product-dex-preopt-module-config": &simpleCallParser{name: baseName + ".add_product_dex_preopt_module_config", returnType: starlarkTypeString, addHandle: true},
"add_soong_config_namespace": &simpleCallParser{name: baseName + ".soong_config_namespace", returnType: starlarkTypeVoid, addGlobals: true},
"add_soong_config_var_value": &simpleCallParser{name: baseName + ".soong_config_set", returnType: starlarkTypeVoid, addGlobals: true},
soongConfigAssign: &simpleCallParser{name: baseName + ".soong_config_set", returnType: starlarkTypeVoid, addGlobals: true},
soongConfigAppend: &simpleCallParser{name: baseName + ".soong_config_append", returnType: starlarkTypeVoid, addGlobals: true},
"soong_config_get": &simpleCallParser{name: baseName + ".soong_config_get", returnType: starlarkTypeString, addGlobals: true},
"add-to-product-copy-files-if-exists": &simpleCallParser{name: baseName + ".copy_if_exists", returnType: starlarkTypeList},
"addprefix": &simpleCallParser{name: baseName + ".addprefix", returnType: starlarkTypeList},
"addsuffix": &simpleCallParser{name: baseName + ".addsuffix", returnType: starlarkTypeList},
"and": &andOrParser{isAnd: true},
"clear-var-list": &simpleCallParser{name: baseName + ".clear_var_list", returnType: starlarkTypeVoid, addGlobals: true, addHandle: true},
"copy-files": &simpleCallParser{name: baseName + ".copy_files", returnType: starlarkTypeList},
"dir": &simpleCallParser{name: baseName + ".dir", returnType: starlarkTypeString},
"dist-for-goals": &simpleCallParser{name: baseName + ".mkdist_for_goals", returnType: starlarkTypeVoid, addGlobals: true},
"enforce-product-packages-exist": &simpleCallParser{name: baseName + ".enforce_product_packages_exist", returnType: starlarkTypeVoid, addHandle: true},
"error": &makeControlFuncParser{name: baseName + ".mkerror"},
"findstring": &simpleCallParser{name: baseName + ".findstring", returnType: starlarkTypeInt},
"find-copy-subdir-files": &simpleCallParser{name: baseName + ".find_and_copy", returnType: starlarkTypeList},
"filter": &simpleCallParser{name: baseName + ".filter", returnType: starlarkTypeList},
"filter-out": &simpleCallParser{name: baseName + ".filter_out", returnType: starlarkTypeList},
"firstword": &simpleCallParser{name: baseName + ".first_word", returnType: starlarkTypeString},
"foreach": &foreachCallParser{},
"if": &ifCallParser{},
"info": &makeControlFuncParser{name: baseName + ".mkinfo"},
"is-board-platform": &simpleCallParser{name: baseName + ".board_platform_is", returnType: starlarkTypeBool, addGlobals: true},
"is-board-platform2": &simpleCallParser{name: baseName + ".board_platform_is", returnType: starlarkTypeBool, addGlobals: true},
"is-board-platform-in-list": &simpleCallParser{name: baseName + ".board_platform_in", returnType: starlarkTypeBool, addGlobals: true},
"is-board-platform-in-list2": &simpleCallParser{name: baseName + ".board_platform_in", returnType: starlarkTypeBool, addGlobals: true},
"is-product-in-list": &isProductInListCallParser{},
"is-vendor-board-platform": &isVendorBoardPlatformCallParser{},
"is-vendor-board-qcom": &isVendorBoardQcomCallParser{},
"lastword": &simpleCallParser{name: baseName + ".last_word", returnType: starlarkTypeString},
"notdir": &simpleCallParser{name: baseName + ".notdir", returnType: starlarkTypeString},
"math_max": &mathMaxOrMinCallParser{function: "max"},
"math_min": &mathMaxOrMinCallParser{function: "min"},
"math_gt_or_eq": &mathComparisonCallParser{op: ">="},
"math_gt": &mathComparisonCallParser{op: ">"},
"math_lt": &mathComparisonCallParser{op: "<"},
"my-dir": &myDirCallParser{},
"or": &andOrParser{isAnd: false},
"patsubst": &substCallParser{fname: "patsubst"},
"product-copy-files-by-pattern": &simpleCallParser{name: baseName + ".product_copy_files_by_pattern", returnType: starlarkTypeList},
"require-artifacts-in-path": &simpleCallParser{name: baseName + ".require_artifacts_in_path", returnType: starlarkTypeVoid, addHandle: true},
"require-artifacts-in-path-relaxed": &simpleCallParser{name: baseName + ".require_artifacts_in_path_relaxed", returnType: starlarkTypeVoid, addHandle: true},
// TODO(asmundak): remove it once all calls are removed from configuration makefiles. see b/183161002
"shell": &shellCallParser{},
"sort": &simpleCallParser{name: baseName + ".mksort", returnType: starlarkTypeList},
"strip": &simpleCallParser{name: baseName + ".mkstrip", returnType: starlarkTypeString},
"subst": &substCallParser{fname: "subst"},
"to-lower": &lowerUpperParser{isUpper: false},
"to-upper": &lowerUpperParser{isUpper: true},
"warning": &makeControlFuncParser{name: baseName + ".mkwarning"},
"word": &wordCallParser{},
"words": &wordsCallParser{},
"wildcard": &simpleCallParser{name: baseName + ".expand_wildcard", returnType: starlarkTypeList},
}
// The same as knownFunctions, but returns a []starlarkNode instead of a starlarkExpr
var knownNodeFunctions = map[string]interface {
parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) []starlarkNode
}{
"eval": &evalNodeParser{},
"if": &ifCallNodeParser{},
"inherit-product": &inheritProductCallParser{loadAlways: true},
"inherit-product-if-exists": &inheritProductCallParser{loadAlways: false},
"foreach": &foreachCallNodeParser{},
}
// These look like variables, but are actually functions, and would give
// undefined variable errors if we converted them as variables. Instead,
// emit an error instead of converting them.
var unsupportedFunctions = map[string]bool{
"local-generated-sources-dir": true,
"local-intermediates-dir": true,
}
// These are functions that we don't implement conversions for, but
// we allow seeing their definitions in the product config files.
var ignoredDefines = map[string]bool{
"find-word-in-list": true, // internal macro
"get-vendor-board-platforms": true, // internal macro, used by is-board-platform, etc.
"is-android-codename": true, // unused by product config
"is-android-codename-in-list": true, // unused by product config
"is-chipset-in-board-platform": true, // unused by product config
"is-chipset-prefix-in-board-platform": true, // unused by product config
"is-not-board-platform": true, // defined but never used
"is-platform-sdk-version-at-least": true, // unused by product config
"match-prefix": true, // internal macro
"match-word": true, // internal macro
"match-word-in-list": true, // internal macro
"tb-modules": true, // defined in hardware/amlogic/tb_modules/tb_detect.mk, unused
}
var identifierFullMatchRegex = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$")
func RelativeToCwd(path string) (string, error) {
cwd, err := os.Getwd()
if err != nil {
return "", err
}
path, err = filepath.Rel(cwd, path)
if err != nil {
return "", err
}
if strings.HasPrefix(path, "../") {
return "", fmt.Errorf("Could not make path relative to current working directory: " + path)
}
return path, nil
}
// Conversion request parameters
type Request struct {
MkFile string // file to convert
Reader io.Reader // if set, read input from this stream instead
OutputSuffix string // generated Starlark files suffix
OutputDir string // if set, root of the output hierarchy
ErrorLogger ErrorLogger
TracedVariables []string // trace assignment to these variables
TraceCalls bool
SourceFS fs.FS
MakefileFinder MakefileFinder
}
// ErrorLogger prints errors and gathers error statistics.
// Its NewError function is called on every error encountered during the conversion.
type ErrorLogger interface {
NewError(el ErrorLocation, node mkparser.Node, text string, args ...interface{})
}
type ErrorLocation struct {
MkFile string
MkLine int
}
func (el ErrorLocation) String() string {
return fmt.Sprintf("%s:%d", el.MkFile, el.MkLine)
}
// Derives module name for a given file. It is base name
// (file name without suffix), with some characters replaced to make it a Starlark identifier
func moduleNameForFile(mkFile string) string {
base := strings.TrimSuffix(filepath.Base(mkFile), filepath.Ext(mkFile))
// TODO(asmundak): what else can be in the product file names?
return strings.NewReplacer("-", "_", ".", "_").Replace(base)
}
func cloneMakeString(mkString *mkparser.MakeString) *mkparser.MakeString {
r := &mkparser.MakeString{StringPos: mkString.StringPos}
r.Strings = append(r.Strings, mkString.Strings...)
r.Variables = append(r.Variables, mkString.Variables...)
return r
}
func isMakeControlFunc(s string) bool {
return s == "error" || s == "warning" || s == "info"
}
// varAssignmentScope points to the last assignment for each variable
// in the current block. It is used during the parsing to chain
// the assignments to a variable together.
type varAssignmentScope struct {
outer *varAssignmentScope
vars map[string]bool
}
// Starlark output generation context
type generationContext struct {
buf strings.Builder
starScript *StarlarkScript
indentLevel int
inAssignment bool
tracedCount int
varAssignments *varAssignmentScope
}
func NewGenerateContext(ss *StarlarkScript) *generationContext {
return &generationContext{
starScript: ss,
varAssignments: &varAssignmentScope{
outer: nil,
vars: make(map[string]bool),
},
}
}
func (gctx *generationContext) pushVariableAssignments() {
va := &varAssignmentScope{
outer: gctx.varAssignments,
vars: make(map[string]bool),
}
gctx.varAssignments = va
}
func (gctx *generationContext) popVariableAssignments() {
gctx.varAssignments = gctx.varAssignments.outer
}
func (gctx *generationContext) hasBeenAssigned(v variable) bool {
for va := gctx.varAssignments; va != nil; va = va.outer {
if _, ok := va.vars[v.name()]; ok {
return true
}
}
return false
}
func (gctx *generationContext) setHasBeenAssigned(v variable) {
gctx.varAssignments.vars[v.name()] = true
}
// emit returns generated script
func (gctx *generationContext) emit() string {
ss := gctx.starScript
// The emitted code has the following layout:
// <initial comments>
// preamble, i.e.,
// load statement for the runtime support
// load statement for each unique submodule pulled in by this one
// def init(g, handle):
// cfg = rblf.cfg(handle)
// <statements>
// <warning if conversion was not clean>
iNode := len(ss.nodes)
for i, node := range ss.nodes {
if _, ok := node.(*commentNode); !ok {
iNode = i
break
}
node.emit(gctx)
}
gctx.emitPreamble()
gctx.newLine()
// The arguments passed to the init function are the global dictionary
// ('g') and the product configuration dictionary ('cfg')
gctx.write("def init(g, handle):")
gctx.indentLevel++
if gctx.starScript.traceCalls {
gctx.newLine()
gctx.writef(`print(">%s")`, gctx.starScript.mkFile)
}
gctx.newLine()
gctx.writef("cfg = %s(handle)", cfnGetCfg)
for _, node := range ss.nodes[iNode:] {
node.emit(gctx)
}
if gctx.starScript.traceCalls {
gctx.newLine()
gctx.writef(`print("<%s")`, gctx.starScript.mkFile)
}
gctx.indentLevel--
gctx.write("\n")
return gctx.buf.String()
}
func (gctx *generationContext) emitPreamble() {
gctx.newLine()
gctx.writef("load(%q, %q)", baseUri, baseName)
// Emit exactly one load statement for each URI.
loadedSubConfigs := make(map[string]string)
for _, mi := range gctx.starScript.inherited {
uri := mi.path
if strings.HasPrefix(uri, "/") && !strings.HasPrefix(uri, "//") {
var err error
uri, err = RelativeToCwd(uri)
if err != nil {
panic(err)
}
uri = "//" + uri
}
if m, ok := loadedSubConfigs[uri]; ok {
// No need to emit load statement, but fix module name.
mi.moduleLocalName = m
continue
}
if mi.optional || mi.missing {
uri += "|init"
}
gctx.newLine()
gctx.writef("load(%q, %s = \"init\")", uri, mi.entryName())
loadedSubConfigs[uri] = mi.moduleLocalName
}
gctx.write("\n")
}
func (gctx *generationContext) emitPass() {
gctx.newLine()
gctx.write("pass")
}
func (gctx *generationContext) write(ss ...string) {
for _, s := range ss {
gctx.buf.WriteString(s)
}
}
func (gctx *generationContext) writef(format string, args ...interface{}) {
gctx.write(fmt.Sprintf(format, args...))
}
func (gctx *generationContext) newLine() {
if gctx.buf.Len() == 0 {
return
}
gctx.write("\n")
gctx.writef("%*s", 2*gctx.indentLevel, "")
}
func (gctx *generationContext) emitConversionError(el ErrorLocation, message string) {
gctx.writef(`rblf.mk2rbc_error("%s", %q)`, el, message)
}
func (gctx *generationContext) emitLoadCheck(im inheritedModule) {
if !im.needsLoadCheck() {
return
}
gctx.newLine()
gctx.writef("if not %s:", im.entryName())
gctx.indentLevel++
gctx.newLine()
gctx.write(`rblf.mkerror("`, gctx.starScript.mkFile, `", "Cannot find %s" % (`)
im.pathExpr().emit(gctx)
gctx.write("))")
gctx.indentLevel--
}
type knownVariable struct {
name string
class varClass
valueType starlarkType
}
type knownVariables map[string]knownVariable
func (pcv knownVariables) NewVariable(name string, varClass varClass, valueType starlarkType) {
v, exists := pcv[name]
if !exists {
pcv[name] = knownVariable{name, varClass, valueType}
return
}
// Conflict resolution:
// * config class trumps everything
// * any type trumps unknown type
match := varClass == v.class
if !match {
if varClass == VarClassConfig {
v.class = VarClassConfig
match = true
} else if v.class == VarClassConfig {
match = true
}
}
if valueType != v.valueType {
if valueType != starlarkTypeUnknown {
if v.valueType == starlarkTypeUnknown {
v.valueType = valueType
} else {
match = false
}
}
}
if !match {
fmt.Fprintf(os.Stderr, "cannot redefine %s as %v/%v (already defined as %v/%v)\n",
name, varClass, valueType, v.class, v.valueType)
}
}
// All known product variables.
var KnownVariables = make(knownVariables)
func init() {
for _, kv := range []string{
// Kernel-related variables that we know are lists.
"BOARD_VENDOR_KERNEL_MODULES",
"BOARD_VENDOR_RAMDISK_KERNEL_MODULES",
"BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD",
"BOARD_RECOVERY_KERNEL_MODULES",
// Other variables we knwo are lists
"ART_APEX_JARS",
} {
KnownVariables.NewVariable(kv, VarClassSoong, starlarkTypeList)
}
}
// Information about the generated Starlark script.
type StarlarkScript struct {
mkFile string
moduleName string
mkPos scanner.Position
nodes []starlarkNode
inherited []*moduleInfo
hasErrors bool
traceCalls bool // print enter/exit each init function
sourceFS fs.FS
makefileFinder MakefileFinder
nodeLocator func(pos mkparser.Pos) int
}
// parseContext holds the script we are generating and all the ephemeral data
// needed during the parsing.
type parseContext struct {
script *StarlarkScript
nodes []mkparser.Node // Makefile as parsed by mkparser
currentNodeIndex int // Node in it we are processing
ifNestLevel int
moduleNameCount map[string]int // count of imported modules with given basename
fatalError error
outputSuffix string
errorLogger ErrorLogger
tracedVariables map[string]bool // variables to be traced in the generated script
variables map[string]variable
outputDir string
dependentModules map[string]*moduleInfo
soongNamespaces map[string]map[string]bool
includeTops []string
typeHints map[string]starlarkType
atTopOfMakefile bool
}
func newParseContext(ss *StarlarkScript, nodes []mkparser.Node) *parseContext {
predefined := []struct{ name, value string }{
{"SRC_TARGET_DIR", filepath.Join("build", "make", "target")},
{"LOCAL_PATH", filepath.Dir(ss.mkFile)},
{"MAKEFILE_LIST", ss.mkFile},
{"TOPDIR", ""}, // TOPDIR is just set to an empty string in cleanbuild.mk and core.mk
// TODO(asmundak): maybe read it from build/make/core/envsetup.mk?
{"TARGET_COPY_OUT_SYSTEM", "system"},
{"TARGET_COPY_OUT_SYSTEM_OTHER", "system_other"},
{"TARGET_COPY_OUT_DATA", "data"},
{"TARGET_COPY_OUT_ASAN", filepath.Join("data", "asan")},
{"TARGET_COPY_OUT_OEM", "oem"},
{"TARGET_COPY_OUT_RAMDISK", "ramdisk"},
{"TARGET_COPY_OUT_DEBUG_RAMDISK", "debug_ramdisk"},
{"TARGET_COPY_OUT_VENDOR_DEBUG_RAMDISK", "vendor_debug_ramdisk"},
{"TARGET_COPY_OUT_TEST_HARNESS_RAMDISK", "test_harness_ramdisk"},
{"TARGET_COPY_OUT_ROOT", "root"},
{"TARGET_COPY_OUT_RECOVERY", "recovery"},
{"TARGET_COPY_OUT_VENDOR_RAMDISK", "vendor_ramdisk"},
// TODO(asmundak): to process internal config files, we need the following variables:
// TARGET_VENDOR
// target_base_product
//
// the following utility variables are set in build/make/common/core.mk:
{"empty", ""},
{"space", " "},
{"comma", ","},
{"newline", "\n"},
{"pound", "#"},
{"backslash", "\\"},
}
ctx := &parseContext{
script: ss,
nodes: nodes,
currentNodeIndex: 0,
ifNestLevel: 0,
moduleNameCount: make(map[string]int),
variables: make(map[string]variable),
dependentModules: make(map[string]*moduleInfo),
soongNamespaces: make(map[string]map[string]bool),
includeTops: []string{},
typeHints: make(map[string]starlarkType),
atTopOfMakefile: true,
}
for _, item := range predefined {
ctx.variables[item.name] = &predefinedVariable{
baseVariable: baseVariable{nam: item.name, typ: starlarkTypeString},
value: &stringLiteralExpr{item.value},
}
}
return ctx
}
func (ctx *parseContext) hasNodes() bool {
return ctx.currentNodeIndex < len(ctx.nodes)
}
func (ctx *parseContext) getNode() mkparser.Node {
if !ctx.hasNodes() {
return nil
}
node := ctx.nodes[ctx.currentNodeIndex]
ctx.currentNodeIndex++
return node
}
func (ctx *parseContext) backNode() {
if ctx.currentNodeIndex <= 0 {
panic("Cannot back off")
}
ctx.currentNodeIndex--
}
func (ctx *parseContext) handleAssignment(a *mkparser.Assignment) []starlarkNode {
// Handle only simple variables
if !a.Name.Const() || a.Target != nil {
return []starlarkNode{ctx.newBadNode(a, "Only simple variables are handled")}
}
name := a.Name.Strings[0]
// The `override` directive
// override FOO :=
// is parsed as an assignment to a variable named `override FOO`.
// There are very few places where `override` is used, just flag it.
if strings.HasPrefix(name, "override ") {
return []starlarkNode{ctx.newBadNode(a, "cannot handle override directive")}
}
if name == ".KATI_READONLY" {
// Skip assignments to .KATI_READONLY. If it was in the output file, it
// would be an error because it would be sorted before the definition of
// the variable it's trying to make readonly.
return []starlarkNode{}
}
// Soong configuration
if strings.HasPrefix(name, soongNsPrefix) {
return ctx.handleSoongNsAssignment(strings.TrimPrefix(name, soongNsPrefix), a)
}
lhs := ctx.addVariable(name)
if lhs == nil {
return []starlarkNode{ctx.newBadNode(a, "unknown variable %s", name)}
}
_, isTraced := ctx.tracedVariables[lhs.name()]
asgn := &assignmentNode{lhs: lhs, mkValue: a.Value, isTraced: isTraced, location: ctx.errorLocation(a)}
if lhs.valueType() == starlarkTypeUnknown {
// Try to divine variable type from the RHS
asgn.value = ctx.parseMakeString(a, a.Value)
inferred_type := asgn.value.typ()
if inferred_type != starlarkTypeUnknown {
lhs.setValueType(inferred_type)
}
}
if lhs.valueType() == starlarkTypeList {
xConcat, xBad := ctx.buildConcatExpr(a)
if xBad != nil {
asgn.value = xBad
} else {
switch len(xConcat.items) {
case 0:
asgn.value = &listExpr{}
case 1:
asgn.value = xConcat.items[0]
default:
asgn.value = xConcat
}
}
} else {
asgn.value = ctx.parseMakeString(a, a.Value)
}
if asgn.lhs.valueType() == starlarkTypeString &&
asgn.value.typ() != starlarkTypeUnknown &&
asgn.value.typ() != starlarkTypeString {
asgn.value = &toStringExpr{expr: asgn.value}
}
switch a.Type {
case "=", ":=":
asgn.flavor = asgnSet
case "+=":
asgn.flavor = asgnAppend
case "?=":
if _, ok := lhs.(*productConfigVariable); ok {
// Make sets all product configuration variables to empty strings before running product
// config makefiles. ?= will have no effect on a variable that has been assigned before,
// even if assigned to an empty string. So just skip emitting any code for this
// assignment.
return nil
}
asgn.flavor = asgnMaybeSet
default:
panic(fmt.Errorf("unexpected assignment type %s", a.Type))
}
return []starlarkNode{asgn}
}
func (ctx *parseContext) handleSoongNsAssignment(name string, asgn *mkparser.Assignment) []starlarkNode {
val := ctx.parseMakeString(asgn, asgn.Value)
if xBad, ok := val.(*badExpr); ok {
return []starlarkNode{&exprNode{expr: xBad}}
}
// Unfortunately, Soong namespaces can be set up by directly setting corresponding Make
// variables instead of via add_soong_config_namespace + add_soong_config_var_value.
// Try to divine the call from the assignment as follows:
if name == "NAMESPACES" {
// Upon seeng
// SOONG_CONFIG_NAMESPACES += foo
// remember that there is a namespace `foo` and act as we saw
// $(call add_soong_config_namespace,foo)
s, ok := maybeString(val)
if !ok {
return []starlarkNode{ctx.newBadNode(asgn, "cannot handle variables in SOONG_CONFIG_NAMESPACES assignment, please use add_soong_config_namespace instead")}
}
result := make([]starlarkNode, 0)
for _, ns := range strings.Fields(s) {
ctx.addSoongNamespace(ns)
result = append(result, &exprNode{&callExpr{
name: baseName + ".soong_config_namespace",
args: []starlarkExpr{&globalsExpr{}, &stringLiteralExpr{ns}},
returnType: starlarkTypeVoid,
}})
}
return result
} else {
// Upon seeing
// SOONG_CONFIG_x_y = v
// find a namespace called `x` and act as if we encountered
// $(call soong_config_set,x,y,v)
// or check that `x_y` is a namespace, and then add the RHS of this assignment as variables in
// it.
// Emit an error in the ambiguous situation (namespaces `foo_bar` with a variable `baz`
// and `foo` with a variable `bar_baz`.
namespaceName := ""
if ctx.hasSoongNamespace(name) {
namespaceName = name
}
var varName string
for pos, ch := range name {
if !(ch == '_' && ctx.hasSoongNamespace(name[0:pos])) {
continue
}
if namespaceName != "" {
return []starlarkNode{ctx.newBadNode(asgn, "ambiguous soong namespace (may be either `%s` or `%s`)", namespaceName, name[0:pos])}
}
namespaceName = name[0:pos]
varName = name[pos+1:]
}
if namespaceName == "" {
return []starlarkNode{ctx.newBadNode(asgn, "cannot figure out Soong namespace, please use add_soong_config_var_value macro instead")}
}
if varName == "" {
// Remember variables in this namespace
s, ok := maybeString(val)
if !ok {
return []starlarkNode{ctx.newBadNode(asgn, "cannot handle variables in SOONG_CONFIG_ assignment, please use add_soong_config_var_value instead")}
}
ctx.updateSoongNamespace(asgn.Type != "+=", namespaceName, strings.Fields(s))
return []starlarkNode{}
}
// Finally, handle assignment to a namespace variable
if !ctx.hasNamespaceVar(namespaceName, varName) {
return []starlarkNode{ctx.newBadNode(asgn, "no %s variable in %s namespace, please use add_soong_config_var_value instead", varName, namespaceName)}
}
fname := baseName + "." + soongConfigAssign
if asgn.Type == "+=" {
fname = baseName + "." + soongConfigAppend
}
return []starlarkNode{&exprNode{&callExpr{
name: fname,
args: []starlarkExpr{&globalsExpr{}, &stringLiteralExpr{namespaceName}, &stringLiteralExpr{varName}, val},
returnType: starlarkTypeVoid,
}}}
}
}
func (ctx *parseContext) buildConcatExpr(a *mkparser.Assignment) (*concatExpr, *badExpr) {
xConcat := &concatExpr{}
var xItemList *listExpr
addToItemList := func(x ...starlarkExpr) {
if xItemList == nil {
xItemList = &listExpr{[]starlarkExpr{}}
}
xItemList.items = append(xItemList.items, x...)
}
finishItemList := func() {
if xItemList != nil {
xConcat.items = append(xConcat.items, xItemList)
xItemList = nil
}
}
items := a.Value.Words()
for _, item := range items {
// A function call in RHS is supposed to return a list, all other item
// expressions return individual elements.
switch x := ctx.parseMakeString(a, item).(type) {
case *badExpr:
return nil, x
case *stringLiteralExpr:
addToItemList(maybeConvertToStringList(x).(*listExpr).items...)
default:
switch x.typ() {
case starlarkTypeList:
finishItemList()
xConcat.items = append(xConcat.items, x)
case starlarkTypeString:
finishItemList()
xConcat.items = append(xConcat.items, &callExpr{
object: x,
name: "split",
args: nil,
returnType: starlarkTypeList,
})
default:
addToItemList(x)
}
}
}
if xItemList != nil {
xConcat.items = append(xConcat.items, xItemList)
}
return xConcat, nil
}
func (ctx *parseContext) newDependentModule(path string, optional bool) *moduleInfo {
modulePath := ctx.loadedModulePath(path)
if mi, ok := ctx.dependentModules[modulePath]; ok {
mi.optional = mi.optional && optional
return mi
}
moduleName := moduleNameForFile(path)
moduleLocalName := "_" + moduleName
n, found := ctx.moduleNameCount[moduleName]
if found {
moduleLocalName += fmt.Sprintf("%d", n)
}
ctx.moduleNameCount[moduleName] = n + 1
_, err := fs.Stat(ctx.script.sourceFS, path)
mi := &moduleInfo{
path: modulePath,
originalPath: path,
moduleLocalName: moduleLocalName,
optional: optional,
missing: err != nil,
}
ctx.dependentModules[modulePath] = mi
ctx.script.inherited = append(ctx.script.inherited, mi)
return mi
}
func (ctx *parseContext) handleSubConfig(
v mkparser.Node, pathExpr starlarkExpr, loadAlways bool, processModule func(inheritedModule) starlarkNode) []starlarkNode {
// Allow seeing $(sort $(wildcard realPathExpr)) or $(wildcard realPathExpr)
// because those are functionally the same as not having the sort/wildcard calls.
if ce, ok := pathExpr.(*callExpr); ok && ce.name == "rblf.mksort" && len(ce.args) == 1 {
if ce2, ok2 := ce.args[0].(*callExpr); ok2 && ce2.name == "rblf.expand_wildcard" && len(ce2.args) == 1 {
pathExpr = ce2.args[0]
}
} else if ce2, ok2 := pathExpr.(*callExpr); ok2 && ce2.name == "rblf.expand_wildcard" && len(ce2.args) == 1 {
pathExpr = ce2.args[0]
}
// In a simple case, the name of a module to inherit/include is known statically.
if path, ok := maybeString(pathExpr); ok {
// Note that even if this directive loads a module unconditionally, a module may be
// absent without causing any harm if this directive is inside an if/else block.
moduleShouldExist := loadAlways && ctx.ifNestLevel == 0
if strings.Contains(path, "*") {
if paths, err := fs.Glob(ctx.script.sourceFS, path); err == nil {
sort.Strings(paths)
result := make([]starlarkNode, 0)
for _, p := range paths {
mi := ctx.newDependentModule(p, !moduleShouldExist)
result = append(result, processModule(inheritedStaticModule{mi, loadAlways}))
}
return result
} else {
return []starlarkNode{ctx.newBadNode(v, "cannot glob wildcard argument")}
}
} else {
mi := ctx.newDependentModule(path, !moduleShouldExist)
return []starlarkNode{processModule(inheritedStaticModule{mi, loadAlways})}
}
}
// If module path references variables (e.g., $(v1)/foo/$(v2)/device-config.mk), find all the paths in the
// source tree that may be a match and the corresponding variable values. For instance, if the source tree
// contains vendor1/foo/abc/dev.mk and vendor2/foo/def/dev.mk, the first one will be inherited when
// (v1, v2) == ('vendor1', 'abc'), and the second one when (v1, v2) == ('vendor2', 'def').
// We then emit the code that loads all of them, e.g.:
// load("//vendor1/foo/abc:dev.rbc", _dev1_init="init")
// load("//vendor2/foo/def/dev.rbc", _dev2_init="init")
// And then inherit it as follows:
// _e = {
// "vendor1/foo/abc/dev.mk": ("vendor1/foo/abc/dev", _dev1_init),
// "vendor2/foo/def/dev.mk": ("vendor2/foo/def/dev", _dev_init2) }.get("%s/foo/%s/dev.mk" % (v1, v2))
// if _e:
// rblf.inherit(handle, _e[0], _e[1])
//
var matchingPaths []string
var needsWarning = false
if interpolate, ok := pathExpr.(*interpolateExpr); ok {
pathPattern := []string{interpolate.chunks[0]}
for _, chunk := range interpolate.chunks[1:] {
if chunk != "" {
pathPattern = append(pathPattern, chunk)
}
}
if len(pathPattern) == 1 {
pathPattern = append(pathPattern, "")
}
matchingPaths = ctx.findMatchingPaths(pathPattern)
needsWarning = pathPattern[0] == "" && len(ctx.includeTops) == 0
} else if len(ctx.includeTops) > 0 {
matchingPaths = append(matchingPaths, ctx.findMatchingPaths([]string{"", ""})...)
} else {
return []starlarkNode{ctx.newBadNode(v, "inherit-product/include argument is too complex")}
}
// Safeguard against $(call inherit-product,$(PRODUCT_PATH))
const maxMatchingFiles = 150
if len(matchingPaths) > maxMatchingFiles {
return []starlarkNode{ctx.newBadNode(v, "there are >%d files matching the pattern, please rewrite it", maxMatchingFiles)}
}
res := inheritedDynamicModule{pathExpr, []*moduleInfo{}, loadAlways, ctx.errorLocation(v), needsWarning}
for _, p := range matchingPaths {
// A product configuration files discovered dynamically may attempt to inherit
// from another one which does not exist in this source tree. Prevent load errors
// by always loading the dynamic files as optional.
res.candidateModules = append(res.candidateModules, ctx.newDependentModule(p, true))
}
return []starlarkNode{processModule(res)}
}
func (ctx *parseContext) findMatchingPaths(pattern []string) []string {
files := ctx.script.makefileFinder.Find(".")
if len(pattern) == 0 {
return files
}
// Create regular expression from the pattern
regexString := "^" + regexp.QuoteMeta(pattern[0])
for _, s := range pattern[1:] {
regexString += ".*" + regexp.QuoteMeta(s)
}
regexString += "$"
rex := regexp.MustCompile(regexString)
includeTopRegexString := ""
if len(ctx.includeTops) > 0 {
for i, top := range ctx.includeTops {
if i > 0 {
includeTopRegexString += "|"
}
includeTopRegexString += "^" + regexp.QuoteMeta(top)
}
} else {
includeTopRegexString = ".*"
}
includeTopRegex := regexp.MustCompile(includeTopRegexString)
// Now match
var res []string
for _, p := range files {
if rex.MatchString(p) && includeTopRegex.MatchString(p) {
res = append(res, p)
}
}
return res
}
type inheritProductCallParser struct {
loadAlways bool
}
func (p *inheritProductCallParser) parse(ctx *parseContext, v mkparser.Node, args *mkparser.MakeString) []starlarkNode {
args.TrimLeftSpaces()
args.TrimRightSpaces()
pathExpr := ctx.parseMakeString(v, args)
if _, ok := pathExpr.(*badExpr); ok {
return []starlarkNode{ctx.newBadNode(v, "Unable to parse argument to inherit")}
}
return ctx.handleSubConfig(v, pathExpr, p.loadAlways, func(im inheritedModule) starlarkNode {
return &inheritNode{im, p.loadAlways}
})
}
func (ctx *parseContext) handleInclude(v *mkparser.Directive) []starlarkNode {
loadAlways := v.Name[0] != '-'
v.Args.TrimRightSpaces()
v.Args.TrimLeftSpaces()
return ctx.handleSubConfig(v, ctx.parseMakeString(v, v.Args), loadAlways, func(im inheritedModule) starlarkNode {
return &includeNode{im, loadAlways}
})
}
func (ctx *parseContext) handleVariable(v *mkparser.Variable) []starlarkNode {
// Handle:
// $(call inherit-product,...)
// $(call inherit-product-if-exists,...)
// $(info xxx)
// $(warning xxx)
// $(error xxx)
// $(call other-custom-functions,...)
if name, args, ok := ctx.maybeParseFunctionCall(v, v.Name); ok {
if kf, ok := knownNodeFunctions[name]; ok {
return kf.parse(ctx, v, args)
}
}
return []starlarkNode{&exprNode{expr: ctx.parseReference(v, v.Name)}}
}
func (ctx *parseContext) maybeHandleDefine(directive *mkparser.Directive) starlarkNode {
macro_name := strings.Fields(directive.Args.Strings[0])[0]
// Ignore the macros that we handle
_, ignored := ignoredDefines[macro_name]
_, known := knownFunctions[macro_name]
if !ignored && !known {
return ctx.newBadNode(directive, "define is not supported: %s", macro_name)
}
return nil
}
func (ctx *parseContext) handleIfBlock(ifDirective *mkparser.Directive) starlarkNode {
ssSwitch := &switchNode{
ssCases: []*switchCase{ctx.processBranch(ifDirective)},
}
for ctx.hasNodes() && ctx.fatalError == nil {
node := ctx.getNode()
switch x := node.(type) {
case *mkparser.Directive:
switch x.Name {
case "else", "elifdef", "elifndef", "elifeq", "elifneq":
ssSwitch.ssCases = append(ssSwitch.ssCases, ctx.processBranch(x))
case "endif":
return ssSwitch
default:
return ctx.newBadNode(node, "unexpected directive %s", x.Name)
}
default:
return ctx.newBadNode(ifDirective, "unexpected statement")
}
}
if ctx.fatalError == nil {
ctx.fatalError = fmt.Errorf("no matching endif for %s", ifDirective.Dump())
}
return ctx.newBadNode(ifDirective, "no matching endif for %s", ifDirective.Dump())
}
// processBranch processes a single branch (if/elseif/else) until the next directive
// on the same level.
func (ctx *parseContext) processBranch(check *mkparser.Directive) *switchCase {
block := &switchCase{gate: ctx.parseCondition(check)}
defer func() {
ctx.ifNestLevel--
}()
ctx.ifNestLevel++
for ctx.hasNodes() {
node := ctx.getNode()
if d, ok := node.(*mkparser.Directive); ok {
switch d.Name {
case "else", "elifdef", "elifndef", "elifeq", "elifneq", "endif":
ctx.backNode()
return block
}
}
block.nodes = append(block.nodes, ctx.handleSimpleStatement(node)...)
}
ctx.fatalError = fmt.Errorf("no matching endif for %s", check.Dump())
return block
}
func (ctx *parseContext) parseCondition(check *mkparser.Directive) starlarkNode {
switch check.Name {
case "ifdef", "ifndef", "elifdef", "elifndef":
if !check.Args.Const() {
return ctx.newBadNode(check, "ifdef variable ref too complex: %s", check.Args.Dump())
}
v := NewVariableRefExpr(ctx.addVariable(check.Args.Strings[0]))
if strings.HasSuffix(check.Name, "ndef") {
v = &notExpr{v}
}
return &ifNode{
isElif: strings.HasPrefix(check.Name, "elif"),
expr: v,
}
case "ifeq", "ifneq", "elifeq", "elifneq":
return &ifNode{
isElif: strings.HasPrefix(check.Name, "elif"),
expr: ctx.parseCompare(check),
}
case "else":
return &elseNode{}
default:
panic(fmt.Errorf("%s: unknown directive: %s", ctx.script.mkFile, check.Dump()))
}
}
func (ctx *parseContext) newBadExpr(node mkparser.Node, text string, args ...interface{}) starlarkExpr {
if ctx.errorLogger != nil {
ctx.errorLogger.NewError(ctx.errorLocation(node), node, text, args...)
}
ctx.script.hasErrors = true
return &badExpr{errorLocation: ctx.errorLocation(node), message: fmt.Sprintf(text, args...)}
}
// records that the given node failed to be converted and includes an explanatory message
func (ctx *parseContext) newBadNode(failedNode mkparser.Node, message string, args ...interface{}) starlarkNode {
return &exprNode{ctx.newBadExpr(failedNode, message, args...)}
}
func (ctx *parseContext) parseCompare(cond *mkparser.Directive) starlarkExpr {
// Strip outer parentheses
mkArg := cloneMakeString(cond.Args)
mkArg.Strings[0] = strings.TrimLeft(mkArg.Strings[0], "( ")
n := len(mkArg.Strings)
mkArg.Strings[n-1] = strings.TrimRight(mkArg.Strings[n-1], ") ")
args := mkArg.Split(",")
// TODO(asmundak): handle the case where the arguments are in quotes and space-separated
if len(args) != 2 {
return ctx.newBadExpr(cond, "ifeq/ifneq len(args) != 2 %s", cond.Dump())
}
args[0].TrimRightSpaces()
args[1].TrimLeftSpaces()
isEq := !strings.HasSuffix(cond.Name, "neq")
xLeft := ctx.parseMakeString(cond, args[0])
xRight := ctx.parseMakeString(cond, args[1])
if bad, ok := xLeft.(*badExpr); ok {
return bad
}
if bad, ok := xRight.(*badExpr); ok {
return bad
}
if expr, ok := ctx.parseCompareSpecialCases(cond, xLeft, xRight); ok {
return expr
}
var stringOperand string
var otherOperand starlarkExpr
if s, ok := maybeString(xLeft); ok {
stringOperand = s
otherOperand = xRight
} else if s, ok := maybeString(xRight); ok {
stringOperand = s
otherOperand = xLeft
}
// If we've identified one of the operands as being a string literal, check
// for some special cases we can do to simplify the resulting expression.
if otherOperand != nil {
if stringOperand == "" {
if isEq {
return negateExpr(otherOperand)
} else {
return otherOperand
}
}
if stringOperand == "true" && otherOperand.typ() == starlarkTypeBool {
if !isEq {
return negateExpr(otherOperand)
} else {
return otherOperand
}
}
if otherOperand.typ() == starlarkTypeList {
fields := strings.Fields(stringOperand)
elements := make([]starlarkExpr, len(fields))
for i, s := range fields {
elements[i] = &stringLiteralExpr{literal: s}
}
return &eqExpr{
left: otherOperand,
right: &listExpr{elements},
isEq: isEq,
}
}
if intOperand, err := strconv.Atoi(strings.TrimSpace(stringOperand)); err == nil && otherOperand.typ() == starlarkTypeInt {
return &eqExpr{
left: otherOperand,
right: &intLiteralExpr{literal: intOperand},
isEq: isEq,
}
}
}
return &eqExpr{left: xLeft, right: xRight, isEq: isEq}
}
// Given an if statement's directive and the left/right starlarkExprs,
// check if the starlarkExprs are one of a few hardcoded special cases
// that can be converted to a simpler equality expression than simply comparing
// the two.
func (ctx *parseContext) parseCompareSpecialCases(directive *mkparser.Directive, left starlarkExpr,
right starlarkExpr) (starlarkExpr, bool) {
isEq := !strings.HasSuffix(directive.Name, "neq")
// All the special cases require a call on one side and a
// string literal/variable on the other. Turn the left/right variables into
// call/value variables, and return false if that's not possible.
var value starlarkExpr = nil
call, ok := left.(*callExpr)
if ok {
switch right.(type) {
case *stringLiteralExpr, *variableRefExpr:
value = right
}
} else {
call, _ = right.(*callExpr)
switch left.(type) {
case *stringLiteralExpr, *variableRefExpr:
value = left
}
}
if call == nil || value == nil {
return nil, false
}
switch call.name {
case baseName + ".filter":
return ctx.parseCompareFilterFuncResult(directive, call, value, isEq)
case baseName + ".findstring":
return ctx.parseCheckFindstringFuncResult(directive, call, value, !isEq), true
case baseName + ".strip":
return ctx.parseCompareStripFuncResult(directive, call, value, !isEq), true
}
return nil, false
}
func (ctx *parseContext) parseCompareFilterFuncResult(cond *mkparser.Directive,
filterFuncCall *callExpr, xValue starlarkExpr, negate bool) (starlarkExpr, bool) {
// We handle:
// * ifeq/ifneq (,$(filter v1 v2 ..., EXPR) becomes if EXPR not in/in ["v1", "v2", ...]
// * ifeq/ifneq (,$(filter EXPR, v1 v2 ...) becomes if EXPR not in/in ["v1", "v2", ...]
if x, ok := xValue.(*stringLiteralExpr); !ok || x.literal != "" {
return nil, false
}
xPattern := filterFuncCall.args[0]
xText := filterFuncCall.args[1]
var xInList *stringLiteralExpr
var expr starlarkExpr
var ok bool
if xInList, ok = xPattern.(*stringLiteralExpr); ok && !strings.ContainsRune(xInList.literal, '%') && xText.typ() == starlarkTypeList {
expr = xText
} else if xInList, ok = xText.(*stringLiteralExpr); ok {
expr = xPattern
} else {
return nil, false
}
slExpr := newStringListExpr(strings.Fields(xInList.literal))
// Generate simpler code for the common cases:
if expr.typ() == starlarkTypeList {
if len(slExpr.items) == 1 {
// Checking that a string belongs to list
return &inExpr{isNot: negate, list: expr, expr: slExpr.items[0]}, true
} else {
return nil, false
}
} else if len(slExpr.items) == 1 {
return &eqExpr{left: expr, right: slExpr.items[0], isEq: !negate}, true
} else {
return &inExpr{isNot: negate, list: newStringListExpr(strings.Fields(xInList.literal)), expr: expr}, true
}
}
func (ctx *parseContext) parseCheckFindstringFuncResult(directive *mkparser.Directive,
xCall *callExpr, xValue starlarkExpr, negate bool) starlarkExpr {
if isEmptyString(xValue) {
return &eqExpr{
left: &callExpr{
object: xCall.args[1],
name: "find",
args: []starlarkExpr{xCall.args[0]},
returnType: starlarkTypeInt,
},
right: &intLiteralExpr{-1},
isEq: !negate,
}
} else if s, ok := maybeString(xValue); ok {
if s2, ok := maybeString(xCall.args[0]); ok && s == s2 {
return &eqExpr{
left: &callExpr{
object: xCall.args[1],
name: "find",
args: []starlarkExpr{xCall.args[0]},
returnType: starlarkTypeInt,
},
right: &intLiteralExpr{-1},
isEq: negate,
}
}
}
return ctx.newBadExpr(directive, "$(findstring) can only be compared to nothing or its first argument")
}
func (ctx *parseContext) parseCompareStripFuncResult(directive *mkparser.Directive,
xCall *callExpr, xValue starlarkExpr, negate bool) starlarkExpr {
if _, ok := xValue.(*stringLiteralExpr); !ok {
return ctx.newBadExpr(directive, "strip result can be compared only to string: %s", xValue)
}
return &eqExpr{
left: &callExpr{
name: "strip",
args: xCall.args,
returnType: starlarkTypeString,
},
right: xValue, isEq: !negate}
}
func (ctx *parseContext) maybeParseFunctionCall(node mkparser.Node, ref *mkparser.MakeString) (name string, args *mkparser.MakeString, ok bool) {
ref.TrimLeftSpaces()
ref.TrimRightSpaces()
words := ref.SplitN(" ", 2)
if !words[0].Const() {
return "", nil, false
}
name = words[0].Dump()
args = mkparser.SimpleMakeString("", words[0].Pos())
if len(words) >= 2 {
args = words[1]
}
args.TrimLeftSpaces()
if name == "call" {
words = args.SplitN(",", 2)
if words[0].Empty() || !words[0].Const() {
return "", nil, false
}
name = words[0].Dump()
if len(words) < 2 {
args = mkparser.SimpleMakeString("", words[0].Pos())
} else {
args = words[1]
}
}
ok = true
return
}
// parses $(...), returning an expression
func (ctx *parseContext) parseReference(node mkparser.Node, ref *mkparser.MakeString) starlarkExpr {
ref.TrimLeftSpaces()
ref.TrimRightSpaces()
refDump := ref.Dump()
// Handle only the case where the first (or only) word is constant
words := ref.SplitN(" ", 2)
if !words[0].Const() {
if len(words) == 1 {
expr := ctx.parseMakeString(node, ref)
return &callExpr{
object: &identifierExpr{"cfg"},
name: "get",
args: []starlarkExpr{
expr,
&callExpr{
object: &identifierExpr{"g"},
name: "get",
args: []starlarkExpr{
expr,
&stringLiteralExpr{literal: ""},
},
returnType: starlarkTypeUnknown,
},
},
returnType: starlarkTypeUnknown,
}
} else {
return ctx.newBadExpr(node, "reference is too complex: %s", refDump)
}
}
if name, _, ok := ctx.maybeParseFunctionCall(node, ref); ok {
if _, unsupported := unsupportedFunctions[name]; unsupported {
return ctx.newBadExpr(node, "%s is not supported", refDump)
}
}
// If it is a single word, it can be a simple variable
// reference or a function call
if len(words) == 1 && !isMakeControlFunc(refDump) && refDump != "shell" && refDump != "eval" {
if strings.HasPrefix(refDump, soongNsPrefix) {
// TODO (asmundak): if we find many, maybe handle them.
return ctx.newBadExpr(node, "SOONG_CONFIG_ variables cannot be referenced, use soong_config_get instead: %s", refDump)
}
// Handle substitution references: https://www.gnu.org/software/make/manual/html_node/Substitution-Refs.html
if strings.Contains(refDump, ":") {
parts := strings.SplitN(refDump, ":", 2)
substParts := strings.SplitN(parts[1], "=", 2)
if len(substParts) < 2 || strings.Count(substParts[0], "%") > 1 {
return ctx.newBadExpr(node, "Invalid substitution reference")
}
if !strings.Contains(substParts[0], "%") {
if strings.Contains(substParts[1], "%") {
return ctx.newBadExpr(node, "A substitution reference must have a %% in the \"before\" part of the substitution if it has one in the \"after\" part.")
}
substParts[0] = "%" + substParts[0]
substParts[1] = "%" + substParts[1]
}
v := ctx.addVariable(parts[0])
if v == nil {
return ctx.newBadExpr(node, "unknown variable %s", refDump)
}
return &callExpr{
name: baseName + ".mkpatsubst",
returnType: starlarkTypeString,
args: []starlarkExpr{
&stringLiteralExpr{literal: substParts[0]},
&stringLiteralExpr{literal: substParts[1]},
NewVariableRefExpr(v),
},
}
}
if v := ctx.addVariable(refDump); v != nil {
return NewVariableRefExpr(v)
}
return ctx.newBadExpr(node, "unknown variable %s", refDump)
}
if name, args, ok := ctx.maybeParseFunctionCall(node, ref); ok {
if kf, found := knownFunctions[name]; found {
return kf.parse(ctx, node, args)
} else {
return ctx.newBadExpr(node, "cannot handle invoking %s", name)
}
}
return ctx.newBadExpr(node, "cannot handle %s", refDump)
}
type simpleCallParser struct {
name string
returnType starlarkType
addGlobals bool
addHandle bool
}
func (p *simpleCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
expr := &callExpr{name: p.name, returnType: p.returnType}
if p.addGlobals {
expr.args = append(expr.args, &globalsExpr{})
}
if p.addHandle {
expr.args = append(expr.args, &identifierExpr{name: "handle"})
}
for _, arg := range args.Split(",") {
arg.TrimLeftSpaces()
arg.TrimRightSpaces()
x := ctx.parseMakeString(node, arg)
if xBad, ok := x.(*badExpr); ok {
return xBad
}
expr.args = append(expr.args, x)
}
return expr
}
type makeControlFuncParser struct {
name string
}
func (p *makeControlFuncParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
// Make control functions need special treatment as everything
// after the name is a single text argument
x := ctx.parseMakeString(node, args)
if xBad, ok := x.(*badExpr); ok {
return xBad
}
return &callExpr{
name: p.name,
args: []starlarkExpr{
&stringLiteralExpr{ctx.script.mkFile},
x,
},
returnType: starlarkTypeUnknown,
}
}
type shellCallParser struct{}
func (p *shellCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
// Shell functions need special treatment as everything
// after the name is a single text argument
x := ctx.parseMakeString(node, args)
if xBad, ok := x.(*badExpr); ok {
return xBad
}
return &callExpr{
name: baseName + ".shell",
args: []starlarkExpr{x},
returnType: starlarkTypeUnknown,
}
}
type myDirCallParser struct{}
func (p *myDirCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
if !args.Empty() {
return ctx.newBadExpr(node, "my-dir function cannot have any arguments passed to it.")
}
return &stringLiteralExpr{literal: filepath.Dir(ctx.script.mkFile)}
}
type andOrParser struct {
isAnd bool
}
func (p *andOrParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
if args.Empty() {
return ctx.newBadExpr(node, "and/or function must have at least 1 argument")
}
op := "or"
if p.isAnd {
op = "and"
}
argsParsed := make([]starlarkExpr, 0)
for _, arg := range args.Split(",") {
arg.TrimLeftSpaces()
arg.TrimRightSpaces()
x := ctx.parseMakeString(node, arg)
if xBad, ok := x.(*badExpr); ok {
return xBad
}
argsParsed = append(argsParsed, x)
}
typ := starlarkTypeUnknown
for _, arg := range argsParsed {
if typ != arg.typ() && arg.typ() != starlarkTypeUnknown && typ != starlarkTypeUnknown {
return ctx.newBadExpr(node, "Expected all arguments to $(or) or $(and) to have the same type, found %q and %q", typ.String(), arg.typ().String())
}
if arg.typ() != starlarkTypeUnknown {
typ = arg.typ()
}
}
result := argsParsed[0]
for _, arg := range argsParsed[1:] {
result = &binaryOpExpr{
left: result,
right: arg,
op: op,
returnType: typ,
}
}
return result
}
type isProductInListCallParser struct{}
func (p *isProductInListCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
if args.Empty() {
return ctx.newBadExpr(node, "is-product-in-list requires an argument")
}
return &inExpr{
expr: NewVariableRefExpr(ctx.addVariable("TARGET_PRODUCT")),
list: maybeConvertToStringList(ctx.parseMakeString(node, args)),
isNot: false,
}
}
type isVendorBoardPlatformCallParser struct{}
func (p *isVendorBoardPlatformCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
if args.Empty() || !identifierFullMatchRegex.MatchString(args.Dump()) {
return ctx.newBadExpr(node, "cannot handle non-constant argument to is-vendor-board-platform")
}
return &inExpr{
expr: NewVariableRefExpr(ctx.addVariable("TARGET_BOARD_PLATFORM")),
list: NewVariableRefExpr(ctx.addVariable(args.Dump() + "_BOARD_PLATFORMS")),
isNot: false,
}
}
type isVendorBoardQcomCallParser struct{}
func (p *isVendorBoardQcomCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
if !args.Empty() {
return ctx.newBadExpr(node, "is-vendor-board-qcom does not accept any arguments")
}
return &inExpr{
expr: NewVariableRefExpr(ctx.addVariable("TARGET_BOARD_PLATFORM")),
list: NewVariableRefExpr(ctx.addVariable("QCOM_BOARD_PLATFORMS")),
isNot: false,
}
}
type substCallParser struct {
fname string
}
func (p *substCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
words := args.Split(",")
if len(words) != 3 {
return ctx.newBadExpr(node, "%s function should have 3 arguments", p.fname)
}
from := ctx.parseMakeString(node, words[0])
if xBad, ok := from.(*badExpr); ok {
return xBad
}
to := ctx.parseMakeString(node, words[1])
if xBad, ok := to.(*badExpr); ok {
return xBad
}
words[2].TrimLeftSpaces()
words[2].TrimRightSpaces()
obj := ctx.parseMakeString(node, words[2])
typ := obj.typ()
if typ == starlarkTypeString && p.fname == "subst" {
// Optimization: if it's $(subst from, to, string), emit string.replace(from, to)
return &callExpr{
object: obj,
name: "replace",
args: []starlarkExpr{from, to},
returnType: typ,
}
}
return &callExpr{
name: baseName + ".mk" + p.fname,
args: []starlarkExpr{from, to, obj},
returnType: obj.typ(),
}
}
type ifCallParser struct{}
func (p *ifCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
words := args.Split(",")
if len(words) != 2 && len(words) != 3 {
return ctx.newBadExpr(node, "if function should have 2 or 3 arguments, found "+strconv.Itoa(len(words)))
}
condition := ctx.parseMakeString(node, words[0])
ifTrue := ctx.parseMakeString(node, words[1])
var ifFalse starlarkExpr
if len(words) == 3 {
ifFalse = ctx.parseMakeString(node, words[2])
} else {
switch ifTrue.typ() {
case starlarkTypeList:
ifFalse = &listExpr{items: []starlarkExpr{}}
case starlarkTypeInt:
ifFalse = &intLiteralExpr{literal: 0}
case starlarkTypeBool:
ifFalse = &boolLiteralExpr{literal: false}
default:
ifFalse = &stringLiteralExpr{literal: ""}
}
}
return &ifExpr{
condition,
ifTrue,
ifFalse,
}
}
type ifCallNodeParser struct{}
func (p *ifCallNodeParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) []starlarkNode {
words := args.Split(",")
if len(words) != 2 && len(words) != 3 {
return []starlarkNode{ctx.newBadNode(node, "if function should have 2 or 3 arguments, found "+strconv.Itoa(len(words)))}
}
ifn := &ifNode{expr: ctx.parseMakeString(node, words[0])}
cases := []*switchCase{
{
gate: ifn,
nodes: ctx.parseNodeMakeString(node, words[1]),
},
}
if len(words) == 3 {
cases = append(cases, &switchCase{
gate: &elseNode{},
nodes: ctx.parseNodeMakeString(node, words[2]),
})
}
if len(cases) == 2 {
if len(cases[1].nodes) == 0 {
// Remove else branch if it has no contents
cases = cases[:1]
} else if len(cases[0].nodes) == 0 {
// If the if branch has no contents but the else does,
// move them to the if and negate its condition
ifn.expr = negateExpr(ifn.expr)
cases[0].nodes = cases[1].nodes
cases = cases[:1]
}
}
return []starlarkNode{&switchNode{ssCases: cases}}
}
type foreachCallParser struct{}
func (p *foreachCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
words := args.Split(",")
if len(words) != 3 {
return ctx.newBadExpr(node, "foreach function should have 3 arguments, found "+strconv.Itoa(len(words)))
}
if !words[0].Const() || words[0].Empty() || !identifierFullMatchRegex.MatchString(words[0].Strings[0]) {
return ctx.newBadExpr(node, "first argument to foreach function must be a simple string identifier")
}
loopVarName := words[0].Strings[0]
list := ctx.parseMakeString(node, words[1])
action := ctx.parseMakeString(node, words[2]).transform(func(expr starlarkExpr) starlarkExpr {
if varRefExpr, ok := expr.(*variableRefExpr); ok && varRefExpr.ref.name() == loopVarName {
return &identifierExpr{loopVarName}
}
return nil
})
if list.typ() != starlarkTypeList {
list = &callExpr{
name: baseName + ".words",
returnType: starlarkTypeList,
args: []starlarkExpr{list},
}
}
var result starlarkExpr = &foreachExpr{
varName: loopVarName,
list: list,
action: action,
}
if action.typ() == starlarkTypeList {
result = &callExpr{
name: baseName + ".flatten_2d_list",
args: []starlarkExpr{result},
returnType: starlarkTypeList,
}
}
return result
}
func transformNode(node starlarkNode, transformer func(expr starlarkExpr) starlarkExpr) {
switch a := node.(type) {
case *ifNode:
a.expr = a.expr.transform(transformer)
case *switchCase:
transformNode(a.gate, transformer)
for _, n := range a.nodes {
transformNode(n, transformer)
}
case *switchNode:
for _, n := range a.ssCases {
transformNode(n, transformer)
}
case *exprNode:
a.expr = a.expr.transform(transformer)
case *assignmentNode:
a.value = a.value.transform(transformer)
case *foreachNode:
a.list = a.list.transform(transformer)
for _, n := range a.actions {
transformNode(n, transformer)
}
case *inheritNode:
if b, ok := a.module.(inheritedDynamicModule); ok {
b.path = b.path.transform(transformer)
a.module = b
}
case *includeNode:
if b, ok := a.module.(inheritedDynamicModule); ok {
b.path = b.path.transform(transformer)
a.module = b
}
}
}
type foreachCallNodeParser struct{}
func (p *foreachCallNodeParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) []starlarkNode {
words := args.Split(",")
if len(words) != 3 {
return []starlarkNode{ctx.newBadNode(node, "foreach function should have 3 arguments, found "+strconv.Itoa(len(words)))}
}
if !words[0].Const() || words[0].Empty() || !identifierFullMatchRegex.MatchString(words[0].Strings[0]) {
return []starlarkNode{ctx.newBadNode(node, "first argument to foreach function must be a simple string identifier")}
}
loopVarName := words[0].Strings[0]
list := ctx.parseMakeString(node, words[1])
if list.typ() != starlarkTypeList {
list = &callExpr{
name: baseName + ".words",
returnType: starlarkTypeList,
args: []starlarkExpr{list},
}
}
actions := ctx.parseNodeMakeString(node, words[2])
// TODO(colefaust): Replace transforming code with something more elegant
for _, action := range actions {
transformNode(action, func(expr starlarkExpr) starlarkExpr {
if varRefExpr, ok := expr.(*variableRefExpr); ok && varRefExpr.ref.name() == loopVarName {
return &identifierExpr{loopVarName}
}
return nil
})
}
return []starlarkNode{&foreachNode{
varName: loopVarName,
list: list,
actions: actions,
}}
}
type wordCallParser struct{}
func (p *wordCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
words := args.Split(",")
if len(words) != 2 {
return ctx.newBadExpr(node, "word function should have 2 arguments")
}
var index = 0
if words[0].Const() {
if i, err := strconv.Atoi(strings.TrimSpace(words[0].Strings[0])); err == nil {
index = i
}
}
if index < 1 {
return ctx.newBadExpr(node, "word index should be constant positive integer")
}
words[1].TrimLeftSpaces()
words[1].TrimRightSpaces()
array := ctx.parseMakeString(node, words[1])
if bad, ok := array.(*badExpr); ok {
return bad
}
if array.typ() != starlarkTypeList {
array = &callExpr{
name: baseName + ".words",
args: []starlarkExpr{array},
returnType: starlarkTypeList,
}
}
return &indexExpr{array, &intLiteralExpr{index - 1}}
}
type wordsCallParser struct{}
func (p *wordsCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
args.TrimLeftSpaces()
args.TrimRightSpaces()
array := ctx.parseMakeString(node, args)
if bad, ok := array.(*badExpr); ok {
return bad
}
if array.typ() != starlarkTypeList {
array = &callExpr{
name: baseName + ".words",
args: []starlarkExpr{array},
returnType: starlarkTypeList,
}
}
return &callExpr{
name: "len",
args: []starlarkExpr{array},
returnType: starlarkTypeInt,
}
}
func parseIntegerArguments(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString, expectedArgs int) ([]starlarkExpr, error) {
parsedArgs := make([]starlarkExpr, 0)
for _, arg := range args.Split(",") {
expr := ctx.parseMakeString(node, arg)
if expr.typ() == starlarkTypeList {
return nil, fmt.Errorf("argument to math argument has type list, which cannot be converted to int")
}
if s, ok := maybeString(expr); ok {
intVal, err := strconv.Atoi(strings.TrimSpace(s))
if err != nil {
return nil, err
}
expr = &intLiteralExpr{literal: intVal}
} else if expr.typ() != starlarkTypeInt {
expr = &callExpr{
name: "int",
args: []starlarkExpr{expr},
returnType: starlarkTypeInt,
}
}
parsedArgs = append(parsedArgs, expr)
}
if len(parsedArgs) != expectedArgs {
return nil, fmt.Errorf("function should have %d arguments", expectedArgs)
}
return parsedArgs, nil
}
type mathComparisonCallParser struct {
op string
}
func (p *mathComparisonCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
parsedArgs, err := parseIntegerArguments(ctx, node, args, 2)
if err != nil {
return ctx.newBadExpr(node, err.Error())
}
return &binaryOpExpr{
left: parsedArgs[0],
right: parsedArgs[1],
op: p.op,
returnType: starlarkTypeBool,
}
}
type mathMaxOrMinCallParser struct {
function string
}
func (p *mathMaxOrMinCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
parsedArgs, err := parseIntegerArguments(ctx, node, args, 2)
if err != nil {
return ctx.newBadExpr(node, err.Error())
}
return &callExpr{
object: nil,
name: p.function,
args: parsedArgs,
returnType: starlarkTypeInt,
}
}
type evalNodeParser struct{}
func (p *evalNodeParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) []starlarkNode {
parser := mkparser.NewParser("Eval expression", strings.NewReader(args.Dump()))
nodes, errs := parser.Parse()
if errs != nil {
return []starlarkNode{ctx.newBadNode(node, "Unable to parse eval statement")}
}
if len(nodes) == 0 {
return []starlarkNode{}
} else if len(nodes) == 1 {
// Replace the nodeLocator with one that just returns the location of
// the $(eval) node. Otherwise, statements inside an $(eval) will show as
// being on line 1 of the file, because they're on line 1 of
// strings.NewReader(args.Dump())
oldNodeLocator := ctx.script.nodeLocator
ctx.script.nodeLocator = func(pos mkparser.Pos) int {
return oldNodeLocator(node.Pos())
}
defer func() {
ctx.script.nodeLocator = oldNodeLocator
}()
switch n := nodes[0].(type) {
case *mkparser.Assignment:
if n.Name.Const() {
return ctx.handleAssignment(n)
}
case *mkparser.Comment:
return []starlarkNode{&commentNode{strings.TrimSpace("#" + n.Comment)}}
case *mkparser.Directive:
if n.Name == "include" || n.Name == "-include" {
return ctx.handleInclude(n)
}
case *mkparser.Variable:
// Technically inherit-product(-if-exists) don't need to be put inside
// an eval, but some makefiles do it, presumably because they copy+pasted
// from a $(eval include ...)
if name, _, ok := ctx.maybeParseFunctionCall(n, n.Name); ok {
if name == "inherit-product" || name == "inherit-product-if-exists" {
return ctx.handleVariable(n)
}
}
}
}
return []starlarkNode{ctx.newBadNode(node, "Eval expression too complex; only assignments, comments, includes, and inherit-products are supported")}
}
type lowerUpperParser struct {
isUpper bool
}
func (p *lowerUpperParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
fn := "lower"
if p.isUpper {
fn = "upper"
}
arg := ctx.parseMakeString(node, args)
return &callExpr{
object: arg,
name: fn,
returnType: starlarkTypeString,
}
}
func (ctx *parseContext) parseMakeString(node mkparser.Node, mk *mkparser.MakeString) starlarkExpr {
if mk.Const() {
return &stringLiteralExpr{mk.Dump()}
}
if mkRef, ok := mk.SingleVariable(); ok {
return ctx.parseReference(node, mkRef)
}
// If we reached here, it's neither string literal nor a simple variable,
// we need a full-blown interpolation node that will generate
// "a%b%c" % (X, Y) for a$(X)b$(Y)c
parts := make([]starlarkExpr, len(mk.Variables)+len(mk.Strings))
for i := 0; i < len(parts); i++ {
if i%2 == 0 {
parts[i] = &stringLiteralExpr{literal: mk.Strings[i/2]}
} else {
parts[i] = ctx.parseReference(node, mk.Variables[i/2].Name)
if x, ok := parts[i].(*badExpr); ok {
return x
}
}
}
return NewInterpolateExpr(parts)
}
func (ctx *parseContext) parseNodeMakeString(node mkparser.Node, mk *mkparser.MakeString) []starlarkNode {
// Discard any constant values in the make string, as they would be top level
// string literals and do nothing.
result := make([]starlarkNode, 0, len(mk.Variables))
for i := range mk.Variables {
result = append(result, ctx.handleVariable(&mk.Variables[i])...)
}
return result
}
// Handles the statements whose treatment is the same in all contexts: comment,
// assignment, variable (which is a macro call in reality) and all constructs that
// do not handle in any context ('define directive and any unrecognized stuff).
func (ctx *parseContext) handleSimpleStatement(node mkparser.Node) []starlarkNode {
var result []starlarkNode
switch x := node.(type) {
case *mkparser.Comment:
if n, handled := ctx.maybeHandleAnnotation(x); handled && n != nil {
result = []starlarkNode{n}
} else if !handled {
result = []starlarkNode{&commentNode{strings.TrimSpace("#" + x.Comment)}}
}
case *mkparser.Assignment:
result = ctx.handleAssignment(x)
case *mkparser.Variable:
result = ctx.handleVariable(x)
case *mkparser.Directive:
switch x.Name {
case "define":
if res := ctx.maybeHandleDefine(x); res != nil {
result = []starlarkNode{res}
}
case "include", "-include":
result = ctx.handleInclude(x)
case "ifeq", "ifneq", "ifdef", "ifndef":
result = []starlarkNode{ctx.handleIfBlock(x)}
default:
result = []starlarkNode{ctx.newBadNode(x, "unexpected directive %s", x.Name)}
}
default:
result = []starlarkNode{ctx.newBadNode(x, "unsupported line %s", strings.ReplaceAll(x.Dump(), "\n", "\n#"))}
}
// Clear the includeTops after each non-comment statement
// so that include annotations placed on certain statements don't apply
// globally for the rest of the makefile was well.
if _, wasComment := node.(*mkparser.Comment); !wasComment {
ctx.atTopOfMakefile = false
ctx.includeTops = []string{}
}
if result == nil {
result = []starlarkNode{}
}
return result
}
// The types allowed in a type_hint
var typeHintMap = map[string]starlarkType{
"string": starlarkTypeString,
"list": starlarkTypeList,
}
// Processes annotation. An annotation is a comment that starts with #RBC# and provides
// a conversion hint -- say, where to look for the dynamically calculated inherit/include
// paths. Returns true if the comment was a successfully-handled annotation.
func (ctx *parseContext) maybeHandleAnnotation(cnode *mkparser.Comment) (starlarkNode, bool) {
maybeTrim := func(s, prefix string) (string, bool) {
if strings.HasPrefix(s, prefix) {
return strings.TrimSpace(strings.TrimPrefix(s, prefix)), true
}
return s, false
}
annotation, ok := maybeTrim(cnode.Comment, annotationCommentPrefix)
if !ok {
return nil, false
}
if p, ok := maybeTrim(annotation, "include_top"); ok {
// Don't allow duplicate include tops, because then we will generate
// invalid starlark code. (duplicate keys in the _entry dictionary)
for _, top := range ctx.includeTops {
if top == p {
return nil, true
}
}
ctx.includeTops = append(ctx.includeTops, p)
return nil, true
} else if p, ok := maybeTrim(annotation, "type_hint"); ok {
// Type hints must come at the beginning the file, to avoid confusion
// if a type hint was specified later and thus only takes effect for half
// of the file.
if !ctx.atTopOfMakefile {
return ctx.newBadNode(cnode, "type_hint annotations must come before the first Makefile statement"), true
}
parts := strings.Fields(p)
if len(parts) <= 1 {
return ctx.newBadNode(cnode, "Invalid type_hint annotation: %s. Must be a variable type followed by a list of variables of that type", p), true
}
var varType starlarkType
if varType, ok = typeHintMap[parts[0]]; !ok {
varType = starlarkTypeUnknown
}
if varType == starlarkTypeUnknown {
return ctx.newBadNode(cnode, "Invalid type_hint annotation. Only list/string types are accepted, found %s", parts[0]), true
}
for _, name := range parts[1:] {
// Don't allow duplicate type hints
if _, ok := ctx.typeHints[name]; ok {
return ctx.newBadNode(cnode, "Duplicate type hint for variable %s", name), true
}
ctx.typeHints[name] = varType
}
return nil, true
}
return ctx.newBadNode(cnode, "unsupported annotation %s", cnode.Comment), true
}
func (ctx *parseContext) loadedModulePath(path string) string {
// During the transition to Roboleaf some of the product configuration files
// will be converted and checked in while the others will be generated on the fly
// and run. The runner (rbcrun application) accommodates this by allowing three
// different ways to specify the loaded file location:
// 1) load(":<file>",...) loads <file> from the same directory
// 2) load("//path/relative/to/source/root:<file>", ...) loads <file> source tree
// 3) load("/absolute/path/to/<file> absolute path
// If the file being generated and the file it wants to load are in the same directory,
// generate option 1.
// Otherwise, if output directory is not specified, generate 2)
// Finally, if output directory has been specified and the file being generated and
// the file it wants to load from are in the different directories, generate 2) or 3):
// * if the file being loaded exists in the source tree, generate 2)
// * otherwise, generate 3)
// Finally, figure out the loaded module path and name and create a node for it
loadedModuleDir := filepath.Dir(path)
base := filepath.Base(path)
loadedModuleName := strings.TrimSuffix(base, filepath.Ext(base)) + ctx.outputSuffix
if loadedModuleDir == filepath.Dir(ctx.script.mkFile) {
return ":" + loadedModuleName
}
if ctx.outputDir == "" {
return fmt.Sprintf("//%s:%s", loadedModuleDir, loadedModuleName)
}
if _, err := os.Stat(filepath.Join(loadedModuleDir, loadedModuleName)); err == nil {
return fmt.Sprintf("//%s:%s", loadedModuleDir, loadedModuleName)
}
return filepath.Join(ctx.outputDir, loadedModuleDir, loadedModuleName)
}
func (ctx *parseContext) addSoongNamespace(ns string) {
if _, ok := ctx.soongNamespaces[ns]; ok {
return
}
ctx.soongNamespaces[ns] = make(map[string]bool)
}
func (ctx *parseContext) hasSoongNamespace(name string) bool {
_, ok := ctx.soongNamespaces[name]
return ok
}
func (ctx *parseContext) updateSoongNamespace(replace bool, namespaceName string, varNames []string) {
ctx.addSoongNamespace(namespaceName)
vars := ctx.soongNamespaces[namespaceName]
if replace {
vars = make(map[string]bool)
ctx.soongNamespaces[namespaceName] = vars
}
for _, v := range varNames {
vars[v] = true
}
}
func (ctx *parseContext) hasNamespaceVar(namespaceName string, varName string) bool {
vars, ok := ctx.soongNamespaces[namespaceName]
if ok {
_, ok = vars[varName]
}
return ok
}
func (ctx *parseContext) errorLocation(node mkparser.Node) ErrorLocation {
return ErrorLocation{ctx.script.mkFile, ctx.script.nodeLocator(node.Pos())}
}
func (ss *StarlarkScript) String() string {
return NewGenerateContext(ss).emit()
}
func (ss *StarlarkScript) SubConfigFiles() []string {
var subs []string
for _, src := range ss.inherited {
subs = append(subs, src.originalPath)
}
return subs
}
func (ss *StarlarkScript) HasErrors() bool {
return ss.hasErrors
}
// Convert reads and parses a makefile. If successful, parsed tree
// is returned and then can be passed to String() to get the generated
// Starlark file.
func Convert(req Request) (*StarlarkScript, error) {
reader := req.Reader
if reader == nil {
mkContents, err := ioutil.ReadFile(req.MkFile)
if err != nil {
return nil, err
}
reader = bytes.NewBuffer(mkContents)
}
parser := mkparser.NewParser(req.MkFile, reader)
nodes, errs := parser.Parse()
if len(errs) > 0 {
for _, e := range errs {
fmt.Fprintln(os.Stderr, "ERROR:", e)
}
return nil, fmt.Errorf("bad makefile %s", req.MkFile)
}
starScript := &StarlarkScript{
moduleName: moduleNameForFile(req.MkFile),
mkFile: req.MkFile,
traceCalls: req.TraceCalls,
sourceFS: req.SourceFS,
makefileFinder: req.MakefileFinder,
nodeLocator: func(pos mkparser.Pos) int { return parser.Unpack(pos).Line },
nodes: make([]starlarkNode, 0),
}
ctx := newParseContext(starScript, nodes)
ctx.outputSuffix = req.OutputSuffix
ctx.outputDir = req.OutputDir
ctx.errorLogger = req.ErrorLogger
if len(req.TracedVariables) > 0 {
ctx.tracedVariables = make(map[string]bool)
for _, v := range req.TracedVariables {
ctx.tracedVariables[v] = true
}
}
for ctx.hasNodes() && ctx.fatalError == nil {
starScript.nodes = append(starScript.nodes, ctx.handleSimpleStatement(ctx.getNode())...)
}
if ctx.fatalError != nil {
return nil, ctx.fatalError
}
return starScript, nil
}
func Launcher(mainModuleUri, inputVariablesUri, mainModuleName string) string {
var buf bytes.Buffer
fmt.Fprintf(&buf, "load(%q, %q)\n", baseUri, baseName)
fmt.Fprintf(&buf, "load(%q, input_variables_init = \"init\")\n", inputVariablesUri)
fmt.Fprintf(&buf, "load(%q, \"init\")\n", mainModuleUri)
fmt.Fprintf(&buf, "%s(%s(%q, init, input_variables_init))\n", cfnPrintVars, cfnMain, mainModuleName)
return buf.String()
}
func BoardLauncher(mainModuleUri string, inputVariablesUri string) string {
var buf bytes.Buffer
fmt.Fprintf(&buf, "load(%q, %q)\n", baseUri, baseName)
fmt.Fprintf(&buf, "load(%q, \"init\")\n", mainModuleUri)
fmt.Fprintf(&buf, "load(%q, input_variables_init = \"init\")\n", inputVariablesUri)
fmt.Fprintf(&buf, "%s(%s(init, input_variables_init))\n", cfnPrintVars, cfnBoardMain)
return buf.String()
}
func MakePath2ModuleName(mkPath string) string {
return strings.TrimSuffix(mkPath, filepath.Ext(mkPath))
}