diff options
author | Colin Cross <ccross@android.com> | 2015-01-30 17:27:36 -0800 |
---|---|---|
committer | Colin Cross <ccross@android.com> | 2015-03-13 20:28:16 -0700 |
commit | 3f40fa460d85b10646d383a3b6b01ea6d569b01b (patch) | |
tree | 542d913a3f0f818042b503948869818a77e99ebc /common | |
parent | e441b9df9a68595d0dd7b8ed184aecb27c86054b (diff) | |
download | build_soong-3f40fa460d85b10646d383a3b6b01ea6d569b01b.tar.gz build_soong-3f40fa460d85b10646d383a3b6b01ea6d569b01b.tar.bz2 build_soong-3f40fa460d85b10646d383a3b6b01ea6d569b01b.zip |
Add soong_build primary builder
Initial build logic for building android with soong. It can build
a variety of C and C++ files for arm/arm64 and host.
Change-Id: I10eb37c2c2a50be6af1bb5fd568c0962b9476bf0
Diffstat (limited to 'common')
-rw-r--r-- | common/arch.go | 545 | ||||
-rw-r--r-- | common/defs.go | 61 | ||||
-rw-r--r-- | common/glob.go | 133 | ||||
-rw-r--r-- | common/module.go | 327 | ||||
-rw-r--r-- | common/paths.go | 83 |
5 files changed, 1149 insertions, 0 deletions
diff --git a/common/arch.go b/common/arch.go new file mode 100644 index 00000000..8daade0f --- /dev/null +++ b/common/arch.go @@ -0,0 +1,545 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "blueprint" + "blueprint/proptools" + "fmt" + "reflect" + "runtime" + "strings" +) + +var ( + Arm = newArch32("Arm") + Arm64 = newArch64("Arm64") + Mips = newArch32("Mips") + Mips64 = newArch64("Mips64") + X86 = newArch32("X86") + X86_64 = newArch64("X86_64") +) + +/* +Example blueprints file containing all variant property groups, with comment listing what type +of variants get properties in that group: + +module { + arch: { + arm: { + // Host or device variants with arm architecture + }, + arm64: { + // Host or device variants with arm64 architecture + }, + mips: { + // Host or device variants with mips architecture + }, + mips64: { + // Host or device variants with mips64 architecture + }, + x86: { + // Host or device variants with x86 architecture + }, + x86_64: { + // Host or device variants with x86_64 architecture + }, + }, + multilib: { + lib32: { + // Host or device variants for 32-bit architectures + }, + lib64: { + // Host or device variants for 64-bit architectures + }, + }, + target: { + android: { + // Device variants + }, + host: { + // Host variants + }, + linux: { + // Linux host variants + }, + darwin: { + // Darwin host variants + }, + windows: { + // Windows host variants + }, + not_windows: { + // Non-windows host variants + }, + }, +} +*/ +type archProperties struct { + Arch struct { + Arm interface{} + Arm64 interface{} + Mips interface{} + Mips64 interface{} + X86 interface{} + X86_64 interface{} + } + Multilib struct { + Lib32 interface{} + Lib64 interface{} + } + Target struct { + Host interface{} + Android interface{} + Linux interface{} + Darwin interface{} + Windows interface{} + Not_windows interface{} + } +} + +// An Arch indicates a single CPU architecture. +type Arch struct { + HostOrDevice HostOrDevice + ArchType ArchType + ArchVariant string + CpuVariant string +} + +func (a Arch) String() string { + s := a.HostOrDevice.String() + "_" + a.ArchType.String() + if a.ArchVariant != "" { + s += "_" + a.ArchVariant + } + if a.CpuVariant != "" { + s += "_" + a.CpuVariant + } + return s +} + +type ArchType struct { + Name string + Field string + Multilib string + MultilibField string +} + +func newArch32(field string) ArchType { + return ArchType{ + Name: strings.ToLower(field), + Field: field, + Multilib: "lib32", + MultilibField: "Lib32", + } +} + +func newArch64(field string) ArchType { + return ArchType{ + Name: strings.ToLower(field), + Field: field, + Multilib: "lib64", + MultilibField: "Lib64", + } +} + +func (a ArchType) String() string { + return a.Name +} + +type HostOrDeviceSupported int + +const ( + _ HostOrDeviceSupported = iota + HostSupported + DeviceSupported + HostAndDeviceSupported +) + +type HostOrDevice int + +const ( + _ HostOrDevice = iota + Host + Device +) + +func (hod HostOrDevice) String() string { + switch hod { + case Device: + return "device" + case Host: + return "host" + default: + panic(fmt.Sprintf("unexpected HostOrDevice value %d", hod)) + } +} + +func (hod HostOrDevice) FieldLower() string { + switch hod { + case Device: + return "android" + case Host: + return "host" + default: + panic(fmt.Sprintf("unexpected HostOrDevice value %d", hod)) + } +} + +func (hod HostOrDevice) Field() string { + switch hod { + case Device: + return "Android" + case Host: + return "Host" + default: + panic(fmt.Sprintf("unexpected HostOrDevice value %d", hod)) + } +} + +func (hod HostOrDevice) Host() bool { + if hod == 0 { + panic("HostOrDevice unset") + } + return hod == Host +} + +func (hod HostOrDevice) Device() bool { + if hod == 0 { + panic("HostOrDevice unset") + } + return hod == Device +} + +var hostOrDeviceName = map[HostOrDevice]string{ + Device: "device", + Host: "host", +} + +var ( + armArch = Arch{ + HostOrDevice: Device, + ArchType: Arm, + ArchVariant: "armv7-a-neon", + CpuVariant: "cortex-a15", + } + arm64Arch = Arch{ + HostOrDevice: Device, + ArchType: Arm64, + ArchVariant: "armv8-a", + CpuVariant: "denver", + } + hostArch = Arch{ + HostOrDevice: Host, + ArchType: X86, + } + host64Arch = Arch{ + HostOrDevice: Host, + ArchType: X86_64, + } +) + +func ArchMutator(mctx blueprint.EarlyMutatorContext) { + var module AndroidModule + var ok bool + if module, ok = mctx.Module().(AndroidModule); !ok { + return + } + + // TODO: this is all hardcoded for arm64 primary, arm secondary for now + // Replace with a configuration file written by lunch or bootstrap + + arches := []Arch{} + + if module.base().HostSupported() { + arches = append(arches, host64Arch) + } + + if module.base().DeviceSupported() { + switch module.base().commonProperties.Compile_multilib { + case "both": + arches = append(arches, arm64Arch, armArch) + case "first", "64": + arches = append(arches, arm64Arch) + case "32": + arches = append(arches, armArch) + default: + mctx.ModuleErrorf(`compile_multilib must be "both", "first", "32", or "64", found %q`, + module.base().commonProperties.Compile_multilib) + } + } + + archNames := []string{} + for _, arch := range arches { + archNames = append(archNames, arch.String()) + } + + modules := mctx.CreateVariations(archNames...) + + for i, m := range modules { + m.(AndroidModule).base().SetArch(arches[i]) + m.(AndroidModule).base().setArchProperties(mctx, arches[i]) + } +} + +func InitArchModule(m AndroidModule, defaultMultilib string, + propertyStructs ...interface{}) (blueprint.Module, []interface{}) { + + base := m.base() + + base.commonProperties.Compile_multilib = defaultMultilib + + base.generalProperties = append(base.generalProperties, + &base.commonProperties) + base.generalProperties = append(base.generalProperties, + propertyStructs...) + + for _, properties := range base.generalProperties { + propertiesValue := reflect.ValueOf(properties) + if propertiesValue.Kind() != reflect.Ptr { + panic("properties must be a pointer to a struct") + } + + propertiesValue = propertiesValue.Elem() + if propertiesValue.Kind() != reflect.Struct { + panic("properties must be a pointer to a struct") + } + + archProperties := &archProperties{} + forEachInterface(reflect.ValueOf(archProperties), func(v reflect.Value) { + newValue := proptools.CloneProperties(propertiesValue) + proptools.ZeroProperties(newValue.Elem()) + v.Set(newValue) + }) + + base.archProperties = append(base.archProperties, archProperties) + } + + var allProperties []interface{} + allProperties = append(allProperties, base.generalProperties...) + for _, asp := range base.archProperties { + allProperties = append(allProperties, asp) + } + + return m, allProperties +} + +// Rewrite the module's properties structs to contain arch-specific values. +func (a *AndroidModuleBase) setArchProperties(ctx blueprint.EarlyMutatorContext, arch Arch) { + for i := range a.generalProperties { + generalPropsValue := reflect.ValueOf(a.generalProperties[i]).Elem() + + // Handle arch-specific properties in the form: + // arch { + // arm64 { + // key: value, + // }, + // }, + t := arch.ArchType + extendProperties(ctx, "arch", t.Name, generalPropsValue, + reflect.ValueOf(a.archProperties[i].Arch).FieldByName(t.Field).Elem().Elem()) + + // Handle multilib-specific properties in the form: + // multilib { + // lib32 { + // key: value, + // }, + // }, + extendProperties(ctx, "multilib", t.Multilib, generalPropsValue, + reflect.ValueOf(a.archProperties[i].Multilib).FieldByName(t.MultilibField).Elem().Elem()) + + // Handle host-or-device-specific properties in the form: + // target { + // host { + // key: value, + // }, + // }, + hod := arch.HostOrDevice + extendProperties(ctx, "target", hod.FieldLower(), generalPropsValue, + reflect.ValueOf(a.archProperties[i].Target).FieldByName(hod.Field()).Elem().Elem()) + + // Handle host target properties in the form: + // target { + // linux { + // key: value, + // }, + // not_windows { + // key: value, + // }, + // }, + var osList = []struct { + goos string + field string + }{ + {"darwin", "Darwin"}, + {"linux", "Linux"}, + {"windows", "Windows"}, + } + + if hod.Host() { + for _, v := range osList { + if v.goos == runtime.GOOS { + extendProperties(ctx, "target", v.goos, generalPropsValue, + reflect.ValueOf(a.archProperties[i].Target).FieldByName(v.field).Elem().Elem()) + } + } + extendProperties(ctx, "target", "not_windows", generalPropsValue, + reflect.ValueOf(a.archProperties[i].Target).FieldByName("Not_windows").Elem().Elem()) + } + + if ctx.Failed() { + return + } + } +} + +func forEachInterface(v reflect.Value, f func(reflect.Value)) { + switch v.Kind() { + case reflect.Interface: + f(v) + case reflect.Struct: + for i := 0; i < v.NumField(); i++ { + forEachInterface(v.Field(i), f) + } + case reflect.Ptr: + forEachInterface(v.Elem(), f) + default: + panic(fmt.Errorf("Unsupported kind %s", v.Kind())) + } +} + +// TODO: move this to proptools +func extendProperties(ctx blueprint.EarlyMutatorContext, variationType, variationName string, + dstValue, srcValue reflect.Value) { + extendPropertiesRecursive(ctx, variationType, variationName, dstValue, srcValue, "") +} + +func extendPropertiesRecursive(ctx blueprint.EarlyMutatorContext, variationType, variationName string, + dstValue, srcValue reflect.Value, recursePrefix string) { + + typ := dstValue.Type() + if srcValue.Type() != typ { + panic(fmt.Errorf("can't extend mismatching types (%s <- %s)", + dstValue.Kind(), srcValue.Kind())) + } + + for i := 0; i < srcValue.NumField(); i++ { + field := typ.Field(i) + if field.PkgPath != "" { + // The field is not exported so just skip it. + continue + } + + srcFieldValue := srcValue.Field(i) + dstFieldValue := dstValue.Field(i) + + localPropertyName := proptools.PropertyNameForField(field.Name) + propertyName := fmt.Sprintf("%s.%s.%s%s", variationType, variationName, + recursePrefix, localPropertyName) + propertyPresentInVariation := ctx.ContainsProperty(propertyName) + + if !propertyPresentInVariation { + continue + } + + tag := field.Tag.Get("android") + tags := map[string]bool{} + for _, entry := range strings.Split(tag, ",") { + if entry != "" { + tags[entry] = true + } + } + + if !tags["arch_variant"] { + ctx.PropertyErrorf(propertyName, "property %q can't be specific to a build variant", + recursePrefix+proptools.PropertyNameForField(field.Name)) + continue + } + + switch srcFieldValue.Kind() { + case reflect.Bool: + // Replace the original value. + dstFieldValue.Set(srcFieldValue) + case reflect.String: + // Append the extension string. + dstFieldValue.SetString(dstFieldValue.String() + + srcFieldValue.String()) + case reflect.Struct: + // Recursively extend the struct's fields. + newRecursePrefix := fmt.Sprintf("%s%s.", recursePrefix, strings.ToLower(field.Name)) + extendPropertiesRecursive(ctx, variationType, variationName, + dstFieldValue, srcFieldValue, + newRecursePrefix) + case reflect.Slice: + val, err := archCombineSlices(dstFieldValue, srcFieldValue, tags["arch_subtract"]) + if err != nil { + ctx.PropertyErrorf(propertyName, err.Error()) + continue + } + dstFieldValue.Set(val) + case reflect.Ptr, reflect.Interface: + // Recursively extend the pointed-to struct's fields. + if dstFieldValue.IsNil() != srcFieldValue.IsNil() { + panic(fmt.Errorf("can't extend field %q: nilitude mismatch")) + } + if dstFieldValue.Type() != srcFieldValue.Type() { + panic(fmt.Errorf("can't extend field %q: type mismatch")) + } + if !dstFieldValue.IsNil() { + newRecursePrefix := fmt.Sprintf("%s.%s", recursePrefix, field.Name) + extendPropertiesRecursive(ctx, variationType, variationName, + dstFieldValue.Elem(), srcFieldValue.Elem(), + newRecursePrefix) + } + default: + panic(fmt.Errorf("unexpected kind for property struct field %q: %s", + field.Name, srcFieldValue.Kind())) + } + } +} + +func archCombineSlices(general, arch reflect.Value, canSubtract bool) (reflect.Value, error) { + if !canSubtract { + // Append the extension slice. + return reflect.AppendSlice(general, arch), nil + } + + // Support -val in arch list to subtract a value from original list + l := general.Interface().([]string) + for archIndex := 0; archIndex < arch.Len(); archIndex++ { + archString := arch.Index(archIndex).String() + if strings.HasPrefix(archString, "-") { + generalIndex := findStringInSlice(archString[1:], l) + if generalIndex == -1 { + return reflect.Value{}, + fmt.Errorf("can't find %q to subtract from general properties", archString[1:]) + } + l = append(l[:generalIndex], l[generalIndex+1:]...) + } else { + l = append(l, archString) + } + } + + return reflect.ValueOf(l), nil +} + +func findStringInSlice(str string, slice []string) int { + for i, s := range slice { + if s == str { + return i + } + } + + return -1 +} diff --git a/common/defs.go b/common/defs.go new file mode 100644 index 00000000..44992166 --- /dev/null +++ b/common/defs.go @@ -0,0 +1,61 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "blueprint" +) + +var ( + pctx = blueprint.NewPackageContext("android/soong/common") + + cpPreserveSymlinks = pctx.VariableConfigMethod("cpPreserveSymlinks", + Config.CpPreserveSymlinksFlags) + + // A phony rule that is not the built-in Ninja phony rule. The built-in + // phony rule has special behavior that is sometimes not desired. See the + // Ninja docs for more details. + Phony = pctx.StaticRule("Phony", + blueprint.RuleParams{ + Command: "# phony $out", + Description: "phony $out", + }) + + // GeneratedFile is a rule for indicating that a given file was generated + // while running soong. This allows the file to be cleaned up if it ever + // stops being generated by soong. + GeneratedFile = pctx.StaticRule("GeneratedFile", + blueprint.RuleParams{ + Command: "# generated $out", + Description: "generated $out", + Generator: true, + }) + + // A copy rule. + Cp = pctx.StaticRule("Cp", + blueprint.RuleParams{ + Command: "cp $cpPreserveSymlinks $cpFlags $in $out", + Description: "cp $out", + }, + "cpFlags") + + // A symlink rule. + Symlink = pctx.StaticRule("Symlink", + blueprint.RuleParams{ + Command: "ln -f -s $fromPath $out", + Description: "symlink $out", + }, + "fromPath") +) diff --git a/common/glob.go b/common/glob.go new file mode 100644 index 00000000..9aada958 --- /dev/null +++ b/common/glob.go @@ -0,0 +1,133 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "fmt" + "path/filepath" + + "blueprint" + "blueprint/bootstrap" + + "android/soong/glob" +) + +// This file supports globbing source files in Blueprints files. +// +// The build.ninja file needs to be regenerated any time a file matching the glob is added +// or removed. The naive solution is to have the build.ninja file depend on all the +// traversed directories, but this will cause the regeneration step to run every time a +// non-matching file is added to a traversed directory, including backup files created by +// editors. +// +// The solution implemented here optimizes out regenerations when the directory modifications +// don't match the glob by having the build.ninja file depend on an intermedate file that +// is only updated when a file matching the glob is added or removed. The intermediate file +// depends on the traversed directories via a depfile. The depfile is used to avoid build +// errors if a directory is deleted - a direct dependency on the deleted directory would result +// in a build failure with a "missing and no known rule to make it" error. + +var ( + globCmd = filepath.Join(bootstrap.BinDir, "soong_glob") + + // globRule rule traverses directories to produce a list of files that match $glob + // and writes it to $out if it has changed, and writes the directories to $out.d + globRule = pctx.StaticRule("globRule", + blueprint.RuleParams{ + Command: fmt.Sprintf(`%s -o $out "$glob"`, globCmd), + Description: "glob $glob", + + Restat: true, + Generator: true, + Deps: blueprint.DepsGCC, + Depfile: "$out.d", + }, + "glob") +) + +func hasGlob(in []string) bool { + for _, s := range in { + if glob.IsGlob(s) { + return true + } + } + + return false +} + +func ExpandGlobs(ctx AndroidModuleContext, in []string) []string { + if !hasGlob(in) { + return in + } + + out := make([]string, 0, len(in)) + for _, s := range in { + if glob.IsGlob(s) { + out = append(out, Glob(ctx, s)...) + } else { + out = append(out, s) + } + } + + return out +} + +func Glob(ctx AndroidModuleContext, globPattern string) []string { + fileListFile := filepath.Join(ModuleOutDir(ctx), "glob", globToString(globPattern)) + depFile := fileListFile + ".d" + + // Get a globbed file list, and write out fileListFile and depFile + files, err := glob.GlobWithDepFile(globPattern, fileListFile, depFile) + if err != nil { + ctx.ModuleErrorf("glob: %s", err.Error()) + return []string{globPattern} + } + + // Create a rule to rebuild fileListFile if a directory in depFile changes. fileListFile + // will only be rewritten if it has changed, preventing unnecesary build.ninja regenerations. + ctx.Build(pctx, blueprint.BuildParams{ + Rule: globRule, + Outputs: []string{fileListFile}, + Implicits: []string{globCmd}, + Args: map[string]string{ + "glob": globPattern, + }, + }) + + // Phony rule so the cleanup phase doesn't delete the depFile + ctx.Build(pctx, blueprint.BuildParams{ + Rule: blueprint.Phony, + Outputs: []string{depFile}, + }) + + // Make build.ninja depend on the fileListFile + ctx.AddNinjaFileDeps(fileListFile) + + return files +} + +func globToString(glob string) string { + ret := "" + for _, c := range glob { + if c >= 'a' && c <= 'z' || + c >= 'A' && c <= 'Z' || + c >= '0' && c <= '9' || + c == '_' || c == '-' || c == '/' { + ret += string(c) + } + } + + return ret +} diff --git a/common/module.go b/common/module.go new file mode 100644 index 00000000..0cbe4b07 --- /dev/null +++ b/common/module.go @@ -0,0 +1,327 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "blueprint" + "path/filepath" +) + +var ( + DeviceSharedLibrary = "shared_library" + DeviceStaticLibrary = "static_library" + DeviceExecutable = "executable" + HostSharedLibrary = "host_shared_library" + HostStaticLibrary = "host_static_library" + HostExecutable = "host_executable" +) + +type AndroidModuleContext interface { + blueprint.ModuleContext + + Arch() Arch + InstallFile(installPath, srcPath string) + CheckbuildFile(srcPath string) +} + +type AndroidModule interface { + blueprint.Module + + GenerateAndroidBuildActions(AndroidModuleContext) + + base() *AndroidModuleBase + Disabled() bool + HostOrDevice() HostOrDevice +} + +type AndroidDynamicDepender interface { + AndroidDynamicDependencies(ctx AndroidDynamicDependerModuleContext) []string +} + +type AndroidDynamicDependerModuleContext interface { + blueprint.DynamicDependerModuleContext +} + +type commonProperties struct { + Name string + Deps []string + ResourceDirs []string + + // disabled: don't emit any build rules for this module + Disabled bool `android:"arch_variant"` + + // multilib: control whether this module compiles for 32-bit, 64-bit, or both. Possible values + // are "32" (compile for 32-bit only), "64" (compile for 64-bit only), "both" (compile for both + // architectures), or "first" (compile for 64-bit on a 64-bit platform, and 32-bit on a 32-bit + // platform + Compile_multilib string + + // Set by ArchMutator + CompileArch Arch `blueprint:"mutated"` + + // Set by InitAndroidModule + HostOrDeviceSupported HostOrDeviceSupported `blueprint:"mutated"` +} + +type hostAndDeviceProperties struct { + Host_supported bool + Device_supported bool +} + +func InitAndroidModule(m AndroidModule, hod HostOrDeviceSupported, defaultMultilib string, + propertyStructs ...interface{}) (blueprint.Module, []interface{}) { + + base := m.base() + base.module = m + base.commonProperties.HostOrDeviceSupported = hod + + if hod == HostAndDeviceSupported { + // Default to module to device supported, host not supported, can override in module + // properties + base.hostAndDeviceProperties.Device_supported = true + propertyStructs = append(propertyStructs, &base.hostAndDeviceProperties) + } + + return InitArchModule(m, defaultMultilib, propertyStructs...) +} + +// A AndroidModuleBase object contains the properties that are common to all Android +// modules. It should be included as an anonymous field in every module +// struct definition. InitAndroidModule should then be called from the module's +// factory function, and the return values from InitAndroidModule should be +// returned from the factory function. +// +// The AndroidModuleBase type is responsible for implementing the +// GenerateBuildActions method to support the blueprint.Module interface. This +// method will then call the module's GenerateAndroidBuildActions method once +// for each build variant that is to be built. GenerateAndroidBuildActions is +// passed a AndroidModuleContext rather than the usual blueprint.ModuleContext. +// AndroidModuleContext exposes extra functionality specific to the Android build +// system including details about the particular build variant that is to be +// generated. +// +// For example: +// +// import ( +// "android/soong/common" +// "blueprint" +// ) +// +// type myModule struct { +// common.AndroidModuleBase +// properties struct { +// MyProperty string +// } +// } +// +// func NewMyModule() (blueprint.Module, []interface{}) { +// m := &myModule{} +// return common.InitAndroidModule(m, &m.properties) +// } +// +// func (m *myModule) GenerateAndroidBuildActions(ctx common.AndroidModuleContext) { +// // Get the CPU architecture for the current build variant. +// variantArch := ctx.Arch() +// +// // ... +// } +type AndroidModuleBase struct { + // Putting the curiously recurring thing pointing to the thing that contains + // the thing pattern to good use. + module AndroidModule + + commonProperties commonProperties + hostAndDeviceProperties hostAndDeviceProperties + generalProperties []interface{} + archProperties []*archProperties + + noAddressSanitizer bool + installFiles []string + checkbuildFiles []string +} + +func (a *AndroidModuleBase) base() *AndroidModuleBase { + return a +} + +func (a *AndroidModuleBase) SetArch(arch Arch) { + a.commonProperties.CompileArch = arch +} + +func (a *AndroidModuleBase) HostOrDevice() HostOrDevice { + return a.commonProperties.CompileArch.HostOrDevice +} + +func (a *AndroidModuleBase) HostSupported() bool { + return a.commonProperties.HostOrDeviceSupported == HostSupported || + a.commonProperties.HostOrDeviceSupported == HostAndDeviceSupported && + a.hostAndDeviceProperties.Host_supported +} + +func (a *AndroidModuleBase) DeviceSupported() bool { + return a.commonProperties.HostOrDeviceSupported == DeviceSupported || + a.commonProperties.HostOrDeviceSupported == HostAndDeviceSupported && + a.hostAndDeviceProperties.Device_supported +} + +func (a *AndroidModuleBase) Disabled() bool { + return a.commonProperties.Disabled +} + +func (a *AndroidModuleBase) computeInstallDeps( + ctx blueprint.ModuleContext) []string { + + result := []string{} + ctx.VisitDepsDepthFirstIf(isFileInstaller, + func(m blueprint.Module) { + fileInstaller := m.(fileInstaller) + files := fileInstaller.filesToInstall() + result = append(result, files...) + }) + + return result +} + +func (a *AndroidModuleBase) filesToInstall() []string { + return a.installFiles +} + +func (p *AndroidModuleBase) NoAddressSanitizer() bool { + return p.noAddressSanitizer +} + +func (p *AndroidModuleBase) resourceDirs() []string { + return p.commonProperties.ResourceDirs +} + +func (a *AndroidModuleBase) generateModuleTarget(ctx blueprint.ModuleContext) { + if a != ctx.FinalModule().(AndroidModule).base() { + return + } + + allInstalledFiles := []string{} + ctx.VisitAllModuleVariants(func(module blueprint.Module) { + if androidModule, ok := module.(AndroidModule); ok { + files := androidModule.base().installFiles + allInstalledFiles = append(allInstalledFiles, files...) + } + }) + + if len(allInstalledFiles) > 0 { + ctx.Build(pctx, blueprint.BuildParams{ + Rule: blueprint.Phony, + Outputs: []string{ctx.ModuleName()}, + Inputs: allInstalledFiles, + }) + } +} + +func (a *AndroidModuleBase) DynamicDependencies(ctx blueprint.DynamicDependerModuleContext) []string { + actx := &androidDynamicDependerContext{ + DynamicDependerModuleContext: ctx, + module: a, + } + + if dynamic, ok := a.module.(AndroidDynamicDepender); ok { + return dynamic.AndroidDynamicDependencies(actx) + } + + return nil +} + +func (a *AndroidModuleBase) GenerateBuildActions(ctx blueprint.ModuleContext) { + androidCtx := &androidModuleContext{ + ModuleContext: ctx, + installDeps: a.computeInstallDeps(ctx), + installFiles: a.installFiles, + arch: a.commonProperties.CompileArch, + } + + if a.commonProperties.Disabled { + return + } + + a.module.GenerateAndroidBuildActions(androidCtx) + if ctx.Failed() { + return + } + + a.generateModuleTarget(ctx) + if ctx.Failed() { + return + } +} + +type androidModuleContext struct { + blueprint.ModuleContext + arch Arch + installDeps []string + installFiles []string + checkbuildFiles []string +} + +func (a *androidModuleContext) Build(pctx *blueprint.PackageContext, params blueprint.BuildParams) { + params.Optional = true + a.ModuleContext.Build(pctx, params) +} + +func (a *androidModuleContext) Arch() Arch { + return a.arch +} + +func (a *androidModuleContext) InstallFile(installPath, srcPath string) { + var fullInstallPath string + if a.arch.HostOrDevice.Device() { + // TODO: replace unset with a device name once we have device targeting + fullInstallPath = filepath.Join("out/target/product/unset/system", installPath, + filepath.Base(srcPath)) + } else { + // TODO: replace unset with a host name + fullInstallPath = filepath.Join("out/host/unset/", installPath, filepath.Base(srcPath)) + } + + a.ModuleContext.Build(pctx, blueprint.BuildParams{ + Rule: Cp, + Outputs: []string{fullInstallPath}, + Inputs: []string{srcPath}, + OrderOnly: a.installDeps, + }) + + a.installFiles = append(a.installFiles, fullInstallPath) + a.checkbuildFiles = append(a.checkbuildFiles, srcPath) +} + +func (a *androidModuleContext) CheckbuildFile(srcPath string) { + a.checkbuildFiles = append(a.checkbuildFiles, srcPath) +} + +type androidDynamicDependerContext struct { + blueprint.DynamicDependerModuleContext + module *AndroidModuleBase +} + +type fileInstaller interface { + filesToInstall() []string +} + +func isFileInstaller(m blueprint.Module) bool { + _, ok := m.(fileInstaller) + return ok +} + +func isAndroidModule(m blueprint.Module) bool { + _, ok := m.(AndroidModule) + return ok +} diff --git a/common/paths.go b/common/paths.go new file mode 100644 index 00000000..91b8f99c --- /dev/null +++ b/common/paths.go @@ -0,0 +1,83 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "path/filepath" + + "blueprint" +) + +type Config interface { + CpPreserveSymlinksFlags() string + SrcDir() string +} + +// ModuleOutDir returns the path to the module-specific output directory. +func ModuleOutDir(ctx AndroidModuleContext) string { + return filepath.Join(".intermediates", ctx.ModuleDir(), ctx.ModuleName(), ctx.ModuleSubDir()) +} + +// ModuleSrcDir returns the path of the directory that all source file paths are +// specified relative to. +func ModuleSrcDir(ctx blueprint.ModuleContext) string { + config := ctx.Config().(Config) + return filepath.Join(config.SrcDir(), ctx.ModuleDir()) +} + +// ModuleBinDir returns the path to the module- and architecture-specific binary +// output directory. +func ModuleBinDir(ctx AndroidModuleContext) string { + return filepath.Join(ModuleOutDir(ctx), "bin") +} + +// ModuleLibDir returns the path to the module- and architecture-specific +// library output directory. +func ModuleLibDir(ctx AndroidModuleContext) string { + return filepath.Join(ModuleOutDir(ctx), "lib") +} + +// ModuleGenDir returns the module directory for generated files +// path. +func ModuleGenDir(ctx AndroidModuleContext) string { + return filepath.Join(ModuleOutDir(ctx), "gen") +} + +// ModuleObjDir returns the module- and architecture-specific object directory +// path. +func ModuleObjDir(ctx AndroidModuleContext) string { + return filepath.Join(ModuleOutDir(ctx), "obj") +} + +// ModuleGoPackageDir returns the module-specific package root directory path. +// This directory is where the final package .a files are output and where +// dependent modules search for this package via -I arguments. +func ModuleGoPackageDir(ctx AndroidModuleContext) string { + return filepath.Join(ModuleOutDir(ctx), "pkg") +} + +// ModuleIncludeDir returns the module-specific public include directory path. +func ModuleIncludeDir(ctx AndroidModuleContext) string { + return filepath.Join(ModuleOutDir(ctx), "include") +} + +// ModuleProtoDir returns the module-specific public proto include directory path. +func ModuleProtoDir(ctx AndroidModuleContext) string { + return filepath.Join(ModuleOutDir(ctx), "proto") +} + +func ModuleJSCompiledDir(ctx AndroidModuleContext) string { + return filepath.Join(ModuleOutDir(ctx), "js") +} |