Merge "Add script to make a Go-style workspace for Soong"
diff --git a/Android.bp b/Android.bp
index 23cbad8..2ae993f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -14,6 +14,7 @@
"androidmk",
"cmd/*",
"third_party/zip",
+ "ui/*",
]
bootstrap_go_package {
@@ -46,6 +47,7 @@
],
srcs: [
"android/androidmk.go",
+ "android/api_levels.go",
"android/arch.go",
"android/config.go",
"android/defaults.go",
@@ -94,6 +96,7 @@
"cc/config/x86_darwin_host.go",
"cc/config/x86_linux_host.go",
+ "cc/config/x86_linux_bionic_host.go",
"cc/config/x86_windows_host.go",
],
testSrcs: [
@@ -117,6 +120,7 @@
"cc/builder.go",
"cc/cc.go",
"cc/check.go",
+ "cc/coverage.go",
"cc/gen.go",
"cc/makevars.go",
"cc/prebuilt.go",
@@ -128,6 +132,7 @@
"cc/tidy.go",
"cc/util.go",
+ "cc/cmakelists.go",
"cc/compiler.go",
"cc/installer.go",
"cc/linker.go",
@@ -142,9 +147,12 @@
"cc/ndk_headers.go",
"cc/ndk_library.go",
"cc/ndk_sysroot.go",
+
+ "cc/llndk_library.go",
],
testSrcs: [
"cc/cc_test.go",
+ "cc/test_data_test.go",
],
pluginFor: ["soong_build"],
}
@@ -166,6 +174,19 @@
}
bootstrap_go_package {
+ name: "soong-phony",
+ pkgPath: "android/soong/phony",
+ deps: [
+ "blueprint",
+ "soong-android",
+ ],
+ srcs: [
+ "phony/phony.go",
+ ],
+ pluginFor: ["soong_build"],
+}
+
+bootstrap_go_package {
name: "soong-java",
pkgPath: "android/soong/java",
deps: [
@@ -211,6 +232,7 @@
toolchain_library {
name: "libatomic",
defaults: ["linux_bionic_supported"],
+ vendor_available: true,
arch: {
arm: {
instruction_set: "arm",
@@ -221,6 +243,7 @@
toolchain_library {
name: "libgcc",
defaults: ["linux_bionic_supported"],
+ vendor_available: true,
arch: {
arm: {
instruction_set: "arm",
@@ -229,6 +252,17 @@
}
toolchain_library {
+ name: "libwinpthread",
+ host_supported: true,
+ enabled: false,
+ target: {
+ windows: {
+ enabled: true
+ },
+ },
+}
+
+toolchain_library {
name: "libgcov",
defaults: ["linux_bionic_supported"],
arch: {
diff --git a/android/androidmk.go b/android/androidmk.go
index aeb0aa5..af6608f 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -43,7 +43,7 @@
OutputFile OptionalPath
Disabled bool
- Custom func(w io.Writer, name, prefix string) error
+ Custom func(w io.Writer, name, prefix, moduleDir string) error
Extra []func(w io.Writer, outputFile Path) error
}
@@ -164,28 +164,35 @@
return err
}
+ // Make does not understand LinuxBionic
+ if amod.Os() == LinuxBionic {
+ return nil
+ }
+
if data.SubName != "" {
name += data.SubName
}
if data.Custom != nil {
prefix := ""
- switch amod.Os().Class {
- case Host:
- prefix = "HOST_"
- case HostCross:
- prefix = "HOST_CROSS_"
- case Device:
- prefix = "TARGET_"
+ if amod.ArchSpecific() {
+ switch amod.Os().Class {
+ case Host:
+ prefix = "HOST_"
+ case HostCross:
+ prefix = "HOST_CROSS_"
+ case Device:
+ prefix = "TARGET_"
+ }
+
+ config := ctx.Config().(Config)
+ if amod.Arch().ArchType != config.Targets[amod.Os().Class][0].Arch.ArchType {
+ prefix = "2ND_" + prefix
+ }
}
- config := ctx.Config().(Config)
- if amod.Arch().ArchType != config.Targets[amod.Os().Class][0].Arch.ArchType {
- prefix = "2ND_" + prefix
- }
-
- return data.Custom(w, name, prefix)
+ return data.Custom(w, name, prefix, filepath.Dir(ctx.BlueprintFile(mod)))
}
if data.Disabled {
@@ -227,6 +234,12 @@
if amod.commonProperties.Proprietary {
fmt.Fprintln(w, "LOCAL_PROPRIETARY_MODULE := true")
}
+ if amod.commonProperties.Vendor {
+ fmt.Fprintln(w, "LOCAL_VENDOR_MODULE := true")
+ }
+ if amod.commonProperties.Owner != "" {
+ fmt.Fprintln(w, "LOCAL_MODULE_OWNER :=", amod.commonProperties.Owner)
+ }
}
if host {
diff --git a/android/api_levels.go b/android/api_levels.go
new file mode 100644
index 0000000..c77ced1
--- /dev/null
+++ b/android/api_levels.go
@@ -0,0 +1,63 @@
+// Copyright 2017 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"
+
+ "github.com/google/blueprint"
+)
+
+func init() {
+ RegisterSingletonType("api_levels", ApiLevelsSingleton)
+}
+
+func ApiLevelsSingleton() blueprint.Singleton {
+ return &apiLevelsSingleton{}
+}
+
+type apiLevelsSingleton struct{}
+
+func createApiLevelsJson(ctx blueprint.SingletonContext, file string,
+ apiLevelsMap map[string]int) {
+
+ jsonStr, err := json.Marshal(apiLevelsMap)
+ if err != nil {
+ ctx.Errorf(err.Error())
+ }
+
+ ctx.Build(pctx, blueprint.BuildParams{
+ Rule: WriteFile,
+ Outputs: []string{file},
+ Args: map[string]string{
+ "content": string(jsonStr[:]),
+ },
+ })
+}
+
+func GetApiLevelsJson(ctx PathContext) Path {
+ return PathForOutput(ctx, "api_levels.json")
+}
+
+func (a *apiLevelsSingleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
+ baseApiLevel := 9000
+ apiLevelsMap := map[string]int{}
+ for i, codename := range ctx.Config().(Config).PlatformVersionAllCodenames() {
+ apiLevelsMap[codename] = baseApiLevel + i
+ }
+
+ apiLevelsJson := GetApiLevelsJson(ctx)
+ createApiLevelsJson(ctx, apiLevelsJson.String(), apiLevelsMap)
+}
diff --git a/android/arch.go b/android/arch.go
index df50afa..f9697bc 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -217,7 +217,8 @@
type OsClass int
const (
- Device OsClass = iota
+ Generic OsClass = iota
+ Device
Host
HostCross
)
@@ -780,6 +781,10 @@
addTarget(BuildOs, *variables.HostSecondaryArch, nil, nil, nil)
}
+ if config.Host_bionic != nil && *config.Host_bionic {
+ addTarget(LinuxBionic, "x86_64", nil, nil, nil)
+ }
+
if variables.CrossHost != nil && *variables.CrossHost != "" {
crossHostOs := osByName(*variables.CrossHost)
if crossHostOs == NoOsType {
@@ -851,8 +856,10 @@
{"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"}},
+ {"arm", "armv7-a-neon", "kryo", []string{"armeabi-v7a"}},
{"arm64", "armv8-a", "cortex-a53", []string{"arm64-v8a"}},
{"arm64", "armv8-a", "denver64", []string{"arm64-v8a"}},
+ {"arm64", "armv8-a", "kryo", []string{"arm64-v8a"}},
{"mips", "mips32-fp", "", []string{"mips"}},
{"mips", "mips32r2-fp", "", []string{"mips"}},
{"mips", "mips32r2-fp-xburst", "", []string{"mips"}},
diff --git a/android/config.go b/android/config.go
index 3603477..3c25485 100644
--- a/android/config.go
+++ b/android/config.go
@@ -40,6 +40,7 @@
type FileConfigurableOptions struct {
Mega_device *bool `json:",omitempty"`
Ndk_abis *bool `json:",omitempty"`
+ Host_bionic *bool `json:",omitempty"`
}
func (f *FileConfigurableOptions) SetDefaultConfig() {
@@ -177,7 +178,6 @@
srcDir: srcDir,
buildDir: buildDir,
- envDeps: make(map[string]string),
deviceConfig: &deviceConfig{},
}
@@ -281,6 +281,10 @@
var val string
var exists bool
c.envLock.Lock()
+ defer c.envLock.Unlock()
+ if c.envDeps == nil {
+ c.envDeps = make(map[string]string)
+ }
if val, exists = c.envDeps[key]; !exists {
if c.envFrozen {
panic("Cannot access new environment variables after envdeps are frozen")
@@ -288,7 +292,6 @@
val = os.Getenv(key)
c.envDeps[key] = val
}
- c.envLock.Unlock()
return val
}
@@ -312,8 +315,8 @@
func (c *config) EnvDeps() map[string]string {
c.envLock.Lock()
+ defer c.envLock.Unlock()
c.envFrozen = true
- c.envLock.Unlock()
return c.envDeps
}
@@ -350,6 +353,10 @@
return strconv.Itoa(c.PlatformSdkVersionInt())
}
+func (c *config) PlatformVersionAllCodenames() []string {
+ return c.ProductVariables.Platform_version_all_codenames
+}
+
func (c *config) BuildNumber() string {
return "000000"
}
@@ -436,7 +443,7 @@
default:
return "0x70000000"
case Mips, Mips64:
- return "0x30000000"
+ return "0x64000000"
}
}
@@ -459,21 +466,38 @@
return "vendor"
}
-func (c *deviceConfig) VndkVersion() string {
+func (c *deviceConfig) CompileVndk() bool {
if c.config.ProductVariables.DeviceVndkVersion == nil {
- return ""
+ return false
}
- return *c.config.ProductVariables.DeviceVndkVersion
+ return *c.config.ProductVariables.DeviceVndkVersion == "current"
}
func (c *deviceConfig) BtConfigIncludeDir() string {
return String(c.config.ProductVariables.BtConfigIncludeDir)
}
-func (c *deviceConfig) BtHcilpIncluded() string {
- return String(c.config.ProductVariables.BtHcilpIncluded)
+func (c *deviceConfig) NativeCoverageEnabled() bool {
+ return Bool(c.config.ProductVariables.NativeCoverage)
}
-func (c *deviceConfig) BtHciUseMct() bool {
- return Bool(c.config.ProductVariables.BtHciUseMct)
+func (c *deviceConfig) CoverageEnabledForPath(path string) bool {
+ coverage := false
+ if c.config.ProductVariables.CoveragePaths != nil {
+ for _, prefix := range *c.config.ProductVariables.CoveragePaths {
+ if strings.HasPrefix(path, prefix) {
+ coverage = true
+ break
+ }
+ }
+ }
+ if coverage && c.config.ProductVariables.CoverageExcludePaths != nil {
+ for _, prefix := range *c.config.ProductVariables.CoverageExcludePaths {
+ if strings.HasPrefix(path, prefix) {
+ coverage = false
+ break
+ }
+ }
+ }
+ return coverage
}
diff --git a/android/defs.go b/android/defs.go
index d7e2a9f..6f46316 100644
--- a/android/defs.go
+++ b/android/defs.go
@@ -80,6 +80,13 @@
Description: "concatenate licenses $out",
})
+ WriteFile = pctx.AndroidStaticRule("WriteFile",
+ blueprint.RuleParams{
+ Command: "echo '$content' > $out",
+ Description: "writing file $out",
+ },
+ "content")
+
// Used only when USE_GOMA=true is set, to restrict non-goma jobs to the local parallelism value
localPool = blueprint.NewBuiltinPool("local_pool")
)
diff --git a/android/env.go b/android/env.go
index 3b523a2..c7409e8 100644
--- a/android/env.go
+++ b/android/env.go
@@ -27,10 +27,6 @@
// compare the contents of the environment variables, rewriting the file if necessary to cause
// a manifest regeneration.
-func init() {
- RegisterSingletonType("env", EnvSingleton)
-}
-
func EnvSingleton() blueprint.Singleton {
return &envSingleton{}
}
diff --git a/android/module.go b/android/module.go
index 7156e8c..963d611 100644
--- a/android/module.go
+++ b/android/module.go
@@ -57,8 +57,10 @@
Host() bool
Device() bool
Darwin() bool
+ Windows() bool
Debug() bool
PrimaryArch() bool
+ Vendor() bool
AConfig() Config
DeviceConfig() DeviceConfig
}
@@ -77,6 +79,7 @@
ModuleBuild(pctx blueprint.PackageContext, params ModuleBuildParams)
ExpandSources(srcFiles, excludes []string) Paths
+ ExpandSourcesSubDir(srcFiles, excludes []string, subDir string) Paths
Glob(globPattern string, excludes []string) Paths
InstallFile(installPath OutputPath, srcPath Path, deps ...Path) OutputPath
@@ -86,8 +89,10 @@
AddMissingDependencies(deps []string)
- Proprietary() bool
InstallInData() bool
+ InstallInSanitizerDir() bool
+
+ RequiredModuleNames() []string
}
type Module interface {
@@ -100,6 +105,7 @@
Enabled() bool
Target() Target
InstallInData() bool
+ InstallInSanitizerDir() bool
SkipInstall()
}
@@ -134,6 +140,12 @@
// whether this is a proprietary vendor module, and should be installed into /vendor
Proprietary bool
+ // vendor who owns this module
+ Owner string
+
+ // whether this module is device specific and should be installed into /vendor
+ Vendor bool
+
// *.logtags files, to combine together in order to generate the /system/etc/event-log-tags
// file
Logtags []string
@@ -218,17 +230,17 @@
return InitArchModule(m, propertyStructs...)
}
-// A AndroidModuleBase object contains the properties that are common to all Android
+// A ModuleBase 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.
+// The ModuleBase 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.
@@ -236,12 +248,12 @@
// For example:
//
// import (
-// "android/soong/common"
+// "android/soong/android"
// "github.com/google/blueprint"
// )
//
// type myModule struct {
-// common.AndroidModuleBase
+// android.ModuleBase
// properties struct {
// MyProperty string
// }
@@ -249,10 +261,10 @@
//
// func NewMyModule() (blueprint.Module, []interface{}) {
// m := &myModule{}
-// return common.InitAndroidModule(m, &m.properties)
+// return android.InitAndroidModule(m, &m.properties)
// }
//
-// func (m *myModule) GenerateAndroidBuildActions(ctx common.AndroidModuleContext) {
+// func (m *myModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
// // Get the CPU architecture for the current build variant.
// variantArch := ctx.Arch()
//
@@ -393,6 +405,10 @@
return false
}
+func (p *ModuleBase) InstallInSanitizerDir() bool {
+ return false
+}
+
func (a *ModuleBase) generateModuleTarget(ctx blueprint.ModuleContext) {
allInstalledFiles := Paths{}
allCheckbuildFiles := Paths{}
@@ -449,6 +465,7 @@
return androidBaseContextImpl{
target: a.commonProperties.CompileTarget,
targetPrimary: a.commonProperties.CompilePrimary,
+ vendor: a.commonProperties.Proprietary || a.commonProperties.Vendor,
config: ctx.Config().(Config),
}
}
@@ -485,6 +502,7 @@
target Target
targetPrimary bool
debug bool
+ vendor bool
config Config
}
@@ -597,6 +615,10 @@
return a.target.Os == Darwin
}
+func (a *androidBaseContextImpl) Windows() bool {
+ return a.target.Os == Windows
+}
+
func (a *androidBaseContextImpl) Debug() bool {
return a.debug
}
@@ -613,14 +635,18 @@
return DeviceConfig{a.config.deviceConfig}
}
-func (a *androidModuleContext) Proprietary() bool {
- return a.module.base().commonProperties.Proprietary
+func (a *androidBaseContextImpl) Vendor() bool {
+ return a.vendor
}
func (a *androidModuleContext) InstallInData() bool {
return a.module.InstallInData()
}
+func (a *androidModuleContext) InstallInSanitizerDir() bool {
+ return a.module.InstallInSanitizerDir()
+}
+
func (a *androidModuleContext) InstallFileName(installPath OutputPath, name string, srcPath Path,
deps ...Path) OutputPath {
@@ -628,7 +654,7 @@
a.module.base().hooks.runInstallHooks(a, fullInstallPath, false)
if !a.module.base().commonProperties.SkipInstall &&
- (a.Host() || !a.AConfig().SkipDeviceInstall()) {
+ (!a.Device() || !a.AConfig().SkipDeviceInstall()) {
deps = append(deps, a.installDeps...)
@@ -666,7 +692,7 @@
a.module.base().hooks.runInstallHooks(a, fullInstallPath, true)
if !a.module.base().commonProperties.SkipInstall &&
- (a.Host() || !a.AConfig().SkipDeviceInstall()) {
+ (!a.Device() || !a.AConfig().SkipDeviceInstall()) {
a.ModuleBuild(pctx, ModuleBuildParams{
Rule: Symlink,
@@ -742,9 +768,14 @@
}
// Returns a list of paths expanded from globs and modules referenced using ":module" syntax.
-// ExpandSourceDeps must have already been called during the dependency resolution phase.
+// ExtractSourcesDeps must have already been called during the dependency resolution phase.
func (ctx *androidModuleContext) ExpandSources(srcFiles, excludes []string) Paths {
+ return ctx.ExpandSourcesSubDir(srcFiles, excludes, "")
+}
+
+func (ctx *androidModuleContext) ExpandSourcesSubDir(srcFiles, excludes []string, subDir string) Paths {
prefix := PathForModuleSrc(ctx).String()
+
for i, e := range excludes {
j := findStringInSlice(e, srcFiles)
if j != -1 {
@@ -754,23 +785,32 @@
excludes[i] = filepath.Join(prefix, e)
}
- globbedSrcFiles := make(Paths, 0, len(srcFiles))
+ expandedSrcFiles := make(Paths, 0, len(srcFiles))
for _, s := range srcFiles {
if m := SrcIsModule(s); m != "" {
module := ctx.GetDirectDepWithTag(m, SourceDepTag)
if srcProducer, ok := module.(SourceFileProducer); ok {
- globbedSrcFiles = append(globbedSrcFiles, srcProducer.Srcs()...)
+ expandedSrcFiles = append(expandedSrcFiles, srcProducer.Srcs()...)
} else {
ctx.ModuleErrorf("srcs dependency %q is not a source file producing module", m)
}
} else if pathtools.IsGlob(s) {
- globbedSrcFiles = append(globbedSrcFiles, ctx.Glob(filepath.Join(prefix, s), excludes)...)
+ globbedSrcFiles := ctx.Glob(filepath.Join(prefix, s), excludes)
+ expandedSrcFiles = append(expandedSrcFiles, globbedSrcFiles...)
+ for i, s := range expandedSrcFiles {
+ expandedSrcFiles[i] = s.(ModuleSrcPath).WithSubDir(ctx, subDir)
+ }
} else {
- globbedSrcFiles = append(globbedSrcFiles, PathForModuleSrc(ctx, s))
+ s := PathForModuleSrc(ctx, s).WithSubDir(ctx, subDir)
+ expandedSrcFiles = append(expandedSrcFiles, s)
}
}
- return globbedSrcFiles
+ return expandedSrcFiles
+}
+
+func (ctx *androidModuleContext) RequiredModuleNames() []string {
+ return ctx.module.base().commonProperties.Required
}
func (ctx *androidModuleContext) Glob(globPattern string, excludes []string) Paths {
diff --git a/android/mutator.go b/android/mutator.go
index 3420280..940b0ff 100644
--- a/android/mutator.go
+++ b/android/mutator.go
@@ -14,7 +14,11 @@
package android
-import "github.com/google/blueprint"
+import (
+ "sync"
+
+ "github.com/google/blueprint"
+)
// Mutator phases:
// Pre-arch
@@ -23,36 +27,68 @@
// Deps
// PostDeps
-func registerMutators() {
- ctx := registerMutatorsContext{}
+var registerMutatorsOnce sync.Once
+var registeredMutators []*mutator
- register := func(funcs []RegisterMutatorFunc) {
- for _, f := range funcs {
- f(ctx)
+func registerMutatorsToContext(ctx *blueprint.Context, mutators []*mutator) {
+ for _, t := range mutators {
+ var handle blueprint.MutatorHandle
+ if t.bottomUpMutator != nil {
+ handle = ctx.RegisterBottomUpMutator(t.name, t.bottomUpMutator)
+ } else if t.topDownMutator != nil {
+ handle = ctx.RegisterTopDownMutator(t.name, t.topDownMutator)
+ }
+ if t.parallel {
+ handle.Parallel()
}
}
-
- ctx.TopDown("load_hooks", loadHookMutator).Parallel()
- ctx.BottomUp("prebuilts", prebuiltMutator).Parallel()
- ctx.BottomUp("defaults_deps", defaultsDepsMutator).Parallel()
- ctx.TopDown("defaults", defaultsMutator).Parallel()
-
- register(preArch)
-
- ctx.BottomUp("arch", archMutator).Parallel()
- ctx.TopDown("arch_hooks", archHookMutator).Parallel()
-
- register(preDeps)
-
- ctx.BottomUp("deps", depsMutator).Parallel()
-
- ctx.TopDown("prebuilt_select", PrebuiltSelectModuleMutator).Parallel()
- ctx.BottomUp("prebuilt_replace", PrebuiltReplaceMutator).Parallel()
-
- register(postDeps)
}
-type registerMutatorsContext struct{}
+func registerMutators(ctx *blueprint.Context) {
+
+ registerMutatorsOnce.Do(func() {
+ ctx := ®isterMutatorsContext{}
+
+ register := func(funcs []RegisterMutatorFunc) {
+ for _, f := range funcs {
+ f(ctx)
+ }
+ }
+
+ ctx.TopDown("load_hooks", loadHookMutator).Parallel()
+ ctx.BottomUp("prebuilts", prebuiltMutator).Parallel()
+ ctx.BottomUp("defaults_deps", defaultsDepsMutator).Parallel()
+ ctx.TopDown("defaults", defaultsMutator).Parallel()
+
+ register(preArch)
+
+ ctx.BottomUp("arch", archMutator).Parallel()
+ ctx.TopDown("arch_hooks", archHookMutator).Parallel()
+
+ register(preDeps)
+
+ ctx.BottomUp("deps", depsMutator).Parallel()
+
+ ctx.TopDown("prebuilt_select", PrebuiltSelectModuleMutator).Parallel()
+ ctx.BottomUp("prebuilt_replace", PrebuiltReplaceMutator).Parallel()
+
+ register(postDeps)
+
+ registeredMutators = ctx.mutators
+ })
+
+ registerMutatorsToContext(ctx, registeredMutators)
+}
+
+func RegisterTestMutators(ctx *blueprint.Context) {
+ mutators := registerMutatorsContext{}
+ mutators.BottomUp("deps", depsMutator).Parallel()
+ registerMutatorsToContext(ctx, mutators.mutators)
+}
+
+type registerMutatorsContext struct {
+ mutators []*mutator
+}
type RegisterMutatorsContext interface {
TopDown(name string, m AndroidTopDownMutator) MutatorHandle
@@ -99,7 +135,7 @@
androidBaseContextImpl
}
-func (registerMutatorsContext) BottomUp(name string, m AndroidBottomUpMutator) MutatorHandle {
+func (x *registerMutatorsContext) BottomUp(name string, m AndroidBottomUpMutator) MutatorHandle {
f := func(ctx blueprint.BottomUpMutatorContext) {
if a, ok := ctx.Module().(Module); ok {
actx := &androidBottomUpMutatorContext{
@@ -110,11 +146,11 @@
}
}
mutator := &mutator{name: name, bottomUpMutator: f}
- mutators = append(mutators, mutator)
+ x.mutators = append(x.mutators, mutator)
return mutator
}
-func (registerMutatorsContext) TopDown(name string, m AndroidTopDownMutator) MutatorHandle {
+func (x *registerMutatorsContext) TopDown(name string, m AndroidTopDownMutator) MutatorHandle {
f := func(ctx blueprint.TopDownMutatorContext) {
if a, ok := ctx.Module().(Module); ok {
actx := &androidTopDownMutatorContext{
@@ -125,7 +161,7 @@
}
}
mutator := &mutator{name: name, topDownMutator: f}
- mutators = append(mutators, mutator)
+ x.mutators = append(x.mutators, mutator)
return mutator
}
diff --git a/android/package_ctx.go b/android/package_ctx.go
index ee826c8..d9bb109 100644
--- a/android/package_ctx.go
+++ b/android/package_ctx.go
@@ -18,6 +18,7 @@
"fmt"
"github.com/google/blueprint"
+ "github.com/google/blueprint/pathtools"
)
// AndroidPackageContext is a wrapper for blueprint.PackageContext that adds
@@ -55,6 +56,10 @@
e.pctx.AddNinjaFileDeps(deps...)
}
+func (e *configErrorWrapper) Fs() pathtools.FileSystem {
+ return nil
+}
+
// 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
diff --git a/android/paths.go b/android/paths.go
index e76e1fe..969c753 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -16,7 +16,6 @@
import (
"fmt"
- "os"
"path/filepath"
"reflect"
"strings"
@@ -28,6 +27,7 @@
// PathContext is the subset of a (Module|Singleton)Context required by the
// Path methods.
type PathContext interface {
+ Fs() pathtools.FileSystem
Config() interface{}
AddNinjaFileDeps(deps ...string)
}
@@ -86,6 +86,11 @@
// Base returns the last element of the path
Base() string
+
+ // Rel returns the portion of the path relative to the directory it was created from. For
+ // example, Rel on a PathsForModuleSrc would return the path relative to the module source
+ // directory.
+ Rel() string
}
// WritablePath is a type of path that can be used as an output for build rules.
@@ -283,6 +288,7 @@
type basePath struct {
path string
config Config
+ rel string
}
func (p basePath) Ext() string {
@@ -293,6 +299,13 @@
return filepath.Base(p.path)
}
+func (p basePath) Rel() string {
+ if p.rel != "" {
+ return p.rel
+ }
+ return p.path
+}
+
// SourcePath is a Path representing a file path rooted from SrcDir
type SourcePath struct {
basePath
@@ -304,7 +317,7 @@
// 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)}}
+ ret := SourcePath{basePath{p, pathConfig(ctx), ""}}
abs, err := filepath.Abs(ret.String())
if err != nil {
@@ -330,7 +343,7 @@
// 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)}}
+ ret := SourcePath{basePath{p, pathConfig(ctx), ""}}
abs, err := filepath.Abs(ret.String())
if err != nil {
@@ -347,12 +360,10 @@
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())
- }
+ if exists, _, err := ctx.Fs().Exists(ret.String()); err != nil {
+ reportPathError(ctx, "%s: %s", ret, err.Error())
+ } else if !exists {
+ reportPathError(ctx, "source path %s does not exist", ret)
}
return ret
}
@@ -367,7 +378,7 @@
}
p := validatePath(ctx, paths...)
- path := SourcePath{basePath{p, pathConfig(ctx)}}
+ path := SourcePath{basePath{p, pathConfig(ctx), ""}}
abs, err := filepath.Abs(path.String())
if err != nil {
@@ -404,7 +415,7 @@
} else {
// We cannot add build statements in this context, so we fall back to
// AddNinjaFileDeps
- files, dirs, err := pathtools.Glob(path.String())
+ files, dirs, err := pathtools.Glob(path.String(), nil)
if err != nil {
reportPathError(ctx, "glob: %s", err.Error())
return OptionalPath{}
@@ -437,7 +448,7 @@
func (p SourcePath) OverlayPath(ctx ModuleContext, path Path) OptionalPath {
var relDir string
if moduleSrcPath, ok := path.(ModuleSrcPath); ok {
- relDir = moduleSrcPath.sourcePath.path
+ relDir = moduleSrcPath.path
} else if srcPath, ok := path.(SourcePath); ok {
relDir = srcPath.path
} else {
@@ -478,7 +489,7 @@
// OutputPath, and report a ModuleError.
func PathForOutput(ctx PathContext, paths ...string) OutputPath {
path := validatePath(ctx, paths...)
- return OutputPath{basePath{path, pathConfig(ctx)}}
+ return OutputPath{basePath{path, pathConfig(ctx), ""}}
}
func (p OutputPath) writablePath() {}
@@ -507,9 +518,7 @@
// ModuleSrcPath is a Path representing a file rooted from a module's local source dir
type ModuleSrcPath struct {
- basePath
- sourcePath SourcePath
- moduleDir string
+ SourcePath
}
var _ Path = ModuleSrcPath{}
@@ -520,8 +529,10 @@
// 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()}
+ p := validatePath(ctx, paths...)
+ path := ModuleSrcPath{PathForSource(ctx, ctx.ModuleDir(), p)}
+ path.basePath.rel = p
+ return path
}
// OptionalPathForModuleSrc returns an OptionalPath. The OptionalPath contains a
@@ -533,16 +544,12 @@
return OptionalPathForPath(PathForModuleSrc(ctx, *p))
}
-func (p ModuleSrcPath) String() string {
- return p.sourcePath.String()
-}
-
func (p ModuleSrcPath) genPathWithExt(ctx ModuleContext, subdir, ext string) ModuleGenPath {
- return PathForModuleGen(ctx, subdir, p.moduleDir, pathtools.ReplaceExtension(p.path, ext))
+ return PathForModuleGen(ctx, subdir, 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))
+ return PathForModuleObj(ctx, subdir, pathtools.ReplaceExtension(p.path, ext))
}
func (p ModuleSrcPath) resPathWithName(ctx ModuleContext, name string) ModuleResPath {
@@ -550,6 +557,18 @@
return PathForModuleRes(ctx, p.path, name)
}
+func (p ModuleSrcPath) WithSubDir(ctx ModuleContext, subdir string) ModuleSrcPath {
+ subdir = PathForModuleSrc(ctx, subdir).String()
+ var err error
+ rel, err := filepath.Rel(subdir, p.path)
+ if err != nil {
+ ctx.ModuleErrorf("source file %q is not under path %q", p.path, subdir)
+ return p
+ }
+ p.rel = rel
+ return p
+}
+
// ModuleOutPath is a Path representing a module's output directory.
type ModuleOutPath struct {
OutputPath
@@ -630,10 +649,13 @@
var outPaths []string
if ctx.Device() {
partition := "system"
- if ctx.Proprietary() {
+ if ctx.Vendor() {
partition = ctx.DeviceConfig().VendorPath()
}
- if ctx.InstallInData() {
+
+ if ctx.InstallInSanitizerDir() {
+ partition = "data/asan/" + partition
+ } else if ctx.InstallInData() {
partition = "data"
}
outPaths = []string{"target", "product", ctx.AConfig().DeviceName(), partition}
diff --git a/android/prebuilt.go b/android/prebuilt.go
index 5f9b4b0..772df7a 100644
--- a/android/prebuilt.go
+++ b/android/prebuilt.go
@@ -19,7 +19,11 @@
// This file implements common functionality for handling modules that may exist as prebuilts,
// source, or both.
-var prebuiltDependencyTag blueprint.BaseDependencyTag
+type prebuiltDependencyTag struct {
+ blueprint.BaseDependencyTag
+}
+
+var prebuiltDepTag prebuiltDependencyTag
type Prebuilt struct {
Properties struct {
@@ -64,7 +68,7 @@
p := m.Prebuilt()
name := m.base().BaseModuleName()
if ctx.OtherModuleExists(name) {
- ctx.AddReverseDependency(ctx.Module(), prebuiltDependencyTag, name)
+ ctx.AddReverseDependency(ctx.Module(), prebuiltDepTag, name)
p.Properties.SourceExists = true
} else {
ctx.Rename(name)
@@ -72,12 +76,17 @@
}
}
-// PrebuiltSelectModuleMutator marks prebuilts that are overriding source modules, and disables
-// installing the source module.
+// PrebuiltSelectModuleMutator marks prebuilts that are used, either overriding source modules or
+// because the source module doesn't exist. It also disables installing overridden source modules.
func PrebuiltSelectModuleMutator(ctx TopDownMutatorContext) {
- if s, ok := ctx.Module().(Module); ok {
+ if m, ok := ctx.Module().(PrebuiltInterface); ok && m.Prebuilt() != nil {
+ p := m.Prebuilt()
+ if !p.Properties.SourceExists {
+ p.Properties.UsePrebuilt = p.usePrebuilt(ctx, nil)
+ }
+ } else if s, ok := ctx.Module().(Module); ok {
ctx.VisitDirectDeps(func(m blueprint.Module) {
- if ctx.OtherModuleDependencyTag(m) == prebuiltDependencyTag {
+ if ctx.OtherModuleDependencyTag(m) == prebuiltDepTag {
p := m.(PrebuiltInterface).Prebuilt()
if p.usePrebuilt(ctx, s) {
p.Properties.UsePrebuilt = true
@@ -117,5 +126,5 @@
return true
}
- return !source.Enabled()
+ return source == nil || !source.Enabled()
}
diff --git a/android/prebuilt_test.go b/android/prebuilt_test.go
index 311f821..d09518b 100644
--- a/android/prebuilt_test.go
+++ b/android/prebuilt_test.go
@@ -144,20 +144,33 @@
t.Fatalf("failed to find module foo")
}
+ var dependsOnSourceModule, dependsOnPrebuiltModule bool
+ ctx.VisitDirectDeps(foo, func(m blueprint.Module) {
+ if _, ok := m.(*sourceModule); ok {
+ dependsOnSourceModule = true
+ }
+ if p, ok := m.(*prebuiltModule); ok {
+ dependsOnPrebuiltModule = true
+ if !p.Prebuilt().Properties.UsePrebuilt {
+ t.Errorf("dependency on prebuilt module not marked used")
+ }
+ }
+ })
+
if test.prebuilt {
- if !foo.(*sourceModule).dependsOnPrebuiltModule {
+ if !dependsOnPrebuiltModule {
t.Errorf("doesn't depend on prebuilt module")
}
- if foo.(*sourceModule).dependsOnSourceModule {
+ if dependsOnSourceModule {
t.Errorf("depends on source module")
}
} else {
- if foo.(*sourceModule).dependsOnPrebuiltModule {
+ if dependsOnPrebuiltModule {
t.Errorf("depends on prebuilt module")
}
- if !foo.(*sourceModule).dependsOnSourceModule {
+ if !dependsOnSourceModule {
t.Errorf("doens't depend on source module")
}
}
@@ -209,14 +222,6 @@
}
func (s *sourceModule) GenerateAndroidBuildActions(ctx ModuleContext) {
- ctx.VisitDirectDeps(func(m blueprint.Module) {
- if _, ok := m.(*sourceModule); ok {
- s.dependsOnSourceModule = true
- }
- if _, ok := m.(*prebuiltModule); ok {
- s.dependsOnPrebuiltModule = true
- }
- })
}
func findModule(ctx *blueprint.Context, name string) blueprint.Module {
diff --git a/android/register.go b/android/register.go
index 0ad9d30..9396664 100644
--- a/android/register.go
+++ b/android/register.go
@@ -15,8 +15,6 @@
package android
import (
- "sync"
-
"github.com/google/blueprint"
)
@@ -51,8 +49,6 @@
singletons = append(singletons, singleton{name, factory})
}
-var registerMutatorsOnce sync.Once
-
func NewContext() *blueprint.Context {
ctx := blueprint.NewContext()
@@ -64,19 +60,9 @@
ctx.RegisterSingletonType(t.name, t.factory)
}
- registerMutatorsOnce.Do(registerMutators)
+ registerMutators(ctx)
- for _, t := range mutators {
- var handle blueprint.MutatorHandle
- if t.bottomUpMutator != nil {
- handle = ctx.RegisterBottomUpMutator(t.name, t.bottomUpMutator)
- } else if t.topDownMutator != nil {
- handle = ctx.RegisterTopDownMutator(t.name, t.topDownMutator)
- }
- if t.parallel {
- handle.Parallel()
- }
- }
+ ctx.RegisterSingletonType("env", EnvSingleton)
return ctx
}
diff --git a/android/variable.go b/android/variable.go
index bb84be2..b0ab2d0 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -54,14 +54,6 @@
Cflags []string `android:"arch_variant"`
} `android:"arch_variant"`
- Cpusets struct {
- Cflags []string
- }
-
- Schedboost struct {
- Cflags []string
- }
-
Binder32bit struct {
Cflags []string
}
@@ -90,7 +82,8 @@
// Suffix to add to generated Makefiles
Make_suffix *string `json:",omitempty"`
- Platform_sdk_version *int `json:",omitempty"`
+ Platform_sdk_version *int `json:",omitempty"`
+ Platform_version_all_codenames []string `json:",omitempty"`
DeviceName *string `json:",omitempty"`
DeviceArch *string `json:",omitempty"`
@@ -118,8 +111,6 @@
Malloc_not_svelte *bool `json:",omitempty"`
Safestack *bool `json:",omitempty"`
HostStaticBinaries *bool `json:",omitempty"`
- Cpusets *bool `json:",omitempty"`
- Schedboost *bool `json:",omitempty"`
Binder32bit *bool `json:",omitempty"`
UseGoma *bool `json:",omitempty"`
Debuggable *bool `json:",omitempty"`
@@ -131,6 +122,10 @@
ClangTidy *bool `json:",omitempty"`
TidyChecks *string `json:",omitempty"`
+ NativeCoverage *bool `json:",omitempty"`
+ CoveragePaths *[]string `json:",omitempty"`
+ CoverageExcludePaths *[]string `json:",omitempty"`
+
DevicePrefer32BitExecutables *bool `json:",omitempty"`
HostPrefer32BitExecutables *bool `json:",omitempty"`
@@ -141,8 +136,6 @@
ArtUseReadBarrier *bool `json:",omitempty"`
BtConfigIncludeDir *string `json:",omitempty"`
- BtHcilpIncluded *string `json:",omitempty"`
- BtHciUseMct *bool `json:",omitempty"`
}
func boolPtr(v bool) *bool {
diff --git a/androidmk/cmd/androidmk/android.go b/androidmk/cmd/androidmk/android.go
index 72ececb..bd9d8ee 100644
--- a/androidmk/cmd/androidmk/android.go
+++ b/androidmk/cmd/androidmk/android.go
@@ -12,87 +12,120 @@
clear_vars = "__android_mk_clear_vars"
)
-var standardProperties = map[string]struct {
- string
- bpparser.Type
-}{
- // String properties
- "LOCAL_MODULE": {"name", bpparser.StringType},
- "LOCAL_MODULE_CLASS": {"class", bpparser.StringType},
- "LOCAL_CXX_STL": {"stl", bpparser.StringType},
- "LOCAL_STRIP_MODULE": {"strip", bpparser.StringType},
- "LOCAL_MULTILIB": {"compile_multilib", bpparser.StringType},
- "LOCAL_ARM_MODE_HACK": {"instruction_set", bpparser.StringType},
- "LOCAL_SDK_VERSION": {"sdk_version", bpparser.StringType},
- "LOCAL_NDK_STL_VARIANT": {"stl", bpparser.StringType},
- "LOCAL_JAR_MANIFEST": {"manifest", bpparser.StringType},
- "LOCAL_JARJAR_RULES": {"jarjar_rules", bpparser.StringType},
- "LOCAL_CERTIFICATE": {"certificate", bpparser.StringType},
- "LOCAL_PACKAGE_NAME": {"name", bpparser.StringType},
- "LOCAL_MODULE_RELATIVE_PATH": {"relative_install_path", bpparser.StringType},
- "LOCAL_PROTOC_OPTIMIZE_TYPE": {"proto.type", bpparser.StringType},
-
- // List properties
- "LOCAL_SRC_FILES_EXCLUDE": {"exclude_srcs", bpparser.ListType},
- "LOCAL_SHARED_LIBRARIES": {"shared_libs", bpparser.ListType},
- "LOCAL_STATIC_LIBRARIES": {"static_libs", bpparser.ListType},
- "LOCAL_WHOLE_STATIC_LIBRARIES": {"whole_static_libs", bpparser.ListType},
- "LOCAL_SYSTEM_SHARED_LIBRARIES": {"system_shared_libs", bpparser.ListType},
- "LOCAL_ASFLAGS": {"asflags", bpparser.ListType},
- "LOCAL_CLANG_ASFLAGS": {"clang_asflags", bpparser.ListType},
- "LOCAL_CFLAGS": {"cflags", bpparser.ListType},
- "LOCAL_CONLYFLAGS": {"conlyflags", bpparser.ListType},
- "LOCAL_CPPFLAGS": {"cppflags", bpparser.ListType},
- "LOCAL_REQUIRED_MODULES": {"required", bpparser.ListType},
- "LOCAL_MODULE_TAGS": {"tags", bpparser.ListType},
- "LOCAL_LDLIBS": {"host_ldlibs", bpparser.ListType},
- "LOCAL_CLANG_CFLAGS": {"clang_cflags", bpparser.ListType},
- "LOCAL_YACCFLAGS": {"yaccflags", bpparser.ListType},
- "LOCAL_SANITIZE_RECOVER": {"sanitize.recover", bpparser.ListType},
- "LOCAL_LOGTAGS_FILES": {"logtags", bpparser.ListType},
- "LOCAL_EXPORT_SHARED_LIBRARY_HEADERS": {"export_shared_lib_headers", bpparser.ListType},
- "LOCAL_EXPORT_STATIC_LIBRARY_HEADERS": {"export_static_lib_headers", bpparser.ListType},
- "LOCAL_INIT_RC": {"init_rc", bpparser.ListType},
- "LOCAL_TIDY_FLAGS": {"tidy_flags", bpparser.ListType},
- // TODO: This is comma-seperated, not space-separated
- "LOCAL_TIDY_CHECKS": {"tidy_checks", bpparser.ListType},
-
- "LOCAL_JAVA_RESOURCE_DIRS": {"java_resource_dirs", bpparser.ListType},
- "LOCAL_JAVACFLAGS": {"javacflags", bpparser.ListType},
- "LOCAL_DX_FLAGS": {"dxflags", bpparser.ListType},
- "LOCAL_JAVA_LIBRARIES": {"java_libs", bpparser.ListType},
- "LOCAL_STATIC_JAVA_LIBRARIES": {"java_static_libs", bpparser.ListType},
- "LOCAL_AIDL_INCLUDES": {"aidl_includes", bpparser.ListType},
- "LOCAL_AAPT_FLAGS": {"aaptflags", bpparser.ListType},
- "LOCAL_PACKAGE_SPLITS": {"package_splits", bpparser.ListType},
-
- // Bool properties
- "LOCAL_IS_HOST_MODULE": {"host", bpparser.BoolType},
- "LOCAL_CLANG": {"clang", bpparser.BoolType},
- "LOCAL_FORCE_STATIC_EXECUTABLE": {"static_executable", bpparser.BoolType},
- "LOCAL_NATIVE_COVERAGE": {"native_coverage", bpparser.BoolType},
- "LOCAL_NO_CRT": {"nocrt", bpparser.BoolType},
- "LOCAL_ALLOW_UNDEFINED_SYMBOLS": {"allow_undefined_symbols", bpparser.BoolType},
- "LOCAL_RTTI_FLAG": {"rtti", bpparser.BoolType},
- "LOCAL_NO_STANDARD_LIBRARIES": {"no_standard_libraries", bpparser.BoolType},
- "LOCAL_PACK_MODULE_RELOCATIONS": {"pack_relocations", bpparser.BoolType},
- "LOCAL_TIDY": {"tidy", bpparser.BoolType},
- "LOCAL_USE_VNDK": {"use_vndk", bpparser.BoolType},
- "LOCAL_PROPRIETARY_MODULE": {"proprietary", bpparser.BoolType},
-
- "LOCAL_EXPORT_PACKAGE_RESOURCES": {"export_package_resources", bpparser.BoolType},
+type bpVariable struct {
+ name string
+ variableType bpparser.Type
}
-var rewriteProperties = map[string]struct {
- f func(file *bpFile, prefix string, value *mkparser.MakeString, append bool) error
-}{
- "LOCAL_C_INCLUDES": {localIncludeDirs},
- "LOCAL_EXPORT_C_INCLUDE_DIRS": {exportIncludeDirs},
- "LOCAL_MODULE_STEM": {stem},
- "LOCAL_MODULE_HOST_OS": {hostOs},
- "LOCAL_SRC_FILES": {srcFiles},
- "LOCAL_SANITIZE": {sanitize},
- "LOCAL_LDFLAGS": {ldflags},
+type variableAssignmentContext struct {
+ file *bpFile
+ prefix string
+ mkvalue *mkparser.MakeString
+ append bool
+}
+
+var rewriteProperties = map[string](func(variableAssignmentContext) error){
+ // custom functions
+ "LOCAL_C_INCLUDES": localIncludeDirs,
+ "LOCAL_EXPORT_C_INCLUDE_DIRS": exportIncludeDirs,
+ "LOCAL_LDFLAGS": ldflags,
+ "LOCAL_MODULE_CLASS": prebuiltClass,
+ "LOCAL_MODULE_STEM": stem,
+ "LOCAL_MODULE_HOST_OS": hostOs,
+ "LOCAL_SRC_FILES": srcFiles,
+ "LOCAL_SANITIZE": sanitize,
+
+ // composite functions
+ "LOCAL_MODULE_TAGS": includeVariableIf(bpVariable{"tags", bpparser.ListType}, not(valueDumpEquals("optional"))),
+
+ // skip functions
+ "LOCAL_ADDITIONAL_DEPENDENCIES": skip, // TODO: check for only .mk files?
+ "LOCAL_CPP_EXTENSION": skip,
+ "LOCAL_MODULE_SUFFIX": skip, // TODO
+ "LOCAL_PATH": skip, // Nothing to do, except maybe avoid the "./" in paths?
+ "LOCAL_PRELINK_MODULE": skip, // Already phased out
+}
+
+// adds a group of properties all having the same type
+func addStandardProperties(propertyType bpparser.Type, properties map[string]string) {
+ for key, val := range properties {
+ rewriteProperties[key] = includeVariable(bpVariable{val, propertyType})
+ }
+}
+
+func init() {
+ addStandardProperties(bpparser.StringType,
+ map[string]string{
+ "LOCAL_MODULE": "name",
+ "LOCAL_CXX_STL": "stl",
+ "LOCAL_STRIP_MODULE": "strip",
+ "LOCAL_MULTILIB": "compile_multilib",
+ "LOCAL_ARM_MODE_HACK": "instruction_set",
+ "LOCAL_SDK_VERSION": "sdk_version",
+ "LOCAL_NDK_STL_VARIANT": "stl",
+ "LOCAL_JAR_MANIFEST": "manifest",
+ "LOCAL_JARJAR_RULES": "jarjar_rules",
+ "LOCAL_CERTIFICATE": "certificate",
+ "LOCAL_PACKAGE_NAME": "name",
+ "LOCAL_MODULE_RELATIVE_PATH": "relative_install_path",
+ "LOCAL_PROTOC_OPTIMIZE_TYPE": "proto.type",
+ "LOCAL_MODULE_OWNER": "owner",
+ })
+ addStandardProperties(bpparser.ListType,
+ map[string]string{
+ "LOCAL_SRC_FILES_EXCLUDE": "exclude_srcs",
+ "LOCAL_HEADER_LIBRARIES": "header_libs",
+ "LOCAL_SHARED_LIBRARIES": "shared_libs",
+ "LOCAL_STATIC_LIBRARIES": "static_libs",
+ "LOCAL_WHOLE_STATIC_LIBRARIES": "whole_static_libs",
+ "LOCAL_SYSTEM_SHARED_LIBRARIES": "system_shared_libs",
+ "LOCAL_ASFLAGS": "asflags",
+ "LOCAL_CLANG_ASFLAGS": "clang_asflags",
+ "LOCAL_CFLAGS": "cflags",
+ "LOCAL_CONLYFLAGS": "conlyflags",
+ "LOCAL_CPPFLAGS": "cppflags",
+ "LOCAL_REQUIRED_MODULES": "required",
+ "LOCAL_LDLIBS": "host_ldlibs",
+ "LOCAL_CLANG_CFLAGS": "clang_cflags",
+ "LOCAL_YACCFLAGS": "yaccflags",
+ "LOCAL_SANITIZE_RECOVER": "sanitize.recover",
+ "LOCAL_LOGTAGS_FILES": "logtags",
+ "LOCAL_EXPORT_HEADER_LIBRARY_HEADERS": "export_header_lib_headers",
+ "LOCAL_EXPORT_SHARED_LIBRARY_HEADERS": "export_shared_lib_headers",
+ "LOCAL_EXPORT_STATIC_LIBRARY_HEADERS": "export_static_lib_headers",
+ "LOCAL_INIT_RC": "init_rc",
+ "LOCAL_TIDY_FLAGS": "tidy_flags",
+ // TODO: This is comma-separated, not space-separated
+ "LOCAL_TIDY_CHECKS": "tidy_checks",
+
+ "LOCAL_JAVA_RESOURCE_DIRS": "java_resource_dirs",
+ "LOCAL_JAVACFLAGS": "javacflags",
+ "LOCAL_DX_FLAGS": "dxflags",
+ "LOCAL_JAVA_LIBRARIES": "java_libs",
+ "LOCAL_STATIC_JAVA_LIBRARIES": "java_static_libs",
+ "LOCAL_AIDL_INCLUDES": "aidl_includes",
+ "LOCAL_AAPT_FLAGS": "aaptflags",
+ "LOCAL_PACKAGE_SPLITS": "package_splits",
+ "LOCAL_COMPATIBILITY_SUITE": "test_suites",
+ })
+ addStandardProperties(bpparser.BoolType,
+ map[string]string{
+ // Bool properties
+ "LOCAL_IS_HOST_MODULE": "host",
+ "LOCAL_CLANG": "clang",
+ "LOCAL_FORCE_STATIC_EXECUTABLE": "static_executable",
+ "LOCAL_NATIVE_COVERAGE": "native_coverage",
+ "LOCAL_NO_CRT": "nocrt",
+ "LOCAL_ALLOW_UNDEFINED_SYMBOLS": "allow_undefined_symbols",
+ "LOCAL_RTTI_FLAG": "rtti",
+ "LOCAL_NO_STANDARD_LIBRARIES": "no_standard_libraries",
+ "LOCAL_PACK_MODULE_RELOCATIONS": "pack_relocations",
+ "LOCAL_TIDY": "tidy",
+ "LOCAL_PROPRIETARY_MODULE": "proprietary",
+ "LOCAL_VENDOR_MODULE": "vendor",
+
+ "LOCAL_EXPORT_PACKAGE_RESOURCES": "export_package_resources",
+ })
}
type listSplitFunc func(bpparser.Expression) (string, bpparser.Expression, error)
@@ -219,8 +252,8 @@
}
}
-func localIncludeDirs(file *bpFile, prefix string, value *mkparser.MakeString, appendVariable bool) error {
- val, err := makeVariableToBlueprint(file, value, bpparser.ListType)
+func localIncludeDirs(ctx variableAssignmentContext) error {
+ val, err := makeVariableToBlueprint(ctx.file, ctx.mkvalue, bpparser.ListType)
if err != nil {
return err
}
@@ -231,14 +264,14 @@
}
if global, ok := lists["global"]; ok && !emptyList(global) {
- err = setVariable(file, appendVariable, prefix, "include_dirs", global, true)
+ err = setVariable(ctx.file, ctx.append, ctx.prefix, "include_dirs", global, true)
if err != nil {
return err
}
}
if local, ok := lists["local"]; ok && !emptyList(local) {
- err = setVariable(file, appendVariable, prefix, "local_include_dirs", local, true)
+ err = setVariable(ctx.file, ctx.append, ctx.prefix, "local_include_dirs", local, true)
if err != nil {
return err
}
@@ -247,8 +280,8 @@
return nil
}
-func exportIncludeDirs(file *bpFile, prefix string, value *mkparser.MakeString, appendVariable bool) error {
- val, err := makeVariableToBlueprint(file, value, bpparser.ListType)
+func exportIncludeDirs(ctx variableAssignmentContext) error {
+ val, err := makeVariableToBlueprint(ctx.file, ctx.mkvalue, bpparser.ListType)
if err != nil {
return err
}
@@ -259,17 +292,17 @@
}
if local, ok := lists["local"]; ok && !emptyList(local) {
- err = setVariable(file, appendVariable, prefix, "export_include_dirs", local, true)
+ err = setVariable(ctx.file, ctx.append, ctx.prefix, "export_include_dirs", local, true)
if err != nil {
return err
}
- appendVariable = true
+ ctx.append = true
}
// Add any paths that could not be converted to local relative paths to export_include_dirs
// anyways, they will cause an error if they don't exist and can be fixed manually.
if global, ok := lists["global"]; ok && !emptyList(global) {
- err = setVariable(file, appendVariable, prefix, "export_include_dirs", global, true)
+ err = setVariable(ctx.file, ctx.append, ctx.prefix, "export_include_dirs", global, true)
if err != nil {
return err
}
@@ -278,8 +311,8 @@
return nil
}
-func stem(file *bpFile, prefix string, value *mkparser.MakeString, appendVariable bool) error {
- val, err := makeVariableToBlueprint(file, value, bpparser.StringType)
+func stem(ctx variableAssignmentContext) error {
+ val, err := makeVariableToBlueprint(ctx.file, ctx.mkvalue, bpparser.StringType)
if err != nil {
return err
}
@@ -292,11 +325,11 @@
}
}
- return setVariable(file, appendVariable, prefix, varName, val, true)
+ return setVariable(ctx.file, ctx.append, ctx.prefix, varName, val, true)
}
-func hostOs(file *bpFile, prefix string, value *mkparser.MakeString, appendVariable bool) error {
- val, err := makeVariableToBlueprint(file, value, bpparser.ListType)
+func hostOs(ctx variableAssignmentContext) error {
+ val, err := makeVariableToBlueprint(ctx.file, ctx.mkvalue, bpparser.ListType)
if err != nil {
return err
}
@@ -319,15 +352,15 @@
}
if inList("windows") {
- err = setVariable(file, appendVariable, "target.windows", "enabled", trueValue, true)
+ err = setVariable(ctx.file, ctx.append, "target.windows", "enabled", trueValue, true)
}
if !inList("linux") && err == nil {
- err = setVariable(file, appendVariable, "target.linux", "enabled", falseValue, true)
+ err = setVariable(ctx.file, ctx.append, "target.linux", "enabled", falseValue, true)
}
if !inList("darwin") && err == nil {
- err = setVariable(file, appendVariable, "target.darwin", "enabled", falseValue, true)
+ err = setVariable(ctx.file, ctx.append, "target.darwin", "enabled", falseValue, true)
}
return err
@@ -352,8 +385,8 @@
}
-func srcFiles(file *bpFile, prefix string, value *mkparser.MakeString, appendVariable bool) error {
- val, err := makeVariableToBlueprint(file, value, bpparser.ListType)
+func srcFiles(ctx variableAssignmentContext) error {
+ val, err := makeVariableToBlueprint(ctx.file, ctx.mkvalue, bpparser.ListType)
if err != nil {
return err
}
@@ -361,14 +394,14 @@
lists, err := splitBpList(val, splitSrcsLogtags)
if srcs, ok := lists["srcs"]; ok && !emptyList(srcs) {
- err = setVariable(file, appendVariable, prefix, "srcs", srcs, true)
+ err = setVariable(ctx.file, ctx.append, ctx.prefix, "srcs", srcs, true)
if err != nil {
return err
}
}
if logtags, ok := lists["logtags"]; ok && !emptyList(logtags) {
- err = setVariable(file, true, prefix, "logtags", logtags, true)
+ err = setVariable(ctx.file, true, ctx.prefix, "logtags", logtags, true)
if err != nil {
return err
}
@@ -377,8 +410,8 @@
return nil
}
-func sanitize(file *bpFile, prefix string, mkvalue *mkparser.MakeString, appendVariable bool) error {
- val, err := makeVariableToBlueprint(file, mkvalue, bpparser.ListType)
+func sanitize(ctx variableAssignmentContext) error {
+ val, err := makeVariableToBlueprint(ctx.file, ctx.mkvalue, bpparser.ListType)
if err != nil {
return err
}
@@ -388,7 +421,7 @@
case *bpparser.Variable:
return "vars", value, nil
case *bpparser.Operator:
- file.errorf(mkvalue, "unknown sanitize expression")
+ ctx.file.errorf(ctx.mkvalue, "unknown sanitize expression")
return "unknown", value, nil
case *bpparser.String:
switch v.Value {
@@ -398,7 +431,7 @@
}
return v.Value, bpTrue, nil
default:
- file.errorf(mkvalue, "unknown sanitize argument: %s", v.Value)
+ ctx.file.errorf(ctx.mkvalue, "unknown sanitize argument: %s", v.Value)
return "unknown", value, nil
}
default:
@@ -416,13 +449,13 @@
switch k {
case "never", "address", "coverage", "integer", "thread", "undefined":
- err = setVariable(file, false, prefix, "sanitize."+k, lists[k].(*bpparser.List).Values[0], true)
+ err = setVariable(ctx.file, false, ctx.prefix, "sanitize."+k, lists[k].(*bpparser.List).Values[0], true)
case "unknown":
- // Nothing, we already added the error above
+ // Nothing, we already added the error above
case "vars":
fallthrough
default:
- err = setVariable(file, true, prefix, "sanitize", v, true)
+ err = setVariable(ctx.file, true, ctx.prefix, "sanitize", v, true)
}
if err != nil {
@@ -433,8 +466,19 @@
return err
}
-func ldflags(file *bpFile, prefix string, mkvalue *mkparser.MakeString, appendVariable bool) error {
- val, err := makeVariableToBlueprint(file, mkvalue, bpparser.ListType)
+func prebuiltClass(ctx variableAssignmentContext) error {
+ class := ctx.mkvalue.Value(nil)
+ if v, ok := prebuiltTypes[class]; ok {
+ ctx.file.scope.Set("BUILD_PREBUILT", v)
+ } else {
+ // reset to default
+ ctx.file.scope.Set("BUILD_PREBUILT", "prebuilt")
+ }
+ return nil
+}
+
+func ldflags(ctx variableAssignmentContext) error {
+ val, err := makeVariableToBlueprint(ctx.file, ctx.mkvalue, bpparser.ListType)
if err != nil {
return err
}
@@ -456,13 +500,13 @@
}
if v, ok := exp2.Args[1].(*bpparser.Variable); !ok || v.Name != "LOCAL_PATH" {
- file.errorf(mkvalue, "Unrecognized version-script")
+ ctx.file.errorf(ctx.mkvalue, "Unrecognized version-script")
return "ldflags", value, nil
}
s, ok := exp1.Args[1].(*bpparser.String)
if !ok {
- file.errorf(mkvalue, "Unrecognized version-script")
+ ctx.file.errorf(ctx.mkvalue, "Unrecognized version-script")
return "ldflags", value, nil
}
@@ -475,7 +519,7 @@
}
if ldflags, ok := lists["ldflags"]; ok && !emptyList(ldflags) {
- err = setVariable(file, appendVariable, prefix, "ldflags", ldflags, true)
+ err = setVariable(ctx.file, ctx.append, ctx.prefix, "ldflags", ldflags, true)
if err != nil {
return err
}
@@ -483,9 +527,9 @@
if version_script, ok := lists["version"]; ok && !emptyList(version_script) {
if len(version_script.(*bpparser.List).Values) > 1 {
- file.errorf(mkvalue, "multiple version scripts found?")
+ ctx.file.errorf(ctx.mkvalue, "multiple version scripts found?")
}
- err = setVariable(file, false, prefix, "version_script", version_script.(*bpparser.List).Values[0], true)
+ err = setVariable(ctx.file, false, ctx.prefix, "version_script", version_script.(*bpparser.List).Values[0], true)
if err != nil {
return err
}
@@ -494,8 +538,52 @@
return nil
}
-var deleteProperties = map[string]struct{}{
- "LOCAL_CPP_EXTENSION": struct{}{},
+// given a conditional, returns a function that will insert a variable assignment or not, based on the conditional
+func includeVariableIf(bpVar bpVariable, conditional func(ctx variableAssignmentContext) bool) func(ctx variableAssignmentContext) error {
+ return func(ctx variableAssignmentContext) error {
+ var err error
+ if conditional(ctx) {
+ err = includeVariableNow(bpVar, ctx)
+ }
+ return err
+ }
+}
+
+// given a variable, returns a function that will always insert a variable assignment
+func includeVariable(bpVar bpVariable) func(ctx variableAssignmentContext) error {
+ return includeVariableIf(bpVar, always)
+}
+
+func includeVariableNow(bpVar bpVariable, ctx variableAssignmentContext) error {
+ var val bpparser.Expression
+ var err error
+ val, err = makeVariableToBlueprint(ctx.file, ctx.mkvalue, bpVar.variableType)
+ if err == nil {
+ err = setVariable(ctx.file, ctx.append, ctx.prefix, bpVar.name, val, true)
+ }
+ return err
+}
+
+// given a function that returns a bool, returns a function that returns the opposite
+func not(conditional func(ctx variableAssignmentContext) bool) func(ctx variableAssignmentContext) bool {
+ return func(ctx variableAssignmentContext) bool {
+ return !conditional(ctx)
+ }
+}
+
+// returns a function that tells whether mkvalue.Dump equals the given query string
+func valueDumpEquals(textToMatch string) func(ctx variableAssignmentContext) bool {
+ return func(ctx variableAssignmentContext) bool {
+ return (ctx.mkvalue.Dump() == textToMatch)
+ }
+}
+
+func always(ctx variableAssignmentContext) bool {
+ return true
+}
+
+func skip(ctx variableAssignmentContext) error {
+ return nil
}
// Shorter suffixes of other suffixes must be at the end of the list
@@ -572,6 +660,7 @@
"BUILD_STATIC_LIBRARY": "cc_library_static",
"BUILD_HOST_SHARED_LIBRARY": "cc_library_host_shared",
"BUILD_HOST_STATIC_LIBRARY": "cc_library_host_static",
+ "BUILD_HEADER_LIBRARY": "cc_library_headers",
"BUILD_EXECUTABLE": "cc_binary",
"BUILD_HOST_EXECUTABLE": "cc_binary_host",
"BUILD_NATIVE_TEST": "cc_test",
@@ -584,8 +673,13 @@
"BUILD_HOST_JAVA_LIBRARY": "java_library_host",
"BUILD_HOST_DALVIK_JAVA_LIBRARY": "java_library_host_dalvik",
"BUILD_PACKAGE": "android_app",
+}
- "BUILD_PREBUILT": "prebuilt",
+var prebuiltTypes = map[string]string{
+ "SHARED_LIBRARIES": "cc_prebuilt_library_shared",
+ "STATIC_LIBRARIES": "cc_prebuilt_library_static",
+ "EXECUTABLES": "cc_prebuilt_binary",
+ "JAVA_LIBRARIES": "prebuilt_java_library",
}
var soongModuleTypes = map[string]bool{}
@@ -601,6 +695,9 @@
globalScope.Set(k, v)
soongModuleTypes[v] = true
}
+ for _, v := range prebuiltTypes {
+ soongModuleTypes[v] = true
+ }
return globalScope
}
diff --git a/androidmk/cmd/androidmk/androidmk.go b/androidmk/cmd/androidmk/androidmk.go
index 729e4f2..1d94b65 100644
--- a/androidmk/cmd/androidmk/androidmk.go
+++ b/androidmk/cmd/androidmk/androidmk.go
@@ -233,28 +233,16 @@
appendVariable := assignment.Type == "+="
var err error
- if prop, ok := standardProperties[name]; ok {
- var val bpparser.Expression
- val, err = makeVariableToBlueprint(file, assignment.Value, prop.Type)
- if err == nil {
- err = setVariable(file, appendVariable, prefix, prop.string, val, true)
- }
- } else if prop, ok := rewriteProperties[name]; ok {
- err = prop.f(file, prefix, assignment.Value, appendVariable)
- } else if _, ok := deleteProperties[name]; ok {
- return
+ if prop, ok := rewriteProperties[name]; ok {
+ err = prop(variableAssignmentContext{file, prefix, assignment.Value, appendVariable})
} else {
switch {
- case name == "LOCAL_PATH":
- // Nothing to do, except maybe avoid the "./" in paths?
case name == "LOCAL_ARM_MODE":
// This is a hack to get the LOCAL_ARM_MODE value inside
// of an arch: { arm: {} } block.
armModeAssign := assignment
armModeAssign.Name = mkparser.SimpleMakeString("LOCAL_ARM_MODE_HACK_arm", assignment.Name.Pos())
handleAssignment(file, armModeAssign, c)
- case name == "LOCAL_ADDITIONAL_DEPENDENCIES":
- // TODO: check for only .mk files?
case strings.HasPrefix(name, "LOCAL_"):
file.errorf(assignment, "unsupported assignment to %s", name)
return
diff --git a/androidmk/cmd/androidmk/androidmk_test.go b/androidmk/cmd/androidmk/androidmk_test.go
index 2f4daf7..0c44ea7 100644
--- a/androidmk/cmd/androidmk/androidmk_test.go
+++ b/androidmk/cmd/androidmk/androidmk_test.go
@@ -357,6 +357,34 @@
}
`,
},
+ {
+ desc: "Remove LOCAL_MODULE_TAGS optional",
+ in: `
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := optional
+include $(BUILD_SHARED_LIBRARY)
+`,
+
+ expected: `
+cc_library_shared {
+
+}
+`,
+ },
+ {
+ desc: "Keep LOCAL_MODULE_TAGS non-optional",
+ in: `
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := debug
+include $(BUILD_SHARED_LIBRARY)
+`,
+
+ expected: `
+cc_library_shared {
+ tags: ["debug"],
+}
+`,
+ },
}
func reformatBlueprint(input string) string {
diff --git a/build.ninja.in b/build.ninja.in
index a717672..9449d73 100644
--- a/build.ninja.in
+++ b/build.ninja.in
@@ -70,7 +70,6 @@
build $
${g.bootstrap.buildDir}/.bootstrap/blueprint/test/github.com/google/blueprint.a $
: g.bootstrap.compile ${g.bootstrap.srcDir}/build/blueprint/context.go $
- ${g.bootstrap.srcDir}/build/blueprint/fs.go $
${g.bootstrap.srcDir}/build/blueprint/glob.go $
${g.bootstrap.srcDir}/build/blueprint/live_tracker.go $
${g.bootstrap.srcDir}/build/blueprint/mangle.go $
@@ -139,7 +138,6 @@
build $
${g.bootstrap.buildDir}/.bootstrap/blueprint/pkg/github.com/google/blueprint.a $
: g.bootstrap.compile ${g.bootstrap.srcDir}/build/blueprint/context.go $
- ${g.bootstrap.srcDir}/build/blueprint/fs.go $
${g.bootstrap.srcDir}/build/blueprint/glob.go $
${g.bootstrap.srcDir}/build/blueprint/live_tracker.go $
${g.bootstrap.srcDir}/build/blueprint/mangle.go $
@@ -217,7 +215,7 @@
# Variant:
# Type: bootstrap_go_package
# Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1
-# Defined: build/blueprint/Blueprints:50:1
+# Defined: build/blueprint/Blueprints:49:1
build $
${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a $
@@ -233,7 +231,7 @@
# Variant:
# Type: bootstrap_go_package
# Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1
-# Defined: build/blueprint/Blueprints:34:1
+# Defined: build/blueprint/Blueprints:33:1
build $
${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/test/github.com/google/blueprint/parser.a $
@@ -300,12 +298,13 @@
# Variant:
# Type: bootstrap_go_package
# Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1
-# Defined: build/blueprint/Blueprints:56:1
+# Defined: build/blueprint/Blueprints:55:1
build $
${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/test/github.com/google/blueprint/pathtools.a $
: g.bootstrap.compile $
${g.bootstrap.srcDir}/build/blueprint/pathtools/lists.go $
+ ${g.bootstrap.srcDir}/build/blueprint/pathtools/fs.go $
${g.bootstrap.srcDir}/build/blueprint/pathtools/glob.go $
${g.bootstrap.srcDir}/build/blueprint/pathtools/glob_test.go | $
${g.bootstrap.compileCmd} $
@@ -351,6 +350,7 @@
${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a $
: g.bootstrap.compile $
${g.bootstrap.srcDir}/build/blueprint/pathtools/lists.go $
+ ${g.bootstrap.srcDir}/build/blueprint/pathtools/fs.go $
${g.bootstrap.srcDir}/build/blueprint/pathtools/glob.go | $
${g.bootstrap.compileCmd} $
${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a
diff --git a/cc/androidmk.go b/cc/androidmk.go
index c0be111..59a1db8 100644
--- a/cc/androidmk.go
+++ b/cc/androidmk.go
@@ -26,6 +26,7 @@
type AndroidMkContext interface {
Target() android.Target
subAndroidMk(*android.AndroidMkData, interface{})
+ vndk() bool
}
type subAndroidMkProvider interface {
@@ -56,16 +57,16 @@
if len(c.Properties.AndroidMkSharedLibs) > 0 {
fmt.Fprintln(w, "LOCAL_SHARED_LIBRARIES := "+strings.Join(c.Properties.AndroidMkSharedLibs, " "))
}
- if c.Target().Os == android.Android && c.Properties.Sdk_version != "" {
+ if c.Target().Os == android.Android && c.Properties.Sdk_version != "" && !c.vndk() {
fmt.Fprintln(w, "LOCAL_SDK_VERSION := "+c.Properties.Sdk_version)
fmt.Fprintln(w, "LOCAL_NDK_STL_VARIANT := none")
- } else if c.Target().Os == android.Android && c.Properties.Use_vndk {
- fmt.Fprintln(w, "LOCAL_USE_VNDK := true")
- fmt.Fprintln(w, "LOCAL_NDK_STL_VARIANT := none")
} else {
// These are already included in LOCAL_SHARED_LIBRARIES
fmt.Fprintln(w, "LOCAL_CXX_STL := none")
}
+ if c.vndk() {
+ fmt.Fprintln(w, "LOCAL_USE_VNDK := true")
+ }
return nil
})
@@ -77,50 +78,88 @@
c.subAndroidMk(&ret, c.linker)
c.subAndroidMk(&ret, c.installer)
+ if c.vndk() {
+ ret.SubName += ".vendor"
+ }
+
return ret, nil
}
-func (library *libraryDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
- if !library.static() {
- ctx.subAndroidMk(ret, &library.stripper)
- ctx.subAndroidMk(ret, &library.relocationPacker)
+func (library *libraryDecorator) androidMkWriteExportedFlags(w io.Writer) {
+ exportedFlags := library.exportedFlags()
+ if len(exportedFlags) > 0 {
+ fmt.Fprintln(w, "LOCAL_EXPORT_CFLAGS :=", strings.Join(exportedFlags, " "))
}
+ exportedFlagsDeps := library.exportedFlagsDeps()
+ if len(exportedFlagsDeps) > 0 {
+ fmt.Fprintln(w, "LOCAL_EXPORT_C_INCLUDE_DEPS :=", strings.Join(exportedFlagsDeps.Strings(), " "))
+ }
+}
+func (library *libraryDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
if library.static() {
ret.Class = "STATIC_LIBRARIES"
- } else {
+ } else if library.shared() {
+ ctx.subAndroidMk(ret, &library.stripper)
+ ctx.subAndroidMk(ret, &library.relocationPacker)
+
ret.Class = "SHARED_LIBRARIES"
+ } else if library.header() {
+ ret.Custom = func(w io.Writer, name, prefix, moduleDir string) error {
+ fmt.Fprintln(w, "\ninclude $(CLEAR_VARS)")
+ fmt.Fprintln(w, "LOCAL_PATH :=", moduleDir)
+ fmt.Fprintln(w, "LOCAL_MODULE :=", name)
+
+ archStr := ctx.Target().Arch.ArchType.String()
+ var host bool
+ switch ctx.Target().Os.Class {
+ case android.Host:
+ fmt.Fprintln(w, "LOCAL_MODULE_HOST_ARCH := ", archStr)
+ host = true
+ case android.HostCross:
+ fmt.Fprintln(w, "LOCAL_MODULE_HOST_CROSS_ARCH := ", archStr)
+ host = true
+ case android.Device:
+ fmt.Fprintln(w, "LOCAL_MODULE_TARGET_ARCH := ", archStr)
+ }
+
+ if host {
+ fmt.Fprintln(w, "LOCAL_MODULE_HOST_OS :=", ctx.Target().Os.String())
+ fmt.Fprintln(w, "LOCAL_IS_HOST_MODULE := true")
+ } else if ctx.vndk() {
+ fmt.Fprintln(w, "LOCAL_USE_VNDK := true")
+ }
+
+ library.androidMkWriteExportedFlags(w)
+ fmt.Fprintln(w, "include $(BUILD_HEADER_LIBRARY)")
+
+ return nil
+ }
+
+ return
}
ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) error {
- var exportedIncludes []string
- for _, flag := range library.exportedFlags() {
- if strings.HasPrefix(flag, "-I") {
- exportedIncludes = append(exportedIncludes, strings.TrimPrefix(flag, "-I"))
- }
- }
- if len(exportedIncludes) > 0 {
- fmt.Fprintln(w, "LOCAL_EXPORT_C_INCLUDE_DIRS :=", strings.Join(exportedIncludes, " "))
- }
- exportedIncludeDeps := library.exportedFlagsDeps()
- if len(exportedIncludeDeps) > 0 {
- fmt.Fprintln(w, "LOCAL_EXPORT_C_INCLUDE_DEPS :=", strings.Join(exportedIncludeDeps.Strings(), " "))
- }
+ library.androidMkWriteExportedFlags(w)
fmt.Fprintln(w, "LOCAL_BUILT_MODULE_STEM := $(LOCAL_MODULE)"+outputFile.Ext())
fmt.Fprintln(w, "LOCAL_SYSTEM_SHARED_LIBRARIES :=")
+ if library.coverageOutputFile.Valid() {
+ fmt.Fprintln(w, "LOCAL_PREBUILT_COVERAGE_ARCHIVE :=", library.coverageOutputFile.String())
+ }
+
return nil
})
- if !library.static() {
+ if library.shared() {
ctx.subAndroidMk(ret, library.baseInstaller)
}
}
func (object *objectLinker) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
- ret.Custom = func(w io.Writer, name, prefix string) error {
+ ret.Custom = func(w io.Writer, name, prefix, moduleDir string) error {
out := ret.OutputFile.Path()
fmt.Fprintln(w, "\n$("+prefix+"OUT_INTERMEDIATE_LIBRARIES)/"+name+objectExtension+":", out.String())
@@ -145,6 +184,9 @@
fmt.Fprintln(w, "LOCAL_MODULE_SYMLINKS := "+strings.Join(binary.symlinks, " "))
}
+ if binary.coverageOutputFile.Valid() {
+ fmt.Fprintln(w, "LOCAL_PREBUILT_COVERAGE_ARCHIVE :=", binary.coverageOutputFile.String())
+ }
return nil
})
}
@@ -159,6 +201,31 @@
if Bool(test.Properties.Test_per_src) {
ret.SubName = "_" + test.binaryDecorator.Properties.Stem
}
+
+ ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) error {
+ if len(test.Properties.Test_suites) > 0 {
+ fmt.Fprintln(w, "LOCAL_COMPATIBILITY_SUITE :=",
+ strings.Join(test.Properties.Test_suites, " "))
+ }
+ return nil
+ })
+
+ var testFiles []string
+ for _, d := range test.data {
+ rel := d.Rel()
+ path := d.String()
+ if !strings.HasSuffix(path, rel) {
+ panic(fmt.Errorf("path %q does not end with %q", path, rel))
+ }
+ path = strings.TrimSuffix(path, rel)
+ testFiles = append(testFiles, path+":"+rel)
+ }
+ if len(testFiles) > 0 {
+ ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) error {
+ fmt.Fprintln(w, "LOCAL_TEST_DATA := "+strings.Join(testFiles, " "))
+ return nil
+ })
+ }
}
func (test *testLibrary) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
@@ -222,7 +289,7 @@
}
func (c *stubDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
- ret.SubName = "." + c.properties.ApiLevel
+ ret.SubName = ndkLibrarySuffix + "." + c.properties.ApiLevel
ret.Class = "SHARED_LIBRARIES"
ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) error {
@@ -232,6 +299,7 @@
fmt.Fprintln(w, "LOCAL_MODULE_SUFFIX := "+outputFile.Ext())
fmt.Fprintln(w, "LOCAL_MODULE_PATH := "+path)
fmt.Fprintln(w, "LOCAL_MODULE_STEM := "+stem)
+ fmt.Fprintln(w, "LOCAL_NO_NOTICE_FILE := true")
// Prevent make from installing the libraries to obj/lib (since we have
// dozens of libraries with the same name, they'll clobber each other
@@ -240,3 +308,21 @@
return nil
})
}
+
+func (c *llndkStubDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+ ret.Class = "SHARED_LIBRARIES"
+ ret.SubName = ".vendor"
+
+ ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) error {
+ c.libraryDecorator.androidMkWriteExportedFlags(w)
+
+ fmt.Fprintln(w, "LOCAL_BUILT_MODULE_STEM := $(LOCAL_MODULE)"+outputFile.Ext())
+ fmt.Fprintln(w, "LOCAL_STRIP_MODULE := false")
+ fmt.Fprintln(w, "LOCAL_SYSTEM_SHARED_LIBRARIES :=")
+ fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := true")
+ fmt.Fprintln(w, "LOCAL_NO_NOTICE_FILE := true")
+ fmt.Fprintln(w, "LOCAL_USE_VNDK := true")
+
+ return nil
+ })
+}
diff --git a/cc/binary.go b/cc/binary.go
index afc8a99..b4610ed 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -15,6 +15,8 @@
package cc
import (
+ "path/filepath"
+
"github.com/google/blueprint"
"github.com/google/blueprint/proptools"
@@ -79,6 +81,9 @@
// Names of symlinks to be installed for use in LOCAL_MODULE_SYMLINKS
symlinks []string
+
+ // Output archive of gcno coverage information
+ coverageOutputFile android.OptionalPath
}
var _ linker = (*binaryDecorator)(nil)
@@ -103,7 +108,7 @@
deps = binary.baseLinker.linkerDeps(ctx, deps)
if ctx.toolchain().Bionic() {
if !Bool(binary.baseLinker.Properties.Nocrt) {
- if !ctx.sdk() && !ctx.vndk() {
+ if !ctx.sdk() {
if binary.static() {
deps.CrtBegin = "crtbegin_static"
} else {
@@ -199,7 +204,7 @@
if ctx.Host() && !binary.static() {
if !ctx.AConfig().IsEnvTrue("DISABLE_HOST_PIE") {
flags.LdFlags = append(flags.LdFlags, "-pie")
- if ctx.Os() == android.Windows {
+ if ctx.Windows() {
flags.LdFlags = append(flags.LdFlags, "-Wl,-e_mainCRTStartup")
}
}
@@ -208,7 +213,7 @@
// MinGW spits out warnings about -fPIC even for -fpie?!) being ignored because
// all code is position independent, and then those warnings get promoted to
// errors.
- if ctx.Os() != android.Windows {
+ if !ctx.Windows() {
flags.CFlags = append(flags.CFlags, "-fpie")
}
@@ -233,7 +238,22 @@
if binary.Properties.DynamicLinker != "" {
flags.DynamicLinker = binary.Properties.DynamicLinker
} else {
- flags.DynamicLinker = "/system/bin/linker"
+ switch ctx.Os() {
+ case android.Android:
+ flags.DynamicLinker = "/system/bin/linker"
+ case android.LinuxBionic:
+ // The linux kernel expects the linker to be an
+ // absolute path
+ path := android.PathForOutput(ctx,
+ "host", "linux_bionic-x86", "bin", "linker")
+ if p, err := filepath.Abs(path.String()); err == nil {
+ flags.DynamicLinker = p
+ } else {
+ ctx.ModuleErrorf("can't find path to dynamic linker: %q", err)
+ }
+ default:
+ ctx.ModuleErrorf("unknown dynamic linker")
+ }
if flags.Toolchain.Is64Bit() {
flags.DynamicLinker += "64"
}
@@ -299,6 +319,10 @@
deps.LateStaticLibs, deps.WholeStaticLibs, linkerDeps, deps.CrtBegin, deps.CrtEnd, true,
builderFlags, outputFile)
+ objs.coverageFiles = append(objs.coverageFiles, deps.StaticLibObjs.coverageFiles...)
+ objs.coverageFiles = append(objs.coverageFiles, deps.WholeStaticLibObjs.coverageFiles...)
+ binary.coverageOutputFile = TransformCoverageFilesToLib(ctx, objs, builderFlags, binary.getStem(ctx))
+
return ret
}
diff --git a/cc/builder.go b/cc/builder.go
index c9a6722..0694cb7 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -109,7 +109,7 @@
},
"objcopyCmd", "prefix")
- stripPath = pctx.SourcePathVariable("stripPath", "build/soong/scripts/strip.sh")
+ _ = pctx.SourcePathVariable("stripPath", "build/soong/scripts/strip.sh")
strip = pctx.AndroidStaticRule("strip",
blueprint.RuleParams{
@@ -127,7 +127,7 @@
Description: "empty file $out",
})
- copyGccLibPath = pctx.SourcePathVariable("copyGccLibPath", "build/soong/scripts/copygcclib.sh")
+ _ = pctx.SourcePathVariable("copyGccLibPath", "build/soong/scripts/copygcclib.sh")
copyGccLib = pctx.AndroidStaticRule("copyGccLib",
blueprint.RuleParams{
@@ -139,7 +139,7 @@
},
"ccCmd", "cFlags", "libName")
- tocPath = pctx.SourcePathVariable("tocPath", "build/soong/scripts/toc.sh")
+ _ = pctx.SourcePathVariable("tocPath", "build/soong/scripts/toc.sh")
toc = pctx.AndroidStaticRule("toc",
blueprint.RuleParams{
@@ -159,7 +159,7 @@
},
"cFlags", "tidyFlags")
- yasmCmd = pctx.SourcePathVariable("yasmCmd", "prebuilts/misc/${config.HostPrebuiltTag}/yasm/yasm")
+ _ = pctx.SourcePathVariable("yasmCmd", "prebuilts/misc/${config.HostPrebuiltTag}/yasm/yasm")
yasm = pctx.AndroidStaticRule("yasm",
blueprint.RuleParams{
@@ -184,6 +184,7 @@
type builderFlags struct {
globalFlags string
+ arFlags string
asFlags string
cFlags string
conlyFlags string
@@ -198,6 +199,9 @@
toolchain config.Toolchain
clang bool
tidy bool
+ coverage bool
+
+ systemIncludeFlags string
groupStaticLibs bool
@@ -207,21 +211,24 @@
}
type Objects struct {
- objFiles android.Paths
- tidyFiles android.Paths
+ objFiles android.Paths
+ tidyFiles android.Paths
+ coverageFiles android.Paths
}
func (a Objects) Copy() Objects {
return Objects{
- objFiles: append(android.Paths{}, a.objFiles...),
- tidyFiles: append(android.Paths{}, a.tidyFiles...),
+ objFiles: append(android.Paths{}, a.objFiles...),
+ tidyFiles: append(android.Paths{}, a.tidyFiles...),
+ coverageFiles: append(android.Paths{}, a.coverageFiles...),
}
}
func (a Objects) Append(b Objects) Objects {
return Objects{
- objFiles: append(a.objFiles, b.objFiles...),
- tidyFiles: append(a.tidyFiles, b.tidyFiles...),
+ objFiles: append(a.objFiles, b.objFiles...),
+ tidyFiles: append(a.tidyFiles, b.tidyFiles...),
+ coverageFiles: append(a.coverageFiles, b.coverageFiles...),
}
}
@@ -234,10 +241,30 @@
if flags.tidy && flags.clang {
tidyFiles = make(android.Paths, 0, len(srcFiles))
}
+ var coverageFiles android.Paths
+ if flags.coverage {
+ coverageFiles = make(android.Paths, 0, len(srcFiles))
+ }
- cflags := flags.globalFlags + " " + flags.cFlags + " " + flags.conlyFlags
- cppflags := flags.globalFlags + " " + flags.cFlags + " " + flags.cppFlags
- asflags := flags.globalFlags + " " + flags.asFlags
+ cflags := strings.Join([]string{
+ flags.globalFlags,
+ flags.systemIncludeFlags,
+ flags.cFlags,
+ flags.conlyFlags,
+ }, " ")
+
+ cppflags := strings.Join([]string{
+ flags.globalFlags,
+ flags.systemIncludeFlags,
+ flags.cFlags,
+ flags.cppFlags,
+ }, " ")
+
+ asflags := strings.Join([]string{
+ flags.globalFlags,
+ flags.systemIncludeFlags,
+ flags.asFlags,
+ }, " ")
if flags.clang {
cflags += " ${config.NoOverrideClangGlobalCflags}"
@@ -268,12 +295,14 @@
var moduleCflags string
var ccCmd string
tidy := flags.tidy && flags.clang
+ coverage := flags.coverage
switch srcFile.Ext() {
case ".S", ".s":
ccCmd = "gcc"
moduleCflags = asflags
tidy = false
+ coverage = false
case ".c":
ccCmd = "gcc"
moduleCflags = cflags
@@ -300,11 +329,19 @@
ccCmd = gccCmd(flags.toolchain, ccCmd)
}
+ var implicitOutputs android.WritablePaths
+ if coverage {
+ gcnoFile := android.ObjPathWithExt(ctx, subdir, srcFile, "gcno")
+ implicitOutputs = append(implicitOutputs, gcnoFile)
+ coverageFiles = append(coverageFiles, gcnoFile)
+ }
+
ctx.ModuleBuild(pctx, android.ModuleBuildParams{
- Rule: cc,
- Output: objFile,
- Input: srcFile,
- OrderOnly: deps,
+ Rule: cc,
+ Output: objFile,
+ ImplicitOutputs: implicitOutputs,
+ Input: srcFile,
+ OrderOnly: deps,
Args: map[string]string{
"cFlags": moduleCflags,
"ccCmd": ccCmd,
@@ -332,8 +369,9 @@
}
return Objects{
- objFiles: objFiles,
- tidyFiles: tidyFiles,
+ objFiles: objFiles,
+ tidyFiles: tidyFiles,
+ coverageFiles: coverageFiles,
}
}
@@ -341,8 +379,16 @@
func TransformObjToStaticLib(ctx android.ModuleContext, objFiles android.Paths,
flags builderFlags, outputFile android.ModuleOutPath, deps android.Paths) {
+ if ctx.Darwin() {
+ transformDarwinObjToStaticLib(ctx, objFiles, flags, outputFile, deps)
+ return
+ }
+
arCmd := gccCmd(flags.toolchain, "ar")
arFlags := "crsPD"
+ if flags.arFlags != "" {
+ arFlags += " " + flags.arFlags
+ }
ctx.ModuleBuild(pctx, android.ModuleBuildParams{
Rule: ar,
@@ -360,7 +406,7 @@
// darwin. The darwin ar tool doesn't support @file for list files, and has a
// very small command line length limit, so we have to split the ar into multiple
// steps, each appending to the previous one.
-func TransformDarwinObjToStaticLib(ctx android.ModuleContext, objFiles android.Paths,
+func transformDarwinObjToStaticLib(ctx android.ModuleContext, objFiles android.Paths,
flags builderFlags, outputPath android.ModuleOutPath, deps android.Paths) {
arFlags := "cqs"
@@ -599,6 +645,20 @@
})
}
+func TransformCoverageFilesToLib(ctx android.ModuleContext,
+ inputs Objects, flags builderFlags, baseName string) android.OptionalPath {
+
+ if len(inputs.coverageFiles) > 0 {
+ outputFile := android.PathForModuleOut(ctx, baseName+".gcnodir")
+
+ TransformObjToStaticLib(ctx, inputs.coverageFiles, flags, outputFile, nil)
+
+ return android.OptionalPathForPath(outputFile)
+ }
+
+ return android.OptionalPath{}
+}
+
func CopyGccLib(ctx android.ModuleContext, libName string,
flags builderFlags, outputFile android.WritablePath) {
diff --git a/cc/cc.go b/cc/cc.go
index 8bf1467..63caf3a 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -35,6 +35,7 @@
android.PreDepsMutators(func(ctx android.RegisterMutatorsContext) {
ctx.BottomUp("link", linkageMutator).Parallel()
+ ctx.BottomUp("image", vendorMutator).Parallel()
ctx.BottomUp("ndk_api", ndkApiMutator).Parallel()
ctx.BottomUp("test_per_src", testPerSrcMutator).Parallel()
ctx.BottomUp("begin", beginMutator).Parallel()
@@ -46,6 +47,8 @@
ctx.TopDown("tsan_deps", sanitizerDepsMutator(tsan))
ctx.BottomUp("tsan", sanitizerMutator(tsan)).Parallel()
+
+ ctx.BottomUp("coverage", coverageLinkingMutator).Parallel()
})
pctx.Import("android/soong/cc/config")
@@ -78,6 +81,7 @@
// Paths to .o files
Objs Objects
+ StaticLibObjs Objects
WholeStaticLibObjs Objects
// Paths to generated source files
@@ -93,6 +97,7 @@
type Flags struct {
GlobalFlags []string // Flags that apply to C, C++, and assembly source files
+ ArFlags []string // Flags that apply to ar
AsFlags []string // Flags that apply to assembly source files
CFlags []string // Flags that apply to C and C++ source files
ConlyFlags []string // Flags that apply to C source files
@@ -105,9 +110,14 @@
TidyFlags []string // Flags that apply to clang-tidy
YasmFlags []string // Flags that apply to yasm assembly source files
+ // Global include flags that apply to C, C++, and assembly source files
+ // These must be after any module include flags, which will be in GlobalFlags.
+ SystemIncludeFlags []string
+
Toolchain config.Toolchain
Clang bool
Tidy bool
+ Coverage bool
RequiredInstructionSet string
DynamicLinker string
@@ -130,22 +140,32 @@
// Minimum sdk version supported when compiling against the ndk
Sdk_version string
- // Whether to compile against the VNDK
- Use_vndk bool
-
// don't insert default compiler flags into asflags, cflags,
// cppflags, conlyflags, ldflags, or include_dirs
No_default_compiler_flags *bool
+ // whether this module should be allowed to install onto /vendor as
+ // well as /system. The two variants will be built separately, one
+ // like normal, and the other limited to the set of libraries and
+ // headers that are exposed to /vendor modules.
+ //
+ // The vendor variant may be used with a different (newer) /system,
+ // so it shouldn't have any unversioned runtime dependencies, or
+ // make assumptions about the system that may not be true in the
+ // future.
+ //
+ // Nothing happens if BOARD_VNDK_VERSION isn't set in the BoardConfig.mk
+ Vendor_available *bool
+
AndroidMkSharedLibs []string `blueprint:"mutated"`
HideFromMake bool `blueprint:"mutated"`
PreventInstall bool `blueprint:"mutated"`
- Vndk_version string `blueprint:"mutated"`
+
+ UseVndk bool `blueprint:"mutated"`
}
type UnusedProperties struct {
- Native_coverage *bool
- Tags []string
+ Tags []string
}
type ModuleContextIntf interface {
@@ -208,6 +228,7 @@
installerProps() []interface{}
install(ctx ModuleContext, path android.Path)
inData() bool
+ inSanitizerDir() bool
hostToolPath() android.OptionalPath
}
@@ -261,6 +282,7 @@
installer installer
stl *stl
sanitize *sanitize
+ coverage *coverage
androidMkSharedLibDeps []string
@@ -269,6 +291,9 @@
cachedToolchain config.Toolchain
subAndroidMkOnce map[subAndroidMkProvider]bool
+
+ // Flags used to compile this module
+ flags Flags
}
func (c *Module) Init() (blueprint.Module, []interface{}) {
@@ -288,6 +313,9 @@
if c.sanitize != nil {
props = append(props, c.sanitize.props()...)
}
+ if c.coverage != nil {
+ props = append(props, c.coverage.props()...)
+ }
for _, feature := range c.features {
props = append(props, feature.props()...)
}
@@ -308,6 +336,10 @@
return false
}
+func (c *Module) vndk() bool {
+ return c.Properties.UseVndk
+}
+
type baseModuleContext struct {
android.BaseContext
moduleContextImpl
@@ -323,6 +355,12 @@
moduleContextImpl
}
+// Vendor returns true for vendor modules so that they get installed onto the
+// correct partition
+func (ctx *moduleContext) Vendor() bool {
+ return ctx.ModuleContext.Vendor() || ctx.moduleContextImpl.mod.Properties.UseVndk
+}
+
type moduleContextImpl struct {
mod *Module
ctx BaseModuleContext
@@ -359,7 +397,7 @@
}
func (ctx *moduleContextImpl) sdk() bool {
- if ctx.ctx.Device() {
+ if ctx.ctx.Device() && !ctx.vndk() {
return ctx.mod.Properties.Sdk_version != ""
}
return false
@@ -367,8 +405,8 @@
func (ctx *moduleContextImpl) sdkVersion() string {
if ctx.ctx.Device() {
- if ctx.mod.Properties.Use_vndk {
- return ctx.mod.Properties.Vndk_version
+ if ctx.vndk() {
+ return "current"
} else {
return ctx.mod.Properties.Sdk_version
}
@@ -377,10 +415,7 @@
}
func (ctx *moduleContextImpl) vndk() bool {
- if ctx.ctx.Device() {
- return ctx.mod.Properties.Use_vndk
- }
- return false
+ return ctx.mod.vndk()
}
func (ctx *moduleContextImpl) selectedStl() string {
@@ -408,6 +443,7 @@
}
module.stl = &stl{}
module.sanitize = &sanitize{}
+ module.coverage = &coverage{}
return module
}
@@ -420,7 +456,9 @@
func (c *Module) Name() string {
name := c.ModuleBase.Name()
- if p, ok := c.linker.(prebuiltLinkerInterface); ok {
+ if p, ok := c.linker.(interface {
+ Name(string) string
+ }); ok {
name = p.Name(name)
}
return name
@@ -451,6 +489,9 @@
if c.sanitize != nil {
flags = c.sanitize.flags(ctx, flags)
}
+ if c.coverage != nil {
+ flags = c.coverage.flags(ctx, flags)
+ }
for _, feature := range c.features {
flags = feature.flags(ctx, flags)
}
@@ -462,6 +503,13 @@
flags.CppFlags, _ = filterList(flags.CppFlags, config.IllegalFlags)
flags.ConlyFlags, _ = filterList(flags.ConlyFlags, config.IllegalFlags)
+ deps := c.depsToPaths(ctx)
+ if ctx.Failed() {
+ return
+ }
+ flags.GlobalFlags = append(flags.GlobalFlags, deps.Flags...)
+ c.flags = flags
+
// Optimization to reduce size of build.ninja
// Replace the long list of flags for each file with a module-local variable
ctx.Variable(pctx, "cflags", strings.Join(flags.CFlags, " "))
@@ -471,13 +519,6 @@
flags.CppFlags = []string{"$cppflags"}
flags.AsFlags = []string{"$asflags"}
- deps := c.depsToPaths(ctx)
- if ctx.Failed() {
- return
- }
-
- flags.GlobalFlags = append(flags.GlobalFlags, deps.Flags...)
-
var objs Objects
if c.compiler != nil {
objs = c.compiler.compile(ctx, flags, deps)
@@ -522,26 +563,18 @@
if c.sanitize != nil {
c.sanitize.begin(ctx)
}
+ if c.coverage != nil {
+ c.coverage.begin(ctx)
+ }
for _, feature := range c.features {
feature.begin(ctx)
}
if ctx.sdk() {
- if ctx.vndk() {
- ctx.PropertyErrorf("use_vndk",
- "sdk_version and use_vndk cannot be used at the same time")
- }
-
version, err := normalizeNdkApiLevel(ctx.sdkVersion(), ctx.Arch())
if err != nil {
ctx.PropertyErrorf("sdk_version", err.Error())
}
c.Properties.Sdk_version = version
- } else if ctx.vndk() {
- version, err := normalizeNdkApiLevel(ctx.DeviceConfig().VndkVersion(), ctx.Arch())
- if err != nil {
- ctx.ModuleErrorf("Bad BOARD_VNDK_VERSION: %s", err.Error())
- }
- c.Properties.Vndk_version = version
}
}
@@ -560,6 +593,9 @@
if c.sanitize != nil {
deps = c.sanitize.deps(ctx, deps)
}
+ if c.coverage != nil {
+ deps = c.coverage.deps(ctx, deps)
+ }
for _, feature := range c.features {
deps = feature.deps(ctx, deps)
}
@@ -630,7 +666,7 @@
variantNdkLibs := []string{}
variantLateNdkLibs := []string{}
- if ctx.sdk() || ctx.vndk() {
+ if ctx.Os() == android.Android {
version := ctx.sdkVersion()
// Rewrites the names of shared libraries into the names of the NDK
@@ -647,14 +683,16 @@
variantLibs := []string{}
nonvariantLibs := []string{}
for _, entry := range list {
- if inList(entry, ndkPrebuiltSharedLibraries) {
+ if ctx.sdk() && inList(entry, ndkPrebuiltSharedLibraries) {
if !inList(entry, ndkMigratedLibs) {
nonvariantLibs = append(nonvariantLibs, entry+".ndk."+version)
} else {
variantLibs = append(variantLibs, entry+ndkLibrarySuffix)
}
+ } else if ctx.vndk() && inList(entry, config.LLndkLibraries()) {
+ nonvariantLibs = append(nonvariantLibs, entry+llndkLibrarySuffix)
} else {
- nonvariantLibs = append(variantLibs, entry)
+ nonvariantLibs = append(nonvariantLibs, entry)
}
}
return nonvariantLibs, variantLibs
@@ -760,6 +798,10 @@
// Host code is not restricted
return
}
+ if from.Properties.UseVndk {
+ // Vendor code is already limited by the vendor mutator
+ return
+ }
if from.Properties.Sdk_version == "" {
// Platform code can link to anything
return
@@ -948,6 +990,20 @@
depPaths.CrtEnd = linkFile
}
+ switch tag {
+ case staticDepTag, staticExportDepTag, lateStaticDepTag:
+ staticLib, ok := cc.linker.(libraryInterface)
+ if !ok || !staticLib.static() {
+ ctx.ModuleErrorf("module %q not a static library", name)
+ return
+ }
+
+ // When combining coverage files for shared libraries and executables, coverage files
+ // in static libraries act as if they were whole static libraries.
+ depPaths.StaticLibObjs.coverageFiles = append(depPaths.StaticLibObjs.coverageFiles,
+ staticLib.objs().coverageFiles...)
+ }
+
if ptr != nil {
if !linkFile.Valid() {
ctx.ModuleErrorf("module %q missing output file", name)
@@ -972,10 +1028,17 @@
if c.installer == nil {
return false
}
- if c.sanitize != nil && c.sanitize.inData() {
+ return c.installer.inData()
+}
+
+func (c *Module) InstallInSanitizerDir() bool {
+ if c.installer == nil {
+ return false
+ }
+ if c.sanitize != nil && c.sanitize.inSanitizerDir() {
return true
}
- return c.installer.inData()
+ return c.installer.inSanitizerDir()
}
func (c *Module) HostToolPath() android.OptionalPath {
@@ -1021,11 +1084,63 @@
&StripProperties{},
&InstallerProperties{},
&TidyProperties{},
+ &CoverageProperties{},
)
return android.InitDefaultsModule(module, module, props...)
}
+const (
+ // coreMode is the variant used for framework-private libraries, or
+ // SDK libraries. (which framework-private libraries can use)
+ coreMode = "core"
+
+ // vendorMode is the variant used for /vendor code that compiles
+ // against the VNDK.
+ vendorMode = "vendor"
+)
+
+func vendorMutator(mctx android.BottomUpMutatorContext) {
+ if mctx.Os() != android.Android {
+ return
+ }
+
+ m, ok := mctx.Module().(*Module)
+ if !ok {
+ return
+ }
+
+ // Sanity check
+ if Bool(m.Properties.Vendor_available) && mctx.Vendor() {
+ mctx.PropertyErrorf("vendor_available",
+ "doesn't make sense at the same time as `vendor: true` or `proprietary: true`")
+ return
+ }
+
+ if !mctx.DeviceConfig().CompileVndk() {
+ // If the device isn't compiling against the VNDK, we always
+ // use the core mode.
+ mctx.CreateVariations(coreMode)
+ } else if _, ok := m.linker.(*llndkStubDecorator); ok {
+ // LL-NDK stubs only exist in the vendor variant, since the
+ // real libraries will be used in the core variant.
+ mctx.CreateVariations(vendorMode)
+ } else if Bool(m.Properties.Vendor_available) {
+ // This will be available in both /system and /vendor
+ mod := mctx.CreateVariations(coreMode, vendorMode)
+ mod[1].(*Module).Properties.UseVndk = true
+ } else if mctx.Vendor() && m.Properties.Sdk_version == "" {
+ // This will be available in /vendor only
+ mod := mctx.CreateVariations(vendorMode)
+ mod[0].(*Module).Properties.UseVndk = true
+ } else {
+ // This is either in /system (or similar: /data), or is a
+ // modules built with the NDK. Modules built with the NDK
+ // will be restricted using the existing link type checks.
+ mctx.CreateVariations(coreMode)
+ }
+}
+
// lastUniqueElements returns all unique elements of a slice, keeping the last copy of each
// modifies the slice contents in place, and returns a subslice of the original slice
func lastUniqueElements(list []string) []string {
diff --git a/cc/check.go b/cc/check.go
index 340464e..d04b145 100644
--- a/cc/check.go
+++ b/cc/check.go
@@ -36,6 +36,8 @@
ctx.PropertyErrorf(prop, "Bad flag `%s`, use local_include_dirs or include_dirs instead", flag)
} else if inList(flag, config.IllegalFlags) {
ctx.PropertyErrorf(prop, "Illegal flag `%s`", flag)
+ } else if flag == "--coverage" {
+ ctx.PropertyErrorf(prop, "Bad flag: `%s`, use native_coverage instead", flag)
} else if strings.Contains(flag, " ") {
args := strings.Split(flag, " ")
if args[0] == "-include" {
@@ -73,6 +75,8 @@
ctx.PropertyErrorf(prop, "Bad flag: `%s` is not allowed", flag)
} else if strings.HasPrefix(flag, "-Wl,--version-script") {
ctx.PropertyErrorf(prop, "Bad flag: `%s`, use version_script instead", flag)
+ } else if flag == "--coverage" {
+ ctx.PropertyErrorf(prop, "Bad flag: `%s`, use native_coverage instead", flag)
} else if strings.Contains(flag, " ") {
args := strings.Split(flag, " ")
if args[0] == "-z" {
diff --git a/cc/cmakelists.go b/cc/cmakelists.go
new file mode 100644
index 0000000..4df9ece
--- /dev/null
+++ b/cc/cmakelists.go
@@ -0,0 +1,398 @@
+package cc
+
+import (
+ "fmt"
+
+ "android/soong/android"
+ "android/soong/cc/config"
+ "os"
+ "path"
+ "path/filepath"
+ "strings"
+
+ "github.com/google/blueprint"
+)
+
+// This singleton generates CMakeLists.txt files. It does so for each blueprint Android.bp resulting in a cc.Module
+// when either make, mm, mma, mmm or mmma is called. CMakeLists.txt files are generated in a separate folder
+// structure (see variable CLionOutputProjectsDirectory for root).
+
+func init() {
+ android.RegisterSingletonType("cmakelists_generator", cMakeListsGeneratorSingleton)
+}
+
+func cMakeListsGeneratorSingleton() blueprint.Singleton {
+ return &cmakelistsGeneratorSingleton{}
+}
+
+type cmakelistsGeneratorSingleton struct{}
+
+const (
+ cMakeListsFilename = "CMakeLists.txt"
+ cLionAggregateProjectsDirectory = "development" + string(os.PathSeparator) + "ide" + string(os.PathSeparator) + "clion"
+ cLionOutputProjectsDirectory = "out" + string(os.PathSeparator) + cLionAggregateProjectsDirectory
+ minimumCMakeVersionSupported = "3.5"
+
+ // Environment variables used to modify behavior of this singleton.
+ envVariableGenerateCMakeLists = "SOONG_GEN_CMAKEFILES"
+ envVariableGenerateDebugInfo = "SOONG_GEN_CMAKEFILES_DEBUG"
+ envVariableTrue = "1"
+)
+
+// Instruct generator to trace how header include path and flags were generated.
+// This is done to ease investigating bug reports.
+var outputDebugInfo = false
+
+func (c *cmakelistsGeneratorSingleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
+ if getEnvVariable(envVariableGenerateCMakeLists, ctx) != envVariableTrue {
+ return
+ }
+
+ outputDebugInfo = (getEnvVariable(envVariableGenerateDebugInfo, ctx) == envVariableTrue)
+
+ ctx.VisitAllModules(func(module blueprint.Module) {
+ if ccModule, ok := module.(*Module); ok {
+ if compiledModule, ok := ccModule.compiler.(CompiledInterface); ok {
+ generateCLionProject(compiledModule, ctx, ccModule)
+ }
+ }
+ })
+
+ // Link all handmade CMakeLists.txt aggregate from
+ // BASE/development/ide/clion to
+ // BASE/out/development/ide/clion.
+ dir := filepath.Join(getAndroidSrcRootDirectory(ctx), cLionAggregateProjectsDirectory)
+ filepath.Walk(dir, linkAggregateCMakeListsFiles)
+
+ return
+}
+
+func getEnvVariable(name string, ctx blueprint.SingletonContext) string {
+ // Using android.Config.Getenv instead of os.getEnv to guarantee soong will
+ // re-run in case this environment variable changes.
+ return ctx.Config().(android.Config).Getenv(name)
+}
+
+func exists(path string) bool {
+ _, err := os.Stat(path)
+ if err == nil {
+ return true
+ }
+ if os.IsNotExist(err) {
+ return false
+ }
+ return true
+}
+
+func linkAggregateCMakeListsFiles(path string, info os.FileInfo, err error) error {
+
+ if info == nil {
+ return nil
+ }
+
+ dst := strings.Replace(path, cLionAggregateProjectsDirectory, cLionOutputProjectsDirectory, 1)
+ if info.IsDir() {
+ // This is a directory to create
+ os.MkdirAll(dst, os.ModePerm)
+ } else {
+ // This is a file to link
+ os.Remove(dst)
+ os.Symlink(path, dst)
+ }
+ return nil
+}
+
+func generateCLionProject(compiledModule CompiledInterface, ctx blueprint.SingletonContext, ccModule *Module) {
+ srcs := compiledModule.Srcs()
+ if len(srcs) == 0 {
+ return
+ }
+
+ // Ensure the directory hosting the cmakelists.txt exists
+ clionproject_location := getCMakeListsForModule(ccModule, ctx)
+ projectDir := path.Dir(clionproject_location)
+ os.MkdirAll(projectDir, os.ModePerm)
+
+ // Create cmakelists.txt
+ f, _ := os.Create(filepath.Join(projectDir, cMakeListsFilename))
+ defer f.Close()
+
+ // Header.
+ f.WriteString("# THIS FILE WAS AUTOMATICALY GENERATED!\n")
+ f.WriteString("# ANY MODIFICATION WILL BE OVERWRITTEN!\n\n")
+ f.WriteString("# To improve project view in Clion :\n")
+ f.WriteString("# Tools > CMake > Change Project Root \n\n")
+ f.WriteString(fmt.Sprintf("cmake_minimum_required(VERSION %s)\n", minimumCMakeVersionSupported))
+ f.WriteString(fmt.Sprintf("project(%s)\n", ccModule.ModuleBase.Name()))
+ f.WriteString(fmt.Sprintf("set(ANDROID_ROOT %s)\n\n", getAndroidSrcRootDirectory(ctx)))
+
+ if ccModule.flags.Clang {
+ pathToCC, _ := evalVariable(ctx, "${config.ClangBin}/")
+ f.WriteString(fmt.Sprintf("set(CMAKE_C_COMPILER \"%s%s\")\n", buildCMakePath(pathToCC), "clang"))
+ f.WriteString(fmt.Sprintf("set(CMAKE_CXX_COMPILER \"%s%s\")\n", buildCMakePath(pathToCC), "clang++"))
+ } else {
+ toolchain := config.FindToolchain(ccModule.Os(), ccModule.Arch())
+ root, _ := evalVariable(ctx, toolchain.GccRoot())
+ triple, _ := evalVariable(ctx, toolchain.GccTriple())
+ pathToCC := filepath.Join(root, "bin", triple+"-")
+ f.WriteString(fmt.Sprintf("set(CMAKE_C_COMPILER \"%s%s\")\n", buildCMakePath(pathToCC), "gcc"))
+ f.WriteString(fmt.Sprintf("set(CMAKE_CXX_COMPILER \"%s%s\")\n", buildCMakePath(pathToCC), "g++"))
+ }
+ // Add all sources to the project.
+ f.WriteString("list(APPEND\n")
+ f.WriteString(" SOURCE_FILES\n")
+ for _, src := range srcs {
+ f.WriteString(fmt.Sprintf(" ${ANDROID_ROOT}/%s\n", src.String()))
+ }
+ f.WriteString(")\n")
+
+ // Add all header search path and compiler parameters (-D, -W, -f, -XXXX)
+ f.WriteString("\n# GLOBAL FLAGS:\n")
+ globalParameters := parseCompilerParameters(ccModule.flags.GlobalFlags, ctx, f)
+ translateToCMake(globalParameters, f, true, true)
+
+ f.WriteString("\n# CFLAGS:\n")
+ cParameters := parseCompilerParameters(ccModule.flags.CFlags, ctx, f)
+ translateToCMake(cParameters, f, true, true)
+
+ f.WriteString("\n# C ONLY FLAGS:\n")
+ cOnlyParameters := parseCompilerParameters(ccModule.flags.ConlyFlags, ctx, f)
+ translateToCMake(cOnlyParameters, f, true, false)
+
+ f.WriteString("\n# CPP FLAGS:\n")
+ cppParameters := parseCompilerParameters(ccModule.flags.CppFlags, ctx, f)
+ translateToCMake(cppParameters, f, false, true)
+
+ f.WriteString("\n# SYSTEM INCLUDE FLAGS:\n")
+ includeParameters := parseCompilerParameters(ccModule.flags.SystemIncludeFlags, ctx, f)
+ translateToCMake(includeParameters, f, true, true)
+
+ // Add project executable.
+ f.WriteString(fmt.Sprintf("\nadd_executable(%s ${SOURCE_FILES})\n",
+ cleanExecutableName(ccModule.ModuleBase.Name())))
+}
+
+func cleanExecutableName(s string) string {
+ return strings.Replace(s, "@", "-", -1)
+}
+
+func translateToCMake(c compilerParameters, f *os.File, cflags bool, cppflags bool) {
+ writeAllIncludeDirectories(c.systemHeaderSearchPath, f, true)
+ writeAllIncludeDirectories(c.headerSearchPath, f, false)
+ if cflags {
+ writeAllFlags(c.flags, f, "CMAKE_C_FLAGS")
+ }
+
+ if cppflags {
+ writeAllFlags(c.flags, f, "CMAKE_CXX_FLAGS")
+ }
+ if c.sysroot != "" {
+ f.WriteString(fmt.Sprintf("include_directories(SYSTEM \"%s\")\n", buildCMakePath(path.Join(c.sysroot, "usr", "include"))))
+ }
+
+}
+
+func buildCMakePath(p string) string {
+ if path.IsAbs(p) {
+ return p
+ }
+ return fmt.Sprintf("${ANDROID_ROOT}/%s", p)
+}
+
+func writeAllIncludeDirectories(includes []string, f *os.File, isSystem bool) {
+ if len(includes) == 0 {
+ return
+ }
+
+ system := ""
+ if isSystem {
+ system = "SYSTEM"
+ }
+
+ f.WriteString(fmt.Sprintf("include_directories(%s \n", system))
+
+ for _, include := range includes {
+ f.WriteString(fmt.Sprintf(" \"%s\"\n", buildCMakePath(include)))
+ }
+ f.WriteString(")\n\n")
+
+ // Also add all headers to source files.
+ f.WriteString("file (GLOB_RECURSE TMP_HEADERS\n");
+ for _, include := range includes {
+ f.WriteString(fmt.Sprintf(" \"%s/**/*.h\"\n", buildCMakePath(include)))
+ }
+ f.WriteString(")\n")
+ f.WriteString("list (APPEND SOURCE_FILES ${TMP_HEADERS})\n\n");
+}
+
+func writeAllFlags(flags []string, f *os.File, tag string) {
+ for _, flag := range flags {
+ f.WriteString(fmt.Sprintf("set(%s \"${%s} %s\")\n", tag, tag, flag))
+ }
+}
+
+type parameterType int
+
+const (
+ headerSearchPath parameterType = iota
+ variable
+ systemHeaderSearchPath
+ flag
+ systemRoot
+)
+
+type compilerParameters struct {
+ headerSearchPath []string
+ systemHeaderSearchPath []string
+ flags []string
+ sysroot string
+}
+
+func makeCompilerParameters() compilerParameters {
+ return compilerParameters{
+ sysroot: "",
+ }
+}
+
+func categorizeParameter(parameter string) parameterType {
+ if strings.HasPrefix(parameter, "-I") {
+ return headerSearchPath
+ }
+ if strings.HasPrefix(parameter, "$") {
+ return variable
+ }
+ if strings.HasPrefix(parameter, "-isystem") {
+ return systemHeaderSearchPath
+ }
+ if strings.HasPrefix(parameter, "-isysroot") {
+ return systemRoot
+ }
+ if strings.HasPrefix(parameter, "--sysroot") {
+ return systemRoot
+ }
+ return flag
+}
+
+func parseCompilerParameters(params []string, ctx blueprint.SingletonContext, f *os.File) compilerParameters {
+ var compilerParameters = makeCompilerParameters()
+
+ for i, str := range params {
+ f.WriteString(fmt.Sprintf("# Raw param [%d] = '%s'\n", i, str))
+ }
+
+ for i := 0; i < len(params); i++ {
+ param := params[i]
+ if param == "" {
+ continue
+ }
+
+ switch categorizeParameter(param) {
+ case headerSearchPath:
+ compilerParameters.headerSearchPath =
+ append(compilerParameters.headerSearchPath, strings.TrimPrefix(param, "-I"))
+ case variable:
+ if evaluated, error := evalVariable(ctx, param); error == nil {
+ if outputDebugInfo {
+ f.WriteString(fmt.Sprintf("# variable %s = '%s'\n", param, evaluated))
+ }
+
+ paramsFromVar := parseCompilerParameters(strings.Split(evaluated, " "), ctx, f)
+ concatenateParams(&compilerParameters, paramsFromVar)
+
+ } else {
+ if outputDebugInfo {
+ f.WriteString(fmt.Sprintf("# variable %s could NOT BE RESOLVED\n", param))
+ }
+ }
+ case systemHeaderSearchPath:
+ if i < len(params)-1 {
+ compilerParameters.systemHeaderSearchPath =
+ append(compilerParameters.systemHeaderSearchPath, params[i+1])
+ } else if outputDebugInfo {
+ f.WriteString("# Found a header search path marker with no path")
+ }
+ i = i + 1
+ case flag:
+ c := cleanupParameter(param)
+ f.WriteString(fmt.Sprintf("# FLAG '%s' became %s\n", param, c))
+ compilerParameters.flags = append(compilerParameters.flags, c)
+ case systemRoot:
+ if i < len(params)-1 {
+ compilerParameters.sysroot = params[i+1]
+ } else if outputDebugInfo {
+ f.WriteString("# Found a system root path marker with no path")
+ }
+ i = i + 1
+ }
+ }
+ return compilerParameters
+}
+
+func cleanupParameter(p string) string {
+ // In the blueprint, c flags can be passed as:
+ // cflags: [ "-DLOG_TAG=\"libEGL\"", ]
+ // which becomes:
+ // '-DLOG_TAG="libEGL"' in soong.
+ // In order to be injected in CMakelists.txt we need to:
+ // - Remove the wrapping ' character
+ // - Double escape all special \ and " characters.
+ // For a end result like:
+ // -DLOG_TAG=\\\"libEGL\\\"
+ if !strings.HasPrefix(p, "'") || !strings.HasSuffix(p, "'") || len(p) < 3 {
+ return p
+ }
+
+ // Reverse wrapper quotes and escaping that may have happened in NinjaAndShellEscape
+ // TODO: It is ok to reverse here for now but if NinjaAndShellEscape becomes more complex,
+ // we should create a method NinjaAndShellUnescape in escape.go and use that instead.
+ p = p[1 : len(p)-1]
+ p = strings.Replace(p, `'\''`, `'`, -1)
+ p = strings.Replace(p, `$$`, `$`, -1)
+
+ p = doubleEscape(p)
+ return p
+}
+
+func escape(s string) string {
+ s = strings.Replace(s, `\`, `\\`, -1)
+ s = strings.Replace(s, `"`, `\"`, -1)
+ return s
+}
+
+func doubleEscape(s string) string {
+ s = escape(s)
+ s = escape(s)
+ return s
+}
+
+func concatenateParams(c1 *compilerParameters, c2 compilerParameters) {
+ c1.headerSearchPath = append(c1.headerSearchPath, c2.headerSearchPath...)
+ c1.systemHeaderSearchPath = append(c1.systemHeaderSearchPath, c2.systemHeaderSearchPath...)
+ if c2.sysroot != "" {
+ c1.sysroot = c2.sysroot
+ }
+ c1.flags = append(c1.flags, c2.flags...)
+}
+
+func evalVariable(ctx blueprint.SingletonContext, str string) (string, error) {
+ evaluated, err := ctx.Eval(pctx, str)
+ if err == nil {
+ return evaluated, nil
+ }
+ return "", err
+}
+
+func getCMakeListsForModule(module *Module, ctx blueprint.SingletonContext) string {
+ return filepath.Join(getAndroidSrcRootDirectory(ctx),
+ cLionOutputProjectsDirectory,
+ path.Dir(ctx.BlueprintFile(module)),
+ module.ModuleBase.Name()+"-"+
+ module.ModuleBase.Arch().ArchType.Name+"-"+
+ module.ModuleBase.Os().Name,
+ cMakeListsFilename)
+}
+
+func getAndroidSrcRootDirectory(ctx blueprint.SingletonContext) string {
+ srcPath, _ := filepath.Abs(android.PathForSource(ctx).String())
+ return srcPath
+}
diff --git a/cc/compiler.go b/cc/compiler.go
index d53e799..aed4480 100644
--- a/cc/compiler.go
+++ b/cc/compiler.go
@@ -86,6 +86,16 @@
// pass -frtti instead of -fno-rtti
Rtti *bool
+ // C standard version to use. Can be a specific version (such as "gnu11"),
+ // "experimental" (which will use draft versions like C1x when available),
+ // or the empty string (which will use the default).
+ C_std string
+
+ // C++ standard version to use. Can be a specific version (such as
+ // "gnu++11"), "experimental" (which will use draft versions like C++1z when
+ // available), or the empty string (which will use the default).
+ Cpp_std string
+
// if set to false, use -std=c++* instead of -std=gnu++*
Gnu_extensions *bool
@@ -103,6 +113,18 @@
// release builds
Cflags []string `android:"arch_variant"`
} `android:"arch_variant"`
+
+ Target struct {
+ Vendor struct {
+ // list of source files that should only be used in the
+ // vendor variant of the C/C++ module.
+ Srcs []string
+
+ // list of source files that should not be used to
+ // build the vendor variant of the C/C++ module.
+ Exclude_srcs []string
+ }
+ }
}
func NewBaseCompiler() *baseCompiler {
@@ -113,10 +135,20 @@
Properties BaseCompilerProperties
Proto ProtoProperties
deps android.Paths
+ srcs android.Paths
+ flags builderFlags
}
var _ compiler = (*baseCompiler)(nil)
+type CompiledInterface interface {
+ Srcs() android.Paths
+}
+
+func (compiler *baseCompiler) Srcs() android.Paths {
+ return compiler.srcs
+}
+
func (compiler *baseCompiler) appendCflags(flags []string) {
compiler.Properties.Cflags = append(compiler.Properties.Cflags, flags...)
}
@@ -174,23 +206,23 @@
}
if !ctx.noDefaultCompilerFlags() {
+ flags.GlobalFlags = append(flags.GlobalFlags, "-I"+android.PathForModuleSrc(ctx).String())
+
if !(ctx.sdk() || ctx.vndk()) || ctx.Host() {
- flags.GlobalFlags = append(flags.GlobalFlags,
+ flags.SystemIncludeFlags = append(flags.SystemIncludeFlags,
"${config.CommonGlobalIncludes}",
"${config.CommonGlobalSystemIncludes}",
tc.IncludeFlags(),
"${config.CommonNativehelperInclude}")
}
-
- flags.GlobalFlags = append(flags.GlobalFlags, "-I"+android.PathForModuleSrc(ctx).String())
}
- if ctx.sdk() || ctx.vndk() {
+ if ctx.sdk() {
// The NDK headers are installed to a common sysroot. While a more
// typical Soong approach would be to only make the headers for the
// library you're using available, we're trying to emulate the NDK
// behavior here, and the NDK always has all the NDK headers available.
- flags.GlobalFlags = append(flags.GlobalFlags,
+ flags.SystemIncludeFlags = append(flags.SystemIncludeFlags,
"-isystem "+getCurrentIncludePath(ctx).String(),
"-isystem "+getCurrentIncludePath(ctx).Join(ctx, tc.ClangTriple()).String())
@@ -210,7 +242,12 @@
legacyIncludes := fmt.Sprintf(
"prebuilts/ndk/current/platforms/android-%s/arch-%s/usr/include",
ctx.sdkVersion(), ctx.Arch().ArchType.String())
- flags.GlobalFlags = append(flags.GlobalFlags, "-isystem "+legacyIncludes)
+ flags.SystemIncludeFlags = append(flags.SystemIncludeFlags, "-isystem "+legacyIncludes)
+ }
+
+ if ctx.vndk() {
+ flags.GlobalFlags = append(flags.GlobalFlags,
+ "-D__ANDROID_API__=__ANDROID_API_FUTURE__", "-D__ANDROID_VNDK__")
}
instructionSet := compiler.Properties.Instruction_set
@@ -242,10 +279,7 @@
flags.LdFlags = config.ClangFilterUnknownCflags(flags.LdFlags)
target := "-target " + tc.ClangTriple()
- var gccPrefix string
- if !ctx.Darwin() {
- gccPrefix = "-B" + filepath.Join(tc.GccRoot(), tc.GccTriple(), "bin")
- }
+ gccPrefix := "-B" + config.ToolPath(tc)
flags.CFlags = append(flags.CFlags, target, gccPrefix)
flags.AsFlags = append(flags.AsFlags, target, gccPrefix)
@@ -307,7 +341,18 @@
if !ctx.sdk() {
cStd := config.CStdVersion
+ if compiler.Properties.C_std == "experimental" {
+ cStd = config.ExperimentalCStdVersion
+ } else if compiler.Properties.C_std != "" {
+ cStd = compiler.Properties.C_std
+ }
+
cppStd := config.CppStdVersion
+ if compiler.Properties.Cpp_std == "experimental" {
+ cppStd = config.ExperimentalCppStdVersion
+ } else if compiler.Properties.Cpp_std != "" {
+ cppStd = compiler.Properties.Cpp_std
+ }
if !flags.Clang {
// GCC uses an invalid C++14 ABI (emits calls to
@@ -384,7 +429,7 @@
var gnuToCReplacer = strings.NewReplacer("gnu", "c")
func ndkPathDeps(ctx ModuleContext) android.Paths {
- if ctx.sdk() || ctx.vndk() {
+ if ctx.sdk() {
// The NDK sysroot timestamp file depends on all the NDK sysroot files
// (headers and libraries).
return android.Paths{getNdkSysrootTimestampFile(ctx)}
@@ -396,6 +441,14 @@
pathDeps := deps.GeneratedHeaders
pathDeps = append(pathDeps, ndkPathDeps(ctx)...)
+ if ctx.vndk() {
+ compiler.Properties.Srcs = append(compiler.Properties.Srcs,
+ compiler.Properties.Target.Vendor.Srcs...)
+
+ compiler.Properties.Exclude_srcs = append(compiler.Properties.Exclude_srcs,
+ compiler.Properties.Target.Vendor.Exclude_srcs...)
+ }
+
srcs := ctx.ExpandSources(compiler.Properties.Srcs, compiler.Properties.Exclude_srcs)
srcs = append(srcs, deps.GeneratedSources...)
@@ -408,6 +461,9 @@
compiler.deps = pathDeps
+ // Save src, buildFlags and context
+ compiler.srcs = srcs
+
// Compile files listed in c.Properties.Srcs into objects
objs := compileObjs(ctx, buildFlags, "", srcs, compiler.deps)
diff --git a/cc/config/arm64_device.go b/cc/config/arm64_device.go
index fe47ddf..f387ddf 100644
--- a/cc/config/arm64_device.go
+++ b/cc/config/arm64_device.go
@@ -66,9 +66,6 @@
"-fuse-ld=gold",
"-Wl,--icf=safe",
"-Wl,--no-undefined-version",
-
- // Disable transitive dependency library symbol resolving.
- "-Wl,--allow-shlib-undefined",
}
arm64Cppflags = []string{
@@ -79,6 +76,11 @@
"cortex-a53": []string{
"-mcpu=cortex-a53",
},
+ "kryo": []string{
+ // Use the cortex-a57 cpu since some compilers
+ // don't support a Kryo specific target yet.
+ "-mcpu=cortex-a57",
+ },
}
arm64ClangCpuVariantCflags = copyVariantFlags(arm64CpuVariantCflags)
@@ -92,8 +94,12 @@
android.RegisterArchVariants(android.Arm64,
"armv8_a",
"cortex_a53",
+ "kryo",
"denver64")
+ // Clang supports specific Kryo targeting
+ replaceFirst(arm64ClangCpuVariantCflags["kryo"], "-mcpu=cortex-a57", "-mcpu=kryo")
+
pctx.StaticVariable("arm64GccVersion", arm64GccVersion)
pctx.SourcePathVariable("Arm64GccRoot",
@@ -112,17 +118,24 @@
strings.Join(arm64CpuVariantCflags["cortex-a53"], " "))
pctx.StaticVariable("Arm64ClangCortexA53Cflags",
strings.Join(arm64ClangCpuVariantCflags["cortex-a53"], " "))
+
+ pctx.StaticVariable("Arm64KryoCflags",
+ strings.Join(arm64CpuVariantCflags["kryo"], " "))
+ pctx.StaticVariable("Arm64ClangKryoCflags",
+ strings.Join(arm64ClangCpuVariantCflags["kryo"], " "))
}
var (
arm64CpuVariantCflagsVar = map[string]string{
"": "",
"cortex-a53": "${config.Arm64CortexA53Cflags}",
+ "kryo": "${config.Arm64KryoCflags}",
}
arm64ClangCpuVariantCflagsVar = map[string]string{
"": "",
"cortex-a53": "${config.Arm64ClangCortexA53Cflags}",
+ "kryo": "${config.Arm64ClangKryoCflags}",
}
)
diff --git a/cc/config/arm_device.go b/cc/config/arm_device.go
index 1b60eae..9058de2 100644
--- a/cc/config/arm_device.go
+++ b/cc/config/arm_device.go
@@ -128,6 +128,16 @@
},
"krait": []string{
"-mcpu=cortex-a15",
+ "-mfpu=neon-vfpv4",
+ // Fake an ARM compiler flag as these processors support LPAE which GCC/clang
+ // don't advertise.
+ // TODO This is a hack and we need to add it for each processor that supports LPAE until some
+ // better solution comes around. See Bug 27340895
+ "-D__ARM_FEATURE_LPAE=1",
+ },
+ "kryo": []string{
+ "-mcpu=cortex-a15",
+ "-mfpu=neon-fp-armv8",
// Fake an ARM compiler flag as these processors support LPAE which GCC/clang
// don't advertise.
// TODO This is a hack and we need to add it for each processor that supports LPAE until some
@@ -156,18 +166,13 @@
"cortex_a53",
"cortex_a53_a57",
"krait",
+ "kryo",
"denver")
- replaceFirst := func(slice []string, from, to string) {
- if slice[0] != from {
- panic(fmt.Errorf("Expected %q, found %q", from, to))
- }
-
- slice[0] = to
- }
-
+ // Krait and Kryo targets are not supported by GCC, but are supported by Clang,
+ // so override the definitions when building modules with Clang.
replaceFirst(armClangCpuVariantCflags["krait"], "-mcpu=cortex-a15", "-mcpu=krait")
- armClangCpuVariantCflags["krait"] = append(armClangCpuVariantCflags["krait"], "-mfpu=neon-vfpv4")
+ replaceFirst(armClangCpuVariantCflags["kryo"], "-mcpu=cortex-a15", "-mcpu=krait")
pctx.StaticVariable("armGccVersion", armGccVersion)
@@ -197,6 +202,7 @@
pctx.StaticVariable("ArmCortexA8Cflags", strings.Join(armCpuVariantCflags["cortex-a8"], " "))
pctx.StaticVariable("ArmCortexA15Cflags", strings.Join(armCpuVariantCflags["cortex-a15"], " "))
pctx.StaticVariable("ArmKraitCflags", strings.Join(armCpuVariantCflags["krait"], " "))
+ pctx.StaticVariable("ArmKryoCflags", strings.Join(armCpuVariantCflags["kryo"], " "))
// Clang cflags
pctx.StaticVariable("ArmToolchainClangCflags", strings.Join(ClangFilterUnknownCflags(armToolchainCflags), " "))
@@ -227,6 +233,8 @@
strings.Join(armClangCpuVariantCflags["cortex-a15"], " "))
pctx.StaticVariable("ArmClangKraitCflags",
strings.Join(armClangCpuVariantCflags["krait"], " "))
+ pctx.StaticVariable("ArmClangKryoCflags",
+ strings.Join(armClangCpuVariantCflags["kryo"], " "))
}
var (
@@ -244,6 +252,7 @@
"cortex-a53": "${config.ArmCortexA7Cflags}",
"cortex-a53.a57": "${config.ArmCortexA7Cflags}",
"krait": "${config.ArmKraitCflags}",
+ "kryo": "${config.ArmKryoCflags}",
"denver": "${config.ArmCortexA15Cflags}",
}
@@ -261,6 +270,7 @@
"cortex-a53": "${config.ArmClangCortexA7Cflags}",
"cortex-a53.a57": "${config.ArmClangCortexA7Cflags}",
"krait": "${config.ArmClangKraitCflags}",
+ "kryo": "${config.ArmClangKryoCflags}",
"denver": "${config.ArmClangCortexA15Cflags}",
}
)
diff --git a/cc/config/clang.go b/cc/config/clang.go
index 10f4cea..30ab1c6 100644
--- a/cc/config/clang.go
+++ b/cc/config/clang.go
@@ -91,6 +91,10 @@
// http://b/29823425 Disable -Wexpansion-to-defined for Clang update to r271374
"-Wno-expansion-to-defined",
+
+ // http://b/36463318 Clang executes with an absolute path, so clang-provided
+ // headers are now absolute.
+ "-fdebug-prefix-map=$$PWD/=",
}, " "))
pctx.StaticVariable("ClangExtraCppflags", strings.Join([]string{
diff --git a/cc/config/global.go b/cc/config/global.go
index e248040..774f3f7 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -15,6 +15,7 @@
package config
import (
+ "fmt"
"strings"
"android/soong/android"
@@ -65,9 +66,11 @@
"-w",
}
- CStdVersion = "gnu99"
- CppStdVersion = "gnu++14"
- GccCppStdVersion = "gnu++11"
+ CStdVersion = "gnu99"
+ CppStdVersion = "gnu++14"
+ GccCppStdVersion = "gnu++11"
+ ExperimentalCStdVersion = "gnu11"
+ ExperimentalCppStdVersion = "gnu++1z"
)
var pctx = android.NewPackageContext("android/soong/cc/config")
@@ -130,7 +133,7 @@
if override := config.(android.Config).Getenv("LLVM_PREBUILTS_VERSION"); override != "" {
return override, nil
}
- return "clang-3688880", nil
+ return "clang-3859424", nil
})
pctx.StaticVariable("ClangPath", "${ClangBase}/${HostPrebuiltTag}/${ClangVersion}")
pctx.StaticVariable("ClangBin", "${ClangPath}/bin")
@@ -143,6 +146,14 @@
})
pctx.StaticVariable("ClangAsanLibDir", "${ClangPath}/lib64/clang/${ClangShortVersion}/lib/linux")
+ // These are tied to the version of LLVM directly in external/llvm, so they might trail the host prebuilts
+ // being used for the rest of the build process.
+ pctx.SourcePathVariable("RSClangBase", "prebuilts/clang/host")
+ pctx.SourcePathVariable("RSClangVersion", "clang-3289846")
+ pctx.SourcePathVariable("RSReleaseVersion", "3.8")
+ pctx.StaticVariable("RSLLVMPrebuiltsPath", "${RSClangBase}/${HostPrebuiltTag}/${RSClangVersion}/bin")
+ pctx.StaticVariable("RSIncludePath", "${RSLLVMPrebuiltsPath}/../lib64/clang/${RSReleaseVersion}/include")
+
pctx.VariableFunc("CcWrapper", func(config interface{}) (string, error) {
if override := config.(android.Config).Getenv("CC_WRAPPER"); override != "" {
return override + " ", nil
@@ -166,3 +177,17 @@
func VndkLibraries() []string {
return []string{}
}
+
+// This needs to be kept up to date with the list in system/core/rootdir/etc/ld.config.txt:
+// [vendor]
+// namespace.default.link.system.shared_libs
+func LLndkLibraries() []string {
+ return []string{"libc", "libm", "libdl", "liblog", "ld-android"}
+}
+
+func replaceFirst(slice []string, from, to string) {
+ if slice[0] != from {
+ panic(fmt.Errorf("Expected %q, found %q", from, to))
+ }
+ slice[0] = to
+}
diff --git a/cc/config/toolchain.go b/cc/config/toolchain.go
index 995c8c6..5270437 100644
--- a/cc/config/toolchain.go
+++ b/cc/config/toolchain.go
@@ -16,6 +16,7 @@
import (
"fmt"
+ "path/filepath"
"android/soong/android"
)
@@ -47,6 +48,7 @@
GccTriple() string
// GccVersion should return a real value, not a ninja reference
GccVersion() string
+ ToolPath() string
ToolchainCflags() string
ToolchainLdflags() string
@@ -145,6 +147,10 @@
return true
}
+func (t toolchainBase) ToolPath() string {
+ return ""
+}
+
type toolchain64Bit struct {
toolchainBase
}
@@ -201,18 +207,29 @@
return indexList(s, list) != -1
}
-func AddressSanitizerRuntimeLibrary(t Toolchain) string {
+func SanitizerRuntimeLibrary(t Toolchain, sanitizer string) string {
arch := t.SanitizerRuntimeLibraryArch()
if arch == "" {
return ""
}
- return "libclang_rt.asan-" + arch + "-android.so"
+ return "libclang_rt." + sanitizer + "-" + arch + "-android.so"
+}
+
+func AddressSanitizerRuntimeLibrary(t Toolchain) string {
+ return SanitizerRuntimeLibrary(t, "asan")
}
func UndefinedBehaviorSanitizerRuntimeLibrary(t Toolchain) string {
- arch := t.SanitizerRuntimeLibraryArch()
- if arch == "" {
- return ""
+ return SanitizerRuntimeLibrary(t, "ubsan_standalone")
+}
+
+func ThreadSanitizerRuntimeLibrary(t Toolchain) string {
+ return SanitizerRuntimeLibrary(t, "tsan")
+}
+
+func ToolPath(t Toolchain) string {
+ if p := t.ToolPath(); p != "" {
+ return p
}
- return "libclang_rt.ubsan_standalone-" + arch + "-android.so"
+ return filepath.Join(t.GccRoot(), t.GccTriple(), "bin")
}
diff --git a/cc/config/x86_darwin_host.go b/cc/config/x86_darwin_host.go
index 18acef8..b6b08fe 100644
--- a/cc/config/x86_darwin_host.go
+++ b/cc/config/x86_darwin_host.go
@@ -17,6 +17,7 @@
import (
"fmt"
"os/exec"
+ "path/filepath"
"strings"
"android/soong/android"
@@ -131,6 +132,11 @@
return strings.TrimSpace(string(bytes)), err
})
+ pctx.VariableFunc("MacToolPath", func(config interface{}) (string, error) {
+ bytes, err := exec.Command("xcrun", "--find", "ld").Output()
+ return filepath.Dir(strings.TrimSpace(string(bytes))), err
+ })
+
pctx.StaticVariable("DarwinGccVersion", darwinGccVersion)
pctx.SourcePathVariable("DarwinGccRoot",
"prebuilts/gcc/${HostPrebuiltTag}/host/i686-apple-darwin-${DarwinGccVersion}")
@@ -276,6 +282,10 @@
return false
}
+func (t *toolchainDarwin) ToolPath() string {
+ return "${config.MacToolPath}"
+}
+
var toolchainDarwinX86Singleton Toolchain = &toolchainDarwinX86{}
var toolchainDarwinX8664Singleton Toolchain = &toolchainDarwinX8664{}
diff --git a/cc/config/x86_linux_bionic_host.go b/cc/config/x86_linux_bionic_host.go
new file mode 100644
index 0000000..bd6cd0e
--- /dev/null
+++ b/cc/config/x86_linux_bionic_host.go
@@ -0,0 +1,168 @@
+// 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 config
+
+import (
+ "strings"
+
+ "android/soong/android"
+)
+
+var (
+ linuxBionicCflags = ClangFilterUnknownCflags([]string{
+ "-fno-exceptions", // from build/core/combo/select.mk
+ "-Wno-multichar", // from build/core/combo/select.mk
+
+ "-fdiagnostics-color",
+
+ "-Wa,--noexecstack",
+
+ "-fPIC",
+ "-no-canonical-prefixes",
+
+ "-U_FORTIFY_SOURCE",
+ "-D_FORTIFY_SOURCE=2",
+ "-fstack-protector-strong",
+
+ // From x86_64_device
+ "-ffunction-sections",
+ "-finline-functions",
+ "-finline-limit=300",
+ "-fno-short-enums",
+ "-funswitch-loops",
+ "-funwind-tables",
+ "-no-canonical-prefixes",
+ "-fno-canonical-system-headers",
+
+ // HOST_RELEASE_CFLAGS
+ "-O2", // from build/core/combo/select.mk
+ "-g", // from build/core/combo/select.mk
+ "-fno-strict-aliasing", // from build/core/combo/select.mk
+
+ // Tell clang where the gcc toolchain is
+ "--gcc-toolchain=${LinuxBionicGccRoot}",
+
+ // TODO: We're not really android, but we don't have a triple yet b/31393676
+ "-U__ANDROID__",
+ "-fno-emulated-tls",
+
+ // This is normally in ClangExtraTargetCflags, but this is considered host
+ "-nostdlibinc",
+ })
+
+ linuxBionicLdflags = ClangFilterUnknownCflags([]string{
+ "-Wl,-z,noexecstack",
+ "-Wl,-z,relro",
+ "-Wl,-z,now",
+ "-Wl,--build-id=md5",
+ "-Wl,--warn-shared-textrel",
+ "-Wl,--fatal-warnings",
+ "-Wl,--gc-sections",
+ "-Wl,--hash-style=gnu",
+ "-Wl,--no-undefined-version",
+
+ // Use the device gcc toolchain
+ "--gcc-toolchain=${LinuxBionicGccRoot}",
+ })
+)
+
+func init() {
+ pctx.StaticVariable("LinuxBionicCflags", strings.Join(linuxBionicCflags, " "))
+ pctx.StaticVariable("LinuxBionicLdflags", strings.Join(linuxBionicLdflags, " "))
+
+ pctx.StaticVariable("LinuxBionicIncludeFlags", bionicHeaders("x86_64", "x86"))
+
+ // Use the device gcc toolchain for now
+ pctx.StaticVariable("LinuxBionicGccRoot", "${X86_64GccRoot}")
+}
+
+type toolchainLinuxBionic struct {
+ toolchain64Bit
+}
+
+func (t *toolchainLinuxBionic) Name() string {
+ return "x86_64"
+}
+
+func (t *toolchainLinuxBionic) GccRoot() string {
+ return "${config.LinuxBionicGccRoot}"
+}
+
+func (t *toolchainLinuxBionic) GccTriple() string {
+ return "x86_64-linux-android"
+}
+
+func (t *toolchainLinuxBionic) GccVersion() string {
+ return "4.9"
+}
+
+func (t *toolchainLinuxBionic) Cflags() string {
+ return ""
+}
+
+func (t *toolchainLinuxBionic) Cppflags() string {
+ return ""
+}
+
+func (t *toolchainLinuxBionic) Ldflags() string {
+ return ""
+}
+
+func (t *toolchainLinuxBionic) IncludeFlags() string {
+ return "${config.LinuxBionicIncludeFlags}"
+}
+
+func (t *toolchainLinuxBionic) ClangTriple() string {
+ // TODO: we don't have a triple yet b/31393676
+ return "x86_64-linux-android"
+}
+
+func (t *toolchainLinuxBionic) ClangCflags() string {
+ return "${config.LinuxBionicCflags}"
+}
+
+func (t *toolchainLinuxBionic) ClangCppflags() string {
+ return ""
+}
+
+func (t *toolchainLinuxBionic) ClangLdflags() string {
+ return "${config.LinuxBionicLdflags}"
+}
+
+func (t *toolchainLinuxBionic) ToolchainClangCflags() string {
+ return "-m64 -march=x86-64"
+}
+
+func (t *toolchainLinuxBionic) ToolchainClangLdflags() string {
+ return "-m64"
+}
+
+func (t *toolchainLinuxBionic) AvailableLibraries() []string {
+ return nil
+}
+
+func (t *toolchainLinuxBionic) Bionic() bool {
+ return true
+}
+
+var toolchainLinuxBionicSingleton Toolchain = &toolchainLinuxBionic{}
+
+func linuxBionicToolchainFactory(arch android.Arch) Toolchain {
+ return toolchainLinuxBionicSingleton
+}
+
+func init() {
+ registerToolchainFactory(android.LinuxBionic, android.X86_64, linuxBionicToolchainFactory)
+}
diff --git a/cc/config/x86_windows_host.go b/cc/config/x86_windows_host.go
index 4aad9c8..4709823 100644
--- a/cc/config/x86_windows_host.go
+++ b/cc/config/x86_windows_host.go
@@ -69,6 +69,7 @@
windowsX86Ldflags = []string{
"-m32",
+ "-Wl,--large-address-aware",
"-L${WindowsGccRoot}/${WindowsGccTriple}/lib32",
}
diff --git a/cc/coverage.go b/cc/coverage.go
new file mode 100644
index 0000000..b1c8783
--- /dev/null
+++ b/cc/coverage.go
@@ -0,0 +1,124 @@
+// Copyright 2017 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cc
+
+import (
+ "android/soong/android"
+ "github.com/google/blueprint"
+)
+
+type CoverageProperties struct {
+ Native_coverage *bool
+
+ CoverageEnabled bool `blueprint:"mutated"`
+}
+
+type coverage struct {
+ Properties CoverageProperties
+
+ // Whether binaries containing this module need --coverage added to their ldflags
+ linkCoverage bool
+}
+
+func (cov *coverage) props() []interface{} {
+ return []interface{}{&cov.Properties}
+}
+
+func (cov *coverage) begin(ctx BaseModuleContext) {}
+
+func (cov *coverage) deps(ctx BaseModuleContext, deps Deps) Deps {
+ return deps
+}
+
+func (cov *coverage) flags(ctx ModuleContext, flags Flags) Flags {
+ if !ctx.DeviceConfig().NativeCoverageEnabled() {
+ return flags
+ }
+
+ if cov.Properties.CoverageEnabled {
+ flags.Coverage = true
+ flags.GlobalFlags = append(flags.GlobalFlags, "--coverage", "-O0")
+ cov.linkCoverage = true
+ }
+
+ // Even if we don't have coverage enabled, if any of our object files were compiled
+ // with coverage, then we need to add --coverage to our ldflags.
+ if !cov.linkCoverage {
+ if ctx.static() && !ctx.staticBinary() {
+ // For static libraries, the only thing that changes our object files
+ // are included whole static libraries, so check to see if any of
+ // those have coverage enabled.
+ ctx.VisitDirectDeps(func(m blueprint.Module) {
+ if ctx.OtherModuleDependencyTag(m) != wholeStaticDepTag {
+ return
+ }
+
+ if cc, ok := m.(*Module); ok && cc.coverage != nil {
+ if cc.coverage.linkCoverage {
+ cov.linkCoverage = true
+ }
+ }
+ })
+ } else {
+ // For executables and shared libraries, we need to check all of
+ // our static dependencies.
+ ctx.VisitDirectDeps(func(m blueprint.Module) {
+ cc, ok := m.(*Module)
+ if !ok || cc.coverage == nil {
+ return
+ }
+
+ if static, ok := cc.linker.(libraryInterface); !ok || !static.static() {
+ return
+ }
+
+ if cc.coverage.linkCoverage {
+ cov.linkCoverage = true
+ }
+ })
+ }
+ }
+
+ if cov.linkCoverage {
+ flags.LdFlags = append(flags.LdFlags, "--coverage")
+ }
+
+ return flags
+}
+
+func coverageLinkingMutator(mctx android.BottomUpMutatorContext) {
+ if c, ok := mctx.Module().(*Module); ok && c.coverage != nil {
+ var enabled bool
+
+ if !mctx.DeviceConfig().NativeCoverageEnabled() {
+ // Coverage is disabled globally
+ } else if mctx.Host() {
+ // TODO(dwillemsen): because of -nodefaultlibs, we must depend on libclang_rt.profile-*.a
+ // Just turn off for now.
+ } else if c.coverage.Properties.Native_coverage != nil {
+ enabled = *c.coverage.Properties.Native_coverage
+ } else {
+ enabled = mctx.DeviceConfig().CoverageEnabledForPath(mctx.ModuleDir())
+ }
+
+ if enabled {
+ // Create a variation so that we don't need to recompile objects
+ // when turning on or off coverage. We'll still relink the necessary
+ // binaries, since we don't know which ones those are until later.
+ m := mctx.CreateLocalVariations("cov")
+ m[0].(*Module).coverage.Properties.CoverageEnabled = true
+ }
+ }
+}
diff --git a/cc/gen_stub_libs.py b/cc/gen_stub_libs.py
index d6cd973..7c70406 100755
--- a/cc/gen_stub_libs.py
+++ b/cc/gen_stub_libs.py
@@ -79,7 +79,7 @@
return version.endswith('_PRIVATE') or version.endswith('_PLATFORM')
-def should_omit_version(name, tags, arch, api):
+def should_omit_version(name, tags, arch, api, vndk):
"""Returns True if the version section should be ommitted.
We want to omit any sections that do not have any symbols we'll have in the
@@ -90,6 +90,8 @@
return True
if 'platform-only' in tags:
return True
+ if 'vndk' in tags and not vndk:
+ return True
if not symbol_in_arch(tags, arch):
return True
if not symbol_in_api(tags, arch, api):
@@ -271,11 +273,12 @@
class Generator(object):
"""Output generator that writes stub source files and version scripts."""
- def __init__(self, src_file, version_script, arch, api):
+ def __init__(self, src_file, version_script, arch, api, vndk):
self.src_file = src_file
self.version_script = version_script
self.arch = arch
self.api = api
+ self.vndk = vndk
def write(self, versions):
"""Writes all symbol data to the output files."""
@@ -286,13 +289,15 @@
"""Writes a single version block's data to the output files."""
name = version.name
tags = version.tags
- if should_omit_version(name, tags, self.arch, self.api):
+ if should_omit_version(name, tags, self.arch, self.api, self.vndk):
return
section_versioned = symbol_versioned_in_api(tags, self.api)
version_empty = True
pruned_symbols = []
for symbol in version.symbols:
+ if not self.vndk and 'vndk' in symbol.tags:
+ continue
if not symbol_in_arch(symbol.tags, self.arch):
continue
if not symbol_in_api(symbol.tags, self.arch, self.api):
@@ -333,6 +338,8 @@
parser.add_argument(
'--arch', choices=ALL_ARCHITECTURES, required=True,
help='Architecture being targeted.')
+ parser.add_argument(
+ '--vndk', action='store_true', help='Use the VNDK variant.')
parser.add_argument(
'symbol_file', type=os.path.realpath, help='Path to symbol file.')
@@ -361,7 +368,8 @@
with open(args.stub_src, 'w') as src_file:
with open(args.version_script, 'w') as version_file:
- generator = Generator(src_file, version_file, args.arch, args.api)
+ generator = Generator(src_file, version_file, args.arch, args.api,
+ args.vndk)
generator.write(versions)
diff --git a/cc/installer.go b/cc/installer.go
index 64f87d9..112a7ea 100644
--- a/cc/installer.go
+++ b/cc/installer.go
@@ -30,8 +30,9 @@
type installLocation int
const (
- InstallInSystem installLocation = 0
- InstallInData = iota
+ InstallInSystem installLocation = 0
+ InstallInData = iota
+ InstallInSanitizerDir = iota
)
func NewBaseInstaller(dir, dir64 string, location installLocation) *baseInstaller {
@@ -67,6 +68,9 @@
if !ctx.Host() && !ctx.Arch().Native {
subDir = filepath.Join(subDir, ctx.Arch().ArchType.String())
}
+ if installer.location == InstallInData && ctx.vndk() {
+ subDir = filepath.Join(subDir, "vendor")
+ }
return android.PathForModuleInstall(ctx, subDir, installer.Properties.Relative_install_path, installer.relative)
}
@@ -78,6 +82,10 @@
return installer.location == InstallInData
}
+func (installer *baseInstaller) inSanitizerDir() bool {
+ return installer.location == InstallInSanitizerDir
+}
+
func (installer *baseInstaller) hostToolPath() android.OptionalPath {
return android.OptionalPath{}
}
diff --git a/cc/library.go b/cc/library.go
index 8474f96..0ba7088 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -64,7 +64,9 @@
// export headers generated from .proto sources
Export_proto_headers bool
}
+}
+type LibraryMutatedProperties struct {
VariantName string `blueprint:"mutated"`
// Build a static variant
@@ -82,6 +84,16 @@
// be added to the include path (using -I) for this module and any module that links
// against this module
Export_include_dirs []string `android:"arch_variant"`
+
+ Target struct {
+ Vendor struct {
+ // list of exported include directories, like
+ // export_include_dirs, that will be applied to the
+ // vendor variant of this library. This will overwrite
+ // any other declarations.
+ Export_include_dirs []string
+ }
+ }
}
func init() {
@@ -142,8 +154,16 @@
flagsDeps android.Paths
}
+func (f *flagExporter) exportedIncludes(ctx ModuleContext) android.Paths {
+ if ctx.Vendor() && f.Properties.Target.Vendor.Export_include_dirs != nil {
+ return android.PathsForModuleSrc(ctx, f.Properties.Target.Vendor.Export_include_dirs)
+ } else {
+ return android.PathsForModuleSrc(ctx, f.Properties.Export_include_dirs)
+ }
+}
+
func (f *flagExporter) exportIncludes(ctx ModuleContext, inc string) {
- includeDirs := android.PathsForModuleSrc(ctx, f.Properties.Export_include_dirs)
+ includeDirs := f.exportedIncludes(ctx)
for _, dir := range includeDirs.Strings() {
f.flags = append(f.flags, inc+dir)
}
@@ -175,7 +195,8 @@
// libraryDecorator wraps baseCompiler, baseLinker and baseInstaller to provide library-specific
// functionality: static vs. shared linkage, reusing object files for shared libraries
type libraryDecorator struct {
- Properties LibraryProperties
+ Properties LibraryProperties
+ MutatedProperties LibraryMutatedProperties
// For reusing static library objects for shared library
reuseObjects Objects
@@ -199,6 +220,9 @@
sanitize *sanitize
+ // Output archive of gcno coverage information files
+ coverageOutputFile android.OptionalPath
+
// Decorated interafaces
*baseCompiler
*baseLinker
@@ -210,6 +234,7 @@
props = append(props, library.baseLinker.linkerProps()...)
return append(props,
&library.Properties,
+ &library.MutatedProperties,
&library.flagExporter.Properties,
&library.stripper.StripProperties,
&library.relocationPacker.Properties)
@@ -221,17 +246,17 @@
// MinGW spits out warnings about -fPIC even for -fpie?!) being ignored because
// all code is position independent, and then those warnings get promoted to
// errors.
- if ctx.Os() != android.Windows {
+ if !ctx.Windows() {
flags.CFlags = append(flags.CFlags, "-fPIC")
}
if library.static() {
flags.CFlags = append(flags.CFlags, library.Properties.Static.Cflags...)
- } else {
+ } else if library.shared() {
flags.CFlags = append(flags.CFlags, library.Properties.Shared.Cflags...)
}
- if !library.static() {
+ if library.shared() {
libName := library.getLibName(ctx)
// GCC for Android assumes that -shared means -Bsymbolic, use -Wl,-shared instead
sharedFlag := "-Wl,-shared"
@@ -239,7 +264,7 @@
sharedFlag = "-shared"
}
var f []string
- if ctx.Device() {
+ if ctx.toolchain().Bionic() {
f = append(f,
"-nostdlib",
"-Wl,--gc-sections",
@@ -270,7 +295,7 @@
}
func (library *libraryDecorator) compilerFlags(ctx ModuleContext, flags Flags) Flags {
- exportIncludeDirs := android.PathsForModuleSrc(ctx, library.flagExporter.Properties.Export_include_dirs)
+ exportIncludeDirs := library.flagExporter.exportedIncludes(ctx)
if len(exportIncludeDirs) > 0 {
flags.GlobalFlags = append(flags.GlobalFlags, includeDirsToFlags(exportIncludeDirs))
}
@@ -300,7 +325,7 @@
srcs := android.PathsForModuleSrc(ctx, library.Properties.Static.Srcs)
objs = objs.Append(compileObjs(ctx, buildFlags, android.DeviceStaticLibrary,
srcs, library.baseCompiler.deps))
- } else {
+ } else if library.shared() {
srcs := android.PathsForModuleSrc(ctx, library.Properties.Shared.Srcs)
objs = objs.Append(compileObjs(ctx, buildFlags, android.DeviceSharedLibrary,
srcs, library.baseCompiler.deps))
@@ -321,7 +346,8 @@
buildShared() bool
// Sets whether a specific variant is static or shared
- setStatic(bool)
+ setStatic()
+ setShared()
}
func (library *libraryDecorator) getLibName(ctx ModuleContext) string {
@@ -336,13 +362,13 @@
}
}
- return name + library.Properties.VariantName
+ return name + library.MutatedProperties.VariantName
}
func (library *libraryDecorator) linkerInit(ctx BaseModuleContext) {
location := InstallInSystem
- if library.sanitize.inData() {
- location = InstallInData
+ if library.sanitize.inSanitizerDir() {
+ location = InstallInSanitizerDir
}
library.baseInstaller.location = location
@@ -359,9 +385,9 @@
library.Properties.Static.Whole_static_libs...)
deps.StaticLibs = append(deps.StaticLibs, library.Properties.Static.Static_libs...)
deps.SharedLibs = append(deps.SharedLibs, library.Properties.Static.Shared_libs...)
- } else {
+ } else if library.shared() {
if ctx.toolchain().Bionic() && !Bool(library.baseLinker.Properties.Nocrt) {
- if !ctx.sdk() && !ctx.vndk() {
+ if !ctx.sdk() {
deps.CrtBegin = "crtbegin_so"
deps.CrtEnd = "crtend_so"
} else {
@@ -392,13 +418,13 @@
library.objects = library.objects.Append(objs)
outputFile := android.PathForModuleOut(ctx,
- ctx.ModuleName()+library.Properties.VariantName+staticLibraryExtension)
+ ctx.ModuleName()+library.MutatedProperties.VariantName+staticLibraryExtension)
+ builderFlags := flagsToBuilderFlags(flags)
- if ctx.Darwin() {
- TransformDarwinObjToStaticLib(ctx, library.objects.objFiles, flagsToBuilderFlags(flags), outputFile, objs.tidyFiles)
- } else {
- TransformObjToStaticLib(ctx, library.objects.objFiles, flagsToBuilderFlags(flags), outputFile, objs.tidyFiles)
- }
+ TransformObjToStaticLib(ctx, library.objects.objFiles, builderFlags, outputFile, objs.tidyFiles)
+
+ library.coverageOutputFile = TransformCoverageFilesToLib(ctx, library.objects, builderFlags,
+ ctx.ModuleName()+library.MutatedProperties.VariantName)
library.wholeStaticMissingDeps = ctx.GetMissingDependencies()
@@ -454,7 +480,7 @@
builderFlags := flagsToBuilderFlags(flags)
- if !ctx.Darwin() {
+ if !ctx.Darwin() && !ctx.Windows() {
// Optimize out relinking against shared libraries whose interface hasn't changed by
// depending on a table of contents file instead of the library itself.
tocPath := outputFile.RelPathString()
@@ -506,6 +532,10 @@
deps.StaticLibs, deps.LateStaticLibs, deps.WholeStaticLibs,
linkerDeps, deps.CrtBegin, deps.CrtEnd, false, builderFlags, outputFile)
+ objs.coverageFiles = append(objs.coverageFiles, deps.StaticLibObjs.coverageFiles...)
+ objs.coverageFiles = append(objs.coverageFiles, deps.WholeStaticLibObjs.coverageFiles...)
+ library.coverageOutputFile = TransformCoverageFilesToLib(ctx, objs, builderFlags, library.getLibName(ctx))
+
return ret
}
@@ -515,7 +545,7 @@
objs = objs.Append(deps.Objs)
var out android.Path
- if library.static() {
+ if library.static() || library.header() {
out = library.linkStatic(ctx, flags, deps, objs)
} else {
out = library.linkShared(ctx, flags, deps, objs)
@@ -548,12 +578,12 @@
}
func (library *libraryDecorator) buildStatic() bool {
- return library.Properties.BuildStatic &&
+ return library.MutatedProperties.BuildStatic &&
(library.Properties.Static.Enabled == nil || *library.Properties.Static.Enabled)
}
func (library *libraryDecorator) buildShared() bool {
- return library.Properties.BuildShared &&
+ return library.MutatedProperties.BuildShared &&
(library.Properties.Shared.Enabled == nil || *library.Properties.Shared.Enabled)
}
@@ -580,31 +610,45 @@
}
func (library *libraryDecorator) static() bool {
- return library.Properties.VariantIsStatic
+ return library.MutatedProperties.VariantIsStatic
}
-func (library *libraryDecorator) setStatic(static bool) {
- library.Properties.VariantIsStatic = static
+func (library *libraryDecorator) shared() bool {
+ return library.MutatedProperties.VariantIsShared
+}
+
+func (library *libraryDecorator) header() bool {
+ return !library.static() && !library.shared()
+}
+
+func (library *libraryDecorator) setStatic() {
+ library.MutatedProperties.VariantIsStatic = true
+ library.MutatedProperties.VariantIsShared = false
+}
+
+func (library *libraryDecorator) setShared() {
+ library.MutatedProperties.VariantIsStatic = false
+ library.MutatedProperties.VariantIsShared = true
}
func (library *libraryDecorator) BuildOnlyStatic() {
- library.Properties.BuildShared = false
+ library.MutatedProperties.BuildShared = false
}
func (library *libraryDecorator) BuildOnlyShared() {
- library.Properties.BuildStatic = false
+ library.MutatedProperties.BuildStatic = false
}
func (library *libraryDecorator) HeaderOnly() {
- library.Properties.BuildShared = false
- library.Properties.BuildStatic = false
+ library.MutatedProperties.BuildShared = false
+ library.MutatedProperties.BuildStatic = false
}
func NewLibrary(hod android.HostOrDeviceSupported) (*Module, *libraryDecorator) {
module := newModule(hod, android.MultilibBoth)
library := &libraryDecorator{
- Properties: LibraryProperties{
+ MutatedProperties: LibraryMutatedProperties{
BuildShared: true,
BuildStatic: true,
},
@@ -630,8 +674,8 @@
static := modules[0].(*Module)
shared := modules[1].(*Module)
- static.linker.(libraryInterface).setStatic(true)
- shared.linker.(libraryInterface).setStatic(false)
+ static.linker.(libraryInterface).setStatic()
+ shared.linker.(libraryInterface).setShared()
if staticCompiler, ok := static.compiler.(*libraryDecorator); ok {
sharedCompiler := shared.compiler.(*libraryDecorator)
@@ -645,10 +689,10 @@
}
} else if library.buildStatic() {
modules = mctx.CreateLocalVariations("static")
- modules[0].(*Module).linker.(libraryInterface).setStatic(true)
+ modules[0].(*Module).linker.(libraryInterface).setStatic()
} else if library.buildShared() {
modules = mctx.CreateLocalVariations("shared")
- modules[0].(*Module).linker.(libraryInterface).setStatic(false)
+ modules[0].(*Module).linker.(libraryInterface).setShared()
}
}
}
diff --git a/cc/linker.go b/cc/linker.go
index 4619926..5a3b478 100644
--- a/cc/linker.go
+++ b/cc/linker.go
@@ -143,7 +143,7 @@
if linker.Properties.System_shared_libs != nil {
deps.LateSharedLibs = append(deps.LateSharedLibs,
linker.Properties.System_shared_libs...)
- } else if !ctx.sdk() {
+ } else if !ctx.sdk() && !ctx.vndk() {
deps.LateSharedLibs = append(deps.LateSharedLibs, "libc", "libm")
}
}
@@ -154,6 +154,13 @@
"libm",
)
}
+ if ctx.vndk() {
+ deps.LateSharedLibs = append(deps.LateSharedLibs, "libc", "libm")
+ }
+ }
+
+ if ctx.Windows() {
+ deps.LateStaticLibs = append(deps.LateStaticLibs, "libwinpthread")
}
return deps
diff --git a/cc/llndk_library.go b/cc/llndk_library.go
new file mode 100644
index 0000000..cde1bc7
--- /dev/null
+++ b/cc/llndk_library.go
@@ -0,0 +1,164 @@
+// Copyright 2017 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cc
+
+import (
+ "path/filepath"
+ "strings"
+
+ "github.com/google/blueprint"
+
+ "android/soong/android"
+)
+
+var (
+ llndkLibrarySuffix = ".llndk"
+)
+
+// Creates a stub shared library based on the provided version file.
+//
+// Example:
+//
+// llndk_library {
+// name: "libfoo",
+// symbol_file: "libfoo.map.txt",
+// export_include_dirs: ["include_vndk"],
+// }
+//
+type llndkLibraryProperties struct {
+ // Relative path to the symbol map.
+ // An example file can be seen here: TODO(danalbert): Make an example.
+ Symbol_file string
+
+ // Whether to export any headers as -isystem instead of -I. Mainly for use by
+ // bionic/libc.
+ Export_headers_as_system bool
+
+ // Which headers to process with versioner. This really only handles
+ // bionic/libc/include right now.
+ Export_preprocessed_headers []string
+
+ // Whether the system library uses symbol versions.
+ Unversioned bool
+}
+
+type llndkStubDecorator struct {
+ *libraryDecorator
+
+ Properties llndkLibraryProperties
+
+ exportHeadersTimestamp android.OptionalPath
+ versionScriptPath android.ModuleGenPath
+}
+
+func (stub *llndkStubDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) Objects {
+ objs, versionScript := compileStubLibrary(ctx, flags, stub.Properties.Symbol_file, "current", "--vndk")
+ stub.versionScriptPath = versionScript
+ return objs
+}
+
+func (stub *llndkStubDecorator) linkerDeps(ctx DepsContext, deps Deps) Deps {
+ return Deps{}
+}
+
+func (stub *llndkStubDecorator) Name(name string) string {
+ return name + llndkLibrarySuffix
+}
+
+func (stub *llndkStubDecorator) linkerFlags(ctx ModuleContext, flags Flags) Flags {
+ stub.libraryDecorator.libName = strings.TrimSuffix(ctx.ModuleName(),
+ llndkLibrarySuffix)
+ return stub.libraryDecorator.linkerFlags(ctx, flags)
+}
+
+func (stub *llndkStubDecorator) processHeaders(ctx ModuleContext, srcHeaderDir string, outDir android.ModuleGenPath) android.Path {
+ srcDir := android.PathForModuleSrc(ctx, srcHeaderDir)
+ srcFiles := ctx.Glob(filepath.Join(srcDir.String(), "**/*.h"), nil)
+
+ var installPaths []android.WritablePath
+ for _, header := range srcFiles {
+ headerDir := filepath.Dir(header.String())
+ relHeaderDir, err := filepath.Rel(srcDir.String(), headerDir)
+ if err != nil {
+ ctx.ModuleErrorf("filepath.Rel(%q, %q) failed: %s",
+ srcDir.String(), headerDir, err)
+ continue
+ }
+
+ installPaths = append(installPaths, outDir.Join(ctx, relHeaderDir, header.Base()))
+ }
+
+ return processHeadersWithVersioner(ctx, srcDir, outDir, srcFiles, installPaths)
+}
+
+func (stub *llndkStubDecorator) link(ctx ModuleContext, flags Flags, deps PathDeps,
+ objs Objects) android.Path {
+
+ if !stub.Properties.Unversioned {
+ linkerScriptFlag := "-Wl,--version-script," + stub.versionScriptPath.String()
+ flags.LdFlags = append(flags.LdFlags, linkerScriptFlag)
+ }
+
+ if len(stub.Properties.Export_preprocessed_headers) > 0 {
+ genHeaderOutDir := android.PathForModuleGen(ctx, "include")
+
+ var timestampFiles android.Paths
+ for _, dir := range stub.Properties.Export_preprocessed_headers {
+ timestampFiles = append(timestampFiles, stub.processHeaders(ctx, dir, genHeaderOutDir))
+ }
+
+ includePrefix := "-I "
+ if stub.Properties.Export_headers_as_system {
+ includePrefix = "-isystem "
+ }
+
+ stub.reexportFlags([]string{includePrefix + " " + genHeaderOutDir.String()})
+ stub.reexportDeps(timestampFiles)
+ }
+
+ if stub.Properties.Export_headers_as_system {
+ stub.exportIncludes(ctx, "-isystem")
+ stub.libraryDecorator.flagExporter.Properties.Export_include_dirs = []string{}
+ }
+
+ return stub.libraryDecorator.link(ctx, flags, deps, objs)
+}
+
+func newLLndkStubLibrary() (*Module, []interface{}) {
+ module, library := NewLibrary(android.DeviceSupported)
+ library.BuildOnlyShared()
+ module.stl = nil
+ module.sanitize = nil
+ library.StripProperties.Strip.None = true
+
+ stub := &llndkStubDecorator{
+ libraryDecorator: library,
+ }
+ module.compiler = stub
+ module.linker = stub
+ module.installer = nil
+
+ return module, []interface{}{&stub.Properties, &library.MutatedProperties, &library.flagExporter.Properties}
+}
+
+func llndkLibraryFactory() (blueprint.Module, []interface{}) {
+ module, properties := newLLndkStubLibrary()
+ return android.InitAndroidArchModule(module, android.DeviceSupported,
+ android.MultilibBoth, properties...)
+}
+
+func init() {
+ android.RegisterModuleType("llndk_library", llndkLibraryFactory)
+}
diff --git a/cc/makevars.go b/cc/makevars.go
index 2e64e3c..ce2ac5a 100644
--- a/cc/makevars.go
+++ b/cc/makevars.go
@@ -16,7 +16,6 @@
import (
"fmt"
- "path/filepath"
"sort"
"strings"
@@ -40,6 +39,13 @@
ctx.Strict("PATH_TO_CLANG_TIDY", "${config.ClangBin}/clang-tidy")
ctx.StrictSorted("CLANG_CONFIG_UNKNOWN_CFLAGS", strings.Join(config.ClangUnknownCflags, " "))
+ ctx.Strict("RS_LLVM_PREBUILTS_VERSION", "${config.RSClangVersion}")
+ ctx.Strict("RS_LLVM_PREBUILTS_BASE", "${config.RSClangBase}")
+ ctx.Strict("RS_LLVM_PREBUILTS_PATH", "${config.RSLLVMPrebuiltsPath}")
+ ctx.Strict("RS_CLANG", "${config.RSLLVMPrebuiltsPath}/clang")
+ ctx.Strict("RS_LLVM_AS", "${config.RSLLVMPrebuiltsPath}/llvm-as")
+ ctx.Strict("RS_LLVM_LINK", "${config.RSLLVMPrebuiltsPath}/llvm-link")
+
ctx.Strict("GLOBAL_CFLAGS_NO_OVERRIDE", "${config.NoOverrideGlobalCflags}")
ctx.Strict("GLOBAL_CLANG_CFLAGS_NO_OVERRIDE", "${config.ClangExtraNoOverrideCflags}")
ctx.Strict("GLOBAL_CPPFLAGS_NO_OVERRIDE", "")
@@ -51,15 +57,19 @@
} else {
ctx.Strict("BOARD_VNDK_VERSION", "")
}
- ctx.Strict("VNDK_LIBRARIES", strings.Join(config.VndkLibraries(), " "))
ctx.Strict("ADDRESS_SANITIZER_CONFIG_EXTRA_CFLAGS", asanCflags)
ctx.Strict("ADDRESS_SANITIZER_CONFIG_EXTRA_LDFLAGS", asanLdflags)
ctx.Strict("ADDRESS_SANITIZER_CONFIG_EXTRA_STATIC_LIBRARIES", asanLibs)
+ ctx.Strict("CFI_EXTRA_CFLAGS", cfiCflags)
+ ctx.Strict("CFI_EXTRA_LDFLAGS", cfiLdflags)
+
ctx.Strict("DEFAULT_C_STD_VERSION", config.CStdVersion)
ctx.Strict("DEFAULT_CPP_STD_VERSION", config.CppStdVersion)
ctx.Strict("DEFAULT_GCC_CPP_STD_VERSION", config.GccCppStdVersion)
+ ctx.Strict("EXPERIMENTAL_C_STD_VERSION", config.ExperimentalCStdVersion)
+ ctx.Strict("EXPERIMENTAL_CPP_STD_VERSION", config.ExperimentalCppStdVersion)
ctx.Strict("DEFAULT_GLOBAL_TIDY_CHECKS", "${config.TidyDefaultGlobalChecks}")
ctx.Strict("DEFAULT_LOCAL_TIDY_CHECKS", joinLocalTidyChecks(config.DefaultLocalTidyChecks))
@@ -174,9 +184,7 @@
if toolchain.ClangSupported() {
clangPrefix := secondPrefix + "CLANG_" + typePrefix
clangExtras := "-target " + toolchain.ClangTriple()
- if target.Os != android.Darwin {
- clangExtras += " -B" + filepath.Join(toolchain.GccRoot(), toolchain.GccTriple(), "bin")
- }
+ clangExtras += " -B" + config.ToolPath(toolchain)
ctx.Strict(clangPrefix+"GLOBAL_CFLAGS", strings.Join([]string{
toolchain.ClangCflags(),
@@ -200,6 +208,7 @@
if target.Os.Class == android.Device {
ctx.Strict(secondPrefix+"ADDRESS_SANITIZER_RUNTIME_LIBRARY", strings.TrimSuffix(config.AddressSanitizerRuntimeLibrary(toolchain), ".so"))
ctx.Strict(secondPrefix+"UBSAN_RUNTIME_LIBRARY", strings.TrimSuffix(config.UndefinedBehaviorSanitizerRuntimeLibrary(toolchain), ".so"))
+ ctx.Strict(secondPrefix+"TSAN_RUNTIME_LIBRARY", strings.TrimSuffix(config.ThreadSanitizerRuntimeLibrary(toolchain), ".so"))
}
// This is used by external/gentoo/...
diff --git a/cc/ndk_headers.go b/cc/ndk_headers.go
index 9cc3417..deb735a 100644
--- a/cc/ndk_headers.go
+++ b/cc/ndk_headers.go
@@ -15,6 +15,8 @@
package cc
import (
+ "fmt"
+ "os"
"path/filepath"
"github.com/google/blueprint"
@@ -22,6 +24,22 @@
"android/soong/android"
)
+var (
+ preprocessBionicHeaders = pctx.AndroidStaticRule("preprocessBionicHeaders",
+ blueprint.RuleParams{
+ // The `&& touch $out` isn't really necessary, but Blueprint won't
+ // let us have only implicit outputs.
+ Command: "$versionerCmd -o $outDir $srcDir $depsPath && touch $out",
+ CommandDeps: []string{"$versionerCmd"},
+ Description: "versioner preprocess $in",
+ },
+ "depsPath", "srcDir", "outDir")
+)
+
+func init() {
+ pctx.HostBinToolVariable("versionerCmd", "versioner")
+}
+
// Returns the NDK base include path for use with sdk_version current. Usable with -I.
func getCurrentIncludePath(ctx android.ModuleContext) android.OutputPath {
return getNdkSysrootBase(ctx).Join(ctx, "usr/include")
@@ -63,6 +81,38 @@
func (m *headerModule) DepsMutator(ctx android.BottomUpMutatorContext) {
}
+func getHeaderInstallDir(ctx android.ModuleContext, header android.Path, from string,
+ to string) android.OutputPath {
+ // Output path is the sysroot base + "usr/include" + to directory + directory component
+ // of the file without the leading from directory stripped.
+ //
+ // Given:
+ // sysroot base = "ndk/sysroot"
+ // from = "include/foo"
+ // to = "bar"
+ // header = "include/foo/woodly/doodly.h"
+ // output path = "ndk/sysroot/usr/include/bar/woodly/doodly.h"
+
+ // full/platform/path/to/include/foo
+ fullFromPath := android.PathForModuleSrc(ctx, from)
+
+ // full/platform/path/to/include/foo/woodly
+ headerDir := filepath.Dir(header.String())
+
+ // woodly
+ strippedHeaderDir, err := filepath.Rel(fullFromPath.String(), headerDir)
+ if err != nil {
+ ctx.ModuleErrorf("filepath.Rel(%q, %q) failed: %s", headerDir,
+ fullFromPath.String(), err)
+ }
+
+ // full/platform/path/to/sysroot/usr/include/bar/woodly
+ installDir := getCurrentIncludePath(ctx).Join(ctx, to, strippedHeaderDir)
+
+ // full/platform/path/to/sysroot/usr/include/bar/woodly/doodly.h
+ return installDir
+}
+
func (m *headerModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
if m.properties.License == "" {
ctx.PropertyErrorf("license", "field is required")
@@ -72,34 +122,14 @@
srcFiles := ctx.ExpandSources(m.properties.Srcs, nil)
for _, header := range srcFiles {
- // Output path is the sysroot base + "usr/include" + to directory + directory component
- // of the file without the leading from directory stripped.
- //
- // Given:
- // sysroot base = "ndk/sysroot"
- // from = "include/foo"
- // to = "bar"
- // header = "include/foo/woodly/doodly.h"
- // output path = "ndk/sysroot/usr/include/bar/woodly/doodly.h"
-
- // full/platform/path/to/include/foo
- fullFromPath := android.PathForModuleSrc(ctx, m.properties.From)
-
- // full/platform/path/to/include/foo/woodly
- headerDir := filepath.Dir(header.String())
-
- // woodly
- strippedHeaderDir, err := filepath.Rel(fullFromPath.String(), headerDir)
- if err != nil {
- ctx.ModuleErrorf("filepath.Rel(%q, %q) failed: %s", headerDir,
- fullFromPath.String(), err)
+ installDir := getHeaderInstallDir(ctx, header, m.properties.From, m.properties.To)
+ installedPath := ctx.InstallFile(installDir, header)
+ installPath := installDir.Join(ctx, header.Base())
+ if installPath != installedPath {
+ panic(fmt.Sprintf(
+ "expected header install path (%q) not equal to actual install path %q",
+ installPath, installedPath))
}
-
- // full/platform/path/to/sysroot/usr/include/bar/woodly
- installDir := getCurrentIncludePath(ctx).Join(ctx, m.properties.To, strippedHeaderDir)
-
- // full/platform/path/to/sysroot/usr/include/bar/woodly/doodly.h
- installPath := ctx.InstallFile(installDir, header)
m.installPaths = append(m.installPaths, installPath.String())
}
@@ -110,6 +140,119 @@
func ndkHeadersFactory() (blueprint.Module, []interface{}) {
module := &headerModule{}
+ return android.InitAndroidModule(module, &module.properties)
+}
+
+type preprocessedHeaderProperies struct {
+ // Base directory of the headers being installed. As an example:
+ //
+ // preprocessed_ndk_headers {
+ // name: "foo",
+ // from: "include",
+ // to: "",
+ // }
+ //
+ // Will install $SYSROOT/usr/include/foo/bar/baz.h. If `from` were instead
+ // "include/foo", it would have installed $SYSROOT/usr/include/bar/baz.h.
+ From string
+
+ // Install path within the sysroot. This is relative to usr/include.
+ To string
+
+ // Path to the NOTICE file associated with the headers.
+ License string
+}
+
+// Like ndk_headers, but preprocesses the headers with the bionic versioner:
+// https://android.googlesource.com/platform/bionic/+/master/tools/versioner/README.md.
+//
+// Unlike ndk_headers, we don't operate on a list of sources but rather a whole directory, the
+// module does not have the srcs property, and operates on a full directory (the `from` property).
+//
+// Note that this is really only built to handle bionic/libc/include.
+type preprocessedHeaderModule struct {
+ android.ModuleBase
+
+ properties preprocessedHeaderProperies
+
+ installPaths []string
+ licensePath android.ModuleSrcPath
+}
+
+func (m *preprocessedHeaderModule) DepsMutator(ctx android.BottomUpMutatorContext) {
+}
+
+func (m *preprocessedHeaderModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+ if m.properties.License == "" {
+ ctx.PropertyErrorf("license", "field is required")
+ }
+
+ m.licensePath = android.PathForModuleSrc(ctx, m.properties.License)
+
+ fromSrcPath := android.PathForModuleSrc(ctx, m.properties.From)
+ toOutputPath := getCurrentIncludePath(ctx).Join(ctx, m.properties.To)
+ srcFiles := ctx.Glob(filepath.Join(fromSrcPath.String(), "**/*.h"), nil)
+ var installPaths []android.WritablePath
+ for _, header := range srcFiles {
+ installDir := getHeaderInstallDir(ctx, header, m.properties.From, m.properties.To)
+ installPath := installDir.Join(ctx, header.Base())
+ installPaths = append(installPaths, installPath)
+ m.installPaths = append(m.installPaths, installPath.String())
+ }
+
+ if len(m.installPaths) == 0 {
+ ctx.ModuleErrorf("glob %q matched zero files", m.properties.From)
+ }
+
+ processHeadersWithVersioner(ctx, fromSrcPath, toOutputPath, srcFiles, installPaths)
+}
+
+func processHeadersWithVersioner(ctx android.ModuleContext, srcDir, outDir android.Path, srcFiles android.Paths, installPaths []android.WritablePath) android.Path {
+ // The versioner depends on a dependencies directory to simplify determining include paths
+ // when parsing headers. This directory contains architecture specific directories as well
+ // as a common directory, each of which contains symlinks to the actually directories to
+ // be included.
+ //
+ // ctx.Glob doesn't follow symlinks, so we need to do this ourselves so we correctly
+ // depend on these headers.
+ // TODO(http://b/35673191): Update the versioner to use a --sysroot.
+ depsPath := android.PathForSource(ctx, "bionic/libc/versioner-dependencies")
+ depsGlob := ctx.Glob(filepath.Join(depsPath.String(), "**/*"), nil)
+ for i, path := range depsGlob {
+ fileInfo, err := os.Lstat(path.String())
+ if err != nil {
+ ctx.ModuleErrorf("os.Lstat(%q) failed: %s", path.String, err)
+ }
+ if fileInfo.Mode()&os.ModeSymlink == os.ModeSymlink {
+ dest, err := os.Readlink(path.String())
+ if err != nil {
+ ctx.ModuleErrorf("os.Readlink(%q) failed: %s",
+ path.String, err)
+ }
+ // Additional .. to account for the symlink itself.
+ depsGlob[i] = android.PathForSource(
+ ctx, filepath.Clean(filepath.Join(path.String(), "..", dest)))
+ }
+ }
+
+ timestampFile := android.PathForModuleOut(ctx, "versioner.timestamp")
+ ctx.ModuleBuild(pctx, android.ModuleBuildParams{
+ Rule: preprocessBionicHeaders,
+ Output: timestampFile,
+ Implicits: append(srcFiles, depsGlob...),
+ ImplicitOutputs: installPaths,
+ Args: map[string]string{
+ "depsPath": depsPath.String(),
+ "srcDir": srcDir.String(),
+ "outDir": outDir.String(),
+ },
+ })
+
+ return timestampFile
+}
+
+func preprocessedNdkHeadersFactory() (blueprint.Module, []interface{}) {
+ module := &preprocessedHeaderModule{}
// Host module rather than device module because device module install steps
// do not get run when embedded in make. We're not any of the existing
// module types that can be exposed via the Android.mk exporter, so just use
diff --git a/cc/ndk_library.go b/cc/ndk_library.go
index da3d5c7..f5499dd 100644
--- a/cc/ndk_library.go
+++ b/cc/ndk_library.go
@@ -30,10 +30,10 @@
genStubSrc = pctx.AndroidStaticRule("genStubSrc",
blueprint.RuleParams{
- Command: "$toolPath --arch $arch --api $apiLevel $in $out",
+ Command: "$toolPath --arch $arch --api $apiLevel $vndk $in $out",
Description: "genStubSrc $out",
CommandDeps: []string{"$toolPath"},
- }, "arch", "apiLevel")
+ }, "arch", "apiLevel", "vndk")
ndkLibrarySuffix = ".ndk"
@@ -65,15 +65,10 @@
// Creates a stub shared library based on the provided version file.
//
-// The name of the generated file will be based on the module name by stripping
-// the ".ndk" suffix from the module name. Module names must end with ".ndk"
-// (as a convention to allow soong to guess the NDK name of a dependency when
-// needed). "libfoo.ndk" will generate "libfoo.so.
-//
// Example:
//
// ndk_library {
-// name: "libfoo.ndk",
+// name: "libfoo",
// symbol_file: "libfoo.map.txt",
// first_version: "9",
// }
@@ -130,6 +125,16 @@
"x86_64": 21,
}
+ archStr := arch.ArchType.String()
+ firstArchVersion, ok := firstArchVersions[archStr]
+ if !ok {
+ panic(fmt.Errorf("Arch %q not found in firstArchVersions", archStr))
+ }
+
+ if apiLevel == "minimum" {
+ return strconv.Itoa(firstArchVersion), nil
+ }
+
// If the NDK drops support for a platform version, we don't want to have to
// fix up every module that was using it as its SDK version. Clip to the
// supported version here instead.
@@ -139,12 +144,6 @@
}
version = intMax(version, minVersion)
- archStr := arch.ArchType.String()
- firstArchVersion, ok := firstArchVersions[archStr]
- if !ok {
- panic(fmt.Errorf("Arch %q not found in firstArchVersions", archStr))
- }
-
return strconv.Itoa(intMax(version, firstArchVersion)), nil
}
@@ -226,7 +225,11 @@
func (c *stubDecorator) compilerInit(ctx BaseModuleContext) {
c.baseCompiler.compilerInit(ctx)
- name := strings.TrimSuffix(ctx.ModuleName(), ".ndk")
+ name := ctx.baseModuleName()
+ if strings.HasSuffix(name, ndkLibrarySuffix) {
+ ctx.PropertyErrorf("name", "Do not append %q manually, just use the base name", ndkLibrarySuffix)
+ }
+
ndkMigratedLibsLock.Lock()
defer ndkMigratedLibsLock.Unlock()
for _, lib := range ndkMigratedLibs {
@@ -237,28 +240,20 @@
ndkMigratedLibs = append(ndkMigratedLibs, name)
}
-func (c *stubDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) Objects {
+func compileStubLibrary(ctx ModuleContext, flags Flags, symbolFile, apiLevel, vndk string) (Objects, android.ModuleGenPath) {
arch := ctx.Arch().ArchType.String()
- if !strings.HasSuffix(ctx.ModuleName(), ndkLibrarySuffix) {
- ctx.ModuleErrorf("ndk_library modules names must be suffixed with %q\n",
- ndkLibrarySuffix)
- }
- libName := strings.TrimSuffix(ctx.ModuleName(), ndkLibrarySuffix)
- fileBase := fmt.Sprintf("%s.%s.%s", libName, arch, c.properties.ApiLevel)
- stubSrcName := fileBase + ".c"
- stubSrcPath := android.PathForModuleGen(ctx, stubSrcName)
- versionScriptName := fileBase + ".map"
- versionScriptPath := android.PathForModuleGen(ctx, versionScriptName)
- c.versionScriptPath = versionScriptPath
- symbolFilePath := android.PathForModuleSrc(ctx, c.properties.Symbol_file)
+ stubSrcPath := android.PathForModuleGen(ctx, "stub.c")
+ versionScriptPath := android.PathForModuleGen(ctx, "stub.map")
+ symbolFilePath := android.PathForModuleSrc(ctx, symbolFile)
ctx.ModuleBuild(pctx, android.ModuleBuildParams{
Rule: genStubSrc,
Outputs: []android.WritablePath{stubSrcPath, versionScriptPath},
Input: symbolFilePath,
Args: map[string]string{
"arch": arch,
- "apiLevel": c.properties.ApiLevel,
+ "apiLevel": apiLevel,
+ "vndk": vndk,
},
})
@@ -276,16 +271,25 @@
subdir := ""
srcs := []android.Path{stubSrcPath}
- return compileObjs(ctx, flagsToBuilderFlags(flags), subdir, srcs, nil)
+ return compileObjs(ctx, flagsToBuilderFlags(flags), subdir, srcs, nil), versionScriptPath
+}
+
+func (c *stubDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) Objects {
+ objs, versionScript := compileStubLibrary(ctx, flags, c.properties.Symbol_file, c.properties.ApiLevel, "")
+ c.versionScriptPath = versionScript
+ return objs
}
func (linker *stubDecorator) linkerDeps(ctx DepsContext, deps Deps) Deps {
return Deps{}
}
+func (linker *stubDecorator) Name(name string) string {
+ return name + ndkLibrarySuffix
+}
+
func (stub *stubDecorator) linkerFlags(ctx ModuleContext, flags Flags) Flags {
- stub.libraryDecorator.libName = strings.TrimSuffix(ctx.ModuleName(),
- ndkLibrarySuffix)
+ stub.libraryDecorator.libName = ctx.baseModuleName()
return stub.libraryDecorator.linkerFlags(ctx, flags)
}
@@ -335,7 +339,7 @@
module.linker = stub
module.installer = stub
- return module, []interface{}{&stub.properties}
+ return module, []interface{}{&stub.properties, &library.MutatedProperties}
}
func ndkLibraryFactory() (blueprint.Module, []interface{}) {
diff --git a/cc/ndk_prebuilt.go b/cc/ndk_prebuilt.go
index 676d63d..f1bd3b5 100644
--- a/cc/ndk_prebuilt.go
+++ b/cc/ndk_prebuilt.go
@@ -194,7 +194,7 @@
libName := strings.TrimPrefix(ctx.ModuleName(), "ndk_")
libExt := flags.Toolchain.ShlibSuffix()
- if ndk.Properties.BuildStatic {
+ if ndk.static() {
libExt = staticLibraryExtension
}
diff --git a/cc/ndk_sysroot.go b/cc/ndk_sysroot.go
index e82f12b..47dda27 100644
--- a/cc/ndk_sysroot.go
+++ b/cc/ndk_sysroot.go
@@ -61,6 +61,7 @@
func init() {
android.RegisterModuleType("ndk_headers", ndkHeadersFactory)
android.RegisterModuleType("ndk_library", ndkLibraryFactory)
+ android.RegisterModuleType("preprocessed_ndk_headers", preprocessedNdkHeadersFactory)
android.RegisterSingletonType("ndk", NdkSingleton)
pctx.Import("android/soong/common")
@@ -93,9 +94,12 @@
installPaths = append(installPaths, m.installPaths...)
licensePaths = append(licensePaths, m.licensePath.String())
}
- })
- ctx.VisitAllModules(func(module blueprint.Module) {
+ if m, ok := module.(*preprocessedHeaderModule); ok {
+ installPaths = append(installPaths, m.installPaths...)
+ licensePaths = append(licensePaths, m.licensePath.String())
+ }
+
if m, ok := module.(*Module); ok {
if installer, ok := m.installer.(*stubDecorator); ok {
installPaths = append(installPaths, installer.installPath)
diff --git a/cc/prebuilt.go b/cc/prebuilt.go
index abe03b9..08ba145 100644
--- a/cc/prebuilt.go
+++ b/cc/prebuilt.go
@@ -21,7 +21,9 @@
)
func init() {
- android.RegisterModuleType("cc_prebuilt_shared_library", prebuiltSharedLibraryFactory)
+ android.RegisterModuleType("cc_prebuilt_library_shared", prebuiltSharedLibraryFactory)
+ android.RegisterModuleType("cc_prebuilt_library_static", prebuiltStaticLibraryFactory)
+ android.RegisterModuleType("cc_prebuilt_binary", prebuiltBinaryFactory)
}
type prebuiltLinkerInterface interface {
@@ -29,17 +31,21 @@
prebuilt() *android.Prebuilt
}
-type prebuiltLibraryLinker struct {
- *libraryDecorator
+type prebuiltLinker struct {
android.Prebuilt
}
-var _ prebuiltLinkerInterface = (*prebuiltLibraryLinker)(nil)
-
-func (p *prebuiltLibraryLinker) prebuilt() *android.Prebuilt {
+func (p *prebuiltLinker) prebuilt() *android.Prebuilt {
return &p.Prebuilt
}
+type prebuiltLibraryLinker struct {
+ *libraryDecorator
+ prebuiltLinker
+}
+
+var _ prebuiltLinkerInterface = (*prebuiltLibraryLinker)(nil)
+
func (p *prebuiltLibraryLinker) linkerProps() []interface{} {
props := p.libraryDecorator.linkerProps()
return append(props, &p.Prebuilt.Properties)
@@ -61,13 +67,61 @@
func prebuiltSharedLibraryFactory() (blueprint.Module, []interface{}) {
module, library := NewLibrary(android.HostAndDeviceSupported)
+ library.BuildOnlyShared()
module.compiler = nil
prebuilt := &prebuiltLibraryLinker{
libraryDecorator: library,
}
module.linker = prebuilt
- module.installer = prebuilt
+
+ return module.Init()
+}
+
+func prebuiltStaticLibraryFactory() (blueprint.Module, []interface{}) {
+ module, library := NewLibrary(android.HostAndDeviceSupported)
+ library.BuildOnlyStatic()
+ module.compiler = nil
+
+ prebuilt := &prebuiltLibraryLinker{
+ libraryDecorator: library,
+ }
+ module.linker = prebuilt
+
+ return module.Init()
+}
+
+type prebuiltBinaryLinker struct {
+ *binaryDecorator
+ prebuiltLinker
+}
+
+var _ prebuiltLinkerInterface = (*prebuiltBinaryLinker)(nil)
+
+func (p *prebuiltBinaryLinker) linkerProps() []interface{} {
+ props := p.binaryDecorator.linkerProps()
+ return append(props, &p.Prebuilt.Properties)
+}
+
+func (p *prebuiltBinaryLinker) link(ctx ModuleContext,
+ flags Flags, deps PathDeps, objs Objects) android.Path {
+ // TODO(ccross): verify shared library dependencies
+ if len(p.Prebuilt.Properties.Srcs) > 0 {
+ // TODO(ccross): .toc optimization, stripping, packing
+ return p.Prebuilt.Path(ctx)
+ }
+
+ return nil
+}
+
+func prebuiltBinaryFactory() (blueprint.Module, []interface{}) {
+ module, binary := NewBinary(android.HostAndDeviceSupported)
+ module.compiler = nil
+
+ prebuilt := &prebuiltBinaryLinker{
+ binaryDecorator: binary,
+ }
+ module.linker = prebuilt
return module.Init()
}
diff --git a/cc/sanitize.go b/cc/sanitize.go
index 79c86aa..18d6c16 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -28,6 +28,13 @@
asanCflags = "-fno-omit-frame-pointer"
asanLdflags = "-Wl,-u,__asan_preinit"
asanLibs = "libasan"
+
+ cfiCflags = "-flto -fsanitize-cfi-cross-dso -fvisibility=default " +
+ "-fsanitize-blacklist=external/compiler-rt/lib/cfi/cfi_blacklist.txt"
+ // FIXME: revert the __cfi_check flag when clang is updated to r280031.
+ cfiLdflags = "-flto -fsanitize-cfi-cross-dso -fsanitize=cfi " +
+ "-Wl,-plugin-opt,O1 -Wl,-export-dynamic-symbol=__cfi_check"
+ cfiArflags = "--plugin ${config.ClangBin}/../lib64/LLVMgold.so"
)
type sanitizerType int
@@ -90,7 +97,7 @@
SanitizerEnabled bool `blueprint:"mutated"`
SanitizeDep bool `blueprint:"mutated"`
- InData bool `blueprint:"mutated"`
+ InSanitizerDir bool `blueprint:"mutated"`
}
type sanitize struct {
@@ -167,7 +174,14 @@
}
}
- if !ctx.AConfig().EnableCFI() {
+ // CFI needs gold linker, and mips toolchain does not have one.
+ if !ctx.AConfig().EnableCFI() || ctx.Arch().ArchType == android.Mips || ctx.Arch().ArchType == android.Mips64 {
+ s.Cfi = nil
+ s.Diag.Cfi = nil
+ }
+
+ // Also disable CFI for arm32 until b/35157333 is fixed.
+ if ctx.Arch().ArchType == android.Arm {
s.Cfi = nil
s.Diag.Cfi = nil
}
@@ -280,11 +294,6 @@
flags.CFlags = append(flags.CFlags, asanCflags)
flags.LdFlags = append(flags.LdFlags, asanLdflags)
- // ASan runtime library must be the first in the link order.
- runtimeLibrary := config.AddressSanitizerRuntimeLibrary(ctx.toolchain())
- if runtimeLibrary != "" {
- flags.libFlags = append([]string{"${config.ClangAsanLibDir}/" + runtimeLibrary}, flags.libFlags...)
- }
if ctx.Host() {
// -nodefaultlibs (provided with libc++) prevents the driver from linking
// libraries needed with -fsanitize=address. http://b/18650275 (WAI)
@@ -317,14 +326,14 @@
// __cfi_check needs to be built as Thumb (see the code in linker_cfi.cpp). LLVM is not set up
// to do this on a function basis, so force Thumb on the entire module.
flags.RequiredInstructionSet = "thumb"
+ // Workaround for b/33678192. CFI jumptables need Thumb2 codegen. Revert when
+ // Clang is updated past r290384.
+ flags.LdFlags = append(flags.LdFlags, "-march=armv7-a")
}
sanitizers = append(sanitizers, "cfi")
- cfiFlags := []string{"-flto", "-fsanitize=cfi", "-fsanitize-cfi-cross-dso"}
- flags.CFlags = append(flags.CFlags, cfiFlags...)
- flags.CFlags = append(flags.CFlags, "-fvisibility=default")
- flags.LdFlags = append(flags.LdFlags, cfiFlags...)
- // FIXME: revert the __cfi_check flag when clang is updated to r280031.
- flags.LdFlags = append(flags.LdFlags, "-Wl,-plugin-opt,O1", "-Wl,-export-dynamic-symbol=__cfi_check")
+ flags.CFlags = append(flags.CFlags, cfiCflags)
+ flags.LdFlags = append(flags.LdFlags, cfiLdflags)
+ flags.ArFlags = append(flags.ArFlags, cfiArflags)
if Bool(sanitize.Properties.Sanitize.Diag.Cfi) {
diagSanitizers = append(diagSanitizers, "cfi")
}
@@ -374,8 +383,8 @@
return flags
}
-func (sanitize *sanitize) inData() bool {
- return sanitize.Properties.InData
+func (sanitize *sanitize) inSanitizerDir() bool {
+ return sanitize.Properties.InSanitizerDir
}
func (sanitize *sanitize) Sanitizer(t sanitizerType) bool {
@@ -438,7 +447,7 @@
modules[0].(*Module).sanitize.Properties.SanitizeDep = false
modules[1].(*Module).sanitize.Properties.SanitizeDep = false
if mctx.Device() {
- modules[1].(*Module).sanitize.Properties.InData = true
+ modules[1].(*Module).sanitize.Properties.InSanitizerDir = true
} else {
modules[0].(*Module).Properties.PreventInstall = true
}
diff --git a/cc/stl.go b/cc/stl.go
index 43776f7..9e67145 100644
--- a/cc/stl.go
+++ b/cc/stl.go
@@ -60,7 +60,7 @@
ctx.ModuleErrorf("stl: %q is not a supported STL with sdk_version set", s)
return ""
}
- } else if ctx.Os() == android.Windows {
+ } else if ctx.Windows() {
switch s {
case "libc++", "libc++_static", "libstdc++", "":
// libc++ is not supported on mingw
diff --git a/cc/test.go b/cc/test.go
index f60996c..145b5b0 100644
--- a/cc/test.go
+++ b/cc/test.go
@@ -19,9 +19,9 @@
"runtime"
"strings"
- "github.com/google/blueprint"
-
"android/soong/android"
+
+ "github.com/google/blueprint"
)
type TestProperties struct {
@@ -38,6 +38,14 @@
// relative_install_path. Useful if several tests need to be in the same
// directory, but test_per_src doesn't work.
No_named_install_directory *bool
+
+ // list of files or filegroup modules that provide data that should be installed alongside
+ // the test
+ Data []string
+
+ // list of compatibility suites (for example "cts", "vts") that the module should be
+ // installed into.
+ Test_suites []string
}
func init() {
@@ -191,6 +199,7 @@
*binaryDecorator
*baseCompiler
Properties TestBinaryProperties
+ data android.Paths
}
func (test *testBinary) linkerProps() []interface{} {
@@ -205,6 +214,8 @@
}
func (test *testBinary) linkerDeps(ctx DepsContext, deps Deps) Deps {
+ android.ExtractSourcesDeps(ctx, test.Properties.Data)
+
deps = test.testDecorator.linkerDeps(ctx, deps)
deps = test.binaryDecorator.linkerDeps(ctx, deps)
return deps
@@ -217,6 +228,8 @@
}
func (test *testBinary) install(ctx ModuleContext, file android.Path) {
+ test.data = ctx.ExpandSources(test.Properties.Data, nil)
+
test.binaryDecorator.baseInstaller.dir = "nativetest"
test.binaryDecorator.baseInstaller.dir64 = "nativetest64"
diff --git a/cc/test_data_test.go b/cc/test_data_test.go
new file mode 100644
index 0000000..9b5a85a
--- /dev/null
+++ b/cc/test_data_test.go
@@ -0,0 +1,208 @@
+// Copyright 2017 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cc
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "android/soong/android"
+ "android/soong/genrule"
+
+ "github.com/google/blueprint"
+)
+
+type dataFile struct {
+ path string
+ file string
+}
+
+var testDataTests = []struct {
+ name string
+ modules string
+ data []dataFile
+}{
+ {
+ name: "data files",
+ modules: `
+ test {
+ name: "foo",
+ data: [
+ "baz",
+ "bar/baz",
+ ],
+ }`,
+ data: []dataFile{
+ {"dir", "baz"},
+ {"dir", "bar/baz"},
+ },
+ },
+ {
+ name: "filegroup",
+ modules: `
+ filegroup {
+ name: "fg",
+ srcs: [
+ "baz",
+ "bar/baz",
+ ],
+ }
+
+ test {
+ name: "foo",
+ data: [":fg"],
+ }`,
+ data: []dataFile{
+ {"dir", "baz"},
+ {"dir", "bar/baz"},
+ },
+ },
+ {
+ name: "relative filegroup",
+ modules: `
+ filegroup {
+ name: "fg",
+ srcs: [
+ "bar/baz",
+ ],
+ path: "bar",
+ }
+
+ test {
+ name: "foo",
+ data: [":fg"],
+ }`,
+ data: []dataFile{
+ {"dir/bar", "baz"},
+ },
+ },
+ {
+ name: "relative filegroup trailing slash",
+ modules: `
+ filegroup {
+ name: "fg",
+ srcs: [
+ "bar/baz",
+ ],
+ path: "bar/",
+ }
+
+ test {
+ name: "foo",
+ data: [":fg"],
+ }`,
+ data: []dataFile{
+ {"dir/bar", "baz"},
+ },
+ },
+}
+
+func TestDataTests(t *testing.T) {
+ buildDir, err := ioutil.TempDir("", "soong_test_test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(buildDir)
+
+ config := android.TestConfig(buildDir)
+
+ for _, test := range testDataTests {
+ t.Run(test.name, func(t *testing.T) {
+ ctx := blueprint.NewContext()
+ android.RegisterTestMutators(ctx)
+ ctx.MockFileSystem(map[string][]byte{
+ "Blueprints": []byte(`subdirs = ["dir"]`),
+ "dir/Blueprints": []byte(test.modules),
+ "dir/baz": nil,
+ "dir/bar/baz": nil,
+ })
+ ctx.RegisterModuleType("filegroup", genrule.FileGroupFactory)
+ ctx.RegisterModuleType("test", newTest)
+
+ _, errs := ctx.ParseBlueprintsFiles("Blueprints")
+ fail(t, errs)
+ _, errs = ctx.PrepareBuildActions(config)
+ fail(t, errs)
+
+ foo := findModule(ctx, "foo")
+ if foo == nil {
+ t.Fatalf("failed to find module foo")
+ }
+
+ got := foo.(*testDataTest).data
+ if len(got) != len(test.data) {
+ t.Errorf("expected %d data files, got %d",
+ len(test.data), len(got))
+ }
+
+ for i := range got {
+ if i >= len(test.data) {
+ break
+ }
+
+ path := filepath.Join(test.data[i].path, test.data[i].file)
+ if test.data[i].file != got[i].Rel() ||
+ path != got[i].String() {
+ fmt.Errorf("expected %s:%s got %s:%s",
+ path, test.data[i].file,
+ got[i].String(), got[i].Rel())
+ }
+ }
+ })
+ }
+}
+
+type testDataTest struct {
+ android.ModuleBase
+ data android.Paths
+ Properties struct {
+ Data []string
+ }
+}
+
+func newTest() (blueprint.Module, []interface{}) {
+ m := &testDataTest{}
+ return android.InitAndroidModule(m, &m.Properties)
+}
+
+func (test *testDataTest) DepsMutator(ctx android.BottomUpMutatorContext) {
+ android.ExtractSourcesDeps(ctx, test.Properties.Data)
+}
+
+func (test *testDataTest) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+ test.data = ctx.ExpandSources(test.Properties.Data, nil)
+}
+
+func findModule(ctx *blueprint.Context, name string) blueprint.Module {
+ var ret blueprint.Module
+ ctx.VisitAllModules(func(m blueprint.Module) {
+ if ctx.ModuleName(m) == name {
+ ret = m
+ }
+ })
+ return ret
+}
+
+func fail(t *testing.T, errs []error) {
+ if len(errs) > 0 {
+ for _, err := range errs {
+ t.Error(err)
+ }
+ t.FailNow()
+ }
+}
diff --git a/cc/test_gen_stub_libs.py b/cc/test_gen_stub_libs.py
index 8683d31..8611ef3 100755
--- a/cc/test_gen_stub_libs.py
+++ b/cc/test_gen_stub_libs.py
@@ -107,27 +107,39 @@
class OmitVersionTest(unittest.TestCase):
def test_omit_private(self):
- self.assertFalse(gsl.should_omit_version('foo', [], 'arm', 9))
-
- self.assertTrue(gsl.should_omit_version('foo_PRIVATE', [], 'arm', 9))
- self.assertTrue(gsl.should_omit_version('foo_PLATFORM', [], 'arm', 9))
+ self.assertFalse(gsl.should_omit_version('foo', [], 'arm', 9, False))
self.assertTrue(gsl.should_omit_version(
- 'foo', ['platform-only'], 'arm', 9))
+ 'foo_PRIVATE', [], 'arm', 9, False))
+ self.assertTrue(gsl.should_omit_version(
+ 'foo_PLATFORM', [], 'arm', 9, False))
+
+ self.assertTrue(gsl.should_omit_version(
+ 'foo', ['platform-only'], 'arm', 9, False))
+
+ def test_omit_vndk(self):
+ self.assertTrue(gsl.should_omit_version(
+ 'foo', ['vndk'], 'arm', 9, False))
+
+ self.assertFalse(gsl.should_omit_version('foo', [], 'arm', 9, True))
+ self.assertFalse(gsl.should_omit_version(
+ 'foo', ['vndk'], 'arm', 9, True))
def test_omit_arch(self):
- self.assertFalse(gsl.should_omit_version('foo', [], 'arm', 9))
- self.assertFalse(gsl.should_omit_version('foo', ['arm'], 'arm', 9))
+ self.assertFalse(gsl.should_omit_version('foo', [], 'arm', 9, False))
+ self.assertFalse(gsl.should_omit_version(
+ 'foo', ['arm'], 'arm', 9, False))
- self.assertTrue(gsl.should_omit_version('foo', ['x86'], 'arm', 9))
+ self.assertTrue(gsl.should_omit_version(
+ 'foo', ['x86'], 'arm', 9, False))
def test_omit_api(self):
- self.assertFalse(gsl.should_omit_version('foo', [], 'arm', 9))
+ self.assertFalse(gsl.should_omit_version('foo', [], 'arm', 9, False))
self.assertFalse(
- gsl.should_omit_version('foo', ['introduced=9'], 'arm', 9))
+ gsl.should_omit_version('foo', ['introduced=9'], 'arm', 9, False))
self.assertTrue(
- gsl.should_omit_version('foo', ['introduced=14'], 'arm', 9))
+ gsl.should_omit_version('foo', ['introduced=14'], 'arm', 9, False))
class SymbolFileParseTest(unittest.TestCase):
@@ -302,7 +314,7 @@
# OmitVersionTest, PrivateVersionTest, and SymbolPresenceTest.
src_file = cStringIO.StringIO()
version_file = cStringIO.StringIO()
- generator = gsl.Generator(src_file, version_file, 'arm', 9)
+ generator = gsl.Generator(src_file, version_file, 'arm', 9, False)
version = gsl.Version('VERSION_PRIVATE', None, [], [
gsl.Symbol('foo', []),
@@ -330,7 +342,7 @@
# SymbolPresenceTest.
src_file = cStringIO.StringIO()
version_file = cStringIO.StringIO()
- generator = gsl.Generator(src_file, version_file, 'arm', 9)
+ generator = gsl.Generator(src_file, version_file, 'arm', 9, False)
version = gsl.Version('VERSION_1', None, [], [
gsl.Symbol('foo', ['x86']),
@@ -346,10 +358,17 @@
self.assertEqual('', src_file.getvalue())
self.assertEqual('', version_file.getvalue())
+ version = gsl.Version('VERSION_1', None, [], [
+ gsl.Symbol('foo', ['vndk']),
+ ])
+ generator.write_version(version)
+ self.assertEqual('', src_file.getvalue())
+ self.assertEqual('', version_file.getvalue())
+
def test_write(self):
src_file = cStringIO.StringIO()
version_file = cStringIO.StringIO()
- generator = gsl.Generator(src_file, version_file, 'arm', 9)
+ generator = gsl.Generator(src_file, version_file, 'arm', 9, False)
versions = [
gsl.Version('VERSION_1', None, [], [
@@ -410,6 +429,7 @@
VERSION_4 { # versioned=9
wibble;
+ wizzes; # vndk
} VERSION_2;
VERSION_5 { # versioned=14
@@ -421,7 +441,7 @@
src_file = cStringIO.StringIO()
version_file = cStringIO.StringIO()
- generator = gsl.Generator(src_file, version_file, 'arm', 9)
+ generator = gsl.Generator(src_file, version_file, 'arm', 9, False)
generator.write(versions)
expected_src = textwrap.dedent("""\
diff --git a/cc/util.go b/cc/util.go
index 466266c..36d8dd2 100644
--- a/cc/util.go
+++ b/cc/util.go
@@ -88,6 +88,7 @@
func flagsToBuilderFlags(in Flags) builderFlags {
return builderFlags{
globalFlags: strings.Join(in.GlobalFlags, " "),
+ arFlags: strings.Join(in.ArFlags, " "),
asFlags: strings.Join(in.AsFlags, " "),
cFlags: strings.Join(in.CFlags, " "),
conlyFlags: strings.Join(in.ConlyFlags, " "),
@@ -101,8 +102,11 @@
yasmFlags: strings.Join(in.YasmFlags, " "),
toolchain: in.Toolchain,
clang: in.Clang,
+ coverage: in.Coverage,
tidy: in.Tidy,
+ systemIncludeFlags: strings.Join(in.SystemIncludeFlags, " "),
+
groupStaticLibs: in.GroupStaticLibs,
}
}
diff --git a/cmd/fileslist/Android.bp b/cmd/fileslist/Android.bp
new file mode 100644
index 0000000..cbf939a
--- /dev/null
+++ b/cmd/fileslist/Android.bp
@@ -0,0 +1,20 @@
+// Copyright 2017 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.
+
+blueprint_go_binary {
+ name: "fileslist",
+ srcs: [
+ "fileslist.go",
+ ],
+}
diff --git a/cmd/fileslist/fileslist.go b/cmd/fileslist/fileslist.go
new file mode 100755
index 0000000..56ea66d
--- /dev/null
+++ b/cmd/fileslist/fileslist.go
@@ -0,0 +1,173 @@
+// Copyright 2017 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.
+
+// fileslist.py replacement written in GO, which utilizes multi-cores.
+
+package main
+
+import (
+ "crypto/sha256"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "runtime"
+ "sort"
+ "strings"
+ "sync"
+)
+
+const (
+ MAX_DEFAULT_PARA = 24
+)
+
+func defaultPara() int {
+ ret := runtime.NumCPU()
+ if ret > MAX_DEFAULT_PARA {
+ return MAX_DEFAULT_PARA
+ }
+ return ret
+}
+
+var (
+ para = flag.Int("para", defaultPara(), "Number of goroutines")
+)
+
+// Represents each file.
+type Node struct {
+ SHA256 string
+ Name string // device side path.
+ Size int64
+ path string // host side path.
+ stat os.FileInfo
+}
+
+func newNode(hostPath string, devicePath string, stat os.FileInfo) Node {
+ return Node{Name: devicePath, path: hostPath, stat: stat}
+}
+
+// Scan a Node and returns true if it should be added to the result.
+func (n *Node) scan() bool {
+ n.Size = n.stat.Size()
+
+ // Calculate SHA256.
+ h := sha256.New()
+ if n.stat.Mode()&os.ModeSymlink == 0 {
+ f, err := os.Open(n.path)
+ if err != nil {
+ panic(err)
+ }
+ defer f.Close()
+
+ if _, err := io.Copy(h, f); err != nil {
+ panic(err)
+ }
+ } else {
+ // Hash the content of symlink, not the file it points to.
+ s, err := os.Readlink(n.path)
+ if err != nil {
+ panic(err)
+ }
+ if _, err := io.WriteString(h, s); err != nil {
+ panic(err)
+ }
+ }
+ n.SHA256 = fmt.Sprintf("%x", h.Sum(nil))
+ return true
+}
+
+func main() {
+ flag.Parse()
+
+ allOutput := make([]Node, 0, 1024) // Store all outputs.
+ mutex := &sync.Mutex{} // Guard allOutput
+
+ ch := make(chan Node) // Pass nodes to goroutines.
+
+ var wg sync.WaitGroup // To wait for all goroutines.
+ wg.Add(*para)
+
+ // Scan files in multiple goroutines.
+ for i := 0; i < *para; i++ {
+ go func() {
+ defer wg.Done()
+
+ output := make([]Node, 0, 1024) // Local output list.
+ for node := range ch {
+ if node.scan() {
+ output = append(output, node)
+ }
+ }
+ // Add to the global output list.
+ mutex.Lock()
+ allOutput = append(allOutput, output...)
+ mutex.Unlock()
+ }()
+ }
+
+ // Walk the directories and find files to scan.
+ for _, dir := range flag.Args() {
+ absDir, err := filepath.Abs(dir)
+ if err != nil {
+ panic(err)
+ }
+ deviceRoot := filepath.Clean(absDir + "/..")
+ err = filepath.Walk(dir, func(path string, stat os.FileInfo, err error) error {
+ if err != nil {
+ panic(err)
+ }
+ if stat.IsDir() {
+ return nil
+ }
+ absPath, err := filepath.Abs(path)
+ if err != nil {
+ panic(err)
+ }
+ devicePath, err := filepath.Rel(deviceRoot, absPath)
+ if err != nil {
+ panic(err)
+ }
+ devicePath = "/" + devicePath
+ ch <- newNode(absPath, devicePath, stat)
+ return nil
+ })
+ if err != nil {
+ panic(err)
+ }
+ }
+
+ // Wait until all the goroutines finish.
+ close(ch)
+ wg.Wait()
+
+ // Sort the entries and dump as json.
+ sort.Slice(allOutput, func(i, j int) bool {
+ if allOutput[i].Size > allOutput[j].Size {
+ return true
+ }
+ if allOutput[i].Size == allOutput[j].Size && strings.Compare(allOutput[i].Name, allOutput[j].Name) > 0 {
+ return true
+ }
+ return false
+ })
+
+ j, err := json.MarshalIndent(allOutput, "", " ")
+ if err != nil {
+ panic(nil)
+ }
+
+ fmt.Printf("%s\n", j)
+}
diff --git a/cmd/javac_filter/Android.bp b/cmd/javac_filter/Android.bp
new file mode 100644
index 0000000..cbdabb9
--- /dev/null
+++ b/cmd/javac_filter/Android.bp
@@ -0,0 +1,23 @@
+// Copyright 2017 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.
+
+blueprint_go_binary {
+ name: "soong_javac_filter",
+ srcs: [
+ "javac_filter.go",
+ ],
+ testSrcs: [
+ "javac_filter_test.go",
+ ],
+}
diff --git a/cmd/javac_filter/javac_filter.go b/cmd/javac_filter/javac_filter.go
new file mode 100644
index 0000000..a089acd
--- /dev/null
+++ b/cmd/javac_filter/javac_filter.go
@@ -0,0 +1,109 @@
+// Copyright 2017 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// soong_javac_filter expects the output of javac on stdin, and produces
+// an ANSI colorized version of the output on stdout.
+//
+// It also hides the unhelpful and unhideable "warning there is a warning"
+// messages.
+package main
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "os"
+ "regexp"
+)
+
+// Regular expressions are based on
+// https://chromium.googlesource.com/chromium/src/+/master/build/android/gyp/javac.py
+// Colors are based on clang's output
+var (
+ filelinePrefix = `^[-.\w/\\]+.java:[0-9]+:`
+ warningRe = regexp.MustCompile(filelinePrefix + ` (warning:) .*$`)
+ errorRe = regexp.MustCompile(filelinePrefix + ` (.*?:) .*$`)
+ markerRe = regexp.MustCompile(`\s*(\^)\s*$`)
+
+ escape = "\x1b"
+ reset = escape + "[0m"
+ bold = escape + "[1m"
+ red = escape + "[31m"
+ green = escape + "[32m"
+ magenta = escape + "[35m"
+)
+
+func main() {
+ err := process(bufio.NewReader(os.Stdin), os.Stdout)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, "reading standard input:", err)
+ os.Exit(-1)
+ }
+}
+
+func process(r io.Reader, w io.Writer) error {
+ scanner := bufio.NewScanner(r)
+ // Some javac wrappers output the entire list of java files being
+ // compiled on a single line, which can be very large, set the maximum
+ // buffer size to 2MB.
+ scanner.Buffer(nil, 2*1024*1024)
+ for scanner.Scan() {
+ processLine(w, scanner.Text())
+ }
+ return scanner.Err()
+}
+
+func processLine(w io.Writer, line string) {
+ for _, f := range filters {
+ if f.MatchString(line) {
+ return
+ }
+ }
+ for _, p := range colorPatterns {
+ var matched bool
+ if line, matched = applyColor(line, p.color, p.re); matched {
+ break
+ }
+ }
+ fmt.Fprintln(w, line)
+}
+
+// If line matches re, make it bold and apply color to the first submatch
+// Returns line, modified if it matched, and true if it matched.
+func applyColor(line, color string, re *regexp.Regexp) (string, bool) {
+ if m := re.FindStringSubmatchIndex(line); m != nil {
+ tagStart, tagEnd := m[2], m[3]
+ line = bold + line[:tagStart] +
+ color + line[tagStart:tagEnd] + reset + bold +
+ line[tagEnd:] + reset
+ return line, true
+ }
+ return line, false
+}
+
+var colorPatterns = []struct {
+ re *regexp.Regexp
+ color string
+}{
+ {warningRe, magenta},
+ {errorRe, red},
+ {markerRe, green},
+}
+
+var filters = []*regexp.Regexp{
+ regexp.MustCompile(`Note: (Some input files|.*\.java) uses? or overrides? a deprecated API.`),
+ regexp.MustCompile(`Note: Recompile with -Xlint:deprecation for details.`),
+ regexp.MustCompile(`Note: (Some input files|.*\.java) uses? unchecked or unsafe operations.`),
+ regexp.MustCompile(`Note: Recompile with -Xlint:unchecked for details.`),
+}
diff --git a/cmd/javac_filter/javac_filter_test.go b/cmd/javac_filter/javac_filter_test.go
new file mode 100644
index 0000000..43381ce
--- /dev/null
+++ b/cmd/javac_filter/javac_filter_test.go
@@ -0,0 +1,74 @@
+// Copyright 2017 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "bytes"
+ "testing"
+)
+
+var testCases = []struct {
+ in, out string
+}{
+ {
+ in: "File.java:40: error: cannot find symbol\n",
+ out: "\x1b[1mFile.java:40: \x1b[31merror:\x1b[0m\x1b[1m cannot find symbol\x1b[0m\n",
+ },
+ {
+ in: "import static com.blah.SYMBOL;\n",
+ out: "import static com.blah.SYMBOL;\n",
+ },
+ {
+ in: " ^ \n",
+ out: "\x1b[1m \x1b[32m^\x1b[0m\x1b[1m \x1b[0m\n",
+ },
+ {
+ in: "File.java:398: warning: [RectIntersectReturnValueIgnored] Return value of com.blah.function() must be checked\n",
+ out: "\x1b[1mFile.java:398: \x1b[35mwarning:\x1b[0m\x1b[1m [RectIntersectReturnValueIgnored] Return value of com.blah.function() must be checked\x1b[0m\n",
+ },
+ {
+ in: " (see http://go/errorprone/bugpattern/RectIntersectReturnValueIgnored.md)\n",
+ out: " (see http://go/errorprone/bugpattern/RectIntersectReturnValueIgnored.md)\n",
+ },
+ {
+ in: `
+Note: Some input files use or override a deprecated API.
+Note: Recompile with -Xlint:deprecation for details.
+Note: Some input files use unchecked or unsafe operations.
+Note: Recompile with -Xlint:unchecked for details.
+Note: dir/file.java uses or overrides a deprecated API.
+Note: dir/file.java uses unchecked or unsafe operations.
+`,
+ out: "\n",
+ },
+ {
+ in: "\n",
+ out: "\n",
+ },
+}
+
+func TestJavacColorize(t *testing.T) {
+ for _, test := range testCases {
+ buf := new(bytes.Buffer)
+ err := process(bytes.NewReader([]byte(test.in)), buf)
+ if err != nil {
+ t.Errorf("error: %q", err)
+ }
+ got := string(buf.Bytes())
+ if got != test.out {
+ t.Errorf("expected %q got %q", test.out, got)
+ }
+ }
+}
diff --git a/cmd/microfactory/Android.bp b/cmd/microfactory/Android.bp
new file mode 100644
index 0000000..a457f43
--- /dev/null
+++ b/cmd/microfactory/Android.bp
@@ -0,0 +1,23 @@
+// Copyright 2017 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.
+
+blueprint_go_binary {
+ name: "microfactory",
+ srcs: [
+ "microfactory.go",
+ ],
+ testSrcs: [
+ "microfactory_test.go",
+ ],
+}
diff --git a/cmd/microfactory/microfactory.go b/cmd/microfactory/microfactory.go
new file mode 100644
index 0000000..6c062e2
--- /dev/null
+++ b/cmd/microfactory/microfactory.go
@@ -0,0 +1,572 @@
+// Copyright 2017 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.
+
+// Microfactory is a tool to incrementally compile a go program. It's similar
+// to `go install`, but doesn't require a GOPATH. A package->path mapping can
+// be specified as command line options:
+//
+// -pkg-path android/soong=build/soong
+// -pkg-path github.com/google/blueprint=build/blueprint
+//
+// The paths can be relative to the current working directory, or an absolute
+// path. Both packages and paths are compared with full directory names, so the
+// android/soong-test package wouldn't be mapped in the above case.
+//
+// Microfactory will ignore *_test.go files, and limits *_darwin.go and
+// *_linux.go files to MacOS and Linux respectively. It does not support build
+// tags or any other suffixes.
+//
+// Builds are incremental by package. All input files are hashed, and if the
+// hash of an input or dependency changes, the package is rebuilt.
+//
+// It also exposes the -trimpath option from go's compiler so that embedded
+// path names (such as in log.Llongfile) are relative paths instead of absolute
+// paths.
+//
+// If you don't have a previously built version of Microfactory, when used with
+// -s <microfactory_src_dir> -b <microfactory_bin_file>, Microfactory can
+// rebuild itself as necessary. Combined with a shell script like soong_ui.bash
+// that uses `go run` to run Microfactory for the first time, go programs can be
+// quickly bootstrapped entirely from source (and a standard go distribution).
+package main
+
+import (
+ "bytes"
+ "crypto/sha1"
+ "flag"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "io"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "sort"
+ "strconv"
+ "strings"
+ "sync"
+ "syscall"
+)
+
+var (
+ race = false
+ verbose = false
+
+ goToolDir = filepath.Join(runtime.GOROOT(), "pkg", "tool", runtime.GOOS+"_"+runtime.GOARCH)
+ goVersion = findGoVersion()
+)
+
+func findGoVersion() string {
+ if version, err := ioutil.ReadFile(filepath.Join(runtime.GOROOT(), "VERSION")); err == nil {
+ return string(version)
+ }
+
+ cmd := exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"), "version")
+ if version, err := cmd.Output(); err == nil {
+ return string(version)
+ } else {
+ panic(fmt.Sprintf("Unable to discover go version: %v", err))
+ }
+}
+
+type GoPackage struct {
+ Name string
+
+ // Inputs
+ directDeps []*GoPackage // specified directly by the module
+ allDeps []*GoPackage // direct dependencies and transitive dependencies
+ files []string
+
+ // Outputs
+ pkgDir string
+ output string
+ hashResult []byte
+
+ // Status
+ mutex sync.Mutex
+ compiled bool
+ failed error
+ rebuilt bool
+}
+
+// LinkedHashMap<string, GoPackage>
+type linkedDepSet struct {
+ packageSet map[string](*GoPackage)
+ packageList []*GoPackage
+}
+
+func newDepSet() *linkedDepSet {
+ return &linkedDepSet{packageSet: make(map[string]*GoPackage)}
+}
+func (s *linkedDepSet) tryGetByName(name string) (*GoPackage, bool) {
+ pkg, contained := s.packageSet[name]
+ return pkg, contained
+}
+func (s *linkedDepSet) getByName(name string) *GoPackage {
+ pkg, _ := s.tryGetByName(name)
+ return pkg
+}
+func (s *linkedDepSet) add(name string, goPackage *GoPackage) {
+ s.packageSet[name] = goPackage
+ s.packageList = append(s.packageList, goPackage)
+}
+func (s *linkedDepSet) ignore(name string) {
+ s.packageSet[name] = nil
+}
+
+// FindDeps searches all applicable go files in `path`, parses all of them
+// for import dependencies that exist in pkgMap, then recursively does the
+// same for all of those dependencies.
+func (p *GoPackage) FindDeps(path string, pkgMap *pkgPathMapping) error {
+ depSet := newDepSet()
+ err := p.findDeps(path, pkgMap, depSet)
+ if err != nil {
+ return err
+ }
+ p.allDeps = depSet.packageList
+ return nil
+}
+
+// findDeps is the recursive version of FindDeps. allPackages is the map of
+// all locally defined packages so that the same dependency of two different
+// packages is only resolved once.
+func (p *GoPackage) findDeps(path string, pkgMap *pkgPathMapping, allPackages *linkedDepSet) error {
+ // If this ever becomes too slow, we can look at reading the files once instead of twice
+ // But that just complicates things today, and we're already really fast.
+ foundPkgs, err := parser.ParseDir(token.NewFileSet(), path, func(fi os.FileInfo) bool {
+ name := fi.Name()
+ if fi.IsDir() || strings.HasSuffix(name, "_test.go") || name[0] == '.' || name[0] == '_' {
+ return false
+ }
+ if runtime.GOOS != "darwin" && strings.HasSuffix(name, "_darwin.go") {
+ return false
+ }
+ if runtime.GOOS != "linux" && strings.HasSuffix(name, "_linux.go") {
+ return false
+ }
+ return true
+ }, parser.ImportsOnly)
+ if err != nil {
+ return fmt.Errorf("Error parsing directory %q: %v", path, err)
+ }
+
+ var foundPkg *ast.Package
+ // foundPkgs is a map[string]*ast.Package, but we only want one package
+ if len(foundPkgs) != 1 {
+ return fmt.Errorf("Expected one package in %q, got %d", path, len(foundPkgs))
+ }
+ // Extract the first (and only) entry from the map.
+ for _, pkg := range foundPkgs {
+ foundPkg = pkg
+ }
+
+ var deps []string
+ localDeps := make(map[string]bool)
+
+ for filename, astFile := range foundPkg.Files {
+ p.files = append(p.files, filename)
+
+ for _, importSpec := range astFile.Imports {
+ name, err := strconv.Unquote(importSpec.Path.Value)
+ if err != nil {
+ return fmt.Errorf("%s: invalid quoted string: <%s> %v", filename, importSpec.Path.Value, err)
+ }
+
+ if pkg, ok := allPackages.tryGetByName(name); ok {
+ if pkg != nil {
+ if _, ok := localDeps[name]; !ok {
+ deps = append(deps, name)
+ localDeps[name] = true
+ }
+ }
+ continue
+ }
+
+ var pkgPath string
+ if path, ok, err := pkgMap.Path(name); err != nil {
+ return err
+ } else if !ok {
+ // Probably in the stdlib, but if not, then the compiler will fail with a reasonable error message
+ // Mark it as such so that we don't try to decode its path again.
+ allPackages.ignore(name)
+ continue
+ } else {
+ pkgPath = path
+ }
+
+ pkg := &GoPackage{
+ Name: name,
+ }
+ deps = append(deps, name)
+ allPackages.add(name, pkg)
+ localDeps[name] = true
+
+ if err := pkg.findDeps(pkgPath, pkgMap, allPackages); err != nil {
+ return err
+ }
+ }
+ }
+
+ sort.Strings(p.files)
+
+ if verbose {
+ fmt.Fprintf(os.Stderr, "Package %q depends on %v\n", p.Name, deps)
+ }
+
+ for _, dep := range deps {
+ p.directDeps = append(p.directDeps, allPackages.getByName(dep))
+ }
+
+ return nil
+}
+
+func (p *GoPackage) Compile(outDir, trimPath string) error {
+ p.mutex.Lock()
+ defer p.mutex.Unlock()
+ if p.compiled {
+ return p.failed
+ }
+ p.compiled = true
+
+ // Build all dependencies in parallel, then fail if any of them failed.
+ var wg sync.WaitGroup
+ for _, dep := range p.directDeps {
+ wg.Add(1)
+ go func(dep *GoPackage) {
+ defer wg.Done()
+ dep.Compile(outDir, trimPath)
+ }(dep)
+ }
+ wg.Wait()
+ for _, dep := range p.directDeps {
+ if dep.failed != nil {
+ p.failed = dep.failed
+ return p.failed
+ }
+ }
+
+ p.pkgDir = filepath.Join(outDir, p.Name)
+ p.output = filepath.Join(p.pkgDir, p.Name) + ".a"
+ shaFile := p.output + ".hash"
+
+ hash := sha1.New()
+ fmt.Fprintln(hash, runtime.GOOS, runtime.GOARCH, goVersion)
+
+ cmd := exec.Command(filepath.Join(goToolDir, "compile"),
+ "-o", p.output,
+ "-p", p.Name,
+ "-complete", "-pack", "-nolocalimports")
+ if race {
+ cmd.Args = append(cmd.Args, "-race")
+ fmt.Fprintln(hash, "-race")
+ }
+ if trimPath != "" {
+ cmd.Args = append(cmd.Args, "-trimpath", trimPath)
+ fmt.Fprintln(hash, trimPath)
+ }
+ for _, dep := range p.directDeps {
+ cmd.Args = append(cmd.Args, "-I", dep.pkgDir)
+ hash.Write(dep.hashResult)
+ }
+ for _, filename := range p.files {
+ cmd.Args = append(cmd.Args, filename)
+ fmt.Fprintln(hash, filename)
+
+ // Hash the contents of the input files
+ f, err := os.Open(filename)
+ if err != nil {
+ f.Close()
+ err = fmt.Errorf("%s: %v", filename, err)
+ p.failed = err
+ return err
+ }
+ _, err = io.Copy(hash, f)
+ if err != nil {
+ f.Close()
+ err = fmt.Errorf("%s: %v", filename, err)
+ p.failed = err
+ return err
+ }
+ f.Close()
+ }
+ p.hashResult = hash.Sum(nil)
+
+ var rebuild bool
+ if _, err := os.Stat(p.output); err != nil {
+ rebuild = true
+ }
+ if !rebuild {
+ if oldSha, err := ioutil.ReadFile(shaFile); err == nil {
+ rebuild = !bytes.Equal(oldSha, p.hashResult)
+ } else {
+ rebuild = true
+ }
+ }
+
+ if !rebuild {
+ return nil
+ }
+
+ err := os.RemoveAll(p.pkgDir)
+ if err != nil {
+ err = fmt.Errorf("%s: %v", p.Name, err)
+ p.failed = err
+ return err
+ }
+
+ err = os.MkdirAll(filepath.Dir(p.output), 0777)
+ if err != nil {
+ err = fmt.Errorf("%s: %v", p.Name, err)
+ p.failed = err
+ return err
+ }
+
+ cmd.Stdin = nil
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if verbose {
+ fmt.Fprintln(os.Stderr, cmd.Args)
+ }
+ err = cmd.Run()
+ if err != nil {
+ err = fmt.Errorf("%s: %v", p.Name, err)
+ p.failed = err
+ return err
+ }
+
+ err = ioutil.WriteFile(shaFile, p.hashResult, 0666)
+ if err != nil {
+ err = fmt.Errorf("%s: %v", p.Name, err)
+ p.failed = err
+ return err
+ }
+
+ p.rebuilt = true
+
+ return nil
+}
+
+func (p *GoPackage) Link(out string) error {
+ if p.Name != "main" {
+ return fmt.Errorf("Can only link main package")
+ }
+
+ shaFile := filepath.Join(filepath.Dir(out), "."+filepath.Base(out)+"_hash")
+
+ if !p.rebuilt {
+ if _, err := os.Stat(out); err != nil {
+ p.rebuilt = true
+ } else if oldSha, err := ioutil.ReadFile(shaFile); err != nil {
+ p.rebuilt = true
+ } else {
+ p.rebuilt = !bytes.Equal(oldSha, p.hashResult)
+ }
+ }
+ if !p.rebuilt {
+ return nil
+ }
+
+ err := os.Remove(shaFile)
+ if err != nil && !os.IsNotExist(err) {
+ return err
+ }
+ err = os.Remove(out)
+ if err != nil && !os.IsNotExist(err) {
+ return err
+ }
+
+ cmd := exec.Command(filepath.Join(goToolDir, "link"), "-o", out)
+ if race {
+ cmd.Args = append(cmd.Args, "-race")
+ }
+ for _, dep := range p.allDeps {
+ cmd.Args = append(cmd.Args, "-L", dep.pkgDir)
+ }
+ cmd.Args = append(cmd.Args, p.output)
+ cmd.Stdin = nil
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if verbose {
+ fmt.Fprintln(os.Stderr, cmd.Args)
+ }
+ err = cmd.Run()
+ if err != nil {
+ return fmt.Errorf("command %s failed with error %v", cmd.Args, err)
+ }
+
+ return ioutil.WriteFile(shaFile, p.hashResult, 0666)
+}
+
+// rebuildMicrofactory checks to see if microfactory itself needs to be rebuilt,
+// and if does, it will launch a new copy instead of returning.
+func rebuildMicrofactory(mybin, mysrc string, pkgMap *pkgPathMapping) {
+ intermediates := filepath.Join(filepath.Dir(mybin), "."+filepath.Base(mybin)+"_intermediates")
+
+ err := os.MkdirAll(intermediates, 0777)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, "Failed to create intermediates directory: %v", err)
+ os.Exit(1)
+ }
+
+ pkg := &GoPackage{
+ Name: "main",
+ }
+
+ if err := pkg.FindDeps(mysrc, pkgMap); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+
+ if err := pkg.Compile(intermediates, mysrc); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+
+ if err := pkg.Link(mybin); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+
+ if !pkg.rebuilt {
+ return
+ }
+
+ cmd := exec.Command(mybin, os.Args[1:]...)
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if err := cmd.Run(); err == nil {
+ os.Exit(0)
+ } else if e, ok := err.(*exec.ExitError); ok {
+ os.Exit(e.ProcessState.Sys().(syscall.WaitStatus).ExitStatus())
+ }
+ os.Exit(1)
+}
+
+func main() {
+ var output, mysrc, mybin, trimPath string
+ var pkgMap pkgPathMapping
+
+ flags := flag.NewFlagSet("", flag.ExitOnError)
+ flags.BoolVar(&race, "race", false, "enable data race detection.")
+ flags.BoolVar(&verbose, "v", false, "Verbose")
+ flags.StringVar(&output, "o", "", "Output file")
+ flags.StringVar(&mysrc, "s", "", "Microfactory source directory (for rebuilding microfactory if necessary)")
+ flags.StringVar(&mybin, "b", "", "Microfactory binary location")
+ flags.StringVar(&trimPath, "trimpath", "", "remove prefix from recorded source file paths")
+ flags.Var(&pkgMap, "pkg-path", "Mapping of package prefixes to file paths")
+ err := flags.Parse(os.Args[1:])
+
+ if err == flag.ErrHelp || flags.NArg() != 1 || output == "" {
+ fmt.Fprintln(os.Stderr, "Usage:", os.Args[0], "-o out/binary <main-package>")
+ flags.PrintDefaults()
+ os.Exit(1)
+ }
+
+ if mybin != "" && mysrc != "" {
+ rebuildMicrofactory(mybin, mysrc, &pkgMap)
+ }
+
+ mainPackage := &GoPackage{
+ Name: "main",
+ }
+
+ if path, ok, err := pkgMap.Path(flags.Arg(0)); err != nil {
+ fmt.Fprintln(os.Stderr, "Error finding main path:", err)
+ os.Exit(1)
+ } else if !ok {
+ fmt.Fprintln(os.Stderr, "Cannot find path for", flags.Arg(0))
+ } else {
+ if err := mainPackage.FindDeps(path, &pkgMap); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ }
+
+ intermediates := filepath.Join(filepath.Dir(output), "."+filepath.Base(output)+"_intermediates")
+
+ err = os.MkdirAll(intermediates, 0777)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, "Failed to create intermediates directory: %ve", err)
+ os.Exit(1)
+ }
+
+ err = mainPackage.Compile(intermediates, trimPath)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, "Failed to compile:", err)
+ os.Exit(1)
+ }
+
+ err = mainPackage.Link(output)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, "microfactory.go failed to link:", err)
+ os.Exit(1)
+ }
+}
+
+// pkgPathMapping can be used with flag.Var to parse -pkg-path arguments of
+// <package-prefix>=<path-prefix> mappings.
+type pkgPathMapping struct {
+ pkgs []string
+
+ paths map[string]string
+}
+
+func (pkgPathMapping) String() string {
+ return "<package-prefix>=<path-prefix>"
+}
+
+func (p *pkgPathMapping) Set(value string) error {
+ equalPos := strings.Index(value, "=")
+ if equalPos == -1 {
+ return fmt.Errorf("Argument must be in the form of: %q", p.String())
+ }
+
+ pkgPrefix := strings.TrimSuffix(value[:equalPos], "/")
+ pathPrefix := strings.TrimSuffix(value[equalPos+1:], "/")
+
+ if p.paths == nil {
+ p.paths = make(map[string]string)
+ }
+ if _, ok := p.paths[pkgPrefix]; ok {
+ return fmt.Errorf("Duplicate package prefix: %q", pkgPrefix)
+ }
+
+ p.pkgs = append(p.pkgs, pkgPrefix)
+ p.paths[pkgPrefix] = pathPrefix
+
+ return nil
+}
+
+// Path takes a package name, applies the path mappings and returns the resulting path.
+//
+// If the package isn't mapped, we'll return false to prevent compilation attempts.
+func (p *pkgPathMapping) Path(pkg string) (string, bool, error) {
+ if p.paths == nil {
+ return "", false, fmt.Errorf("No package mappings")
+ }
+
+ for _, pkgPrefix := range p.pkgs {
+ if pkg == pkgPrefix {
+ return p.paths[pkgPrefix], true, nil
+ } else if strings.HasPrefix(pkg, pkgPrefix+"/") {
+ return filepath.Join(p.paths[pkgPrefix], strings.TrimPrefix(pkg, pkgPrefix+"/")), true, nil
+ }
+ }
+
+ return "", false, nil
+}
diff --git a/cmd/microfactory/microfactory_test.go b/cmd/microfactory/microfactory_test.go
new file mode 100644
index 0000000..8c02bcf
--- /dev/null
+++ b/cmd/microfactory/microfactory_test.go
@@ -0,0 +1,422 @@
+// Copyright 2017 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "flag"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "reflect"
+ "runtime"
+ "testing"
+ "time"
+)
+
+func TestSimplePackagePathMap(t *testing.T) {
+ t.Parallel()
+
+ var pkgMap pkgPathMapping
+ flags := flag.NewFlagSet("", flag.ContinueOnError)
+ flags.Var(&pkgMap, "m", "")
+ err := flags.Parse([]string{
+ "-m", "android/soong=build/soong/",
+ "-m", "github.com/google/blueprint/=build/blueprint",
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ compare := func(got, want interface{}) {
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("Unexpected values in .pkgs:\nwant: %v\n got: %v",
+ want, got)
+ }
+ }
+
+ wantPkgs := []string{"android/soong", "github.com/google/blueprint"}
+ compare(pkgMap.pkgs, wantPkgs)
+ compare(pkgMap.paths[wantPkgs[0]], "build/soong")
+ compare(pkgMap.paths[wantPkgs[1]], "build/blueprint")
+
+ got, ok, err := pkgMap.Path("android/soong/ui/test")
+ if err != nil {
+ t.Error("Unexpected error in pkgMap.Path(soong):", err)
+ } else if !ok {
+ t.Error("Expected a result from pkgMap.Path(soong)")
+ } else {
+ compare(got, "build/soong/ui/test")
+ }
+
+ got, ok, err = pkgMap.Path("github.com/google/blueprint")
+ if err != nil {
+ t.Error("Unexpected error in pkgMap.Path(blueprint):", err)
+ } else if !ok {
+ t.Error("Expected a result from pkgMap.Path(blueprint)")
+ } else {
+ compare(got, "build/blueprint")
+ }
+}
+
+func TestBadPackagePathMap(t *testing.T) {
+ t.Parallel()
+
+ var pkgMap pkgPathMapping
+ if _, _, err := pkgMap.Path("testing"); err == nil {
+ t.Error("Expected error if no maps are specified")
+ }
+ if err := pkgMap.Set(""); err == nil {
+ t.Error("Expected error with blank argument, but none returned")
+ }
+ if err := pkgMap.Set("a=a"); err != nil {
+ t.Error("Unexpected error: %v", err)
+ }
+ if err := pkgMap.Set("a=b"); err == nil {
+ t.Error("Expected error with duplicate package prefix, but none returned")
+ }
+ if _, ok, err := pkgMap.Path("testing"); err != nil {
+ t.Error("Unexpected error: %v", err)
+ } else if ok {
+ t.Error("Expected testing to be consider in the stdlib")
+ }
+}
+
+// TestSingleBuild ensures that just a basic build works.
+func TestSingleBuild(t *testing.T) {
+ t.Parallel()
+
+ setupDir(t, func(dir string, loadPkg loadPkgFunc) {
+ // The output binary
+ out := filepath.Join(dir, "out", "test")
+
+ pkg := loadPkg()
+
+ if err := pkg.Compile(filepath.Join(dir, "out"), ""); err != nil {
+ t.Fatalf("Got error when compiling:", err)
+ }
+
+ if err := pkg.Link(out); err != nil {
+ t.Fatal("Got error when linking:", err)
+ }
+
+ if _, err := os.Stat(out); err != nil {
+ t.Error("Cannot stat output:", err)
+ }
+ })
+}
+
+// testBuildAgain triggers two builds, running the modify function in between
+// each build. It verifies that the second build did or did not actually need
+// to rebuild anything based on the shouldRebuild argument.
+func testBuildAgain(t *testing.T,
+ shouldRecompile, shouldRelink bool,
+ modify func(dir string, loadPkg loadPkgFunc),
+ after func(pkg *GoPackage)) {
+
+ t.Parallel()
+
+ setupDir(t, func(dir string, loadPkg loadPkgFunc) {
+ // The output binary
+ out := filepath.Join(dir, "out", "test")
+
+ pkg := loadPkg()
+
+ if err := pkg.Compile(filepath.Join(dir, "out"), ""); err != nil {
+ t.Fatal("Got error when compiling:", err)
+ }
+
+ if err := pkg.Link(out); err != nil {
+ t.Fatal("Got error when linking:", err)
+ }
+
+ var firstTime time.Time
+ if stat, err := os.Stat(out); err == nil {
+ firstTime = stat.ModTime()
+ } else {
+ t.Fatal("Failed to stat output file:", err)
+ }
+
+ // mtime on HFS+ (the filesystem on darwin) are stored with 1
+ // second granularity, so the timestamp checks will fail unless
+ // we wait at least a second. Sleeping 1.1s to be safe.
+ if runtime.GOOS == "darwin" {
+ time.Sleep(1100 * time.Millisecond)
+ }
+
+ modify(dir, loadPkg)
+
+ pkg = loadPkg()
+
+ if err := pkg.Compile(filepath.Join(dir, "out"), ""); err != nil {
+ t.Fatal("Got error when compiling:", err)
+ }
+ if shouldRecompile {
+ if !pkg.rebuilt {
+ t.Fatal("Package should have recompiled, but was not recompiled.")
+ }
+ } else {
+ if pkg.rebuilt {
+ t.Fatal("Package should not have needed to be recompiled, but was recompiled.")
+ }
+ }
+
+ if err := pkg.Link(out); err != nil {
+ t.Fatal("Got error while linking:", err)
+ }
+ if shouldRelink {
+ if !pkg.rebuilt {
+ t.Error("Package should have relinked, but was not relinked.")
+ }
+ } else {
+ if pkg.rebuilt {
+ t.Error("Package should not have needed to be relinked, but was relinked.")
+ }
+ }
+
+ if stat, err := os.Stat(out); err == nil {
+ if shouldRelink {
+ if stat.ModTime() == firstTime {
+ t.Error("Output timestamp should be different, but both were", firstTime)
+ }
+ } else {
+ if stat.ModTime() != firstTime {
+ t.Error("Output timestamp should be the same.")
+ t.Error(" first:", firstTime)
+ t.Error("second:", stat.ModTime())
+ }
+ }
+ } else {
+ t.Fatal("Failed to stat output file:", err)
+ }
+
+ after(pkg)
+ })
+}
+
+// TestRebuildAfterNoChanges ensures that we don't rebuild if nothing
+// changes
+func TestRebuildAfterNoChanges(t *testing.T) {
+ testBuildAgain(t, false, false, func(dir string, loadPkg loadPkgFunc) {}, func(pkg *GoPackage) {})
+}
+
+// TestRebuildAfterTimestamp ensures that we don't rebuild because
+// timestamps of important files have changed. We should only rebuild if the
+// content hashes are different.
+func TestRebuildAfterTimestampChange(t *testing.T) {
+ testBuildAgain(t, false, false, func(dir string, loadPkg loadPkgFunc) {
+ // Ensure that we've spent some amount of time asleep
+ time.Sleep(100 * time.Millisecond)
+
+ newTime := time.Now().Local()
+ os.Chtimes(filepath.Join(dir, "test.fact"), newTime, newTime)
+ os.Chtimes(filepath.Join(dir, "main/main.go"), newTime, newTime)
+ os.Chtimes(filepath.Join(dir, "a/a.go"), newTime, newTime)
+ os.Chtimes(filepath.Join(dir, "a/b.go"), newTime, newTime)
+ os.Chtimes(filepath.Join(dir, "b/a.go"), newTime, newTime)
+ }, func(pkg *GoPackage) {})
+}
+
+// TestRebuildAfterGoChange ensures that we rebuild after a content change
+// to a package's go file.
+func TestRebuildAfterGoChange(t *testing.T) {
+ testBuildAgain(t, true, true, func(dir string, loadPkg loadPkgFunc) {
+ if err := ioutil.WriteFile(filepath.Join(dir, "a", "a.go"), []byte(go_a_a+"\n"), 0666); err != nil {
+ t.Fatal("Error writing a/a.go:", err)
+ }
+ }, func(pkg *GoPackage) {
+ if !pkg.directDeps[0].rebuilt {
+ t.Fatal("android/soong/a should have rebuilt")
+ }
+ if !pkg.directDeps[1].rebuilt {
+ t.Fatal("android/soong/b should have rebuilt")
+ }
+ })
+}
+
+// TestRebuildAfterMainChange ensures that we don't rebuild any dependencies
+// if only the main package's go files are touched.
+func TestRebuildAfterMainChange(t *testing.T) {
+ testBuildAgain(t, true, true, func(dir string, loadPkg loadPkgFunc) {
+ if err := ioutil.WriteFile(filepath.Join(dir, "main", "main.go"), []byte(go_main_main+"\n"), 0666); err != nil {
+ t.Fatal("Error writing main/main.go:", err)
+ }
+ }, func(pkg *GoPackage) {
+ if pkg.directDeps[0].rebuilt {
+ t.Fatal("android/soong/a should not have rebuilt")
+ }
+ if pkg.directDeps[1].rebuilt {
+ t.Fatal("android/soong/b should not have rebuilt")
+ }
+ })
+}
+
+// TestRebuildAfterRemoveOut ensures that we rebuild if the output file is
+// missing, even if everything else doesn't need rebuilding.
+func TestRebuildAfterRemoveOut(t *testing.T) {
+ testBuildAgain(t, false, true, func(dir string, loadPkg loadPkgFunc) {
+ if err := os.Remove(filepath.Join(dir, "out", "test")); err != nil {
+ t.Fatal("Failed to remove output:", err)
+ }
+ }, func(pkg *GoPackage) {})
+}
+
+// TestRebuildAfterPartialBuild ensures that even if the build was interrupted
+// between the recompile and relink stages, we'll still relink when we run again.
+func TestRebuildAfterPartialBuild(t *testing.T) {
+ testBuildAgain(t, false, true, func(dir string, loadPkg loadPkgFunc) {
+ if err := ioutil.WriteFile(filepath.Join(dir, "main", "main.go"), []byte(go_main_main+"\n"), 0666); err != nil {
+ t.Fatal("Error writing main/main.go:", err)
+ }
+
+ pkg := loadPkg()
+
+ if err := pkg.Compile(filepath.Join(dir, "out"), ""); err != nil {
+ t.Fatal("Got error when compiling:", err)
+ }
+ if !pkg.rebuilt {
+ t.Fatal("Package should have recompiled, but was not recompiled.")
+ }
+ }, func(pkg *GoPackage) {})
+}
+
+// BenchmarkInitialBuild computes how long a clean build takes (for tiny test
+// inputs).
+func BenchmarkInitialBuild(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ setupDir(b, func(dir string, loadPkg loadPkgFunc) {
+ pkg := loadPkg()
+ if err := pkg.Compile(filepath.Join(dir, "out"), ""); err != nil {
+ b.Fatal("Got error when compiling:", err)
+ }
+
+ if err := pkg.Link(filepath.Join(dir, "out", "test")); err != nil {
+ b.Fatal("Got error when linking:", err)
+ }
+ })
+ }
+}
+
+// BenchmarkMinIncrementalBuild computes how long an incremental build that
+// doesn't actually need to build anything takes.
+func BenchmarkMinIncrementalBuild(b *testing.B) {
+ setupDir(b, func(dir string, loadPkg loadPkgFunc) {
+ pkg := loadPkg()
+
+ if err := pkg.Compile(filepath.Join(dir, "out"), ""); err != nil {
+ b.Fatal("Got error when compiling:", err)
+ }
+
+ if err := pkg.Link(filepath.Join(dir, "out", "test")); err != nil {
+ b.Fatal("Got error when linking:", err)
+ }
+
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ pkg := loadPkg()
+
+ if err := pkg.Compile(filepath.Join(dir, "out"), ""); err != nil {
+ b.Fatal("Got error when compiling:", err)
+ }
+
+ if err := pkg.Link(filepath.Join(dir, "out", "test")); err != nil {
+ b.Fatal("Got error when linking:", err)
+ }
+
+ if pkg.rebuilt {
+ b.Fatal("Should not have rebuilt anything")
+ }
+ }
+ })
+}
+
+///////////////////////////////////////////////////////
+// Templates used to create fake compilable packages //
+///////////////////////////////////////////////////////
+
+const go_main_main = `
+package main
+import (
+ "fmt"
+ "android/soong/a"
+ "android/soong/b"
+)
+func main() {
+ fmt.Println(a.Stdout, b.Stdout)
+}
+`
+
+const go_a_a = `
+package a
+import "os"
+var Stdout = os.Stdout
+`
+
+const go_a_b = `
+package a
+`
+
+const go_b_a = `
+package b
+import "android/soong/a"
+var Stdout = a.Stdout
+`
+
+type T interface {
+ Fatal(args ...interface{})
+ Fatalf(format string, args ...interface{})
+}
+
+type loadPkgFunc func() *GoPackage
+
+func setupDir(t T, test func(dir string, loadPkg loadPkgFunc)) {
+ dir, err := ioutil.TempDir("", "test")
+ if err != nil {
+ t.Fatalf("Error creating temporary directory: %#v", err)
+ }
+ defer os.RemoveAll(dir)
+
+ writeFile := func(name, contents string) {
+ if err := ioutil.WriteFile(filepath.Join(dir, name), []byte(contents), 0666); err != nil {
+ t.Fatalf("Error writing %q: %#v", name, err)
+ }
+ }
+ mkdir := func(name string) {
+ if err := os.Mkdir(filepath.Join(dir, name), 0777); err != nil {
+ t.Fatalf("Error creating %q directory: %#v", name, err)
+ }
+ }
+ mkdir("main")
+ mkdir("a")
+ mkdir("b")
+ writeFile("main/main.go", go_main_main)
+ writeFile("a/a.go", go_a_a)
+ writeFile("a/b.go", go_a_b)
+ writeFile("b/a.go", go_b_a)
+
+ loadPkg := func() *GoPackage {
+ pkg := &GoPackage{
+ Name: "main",
+ }
+ pkgMap := &pkgPathMapping{}
+ pkgMap.Set("android/soong=" + dir)
+ if err := pkg.FindDeps(filepath.Join(dir, "main"), pkgMap); err != nil {
+ t.Fatalf("Error finding deps: %v", err)
+ }
+ return pkg
+ }
+
+ test(dir, loadPkg)
+}
diff --git a/cmd/multiproduct_kati/Android.bp b/cmd/multiproduct_kati/Android.bp
new file mode 100644
index 0000000..b264c35
--- /dev/null
+++ b/cmd/multiproduct_kati/Android.bp
@@ -0,0 +1,25 @@
+// Copyright 2017 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.
+
+blueprint_go_binary {
+ name: "multiproduct_kati",
+ deps: [
+ "soong-ui-build",
+ "soong-ui-logger",
+ "soong-ui-tracer",
+ ],
+ srcs: [
+ "main.go",
+ ],
+}
diff --git a/cmd/multiproduct_kati/main.go b/cmd/multiproduct_kati/main.go
new file mode 100644
index 0000000..3aa5a87
--- /dev/null
+++ b/cmd/multiproduct_kati/main.go
@@ -0,0 +1,206 @@
+// Copyright 2017 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "bytes"
+ "context"
+ "flag"
+ "fmt"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "sync"
+ "time"
+
+ "android/soong/ui/build"
+ "android/soong/ui/logger"
+ "android/soong/ui/tracer"
+)
+
+// We default to number of cpus / 4, which seems to be the sweet spot for my
+// system. I suspect this is mostly due to memory or disk bandwidth though, and
+// may depend on the size ofthe source tree, so this probably isn't a great
+// default.
+func detectNumJobs() int {
+ if runtime.NumCPU() < 4 {
+ return 1
+ }
+ return runtime.NumCPU() / 4
+}
+
+var numJobs = flag.Int("j", detectNumJobs(), "number of parallel kati jobs")
+
+var keep = flag.Bool("keep", false, "keep successful output files")
+
+var outDir = flag.String("out", "", "path to store output directories (defaults to tmpdir under $OUT when empty)")
+
+var onlyConfig = flag.Bool("only-config", false, "Only run product config (not Soong or Kati)")
+var onlySoong = flag.Bool("only-soong", false, "Only run product config and Soong (not Kati)")
+
+type Product struct {
+ ctx build.Context
+ config build.Config
+}
+
+func main() {
+ log := logger.New(os.Stderr)
+ defer log.Cleanup()
+
+ flag.Parse()
+
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ trace := tracer.New(log)
+ defer trace.Close()
+
+ build.SetupSignals(log, cancel, func() {
+ trace.Close()
+ log.Cleanup()
+ })
+
+ buildCtx := build.Context{&build.ContextImpl{
+ Context: ctx,
+ Logger: log,
+ Tracer: trace,
+ StdioInterface: build.StdioImpl{},
+ }}
+
+ failed := false
+
+ config := build.NewConfig(buildCtx)
+ if *outDir == "" {
+ name := "multiproduct-" + time.Now().Format("20060102150405")
+
+ *outDir = filepath.Join(config.OutDir(), name)
+
+ if err := os.MkdirAll(*outDir, 0777); err != nil {
+ log.Fatalf("Failed to create tempdir: %v", err)
+ }
+
+ if !*keep {
+ defer func() {
+ if !failed {
+ os.RemoveAll(*outDir)
+ }
+ }()
+ }
+ }
+ config.Environment().Set("OUT_DIR", *outDir)
+ log.Println("Output directory:", *outDir)
+
+ build.SetupOutDir(buildCtx, config)
+ log.SetOutput(filepath.Join(config.OutDir(), "soong.log"))
+ trace.SetOutput(filepath.Join(config.OutDir(), "build.trace"))
+
+ vars, err := build.DumpMakeVars(buildCtx, config, nil, nil, []string{"all_named_products"})
+ if err != nil {
+ log.Fatal(err)
+ }
+ products := strings.Fields(vars["all_named_products"])
+ log.Verbose("Got product list:", products)
+
+ var wg sync.WaitGroup
+ errs := make(chan error, len(products))
+ productConfigs := make(chan Product, len(products))
+
+ // Run the product config for every product in parallel
+ for _, product := range products {
+ wg.Add(1)
+ go func(product string) {
+ defer wg.Done()
+ defer logger.Recover(func(err error) {
+ errs <- fmt.Errorf("Error building %s: %v", product, err)
+ })
+
+ productOutDir := filepath.Join(config.OutDir(), product)
+
+ if err := os.MkdirAll(productOutDir, 0777); err != nil {
+ log.Fatalf("Error creating out directory: %v", err)
+ }
+
+ f, err := os.Create(filepath.Join(productOutDir, "std.log"))
+ if err != nil {
+ log.Fatalf("Error creating std.log: %v", err)
+ }
+
+ productLog := logger.New(&bytes.Buffer{})
+ productLog.SetOutput(filepath.Join(productOutDir, "soong.log"))
+
+ productCtx := build.Context{&build.ContextImpl{
+ Context: ctx,
+ Logger: productLog,
+ Tracer: trace,
+ StdioInterface: build.NewCustomStdio(nil, f, f),
+ Thread: trace.NewThread(product),
+ }}
+
+ productConfig := build.NewConfig(productCtx)
+ productConfig.Environment().Set("OUT_DIR", productOutDir)
+ productConfig.Lunch(productCtx, product, "eng")
+
+ build.Build(productCtx, productConfig, build.BuildProductConfig)
+ productConfigs <- Product{productCtx, productConfig}
+ }(product)
+ }
+ go func() {
+ defer close(productConfigs)
+ wg.Wait()
+ }()
+
+ var wg2 sync.WaitGroup
+ // Then run up to numJobs worth of Soong and Kati
+ for i := 0; i < *numJobs; i++ {
+ wg2.Add(1)
+ go func() {
+ defer wg2.Done()
+ for product := range productConfigs {
+ func() {
+ defer logger.Recover(func(err error) {
+ errs <- fmt.Errorf("Error building %s: %v", product.config.TargetProduct(), err)
+ })
+
+ buildWhat := 0
+ if !*onlyConfig {
+ buildWhat |= build.BuildSoong
+ if !*onlySoong {
+ buildWhat |= build.BuildKati
+ }
+ }
+ build.Build(product.ctx, product.config, buildWhat)
+ if !*keep {
+ os.RemoveAll(product.config.OutDir())
+ }
+ log.Println("Finished running for", product.config.TargetProduct())
+ }()
+ }
+ }()
+ }
+ go func() {
+ wg2.Wait()
+ close(errs)
+ }()
+
+ for err := range errs {
+ failed = true
+ log.Print(err)
+ }
+
+ if failed {
+ log.Fatalln("Failed")
+ }
+}
diff --git a/cmd/soong_ui/Android.bp b/cmd/soong_ui/Android.bp
new file mode 100644
index 0000000..f09e42e
--- /dev/null
+++ b/cmd/soong_ui/Android.bp
@@ -0,0 +1,25 @@
+// Copyright 2017 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.
+
+blueprint_go_binary {
+ name: "soong_ui",
+ deps: [
+ "soong-ui-build",
+ "soong-ui-logger",
+ "soong-ui-tracer",
+ ],
+ srcs: [
+ "main.go",
+ ],
+}
diff --git a/cmd/soong_ui/main.go b/cmd/soong_ui/main.go
new file mode 100644
index 0000000..26887ae
--- /dev/null
+++ b/cmd/soong_ui/main.go
@@ -0,0 +1,95 @@
+// Copyright 2017 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "context"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "time"
+
+ "android/soong/ui/build"
+ "android/soong/ui/logger"
+ "android/soong/ui/tracer"
+)
+
+func indexList(s string, list []string) int {
+ for i, l := range list {
+ if l == s {
+ return i
+ }
+ }
+
+ return -1
+}
+
+func inList(s string, list []string) bool {
+ return indexList(s, list) != -1
+}
+
+func main() {
+ log := logger.New(os.Stderr)
+ defer log.Cleanup()
+
+ if len(os.Args) < 2 || !inList("--make-mode", os.Args) {
+ log.Fatalln("The `soong` native UI is not yet available.")
+ }
+
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ trace := tracer.New(log)
+ defer trace.Close()
+
+ build.SetupSignals(log, cancel, func() {
+ trace.Close()
+ log.Cleanup()
+ })
+
+ buildCtx := build.Context{&build.ContextImpl{
+ Context: ctx,
+ Logger: log,
+ Tracer: trace,
+ StdioInterface: build.StdioImpl{},
+ }}
+ config := build.NewConfig(buildCtx, os.Args[1:]...)
+
+ log.SetVerbose(config.IsVerbose())
+ build.SetupOutDir(buildCtx, config)
+
+ if config.Dist() {
+ logsDir := filepath.Join(config.DistDir(), "logs")
+ os.MkdirAll(logsDir, 0777)
+ log.SetOutput(filepath.Join(logsDir, "soong.log"))
+ trace.SetOutput(filepath.Join(logsDir, "build.trace"))
+ } else {
+ log.SetOutput(filepath.Join(config.OutDir(), "soong.log"))
+ trace.SetOutput(filepath.Join(config.OutDir(), "build.trace"))
+ }
+
+ if start, ok := os.LookupEnv("TRACE_BEGIN_SOONG"); ok {
+ if !strings.HasSuffix(start, "N") {
+ if start_time, err := strconv.ParseUint(start, 10, 64); err == nil {
+ log.Verbosef("Took %dms to start up.",
+ time.Since(time.Unix(0, int64(start_time))).Nanoseconds()/time.Millisecond.Nanoseconds())
+ buildCtx.CompleteTrace("startup", start_time, uint64(time.Now().UnixNano()))
+ }
+ }
+ }
+
+ build.Build(buildCtx, config, build.BuildAll)
+}
diff --git a/cmd/soong_zip/soong_zip.go b/cmd/soong_zip/soong_zip.go
index b0f3daa..3e62016 100644
--- a/cmd/soong_zip/soong_zip.go
+++ b/cmd/soong_zip/soong_zip.go
@@ -22,6 +22,7 @@
"hash/crc32"
"io"
"io/ioutil"
+ "log"
"os"
"path/filepath"
"runtime"
@@ -54,7 +55,11 @@
}
type fileArg struct {
- relativeRoot, file string
+ rootPrefix, relativeRoot, file string
+}
+
+type pathMapping struct {
+ dest, src string
}
type fileArgs []fileArg
@@ -65,10 +70,13 @@
func (l *fileArgs) Set(s string) error {
if *relativeRoot == "" {
- return fmt.Errorf("must pass -C before -f")
+ return fmt.Errorf("must pass -C before -f or -l")
}
- *l = append(*l, fileArg{filepath.Clean(*relativeRoot), s})
+ *l = append(*l,
+ fileArg{rootPrefix: filepath.Clean(*rootPrefix),
+ relativeRoot: filepath.Clean(*relativeRoot),
+ file: s})
return nil
}
@@ -80,11 +88,13 @@
out = flag.String("o", "", "file to write zip file to")
manifest = flag.String("m", "", "input jar manifest file name")
directories = flag.Bool("d", false, "include directories in zip")
+ rootPrefix = flag.String("P", "", "path prefix within the zip at which to place files")
relativeRoot = flag.String("C", "", "path to use as relative root of files in next -f or -l argument")
parallelJobs = flag.Int("j", runtime.NumCPU(), "number of parallel threads to use")
compLevel = flag.Int("L", 5, "deflate compression level (0-9)")
- listFiles fileArgs
- files fileArgs
+
+ listFiles fileArgs
+ files fileArgs
cpuProfile = flag.String("cpuprofile", "", "write cpu profile to file")
traceFile = flag.String("trace", "", "write trace to file")
@@ -163,14 +173,66 @@
compLevel: *compLevel,
}
- err := w.write(*out, listFiles, *manifest)
+ pathMappings := []pathMapping{}
+ set := make(map[string]string)
+
+ // load listFiles, which specify other files to include.
+ for _, l := range listFiles {
+ list, err := ioutil.ReadFile(l.file)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err.Error())
+ os.Exit(1)
+ }
+ srcs := strings.Split(string(list), "\n")
+ for _, src := range srcs {
+ if err := fillPathPairs(l.rootPrefix, l.relativeRoot, src,
+ set, &pathMappings); err != nil {
+ log.Fatal(err)
+ }
+ }
+ }
+
+ // also include the usual files that are to be added directly.
+ for _, f := range files {
+ if err := fillPathPairs(f.rootPrefix, f.relativeRoot,
+ f.file, set, &pathMappings); err != nil {
+ log.Fatal(err)
+ }
+ }
+
+ err := w.write(*out, pathMappings, *manifest)
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
}
}
-func (z *zipWriter) write(out string, listFiles fileArgs, manifest string) error {
+func fillPathPairs(prefix, rel, src string, set map[string]string, pathMappings *[]pathMapping) error {
+ src = strings.TrimSpace(src)
+ if src == "" {
+ return nil
+ }
+ src = filepath.Clean(src)
+ dest, err := filepath.Rel(rel, src)
+ if err != nil {
+ return err
+ }
+ dest = filepath.Join(prefix, dest)
+
+ if _, found := set[dest]; found {
+ return fmt.Errorf("found two file paths to be copied into dest path: %q,"+
+ " both [%q]%q and [%q]%q!",
+ dest, dest, src, dest, set[dest])
+ } else {
+ set[dest] = src
+ }
+
+ *pathMappings = append(*pathMappings, pathMapping{dest: dest, src: src})
+
+ return nil
+}
+
+func (z *zipWriter) write(out string, pathMappings []pathMapping, manifest string) error {
f, err := os.Create(out)
if err != nil {
return err
@@ -206,16 +268,8 @@
var err error
defer close(z.writeOps)
- for _, listFile := range listFiles {
- err = z.writeListFile(listFile)
- if err != nil {
- z.errors <- err
- return
- }
- }
-
- for _, file := range files {
- err = z.writeRelFile(file.relativeRoot, file.file)
+ for _, ele := range pathMappings {
+ err = z.writeFile(ele.dest, ele.src)
if err != nil {
z.errors <- err
return
@@ -317,64 +371,28 @@
}
}
-func (z *zipWriter) writeListFile(listFile fileArg) error {
- list, err := ioutil.ReadFile(listFile.file)
- if err != nil {
- return err
- }
-
- files := strings.Split(string(list), "\n")
-
- for _, file := range files {
- file = strings.TrimSpace(file)
- if file == "" {
- continue
- }
- err = z.writeRelFile(listFile.relativeRoot, file)
- if err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func (z *zipWriter) writeRelFile(root, file string) error {
- file = filepath.Clean(file)
-
- rel, err := filepath.Rel(root, file)
- if err != nil {
- return err
- }
-
- err = z.writeFile(rel, file)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (z *zipWriter) writeFile(rel, file string) error {
+func (z *zipWriter) writeFile(dest, src string) error {
var fileSize int64
+ var executable bool
- if s, err := os.Lstat(file); err != nil {
+ if s, err := os.Lstat(src); err != nil {
return err
} else if s.IsDir() {
if z.directories {
- return z.writeDirectory(rel)
+ return z.writeDirectory(dest)
}
return nil
} else if s.Mode()&os.ModeSymlink != 0 {
- return z.writeSymlink(rel, file)
+ return z.writeSymlink(dest, src)
} else if !s.Mode().IsRegular() {
- return fmt.Errorf("%s is not a file, directory, or symlink", file)
+ return fmt.Errorf("%s is not a file, directory, or symlink", src)
} else {
fileSize = s.Size()
+ executable = s.Mode()&0100 != 0
}
if z.directories {
- dir, _ := filepath.Split(rel)
+ dir, _ := filepath.Split(dest)
err := z.writeDirectory(dir)
if err != nil {
return err
@@ -388,15 +406,18 @@
// we're sure about the Method and CRC.
ze := &zipEntry{
fh: &zip.FileHeader{
- Name: rel,
+ Name: dest,
Method: zip.Deflate,
UncompressedSize64: uint64(fileSize),
},
}
ze.fh.SetModTime(z.time)
+ if executable {
+ ze.fh.SetMode(0700)
+ }
- r, err := os.Open(file)
+ r, err := os.Open(src)
if err != nil {
return err
}
@@ -445,7 +466,7 @@
f.Close()
}(wg, r)
} else {
- go z.compressWholeFile(rel, r, exec, compressChan)
+ go z.compressWholeFile(ze, r, exec, compressChan)
}
return nil
@@ -514,26 +535,19 @@
return buf, nil
}
-func (z *zipWriter) compressWholeFile(rel string, r *os.File, exec Execution, compressChan chan *zipEntry) {
+func (z *zipWriter) compressWholeFile(ze *zipEntry, r *os.File, exec Execution, compressChan chan *zipEntry) {
var bufSize int
defer r.Close()
- fileHeader := &zip.FileHeader{
- Name: rel,
- Method: zip.Deflate,
- }
- fileHeader.SetModTime(z.time)
-
crc := crc32.NewIEEE()
- count, err := io.Copy(crc, r)
+ _, err := io.Copy(crc, r)
if err != nil {
z.errors <- err
return
}
- fileHeader.CRC32 = crc.Sum32()
- fileHeader.UncompressedSize64 = uint64(count)
+ ze.fh.CRC32 = crc.Sum32()
_, err = r.Seek(0, 0)
if err != nil {
@@ -543,10 +557,7 @@
compressed, err := z.compressBlock(r, nil, true)
- ze := &zipEntry{
- fh: fileHeader,
- futureReaders: make(chan chan io.Reader, 1),
- }
+ ze.futureReaders = make(chan chan io.Reader, 1)
futureReader := make(chan io.Reader, 1)
ze.futureReaders <- futureReader
close(ze.futureReaders)
diff --git a/docs/after.png b/docs/after.png
new file mode 100644
index 0000000..fdd14d2
--- /dev/null
+++ b/docs/after.png
Binary files differ
diff --git a/docs/before.png b/docs/before.png
new file mode 100644
index 0000000..a6a1424
--- /dev/null
+++ b/docs/before.png
Binary files differ
diff --git a/docs/clion.md b/docs/clion.md
new file mode 100644
index 0000000..d6ae19a
--- /dev/null
+++ b/docs/clion.md
@@ -0,0 +1,72 @@
+# CLion project generator
+
+Soong can generate CLion projects. This is intended for source code editing
+only. Build should still be done via make/m/mm(a)/mmm(a).
+
+CMakeLists.txt project file generation is enabled via environment variable:
+
+```bash
+$ export SOONG_GEN_CMAKEFILES=1
+$ export SOONG_GEN_CMAKEFILES_DEBUG=1
+```
+
+You can then trigger a full build:
+
+```bash
+$ make -j64
+```
+or build only the project you are interested in:
+
+```bash
+$ make frameworks/native/service/libs/ui
+```
+
+Projects are generated in the ``out`` directory. In the case of libui, the path would
+be:
+
+```bash
+out/development/ide/clion/frameworks/native/libs/ui/libui-arm64-android/CMakeLists.txt
+```
+Note: The generator creates one folder per targetname-architecture-os combination.
+In the case of libui you endup with two projects:
+
+```bash
+$ ls out/development/ide/clion/frameworks/native/libs/ui
+libui-arm64-android libui-arm-android
+```
+
+### Edit multiple projects at once
+To combine several projects into one, you can create super projects
+and place them in:
+
+```bash
+development/ide/clion/[PATH_YOU_WANT]/..
+```
+
+These file will be symbolicaly linked in ``out/development/ide/clion``. All folders
+will also be created there.
+
+An example of a super project for surfaceflinger (using libui and libgui)
+located in development/ide/clion/frameworks/native:
+
+```
+cmake_minimum_required(VERSION 3.6)
+project(native)
+add_subdirectory(services/surfaceflinger)
+add_subdirectory(libs/ui/libui-arm64-android)
+add_subdirectory(libs/gui/libgui-arm64-android)
+```
+
+
+### Flattened filesystem
+
+Upon opening a project it looks like all the folder structure has been
+flattened:
+
+
+
+This is because you need to change the project root. Via Tools > CMake >
+Change Project Root:
+
+
+
diff --git a/genrule/filegroup.go b/genrule/filegroup.go
index 9b53c9f..71c5439 100644
--- a/genrule/filegroup.go
+++ b/genrule/filegroup.go
@@ -21,7 +21,7 @@
)
func init() {
- android.RegisterModuleType("filegroup", fileGroupFactory)
+ android.RegisterModuleType("filegroup", FileGroupFactory)
}
type fileGroupProperties struct {
@@ -29,6 +29,12 @@
Srcs []string
Exclude_srcs []string
+
+ // The base path to the files. May be used by other modules to determine which portion
+ // of the path to use. For example, when a filegroup is used as data in a cc_test rule,
+ // the base path is stripped off the path and the remaining path is used as the
+ // installation directory.
+ Path string
}
type fileGroup struct {
@@ -42,7 +48,7 @@
// filegroup modules contain a list of files, and can be used to export files across package
// boundaries. filegroups (and genrules) can be referenced from srcs properties of other modules
// using the syntax ":module".
-func fileGroupFactory() (blueprint.Module, []interface{}) {
+func FileGroupFactory() (blueprint.Module, []interface{}) {
module := &fileGroup{}
return android.InitAndroidModule(module, &module.properties)
@@ -53,7 +59,7 @@
}
func (fg *fileGroup) GenerateAndroidBuildActions(ctx android.ModuleContext) {
- fg.srcs = ctx.ExpandSources(fg.properties.Srcs, fg.properties.Exclude_srcs)
+ fg.srcs = ctx.ExpandSourcesSubDir(fg.properties.Srcs, fg.properties.Exclude_srcs, fg.properties.Path)
}
func (fg *fileGroup) Srcs() android.Paths {
diff --git a/genrule/genrule.go b/genrule/genrule.go
index 5c71742..ee4c503 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -150,8 +150,6 @@
} else {
ctx.ModuleErrorf("host tool %q missing output file", ctx.OtherModuleName(module))
}
- } else {
- ctx.ModuleErrorf("unknown dependency %q", ctx.OtherModuleName(module))
}
})
}
diff --git a/phony/phony.go b/phony/phony.go
new file mode 100644
index 0000000..c405af8
--- /dev/null
+++ b/phony/phony.go
@@ -0,0 +1,63 @@
+// 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 phony
+
+import (
+ "fmt"
+ "io"
+ "strings"
+
+ "github.com/google/blueprint"
+
+ "android/soong/android"
+)
+
+func init() {
+ android.RegisterModuleType("phony", phonyFactory)
+}
+
+type phony struct {
+ android.ModuleBase
+ requiredModuleNames []string
+}
+
+func phonyFactory() (blueprint.Module, []interface{}) {
+ module := &phony{}
+
+ return android.InitAndroidModule(module)
+}
+
+func (p *phony) DepsMutator(ctx android.BottomUpMutatorContext) {
+}
+
+func (p *phony) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+ p.requiredModuleNames = ctx.RequiredModuleNames()
+ if len(p.requiredModuleNames) == 0 {
+ ctx.PropertyErrorf("required", "phony must not have empty required dependencies in order to be useful(and therefore permitted).")
+ }
+}
+
+func (p *phony) AndroidMk() (ret android.AndroidMkData, err error) {
+ ret.Custom = func(w io.Writer, name, prefix, moduleDir string) error {
+ fmt.Fprintln(w, "\ninclude $(CLEAR_VARS)")
+ fmt.Fprintln(w, "LOCAL_PATH :=", moduleDir)
+ fmt.Fprintln(w, "LOCAL_MODULE :=", name)
+ fmt.Fprintln(w, "LOCAL_REQUIRED_MODULES := "+strings.Join(p.requiredModuleNames, " "))
+ fmt.Fprintln(w, "include $(BUILD_PHONY_PACKAGE)")
+
+ return nil
+ }
+ return
+}
diff --git a/root.bp b/root.bp
index a1b1f6b..f14b047 100644
--- a/root.bp
+++ b/root.bp
@@ -15,10 +15,12 @@
"build/tools/*",
"dalvik",
"development/*",
+ "device/*/*",
"external/*",
"frameworks/av",
"frameworks/base",
"frameworks/compile/*",
+ "frameworks/hardware/interfaces",
"frameworks/native",
"frameworks/rs",
"frameworks/wilhelm",
@@ -26,9 +28,11 @@
"libcore",
"libnativehelper",
"packages/apps/HTMLViewer",
+ "prebuilts/clang/host/linux-x86",
"prebuilts/ndk",
"prebuilts/sdk",
"system/*",
+ "system/hardware/interfaces",
"system/tools/*",
"test/vts",
"test/vts-testcase/*",
diff --git a/soong_ui.bash b/soong_ui.bash
new file mode 100755
index 0000000..e3997cf
--- /dev/null
+++ b/soong_ui.bash
@@ -0,0 +1,109 @@
+#!/bin/bash -eu
+#
+# Copyright 2017 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.
+
+# To track how long we took to startup. %N isn't supported on Darwin, but
+# that's detected in the Go code, and skip calculating the startup time.
+export TRACE_BEGIN_SOONG=$(date +%s%N)
+
+# Function to find top of the source tree (if $TOP isn't set) by walking up the
+# tree.
+function gettop
+{
+ local TOPFILE=build/soong/root.bp
+ if [ -z "${TOP-}" -a -f "${TOP-}/${TOPFILE}" ] ; then
+ # The following circumlocution ensures we remove symlinks from TOP.
+ (cd $TOP; PWD= /bin/pwd)
+ else
+ if [ -f $TOPFILE ] ; then
+ # The following circumlocution (repeated below as well) ensures
+ # that we record the true directory name and not one that is
+ # faked up with symlink names.
+ PWD= /bin/pwd
+ else
+ local HERE=$PWD
+ T=
+ while [ \( ! \( -f $TOPFILE \) \) -a \( $PWD != "/" \) ]; do
+ \cd ..
+ T=`PWD= /bin/pwd -P`
+ done
+ \cd $HERE
+ if [ -f "$T/$TOPFILE" ]; then
+ echo $T
+ fi
+ fi
+ fi
+}
+
+# Bootstrap microfactory from source if necessary and use it to build the
+# soong_ui binary, then run soong_ui.
+function run_go
+{
+ # Increment when microfactory changes enough that it cannot rebuild itself.
+ # For example, if we use a new command line argument that doesn't work on older versions.
+ local mf_version=2
+
+ local mf_src="${TOP}/build/soong/cmd/microfactory"
+
+ local out_dir="${OUT_DIR-}"
+ if [ -z "${out_dir}" ]; then
+ if [ "${OUT_DIR_COMMON_BASE-}" ]; then
+ out_dir="${OUT_DIR_COMMON_BASE}/$(basename ${TOP})"
+ else
+ out_dir="${TOP}/out"
+ fi
+ fi
+
+ local mf_bin="${out_dir}/microfactory_$(uname)"
+ local mf_version_file="${out_dir}/.microfactory_$(uname)_version"
+ local soong_ui_bin="${out_dir}/soong_ui"
+ local from_src=1
+
+ if [ -f "${mf_bin}" ] && [ -f "${mf_version_file}" ]; then
+ if [ "${mf_version}" -eq "$(cat "${mf_version_file}")" ]; then
+ from_src=0
+ fi
+ fi
+
+ local mf_cmd
+ if [ $from_src -eq 1 ]; then
+ mf_cmd="${GOROOT}/bin/go run ${mf_src}/microfactory.go"
+ else
+ mf_cmd="${mf_bin}"
+ fi
+
+ ${mf_cmd} -s "${mf_src}" -b "${mf_bin}" \
+ -pkg-path "android/soong=${TOP}/build/soong" -trimpath "${TOP}/build/soong" \
+ -o "${soong_ui_bin}" android/soong/cmd/soong_ui
+
+ if [ $from_src -eq 1 ]; then
+ echo "${mf_version}" >"${mf_version_file}"
+ fi
+
+ exec "${out_dir}/soong_ui" "$@"
+}
+
+export TOP=$(gettop)
+case $(uname) in
+ Linux)
+ export GOROOT="${TOP}/prebuilts/go/linux-x86/"
+ ;;
+ Darwin)
+ export GOROOT="${TOP}/prebuilts/go/darwin-x86/"
+ ;;
+ *) echo "unknown OS:" $(uname) >&2 && exit 1;;
+esac
+
+run_go "$@"
diff --git a/third_party/zip/Android.bp b/third_party/zip/Android.bp
index 044e6f8..ec89c0c 100644
--- a/third_party/zip/Android.bp
+++ b/third_party/zip/Android.bp
@@ -24,6 +24,7 @@
"android.go",
],
testSrcs: [
+ "android_test.go",
"reader_test.go",
"writer_test.go",
"zip_test.go",
diff --git a/third_party/zip/android.go b/third_party/zip/android.go
index e2e46ff..f3b6055 100644
--- a/third_party/zip/android.go
+++ b/third_party/zip/android.go
@@ -32,6 +32,12 @@
fh := &fileHeader
fh.Flags |= 0x8
+ // The zip64 extras change between the Central Directory and Local File Header, while we use
+ // the same structure for both. The Local File Haeder is taken care of by us writing a data
+ // descriptor with the zip64 values. The Central Directory Entry is written by Close(), where
+ // the zip64 extra is automatically created and appended when necessary.
+ fh.Extra = stripZip64Extras(fh.Extra)
+
h := &header{
FileHeader: fh,
offset: uint64(w.cw.count),
@@ -70,6 +76,29 @@
return err
}
+// Strip any Zip64 extra fields
+func stripZip64Extras(input []byte) []byte {
+ ret := []byte{}
+
+ for len(input) >= 4 {
+ r := readBuf(input)
+ tag := r.uint16()
+ size := r.uint16()
+ if int(size) > len(r) {
+ break
+ }
+ if tag != zip64ExtraId {
+ ret = append(ret, input[:4+size]...)
+ }
+ input = input[4+size:]
+ }
+
+ // Keep any trailing data
+ ret = append(ret, input...)
+
+ return ret
+}
+
// CreateCompressedHeader adds a file to the zip file using the provied
// FileHeader for the file metadata.
// It returns a Writer to which the already compressed file contents
diff --git a/third_party/zip/android_test.go b/third_party/zip/android_test.go
new file mode 100644
index 0000000..cdf66ff
--- /dev/null
+++ b/third_party/zip/android_test.go
@@ -0,0 +1,71 @@
+// Copyright 2017 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 zip
+
+import (
+ "bytes"
+ "testing"
+)
+
+var stripZip64Testcases = []struct {
+ name string
+ in []byte
+ out []byte
+}{
+ {
+ name: "empty",
+ in: []byte{},
+ out: []byte{},
+ },
+ {
+ name: "trailing data",
+ in: []byte{1, 2, 3},
+ out: []byte{1, 2, 3},
+ },
+ {
+ name: "valid non-zip64 extra",
+ in: []byte{2, 0, 2, 0, 1, 2},
+ out: []byte{2, 0, 2, 0, 1, 2},
+ },
+ {
+ name: "two valid non-zip64 extras",
+ in: []byte{2, 0, 2, 0, 1, 2, 2, 0, 0, 0},
+ out: []byte{2, 0, 2, 0, 1, 2, 2, 0, 0, 0},
+ },
+ {
+ name: "simple zip64 extra",
+ in: []byte{1, 0, 8, 0, 1, 2, 3, 4, 5, 6, 7, 8},
+ out: []byte{},
+ },
+ {
+ name: "zip64 extra and valid non-zip64 extra",
+ in: []byte{1, 0, 8, 0, 1, 2, 3, 4, 5, 6, 7, 8, 2, 0, 0, 0},
+ out: []byte{2, 0, 0, 0},
+ },
+ {
+ name: "invalid extra",
+ in: []byte{0, 0, 8, 0, 0, 0},
+ out: []byte{0, 0, 8, 0, 0, 0},
+ },
+}
+
+func TestStripZip64Extras(t *testing.T) {
+ for _, testcase := range stripZip64Testcases {
+ got := stripZip64Extras(testcase.in)
+ if !bytes.Equal(got, testcase.out) {
+ t.Errorf("Failed testcase %s\ninput: %v\n want: %v\n got: %v\n", testcase.name, testcase.in, testcase.out, got)
+ }
+ }
+}
diff --git a/third_party/zip/zip_test.go b/third_party/zip/zip_test.go
index 3a3c915..7373660 100644
--- a/third_party/zip/zip_test.go
+++ b/third_party/zip/zip_test.go
@@ -10,7 +10,6 @@
"bytes"
"fmt"
"hash"
- "internal/testenv"
"io"
"io/ioutil"
"sort"
@@ -20,7 +19,7 @@
)
func TestOver65kFiles(t *testing.T) {
- if testing.Short() && testenv.Builder() == "" {
+ if testing.Short() {
t.Skip("skipping in short mode")
}
buf := new(bytes.Buffer)
diff --git a/ui/build/Android.bp b/ui/build/Android.bp
new file mode 100644
index 0000000..51aed2c
--- /dev/null
+++ b/ui/build/Android.bp
@@ -0,0 +1,44 @@
+// Copyright 2017 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.
+
+bootstrap_go_package {
+ name: "soong-ui-build",
+ pkgPath: "android/soong/ui/build",
+ deps: [
+ "soong-ui-logger",
+ "soong-ui-tracer",
+ ],
+ srcs: [
+ "build.go",
+ "config.go",
+ "context.go",
+ "environment.go",
+ "kati.go",
+ "make.go",
+ "ninja.go",
+ "signal.go",
+ "soong.go",
+ "util.go",
+ ],
+ testSrcs: [
+ "environment_test.go",
+ "util_test.go",
+ ],
+ darwin: {
+ srcs: ["util_darwin.go"],
+ },
+ linux: {
+ srcs: ["util_linux.go"],
+ },
+}
diff --git a/ui/build/build.go b/ui/build/build.go
new file mode 100644
index 0000000..506ff51
--- /dev/null
+++ b/ui/build/build.go
@@ -0,0 +1,105 @@
+// Copyright 2017 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 build
+
+import (
+ "os"
+ "os/exec"
+ "path/filepath"
+ "text/template"
+)
+
+// Ensures the out directory exists, and has the proper files to prevent kati
+// from recursing into it.
+func SetupOutDir(ctx Context, config Config) {
+ ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), "Android.mk"))
+ ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), "CleanSpec.mk"))
+ ensureEmptyFileExists(ctx, filepath.Join(config.SoongOutDir(), ".soong.in_make"))
+ // The ninja_build file is used by our buildbots to understand that the output
+ // can be parsed as ninja output.
+ ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), "ninja_build"))
+}
+
+var combinedBuildNinjaTemplate = template.Must(template.New("combined").Parse(`
+builddir = {{.OutDir}}
+include {{.KatiNinjaFile}}
+include {{.SoongNinjaFile}}
+build {{.CombinedNinjaFile}}: phony {{.SoongNinjaFile}}
+`))
+
+func createCombinedBuildNinjaFile(ctx Context, config Config) {
+ file, err := os.Create(config.CombinedNinjaFile())
+ if err != nil {
+ ctx.Fatalln("Failed to create combined ninja file:", err)
+ }
+ defer file.Close()
+
+ if err := combinedBuildNinjaTemplate.Execute(file, config); err != nil {
+ ctx.Fatalln("Failed to write combined ninja file:", err)
+ }
+}
+
+const (
+ BuildNone = iota
+ BuildProductConfig = 1 << iota
+ BuildSoong = 1 << iota
+ BuildKati = 1 << iota
+ BuildNinja = 1 << iota
+ BuildAll = BuildProductConfig | BuildSoong | BuildKati | BuildNinja
+)
+
+// Build the tree. The 'what' argument can be used to chose which components of
+// the build to run.
+func Build(ctx Context, config Config, what int) {
+ ctx.Verboseln("Starting build with args:", config.Arguments())
+ ctx.Verboseln("Environment:", config.Environment().Environ())
+
+ if inList("help", config.Arguments()) {
+ cmd := exec.CommandContext(ctx.Context, "make", "-f", "build/core/help.mk")
+ cmd.Env = config.Environment().Environ()
+ cmd.Stdout = ctx.Stdout()
+ cmd.Stderr = ctx.Stderr()
+ if err := cmd.Run(); err != nil {
+ ctx.Fatalln("Failed to run make:", err)
+ }
+ return
+ }
+
+ SetupOutDir(ctx, config)
+
+ if what&BuildProductConfig != 0 {
+ // Run make for product config
+ runMakeProductConfig(ctx, config)
+ }
+
+ if what&BuildSoong != 0 {
+ // Run Soong
+ runSoongBootstrap(ctx, config)
+ runSoong(ctx, config)
+ }
+
+ if what&BuildKati != 0 {
+ // Run ckati
+ runKati(ctx, config)
+ }
+
+ if what&BuildNinja != 0 {
+ // Write combined ninja file
+ createCombinedBuildNinjaFile(ctx, config)
+
+ // Run ninja
+ runNinja(ctx, config)
+ }
+}
diff --git a/ui/build/config.go b/ui/build/config.go
new file mode 100644
index 0000000..9a8a3fb
--- /dev/null
+++ b/ui/build/config.go
@@ -0,0 +1,316 @@
+// Copyright 2017 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 build
+
+import (
+ "log"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strconv"
+ "strings"
+)
+
+type Config struct{ *configImpl }
+
+type configImpl struct {
+ // From the environment
+ arguments []string
+ goma bool
+ environ *Environment
+
+ // From the arguments
+ parallel int
+ keepGoing int
+ verbose bool
+ dist bool
+
+ // From the product config
+ katiArgs []string
+ ninjaArgs []string
+ katiSuffix string
+}
+
+const srcDirFileCheck = "build/soong/root.bp"
+
+func NewConfig(ctx Context, args ...string) Config {
+ ret := &configImpl{
+ environ: OsEnvironment(),
+ }
+
+ // Make sure OUT_DIR is set appropriately
+ if _, ok := ret.environ.Get("OUT_DIR"); !ok {
+ outDir := "out"
+ if baseDir, ok := ret.environ.Get("OUT_DIR_COMMON_BASE"); ok {
+ if wd, err := os.Getwd(); err != nil {
+ ctx.Fatalln("Failed to get working directory:", err)
+ } else {
+ outDir = filepath.Join(baseDir, filepath.Base(wd))
+ }
+ }
+ ret.environ.Set("OUT_DIR", outDir)
+ }
+
+ ret.environ.Unset(
+ // We're already using it
+ "USE_SOONG_UI",
+
+ // We should never use GOROOT/GOPATH from the shell environment
+ "GOROOT",
+ "GOPATH",
+
+ // These should only come from Soong, not the environment.
+ "CLANG",
+ "CLANG_CXX",
+ "CCC_CC",
+ "CCC_CXX",
+
+ // Used by the goma compiler wrapper, but should only be set by
+ // gomacc
+ "GOMACC_PATH",
+
+ // We handle this above
+ "OUT_DIR_COMMON_BASE",
+ )
+
+ // Tell python not to spam the source tree with .pyc files.
+ ret.environ.Set("PYTHONDONTWRITEBYTECODE", "1")
+
+ // Sane default matching ninja
+ ret.parallel = runtime.NumCPU() + 2
+ ret.keepGoing = 1
+
+ // Precondition: the current directory is the top of the source tree
+ if _, err := os.Stat(srcDirFileCheck); err != nil {
+ if os.IsNotExist(err) {
+ log.Fatalf("Current working directory must be the source tree. %q not found", srcDirFileCheck)
+ }
+ log.Fatalln("Error verifying tree state:", err)
+ }
+
+ for _, arg := range args {
+ arg = strings.TrimSpace(arg)
+ if arg == "--make-mode" {
+ continue
+ } else if arg == "showcommands" {
+ ret.verbose = true
+ continue
+ } else if arg == "dist" {
+ ret.dist = true
+ }
+ if arg[0] == '-' {
+ var err error
+ if arg[1] == 'j' {
+ // TODO: handle space between j and number
+ // Unnecessary if used with makeparallel
+ ret.parallel, err = strconv.Atoi(arg[2:])
+ } else if arg[1] == 'k' {
+ // TODO: handle space between k and number
+ // Unnecessary if used with makeparallel
+ ret.keepGoing, err = strconv.Atoi(arg[2:])
+ } else {
+ ctx.Fatalln("Unknown option:", arg)
+ }
+ if err != nil {
+ ctx.Fatalln("Argument error:", err, arg)
+ }
+ } else {
+ ret.arguments = append(ret.arguments, arg)
+ }
+ }
+
+ return Config{ret}
+}
+
+// Lunch configures the environment for a specific product similarly to the
+// `lunch` bash function.
+func (c *configImpl) Lunch(ctx Context, product, variant string) {
+ if variant != "eng" && variant != "userdebug" && variant != "user" {
+ ctx.Fatalf("Invalid variant %q. Must be one of 'user', 'userdebug' or 'eng'", variant)
+ }
+
+ c.environ.Set("TARGET_PRODUCT", product)
+ c.environ.Set("TARGET_BUILD_VARIANT", variant)
+ c.environ.Set("TARGET_BUILD_TYPE", "release")
+ c.environ.Unset("TARGET_BUILD_APPS")
+}
+
+// Tapas configures the environment to build one or more unbundled apps,
+// similarly to the `tapas` bash function.
+func (c *configImpl) Tapas(ctx Context, apps []string, arch, variant string) {
+ if len(apps) == 0 {
+ apps = []string{"all"}
+ }
+ if variant == "" {
+ variant = "eng"
+ }
+
+ if variant != "eng" && variant != "userdebug" && variant != "user" {
+ ctx.Fatalf("Invalid variant %q. Must be one of 'user', 'userdebug' or 'eng'", variant)
+ }
+
+ var product string
+ switch arch {
+ case "armv5":
+ product = "generic_armv5"
+ case "arm", "":
+ product = "aosp_arm"
+ case "arm64":
+ product = "aosm_arm64"
+ case "mips":
+ product = "aosp_mips"
+ case "mips64":
+ product = "aosp_mips64"
+ case "x86":
+ product = "aosp_x86"
+ case "x86_64":
+ product = "aosp_x86_64"
+ default:
+ ctx.Fatalf("Invalid architecture: %q", arch)
+ }
+
+ c.environ.Set("TARGET_PRODUCT", product)
+ c.environ.Set("TARGET_BUILD_VARIANT", variant)
+ c.environ.Set("TARGET_BUILD_TYPE", "release")
+ c.environ.Set("TARGET_BUILD_APPS", strings.Join(apps, " "))
+}
+
+func (c *configImpl) Environment() *Environment {
+ return c.environ
+}
+
+func (c *configImpl) Arguments() []string {
+ return c.arguments
+}
+
+func (c *configImpl) OutDir() string {
+ if outDir, ok := c.environ.Get("OUT_DIR"); ok {
+ return outDir
+ }
+ return "out"
+}
+
+func (c *configImpl) DistDir() string {
+ if distDir, ok := c.environ.Get("DIST_DIR"); ok {
+ return distDir
+ }
+ return filepath.Join(c.OutDir(), "dist")
+}
+
+func (c *configImpl) NinjaArgs() []string {
+ return c.ninjaArgs
+}
+
+func (c *configImpl) SoongOutDir() string {
+ return filepath.Join(c.OutDir(), "soong")
+}
+
+func (c *configImpl) KatiSuffix() string {
+ if c.katiSuffix != "" {
+ return c.katiSuffix
+ }
+ panic("SetKatiSuffix has not been called")
+}
+
+func (c *configImpl) Dist() bool {
+ return c.dist
+}
+
+func (c *configImpl) IsVerbose() bool {
+ return c.verbose
+}
+
+func (c *configImpl) TargetProduct() string {
+ if v, ok := c.environ.Get("TARGET_PRODUCT"); ok {
+ return v
+ }
+ panic("TARGET_PRODUCT is not defined")
+}
+
+func (c *configImpl) KatiArgs() []string {
+ return c.katiArgs
+}
+
+func (c *configImpl) Parallel() int {
+ return c.parallel
+}
+
+func (c *configImpl) UseGoma() bool {
+ if v, ok := c.environ.Get("USE_GOMA"); ok {
+ v = strings.TrimSpace(v)
+ if v != "" && v != "false" {
+ return true
+ }
+ }
+ return false
+}
+
+// RemoteParallel controls how many remote jobs (i.e., commands which contain
+// gomacc) are run in parallel. Note the paralleism of all other jobs is
+// still limited by Parallel()
+func (c *configImpl) RemoteParallel() int {
+ if v, ok := c.environ.Get("NINJA_REMOTE_NUM_JOBS"); ok {
+ if i, err := strconv.Atoi(v); err == nil {
+ return i
+ }
+ }
+ return 500
+}
+
+func (c *configImpl) SetKatiArgs(args []string) {
+ c.katiArgs = args
+}
+
+func (c *configImpl) SetNinjaArgs(args []string) {
+ c.ninjaArgs = args
+}
+
+func (c *configImpl) SetKatiSuffix(suffix string) {
+ c.katiSuffix = suffix
+}
+
+func (c *configImpl) KatiEnvFile() string {
+ return filepath.Join(c.OutDir(), "env"+c.KatiSuffix()+".sh")
+}
+
+func (c *configImpl) KatiNinjaFile() string {
+ return filepath.Join(c.OutDir(), "build"+c.KatiSuffix()+".ninja")
+}
+
+func (c *configImpl) SoongNinjaFile() string {
+ return filepath.Join(c.SoongOutDir(), "build.ninja")
+}
+
+func (c *configImpl) CombinedNinjaFile() string {
+ return filepath.Join(c.OutDir(), "combined"+c.KatiSuffix()+".ninja")
+}
+
+func (c *configImpl) SoongAndroidMk() string {
+ return filepath.Join(c.SoongOutDir(), "Android-"+c.TargetProduct()+".mk")
+}
+
+func (c *configImpl) SoongMakeVarsMk() string {
+ return filepath.Join(c.SoongOutDir(), "make_vars-"+c.TargetProduct()+".mk")
+}
+
+func (c *configImpl) HostPrebuiltTag() string {
+ if runtime.GOOS == "linux" {
+ return "linux-x86"
+ } else if runtime.GOOS == "darwin" {
+ return "darwin-x86"
+ } else {
+ panic("Unsupported OS")
+ }
+}
diff --git a/ui/build/context.go b/ui/build/context.go
new file mode 100644
index 0000000..f85bb6c
--- /dev/null
+++ b/ui/build/context.go
@@ -0,0 +1,104 @@
+// Copyright 2017 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 build
+
+import (
+ "context"
+ "io"
+ "os"
+ "time"
+
+ "android/soong/ui/logger"
+ "android/soong/ui/tracer"
+)
+
+type StdioInterface interface {
+ Stdin() io.Reader
+ Stdout() io.Writer
+ Stderr() io.Writer
+}
+
+type StdioImpl struct{}
+
+func (StdioImpl) Stdin() io.Reader { return os.Stdin }
+func (StdioImpl) Stdout() io.Writer { return os.Stdout }
+func (StdioImpl) Stderr() io.Writer { return os.Stderr }
+
+var _ StdioInterface = StdioImpl{}
+
+type customStdio struct {
+ stdin io.Reader
+ stdout io.Writer
+ stderr io.Writer
+}
+
+func NewCustomStdio(stdin io.Reader, stdout, stderr io.Writer) StdioInterface {
+ return customStdio{stdin, stdout, stderr}
+}
+
+func (c customStdio) Stdin() io.Reader { return c.stdin }
+func (c customStdio) Stdout() io.Writer { return c.stdout }
+func (c customStdio) Stderr() io.Writer { return c.stderr }
+
+var _ StdioInterface = customStdio{}
+
+// Context combines a context.Context, logger.Logger, and StdIO redirection.
+// These all are agnostic of the current build, and may be used for multiple
+// builds, while the Config objects contain per-build information.
+type Context struct{ *ContextImpl }
+type ContextImpl struct {
+ context.Context
+ logger.Logger
+
+ StdioInterface
+
+ Thread tracer.Thread
+ Tracer tracer.Tracer
+}
+
+// BeginTrace starts a new Duration Event.
+func (c ContextImpl) BeginTrace(name string) {
+ if c.Tracer != nil {
+ c.Tracer.Begin(name, c.Thread)
+ }
+}
+
+// EndTrace finishes the last Duration Event.
+func (c ContextImpl) EndTrace() {
+ if c.Tracer != nil {
+ c.Tracer.End(c.Thread)
+ }
+}
+
+// CompleteTrace writes a trace with a beginning and end times.
+func (c ContextImpl) CompleteTrace(name string, begin, end uint64) {
+ if c.Tracer != nil {
+ c.Tracer.Complete(name, c.Thread, begin, end)
+ }
+}
+
+// ImportNinjaLog imports a .ninja_log file into the tracer.
+func (c ContextImpl) ImportNinjaLog(filename string, startOffset time.Time) {
+ if c.Tracer != nil {
+ c.Tracer.ImportNinjaLog(c.Thread, filename, startOffset)
+ }
+}
+
+func (c ContextImpl) IsTerminal() bool {
+ if term, ok := os.LookupEnv("TERM"); ok {
+ return term != "dumb" && isTerminal(c.Stdout()) && isTerminal(c.Stderr())
+ }
+ return false
+}
diff --git a/ui/build/environment.go b/ui/build/environment.go
new file mode 100644
index 0000000..baab101
--- /dev/null
+++ b/ui/build/environment.go
@@ -0,0 +1,152 @@
+// Copyright 2017 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 build
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "os"
+ "strings"
+)
+
+// Environment adds a number of useful manipulation functions to the list of
+// strings returned by os.Environ() and used in exec.Cmd.Env.
+type Environment []string
+
+// OsEnvironment wraps the current environment returned by os.Environ()
+func OsEnvironment() *Environment {
+ env := Environment(os.Environ())
+ return &env
+}
+
+// Get returns the value associated with the key, and whether it exists.
+// It's equivalent to the os.LookupEnv function, but with this copy of the
+// Environment.
+func (e *Environment) Get(key string) (string, bool) {
+ for _, env := range *e {
+ if k, v, ok := decodeKeyValue(env); ok && k == key {
+ return v, true
+ }
+ }
+ return "", false
+}
+
+// Set sets the value associated with the key, overwriting the current value
+// if it exists.
+func (e *Environment) Set(key, value string) {
+ e.Unset(key)
+ *e = append(*e, key+"="+value)
+}
+
+// Unset removes the specified keys from the Environment.
+func (e *Environment) Unset(keys ...string) {
+ out := (*e)[:0]
+ for _, env := range *e {
+ if key, _, ok := decodeKeyValue(env); ok && inList(key, keys) {
+ continue
+ }
+ out = append(out, env)
+ }
+ *e = out
+}
+
+// Environ returns the []string required for exec.Cmd.Env
+func (e *Environment) Environ() []string {
+ return []string(*e)
+}
+
+// Copy returns a copy of the Environment so that independent changes may be made.
+func (e *Environment) Copy() *Environment {
+ ret := Environment(make([]string, len(*e)))
+ for i, v := range *e {
+ ret[i] = v
+ }
+ return &ret
+}
+
+// IsTrue returns whether an environment variable is set to a positive value (1,y,yes,on,true)
+func (e *Environment) IsEnvTrue(key string) bool {
+ if value, ok := e.Get(key); ok {
+ return value == "1" || value == "y" || value == "yes" || value == "on" || value == "true"
+ }
+ return false
+}
+
+// IsFalse returns whether an environment variable is set to a negative value (0,n,no,off,false)
+func (e *Environment) IsFalse(key string) bool {
+ if value, ok := e.Get(key); ok {
+ return value == "0" || value == "n" || value == "no" || value == "off" || value == "false"
+ }
+ return false
+}
+
+// AppendFromKati reads a shell script written by Kati that exports or unsets
+// environment variables, and applies those to the local Environment.
+func (e *Environment) AppendFromKati(filename string) error {
+ file, err := os.Open(filename)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ return e.appendFromKati(file)
+}
+
+func (e *Environment) appendFromKati(reader io.Reader) error {
+ scanner := bufio.NewScanner(reader)
+ for scanner.Scan() {
+ text := strings.TrimSpace(scanner.Text())
+
+ if len(text) == 0 || text[0] == '#' {
+ continue
+ }
+
+ cmd := strings.SplitN(text, " ", 2)
+ if len(cmd) != 2 {
+ return fmt.Errorf("Unknown kati environment line: %q", text)
+ }
+
+ if cmd[0] == "unset" {
+ str, ok := singleUnquote(cmd[1])
+ if !ok {
+ fmt.Errorf("Failed to unquote kati line: %q", text)
+ }
+ e.Unset(str)
+ } else if cmd[0] == "export" {
+ key, value, ok := decodeKeyValue(cmd[1])
+ if !ok {
+ return fmt.Errorf("Failed to parse export: %v", cmd)
+ }
+
+ key, ok = singleUnquote(key)
+ if !ok {
+ return fmt.Errorf("Failed to unquote kati line: %q", text)
+ }
+ value, ok = singleUnquote(value)
+ if !ok {
+ return fmt.Errorf("Failed to unquote kati line: %q", text)
+ }
+
+ e.Set(key, value)
+ } else {
+ return fmt.Errorf("Unknown kati environment command: %q", text)
+ }
+ }
+ if err := scanner.Err(); err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/ui/build/environment_test.go b/ui/build/environment_test.go
new file mode 100644
index 0000000..0294dac
--- /dev/null
+++ b/ui/build/environment_test.go
@@ -0,0 +1,80 @@
+// Copyright 2017 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 build
+
+import (
+ "reflect"
+ "strings"
+ "testing"
+)
+
+func TestEnvUnset(t *testing.T) {
+ initial := &Environment{"TEST=1", "TEST2=0"}
+ initial.Unset("TEST")
+ got := initial.Environ()
+ if len(got) != 1 || got[0] != "TEST2=0" {
+ t.Errorf("Expected [TEST2=0], got: %v", got)
+ }
+}
+
+func TestEnvUnsetMissing(t *testing.T) {
+ initial := &Environment{"TEST2=0"}
+ initial.Unset("TEST")
+ got := initial.Environ()
+ if len(got) != 1 || got[0] != "TEST2=0" {
+ t.Errorf("Expected [TEST2=0], got: %v", got)
+ }
+}
+
+func TestEnvSet(t *testing.T) {
+ initial := &Environment{}
+ initial.Set("TEST", "0")
+ got := initial.Environ()
+ if len(got) != 1 || got[0] != "TEST=0" {
+ t.Errorf("Expected [TEST=0], got: %v", got)
+ }
+}
+
+func TestEnvSetDup(t *testing.T) {
+ initial := &Environment{"TEST=1"}
+ initial.Set("TEST", "0")
+ got := initial.Environ()
+ if len(got) != 1 || got[0] != "TEST=0" {
+ t.Errorf("Expected [TEST=0], got: %v", got)
+ }
+}
+
+const testKatiEnvFileContents = `#!/bin/sh
+# Generated by kati unknown
+
+unset 'CLANG'
+export 'BUILD_ID'='NYC'
+`
+
+func TestEnvAppendFromKati(t *testing.T) {
+ initial := &Environment{"CLANG=/usr/bin/clang", "TEST=0"}
+ err := initial.appendFromKati(strings.NewReader(testKatiEnvFileContents))
+ if err != nil {
+ t.Fatalf("Unexpected error from %v", err)
+ }
+
+ got := initial.Environ()
+ expected := []string{"TEST=0", "BUILD_ID=NYC"}
+ if !reflect.DeepEqual(got, expected) {
+ t.Errorf("Environment list does not match")
+ t.Errorf("expected: %v", expected)
+ t.Errorf(" got: %v", got)
+ }
+}
diff --git a/ui/build/kati.go b/ui/build/kati.go
new file mode 100644
index 0000000..f990767
--- /dev/null
+++ b/ui/build/kati.go
@@ -0,0 +1,172 @@
+// Copyright 2017 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 build
+
+import (
+ "bufio"
+ "crypto/md5"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os/exec"
+ "path/filepath"
+ "regexp"
+ "strconv"
+ "strings"
+)
+
+var spaceSlashReplacer = strings.NewReplacer("/", "_", " ", "_")
+
+// genKatiSuffix creates a suffix for kati-generated files so that we can cache
+// them based on their inputs. So this should encode all common changes to Kati
+// inputs. Currently that includes the TARGET_PRODUCT, kati-processed command
+// line arguments, and the directories specified by mm/mmm.
+func genKatiSuffix(ctx Context, config Config) {
+ katiSuffix := "-" + config.TargetProduct()
+ if args := config.KatiArgs(); len(args) > 0 {
+ katiSuffix += "-" + spaceSlashReplacer.Replace(strings.Join(args, "_"))
+ }
+ if oneShot, ok := config.Environment().Get("ONE_SHOT_MAKEFILE"); ok {
+ katiSuffix += "-" + spaceSlashReplacer.Replace(oneShot)
+ }
+
+ // If the suffix is too long, replace it with a md5 hash and write a
+ // file that contains the original suffix.
+ if len(katiSuffix) > 64 {
+ shortSuffix := "-" + fmt.Sprintf("%x", md5.Sum([]byte(katiSuffix)))
+ config.SetKatiSuffix(shortSuffix)
+
+ ctx.Verbosef("Kati ninja suffix too long: %q", katiSuffix)
+ ctx.Verbosef("Replacing with: %q", shortSuffix)
+
+ if err := ioutil.WriteFile(strings.TrimSuffix(config.KatiNinjaFile(), "ninja")+"suf", []byte(katiSuffix), 0777); err != nil {
+ ctx.Println("Error writing suffix file:", err)
+ }
+ } else {
+ config.SetKatiSuffix(katiSuffix)
+ }
+}
+
+func runKati(ctx Context, config Config) {
+ ctx.BeginTrace("kati")
+ defer ctx.EndTrace()
+
+ genKatiSuffix(ctx, config)
+
+ executable := "prebuilts/build-tools/" + config.HostPrebuiltTag() + "/bin/ckati"
+ args := []string{
+ "--ninja",
+ "--ninja_dir=" + config.OutDir(),
+ "--ninja_suffix=" + config.KatiSuffix(),
+ "--regen",
+ "--ignore_optional_include=" + filepath.Join(config.OutDir(), "%.P"),
+ "--detect_android_echo",
+ "--color_warnings",
+ "--gen_all_targets",
+ "-f", "build/core/main.mk",
+ }
+
+ if !config.Environment().IsFalse("KATI_EMULATE_FIND") {
+ args = append(args, "--use_find_emulator")
+ }
+
+ args = append(args, config.KatiArgs()...)
+
+ args = append(args,
+ "BUILDING_WITH_NINJA=true",
+ "SOONG_ANDROID_MK="+config.SoongAndroidMk(),
+ "SOONG_MAKEVARS_MK="+config.SoongMakeVarsMk())
+
+ if config.UseGoma() {
+ args = append(args, "-j"+strconv.Itoa(config.Parallel()))
+ }
+
+ cmd := exec.CommandContext(ctx.Context, executable, args...)
+ cmd.Env = config.Environment().Environ()
+ pipe, err := cmd.StdoutPipe()
+ if err != nil {
+ ctx.Fatalln("Error getting output pipe for ckati:", err)
+ }
+ cmd.Stderr = cmd.Stdout
+
+ ctx.Verboseln(cmd.Path, cmd.Args)
+ if err := cmd.Start(); err != nil {
+ ctx.Fatalln("Failed to run ckati:", err)
+ }
+
+ katiRewriteOutput(ctx, pipe)
+
+ if err := cmd.Wait(); err != nil {
+ if e, ok := err.(*exec.ExitError); ok {
+ ctx.Fatalln("ckati failed with:", e.ProcessState.String())
+ } else {
+ ctx.Fatalln("Failed to run ckati:", err)
+ }
+ }
+}
+
+var katiIncludeRe = regexp.MustCompile(`^(\[\d+/\d+] )?including [^ ]+ ...$`)
+
+func katiRewriteOutput(ctx Context, pipe io.ReadCloser) {
+ haveBlankLine := true
+ smartTerminal := ctx.IsTerminal()
+
+ scanner := bufio.NewScanner(pipe)
+ for scanner.Scan() {
+ line := scanner.Text()
+ verbose := katiIncludeRe.MatchString(line)
+
+ // For verbose lines, write them on the current line without a newline,
+ // then overwrite them if the next thing we're printing is another
+ // verbose line.
+ if smartTerminal && verbose {
+ // Limit line width to the terminal width, otherwise we'll wrap onto
+ // another line and we won't delete the previous line.
+ //
+ // Run this on every line in case the window has been resized while
+ // we're printing. This could be optimized to only re-run when we
+ // get SIGWINCH if it ever becomes too time consuming.
+ if max, ok := termWidth(ctx.Stdout()); ok {
+ if len(line) > max {
+ // Just do a max. Ninja elides the middle, but that's
+ // more complicated and these lines aren't that important.
+ line = line[:max]
+ }
+ }
+
+ // Move to the beginning on the line, print the output, then clear
+ // the rest of the line.
+ fmt.Fprint(ctx.Stdout(), "\r", line, "\x1b[K")
+ haveBlankLine = false
+ continue
+ } else if smartTerminal && !haveBlankLine {
+ // If we've previously written a verbose message, send a newline to save
+ // that message instead of overwriting it.
+ fmt.Fprintln(ctx.Stdout())
+ haveBlankLine = true
+ } else if !smartTerminal {
+ // Most editors display these as garbage, so strip them out.
+ line = string(stripAnsiEscapes([]byte(line)))
+ }
+
+ // Assume that non-verbose lines are important enough for stderr
+ fmt.Fprintln(ctx.Stderr(), line)
+ }
+
+ // Save our last verbose line.
+ if !haveBlankLine {
+ fmt.Fprintln(ctx.Stdout())
+ }
+}
diff --git a/ui/build/make.go b/ui/build/make.go
new file mode 100644
index 0000000..a8d4483
--- /dev/null
+++ b/ui/build/make.go
@@ -0,0 +1,164 @@
+// Copyright 2017 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 build
+
+import (
+ "fmt"
+ "os/exec"
+ "path/filepath"
+ "strings"
+)
+
+// DumpMakeVars can be used to extract the values of Make variables after the
+// product configurations are loaded. This is roughly equivalent to the
+// `get_build_var` bash function.
+//
+// goals can be used to set MAKECMDGOALS, which emulates passing arguments to
+// Make without actually building them. So all the variables based on
+// MAKECMDGOALS can be read.
+//
+// extra_targets adds real arguments to the make command, in case other targets
+// actually need to be run (like the Soong config generator).
+//
+// vars is the list of variables to read. The values will be put in the
+// returned map.
+func DumpMakeVars(ctx Context, config Config, goals, extra_targets, vars []string) (map[string]string, error) {
+ ctx.BeginTrace("dumpvars")
+ defer ctx.EndTrace()
+
+ cmd := exec.CommandContext(ctx.Context,
+ "make",
+ "--no-print-directory",
+ "-f", "build/core/config.mk",
+ "dump-many-vars",
+ "CALLED_FROM_SETUP=true",
+ "BUILD_SYSTEM=build/core",
+ "MAKECMDGOALS="+strings.Join(goals, " "),
+ "DUMP_MANY_VARS="+strings.Join(vars, " "),
+ "OUT_DIR="+config.OutDir())
+ cmd.Env = config.Environment().Environ()
+ cmd.Args = append(cmd.Args, extra_targets...)
+ // TODO: error out when Stderr contains any content
+ cmd.Stderr = ctx.Stderr()
+ ctx.Verboseln(cmd.Path, cmd.Args)
+ output, err := cmd.Output()
+ if err != nil {
+ return nil, err
+ }
+
+ ret := make(map[string]string, len(vars))
+ for _, line := range strings.Split(string(output), "\n") {
+ if len(line) == 0 {
+ continue
+ }
+
+ if key, value, ok := decodeKeyValue(line); ok {
+ if value, ok = singleUnquote(value); ok {
+ ret[key] = value
+ ctx.Verboseln(key, value)
+ } else {
+ return nil, fmt.Errorf("Failed to parse make line: %q", line)
+ }
+ } else {
+ return nil, fmt.Errorf("Failed to parse make line: %q", line)
+ }
+ }
+
+ return ret, nil
+}
+
+func runMakeProductConfig(ctx Context, config Config) {
+ // Variables to export into the environment of Kati/Ninja
+ exportEnvVars := []string{
+ // So that we can use the correct TARGET_PRODUCT if it's been
+ // modified by PRODUCT-* arguments
+ "TARGET_PRODUCT",
+
+ // compiler wrappers set up by make
+ "CC_WRAPPER",
+ "CXX_WRAPPER",
+ "JAVAC_WRAPPER",
+
+ // ccache settings
+ "CCACHE_COMPILERCHECK",
+ "CCACHE_SLOPPINESS",
+ "CCACHE_BASEDIR",
+ "CCACHE_CPP2",
+ }
+
+ // Variables to print out in the top banner
+ bannerVars := []string{
+ "PLATFORM_VERSION_CODENAME",
+ "PLATFORM_VERSION",
+ "TARGET_PRODUCT",
+ "TARGET_BUILD_VARIANT",
+ "TARGET_BUILD_TYPE",
+ "TARGET_BUILD_APPS",
+ "TARGET_ARCH",
+ "TARGET_ARCH_VARIANT",
+ "TARGET_CPU_VARIANT",
+ "TARGET_2ND_ARCH",
+ "TARGET_2ND_ARCH_VARIANT",
+ "TARGET_2ND_CPU_VARIANT",
+ "HOST_ARCH",
+ "HOST_2ND_ARCH",
+ "HOST_OS",
+ "HOST_OS_EXTRA",
+ "HOST_CROSS_OS",
+ "HOST_CROSS_ARCH",
+ "HOST_CROSS_2ND_ARCH",
+ "HOST_BUILD_TYPE",
+ "BUILD_ID",
+ "OUT_DIR",
+ "AUX_OS_VARIANT_LIST",
+ "TARGET_BUILD_PDK",
+ "PDK_FUSION_PLATFORM_ZIP",
+ }
+
+ allVars := append(append([]string{
+ // Used to execute Kati and Ninja
+ "NINJA_GOALS",
+ "KATI_GOALS",
+ }, exportEnvVars...), bannerVars...)
+
+ make_vars, err := DumpMakeVars(ctx, config, config.Arguments(), []string{
+ filepath.Join(config.SoongOutDir(), "soong.variables"),
+ }, allVars)
+ if err != nil {
+ ctx.Fatalln("Error dumping make vars:", err)
+ }
+
+ // Print the banner like make does
+ fmt.Fprintln(ctx.Stdout(), "============================================")
+ for _, name := range bannerVars {
+ if make_vars[name] != "" {
+ fmt.Fprintf(ctx.Stdout(), "%s=%s\n", name, make_vars[name])
+ }
+ }
+ fmt.Fprintln(ctx.Stdout(), "============================================")
+
+ // Populate the environment
+ env := config.Environment()
+ for _, name := range exportEnvVars {
+ if make_vars[name] == "" {
+ env.Unset(name)
+ } else {
+ env.Set(name, make_vars[name])
+ }
+ }
+
+ config.SetKatiArgs(strings.Fields(make_vars["KATI_GOALS"]))
+ config.SetNinjaArgs(strings.Fields(make_vars["NINJA_GOALS"]))
+}
diff --git a/ui/build/ninja.go b/ui/build/ninja.go
new file mode 100644
index 0000000..33f9a07
--- /dev/null
+++ b/ui/build/ninja.go
@@ -0,0 +1,85 @@
+// Copyright 2017 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 build
+
+import (
+ "os/exec"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "time"
+)
+
+func runNinja(ctx Context, config Config) {
+ ctx.BeginTrace("ninja")
+ defer ctx.EndTrace()
+
+ executable := "prebuilts/build-tools/" + config.HostPrebuiltTag() + "/bin/ninja"
+ args := []string{
+ "-d", "keepdepfile",
+ }
+
+ args = append(args, config.NinjaArgs()...)
+
+ var parallel int
+ if config.UseGoma() {
+ parallel = config.RemoteParallel()
+ } else {
+ parallel = config.Parallel()
+ }
+ args = append(args, "-j", strconv.Itoa(parallel))
+ if config.keepGoing != 1 {
+ args = append(args, "-k", strconv.Itoa(config.keepGoing))
+ }
+
+ args = append(args, "-f", config.CombinedNinjaFile())
+
+ if config.IsVerbose() {
+ args = append(args, "-v")
+ }
+ args = append(args, "-w", "dupbuild=err")
+
+ env := config.Environment().Copy()
+ env.AppendFromKati(config.KatiEnvFile())
+
+ // Allow both NINJA_ARGS and NINJA_EXTRA_ARGS, since both have been
+ // used in the past to specify extra ninja arguments.
+ if extra, ok := env.Get("NINJA_ARGS"); ok {
+ args = append(args, strings.Fields(extra)...)
+ }
+ if extra, ok := env.Get("NINJA_EXTRA_ARGS"); ok {
+ args = append(args, strings.Fields(extra)...)
+ }
+
+ if _, ok := env.Get("NINJA_STATUS"); !ok {
+ env.Set("NINJA_STATUS", "[%p %f/%t] ")
+ }
+
+ cmd := exec.CommandContext(ctx.Context, executable, args...)
+ cmd.Env = env.Environ()
+ cmd.Stdin = ctx.Stdin()
+ cmd.Stdout = ctx.Stdout()
+ cmd.Stderr = ctx.Stderr()
+ ctx.Verboseln(cmd.Path, cmd.Args)
+ startTime := time.Now()
+ defer ctx.ImportNinjaLog(filepath.Join(config.OutDir(), ".ninja_log"), startTime)
+ if err := cmd.Run(); err != nil {
+ if e, ok := err.(*exec.ExitError); ok {
+ ctx.Fatalln("ninja failed with:", e.ProcessState.String())
+ } else {
+ ctx.Fatalln("Failed to run ninja:", err)
+ }
+ }
+}
diff --git a/ui/build/signal.go b/ui/build/signal.go
new file mode 100644
index 0000000..6643e2f
--- /dev/null
+++ b/ui/build/signal.go
@@ -0,0 +1,94 @@
+// Copyright 2017 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 build
+
+import (
+ "os"
+ "os/signal"
+ "runtime/debug"
+ "syscall"
+
+ "android/soong/ui/logger"
+ "time"
+)
+
+// SetupSignals sets up signal handling to ensure all of our subprocesses are killed and that
+// our log/trace buffers are flushed to disk.
+//
+// All of our subprocesses are in the same process group, so they'll receive a SIGINT at the
+// same time we do. Most of the time this means we just need to ignore the signal and we'll
+// just see errors from all of our subprocesses. But in case that fails, when we get a signal:
+//
+// 1. Wait two seconds to exit normally.
+// 2. Call cancel() which is normally the cancellation of a Context. This will send a SIGKILL
+// to any subprocesses attached to that context.
+// 3. Wait two seconds to exit normally.
+// 4. Call cleanup() to close the log/trace buffers, then panic.
+// 5. If another two seconds passes (if cleanup got stuck, etc), then panic.
+//
+func SetupSignals(log logger.Logger, cancel, cleanup func()) {
+ signals := make(chan os.Signal, 5)
+ signal.Notify(signals, os.Interrupt, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM)
+ go handleSignals(signals, log, cancel, cleanup)
+}
+
+func handleSignals(signals chan os.Signal, log logger.Logger, cancel, cleanup func()) {
+ var timeouts int
+ var timeout <-chan time.Time
+
+ handleTimeout := func() {
+ timeouts += 1
+ switch timeouts {
+ case 1:
+ // Things didn't exit cleanly, cancel our ctx (SIGKILL to subprocesses)
+ // Do this asynchronously to ensure it won't block and prevent us from
+ // taking more drastic measures.
+ log.Println("Still alive, killing subprocesses...")
+ go cancel()
+ case 2:
+ // Cancel didn't work. Try to run cleanup manually, then we'll panic
+ // at the next timer whether it finished or not.
+ log.Println("Still alive, cleaning up...")
+
+ // Get all stacktraces to see what was stuck
+ debug.SetTraceback("all")
+
+ go func() {
+ defer log.Panicln("Timed out exiting...")
+ cleanup()
+ }()
+ default:
+ // In case cleanup() deadlocks, the next tick will panic.
+ log.Panicln("Got signal, but timed out exiting...")
+ }
+ }
+
+ for {
+ select {
+ case s := <-signals:
+ log.Println("Got signal:", s)
+
+ // Another signal triggers our next timeout handler early
+ if timeout != nil {
+ handleTimeout()
+ }
+
+ // Wait 2 seconds for everything to exit cleanly.
+ timeout = time.Tick(time.Second * 2)
+ case <-timeout:
+ handleTimeout()
+ }
+ }
+}
diff --git a/ui/build/soong.go b/ui/build/soong.go
new file mode 100644
index 0000000..6554f1d
--- /dev/null
+++ b/ui/build/soong.go
@@ -0,0 +1,64 @@
+// Copyright 2017 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 build
+
+import (
+ "os/exec"
+ "path/filepath"
+)
+
+func runSoongBootstrap(ctx Context, config Config) {
+ ctx.BeginTrace("bootstrap soong")
+ defer ctx.EndTrace()
+
+ cmd := exec.CommandContext(ctx.Context, "./bootstrap.bash")
+ env := config.Environment().Copy()
+ env.Set("BUILDDIR", config.SoongOutDir())
+ cmd.Env = env.Environ()
+ cmd.Stdout = ctx.Stdout()
+ cmd.Stderr = ctx.Stderr()
+ ctx.Verboseln(cmd.Path, cmd.Args)
+ if err := cmd.Run(); err != nil {
+ if e, ok := err.(*exec.ExitError); ok {
+ ctx.Fatalln("soong bootstrap failed with:", e.ProcessState.String())
+ } else {
+ ctx.Fatalln("Failed to run soong bootstrap:", err)
+ }
+ }
+}
+
+func runSoong(ctx Context, config Config) {
+ ctx.BeginTrace("soong")
+ defer ctx.EndTrace()
+
+ cmd := exec.CommandContext(ctx.Context, filepath.Join(config.SoongOutDir(), "soong"), "-w", "dupbuild=err")
+ if config.IsVerbose() {
+ cmd.Args = append(cmd.Args, "-v")
+ }
+ env := config.Environment().Copy()
+ env.Set("SKIP_NINJA", "true")
+ cmd.Env = env.Environ()
+ cmd.Stdin = ctx.Stdin()
+ cmd.Stdout = ctx.Stdout()
+ cmd.Stderr = ctx.Stderr()
+ ctx.Verboseln(cmd.Path, cmd.Args)
+ if err := cmd.Run(); err != nil {
+ if e, ok := err.(*exec.ExitError); ok {
+ ctx.Fatalln("soong bootstrap failed with:", e.ProcessState.String())
+ } else {
+ ctx.Fatalln("Failed to run soong bootstrap:", err)
+ }
+ }
+}
diff --git a/ui/build/util.go b/ui/build/util.go
new file mode 100644
index 0000000..37ac6b9
--- /dev/null
+++ b/ui/build/util.go
@@ -0,0 +1,161 @@
+// Copyright 2017 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 build
+
+import (
+ "bytes"
+ "io"
+ "os"
+ "path/filepath"
+ "strings"
+ "syscall"
+ "unsafe"
+)
+
+// indexList finds the index of a string in a []string
+func indexList(s string, list []string) int {
+ for i, l := range list {
+ if l == s {
+ return i
+ }
+ }
+
+ return -1
+}
+
+// inList determines whether a string is in a []string
+func inList(s string, list []string) bool {
+ return indexList(s, list) != -1
+}
+
+// ensureDirectoriesExist is a shortcut to os.MkdirAll, sending errors to the ctx logger.
+func ensureDirectoriesExist(ctx Context, dirs ...string) {
+ for _, dir := range dirs {
+ err := os.MkdirAll(dir, 0777)
+ if err != nil {
+ ctx.Fatalf("Error creating %s: %q\n", dir, err)
+ }
+ }
+}
+
+// ensureEmptyFileExists ensures that the containing directory exists, and the
+// specified file exists. If it doesn't exist, it will write an empty file.
+func ensureEmptyFileExists(ctx Context, file string) {
+ ensureDirectoriesExist(ctx, filepath.Dir(file))
+ if _, err := os.Stat(file); os.IsNotExist(err) {
+ f, err := os.Create(file)
+ if err != nil {
+ ctx.Fatalf("Error creating %s: %q\n", file, err)
+ }
+ f.Close()
+ } else if err != nil {
+ ctx.Fatalf("Error checking %s: %q\n", file, err)
+ }
+}
+
+// singleUnquote is similar to strconv.Unquote, but can handle multi-character strings inside single quotes.
+func singleUnquote(str string) (string, bool) {
+ if len(str) < 2 || str[0] != '\'' || str[len(str)-1] != '\'' {
+ return "", false
+ }
+ return str[1 : len(str)-1], true
+}
+
+// decodeKeyValue decodes a key=value string
+func decodeKeyValue(str string) (string, string, bool) {
+ idx := strings.IndexRune(str, '=')
+ if idx == -1 {
+ return "", "", false
+ }
+ return str[:idx], str[idx+1:], true
+}
+
+func isTerminal(w io.Writer) bool {
+ if f, ok := w.(*os.File); ok {
+ var termios syscall.Termios
+ _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, f.Fd(),
+ ioctlGetTermios, uintptr(unsafe.Pointer(&termios)),
+ 0, 0, 0)
+ return err == 0
+ }
+ return false
+}
+
+func termWidth(w io.Writer) (int, bool) {
+ if f, ok := w.(*os.File); ok {
+ var winsize struct {
+ ws_row, ws_column uint16
+ ws_xpixel, ws_ypixel uint16
+ }
+ _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, f.Fd(),
+ syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&winsize)),
+ 0, 0, 0)
+ return int(winsize.ws_column), err == 0
+ }
+ return 0, false
+}
+
+// stripAnsiEscapes strips ANSI control codes from a byte array in place.
+func stripAnsiEscapes(input []byte) []byte {
+ // read represents the remaining part of input that needs to be processed.
+ read := input
+ // write represents where we should be writing in input.
+ // It will share the same backing store as input so that we make our modifications
+ // in place.
+ write := input
+
+ // advance will copy count bytes from read to write and advance those slices
+ advance := func(write, read []byte, count int) ([]byte, []byte) {
+ copy(write, read[:count])
+ return write[count:], read[count:]
+ }
+
+ for {
+ // Find the next escape sequence
+ i := bytes.IndexByte(read, 0x1b)
+ // If it isn't found, or if there isn't room for <ESC>[, finish
+ if i == -1 || i+1 >= len(read) {
+ copy(write, read)
+ break
+ }
+
+ // Not a CSI code, continue searching
+ if read[i+1] != '[' {
+ write, read = advance(write, read, i+1)
+ continue
+ }
+
+ // Found a CSI code, advance up to the <ESC>
+ write, read = advance(write, read, i)
+
+ // Find the end of the CSI code
+ i = bytes.IndexFunc(read, func(r rune) bool {
+ return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z')
+ })
+ if i == -1 {
+ // We didn't find the end of the code, just remove the rest
+ i = len(read) - 1
+ }
+
+ // Strip off the end marker too
+ i = i + 1
+
+ // Skip the reader forward and reduce final length by that amount
+ read = read[i:]
+ input = input[:len(input)-i]
+ }
+
+ return input
+}
diff --git a/ui/build/util_darwin.go b/ui/build/util_darwin.go
new file mode 100644
index 0000000..254a9b8
--- /dev/null
+++ b/ui/build/util_darwin.go
@@ -0,0 +1,21 @@
+// Copyright 2017 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 build
+
+import (
+ "syscall"
+)
+
+const ioctlGetTermios = syscall.TIOCGETA
diff --git a/ui/build/util_linux.go b/ui/build/util_linux.go
new file mode 100644
index 0000000..0a4e1d2
--- /dev/null
+++ b/ui/build/util_linux.go
@@ -0,0 +1,21 @@
+// Copyright 2017 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 build
+
+import (
+ "syscall"
+)
+
+const ioctlGetTermios = syscall.TCGETS
diff --git a/ui/build/util_test.go b/ui/build/util_test.go
new file mode 100644
index 0000000..e85eada
--- /dev/null
+++ b/ui/build/util_test.go
@@ -0,0 +1,62 @@
+// Copyright 2017 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 build
+
+import "testing"
+
+func TestStripAnsiEscapes(t *testing.T) {
+ testcases := []struct {
+ input string
+ output string
+ }{
+ {
+ "",
+ "",
+ },
+ {
+ "This is a test",
+ "This is a test",
+ },
+ {
+ "interrupted: \x1b[12",
+ "interrupted: ",
+ },
+ {
+ "other \x1bescape \x1b",
+ "other \x1bescape \x1b",
+ },
+ { // from pretty-error macro
+ "\x1b[1mart/Android.mk: \x1b[31merror:\x1b[0m\x1b[1m art: test error \x1b[0m",
+ "art/Android.mk: error: art: test error ",
+ },
+ { // from envsetup.sh make wrapper
+ "\x1b[0;31m#### make failed to build some targets (2 seconds) ####\x1b[00m",
+ "#### make failed to build some targets (2 seconds) ####",
+ },
+ { // from clang (via ninja testcase)
+ "\x1b[1maffixmgr.cxx:286:15: \x1b[0m\x1b[0;1;35mwarning: \x1b[0m\x1b[1musing the result... [-Wparentheses]\x1b[0m",
+ "affixmgr.cxx:286:15: warning: using the result... [-Wparentheses]",
+ },
+ }
+ for _, tc := range testcases {
+ got := string(stripAnsiEscapes([]byte(tc.input)))
+ if got != tc.output {
+ t.Errorf("output strings didn't match\n"+
+ "input: %#v\n"+
+ " want: %#v\n"+
+ " got: %#v", tc.input, tc.output, got)
+ }
+ }
+}
diff --git a/ui/logger/Android.bp b/ui/logger/Android.bp
new file mode 100644
index 0000000..8091ef9
--- /dev/null
+++ b/ui/logger/Android.bp
@@ -0,0 +1,24 @@
+// Copyright 2017 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.
+
+bootstrap_go_package {
+ name: "soong-ui-logger",
+ pkgPath: "android/soong/ui/logger",
+ srcs: [
+ "logger.go",
+ ],
+ testSrcs: [
+ "logger_test.go",
+ ],
+}
diff --git a/ui/logger/logger.go b/ui/logger/logger.go
new file mode 100644
index 0000000..db7e82a
--- /dev/null
+++ b/ui/logger/logger.go
@@ -0,0 +1,302 @@
+// Copyright 2017 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 logger implements a logging package designed for command line
+// utilities. It uses the standard 'log' package and function, but splits
+// output between stderr and a rotating log file.
+//
+// In addition to the standard logger functions, Verbose[f|ln] calls only go to
+// the log file by default, unless SetVerbose(true) has been called.
+//
+// The log file also includes extended date/time/source information, which are
+// omitted from the stderr output for better readability.
+//
+// In order to better handle resource cleanup after a Fatal error, the Fatal
+// functions panic instead of calling os.Exit(). To actually do the cleanup,
+// and prevent the printing of the panic, call defer logger.Cleanup() at the
+// beginning of your main function.
+package logger
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log"
+ "os"
+ "path/filepath"
+ "strconv"
+ "sync"
+)
+
+type Logger interface {
+ // Print* prints to both stderr and the file log.
+ // Arguments to Print are handled in the manner of fmt.Print.
+ Print(v ...interface{})
+ // Arguments to Printf are handled in the manner of fmt.Printf
+ Printf(format string, v ...interface{})
+ // Arguments to Println are handled in the manner of fmt.Println
+ Println(v ...interface{})
+
+ // Verbose* is equivalent to Print*, but skips stderr unless the
+ // logger has been configured in verbose mode.
+ Verbose(v ...interface{})
+ Verbosef(format string, v ...interface{})
+ Verboseln(v ...interface{})
+
+ // Fatal* is equivalent to Print* followed by a call to panic that
+ // can be converted to an error using Recover, or will be converted
+ // to a call to os.Exit(1) with a deferred call to Cleanup()
+ Fatal(v ...interface{})
+ Fatalf(format string, v ...interface{})
+ Fatalln(v ...interface{})
+
+ // Panic is equivalent to Print* followed by a call to panic.
+ Panic(v ...interface{})
+ Panicf(format string, v ...interface{})
+ Panicln(v ...interface{})
+
+ // Output writes the string to both stderr and the file log.
+ Output(calldepth int, str string) error
+}
+
+// fatalLog is the type used when Fatal[f|ln]
+type fatalLog error
+
+func fileRotation(from, baseName, ext string, cur, max int) error {
+ newName := baseName + "." + strconv.Itoa(cur) + ext
+
+ if _, err := os.Lstat(newName); err == nil {
+ if cur+1 <= max {
+ fileRotation(newName, baseName, ext, cur+1, max)
+ }
+ }
+
+ if err := os.Rename(from, newName); err != nil {
+ return fmt.Errorf("Failed to rotate", from, "to", newName, ".", err)
+ }
+ return nil
+}
+
+// CreateFileWithRotation returns a new os.File using os.Create, renaming any
+// existing files to <filename>.#.<ext>, keeping up to maxCount files.
+// <filename>.1.<ext> is the most recent backup, <filename>.2.<ext> is the
+// second most recent backup, etc.
+//
+// TODO: This function is not guaranteed to be atomic, if there are multiple
+// users attempting to do the same operation, the result is undefined.
+func CreateFileWithRotation(filename string, maxCount int) (*os.File, error) {
+ if _, err := os.Lstat(filename); err == nil {
+ ext := filepath.Ext(filename)
+ basename := filename[:len(filename)-len(ext)]
+ if err = fileRotation(filename, basename, ext, 1, maxCount); err != nil {
+ return nil, err
+ }
+ }
+
+ return os.Create(filename)
+}
+
+// Recover can be used with defer in a GoRoutine to convert a Fatal panics to
+// an error that can be handled.
+func Recover(fn func(err error)) {
+ p := recover()
+
+ if p == nil {
+ return
+ } else if log, ok := p.(fatalLog); ok {
+ fn(error(log))
+ } else {
+ panic(p)
+ }
+}
+
+type stdLogger struct {
+ stderr *log.Logger
+ verbose bool
+
+ fileLogger *log.Logger
+ mutex sync.Mutex
+ file *os.File
+}
+
+var _ Logger = &stdLogger{}
+
+// New creates a new Logger. The out variable sets the destination, commonly
+// os.Stderr, but it may be a buffer for tests, or a separate log file if
+// the user doesn't need to see the output.
+func New(out io.Writer) *stdLogger {
+ return &stdLogger{
+ stderr: log.New(out, "", log.Ltime),
+ fileLogger: log.New(ioutil.Discard, "", log.Ldate|log.Lmicroseconds|log.Llongfile),
+ }
+}
+
+// SetVerbose controls whether Verbose[f|ln] logs to stderr as well as the
+// file-backed log.
+func (s *stdLogger) SetVerbose(v bool) {
+ s.verbose = v
+}
+
+// SetOutput controls where the file-backed log will be saved. It will keep
+// some number of backups of old log files.
+func (s *stdLogger) SetOutput(path string) {
+ if f, err := CreateFileWithRotation(path, 5); err == nil {
+ s.mutex.Lock()
+ defer s.mutex.Unlock()
+
+ if s.file != nil {
+ s.file.Close()
+ }
+ s.file = f
+ s.fileLogger.SetOutput(f)
+ } else {
+ s.Fatal(err.Error())
+ }
+}
+
+// Close disables logging to the file and closes the file handle.
+func (s *stdLogger) Close() {
+ s.mutex.Lock()
+ defer s.mutex.Unlock()
+ if s.file != nil {
+ s.fileLogger.SetOutput(ioutil.Discard)
+ s.file.Close()
+ s.file = nil
+ }
+}
+
+// Cleanup should be used with defer in your main function. It will close the
+// log file and convert any Fatal panics back to os.Exit(1)
+func (s *stdLogger) Cleanup() {
+ fatal := false
+ p := recover()
+
+ if _, ok := p.(fatalLog); ok {
+ fatal = true
+ p = nil
+ } else if p != nil {
+ s.Println(p)
+ }
+
+ s.Close()
+
+ if p != nil {
+ panic(p)
+ } else if fatal {
+ os.Exit(1)
+ }
+}
+
+// Output writes string to both stderr and the file log.
+func (s *stdLogger) Output(calldepth int, str string) error {
+ s.stderr.Output(calldepth+1, str)
+ return s.fileLogger.Output(calldepth+1, str)
+}
+
+// VerboseOutput is equivalent to Output, but only goes to the file log
+// unless SetVerbose(true) has been called.
+func (s *stdLogger) VerboseOutput(calldepth int, str string) error {
+ if s.verbose {
+ s.stderr.Output(calldepth+1, str)
+ }
+ return s.fileLogger.Output(calldepth+1, str)
+}
+
+// Print prints to both stderr and the file log.
+// Arguments are handled in the manner of fmt.Print.
+func (s *stdLogger) Print(v ...interface{}) {
+ output := fmt.Sprint(v...)
+ s.Output(2, output)
+}
+
+// Printf prints to both stderr and the file log.
+// Arguments are handled in the manner of fmt.Printf.
+func (s *stdLogger) Printf(format string, v ...interface{}) {
+ output := fmt.Sprintf(format, v...)
+ s.Output(2, output)
+}
+
+// Println prints to both stderr and the file log.
+// Arguments are handled in the manner of fmt.Println.
+func (s *stdLogger) Println(v ...interface{}) {
+ output := fmt.Sprintln(v...)
+ s.Output(2, output)
+}
+
+// Verbose is equivalent to Print, but only goes to the file log unless
+// SetVerbose(true) has been called.
+func (s *stdLogger) Verbose(v ...interface{}) {
+ output := fmt.Sprint(v...)
+ s.VerboseOutput(2, output)
+}
+
+// Verbosef is equivalent to Printf, but only goes to the file log unless
+// SetVerbose(true) has been called.
+func (s *stdLogger) Verbosef(format string, v ...interface{}) {
+ output := fmt.Sprintf(format, v...)
+ s.VerboseOutput(2, output)
+}
+
+// Verboseln is equivalent to Println, but only goes to the file log unless
+// SetVerbose(true) has been called.
+func (s *stdLogger) Verboseln(v ...interface{}) {
+ output := fmt.Sprintln(v...)
+ s.VerboseOutput(2, output)
+}
+
+// Fatal is equivalent to Print() followed by a call to panic() that
+// Cleanup will convert to a os.Exit(1).
+func (s *stdLogger) Fatal(v ...interface{}) {
+ output := fmt.Sprint(v...)
+ s.Output(2, output)
+ panic(fatalLog(errors.New(output)))
+}
+
+// Fatalf is equivalent to Printf() followed by a call to panic() that
+// Cleanup will convert to a os.Exit(1).
+func (s *stdLogger) Fatalf(format string, v ...interface{}) {
+ output := fmt.Sprintf(format, v...)
+ s.Output(2, output)
+ panic(fatalLog(errors.New(output)))
+}
+
+// Fatalln is equivalent to Println() followed by a call to panic() that
+// Cleanup will convert to a os.Exit(1).
+func (s *stdLogger) Fatalln(v ...interface{}) {
+ output := fmt.Sprintln(v...)
+ s.Output(2, output)
+ panic(fatalLog(errors.New(output)))
+}
+
+// Panic is equivalent to Print() followed by a call to panic().
+func (s *stdLogger) Panic(v ...interface{}) {
+ output := fmt.Sprint(v...)
+ s.Output(2, output)
+ panic(output)
+}
+
+// Panicf is equivalent to Printf() followed by a call to panic().
+func (s *stdLogger) Panicf(format string, v ...interface{}) {
+ output := fmt.Sprintf(format, v...)
+ s.Output(2, output)
+ panic(output)
+}
+
+// Panicln is equivalent to Println() followed by a call to panic().
+func (s *stdLogger) Panicln(v ...interface{}) {
+ output := fmt.Sprintln(v...)
+ s.Output(2, output)
+ panic(output)
+}
diff --git a/ui/logger/logger_test.go b/ui/logger/logger_test.go
new file mode 100644
index 0000000..0f88ab3
--- /dev/null
+++ b/ui/logger/logger_test.go
@@ -0,0 +1,198 @@
+// Copyright 2017 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 logger
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "reflect"
+ "sort"
+ "syscall"
+ "testing"
+)
+
+func TestCreateFileWithRotation(t *testing.T) {
+ dir, err := ioutil.TempDir("", "test-rotation")
+ if err != nil {
+ t.Fatalf("Failed to get TempDir: %v", err)
+ }
+ defer os.RemoveAll(dir)
+
+ file := filepath.Join(dir, "build.log")
+
+ writeFile := func(name string, data string) {
+ f, err := CreateFileWithRotation(name, 3)
+ if err != nil {
+ t.Fatalf("Failed to create file: %v", err)
+ }
+ if n, err := io.WriteString(f, data); err == nil && n < len(data) {
+ t.Fatalf("Short write")
+ } else if err != nil {
+ t.Fatalf("Failed to write: %v", err)
+ }
+ if err := f.Close(); err != nil {
+ t.Fatalf("Failed to close: %v", err)
+ }
+ }
+
+ writeFile(file, "a")
+ writeFile(file, "b")
+ writeFile(file, "c")
+ writeFile(file, "d")
+ writeFile(file, "e")
+
+ d, err := os.Open(dir)
+ if err != nil {
+ t.Fatalf("Failed to open dir: %v", err)
+ }
+ names, err := d.Readdirnames(0)
+ if err != nil {
+ t.Fatalf("Failed to read dir: %v", err)
+ }
+ sort.Strings(names)
+ expected := []string{"build.1.log", "build.2.log", "build.3.log", "build.log"}
+ if !reflect.DeepEqual(names, expected) {
+ t.Errorf("File list does not match.")
+ t.Errorf(" got: %v", names)
+ t.Errorf("expected: %v", expected)
+ t.FailNow()
+ }
+
+ expectFileContents := func(name, expected string) {
+ data, err := ioutil.ReadFile(filepath.Join(dir, name))
+ if err != nil {
+ t.Errorf("Error reading file: %v", err)
+ return
+ }
+ str := string(data)
+ if str != expected {
+ t.Errorf("Contents of %v does not match.", name)
+ t.Errorf(" got: %v", data)
+ t.Errorf("expected: %v", expected)
+ }
+ }
+
+ expectFileContents("build.log", "e")
+ expectFileContents("build.1.log", "d")
+ expectFileContents("build.2.log", "c")
+ expectFileContents("build.3.log", "b")
+}
+
+func TestPanic(t *testing.T) {
+ if os.Getenv("ACTUALLY_PANIC") == "1" {
+ panicValue := "foo"
+ log := New(&bytes.Buffer{})
+
+ defer func() {
+ p := recover()
+
+ if p == panicValue {
+ os.Exit(42)
+ } else {
+ fmt.Fprintln(os.Stderr, "Expected %q, got %v", panicValue, p)
+ os.Exit(3)
+ }
+ }()
+ defer log.Cleanup()
+
+ log.Panic(panicValue)
+ os.Exit(2)
+ return
+ }
+
+ // Run this in an external process so that we don't pollute stderr
+ cmd := exec.Command(os.Args[0], "-test.run=TestPanic")
+ cmd.Env = append(os.Environ(), "ACTUALLY_PANIC=1")
+ err := cmd.Run()
+ if e, ok := err.(*exec.ExitError); ok && e.Sys().(syscall.WaitStatus).ExitStatus() == 42 {
+ return
+ }
+ t.Errorf("Expected process to exit with status 42, got %v", err)
+}
+
+func TestFatal(t *testing.T) {
+ if os.Getenv("ACTUALLY_FATAL") == "1" {
+ log := New(&bytes.Buffer{})
+ defer func() {
+ // Shouldn't get here
+ os.Exit(3)
+ }()
+ defer log.Cleanup()
+ log.Fatal("Test")
+ os.Exit(0)
+ return
+ }
+
+ cmd := exec.Command(os.Args[0], "-test.run=TestFatal")
+ cmd.Env = append(os.Environ(), "ACTUALLY_FATAL=1")
+ err := cmd.Run()
+ if e, ok := err.(*exec.ExitError); ok && e.Sys().(syscall.WaitStatus).ExitStatus() == 1 {
+ return
+ }
+ t.Errorf("Expected process to exit with status 1, got %v", err)
+}
+
+func TestNonFatal(t *testing.T) {
+ if os.Getenv("ACTUAL_TEST") == "1" {
+ log := New(&bytes.Buffer{})
+ defer log.Cleanup()
+ log.Println("Test")
+ return
+ }
+
+ cmd := exec.Command(os.Args[0], "-test.run=TestNonFatal")
+ cmd.Env = append(os.Environ(), "ACTUAL_TEST=1")
+ err := cmd.Run()
+ if e, ok := err.(*exec.ExitError); ok || (ok && !e.Success()) {
+ t.Errorf("Expected process to exit cleanly, got %v", err)
+ }
+}
+
+func TestRecoverFatal(t *testing.T) {
+ log := New(&bytes.Buffer{})
+ defer func() {
+ if p := recover(); p != nil {
+ t.Errorf("Unexpected panic: %#v", p)
+ }
+ }()
+ defer Recover(func(err error) {
+ if err.Error() != "Test" {
+ t.Errorf("Expected %q, but got %q", "Test", err.Error())
+ }
+ })
+ log.Fatal("Test")
+ t.Errorf("Should not get here")
+}
+
+func TestRecoverNonFatal(t *testing.T) {
+ log := New(&bytes.Buffer{})
+ defer func() {
+ if p := recover(); p == nil {
+ t.Errorf("Panic not thrown")
+ } else if p != "Test" {
+ t.Errorf("Expected %q, but got %#v", "Test", p)
+ }
+ }()
+ defer Recover(func(err error) {
+ t.Errorf("Recover function should not be called")
+ })
+ log.Panic("Test")
+ t.Errorf("Should not get here")
+}
diff --git a/ui/tracer/Android.bp b/ui/tracer/Android.bp
new file mode 100644
index 0000000..89812a1
--- /dev/null
+++ b/ui/tracer/Android.bp
@@ -0,0 +1,23 @@
+// 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.
+
+bootstrap_go_package {
+ name: "soong-ui-tracer",
+ pkgPath: "android/soong/ui/tracer",
+ deps: ["soong-ui-logger"],
+ srcs: [
+ "ninja.go",
+ "tracer.go",
+ ],
+}
diff --git a/ui/tracer/ninja.go b/ui/tracer/ninja.go
new file mode 100644
index 0000000..da558f1
--- /dev/null
+++ b/ui/tracer/ninja.go
@@ -0,0 +1,130 @@
+// 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 tracer
+
+import (
+ "bufio"
+ "os"
+ "sort"
+ "strconv"
+ "strings"
+ "time"
+)
+
+type ninjaLogEntry struct {
+ Name string
+ Begin int
+ End int
+}
+type ninjaLogEntries []*ninjaLogEntry
+
+func (n ninjaLogEntries) Len() int { return len(n) }
+func (n ninjaLogEntries) Less(i, j int) bool { return n[i].Begin < n[j].Begin }
+func (n ninjaLogEntries) Swap(i, j int) { n[i], n[j] = n[j], n[i] }
+
+// ImportNinjaLog reads a .ninja_log file from ninja and writes the events out
+// to the trace.
+//
+// startOffset is when the ninja process started, and is used to position the
+// relative times from the ninja log into the trace. It's also used to skip
+// reading the ninja log if nothing was run.
+func (t *tracerImpl) ImportNinjaLog(thread Thread, filename string, startOffset time.Time) {
+ t.Begin("ninja log import", thread)
+ defer t.End(thread)
+
+ if stat, err := os.Stat(filename); err != nil {
+ t.log.Println("Missing ninja log:", err)
+ return
+ } else if stat.ModTime().Before(startOffset) {
+ t.log.Verboseln("Ninja log not modified, not importing any entries.")
+ return
+ }
+
+ f, err := os.Open(filename)
+ if err != nil {
+ t.log.Println("Error opening ninja log:", err)
+ return
+ }
+ defer f.Close()
+
+ s := bufio.NewScanner(f)
+ header := true
+ entries := ninjaLogEntries{}
+ prevEnd := 0
+ for s.Scan() {
+ if header {
+ hdr := s.Text()
+ if hdr != "# ninja log v5" {
+ t.log.Printf("Unknown ninja log header: %q", hdr)
+ return
+ }
+ header = false
+ continue
+ }
+
+ fields := strings.Split(s.Text(), "\t")
+ begin, err := strconv.Atoi(fields[0])
+ if err != nil {
+ t.log.Printf("Unable to parse ninja entry %q: %v", s.Text(), err)
+ return
+ }
+ end, err := strconv.Atoi(fields[1])
+ if err != nil {
+ t.log.Printf("Unable to parse ninja entry %q: %v", s.Text(), err)
+ return
+ }
+ if end < prevEnd {
+ entries = nil
+ }
+ prevEnd = end
+ entries = append(entries, &ninjaLogEntry{
+ Name: fields[3],
+ Begin: begin,
+ End: end,
+ })
+ }
+ if err := s.Err(); err != nil {
+ t.log.Println("Unable to parse ninja log:", err)
+ return
+ }
+
+ sort.Sort(entries)
+
+ cpus := []int{}
+ offset := uint64(startOffset.UnixNano()) / 1000
+ for _, entry := range entries {
+ tid := -1
+ for cpu, endTime := range cpus {
+ if endTime <= entry.Begin {
+ tid = cpu
+ cpus[cpu] = entry.End
+ break
+ }
+ }
+ if tid == -1 {
+ tid = len(cpus)
+ cpus = append(cpus, entry.End)
+ }
+
+ t.writeEvent(&viewerEvent{
+ Name: entry.Name,
+ Phase: "X",
+ Time: offset + uint64(entry.Begin)*1000,
+ Dur: uint64(entry.End-entry.Begin) * 1000,
+ Pid: 1,
+ Tid: uint64(tid),
+ })
+ }
+}
diff --git a/ui/tracer/tracer.go b/ui/tracer/tracer.go
new file mode 100644
index 0000000..b372885
--- /dev/null
+++ b/ui/tracer/tracer.go
@@ -0,0 +1,244 @@
+// Copyright 2017 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.
+
+// This package implements a trace file writer, whose files can be opened in
+// chrome://tracing.
+//
+// It implements the JSON Array Format defined here:
+// https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/edit
+package tracer
+
+import (
+ "bytes"
+ "compress/gzip"
+ "encoding/json"
+ "fmt"
+ "io"
+ "os"
+ "strings"
+ "sync"
+ "time"
+
+ "android/soong/ui/logger"
+)
+
+type Thread uint64
+
+const (
+ MainThread = Thread(iota)
+ MaxInitThreads = Thread(iota)
+)
+
+type Tracer interface {
+ Begin(name string, thread Thread)
+ End(thread Thread)
+ Complete(name string, thread Thread, begin, end uint64)
+
+ ImportNinjaLog(thread Thread, filename string, startOffset time.Time)
+}
+
+type tracerImpl struct {
+ lock sync.Mutex
+ log logger.Logger
+
+ buf bytes.Buffer
+ file *os.File
+ w io.WriteCloser
+
+ firstEvent bool
+ nextTid uint64
+}
+
+var _ Tracer = &tracerImpl{}
+
+type viewerEvent struct {
+ Name string `json:"name,omitempty"`
+ Phase string `json:"ph"`
+ Scope string `json:"s,omitempty"`
+ Time uint64 `json:"ts"`
+ Dur uint64 `json:"dur,omitempty"`
+ Pid uint64 `json:"pid"`
+ Tid uint64 `json:"tid"`
+ ID uint64 `json:"id,omitempty"`
+ Arg interface{} `json:"args,omitempty"`
+}
+
+type nameArg struct {
+ Name string `json:"name"`
+}
+
+type nopCloser struct{ io.Writer }
+
+func (nopCloser) Close() error { return nil }
+
+// New creates a new Tracer, storing log in order to log errors later.
+// Events are buffered in memory until SetOutput is called.
+func New(log logger.Logger) *tracerImpl {
+ ret := &tracerImpl{
+ log: log,
+
+ firstEvent: true,
+ nextTid: uint64(MaxInitThreads),
+ }
+ ret.startBuffer()
+
+ return ret
+}
+
+func (t *tracerImpl) startBuffer() {
+ t.w = nopCloser{&t.buf}
+ fmt.Fprintln(t.w, "[")
+
+ t.defineThread(MainThread, "main")
+}
+
+func (t *tracerImpl) close() {
+ if t.file != nil {
+ fmt.Fprintln(t.w, "]")
+
+ if err := t.w.Close(); err != nil {
+ t.log.Println("Error closing trace writer:", err)
+ }
+
+ if err := t.file.Close(); err != nil {
+ t.log.Println("Error closing trace file:", err)
+ }
+ t.file = nil
+ t.startBuffer()
+ }
+}
+
+// SetOutput creates the output file (rotating old files).
+func (t *tracerImpl) SetOutput(filename string) {
+ t.lock.Lock()
+ defer t.lock.Unlock()
+
+ t.close()
+
+ // chrome://tracing requires that compressed trace files end in .gz
+ if !strings.HasSuffix(filename, ".gz") {
+ filename += ".gz"
+ }
+
+ f, err := logger.CreateFileWithRotation(filename, 5)
+ if err != nil {
+ t.log.Println("Failed to create trace file:", err)
+ return
+ }
+ // Save the file, since closing the gzip Writer doesn't close the
+ // underlying file.
+ t.file = f
+ t.w = gzip.NewWriter(f)
+
+ // Write out everything that happened since the start
+ if _, err := io.Copy(t.w, &t.buf); err != nil {
+ t.log.Println("Failed to write trace buffer to file:", err)
+ }
+ t.buf = bytes.Buffer{}
+}
+
+// Close closes the output file. Any future events will be buffered until the
+// next call to SetOutput.
+func (t *tracerImpl) Close() {
+ t.lock.Lock()
+ defer t.lock.Unlock()
+
+ t.close()
+}
+
+func (t *tracerImpl) writeEvent(event *viewerEvent) {
+ t.lock.Lock()
+ defer t.lock.Unlock()
+
+ t.writeEventLocked(event)
+}
+
+func (t *tracerImpl) writeEventLocked(event *viewerEvent) {
+ bytes, err := json.Marshal(event)
+ if err != nil {
+ t.log.Println("Failed to marshal event:", err)
+ t.log.Verbosef("Event: %#v", event)
+ return
+ }
+
+ if !t.firstEvent {
+ fmt.Fprintln(t.w, ",")
+ } else {
+ t.firstEvent = false
+ }
+
+ if _, err = t.w.Write(bytes); err != nil {
+ t.log.Println("Trace write error:", err)
+ }
+}
+
+func (t *tracerImpl) defineThread(thread Thread, name string) {
+ t.writeEventLocked(&viewerEvent{
+ Name: "thread_name",
+ Phase: "M",
+ Pid: 0,
+ Tid: uint64(thread),
+ Arg: &nameArg{
+ Name: name,
+ },
+ })
+}
+
+// NewThread returns a new Thread with an unused tid, writing the name out to
+// the trace file.
+func (t *tracerImpl) NewThread(name string) Thread {
+ t.lock.Lock()
+ defer t.lock.Unlock()
+
+ ret := Thread(t.nextTid)
+ t.nextTid += 1
+
+ t.defineThread(ret, name)
+ return ret
+}
+
+// Begin starts a new Duration Event. More than one Duration Event may be active
+// at a time on each Thread, but they're nested.
+func (t *tracerImpl) Begin(name string, thread Thread) {
+ t.writeEvent(&viewerEvent{
+ Name: name,
+ Phase: "B",
+ Time: uint64(time.Now().UnixNano()) / 1000,
+ Pid: 0,
+ Tid: uint64(thread),
+ })
+}
+
+// End finishes the most recent active Duration Event on the thread.
+func (t *tracerImpl) End(thread Thread) {
+ t.writeEvent(&viewerEvent{
+ Phase: "E",
+ Time: uint64(time.Now().UnixNano()) / 1000,
+ Pid: 0,
+ Tid: uint64(thread),
+ })
+}
+
+// Complete writes a Complete Event, which are like Duration Events, but include
+// a begin and end timestamp in the same event.
+func (t *tracerImpl) Complete(name string, thread Thread, begin, end uint64) {
+ t.writeEvent(&viewerEvent{
+ Name: name,
+ Phase: "X",
+ Time: begin / 1000,
+ Dur: (end - begin) / 1000,
+ Pid: 0,
+ Tid: uint64(thread),
+ })
+}