From 9272ade7a862376d53b43de40a30d89b3ac8db08 Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Wed, 17 Aug 2016 15:24:12 -0700 Subject: [PATCH] Add DeviceConfig and OncePer objects Add DeviceConfig to store per-device configuration information. Put a OncePer object inside Config and DeviceConfig, which computes a value once per key per object to allow build logic to store arbitrary per-build or per-device computed values. Change-Id: I1a38b426f29d223ef5e803e0d4d9604500de2fd2 --- Android.bp | 1 + android/arch.go | 2 +- android/config.go | 52 +++++++++++++++++++++++++------- android/module.go | 5 ++++ android/onceper.go | 75 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 123 insertions(+), 12 deletions(-) create mode 100644 android/onceper.go diff --git a/Android.bp b/Android.bp index eb70cc1f3..bb9ab74c3 100644 --- a/Android.bp +++ b/Android.bp @@ -100,6 +100,7 @@ bootstrap_go_package { "android/makevars.go", "android/module.go", "android/mutator.go", + "android/onceper.go", "android/package_ctx.go", "android/paths.go", "android/util.go", diff --git a/android/arch.go b/android/arch.go index 2d4ad2e41..2d38dafc1 100644 --- a/android/arch.go +++ b/android/arch.go @@ -717,7 +717,7 @@ func forEachInterface(v reflect.Value, f func(reflect.Value)) { } // Convert the arch product variables into a list of targets for each os class structs -func decodeTargetProductVariables(config Config) (map[OsClass][]Target, error) { +func decodeTargetProductVariables(config *config) (map[OsClass][]Target, error) { variables := config.ProductVariables targets := make(map[OsClass][]Target) diff --git a/android/config.go b/android/config.go index ae0475696..196df5585 100644 --- a/android/config.go +++ b/android/config.go @@ -43,11 +43,18 @@ func (f *FileConfigurableOptions) SetDefaultConfig() { *f = FileConfigurableOptions{} } +// A Config object represents the entire build configuration for Android. type Config struct { *config } -// A config object represents the entire build configuration for Android. +// A DeviceConfig object represents the configuration for a particular device being built. For +// now there will only be one of these, but in the future there may be multiple devices being +// built +type DeviceConfig struct { + *deviceConfig +} + type config struct { FileConfigurableOptions ProductVariables productVariables @@ -58,6 +65,8 @@ type config struct { Targets map[OsClass][]Target BuildOsVariant string + deviceConfig *deviceConfig + srcDir string // the path of the root source directory buildDir string // the path of the build output directory @@ -66,6 +75,13 @@ type config struct { envFrozen bool inMake bool + OncePer +} + +type deviceConfig struct { + config *config + targets []Arch + OncePer } type jsonConfigurable interface { @@ -138,17 +154,23 @@ func saveToConfigFile(config jsonConfigurable, filename string) error { // 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), + config := &config{ + ConfigFileName: filepath.Join(buildDir, configFileName), + ProductVariablesFileName: filepath.Join(buildDir, productVariablesFileName), - srcDir: srcDir, - buildDir: buildDir, - envDeps: make(map[string]string), - }, + srcDir: srcDir, + buildDir: buildDir, + envDeps: make(map[string]string), + + deviceConfig: &deviceConfig{}, } + deviceConfig := &deviceConfig{ + config: config, + } + + config.deviceConfig = deviceConfig + // 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) @@ -166,7 +188,7 @@ func NewConfig(srcDir, buildDir string) (Config, error) { } // Load any configurable options from the configuration file - err = loadConfig(config.config) + err = loadConfig(config) if err != nil { return Config{}, err } @@ -192,7 +214,7 @@ func NewConfig(srcDir, buildDir string) (Config, error) { config.Targets = targets config.BuildOsVariant = targets[Host][0].String() - return config, nil + return Config{config}, nil } func (c *config) RemoveAbandonedFiles() bool { @@ -337,3 +359,11 @@ func (c *config) Android64() bool { return false } + +func (c *deviceConfig) Arches() []Arch { + var arches []Arch + for _, target := range c.config.Targets[Device] { + arches = append(arches, target.Arch) + } + return arches +} diff --git a/android/module.go b/android/module.go index bf9dfaac9..03c06b42f 100644 --- a/android/module.go +++ b/android/module.go @@ -56,6 +56,7 @@ type androidBaseContext interface { Darwin() bool Debug() bool AConfig() Config + DeviceConfig() DeviceConfig } type BaseContext interface { @@ -535,6 +536,10 @@ func (a *androidBaseContextImpl) AConfig() Config { return a.config } +func (a *androidBaseContextImpl) DeviceConfig() DeviceConfig { + return DeviceConfig{a.config.deviceConfig} +} + func (a *androidModuleContext) Proprietary() bool { return a.module.base().commonProperties.Proprietary } diff --git a/android/onceper.go b/android/onceper.go new file mode 100644 index 000000000..5f7a3102b --- /dev/null +++ b/android/onceper.go @@ -0,0 +1,75 @@ +// 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 ( + "sync" + "sync/atomic" +) + +type OncePer struct { + values atomic.Value + valuesLock sync.Mutex +} + +type valueMap map[interface{}]interface{} + +// Once computes a value the first time it is called with a given key per OncePer, and returns the +// value without recomputing when called with the same key. key must be hashable. +func (once *OncePer) Once(key interface{}, value func() interface{}) interface{} { + // Atomically load the map without locking. If this is the first call Load() will return nil + // and the type assertion will fail, leaving a nil map in m, but that's OK since m is only used + // for reads. + m, _ := once.values.Load().(valueMap) + if v, ok := m[key]; ok { + return v + } + + once.valuesLock.Lock() + defer once.valuesLock.Unlock() + + // Check again with the lock held + m, _ = once.values.Load().(valueMap) + if v, ok := m[key]; ok { + return v + } + + // Copy the existing map + newMap := make(valueMap, len(m)) + for k, v := range m { + newMap[k] = v + } + + v := value() + + newMap[key] = v + once.values.Store(newMap) + + return v +} + +func (once *OncePer) OnceStringSlice(key interface{}, value func() []string) []string { + return once.Once(key, func() interface{} { return value() }).([]string) +} + +func (once *OncePer) Once2StringSlice(key interface{}, value func() ([]string, []string)) ([]string, []string) { + type twoStringSlice [2][]string + s := once.Once(key, func() interface{} { + var s twoStringSlice + s[0], s[1] = value() + return s + }).(twoStringSlice) + return s[0], s[1] +}