Allow dynamically calculated inherit-product path
Bug: 193566316 Test: internal Change-Id: Iaa7b68cf459f9a694ae9d37a32c9372cf8a8335a
This commit is contained in:
parent
e083a05ad0
commit
6609ba7664
7 changed files with 475 additions and 55 deletions
|
@ -21,10 +21,12 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
@ -78,6 +80,7 @@ func init() {
|
||||||
var backupSuffix string
|
var backupSuffix string
|
||||||
var tracedVariables []string
|
var tracedVariables []string
|
||||||
var errorLogger = errorsByType{data: make(map[string]datum)}
|
var errorLogger = errorsByType{data: make(map[string]datum)}
|
||||||
|
var makefileFinder = &LinuxMakefileFinder{}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Usage = func() {
|
flag.Usage = func() {
|
||||||
|
@ -302,6 +305,8 @@ func convertOne(mkFile string) (ok bool) {
|
||||||
TracedVariables: tracedVariables,
|
TracedVariables: tracedVariables,
|
||||||
TraceCalls: *traceCalls,
|
TraceCalls: *traceCalls,
|
||||||
WarnPartialSuccess: *warn,
|
WarnPartialSuccess: *warn,
|
||||||
|
SourceFS: os.DirFS(*rootDir),
|
||||||
|
MakefileFinder: makefileFinder,
|
||||||
}
|
}
|
||||||
if *errstat {
|
if *errstat {
|
||||||
mk2starRequest.ErrorLogger = errorLogger
|
mk2starRequest.ErrorLogger = errorLogger
|
||||||
|
@ -504,3 +509,39 @@ func stringsWithFreq(items []string, topN int) (string, int) {
|
||||||
}
|
}
|
||||||
return res, len(sorted)
|
return res, len(sorted)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LinuxMakefileFinder struct {
|
||||||
|
cachedRoot string
|
||||||
|
cachedMakefiles []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LinuxMakefileFinder) Find(root string) []string {
|
||||||
|
if l.cachedMakefiles != nil && l.cachedRoot == root {
|
||||||
|
return l.cachedMakefiles
|
||||||
|
}
|
||||||
|
l.cachedRoot = root
|
||||||
|
l.cachedMakefiles = make([]string, 0)
|
||||||
|
|
||||||
|
// Return all *.mk files but not in hidden directories.
|
||||||
|
|
||||||
|
// NOTE(asmundak): as it turns out, even the WalkDir (which is an _optimized_ directory tree walker)
|
||||||
|
// is about twice slower than running `find` command (14s vs 6s on the internal Android source tree).
|
||||||
|
common_args := []string{"!", "-type", "d", "-name", "*.mk", "!", "-path", "*/.*/*"}
|
||||||
|
if root != "" {
|
||||||
|
common_args = append([]string{root}, common_args...)
|
||||||
|
}
|
||||||
|
cmd := exec.Command("/usr/bin/find", common_args...)
|
||||||
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
if err == nil {
|
||||||
|
err = cmd.Start()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("cannot get the output from %s: %s", cmd, err))
|
||||||
|
}
|
||||||
|
scanner := bufio.NewScanner(stdout)
|
||||||
|
for scanner.Scan() {
|
||||||
|
l.cachedMakefiles = append(l.cachedMakefiles, strings.TrimPrefix(scanner.Text(), "./"))
|
||||||
|
}
|
||||||
|
stdout.Close()
|
||||||
|
return l.cachedMakefiles
|
||||||
|
}
|
||||||
|
|
121
mk2rbc/find_mockfs.go
Normal file
121
mk2rbc/find_mockfs.go
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
package mk2rbc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mock FS. Maps a directory name to an array of entries.
|
||||||
|
// An entry implements fs.DirEntry, fs.FIleInfo and fs.File interface
|
||||||
|
type FindMockFS struct {
|
||||||
|
dirs map[string][]myFileInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m FindMockFS) locate(name string) (myFileInfo, bool) {
|
||||||
|
if name == "." {
|
||||||
|
return myFileInfo{".", true}, true
|
||||||
|
}
|
||||||
|
dir := filepath.Dir(name)
|
||||||
|
base := filepath.Base(name)
|
||||||
|
if entries, ok := m.dirs[dir]; ok {
|
||||||
|
for _, e := range entries {
|
||||||
|
if e.name == base {
|
||||||
|
return e, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return myFileInfo{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m FindMockFS) create(name string, isDir bool) {
|
||||||
|
dir := filepath.Dir(name)
|
||||||
|
m.dirs[dir] = append(m.dirs[dir], myFileInfo{filepath.Base(name), isDir})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m FindMockFS) Stat(name string) (fs.FileInfo, error) {
|
||||||
|
if fi, ok := m.locate(name); ok {
|
||||||
|
return fi, nil
|
||||||
|
}
|
||||||
|
return nil, os.ErrNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
type myFileInfo struct {
|
||||||
|
name string
|
||||||
|
isDir bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m myFileInfo) Info() (fs.FileInfo, error) {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m myFileInfo) Size() int64 {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m myFileInfo) Mode() fs.FileMode {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m myFileInfo) ModTime() time.Time {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m myFileInfo) Sys() interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m myFileInfo) Stat() (fs.FileInfo, error) {
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m myFileInfo) Read(bytes []byte) (int, error) {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m myFileInfo) Close() error {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m myFileInfo) Name() string {
|
||||||
|
return m.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m myFileInfo) IsDir() bool {
|
||||||
|
return m.isDir
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m myFileInfo) Type() fs.FileMode {
|
||||||
|
return m.Mode()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m FindMockFS) Open(name string) (fs.File, error) {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m FindMockFS) ReadDir(name string) ([]fs.DirEntry, error) {
|
||||||
|
if d, ok := m.dirs[name]; ok {
|
||||||
|
var res []fs.DirEntry
|
||||||
|
for _, e := range d {
|
||||||
|
res = append(res, e)
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
return nil, os.ErrNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFindMockFS(files []string) FindMockFS {
|
||||||
|
myfs := FindMockFS{make(map[string][]myFileInfo)}
|
||||||
|
for _, f := range files {
|
||||||
|
isDir := false
|
||||||
|
for f != "." {
|
||||||
|
if _, ok := myfs.locate(f); !ok {
|
||||||
|
myfs.create(f, isDir)
|
||||||
|
}
|
||||||
|
isDir = true
|
||||||
|
f = filepath.Dir(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return myfs
|
||||||
|
}
|
161
mk2rbc/mk2rbc.go
161
mk2rbc/mk2rbc.go
|
@ -27,6 +27,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -104,6 +105,7 @@ var knownFunctions = map[string]struct {
|
||||||
"match-prefix": {"!match-prefix", starlarkTypeUnknown}, // internal macro
|
"match-prefix": {"!match-prefix", starlarkTypeUnknown}, // internal macro
|
||||||
"match-word": {"!match-word", starlarkTypeUnknown}, // internal macro
|
"match-word": {"!match-word", starlarkTypeUnknown}, // internal macro
|
||||||
"match-word-in-list": {"!match-word-in-list", starlarkTypeUnknown}, // internal macro
|
"match-word-in-list": {"!match-word-in-list", starlarkTypeUnknown}, // internal macro
|
||||||
|
"my-dir": {"!my-dir", starlarkTypeString},
|
||||||
"patsubst": {baseName + ".mkpatsubst", starlarkTypeString},
|
"patsubst": {baseName + ".mkpatsubst", starlarkTypeString},
|
||||||
"produce_copy_files": {baseName + ".produce_copy_files", starlarkTypeList},
|
"produce_copy_files": {baseName + ".produce_copy_files", starlarkTypeList},
|
||||||
"require-artifacts-in-path": {baseName + ".require_artifacts_in_path", starlarkTypeVoid},
|
"require-artifacts-in-path": {baseName + ".require_artifacts_in_path", starlarkTypeVoid},
|
||||||
|
@ -136,6 +138,8 @@ type Request struct {
|
||||||
TracedVariables []string // trace assignment to these variables
|
TracedVariables []string // trace assignment to these variables
|
||||||
TraceCalls bool
|
TraceCalls bool
|
||||||
WarnPartialSuccess bool
|
WarnPartialSuccess bool
|
||||||
|
SourceFS fs.FS
|
||||||
|
MakefileFinder MakefileFinder
|
||||||
}
|
}
|
||||||
|
|
||||||
// An error sink allowing to gather error statistics.
|
// An error sink allowing to gather error statistics.
|
||||||
|
@ -149,7 +153,8 @@ type ErrorMonitorCB interface {
|
||||||
func moduleNameForFile(mkFile string) string {
|
func moduleNameForFile(mkFile string) string {
|
||||||
base := strings.TrimSuffix(filepath.Base(mkFile), filepath.Ext(mkFile))
|
base := strings.TrimSuffix(filepath.Base(mkFile), filepath.Ext(mkFile))
|
||||||
// TODO(asmundak): what else can be in the product file names?
|
// TODO(asmundak): what else can be in the product file names?
|
||||||
return strings.ReplaceAll(base, "-", "_")
|
return strings.NewReplacer("-", "_", ".", "_").Replace(base)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func cloneMakeString(mkString *mkparser.MakeString) *mkparser.MakeString {
|
func cloneMakeString(mkString *mkparser.MakeString) *mkparser.MakeString {
|
||||||
|
@ -241,7 +246,7 @@ func (gctx *generationContext) emitPreamble() {
|
||||||
sc.moduleLocalName = m
|
sc.moduleLocalName = m
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !sc.loadAlways {
|
if sc.optional {
|
||||||
uri += "|init"
|
uri += "|init"
|
||||||
}
|
}
|
||||||
gctx.newLine()
|
gctx.newLine()
|
||||||
|
@ -342,11 +347,13 @@ type StarlarkScript struct {
|
||||||
moduleName string
|
moduleName string
|
||||||
mkPos scanner.Position
|
mkPos scanner.Position
|
||||||
nodes []starlarkNode
|
nodes []starlarkNode
|
||||||
inherited []*inheritedModule
|
inherited []*moduleInfo
|
||||||
hasErrors bool
|
hasErrors bool
|
||||||
topDir string
|
topDir string
|
||||||
traceCalls bool // print enter/exit each init function
|
traceCalls bool // print enter/exit each init function
|
||||||
warnPartialSuccess bool
|
warnPartialSuccess bool
|
||||||
|
sourceFS fs.FS
|
||||||
|
makefileFinder MakefileFinder
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *StarlarkScript) newNode(node starlarkNode) {
|
func (ss *StarlarkScript) newNode(node starlarkNode) {
|
||||||
|
@ -379,13 +386,15 @@ type parseContext struct {
|
||||||
receiver nodeReceiver // receptacle for the generated starlarkNode's
|
receiver nodeReceiver // receptacle for the generated starlarkNode's
|
||||||
receiverStack []nodeReceiver
|
receiverStack []nodeReceiver
|
||||||
outputDir string
|
outputDir string
|
||||||
|
dependentModules map[string]*moduleInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func newParseContext(ss *StarlarkScript, nodes []mkparser.Node) *parseContext {
|
func newParseContext(ss *StarlarkScript, nodes []mkparser.Node) *parseContext {
|
||||||
|
topdir, _ := filepath.Split(filepath.Join(ss.topDir, "foo"))
|
||||||
predefined := []struct{ name, value string }{
|
predefined := []struct{ name, value string }{
|
||||||
{"SRC_TARGET_DIR", filepath.Join("build", "make", "target")},
|
{"SRC_TARGET_DIR", filepath.Join("build", "make", "target")},
|
||||||
{"LOCAL_PATH", filepath.Dir(ss.mkFile)},
|
{"LOCAL_PATH", filepath.Dir(ss.mkFile)},
|
||||||
{"TOPDIR", ss.topDir},
|
{"TOPDIR", topdir},
|
||||||
// TODO(asmundak): maybe read it from build/make/core/envsetup.mk?
|
// TODO(asmundak): maybe read it from build/make/core/envsetup.mk?
|
||||||
{"TARGET_COPY_OUT_SYSTEM", "system"},
|
{"TARGET_COPY_OUT_SYSTEM", "system"},
|
||||||
{"TARGET_COPY_OUT_SYSTEM_OTHER", "system_other"},
|
{"TARGET_COPY_OUT_SYSTEM_OTHER", "system_other"},
|
||||||
|
@ -428,6 +437,7 @@ func newParseContext(ss *StarlarkScript, nodes []mkparser.Node) *parseContext {
|
||||||
moduleNameCount: make(map[string]int),
|
moduleNameCount: make(map[string]int),
|
||||||
builtinMakeVars: map[string]starlarkExpr{},
|
builtinMakeVars: map[string]starlarkExpr{},
|
||||||
variables: make(map[string]variable),
|
variables: make(map[string]variable),
|
||||||
|
dependentModules: make(map[string]*moduleInfo),
|
||||||
}
|
}
|
||||||
ctx.pushVarAssignments()
|
ctx.pushVarAssignments()
|
||||||
for _, item := range predefined {
|
for _, item := range predefined {
|
||||||
|
@ -619,16 +629,12 @@ func (ctx *parseContext) buildConcatExpr(a *mkparser.Assignment) *concatExpr {
|
||||||
return xConcat
|
return xConcat
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *parseContext) newInheritedModule(v mkparser.Node, pathExpr starlarkExpr, loadAlways bool) *inheritedModule {
|
func (ctx *parseContext) newDependentModule(path string, optional bool) *moduleInfo {
|
||||||
var path string
|
modulePath := ctx.loadedModulePath(path)
|
||||||
x, _ := pathExpr.eval(ctx.builtinMakeVars)
|
if mi, ok := ctx.dependentModules[modulePath]; ok {
|
||||||
s, ok := x.(*stringLiteralExpr)
|
mi.optional = mi.optional || optional
|
||||||
if !ok {
|
return mi
|
||||||
ctx.errorf(v, "inherit-product/include argument is too complex")
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
path = s.literal
|
|
||||||
moduleName := moduleNameForFile(path)
|
moduleName := moduleNameForFile(path)
|
||||||
moduleLocalName := "_" + moduleName
|
moduleLocalName := "_" + moduleName
|
||||||
n, found := ctx.moduleNameCount[moduleName]
|
n, found := ctx.moduleNameCount[moduleName]
|
||||||
|
@ -636,27 +642,124 @@ func (ctx *parseContext) newInheritedModule(v mkparser.Node, pathExpr starlarkEx
|
||||||
moduleLocalName += fmt.Sprintf("%d", n)
|
moduleLocalName += fmt.Sprintf("%d", n)
|
||||||
}
|
}
|
||||||
ctx.moduleNameCount[moduleName] = n + 1
|
ctx.moduleNameCount[moduleName] = n + 1
|
||||||
ln := &inheritedModule{
|
mi := &moduleInfo{
|
||||||
path: ctx.loadedModulePath(path),
|
path: modulePath,
|
||||||
originalPath: path,
|
originalPath: path,
|
||||||
moduleName: moduleName,
|
|
||||||
moduleLocalName: moduleLocalName,
|
moduleLocalName: moduleLocalName,
|
||||||
loadAlways: loadAlways,
|
optional: optional,
|
||||||
}
|
}
|
||||||
ctx.script.inherited = append(ctx.script.inherited, ln)
|
ctx.dependentModules[modulePath] = mi
|
||||||
return ln
|
ctx.script.inherited = append(ctx.script.inherited, mi)
|
||||||
|
return mi
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *parseContext) handleSubConfig(
|
||||||
|
v mkparser.Node, pathExpr starlarkExpr, loadAlways bool, processModule func(inheritedModule)) {
|
||||||
|
pathExpr, _ = pathExpr.eval(ctx.builtinMakeVars)
|
||||||
|
|
||||||
|
// In a simple case, the name of a module to inherit/include is known statically.
|
||||||
|
if path, ok := maybeString(pathExpr); ok {
|
||||||
|
if strings.Contains(path, "*") {
|
||||||
|
if paths, err := fs.Glob(ctx.script.sourceFS, path); err == nil {
|
||||||
|
for _, p := range paths {
|
||||||
|
processModule(inheritedStaticModule{ctx.newDependentModule(p, !loadAlways), loadAlways})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.errorf(v, "cannot glob wildcard argument")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
processModule(inheritedStaticModule{ctx.newDependentModule(path, !loadAlways), loadAlways})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
varPath, ok := pathExpr.(*interpolateExpr)
|
||||||
|
if !ok {
|
||||||
|
ctx.errorf(v, "inherit-product/include argument is too complex")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pathPattern := []string{varPath.chunks[0]}
|
||||||
|
for _, chunk := range varPath.chunks[1:] {
|
||||||
|
if chunk != "" {
|
||||||
|
pathPattern = append(pathPattern, chunk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pathPattern[0] != "" {
|
||||||
|
matchingPaths = ctx.findMatchingPaths(pathPattern)
|
||||||
|
} else {
|
||||||
|
// Heuristics -- if pattern starts from top, restrict it to the directories where
|
||||||
|
// we know inherit-product uses dynamically calculated path.
|
||||||
|
for _, t := range []string{"vendor/qcom", "vendor/google_devices"} {
|
||||||
|
pathPattern[0] = t
|
||||||
|
matchingPaths = append(matchingPaths, ctx.findMatchingPaths(pathPattern)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Safeguard against $(call inherit-product,$(PRODUCT_PATH))
|
||||||
|
const maxMatchingFiles = 100
|
||||||
|
if len(matchingPaths) > maxMatchingFiles {
|
||||||
|
ctx.errorf(v, "there are >%d files matching the pattern, please rewrite it", maxMatchingFiles)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res := inheritedDynamicModule{*varPath, []*moduleInfo{}, loadAlways}
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
processModule(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *parseContext) findMatchingPaths(pattern []string) []string {
|
||||||
|
files := ctx.script.makefileFinder.Find(ctx.script.topDir)
|
||||||
|
if len(pattern) == 0 {
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create regular expression from the pattern
|
||||||
|
s_regexp := "^" + regexp.QuoteMeta(pattern[0])
|
||||||
|
for _, s := range pattern[1:] {
|
||||||
|
s_regexp += ".*" + regexp.QuoteMeta(s)
|
||||||
|
}
|
||||||
|
s_regexp += "$"
|
||||||
|
rex := regexp.MustCompile(s_regexp)
|
||||||
|
|
||||||
|
// Now match
|
||||||
|
var res []string
|
||||||
|
for _, p := range files {
|
||||||
|
if rex.MatchString(p) {
|
||||||
|
res = append(res, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *parseContext) handleInheritModule(v mkparser.Node, pathExpr starlarkExpr, loadAlways bool) {
|
func (ctx *parseContext) handleInheritModule(v mkparser.Node, pathExpr starlarkExpr, loadAlways bool) {
|
||||||
if im := ctx.newInheritedModule(v, pathExpr, loadAlways); im != nil {
|
ctx.handleSubConfig(v, pathExpr, loadAlways, func(im inheritedModule) {
|
||||||
ctx.receiver.newNode(&inheritNode{im})
|
ctx.receiver.newNode(&inheritNode{im})
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *parseContext) handleInclude(v mkparser.Node, pathExpr starlarkExpr, loadAlways bool) {
|
func (ctx *parseContext) handleInclude(v mkparser.Node, pathExpr starlarkExpr, loadAlways bool) {
|
||||||
if ln := ctx.newInheritedModule(v, pathExpr, loadAlways); ln != nil {
|
ctx.handleSubConfig(v, pathExpr, loadAlways, func(im inheritedModule) {
|
||||||
ctx.receiver.newNode(&includeNode{ln})
|
ctx.receiver.newNode(&includeNode{im})
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *parseContext) handleVariable(v *mkparser.Variable) {
|
func (ctx *parseContext) handleVariable(v *mkparser.Variable) {
|
||||||
|
@ -1091,10 +1194,11 @@ func (ctx *parseContext) parseReference(node mkparser.Node, ref *mkparser.MakeSt
|
||||||
}
|
}
|
||||||
expr.name = words[0].Dump()
|
expr.name = words[0].Dump()
|
||||||
if len(words) < 2 {
|
if len(words) < 2 {
|
||||||
return expr
|
args = &mkparser.MakeString{}
|
||||||
}
|
} else {
|
||||||
args = words[1]
|
args = words[1]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if kf, found := knownFunctions[expr.name]; found {
|
if kf, found := knownFunctions[expr.name]; found {
|
||||||
expr.returnType = kf.returnType
|
expr.returnType = kf.returnType
|
||||||
} else {
|
} else {
|
||||||
|
@ -1103,6 +1207,8 @@ func (ctx *parseContext) parseReference(node mkparser.Node, ref *mkparser.MakeSt
|
||||||
switch expr.name {
|
switch expr.name {
|
||||||
case "word":
|
case "word":
|
||||||
return ctx.parseWordFunc(node, args)
|
return ctx.parseWordFunc(node, args)
|
||||||
|
case "my-dir":
|
||||||
|
return &variableRefExpr{ctx.addVariable("LOCAL_PATH"), true}
|
||||||
case "subst", "patsubst":
|
case "subst", "patsubst":
|
||||||
return ctx.parseSubstFunc(node, expr.name, args)
|
return ctx.parseSubstFunc(node, expr.name, args)
|
||||||
default:
|
default:
|
||||||
|
@ -1285,6 +1391,7 @@ func (ss *StarlarkScript) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *StarlarkScript) SubConfigFiles() []string {
|
func (ss *StarlarkScript) SubConfigFiles() []string {
|
||||||
|
|
||||||
var subs []string
|
var subs []string
|
||||||
for _, src := range ss.inherited {
|
for _, src := range ss.inherited {
|
||||||
subs = append(subs, src.originalPath)
|
subs = append(subs, src.originalPath)
|
||||||
|
@ -1322,6 +1429,8 @@ func Convert(req Request) (*StarlarkScript, error) {
|
||||||
topDir: req.RootDir,
|
topDir: req.RootDir,
|
||||||
traceCalls: req.TraceCalls,
|
traceCalls: req.TraceCalls,
|
||||||
warnPartialSuccess: req.WarnPartialSuccess,
|
warnPartialSuccess: req.WarnPartialSuccess,
|
||||||
|
sourceFS: req.SourceFS,
|
||||||
|
makefileFinder: req.MakefileFinder,
|
||||||
}
|
}
|
||||||
ctx := newParseContext(starScript, nodes)
|
ctx := newParseContext(starScript, nodes)
|
||||||
ctx.outputSuffix = req.OutputSuffix
|
ctx.outputSuffix = req.OutputSuffix
|
||||||
|
|
|
@ -16,6 +16,8 @@ package mk2rbc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"io/fs"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
@ -100,10 +102,13 @@ def init(g, handle):
|
||||||
desc: "Unknown function",
|
desc: "Unknown function",
|
||||||
mkname: "product.mk",
|
mkname: "product.mk",
|
||||||
in: `
|
in: `
|
||||||
PRODUCT_NAME := $(call foo, bar)
|
PRODUCT_NAME := $(call foo1, bar)
|
||||||
|
PRODUCT_NAME := $(call foo0)
|
||||||
`,
|
`,
|
||||||
expected: `# MK2RBC TRANSLATION ERROR: cannot handle invoking foo
|
expected: `# MK2RBC TRANSLATION ERROR: cannot handle invoking foo1
|
||||||
# PRODUCT_NAME := $(call foo, bar)
|
# PRODUCT_NAME := $(call foo1, bar)
|
||||||
|
# MK2RBC TRANSLATION ERROR: cannot handle invoking foo0
|
||||||
|
# PRODUCT_NAME := $(call foo0)
|
||||||
load("//build/make/core:product_config.rbc", "rblf")
|
load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
def init(g, handle):
|
def init(g, handle):
|
||||||
|
@ -130,7 +135,7 @@ def init(g, handle):
|
||||||
rblf.inherit(handle, "part", _part_init)
|
rblf.inherit(handle, "part", _part_init)
|
||||||
else:
|
else:
|
||||||
# Comment
|
# Comment
|
||||||
rblf.inherit(handle, "./part", _part_init)
|
rblf.inherit(handle, "part", _part_init)
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -144,7 +149,7 @@ load(":part.star|init", _part_init = "init")
|
||||||
|
|
||||||
def init(g, handle):
|
def init(g, handle):
|
||||||
cfg = rblf.cfg(handle)
|
cfg = rblf.cfg(handle)
|
||||||
if _part_init != None:
|
if _part_init:
|
||||||
rblf.inherit(handle, "part", _part_init)
|
rblf.inherit(handle, "part", _part_init)
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
@ -160,7 +165,7 @@ else
|
||||||
endif
|
endif
|
||||||
`,
|
`,
|
||||||
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
load(":part.star", _part_init = "init")
|
load(":part.star|init", _part_init = "init")
|
||||||
|
|
||||||
def init(g, handle):
|
def init(g, handle):
|
||||||
cfg = rblf.cfg(handle)
|
cfg = rblf.cfg(handle)
|
||||||
|
@ -176,8 +181,7 @@ def init(g, handle):
|
||||||
desc: "Synonymous inherited configurations",
|
desc: "Synonymous inherited configurations",
|
||||||
mkname: "path/product.mk",
|
mkname: "path/product.mk",
|
||||||
in: `
|
in: `
|
||||||
$(call inherit-product, foo/font.mk)
|
$(call inherit-product, */font.mk)
|
||||||
$(call inherit-product, bar/font.mk)
|
|
||||||
`,
|
`,
|
||||||
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
load("//foo:font.star", _font_init = "init")
|
load("//foo:font.star", _font_init = "init")
|
||||||
|
@ -254,6 +258,8 @@ def init(g, handle):
|
||||||
in: `
|
in: `
|
||||||
ifdef PRODUCT_NAME
|
ifdef PRODUCT_NAME
|
||||||
# Comment
|
# Comment
|
||||||
|
else
|
||||||
|
TARGET_COPY_OUT_VENDOR := foo
|
||||||
endif
|
endif
|
||||||
`,
|
`,
|
||||||
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
@ -263,6 +269,10 @@ def init(g, handle):
|
||||||
if g.get("PRODUCT_NAME") != None:
|
if g.get("PRODUCT_NAME") != None:
|
||||||
# Comment
|
# Comment
|
||||||
pass
|
pass
|
||||||
|
else:
|
||||||
|
# MK2RBC TRANSLATION ERROR: cannot set predefined variable TARGET_COPY_OUT_VENDOR to "foo", its value should be "||VENDOR-PATH-PH||"
|
||||||
|
pass
|
||||||
|
rblf.warning("product.mk", "partially successful conversion")
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -840,6 +850,30 @@ def init(g, handle):
|
||||||
g["V2"] = g.get("PRODUCT_ADB_KEYS", "")
|
g["V2"] = g.get("PRODUCT_ADB_KEYS", "")
|
||||||
g["PRODUCT_ADB_KEYS"] = "foo"
|
g["PRODUCT_ADB_KEYS"] = "foo"
|
||||||
g["V3"] = g["PRODUCT_ADB_KEYS"]
|
g["V3"] = g["PRODUCT_ADB_KEYS"]
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Dynamic inherit path",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
MY_PATH=foo
|
||||||
|
$(call inherit-product,vendor/$(MY_PATH)/cfg.mk)
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
load("//vendor/foo1:cfg.star|init", _cfg_init = "init")
|
||||||
|
load("//vendor/bar/baz:cfg.star|init", _cfg1_init = "init")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
g["MY_PATH"] = "foo"
|
||||||
|
_entry = {
|
||||||
|
"vendor/foo1/cfg.mk": ("_cfg", _cfg_init),
|
||||||
|
"vendor/bar/baz/cfg.mk": ("_cfg1", _cfg1_init),
|
||||||
|
}.get("vendor/%s/cfg.mk" % g["MY_PATH"])
|
||||||
|
(_varmod, _varmod_init) = _entry if _entry else (None, None)
|
||||||
|
if not _varmod_init:
|
||||||
|
rblf.mkerror("cannot")
|
||||||
|
rblf.inherit(handle, _varmod, _varmod_init)
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -865,10 +899,47 @@ var known_variables = []struct {
|
||||||
{"PLATFORM_LIST", VarClassSoong, starlarkTypeList}, // TODO(asmundak): make it local instead of soong
|
{"PLATFORM_LIST", VarClassSoong, starlarkTypeList}, // TODO(asmundak): make it local instead of soong
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type testMakefileFinder struct {
|
||||||
|
fs fs.FS
|
||||||
|
root string
|
||||||
|
files []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testMakefileFinder) Find(root string) []string {
|
||||||
|
if t.files != nil || root == t.root {
|
||||||
|
return t.files
|
||||||
|
}
|
||||||
|
t.files = make([]string, 0)
|
||||||
|
fs.WalkDir(t.fs, root, func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if d.IsDir() {
|
||||||
|
base := filepath.Base(path)
|
||||||
|
if base[0] == '.' && len(base) > 1 {
|
||||||
|
return fs.SkipDir
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(path, ".mk") {
|
||||||
|
t.files = append(t.files, path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return t.files
|
||||||
|
}
|
||||||
|
|
||||||
func TestGood(t *testing.T) {
|
func TestGood(t *testing.T) {
|
||||||
for _, v := range known_variables {
|
for _, v := range known_variables {
|
||||||
KnownVariables.NewVariable(v.name, v.class, v.starlarkType)
|
KnownVariables.NewVariable(v.name, v.class, v.starlarkType)
|
||||||
}
|
}
|
||||||
|
fs := NewFindMockFS([]string{
|
||||||
|
"vendor/foo1/cfg.mk",
|
||||||
|
"vendor/bar/baz/cfg.mk",
|
||||||
|
"part.mk",
|
||||||
|
"foo/font.mk",
|
||||||
|
"bar/font.mk",
|
||||||
|
})
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
t.Run(test.desc,
|
t.Run(test.desc,
|
||||||
func(t *testing.T) {
|
func(t *testing.T) {
|
||||||
|
@ -878,6 +949,8 @@ func TestGood(t *testing.T) {
|
||||||
RootDir: ".",
|
RootDir: ".",
|
||||||
OutputSuffix: ".star",
|
OutputSuffix: ".star",
|
||||||
WarnPartialSuccess: true,
|
WarnPartialSuccess: true,
|
||||||
|
SourceFS: fs,
|
||||||
|
MakefileFinder: &testMakefileFinder{fs: fs},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
|
|
105
mk2rbc/node.go
105
mk2rbc/node.go
|
@ -42,24 +42,85 @@ func (c *commentNode) emit(gctx *generationContext) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type inheritedModule struct {
|
type moduleInfo struct {
|
||||||
path string // Converted Starlark file path
|
path string // Converted Starlark file path
|
||||||
originalPath string // Makefile file path
|
originalPath string // Makefile file path
|
||||||
moduleName string
|
|
||||||
moduleLocalName string
|
moduleLocalName string
|
||||||
loadAlways bool
|
optional bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (im inheritedModule) name() string {
|
func (im moduleInfo) entryName() string {
|
||||||
return MakePath2ModuleName(im.originalPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (im inheritedModule) entryName() string {
|
|
||||||
return im.moduleLocalName + "_init"
|
return im.moduleLocalName + "_init"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type inheritedModule interface {
|
||||||
|
name() string
|
||||||
|
entryName() string
|
||||||
|
emitSelect(gctx *generationContext)
|
||||||
|
isLoadAlways() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type inheritedStaticModule struct {
|
||||||
|
*moduleInfo
|
||||||
|
loadAlways bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (im inheritedStaticModule) name() string {
|
||||||
|
return fmt.Sprintf("%q", MakePath2ModuleName(im.originalPath))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (im inheritedStaticModule) emitSelect(_ *generationContext) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (im inheritedStaticModule) isLoadAlways() bool {
|
||||||
|
return im.loadAlways
|
||||||
|
}
|
||||||
|
|
||||||
|
type inheritedDynamicModule struct {
|
||||||
|
path interpolateExpr
|
||||||
|
candidateModules []*moduleInfo
|
||||||
|
loadAlways bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i inheritedDynamicModule) name() string {
|
||||||
|
return "_varmod"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i inheritedDynamicModule) entryName() string {
|
||||||
|
return i.name() + "_init"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i inheritedDynamicModule) emitSelect(gctx *generationContext) {
|
||||||
|
gctx.newLine()
|
||||||
|
gctx.writef("_entry = {")
|
||||||
|
gctx.indentLevel++
|
||||||
|
for _, mi := range i.candidateModules {
|
||||||
|
gctx.newLine()
|
||||||
|
gctx.writef(`"%s": (%q, %s),`, mi.originalPath, mi.moduleLocalName, mi.entryName())
|
||||||
|
}
|
||||||
|
gctx.indentLevel--
|
||||||
|
gctx.newLine()
|
||||||
|
gctx.write("}.get(")
|
||||||
|
i.path.emit(gctx)
|
||||||
|
gctx.write(")")
|
||||||
|
gctx.newLine()
|
||||||
|
gctx.writef("(%s, %s) = _entry if _entry else (None, None)", i.name(), i.entryName())
|
||||||
|
if i.loadAlways {
|
||||||
|
gctx.newLine()
|
||||||
|
gctx.writef("if not %s:", i.entryName())
|
||||||
|
gctx.indentLevel++
|
||||||
|
gctx.newLine()
|
||||||
|
gctx.write(`rblf.mkerror("cannot")`)
|
||||||
|
gctx.indentLevel--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i inheritedDynamicModule) isLoadAlways() bool {
|
||||||
|
return i.loadAlways
|
||||||
|
}
|
||||||
|
|
||||||
type inheritNode struct {
|
type inheritNode struct {
|
||||||
*inheritedModule
|
module inheritedModule
|
||||||
}
|
}
|
||||||
|
|
||||||
func (inn *inheritNode) emit(gctx *generationContext) {
|
func (inn *inheritNode) emit(gctx *generationContext) {
|
||||||
|
@ -68,32 +129,40 @@ func (inn *inheritNode) emit(gctx *generationContext) {
|
||||||
// Conditional case:
|
// Conditional case:
|
||||||
// if <module>_init != None:
|
// if <module>_init != None:
|
||||||
// same as above
|
// same as above
|
||||||
|
inn.module.emitSelect(gctx)
|
||||||
|
|
||||||
|
name := inn.module.name()
|
||||||
|
entry := inn.module.entryName()
|
||||||
gctx.newLine()
|
gctx.newLine()
|
||||||
if inn.loadAlways {
|
if inn.module.isLoadAlways() {
|
||||||
gctx.writef("%s(handle, %q, %s)", cfnInherit, inn.name(), inn.entryName())
|
gctx.writef("%s(handle, %s, %s)", cfnInherit, name, entry)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
gctx.writef("if %s != None:", inn.entryName())
|
|
||||||
|
gctx.writef("if %s:", entry)
|
||||||
gctx.indentLevel++
|
gctx.indentLevel++
|
||||||
gctx.newLine()
|
gctx.newLine()
|
||||||
gctx.writef("%s(handle, %q, %s)", cfnInherit, inn.name(), inn.entryName())
|
gctx.writef("%s(handle, %s, %s)", cfnInherit, name, entry)
|
||||||
gctx.indentLevel--
|
gctx.indentLevel--
|
||||||
}
|
}
|
||||||
|
|
||||||
type includeNode struct {
|
type includeNode struct {
|
||||||
*inheritedModule
|
module inheritedModule
|
||||||
}
|
}
|
||||||
|
|
||||||
func (inn *includeNode) emit(gctx *generationContext) {
|
func (inn *includeNode) emit(gctx *generationContext) {
|
||||||
|
inn.module.emitSelect(gctx)
|
||||||
|
entry := inn.module.entryName()
|
||||||
gctx.newLine()
|
gctx.newLine()
|
||||||
if inn.loadAlways {
|
if inn.module.isLoadAlways() {
|
||||||
gctx.writef("%s(g, handle)", inn.entryName())
|
gctx.writef("%s(g, handle)", entry)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
gctx.writef("if %s != None:", inn.entryName())
|
|
||||||
|
gctx.writef("if %s != None:", entry)
|
||||||
gctx.indentLevel++
|
gctx.indentLevel++
|
||||||
gctx.newLine()
|
gctx.newLine()
|
||||||
gctx.writef("%s(g, handle)", inn.entryName())
|
gctx.writef("%s(g, handle)", entry)
|
||||||
gctx.indentLevel--
|
gctx.indentLevel--
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,3 +58,8 @@ func (s ScopeBase) Call(_ string, _ []string) []string {
|
||||||
func (s ScopeBase) SetFunc(_ string, _ func([]string) []string) {
|
func (s ScopeBase) SetFunc(_ string, _ func([]string) []string) {
|
||||||
panic("implement me")
|
panic("implement me")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used to find all makefiles in the source tree
|
||||||
|
type MakefileFinder interface {
|
||||||
|
Find(root string) []string
|
||||||
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@ package mk2rbc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -222,15 +221,18 @@ func (pv predefinedVariable) emitGet(gctx *generationContext, _ bool) {
|
||||||
pv.value.emit(gctx)
|
pv.value.emit(gctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pv predefinedVariable) emitSet(_ *generationContext, asgn *assignmentNode) {
|
func (pv predefinedVariable) emitSet(gctx *generationContext, asgn *assignmentNode) {
|
||||||
if expectedValue, ok1 := maybeString(pv.value); ok1 {
|
if expectedValue, ok1 := maybeString(pv.value); ok1 {
|
||||||
actualValue, ok2 := maybeString(asgn.value)
|
actualValue, ok2 := maybeString(asgn.value)
|
||||||
if ok2 {
|
if ok2 {
|
||||||
if actualValue == expectedValue {
|
if actualValue == expectedValue {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Fprintf(os.Stderr, "cannot set predefined variable %s to %q, its value should be %q",
|
gctx.writef("# MK2RBC TRANSLATION ERROR: cannot set predefined variable %s to %q, its value should be %q",
|
||||||
pv.name(), actualValue, expectedValue)
|
pv.name(), actualValue, expectedValue)
|
||||||
|
gctx.newLine()
|
||||||
|
gctx.write("pass")
|
||||||
|
gctx.starScript.hasErrors = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue