From 3f40fa460d85b10646d383a3b6b01ea6d569b01b Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Fri, 30 Jan 2015 17:27:36 -0800 Subject: [PATCH] Add soong_build primary builder Initial build logic for building android with soong. It can build a variety of C and C++ files for arm/arm64 and host. Change-Id: I10eb37c2c2a50be6af1bb5fd568c0962b9476bf0 --- Blueprints | 112 +++ androidmk/cmd/androidmk/android.go | 115 +++ androidmk/cmd/androidmk/androidmk.go | 438 +++++++++ androidmk/cmd/androidmk/values.go | 192 ++++ androidmk/parser/make_strings.go | 170 ++++ androidmk/parser/make_strings_test.go | 96 ++ androidmk/parser/makething.go | 142 +++ androidmk/parser/parser.go | 633 +++++++++++++ androidmk/parser/scope.go | 88 ++ build.ninja.in | 165 +++- cc/arm64_device.go | 137 +++ cc/arm_device.go | 301 +++++++ cc/builder.go | 285 ++++++ cc/cc.go | 1194 +++++++++++++++++++++++++ cc/clang.go | 104 +++ cc/toolchain.go | 62 ++ cc/util.go | 96 ++ cc/x86_linux_host.go | 231 +++++ cmd/soong_build/main.go | 68 ++ cmd/soong_glob/soong_glob.go | 56 ++ common/arch.go | 545 +++++++++++ common/defs.go | 61 ++ common/glob.go | 133 +++ common/module.go | 327 +++++++ common/paths.go | 83 ++ config/config.go | 152 ++++ copygcclib.sh | 7 + doc.go | 56 ++ glob/glob.go | 129 +++ root.bp | 3 + 30 files changed, 6178 insertions(+), 3 deletions(-) create mode 100644 androidmk/cmd/androidmk/android.go create mode 100644 androidmk/cmd/androidmk/androidmk.go create mode 100644 androidmk/cmd/androidmk/values.go create mode 100644 androidmk/parser/make_strings.go create mode 100644 androidmk/parser/make_strings_test.go create mode 100644 androidmk/parser/makething.go create mode 100644 androidmk/parser/parser.go create mode 100644 androidmk/parser/scope.go create mode 100644 cc/arm64_device.go create mode 100644 cc/arm_device.go create mode 100644 cc/builder.go create mode 100644 cc/cc.go create mode 100644 cc/clang.go create mode 100644 cc/toolchain.go create mode 100644 cc/util.go create mode 100644 cc/x86_linux_host.go create mode 100644 cmd/soong_build/main.go create mode 100644 cmd/soong_glob/soong_glob.go create mode 100644 common/arch.go create mode 100644 common/defs.go create mode 100644 common/glob.go create mode 100644 common/module.go create mode 100644 common/paths.go create mode 100644 config/config.go create mode 100755 copygcclib.sh create mode 100644 doc.go create mode 100644 glob/glob.go diff --git a/Blueprints b/Blueprints index e69de29bb..f5379f2a6 100644 --- a/Blueprints +++ b/Blueprints @@ -0,0 +1,112 @@ +// +// WARNING: Modifying this file will NOT automatically regenerate build.ninja.in! +// +// Before modifying this file make sure minibp is up to date: +// 1) "repo sync build/soong" to make sure you have the latest build.ninja.in +// 2) build minibp, which builds automicatically through the normal build steps. For example: +// +// After modifying this file regenerate build.ninja.in and build your changes: +// 1) In your build directory, execute "../bootstrap.bash -r" to regenerate build.ninja.in +// 2) Build again +// + +bootstrap_go_binary( + name = "soong_build", + deps = [ + "blueprint", + "blueprint-bootstrap", + "soong-cc", + "soong-common", + "soong-config", + ], + srcs = [ + "cmd/soong_build/main.go", + ], + primaryBuilder = true, +) + +bootstrap_go_binary( + name = "soong_glob", + deps = [ + "soong-glob", + ], + srcs = [ + "cmd/soong_glob/soong_glob.go", + ], +) + +bootstrap_go_package( + name = "soong-glob", + pkgPath = "android/soong/glob", + deps = [ + "blueprint-deptools", + ], + srcs = [ + "glob/glob.go", + ], +) + +bootstrap_go_package( + name = "soong-common", + pkgPath = "android/soong/common", + deps = [ + "blueprint", + "blueprint-bootstrap", + "soong-glob" + ], + srcs = [ + "common/arch.go", + "common/defs.go", + "common/glob.go", + "common/module.go", + "common/paths.go", + ], +) + +bootstrap_go_package( + name = "soong-config", + pkgPath = "android/soong/config", + deps = [ + "blueprint", + "blueprint-bootstrap", + "soong-common", + ], + srcs = [ + "config/config.go", + ], +) + +bootstrap_go_package( + name = "soong-cc", + pkgPath = "android/soong/cc", + deps = [ + "blueprint", + "blueprint-pathtools", + "soong-common", + "soong-config", + ], + srcs = [ + "cc/builder.go", + "cc/cc.go", + "cc/clang.go", + "cc/toolchain.go", + "cc/util.go", + + "cc/arm_device.go", + "cc/arm64_device.go", + + "cc/x86_linux_host.go", + ], +) + +toolchain_library( + name = "libatomic", +) + +toolchain_library( + name = "libgcc", +) + +toolchain_library( + name = "libgcov", +) diff --git a/androidmk/cmd/androidmk/android.go b/androidmk/cmd/androidmk/android.go new file mode 100644 index 000000000..53ae6b322 --- /dev/null +++ b/androidmk/cmd/androidmk/android.go @@ -0,0 +1,115 @@ +package main + +import ( + "android/soong/androidmk/parser" +) + +const ( + clear_vars = "__android_mk_clear_vars" + build_shared_library = "cc_library_shared" + build_static_library = "cc_library_static" + build_host_static_library = "cc_library_host_static" + build_host_shared_library = "cc_library_host_shared" + build_executable = "cc_binary" + build_host_executable = "cc_binary_host" + build_native_test = "cc_test" + build_prebuilt = "prebuilt" +) + +var stringProperties = map[string]string{ + "LOCAL_MODULE": "name", + "LOCAL_MODULE_STEM": "stem", + "LOCAL_MODULE_CLASS": "class", + "LOCAL_CXX_STL": "cxx_stl", + "LOCAL_STRIP_MODULE": "strip", + "LOCAL_MULTILIB": "compile_multilib", +} + +var listProperties = map[string]string{ + "LOCAL_SRC_FILES": "srcs", + "LOCAL_SHARED_LIBRARIES": "shared_libs", + "LOCAL_STATIC_LIBRARIES": "static_libs", + "LOCAL_WHOLE_STATIC_LIBRARIES": "whole_static_libs", + "LOCAL_SYSTEM_SHARED_LIBRARIES": "system_shared_libs", + "LOCAL_C_INCLUDES": "include_dirs", + "LOCAL_EXPORT_C_INCLUDE_DIRS": "export_include_dirs", + "LOCAL_ASFLAGS": "asflags", + "LOCAL_CLANG_ASFLAGS": "clang_asflags", + "LOCAL_CFLAGS": "cflags", + "LOCAL_CONLYFLAGS": "conlyflags", + "LOCAL_CPPFLAGS": "cppflags", + "LOCAL_LDFLAGS": "ldflags", + "LOCAL_REQUIRED_MODULES": "required", + "LOCAL_MODULE_TAGS": "tags", + "LOCAL_LDLIBS": "host_ldlibs", + "LOCAL_CLANG_CFLAGS": "clang_cflags", +} + +var boolProperties = map[string]string{ + "LOCAL_IS_HOST_MODULE": "host", + "LOCAL_CLANG": "clang", + "LOCAL_FORCE_STATIC_EXECUTABLE": "static", + "LOCAL_ADDRESS_SANITIZER": "asan", + "LOCAL_NATIVE_COVERAGE": "native_coverage", + "LOCAL_NO_CRT": "nocrt", + "LOCAL_ALLOW_UNDEFINED_SYMBOLS": "allow_undefined_symbols", + "LOCAL_RTTI_FLAG": "rtti", +} + +var propertySuffixes = []struct { + suffix string + class string +}{ + {"arm", "arch"}, + {"arm64", "arch"}, + {"mips", "arch"}, + {"mips64", "arch"}, + {"x86", "arch"}, + {"x86_64", "arch"}, + {"32", "multilib"}, + {"64", "multilib"}, +} + +var propertySuffixTranslations = map[string]string{ + "32": "lib32", + "64": "lib64", +} + +var conditionalTranslations = map[string]struct { + class string + suffix string +}{ + "($(HOST_OS),darwin)": {"host_os", "darwin"}, + "($(HOST_OS), darwin)": {"host_os", "darwin"}, + "($(HOST_OS),windows)": {"host_os", "windows"}, + "($(HOST_OS), windows)": {"host_os", "windows"}, +} + +func mydir(args []string) string { + return "." +} + +func androidScope() parser.Scope { + globalScope := parser.NewScope(nil) + globalScope.Set("CLEAR_VARS", clear_vars) + globalScope.Set("BUILD_HOST_EXECUTABLE", build_host_executable) + globalScope.Set("BUILD_SHARED_LIBRARY", build_shared_library) + globalScope.Set("BUILD_STATIC_LIBRARY", build_static_library) + globalScope.Set("BUILD_HOST_STATIC_LIBRARY", build_host_static_library) + globalScope.Set("BUILD_HOST_SHARED_LIBRARY", build_host_shared_library) + globalScope.Set("BUILD_NATIVE_TEST", build_native_test) + globalScope.Set("BUILD_EXECUTABLE", build_executable) + globalScope.Set("BUILD_PREBUILT", build_prebuilt) + globalScope.SetFunc("my-dir", mydir) + + globalScope.Set("lib32", "lib32") + globalScope.Set("lib64", "lib64") + globalScope.Set("arm", "arm") + globalScope.Set("arm64", "arm64") + globalScope.Set("mips", "mips") + globalScope.Set("mips64", "mips64") + globalScope.Set("x86", "x86") + globalScope.Set("x86_64", "x86_64") + + return globalScope +} diff --git a/androidmk/cmd/androidmk/androidmk.go b/androidmk/cmd/androidmk/androidmk.go new file mode 100644 index 000000000..6695181b3 --- /dev/null +++ b/androidmk/cmd/androidmk/androidmk.go @@ -0,0 +1,438 @@ +package main + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "strings" + "text/scanner" + + mkparser "android/soong/androidmk/parser" + + bpparser "blueprint/parser" +) + +// TODO: non-expanded variables with expressions + +type bpFile struct { + comments []bpparser.Comment + defs []bpparser.Definition + localAssignments map[string]*bpparser.Property + globalAssignments map[string]*bpparser.Value + scope mkparser.Scope + module *bpparser.Module + + pos scanner.Position + prevLine, line int +} + +func (f *bpFile) errorf(thing mkparser.MakeThing, s string, args ...interface{}) { + orig := thing.Dump() + s = fmt.Sprintf(s, args...) + f.comments = append(f.comments, bpparser.Comment{ + Comment: fmt.Sprintf("// ANDROIDMK TRANSLATION ERROR: %s", s), + Pos: f.pos, + }) + lines := strings.Split(orig, "\n") + for _, l := range lines { + f.incPos() + f.comments = append(f.comments, bpparser.Comment{ + Comment: "// " + l, + Pos: f.pos, + }) + } +} + +func (f *bpFile) setPos(pos, endPos scanner.Position) { + f.pos = pos + + f.line++ + if f.pos.Line > f.prevLine+1 { + f.line++ + } + + f.pos.Line = f.line + f.prevLine = endPos.Line +} + +func (f *bpFile) incPos() { + f.pos.Line++ + f.line++ + f.prevLine++ +} + +type conditional struct { + cond string + eq bool +} + +func main() { + b, err := ioutil.ReadFile(os.Args[1]) + if err != nil { + fmt.Println(err.Error()) + return + } + + p := mkparser.NewParser(os.Args[1], bytes.NewBuffer(b)) + + things, errs := p.Parse() + if len(errs) > 0 { + for _, err := range errs { + fmt.Println("ERROR: ", err) + } + return + } + + file := &bpFile{ + scope: androidScope(), + localAssignments: make(map[string]*bpparser.Property), + globalAssignments: make(map[string]*bpparser.Value), + } + + var conds []*conditional + var cond *conditional + + for _, t := range things { + file.setPos(t.Pos(), t.EndPos()) + + if comment, ok := t.AsComment(); ok { + file.comments = append(file.comments, bpparser.Comment{ + Pos: file.pos, + Comment: "//" + comment.Comment, + }) + } else if assignment, ok := t.AsAssignment(); ok { + handleAssignment(file, assignment, cond) + } else if directive, ok := t.AsDirective(); ok { + switch directive.Name { + case "include": + val := directive.Args.Value(file.scope) + switch val { + case build_shared_library, build_static_library, + build_executable, build_host_executable, + build_prebuilt, build_host_static_library, + build_host_shared_library, build_native_test: + + handleModuleConditionals(file, directive, cond) + makeModule(file, val) + case clear_vars: + resetModule(file) + default: + file.errorf(directive, "unsupported include") + continue + } + case "ifeq", "ifneq": + args := directive.Args.Dump() + eq := directive.Name == "ifeq" + switch args { + case "($(HOST_OS),windows)", "($(HOST_OS), windows)", + "($(HOST_OS),darwin)", "($(HOST_OS), darwin)": + newCond := conditional{args, eq} + conds = append(conds, &newCond) + if cond == nil { + cond = &newCond + } else { + file.errorf(directive, "unsupported nested conditional") + } + default: + file.errorf(directive, "unsupported conditional") + conds = append(conds, nil) + continue + } + case "else": + if len(conds) == 0 { + file.errorf(directive, "missing if before else") + continue + } else if conds[len(conds)-1] == nil { + file.errorf(directive, "else from unsupported contitional") + continue + } + cond.eq = !cond.eq + case "endif": + if len(conds) == 0 { + file.errorf(directive, "missing if before endif") + continue + } else if conds[len(conds)-1] == nil { + file.errorf(directive, "endif from unsupported contitional") + conds = conds[:len(conds)-1] + } else { + if cond == conds[len(conds)-1] { + cond = nil + } + conds = conds[:len(conds)-1] + } + default: + file.errorf(directive, "unsupported directive") + continue + } + } + } + + out, err := bpparser.Print(&bpparser.File{ + Defs: file.defs, + Comments: file.comments, + }) + if err != nil { + fmt.Println(err) + return + } + + fmt.Print(string(out)) +} + +func handleAssignment(file *bpFile, assignment mkparser.Assignment, c *conditional) { + if !assignment.Name.Const() { + file.errorf(assignment, "unsupported non-const variable name") + return + } + + if assignment.Target != nil { + file.errorf(assignment, "unsupported target assignment") + return + } + + name := assignment.Name.Value(nil) + suffix := "" + class := "" + + if strings.HasPrefix(name, "LOCAL_") { + for _, v := range propertySuffixes { + s, c := v.suffix, v.class + if strings.HasSuffix(name, "_"+s) { + name = strings.TrimSuffix(name, "_"+s) + suffix = s + if s, ok := propertySuffixTranslations[s]; ok { + suffix = s + } + class = c + break + } + } + + if c != nil { + if class != "" { + file.errorf(assignment, "suffix assignment inside conditional, skipping conditional") + } else { + if v, ok := conditionalTranslations[c.cond]; ok { + class = v.class + suffix = v.suffix + if !c.eq { + suffix = "not_" + suffix + } + } else { + panic("unknown conditional") + } + } + } + } else { + if c != nil { + eq := "eq" + if !c.eq { + eq = "neq" + } + file.errorf(assignment, "conditional %s %s on global assignment", eq, c.cond) + } + } + + var err error + if prop, ok := stringProperties[name]; ok { + err = setVariable(file, assignment.Value, assignment.Type == "+=", prop, bpparser.String, true, class, suffix) + } else if prop, ok := listProperties[name]; ok { + err = setVariable(file, assignment.Value, assignment.Type == "+=", prop, bpparser.List, true, class, suffix) + } else if prop, ok := boolProperties[name]; ok { + err = setVariable(file, assignment.Value, assignment.Type == "+=", prop, bpparser.Bool, true, class, suffix) + } else { + if name == "LOCAL_PATH" { + // Nothing to do, except maybe avoid the "./" in paths? + } else if strings.HasPrefix(name, "LOCAL_") { + //setVariable(file, assignment, name, bpparser.String, true) + switch name { + case "LOCAL_ADDITIONAL_DEPENDENCIES": + // TODO: check for only .mk files? + default: + file.errorf(assignment, "unsupported assignment to %s", name) + return + } + } else { + err = setVariable(file, assignment.Value, assignment.Type == "+=", name, bpparser.List, false, class, suffix) + } + } + if err != nil { + file.errorf(assignment, err.Error()) + } +} + +func handleModuleConditionals(file *bpFile, directive mkparser.Directive, c *conditional) { + if c == nil { + return + } + + if v, ok := conditionalTranslations[c.cond]; ok { + class := v.class + suffix := v.suffix + disabledSuffix := v.suffix + if !c.eq { + suffix = "not_" + suffix + } else { + disabledSuffix = "not_" + disabledSuffix + } + + // Hoist all properties inside the condtional up to the top level + file.module.Properties = file.localAssignments[class+"___"+suffix].Value.MapValue + file.module.Properties = append(file.module.Properties, file.localAssignments[class]) + file.localAssignments[class+"___"+suffix].Value.MapValue = nil + for i := range file.localAssignments[class].Value.MapValue { + if file.localAssignments[class].Value.MapValue[i].Name.Name == suffix { + file.localAssignments[class].Value.MapValue = + append(file.localAssignments[class].Value.MapValue[:i], + file.localAssignments[class].Value.MapValue[i+1:]...) + } + } + + // Create a fake assignment with enabled = false + err := setVariable(file, mkparser.SimpleMakeString("true", file.pos), false, + "disabled", bpparser.Bool, true, class, disabledSuffix) + if err != nil { + file.errorf(directive, err.Error()) + } + } else { + panic("unknown conditional") + } +} + +func makeModule(file *bpFile, t string) { + file.module.Type = bpparser.Ident{ + Name: t, + Pos: file.module.LbracePos, + } + file.module.RbracePos = file.pos + file.defs = append(file.defs, file.module) +} + +func resetModule(file *bpFile) { + file.module = &bpparser.Module{} + file.module.LbracePos = file.pos + file.localAssignments = make(map[string]*bpparser.Property) +} + +func setVariable(file *bpFile, val *mkparser.MakeString, plusequals bool, name string, + typ bpparser.ValueType, local bool, class string, suffix string) error { + + pos := file.pos + + var oldValue *bpparser.Value + if local { + var oldProp *bpparser.Property + if class != "" { + oldProp = file.localAssignments[name+"___"+class+"___"+suffix] + } else { + oldProp = file.localAssignments[name] + } + if oldProp != nil { + oldValue = &oldProp.Value + } + } else { + oldValue = file.globalAssignments[name] + } + + var exp *bpparser.Value + var err error + switch typ { + case bpparser.List: + exp, err = makeToListExpression(val) + case bpparser.String: + exp, err = makeToStringExpression(val) + case bpparser.Bool: + exp, err = makeToBoolExpression(val) + default: + panic("unknown type") + } + + if err != nil { + return err + } + + if local { + if oldValue != nil && plusequals { + val, err := addValues(oldValue, exp) + if err != nil { + return fmt.Errorf("unsupported addition: %s", err.Error()) + } + val.Expression.Pos = pos + *oldValue = *val + } else if class == "" { + prop := &bpparser.Property{ + Name: bpparser.Ident{Name: name, Pos: pos}, + Pos: pos, + Value: *exp, + } + file.localAssignments[name] = prop + file.module.Properties = append(file.module.Properties, prop) + } else { + classProp := file.localAssignments[class] + if classProp == nil { + classProp = &bpparser.Property{ + Name: bpparser.Ident{Name: class, Pos: pos}, + Pos: pos, + Value: bpparser.Value{ + Type: bpparser.Map, + MapValue: []*bpparser.Property{}, + }, + } + file.localAssignments[class] = classProp + file.module.Properties = append(file.module.Properties, classProp) + } + + suffixProp := file.localAssignments[class+"___"+suffix] + if suffixProp == nil { + suffixProp = &bpparser.Property{ + Name: bpparser.Ident{Name: suffix, Pos: pos}, + Pos: pos, + Value: bpparser.Value{ + Type: bpparser.Map, + MapValue: []*bpparser.Property{}, + }, + } + file.localAssignments[class+"___"+suffix] = suffixProp + classProp.Value.MapValue = append(classProp.Value.MapValue, suffixProp) + } + + prop := &bpparser.Property{ + Name: bpparser.Ident{Name: name, Pos: pos}, + Pos: pos, + Value: *exp, + } + file.localAssignments[class+"___"+suffix+"___"+name] = prop + suffixProp.Value.MapValue = append(suffixProp.Value.MapValue, prop) + } + } else { + if oldValue != nil && plusequals { + a := &bpparser.Assignment{ + Name: bpparser.Ident{ + Name: name, + Pos: pos, + }, + Value: *exp, + OrigValue: *exp, + Pos: pos, + Assigner: "+=", + } + file.defs = append(file.defs, a) + } else { + a := &bpparser.Assignment{ + Name: bpparser.Ident{ + Name: name, + Pos: pos, + }, + Value: *exp, + OrigValue: *exp, + Pos: pos, + Assigner: "=", + } + file.globalAssignments[name] = &a.Value + file.defs = append(file.defs, a) + } + } + + return nil +} diff --git a/androidmk/cmd/androidmk/values.go b/androidmk/cmd/androidmk/values.go new file mode 100644 index 000000000..2ba0829e5 --- /dev/null +++ b/androidmk/cmd/androidmk/values.go @@ -0,0 +1,192 @@ +package main + +import ( + "fmt" + "strings" + + mkparser "android/soong/androidmk/parser" + + bpparser "blueprint/parser" +) + +func stringToStringValue(s string) *bpparser.Value { + return &bpparser.Value{ + Type: bpparser.String, + StringValue: s, + } +} + +func addValues(val1, val2 *bpparser.Value) (*bpparser.Value, error) { + if val1.Type == bpparser.String && val2.Type == bpparser.List { + val1 = &bpparser.Value{ + Type: bpparser.List, + ListValue: []bpparser.Value{*val1}, + } + } else if val2.Type == bpparser.String && val1.Type == bpparser.List { + val2 = &bpparser.Value{ + Type: bpparser.List, + ListValue: []bpparser.Value{*val1}, + } + } else if val1.Type != val2.Type { + return nil, fmt.Errorf("cannot add mismatched types") + } + + return &bpparser.Value{ + Type: val1.Type, + Expression: &bpparser.Expression{ + Operator: '+', + Args: [2]bpparser.Value{*val1, *val2}, + }, + }, nil +} + +func makeToStringExpression(ms *mkparser.MakeString) (*bpparser.Value, error) { + var val *bpparser.Value + var err error + + if ms.Strings[0] != "" { + val = stringToStringValue(ms.Strings[0]) + } + + for i, s := range ms.Strings[1:] { + name := ms.Variables[i].Name + if !name.Const() { + return nil, fmt.Errorf("Unsupported non-const variable name %s", name.Dump()) + } + tmp := &bpparser.Value{ + Type: bpparser.String, + Variable: name.Value(nil), + } + + if val != nil { + val, err = addValues(val, tmp) + if err != nil { + return nil, err + } + } else { + val = tmp + } + + if s != "" { + tmp := stringToStringValue(s) + val, err = addValues(val, tmp) + if err != nil { + return nil, err + } + } + } + + return val, nil +} + +func stringToListValue(s string) *bpparser.Value { + list := strings.Fields(s) + valList := make([]bpparser.Value, len(list)) + for i, l := range list { + valList[i] = bpparser.Value{ + Type: bpparser.String, + StringValue: l, + } + } + return &bpparser.Value{ + Type: bpparser.List, + ListValue: valList, + } + +} + +func makeToListExpression(ms *mkparser.MakeString) (*bpparser.Value, error) { + fields := ms.Split(" \t") + + var listOfListValues []*bpparser.Value + + listValue := &bpparser.Value{ + Type: bpparser.List, + } + + for _, f := range fields { + if len(f.Variables) == 1 && f.Strings[0] == "" && f.Strings[1] == "" { + // Variable by itself, variable is probably a list + if !f.Variables[0].Name.Const() { + return nil, fmt.Errorf("unsupported non-const variable name") + } + if len(listValue.ListValue) > 0 { + listOfListValues = append(listOfListValues, listValue) + } + listOfListValues = append(listOfListValues, &bpparser.Value{ + Type: bpparser.List, + Variable: f.Variables[0].Name.Value(nil), + }) + listValue = &bpparser.Value{ + Type: bpparser.List, + } + } else { + s, err := makeToStringExpression(f) + if err != nil { + return nil, err + } + if s == nil { + continue + } + + listValue.ListValue = append(listValue.ListValue, *s) + } + } + + if len(listValue.ListValue) > 0 { + listOfListValues = append(listOfListValues, listValue) + } + + if len(listOfListValues) == 0 { + return listValue, nil + } + + val := listOfListValues[0] + for _, tmp := range listOfListValues[1:] { + var err error + val, err = addValues(val, tmp) + if err != nil { + return nil, err + } + } + + return val, nil +} + +func stringToBoolValue(s string) (*bpparser.Value, error) { + var b bool + s = strings.TrimSpace(s) + switch s { + case "true": + b = true + case "false", "": + b = false + case "-frtti": // HACK for LOCAL_RTTI_VALUE + b = true + default: + return nil, fmt.Errorf("unexpected bool value %s", s) + } + return &bpparser.Value{ + Type: bpparser.Bool, + BoolValue: b, + }, nil +} + +func makeToBoolExpression(ms *mkparser.MakeString) (*bpparser.Value, error) { + if !ms.Const() { + if len(ms.Variables) == 1 && ms.Strings[0] == "" && ms.Strings[1] == "" { + name := ms.Variables[0].Name + if !name.Const() { + return nil, fmt.Errorf("unsupported non-const variable name") + } + return &bpparser.Value{ + Type: bpparser.Bool, + Variable: name.Value(nil), + }, nil + } else { + return nil, fmt.Errorf("non-const bool expression %s", ms.Dump()) + } + } + + return stringToBoolValue(ms.Value(nil)) +} diff --git a/androidmk/parser/make_strings.go b/androidmk/parser/make_strings.go new file mode 100644 index 000000000..558853f65 --- /dev/null +++ b/androidmk/parser/make_strings.go @@ -0,0 +1,170 @@ +package parser + +import ( + "strings" + "text/scanner" + "unicode" +) + +// A MakeString is a string that may contain variable substitutions in it. +// It can be considered as an alternating list of raw Strings and variable +// substitutions, where the first and last entries in the list must be raw +// Strings (possibly empty). A MakeString that starts with a variable +// will have an empty first raw string, and a MakeString that ends with a +// variable will have an empty last raw string. Two sequential Variables +// will have an empty raw string between them. +// +// The MakeString is stored as two lists, a list of raw Strings and a list +// of Variables. The raw string list is always one longer than the variable +// list. +type MakeString struct { + Pos scanner.Position + Strings []string + Variables []Variable +} + +func SimpleMakeString(s string, pos scanner.Position) *MakeString { + return &MakeString{ + Pos: pos, + Strings: []string{s}, + } +} + +func (ms *MakeString) appendString(s string) { + if len(ms.Strings) == 0 { + ms.Strings = []string{s} + return + } else { + ms.Strings[len(ms.Strings)-1] += s + } +} + +func (ms *MakeString) appendVariable(v Variable) { + if len(ms.Strings) == 0 { + ms.Strings = []string{"", ""} + ms.Variables = []Variable{v} + } else { + ms.Strings = append(ms.Strings, "") + ms.Variables = append(ms.Variables, v) + } +} + +func (ms *MakeString) appendMakeString(other *MakeString) { + last := len(ms.Strings) - 1 + ms.Strings[last] += other.Strings[0] + ms.Strings = append(ms.Strings, other.Strings[1:]...) + ms.Variables = append(ms.Variables, other.Variables...) +} + +func (ms *MakeString) Value(scope Scope) string { + if len(ms.Strings) == 0 { + return "" + } else { + ret := ms.Strings[0] + for i := range ms.Strings[1:] { + ret += ms.Variables[i].Value(scope) + ret += ms.Strings[i+1] + } + return ret + } +} + +func (ms *MakeString) Dump() string { + if len(ms.Strings) == 0 { + return "" + } else { + ret := ms.Strings[0] + for i := range ms.Strings[1:] { + ret += ms.Variables[i].Dump() + ret += ms.Strings[i+1] + } + return ret + } +} + +func (ms *MakeString) Const() bool { + return len(ms.Strings) <= 1 +} + +func (ms *MakeString) Empty() bool { + return len(ms.Strings) == 0 || (len(ms.Strings) == 1 && ms.Strings[0] == "") +} + +func (ms *MakeString) Split(sep string) []*MakeString { + return ms.SplitN(sep, -1) +} + +func (ms *MakeString) SplitN(sep string, n int) []*MakeString { + ret := []*MakeString{} + + curMs := SimpleMakeString("", ms.Pos) + + var i int + var s string + for i, s = range ms.Strings { + if n != 0 { + split := splitAnyN(s, sep, n) + if n != -1 { + if len(split) > n { + panic("oops!") + } else { + n -= len(split) + } + } + curMs.appendString(split[0]) + + for _, r := range split[1:] { + ret = append(ret, curMs) + curMs = SimpleMakeString(r, ms.Pos) + } + } else { + curMs.appendString(s) + } + + if i < len(ms.Strings)-1 { + curMs.appendVariable(ms.Variables[i]) + } + } + + ret = append(ret, curMs) + return ret +} + +func (ms *MakeString) TrimLeftSpaces() { + ms.Strings[0] = strings.TrimLeftFunc(ms.Strings[0], unicode.IsSpace) +} + +func (ms *MakeString) TrimRightSpaces() { + last := len(ms.Strings) - 1 + ms.Strings[last] = strings.TrimRightFunc(ms.Strings[last], unicode.IsSpace) +} + +func (ms *MakeString) TrimRightOne() { + last := len(ms.Strings) - 1 + if len(ms.Strings[last]) > 1 { + ms.Strings[last] = ms.Strings[last][0 : len(ms.Strings[last])-1] + } +} + +func (ms *MakeString) EndsWith(ch rune) bool { + s := ms.Strings[len(ms.Strings)-1] + return s[len(s)-1] == uint8(ch) +} + +func splitAnyN(s, sep string, n int) []string { + ret := []string{} + for n == -1 || n > 1 { + index := strings.IndexAny(s, sep) + if index >= 0 { + ret = append(ret, s[0:index]) + s = s[index+1:] + if n > 0 { + n-- + } + } else { + break + } + } + ret = append(ret, s) + return ret +} diff --git a/androidmk/parser/make_strings_test.go b/androidmk/parser/make_strings_test.go new file mode 100644 index 000000000..db5840c63 --- /dev/null +++ b/androidmk/parser/make_strings_test.go @@ -0,0 +1,96 @@ +package parser + +import ( + "strings" + "testing" +) + +var splitNTestCases = []struct { + in *MakeString + expected []*MakeString + sep string + n int +}{ + { + in: &MakeString{ + strings: []string{ + "a b c", + "d e f", + " h i j", + }, + variables: []Variable{ + variable{name: SimpleMakeString("var1")}, + variable{name: SimpleMakeString("var2")}, + }, + }, + sep: " ", + n: -1, + expected: []*MakeString{ + SimpleMakeString("a"), + SimpleMakeString("b"), + &MakeString{ + strings: []string{"c", "d"}, + variables: []Variable{ + variable{name: SimpleMakeString("var1")}, + }, + }, + SimpleMakeString("e"), + &MakeString{ + strings: []string{"f", ""}, + variables: []Variable{ + variable{name: SimpleMakeString("var2")}, + }, + }, + SimpleMakeString("h"), + SimpleMakeString("i"), + SimpleMakeString("j"), + }, + }, + { + in: &MakeString{ + strings: []string{ + "a b c", + "d e f", + " h i j", + }, + variables: []Variable{ + variable{name: SimpleMakeString("var1")}, + variable{name: SimpleMakeString("var2")}, + }, + }, + sep: " ", + n: 3, + expected: []*MakeString{ + SimpleMakeString("a"), + SimpleMakeString("b"), + &MakeString{ + strings: []string{"c", "d e f", " h i j"}, + variables: []Variable{ + variable{name: SimpleMakeString("var1")}, + variable{name: SimpleMakeString("var2")}, + }, + }, + }, + }, +} + +func TestMakeStringSplitN(t *testing.T) { + for _, test := range splitNTestCases { + got := test.in.SplitN(test.sep, test.n) + gotString := dumpArray(got) + expectedString := dumpArray(test.expected) + if gotString != expectedString { + t.Errorf("expected:\n%s\ngot:\n%s", expectedString, gotString) + } + } +} + +func dumpArray(a []*MakeString) string { + ret := make([]string, len(a)) + + for i, s := range a { + ret[i] = s.Dump() + } + + return strings.Join(ret, "|||") +} diff --git a/androidmk/parser/makething.go b/androidmk/parser/makething.go new file mode 100644 index 000000000..7d60a779b --- /dev/null +++ b/androidmk/parser/makething.go @@ -0,0 +1,142 @@ +package parser + +import ( + "text/scanner" +) + +type MakeThing interface { + AsAssignment() (Assignment, bool) + AsComment() (Comment, bool) + AsDirective() (Directive, bool) + AsRule() (Rule, bool) + AsVariable() (Variable, bool) + Dump() string + Pos() scanner.Position + EndPos() scanner.Position +} + +type Assignment struct { + makeThing + Name *MakeString + Value *MakeString + Target *MakeString + Type string +} + +type Comment struct { + makeThing + Comment string +} + +type Directive struct { + makeThing + Name string + Args *MakeString +} + +type Rule struct { + makeThing + Target *MakeString + Prerequisites *MakeString + Recipe string +} + +type Variable struct { + makeThing + Name *MakeString +} + +type makeThing struct { + pos scanner.Position + endPos scanner.Position +} + +func (m makeThing) Pos() scanner.Position { + return m.pos +} + +func (m makeThing) EndPos() scanner.Position { + return m.endPos +} + +func (makeThing) AsAssignment() (a Assignment, ok bool) { + return +} + +func (a Assignment) AsAssignment() (Assignment, bool) { + return a, true +} + +func (a Assignment) Dump() string { + target := "" + if a.Target != nil { + target = a.Target.Dump() + ": " + } + return target + a.Name.Dump() + a.Type + a.Value.Dump() +} + +func (makeThing) AsComment() (c Comment, ok bool) { + return +} + +func (c Comment) AsComment() (Comment, bool) { + return c, true +} + +func (c Comment) Dump() string { + return "#" + c.Comment +} + +func (makeThing) AsDirective() (d Directive, ok bool) { + return +} + +func (d Directive) AsDirective() (Directive, bool) { + return d, true +} + +func (d Directive) Dump() string { + return d.Name + " " + d.Args.Dump() +} + +func (makeThing) AsRule() (r Rule, ok bool) { + return +} + +func (r Rule) AsRule() (Rule, bool) { + return r, true +} + +func (r Rule) Dump() string { + recipe := "" + if r.Recipe != "" { + recipe = "\n" + r.Recipe + } + return "rule: " + r.Target.Dump() + ": " + r.Prerequisites.Dump() + recipe +} + +func (makeThing) AsVariable() (v Variable, ok bool) { + return +} + +func (v Variable) AsVariable() (Variable, bool) { + return v, true +} + +func (v Variable) Dump() string { + return "$(" + v.Name.Dump() + ")" +} + +type byPosition []MakeThing + +func (s byPosition) Len() int { + return len(s) +} + +func (s byPosition) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +func (s byPosition) Less(i, j int) bool { + return s[i].Pos().Offset < s[j].Pos().Offset +} diff --git a/androidmk/parser/parser.go b/androidmk/parser/parser.go new file mode 100644 index 000000000..58e612eaa --- /dev/null +++ b/androidmk/parser/parser.go @@ -0,0 +1,633 @@ +package parser + +import ( + "errors" + "fmt" + "io" + "sort" + "text/scanner" +) + +var errTooManyErrors = errors.New("too many errors") + +const maxErrors = 100 + +type ParseError struct { + Err error + Pos scanner.Position +} + +func (e *ParseError) Error() string { + return fmt.Sprintf("%s: %s", e.Pos, e.Err) +} + +func (p *parser) Parse() ([]MakeThing, []error) { + defer func() { + if r := recover(); r != nil { + if r == errTooManyErrors { + return + } + panic(r) + } + }() + + p.parseLines() + p.accept(scanner.EOF) + p.things = append(p.things, p.comments...) + sort.Sort(byPosition(p.things)) + + return p.things, p.errors +} + +type parser struct { + scanner scanner.Scanner + tok rune + errors []error + comments []MakeThing + things []MakeThing +} + +func NewParser(filename string, r io.Reader) *parser { + p := &parser{} + p.scanner.Init(r) + p.scanner.Error = func(sc *scanner.Scanner, msg string) { + p.errorf(msg) + } + p.scanner.Whitespace = 0 + p.scanner.IsIdentRune = func(ch rune, i int) bool { + return ch > 0 && ch != ':' && ch != '#' && ch != '=' && ch != '+' && ch != '$' && + ch != '\\' && ch != '(' && ch != ')' && ch != '{' && ch != '}' && ch != ';' && + ch != '|' && ch != '?' && ch != '\r' && !isWhitespace(ch) + } + p.scanner.Mode = scanner.ScanIdents + p.scanner.Filename = filename + p.next() + return p +} + +func (p *parser) errorf(format string, args ...interface{}) { + pos := p.scanner.Position + if !pos.IsValid() { + pos = p.scanner.Pos() + } + err := &ParseError{ + Err: fmt.Errorf(format, args...), + Pos: pos, + } + p.errors = append(p.errors, err) + if len(p.errors) >= maxErrors { + panic(errTooManyErrors) + } +} + +func (p *parser) accept(toks ...rune) bool { + for _, tok := range toks { + if p.tok != tok { + p.errorf("expected %s, found %s", scanner.TokenString(tok), + scanner.TokenString(p.tok)) + return false + } + p.next() + } + return true +} + +func (p *parser) next() { + if p.tok != scanner.EOF { + p.tok = p.scanner.Scan() + for p.tok == '\r' { + p.tok = p.scanner.Scan() + } + } + return +} + +func (p *parser) parseLines() { + for { + p.ignoreWhitespace() + + if p.parseDirective() { + continue + } + + ident, _ := p.parseExpression('=', '?', ':', '#', '\n') + + p.ignoreSpaces() + + switch p.tok { + case '?': + p.accept('?') + if p.tok == '=' { + p.parseAssignment("?=", nil, ident) + } else { + p.errorf("expected = after ?") + } + case '+': + p.accept('+') + if p.tok == '=' { + p.parseAssignment("+=", nil, ident) + } else { + p.errorf("expected = after +") + } + case ':': + p.accept(':') + switch p.tok { + case '=': + p.parseAssignment(":=", nil, ident) + default: + p.parseRule(ident) + } + case '=': + p.parseAssignment("=", nil, ident) + case '#', '\n', scanner.EOF: + ident.TrimRightSpaces() + if v, ok := toVariable(ident); ok { + p.things = append(p.things, v) + } else if !ident.Empty() { + p.errorf("expected directive, rule, or assignment after ident " + ident.Dump()) + } + switch p.tok { + case scanner.EOF: + return + case '\n': + p.accept('\n') + case '#': + p.parseComment() + } + default: + p.errorf("expected assignment or rule definition, found %s\n", + p.scanner.TokenText()) + return + } + } +} + +func (p *parser) parseDirective() bool { + if p.tok != scanner.Ident || !isDirective(p.scanner.TokenText()) { + return false + } + + d := p.scanner.TokenText() + pos := p.scanner.Position + endPos := pos + p.accept(scanner.Ident) + + expression := SimpleMakeString("", pos) + + switch d { + case "endif", "endef", "else": + // Nothing + case "define": + expression = p.parseDefine() + default: + p.ignoreSpaces() + expression, endPos = p.parseExpression() + } + + p.things = append(p.things, Directive{ + makeThing: makeThing{ + pos: pos, + endPos: endPos, + }, + Name: d, + Args: expression, + }) + return true +} + +func (p *parser) parseDefine() *MakeString { + value := SimpleMakeString("", p.scanner.Position) + +loop: + for { + switch p.tok { + case scanner.Ident: + if p.scanner.TokenText() == "endef" { + p.accept(scanner.Ident) + break loop + } + value.appendString(p.scanner.TokenText()) + p.accept(scanner.Ident) + case '\\': + p.parseEscape() + switch p.tok { + case '\n': + value.appendString(" ") + case scanner.EOF: + p.errorf("expected escaped character, found %s", + scanner.TokenString(p.tok)) + break loop + default: + value.appendString(`\` + string(p.tok)) + } + p.accept(p.tok) + //TODO: handle variables inside defines? result depends if + //define is used in make or rule context + //case '$': + // variable := p.parseVariable() + // value.appendVariable(variable) + case scanner.EOF: + p.errorf("unexpected EOF while looking for endef") + break loop + default: + value.appendString(p.scanner.TokenText()) + p.accept(p.tok) + } + } + + return value +} + +func (p *parser) parseEscape() { + p.scanner.Mode = 0 + p.accept('\\') + p.scanner.Mode = scanner.ScanIdents +} + +func (p *parser) parseExpression(end ...rune) (*MakeString, scanner.Position) { + value := SimpleMakeString("", p.scanner.Position) + + endParen := false + for _, r := range end { + if r == ')' { + endParen = true + } + } + parens := 0 + + endPos := p.scanner.Position + +loop: + for { + if endParen && parens > 0 && p.tok == ')' { + parens-- + value.appendString(")") + endPos = p.scanner.Position + p.accept(')') + continue + } + + for _, r := range end { + if p.tok == r { + break loop + } + } + + switch p.tok { + case '\n': + break loop + case scanner.Ident: + value.appendString(p.scanner.TokenText()) + endPos = p.scanner.Position + p.accept(scanner.Ident) + case '\\': + p.parseEscape() + switch p.tok { + case '\n': + value.appendString(" ") + case scanner.EOF: + p.errorf("expected escaped character, found %s", + scanner.TokenString(p.tok)) + return value, endPos + default: + value.appendString(`\` + string(p.tok)) + } + endPos = p.scanner.Position + p.accept(p.tok) + case '#': + p.parseComment() + break loop + case '$': + var variable Variable + variable, endPos = p.parseVariable() + value.appendVariable(variable) + case scanner.EOF: + break loop + case '(': + if endParen { + parens++ + } + value.appendString("(") + endPos = p.scanner.Position + p.accept('(') + default: + value.appendString(p.scanner.TokenText()) + endPos = p.scanner.Position + p.accept(p.tok) + } + } + + if parens > 0 { + p.errorf("expected closing paren %s", value.Dump()) + } + return value, endPos +} + +func (p *parser) parseVariable() (Variable, scanner.Position) { + pos := p.scanner.Position + endPos := pos + p.accept('$') + var name *MakeString + switch p.tok { + case '(': + return p.parseBracketedVariable('(', ')', pos) + case '{': + return p.parseBracketedVariable('{', '}', pos) + case '$': + name = SimpleMakeString("__builtin_dollar", scanner.Position{}) + case scanner.EOF: + p.errorf("expected variable name, found %s", + scanner.TokenString(p.tok)) + default: + name, endPos = p.parseExpression(variableNameEndRunes...) + } + + return p.nameToVariable(name, pos, endPos), endPos +} + +func (p *parser) parseBracketedVariable(start, end rune, pos scanner.Position) (Variable, scanner.Position) { + p.accept(start) + name, endPos := p.parseExpression(end) + p.accept(end) + return p.nameToVariable(name, pos, endPos), endPos +} + +func (p *parser) nameToVariable(name *MakeString, pos, endPos scanner.Position) Variable { + return Variable{ + makeThing: makeThing{ + pos: pos, + endPos: endPos, + }, + Name: name, + } +} + +func (p *parser) parseRule(target *MakeString) { + prerequisites, newLine := p.parseRulePrerequisites(target) + + recipe := "" + endPos := p.scanner.Position +loop: + for { + if newLine { + if p.tok == '\t' { + endPos = p.scanner.Position + p.accept('\t') + newLine = false + continue loop + } else if p.parseDirective() { + newLine = false + continue + } else { + break loop + } + } + + newLine = false + switch p.tok { + case '\\': + p.parseEscape() + recipe += string(p.tok) + endPos = p.scanner.Position + p.accept(p.tok) + case '\n': + newLine = true + recipe += "\n" + endPos = p.scanner.Position + p.accept('\n') + case scanner.EOF: + break loop + default: + recipe += p.scanner.TokenText() + endPos = p.scanner.Position + p.accept(p.tok) + } + } + + if prerequisites != nil { + p.things = append(p.things, Rule{ + makeThing: makeThing{ + pos: target.Pos, + endPos: endPos, + }, + Target: target, + Prerequisites: prerequisites, + Recipe: recipe, + }) + } +} + +func (p *parser) parseRulePrerequisites(target *MakeString) (*MakeString, bool) { + newLine := false + + p.ignoreSpaces() + + prerequisites, _ := p.parseExpression('#', '\n', ';', ':', '=') + + switch p.tok { + case '\n': + p.accept('\n') + newLine = true + case '#': + p.parseComment() + newLine = true + case ';': + p.accept(';') + case ':': + p.accept(':') + if p.tok == '=' { + p.parseAssignment(":=", target, prerequisites) + return nil, true + } else { + more, _ := p.parseExpression('#', '\n', ';') + prerequisites.appendMakeString(more) + } + case '=': + p.parseAssignment("=", target, prerequisites) + return nil, true + default: + p.errorf("unexpected token %s after rule prerequisites", scanner.TokenString(p.tok)) + } + + return prerequisites, newLine +} + +func (p *parser) parseComment() { + pos := p.scanner.Position + p.accept('#') + comment := "" + endPos := pos +loop: + for { + switch p.tok { + case '\\': + p.parseEscape() + if p.tok == '\n' { + comment += "\n" + } else { + comment += "\\" + p.scanner.TokenText() + } + endPos = p.scanner.Position + p.accept(p.tok) + case '\n': + endPos = p.scanner.Position + p.accept('\n') + break loop + case scanner.EOF: + break loop + default: + comment += p.scanner.TokenText() + endPos = p.scanner.Position + p.accept(p.tok) + } + } + + p.comments = append(p.comments, Comment{ + makeThing: makeThing{ + pos: pos, + endPos: endPos, + }, + Comment: comment, + }) +} + +func (p *parser) parseAssignment(t string, target *MakeString, ident *MakeString) { + // The value of an assignment is everything including and after the first + // non-whitespace character after the = until the end of the logical line, + // which may included escaped newlines + p.accept('=') + value, endPos := p.parseExpression() + value.TrimLeftSpaces() + if ident.EndsWith('+') && t == "=" { + ident.TrimRightOne() + t = "+=" + } + + ident.TrimRightSpaces() + + p.things = append(p.things, Assignment{ + makeThing: makeThing{ + pos: ident.Pos, + endPos: endPos, + }, + Name: ident, + Value: value, + Target: target, + Type: t, + }) +} + +type androidMkModule struct { + assignments map[string]string +} + +type androidMkFile struct { + assignments map[string]string + modules []androidMkModule + includes []string +} + +var directives = [...]string{ + "define", + "else", + "endef", + "endif", + "ifdef", + "ifeq", + "ifndef", + "ifneq", + "include", + "-include", +} + +var functions = [...]string{ + "abspath", + "addprefix", + "addsuffix", + "basename", + "dir", + "notdir", + "subst", + "suffix", + "filter", + "filter-out", + "findstring", + "firstword", + "flavor", + "join", + "lastword", + "patsubst", + "realpath", + "shell", + "sort", + "strip", + "wildcard", + "word", + "wordlist", + "words", + "origin", + "foreach", + "call", + "info", + "error", + "warning", + "if", + "or", + "and", + "value", + "eval", + "file", +} + +func init() { + sort.Strings(directives[:]) + sort.Strings(functions[:]) +} + +func isDirective(s string) bool { + for _, d := range directives { + if s == d { + return true + } else if s < d { + return false + } + } + return false +} + +func isFunctionName(s string) bool { + for _, f := range functions { + if s == f { + return true + } else if s < f { + return false + } + } + return false +} + +func isWhitespace(ch rune) bool { + return ch == ' ' || ch == '\t' || ch == '\n' +} + +func isValidVariableRune(ch rune) bool { + return ch != scanner.Ident && ch != ':' && ch != '=' && ch != '#' +} + +var whitespaceRunes = []rune{' ', '\t', '\n'} +var variableNameEndRunes = append([]rune{':', '=', '#', ')', '}'}, whitespaceRunes...) + +func (p *parser) ignoreSpaces() int { + skipped := 0 + for p.tok == ' ' || p.tok == '\t' { + p.accept(p.tok) + skipped++ + } + return skipped +} + +func (p *parser) ignoreWhitespace() { + for isWhitespace(p.tok) { + p.accept(p.tok) + } +} diff --git a/androidmk/parser/scope.go b/androidmk/parser/scope.go new file mode 100644 index 000000000..742ad3881 --- /dev/null +++ b/androidmk/parser/scope.go @@ -0,0 +1,88 @@ +package parser + +import "strings" + +type Scope interface { + Get(name string) string + Set(name, value string) + Call(name string, args []string) string + SetFunc(name string, f func([]string) string) +} + +type scope struct { + variables map[string]string + functions map[string]func([]string) string + parent Scope +} + +func (s *scope) Get(name string) string { + if val, ok := s.variables[name]; ok { + return val + } else if s.parent != nil { + return s.parent.Get(name) + } else if val, ok := builtinScope[name]; ok { + return val + } else { + return "<'" + name + "' unset>" + } +} + +func (s *scope) Set(name, value string) { + s.variables[name] = value +} + +func (s *scope) Call(name string, args []string) string { + if f, ok := s.functions[name]; ok { + return f(args) + } + + return "" +} + +func (s *scope) SetFunc(name string, f func([]string) string) { + s.functions[name] = f +} + +func NewScope(parent Scope) Scope { + return &scope{ + variables: make(map[string]string), + functions: make(map[string]func([]string) string), + parent: parent, + } +} + +var builtinScope map[string]string + +func init() { + builtinScope := make(map[string]string) + builtinScope["__builtin_dollar"] = "$" +} + +func (v Variable) Value(scope Scope) string { + f := v.Name.SplitN(" \t", 2) + if len(f) > 1 && f[0].Const() { + fname := f[0].Value(nil) + if isFunctionName(fname) { + args := f[1].Split(",") + argVals := make([]string, len(args)) + for i, a := range args { + argVals[i] = a.Value(scope) + } + + if fname == "call" { + return scope.Call(argVals[0], argVals[1:]) + } else { + return "__builtin_func:" + fname + " " + strings.Join(argVals, " ") + } + } + } + + return scope.Get(v.Name.Value(scope)) +} + +func toVariable(ms *MakeString) (Variable, bool) { + if len(ms.Variables) == 1 && ms.Strings[0] == "" && ms.Strings[1] == "" { + return ms.Variables[0], true + } + return Variable{}, false +} diff --git a/build.ninja.in b/build.ninja.in index d5b0a7c31..aa03b7531 100644 --- a/build.ninja.in +++ b/build.ninja.in @@ -50,6 +50,7 @@ rule g.bootstrap.link # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Module: blueprint +# Variant: # Type: bootstrap_go_package # Factory: blueprint/bootstrap.newGoPackageModule # Defined: build/blueprint/Blueprints:1:1 @@ -74,6 +75,7 @@ default .bootstrap/blueprint/pkg/blueprint.a # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Module: blueprint-bootstrap +# Variant: # Type: bootstrap_go_package # Factory: blueprint/bootstrap.newGoPackageModule # Defined: build/blueprint/Blueprints:55:1 @@ -96,6 +98,7 @@ default .bootstrap/blueprint-bootstrap/pkg/blueprint/bootstrap.a # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Module: blueprint-deptools +# Variant: # Type: bootstrap_go_package # Factory: blueprint/bootstrap.newGoPackageModule # Defined: build/blueprint/Blueprints:34:1 @@ -108,6 +111,7 @@ default .bootstrap/blueprint-deptools/pkg/blueprint/deptools.a # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Module: blueprint-parser +# Variant: # Type: bootstrap_go_package # Factory: blueprint/bootstrap.newGoPackageModule # Defined: build/blueprint/Blueprints:23:1 @@ -123,6 +127,7 @@ default .bootstrap/blueprint-parser/pkg/blueprint/parser.a # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Module: blueprint-pathtools +# Variant: # Type: bootstrap_go_package # Factory: blueprint/bootstrap.newGoPackageModule # Defined: build/blueprint/Blueprints:40:1 @@ -136,6 +141,7 @@ default .bootstrap/blueprint-pathtools/pkg/blueprint/pathtools.a # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Module: blueprint-proptools +# Variant: # Type: bootstrap_go_package # Factory: blueprint/bootstrap.newGoPackageModule # Defined: build/blueprint/Blueprints:49:1 @@ -148,6 +154,7 @@ default .bootstrap/blueprint-proptools/pkg/blueprint/proptools.a # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Module: bpfmt +# Variant: # Type: bootstrap_go_binary # Factory: blueprint/bootstrap.newGoBinaryModule # Defined: build/blueprint/Blueprints:81:1 @@ -170,6 +177,7 @@ default .bootstrap/bin/bpfmt # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Module: bpmodify +# Variant: # Type: bootstrap_go_binary # Factory: blueprint/bootstrap.newGoBinaryModule # Defined: build/blueprint/Blueprints:87:1 @@ -192,6 +200,7 @@ default .bootstrap/bin/bpmodify # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Module: minibp +# Variant: # Type: bootstrap_go_binary # Factory: blueprint/bootstrap.newGoBinaryModule # Defined: build/blueprint/Blueprints:72:1 @@ -217,14 +226,163 @@ default .bootstrap/minibp/obj/a.out build .bootstrap/bin/minibp: g.bootstrap.cp .bootstrap/minibp/obj/a.out default .bootstrap/bin/minibp +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Module: soong-cc +# Variant: +# Type: bootstrap_go_package +# Factory: blueprint/bootstrap.newGoPackageModule +# Defined: build/soong/Blueprints:79:1 + +build .bootstrap/soong-cc/pkg/android/soong/cc.a: g.bootstrap.gc $ + ${g.bootstrap.srcDir}/build/soong/cc/builder.go $ + ${g.bootstrap.srcDir}/build/soong/cc/cc.go $ + ${g.bootstrap.srcDir}/build/soong/cc/clang.go $ + ${g.bootstrap.srcDir}/build/soong/cc/toolchain.go $ + ${g.bootstrap.srcDir}/build/soong/cc/util.go $ + ${g.bootstrap.srcDir}/build/soong/cc/arm_device.go $ + ${g.bootstrap.srcDir}/build/soong/cc/arm64_device.go $ + ${g.bootstrap.srcDir}/build/soong/cc/x86_linux_host.go | $ + ${g.bootstrap.gcCmd} $ + .bootstrap/blueprint-parser/pkg/blueprint/parser.a $ + .bootstrap/blueprint-proptools/pkg/blueprint/proptools.a $ + .bootstrap/blueprint/pkg/blueprint.a $ + .bootstrap/blueprint-pathtools/pkg/blueprint/pathtools.a $ + .bootstrap/blueprint-deptools/pkg/blueprint/deptools.a $ + .bootstrap/blueprint-bootstrap/pkg/blueprint/bootstrap.a $ + .bootstrap/soong-glob/pkg/android/soong/glob.a $ + .bootstrap/soong-common/pkg/android/soong/common.a $ + .bootstrap/soong-config/pkg/android/soong/config.a + incFlags = -I .bootstrap/blueprint-parser/pkg -I .bootstrap/blueprint-proptools/pkg -I .bootstrap/blueprint/pkg -I .bootstrap/blueprint-pathtools/pkg -I .bootstrap/blueprint-deptools/pkg -I .bootstrap/blueprint-bootstrap/pkg -I .bootstrap/soong-glob/pkg -I .bootstrap/soong-common/pkg -I .bootstrap/soong-config/pkg + pkgPath = android/soong/cc +default .bootstrap/soong-cc/pkg/android/soong/cc.a + +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Module: soong-common +# Variant: +# Type: bootstrap_go_package +# Factory: blueprint/bootstrap.newGoPackageModule +# Defined: build/soong/Blueprints:49:1 + +build .bootstrap/soong-common/pkg/android/soong/common.a: g.bootstrap.gc $ + ${g.bootstrap.srcDir}/build/soong/common/arch.go $ + ${g.bootstrap.srcDir}/build/soong/common/defs.go $ + ${g.bootstrap.srcDir}/build/soong/common/glob.go $ + ${g.bootstrap.srcDir}/build/soong/common/module.go $ + ${g.bootstrap.srcDir}/build/soong/common/paths.go | $ + ${g.bootstrap.gcCmd} $ + .bootstrap/blueprint-parser/pkg/blueprint/parser.a $ + .bootstrap/blueprint-proptools/pkg/blueprint/proptools.a $ + .bootstrap/blueprint/pkg/blueprint.a $ + .bootstrap/blueprint-deptools/pkg/blueprint/deptools.a $ + .bootstrap/blueprint-pathtools/pkg/blueprint/pathtools.a $ + .bootstrap/blueprint-bootstrap/pkg/blueprint/bootstrap.a $ + .bootstrap/soong-glob/pkg/android/soong/glob.a + incFlags = -I .bootstrap/blueprint-parser/pkg -I .bootstrap/blueprint-proptools/pkg -I .bootstrap/blueprint/pkg -I .bootstrap/blueprint-deptools/pkg -I .bootstrap/blueprint-pathtools/pkg -I .bootstrap/blueprint-bootstrap/pkg -I .bootstrap/soong-glob/pkg + pkgPath = android/soong/common +default .bootstrap/soong-common/pkg/android/soong/common.a + +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Module: soong-config +# Variant: +# Type: bootstrap_go_package +# Factory: blueprint/bootstrap.newGoPackageModule +# Defined: build/soong/Blueprints:66:1 + +build .bootstrap/soong-config/pkg/android/soong/config.a: g.bootstrap.gc $ + ${g.bootstrap.srcDir}/build/soong/config/config.go | $ + ${g.bootstrap.gcCmd} $ + .bootstrap/blueprint-parser/pkg/blueprint/parser.a $ + .bootstrap/blueprint-proptools/pkg/blueprint/proptools.a $ + .bootstrap/blueprint/pkg/blueprint.a $ + .bootstrap/blueprint-deptools/pkg/blueprint/deptools.a $ + .bootstrap/blueprint-pathtools/pkg/blueprint/pathtools.a $ + .bootstrap/blueprint-bootstrap/pkg/blueprint/bootstrap.a $ + .bootstrap/soong-glob/pkg/android/soong/glob.a $ + .bootstrap/soong-common/pkg/android/soong/common.a + incFlags = -I .bootstrap/blueprint-parser/pkg -I .bootstrap/blueprint-proptools/pkg -I .bootstrap/blueprint/pkg -I .bootstrap/blueprint-deptools/pkg -I .bootstrap/blueprint-pathtools/pkg -I .bootstrap/blueprint-bootstrap/pkg -I .bootstrap/soong-glob/pkg -I .bootstrap/soong-common/pkg + pkgPath = android/soong/config +default .bootstrap/soong-config/pkg/android/soong/config.a + +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Module: soong-glob +# Variant: +# Type: bootstrap_go_package +# Factory: blueprint/bootstrap.newGoPackageModule +# Defined: build/soong/Blueprints:38:1 + +build .bootstrap/soong-glob/pkg/android/soong/glob.a: g.bootstrap.gc $ + ${g.bootstrap.srcDir}/build/soong/glob/glob.go | ${g.bootstrap.gcCmd} $ + .bootstrap/blueprint-deptools/pkg/blueprint/deptools.a + incFlags = -I .bootstrap/blueprint-deptools/pkg + pkgPath = android/soong/glob +default .bootstrap/soong-glob/pkg/android/soong/glob.a + +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Module: soong_build +# Variant: +# Type: bootstrap_go_binary +# Factory: blueprint/bootstrap.newGoBinaryModule +# Defined: build/soong/Blueprints:13:1 + +build .bootstrap/soong_build/obj/soong_build.a: g.bootstrap.gc $ + ${g.bootstrap.srcDir}/build/soong/cmd/soong_build/main.go | $ + ${g.bootstrap.gcCmd} $ + .bootstrap/blueprint-parser/pkg/blueprint/parser.a $ + .bootstrap/blueprint-proptools/pkg/blueprint/proptools.a $ + .bootstrap/blueprint/pkg/blueprint.a $ + .bootstrap/blueprint-deptools/pkg/blueprint/deptools.a $ + .bootstrap/blueprint-pathtools/pkg/blueprint/pathtools.a $ + .bootstrap/blueprint-bootstrap/pkg/blueprint/bootstrap.a $ + .bootstrap/soong-glob/pkg/android/soong/glob.a $ + .bootstrap/soong-common/pkg/android/soong/common.a $ + .bootstrap/soong-config/pkg/android/soong/config.a $ + .bootstrap/soong-cc/pkg/android/soong/cc.a + incFlags = -I .bootstrap/blueprint-parser/pkg -I .bootstrap/blueprint-proptools/pkg -I .bootstrap/blueprint/pkg -I .bootstrap/blueprint-deptools/pkg -I .bootstrap/blueprint-pathtools/pkg -I .bootstrap/blueprint-bootstrap/pkg -I .bootstrap/soong-glob/pkg -I .bootstrap/soong-common/pkg -I .bootstrap/soong-config/pkg -I .bootstrap/soong-cc/pkg + pkgPath = soong_build +default .bootstrap/soong_build/obj/soong_build.a + +build .bootstrap/soong_build/obj/a.out: g.bootstrap.link $ + .bootstrap/soong_build/obj/soong_build.a | ${g.bootstrap.linkCmd} + libDirFlags = -L .bootstrap/blueprint-parser/pkg -L .bootstrap/blueprint-proptools/pkg -L .bootstrap/blueprint/pkg -L .bootstrap/blueprint-deptools/pkg -L .bootstrap/blueprint-pathtools/pkg -L .bootstrap/blueprint-bootstrap/pkg -L .bootstrap/soong-glob/pkg -L .bootstrap/soong-common/pkg -L .bootstrap/soong-config/pkg -L .bootstrap/soong-cc/pkg +default .bootstrap/soong_build/obj/a.out + +build .bootstrap/bin/soong_build: g.bootstrap.cp $ + .bootstrap/soong_build/obj/a.out +default .bootstrap/bin/soong_build + +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# Module: soong_glob +# Variant: +# Type: bootstrap_go_binary +# Factory: blueprint/bootstrap.newGoBinaryModule +# Defined: build/soong/Blueprints:28:1 + +build .bootstrap/soong_glob/obj/soong_glob.a: g.bootstrap.gc $ + ${g.bootstrap.srcDir}/build/soong/cmd/soong_glob/soong_glob.go | $ + ${g.bootstrap.gcCmd} $ + .bootstrap/blueprint-deptools/pkg/blueprint/deptools.a $ + .bootstrap/soong-glob/pkg/android/soong/glob.a + incFlags = -I .bootstrap/blueprint-deptools/pkg -I .bootstrap/soong-glob/pkg + pkgPath = soong_glob +default .bootstrap/soong_glob/obj/soong_glob.a + +build .bootstrap/soong_glob/obj/a.out: g.bootstrap.link $ + .bootstrap/soong_glob/obj/soong_glob.a | ${g.bootstrap.linkCmd} + libDirFlags = -L .bootstrap/blueprint-deptools/pkg -L .bootstrap/soong-glob/pkg +default .bootstrap/soong_glob/obj/a.out + +build .bootstrap/bin/soong_glob: g.bootstrap.cp $ + .bootstrap/soong_glob/obj/a.out +default .bootstrap/bin/soong_glob + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Singleton: bootstrap # Factory: blueprint/bootstrap.newSingleton rule s.bootstrap.bigbp - command = .bootstrap/bin/minibp -p -d .bootstrap/main.ninja.in.d -m ${g.bootstrap.bootstrapManifest} -o ${out} ${in} + command = .bootstrap/bin/soong_build -d .bootstrap/main.ninja.in.d -m ${g.bootstrap.bootstrapManifest} -o ${out} ${in} depfile = .bootstrap/main.ninja.in.d - description = minibp ${out} + description = soong_build ${out} rule s.bootstrap.minibp command = .bootstrap/bin/minibp -c ${checkFile} -m ${g.bootstrap.bootstrapManifest} -d ${out}.d -o ${out} ${in} @@ -234,7 +392,8 @@ rule s.bootstrap.minibp build .bootstrap/main.ninja.in: s.bootstrap.bigbp $ ${g.bootstrap.srcDir}/Blueprints | .bootstrap/bin/bpfmt $ - .bootstrap/bin/bpmodify .bootstrap/bin/minibp + .bootstrap/bin/bpmodify .bootstrap/bin/minibp $ + .bootstrap/bin/soong_build .bootstrap/bin/soong_glob default .bootstrap/main.ninja.in build .bootstrap/notAFile: phony default .bootstrap/notAFile diff --git a/cc/arm64_device.go b/cc/arm64_device.go new file mode 100644 index 000000000..4b1598f50 --- /dev/null +++ b/cc/arm64_device.go @@ -0,0 +1,137 @@ +package cc + +import ( + "strings" + + "android/soong/common" +) + +var ( + arm64Cflags = []string{ + "-fno-exceptions", // from build/core/combo/select.mk + "-Wno-multichar", // from build/core/combo/select.mk + "-fno-strict-aliasing", + "-fstack-protector", + "-ffunction-sections", + "-fdata-sections", + "-funwind-tables", + "-Wa,--noexecstack", + "-Werror=format-security", + "-D_FORTIFY_SOURCE=2", + "-fno-short-enums", + "-no-canonical-prefixes", + "-fno-canonical-system-headers", + "-include ${SrcDir}/build/core/combo/include/arch/linux-arm64/AndroidConfig.h", + + // Help catch common 32/64-bit errors. + "-Werror=pointer-to-int-cast", + "-Werror=int-to-pointer-cast", + + "-fno-strict-volatile-bitfields", + + // TARGET_RELEASE_CFLAGS + "-DNDEBUG", + "-O2 -g", + "-Wstrict-aliasing=2", + "-fgcse-after-reload", + "-frerun-cse-after-loop", + "-frename-registers", + } + + arm64Ldflags = []string{ + "-Wl,-z,noexecstack", + "-Wl,-z,relro", + "-Wl,-z,now", + "-Wl,--build-id=md5", + "-Wl,--warn-shared-textrel", + "-Wl,--fatal-warnings", + "-Wl,-maarch64linux", + "-Wl,--hash-style=gnu", + + // Disable transitive dependency library symbol resolving. + "-Wl,--allow-shlib-undefined", + } + + arm64Cppflags = []string{ + "-fvisibility-inlines-hidden", + } +) + +func init() { + pctx.StaticVariable("arm64GccVersion", "4.9") + + pctx.StaticVariable("arm64GccRoot", + "${SrcDir}/prebuilts/gcc/${HostPrebuiltTag}/aarch64/aarch64-linux-android-${arm64GccVersion}") + + pctx.StaticVariable("arm64GccTriple", "aarch64-linux-android") + + pctx.StaticVariable("arm64Cflags", strings.Join(arm64Cflags, " ")) + pctx.StaticVariable("arm64Ldflags", strings.Join(arm64Ldflags, " ")) + pctx.StaticVariable("arm64Cppflags", strings.Join(arm64Cppflags, " ")) + pctx.StaticVariable("arm64IncludeFlags", strings.Join([]string{ + "-isystem ${LibcRoot}/arch-arm64/include", + "-isystem ${LibcRoot}/include", + "-isystem ${LibcRoot}/kernel/uapi", + "-isystem ${LibcRoot}/kernel/uapi/asm-arm64", + "-isystem ${LibmRoot}/include", + "-isystem ${LibmRoot}/include/arm64", + }, " ")) + + pctx.StaticVariable("arm64ClangCflags", strings.Join(clangFilterUnknownCflags(arm64Cflags), " ")) + pctx.StaticVariable("arm64ClangLdflags", strings.Join(clangFilterUnknownCflags(arm64Ldflags), " ")) + pctx.StaticVariable("arm64ClangCppflags", strings.Join(clangFilterUnknownCflags(arm64Cppflags), " ")) +} + +type toolchainArm64 struct { + toolchain64Bit +} + +var toolchainArm64Singleton = &toolchainArm64{} + +func (t *toolchainArm64) GccRoot() string { + return "${arm64GccRoot}" +} + +func (t *toolchainArm64) GccTriple() string { + return "${arm64GccTriple}" +} + +func (t *toolchainArm64) Cflags() string { + return "${arm64Cflags} ${arm64IncludeFlags}" +} + +func (t *toolchainArm64) Cppflags() string { + return "${arm64Cppflags}" +} + +func (t *toolchainArm64) Ldflags() string { + return "${arm64Ldflags}" +} + +func (t *toolchainArm64) IncludeFlags() string { + return "${arm64IncludeFlags}" +} + +func (t *toolchainArm64) ClangTriple() string { + return "${arm64GccTriple}" +} + +func (t *toolchainArm64) ClangCflags() string { + return "${arm64ClangCflags}" +} + +func (t *toolchainArm64) ClangCppflags() string { + return "${arm64ClangCppflags}" +} + +func (t *toolchainArm64) ClangLdflags() string { + return "${arm64Ldflags}" +} + +func arm64ToolchainFactory(archVariant string, cpuVariant string) toolchain { + return toolchainArm64Singleton +} + +func init() { + registerToolchainFactory(common.Device, common.Arm64, arm64ToolchainFactory) +} diff --git a/cc/arm_device.go b/cc/arm_device.go new file mode 100644 index 000000000..d07093504 --- /dev/null +++ b/cc/arm_device.go @@ -0,0 +1,301 @@ +package cc + +import ( + "fmt" + "strings" + + "android/soong/common" +) + +var ( + armCflags = []string{ + "-fno-exceptions", // from build/core/combo/select.mk + "-Wno-multichar", // from build/core/combo/select.mk + "-fno-strict-aliasing", + "-fstack-protector", + "-ffunction-sections", + "-fdata-sections", + "-funwind-tables", + "-fstack-protector", + "-Wa,--noexecstack", + "-Werror=format-security", + "-D_FORTIFY_SOURCE=2", + "-fno-short-enums", + "-no-canonical-prefixes", + "-fno-canonical-system-headers", + "-include ${SrcDir}/build/core/combo/include/arch/linux-arm/AndroidConfig.h", + + "-fno-builtin-sin", + "-fno-strict-volatile-bitfields", + + // TARGET_RELEASE_CFLAGS + "-DNDEBUG", + "-g", + "-Wstrict-aliasing=2", + "-fgcse-after-reload", + "-frerun-cse-after-loop", + "-frename-registers", + } + + armCppflags = []string{ + "-fvisibility-inlines-hidden", + } + + armLdflags = []string{ + "-Wl,-z,noexecstack", + "-Wl,-z,relro", + "-Wl,-z,now", + "-Wl,--build-id=md5", + "-Wl,--warn-shared-textrel", + "-Wl,--fatal-warnings", + "-Wl,-icf=safe", + "-Wl,--hash-style=gnu", + + // Disable transitive dependency library symbol resolving. + "-Wl,--allow-shlib-undefined", + } + + armArmCflags = []string{ + "-O2", + "-fomit-frame-pointer", + "-fstrict-aliasing", + "-funswitch-loops", + } + + armThumbCflags = []string{ + "-mthumb", + "-Os", + "-fomit-frame-pointer", + "-fno-strict-aliasing", + } + + armArchVariantCflags = map[string][]string{ + "armv5te": []string{ + "-march=armv5te", + "-mtune=xscale", + "-D__ARM_ARCH_5__", + "-D__ARM_ARCH_5T__", + "-D__ARM_ARCH_5E__", + "-D__ARM_ARCH_5TE__", + }, + "armv7-a": []string{ + "-march=armv7-a", + "-mfloat-abi=softfp", + "-mfpu=vfpv3-d16", + }, + "armv7-a-neon": []string{ + "-mfloat-abi=softfp", + "-mfpu=neon", + }, + } + + armArchVariantLdflags = map[string][]string{ + "armv7-a": []string{ + "-Wl,--fix-cortex-a8", + }, + } + + armCpuVariantCflags = map[string][]string{ + "cortex-a7": []string{ + "-mcpu=cortex-a7", + }, + "cortex-a8": []string{ + "-mcpu=cortex-a8", + }, + "cortex-a15": []string{ + "-mcpu=cortex-a15", + // Fake an ARM compiler flag as these processors support LPAE which GCC/clang + // don't advertise. + "-D__ARM_FEATURE_LPAE=1", + }, + } + + armClangCpuVariantCflags = armCpuVariantCflags + armClangArchVariantCflags = armArchVariantCflags +) + +func init() { + replaceFirst := func(slice []string, from, to string) { + if slice[0] != from { + panic(fmt.Errorf("Expected %q, found %q", from, to)) + } + + slice[0] = to + } + + replaceFirst(armClangArchVariantCflags["armv5te"], "-march=armv5te", "-march=armv5t") + replaceFirst(armClangCpuVariantCflags["cortex-a15"], "-mcpu=cortex-a15", "-march=armv7-a") + armClangCpuVariantCflags["krait"] = []string{ + "-mcpu=krait", + } + + pctx.StaticVariable("armGccVersion", "4.9") + + pctx.StaticVariable("armGccRoot", + "${SrcDir}/prebuilts/gcc/${HostPrebuiltTag}/arm/arm-linux-androideabi-${armGccVersion}") + + pctx.StaticVariable("armGccTriple", "arm-linux-androideabi") + + pctx.StaticVariable("armCflags", strings.Join(armCflags, " ")) + pctx.StaticVariable("armLdflags", strings.Join(armLdflags, " ")) + pctx.StaticVariable("armCppflags", strings.Join(armCppflags, " ")) + pctx.StaticVariable("armIncludeFlags", strings.Join([]string{ + "-isystem ${LibcRoot}/arch-arm/include", + "-isystem ${LibcRoot}/include", + "-isystem ${LibcRoot}/kernel/uapi", + "-isystem ${LibcRoot}/kernel/uapi/asm-arm", + "-isystem ${LibmRoot}/include", + "-isystem ${LibmRoot}/include/arm", + }, " ")) + + // Extended cflags + + // ARM mode vs. Thumb mode + pctx.StaticVariable("armArmCflags", strings.Join(armArmCflags, " ")) + pctx.StaticVariable("armThumbCflags", strings.Join(armThumbCflags, " ")) + + // Architecture variant cflags + pctx.StaticVariable("armArmv5TECflags", strings.Join(armArchVariantCflags["armv5te"], " ")) + pctx.StaticVariable("armArmv7ACflags", strings.Join(armArchVariantCflags["armv7-a"], " ")) + pctx.StaticVariable("armArmv7ANeonCflags", strings.Join(armArchVariantCflags["armv7-a-neon"], " ")) + + // Architecture variant ldflags + pctx.StaticVariable("armArmv7ALdflags", strings.Join(armArchVariantLdflags["armv7-a"], " ")) + + // Cpu variant cflags + pctx.StaticVariable("armCortexA7Cflags", strings.Join(armCpuVariantCflags["cortex-a7"], " ")) + pctx.StaticVariable("armCortexA8Cflags", strings.Join(armCpuVariantCflags["cortex-a8"], " ")) + pctx.StaticVariable("armCortexA15Cflags", strings.Join(armCpuVariantCflags["cortex-a15"], " ")) + + // Clang cflags + pctx.StaticVariable("armClangCflags", strings.Join(clangFilterUnknownCflags(armCflags), " ")) + pctx.StaticVariable("armClangLdflags", strings.Join(clangFilterUnknownCflags(armLdflags), " ")) + pctx.StaticVariable("armClangCppflags", strings.Join(clangFilterUnknownCflags(armCppflags), " ")) + + // Clang cpu variant cflags + pctx.StaticVariable("armClangArmv5TECflags", + strings.Join(armClangArchVariantCflags["armv5te"], " ")) + pctx.StaticVariable("armClangArmv7ACflags", + strings.Join(armClangArchVariantCflags["armv7-a"], " ")) + pctx.StaticVariable("armClangArmv7ANeonCflags", + strings.Join(armClangArchVariantCflags["armv7-a-neon"], " ")) + + // Clang cpu variant cflags + pctx.StaticVariable("armClangCortexA7Cflags", + strings.Join(armClangCpuVariantCflags["cortex-a7"], " ")) + pctx.StaticVariable("armClangCortexA8Cflags", + strings.Join(armClangCpuVariantCflags["cortex-a8"], " ")) + pctx.StaticVariable("armClangCortexA15Cflags", + strings.Join(armClangCpuVariantCflags["cortex-a15"], " ")) + pctx.StaticVariable("armClangKraitCflags", + strings.Join(armClangCpuVariantCflags["krait"], " ")) +} + +var ( + armArchVariantCflagsVar = map[string]string{ + "armv5te": "${armArmv5TECflags}", + "armv7-a": "${armArmv7ACflags}", + "armv7-a-neon": "${armArmv7ANeonCflags}", + } + + armArchVariantLdflagsVar = map[string]string{ + "armv7-a": "${armArmv7ALdflags}", + "armv7-a-neon": "${armArmv7ALdflags}", + } + + armCpuVariantCflagsVar = map[string]string{ + "": "", + "cortex-a7": "${armCortexA7Cflags}", + "cortex-a8": "${armCortexA8Cflags}", + "cortex-a15": "${armCortexA15Cflags}", + "krait": "${armCortexA15Cflags}", + "denver": "${armCortexA15Cflags}", + } + + armClangArchVariantCflagsVar = map[string]string{ + "armv5te": "${armClangArmv5TECflags}", + "armv7-a": "${armClangArmv7ACflags}", + "armv7-a-neon": "${armClangArmv7ANeonCflags}", + } + + armClangCpuVariantCflagsVar = map[string]string{ + "": "", + "cortex-a7": "${armClangCortexA7Cflags}", + "cortex-a8": "${armClangCortexA8Cflags}", + "cortex-a15": "${armClangCortexA15Cflags}", + "krait": "${armClangKraitCflags}", + "denver": "${armClangCortexA15Cflags}", + } +) + +type toolchainArm struct { + toolchain32Bit + cflags, ldflags, clangCflags string +} + +func (t *toolchainArm) GccRoot() string { + return "${armGccRoot}" +} + +func (t *toolchainArm) GccTriple() string { + return "${armGccTriple}" +} + +func (t *toolchainArm) Cflags() string { + return t.cflags +} + +func (t *toolchainArm) Cppflags() string { + return "${armCppflags}" +} + +func (t *toolchainArm) Ldflags() string { + return t.ldflags +} + +func (t *toolchainArm) IncludeFlags() string { + return "${armIncludeFlags}" +} + +func (t *toolchainArm) ClangTriple() string { + return "${armGccTriple}" +} + +func (t *toolchainArm) ClangCflags() string { + return t.clangCflags +} + +func (t *toolchainArm) ClangCppflags() string { + return "${armClangCppflags}" +} + +func (t *toolchainArm) ClangLdflags() string { + return t.ldflags +} + +func armToolchainFactory(archVariant string, cpuVariant string) toolchain { + return &toolchainArm{ + cflags: strings.Join([]string{ + "${armCflags}", + "${armIncludeFlags}", + "${armThumbCflags}", + armArchVariantCflagsVar[archVariant], + armCpuVariantCflagsVar[cpuVariant], + }, " "), + ldflags: strings.Join([]string{ + "${armLdflags}", + armArchVariantLdflagsVar[archVariant], + }, " "), + clangCflags: strings.Join([]string{ + "${armClangCflags}", + "${armIncludeFlags}", + "${armThumbCflags}", + armClangArchVariantCflagsVar[archVariant], + armClangCpuVariantCflagsVar[cpuVariant], + }, " "), + } +} + +func init() { + registerToolchainFactory(common.Device, common.Arm, armToolchainFactory) +} diff --git a/cc/builder.go b/cc/builder.go new file mode 100644 index 000000000..b61d6727c --- /dev/null +++ b/cc/builder.go @@ -0,0 +1,285 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// 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. + +package cc + +// This file generates the final rules for compiling all C/C++. All properties related to +// compiling should have been translated into builderFlags or another argument to the Transform* +// functions. + +import ( + "android/soong/common" + + "blueprint" + "blueprint/pathtools" + "path/filepath" + "strings" +) + +const ( + sharedLibraryExtension = ".so" + staticLibraryExtension = ".a" +) + +var ( + pctx = blueprint.NewPackageContext("android/soong/cc") + + cc = pctx.StaticRule("cc", + blueprint.RuleParams{ + Depfile: "${out}.d", + Deps: blueprint.DepsGCC, + Command: "$ccCmd $incFlags -c $cFlags -MD -MF ${out}.d -o $out $in", + Description: "cc $out", + }, + "ccCmd", "incFlags", "cFlags") + + ld = pctx.StaticRule("ld", + blueprint.RuleParams{ + Command: "$ldCmd ${ldDirFlags} ${crtBegin} ${in} " + + "${libFlags} ${crtEnd} -o ${out} ${ldFlags} ${ldLibs}", + Description: "ld $out", + }, + "ldCmd", "ldDirFlags", "crtBegin", "libFlags", "crtEnd", "ldFlags", "ldLibs") + + partialLd = pctx.StaticRule("partialLd", + blueprint.RuleParams{ + Command: "$ldCmd -r ${in} -o ${out}", + Description: "partialLd $out", + }, + "ldCmd") + + ar = pctx.StaticRule("ar", + blueprint.RuleParams{ + Command: "rm -f ${out} && $arCmd $arFlags $out $in", + Description: "ar $out", + }, + "arCmd", "arFlags") + + copyGccLibPath = pctx.StaticVariable("copyGccLibPath", "${SrcDir}/build/soong/copygcclib.sh") + + copyGccLib = pctx.StaticRule("copyGccLib", + blueprint.RuleParams{ + Depfile: "${out}.d", + Deps: blueprint.DepsGCC, + Command: "$copyGccLibPath $out $ccCmd $cFlags -print-file-name=${libName}", + Description: "copy gcc $out", + }, + "ccCmd", "cFlags", "libName") +) + +type builderFlags struct { + globalFlags string + asFlags string + cFlags string + conlyFlags string + cppFlags string + ldFlags string + ldLibs string + incFlags string + nocrt bool + toolchain toolchain + clang bool +} + +// Generate rules for compiling multiple .c, .cpp, or .S files to individual .o files +func TransformSourceToObj(ctx common.AndroidModuleContext, subdir string, srcFiles []string, + flags builderFlags) (objFiles []string) { + + objFiles = make([]string, len(srcFiles)) + objDir := common.ModuleObjDir(ctx) + if subdir != "" { + objDir = filepath.Join(objDir, subdir) + } + + cflags := flags.globalFlags + " " + flags.cFlags + " " + flags.conlyFlags + cppflags := flags.globalFlags + " " + flags.cFlags + " " + flags.cppFlags + asflags := flags.globalFlags + " " + flags.asFlags + + for i, srcFile := range srcFiles { + objFile := strings.TrimPrefix(srcFile, common.ModuleSrcDir(ctx)) + objFile = filepath.Join(objDir, objFile) + objFile = pathtools.ReplaceExtension(objFile, "o") + + objFiles[i] = objFile + + var moduleCflags string + var ccCmd string + + switch filepath.Ext(srcFile) { + case ".S", ".s": + ccCmd = "gcc" + moduleCflags = asflags + case ".c": + ccCmd = "gcc" + moduleCflags = cflags + case ".cpp", ".cc": + ccCmd = "g++" + moduleCflags = cppflags + default: + ctx.ModuleErrorf("File %s has unknown extension", srcFile) + continue + } + + if flags.clang { + switch ccCmd { + case "gcc": + ccCmd = "clang" + case "g++": + ccCmd = "clang++" + default: + panic("unrecoginzied ccCmd") + } + + ccCmd = "${clangPath}" + ccCmd + } else { + ccCmd = gccCmd(flags.toolchain, ccCmd) + } + + ctx.Build(pctx, blueprint.BuildParams{ + Rule: cc, + Outputs: []string{objFile}, + Inputs: []string{srcFile}, + Implicits: []string{ccCmd}, + Args: map[string]string{ + "cFlags": moduleCflags, + "incFlags": flags.incFlags, + "ccCmd": ccCmd, + }, + }) + } + + return objFiles +} + +// Generate a rule for compiling multiple .o files to a static library (.a) +func TransformObjToStaticLib(ctx common.AndroidModuleContext, objFiles []string, + flags builderFlags, outputFile string) { + + arCmd := gccCmd(flags.toolchain, "ar") + arFlags := "crsPD" + + ctx.Build(pctx, blueprint.BuildParams{ + Rule: ar, + Outputs: []string{outputFile}, + Inputs: objFiles, + Implicits: []string{arCmd}, + Args: map[string]string{ + "arFlags": arFlags, + "arCmd": arCmd, + }, + }) +} + +// Generate a rule for compiling multiple .o files, plus static libraries, whole static libraries, +// and shared libraires, to a shared library (.so) or dynamic executable +func TransformObjToDynamicBinary(ctx common.AndroidModuleContext, + objFiles, sharedLibs, staticLibs, wholeStaticLibs []string, crtBegin, crtEnd string, + flags builderFlags, outputFile string) { + + var ldCmd string + if flags.clang { + ldCmd = "${clangPath}clang++" + } else { + ldCmd = gccCmd(flags.toolchain, "g++") + } + + var ldDirs []string + var libFlagsList []string + + if len(wholeStaticLibs) > 0 { + libFlagsList = append(libFlagsList, "-Wl,--whole-archive ") + libFlagsList = append(libFlagsList, wholeStaticLibs...) + libFlagsList = append(libFlagsList, "-Wl,--no-whole-archive ") + } + + libFlagsList = append(libFlagsList, staticLibs...) + + for _, lib := range sharedLibs { + dir, file := filepath.Split(lib) + if !strings.HasPrefix(file, "lib") { + panic("shared library " + lib + " does not start with lib") + } + if !strings.HasSuffix(file, sharedLibraryExtension) { + panic("shared library " + lib + " does not end with " + sharedLibraryExtension) + } + libFlagsList = append(libFlagsList, + "-l"+strings.TrimSuffix(strings.TrimPrefix(file, "lib"), sharedLibraryExtension)) + ldDirs = append(ldDirs, dir) + } + + deps := []string{ldCmd} + deps = append(deps, sharedLibs...) + deps = append(deps, staticLibs...) + deps = append(deps, wholeStaticLibs...) + if crtBegin != "" { + deps = append(deps, crtBegin, crtEnd) + } + + ctx.Build(pctx, blueprint.BuildParams{ + Rule: ld, + Outputs: []string{outputFile}, + Inputs: objFiles, + Implicits: deps, + Args: map[string]string{ + "ldCmd": ldCmd, + "ldDirFlags": ldDirsToFlags(ldDirs), + "crtBegin": crtBegin, + "libFlags": strings.Join(libFlagsList, " "), + "ldFlags": flags.ldFlags, + "crtEnd": crtEnd, + "ldLibs": flags.ldLibs, + }, + }) +} + +// Generate a rule for compiling multiple .o files to a .o using ld partial linking +func TransformObjsToObj(ctx common.AndroidModuleContext, objFiles []string, + flags builderFlags, outputFile string) { + + ldCmd := gccCmd(flags.toolchain, "ld") + + deps := []string{ldCmd} + + ctx.Build(pctx, blueprint.BuildParams{ + Rule: partialLd, + Outputs: []string{outputFile}, + Inputs: objFiles, + Implicits: deps, + Args: map[string]string{ + "ldCmd": ldCmd, + }, + }) +} + +func CopyGccLib(ctx common.AndroidModuleContext, libName string, + flags builderFlags, outputFile string) { + + ctx.Build(pctx, blueprint.BuildParams{ + Rule: copyGccLib, + Outputs: []string{outputFile}, + Implicits: []string{ + "$copyGccLibPath", + gccCmd(flags.toolchain, "gcc"), + }, + Args: map[string]string{ + "ccCmd": gccCmd(flags.toolchain, "gcc"), + "cFlags": flags.globalFlags, + "libName": libName, + }, + }) +} + +func gccCmd(toolchain toolchain, cmd string) string { + return filepath.Join(toolchain.GccRoot(), "bin", toolchain.GccTriple()+"-"+cmd) +} diff --git a/cc/cc.go b/cc/cc.go new file mode 100644 index 000000000..641d67f79 --- /dev/null +++ b/cc/cc.go @@ -0,0 +1,1194 @@ +// +// 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. + +package cc + +// This file contains the module types for compiling C/C++ for Android, and converts the properties +// into the flags and filenames necessary to pass to the compiler. The final creation of the rules +// is handled in builder.go + +import ( + "blueprint" + "blueprint/pathtools" + "fmt" + "path/filepath" + "strings" + + "android/soong/common" +) + +type Config interface { + SrcDir() string + PrebuiltOS() string +} + +var ( + HostPrebuiltTag = pctx.VariableConfigMethod("HostPrebuiltTag", Config.PrebuiltOS) + SrcDir = pctx.VariableConfigMethod("SrcDir", Config.SrcDir) + + LibcRoot = pctx.StaticVariable("LibcRoot", "${SrcDir}/bionic/libc") + LibmRoot = pctx.StaticVariable("LibmRoot", "${SrcDir}/bionic/libm") +) + +// Flags used by lots of devices. Putting them in package static variables will save bytes in +// build.ninja so they aren't repeated for every file +var ( + commonGlobalCflags = []string{ + "-DANDROID", + "-fmessage-length=0", + "-W", + "-Wall", + "-Wno-unused", + "-Winit-self", + "-Wpointer-arith", + + // COMMON_RELEASE_CFLAGS + "-DNDEBUG", + "-UDEBUG", + } + + deviceGlobalCflags = []string{ + // TARGET_ERROR_FLAGS + "-Werror=return-type", + "-Werror=non-virtual-dtor", + "-Werror=address", + "-Werror=sequence-point", + } + + hostGlobalCflags = []string{} + + commonGlobalCppflags = []string{ + "-Wsign-promo", + "-std=gnu++11", + } +) + +func init() { + pctx.StaticVariable("commonGlobalCflags", strings.Join(commonGlobalCflags, " ")) + pctx.StaticVariable("deviceGlobalCflags", strings.Join(deviceGlobalCflags, " ")) + pctx.StaticVariable("hostGlobalCflags", strings.Join(hostGlobalCflags, " ")) + + pctx.StaticVariable("commonGlobalCppflags", strings.Join(commonGlobalCppflags, " ")) + + pctx.StaticVariable("commonClangGlobalCflags", + strings.Join(clangFilterUnknownCflags(commonGlobalCflags), " ")) + pctx.StaticVariable("deviceClangGlobalCflags", + strings.Join(clangFilterUnknownCflags(deviceGlobalCflags), " ")) + pctx.StaticVariable("hostClangGlobalCflags", + strings.Join(clangFilterUnknownCflags(hostGlobalCflags), " ")) + + // Everything in this list is a crime against abstraction and dependency tracking. + // Do not add anything to this list. + pctx.StaticVariable("commonGlobalIncludes", strings.Join([]string{ + "-isystem ${SrcDir}/system/core/include", + "-isystem ${SrcDir}/hardware/libhardware/include", + "-isystem ${SrcDir}/hardware/libhardware_legacy/include", + "-isystem ${SrcDir}/hardware/ril/include", + "-isystem ${SrcDir}/libnativehelper/include", + "-isystem ${SrcDir}/frameworks/native/include", + "-isystem ${SrcDir}/frameworks/native/opengl/include", + "-isystem ${SrcDir}/frameworks/av/include", + "-isystem ${SrcDir}/frameworks/base/include", + }, " ")) + + pctx.StaticVariable("clangPath", "${SrcDir}/prebuilts/clang/${HostPrebuiltTag}/host/3.6/bin/") +} + +// CcProperties describes properties used to compile all C or C++ modules +type ccProperties struct { + // srcs: list of source files used to compile the C/C++ module. May be .c, .cpp, or .S files. + Srcs []string `android:"arch_variant,arch_subtract"` + + // cflags: list of module-specific flags that will be used for C and C++ compiles. + Cflags []string `android:"arch_variant"` + + // cppflags: list of module-specific flags that will be used for C++ compiles + Cppflags []string `android:"arch_variant"` + + // conlyflags: list of module-specific flags that will be used for C compiles + Conlyflags []string `android:"arch_variant"` + + // asflags: list of module-specific flags that will be used for .S compiles + Asflags []string `android:"arch_variant"` + + // ldflags: list of module-specific flags that will be used for all link steps + Ldflags []string `android:"arch_variant"` + + // include_dirs: list of directories relative to the root of the source tree that will + // be added to the include path using -I. + // If possible, don't use this. If adding paths from the current directory use + // local_include_dirs, if adding paths from other modules use export_include_dirs in + // that module. + Include_dirs []string `android:"arch_variant"` + + // local_include_dirs: list of directories relative to the Blueprints file that will + // be added to the include path using -I + Local_include_dirs []string `android:"arch_variant"` + + // export_include_dirs: list of directories relative to the Blueprints file that will + // be added to the include path using -I for any module that links against this module + Export_include_dirs []string + + // clang_cflags: list of module-specific flags that will be used for C and C++ compiles when + // compiling with clang + Clang_cflags []string `android:"arch_variant"` + + // clang_asflags: list of module-specific flags that will be used for .S compiles when + // compiling with clang + Clang_asflags []string `android:"arch_variant"` + + // system_shared_libs: list of system libraries that will be dynamically linked to + // shared library and executable modules. If unset, generally defaults to libc + // and libm. Set to [] to prevent linking against libc and libm. + System_shared_libs []string + + // whole_static_libs: list of modules whose object files should be linked into this module + // in their entirety. For static library modules, all of the .o files from the intermediate + // directory of the dependency will be linked into this modules .a file. For a shared library, + // the dependency's .a file will be linked into this module using -Wl,--whole-archive. + Whole_static_libs []string `android:"arch_variant"` + + // static_libs: list of modules that should be statically linked into this module. + Static_libs []string `android:"arch_variant"` + + // shared_libs: list of modules that should be dynamically linked into this module. + Shared_libs []string `android:"arch_variant"` + + // allow_undefined_symbols: allow the module to contain undefined symbols. By default, + // modules cannot contain undefined symbols that are not satisified by their immediate + // dependencies. Set this flag to true to remove --no-undefined from the linker flags. + // This flag should only be necessary for compiling low-level libraries like libc. + Allow_undefined_symbols bool + + // nocrt: don't link in crt_begin and crt_end. This flag should only be necessary for + // compiling crt or libc. + Nocrt bool `android:"arch_variant"` + + // no_default_compiler_flags: don't insert default compiler flags into asflags, cflags, + // cppflags, conlyflags, ldflags, or include_dirs + No_default_compiler_flags bool + + // clang: compile module with clang instead of gcc + Clang bool `android:"arch_variant"` + + // rtti: pass -frtti instead of -fno-rtti + Rtti bool + + // host_ldlibs: -l arguments to pass to linker for host-provided shared libraries + Host_ldlibs []string `android:"arch_variant"` + + // stl: select the STL library to use. Possible values are "libc++", "libc++_static", + // "stlport", "stlport_static", "ndk", "libstdc++", or "none". Leave blank to select the + // default + Stl string + + // Set for combined shared/static libraries to prevent compiling object files a second time + SkipCompileObjs bool `blueprint:"mutated"` +} + +type unusedProperties struct { + Asan bool + Native_coverage bool + Strip string + Tags []string + Required []string +} + +// Building C/C++ code is handled by objects that satisfy this interface via composition +type ccModuleType interface { + common.AndroidModule + + // Return the cflags that are specific to this _type_ of module + moduleTypeCflags(common.AndroidModuleContext, toolchain) []string + + // Return the ldflags that are specific to this _type_ of module + moduleTypeLdflags(common.AndroidModuleContext, toolchain) []string + + // Create a ccDeps struct that collects the module dependency info. Can also + // modify ccFlags in order to add dependency include directories, etc. + collectDeps(common.AndroidModuleContext, ccFlags) (ccDeps, ccFlags) + + // Compile objects into final module + compileModule(common.AndroidModuleContext, ccFlags, ccDeps, []string) + + // Return the output file (.o, .a or .so) for use by other modules + outputFile() string +} + +func (c *ccBase) GenerateAndroidBuildActions(ctx common.AndroidModuleContext) { + toolchain := c.findToolchain(ctx) + if ctx.Failed() { + return + } + + flags := c.flags(ctx, toolchain) + if ctx.Failed() { + return + } + + flags = c.addStlFlags(ctx, flags) + if ctx.Failed() { + return + } + + deps, flags := c.ccModuleType().collectDeps(ctx, flags) + if ctx.Failed() { + return + } + + objFiles := c.compileObjs(ctx, flags, deps) + if ctx.Failed() { + return + } + + c.ccModuleType().compileModule(ctx, flags, deps, objFiles) + if ctx.Failed() { + return + } +} + +func (c *ccBase) ccModuleType() ccModuleType { + return c.module +} + +var _ common.AndroidDynamicDepender = (*ccBase)(nil) + +func (c *ccBase) findToolchain(ctx common.AndroidModuleContext) toolchain { + arch := ctx.Arch() + factory := toolchainFactories[arch.HostOrDevice][arch.ArchType] + if factory == nil { + panic(fmt.Sprintf("Toolchain not found for %s arch %q", + arch.HostOrDevice.String(), arch.String())) + } + return factory(arch.ArchVariant, arch.CpuVariant) +} + +type ccDeps struct { + staticLibs, sharedLibs, wholeStaticLibs, objFiles, includeDirs []string + + crtBegin, crtEnd string +} + +type ccFlags struct { + globalFlags []string + asFlags []string + cFlags []string + conlyFlags []string + cppFlags []string + ldFlags []string + ldLibs []string + includeDirs []string + nocrt bool + toolchain toolchain + clang bool + + extraStaticLibs []string + extraSharedLibs []string +} + +// ccBase contains the properties and members used by all C/C++ module types +type ccBase struct { + common.AndroidModuleBase + module ccModuleType + + properties ccProperties + unused unusedProperties + + installPath string +} + +func (c *ccBase) moduleTypeCflags(ctx common.AndroidModuleContext, toolchain toolchain) []string { + return nil +} + +func (c *ccBase) moduleTypeLdflags(ctx common.AndroidModuleContext, toolchain toolchain) []string { + return nil +} + +func (c *ccBase) AndroidDynamicDependencies(ctx common.AndroidDynamicDependerModuleContext) []string { + ctx.AddVariationDependencies([]blueprint.Variation{{"link", "static"}}, c.properties.Whole_static_libs...) + ctx.AddVariationDependencies([]blueprint.Variation{{"link", "static"}}, c.properties.Static_libs...) + ctx.AddVariationDependencies([]blueprint.Variation{{"link", "shared"}}, c.properties.Shared_libs...) + + return nil +} + +// Create a ccFlags struct that collects the compile flags from global values, +// per-target values, module type values, and per-module Blueprints properties +func (c *ccBase) flags(ctx common.AndroidModuleContext, toolchain toolchain) ccFlags { + + arch := ctx.Arch() + + flags := ccFlags{ + cFlags: c.properties.Cflags, + cppFlags: c.properties.Cppflags, + conlyFlags: c.properties.Conlyflags, + ldFlags: c.properties.Ldflags, + asFlags: c.properties.Asflags, + nocrt: c.properties.Nocrt, + toolchain: toolchain, + clang: c.properties.Clang, + } + + if arch.HostOrDevice.Host() { + // TODO: allow per-module clang disable for host + flags.clang = true + } + + if flags.clang { + flags.cFlags = clangFilterUnknownCflags(flags.cFlags) + flags.cFlags = append(flags.cFlags, c.properties.Clang_cflags...) + flags.asFlags = append(flags.asFlags, c.properties.Clang_asflags...) + flags.cppFlags = clangFilterUnknownCflags(flags.cppFlags) + flags.conlyFlags = clangFilterUnknownCflags(flags.conlyFlags) + flags.ldFlags = clangFilterUnknownCflags(flags.ldFlags) + + target := "-target " + toolchain.ClangTriple() + gccPrefix := "-B" + filepath.Join(toolchain.GccRoot(), toolchain.GccTriple(), "bin") + + flags.cFlags = append(flags.cFlags, target, gccPrefix) + flags.asFlags = append(flags.asFlags, target, gccPrefix) + flags.ldFlags = append(flags.ldFlags, target, gccPrefix) + + if arch.HostOrDevice.Host() { + gccToolchain := "--gcc-toolchain=" + toolchain.GccRoot() + sysroot := "--sysroot=" + filepath.Join(toolchain.GccRoot(), "sysroot") + + // TODO: also need more -B, -L flags to make host builds hermetic + flags.cFlags = append(flags.cFlags, gccToolchain, sysroot) + flags.asFlags = append(flags.asFlags, gccToolchain, sysroot) + flags.ldFlags = append(flags.ldFlags, gccToolchain, sysroot) + } + } + + flags.includeDirs = pathtools.PrefixPaths(c.properties.Include_dirs, ctx.Config().(Config).SrcDir()) + localIncludeDirs := pathtools.PrefixPaths(c.properties.Local_include_dirs, common.ModuleSrcDir(ctx)) + flags.includeDirs = append(flags.includeDirs, localIncludeDirs...) + + if !c.properties.No_default_compiler_flags { + flags.includeDirs = append(flags.includeDirs, []string{ + common.ModuleSrcDir(ctx), + common.ModuleOutDir(ctx), + common.ModuleGenDir(ctx), + }...) + + if arch.HostOrDevice.Device() && !c.properties.Allow_undefined_symbols { + flags.ldFlags = append(flags.ldFlags, "-Wl,--no-undefined") + } + + if flags.clang { + flags.globalFlags = []string{ + "${commonGlobalIncludes}", + toolchain.IncludeFlags(), + toolchain.ClangCflags(), + "${commonClangGlobalCflags}", + fmt.Sprintf("${%sClangGlobalCflags}", arch.HostOrDevice), + } + } else { + flags.globalFlags = []string{ + "${commonGlobalIncludes}", + toolchain.IncludeFlags(), + toolchain.Cflags(), + "${commonGlobalCflags}", + fmt.Sprintf("${%sGlobalCflags}", arch.HostOrDevice), + } + } + + if arch.HostOrDevice.Host() { + flags.ldFlags = append(flags.ldFlags, c.properties.Host_ldlibs...) + } + + if arch.HostOrDevice.Device() { + if c.properties.Rtti { + flags.cppFlags = append(flags.cppFlags, "-frtti") + } else { + flags.cppFlags = append(flags.cppFlags, "-fno-rtti") + } + } + + flags.asFlags = append(flags.asFlags, "-D__ASSEMBLY__") + + if flags.clang { + flags.cppFlags = append(flags.cppFlags, toolchain.ClangCppflags()) + flags.ldFlags = append(flags.ldFlags, toolchain.ClangLdflags()) + } else { + flags.cppFlags = append(flags.cppFlags, toolchain.Cppflags()) + flags.ldFlags = append(flags.ldFlags, toolchain.Ldflags()) + } + } + + flags.cFlags = append(flags.cFlags, c.ccModuleType().moduleTypeCflags(ctx, toolchain)...) + flags.ldFlags = append(flags.ldFlags, c.ccModuleType().moduleTypeLdflags(ctx, toolchain)...) + + // Optimization to reduce size of build.ninja + // Replace the long list of flags for each file with a module-local variable + ctx.Variable(pctx, "cflags", strings.Join(flags.cFlags, " ")) + ctx.Variable(pctx, "cppflags", strings.Join(flags.cppFlags, " ")) + ctx.Variable(pctx, "asflags", strings.Join(flags.asFlags, " ")) + flags.cFlags = []string{"$cflags"} + flags.cppFlags = []string{"$cppflags"} + flags.asFlags = []string{"$asflags"} + + return flags +} + +// Modify ccFlags structs with STL library info +func (c *ccBase) addStlFlags(ctx common.AndroidModuleContext, flags ccFlags) ccFlags { + if !c.properties.No_default_compiler_flags { + arch := ctx.Arch() + stl := "libc++" // TODO: mingw needs libstdc++ + if c.properties.Stl != "" { + stl = c.properties.Stl + } + + stlStatic := false + if strings.HasSuffix(stl, "_static") { + stlStatic = true + } + + switch stl { + case "libc++", "libc++_static": + flags.cFlags = append(flags.cFlags, "-D_USING_LIBCXX") + flags.includeDirs = append(flags.includeDirs, "${SrcDir}/external/libcxx/include") + if arch.HostOrDevice.Host() { + flags.cppFlags = append(flags.cppFlags, "-nostdinc++") + flags.ldFlags = append(flags.ldFlags, "-nodefaultlibs") + flags.ldLibs = append(flags.ldLibs, "-lc", "-lm", "-lpthread") + } + if stlStatic { + flags.extraStaticLibs = append(flags.extraStaticLibs, "libc++_static") + } else { + flags.extraSharedLibs = append(flags.extraSharedLibs, "libc++") + } + case "stlport", "stlport_static": + if arch.HostOrDevice.Device() { + flags.includeDirs = append(flags.includeDirs, + "${SrcDir}/external/stlport/stlport", + "${SrcDir}/bionic/libstdc++/include", + "${SrcDir}/bionic") + if stlStatic { + flags.extraStaticLibs = append(flags.extraStaticLibs, "libstdc++", "libstlport_static") + } else { + flags.extraSharedLibs = append(flags.extraSharedLibs, "libstdc++", "libstlport") + } + } + case "ndk": + panic("TODO") + case "libstdc++": + // Using bionic's basic libstdc++. Not actually an STL. Only around until the + // tree is in good enough shape to not need it. + // Host builds will use GNU libstdc++. + if arch.HostOrDevice.Device() { + flags.includeDirs = append(flags.includeDirs, "${SrcDir}/bionic/libstdc++/include") + flags.extraSharedLibs = append(flags.extraSharedLibs, "libstdc++") + } + case "none": + if arch.HostOrDevice.Host() { + flags.cppFlags = append(flags.cppFlags, "-nostdinc++") + flags.ldFlags = append(flags.ldFlags, "-nodefaultlibs") + flags.ldLibs = append(flags.ldLibs, "-lc", "-lm") + } + default: + ctx.ModuleErrorf("stl: %q is not a supported STL", stl) + } + + } + return flags +} + +// Compile a list of source files into objects a specified subdirectory +func (c *ccBase) customCompileObjs(ctx common.AndroidModuleContext, flags ccFlags, + deps ccDeps, subdir string, srcFiles []string) []string { + + srcFiles = pathtools.PrefixPaths(srcFiles, common.ModuleSrcDir(ctx)) + srcFiles = common.ExpandGlobs(ctx, srcFiles) + + return TransformSourceToObj(ctx, subdir, srcFiles, ccFlagsToBuilderFlags(flags)) +} + +// Compile files listed in c.properties.Srcs into objects +func (c *ccBase) compileObjs(ctx common.AndroidModuleContext, flags ccFlags, + deps ccDeps) []string { + + if c.properties.SkipCompileObjs { + return nil + } + + return c.customCompileObjs(ctx, flags, deps, "", c.properties.Srcs) +} + +func (c *ccBase) outputFile() string { + return "" +} + +func (c *ccBase) collectDepsFromList(ctx common.AndroidModuleContext, + names []string) (modules []common.AndroidModule, + outputFiles []string, exportedIncludeDirs []string) { + + for _, n := range names { + found := false + ctx.VisitDirectDeps(func(m blueprint.Module) { + otherName := ctx.OtherModuleName(m) + if otherName != n { + return + } + + if a, ok := m.(ccModuleType); ok { + if a.Disabled() { + // If a cc_library host+device module depends on a library that exists as both + // cc_library_shared and cc_library_host_shared, it will end up with two + // dependencies with the same name, one of which is marked disabled for each + // of host and device. Ignore the disabled one. + return + } + if a.HostOrDevice() != ctx.Arch().HostOrDevice { + ctx.ModuleErrorf("host/device mismatch between %q and %q", ctx.ModuleName(), + otherName) + return + } + + if outputFile := a.outputFile(); outputFile != "" { + if found { + ctx.ModuleErrorf("multiple modules satisified dependency on %q", otherName) + return + } + outputFiles = append(outputFiles, outputFile) + modules = append(modules, a) + if i, ok := a.(ccExportedIncludeDirsProducer); ok { + exportedIncludeDirs = append(exportedIncludeDirs, i.exportedIncludeDirs()...) + } + found = true + } else { + ctx.ModuleErrorf("module %q missing output file", otherName) + return + } + } else { + ctx.ModuleErrorf("module %q not an android module", otherName) + return + } + }) + if !found { + ctx.ModuleErrorf("unsatisified dependency on %q", n) + } + } + + return modules, outputFiles, exportedIncludeDirs +} + +func (c *ccBase) collectDeps(ctx common.AndroidModuleContext, flags ccFlags) (ccDeps, ccFlags) { + var deps ccDeps + var newIncludeDirs []string + + wholeStaticLibNames := c.properties.Whole_static_libs + _, deps.wholeStaticLibs, newIncludeDirs = c.collectDepsFromList(ctx, wholeStaticLibNames) + + deps.includeDirs = append(deps.includeDirs, newIncludeDirs...) + + staticLibNames := c.properties.Static_libs + staticLibNames = append(staticLibNames, flags.extraStaticLibs...) + _, deps.staticLibs, newIncludeDirs = c.collectDepsFromList(ctx, staticLibNames) + deps.includeDirs = append(deps.includeDirs, newIncludeDirs...) + + return deps, flags +} + +// ccDynamic contains the properties and members used by shared libraries and dynamic executables +type ccDynamic struct { + ccBase +} + +const defaultSystemSharedLibraries = "__default__" + +func (c *ccDynamic) systemSharedLibs() []string { + + if len(c.properties.System_shared_libs) == 1 && + c.properties.System_shared_libs[0] == defaultSystemSharedLibraries { + + if c.HostOrDevice().Host() { + return []string{} + } else { + return []string{"libc", "libm"} + } + } + return c.properties.System_shared_libs +} + +var ( + stlSharedLibs = []string{"libc++", "libstlport", "libstdc++"} + stlSharedHostLibs = []string{"libc++"} + stlStaticLibs = []string{"libc++_static", "libstlport_static", "libstdc++"} + stlStaticHostLibs = []string{"libc++_static"} +) + +func (c *ccDynamic) AndroidDynamicDependencies(ctx common.AndroidDynamicDependerModuleContext) []string { + deps := c.ccBase.AndroidDynamicDependencies(ctx) + + if c.HostOrDevice().Device() { + ctx.AddVariationDependencies([]blueprint.Variation{{"link", "shared"}}, c.systemSharedLibs()...) + ctx.AddVariationDependencies([]blueprint.Variation{{"link", "static"}}, + "libcompiler_rt-extras", + //"libgcov", + "libatomic", + "libgcc") + + if c.properties.Stl != "none" { + ctx.AddVariationDependencies([]blueprint.Variation{{"link", "shared"}}, stlSharedLibs...) + ctx.AddVariationDependencies([]blueprint.Variation{{"link", "static"}}, stlStaticLibs...) + } + } else { + if c.properties.Stl != "none" { + ctx.AddVariationDependencies([]blueprint.Variation{{"link", "shared"}}, stlSharedHostLibs...) + ctx.AddVariationDependencies([]blueprint.Variation{{"link", "static"}}, stlStaticHostLibs...) + } + } + + return deps +} + +var implicitStaticLibs = []string{"libcompiler_rt-extras" /*"libgcov",*/, "libatomic", "libgcc"} + +func (c *ccDynamic) collectDeps(ctx common.AndroidModuleContext, flags ccFlags) (ccDeps, ccFlags) { + var newIncludeDirs []string + + deps, flags := c.ccBase.collectDeps(ctx, flags) + + systemSharedLibs := c.systemSharedLibs() + sharedLibNames := make([]string, 0, len(c.properties.Shared_libs)+len(systemSharedLibs)+ + len(flags.extraSharedLibs)) + sharedLibNames = append(sharedLibNames, c.properties.Shared_libs...) + sharedLibNames = append(sharedLibNames, systemSharedLibs...) + sharedLibNames = append(sharedLibNames, flags.extraSharedLibs...) + _, deps.sharedLibs, newIncludeDirs = c.collectDepsFromList(ctx, sharedLibNames) + deps.includeDirs = append(deps.includeDirs, newIncludeDirs...) + + if ctx.Arch().HostOrDevice.Device() { + var staticLibs []string + staticLibNames := implicitStaticLibs + _, staticLibs, newIncludeDirs = c.collectDepsFromList(ctx, staticLibNames) + deps.staticLibs = append(deps.staticLibs, staticLibs...) + deps.includeDirs = append(deps.includeDirs, newIncludeDirs...) + } + + ctx.VisitDirectDeps(func(m blueprint.Module) { + if obj, ok := m.(*ccObject); ok { + otherName := ctx.OtherModuleName(m) + if strings.HasPrefix(otherName, "crtbegin") { + if !c.properties.Nocrt { + deps.crtBegin = obj.outputFile() + } + } else if strings.HasPrefix(otherName, "crtend") { + if !c.properties.Nocrt { + deps.crtEnd = obj.outputFile() + } + } else { + ctx.ModuleErrorf("object module type only support for crtbegin and crtend, found %q", + ctx.OtherModuleName(m)) + } + } + }) + + flags.includeDirs = append(flags.includeDirs, deps.includeDirs...) + + return deps, flags +} + +type ccExportedIncludeDirsProducer interface { + exportedIncludeDirs() []string +} + +// +// Combined static+shared libraries +// + +type ccLibrary struct { + ccDynamic + + primary *ccLibrary + primaryObjFiles []string + objFiles []string + exportIncludeDirs []string + out string + + libraryProperties struct { + BuildStatic bool `blueprint:"mutated"` + BuildShared bool `blueprint:"mutated"` + IsShared bool `blueprint:"mutated"` + IsStatic bool `blueprint:"mutated"` + + Static struct { + Srcs []string `android:"arch_variant"` + Cflags []string `android:"arch_variant"` + } `android:"arch_variant"` + Shared struct { + Srcs []string `android:"arch_variant"` + Cflags []string `android:"arch_variant"` + } `android:"arch_variant"` + } +} + +func NewCCLibrary() (blueprint.Module, []interface{}) { + module := &ccLibrary{} + module.module = module + module.properties.System_shared_libs = []string{defaultSystemSharedLibraries} + module.libraryProperties.BuildShared = true + module.libraryProperties.BuildStatic = true + + return common.InitAndroidModule(module, common.HostAndDeviceSupported, "both", + &module.properties, &module.unused, &module.libraryProperties) +} + +func (c *ccLibrary) AndroidDynamicDependencies(ctx common.AndroidDynamicDependerModuleContext) []string { + if c.libraryProperties.IsShared { + deps := c.ccDynamic.AndroidDynamicDependencies(ctx) + if c.HostOrDevice().Device() { + deps = append(deps, "crtbegin_so", "crtend_so") + } + return deps + } else { + return c.ccBase.AndroidDynamicDependencies(ctx) + } +} + +func (c *ccLibrary) collectDeps(ctx common.AndroidModuleContext, flags ccFlags) (ccDeps, ccFlags) { + if c.libraryProperties.IsStatic { + deps, flags := c.ccBase.collectDeps(ctx, flags) + wholeStaticLibNames := c.properties.Whole_static_libs + wholeStaticLibs, _, _ := c.collectDepsFromList(ctx, wholeStaticLibNames) + + for _, m := range wholeStaticLibs { + if staticLib, ok := m.(*ccLibrary); ok && staticLib.libraryProperties.IsStatic { + deps.objFiles = append(deps.objFiles, staticLib.allObjFiles()...) + } else { + ctx.ModuleErrorf("module %q not a static library", ctx.OtherModuleName(m)) + } + } + + return deps, flags + } else if c.libraryProperties.IsShared { + return c.ccDynamic.collectDeps(ctx, flags) + } else { + panic("Not shared or static") + } +} + +func (c *ccLibrary) outputFile() string { + return c.out +} + +func (c *ccLibrary) allObjFiles() []string { + return c.objFiles +} + +func (c *ccLibrary) exportedIncludeDirs() []string { + return c.exportIncludeDirs +} + +func (c *ccLibrary) moduleTypeCflags(ctx common.AndroidModuleContext, toolchain toolchain) []string { + return []string{"-fPIC"} +} + +func (c *ccLibrary) moduleTypeLdflags(ctx common.AndroidModuleContext, toolchain toolchain) []string { + if c.libraryProperties.IsShared { + libName := ctx.ModuleName() + // GCC for Android assumes that -shared means -Bsymbolic, use -Wl,-shared instead + sharedFlag := "-Wl,-shared" + if c.properties.Clang || ctx.Arch().HostOrDevice.Host() { + sharedFlag = "-shared" + } + if ctx.Arch().HostOrDevice.Device() { + return []string{ + "-nostdlib", + "-Wl,--gc-sections", + sharedFlag, + "-Wl,-soname," + libName, + } + } else { + return []string{ + "-Wl,--gc-sections", + sharedFlag, + "-Wl,-soname," + libName, + } + } + } else { + return nil + } +} + +func (c *ccLibrary) compileStaticLibrary(ctx common.AndroidModuleContext, + flags ccFlags, deps ccDeps, objFiles []string) { + + staticFlags := flags + staticFlags.cFlags = append(staticFlags.cFlags, c.libraryProperties.Static.Cflags...) + objFilesStatic := c.customCompileObjs(ctx, staticFlags, deps, common.DeviceStaticLibrary, + c.libraryProperties.Static.Srcs) + + objFiles = append(objFiles, objFilesStatic...) + + var includeDirs []string + + wholeStaticLibNames := c.properties.Whole_static_libs + wholeStaticLibs, _, newIncludeDirs := c.collectDepsFromList(ctx, wholeStaticLibNames) + includeDirs = append(includeDirs, newIncludeDirs...) + + for _, m := range wholeStaticLibs { + if staticLib, ok := m.(*ccLibrary); ok && staticLib.libraryProperties.IsStatic { + objFiles = append(objFiles, staticLib.allObjFiles()...) + } else { + ctx.ModuleErrorf("module %q not a static library", ctx.OtherModuleName(m)) + } + } + + staticLibNames := c.properties.Static_libs + _, _, newIncludeDirs = c.collectDepsFromList(ctx, staticLibNames) + includeDirs = append(includeDirs, newIncludeDirs...) + + ctx.VisitDirectDeps(func(m blueprint.Module) { + if obj, ok := m.(*ccObject); ok { + otherName := ctx.OtherModuleName(m) + if !strings.HasPrefix(otherName, "crtbegin") && !strings.HasPrefix(otherName, "crtend") { + objFiles = append(objFiles, obj.outputFile()) + } + } + }) + + outputFile := filepath.Join(common.ModuleOutDir(ctx), ctx.ModuleName()+staticLibraryExtension) + + TransformObjToStaticLib(ctx, objFiles, ccFlagsToBuilderFlags(flags), outputFile) + + c.objFiles = objFiles + c.out = outputFile + c.exportIncludeDirs = pathtools.PrefixPaths(c.properties.Export_include_dirs, + common.ModuleSrcDir(ctx)) + + ctx.CheckbuildFile(outputFile) +} + +func (c *ccLibrary) compileSharedLibrary(ctx common.AndroidModuleContext, + flags ccFlags, deps ccDeps, objFiles []string) { + + sharedFlags := flags + sharedFlags.cFlags = append(sharedFlags.cFlags, c.libraryProperties.Shared.Cflags...) + objFilesShared := c.customCompileObjs(ctx, sharedFlags, deps, common.DeviceSharedLibrary, + c.libraryProperties.Shared.Srcs) + + objFiles = append(objFiles, objFilesShared...) + + outputFile := filepath.Join(common.ModuleOutDir(ctx), ctx.ModuleName()+sharedLibraryExtension) + + TransformObjToDynamicBinary(ctx, objFiles, deps.sharedLibs, deps.staticLibs, deps.wholeStaticLibs, + deps.crtBegin, deps.crtEnd, ccFlagsToBuilderFlags(flags), outputFile) + + c.out = outputFile + c.exportIncludeDirs = pathtools.PrefixPaths(c.properties.Export_include_dirs, + common.ModuleSrcDir(ctx)) + + installDir := "lib" + if flags.toolchain.Is64Bit() { + installDir = "lib64" + } + + ctx.InstallFile(installDir, outputFile) +} + +func (c *ccLibrary) compileModule(ctx common.AndroidModuleContext, + flags ccFlags, deps ccDeps, objFiles []string) { + + // Reuse the object files from the matching static library if it exists + if c.primary == c { + c.primaryObjFiles = objFiles + } else { + objFiles = append([]string(nil), c.primary.primaryObjFiles...) + } + + if c.libraryProperties.IsStatic { + c.compileStaticLibrary(ctx, flags, deps, objFiles) + } else { + c.compileSharedLibrary(ctx, flags, deps, objFiles) + } +} + +// +// Objects (for crt*.o) +// + +type ccObject struct { + ccBase + out string +} + +func NewCCObject() (blueprint.Module, []interface{}) { + module := &ccObject{} + module.module = module + + return common.InitAndroidModule(module, common.DeviceSupported, "both", + &module.properties, &module.unused) +} + +func (*ccObject) AndroidDynamicDependencies(ctx common.AndroidDynamicDependerModuleContext) []string { + // object files can't have any dynamic dependencies + return nil +} + +func (c *ccObject) collectDeps(ctx common.AndroidModuleContext, flags ccFlags) (ccDeps, ccFlags) { + deps, flags := c.ccBase.collectDeps(ctx, flags) + ctx.VisitDirectDeps(func(m blueprint.Module) { + if obj, ok := m.(*ccObject); ok { + deps.objFiles = append(deps.objFiles, obj.outputFile()) + } else { + ctx.ModuleErrorf("Unknown module type for dependency %q", ctx.OtherModuleName(m)) + } + }) + + return deps, flags +} + +func (c *ccObject) compileModule(ctx common.AndroidModuleContext, + flags ccFlags, deps ccDeps, objFiles []string) { + + objFiles = append(objFiles, deps.objFiles...) + + var outputFile string + if len(objFiles) == 1 { + outputFile = objFiles[0] + } else { + outputFile = filepath.Join(common.ModuleOutDir(ctx), ctx.ModuleName()+".o") + TransformObjsToObj(ctx, objFiles, ccFlagsToBuilderFlags(flags), outputFile) + } + + c.out = outputFile + + ctx.CheckbuildFile(outputFile) +} + +func (c *ccObject) outputFile() string { + return c.out +} + +// +// Executables +// + +type ccBinary struct { + ccDynamic + binaryProperties binaryProperties +} + +type binaryProperties struct { + // static_executable: compile executable with -static + Static_executable bool + + // stem: set the name of the output + Stem string `android:"arch_variant"` + + // prefix_symbols: if set, add an extra objcopy --prefix-symbols= step + Prefix_symbols string +} + +func (c *ccBinary) getStem(ctx common.AndroidModuleContext) string { + if c.binaryProperties.Stem != "" { + return c.binaryProperties.Stem + } + return ctx.ModuleName() +} + +func (c *ccBinary) AndroidDynamicDependencies(ctx common.AndroidDynamicDependerModuleContext) []string { + deps := c.ccDynamic.AndroidDynamicDependencies(ctx) + if c.HostOrDevice().Device() { + if c.binaryProperties.Static_executable { + deps = append(deps, "crtbegin_static", "crtend_android") + } else { + deps = append(deps, "crtbegin_dynamic", "crtend_android") + } + } + return deps +} + +func NewCCBinary() (blueprint.Module, []interface{}) { + module := &ccBinary{} + module.module = module + + module.properties.System_shared_libs = []string{defaultSystemSharedLibraries} + return common.InitAndroidModule(module, common.HostAndDeviceSupported, "first", &module.properties, + &module.unused, &module.binaryProperties) +} + +func (c *ccBinary) moduleTypeCflags(ctx common.AndroidModuleContext, toolchain toolchain) []string { + return []string{"-fpie"} +} + +func (c *ccBinary) moduleTypeLdflags(ctx common.AndroidModuleContext, toolchain toolchain) []string { + if ctx.Arch().HostOrDevice.Device() { + linker := "/system/bin/linker" + if toolchain.Is64Bit() { + linker = "/system/bin/linker64" + } + + return []string{ + "-nostdlib", + "-Bdynamic", + fmt.Sprintf("-Wl,-dynamic-linker,%s", linker), + "-Wl,--gc-sections", + "-Wl,-z,nocopyreloc", + } + } + + return nil +} + +func (c *ccBinary) compileModule(ctx common.AndroidModuleContext, + flags ccFlags, deps ccDeps, objFiles []string) { + + if !c.binaryProperties.Static_executable && inList("libc", c.properties.Static_libs) { + ctx.ModuleErrorf("statically linking libc to dynamic executable, please remove libc\n" + + "from static libs or set static_executable: true") + } + + outputFile := filepath.Join(common.ModuleOutDir(ctx), c.getStem(ctx)) + + TransformObjToDynamicBinary(ctx, objFiles, deps.sharedLibs, deps.staticLibs, deps.wholeStaticLibs, + deps.crtBegin, deps.crtEnd, ccFlagsToBuilderFlags(flags), outputFile) + + ctx.InstallFile("bin", outputFile) +} + +// +// Static library +// + +func NewCCLibraryStatic() (blueprint.Module, []interface{}) { + module := &ccLibrary{} + module.module = module + module.libraryProperties.BuildStatic = true + + return common.InitAndroidModule(module, common.HostAndDeviceSupported, "both", + &module.properties, &module.unused) +} + +// +// Shared libraries +// + +func NewCCLibraryShared() (blueprint.Module, []interface{}) { + module := &ccLibrary{} + module.module = module + module.properties.System_shared_libs = []string{defaultSystemSharedLibraries} + module.libraryProperties.BuildShared = true + + return common.InitAndroidModule(module, common.HostAndDeviceSupported, "both", + &module.properties, &module.unused) +} + +// +// Host static library +// + +func NewCCLibraryHostStatic() (blueprint.Module, []interface{}) { + module := &ccLibrary{} + module.module = module + module.libraryProperties.BuildStatic = true + + return common.InitAndroidModule(module, common.HostSupported, "both", + &module.properties, &module.unused) +} + +// +// Host Shared libraries +// + +func NewCCLibraryHostShared() (blueprint.Module, []interface{}) { + module := &ccLibrary{} + module.module = module + module.libraryProperties.BuildShared = true + + return common.InitAndroidModule(module, common.HostSupported, "both", + &module.properties, &module.unused) +} + +// +// Host Binaries +// + +func NewCCBinaryHost() (blueprint.Module, []interface{}) { + module := &ccBinary{} + module.module = module + + return common.InitAndroidModule(module, common.HostSupported, "first", + &module.properties, &module.unused) +} + +// +// Device libraries shipped with gcc +// + +type toolchainLibrary struct { + ccLibrary +} + +func (*toolchainLibrary) AndroidDynamicDependencies(ctx common.AndroidDynamicDependerModuleContext) []string { + // toolchain libraries can't have any dependencies + return nil +} + +func (*toolchainLibrary) collectDeps(ctx common.AndroidModuleContext, flags ccFlags) (ccDeps, ccFlags) { + // toolchain libraries can't have any dependencies + return ccDeps{}, flags +} + +func NewToolchainLibrary() (blueprint.Module, []interface{}) { + module := &toolchainLibrary{} + module.module = module + module.libraryProperties.BuildStatic = true + + return common.InitAndroidModule(module, common.DeviceSupported, "both") +} + +func (c *toolchainLibrary) compileModule(ctx common.AndroidModuleContext, + flags ccFlags, deps ccDeps, objFiles []string) { + + libName := ctx.ModuleName() + staticLibraryExtension + outputFile := filepath.Join(common.ModuleOutDir(ctx), libName) + + CopyGccLib(ctx, libName, ccFlagsToBuilderFlags(flags), outputFile) + + c.out = outputFile + + ctx.CheckbuildFile(outputFile) +} + +func LinkageMutator(mctx blueprint.EarlyMutatorContext) { + if c, ok := mctx.Module().(*ccLibrary); ok { + var modules []blueprint.Module + if c.libraryProperties.BuildStatic && c.libraryProperties.BuildShared { + modules = mctx.CreateLocalVariations("static", "shared") + modules[0].(*ccLibrary).libraryProperties.IsStatic = true + modules[1].(*ccLibrary).libraryProperties.IsShared = true + } else if c.libraryProperties.BuildStatic { + modules = mctx.CreateLocalVariations("static") + modules[0].(*ccLibrary).libraryProperties.IsStatic = true + } else if c.libraryProperties.BuildShared { + modules = mctx.CreateLocalVariations("shared") + modules[0].(*ccLibrary).libraryProperties.IsShared = true + } else { + panic("ccLibrary not static or shared") + } + primary := modules[0].(*ccLibrary) + for _, m := range modules { + m.(*ccLibrary).primary = primary + if m != primary { + m.(*ccLibrary).properties.SkipCompileObjs = true + } + } + } else if _, ok := mctx.Module().(*toolchainLibrary); ok { + mctx.CreateLocalVariations("static") + } +} diff --git a/cc/clang.go b/cc/clang.go new file mode 100644 index 000000000..c348eeac4 --- /dev/null +++ b/cc/clang.go @@ -0,0 +1,104 @@ +package cc + +import ( + "sort" + "strings" +) + +// Cflags that should be filtered out when compiling with clang +var clangUnknownCflags = []string{ + "-finline-functions", + "-finline-limit=64", + "-fno-canonical-system-headers", + "-fno-tree-sra", + "-funswitch-loops", + "-Wmaybe-uninitialized", + "-Wno-error=maybe-uninitialized", + "-Wno-free-nonheap-object", + "-Wno-literal-suffix", + "-Wno-maybe-uninitialized", + "-Wno-old-style-declaration", + "-Wno-psabi", + "-Wno-unused-but-set-variable", + "-Wno-unused-but-set-parameter", + "-Wno-unused-local-typedefs", + + // arm + arm64 + mips + mips64 + "-fgcse-after-reload", + "-frerun-cse-after-loop", + "-frename-registers", + "-fno-strict-volatile-bitfields", + + // arm + arm64 + "-fno-align-jumps", + "-Wa,--noexecstack", + + // arm + "-mthumb-interwork", + "-fno-builtin-sin", + "-fno-caller-saves", + "-fno-early-inlining", + "-fno-move-loop-invariants", + "-fno-partial-inlining", + "-fno-tree-copy-prop", + "-fno-tree-loop-optimize", + + // mips + mips64 + "-msynci", + "-mno-fused-madd", + + // x86 + x86_64 + "-finline-limit=300", + "-fno-inline-functions-called-once", + "-mfpmath=sse", + "-mbionic", +} + +func init() { + sort.Strings(clangUnknownCflags) + + pctx.StaticVariable("clangExtraCflags", strings.Join([]string{ + "-D__compiler_offsetof=__builtin_offsetof", + + // Help catch common 32/64-bit errors. + "-Werror=int-conversion", + + // Workaround for ccache with clang. + // See http://petereisentraut.blogspot.com/2011/05/ccache-and-clang.html. + "-Wno-unused-command-line-argument", + + // Disable -Winconsistent-missing-override until we can clean up the existing + // codebase for it. + "-Wno-inconsistent-missing-override", + }, " ")) + + pctx.StaticVariable("clangExtraConlyflags", strings.Join([]string{ + "-std=gnu99", + }, " ")) + + pctx.StaticVariable("clangExtraTargetCflags", strings.Join([]string{ + "-nostdlibinc", + }, " ")) +} + +func clangFilterUnknownCflags(cflags []string) []string { + ret := make([]string, 0, len(cflags)) + for _, f := range cflags { + if !inListSorted(f, clangUnknownCflags) { + ret = append(ret, f) + } + } + + return ret +} + +func inListSorted(s string, list []string) bool { + for _, l := range list { + if s == l { + return true + } else if s < l { + return false + } + } + return false +} diff --git a/cc/toolchain.go b/cc/toolchain.go new file mode 100644 index 000000000..d79f23cce --- /dev/null +++ b/cc/toolchain.go @@ -0,0 +1,62 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// 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. + +package cc + +import ( + "android/soong/common" +) + +type toolchainFactory func(archVariant string, cpuVariant string) toolchain + +var toolchainFactories = map[common.HostOrDevice]map[common.ArchType]toolchainFactory{ + common.Host: make(map[common.ArchType]toolchainFactory), + common.Device: make(map[common.ArchType]toolchainFactory), +} + +func registerToolchainFactory(hod common.HostOrDevice, arch common.ArchType, + factory toolchainFactory) { + + toolchainFactories[hod][arch] = factory +} + +type toolchain interface { + GccRoot() string + GccTriple() string + Cflags() string + Cppflags() string + Ldflags() string + IncludeFlags() string + + ClangTriple() string + ClangCflags() string + ClangCppflags() string + ClangLdflags() string + + Is64Bit() bool +} + +type toolchain64Bit struct { +} + +func (toolchain64Bit) Is64Bit() bool { + return true +} + +type toolchain32Bit struct { +} + +func (toolchain32Bit) Is64Bit() bool { + return false +} diff --git a/cc/util.go b/cc/util.go new file mode 100644 index 000000000..8703cfb2f --- /dev/null +++ b/cc/util.go @@ -0,0 +1,96 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// 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. + +package cc + +import ( + "fmt" + "regexp" + "strings" +) + +// Efficiently converts a list of include directories to a single string +// of cflags with -I prepended to each directory. +func includeDirsToFlags(dirs []string) string { + return joinWithPrefix(dirs, "-I") +} + +func ldDirsToFlags(dirs []string) string { + return joinWithPrefix(dirs, "-L") +} + +func libNamesToFlags(names []string) string { + return joinWithPrefix(names, "-l") +} + +func joinWithPrefix(strs []string, prefix string) string { + if len(strs) == 0 { + return "" + } + + if len(strs) == 1 { + return prefix + strs[0] + } + + n := len(" ") * (len(strs) - 1) + for _, s := range strs { + n += len(prefix) + len(s) + } + + ret := make([]byte, 0, n) + for i, s := range strs { + if i != 0 { + ret = append(ret, ' ') + } + ret = append(ret, prefix...) + ret = append(ret, s...) + } + return string(ret) +} + +func inList(s string, list []string) bool { + for _, l := range list { + if l == s { + return true + } + } + + return false +} + +var libNameRegexp = regexp.MustCompile(`^lib(.*)$`) + +func moduleToLibName(module string) (string, error) { + matches := libNameRegexp.FindStringSubmatch(module) + if matches == nil { + return "", fmt.Errorf("Library module name %s does not start with lib", module) + } + return matches[1], nil +} + +func ccFlagsToBuilderFlags(in ccFlags) builderFlags { + return builderFlags{ + globalFlags: strings.Join(in.globalFlags, " "), + asFlags: strings.Join(in.asFlags, " "), + cFlags: strings.Join(in.cFlags, " "), + conlyFlags: strings.Join(in.conlyFlags, " "), + cppFlags: strings.Join(in.cppFlags, " "), + ldFlags: strings.Join(in.ldFlags, " "), + ldLibs: strings.Join(in.ldLibs, " "), + incFlags: includeDirsToFlags(in.includeDirs), + nocrt: in.nocrt, + toolchain: in.toolchain, + clang: in.clang, + } +} diff --git a/cc/x86_linux_host.go b/cc/x86_linux_host.go new file mode 100644 index 000000000..1097883c6 --- /dev/null +++ b/cc/x86_linux_host.go @@ -0,0 +1,231 @@ +package cc + +import ( + "runtime" + "strings" + + "android/soong/common" +) + +var ( + linuxCflags = []string{ + "-fno-exceptions", // from build/core/combo/select.mk + "-Wno-multichar", // from build/core/combo/select.mk + + "-Wa,--noexecstack", + + "-fPIC", + "-no-canonical-prefixes", + "-include ${SrcDir}/build/core/combo/include/arch/linux-x86/AndroidConfig.h", + + // Disable new longjmp in glibc 2.11 and later. See bug 2967937. Same for 2.15? + "-U_FORTIFY_SOURCE", + "-D_FORTIFY_SOURCE=0", + + // Workaround differences in inttypes.h between host and target. + //See bug 12708004. + "-D__STDC_FORMAT_MACROS", + "-D__STDC_CONSTANT_MACROS", + + // HOST_RELEASE_CFLAGS + "-O2", // from build/core/combo/select.mk + "-g", // from build/core/combo/select.mk + "-fno-strict-aliasing", // from build/core/combo/select.mk + } + + linuxLdflags = []string{ + "-Wl,-z,noexecstack", + "-Wl,-z,relro", + "-Wl,-z,now", + "-Wl,--build-id=md5", + "-Wl,--warn-shared-textrel", + "-Wl,--fatal-warnings", + "-Wl,-icf=safe", + "-Wl,--hash-style=gnu", + + // Disable transitive dependency library symbol resolving. + "-Wl,--allow-shlib-undefined", + } + + // Extended cflags + linuxX86Cflags = []string{ + "-msse3", + "-mfpmath=sse", + "-m32", + "-march=prescott", + } + + linuxX8664Cflags = []string{ + "-m64", + } + + linuxX86Ldflags = []string{ + "-m32", + } + + linuxX8664Ldflags = []string{ + "-m64", + } + + linuxClangCflags = append([]string{ + "--gcc-toolchain=${linuxGccRoot}", + "--sysroot=${linuxGccRoot}/sysroot", + }, clangFilterUnknownCflags(linuxCflags)...) + + linuxClangLdflags = append([]string{ + "--gcc-toolchain=${linuxGccRoot}", + "--sysroot=${linuxGccRoot}/sysroot", + }, clangFilterUnknownCflags(linuxLdflags)...) + + linuxX86ClangLdflags = append([]string{ + "-B${linuxGccRoot}/lib/gcc/${linuxGccTriple}/${linuxGccVersion}/32", + "-L${linuxGccRoot}/lib/gcc/${linuxGccTriple}/${linuxGccVersion}/32", + "-L${linuxGccRoot}/${linuxGccTriple}/lib32", + }, clangFilterUnknownCflags(linuxX86Ldflags)...) + + linuxX8664ClangLdflags = append([]string{ + "-B${linuxGccRoot}/lib/gcc/${linuxGccTriple}/${linuxGccVersion}", + "-L${linuxGccRoot}/lib/gcc/${linuxGccTriple}/${linuxGccVersion}", + "-L${linuxGccRoot}/${linuxGccTriple}/lib64", + }, clangFilterUnknownCflags(linuxX8664Ldflags)...) + + linuxClangCppflags = []string{ + "-isystem ${linuxGccRoot}/${linuxGccTriple}/include/c++/${linuxGccVersion}", + "-isystem ${linuxGccRoot}/${linuxGccTriple}/include/c++/${linuxGccVersion}/backward", + } + + linuxX86ClangCppflags = []string{ + "-isystem ${linuxGccRoot}/${linuxGccTriple}/include/c++/${linuxGccVersion}/${linuxGccTriple}/32", + } + + linuxX8664ClangCppflags = []string{ + "-isystem ${linuxGccRoot}/${linuxGccTriple}/include/c++/${linuxGccVersion}/${linuxGccTriple}", + } +) + +func init() { + pctx.StaticVariable("linuxGccVersion", "4.8") + + pctx.StaticVariable("linuxGccRoot", + "${SrcDir}/prebuilts/gcc/${HostPrebuiltTag}/host/x86_64-linux-glibc2.15-${linuxGccVersion}") + + pctx.StaticVariable("linuxGccTriple", "x86_64-linux") + + pctx.StaticVariable("linuxCflags", strings.Join(linuxCflags, " ")) + pctx.StaticVariable("linuxLdflags", strings.Join(linuxLdflags, " ")) + + pctx.StaticVariable("linuxClangCflags", strings.Join(linuxClangCflags, " ")) + pctx.StaticVariable("linuxClangLdflags", strings.Join(linuxClangLdflags, " ")) + pctx.StaticVariable("linuxClangCppflags", strings.Join(linuxClangCppflags, " ")) + + // Extended cflags + pctx.StaticVariable("linuxX86Cflags", strings.Join(linuxX86Cflags, " ")) + pctx.StaticVariable("linuxX8664Cflags", strings.Join(linuxX8664Cflags, " ")) + pctx.StaticVariable("linuxX86Ldflags", strings.Join(linuxX86Ldflags, " ")) + pctx.StaticVariable("linuxX8664Ldflags", strings.Join(linuxX8664Ldflags, " ")) + + pctx.StaticVariable("linuxX86ClangCflags", + strings.Join(clangFilterUnknownCflags(linuxX86Cflags), " ")) + pctx.StaticVariable("linuxX8664ClangCflags", + strings.Join(clangFilterUnknownCflags(linuxX8664Cflags), " ")) + pctx.StaticVariable("linuxX86ClangLdflags", strings.Join(linuxX86ClangLdflags, " ")) + pctx.StaticVariable("linuxX8664ClangLdflags", strings.Join(linuxX8664ClangLdflags, " ")) + pctx.StaticVariable("linuxX86ClangCppflags", strings.Join(linuxX86ClangCppflags, " ")) + pctx.StaticVariable("linuxX8664ClangCppflags", strings.Join(linuxX8664ClangCppflags, " ")) +} + +type toolchainLinux struct { + cFlags, ldFlags string +} + +type toolchainLinuxX86 struct { + toolchain32Bit + toolchainLinux +} + +type toolchainLinuxX8664 struct { + toolchain64Bit + toolchainLinux +} + +func (t *toolchainLinux) GccRoot() string { + return "${linuxGccRoot}" +} + +func (t *toolchainLinux) GccTriple() string { + return "${linuxGccTriple}" +} + +func (t *toolchainLinuxX86) Cflags() string { + return "${linuxCflags} ${linuxX86Cflags}" +} + +func (t *toolchainLinuxX8664) Cflags() string { + return "${linuxCflags} ${linuxX8664Cflags}" +} + +func (t *toolchainLinux) Cppflags() string { + return "" +} + +func (t *toolchainLinuxX86) Ldflags() string { + return "${linuxLdflags} ${linuxX86Ldflags}" +} + +func (t *toolchainLinuxX8664) Ldflags() string { + return "${linuxLdflags} ${linuxX8664Ldflags}" +} + +func (t *toolchainLinux) IncludeFlags() string { + return "" +} + +func (t *toolchainLinuxX86) ClangTriple() string { + return "i686-linux-gnu" +} + +func (t *toolchainLinuxX86) ClangCflags() string { + return "${linuxClangCflags} ${linuxX86ClangCflags}" +} + +func (t *toolchainLinuxX86) ClangCppflags() string { + return "${linuxClangCppflags} ${linuxX86ClangCppflags}" +} + +func (t *toolchainLinuxX8664) ClangTriple() string { + return "x86_64-linux-gnu" +} + +func (t *toolchainLinuxX8664) ClangCflags() string { + return "${linuxClangCflags} ${linuxX8664ClangCflags}" +} + +func (t *toolchainLinuxX8664) ClangCppflags() string { + return "${linuxClangCppflags} ${linuxX8664ClangCppflags}" +} + +func (t *toolchainLinuxX86) ClangLdflags() string { + return "${linuxClangLdflags} ${linuxX86ClangLdflags}" +} + +func (t *toolchainLinuxX8664) ClangLdflags() string { + return "${linuxClangLdflags} ${linuxX8664ClangLdflags}" +} + +var toolchainLinuxX86Singleton toolchain = &toolchainLinuxX86{} +var toolchainLinuxX8664Singleton toolchain = &toolchainLinuxX8664{} + +func linuxX86ToolchainFactory(archVariant string, cpuVariant string) toolchain { + return toolchainLinuxX86Singleton +} + +func linuxX8664ToolchainFactory(archVariant string, cpuVariant string) toolchain { + return toolchainLinuxX8664Singleton +} + +func init() { + if runtime.GOOS == "linux" { + registerToolchainFactory(common.Host, common.X86, linuxX86ToolchainFactory) + registerToolchainFactory(common.Host, common.X86_64, linuxX8664ToolchainFactory) + } +} diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go new file mode 100644 index 000000000..c1999fe27 --- /dev/null +++ b/cmd/soong_build/main.go @@ -0,0 +1,68 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// 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. + +package main + +import ( + "flag" + "fmt" + "os" + "path/filepath" + + "blueprint" + "blueprint/bootstrap" + + "android/soong/cc" + "android/soong/common" + "android/soong/config" +) + +func main() { + flag.Parse() + + // The top-level Blueprints file is passed as the first argument. + srcDir := filepath.Dir(flag.Arg(0)) + + ctx := blueprint.NewContext() + + // Module types + ctx.RegisterModuleType("cc_library_static", cc.NewCCLibraryStatic) + ctx.RegisterModuleType("cc_library_shared", cc.NewCCLibraryShared) + ctx.RegisterModuleType("cc_library", cc.NewCCLibrary) + ctx.RegisterModuleType("cc_object", cc.NewCCObject) + ctx.RegisterModuleType("cc_binary", cc.NewCCBinary) + + ctx.RegisterModuleType("toolchain_library", cc.NewToolchainLibrary) + + ctx.RegisterModuleType("cc_library_host_static", cc.NewCCLibraryHostStatic) + ctx.RegisterModuleType("cc_library_host_shared", cc.NewCCLibraryHostShared) + ctx.RegisterModuleType("cc_binary_host", cc.NewCCBinaryHost) + + // Mutators + ctx.RegisterEarlyMutator("arch", common.ArchMutator) + ctx.RegisterEarlyMutator("link", cc.LinkageMutator) + + // Singletons + + configuration, err := config.New(srcDir) + if err != nil { + fmt.Fprintf(os.Stderr, "%s", err) + os.Exit(1) + } + + // Temporary hack + //ctx.SetIgnoreUnknownModuleTypes(true) + + bootstrap.Main(ctx, configuration, config.ConfigFileName) +} diff --git a/cmd/soong_glob/soong_glob.go b/cmd/soong_glob/soong_glob.go new file mode 100644 index 000000000..227d7b018 --- /dev/null +++ b/cmd/soong_glob/soong_glob.go @@ -0,0 +1,56 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// 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. + +// soong_glob is the command line tool that checks if the list of files matching a glob has +// changed, and only updates the output file list if it has changed. It is used to optimize +// out build.ninja regenerations when non-matching files are added. See +// android/soong/common/glob.go for a longer description. +package main + +import ( + "flag" + "fmt" + "os" + + "android/soong/glob" +) + +var ( + out = flag.String("o", "", "file to write list of files that match glob") +) + +func usage() { + fmt.Fprintf(os.Stderr, "usage: soong_glob -o out glob\n") + flag.PrintDefaults() + os.Exit(2) +} + +func main() { + flag.Parse() + + if *out == "" { + fmt.Fprintln(os.Stderr, "error: -o is required\n") + usage() + } + + if flag.NArg() != 1 { + usage() + } + + _, err := glob.GlobWithDepFile(flag.Arg(0), *out, *out+".d") + if err != nil { + fmt.Fprintf(os.Stderr, "error: %s\n", err.Error()) + os.Exit(1) + } +} diff --git a/common/arch.go b/common/arch.go new file mode 100644 index 000000000..8daade0f1 --- /dev/null +++ b/common/arch.go @@ -0,0 +1,545 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// 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. + +package common + +import ( + "blueprint" + "blueprint/proptools" + "fmt" + "reflect" + "runtime" + "strings" +) + +var ( + Arm = newArch32("Arm") + Arm64 = newArch64("Arm64") + Mips = newArch32("Mips") + Mips64 = newArch64("Mips64") + X86 = newArch32("X86") + X86_64 = newArch64("X86_64") +) + +/* +Example blueprints file containing all variant property groups, with comment listing what type +of variants get properties in that group: + +module { + arch: { + arm: { + // Host or device variants with arm architecture + }, + arm64: { + // Host or device variants with arm64 architecture + }, + mips: { + // Host or device variants with mips architecture + }, + mips64: { + // Host or device variants with mips64 architecture + }, + x86: { + // Host or device variants with x86 architecture + }, + x86_64: { + // Host or device variants with x86_64 architecture + }, + }, + multilib: { + lib32: { + // Host or device variants for 32-bit architectures + }, + lib64: { + // Host or device variants for 64-bit architectures + }, + }, + target: { + android: { + // Device variants + }, + host: { + // Host variants + }, + linux: { + // Linux host variants + }, + darwin: { + // Darwin host variants + }, + windows: { + // Windows host variants + }, + not_windows: { + // Non-windows host variants + }, + }, +} +*/ +type archProperties struct { + Arch struct { + Arm interface{} + Arm64 interface{} + Mips interface{} + Mips64 interface{} + X86 interface{} + X86_64 interface{} + } + Multilib struct { + Lib32 interface{} + Lib64 interface{} + } + Target struct { + Host interface{} + Android interface{} + Linux interface{} + Darwin interface{} + Windows interface{} + Not_windows interface{} + } +} + +// An Arch indicates a single CPU architecture. +type Arch struct { + HostOrDevice HostOrDevice + ArchType ArchType + ArchVariant string + CpuVariant string +} + +func (a Arch) String() string { + s := a.HostOrDevice.String() + "_" + a.ArchType.String() + if a.ArchVariant != "" { + s += "_" + a.ArchVariant + } + if a.CpuVariant != "" { + s += "_" + a.CpuVariant + } + return s +} + +type ArchType struct { + Name string + Field string + Multilib string + MultilibField string +} + +func newArch32(field string) ArchType { + return ArchType{ + Name: strings.ToLower(field), + Field: field, + Multilib: "lib32", + MultilibField: "Lib32", + } +} + +func newArch64(field string) ArchType { + return ArchType{ + Name: strings.ToLower(field), + Field: field, + Multilib: "lib64", + MultilibField: "Lib64", + } +} + +func (a ArchType) String() string { + return a.Name +} + +type HostOrDeviceSupported int + +const ( + _ HostOrDeviceSupported = iota + HostSupported + DeviceSupported + HostAndDeviceSupported +) + +type HostOrDevice int + +const ( + _ HostOrDevice = iota + Host + Device +) + +func (hod HostOrDevice) String() string { + switch hod { + case Device: + return "device" + case Host: + return "host" + default: + panic(fmt.Sprintf("unexpected HostOrDevice value %d", hod)) + } +} + +func (hod HostOrDevice) FieldLower() string { + switch hod { + case Device: + return "android" + case Host: + return "host" + default: + panic(fmt.Sprintf("unexpected HostOrDevice value %d", hod)) + } +} + +func (hod HostOrDevice) Field() string { + switch hod { + case Device: + return "Android" + case Host: + return "Host" + default: + panic(fmt.Sprintf("unexpected HostOrDevice value %d", hod)) + } +} + +func (hod HostOrDevice) Host() bool { + if hod == 0 { + panic("HostOrDevice unset") + } + return hod == Host +} + +func (hod HostOrDevice) Device() bool { + if hod == 0 { + panic("HostOrDevice unset") + } + return hod == Device +} + +var hostOrDeviceName = map[HostOrDevice]string{ + Device: "device", + Host: "host", +} + +var ( + armArch = Arch{ + HostOrDevice: Device, + ArchType: Arm, + ArchVariant: "armv7-a-neon", + CpuVariant: "cortex-a15", + } + arm64Arch = Arch{ + HostOrDevice: Device, + ArchType: Arm64, + ArchVariant: "armv8-a", + CpuVariant: "denver", + } + hostArch = Arch{ + HostOrDevice: Host, + ArchType: X86, + } + host64Arch = Arch{ + HostOrDevice: Host, + ArchType: X86_64, + } +) + +func ArchMutator(mctx blueprint.EarlyMutatorContext) { + var module AndroidModule + var ok bool + if module, ok = mctx.Module().(AndroidModule); !ok { + return + } + + // TODO: this is all hardcoded for arm64 primary, arm secondary for now + // Replace with a configuration file written by lunch or bootstrap + + arches := []Arch{} + + if module.base().HostSupported() { + arches = append(arches, host64Arch) + } + + if module.base().DeviceSupported() { + switch module.base().commonProperties.Compile_multilib { + case "both": + arches = append(arches, arm64Arch, armArch) + case "first", "64": + arches = append(arches, arm64Arch) + case "32": + arches = append(arches, armArch) + default: + mctx.ModuleErrorf(`compile_multilib must be "both", "first", "32", or "64", found %q`, + module.base().commonProperties.Compile_multilib) + } + } + + archNames := []string{} + for _, arch := range arches { + archNames = append(archNames, arch.String()) + } + + modules := mctx.CreateVariations(archNames...) + + for i, m := range modules { + m.(AndroidModule).base().SetArch(arches[i]) + m.(AndroidModule).base().setArchProperties(mctx, arches[i]) + } +} + +func InitArchModule(m AndroidModule, defaultMultilib string, + propertyStructs ...interface{}) (blueprint.Module, []interface{}) { + + base := m.base() + + base.commonProperties.Compile_multilib = defaultMultilib + + base.generalProperties = append(base.generalProperties, + &base.commonProperties) + base.generalProperties = append(base.generalProperties, + propertyStructs...) + + for _, properties := range base.generalProperties { + propertiesValue := reflect.ValueOf(properties) + if propertiesValue.Kind() != reflect.Ptr { + panic("properties must be a pointer to a struct") + } + + propertiesValue = propertiesValue.Elem() + if propertiesValue.Kind() != reflect.Struct { + panic("properties must be a pointer to a struct") + } + + archProperties := &archProperties{} + forEachInterface(reflect.ValueOf(archProperties), func(v reflect.Value) { + newValue := proptools.CloneProperties(propertiesValue) + proptools.ZeroProperties(newValue.Elem()) + v.Set(newValue) + }) + + base.archProperties = append(base.archProperties, archProperties) + } + + var allProperties []interface{} + allProperties = append(allProperties, base.generalProperties...) + for _, asp := range base.archProperties { + allProperties = append(allProperties, asp) + } + + return m, allProperties +} + +// Rewrite the module's properties structs to contain arch-specific values. +func (a *AndroidModuleBase) setArchProperties(ctx blueprint.EarlyMutatorContext, arch Arch) { + for i := range a.generalProperties { + generalPropsValue := reflect.ValueOf(a.generalProperties[i]).Elem() + + // Handle arch-specific properties in the form: + // arch { + // arm64 { + // key: value, + // }, + // }, + t := arch.ArchType + extendProperties(ctx, "arch", t.Name, generalPropsValue, + reflect.ValueOf(a.archProperties[i].Arch).FieldByName(t.Field).Elem().Elem()) + + // Handle multilib-specific properties in the form: + // multilib { + // lib32 { + // key: value, + // }, + // }, + extendProperties(ctx, "multilib", t.Multilib, generalPropsValue, + reflect.ValueOf(a.archProperties[i].Multilib).FieldByName(t.MultilibField).Elem().Elem()) + + // Handle host-or-device-specific properties in the form: + // target { + // host { + // key: value, + // }, + // }, + hod := arch.HostOrDevice + extendProperties(ctx, "target", hod.FieldLower(), generalPropsValue, + reflect.ValueOf(a.archProperties[i].Target).FieldByName(hod.Field()).Elem().Elem()) + + // Handle host target properties in the form: + // target { + // linux { + // key: value, + // }, + // not_windows { + // key: value, + // }, + // }, + var osList = []struct { + goos string + field string + }{ + {"darwin", "Darwin"}, + {"linux", "Linux"}, + {"windows", "Windows"}, + } + + if hod.Host() { + for _, v := range osList { + if v.goos == runtime.GOOS { + extendProperties(ctx, "target", v.goos, generalPropsValue, + reflect.ValueOf(a.archProperties[i].Target).FieldByName(v.field).Elem().Elem()) + } + } + extendProperties(ctx, "target", "not_windows", generalPropsValue, + reflect.ValueOf(a.archProperties[i].Target).FieldByName("Not_windows").Elem().Elem()) + } + + if ctx.Failed() { + return + } + } +} + +func forEachInterface(v reflect.Value, f func(reflect.Value)) { + switch v.Kind() { + case reflect.Interface: + f(v) + case reflect.Struct: + for i := 0; i < v.NumField(); i++ { + forEachInterface(v.Field(i), f) + } + case reflect.Ptr: + forEachInterface(v.Elem(), f) + default: + panic(fmt.Errorf("Unsupported kind %s", v.Kind())) + } +} + +// TODO: move this to proptools +func extendProperties(ctx blueprint.EarlyMutatorContext, variationType, variationName string, + dstValue, srcValue reflect.Value) { + extendPropertiesRecursive(ctx, variationType, variationName, dstValue, srcValue, "") +} + +func extendPropertiesRecursive(ctx blueprint.EarlyMutatorContext, variationType, variationName string, + dstValue, srcValue reflect.Value, recursePrefix string) { + + typ := dstValue.Type() + if srcValue.Type() != typ { + panic(fmt.Errorf("can't extend mismatching types (%s <- %s)", + dstValue.Kind(), srcValue.Kind())) + } + + for i := 0; i < srcValue.NumField(); i++ { + field := typ.Field(i) + if field.PkgPath != "" { + // The field is not exported so just skip it. + continue + } + + srcFieldValue := srcValue.Field(i) + dstFieldValue := dstValue.Field(i) + + localPropertyName := proptools.PropertyNameForField(field.Name) + propertyName := fmt.Sprintf("%s.%s.%s%s", variationType, variationName, + recursePrefix, localPropertyName) + propertyPresentInVariation := ctx.ContainsProperty(propertyName) + + if !propertyPresentInVariation { + continue + } + + tag := field.Tag.Get("android") + tags := map[string]bool{} + for _, entry := range strings.Split(tag, ",") { + if entry != "" { + tags[entry] = true + } + } + + if !tags["arch_variant"] { + ctx.PropertyErrorf(propertyName, "property %q can't be specific to a build variant", + recursePrefix+proptools.PropertyNameForField(field.Name)) + continue + } + + switch srcFieldValue.Kind() { + case reflect.Bool: + // Replace the original value. + dstFieldValue.Set(srcFieldValue) + case reflect.String: + // Append the extension string. + dstFieldValue.SetString(dstFieldValue.String() + + srcFieldValue.String()) + case reflect.Struct: + // Recursively extend the struct's fields. + newRecursePrefix := fmt.Sprintf("%s%s.", recursePrefix, strings.ToLower(field.Name)) + extendPropertiesRecursive(ctx, variationType, variationName, + dstFieldValue, srcFieldValue, + newRecursePrefix) + case reflect.Slice: + val, err := archCombineSlices(dstFieldValue, srcFieldValue, tags["arch_subtract"]) + if err != nil { + ctx.PropertyErrorf(propertyName, err.Error()) + continue + } + dstFieldValue.Set(val) + case reflect.Ptr, reflect.Interface: + // Recursively extend the pointed-to struct's fields. + if dstFieldValue.IsNil() != srcFieldValue.IsNil() { + panic(fmt.Errorf("can't extend field %q: nilitude mismatch")) + } + if dstFieldValue.Type() != srcFieldValue.Type() { + panic(fmt.Errorf("can't extend field %q: type mismatch")) + } + if !dstFieldValue.IsNil() { + newRecursePrefix := fmt.Sprintf("%s.%s", recursePrefix, field.Name) + extendPropertiesRecursive(ctx, variationType, variationName, + dstFieldValue.Elem(), srcFieldValue.Elem(), + newRecursePrefix) + } + default: + panic(fmt.Errorf("unexpected kind for property struct field %q: %s", + field.Name, srcFieldValue.Kind())) + } + } +} + +func archCombineSlices(general, arch reflect.Value, canSubtract bool) (reflect.Value, error) { + if !canSubtract { + // Append the extension slice. + return reflect.AppendSlice(general, arch), nil + } + + // Support -val in arch list to subtract a value from original list + l := general.Interface().([]string) + for archIndex := 0; archIndex < arch.Len(); archIndex++ { + archString := arch.Index(archIndex).String() + if strings.HasPrefix(archString, "-") { + generalIndex := findStringInSlice(archString[1:], l) + if generalIndex == -1 { + return reflect.Value{}, + fmt.Errorf("can't find %q to subtract from general properties", archString[1:]) + } + l = append(l[:generalIndex], l[generalIndex+1:]...) + } else { + l = append(l, archString) + } + } + + return reflect.ValueOf(l), nil +} + +func findStringInSlice(str string, slice []string) int { + for i, s := range slice { + if s == str { + return i + } + } + + return -1 +} diff --git a/common/defs.go b/common/defs.go new file mode 100644 index 000000000..44992166e --- /dev/null +++ b/common/defs.go @@ -0,0 +1,61 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// 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. + +package common + +import ( + "blueprint" +) + +var ( + pctx = blueprint.NewPackageContext("android/soong/common") + + cpPreserveSymlinks = pctx.VariableConfigMethod("cpPreserveSymlinks", + Config.CpPreserveSymlinksFlags) + + // A phony rule that is not the built-in Ninja phony rule. The built-in + // phony rule has special behavior that is sometimes not desired. See the + // Ninja docs for more details. + Phony = pctx.StaticRule("Phony", + blueprint.RuleParams{ + Command: "# phony $out", + Description: "phony $out", + }) + + // GeneratedFile is a rule for indicating that a given file was generated + // while running soong. This allows the file to be cleaned up if it ever + // stops being generated by soong. + GeneratedFile = pctx.StaticRule("GeneratedFile", + blueprint.RuleParams{ + Command: "# generated $out", + Description: "generated $out", + Generator: true, + }) + + // A copy rule. + Cp = pctx.StaticRule("Cp", + blueprint.RuleParams{ + Command: "cp $cpPreserveSymlinks $cpFlags $in $out", + Description: "cp $out", + }, + "cpFlags") + + // A symlink rule. + Symlink = pctx.StaticRule("Symlink", + blueprint.RuleParams{ + Command: "ln -f -s $fromPath $out", + Description: "symlink $out", + }, + "fromPath") +) diff --git a/common/glob.go b/common/glob.go new file mode 100644 index 000000000..9aada9584 --- /dev/null +++ b/common/glob.go @@ -0,0 +1,133 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// 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. + +package common + +import ( + "fmt" + "path/filepath" + + "blueprint" + "blueprint/bootstrap" + + "android/soong/glob" +) + +// This file supports globbing source files in Blueprints files. +// +// The build.ninja file needs to be regenerated any time a file matching the glob is added +// or removed. The naive solution is to have the build.ninja file depend on all the +// traversed directories, but this will cause the regeneration step to run every time a +// non-matching file is added to a traversed directory, including backup files created by +// editors. +// +// The solution implemented here optimizes out regenerations when the directory modifications +// don't match the glob by having the build.ninja file depend on an intermedate file that +// is only updated when a file matching the glob is added or removed. The intermediate file +// depends on the traversed directories via a depfile. The depfile is used to avoid build +// errors if a directory is deleted - a direct dependency on the deleted directory would result +// in a build failure with a "missing and no known rule to make it" error. + +var ( + globCmd = filepath.Join(bootstrap.BinDir, "soong_glob") + + // globRule rule traverses directories to produce a list of files that match $glob + // and writes it to $out if it has changed, and writes the directories to $out.d + globRule = pctx.StaticRule("globRule", + blueprint.RuleParams{ + Command: fmt.Sprintf(`%s -o $out "$glob"`, globCmd), + Description: "glob $glob", + + Restat: true, + Generator: true, + Deps: blueprint.DepsGCC, + Depfile: "$out.d", + }, + "glob") +) + +func hasGlob(in []string) bool { + for _, s := range in { + if glob.IsGlob(s) { + return true + } + } + + return false +} + +func ExpandGlobs(ctx AndroidModuleContext, in []string) []string { + if !hasGlob(in) { + return in + } + + out := make([]string, 0, len(in)) + for _, s := range in { + if glob.IsGlob(s) { + out = append(out, Glob(ctx, s)...) + } else { + out = append(out, s) + } + } + + return out +} + +func Glob(ctx AndroidModuleContext, globPattern string) []string { + fileListFile := filepath.Join(ModuleOutDir(ctx), "glob", globToString(globPattern)) + depFile := fileListFile + ".d" + + // Get a globbed file list, and write out fileListFile and depFile + files, err := glob.GlobWithDepFile(globPattern, fileListFile, depFile) + if err != nil { + ctx.ModuleErrorf("glob: %s", err.Error()) + return []string{globPattern} + } + + // Create a rule to rebuild fileListFile if a directory in depFile changes. fileListFile + // will only be rewritten if it has changed, preventing unnecesary build.ninja regenerations. + ctx.Build(pctx, blueprint.BuildParams{ + Rule: globRule, + Outputs: []string{fileListFile}, + Implicits: []string{globCmd}, + Args: map[string]string{ + "glob": globPattern, + }, + }) + + // Phony rule so the cleanup phase doesn't delete the depFile + ctx.Build(pctx, blueprint.BuildParams{ + Rule: blueprint.Phony, + Outputs: []string{depFile}, + }) + + // Make build.ninja depend on the fileListFile + ctx.AddNinjaFileDeps(fileListFile) + + return files +} + +func globToString(glob string) string { + ret := "" + for _, c := range glob { + if c >= 'a' && c <= 'z' || + c >= 'A' && c <= 'Z' || + c >= '0' && c <= '9' || + c == '_' || c == '-' || c == '/' { + ret += string(c) + } + } + + return ret +} diff --git a/common/module.go b/common/module.go new file mode 100644 index 000000000..0cbe4b07c --- /dev/null +++ b/common/module.go @@ -0,0 +1,327 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// 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. + +package common + +import ( + "blueprint" + "path/filepath" +) + +var ( + DeviceSharedLibrary = "shared_library" + DeviceStaticLibrary = "static_library" + DeviceExecutable = "executable" + HostSharedLibrary = "host_shared_library" + HostStaticLibrary = "host_static_library" + HostExecutable = "host_executable" +) + +type AndroidModuleContext interface { + blueprint.ModuleContext + + Arch() Arch + InstallFile(installPath, srcPath string) + CheckbuildFile(srcPath string) +} + +type AndroidModule interface { + blueprint.Module + + GenerateAndroidBuildActions(AndroidModuleContext) + + base() *AndroidModuleBase + Disabled() bool + HostOrDevice() HostOrDevice +} + +type AndroidDynamicDepender interface { + AndroidDynamicDependencies(ctx AndroidDynamicDependerModuleContext) []string +} + +type AndroidDynamicDependerModuleContext interface { + blueprint.DynamicDependerModuleContext +} + +type commonProperties struct { + Name string + Deps []string + ResourceDirs []string + + // disabled: don't emit any build rules for this module + Disabled bool `android:"arch_variant"` + + // multilib: control whether this module compiles for 32-bit, 64-bit, or both. Possible values + // are "32" (compile for 32-bit only), "64" (compile for 64-bit only), "both" (compile for both + // architectures), or "first" (compile for 64-bit on a 64-bit platform, and 32-bit on a 32-bit + // platform + Compile_multilib string + + // Set by ArchMutator + CompileArch Arch `blueprint:"mutated"` + + // Set by InitAndroidModule + HostOrDeviceSupported HostOrDeviceSupported `blueprint:"mutated"` +} + +type hostAndDeviceProperties struct { + Host_supported bool + Device_supported bool +} + +func InitAndroidModule(m AndroidModule, hod HostOrDeviceSupported, defaultMultilib string, + propertyStructs ...interface{}) (blueprint.Module, []interface{}) { + + base := m.base() + base.module = m + base.commonProperties.HostOrDeviceSupported = hod + + if hod == HostAndDeviceSupported { + // Default to module to device supported, host not supported, can override in module + // properties + base.hostAndDeviceProperties.Device_supported = true + propertyStructs = append(propertyStructs, &base.hostAndDeviceProperties) + } + + return InitArchModule(m, defaultMultilib, propertyStructs...) +} + +// A AndroidModuleBase object contains the properties that are common to all Android +// modules. It should be included as an anonymous field in every module +// struct definition. InitAndroidModule should then be called from the module's +// factory function, and the return values from InitAndroidModule should be +// returned from the factory function. +// +// The AndroidModuleBase type is responsible for implementing the +// GenerateBuildActions method to support the blueprint.Module interface. This +// method will then call the module's GenerateAndroidBuildActions method once +// for each build variant that is to be built. GenerateAndroidBuildActions is +// passed a AndroidModuleContext rather than the usual blueprint.ModuleContext. +// AndroidModuleContext exposes extra functionality specific to the Android build +// system including details about the particular build variant that is to be +// generated. +// +// For example: +// +// import ( +// "android/soong/common" +// "blueprint" +// ) +// +// type myModule struct { +// common.AndroidModuleBase +// properties struct { +// MyProperty string +// } +// } +// +// func NewMyModule() (blueprint.Module, []interface{}) { +// m := &myModule{} +// return common.InitAndroidModule(m, &m.properties) +// } +// +// func (m *myModule) GenerateAndroidBuildActions(ctx common.AndroidModuleContext) { +// // Get the CPU architecture for the current build variant. +// variantArch := ctx.Arch() +// +// // ... +// } +type AndroidModuleBase struct { + // Putting the curiously recurring thing pointing to the thing that contains + // the thing pattern to good use. + module AndroidModule + + commonProperties commonProperties + hostAndDeviceProperties hostAndDeviceProperties + generalProperties []interface{} + archProperties []*archProperties + + noAddressSanitizer bool + installFiles []string + checkbuildFiles []string +} + +func (a *AndroidModuleBase) base() *AndroidModuleBase { + return a +} + +func (a *AndroidModuleBase) SetArch(arch Arch) { + a.commonProperties.CompileArch = arch +} + +func (a *AndroidModuleBase) HostOrDevice() HostOrDevice { + return a.commonProperties.CompileArch.HostOrDevice +} + +func (a *AndroidModuleBase) HostSupported() bool { + return a.commonProperties.HostOrDeviceSupported == HostSupported || + a.commonProperties.HostOrDeviceSupported == HostAndDeviceSupported && + a.hostAndDeviceProperties.Host_supported +} + +func (a *AndroidModuleBase) DeviceSupported() bool { + return a.commonProperties.HostOrDeviceSupported == DeviceSupported || + a.commonProperties.HostOrDeviceSupported == HostAndDeviceSupported && + a.hostAndDeviceProperties.Device_supported +} + +func (a *AndroidModuleBase) Disabled() bool { + return a.commonProperties.Disabled +} + +func (a *AndroidModuleBase) computeInstallDeps( + ctx blueprint.ModuleContext) []string { + + result := []string{} + ctx.VisitDepsDepthFirstIf(isFileInstaller, + func(m blueprint.Module) { + fileInstaller := m.(fileInstaller) + files := fileInstaller.filesToInstall() + result = append(result, files...) + }) + + return result +} + +func (a *AndroidModuleBase) filesToInstall() []string { + return a.installFiles +} + +func (p *AndroidModuleBase) NoAddressSanitizer() bool { + return p.noAddressSanitizer +} + +func (p *AndroidModuleBase) resourceDirs() []string { + return p.commonProperties.ResourceDirs +} + +func (a *AndroidModuleBase) generateModuleTarget(ctx blueprint.ModuleContext) { + if a != ctx.FinalModule().(AndroidModule).base() { + return + } + + allInstalledFiles := []string{} + ctx.VisitAllModuleVariants(func(module blueprint.Module) { + if androidModule, ok := module.(AndroidModule); ok { + files := androidModule.base().installFiles + allInstalledFiles = append(allInstalledFiles, files...) + } + }) + + if len(allInstalledFiles) > 0 { + ctx.Build(pctx, blueprint.BuildParams{ + Rule: blueprint.Phony, + Outputs: []string{ctx.ModuleName()}, + Inputs: allInstalledFiles, + }) + } +} + +func (a *AndroidModuleBase) DynamicDependencies(ctx blueprint.DynamicDependerModuleContext) []string { + actx := &androidDynamicDependerContext{ + DynamicDependerModuleContext: ctx, + module: a, + } + + if dynamic, ok := a.module.(AndroidDynamicDepender); ok { + return dynamic.AndroidDynamicDependencies(actx) + } + + return nil +} + +func (a *AndroidModuleBase) GenerateBuildActions(ctx blueprint.ModuleContext) { + androidCtx := &androidModuleContext{ + ModuleContext: ctx, + installDeps: a.computeInstallDeps(ctx), + installFiles: a.installFiles, + arch: a.commonProperties.CompileArch, + } + + if a.commonProperties.Disabled { + return + } + + a.module.GenerateAndroidBuildActions(androidCtx) + if ctx.Failed() { + return + } + + a.generateModuleTarget(ctx) + if ctx.Failed() { + return + } +} + +type androidModuleContext struct { + blueprint.ModuleContext + arch Arch + installDeps []string + installFiles []string + checkbuildFiles []string +} + +func (a *androidModuleContext) Build(pctx *blueprint.PackageContext, params blueprint.BuildParams) { + params.Optional = true + a.ModuleContext.Build(pctx, params) +} + +func (a *androidModuleContext) Arch() Arch { + return a.arch +} + +func (a *androidModuleContext) InstallFile(installPath, srcPath string) { + var fullInstallPath string + if a.arch.HostOrDevice.Device() { + // TODO: replace unset with a device name once we have device targeting + fullInstallPath = filepath.Join("out/target/product/unset/system", installPath, + filepath.Base(srcPath)) + } else { + // TODO: replace unset with a host name + fullInstallPath = filepath.Join("out/host/unset/", installPath, filepath.Base(srcPath)) + } + + a.ModuleContext.Build(pctx, blueprint.BuildParams{ + Rule: Cp, + Outputs: []string{fullInstallPath}, + Inputs: []string{srcPath}, + OrderOnly: a.installDeps, + }) + + a.installFiles = append(a.installFiles, fullInstallPath) + a.checkbuildFiles = append(a.checkbuildFiles, srcPath) +} + +func (a *androidModuleContext) CheckbuildFile(srcPath string) { + a.checkbuildFiles = append(a.checkbuildFiles, srcPath) +} + +type androidDynamicDependerContext struct { + blueprint.DynamicDependerModuleContext + module *AndroidModuleBase +} + +type fileInstaller interface { + filesToInstall() []string +} + +func isFileInstaller(m blueprint.Module) bool { + _, ok := m.(fileInstaller) + return ok +} + +func isAndroidModule(m blueprint.Module) bool { + _, ok := m.(AndroidModule) + return ok +} diff --git a/common/paths.go b/common/paths.go new file mode 100644 index 000000000..91b8f99cd --- /dev/null +++ b/common/paths.go @@ -0,0 +1,83 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// 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. + +package common + +import ( + "path/filepath" + + "blueprint" +) + +type Config interface { + CpPreserveSymlinksFlags() string + SrcDir() string +} + +// ModuleOutDir returns the path to the module-specific output directory. +func ModuleOutDir(ctx AndroidModuleContext) string { + return filepath.Join(".intermediates", ctx.ModuleDir(), ctx.ModuleName(), ctx.ModuleSubDir()) +} + +// ModuleSrcDir returns the path of the directory that all source file paths are +// specified relative to. +func ModuleSrcDir(ctx blueprint.ModuleContext) string { + config := ctx.Config().(Config) + return filepath.Join(config.SrcDir(), ctx.ModuleDir()) +} + +// ModuleBinDir returns the path to the module- and architecture-specific binary +// output directory. +func ModuleBinDir(ctx AndroidModuleContext) string { + return filepath.Join(ModuleOutDir(ctx), "bin") +} + +// ModuleLibDir returns the path to the module- and architecture-specific +// library output directory. +func ModuleLibDir(ctx AndroidModuleContext) string { + return filepath.Join(ModuleOutDir(ctx), "lib") +} + +// ModuleGenDir returns the module directory for generated files +// path. +func ModuleGenDir(ctx AndroidModuleContext) string { + return filepath.Join(ModuleOutDir(ctx), "gen") +} + +// ModuleObjDir returns the module- and architecture-specific object directory +// path. +func ModuleObjDir(ctx AndroidModuleContext) string { + return filepath.Join(ModuleOutDir(ctx), "obj") +} + +// ModuleGoPackageDir returns the module-specific package root directory path. +// This directory is where the final package .a files are output and where +// dependent modules search for this package via -I arguments. +func ModuleGoPackageDir(ctx AndroidModuleContext) string { + return filepath.Join(ModuleOutDir(ctx), "pkg") +} + +// ModuleIncludeDir returns the module-specific public include directory path. +func ModuleIncludeDir(ctx AndroidModuleContext) string { + return filepath.Join(ModuleOutDir(ctx), "include") +} + +// ModuleProtoDir returns the module-specific public proto include directory path. +func ModuleProtoDir(ctx AndroidModuleContext) string { + return filepath.Join(ModuleOutDir(ctx), "proto") +} + +func ModuleJSCompiledDir(ctx AndroidModuleContext) string { + return filepath.Join(ModuleOutDir(ctx), "js") +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 000000000..6cdc211e7 --- /dev/null +++ b/config/config.go @@ -0,0 +1,152 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// 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. + +package config + +import ( + "android/soong/common" + "encoding/json" + "fmt" + "os" + "runtime" +) + +var _ common.Config = (*Config)(nil) + +// The configuration file name +const ConfigFileName = "soong.config" + +// A FileConfigurableOptions contains options which can be configured by the +// config file. These will be included in the config struct. +type FileConfigurableOptions struct { +} + +func NewFileConfigurableOptions() FileConfigurableOptions { + f := FileConfigurableOptions{} + return f +} + +// A Config object represents the entire build configuration for Blue. +type Config struct { + FileConfigurableOptions + + srcDir string // the path of the root source directory +} + +// loads configuration options from a JSON file in the cwd. +func loadFromConfigFile(config *Config) error { + // Make a proxy config + var configProxy FileConfigurableOptions + + // Try to open the file + configFileReader, err := os.Open(ConfigFileName) + defer configFileReader.Close() + if os.IsNotExist(err) { + // Need to create a file, so that blueprint & ninja don't get in + // a dependency tracking loop. + // Make a file-configurable-options with defaults, write it out using + // a json writer. + configProxy = NewFileConfigurableOptions() + err = saveToConfigFile(configProxy) + if err != nil { + return err + } + } else { + // Make a decoder for it + jsonDecoder := json.NewDecoder(configFileReader) + err = jsonDecoder.Decode(&configProxy) + if err != nil { + return fmt.Errorf("config file: %s did not parse correctly: "+err.Error(), ConfigFileName) + } + } + + // Copy the configurable options out of the config_proxy into the config, + // and we're done! + config.FileConfigurableOptions = configProxy + + // No error + return nil +} + +func saveToConfigFile(config FileConfigurableOptions) error { + data, err := json.MarshalIndent(&config, "", " ") + if err != nil { + return fmt.Errorf("cannot marshal config data: %s", err.Error()) + } + + configFileWriter, err := os.Create(ConfigFileName) + if err != nil { + return fmt.Errorf("cannot create empty config file %s: %s\n", ConfigFileName, err.Error()) + } + defer configFileWriter.Close() + + _, err = configFileWriter.Write(data) + if err != nil { + return fmt.Errorf("default config file: %s could not be written: %s", ConfigFileName, err.Error()) + } + + return nil +} + +// New creates a new Config object. The srcDir argument specifies the path to +// the root source directory. It also loads the config file, if found. +func New(srcDir string) (*Config, error) { + // Make a config with default options + config := &Config{srcDir: srcDir} + + // Load any configurable options from the configuration file + err := loadFromConfigFile(config) + if err != nil { + return nil, err + } + + return config, nil +} + +func (c *Config) SrcDir() string { + return c.srcDir +} + +// HostGoOS returns the OS of the system that the Go toolchain is being run on. +func (c *Config) HostGoOS() string { + return runtime.GOOS +} + +// PrebuiltOS returns the name of the host OS used in prebuilts directories +func (c *Config) PrebuiltOS() string { + switch runtime.GOOS { + case "linux": + return "linux-x86" + case "darwin": + return "darwin-x86" + default: + panic("Unknown GOOS") + } +} + +// GoRoot returns the path to the root directory of the Go toolchain. +func (c *Config) GoRoot() string { + return fmt.Sprintf("%s/prebuilts/go/%s", c.srcDir, c.PrebuiltOS()) +} + +func (c *Config) CpPreserveSymlinksFlags() string { + switch c.HostGoOS() { + case "darwin": + return "-R" + case "linux": + return "-d" + default: + return "" + } +} diff --git a/copygcclib.sh b/copygcclib.sh new file mode 100755 index 000000000..93c52cc58 --- /dev/null +++ b/copygcclib.sh @@ -0,0 +1,7 @@ +#!/bin/bash -e + +OUT=$1 +shift +LIBPATH=$($@) +cp -f $LIBPATH $OUT +echo "$OUT: $LIBPATH" > ${OUT}.d diff --git a/doc.go b/doc.go new file mode 100644 index 000000000..543c46031 --- /dev/null +++ b/doc.go @@ -0,0 +1,56 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// 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. + +// Soong is a builder for Android that uses Blueprint to parse Blueprints +// files and Ninja to do the dependency tracking and subprocess management. +// Soong itself is responsible for converting the modules read by Blueprint +// into build rules, which will be written to a build.ninja file by Blueprint. +// +// Android build concepts: +// +// Device +// A device is a piece of hardware that will be running Android. It may specify +// global settings like architecture, filesystem configuration, initialization +// scripts, and device drivers. A device may support all variants of a single +// piece of hardware, or multiple devices may be used for different variants. +// A build is never targeted directly at a device, it is always targeted at a +// "product". +// +// Product +// A product is a configuration of a device, often for a specific market or +// use case. It is sometimes referred to as a "SKU". A product defines +// global settings like supported languages, supported use cases, preinstalled +// modules, and user-visible behavior choices. A product selects one and only +// one device. +// +// Module +// A module is a definition of something to be built. It may be a C library or +// binary, a java library, an Android app, etc. A module may be built for multiple +// targets, even in a single build, for example host and device, or 32-bit device +// and 64-bit device. +// +// Installed module +// An installed module is one that has been requested by the selected product, +// or a dependency of an installed module. +// +// Target architecture +// The target architecture is the preferred architecture supported by the selected +// device. It is most commonly 32-bit arm, but may also be 64-bit arm, 32-bit or +// 64-bit x86, or mips. +// +// Secondary architecture +// The secondary architecture specifies the architecture to compile a second copy +// of some modules for devices that support multiple architectures, for example +// 64-bit devices that also support 32-bit binaries. +package soong diff --git a/glob/glob.go b/glob/glob.go new file mode 100644 index 000000000..45ba2855b --- /dev/null +++ b/glob/glob.go @@ -0,0 +1,129 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// 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. + +package glob + +import ( + "io/ioutil" + "os" + "path/filepath" + "strings" + + "blueprint/deptools" +) + +func IsGlob(glob string) bool { + return strings.IndexAny(glob, "*?[") >= 0 +} + +// GlobWithDepFile finds all files that match glob. It compares the list of files +// against the contents of fileListFile, and rewrites fileListFile if it has changed. It also +// writes all of the the directories it traversed as a depenencies on fileListFile to depFile. +// +// The format of glob is either path/*.ext for a single directory glob, or path/**/*.ext +// for a recursive glob. +// +// Returns a list of file paths, and an error. +func GlobWithDepFile(glob, fileListFile, depFile string) (files []string, err error) { + globPattern := filepath.Base(glob) + globDir := filepath.Dir(glob) + recursive := false + + if filepath.Base(globDir) == "**" { + recursive = true + globDir = filepath.Dir(globDir) + } + + var dirs []string + + err = filepath.Walk(globDir, + func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if info.Mode().IsDir() { + dirs = append(dirs, path) + if !recursive && path != globDir { + return filepath.SkipDir + } + } else if info.Mode().IsRegular() { + match, err := filepath.Match(globPattern, info.Name()) + if err != nil { + return err + } + if match { + files = append(files, path) + } + } + + return nil + }) + + fileList := strings.Join(files, "\n") + + writeFileIfChanged(fileListFile, []byte(fileList), 0666) + deptools.WriteDepFile(depFile, fileListFile, dirs) + + return +} + +func writeFileIfChanged(filename string, data []byte, perm os.FileMode) error { + var isChanged bool + + dir := filepath.Dir(filename) + err := os.MkdirAll(dir, 0777) + if err != nil { + return err + } + + info, err := os.Stat(filename) + if err != nil { + if os.IsNotExist(err) { + // The file does not exist yet. + isChanged = true + } else { + return err + } + } else { + if info.Size() != int64(len(data)) { + isChanged = true + } else { + oldData, err := ioutil.ReadFile(filename) + if err != nil { + return err + } + + if len(oldData) != len(data) { + isChanged = true + } else { + for i := range data { + if oldData[i] != data[i] { + isChanged = true + break + } + } + } + } + } + + if isChanged { + err = ioutil.WriteFile(filename, data, perm) + if err != nil { + return err + } + } + + return nil +} diff --git a/root.bp b/root.bp index 2acb742d8..2dd14d4ae 100644 --- a/root.bp +++ b/root.bp @@ -1,4 +1,7 @@ subdirs = [ "build/blueprint", "build/soong", + "bionic/*", + "external/*", + "system/core/*", ]