Convert BUILD_PREBUILT with LOCAL_MODULE_CLASS=ETC to prebuilt_etc

The conversion is a two-step process: first, when processing
BUILT_PREBUILT, convert LOCAL_SOURCE_PATH to a variable reference+fixed
subpath path in the blueprint AST. Then, set various boolean flags
depending on variable being referenced. androidmk_test.go has a test for
each handled case.

Change-Id: Iabd18d5f8645ca7077536863cd6640df5b24d24a
Fixes: 122906526
Test: treehugger
This commit is contained in:
Sasha Smundak 2019-01-22 10:33:49 -08:00
parent 2f4789d612
commit 7054764304
4 changed files with 468 additions and 1 deletions

View File

@ -56,6 +56,7 @@ var rewriteProperties = map[string](func(variableAssignmentContext) error){
"LOCAL_CFLAGS": cflags,
"LOCAL_UNINSTALLABLE_MODULE": invert("installable"),
"LOCAL_PROGUARD_ENABLED": proguardEnabled,
"LOCAL_MODULE_PATH": prebuiltModulePath,
// composite functions
"LOCAL_MODULE_TAGS": includeVariableIf(bpVariable{"tags", bpparser.ListType}, not(valueDumpEquals("optional"))),
@ -519,6 +520,55 @@ func prebuiltClass(ctx variableAssignmentContext) error {
return nil
}
func makeBlueprintStringAssignment(file *bpFile, prefix string, suffix string, value string) error {
val, err := makeVariableToBlueprint(file, mkparser.SimpleMakeString(value, mkparser.NoPos), bpparser.StringType)
if err == nil {
err = setVariable(file, false, prefix, suffix, val, true)
}
return err
}
// If variable is a literal variable name, return the name, otherwise return ""
func varLiteralName(variable mkparser.Variable) string {
if len(variable.Name.Variables) == 0 {
return variable.Name.Strings[0]
}
return ""
}
func prebuiltModulePath(ctx variableAssignmentContext) error {
// Cannot handle appending
if ctx.append {
return fmt.Errorf("Cannot handle appending to LOCAL_MODULE_PATH")
}
// Analyze value in order to set the correct values for the 'device_specific',
// 'product_specific', 'product_services_specific' 'vendor'/'soc_specific',
// 'product_services_specific' attribute. Two cases are allowed:
// $(VAR)/<literal-value>
// $(PRODUCT_OUT)/$(TARGET_COPY_OUT_VENDOR)/<literal-value>
// The last case is equivalent to $(TARGET_OUT_VENDOR)/<literal-value>
// Map the variable name if present to `local_module_path_var`
// Map literal-path to local_module_path_fixed
varname := ""
fixed := ""
val := ctx.mkvalue
if len(val.Variables) == 1 && varLiteralName(val.Variables[0]) != "" && len(val.Strings) == 2 && val.Strings[0] == "" {
fixed = val.Strings[1]
varname = val.Variables[0].Name.Strings[0]
} else if len(val.Variables) == 2 && varLiteralName(val.Variables[0]) == "PRODUCT_OUT" && varLiteralName(val.Variables[1]) == "TARGET_COPY_OUT_VENDOR" &&
len(val.Strings) == 3 && val.Strings[0] == "" && val.Strings[1] == "/" {
fixed = val.Strings[2]
varname = "TARGET_OUT_VENDOR"
} else {
return fmt.Errorf("LOCAL_MODULE_PATH value should start with $(<some-varaible>)/ or $(PRODUCT_OUT)/$(TARGET_COPY_VENDOR)/")
}
err := makeBlueprintStringAssignment(ctx.file, "local_module_path", "var", varname)
if err == nil && fixed != "" {
err = makeBlueprintStringAssignment(ctx.file, "local_module_path", "fixed", fixed)
}
return err
}
func ldflags(ctx variableAssignmentContext) error {
val, err := makeVariableToBlueprint(ctx.file, ctx.mkvalue, bpparser.ListType)
if err != nil {
@ -816,6 +866,7 @@ var prebuiltTypes = map[string]string{
"STATIC_LIBRARIES": "cc_prebuilt_library_static",
"EXECUTABLES": "cc_prebuilt_binary",
"JAVA_LIBRARIES": "java_import",
"ETC": "prebuilt_etc",
}
var soongModuleTypes = map[string]bool{}
@ -834,7 +885,6 @@ func androidScope() mkparser.Scope {
globalScope.SetFunc("first-makefiles-under", includeIgnored)
globalScope.SetFunc("all-named-subdir-makefiles", includeIgnored)
globalScope.SetFunc("all-subdir-makefiles", includeIgnored)
for k, v := range moduleTypes {
globalScope.Set(k, v)
soongModuleTypes[v] = true

View File

@ -821,6 +821,210 @@ java_library {
name: "foolib",
plugins: ["bar"],
}
`,
},
{
desc: "prebuilt_etc_TARGET_OUT_ETC",
in: `
include $(CLEAR_VARS)
LOCAL_MODULE := etc.test1
LOCAL_SRC_FILES := mymod
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/foo/bar
include $(BUILD_PREBUILT)
`,
expected: `
prebuilt_etc {
name: "etc.test1",
src: "mymod",
sub_dir: "foo/bar",
}
`,
},
{
desc: "prebuilt_etc_PRODUCT_OUT/system/etc",
in: `
include $(CLEAR_VARS)
LOCAL_MODULE := etc.test1
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(PRODUCT_OUT)/system/etc/foo/bar
LOCAL_SRC_FILES := $(LOCAL_MODULE)
include $(BUILD_PREBUILT)
`,
expected: `
prebuilt_etc {
name: "etc.test1",
src: "etc.test1",
sub_dir: "foo/bar",
}
`,
},
{
desc: "prebuilt_etc_TARGET_OUT_ODM/etc",
in: `
include $(CLEAR_VARS)
LOCAL_MODULE := etc.test1
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(TARGET_OUT_ODM)/etc/foo/bar
include $(BUILD_PREBUILT)
`,
expected: `
prebuilt_etc {
name: "etc.test1",
sub_dir: "foo/bar",
device_specific: true,
}
`,
},
{
desc: "prebuilt_etc_TARGET_OUT_PRODUCT/etc",
in: `
include $(CLEAR_VARS)
LOCAL_MODULE := etc.test1
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(TARGET_OUT_PRODUCT)/etc/foo/bar
include $(BUILD_PREBUILT)
`,
expected: `
prebuilt_etc {
name: "etc.test1",
sub_dir: "foo/bar",
product_specific: true,
}
`,
},
{
desc: "prebuilt_etc_TARGET_OUT_PRODUCT_ETC",
in: `
include $(CLEAR_VARS)
LOCAL_MODULE := etc.test1
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(TARGET_OUT_PRODUCT_ETC)/foo/bar
include $(BUILD_PREBUILT)
`,
expected: `
prebuilt_etc {
name: "etc.test1",
sub_dir: "foo/bar",
product_specific: true,
}
`,
},
{
desc: "prebuilt_etc_TARGET_OUT_PRODUCT_SERVICES/etc",
in: `
include $(CLEAR_VARS)
LOCAL_MODULE := etc.test1
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(TARGET_OUT_PRODUCT_SERVICES)/etc/foo/bar
include $(BUILD_PREBUILT)
`,
expected: `
prebuilt_etc {
name: "etc.test1",
sub_dir: "foo/bar",
product_services_specific: true,
}
`,
},
{
desc: "prebuilt_etc_TARGET_OUT_PRODUCT_SERVICES_ETC",
in: `
include $(CLEAR_VARS)
LOCAL_MODULE := etc.test1
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(TARGET_OUT_PRODUCT_SERVICES_ETC)/foo/bar
include $(BUILD_PREBUILT)
`,
expected: `
prebuilt_etc {
name: "etc.test1",
sub_dir: "foo/bar",
product_services_specific: true,
}
`,
},
{
desc: "prebuilt_etc_TARGET_OUT_VENDOR/etc",
in: `
include $(CLEAR_VARS)
LOCAL_MODULE := etc.test1
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR)/etc/foo/bar
include $(BUILD_PREBUILT)
`,
expected: `
prebuilt_etc {
name: "etc.test1",
sub_dir: "foo/bar",
proprietary: true,
}
`,
},
{
desc: "prebuilt_etc_PRODUCT_OUT/TARGET_COPY_OUT_VENDOR/etc",
in: `
include $(CLEAR_VARS)
LOCAL_MODULE := etc.test1
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(PRODUCT_OUT)/$(TARGET_COPY_OUT_VENDOR)/etc/foo/bar
include $(BUILD_PREBUILT)
`,
expected: `
prebuilt_etc {
name: "etc.test1",
sub_dir: "foo/bar",
proprietary: true,
}
`,
},
{
desc: "prebuilt_etc_TARGET_OUT_VENDOR_ETC",
in: `
include $(CLEAR_VARS)
LOCAL_MODULE := etc.test1
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR_ETC)/foo/bar
include $(BUILD_PREBUILT)
`,
expected: `
prebuilt_etc {
name: "etc.test1",
sub_dir: "foo/bar",
proprietary: true,
}
`,
},
{
desc: "prebuilt_etc_TARGET_RECOVERY_ROOT_OUT/system/etc",
in: `
include $(CLEAR_VARS)
LOCAL_MODULE := etc.test1
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/etc/foo/bar
include $(BUILD_PREBUILT)
`,
expected: `
prebuilt_etc {
name: "etc.test1",
sub_dir: "foo/bar",
recovery: true,
}
`,
},

View File

@ -82,6 +82,10 @@ var fixSteps = []fixStep{
name: "rewriteJavaStaticLibs",
fix: rewriteJavaStaticLibs,
},
{
name: "rewritePrebuiltEtc",
fix: rewriteAndroidmkPrebuiltEtc,
},
{
name: "mergeMatchingModuleProperties",
fix: runPatchListMod(mergeMatchingModuleProperties),
@ -407,6 +411,156 @@ func rewriteAndroidmkJavaLibs(f *Fixer) error {
return nil
}
// Helper function to get the value of a string-valued property in a given compound property.
func getStringProperty(prop *parser.Property, fieldName string) string {
if propsAsMap, ok := prop.Value.(*parser.Map); ok {
for _, propField := range propsAsMap.Properties {
if fieldName == propField.Name {
if propFieldAsString, ok := propField.Value.(*parser.String); ok {
return propFieldAsString.Value
} else {
return ""
}
}
}
}
return ""
}
// Create sub_dir: attribute for the given path
func makePrebuiltEtcDestination(mod *parser.Module, path string) {
mod.Properties = append(mod.Properties, &parser.Property{
Name: "sub_dir",
Value: &parser.String{Value: path},
})
}
// Set the value of the given attribute to the error message
func indicateAttributeError(mod *parser.Module, attributeName string, format string, a ...interface{}) error {
msg := fmt.Sprintf(format, a...)
mod.Properties = append(mod.Properties, &parser.Property{
Name: attributeName,
Value: &parser.String{Value: "ERROR: " + msg},
})
return errors.New(msg)
}
// If a variable is LOCAL_MODULE, get its value from the 'name' attribute.
// This handles the statement
// LOCAL_SRC_FILES := $(LOCAL_MODULE)
// which occurs often.
func resolveLocalModule(mod *parser.Module, val parser.Expression) parser.Expression {
if varLocalName, ok := val.(*parser.Variable); ok {
if varLocalName.Name == "LOCAL_MODULE" {
if v, ok := getLiteralStringProperty(mod, "name"); ok {
return v
}
}
}
return val
}
// A prefix to strip before setting 'filename' attribute and an array of boolean attributes to set.
type filenamePrefixToFlags struct {
prefix string
flags []string
}
var localModulePathRewrite = map[string][]filenamePrefixToFlags{
"HOST_OUT": {{prefix: "/etc"}},
"PRODUCT_OUT": {{prefix: "/system/etc"}, {prefix: "/vendor/etc", flags: []string{"proprietary"}}},
"TARGET_OUT": {{prefix: "/etc"}},
"TARGET_OUT_ETC": {{prefix: ""}},
"TARGET_OUT_PRODUCT": {{prefix: "/etc", flags: []string{"product_specific"}}},
"TARGET_OUT_PRODUCT_ETC": {{prefix: "", flags: []string{"product_specific"}}},
"TARGET_OUT_ODM": {{prefix: "/etc", flags: []string{"device_specific"}}},
"TARGET_OUT_PRODUCT_SERVICES": {{prefix: "/etc", flags: []string{"product_services_specific"}}},
"TARGET_OUT_PRODUCT_SERVICES_ETC": {{prefix: "", flags: []string{"product_services_specific"}}},
"TARGET_OUT_VENDOR": {{prefix: "/etc", flags: []string{"proprietary"}}},
"TARGET_OUT_VENDOR_ETC": {{prefix: "", flags: []string{"proprietary"}}},
"TARGET_RECOVERY_ROOT_OUT": {{prefix: "/system/etc", flags: []string{"recovery"}}},
}
// rewriteAndroidPrebuiltEtc fixes prebuilt_etc rule
func rewriteAndroidmkPrebuiltEtc(f *Fixer) error {
for _, def := range f.tree.Defs {
mod, ok := def.(*parser.Module)
if !ok {
continue
}
if mod.Type != "prebuilt_etc" && mod.Type != "prebuilt_etc_host" {
continue
}
// The rewriter converts LOCAL_SRC_FILES to `srcs` attribute. Convert
// it to 'src' attribute (which is where the file is installed). If the
// value 'srcs' is a list, we can convert it only if it contains a single
// element.
if srcs, ok := mod.GetProperty("srcs"); ok {
if srcList, ok := srcs.Value.(*parser.List); ok {
removeProperty(mod, "srcs")
if len(srcList.Values) == 1 {
mod.Properties = append(mod.Properties,
&parser.Property{Name: "src", NamePos: srcs.NamePos, ColonPos: srcs.ColonPos, Value: resolveLocalModule(mod, srcList.Values[0])})
} else if len(srcList.Values) > 1 {
indicateAttributeError(mod, "src", "LOCAL_SRC_FILES should contain at most one item")
}
} else if _, ok = srcs.Value.(*parser.Variable); ok {
removeProperty(mod, "srcs")
mod.Properties = append(mod.Properties,
&parser.Property{Name: "src", NamePos: srcs.NamePos, ColonPos: srcs.ColonPos, Value: resolveLocalModule(mod, srcs.Value)})
} else {
renameProperty(mod, "srcs", "src")
}
}
// The rewriter converts LOCAL_MODULE_PATH attribute into a struct attribute
// 'local_module_path'. Analyze its contents and create the correct sub_dir:,
// filename: and boolean attributes combination
const local_module_path = "local_module_path"
if prop_local_module_path, ok := mod.GetProperty(local_module_path); ok {
removeProperty(mod, local_module_path)
prefixVariableName := getStringProperty(prop_local_module_path, "var")
path := getStringProperty(prop_local_module_path, "fixed")
if prefixRewrites, ok := localModulePathRewrite[prefixVariableName]; ok {
rewritten := false
for _, prefixRewrite := range prefixRewrites {
if path == prefixRewrite.prefix {
rewritten = true
} else if trimmedPath := strings.TrimPrefix(path, prefixRewrite.prefix+"/"); trimmedPath != path {
makePrebuiltEtcDestination(mod, trimmedPath)
rewritten = true
}
if rewritten {
for _, flag := range prefixRewrite.flags {
mod.Properties = append(mod.Properties, &parser.Property{Name: flag, Value: &parser.Bool{Value: true, Token: "true"}})
}
break
}
}
if !rewritten {
expectedPrefices := ""
sep := ""
for _, prefixRewrite := range prefixRewrites {
expectedPrefices += sep
sep = ", "
expectedPrefices += prefixRewrite.prefix
}
return indicateAttributeError(mod, "filename",
"LOCAL_MODULE_PATH value under $(%s) should start with %s", prefixVariableName, expectedPrefices)
}
if prefixVariableName == "HOST_OUT" {
mod.Type = "prebuilt_etc_host"
}
} else {
return indicateAttributeError(mod, "filename", "Cannot handle $(%s) for the prebuilt_etc", prefixVariableName)
}
}
}
return nil
}
func runPatchListMod(modFunc func(mod *parser.Module, buf []byte, patchlist *parser.PatchList) error) func(*Fixer) error {
return func(f *Fixer) error {
// Make sure all the offsets are accurate

View File

@ -692,3 +692,62 @@ func TestRewriteCtsModuleTypes(t *testing.T) {
})
}
}
func TestRewritePrebuiltEtc(t *testing.T) {
tests := []struct {
name string
in string
out string
}{
{
name: "prebuilt_etc src",
in: `
prebuilt_etc {
name: "foo",
srcs: ["bar"],
}
`,
out: `prebuilt_etc {
name: "foo",
src: "bar",
}
`,
},
{
name: "prebuilt_etc src",
in: `
prebuilt_etc {
name: "foo",
srcs: FOO,
}
`,
out: `prebuilt_etc {
name: "foo",
src: FOO,
}
`,
},
{
name: "prebuilt_etc src",
in: `
prebuilt_etc {
name: "foo",
srcs: ["bar", "baz"],
}
`,
out: `prebuilt_etc {
name: "foo",
src: "ERROR: LOCAL_SRC_FILES should contain at most one item",
}
`,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
runPass(t, test.in, test.out, func(fixer *Fixer) error {
return rewriteAndroidmkPrebuiltEtc(fixer)
})
})
}
}