diff --git a/androidbp/cmd/androidbp.go b/androidbp/cmd/androidbp.go index dbca35a8f..2e0d25c4c 100644 --- a/androidbp/cmd/androidbp.go +++ b/androidbp/cmd/androidbp.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path" + "regexp" "strings" bpparser "github.com/google/blueprint/parser" @@ -13,11 +14,13 @@ import ( type androidMkWriter struct { *bufio.Writer - file *bpparser.File - path string + blueprint *bpparser.File + path string + + mapScope map[string][]*bpparser.Property } -func (w *androidMkWriter) valueToString(value bpparser.Value) string { +func valueToString(value bpparser.Value) string { if value.Variable != "" { return fmt.Sprintf("$(%s)", value.Variable) } else { @@ -25,90 +28,216 @@ func (w *androidMkWriter) valueToString(value bpparser.Value) string { case bpparser.Bool: return fmt.Sprintf(`"%t"`, value.BoolValue) case bpparser.String: - return fmt.Sprintf(`"%s"`, value.StringValue) + return fmt.Sprintf(`"%s"`, processWildcards(value.StringValue)) case bpparser.List: - return fmt.Sprintf("\\\n%s\n", w.listToMkString(value.ListValue)) + return fmt.Sprintf("\\\n%s\n", listToMkString(value.ListValue)) case bpparser.Map: - w.errorf("maps not supported in assignment") - return "ERROR: unsupported type map in assignment" + return fmt.Sprintf("ERROR can't convert map to string") + default: + return fmt.Sprintf("ERROR: unsupported type %d", value.Type) } } - - return "" } -func (w *androidMkWriter) listToMkString(list []bpparser.Value) string { +// TODO: handle non-recursive wildcards? +func processWildcards(s string) string { + re := regexp.MustCompile("(.*)/\\*\\*/(.*)") + submatches := re.FindAllStringSubmatch(s, -1) + if submatches != nil && len(submatches[0]) > 2 { + // Found a wildcard rule + return fmt.Sprintf("$(call find-files-in-subdirs, $(LOCAL_PATH), %s, %s)", + submatches[0][2], submatches[0][1]) + } + + return s +} + +func listToMkString(list []bpparser.Value) string { lines := make([]string, 0, len(list)) for _, tok := range list { - lines = append(lines, fmt.Sprintf("\t\"%s\"", tok.StringValue)) + if tok.Type == bpparser.String { + lines = append(lines, fmt.Sprintf("\t\"%s\"", processWildcards(tok.StringValue))) + } else { + lines = append(lines, fmt.Sprintf("# ERROR: unsupported type %s in list", + tok.Type.String())) + } } return strings.Join(lines, " \\\n") } -func (w *androidMkWriter) errorf(format string, values ...interface{}) { - s := fmt.Sprintf(format, values) - w.WriteString("# ANDROIDBP ERROR:\n") - for _, line := range strings.Split(s, "\n") { - fmt.Fprintf(w, "# %s\n", line) +func translateTargetConditionals(props []*bpparser.Property, + disabledBuilds map[string]bool, isHostRule bool) (computedProps []string) { + for _, target := range props { + conditionals := targetScopedPropertyConditionals + if isHostRule { + conditionals = hostScopedPropertyConditionals + } + + conditional, ok := conditionals[target.Name.Name] + if !ok { + // not found + conditional = fmt.Sprintf( + "ifeq(true, true) # ERROR: unsupported conditional host [%s]", + target.Name.Name) + } + + var scopedProps []string + for _, targetScopedProp := range target.Value.MapValue { + if mkProp, ok := standardProperties[targetScopedProp.Name.Name]; ok { + scopedProps = append(scopedProps, fmt.Sprintf("%s += %s", + mkProp.string, valueToString(targetScopedProp.Value))) + } else if "disabled" == targetScopedProp.Name.Name { + if targetScopedProp.Value.BoolValue { + disabledBuilds[target.Name.Name] = true + } else { + delete(disabledBuilds, target.Name.Name) + } + } + } + + if len(scopedProps) > 0 { + computedProps = append(computedProps, conditional) + computedProps = append(computedProps, scopedProps...) + computedProps = append(computedProps, "endif") + } } + + return +} + +func translateSuffixProperties(suffixProps []*bpparser.Property, + suffixMap map[string]string) (computedProps []string) { + for _, suffixProp := range suffixProps { + if suffix, ok := suffixMap[suffixProp.Name.Name]; ok { + for _, stdProp := range suffixProp.Value.MapValue { + if mkProp, ok := standardProperties[stdProp.Name.Name]; ok { + computedProps = append(computedProps, fmt.Sprintf("%s_%s := %s", mkProp.string, suffix, valueToString(stdProp.Value))) + } else { + computedProps = append(computedProps, fmt.Sprintf("# ERROR: unsupported property %s", stdProp.Name.Name)) + } + } + } + } + return +} + +func (w *androidMkWriter) lookupMap(parent bpparser.Value) (mapValue []*bpparser.Property) { + if parent.Variable != "" { + mapValue = w.mapScope[parent.Variable] + } else { + mapValue = parent.MapValue + } + return } func (w *androidMkWriter) handleComment(comment *bpparser.Comment) { for _, c := range comment.Comment { - mkComment := strings.Replace(c, "//", "#", 1) - // TODO: handle /* comments? - fmt.Fprintf(w, "%s\n", mkComment) + fmt.Fprintf(w, "#%s\n", c) } } -func (w *androidMkWriter) handleModule(module *bpparser.Module) { - if moduleName, ok := moduleTypes[module.Type.Name]; ok { - w.WriteString("include $(CLEAR_VARS)\n") - standardProps := make([]string, 0, len(module.Properties)) - //condProps := make([]string, len(module.Properties)) - for _, prop := range module.Properties { - if mkProp, ok := standardProperties[prop.Name.Name]; ok { - standardProps = append(standardProps, fmt.Sprintf("%s := %s", mkProp.string, - w.valueToString(prop.Value))) - } +func (w *androidMkWriter) writeModule(moduleRule string, props []string, + disabledBuilds map[string]bool, isHostRule bool) { + disabledConditionals := disabledTargetConditionals + if isHostRule { + disabledConditionals = disabledHostConditionals + } + for build, _ := range disabledBuilds { + if conditional, ok := disabledConditionals[build]; ok { + fmt.Fprintf(w, "%s\n", conditional) + defer fmt.Fprintf(w, "endif\n") } + } - mkModule := strings.Join(standardProps, "\n") - w.WriteString(mkModule) + fmt.Fprintf(w, "include $(CLEAR_VARS)\n") + fmt.Fprintf(w, "%s\n", strings.Join(props, "\n")) + fmt.Fprintf(w, "include $(%s)\n\n", moduleRule) +} - fmt.Fprintf(w, "include $(%s)\n\n", moduleName) - } else { - w.errorf("Unsupported module %s", module.Type.Name) +func (w *androidMkWriter) handleModule(module *bpparser.Module) { + moduleRule := fmt.Sprintf(module.Type.Name) + if translation, ok := moduleTypeToRule[module.Type.Name]; ok { + moduleRule = translation + } + + isHostRule := strings.Contains(moduleRule, "HOST") + hostSupported := false + standardProps := make([]string, 0, len(module.Properties)) + disabledBuilds := make(map[string]bool) + for _, prop := range module.Properties { + if mkProp, ok := standardProperties[prop.Name.Name]; ok { + standardProps = append(standardProps, fmt.Sprintf("%s := %s", mkProp.string, valueToString(prop.Value))) + } else if suffixMap, ok := suffixProperties[prop.Name.Name]; ok { + suffixProps := w.lookupMap(prop.Value) + standardProps = append(standardProps, translateSuffixProperties(suffixProps, suffixMap)...) + } else if "target" == prop.Name.Name { + props := w.lookupMap(prop.Value) + standardProps = append(standardProps, translateTargetConditionals(props, disabledBuilds, isHostRule)...) + } else if "host_supported" == prop.Name.Name { + hostSupported = prop.Value.BoolValue + } else { + standardProps = append(standardProps, fmt.Sprintf("# ERROR: Unsupported property %s", prop.Name.Name)) + } + } + + // write out target build + w.writeModule(moduleRule, standardProps, disabledBuilds, isHostRule) + if hostSupported { + hostModuleRule := "NO CORRESPONDING HOST RULE" + moduleRule + if trans, ok := targetToHostModuleRule[moduleRule]; ok { + hostModuleRule = trans + } + w.writeModule(hostModuleRule, standardProps, + disabledBuilds, true) + } +} + +func (w *androidMkWriter) handleSubdirs(value bpparser.Value) { + switch value.Type { + case bpparser.String: + fmt.Fprintf(w, "$(call all-makefiles-under, %s)\n", value.StringValue) + case bpparser.List: + for _, tok := range value.ListValue { + fmt.Fprintf(w, "$(call all-makefiles-under, %s)\n", tok.StringValue) + } } } func (w *androidMkWriter) handleAssignment(assignment *bpparser.Assignment) { - assigner := ":=" - if assignment.Assigner != "=" { - assigner = assignment.Assigner + if "subdirs" == assignment.Name.Name { + w.handleSubdirs(assignment.OrigValue) + } else if assignment.OrigValue.Type == bpparser.Map { + // maps may be assigned in Soong, but can only be translated to .mk + // in the context of the module + w.mapScope[assignment.Name.Name] = assignment.OrigValue.MapValue + } else { + assigner := ":=" + if assignment.Assigner != "=" { + assigner = assignment.Assigner + } + fmt.Fprintf(w, "%s %s %s\n", assignment.Name.Name, assigner, + valueToString(assignment.OrigValue)) } - fmt.Fprintf(w, "%s %s %s\n", assignment.Name.Name, assigner, - w.valueToString(assignment.OrigValue)) } func (w *androidMkWriter) iter() <-chan interface{} { - ch := make(chan interface{}, len(w.file.Comments)+len(w.file.Defs)) + ch := make(chan interface{}, len(w.blueprint.Comments)+len(w.blueprint.Defs)) go func() { commIdx := 0 defsIdx := 0 - for defsIdx < len(w.file.Defs) || commIdx < len(w.file.Comments) { - if defsIdx == len(w.file.Defs) { - ch <- w.file.Comments[commIdx] + for defsIdx < len(w.blueprint.Defs) || commIdx < len(w.blueprint.Comments) { + if defsIdx == len(w.blueprint.Defs) { + ch <- w.blueprint.Comments[commIdx] commIdx++ - } else if commIdx == len(w.file.Comments) { - ch <- w.file.Defs[defsIdx] + } else if commIdx == len(w.blueprint.Comments) { + ch <- w.blueprint.Defs[defsIdx] defsIdx++ } else { commentsPos := 0 defsPos := 0 - def := w.file.Defs[defsIdx] + def := w.blueprint.Defs[defsIdx] switch def := def.(type) { case *bpparser.Module: defsPos = def.LbracePos.Line @@ -116,7 +245,7 @@ func (w *androidMkWriter) iter() <-chan interface{} { defsPos = def.Pos.Line } - comment := w.file.Comments[commIdx] + comment := w.blueprint.Comments[commIdx] commentsPos = comment.Pos.Line if commentsPos < defsPos { @@ -134,7 +263,7 @@ func (w *androidMkWriter) iter() <-chan interface{} { } func (w *androidMkWriter) write() { - outFilePath := fmt.Sprintf("%s/Android.mk.out", w.path) + outFilePath := fmt.Sprintf("%s/Androidbp.mk", w.path) fmt.Printf("Writing %s\n", outFilePath) f, err := os.Create(outFilePath) @@ -142,14 +271,12 @@ func (w *androidMkWriter) write() { panic(err) } - defer func() { - if err := f.Close(); err != nil { - panic(err) - } - }() + defer f.Close() w.Writer = bufio.NewWriter(f) + w.WriteString("LOCAL_PATH := $(call my-dir)\n") + for block := range w.iter() { switch block := block.(type) { case *bpparser.Module: @@ -179,7 +306,7 @@ func main() { } scope := bpparser.NewScope(nil) - file, errs := bpparser.Parse(os.Args[1], reader, scope) + blueprint, errs := bpparser.Parse(os.Args[1], reader, scope) if len(errs) > 0 { fmt.Println("%d errors parsing %s", len(errs), os.Args[1]) fmt.Println(errs) @@ -187,8 +314,9 @@ func main() { } writer := &androidMkWriter{ - file: file, - path: path.Dir(os.Args[1]), + blueprint: blueprint, + path: path.Dir(os.Args[1]), + mapScope: make(map[string][]*bpparser.Property), } writer.write() diff --git a/androidbp/cmd/soong.go b/androidbp/cmd/soong.go index db2707100..b8d7e0ee6 100644 --- a/androidbp/cmd/soong.go +++ b/androidbp/cmd/soong.go @@ -19,6 +19,7 @@ var standardProperties = map[string]struct { "manifest": {"LOCAL_JAR_MANIFEST", bpparser.String}, "jarjar_rules": {"LOCAL_JARJAR_RULES", bpparser.String}, "certificate": {"LOCAL_CERTIFICATE", bpparser.String}, + "suffix": {"LOCAL_MODULE_SUFFIX", bpparser.String}, //"name": "LOCAL_PACKAGE_NAME", TODO // ==== LIST PROPERTIES ==== @@ -62,7 +63,7 @@ var standardProperties = map[string]struct { "export_package_resources": {"LOCAL_EXPORT_PACKAGE_RESOURCES", bpparser.Bool}, } -var moduleTypes = map[string]string{ +var moduleTypeToRule = map[string]string{ "cc_library_shared": "BUILD_SHARED_LIBRARY", "cc_library_static": "BUILD_STATIC_LIBRARY", "cc_library_host_shared": "BUILD_HOST_SHARED_LIBRARY", @@ -80,3 +81,50 @@ var moduleTypes = map[string]string{ "android_app": "BUILD_PACKAGE", "prebuilt": "BUILD_PREBUILT", } + +var suffixProperties = map[string]map[string]string{ + "multilib": {"lib32": "32", "lib64": "64"}, + "arch": {"arm": "arm", "arm64": "arm64", "mips": "mips", "mips64": "mips64", + "x86": "x86", "x86_64": "x86_64"}, +} + +var hostScopedPropertyConditionals = map[string]string{ + "darwin": "ifeq ($(HOST_OS), darwin)", + "not_darwin": "ifneq($(HOST_OS), darwin)", + "windows": "ifeq ($(HOST_OS), windows)", + "not_windows": "ifneq($(HOST_OS), windows)", + "linux": "ifeq ($(HOST_OS), linux)", + "not_linux": "ifneq($(HOST_OS), linux)", +} + +// TODO: host target? +var targetScopedPropertyConditionals = map[string]string{ + "android32": "ifeq($(TARGET_IS_64_BIT), false)", + "not_android32": "ifeq($(TARGET_IS_64_BIT), true)", + "android64": "ifeq($(TARGET_IS_64_BIT), true)", + "not_android64": "ifeq($(TARGET_IS_64_BIT), false)", +} + +var disabledHostConditionals = map[string]string{ + "darwin": "ifneq ($(HOST_OS), darwin)", + "not_darwin": "ifeq($(HOST_OS), darwin)", + "windows": "ifneq ($(HOST_OS), windows)", + "not_windows": "ifeq($(HOST_OS), windows)", + "linux": "ifneq ($(HOST_OS), linux)", + "not_linux": "ifeq($(HOST_OS), linux)", +} + +var disabledTargetConditionals = map[string]string{ + "android32": "ifeq($(TARGET_IS_64_BIT), true)", + "not_android32": "ifeq($(TARGET_IS_64_BIT), false)", + "android64": "ifeq($(TARGET_IS_64_BIT), false)", + "not_android64": "ifeq($(TARGET_IS_64_BIT), true)", +} + +var targetToHostModuleRule = map[string]string{ + "BUILD_SHARED_LIBRARY": "BUILD_HOST_SHARED_LIBRARY", + "BUILD_STATIC_LIBRARY": "BUILD_HOST_STATIC_LIBRARY", + "BUILD_EXECUTABLE": "BUILD_HOST_EXECUTABLE", + "BUILD_NATIVE_TEST": "BUILD_HOST_NATIVE_TEST", + "BUILD_JAVA_LIBRARY": "BUILD_HOST_JAVA_LIBRARY", +}