New AndroidMk authoring system based on entry map.

The new system collects all Android.mk variable assignments using a map
and writes them to io.Writer. Compared to the previous system, which
directly writes all entries to buffers, this new system is more robust
and test-friendly.

Test: Built without prebuilt_etc.go change and diffed the mk output.
Test: prebuilt_etc_test.go
Change-Id: Idd28443d129ff70053295015e69328a8fa3eca47
This commit is contained in:
Jaewoong Jung 2019-04-03 15:47:29 -07:00
parent 8bf9bd9f91
commit 9aa3ab1f3f
5 changed files with 295 additions and 202 deletions

View File

@ -32,6 +32,8 @@ func init() {
RegisterSingletonType("androidmk", AndroidMkSingleton)
}
// Deprecated: consider using AndroidMkEntriesProvider instead, especially if you're not going to
// use the Custom function.
type AndroidMkDataProvider interface {
AndroidMk() AndroidMkData
BaseModuleName() string
@ -57,6 +59,200 @@ type AndroidMkData struct {
type AndroidMkExtraFunc func(w io.Writer, outputFile Path)
// Allows modules to customize their Android*.mk output.
type AndroidMkEntriesProvider interface {
AndroidMkEntries() AndroidMkEntries
BaseModuleName() string
}
type AndroidMkEntries struct {
Class string
SubName string
DistFile OptionalPath
OutputFile OptionalPath
Disabled bool
Include string
Required []string
Host_required []string
Target_required []string
header bytes.Buffer
footer bytes.Buffer
AddCustomEntries func(name, prefix, moduleDir string, entries *AndroidMkEntries)
EntryMap map[string][]string
entryOrder []string
}
func (a *AndroidMkEntries) SetString(name, value string) {
if _, ok := a.EntryMap[name]; !ok {
a.entryOrder = append(a.entryOrder, name)
}
a.EntryMap[name] = []string{value}
}
func (a *AndroidMkEntries) SetBoolIfTrue(name string, flag bool) {
if flag {
if _, ok := a.EntryMap[name]; !ok {
a.entryOrder = append(a.entryOrder, name)
}
a.EntryMap[name] = []string{"true"}
}
}
func (a *AndroidMkEntries) AddStrings(name string, value ...string) {
if len(value) == 0 {
return
}
if _, ok := a.EntryMap[name]; !ok {
a.entryOrder = append(a.entryOrder, name)
}
a.EntryMap[name] = append(a.EntryMap[name], value...)
}
func (a *AndroidMkEntries) fillInEntries(config Config, bpPath string, mod blueprint.Module) {
a.EntryMap = make(map[string][]string)
amod := mod.(Module).base()
name := amod.BaseModuleName()
if a.Include == "" {
a.Include = "$(BUILD_PREBUILT)"
}
a.Required = append(a.Required, amod.commonProperties.Required...)
a.Host_required = append(a.Host_required, amod.commonProperties.Host_required...)
a.Target_required = append(a.Target_required, amod.commonProperties.Target_required...)
// Fill in the header part.
if len(amod.commonProperties.Dist.Targets) > 0 {
distFile := a.DistFile
if !distFile.Valid() {
distFile = a.OutputFile
}
if distFile.Valid() {
dest := filepath.Base(distFile.String())
if amod.commonProperties.Dist.Dest != nil {
var err error
if dest, err = validateSafePath(*amod.commonProperties.Dist.Dest); err != nil {
// This was checked in ModuleBase.GenerateBuildActions
panic(err)
}
}
if amod.commonProperties.Dist.Suffix != nil {
ext := filepath.Ext(dest)
suffix := *amod.commonProperties.Dist.Suffix
dest = strings.TrimSuffix(dest, ext) + suffix + ext
}
if amod.commonProperties.Dist.Dir != nil {
var err error
if dest, err = validateSafePath(*amod.commonProperties.Dist.Dir, dest); err != nil {
// This was checked in ModuleBase.GenerateBuildActions
panic(err)
}
}
goals := strings.Join(amod.commonProperties.Dist.Targets, " ")
fmt.Fprintln(&a.header, ".PHONY:", goals)
fmt.Fprintf(&a.header, "$(call dist-for-goals,%s,%s:%s)\n",
goals, distFile.String(), dest)
}
}
fmt.Fprintln(&a.header, "\ninclude $(CLEAR_VARS)")
// Collect make variable assignment entries.
a.SetString("LOCAL_PATH", filepath.Dir(bpPath))
a.SetString("LOCAL_MODULE", name+a.SubName)
a.SetString("LOCAL_MODULE_CLASS", a.Class)
a.SetString("LOCAL_PREBUILT_MODULE_FILE", a.OutputFile.String())
a.AddStrings("LOCAL_REQUIRED_MODULES", a.Required...)
a.AddStrings("LOCAL_HOST_REQUIRED_MODULES", a.Host_required...)
a.AddStrings("LOCAL_TARGET_REQUIRED_MODULES", a.Target_required...)
archStr := amod.Arch().ArchType.String()
host := false
switch amod.Os().Class {
case Host:
// Make cannot identify LOCAL_MODULE_HOST_ARCH:= common.
if archStr != "common" {
a.SetString("LOCAL_MODULE_HOST_ARCH", archStr)
}
host = true
case HostCross:
// Make cannot identify LOCAL_MODULE_HOST_CROSS_ARCH:= common.
if archStr != "common" {
a.SetString("LOCAL_MODULE_HOST_CROSS_ARCH", archStr)
}
host = true
case Device:
// Make cannot identify LOCAL_MODULE_TARGET_ARCH:= common.
if archStr != "common" {
a.SetString("LOCAL_MODULE_TARGET_ARCH", archStr)
}
a.AddStrings("LOCAL_INIT_RC", amod.commonProperties.Init_rc...)
a.AddStrings("LOCAL_VINTF_FRAGMENTS", amod.commonProperties.Vintf_fragments...)
a.SetBoolIfTrue("LOCAL_PROPRIETARY_MODULE", Bool(amod.commonProperties.Proprietary))
if Bool(amod.commonProperties.Vendor) || Bool(amod.commonProperties.Soc_specific) {
a.SetString("LOCAL_VENDOR_MODULE", "true")
}
a.SetBoolIfTrue("LOCAL_ODM_MODULE", Bool(amod.commonProperties.Device_specific))
a.SetBoolIfTrue("LOCAL_PRODUCT_MODULE", Bool(amod.commonProperties.Product_specific))
a.SetBoolIfTrue("LOCAL_PRODUCT_SERVICES_MODULE", Bool(amod.commonProperties.Product_services_specific))
if amod.commonProperties.Owner != nil {
a.SetString("LOCAL_MODULE_OWNER", *amod.commonProperties.Owner)
}
}
if amod.noticeFile.Valid() {
a.SetString("LOCAL_NOTICE_FILE", amod.noticeFile.String())
}
if host {
makeOs := amod.Os().String()
if amod.Os() == Linux || amod.Os() == LinuxBionic {
makeOs = "linux"
}
a.SetString("LOCAL_MODULE_HOST_OS", makeOs)
a.SetString("LOCAL_IS_HOST_MODULE", "true")
}
prefix := ""
if amod.ArchSpecific() {
switch amod.Os().Class {
case Host:
prefix = "HOST_"
case HostCross:
prefix = "HOST_CROSS_"
case Device:
prefix = "TARGET_"
}
if amod.Arch().ArchType != config.Targets[amod.Os()][0].Arch.ArchType {
prefix = "2ND_" + prefix
}
}
blueprintDir := filepath.Dir(bpPath)
if a.AddCustomEntries != nil {
a.AddCustomEntries(name, prefix, blueprintDir, a)
}
// Write to footer.
fmt.Fprintln(&a.footer, "include "+a.Include)
}
func (a *AndroidMkEntries) write(w io.Writer) {
w.Write(a.header.Bytes())
for _, name := range a.entryOrder {
fmt.Fprintln(w, name+" := "+strings.Join(a.EntryMap[name], " "))
}
w.Write(a.footer.Bytes())
}
func AndroidMkSingleton() Singleton {
return &androidMkSingleton{}
}
@ -159,6 +355,8 @@ func translateAndroidMkModule(ctx SingletonContext, w io.Writer, mod blueprint.M
return translateAndroidModule(ctx, w, mod, x)
case bootstrap.GoBinaryTool:
return translateGoBinaryModule(ctx, w, mod, x)
case AndroidMkEntriesProvider:
return translateAndroidMkEntriesModule(ctx, w, mod, x)
default:
return nil
}
@ -178,37 +376,32 @@ func translateGoBinaryModule(ctx SingletonContext, w io.Writer, mod blueprint.Mo
func translateAndroidModule(ctx SingletonContext, w io.Writer, mod blueprint.Module,
provider AndroidMkDataProvider) error {
name := provider.BaseModuleName()
amod := mod.(Module).base()
if !amod.Enabled() {
return nil
}
if amod.commonProperties.SkipInstall {
return nil
}
if !amod.commonProperties.NamespaceExportedToMake {
// TODO(jeffrygaston) do we want to validate that there are no modules being
// exported to Kati that depend on this module?
if shouldSkipAndroidMkProcessing(amod) {
return nil
}
data := provider.AndroidMk()
if data.Include == "" {
data.Include = "$(BUILD_PREBUILT)"
}
data.Required = append(data.Required, amod.commonProperties.Required...)
data.Host_required = append(data.Host_required, amod.commonProperties.Host_required...)
data.Target_required = append(data.Target_required, amod.commonProperties.Target_required...)
// Make does not understand LinuxBionic
if amod.Os() == LinuxBionic {
return nil
// Get the preamble content through AndroidMkEntries logic.
entries := AndroidMkEntries{
Class: data.Class,
SubName: data.SubName,
DistFile: data.DistFile,
OutputFile: data.OutputFile,
Disabled: data.Disabled,
Include: data.Include,
Required: data.Required,
Host_required: data.Host_required,
Target_required: data.Target_required,
}
entries.fillInEntries(ctx.Config(), ctx.BlueprintFile(mod), mod)
// preamble doesn't need the footer content.
entries.footer = bytes.Buffer{}
entries.write(&data.preamble)
prefix := ""
if amod.ArchSpecific() {
@ -227,112 +420,7 @@ func translateAndroidModule(ctx SingletonContext, w io.Writer, mod blueprint.Mod
}
}
if len(amod.commonProperties.Dist.Targets) > 0 {
distFile := data.DistFile
if !distFile.Valid() {
distFile = data.OutputFile
}
if distFile.Valid() {
dest := filepath.Base(distFile.String())
if amod.commonProperties.Dist.Dest != nil {
var err error
dest, err = validateSafePath(*amod.commonProperties.Dist.Dest)
if err != nil {
// This was checked in ModuleBase.GenerateBuildActions
panic(err)
}
}
if amod.commonProperties.Dist.Suffix != nil {
ext := filepath.Ext(dest)
suffix := *amod.commonProperties.Dist.Suffix
dest = strings.TrimSuffix(dest, ext) + suffix + ext
}
if amod.commonProperties.Dist.Dir != nil {
var err error
dest, err = validateSafePath(*amod.commonProperties.Dist.Dir, dest)
if err != nil {
// This was checked in ModuleBase.GenerateBuildActions
panic(err)
}
}
goals := strings.Join(amod.commonProperties.Dist.Targets, " ")
fmt.Fprintln(&data.preamble, ".PHONY:", goals)
fmt.Fprintf(&data.preamble, "$(call dist-for-goals,%s,%s:%s)\n",
goals, distFile.String(), dest)
}
}
fmt.Fprintln(&data.preamble, "\ninclude $(CLEAR_VARS)")
fmt.Fprintln(&data.preamble, "LOCAL_PATH :=", filepath.Dir(ctx.BlueprintFile(mod)))
fmt.Fprintln(&data.preamble, "LOCAL_MODULE :=", name+data.SubName)
fmt.Fprintln(&data.preamble, "LOCAL_MODULE_CLASS :=", data.Class)
fmt.Fprintln(&data.preamble, "LOCAL_PREBUILT_MODULE_FILE :=", data.OutputFile.String())
WriteRequiredModulesSettings(&data.preamble, data)
archStr := amod.Arch().ArchType.String()
host := false
switch amod.Os().Class {
case Host:
// Make cannot identify LOCAL_MODULE_HOST_ARCH:= common.
if archStr != "common" {
fmt.Fprintln(&data.preamble, "LOCAL_MODULE_HOST_ARCH :=", archStr)
}
host = true
case HostCross:
// Make cannot identify LOCAL_MODULE_HOST_CROSS_ARCH:= common.
if archStr != "common" {
fmt.Fprintln(&data.preamble, "LOCAL_MODULE_HOST_CROSS_ARCH :=", archStr)
}
host = true
case Device:
// Make cannot identify LOCAL_MODULE_TARGET_ARCH:= common.
if archStr != "common" {
fmt.Fprintln(&data.preamble, "LOCAL_MODULE_TARGET_ARCH :=", archStr)
}
if len(amod.commonProperties.Init_rc) > 0 {
fmt.Fprintln(&data.preamble, "LOCAL_INIT_RC := ", strings.Join(amod.commonProperties.Init_rc, " "))
}
if len(amod.commonProperties.Vintf_fragments) > 0 {
fmt.Fprintln(&data.preamble, "LOCAL_VINTF_FRAGMENTS := ", strings.Join(amod.commonProperties.Vintf_fragments, " "))
}
if Bool(amod.commonProperties.Proprietary) {
fmt.Fprintln(&data.preamble, "LOCAL_PROPRIETARY_MODULE := true")
}
if Bool(amod.commonProperties.Vendor) || Bool(amod.commonProperties.Soc_specific) {
fmt.Fprintln(&data.preamble, "LOCAL_VENDOR_MODULE := true")
}
if Bool(amod.commonProperties.Device_specific) {
fmt.Fprintln(&data.preamble, "LOCAL_ODM_MODULE := true")
}
if Bool(amod.commonProperties.Product_specific) {
fmt.Fprintln(&data.preamble, "LOCAL_PRODUCT_MODULE := true")
}
if Bool(amod.commonProperties.Product_services_specific) {
fmt.Fprintln(&data.preamble, "LOCAL_PRODUCT_SERVICES_MODULE := true")
}
if amod.commonProperties.Owner != nil {
fmt.Fprintln(&data.preamble, "LOCAL_MODULE_OWNER :=", *amod.commonProperties.Owner)
}
}
if amod.noticeFile.Valid() {
fmt.Fprintln(&data.preamble, "LOCAL_NOTICE_FILE :=", amod.noticeFile.String())
}
if host {
makeOs := amod.Os().String()
if amod.Os() == Linux || amod.Os() == LinuxBionic {
makeOs = "linux"
}
fmt.Fprintln(&data.preamble, "LOCAL_MODULE_HOST_OS :=", makeOs)
fmt.Fprintln(&data.preamble, "LOCAL_IS_HOST_MODULE := true")
}
name := provider.BaseModuleName()
blueprintDir := filepath.Dir(ctx.BlueprintFile(mod))
if data.Custom != nil {
@ -362,14 +450,29 @@ func WriteAndroidMkData(w io.Writer, data AndroidMkData) {
fmt.Fprintln(w, "include "+data.Include)
}
func WriteRequiredModulesSettings(w io.Writer, data AndroidMkData) {
if len(data.Required) > 0 {
fmt.Fprintln(w, "LOCAL_REQUIRED_MODULES :=", strings.Join(data.Required, " "))
}
if len(data.Host_required) > 0 {
fmt.Fprintln(w, "LOCAL_HOST_REQUIRED_MODULES :=", strings.Join(data.Host_required, " "))
}
if len(data.Target_required) > 0 {
fmt.Fprintln(w, "LOCAL_TARGET_REQUIRED_MODULES :=", strings.Join(data.Target_required, " "))
func translateAndroidMkEntriesModule(ctx SingletonContext, w io.Writer, mod blueprint.Module,
provider AndroidMkEntriesProvider) error {
if shouldSkipAndroidMkProcessing(mod.(Module).base()) {
return nil
}
entries := provider.AndroidMkEntries()
entries.fillInEntries(ctx.Config(), ctx.BlueprintFile(mod), mod)
entries.write(w)
return nil
}
func shouldSkipAndroidMkProcessing(module *ModuleBase) bool {
if !module.commonProperties.NamespaceExportedToMake {
// TODO(jeffrygaston) do we want to validate that there are no modules being
// exported to Kati that depend on this module?
return true
}
return !module.Enabled() ||
module.commonProperties.SkipInstall ||
// Make does not understand LinuxBionic
module.Os() == LinuxBionic
}

View File

@ -14,10 +14,7 @@
package android
import (
"fmt"
"io"
)
import "strconv"
// TODO(jungw): Now that it handles more than the ones in etc/, consider renaming this file.
@ -134,37 +131,25 @@ func (p *PrebuiltEtc) GenerateAndroidBuildActions(ctx ModuleContext) {
})
}
func (p *PrebuiltEtc) AndroidMk() AndroidMkData {
return AndroidMkData{
Custom: func(w io.Writer, name, prefix, moduleDir string, data AndroidMkData) {
nameSuffix := ""
if p.inRecovery() && !p.onlyInRecovery() {
nameSuffix = ".recovery"
}
fmt.Fprintln(w, "\ninclude $(CLEAR_VARS)")
fmt.Fprintln(w, "LOCAL_PATH :=", moduleDir)
fmt.Fprintln(w, "LOCAL_MODULE :=", name+nameSuffix)
fmt.Fprintln(w, "LOCAL_MODULE_CLASS := ETC")
if p.commonProperties.Owner != nil {
fmt.Fprintln(w, "LOCAL_MODULE_OWNER :=", *p.commonProperties.Owner)
}
fmt.Fprintln(w, "LOCAL_MODULE_TAGS := optional")
if p.Host() {
fmt.Fprintln(w, "LOCAL_IS_HOST_MODULE := true")
}
fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", p.outputFilePath.String())
fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", "$(OUT_DIR)/"+p.installDirPath.RelPathString())
fmt.Fprintln(w, "LOCAL_INSTALLED_MODULE_STEM :=", p.outputFilePath.Base())
fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE :=", !p.Installable())
WriteRequiredModulesSettings(w, data)
func (p *PrebuiltEtc) AndroidMkEntries() AndroidMkEntries {
nameSuffix := ""
if p.inRecovery() && !p.onlyInRecovery() {
nameSuffix = ".recovery"
}
return AndroidMkEntries{
Class: "ETC",
SubName: nameSuffix,
OutputFile: OptionalPathForPath(p.outputFilePath),
AddCustomEntries: func(name, prefix, moduleDir string, entries *AndroidMkEntries) {
entries.SetString("LOCAL_MODULE_TAGS", "optional")
entries.SetString("LOCAL_MODULE_PATH", "$(OUT_DIR)/"+p.installDirPath.RelPathString())
entries.SetString("LOCAL_INSTALLED_MODULE_STEM", p.outputFilePath.Base())
entries.SetString("LOCAL_UNINSTALLABLE_MODULE", strconv.FormatBool(!p.Installable()))
if p.additionalDependencies != nil {
fmt.Fprint(w, "LOCAL_ADDITIONAL_DEPENDENCIES :=")
for _, path := range *p.additionalDependencies {
fmt.Fprint(w, " "+path.String())
entries.SetString("LOCAL_ADDITIONAL_DEPENDENCIES", path.String())
}
fmt.Fprintln(w, "")
}
fmt.Fprintln(w, "include $(BUILD_PREBUILT)")
},
}
}

View File

@ -15,12 +15,10 @@
package android
import (
"bufio"
"bytes"
"io/ioutil"
"os"
"path/filepath"
"strings"
"reflect"
"testing"
)
@ -139,49 +137,37 @@ func TestPrebuiltEtcGlob(t *testing.T) {
}
func TestPrebuiltEtcAndroidMk(t *testing.T) {
ctx, _ := testPrebuiltEtc(t, `
ctx, config := testPrebuiltEtc(t, `
prebuilt_etc {
name: "foo",
src: "foo.conf",
owner: "abc",
filename_from_src: true,
required: ["modA", "moduleB"],
host_required: ["hostModA", "hostModB"],
target_required: ["targetModA"],
}
`)
data := AndroidMkData{}
data.Required = append(data.Required, "modA", "moduleB")
data.Host_required = append(data.Host_required, "hostModA", "hostModB")
data.Target_required = append(data.Target_required, "targetModA")
expected := map[string]string{
"LOCAL_MODULE": "foo",
"LOCAL_MODULE_CLASS": "ETC",
"LOCAL_MODULE_OWNER": "abc",
"LOCAL_INSTALLED_MODULE_STEM": "foo.conf",
"LOCAL_REQUIRED_MODULES": "modA moduleB",
"LOCAL_HOST_REQUIRED_MODULES": "hostModA hostModB",
"LOCAL_TARGET_REQUIRED_MODULES": "targetModA",
expected := map[string][]string{
"LOCAL_MODULE": {"foo"},
"LOCAL_MODULE_CLASS": {"ETC"},
"LOCAL_MODULE_OWNER": {"abc"},
"LOCAL_INSTALLED_MODULE_STEM": {"foo.conf"},
"LOCAL_REQUIRED_MODULES": {"modA", "moduleB"},
"LOCAL_HOST_REQUIRED_MODULES": {"hostModA", "hostModB"},
"LOCAL_TARGET_REQUIRED_MODULES": {"targetModA"},
}
mod := ctx.ModuleForTests("foo", "android_arm64_armv8-a_core").Module().(*PrebuiltEtc)
buf := &bytes.Buffer{}
mod.AndroidMk().Custom(buf, "foo", "", "", data)
for k, expected := range expected {
found := false
scanner := bufio.NewScanner(bytes.NewReader(buf.Bytes()))
for scanner.Scan() {
line := scanner.Text()
tok := strings.Split(line, " := ")
if tok[0] == k {
found = true
if tok[1] != expected {
t.Errorf("Incorrect %s '%s', expected '%s'", k, tok[1], expected)
}
entries := AndroidMkEntriesForTest(t, config, "", mod)
for k, expectedValue := range expected {
if value, ok := entries.EntryMap[k]; ok {
if !reflect.DeepEqual(value, expectedValue) {
t.Errorf("Incorrect %s '%s', expected '%s'", k, value, expectedValue)
}
}
if !found {
t.Errorf("No %s defined, saw %s", k, buf.String())
} else {
t.Errorf("No %s defined, saw %q", k, entries.EntryMap)
}
}
}

View File

@ -371,3 +371,14 @@ func FailIfNoMatchingErrors(t *testing.T, pattern string, errs []error) {
}
}
}
func AndroidMkEntriesForTest(t *testing.T, config Config, bpPath string, mod blueprint.Module) AndroidMkEntries {
var p AndroidMkEntriesProvider
var ok bool
if p, ok = mod.(AndroidMkEntriesProvider); !ok {
t.Errorf("module does not implmement AndroidMkEntriesProvider: " + mod.Name())
}
entries := p.AndroidMkEntries()
entries.fillInEntries(config, bpPath, mod)
return entries
}

View File

@ -38,7 +38,15 @@ func (library *Library) AndroidMkHostDex(w io.Writer, name string, data android.
}
fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", library.headerJarFile.String())
fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", library.implementationAndResourcesJar.String())
android.WriteRequiredModulesSettings(w, data)
if len(data.Required) > 0 {
fmt.Fprintln(w, "LOCAL_REQUIRED_MODULES :=", strings.Join(data.Required, " "))
}
if len(data.Host_required) > 0 {
fmt.Fprintln(w, "LOCAL_HOST_REQUIRED_MODULES :=", strings.Join(data.Host_required, " "))
}
if len(data.Target_required) > 0 {
fmt.Fprintln(w, "LOCAL_TARGET_REQUIRED_MODULES :=", strings.Join(data.Target_required, " "))
}
fmt.Fprintln(w, "include $(BUILD_SYSTEM)/soong_java_prebuilt.mk")
}
}