diff options
Diffstat (limited to 'android')
-rw-r--r-- | android/androidmk.go | 215 | ||||
-rw-r--r-- | android/arch.go | 1061 | ||||
-rw-r--r-- | android/config.go | 323 | ||||
-rw-r--r-- | android/defaults.go | 133 | ||||
-rw-r--r-- | android/defs.go | 73 | ||||
-rw-r--r-- | android/env.go | 55 | ||||
-rw-r--r-- | android/glob.go | 120 | ||||
-rw-r--r-- | android/makevars.go | 242 | ||||
-rw-r--r-- | android/module.go | 695 | ||||
-rw-r--r-- | android/mutator.go | 69 | ||||
-rw-r--r-- | android/package_ctx.go | 133 | ||||
-rw-r--r-- | android/paths.go | 668 | ||||
-rw-r--r-- | android/paths_test.go | 182 | ||||
-rw-r--r-- | android/util.go | 78 | ||||
-rw-r--r-- | android/variable.go | 232 |
15 files changed, 4279 insertions, 0 deletions
diff --git a/android/androidmk.go b/android/androidmk.go new file mode 100644 index 00000000..c8dce0f8 --- /dev/null +++ b/android/androidmk.go @@ -0,0 +1,215 @@ +// 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 android + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "sort" + + "android/soong" + + "github.com/google/blueprint" + "github.com/google/blueprint/proptools" +) + +func init() { + soong.RegisterSingletonType("androidmk", AndroidMkSingleton) +} + +type AndroidMkDataProvider interface { + AndroidMk() (AndroidMkData, error) +} + +type AndroidMkData struct { + Class string + SubName string + OutputFile OptionalPath + Disabled bool + + Custom func(w io.Writer, name, prefix string) error + + Extra []func(w io.Writer, outputFile Path) error +} + +func AndroidMkSingleton() blueprint.Singleton { + return &androidMkSingleton{} +} + +type androidMkSingleton struct{} + +func (c *androidMkSingleton) GenerateBuildActions(ctx blueprint.SingletonContext) { + config := ctx.Config().(Config) + + if !config.EmbeddedInMake() { + return + } + + ctx.SetNinjaBuildDir(pctx, filepath.Join(config.buildDir, "..")) + + var androidMkModulesList []Module + + ctx.VisitAllModules(func(module blueprint.Module) { + if amod, ok := module.(Module); ok { + androidMkModulesList = append(androidMkModulesList, amod) + } + }) + + sort.Sort(AndroidModulesByName{androidMkModulesList, ctx}) + + transMk := PathForOutput(ctx, "Android"+proptools.String(config.ProductVariables.Make_suffix)+".mk") + if ctx.Failed() { + return + } + + err := translateAndroidMk(ctx, transMk.String(), androidMkModulesList) + if err != nil { + ctx.Errorf(err.Error()) + } + + ctx.Build(pctx, blueprint.BuildParams{ + Rule: blueprint.Phony, + Outputs: []string{transMk.String()}, + Optional: true, + }) +} + +func translateAndroidMk(ctx blueprint.SingletonContext, mkFile string, mods []Module) error { + buf := &bytes.Buffer{} + + fmt.Fprintln(buf, "LOCAL_MODULE_MAKEFILE := $(lastword $(MAKEFILE_LIST))") + + for _, mod := range mods { + err := translateAndroidMkModule(ctx, buf, mod) + if err != nil { + os.Remove(mkFile) + return err + } + } + + // Don't write to the file if it hasn't changed + if _, err := os.Stat(mkFile); !os.IsNotExist(err) { + if data, err := ioutil.ReadFile(mkFile); err == nil { + matches := buf.Len() == len(data) + + if matches { + for i, value := range buf.Bytes() { + if value != data[i] { + matches = false + break + } + } + } + + if matches { + return nil + } + } + } + + return ioutil.WriteFile(mkFile, buf.Bytes(), 0666) +} + +func translateAndroidMkModule(ctx blueprint.SingletonContext, w io.Writer, mod blueprint.Module) error { + name := ctx.ModuleName(mod) + + provider, ok := mod.(AndroidMkDataProvider) + if !ok { + return nil + } + + amod := mod.(Module).base() + data, err := provider.AndroidMk() + if err != nil { + return err + } + + if !amod.Enabled() { + return err + } + + if data.SubName != "" { + name += "_" + data.SubName + } + + hostCross := false + if amod.Host() && amod.HostType() != CurrentHostType() { + hostCross = true + } + + if data.Custom != nil { + prefix := "" + if amod.Host() { + if hostCross { + prefix = "HOST_CROSS_" + } else { + prefix = "HOST_" + } + if amod.Arch().ArchType != ctx.Config().(Config).HostArches[amod.HostType()][0].ArchType { + prefix = "2ND_" + prefix + } + } else { + prefix = "TARGET_" + if amod.Arch().ArchType != ctx.Config().(Config).DeviceArches[0].ArchType { + prefix = "2ND_" + prefix + } + } + + return data.Custom(w, name, prefix) + } + + if data.Disabled { + return nil + } + + if !data.OutputFile.Valid() { + return err + } + + fmt.Fprintln(w, "\ninclude $(CLEAR_VARS)") + fmt.Fprintln(w, "LOCAL_PATH :=", filepath.Dir(ctx.BlueprintFile(mod))) + fmt.Fprintln(w, "LOCAL_MODULE :=", name) + fmt.Fprintln(w, "LOCAL_MODULE_CLASS :=", data.Class) + fmt.Fprintln(w, "LOCAL_MULTILIB :=", amod.commonProperties.Compile_multilib) + fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", data.OutputFile.String()) + + archStr := amod.Arch().ArchType.String() + if amod.Host() { + if hostCross { + fmt.Fprintln(w, "LOCAL_MODULE_HOST_CROSS_ARCH :=", archStr) + } else { + fmt.Fprintln(w, "LOCAL_MODULE_HOST_ARCH :=", archStr) + } + fmt.Fprintln(w, "LOCAL_MODULE_HOST_OS :=", amod.HostType().String()) + fmt.Fprintln(w, "LOCAL_IS_HOST_MODULE := true") + } else { + fmt.Fprintln(w, "LOCAL_MODULE_TARGET_ARCH :=", archStr) + } + + for _, extra := range data.Extra { + err = extra(w, data.OutputFile.Path()) + if err != nil { + return err + } + } + + fmt.Fprintln(w, "include $(BUILD_PREBUILT)") + + return err +} diff --git a/android/arch.go b/android/arch.go new file mode 100644 index 00000000..952b7207 --- /dev/null +++ b/android/arch.go @@ -0,0 +1,1061 @@ +// 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 android + +import ( + "fmt" + "reflect" + "runtime" + "strings" + + "github.com/google/blueprint" + "github.com/google/blueprint/proptools" +) + +func init() { + RegisterBottomUpMutator("defaults_deps", defaultsDepsMutator) + RegisterTopDownMutator("defaults", defaultsMutator) + + RegisterBottomUpMutator("host_or_device", HostOrDeviceMutator) + RegisterBottomUpMutator("host_type", HostTypeMutator) + RegisterBottomUpMutator("arch", ArchMutator) +} + +var ( + Arm = newArch("arm", "lib32") + Arm64 = newArch("arm64", "lib64") + Mips = newArch("mips", "lib32") + Mips64 = newArch("mips64", "lib64") + X86 = newArch("x86", "lib32") + X86_64 = newArch("x86_64", "lib64") + + Common = ArchType{ + Name: "common", + } +) + +var archTypeMap = map[string]ArchType{ + "arm": Arm, + "arm64": Arm64, + "mips": Mips, + "mips64": Mips64, + "x86": X86, + "x86_64": 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 Embed interface{} + +type archProperties struct { + // Properties to vary by target architecture + Arch struct { + // Properties for module variants being built to run on arm (host or device) + Arm struct { + Embed `blueprint:"filter(android:\"arch_variant\")"` + + // Arm arch variants + Armv5te interface{} `blueprint:"filter(android:\"arch_variant\")"` + Armv7_a interface{} `blueprint:"filter(android:\"arch_variant\")"` + Armv7_a_neon interface{} `blueprint:"filter(android:\"arch_variant\")"` + + // Arm cpu variants + Cortex_a7 interface{} `blueprint:"filter(android:\"arch_variant\")"` + Cortex_a8 interface{} `blueprint:"filter(android:\"arch_variant\")"` + Cortex_a9 interface{} `blueprint:"filter(android:\"arch_variant\")"` + Cortex_a15 interface{} `blueprint:"filter(android:\"arch_variant\")"` + Cortex_a53 interface{} `blueprint:"filter(android:\"arch_variant\")"` + Cortex_a53_a57 interface{} `blueprint:"filter(android:\"arch_variant\")"` + Krait interface{} `blueprint:"filter(android:\"arch_variant\")"` + Denver interface{} `blueprint:"filter(android:\"arch_variant\")"` + } + + // Properties for module variants being built to run on arm64 (host or device) + Arm64 struct { + Embed `blueprint:"filter(android:\"arch_variant\")"` + + // Arm64 arch variants + Armv8_a interface{} `blueprint:"filter(android:\"arch_variant\")"` + + // Arm64 cpu variants + Cortex_a53 interface{} `blueprint:"filter(android:\"arch_variant\")"` + Denver64 interface{} `blueprint:"filter(android:\"arch_variant\")"` + } + + // Properties for module variants being built to run on mips (host or device) + Mips struct { + Embed `blueprint:"filter(android:\"arch_variant\")"` + + // Mips arch variants + Mips32_fp interface{} `blueprint:"filter(android:\"arch_variant\")"` + Mips32r2_fp interface{} `blueprint:"filter(android:\"arch_variant\")"` + Mips32r2_fp_xburst interface{} `blueprint:"filter(android:\"arch_variant\")"` + Mips32r2dsp_fp interface{} `blueprint:"filter(android:\"arch_variant\")"` + Mips32r2dspr2_fp interface{} `blueprint:"filter(android:\"arch_variant\")"` + Mips32r6 interface{} `blueprint:"filter(android:\"arch_variant\")"` + + // Mips arch features + Rev6 interface{} `blueprint:"filter(android:\"arch_variant\")"` + } + + // Properties for module variants being built to run on mips64 (host or device) + Mips64 struct { + Embed `blueprint:"filter(android:\"arch_variant\")"` + + // Mips64 arch variants + Mips64r2 interface{} `blueprint:"filter(android:\"arch_variant\")"` + Mips64r6 interface{} `blueprint:"filter(android:\"arch_variant\")"` + + // Mips64 arch features + Rev6 interface{} `blueprint:"filter(android:\"arch_variant\")"` + } + + // Properties for module variants being built to run on x86 (host or device) + X86 struct { + Embed `blueprint:"filter(android:\"arch_variant\")"` + + // X86 arch variants + Atom interface{} `blueprint:"filter(android:\"arch_variant\")"` + Haswell interface{} `blueprint:"filter(android:\"arch_variant\")"` + Ivybridge interface{} `blueprint:"filter(android:\"arch_variant\")"` + Sandybridge interface{} `blueprint:"filter(android:\"arch_variant\")"` + Silvermont interface{} `blueprint:"filter(android:\"arch_variant\")"` + // Generic variant for X86 on X86_64 + X86_64 interface{} `blueprint:"filter(android:\"arch_variant\")"` + + // X86 arch features + Ssse3 interface{} `blueprint:"filter(android:\"arch_variant\")"` + Sse4 interface{} `blueprint:"filter(android:\"arch_variant\")"` + Sse4_1 interface{} `blueprint:"filter(android:\"arch_variant\")"` + Sse4_2 interface{} `blueprint:"filter(android:\"arch_variant\")"` + Aes_ni interface{} `blueprint:"filter(android:\"arch_variant\")"` + Avx interface{} `blueprint:"filter(android:\"arch_variant\")"` + Popcnt interface{} `blueprint:"filter(android:\"arch_variant\")"` + Movbe interface{} `blueprint:"filter(android:\"arch_variant\")"` + } + + // Properties for module variants being built to run on x86_64 (host or device) + X86_64 struct { + Embed `blueprint:"filter(android:\"arch_variant\")"` + + // X86 arch variants + Haswell interface{} `blueprint:"filter(android:\"arch_variant\")"` + Ivybridge interface{} `blueprint:"filter(android:\"arch_variant\")"` + Sandybridge interface{} `blueprint:"filter(android:\"arch_variant\")"` + Silvermont interface{} `blueprint:"filter(android:\"arch_variant\")"` + + // X86 arch features + Ssse3 interface{} `blueprint:"filter(android:\"arch_variant\")"` + Sse4 interface{} `blueprint:"filter(android:\"arch_variant\")"` + Sse4_1 interface{} `blueprint:"filter(android:\"arch_variant\")"` + Sse4_2 interface{} `blueprint:"filter(android:\"arch_variant\")"` + Aes_ni interface{} `blueprint:"filter(android:\"arch_variant\")"` + Avx interface{} `blueprint:"filter(android:\"arch_variant\")"` + Popcnt interface{} `blueprint:"filter(android:\"arch_variant\")"` + } + } + + // Properties to vary by 32-bit or 64-bit + Multilib struct { + // Properties for module variants being built to run on 32-bit devices + Lib32 interface{} `blueprint:"filter(android:\"arch_variant\")"` + // Properties for module variants being built to run on 64-bit devices + Lib64 interface{} `blueprint:"filter(android:\"arch_variant\")"` + } + // Properties to vary by build target (host or device, os, os+archictecture) + Target struct { + // Properties for module variants being built to run on the host + Host interface{} `blueprint:"filter(android:\"arch_variant\")"` + // Properties for module variants being built to run on the device + Android interface{} `blueprint:"filter(android:\"arch_variant\")"` + // Properties for module variants being built to run on arm devices + Android_arm interface{} `blueprint:"filter(android:\"arch_variant\")"` + // Properties for module variants being built to run on arm64 devices + Android_arm64 interface{} `blueprint:"filter(android:\"arch_variant\")"` + // Properties for module variants being built to run on mips devices + Android_mips interface{} `blueprint:"filter(android:\"arch_variant\")"` + // Properties for module variants being built to run on mips64 devices + Android_mips64 interface{} `blueprint:"filter(android:\"arch_variant\")"` + // Properties for module variants being built to run on x86 devices + Android_x86 interface{} `blueprint:"filter(android:\"arch_variant\")"` + // Properties for module variants being built to run on x86_64 devices + Android_x86_64 interface{} `blueprint:"filter(android:\"arch_variant\")"` + // Properties for module variants being built to run on devices that support 64-bit + Android64 interface{} `blueprint:"filter(android:\"arch_variant\")"` + // Properties for module variants being built to run on devices that do not support 64-bit + Android32 interface{} `blueprint:"filter(android:\"arch_variant\")"` + // Properties for module variants being built to run on linux hosts + Linux interface{} `blueprint:"filter(android:\"arch_variant\")"` + // Properties for module variants being built to run on linux x86 hosts + Linux_x86 interface{} `blueprint:"filter(android:\"arch_variant\")"` + // Properties for module variants being built to run on linux x86_64 hosts + Linux_x86_64 interface{} `blueprint:"filter(android:\"arch_variant\")"` + // Properties for module variants being built to run on darwin hosts + Darwin interface{} `blueprint:"filter(android:\"arch_variant\")"` + // Properties for module variants being built to run on darwin x86 hosts + Darwin_x86 interface{} `blueprint:"filter(android:\"arch_variant\")"` + // Properties for module variants being built to run on darwin x86_64 hosts + Darwin_x86_64 interface{} `blueprint:"filter(android:\"arch_variant\")"` + // Properties for module variants being built to run on windows hosts + Windows interface{} `blueprint:"filter(android:\"arch_variant\")"` + // Properties for module variants being built to run on windows x86 hosts + Windows_x86 interface{} `blueprint:"filter(android:\"arch_variant\")"` + // Properties for module variants being built to run on windows x86_64 hosts + Windows_x86_64 interface{} `blueprint:"filter(android:\"arch_variant\")"` + // Properties for module variants being built to run on linux or darwin hosts + Not_windows interface{} `blueprint:"filter(android:\"arch_variant\")"` + } +} + +var archFeatureMap = map[ArchType]map[string][]string{} + +func RegisterArchFeatures(arch ArchType, variant string, features ...string) { + archField := proptools.FieldNameForProperty(arch.Name) + variantField := proptools.FieldNameForProperty(variant) + archStruct := reflect.ValueOf(archProperties{}.Arch).FieldByName(archField) + if variant != "" { + if !archStruct.FieldByName(variantField).IsValid() { + panic(fmt.Errorf("Invalid variant %q for arch %q", variant, arch)) + } + } + for _, feature := range features { + field := proptools.FieldNameForProperty(feature) + if !archStruct.FieldByName(field).IsValid() { + panic(fmt.Errorf("Invalid feature %q for arch %q variant %q", feature, arch, variant)) + } + } + if archFeatureMap[arch] == nil { + archFeatureMap[arch] = make(map[string][]string) + } + archFeatureMap[arch][variant] = features +} + +// An Arch indicates a single CPU architecture. +type Arch struct { + ArchType ArchType + ArchVariant string + CpuVariant string + Abi []string + ArchFeatures []string +} + +func (a Arch) String() string { + s := a.ArchType.String() + if a.ArchVariant != "" { + s += "_" + a.ArchVariant + } + if a.CpuVariant != "" { + s += "_" + a.CpuVariant + } + return s +} + +type ArchType struct { + Name string + Multilib string +} + +func newArch(name, multilib string) ArchType { + return ArchType{ + Name: name, + Multilib: multilib, + } +} + +func (a ArchType) String() string { + return a.Name +} + +type HostOrDeviceSupported int + +const ( + _ HostOrDeviceSupported = iota + HostSupported + DeviceSupported + HostAndDeviceSupported + HostAndDeviceDefault +) + +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) Property() 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", +} + +type HostType int + +const ( + NoHostType HostType = iota + Linux + Darwin + Windows +) + +func CurrentHostType() HostType { + switch runtime.GOOS { + case "linux": + return Linux + case "darwin": + return Darwin + default: + panic(fmt.Sprintf("unsupported OS: %s", runtime.GOOS)) + } +} + +func (ht HostType) String() string { + switch ht { + case Linux: + return "linux" + case Darwin: + return "darwin" + case Windows: + return "windows" + default: + panic(fmt.Sprintf("unexpected HostType value %d", ht)) + } +} + +func (ht HostType) Field() string { + switch ht { + case Linux: + return "Linux" + case Darwin: + return "Darwin" + case Windows: + return "Windows" + default: + panic(fmt.Sprintf("unexpected HostType value %d", ht)) + } +} + +var ( + commonArch = Arch{ + ArchType: Common, + } +) + +func HostOrDeviceMutator(mctx BottomUpMutatorContext) { + var module Module + var ok bool + if module, ok = mctx.Module().(Module); !ok { + return + } + + hods := []HostOrDevice{} + + if module.base().HostSupported() { + hods = append(hods, Host) + } + + if module.base().DeviceSupported() { + hods = append(hods, Device) + } + + if len(hods) == 0 { + return + } + + hodNames := []string{} + for _, hod := range hods { + hodNames = append(hodNames, hod.String()) + } + + modules := mctx.CreateVariations(hodNames...) + for i, m := range modules { + m.(Module).base().SetHostOrDevice(hods[i]) + } +} + +func HostTypeMutator(mctx BottomUpMutatorContext) { + var module Module + var ok bool + if module, ok = mctx.Module().(Module); !ok { + return + } + + if !module.base().HostSupported() || !module.base().HostOrDevice().Host() { + return + } + + buildTypes, err := decodeHostTypesProductVariables(mctx.Config().(Config).ProductVariables) + if err != nil { + mctx.ModuleErrorf("%s", err.Error()) + return + } + + typeNames := []string{} + for _, ht := range buildTypes { + typeNames = append(typeNames, ht.String()) + } + + modules := mctx.CreateVariations(typeNames...) + for i, m := range modules { + m.(Module).base().SetHostType(buildTypes[i]) + } +} + +func ArchMutator(mctx BottomUpMutatorContext) { + var module Module + var ok bool + if module, ok = mctx.Module().(Module); !ok { + return + } + + moduleArches := []Arch{} + multilib := module.base().commonProperties.Compile_multilib + + if module.base().HostSupported() && module.base().HostOrDevice().Host() { + hostModuleArches, err := decodeMultilib(multilib, mctx.Config().(Config).HostArches[module.base().HostType()]) + if err != nil { + mctx.ModuleErrorf("%s", err.Error()) + } + + moduleArches = append(moduleArches, hostModuleArches...) + } + + if module.base().DeviceSupported() && module.base().HostOrDevice().Device() { + deviceModuleArches, err := decodeMultilib(multilib, mctx.Config().(Config).DeviceArches) + if err != nil { + mctx.ModuleErrorf("%s", err.Error()) + } + + moduleArches = append(moduleArches, deviceModuleArches...) + } + + if len(moduleArches) == 0 { + return + } + + archNames := []string{} + for _, arch := range moduleArches { + archNames = append(archNames, arch.String()) + } + + modules := mctx.CreateVariations(archNames...) + + for i, m := range modules { + m.(Module).base().SetArch(moduleArches[i]) + m.(Module).base().setArchProperties(mctx) + } +} + +func InitArchModule(m Module, + propertyStructs ...interface{}) (blueprint.Module, []interface{}) { + + base := m.base() + + base.generalProperties = append(base.generalProperties, + propertyStructs...) + + for _, properties := range base.generalProperties { + propertiesValue := reflect.ValueOf(properties) + if propertiesValue.Kind() != reflect.Ptr { + panic(fmt.Errorf("properties must be a pointer to a struct, got %T", + propertiesValue.Interface())) + } + + propertiesValue = propertiesValue.Elem() + if propertiesValue.Kind() != reflect.Struct { + panic(fmt.Errorf("properties must be a pointer to a struct, got %T", + propertiesValue.Interface())) + } + + archProperties := &archProperties{} + forEachInterface(reflect.ValueOf(archProperties), func(v reflect.Value) { + newValue := proptools.CloneEmptyProperties(propertiesValue) + 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 +} + +var variantReplacer = strings.NewReplacer("-", "_", ".", "_") + +func (a *ModuleBase) appendProperties(ctx BottomUpMutatorContext, + dst, src interface{}, field, srcPrefix string) interface{} { + + srcField := reflect.ValueOf(src).FieldByName(field) + if !srcField.IsValid() { + ctx.ModuleErrorf("field %q does not exist", srcPrefix) + return nil + } + + ret := srcField + + if srcField.Kind() == reflect.Struct { + srcField = srcField.FieldByName("Embed") + } + + src = srcField.Elem().Interface() + + filter := func(property string, + dstField, srcField reflect.StructField, + dstValue, srcValue interface{}) (bool, error) { + + srcProperty := srcPrefix + "." + property + + if !proptools.HasTag(dstField, "android", "arch_variant") { + if ctx.ContainsProperty(srcProperty) { + return false, fmt.Errorf("can't be specific to a build variant") + } else { + return false, nil + } + } + + return true, nil + } + + order := func(property string, + dstField, srcField reflect.StructField, + dstValue, srcValue interface{}) (proptools.Order, error) { + if proptools.HasTag(dstField, "android", "variant_prepend") { + return proptools.Prepend, nil + } else { + return proptools.Append, nil + } + } + + err := proptools.ExtendProperties(dst, src, filter, order) + if err != nil { + if propertyErr, ok := err.(*proptools.ExtendPropertyError); ok { + ctx.PropertyErrorf(propertyErr.Property, "%s", propertyErr.Err.Error()) + } else { + panic(err) + } + } + + return ret.Interface() +} + +// Rewrite the module's properties structs to contain arch-specific values. +func (a *ModuleBase) setArchProperties(ctx BottomUpMutatorContext) { + arch := a.commonProperties.CompileArch + hod := a.commonProperties.CompileHostOrDevice + ht := a.commonProperties.CompileHostType + + if arch.ArchType == Common { + return + } + + for i := range a.generalProperties { + genProps := a.generalProperties[i] + archProps := a.archProperties[i] + // Handle arch-specific properties in the form: + // arch: { + // arm64: { + // key: value, + // }, + // }, + t := arch.ArchType + + field := proptools.FieldNameForProperty(t.Name) + prefix := "arch." + t.Name + archStruct := a.appendProperties(ctx, genProps, archProps.Arch, field, prefix) + + // Handle arch-variant-specific properties in the form: + // arch: { + // variant: { + // key: value, + // }, + // }, + v := variantReplacer.Replace(arch.ArchVariant) + if v != "" { + field := proptools.FieldNameForProperty(v) + prefix := "arch." + t.Name + "." + v + a.appendProperties(ctx, genProps, archStruct, field, prefix) + } + + // Handle cpu-variant-specific properties in the form: + // arch: { + // variant: { + // key: value, + // }, + // }, + c := variantReplacer.Replace(arch.CpuVariant) + if c != "" { + field := proptools.FieldNameForProperty(c) + prefix := "arch." + t.Name + "." + c + a.appendProperties(ctx, genProps, archStruct, field, prefix) + } + + // Handle arch-feature-specific properties in the form: + // arch: { + // feature: { + // key: value, + // }, + // }, + for _, feature := range arch.ArchFeatures { + field := proptools.FieldNameForProperty(feature) + prefix := "arch." + t.Name + "." + feature + a.appendProperties(ctx, genProps, archStruct, field, prefix) + } + + // Handle multilib-specific properties in the form: + // multilib: { + // lib32: { + // key: value, + // }, + // }, + field = proptools.FieldNameForProperty(t.Multilib) + prefix = "multilib." + t.Multilib + a.appendProperties(ctx, genProps, archProps.Multilib, field, prefix) + + // Handle host-or-device-specific properties in the form: + // target: { + // host: { + // key: value, + // }, + // }, + hodProperty := hod.Property() + field = proptools.FieldNameForProperty(hodProperty) + prefix = "target." + hodProperty + a.appendProperties(ctx, genProps, archProps.Target, field, prefix) + + // Handle host target properties in the form: + // target: { + // linux: { + // key: value, + // }, + // not_windows: { + // key: value, + // }, + // linux_x86: { + // key: value, + // }, + // linux_arm: { + // key: value, + // }, + // }, + if hod.Host() { + field := ht.Field() + prefix := "target." + ht.String() + a.appendProperties(ctx, genProps, archProps.Target, field, prefix) + + t := arch.ArchType + field = ht.Field() + "_" + t.Name + prefix = "target." + ht.String() + "_" + t.Name + a.appendProperties(ctx, genProps, archProps.Target, field, prefix) + + if ht != Windows { + field := "Not_windows" + prefix := "target.not_windows" + a.appendProperties(ctx, genProps, archProps.Target, field, prefix) + } + } + + // Handle 64-bit device properties in the form: + // target { + // android64 { + // key: value, + // }, + // android32 { + // key: value, + // }, + // }, + // WARNING: this is probably not what you want to use in your blueprints file, it selects + // options for all targets on a device that supports 64-bit binaries, not just the targets + // that are being compiled for 64-bit. Its expected use case is binaries like linker and + // debuggerd that need to know when they are a 32-bit process running on a 64-bit device + if hod.Device() { + if true /* && target_is_64_bit */ { + field := "Android64" + prefix := "target.android64" + a.appendProperties(ctx, genProps, archProps.Target, field, prefix) + } else { + field := "Android32" + prefix := "target.android32" + a.appendProperties(ctx, genProps, archProps.Target, field, prefix) + } + } + + // Handle device architecture properties in the form: + // target { + // android_arm { + // key: value, + // }, + // android_x86 { + // key: value, + // }, + // }, + if hod.Device() { + t := arch.ArchType + field := "Android_" + t.Name + prefix := "target.android_" + t.Name + a.appendProperties(ctx, genProps, archProps.Target, field, prefix) + } + + 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())) + } +} + +// Get a list of HostTypes from the product variables +func decodeHostTypesProductVariables(variables productVariables) ([]HostType, error) { + ret := []HostType{CurrentHostType()} + + if variables.CrossHost != nil && *variables.CrossHost != "" { + switch *variables.CrossHost { + case "windows": + ret = append(ret, Windows) + default: + return nil, fmt.Errorf("Unsupported secondary host: %s", *variables.CrossHost) + } + } + + return ret, nil +} + +// Convert the arch product variables into a list of host and device Arch structs +func decodeArchProductVariables(variables productVariables) (map[HostType][]Arch, []Arch, error) { + if variables.HostArch == nil { + return nil, nil, fmt.Errorf("No host primary architecture set") + } + + hostArch, err := decodeArch(*variables.HostArch, nil, nil, nil) + if err != nil { + return nil, nil, err + } + + hostArches := []Arch{hostArch} + + if variables.HostSecondaryArch != nil && *variables.HostSecondaryArch != "" { + hostSecondaryArch, err := decodeArch(*variables.HostSecondaryArch, nil, nil, nil) + if err != nil { + return nil, nil, err + } + hostArches = append(hostArches, hostSecondaryArch) + } + + hostTypeArches := map[HostType][]Arch{ + CurrentHostType(): hostArches, + } + + if variables.CrossHost != nil && *variables.CrossHost != "" { + if variables.CrossHostArch == nil || *variables.CrossHostArch == "" { + return nil, nil, fmt.Errorf("No cross-host primary architecture set") + } + + crossHostArch, err := decodeArch(*variables.CrossHostArch, nil, nil, nil) + if err != nil { + return nil, nil, err + } + + crossHostArches := []Arch{crossHostArch} + + if variables.CrossHostSecondaryArch != nil && *variables.CrossHostSecondaryArch != "" { + crossHostSecondaryArch, err := decodeArch(*variables.CrossHostSecondaryArch, nil, nil, nil) + if err != nil { + return nil, nil, err + } + crossHostArches = append(crossHostArches, crossHostSecondaryArch) + } + + switch *variables.CrossHost { + case "windows": + hostTypeArches[Windows] = crossHostArches + default: + return nil, nil, fmt.Errorf("Unsupported cross-host: %s", *variables.CrossHost) + } + } + + if variables.DeviceArch == nil { + return nil, nil, fmt.Errorf("No device primary architecture set") + } + + deviceArch, err := decodeArch(*variables.DeviceArch, variables.DeviceArchVariant, + variables.DeviceCpuVariant, variables.DeviceAbi) + if err != nil { + return nil, nil, err + } + + deviceArches := []Arch{deviceArch} + + if variables.DeviceSecondaryArch != nil && *variables.DeviceSecondaryArch != "" { + deviceSecondaryArch, err := decodeArch(*variables.DeviceSecondaryArch, + variables.DeviceSecondaryArchVariant, variables.DeviceSecondaryCpuVariant, + variables.DeviceSecondaryAbi) + if err != nil { + return nil, nil, err + } + deviceArches = append(deviceArches, deviceSecondaryArch) + } + + return hostTypeArches, deviceArches, nil +} + +func decodeMegaDevice() ([]Arch, error) { + archSettings := []struct { + arch string + archVariant string + cpuVariant string + abi []string + }{ + // armv5 is only used for unbundled apps + //{"arm", "armv5te", "", []string{"armeabi"}}, + {"arm", "armv7-a", "generic", []string{"armeabi-v7a"}}, + {"arm", "armv7-a-neon", "generic", []string{"armeabi-v7a"}}, + {"arm", "armv7-a-neon", "cortex-a7", []string{"armeabi-v7a"}}, + {"arm", "armv7-a-neon", "cortex-a8", []string{"armeabi-v7a"}}, + {"arm", "armv7-a-neon", "cortex-a9", []string{"armeabi-v7a"}}, + {"arm", "armv7-a-neon", "cortex-a15", []string{"armeabi-v7a"}}, + {"arm", "armv7-a-neon", "cortex-a53", []string{"armeabi-v7a"}}, + {"arm", "armv7-a-neon", "cortex-a53.a57", []string{"armeabi-v7a"}}, + {"arm", "armv7-a-neon", "denver", []string{"armeabi-v7a"}}, + {"arm", "armv7-a-neon", "krait", []string{"armeabi-v7a"}}, + {"arm64", "armv8-a", "cortex-a53", []string{"arm64-v8a"}}, + {"arm64", "armv8-a", "denver64", []string{"arm64-v8a"}}, + {"mips", "mips32-fp", "", []string{"mips"}}, + {"mips", "mips32r2-fp", "", []string{"mips"}}, + {"mips", "mips32r2-fp-xburst", "", []string{"mips"}}, + {"mips", "mips32r6", "", []string{"mips"}}, + // mips32r2dsp[r2]-fp fails in the assembler for divdf3.c in compiler-rt: + // (same errors in make and soong) + // Error: invalid operands `mtlo $ac0,$11' + // Error: invalid operands `mthi $ac0,$12' + //{"mips", "mips32r2dsp-fp", "", []string{"mips"}}, + //{"mips", "mips32r2dspr2-fp", "", []string{"mips"}}, + // mips64r2 is mismatching 64r2 and 64r6 libraries during linking to libgcc + //{"mips64", "mips64r2", "", []string{"mips64"}}, + {"mips64", "mips64r6", "", []string{"mips64"}}, + {"x86", "", "", []string{"x86"}}, + {"x86", "atom", "", []string{"x86"}}, + {"x86", "haswell", "", []string{"x86"}}, + {"x86", "ivybridge", "", []string{"x86"}}, + {"x86", "sandybridge", "", []string{"x86"}}, + {"x86", "silvermont", "", []string{"x86"}}, + {"x86", "x86_64", "", []string{"x86"}}, + {"x86_64", "", "", []string{"x86_64"}}, + {"x86_64", "haswell", "", []string{"x86_64"}}, + {"x86_64", "ivybridge", "", []string{"x86_64"}}, + {"x86_64", "sandybridge", "", []string{"x86_64"}}, + {"x86_64", "silvermont", "", []string{"x86_64"}}, + } + + var ret []Arch + + for _, config := range archSettings { + arch, err := decodeArch(config.arch, &config.archVariant, + &config.cpuVariant, &config.abi) + if err != nil { + return nil, err + } + ret = append(ret, arch) + } + + return ret, nil +} + +// Convert a set of strings from product variables into a single Arch struct +func decodeArch(arch string, archVariant, cpuVariant *string, abi *[]string) (Arch, error) { + stringPtr := func(p *string) string { + if p != nil { + return *p + } + return "" + } + + slicePtr := func(p *[]string) []string { + if p != nil { + return *p + } + return nil + } + + archType, ok := archTypeMap[arch] + if !ok { + return Arch{}, fmt.Errorf("unknown arch %q", arch) + } + + a := Arch{ + ArchType: archType, + ArchVariant: stringPtr(archVariant), + CpuVariant: stringPtr(cpuVariant), + Abi: slicePtr(abi), + } + + if a.ArchVariant == a.ArchType.Name || a.ArchVariant == "generic" { + a.ArchVariant = "" + } + + if a.CpuVariant == a.ArchType.Name || a.CpuVariant == "generic" { + a.CpuVariant = "" + } + + for i := 0; i < len(a.Abi); i++ { + if a.Abi[i] == "" { + a.Abi = append(a.Abi[:i], a.Abi[i+1:]...) + i-- + } + } + + if featureMap, ok := archFeatureMap[archType]; ok { + a.ArchFeatures = featureMap[a.ArchVariant] + } + + return a, nil +} + +// Use the module multilib setting to select one or more arches from an arch list +func decodeMultilib(multilib string, arches []Arch) ([]Arch, error) { + buildArches := []Arch{} + switch multilib { + case "common": + buildArches = append(buildArches, commonArch) + case "both": + buildArches = append(buildArches, arches...) + case "first": + buildArches = append(buildArches, arches[0]) + case "32": + for _, a := range arches { + if a.ArchType.Multilib == "lib32" { + buildArches = append(buildArches, a) + } + } + case "64": + for _, a := range arches { + if a.ArchType.Multilib == "lib64" { + buildArches = append(buildArches, a) + } + } + default: + return nil, fmt.Errorf(`compile_multilib must be "both", "first", "32", or "64", found %q`, + multilib) + //buildArches = append(buildArches, arches[0]) + } + + return buildArches, nil +} diff --git a/android/config.go b/android/config.go new file mode 100644 index 00000000..8701960e --- /dev/null +++ b/android/config.go @@ -0,0 +1,323 @@ +// 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 android + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + "sync" + + "github.com/google/blueprint/proptools" +) + +var Bool = proptools.Bool + +// The configuration file name +const configFileName = "soong.config" +const productVariablesFileName = "soong.variables" + +// A FileConfigurableOptions contains options which can be configured by the +// config file. These will be included in the config struct. +type FileConfigurableOptions struct { + Mega_device *bool `json:",omitempty"` +} + +func (f *FileConfigurableOptions) SetDefaultConfig() { + *f = FileConfigurableOptions{} +} + +type Config struct { + *config +} + +// A config object represents the entire build configuration for Android. +type config struct { + FileConfigurableOptions + ProductVariables productVariables + + ConfigFileName string + ProductVariablesFileName string + + DeviceArches []Arch + HostArches map[HostType][]Arch + + srcDir string // the path of the root source directory + buildDir string // the path of the build output directory + + envLock sync.Mutex + envDeps map[string]string + envFrozen bool + + inMake bool +} + +type jsonConfigurable interface { + SetDefaultConfig() +} + +func loadConfig(config *config) error { + err := loadFromConfigFile(&config.FileConfigurableOptions, config.ConfigFileName) + if err != nil { + return err + } + + return loadFromConfigFile(&config.ProductVariables, config.ProductVariablesFileName) +} + +// loads configuration options from a JSON file in the cwd. +func loadFromConfigFile(configurable jsonConfigurable, filename string) error { + // Try to open the file + configFileReader, err := os.Open(filename) + 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. + configurable.SetDefaultConfig() + err = saveToConfigFile(configurable, filename) + if err != nil { + return err + } + } else { + // Make a decoder for it + jsonDecoder := json.NewDecoder(configFileReader) + err = jsonDecoder.Decode(configurable) + if err != nil { + return fmt.Errorf("config file: %s did not parse correctly: "+err.Error(), filename) + } + } + + // No error + return nil +} + +func saveToConfigFile(config jsonConfigurable, filename string) error { + data, err := json.MarshalIndent(&config, "", " ") + if err != nil { + return fmt.Errorf("cannot marshal config data: %s", err.Error()) + } + + configFileWriter, err := os.Create(filename) + if err != nil { + return fmt.Errorf("cannot create empty config file %s: %s\n", filename, err.Error()) + } + defer configFileWriter.Close() + + _, err = configFileWriter.Write(data) + if err != nil { + return fmt.Errorf("default config file: %s could not be written: %s", filename, err.Error()) + } + + _, err = configFileWriter.WriteString("\n") + if err != nil { + return fmt.Errorf("default config file: %s could not be written: %s", filename, 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 NewConfig(srcDir, buildDir string) (Config, error) { + // Make a config with default options + config := Config{ + config: &config{ + ConfigFileName: filepath.Join(buildDir, configFileName), + ProductVariablesFileName: filepath.Join(buildDir, productVariablesFileName), + + srcDir: srcDir, + buildDir: buildDir, + envDeps: make(map[string]string), + }, + } + + // Sanity check the build and source directories. This won't catch strange + // configurations with symlinks, but at least checks the obvious cases. + absBuildDir, err := filepath.Abs(buildDir) + if err != nil { + return Config{}, err + } + + absSrcDir, err := filepath.Abs(srcDir) + if err != nil { + return Config{}, err + } + + if strings.HasPrefix(absSrcDir, absBuildDir) { + return Config{}, fmt.Errorf("Build dir must not contain source directory") + } + + // Load any configurable options from the configuration file + err = loadConfig(config.config) + if err != nil { + return Config{}, err + } + + inMakeFile := filepath.Join(buildDir, ".soong.in_make") + if _, err := os.Stat(inMakeFile); err == nil { + config.inMake = true + } + + hostArches, deviceArches, err := decodeArchProductVariables(config.ProductVariables) + if err != nil { + return Config{}, err + } + + if Bool(config.Mega_device) { + deviceArches, err = decodeMegaDevice() + if err != nil { + return Config{}, err + } + } + + config.HostArches = hostArches + config.DeviceArches = deviceArches + + return config, nil +} + +func (c *config) RemoveAbandonedFiles() bool { + return false +} + +// 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 runtime.GOOS { + case "darwin": + return "-R" + case "linux": + return "-d" + default: + return "" + } +} + +func (c *config) Getenv(key string) string { + var val string + var exists bool + c.envLock.Lock() + if val, exists = c.envDeps[key]; !exists { + if c.envFrozen { + panic("Cannot access new environment variables after envdeps are frozen") + } + val = os.Getenv(key) + c.envDeps[key] = val + } + c.envLock.Unlock() + return val +} + +func (c *config) EnvDeps() map[string]string { + c.envLock.Lock() + c.envFrozen = true + c.envLock.Unlock() + return c.envDeps +} + +func (c *config) EmbeddedInMake() bool { + return c.inMake +} + +// DeviceName returns the name of the current device target +// TODO: take an AndroidModuleContext to select the device name for multi-device builds +func (c *config) DeviceName() string { + return *c.ProductVariables.DeviceName +} + +func (c *config) DeviceUsesClang() bool { + if c.ProductVariables.DeviceUsesClang != nil { + return *c.ProductVariables.DeviceUsesClang + } + return true +} + +func (c *config) ResourceOverlays() []SourcePath { + return nil +} + +func (c *config) PlatformVersion() string { + return "M" +} + +func (c *config) PlatformSdkVersion() string { + return "22" +} + +func (c *config) BuildNumber() string { + return "000000" +} + +func (c *config) ProductAaptConfig() []string { + return []string{"normal", "large", "xlarge", "hdpi", "xhdpi", "xxhdpi"} +} + +func (c *config) ProductAaptPreferredConfig() string { + return "xhdpi" +} + +func (c *config) ProductAaptCharacteristics() string { + return "nosdcard" +} + +func (c *config) DefaultAppCertificateDir(ctx PathContext) SourcePath { + return PathForSource(ctx, "build/target/product/security") +} + +func (c *config) DefaultAppCertificate(ctx PathContext) SourcePath { + return c.DefaultAppCertificateDir(ctx).Join(ctx, "testkey") +} + +func (c *config) AllowMissingDependencies() bool { + return Bool(c.ProductVariables.Allow_missing_dependencies) +} + +func (c *config) SkipDeviceInstall() bool { + return c.EmbeddedInMake() || Bool(c.Mega_device) +} + +func (c *config) SanitizeHost() []string { + if c.ProductVariables.SanitizeHost == nil { + return nil + } + return *c.ProductVariables.SanitizeHost +} + +func (c *config) SanitizeDevice() []string { + if c.ProductVariables.SanitizeDevice == nil { + return nil + } + return *c.ProductVariables.SanitizeDevice +} diff --git a/android/defaults.go b/android/defaults.go new file mode 100644 index 00000000..f9cf0cc8 --- /dev/null +++ b/android/defaults.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 android + +import ( + "github.com/google/blueprint" + "github.com/google/blueprint/proptools" +) + +type defaultsDependencyTag struct { + blueprint.BaseDependencyTag +} + +var DefaultsDepTag defaultsDependencyTag + +type defaultsProperties struct { + Defaults []string +} + +type DefaultableModule struct { + defaultsProperties defaultsProperties + defaultableProperties []interface{} +} + +func (d *DefaultableModule) defaults() *defaultsProperties { + return &d.defaultsProperties +} + +func (d *DefaultableModule) setProperties(props []interface{}) { + d.defaultableProperties = props +} + +type Defaultable interface { + defaults() *defaultsProperties + setProperties([]interface{}) + applyDefaults(TopDownMutatorContext, Defaults) +} + +var _ Defaultable = (*DefaultableModule)(nil) + +func InitDefaultableModule(module Module, d Defaultable, + props ...interface{}) (blueprint.Module, []interface{}) { + + d.setProperties(props) + + props = append(props, d.defaults()) + + return module, props +} + +type DefaultsModule struct { + defaultProperties []interface{} +} + +type Defaults interface { + isDefaults() bool + setProperties([]interface{}) + properties() []interface{} +} + +func (d *DefaultsModule) isDefaults() bool { + return true +} + +func (d *DefaultsModule) properties() []interface{} { + return d.defaultProperties +} + +func (d *DefaultsModule) setProperties(props []interface{}) { + d.defaultProperties = props +} + +func InitDefaultsModule(module Module, d Defaults, props ...interface{}) (blueprint.Module, []interface{}) { + d.setProperties(props) + + return module, props +} + +var _ Defaults = (*DefaultsModule)(nil) + +func (defaultable *DefaultableModule) applyDefaults(ctx TopDownMutatorContext, + defaults Defaults) { + + for _, prop := range defaultable.defaultableProperties { + for _, def := range defaults.properties() { + if proptools.TypeEqual(prop, def) { + err := proptools.PrependProperties(prop, def, nil) + if err != nil { + if propertyErr, ok := err.(*proptools.ExtendPropertyError); ok { + ctx.PropertyErrorf(propertyErr.Property, "%s", propertyErr.Err.Error()) + } else { + panic(err) + } + } + } + } + } +} + +func defaultsDepsMutator(ctx BottomUpMutatorContext) { + if defaultable, ok := ctx.Module().(Defaultable); ok { + ctx.AddDependency(ctx.Module(), DefaultsDepTag, defaultable.defaults().Defaults...) + } +} + +func defaultsMutator(ctx TopDownMutatorContext) { + if defaultable, ok := ctx.Module().(Defaultable); ok { + for _, defaultsDep := range defaultable.defaults().Defaults { + ctx.VisitDirectDeps(func(m blueprint.Module) { + if ctx.OtherModuleName(m) == defaultsDep { + if defaultsModule, ok := m.(Defaults); ok { + defaultable.applyDefaults(ctx, defaultsModule) + } else { + ctx.PropertyErrorf("defaults", "module %s is not an defaults module", + ctx.OtherModuleName(m)) + } + } + }) + } + } +} diff --git a/android/defs.go b/android/defs.go new file mode 100644 index 00000000..be28e8bc --- /dev/null +++ b/android/defs.go @@ -0,0 +1,73 @@ +// 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 android + +import ( + "github.com/google/blueprint" + _ "github.com/google/blueprint/bootstrap" +) + +var ( + pctx = 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") + + ErrorRule = pctx.StaticRule("Error", + blueprint.RuleParams{ + Command: `echo "$error" && false`, + Description: "error building $out", + }, + "error") +) + +func init() { + pctx.Import("github.com/google/blueprint/bootstrap") +} diff --git a/android/env.go b/android/env.go new file mode 100644 index 00000000..f9d8030a --- /dev/null +++ b/android/env.go @@ -0,0 +1,55 @@ +// 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 android + +import ( + "android/soong" + "android/soong/env" + + "github.com/google/blueprint" +) + +// This file supports dependencies on environment variables. During build manifest generation, +// any dependency on an environment variable is added to a list. During the singleton phase +// a JSON file is written containing the current value of all used environment variables. +// The next time the top-level build script is run, it uses the soong_env executable to +// compare the contents of the environment variables, rewriting the file if necessary to cause +// a manifest regeneration. + +func init() { + soong.RegisterSingletonType("env", EnvSingleton) +} + +func EnvSingleton() blueprint.Singleton { + return &envSingleton{} +} + +type envSingleton struct{} + +func (c *envSingleton) GenerateBuildActions(ctx blueprint.SingletonContext) { + envDeps := ctx.Config().(Config).EnvDeps() + + envFile := PathForOutput(ctx, ".soong.environment") + if ctx.Failed() { + return + } + + err := env.WriteEnvFile(envFile.String(), envDeps) + if err != nil { + ctx.Errorf(err.Error()) + } + + ctx.AddNinjaFileDeps(envFile.String()) +} diff --git a/android/glob.go b/android/glob.go new file mode 100644 index 00000000..aec5fa9a --- /dev/null +++ b/android/glob.go @@ -0,0 +1,120 @@ +// 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 android + +import ( + "fmt" + "path/filepath" + + "github.com/google/blueprint" + + "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 $excludes "$glob"`, globCmd), + CommandDeps: []string{globCmd}, + Description: "glob $glob", + + Restat: true, + Deps: blueprint.DepsGCC, + Depfile: "$out.d", + }, + "glob", "excludes") +) + +func hasGlob(in []string) bool { + for _, s := range in { + if glob.IsGlob(s) { + return true + } + } + + return false +} + +// The subset of ModuleContext and SingletonContext needed by Glob +type globContext interface { + Build(pctx blueprint.PackageContext, params blueprint.BuildParams) + AddNinjaFileDeps(deps ...string) +} + +func Glob(ctx globContext, outDir string, globPattern string, excludes []string) ([]string, error) { + fileListFile := filepath.Join(outDir, "glob", globToString(globPattern)+".glob") + depFile := fileListFile + ".d" + + // Get a globbed file list, and write out fileListFile and depFile + files, err := glob.GlobWithDepFile(globPattern, fileListFile, depFile, excludes) + if err != nil { + return nil, err + } + + GlobRule(ctx, globPattern, excludes, fileListFile, depFile) + + // Make build.ninja depend on the fileListFile + ctx.AddNinjaFileDeps(fileListFile) + + return files, nil +} + +func GlobRule(ctx globContext, globPattern string, excludes []string, + fileListFile, depFile string) { + + // 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}, + Args: map[string]string{ + "glob": globPattern, + "excludes": JoinWithPrefixAndQuote(excludes, "-e "), + }, + }) +} + +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/android/makevars.go b/android/makevars.go new file mode 100644 index 00000000..d9c5c6d7 --- /dev/null +++ b/android/makevars.go @@ -0,0 +1,242 @@ +// Copyright 2016 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 android + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + + "android/soong" + + "github.com/google/blueprint" + "github.com/google/blueprint/proptools" +) + +/////////////////////////////////////////////////////////////////////////////// +// Interface for other packages to use to declare make variables +type MakeVarsContext interface { + Config() Config + + // Verify the make variable matches the Soong version, fail the build + // if it does not. If the make variable is empty, just set it. + Strict(name, ninjaStr string) + // Check to see if the make variable matches the Soong version, warn if + // it does not. If the make variable is empty, just set it. + Check(name, ninjaStr string) + + // These are equivalent to the above, but sort the make and soong + // variables before comparing them. They also show the unique entries + // in each list when displaying the difference, instead of the entire + // string. + StrictSorted(name, ninjaStr string) + CheckSorted(name, ninjaStr string) +} + +type MakeVarsProvider func(ctx MakeVarsContext) + +func RegisterMakeVarsProvider(pctx blueprint.PackageContext, provider MakeVarsProvider) { + makeVarsProviders = append(makeVarsProviders, makeVarsProvider{pctx, provider}) +} + +/////////////////////////////////////////////////////////////////////////////// + +func init() { + soong.RegisterSingletonType("makevars", makeVarsSingletonFunc) +} + +func makeVarsSingletonFunc() blueprint.Singleton { + return &makeVarsSingleton{} +} + +type makeVarsSingleton struct{} + +type makeVarsProvider struct { + pctx blueprint.PackageContext + call MakeVarsProvider +} + +var makeVarsProviders []makeVarsProvider + +type makeVarsContext struct { + config Config + ctx blueprint.SingletonContext + pctx blueprint.PackageContext + vars []makeVarsVariable +} + +var _ MakeVarsContext = &makeVarsContext{} + +type makeVarsVariable struct { + name string + value string + sort bool + strict bool +} + +func (s *makeVarsSingleton) GenerateBuildActions(ctx blueprint.SingletonContext) { + config := ctx.Config().(Config) + + if !config.EmbeddedInMake() { + return + } + + outFile := PathForOutput(ctx, "make_vars"+proptools.String(config.ProductVariables.Make_suffix)+".mk").String() + + if ctx.Failed() { + return + } + + vars := []makeVarsVariable{} + for _, provider := range makeVarsProviders { + mctx := &makeVarsContext{ + config: config, + ctx: ctx, + pctx: provider.pctx, + } + + provider.call(mctx) + + vars = append(vars, mctx.vars...) + } + + if ctx.Failed() { + return + } + + outBytes := s.writeVars(vars) + + if _, err := os.Stat(outFile); err == nil { + if data, err := ioutil.ReadFile(outFile); err == nil { + if bytes.Equal(data, outBytes) { + return + } + } + } + + if err := ioutil.WriteFile(outFile, outBytes, 0666); err != nil { + ctx.Errorf(err.Error()) + } +} + +func (s *makeVarsSingleton) writeVars(vars []makeVarsVariable) []byte { + buf := &bytes.Buffer{} + + fmt.Fprintln(buf, `# Autogenerated file + +# Compares SOONG_$(1) against $(1), and warns if they are not equal. +# +# If the original variable is empty, then just set it to the SOONG_ version. +# +# $(1): Name of the variable to check +# $(2): If not-empty, sort the values before comparing +# $(3): Extra snippet to run if it does not match +define soong-compare-var +ifneq ($$($(1)),) + my_val_make := $(if $(2),$$(sort $$($(1))),$$($(1))) + my_val_soong := $(if $(2),$$(sort $$(SOONG_$(1))),$$(SOONG_$(1))) + ifneq ($$(my_val_make),$$(my_val_soong)) + $$(warning $(1) does not match between Make and Soong:) + $(if $(2),$$(warning Make adds: $$(filter-out $$(my_val_soong),$$(my_val_make))),$$(warning Make : $$(my_val_make))) + $(if $(2),$$(warning Soong adds: $$(filter-out $$(my_val_make),$$(my_val_soong))),$$(warning Soong: $$(my_val_soong))) + $(3) + endif + my_val_make := + my_val_soong := +else + $(1) := $$(SOONG_$(1)) +endif +endef + +my_check_failed := false + +`) + + // Write all the strict checks out first so that if one of them errors, + // we get all of the strict errors printed, but not the non-strict + // warnings. + for _, v := range vars { + if !v.strict { + continue + } + + sort := "" + if v.sort { + sort = "true" + } + + fmt.Fprintf(buf, "SOONG_%s := %s\n", v.name, v.value) + fmt.Fprintf(buf, "$(eval $(call soong-compare-var,%s,%s,my_check_failed := true))\n\n", v.name, sort) + } + + fmt.Fprintln(buf, ` +ifneq ($(my_check_failed),false) + $(error Soong variable check failed) +endif +my_check_failed := + + +`) + + for _, v := range vars { + if v.strict { + continue + } + + sort := "" + if v.sort { + sort = "true" + } + + fmt.Fprintf(buf, "SOONG_%s := %s\n", v.name, v.value) + fmt.Fprintf(buf, "$(eval $(call soong-compare-var,%s,%s))\n\n", v.name, sort) + } + + fmt.Fprintln(buf, "\nsoong-compare-var :=") + + return buf.Bytes() +} + +func (c *makeVarsContext) Config() Config { + return c.config +} + +func (c *makeVarsContext) addVariable(name, ninjaStr string, strict, sort bool) { + value, err := c.ctx.Eval(c.pctx, ninjaStr) + if err != nil { + c.ctx.Errorf(err.Error()) + } + c.vars = append(c.vars, makeVarsVariable{ + name: name, + value: value, + strict: strict, + sort: sort, + }) +} + +func (c *makeVarsContext) Strict(name, ninjaStr string) { + c.addVariable(name, ninjaStr, true, false) +} +func (c *makeVarsContext) StrictSorted(name, ninjaStr string) { + c.addVariable(name, ninjaStr, true, true) +} + +func (c *makeVarsContext) Check(name, ninjaStr string) { + c.addVariable(name, ninjaStr, false, false) +} +func (c *makeVarsContext) CheckSorted(name, ninjaStr string) { + c.addVariable(name, ninjaStr, false, true) +} diff --git a/android/module.go b/android/module.go new file mode 100644 index 00000000..08abf78a --- /dev/null +++ b/android/module.go @@ -0,0 +1,695 @@ +// 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 android + +import ( + "fmt" + "path/filepath" + "strings" + + "android/soong" + "android/soong/glob" + + "github.com/google/blueprint" +) + +var ( + DeviceSharedLibrary = "shared_library" + DeviceStaticLibrary = "static_library" + DeviceExecutable = "executable" + HostSharedLibrary = "host_shared_library" + HostStaticLibrary = "host_static_library" + HostExecutable = "host_executable" +) + +type ModuleBuildParams struct { + Rule blueprint.Rule + Output WritablePath + Outputs WritablePaths + Input Path + Inputs Paths + Implicit Path + Implicits Paths + OrderOnly Paths + Default bool + Args map[string]string +} + +type androidBaseContext interface { + Arch() Arch + HostOrDevice() HostOrDevice + HostType() HostType + Host() bool + Device() bool + Darwin() bool + Debug() bool + AConfig() Config + Proprietary() bool + InstallInData() bool +} + +type BaseContext interface { + blueprint.BaseModuleContext + androidBaseContext +} + +type ModuleContext interface { + blueprint.ModuleContext + androidBaseContext + + // Similar to Build, but takes Paths instead of []string, + // and performs more verification. + ModuleBuild(pctx blueprint.PackageContext, params ModuleBuildParams) + + ExpandSources(srcFiles, excludes []string) Paths + Glob(outDir, globPattern string, excludes []string) Paths + + InstallFile(installPath OutputPath, srcPath Path, deps ...Path) OutputPath + InstallFileName(installPath OutputPath, name string, srcPath Path, deps ...Path) OutputPath + CheckbuildFile(srcPath Path) + + AddMissingDependencies(deps []string) +} + +type Module interface { + blueprint.Module + + GenerateAndroidBuildActions(ModuleContext) + + base() *ModuleBase + Enabled() bool + HostOrDevice() HostOrDevice + InstallInData() bool +} + +type commonProperties struct { + Name string + Deps []string + Tags []string + + // emit build rules for this module + Enabled *bool `android:"arch_variant"` + + // 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 + + // whether this is a proprietary vendor module, and should be installed into /vendor + Proprietary bool + + // Set by HostOrDeviceMutator + CompileHostOrDevice HostOrDevice `blueprint:"mutated"` + + // Set by HostTypeMutator + CompileHostType HostType `blueprint:"mutated"` + + // Set by ArchMutator + CompileArch Arch `blueprint:"mutated"` + + // Set by InitAndroidModule + HostOrDeviceSupported HostOrDeviceSupported `blueprint:"mutated"` +} + +type hostAndDeviceProperties struct { + Host_supported bool + Device_supported bool +} + +type Multilib string + +const ( + MultilibBoth Multilib = "both" + MultilibFirst Multilib = "first" + MultilibCommon Multilib = "common" + MultilibDefault Multilib = "" +) + +func InitAndroidModule(m Module, + propertyStructs ...interface{}) (blueprint.Module, []interface{}) { + + base := m.base() + base.module = m + + propertyStructs = append(propertyStructs, &base.commonProperties, &base.variableProperties) + + return m, propertyStructs +} + +func InitAndroidArchModule(m Module, hod HostOrDeviceSupported, defaultMultilib Multilib, + propertyStructs ...interface{}) (blueprint.Module, []interface{}) { + + _, propertyStructs = InitAndroidModule(m, propertyStructs...) + + base := m.base() + base.commonProperties.HostOrDeviceSupported = hod + base.commonProperties.Compile_multilib = string(defaultMultilib) + + switch hod { + case HostAndDeviceSupported: + // Default to module to device supported, host not supported, can override in module + // properties + base.hostAndDeviceProperties.Device_supported = true + fallthrough + case HostAndDeviceDefault: + propertyStructs = append(propertyStructs, &base.hostAndDeviceProperties) + } + + return InitArchModule(m, 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" +// "github.com/google/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 ModuleBase struct { + // Putting the curiously recurring thing pointing to the thing that contains + // the thing pattern to good use. + module Module + + commonProperties commonProperties + variableProperties variableProperties + hostAndDeviceProperties hostAndDeviceProperties + generalProperties []interface{} + archProperties []*archProperties + + noAddressSanitizer bool + installFiles Paths + checkbuildFiles Paths + + // Used by buildTargetSingleton to create checkbuild and per-directory build targets + // Only set on the final variant of each module + installTarget string + checkbuildTarget string + blueprintDir string +} + +func (a *ModuleBase) base() *ModuleBase { + return a +} + +func (a *ModuleBase) SetHostOrDevice(hod HostOrDevice) { + a.commonProperties.CompileHostOrDevice = hod +} + +func (a *ModuleBase) SetHostType(ht HostType) { + a.commonProperties.CompileHostType = ht +} + +func (a *ModuleBase) SetArch(arch Arch) { + a.commonProperties.CompileArch = arch +} + +func (a *ModuleBase) HostOrDevice() HostOrDevice { + return a.commonProperties.CompileHostOrDevice +} + +func (a *ModuleBase) HostType() HostType { + return a.commonProperties.CompileHostType +} + +func (a *ModuleBase) Host() bool { + return a.HostOrDevice().Host() +} + +func (a *ModuleBase) Arch() Arch { + return a.commonProperties.CompileArch +} + +func (a *ModuleBase) HostSupported() bool { + return a.commonProperties.HostOrDeviceSupported == HostSupported || + a.commonProperties.HostOrDeviceSupported == HostAndDeviceSupported && + a.hostAndDeviceProperties.Host_supported +} + +func (a *ModuleBase) DeviceSupported() bool { + return a.commonProperties.HostOrDeviceSupported == DeviceSupported || + a.commonProperties.HostOrDeviceSupported == HostAndDeviceSupported && + a.hostAndDeviceProperties.Device_supported +} + +func (a *ModuleBase) Enabled() bool { + if a.commonProperties.Enabled == nil { + if a.HostSupported() && a.HostOrDevice().Host() && a.HostType() == Windows { + return false + } else { + return true + } + } + return *a.commonProperties.Enabled +} + +func (a *ModuleBase) computeInstallDeps( + ctx blueprint.ModuleContext) Paths { + + result := Paths{} + ctx.VisitDepsDepthFirstIf(isFileInstaller, + func(m blueprint.Module) { + fileInstaller := m.(fileInstaller) + files := fileInstaller.filesToInstall() + result = append(result, files...) + }) + + return result +} + +func (a *ModuleBase) filesToInstall() Paths { + return a.installFiles +} + +func (p *ModuleBase) NoAddressSanitizer() bool { + return p.noAddressSanitizer +} + +func (p *ModuleBase) InstallInData() bool { + return false +} + +func (a *ModuleBase) generateModuleTarget(ctx blueprint.ModuleContext) { + if a != ctx.FinalModule().(Module).base() { + return + } + + allInstalledFiles := Paths{} + allCheckbuildFiles := Paths{} + ctx.VisitAllModuleVariants(func(module blueprint.Module) { + a := module.(Module).base() + allInstalledFiles = append(allInstalledFiles, a.installFiles...) + allCheckbuildFiles = append(allCheckbuildFiles, a.checkbuildFiles...) + }) + + deps := []string{} + + if len(allInstalledFiles) > 0 { + name := ctx.ModuleName() + "-install" + ctx.Build(pctx, blueprint.BuildParams{ + Rule: blueprint.Phony, + Outputs: []string{name}, + Implicits: allInstalledFiles.Strings(), + Optional: ctx.Config().(Config).EmbeddedInMake(), + }) + deps = append(deps, name) + a.installTarget = name + } + + if len(allCheckbuildFiles) > 0 { + name := ctx.ModuleName() + "-checkbuild" + ctx.Build(pctx, blueprint.BuildParams{ + Rule: blueprint.Phony, + Outputs: []string{name}, + Implicits: allCheckbuildFiles.Strings(), + Optional: true, + }) + deps = append(deps, name) + a.checkbuildTarget = name + } + + if len(deps) > 0 { + suffix := "" + if ctx.Config().(Config).EmbeddedInMake() { + suffix = "-soong" + } + + ctx.Build(pctx, blueprint.BuildParams{ + Rule: blueprint.Phony, + Outputs: []string{ctx.ModuleName() + suffix}, + Implicits: deps, + Optional: true, + }) + + a.blueprintDir = ctx.ModuleDir() + } +} + +func (a *ModuleBase) androidBaseContextFactory(ctx blueprint.BaseModuleContext) androidBaseContextImpl { + return androidBaseContextImpl{ + arch: a.commonProperties.CompileArch, + hod: a.commonProperties.CompileHostOrDevice, + ht: a.commonProperties.CompileHostType, + proprietary: a.commonProperties.Proprietary, + config: ctx.Config().(Config), + installInData: a.module.InstallInData(), + } +} + +func (a *ModuleBase) GenerateBuildActions(ctx blueprint.ModuleContext) { + androidCtx := &androidModuleContext{ + ModuleContext: ctx, + androidBaseContextImpl: a.androidBaseContextFactory(ctx), + installDeps: a.computeInstallDeps(ctx), + installFiles: a.installFiles, + missingDeps: ctx.GetMissingDependencies(), + } + + if !a.Enabled() { + return + } + + a.module.GenerateAndroidBuildActions(androidCtx) + if ctx.Failed() { + return + } + + a.installFiles = append(a.installFiles, androidCtx.installFiles...) + a.checkbuildFiles = append(a.checkbuildFiles, androidCtx.checkbuildFiles...) + + a.generateModuleTarget(ctx) + if ctx.Failed() { + return + } +} + +type androidBaseContextImpl struct { + arch Arch + hod HostOrDevice + ht HostType + debug bool + config Config + proprietary bool + installInData bool +} + +type androidModuleContext struct { + blueprint.ModuleContext + androidBaseContextImpl + installDeps Paths + installFiles Paths + checkbuildFiles Paths + missingDeps []string +} + +func (a *androidModuleContext) ninjaError(outputs []string, err error) { + a.ModuleContext.Build(pctx, blueprint.BuildParams{ + Rule: ErrorRule, + Outputs: outputs, + Optional: true, + Args: map[string]string{ + "error": err.Error(), + }, + }) + return +} + +func (a *androidModuleContext) Build(pctx blueprint.PackageContext, params blueprint.BuildParams) { + if a.missingDeps != nil && params.Rule != globRule { + a.ninjaError(params.Outputs, fmt.Errorf("module %s missing dependencies: %s\n", + a.ModuleName(), strings.Join(a.missingDeps, ", "))) + return + } + + params.Optional = true + a.ModuleContext.Build(pctx, params) +} + +func (a *androidModuleContext) ModuleBuild(pctx blueprint.PackageContext, params ModuleBuildParams) { + bparams := blueprint.BuildParams{ + Rule: params.Rule, + Outputs: params.Outputs.Strings(), + Inputs: params.Inputs.Strings(), + Implicits: params.Implicits.Strings(), + OrderOnly: params.OrderOnly.Strings(), + Args: params.Args, + Optional: !params.Default, + } + + if params.Output != nil { + bparams.Outputs = append(bparams.Outputs, params.Output.String()) + } + if params.Input != nil { + bparams.Inputs = append(bparams.Inputs, params.Input.String()) + } + if params.Implicit != nil { + bparams.Implicits = append(bparams.Implicits, params.Implicit.String()) + } + + if a.missingDeps != nil { + a.ninjaError(bparams.Outputs, fmt.Errorf("module %s missing dependencies: %s\n", + a.ModuleName(), strings.Join(a.missingDeps, ", "))) + return + } + + a.ModuleContext.Build(pctx, bparams) +} + +func (a *androidModuleContext) GetMissingDependencies() []string { + return a.missingDeps +} + +func (a *androidModuleContext) AddMissingDependencies(deps []string) { + if deps != nil { + a.missingDeps = append(a.missingDeps, deps...) + } +} + +func (a *androidBaseContextImpl) Arch() Arch { + return a.arch +} + +func (a *androidBaseContextImpl) HostOrDevice() HostOrDevice { + return a.hod +} + +func (a *androidBaseContextImpl) HostType() HostType { + return a.ht +} + +func (a *androidBaseContextImpl) Host() bool { + return a.hod.Host() +} + +func (a *androidBaseContextImpl) Device() bool { + return a.hod.Device() +} + +func (a *androidBaseContextImpl) Darwin() bool { + return a.hod.Host() && a.ht == Darwin +} + +func (a *androidBaseContextImpl) Debug() bool { + return a.debug +} + +func (a *androidBaseContextImpl) AConfig() Config { + return a.config +} + +func (a *androidBaseContextImpl) Proprietary() bool { + return a.proprietary +} + +func (a *androidBaseContextImpl) InstallInData() bool { + return a.installInData +} + +func (a *androidModuleContext) InstallFileName(installPath OutputPath, name string, srcPath Path, + deps ...Path) OutputPath { + + fullInstallPath := installPath.Join(a, name) + + if a.Host() || !a.AConfig().SkipDeviceInstall() { + deps = append(deps, a.installDeps...) + + a.ModuleBuild(pctx, ModuleBuildParams{ + Rule: Cp, + Output: fullInstallPath, + Input: srcPath, + OrderOnly: Paths(deps), + Default: !a.AConfig().EmbeddedInMake(), + }) + + a.installFiles = append(a.installFiles, fullInstallPath) + } + a.checkbuildFiles = append(a.checkbuildFiles, srcPath) + return fullInstallPath +} + +func (a *androidModuleContext) InstallFile(installPath OutputPath, srcPath Path, deps ...Path) OutputPath { + return a.InstallFileName(installPath, filepath.Base(srcPath.String()), srcPath, deps...) +} + +func (a *androidModuleContext) CheckbuildFile(srcPath Path) { + a.checkbuildFiles = append(a.checkbuildFiles, srcPath) +} + +type fileInstaller interface { + filesToInstall() Paths +} + +func isFileInstaller(m blueprint.Module) bool { + _, ok := m.(fileInstaller) + return ok +} + +func isAndroidModule(m blueprint.Module) bool { + _, ok := m.(Module) + return ok +} + +func findStringInSlice(str string, slice []string) int { + for i, s := range slice { + if s == str { + return i + } + } + return -1 +} + +func (ctx *androidModuleContext) ExpandSources(srcFiles, excludes []string) Paths { + prefix := PathForModuleSrc(ctx).String() + for i, e := range excludes { + j := findStringInSlice(e, srcFiles) + if j != -1 { + srcFiles = append(srcFiles[:j], srcFiles[j+1:]...) + } + + excludes[i] = filepath.Join(prefix, e) + } + + globbedSrcFiles := make(Paths, 0, len(srcFiles)) + for _, s := range srcFiles { + if glob.IsGlob(s) { + globbedSrcFiles = append(globbedSrcFiles, ctx.Glob("src_glob", filepath.Join(prefix, s), excludes)...) + } else { + globbedSrcFiles = append(globbedSrcFiles, PathForModuleSrc(ctx, s)) + } + } + + return globbedSrcFiles +} + +func (ctx *androidModuleContext) Glob(outDir, globPattern string, excludes []string) Paths { + ret, err := Glob(ctx, PathForModuleOut(ctx, outDir).String(), globPattern, excludes) + if err != nil { + ctx.ModuleErrorf("glob: %s", err.Error()) + } + return pathsForModuleSrcFromFullPath(ctx, ret) +} + +func init() { + soong.RegisterSingletonType("buildtarget", BuildTargetSingleton) +} + +func BuildTargetSingleton() blueprint.Singleton { + return &buildTargetSingleton{} +} + +type buildTargetSingleton struct{} + +func (c *buildTargetSingleton) GenerateBuildActions(ctx blueprint.SingletonContext) { + checkbuildDeps := []string{} + + dirModules := make(map[string][]string) + + ctx.VisitAllModules(func(module blueprint.Module) { + if a, ok := module.(Module); ok { + blueprintDir := a.base().blueprintDir + installTarget := a.base().installTarget + checkbuildTarget := a.base().checkbuildTarget + + if checkbuildTarget != "" { + checkbuildDeps = append(checkbuildDeps, checkbuildTarget) + dirModules[blueprintDir] = append(dirModules[blueprintDir], checkbuildTarget) + } + + if installTarget != "" { + dirModules[blueprintDir] = append(dirModules[blueprintDir], installTarget) + } + } + }) + + suffix := "" + if ctx.Config().(Config).EmbeddedInMake() { + suffix = "-soong" + } + + // Create a top-level checkbuild target that depends on all modules + ctx.Build(pctx, blueprint.BuildParams{ + Rule: blueprint.Phony, + Outputs: []string{"checkbuild" + suffix}, + Implicits: checkbuildDeps, + Optional: true, + }) + + // Create a mm/<directory> target that depends on all modules in a directory + dirs := sortedKeys(dirModules) + for _, dir := range dirs { + ctx.Build(pctx, blueprint.BuildParams{ + Rule: blueprint.Phony, + Outputs: []string{filepath.Join("mm", dir)}, + Implicits: dirModules[dir], + // HACK: checkbuild should be an optional build, but force it + // enabled for now in standalone builds + Optional: ctx.Config().(Config).EmbeddedInMake(), + }) + } +} + +type AndroidModulesByName struct { + slice []Module + ctx interface { + ModuleName(blueprint.Module) string + ModuleSubDir(blueprint.Module) string + } +} + +func (s AndroidModulesByName) Len() int { return len(s.slice) } +func (s AndroidModulesByName) Less(i, j int) bool { + mi, mj := s.slice[i], s.slice[j] + ni, nj := s.ctx.ModuleName(mi), s.ctx.ModuleName(mj) + + if ni != nj { + return ni < nj + } else { + return s.ctx.ModuleSubDir(mi) < s.ctx.ModuleSubDir(mj) + } +} +func (s AndroidModulesByName) Swap(i, j int) { s.slice[i], s.slice[j] = s.slice[j], s.slice[i] } diff --git a/android/mutator.go b/android/mutator.go new file mode 100644 index 00000000..9405f2d2 --- /dev/null +++ b/android/mutator.go @@ -0,0 +1,69 @@ +// 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 android + +import ( + "android/soong" + + "github.com/google/blueprint" +) + +type AndroidTopDownMutator func(TopDownMutatorContext) + +type TopDownMutatorContext interface { + blueprint.TopDownMutatorContext + androidBaseContext +} + +type androidTopDownMutatorContext struct { + blueprint.TopDownMutatorContext + androidBaseContextImpl +} + +type AndroidBottomUpMutator func(BottomUpMutatorContext) + +type BottomUpMutatorContext interface { + blueprint.BottomUpMutatorContext + androidBaseContext +} + +type androidBottomUpMutatorContext struct { + blueprint.BottomUpMutatorContext + androidBaseContextImpl +} + +func RegisterBottomUpMutator(name string, mutator AndroidBottomUpMutator) { + soong.RegisterBottomUpMutator(name, func(ctx blueprint.BottomUpMutatorContext) { + if a, ok := ctx.Module().(Module); ok { + actx := &androidBottomUpMutatorContext{ + BottomUpMutatorContext: ctx, + androidBaseContextImpl: a.base().androidBaseContextFactory(ctx), + } + mutator(actx) + } + }) +} + +func RegisterTopDownMutator(name string, mutator AndroidTopDownMutator) { + soong.RegisterTopDownMutator(name, func(ctx blueprint.TopDownMutatorContext) { + if a, ok := ctx.Module().(Module); ok { + actx := &androidTopDownMutatorContext{ + TopDownMutatorContext: ctx, + androidBaseContextImpl: a.base().androidBaseContextFactory(ctx), + } + mutator(actx) + } + }) +} diff --git a/android/package_ctx.go b/android/package_ctx.go new file mode 100644 index 00000000..56ba2d82 --- /dev/null +++ b/android/package_ctx.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 android + +import ( + "fmt" + + "github.com/google/blueprint" +) + +// AndroidPackageContext is a wrapper for blueprint.PackageContext that adds +// some android-specific helper functions. +type AndroidPackageContext struct { + blueprint.PackageContext +} + +func NewPackageContext(pkgPath string) AndroidPackageContext { + return AndroidPackageContext{blueprint.NewPackageContext(pkgPath)} +} + +// configErrorWrapper can be used with Path functions when a Context is not +// available. A Config can be provided, and errors are stored as a list for +// later retrieval. +// +// The most common use here will be with VariableFunc, where only a config is +// provided, and an error should be returned. +type configErrorWrapper struct { + pctx AndroidPackageContext + config Config + errors []error +} + +var _ PathContext = &configErrorWrapper{} +var _ errorfContext = &configErrorWrapper{} + +func (e *configErrorWrapper) Config() interface{} { + return e.config +} +func (e *configErrorWrapper) Errorf(format string, args ...interface{}) { + e.errors = append(e.errors, fmt.Errorf(format, args...)) +} +func (e *configErrorWrapper) AddNinjaFileDeps(deps ...string) { + e.pctx.AddNinjaFileDeps(deps...) +} + +// SourcePathVariable returns a Variable whose value is the source directory +// appended with the supplied path. It may only be called during a Go package's +// initialization - either from the init() function or as part of a +// package-scoped variable's initialization. +func (p AndroidPackageContext) SourcePathVariable(name, path string) blueprint.Variable { + return p.VariableFunc(name, func(config interface{}) (string, error) { + ctx := &configErrorWrapper{p, config.(Config), []error{}} + p := safePathForSource(ctx, path) + if len(ctx.errors) > 0 { + return "", ctx.errors[0] + } + return p.String(), nil + }) +} + +// HostBinVariable returns a Variable whose value is the path to a host tool +// in the bin directory for host targets. It may only be called during a Go +// package's initialization - either from the init() function or as part of a +// package-scoped variable's initialization. +func (p AndroidPackageContext) HostBinToolVariable(name, path string) blueprint.Variable { + return p.VariableFunc(name, func(config interface{}) (string, error) { + ctx := &configErrorWrapper{p, config.(Config), []error{}} + p := PathForOutput(ctx, "host", ctx.config.PrebuiltOS(), "bin", path) + if len(ctx.errors) > 0 { + return "", ctx.errors[0] + } + return p.String(), nil + }) +} + +// HostJavaToolVariable returns a Variable whose value is the path to a host +// tool in the frameworks directory for host targets. It may only be called +// during a Go package's initialization - either from the init() function or as +// part of a package-scoped variable's initialization. +func (p AndroidPackageContext) HostJavaToolVariable(name, path string) blueprint.Variable { + return p.VariableFunc(name, func(config interface{}) (string, error) { + ctx := &configErrorWrapper{p, config.(Config), []error{}} + p := PathForOutput(ctx, "host", ctx.config.PrebuiltOS(), "framework", path) + if len(ctx.errors) > 0 { + return "", ctx.errors[0] + } + return p.String(), nil + }) +} + +// IntermediatesPathVariable returns a Variable whose value is the intermediate +// directory appended with the supplied path. It may only be called during a Go +// package's initialization - either from the init() function or as part of a +// package-scoped variable's initialization. +func (p AndroidPackageContext) IntermediatesPathVariable(name, path string) blueprint.Variable { + return p.VariableFunc(name, func(config interface{}) (string, error) { + ctx := &configErrorWrapper{p, config.(Config), []error{}} + p := PathForIntermediates(ctx, path) + if len(ctx.errors) > 0 { + return "", ctx.errors[0] + } + return p.String(), nil + }) +} + +// PrefixedPathsForOptionalSourceVariable returns a Variable whose value is the +// list of present source paths prefixed with the supplied prefix. It may only +// be called during a Go package's initialization - either from the init() +// function or as part of a package-scoped variable's initialization. +func (p AndroidPackageContext) PrefixedPathsForOptionalSourceVariable( + name, prefix string, paths []string) blueprint.Variable { + + return p.VariableFunc(name, func(config interface{}) (string, error) { + ctx := &configErrorWrapper{p, config.(Config), []error{}} + paths := PathsForOptionalSource(ctx, "", paths) + if len(ctx.errors) > 0 { + return "", ctx.errors[0] + } + return JoinWithPrefix(paths.Strings(), prefix), nil + }) +} diff --git a/android/paths.go b/android/paths.go new file mode 100644 index 00000000..910ebd28 --- /dev/null +++ b/android/paths.go @@ -0,0 +1,668 @@ +// 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 android + +import ( + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + + "android/soong/glob" + + "github.com/google/blueprint" + "github.com/google/blueprint/pathtools" +) + +// PathContext is the subset of a (Module|Singleton)Context required by the +// Path methods. +type PathContext interface { + Config() interface{} + AddNinjaFileDeps(deps ...string) +} + +var _ PathContext = blueprint.SingletonContext(nil) +var _ PathContext = blueprint.ModuleContext(nil) + +// errorfContext is the interface containing the Errorf method matching the +// Errorf method in blueprint.SingletonContext. +type errorfContext interface { + Errorf(format string, args ...interface{}) +} + +var _ errorfContext = blueprint.SingletonContext(nil) + +// moduleErrorf is the interface containing the ModuleErrorf method matching +// the ModuleErrorf method in blueprint.ModuleContext. +type moduleErrorf interface { + ModuleErrorf(format string, args ...interface{}) +} + +var _ moduleErrorf = blueprint.ModuleContext(nil) + +// pathConfig returns the android Config interface associated to the context. +// Panics if the context isn't affiliated with an android build. +func pathConfig(ctx PathContext) Config { + if ret, ok := ctx.Config().(Config); ok { + return ret + } + panic("Paths may only be used on Soong builds") +} + +// reportPathError will register an error with the attached context. It +// attempts ctx.ModuleErrorf for a better error message first, then falls +// back to ctx.Errorf. +func reportPathError(ctx PathContext, format string, args ...interface{}) { + if mctx, ok := ctx.(moduleErrorf); ok { + mctx.ModuleErrorf(format, args...) + } else if ectx, ok := ctx.(errorfContext); ok { + ectx.Errorf(format, args...) + } else { + panic(fmt.Sprintf(format, args...)) + } +} + +type Path interface { + // Returns the path in string form + String() string + + // Returns the current file extension of the path + Ext() string +} + +// WritablePath is a type of path that can be used as an output for build rules. +type WritablePath interface { + Path + + writablePath() +} + +type genPathProvider interface { + genPathWithExt(ctx ModuleContext, ext string) ModuleGenPath +} +type objPathProvider interface { + objPathWithExt(ctx ModuleContext, subdir, ext string) ModuleObjPath +} +type resPathProvider interface { + resPathWithName(ctx ModuleContext, name string) ModuleResPath +} + +// GenPathWithExt derives a new file path in ctx's generated sources directory +// from the current path, but with the new extension. +func GenPathWithExt(ctx ModuleContext, p Path, ext string) ModuleGenPath { + if path, ok := p.(genPathProvider); ok { + return path.genPathWithExt(ctx, ext) + } + reportPathError(ctx, "Tried to create generated file from unsupported path: %s(%s)", reflect.TypeOf(p).Name(), p) + return PathForModuleGen(ctx) +} + +// ObjPathWithExt derives a new file path in ctx's object directory from the +// current path, but with the new extension. +func ObjPathWithExt(ctx ModuleContext, p Path, subdir, ext string) ModuleObjPath { + if path, ok := p.(objPathProvider); ok { + return path.objPathWithExt(ctx, subdir, ext) + } + reportPathError(ctx, "Tried to create object file from unsupported path: %s (%s)", reflect.TypeOf(p).Name(), p) + return PathForModuleObj(ctx) +} + +// ResPathWithName derives a new path in ctx's output resource directory, using +// the current path to create the directory name, and the `name` argument for +// the filename. +func ResPathWithName(ctx ModuleContext, p Path, name string) ModuleResPath { + if path, ok := p.(resPathProvider); ok { + return path.resPathWithName(ctx, name) + } + reportPathError(ctx, "Tried to create object file from unsupported path: %s (%s)", reflect.TypeOf(p).Name(), p) + return PathForModuleRes(ctx) +} + +// OptionalPath is a container that may or may not contain a valid Path. +type OptionalPath struct { + valid bool + path Path +} + +// OptionalPathForPath returns an OptionalPath containing the path. +func OptionalPathForPath(path Path) OptionalPath { + if path == nil { + return OptionalPath{} + } + return OptionalPath{valid: true, path: path} +} + +// Valid returns whether there is a valid path +func (p OptionalPath) Valid() bool { + return p.valid +} + +// Path returns the Path embedded in this OptionalPath. You must be sure that +// there is a valid path, since this method will panic if there is not. +func (p OptionalPath) Path() Path { + if !p.valid { + panic("Requesting an invalid path") + } + return p.path +} + +// String returns the string version of the Path, or "" if it isn't valid. +func (p OptionalPath) String() string { + if p.valid { + return p.path.String() + } else { + return "" + } +} + +// Paths is a slice of Path objects, with helpers to operate on the collection. +type Paths []Path + +// PathsForSource returns Paths rooted from SrcDir +func PathsForSource(ctx PathContext, paths []string) Paths { + if pathConfig(ctx).AllowMissingDependencies() { + if modCtx, ok := ctx.(ModuleContext); ok { + ret := make(Paths, 0, len(paths)) + intermediates := filepath.Join(modCtx.ModuleDir(), modCtx.ModuleName(), modCtx.ModuleSubDir(), "missing") + for _, path := range paths { + p := OptionalPathForSource(ctx, intermediates, path) + if p.Valid() { + ret = append(ret, p.Path()) + } else { + modCtx.AddMissingDependencies([]string{path}) + } + } + return ret + } + } + ret := make(Paths, len(paths)) + for i, path := range paths { + ret[i] = PathForSource(ctx, path) + } + return ret +} + +// PathsForOptionalSource returns a list of Paths rooted from SrcDir that are +// found in the tree. If any are not found, they are omitted from the list, +// and dependencies are added so that we're re-run when they are added. +func PathsForOptionalSource(ctx PathContext, intermediates string, paths []string) Paths { + ret := make(Paths, 0, len(paths)) + for _, path := range paths { + p := OptionalPathForSource(ctx, intermediates, path) + if p.Valid() { + ret = append(ret, p.Path()) + } + } + return ret +} + +// PathsForModuleSrc returns Paths rooted from the module's local source +// directory +func PathsForModuleSrc(ctx ModuleContext, paths []string) Paths { + ret := make(Paths, len(paths)) + for i, path := range paths { + ret[i] = PathForModuleSrc(ctx, path) + } + return ret +} + +// pathsForModuleSrcFromFullPath returns Paths rooted from the module's local +// source directory, but strip the local source directory from the beginning of +// each string. +func pathsForModuleSrcFromFullPath(ctx ModuleContext, paths []string) Paths { + prefix := filepath.Join(ctx.AConfig().srcDir, ctx.ModuleDir()) + "/" + ret := make(Paths, 0, len(paths)) + for _, p := range paths { + path := filepath.Clean(p) + if !strings.HasPrefix(path, prefix) { + reportPathError(ctx, "Path '%s' is not in module source directory '%s'", p, prefix) + continue + } + ret = append(ret, PathForModuleSrc(ctx, path[len(prefix):])) + } + return ret +} + +// PathsWithOptionalDefaultForModuleSrc returns Paths rooted from the module's +// local source directory. If none are provided, use the default if it exists. +func PathsWithOptionalDefaultForModuleSrc(ctx ModuleContext, input []string, def string) Paths { + if len(input) > 0 { + return PathsForModuleSrc(ctx, input) + } + // Use Glob so that if the default doesn't exist, a dependency is added so that when it + // is created, we're run again. + path := filepath.Join(ctx.AConfig().srcDir, ctx.ModuleDir(), def) + return ctx.Glob("default", path, []string{}) +} + +// Strings returns the Paths in string form +func (p Paths) Strings() []string { + if p == nil { + return nil + } + ret := make([]string, len(p)) + for i, path := range p { + ret[i] = path.String() + } + return ret +} + +// WritablePaths is a slice of WritablePaths, used for multiple outputs. +type WritablePaths []WritablePath + +// Strings returns the string forms of the writable paths. +func (p WritablePaths) Strings() []string { + if p == nil { + return nil + } + ret := make([]string, len(p)) + for i, path := range p { + ret[i] = path.String() + } + return ret +} + +type basePath struct { + path string + config Config +} + +func (p basePath) Ext() string { + return filepath.Ext(p.path) +} + +// SourcePath is a Path representing a file path rooted from SrcDir +type SourcePath struct { + basePath +} + +var _ Path = SourcePath{} + +// safePathForSource is for paths that we expect are safe -- only for use by go +// code that is embedding ninja variables in paths +func safePathForSource(ctx PathContext, path string) SourcePath { + p := validateSafePath(ctx, path) + ret := SourcePath{basePath{p, pathConfig(ctx)}} + + abs, err := filepath.Abs(ret.String()) + if err != nil { + reportPathError(ctx, "%s", err.Error()) + return ret + } + buildroot, err := filepath.Abs(pathConfig(ctx).buildDir) + if err != nil { + reportPathError(ctx, "%s", err.Error()) + return ret + } + if strings.HasPrefix(abs, buildroot) { + reportPathError(ctx, "source path %s is in output", abs) + return ret + } + + return ret +} + +// PathForSource returns a SourcePath for the provided paths... (which are +// joined together with filepath.Join). This also validates that the path +// doesn't escape the source dir, or is contained in the build dir. On error, it +// will return a usable, but invalid SourcePath, and report a ModuleError. +func PathForSource(ctx PathContext, paths ...string) SourcePath { + p := validatePath(ctx, paths...) + ret := SourcePath{basePath{p, pathConfig(ctx)}} + + abs, err := filepath.Abs(ret.String()) + if err != nil { + reportPathError(ctx, "%s", err.Error()) + return ret + } + buildroot, err := filepath.Abs(pathConfig(ctx).buildDir) + if err != nil { + reportPathError(ctx, "%s", err.Error()) + return ret + } + if strings.HasPrefix(abs, buildroot) { + reportPathError(ctx, "source path %s is in output", abs) + return ret + } + + if _, err = os.Stat(ret.String()); err != nil { + if os.IsNotExist(err) { + reportPathError(ctx, "source path %s does not exist", ret) + } else { + reportPathError(ctx, "%s: %s", ret, err.Error()) + } + } + return ret +} + +// OptionalPathForSource returns an OptionalPath with the SourcePath if the +// path exists, or an empty OptionalPath if it doesn't exist. Dependencies are added +// so that the ninja file will be regenerated if the state of the path changes. +func OptionalPathForSource(ctx PathContext, intermediates string, paths ...string) OptionalPath { + if len(paths) == 0 { + // For when someone forgets the 'intermediates' argument + panic("Missing path(s)") + } + + p := validatePath(ctx, paths...) + path := SourcePath{basePath{p, pathConfig(ctx)}} + + abs, err := filepath.Abs(path.String()) + if err != nil { + reportPathError(ctx, "%s", err.Error()) + return OptionalPath{} + } + buildroot, err := filepath.Abs(pathConfig(ctx).buildDir) + if err != nil { + reportPathError(ctx, "%s", err.Error()) + return OptionalPath{} + } + if strings.HasPrefix(abs, buildroot) { + reportPathError(ctx, "source path %s is in output", abs) + return OptionalPath{} + } + + if glob.IsGlob(path.String()) { + reportPathError(ctx, "path may not contain a glob: %s", path.String()) + return OptionalPath{} + } + + if gctx, ok := ctx.(globContext); ok { + // Use glob to produce proper dependencies, even though we only want + // a single file. + files, err := Glob(gctx, PathForIntermediates(ctx, intermediates).String(), path.String(), nil) + if err != nil { + reportPathError(ctx, "glob: %s", err.Error()) + return OptionalPath{} + } + + if len(files) == 0 { + return OptionalPath{} + } + } else { + // We cannot add build statements in this context, so we fall back to + // AddNinjaFileDeps + files, dirs, err := pathtools.Glob(path.String()) + if err != nil { + reportPathError(ctx, "glob: %s", err.Error()) + return OptionalPath{} + } + + ctx.AddNinjaFileDeps(dirs...) + + if len(files) == 0 { + return OptionalPath{} + } + + ctx.AddNinjaFileDeps(path.String()) + } + return OptionalPathForPath(path) +} + +func (p SourcePath) String() string { + return filepath.Join(p.config.srcDir, p.path) +} + +// Join creates a new SourcePath with paths... joined with the current path. The +// provided paths... may not use '..' to escape from the current path. +func (p SourcePath) Join(ctx PathContext, paths ...string) SourcePath { + path := validatePath(ctx, paths...) + return PathForSource(ctx, p.path, path) +} + +// OverlayPath returns the overlay for `path' if it exists. This assumes that the +// SourcePath is the path to a resource overlay directory. +func (p SourcePath) OverlayPath(ctx ModuleContext, path Path) OptionalPath { + var relDir string + if moduleSrcPath, ok := path.(ModuleSrcPath); ok { + relDir = moduleSrcPath.sourcePath.path + } else if srcPath, ok := path.(SourcePath); ok { + relDir = srcPath.path + } else { + reportPathError(ctx, "Cannot find relative path for %s(%s)", reflect.TypeOf(path).Name(), path) + return OptionalPath{} + } + dir := filepath.Join(p.config.srcDir, p.path, relDir) + // Use Glob so that we are run again if the directory is added. + if glob.IsGlob(dir) { + reportPathError(ctx, "Path may not contain a glob: %s", dir) + } + paths, err := Glob(ctx, PathForModuleOut(ctx, "overlay").String(), dir, []string{}) + if err != nil { + reportPathError(ctx, "glob: %s", err.Error()) + return OptionalPath{} + } + if len(paths) == 0 { + return OptionalPath{} + } + relPath, err := filepath.Rel(p.config.srcDir, paths[0]) + if err != nil { + reportPathError(ctx, "%s", err.Error()) + return OptionalPath{} + } + return OptionalPathForPath(PathForSource(ctx, relPath)) +} + +// OutputPath is a Path representing a file path rooted from the build directory +type OutputPath struct { + basePath +} + +var _ Path = OutputPath{} + +// PathForOutput returns an OutputPath for the provided paths... (which are +// joined together with filepath.Join). This also validates that the path +// does not escape the build dir. On error, it will return a usable, but invalid +// OutputPath, and report a ModuleError. +func PathForOutput(ctx PathContext, paths ...string) OutputPath { + path := validatePath(ctx, paths...) + return OutputPath{basePath{path, pathConfig(ctx)}} +} + +func (p OutputPath) writablePath() {} + +func (p OutputPath) String() string { + return filepath.Join(p.config.buildDir, p.path) +} + +func (p OutputPath) RelPathString() string { + return p.path +} + +// Join creates a new OutputPath with paths... joined with the current path. The +// provided paths... may not use '..' to escape from the current path. +func (p OutputPath) Join(ctx PathContext, paths ...string) OutputPath { + path := validatePath(ctx, paths...) + return PathForOutput(ctx, p.path, path) +} + +// PathForIntermediates returns an OutputPath representing the top-level +// intermediates directory. +func PathForIntermediates(ctx PathContext, paths ...string) OutputPath { + path := validatePath(ctx, paths...) + return PathForOutput(ctx, ".intermediates", path) +} + +// ModuleSrcPath is a Path representing a file rooted from a module's local source dir +type ModuleSrcPath struct { + basePath + sourcePath SourcePath + moduleDir string +} + +var _ Path = ModuleSrcPath{} +var _ genPathProvider = ModuleSrcPath{} +var _ objPathProvider = ModuleSrcPath{} +var _ resPathProvider = ModuleSrcPath{} + +// PathForModuleSrc returns a ModuleSrcPath representing the paths... under the +// module's local source directory. +func PathForModuleSrc(ctx ModuleContext, paths ...string) ModuleSrcPath { + path := validatePath(ctx, paths...) + return ModuleSrcPath{basePath{path, ctx.AConfig()}, PathForSource(ctx, ctx.ModuleDir(), path), ctx.ModuleDir()} +} + +// OptionalPathForModuleSrc returns an OptionalPath. The OptionalPath contains a +// valid path if p is non-nil. +func OptionalPathForModuleSrc(ctx ModuleContext, p *string) OptionalPath { + if p == nil { + return OptionalPath{} + } + return OptionalPathForPath(PathForModuleSrc(ctx, *p)) +} + +func (p ModuleSrcPath) String() string { + return p.sourcePath.String() +} + +func (p ModuleSrcPath) genPathWithExt(ctx ModuleContext, ext string) ModuleGenPath { + return PathForModuleGen(ctx, p.moduleDir, pathtools.ReplaceExtension(p.path, ext)) +} + +func (p ModuleSrcPath) objPathWithExt(ctx ModuleContext, subdir, ext string) ModuleObjPath { + return PathForModuleObj(ctx, subdir, p.moduleDir, pathtools.ReplaceExtension(p.path, ext)) +} + +func (p ModuleSrcPath) resPathWithName(ctx ModuleContext, name string) ModuleResPath { + // TODO: Use full directory if the new ctx is not the current ctx? + return PathForModuleRes(ctx, p.path, name) +} + +// ModuleOutPath is a Path representing a module's output directory. +type ModuleOutPath struct { + OutputPath +} + +var _ Path = ModuleOutPath{} + +// PathForModuleOut returns a Path representing the paths... under the module's +// output directory. +func PathForModuleOut(ctx ModuleContext, paths ...string) ModuleOutPath { + p := validatePath(ctx, paths...) + return ModuleOutPath{PathForOutput(ctx, ".intermediates", ctx.ModuleDir(), ctx.ModuleName(), ctx.ModuleSubDir(), p)} +} + +// ModuleGenPath is a Path representing the 'gen' directory in a module's output +// directory. Mainly used for generated sources. +type ModuleGenPath struct { + ModuleOutPath + path string +} + +var _ Path = ModuleGenPath{} +var _ genPathProvider = ModuleGenPath{} +var _ objPathProvider = ModuleGenPath{} + +// PathForModuleGen returns a Path representing the paths... under the module's +// `gen' directory. +func PathForModuleGen(ctx ModuleContext, paths ...string) ModuleGenPath { + p := validatePath(ctx, paths...) + return ModuleGenPath{ + PathForModuleOut(ctx, "gen", p), + p, + } +} + +func (p ModuleGenPath) genPathWithExt(ctx ModuleContext, ext string) ModuleGenPath { + // TODO: make a different path for local vs remote generated files? + return PathForModuleGen(ctx, pathtools.ReplaceExtension(p.path, ext)) +} + +func (p ModuleGenPath) objPathWithExt(ctx ModuleContext, subdir, ext string) ModuleObjPath { + return PathForModuleObj(ctx, subdir, pathtools.ReplaceExtension(p.path, ext)) +} + +// ModuleObjPath is a Path representing the 'obj' directory in a module's output +// directory. Used for compiled objects. +type ModuleObjPath struct { + ModuleOutPath +} + +var _ Path = ModuleObjPath{} + +// PathForModuleObj returns a Path representing the paths... under the module's +// 'obj' directory. +func PathForModuleObj(ctx ModuleContext, paths ...string) ModuleObjPath { + p := validatePath(ctx, paths...) + return ModuleObjPath{PathForModuleOut(ctx, "obj", p)} +} + +// ModuleResPath is a a Path representing the 'res' directory in a module's +// output directory. +type ModuleResPath struct { + ModuleOutPath +} + +var _ Path = ModuleResPath{} + +// PathForModuleRes returns a Path representing the paths... under the module's +// 'res' directory. +func PathForModuleRes(ctx ModuleContext, paths ...string) ModuleResPath { + p := validatePath(ctx, paths...) + return ModuleResPath{PathForModuleOut(ctx, "res", p)} +} + +// PathForModuleInstall returns a Path representing the install path for the +// module appended with paths... +func PathForModuleInstall(ctx ModuleContext, paths ...string) OutputPath { + var outPaths []string + if ctx.Device() { + partition := "system" + if ctx.Proprietary() { + partition = "vendor" + } + if ctx.InstallInData() { + partition = "data" + } + outPaths = []string{"target", "product", ctx.AConfig().DeviceName(), partition} + } else { + outPaths = []string{"host", ctx.HostType().String() + "-x86"} + } + if ctx.Debug() { + outPaths = append([]string{"debug"}, outPaths...) + } + outPaths = append(outPaths, paths...) + return PathForOutput(ctx, outPaths...) +} + +// validateSafePath validates a path that we trust (may contain ninja variables). +// Ensures that each path component does not attempt to leave its component. +func validateSafePath(ctx PathContext, paths ...string) string { + for _, path := range paths { + path := filepath.Clean(path) + if path == ".." || strings.HasPrefix(path, "../") || strings.HasPrefix(path, "/") { + reportPathError(ctx, "Path is outside directory: %s", path) + return "" + } + } + // TODO: filepath.Join isn't necessarily correct with embedded ninja + // variables. '..' may remove the entire ninja variable, even if it + // will be expanded to multiple nested directories. + return filepath.Join(paths...) +} + +// validatePath validates that a path does not include ninja variables, and that +// each path component does not attempt to leave its component. Returns a joined +// version of each path component. +func validatePath(ctx PathContext, paths ...string) string { + for _, path := range paths { + if strings.Contains(path, "$") { + reportPathError(ctx, "Path contains invalid character($): %s", path) + return "" + } + } + return validateSafePath(ctx, paths...) +} diff --git a/android/paths_test.go b/android/paths_test.go new file mode 100644 index 00000000..9d69473a --- /dev/null +++ b/android/paths_test.go @@ -0,0 +1,182 @@ +// 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 android + +import ( + "errors" + "fmt" + "reflect" + "strings" + "testing" +) + +type strsTestCase struct { + in []string + out string + err []error +} + +var commonValidatePathTestCases = []strsTestCase{ + { + in: []string{""}, + out: "", + }, + { + in: []string{"a/b"}, + out: "a/b", + }, + { + in: []string{"a/b", "c"}, + out: "a/b/c", + }, + { + in: []string{"a/.."}, + out: ".", + }, + { + in: []string{"."}, + out: ".", + }, + { + in: []string{".."}, + out: "", + err: []error{errors.New("Path is outside directory: ..")}, + }, + { + in: []string{"../a"}, + out: "", + err: []error{errors.New("Path is outside directory: ../a")}, + }, + { + in: []string{"b/../../a"}, + out: "", + err: []error{errors.New("Path is outside directory: ../a")}, + }, + { + in: []string{"/a"}, + out: "", + err: []error{errors.New("Path is outside directory: /a")}, + }, + { + in: []string{"a", "../b"}, + out: "", + err: []error{errors.New("Path is outside directory: ../b")}, + }, + { + in: []string{"a", "b/../../c"}, + out: "", + err: []error{errors.New("Path is outside directory: ../c")}, + }, + { + in: []string{"a", "./.."}, + out: "", + err: []error{errors.New("Path is outside directory: ..")}, + }, +} + +var validateSafePathTestCases = append(commonValidatePathTestCases, []strsTestCase{ + { + in: []string{"$host/../$a"}, + out: "$a", + }, +}...) + +var validatePathTestCases = append(commonValidatePathTestCases, []strsTestCase{ + { + in: []string{"$host/../$a"}, + out: "", + err: []error{errors.New("Path contains invalid character($): $host/../$a")}, + }, + { + in: []string{"$host/.."}, + out: "", + err: []error{errors.New("Path contains invalid character($): $host/..")}, + }, +}...) + +func TestValidateSafePath(t *testing.T) { + for _, testCase := range validateSafePathTestCases { + ctx := &configErrorWrapper{} + out := validateSafePath(ctx, testCase.in...) + check(t, "validateSafePath", p(testCase.in), out, ctx.errors, testCase.out, testCase.err) + } +} + +func TestValidatePath(t *testing.T) { + for _, testCase := range validatePathTestCases { + ctx := &configErrorWrapper{} + out := validatePath(ctx, testCase.in...) + check(t, "validatePath", p(testCase.in), out, ctx.errors, testCase.out, testCase.err) + } +} + +func TestOptionalPath(t *testing.T) { + var path OptionalPath + checkInvalidOptionalPath(t, path) + + path = OptionalPathForPath(nil) + checkInvalidOptionalPath(t, path) +} + +func checkInvalidOptionalPath(t *testing.T, path OptionalPath) { + if path.Valid() { + t.Errorf("Uninitialized OptionalPath should not be valid") + } + if path.String() != "" { + t.Errorf("Uninitialized OptionalPath String() should return \"\", not %q", path.String()) + } + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected a panic when calling Path() on an uninitialized OptionalPath") + } + }() + path.Path() +} + +func check(t *testing.T, testType, testString string, + got interface{}, err []error, + expected interface{}, expectedErr []error) { + + printedTestCase := false + e := func(s string, expected, got interface{}) { + if !printedTestCase { + t.Errorf("test case %s: %s", testType, testString) + printedTestCase = true + } + t.Errorf("incorrect %s", s) + t.Errorf(" expected: %s", p(expected)) + t.Errorf(" got: %s", p(got)) + } + + if !reflect.DeepEqual(expectedErr, err) { + e("errors:", expectedErr, err) + } + + if !reflect.DeepEqual(expected, got) { + e("output:", expected, got) + } +} + +func p(in interface{}) string { + if v, ok := in.([]interface{}); ok { + s := make([]string, len(v)) + for i := range v { + s[i] = fmt.Sprintf("%#v", v[i]) + } + return "[" + strings.Join(s, ", ") + "]" + } else { + return fmt.Sprintf("%#v", in) + } +} diff --git a/android/util.go b/android/util.go new file mode 100644 index 00000000..60c8a63a --- /dev/null +++ b/android/util.go @@ -0,0 +1,78 @@ +// 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 android + +import "sort" + +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 JoinWithPrefixAndQuote(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) + len(`""`) + } + + ret := make([]byte, 0, n) + for i, s := range strs { + if i != 0 { + ret = append(ret, ' ') + } + ret = append(ret, prefix...) + ret = append(ret, '"') + ret = append(ret, s...) + ret = append(ret, '"') + } + return string(ret) +} + +func sortedKeys(m map[string][]string) []string { + s := make([]string, 0, len(m)) + for k := range m { + s = append(s, k) + } + sort.Strings(s) + return s +} diff --git a/android/variable.go b/android/variable.go new file mode 100644 index 00000000..29d7a080 --- /dev/null +++ b/android/variable.go @@ -0,0 +1,232 @@ +// 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 android + +import ( + "fmt" + "reflect" + "runtime" + "strings" + + "github.com/google/blueprint/proptools" +) + +func init() { + RegisterBottomUpMutator("variable", variableMutator) +} + +type variableProperties struct { + Product_variables struct { + Platform_sdk_version struct { + Asflags []string + } + + // unbundled_build is a catch-all property to annotate modules that don't build in one or + // more unbundled branches, usually due to dependencies missing from the manifest. + Unbundled_build struct { + Enabled *bool `android:"arch_variant"` + } `android:"arch_variant"` + + Brillo struct { + Version_script *string `android:"arch_variant"` + } `android:"arch_variant"` + + Malloc_not_svelte struct { + Cflags []string + } + + Safestack struct { + Cflags []string `android:"arch_variant"` + } `android:"arch_variant"` + } `android:"arch_variant"` +} + +var zeroProductVariables variableProperties + +type productVariables struct { + // Suffix to add to generated Makefiles + Make_suffix *string `json:",omitempty"` + + Platform_sdk_version *int `json:",omitempty"` + + DeviceName *string `json:",omitempty"` + DeviceArch *string `json:",omitempty"` + DeviceArchVariant *string `json:",omitempty"` + DeviceCpuVariant *string `json:",omitempty"` + DeviceAbi *[]string `json:",omitempty"` + DeviceUsesClang *bool `json:",omitempty"` + + DeviceSecondaryArch *string `json:",omitempty"` + DeviceSecondaryArchVariant *string `json:",omitempty"` + DeviceSecondaryCpuVariant *string `json:",omitempty"` + DeviceSecondaryAbi *[]string `json:",omitempty"` + + HostArch *string `json:",omitempty"` + HostSecondaryArch *string `json:",omitempty"` + + CrossHost *string `json:",omitempty"` + CrossHostArch *string `json:",omitempty"` + CrossHostSecondaryArch *string `json:",omitempty"` + + Allow_missing_dependencies *bool `json:",omitempty"` + Unbundled_build *bool `json:",omitempty"` + Brillo *bool `json:",omitempty"` + Malloc_not_svelte *bool `json:",omitempty"` + Safestack *bool `json:",omitempty"` + HostStaticBinaries *bool `json:",omitempty"` + + SanitizeHost *[]string `json:",omitempty"` + SanitizeDevice *[]string `json:",omitempty"` +} + +func boolPtr(v bool) *bool { + return &v +} + +func intPtr(v int) *int { + return &v +} + +func stringPtr(v string) *string { + return &v +} + +func (v *productVariables) SetDefaultConfig() { + *v = productVariables{ + Platform_sdk_version: intPtr(22), + HostArch: stringPtr("x86_64"), + HostSecondaryArch: stringPtr("x86"), + DeviceName: stringPtr("flounder"), + DeviceArch: stringPtr("arm64"), + DeviceArchVariant: stringPtr("armv8-a"), + DeviceCpuVariant: stringPtr("denver64"), + DeviceAbi: &[]string{"arm64-v8a"}, + DeviceUsesClang: boolPtr(true), + DeviceSecondaryArch: stringPtr("arm"), + DeviceSecondaryArchVariant: stringPtr("armv7-a-neon"), + DeviceSecondaryCpuVariant: stringPtr("denver"), + DeviceSecondaryAbi: &[]string{"armeabi-v7a"}, + Malloc_not_svelte: boolPtr(false), + Safestack: boolPtr(false), + } + + if runtime.GOOS == "linux" { + v.CrossHost = stringPtr("windows") + v.CrossHostArch = stringPtr("x86") + v.CrossHostSecondaryArch = stringPtr("x86_64") + } +} + +func variableMutator(mctx BottomUpMutatorContext) { + var module Module + var ok bool + if module, ok = mctx.Module().(Module); !ok { + return + } + + // TODO: depend on config variable, create variants, propagate variants up tree + a := module.base() + variableValues := reflect.ValueOf(&a.variableProperties.Product_variables).Elem() + zeroValues := reflect.ValueOf(zeroProductVariables.Product_variables) + + for i := 0; i < variableValues.NumField(); i++ { + variableValue := variableValues.Field(i) + zeroValue := zeroValues.Field(i) + name := variableValues.Type().Field(i).Name + property := "product_variables." + proptools.PropertyNameForField(name) + + // Check that the variable was set for the product + val := reflect.ValueOf(mctx.Config().(Config).ProductVariables).FieldByName(name) + if !val.IsValid() || val.Kind() != reflect.Ptr || val.IsNil() { + continue + } + + val = val.Elem() + + // For bools, check that the value is true + if val.Kind() == reflect.Bool && val.Bool() == false { + continue + } + + // Check if any properties were set for the module + if reflect.DeepEqual(variableValue.Interface(), zeroValue.Interface()) { + continue + } + + a.setVariableProperties(mctx, property, variableValue, val.Interface()) + } +} + +func (a *ModuleBase) setVariableProperties(ctx BottomUpMutatorContext, + prefix string, productVariablePropertyValue reflect.Value, variableValue interface{}) { + + printfIntoProperties(productVariablePropertyValue, variableValue) + + err := proptools.AppendMatchingProperties(a.generalProperties, + productVariablePropertyValue.Addr().Interface(), nil) + if err != nil { + if propertyErr, ok := err.(*proptools.ExtendPropertyError); ok { + ctx.PropertyErrorf(propertyErr.Property, "%s", propertyErr.Err.Error()) + } else { + panic(err) + } + } +} + +func printfIntoProperties(productVariablePropertyValue reflect.Value, variableValue interface{}) { + for i := 0; i < productVariablePropertyValue.NumField(); i++ { + propertyValue := productVariablePropertyValue.Field(i) + kind := propertyValue.Kind() + if kind == reflect.Ptr { + if propertyValue.IsNil() { + continue + } + propertyValue = propertyValue.Elem() + } + switch propertyValue.Kind() { + case reflect.String: + printfIntoProperty(propertyValue, variableValue) + case reflect.Slice: + for j := 0; j < propertyValue.Len(); j++ { + printfIntoProperty(propertyValue.Index(j), variableValue) + } + case reflect.Bool: + // Nothing + case reflect.Struct: + printfIntoProperties(propertyValue, variableValue) + default: + panic(fmt.Errorf("unsupported field kind %q", propertyValue.Kind())) + } + } +} + +func printfIntoProperty(propertyValue reflect.Value, variableValue interface{}) { + s := propertyValue.String() + // For now, we only support int formats + var i int + if strings.Contains(s, "%d") { + switch v := variableValue.(type) { + case int: + i = v + case bool: + if v { + i = 1 + } + default: + panic(fmt.Errorf("unsupported type %T", variableValue)) + } + propertyValue.Set(reflect.ValueOf(fmt.Sprintf(s, i))) + } +} |