Merge "Stop versioning NDK stubs pre-M."
diff --git a/Android.bp b/Android.bp
index 4a06a11..26aeac2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -221,6 +221,7 @@
srcs: [
"java/aapt2.go",
"java/aar.go",
+ "java/android_manifest.go",
"java/android_resources.go",
"java/androidmk.go",
"java/app_builder.go",
@@ -233,8 +234,10 @@
"java/jacoco.go",
"java/java.go",
"java/java_resources.go",
+ "java/prebuilt_apis.go",
"java/proto.go",
"java/sdk_library.go",
+ "java/support_libraries.go",
"java/system_modules.go",
],
testSrcs: [
@@ -334,6 +337,7 @@
name: "libatomic",
defaults: ["linux_bionic_supported"],
vendor_available: true,
+ recovery_available: true,
arch: {
arm: {
instruction_set: "arm",
@@ -345,6 +349,7 @@
name: "libgcc",
defaults: ["linux_bionic_supported"],
vendor_available: true,
+ recovery_available: true,
arch: {
arm: {
instruction_set: "arm",
diff --git a/README.md b/README.md
index 3549b02..9f427c4 100644
--- a/README.md
+++ b/README.md
@@ -217,6 +217,18 @@
or [external/llvm/soong/llvm.go](https://android.googlesource.com/platform/external/llvm/+/master/soong/llvm.go)
for examples of more complex conditionals on product variables or environment variables.
+## Developing for Soong
+
+To load Soong code in a Go-aware IDE, create a directory outside your android tree and then:
+```bash
+apt install bindfs
+export GOPATH=<path to the directory you created>
+build/soong/scripts/setup_go_workspace_for_soong.sh
+```
+
+This will bind mount the Soong source directories into the directory in the layout expected by
+the IDE.
+
## Contact
Email android-building@googlegroups.com (external) for any questions, or see
diff --git a/android/arch.go b/android/arch.go
index 9f03f1b..d84c829 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -330,6 +330,10 @@
// Windows builds always prefer 32-bit
prefer32 = true
}
+ // only the primary arch in the recovery partition
+ if module.InstallInRecovery() {
+ targets = []Target{mctx.Config().Targets[Device][0]}
+ }
targets, err := decodeMultilib(multilib, targets, prefer32)
if err != nil {
mctx.ModuleErrorf("%s", err.Error())
@@ -956,6 +960,7 @@
{"arm", "armv7-a-neon", "cortex-a53", []string{"armeabi-v7a"}},
{"arm", "armv7-a-neon", "cortex-a53.a57", []string{"armeabi-v7a"}},
{"arm", "armv7-a-neon", "cortex-a73", []string{"armeabi-v7a"}},
+ {"arm", "armv7-a-neon", "cortex-a75", []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"}},
@@ -967,6 +972,7 @@
{"arm64", "armv8-a", "kryo", []string{"arm64-v8a"}},
{"arm64", "armv8-a", "exynos-m1", []string{"arm64-v8a"}},
{"arm64", "armv8-a", "exynos-m2", []string{"arm64-v8a"}},
+ {"arm64", "armv8-2a", "cortex-a75", []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 40ba8c1..7ba05c1 100644
--- a/android/config.go
+++ b/android/config.go
@@ -90,8 +90,9 @@
ConfigFileName string
ProductVariablesFileName string
- Targets map[OsClass][]Target
- BuildOsVariant string
+ Targets map[OsClass][]Target
+ BuildOsVariant string
+ BuildOsCommonVariant string
deviceConfig *deviceConfig
@@ -108,8 +109,7 @@
captureBuild bool // true for tests, saves build parameters for each module
ignoreEnvironment bool // true for tests, returns empty from all Getenv calls
- useOpenJDK9 bool // Use OpenJDK9, but possibly target 1.8
- targetOpenJDK9 bool // Use OpenJDK9 and target 1.9
+ targetOpenJDK9 bool // Target 1.9
stopBefore bootstrap.StopBefore
@@ -311,6 +311,7 @@
config.Targets = targets
config.BuildOsVariant = targets[Host][0].String()
+ config.BuildOsCommonVariant = getCommonTargets(targets[Host])[0].String()
if err := config.fromEnv(); err != nil {
return Config{}, err
@@ -321,22 +322,13 @@
func (c *config) fromEnv() error {
switch c.Getenv("EXPERIMENTAL_USE_OPENJDK9") {
- case "":
- if c.Getenv("RUN_ERROR_PRONE") != "true" {
- // Use OpenJDK9, but target 1.8
- c.useOpenJDK9 = true
- }
- case "false":
- // Use OpenJDK8
- case "1.8":
- // Use OpenJDK9, but target 1.8
- c.useOpenJDK9 = true
+ case "", "1.8":
+ // Nothing, we always use OpenJDK9
case "true":
// Use OpenJDK9 and target 1.9
- c.useOpenJDK9 = true
c.targetOpenJDK9 = true
default:
- return fmt.Errorf(`Invalid value for EXPERIMENTAL_USE_OPENJDK9, should be "", "false", "1.8", or "true"`)
+ return fmt.Errorf(`Invalid value for EXPERIMENTAL_USE_OPENJDK9, should be "", "1.8", or "true"`)
}
return nil
@@ -468,6 +460,10 @@
return *c.productVariables.ResourceOverlays
}
+func (c *config) PlatformVersionName() string {
+ return String(c.productVariables.Platform_version_name)
+}
+
func (c *config) PlatformSdkVersionInt() int {
return *c.productVariables.Platform_sdk_version
}
@@ -580,6 +576,10 @@
return Bool(c.productVariables.DevicePrefer32BitExecutables)
}
+func (c *config) DevicePrimaryArchType() ArchType {
+ return c.Targets[Device][0].Arch.ArchType
+}
+
func (c *config) SkipDeviceInstall() bool {
return c.EmbeddedInMake()
}
@@ -623,17 +623,12 @@
return false
}
-func (c *config) UseD8Desugar() bool {
- return !c.IsEnvFalse("USE_D8_DESUGAR")
-}
-
func (c *config) UseGoma() bool {
return Bool(c.productVariables.UseGoma)
}
-// Returns true if OpenJDK9 prebuilts are being used
-func (c *config) UseOpenJDK9() bool {
- return c.useOpenJDK9
+func (c *config) RunErrorProne() bool {
+ return c.IsEnvTrue("RUN_ERROR_PRONE")
}
// Returns true if -source 1.9 -target 1.9 is being passed to javac
@@ -799,6 +794,22 @@
return c.config.productVariables.PgoAdditionalProfileDirs
}
+func (c *deviceConfig) VendorSepolicyDirs() []string {
+ return c.config.productVariables.BoardVendorSepolicyDirs
+}
+
+func (c *deviceConfig) OdmSepolicyDirs() []string {
+ return c.config.productVariables.BoardOdmSepolicyDirs
+}
+
+func (c *deviceConfig) PlatPublicSepolicyDirs() []string {
+ return c.config.productVariables.BoardPlatPublicSepolicyDirs
+}
+
+func (c *deviceConfig) PlatPrivateSepolicyDirs() []string {
+ return c.config.productVariables.BoardPlatPrivateSepolicyDirs
+}
+
func (c *config) IntegerOverflowDisabledForPath(path string) bool {
if c.productVariables.IntegerOverflowExcludePaths == nil {
return false
diff --git a/android/defaults.go b/android/defaults.go
index c704529..d4fbf48 100644
--- a/android/defaults.go
+++ b/android/defaults.go
@@ -131,11 +131,16 @@
func defaultsMutator(ctx TopDownMutatorContext) {
if defaultable, ok := ctx.Module().(Defaultable); ok && len(defaultable.defaults().Defaults) > 0 {
var defaultsList []Defaults
+ seen := make(map[Defaults]bool)
+
ctx.WalkDeps(func(module, parent Module) bool {
if ctx.OtherModuleDependencyTag(module) == DefaultsDepTag {
if defaults, ok := module.(Defaults); ok {
- defaultsList = append(defaultsList, defaults)
- return len(defaults.defaults().Defaults) > 0
+ if !seen[defaults] {
+ seen[defaults] = true
+ defaultsList = append(defaultsList, defaults)
+ return len(defaults.defaults().Defaults) > 0
+ }
} else {
ctx.PropertyErrorf("defaults", "module %s is not an defaults module",
ctx.OtherModuleName(module))
diff --git a/android/makevars.go b/android/makevars.go
index 3094a48..accc4d3 100644
--- a/android/makevars.go
+++ b/android/makevars.go
@@ -20,6 +20,7 @@
"io/ioutil"
"os"
"strconv"
+ "strings"
"github.com/google/blueprint/proptools"
)
@@ -240,8 +241,16 @@
return c.ctx
}
+var ninjaDescaper = strings.NewReplacer("$$", "$")
+
func (c *makeVarsContext) Eval(ninjaStr string) (string, error) {
- return c.ctx.Eval(c.pctx, ninjaStr)
+ s, err := c.ctx.Eval(c.pctx, ninjaStr)
+ if err != nil {
+ return "", err
+ }
+ // SingletonContext.Eval returns an exapnded string that is valid for a ninja file, de-escape $$ to $ for use
+ // in a Makefile
+ return ninjaDescaper.Replace(s), nil
}
func (c *makeVarsContext) addVariableRaw(name, value string, strict, sort bool) {
diff --git a/android/module.go b/android/module.go
index 552d165..b6220dc 100644
--- a/android/module.go
+++ b/android/module.go
@@ -124,6 +124,7 @@
InstallInData() bool
InstallInSanitizerDir() bool
+ InstallInRecovery() bool
RequiredModuleNames() []string
@@ -143,7 +144,9 @@
VisitDirectDeps(visit func(Module))
VisitDirectDepsWithTag(tag blueprint.DependencyTag, visit func(Module))
VisitDirectDepsIf(pred func(Module) bool, visit func(Module))
+ // Deprecated: use WalkDeps instead to support multiple dependency tags on the same module
VisitDepsDepthFirst(visit func(Module))
+ // Deprecated: use WalkDeps instead to support multiple dependency tags on the same module
VisitDepsDepthFirstIf(pred func(Module) bool, visit func(Module))
WalkDeps(visit func(Module, Module) bool)
@@ -176,6 +179,7 @@
Target() Target
InstallInData() bool
InstallInSanitizerDir() bool
+ InstallInRecovery() bool
SkipInstall()
ExportedToMake() bool
@@ -191,8 +195,6 @@
}
type commonProperties struct {
- Tags []string
-
// emit build rules for this module
Enabled *bool `android:"arch_variant"`
@@ -239,6 +241,9 @@
// /system/product if product partition does not exist).
Product_specific *bool
+ // Whether this module is installed to recovery partition
+ Recovery *bool
+
// init.rc files to be installed if this module is installed
Init_rc []string
@@ -536,6 +541,7 @@
ctx blueprint.ModuleContext) Paths {
result := Paths{}
+ // TODO(ccross): we need to use WalkDeps and have some way to know which dependencies require installation
ctx.VisitDepsDepthFirstIf(isFileInstaller,
func(m blueprint.Module) {
fileInstaller := m.(fileInstaller)
@@ -562,6 +568,10 @@
return false
}
+func (p *ModuleBase) InstallInRecovery() bool {
+ return Bool(p.commonProperties.Recovery)
+}
+
func (a *ModuleBase) generateModuleTarget(ctx ModuleContext) {
allInstalledFiles := Paths{}
allCheckbuildFiles := Paths{}
@@ -1010,11 +1020,22 @@
return a.module.InstallInSanitizerDir()
}
+func (a *androidModuleContext) InstallInRecovery() bool {
+ return a.module.InstallInRecovery()
+}
+
func (a *androidModuleContext) skipInstall(fullInstallPath OutputPath) bool {
if a.module.base().commonProperties.SkipInstall {
return true
}
+ // We'll need a solution for choosing which of modules with the same name in different
+ // namespaces to install. For now, reuse the list of namespaces exported to Make as the
+ // list of namespaces to install in a Soong-only build.
+ if !a.module.base().commonProperties.NamespaceExportedToMake {
+ return true
+ }
+
if a.Device() {
if a.Config().SkipDeviceInstall() {
return true
diff --git a/android/paths.go b/android/paths.go
index 91dd9a6..af2f956 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -47,6 +47,7 @@
InstallInData() bool
InstallInSanitizerDir() bool
+ InstallInRecovery() bool
}
var _ ModuleInstallPathContext = ModuleContext(nil)
@@ -948,6 +949,9 @@
var partition string
if ctx.InstallInData() {
partition = "data"
+ } else if ctx.InstallInRecovery() {
+ // the layout of recovery partion is the same as that of system partition
+ partition = "recovery/root/system"
} else if ctx.SocSpecific() {
partition = ctx.DeviceConfig().VendorPath()
} else if ctx.DeviceSpecific() {
diff --git a/android/paths_test.go b/android/paths_test.go
index cd9fbfd..b3dc9de 100644
--- a/android/paths_test.go
+++ b/android/paths_test.go
@@ -201,6 +201,7 @@
inData bool
inSanitizerDir bool
+ inRecovery bool
}
func (moduleInstallPathContextImpl) Fs() pathtools.FileSystem {
@@ -221,6 +222,10 @@
return m.inSanitizerDir
}
+func (m moduleInstallPathContextImpl) InstallInRecovery() bool {
+ return m.inRecovery
+}
+
func TestPathForModuleInstall(t *testing.T) {
testConfig := TestConfig("", nil)
diff --git a/android/singleton.go b/android/singleton.go
index f577b0a..fa1efdc 100644
--- a/android/singleton.go
+++ b/android/singleton.go
@@ -50,7 +50,9 @@
VisitAllModules(visit func(Module))
VisitAllModulesIf(pred func(Module) bool, visit func(Module))
+ // Deprecated: use WalkDeps instead to support multiple dependency tags on the same module
VisitDepsDepthFirst(module Module, visit func(Module))
+ // Deprecated: use WalkDeps instead to support multiple dependency tags on the same module
VisitDepsDepthFirstIf(module Module, pred func(Module) bool,
visit func(Module))
diff --git a/android/testing.go b/android/testing.go
index f5d33e1..ca7e7ce 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -92,6 +92,16 @@
return TestingModule{module}
}
+func (ctx *TestContext) ModuleVariantsForTests(name string) []string {
+ var variants []string
+ ctx.VisitAllModules(func(m blueprint.Module) {
+ if ctx.ModuleName(m) == name {
+ variants = append(variants, ctx.ModuleSubDir(m))
+ }
+ })
+ return variants
+}
+
// MockFileSystem causes the Context to replace all reads with accesses to the provided map of
// filenames to contents stored as a byte slice.
func (ctx *TestContext) MockFileSystem(files map[string][]byte) {
diff --git a/android/variable.go b/android/variable.go
index 2057903..5edcdbc 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -62,6 +62,11 @@
Cflags []string
}
+ // Product_is_iot is true for Android Things devices.
+ Product_is_iot struct {
+ Cflags []string
+ }
+
// treble_linker_namespaces is true when the system/vendor linker namespace separation is
// enabled.
Treble_linker_namespaces struct {
@@ -87,6 +92,9 @@
Eng struct {
Cflags []string
Cppflags []string
+ Lto struct {
+ Never *bool
+ }
}
Pdk struct {
@@ -109,6 +117,7 @@
BuildNumberFromFile *string `json:",omitempty"`
DateFromFile *string `json:",omitempty"`
+ Platform_version_name *string `json:",omitempty"`
Platform_sdk_version *int `json:",omitempty"`
Platform_sdk_codename *string `json:",omitempty"`
Platform_sdk_final *bool `json:",omitempty"`
@@ -200,6 +209,8 @@
Override_rs_driver *string `json:",omitempty"`
+ Product_is_iot *bool `json:",omitempty"`
+
DeviceKernelHeaders []string `json:",omitempty"`
DistDir *string `json:",omitempty"`
@@ -209,6 +220,11 @@
PgoAdditionalProfileDirs []string `json:",omitempty"`
+ BoardVendorSepolicyDirs []string `json:",omitempty"`
+ BoardOdmSepolicyDirs []string `json:",omitempty"`
+ BoardPlatPublicSepolicyDirs []string `json:",omitempty"`
+ BoardPlatPrivateSepolicyDirs []string `json:",omitempty"`
+
VendorVars map[string]map[string]string `json:",omitempty"`
}
diff --git a/androidmk/Android.bp b/androidmk/Android.bp
index 442452f..1d939b0 100644
--- a/androidmk/Android.bp
+++ b/androidmk/Android.bp
@@ -44,6 +44,6 @@
],
testSrcs: [
"parser/make_strings_test.go",
+ "parser/parser_test.go",
],
}
-
diff --git a/androidmk/cmd/androidmk/android.go b/androidmk/cmd/androidmk/android.go
index 37877c8..29c7365 100644
--- a/androidmk/cmd/androidmk/android.go
+++ b/androidmk/cmd/androidmk/android.go
@@ -85,6 +85,7 @@
"LOCAL_MULTILIB": "compile_multilib",
"LOCAL_ARM_MODE_HACK": "instruction_set",
"LOCAL_SDK_VERSION": "sdk_version",
+ "LOCAL_MIN_SDK_VERSION": "min_sdk_version",
"LOCAL_NDK_STL_VARIANT": "stl",
"LOCAL_JAR_MANIFEST": "manifest",
"LOCAL_JARJAR_RULES": "jarjar_rules",
@@ -147,7 +148,7 @@
"LOCAL_ANNOTATION_PROCESSOR_CLASSES": "annotation_processor_classes",
"LOCAL_PROGUARD_FLAGS": "optimize.proguard_flags",
- "LOCAL_PROGUARD_FLAG_FILES": "optimize.proguard_flag_files",
+ "LOCAL_PROGUARD_FLAG_FILES": "optimize.proguard_flags_files",
// These will be rewritten to libs/static_libs by bpfix, after their presence is used to convert
// java_library_static to android_library.
@@ -179,6 +180,8 @@
"LOCAL_DEX_PREOPT": "dex_preopt.enabled",
"LOCAL_DEX_PREOPT_APP_IMAGE": "dex_preopt.app_image",
"LOCAL_DEX_PREOPT_GENERATE_PROFILE": "dex_preopt.profile_guided",
+
+ "LOCAL_PRIVATE_PLATFORM_APIS": "platform_apis",
})
}
@@ -744,8 +747,8 @@
"BUILD_NATIVE_BENCHMARK": "cc_benchmark",
"BUILD_HOST_NATIVE_BENCHMARK": "cc_benchmark_host",
- "BUILD_JAVA_LIBRARY": "java_library",
- "BUILD_STATIC_JAVA_LIBRARY": "java_library_static",
+ "BUILD_JAVA_LIBRARY": "java_library_installable", // will be rewritten to java_library by bpfix
+ "BUILD_STATIC_JAVA_LIBRARY": "java_library",
"BUILD_HOST_JAVA_LIBRARY": "java_library_host",
"BUILD_HOST_DALVIK_JAVA_LIBRARY": "java_library_host_dalvik",
"BUILD_PACKAGE": "android_app",
diff --git a/androidmk/cmd/androidmk/androidmk_test.go b/androidmk/cmd/androidmk/androidmk_test.go
index edf3d42..80e7a75 100644
--- a/androidmk/cmd/androidmk/androidmk_test.go
+++ b/androidmk/cmd/androidmk/androidmk_test.go
@@ -331,7 +331,7 @@
`,
},
{
- desc: "Keep LOCAL_MODULE_TAGS non-optional",
+ desc: "Warn for LOCAL_MODULE_TAGS non-optional",
in: `
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := debug
@@ -340,7 +340,68 @@
expected: `
cc_library_shared {
- tags: ["debug"],
+ // WARNING: Module tags are not supported in Soong.
+ // Add this module to PRODUCT_PACKAGES_DEBUG in your product file if you want to
+ // force installation for -userdebug and -eng builds.
+}
+`,
+ },
+ {
+ desc: "Custom warning for LOCAL_MODULE_TAGS tests",
+ in: `
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := debug tests
+include $(BUILD_SHARED_LIBRARY)
+`,
+
+ expected: `
+cc_library_shared {
+ // WARNING: Module tags are not supported in Soong.
+ // Add this module to PRODUCT_PACKAGES_DEBUG in your product file if you want to
+ // force installation for -userdebug and -eng builds.
+ // WARNING: Module tags are not supported in Soong.
+ // To make a shared library only for tests, use the "cc_test_library" module
+ // type. If you don't use gtest, set "gtest: false".
+}
+`,
+ },
+ {
+ desc: "Ignore LOCAL_MODULE_TAGS tests for cc_test",
+ in: `
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+include $(BUILD_NATIVE_TEST)
+`,
+
+ expected: `
+cc_test {
+}
+`,
+ },
+ {
+ desc: "Convert LOCAL_MODULE_TAGS tests to java_test",
+ in: `
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+include $(BUILD_JAVA_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+include $(BUILD_PACKAGE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+include $(BUILD_HOST_JAVA_LIBRARY)
+`,
+
+ expected: `
+java_test {
+}
+
+android_test {
+}
+
+java_test_host {
}
`,
},
@@ -451,7 +512,7 @@
LOCAL_PROGUARD_ENABLED := obfuscation optimization
# Custom
LOCAL_PROGUARD_ENABLED := custom
- include $(BUILD_JAVA_LIBRARY)
+ include $(BUILD_STATIC_JAVA_LIBRARY)
`,
expected: `
java_library {
@@ -474,11 +535,53 @@
`,
},
{
+ desc: "java library",
+ in: `
+ include $(CLEAR_VARS)
+ LOCAL_SRC_FILES := a.java
+ include $(BUILD_STATIC_JAVA_LIBRARY)
+
+ include $(CLEAR_VARS)
+ LOCAL_SRC_FILES := b.java
+ include $(BUILD_JAVA_LIBRARY)
+
+ include $(CLEAR_VARS)
+ LOCAL_SRC_FILES := c.java
+ LOCAL_UNINSTALLABLE_MODULE := true
+ include $(BUILD_JAVA_LIBRARY)
+
+ include $(CLEAR_VARS)
+ LOCAL_SRC_FILES := d.java
+ LOCAL_UNINSTALLABLE_MODULE := false
+ include $(BUILD_JAVA_LIBRARY)
+ `,
+ expected: `
+ java_library {
+ srcs: ["a.java"],
+ }
+
+ java_library {
+ installable: true,
+ srcs: ["b.java"],
+ }
+
+ java_library {
+ installable: false,
+ srcs: ["c.java"],
+ }
+
+ java_library {
+ installable: true,
+ srcs: ["d.java"],
+ }
+ `,
+ },
+ {
desc: "errorprone options for java library",
in: `
include $(CLEAR_VARS)
LOCAL_ERROR_PRONE_FLAGS := -Xep:AsyncCallableReturnsNull:ERROR -Xep:AsyncFunctionReturnsNull:ERROR
- include $(BUILD_JAVA_LIBRARY)
+ include $(BUILD_STATIC_JAVA_LIBRARY)
`,
expected: `
java_library {
@@ -570,12 +673,25 @@
],
}
- java_library_static {
+ java_library {
srcs: ["test.java"],
static_libs: [],
}
`,
},
+ {
+ desc: "cc_library shared_libs",
+ in: `
+ include $(CLEAR_VARS)
+ LOCAL_SHARED_LIBRARIES := libfoo
+ include $(BUILD_SHARED_LIBRARY)
+ `,
+ expected: `
+ cc_library_shared {
+ shared_libs: ["libfoo"],
+ }
+ `,
+ },
}
func TestEndToEnd(t *testing.T) {
diff --git a/androidmk/parser/make_strings.go b/androidmk/parser/make_strings.go
index e6885a8..4b782a2 100644
--- a/androidmk/parser/make_strings.go
+++ b/androidmk/parser/make_strings.go
@@ -90,10 +90,10 @@
if len(ms.Strings) == 0 {
return ""
} else {
- ret := ms.Strings[0]
+ ret := unescape(ms.Strings[0])
for i := range ms.Strings[1:] {
ret += ms.Variables[i].Value(scope)
- ret += ms.Strings[i+1]
+ ret += unescape(ms.Strings[i+1])
}
return ret
}
@@ -125,6 +125,16 @@
}
func (ms *MakeString) SplitN(sep string, n int) []*MakeString {
+ return ms.splitNFunc(n, func(s string, n int) []string {
+ return splitAnyN(s, sep, n)
+ })
+}
+
+func (ms *MakeString) Words() []*MakeString {
+ return ms.splitNFunc(-1, splitWords)
+}
+
+func (ms *MakeString) splitNFunc(n int, splitFunc func(s string, n int) []string) []*MakeString {
ret := []*MakeString{}
curMs := SimpleMakeString("", ms.Pos())
@@ -133,7 +143,7 @@
var s string
for i, s = range ms.Strings {
if n != 0 {
- split := splitAnyN(s, sep, n)
+ split := splitFunc(s, n)
if n != -1 {
if len(split) > n {
panic("oops!")
@@ -156,7 +166,9 @@
}
}
- ret = append(ret, curMs)
+ if !curMs.Empty() {
+ ret = append(ret, curMs)
+ }
return ret
}
@@ -206,3 +218,64 @@
ret = append(ret, s)
return ret
}
+
+func splitWords(s string, n int) []string {
+ ret := []string{}
+ preserve := ""
+ for n == -1 || n > 1 {
+ index := strings.IndexAny(s, " \t")
+ if index == 0 && len(preserve) == 0 {
+ s = s[1:]
+ } else if index >= 0 {
+ escapeCount := 0
+ for i := index - 1; i >= 0; i-- {
+ if s[i] != '\\' {
+ break
+ }
+ escapeCount += 1
+ }
+
+ if escapeCount%2 == 1 {
+ preserve += s[0 : index+1]
+ s = s[index+1:]
+ continue
+ }
+
+ ret = append(ret, preserve+s[0:index])
+ s = s[index+1:]
+ preserve = ""
+ if n > 0 {
+ n--
+ }
+ } else {
+ break
+ }
+ }
+ if preserve != "" || s != "" || len(ret) == 0 {
+ ret = append(ret, preserve+s)
+ }
+ return ret
+}
+
+func unescape(s string) string {
+ ret := ""
+ for {
+ index := strings.IndexByte(s, '\\')
+ if index < 0 {
+ break
+ }
+
+ if index+1 == len(s) {
+ break
+ }
+
+ switch s[index+1] {
+ case ' ', '\\', '#', ':', '*', '[', '|', '\t', '\n', '\r':
+ ret += s[:index] + s[index+1:index+2]
+ default:
+ ret += s[:index+2]
+ }
+ s = s[index+2:]
+ }
+ return ret + s
+}
diff --git a/androidmk/parser/make_strings_test.go b/androidmk/parser/make_strings_test.go
index 8ad3d74..6995e89 100644
--- a/androidmk/parser/make_strings_test.go
+++ b/androidmk/parser/make_strings_test.go
@@ -99,6 +99,78 @@
}
}
+var valueTestCases = []struct {
+ in *MakeString
+ expected string
+}{
+ {
+ in: SimpleMakeString("a b", NoPos),
+ expected: "a b",
+ },
+ {
+ in: SimpleMakeString("a\\ \\\tb\\\\", NoPos),
+ expected: "a \tb\\",
+ },
+ {
+ in: SimpleMakeString("a\\b\\", NoPos),
+ expected: "a\\b\\",
+ },
+}
+
+func TestMakeStringValue(t *testing.T) {
+ for _, test := range valueTestCases {
+ got := test.in.Value(nil)
+ if got != test.expected {
+ t.Errorf("\nwith: %q\nwant: %q\n got: %q", test.in.Dump(), test.expected, got)
+ }
+ }
+}
+
+var splitWordsTestCases = []struct {
+ in *MakeString
+ expected []*MakeString
+}{
+ {
+ in: SimpleMakeString("", NoPos),
+ expected: []*MakeString{},
+ },
+ {
+ in: SimpleMakeString(" a b\\ c d", NoPos),
+ expected: []*MakeString{
+ SimpleMakeString("a", NoPos),
+ SimpleMakeString("b\\ c", NoPos),
+ SimpleMakeString("d", NoPos),
+ },
+ },
+ {
+ in: SimpleMakeString(" a\tb\\\t\\ c d ", NoPos),
+ expected: []*MakeString{
+ SimpleMakeString("a", NoPos),
+ SimpleMakeString("b\\\t\\ c", NoPos),
+ SimpleMakeString("d", NoPos),
+ },
+ },
+ {
+ in: SimpleMakeString(`a\\ b\\\ c d`, NoPos),
+ expected: []*MakeString{
+ SimpleMakeString(`a\\`, NoPos),
+ SimpleMakeString(`b\\\ c`, NoPos),
+ SimpleMakeString("d", NoPos),
+ },
+ },
+}
+
+func TestMakeStringWords(t *testing.T) {
+ for _, test := range splitWordsTestCases {
+ got := test.in.Words()
+ gotString := dumpArray(got)
+ expectedString := dumpArray(test.expected)
+ if gotString != expectedString {
+ t.Errorf("with:\n%q\nexpected:\n%s\ngot:\n%s", test.in.Dump(), expectedString, gotString)
+ }
+ }
+}
+
func dumpArray(a []*MakeString) string {
ret := make([]string, len(a))
diff --git a/androidmk/parser/parser.go b/androidmk/parser/parser.go
index 89ee308..89c1af9 100644
--- a/androidmk/parser/parser.go
+++ b/androidmk/parser/parser.go
@@ -35,6 +35,10 @@
return fmt.Sprintf("%s: %s", e.Pos, e.Err)
}
+const builtinDollar = "__builtin_dollar"
+
+var builtinDollarName = SimpleMakeString(builtinDollar, NoPos)
+
func (p *parser) Parse() ([]Node, []error) {
defer func() {
if r := recover(); r != nil {
@@ -326,7 +330,11 @@
case '$':
var variable Variable
variable = p.parseVariable()
- value.appendVariable(variable)
+ if variable.Name == builtinDollarName {
+ value.appendString("$")
+ } else {
+ value.appendVariable(variable)
+ }
case scanner.EOF:
break loop
case '(':
@@ -357,7 +365,8 @@
case '{':
return p.parseBracketedVariable('{', '}', pos)
case '$':
- name = SimpleMakeString("__builtin_dollar", NoPos)
+ name = builtinDollarName
+ p.accept(p.tok)
case scanner.EOF:
p.errorf("expected variable name, found %s",
scanner.TokenString(p.tok))
@@ -457,6 +466,8 @@
case '=':
p.parseAssignment("=", target, prerequisites)
return nil, true
+ case scanner.EOF:
+ // do nothing
default:
p.errorf("unexpected token %s after rule prerequisites", scanner.TokenString(p.tok))
}
diff --git a/androidmk/parser/parser_test.go b/androidmk/parser/parser_test.go
new file mode 100644
index 0000000..f562c29
--- /dev/null
+++ b/androidmk/parser/parser_test.go
@@ -0,0 +1,61 @@
+// Copyright 2018 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 parser
+
+import (
+ "bytes"
+ "testing"
+)
+
+var parserTestCases = []struct {
+ name string
+ in string
+ out []Node
+}{
+ {
+ name: "Escaped $",
+ in: `a$$ b: c`,
+ out: []Node{
+ &Rule{
+ Target: SimpleMakeString("a$ b", NoPos),
+ Prerequisites: SimpleMakeString("c", NoPos),
+ },
+ },
+ },
+}
+
+func TestParse(t *testing.T) {
+ for _, test := range parserTestCases {
+ t.Run(test.name, func(t *testing.T) {
+ p := NewParser(test.name, bytes.NewBufferString(test.in))
+ got, errs := p.Parse()
+
+ if len(errs) != 0 {
+ t.Fatalf("Unexpected errors while parsing: %v", errs)
+ }
+
+ if len(got) != len(test.out) {
+ t.Fatalf("length mismatch, expected %d nodes, got %d", len(test.out), len(got))
+ }
+
+ for i := range got {
+ if got[i].Dump() != test.out[i].Dump() {
+ t.Errorf("incorrect node %d:\nexpected: %#v (%s)\n got: %#v (%s)",
+ i, test.out[i], test.out[i].Dump(), got[i], got[i].Dump())
+ }
+ }
+ })
+ }
+}
diff --git a/androidmk/parser/scope.go b/androidmk/parser/scope.go
index 7a514fa..167e470 100644
--- a/androidmk/parser/scope.go
+++ b/androidmk/parser/scope.go
@@ -71,7 +71,7 @@
func init() {
builtinScope := make(map[string]string)
- builtinScope["__builtin_dollar"] = "$"
+ builtinScope[builtinDollar] = "$"
}
func (v Variable) EvalFunction(scope Scope) (string, bool) {
diff --git a/bpf/Android.bp b/bpf/Android.bp
new file mode 100644
index 0000000..7bd4d44
--- /dev/null
+++ b/bpf/Android.bp
@@ -0,0 +1,30 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// 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-bpf",
+ pkgPath: "android/soong/bpf",
+ deps: [
+ "blueprint",
+ "blueprint-proptools",
+ "soong-android",
+ "soong-cc-config",
+ ],
+ srcs: [
+ "bpf.go",
+ ],
+ pluginFor: ["soong_build"],
+}
diff --git a/bpf/bpf.go b/bpf/bpf.go
new file mode 100644
index 0000000..fa1f3ff
--- /dev/null
+++ b/bpf/bpf.go
@@ -0,0 +1,135 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// 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 bpf
+
+import (
+ "fmt"
+ "io"
+ "strings"
+
+ "android/soong/android"
+ _ "android/soong/cc/config"
+
+ "github.com/google/blueprint"
+)
+
+func init() {
+ android.RegisterModuleType("bpf", bpfFactory)
+ pctx.Import("android/soong/cc/config")
+}
+
+var (
+ pctx = android.NewPackageContext("android/soong/bpf")
+
+ cc = pctx.AndroidGomaStaticRule("cc",
+ blueprint.RuleParams{
+ Depfile: "${out}.d",
+ Deps: blueprint.DepsGCC,
+ Command: "$ccCmd --target=bpf -c $cFlags -MD -MF ${out}.d -o $out $in",
+ CommandDeps: []string{"$ccCmd"},
+ },
+ "ccCmd", "cFlags")
+)
+
+type BpfProperties struct {
+ Srcs []string
+ Cflags []string
+ Include_dirs []string
+}
+
+type bpf struct {
+ android.ModuleBase
+
+ properties BpfProperties
+
+ objs android.Paths
+}
+
+func (bpf *bpf) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+ cflags := []string{
+ "-nostdlibinc",
+ "-O2",
+ "-isystem bionic/libc/include",
+ "-isystem bionic/libc/kernel/uapi",
+ // The architecture doesn't matter here, but asm/types.h is included by linux/types.h.
+ "-isystem bionic/libc/kernel/uapi/asm-arm64",
+ "-isystem bionic/libc/kernel/android/uapi",
+ "-I " + ctx.ModuleDir(),
+ }
+
+ for _, dir := range android.PathsForSource(ctx, bpf.properties.Include_dirs) {
+ cflags = append(cflags, "-I "+dir.String())
+ }
+
+ cflags = append(cflags, bpf.properties.Cflags...)
+
+ srcs := ctx.ExpandSources(bpf.properties.Srcs, nil)
+
+ for _, src := range srcs {
+ obj := android.ObjPathWithExt(ctx, "", src, "o")
+
+ ctx.Build(pctx, android.BuildParams{
+ Rule: cc,
+ Input: src,
+ Output: obj,
+ Args: map[string]string{
+ "cFlags": strings.Join(cflags, " "),
+ "ccCmd": "${config.ClangBin}/clang",
+ },
+ })
+
+ bpf.objs = append(bpf.objs, obj)
+ }
+}
+
+func (bpf *bpf) DepsMutator(ctx android.BottomUpMutatorContext) {
+ android.ExtractSourcesDeps(ctx, bpf.properties.Srcs)
+}
+
+func (bpf *bpf) AndroidMk() android.AndroidMkData {
+ return android.AndroidMkData{
+ Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
+ var names []string
+ fmt.Fprintln(w)
+ fmt.Fprintln(w, "LOCAL_PATH :=", moduleDir)
+ fmt.Fprintln(w)
+ for _, obj := range bpf.objs {
+ objName := name + "_" + obj.Base()
+ names = append(names, objName)
+ fmt.Fprintln(w, "include $(CLEAR_VARS)")
+ fmt.Fprintln(w, "LOCAL_MODULE := ", objName)
+ fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", obj.String())
+ fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", obj.Base())
+ fmt.Fprintln(w, "LOCAL_MODULE_CLASS := ETC")
+ fmt.Fprintln(w, "LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/bpf")
+ fmt.Fprintln(w, "include $(BUILD_PREBUILT)")
+ fmt.Fprintln(w)
+ }
+ fmt.Fprintln(w, "include $(CLEAR_VARS)")
+ fmt.Fprintln(w, "LOCAL_MODULE := ", name)
+ fmt.Fprintln(w, "LOCAL_REQUIRED_MODULES :=", strings.Join(names, " "))
+ fmt.Fprintln(w, "include $(BUILD_PHONY_PACKAGE)")
+ },
+ }
+}
+
+func bpfFactory() android.Module {
+ module := &bpf{}
+
+ module.AddProperties(&module.properties)
+
+ android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+ return module
+}
diff --git a/bpfix/bpfix/bpfix.go b/bpfix/bpfix/bpfix.go
index e4d4e34..056a01b 100644
--- a/bpfix/bpfix/bpfix.go
+++ b/bpfix/bpfix/bpfix.go
@@ -22,6 +22,7 @@
"fmt"
"io"
"path/filepath"
+ "strings"
"github.com/google/blueprint/parser"
)
@@ -44,10 +45,51 @@
// A FixRequest specifies the details of which fixes to apply to an individual file
// A FixRequest doesn't specify whether to do a dry run or where to write the results; that's in cmd/bpfix.go
type FixRequest struct {
- simplifyKnownRedundantVariables bool
- rewriteIncorrectAndroidmkPrebuilts bool
- rewriteIncorrectAndroidmkAndroidLibraries bool
- mergeMatchingModuleProperties bool
+ steps []fixStep
+}
+
+type fixStep struct {
+ name string
+ fix func(f *Fixer) error
+}
+
+var fixSteps = []fixStep{
+ {
+ name: "simplifyKnownRedundantVariables",
+ fix: runPatchListMod(simplifyKnownPropertiesDuplicatingEachOther),
+ },
+ {
+ name: "rewriteIncorrectAndroidmkPrebuilts",
+ fix: rewriteIncorrectAndroidmkPrebuilts,
+ },
+ {
+ name: "rewriteIncorrectAndroidmkAndroidLibraries",
+ fix: rewriteIncorrectAndroidmkAndroidLibraries,
+ },
+ {
+ name: "rewriteTestModuleTypes",
+ fix: rewriteTestModuleTypes,
+ },
+ {
+ name: "rewriteAndroidmkJavaLibs",
+ fix: rewriteAndroidmkJavaLibs,
+ },
+ {
+ name: "rewriteJavaStaticLibs",
+ fix: rewriteJavaStaticLibs,
+ },
+ {
+ name: "mergeMatchingModuleProperties",
+ fix: runPatchListMod(mergeMatchingModuleProperties),
+ },
+ {
+ name: "reorderCommonProperties",
+ fix: runPatchListMod(reorderCommonProperties),
+ },
+ {
+ name: "removeTags",
+ fix: runPatchListMod(removeTags),
+ },
}
func NewFixRequest() FixRequest {
@@ -55,11 +97,8 @@
}
func (r FixRequest) AddAll() (result FixRequest) {
- result = r
- result.simplifyKnownRedundantVariables = true
- result.rewriteIncorrectAndroidmkPrebuilts = true
- result.rewriteIncorrectAndroidmkAndroidLibraries = true
- result.mergeMatchingModuleProperties = true
+ result.steps = append([]fixStep(nil), r.steps...)
+ result.steps = append(result.steps, fixSteps...)
return result
}
@@ -143,28 +182,8 @@
}
func (f *Fixer) fixTreeOnce(config FixRequest) error {
- if config.simplifyKnownRedundantVariables {
- err := f.simplifyKnownPropertiesDuplicatingEachOther()
- if err != nil {
- return err
- }
- }
- if config.rewriteIncorrectAndroidmkPrebuilts {
- err := f.rewriteIncorrectAndroidmkPrebuilts()
- if err != nil {
- return err
- }
- }
-
- if config.rewriteIncorrectAndroidmkAndroidLibraries {
- err := f.rewriteIncorrectAndroidmkAndroidLibraries()
- if err != nil {
- return err
- }
- }
-
- if config.mergeMatchingModuleProperties {
- err := f.mergeMatchingModuleProperties()
+ for _, fix := range config.steps {
+ err := fix.fix(f)
if err != nil {
return err
}
@@ -172,12 +191,13 @@
return nil
}
-func (f *Fixer) simplifyKnownPropertiesDuplicatingEachOther() error {
+func simplifyKnownPropertiesDuplicatingEachOther(mod *parser.Module, buf []byte, patchList *parser.PatchList) error {
// remove from local_include_dirs anything in export_include_dirs
- return f.removeMatchingModuleListProperties("export_include_dirs", "local_include_dirs")
+ return removeMatchingModuleListProperties(mod, patchList,
+ "export_include_dirs", "local_include_dirs")
}
-func (f *Fixer) rewriteIncorrectAndroidmkPrebuilts() error {
+func rewriteIncorrectAndroidmkPrebuilts(f *Fixer) error {
for _, def := range f.tree.Defs {
mod, ok := def.(*parser.Module)
if !ok {
@@ -213,19 +233,23 @@
return nil
}
-func (f *Fixer) rewriteIncorrectAndroidmkAndroidLibraries() error {
+func rewriteIncorrectAndroidmkAndroidLibraries(f *Fixer) error {
for _, def := range f.tree.Defs {
mod, ok := def.(*parser.Module)
if !ok {
continue
}
+ if !strings.HasPrefix(mod.Type, "java_") && !strings.HasPrefix(mod.Type, "android_") {
+ continue
+ }
+
hasAndroidLibraries := hasNonEmptyLiteralListProperty(mod, "android_libs")
hasStaticAndroidLibraries := hasNonEmptyLiteralListProperty(mod, "android_static_libs")
hasResourceDirs := hasNonEmptyLiteralListProperty(mod, "resource_dirs")
if hasAndroidLibraries || hasStaticAndroidLibraries || hasResourceDirs {
- if mod.Type == "java_library_static" {
+ if mod.Type == "java_library_static" || mod.Type == "java_library" {
mod.Type = "android_library"
}
}
@@ -244,42 +268,264 @@
return nil
}
-func (f *Fixer) mergeMatchingModuleProperties() error {
- // Make sure all the offsets are accurate
- buf, err := f.reparse()
- if err != nil {
- return err
- }
-
- var patchlist parser.PatchList
+// rewriteTestModuleTypes looks for modules that are identifiable as tests but for which Make doesn't have a separate
+// module class, and moves them to the appropriate Soong module type.
+func rewriteTestModuleTypes(f *Fixer) error {
for _, def := range f.tree.Defs {
mod, ok := def.(*parser.Module)
if !ok {
continue
}
- err := mergeMatchingProperties(&mod.Properties, buf, &patchlist)
+ if !strings.HasPrefix(mod.Type, "java_") && !strings.HasPrefix(mod.Type, "android_") {
+ continue
+ }
+
+ hasInstrumentationFor := hasNonEmptyLiteralStringProperty(mod, "instrumentation_for")
+ tags, _ := getLiteralListPropertyValue(mod, "tags")
+
+ var hasTestsTag bool
+ for _, tag := range tags {
+ if tag == "tests" {
+ hasTestsTag = true
+ }
+ }
+
+ isTest := hasInstrumentationFor || hasTestsTag
+
+ if isTest {
+ switch mod.Type {
+ case "android_app":
+ mod.Type = "android_test"
+ case "java_library", "java_library_installable":
+ mod.Type = "java_test"
+ case "java_library_host":
+ mod.Type = "java_test_host"
+ }
+ }
+ }
+
+ return nil
+}
+
+// rewriteJavaStaticLibs rewrites java_library_static into java_library
+func rewriteJavaStaticLibs(f *Fixer) error {
+ for _, def := range f.tree.Defs {
+ mod, ok := def.(*parser.Module)
+ if !ok {
+ continue
+ }
+
+ if mod.Type == "java_library_static" {
+ mod.Type = "java_library"
+ }
+ }
+
+ return nil
+}
+
+// rewriteAndroidmkJavaLibs rewrites java_library_installable into java_library plus installable: true
+func rewriteAndroidmkJavaLibs(f *Fixer) error {
+ for _, def := range f.tree.Defs {
+ mod, ok := def.(*parser.Module)
+ if !ok {
+ continue
+ }
+
+ if mod.Type != "java_library_installable" {
+ continue
+ }
+
+ mod.Type = "java_library"
+
+ _, hasInstallable := mod.GetProperty("installable")
+ if !hasInstallable {
+ prop := &parser.Property{
+ Name: "installable",
+ Value: &parser.Bool{
+ Value: true,
+ },
+ }
+ mod.Properties = append(mod.Properties, prop)
+ }
+ }
+
+ return nil
+}
+
+func runPatchListMod(modFunc func(mod *parser.Module, buf []byte, patchlist *parser.PatchList) error) func(*Fixer) error {
+ return func(f *Fixer) error {
+ // Make sure all the offsets are accurate
+ buf, err := f.reparse()
+ if err != nil {
+ return err
+ }
+
+ var patchlist parser.PatchList
+ for _, def := range f.tree.Defs {
+ mod, ok := def.(*parser.Module)
+ if !ok {
+ continue
+ }
+
+ err := modFunc(mod, buf, &patchlist)
+ if err != nil {
+ return err
+ }
+ }
+
+ newBuf := new(bytes.Buffer)
+ err = patchlist.Apply(bytes.NewReader(buf), newBuf)
+ if err != nil {
+ return err
+ }
+
+ // Save a copy of the buffer to print for errors below
+ bufCopy := append([]byte(nil), newBuf.Bytes()...)
+
+ newTree, err := parse(f.tree.Name, newBuf)
+ if err != nil {
+ return fmt.Errorf("Failed to parse: %v\nBuffer:\n%s", err, string(bufCopy))
+ }
+
+ f.tree = newTree
+
+ return nil
+ }
+}
+
+var commonPropertyPriorities = []string{
+ "name",
+ "defaults",
+ "device_supported",
+ "host_supported",
+ "installable",
+}
+
+func reorderCommonProperties(mod *parser.Module, buf []byte, patchlist *parser.PatchList) error {
+ if len(mod.Properties) == 0 {
+ return nil
+ }
+
+ pos := mod.LBracePos.Offset + 1
+ stage := ""
+
+ for _, name := range commonPropertyPriorities {
+ idx := propertyIndex(mod.Properties, name)
+ if idx == -1 {
+ continue
+ }
+ if idx == 0 {
+ err := patchlist.Add(pos, pos, stage)
+ if err != nil {
+ return err
+ }
+ stage = ""
+
+ pos = mod.Properties[0].End().Offset + 1
+ mod.Properties = mod.Properties[1:]
+ continue
+ }
+
+ prop := mod.Properties[idx]
+ mod.Properties = append(mod.Properties[:idx], mod.Properties[idx+1:]...)
+
+ stage += string(buf[prop.Pos().Offset : prop.End().Offset+1])
+
+ err := patchlist.Add(prop.Pos().Offset, prop.End().Offset+2, "")
if err != nil {
return err
}
}
- newBuf := new(bytes.Buffer)
- err = patchlist.Apply(bytes.NewReader(buf), newBuf)
- if err != nil {
- return err
+ if stage != "" {
+ err := patchlist.Add(pos, pos, stage)
+ if err != nil {
+ return err
+ }
}
- newTree, err := parse(f.tree.Name, newBuf)
- if err != nil {
- return err
- }
-
- f.tree = newTree
-
return nil
}
+func removeTags(mod *parser.Module, buf []byte, patchlist *parser.PatchList) error {
+ prop, ok := mod.GetProperty("tags")
+ if !ok {
+ return nil
+ }
+ list, ok := prop.Value.(*parser.List)
+ if !ok {
+ return nil
+ }
+
+ replaceStr := ""
+
+ for _, item := range list.Values {
+ str, ok := item.(*parser.String)
+ if !ok {
+ replaceStr += fmt.Sprintf("// ERROR: Unable to parse tag %q\n", item)
+ continue
+ }
+
+ switch str.Value {
+ case "optional":
+ continue
+ case "debug":
+ replaceStr += `// WARNING: Module tags are not supported in Soong.
+ // Add this module to PRODUCT_PACKAGES_DEBUG in your product file if you want to
+ // force installation for -userdebug and -eng builds.
+ `
+ case "eng":
+ replaceStr += `// WARNING: Module tags are not supported in Soong.
+ // Add this module to PRODUCT_PACKAGES_ENG in your product file if you want to
+ // force installation for -eng builds.
+ `
+ case "tests":
+ switch {
+ case strings.Contains(mod.Type, "cc_test"),
+ strings.Contains(mod.Type, "cc_library_static"),
+ strings.Contains(mod.Type, "java_test"),
+ mod.Type == "android_test":
+ continue
+ case strings.Contains(mod.Type, "cc_lib"):
+ replaceStr += `// WARNING: Module tags are not supported in Soong.
+ // To make a shared library only for tests, use the "cc_test_library" module
+ // type. If you don't use gtest, set "gtest: false".
+ `
+ case strings.Contains(mod.Type, "cc_bin"):
+ replaceStr += `// WARNING: Module tags are not supported in Soong.
+ // For native test binaries, use the "cc_test" module type. Some differences:
+ // - If you don't use gtest, set "gtest: false"
+ // - Binaries will be installed into /data/nativetest[64]/<name>/<name>
+ // - Both 32 & 64 bit versions will be built (as appropriate)
+ `
+ case strings.Contains(mod.Type, "java_lib"):
+ replaceStr += `// WARNING: Module tags are not supported in Soong.
+ // For JUnit or similar tests, use the "java_test" module type. A dependency on
+ // Junit will be added by default, if it is using some other runner, set "junit: false".
+ `
+ case mod.Type == "android_app":
+ replaceStr += `// WARNING: Module tags are not supported in Soong.
+ // For JUnit or instrumentataion app tests, use the "android_test" module type.
+ `
+ default:
+ replaceStr += `// WARNING: Module tags are not supported in Soong.
+ // In most cases, tests are now identified by their module type:
+ // cc_test, java_test, python_test
+ `
+ }
+ default:
+ replaceStr += fmt.Sprintf("// WARNING: Unknown module tag %q\n", str.Value)
+ }
+ }
+
+ return patchlist.Add(prop.Pos().Offset, prop.End().Offset+2, replaceStr)
+}
+
+func mergeMatchingModuleProperties(mod *parser.Module, buf []byte, patchlist *parser.PatchList) error {
+ return mergeMatchingProperties(&mod.Properties, buf, patchlist)
+}
+
func mergeMatchingProperties(properties *[]*parser.Property, buf []byte, patchlist *parser.PatchList) error {
seen := make(map[string]*parser.Property)
for i := 0; i < len(*properties); i++ {
@@ -354,7 +600,7 @@
}
// removes from <items> every item present in <removals>
-func filterExpressionList(items *parser.List, removals *parser.List) {
+func filterExpressionList(patchList *parser.PatchList, items *parser.List, removals *parser.List) {
writeIndex := 0
for _, item := range items.Values {
included := true
@@ -371,28 +617,39 @@
if included {
items.Values[writeIndex] = item
writeIndex++
+ } else {
+ patchList.Add(item.Pos().Offset, item.End().Offset+2, "")
}
}
items.Values = items.Values[:writeIndex]
}
// Remove each modules[i].Properties[<legacyName>][j] that matches a modules[i].Properties[<canonicalName>][k]
-func (f *Fixer) removeMatchingModuleListProperties(canonicalName string, legacyName string) error {
- for _, def := range f.tree.Defs {
- mod, ok := def.(*parser.Module)
- if !ok {
- continue
- }
- legacyList, ok := getLiteralListProperty(mod, legacyName)
- if !ok {
- continue
- }
- canonicalList, ok := getLiteralListProperty(mod, canonicalName)
- if !ok {
- continue
- }
- filterExpressionList(legacyList, canonicalList)
+func removeMatchingModuleListProperties(mod *parser.Module, patchList *parser.PatchList, canonicalName string, legacyName string) error {
+ legacyProp, ok := mod.GetProperty(legacyName)
+ if !ok {
+ return nil
}
+ legacyList, ok := legacyProp.Value.(*parser.List)
+ if !ok || len(legacyList.Values) == 0 {
+ return nil
+ }
+ canonicalList, ok := getLiteralListProperty(mod, canonicalName)
+ if !ok {
+ return nil
+ }
+
+ localPatches := parser.PatchList{}
+ filterExpressionList(&localPatches, legacyList, canonicalList)
+
+ if len(legacyList.Values) == 0 {
+ patchList.Add(legacyProp.Pos().Offset, legacyProp.End().Offset+2, "")
+ } else {
+ for _, p := range localPatches {
+ patchList.Add(p.Start, p.End, p.Replacement)
+ }
+ }
+
return nil
}
@@ -401,6 +658,11 @@
return found && len(list.Values) > 0
}
+func hasNonEmptyLiteralStringProperty(mod *parser.Module, name string) bool {
+ s, found := getLiteralStringPropertyValue(mod, name)
+ return found && len(s) > 0
+}
+
func getLiteralListProperty(mod *parser.Module, name string) (list *parser.List, found bool) {
prop, ok := mod.GetProperty(name)
if !ok {
@@ -410,6 +672,49 @@
return list, ok
}
+func getLiteralListPropertyValue(mod *parser.Module, name string) (list []string, found bool) {
+ listValue, ok := getLiteralListProperty(mod, name)
+ if !ok {
+ return nil, false
+ }
+ for _, v := range listValue.Values {
+ stringValue, ok := v.(*parser.String)
+ if !ok {
+ return nil, false
+ }
+ list = append(list, stringValue.Value)
+ }
+
+ return list, true
+}
+
+func getLiteralStringProperty(mod *parser.Module, name string) (s *parser.String, found bool) {
+ prop, ok := mod.GetProperty(name)
+ if !ok {
+ return nil, false
+ }
+ s, ok = prop.Value.(*parser.String)
+ return s, ok
+}
+
+func getLiteralStringPropertyValue(mod *parser.Module, name string) (s string, found bool) {
+ stringValue, ok := getLiteralStringProperty(mod, name)
+ if !ok {
+ return "", false
+ }
+
+ return stringValue.Value, true
+}
+
+func propertyIndex(props []*parser.Property, propertyName string) int {
+ for i, prop := range props {
+ if prop.Name == propertyName {
+ return i
+ }
+ }
+ return -1
+}
+
func renameProperty(mod *parser.Module, from, to string) {
for _, prop := range mod.Properties {
if prop.Name == from {
diff --git a/bpfix/bpfix/bpfix_test.go b/bpfix/bpfix/bpfix_test.go
index 51708eb..16dfce0 100644
--- a/bpfix/bpfix/bpfix_test.go
+++ b/bpfix/bpfix/bpfix_test.go
@@ -66,27 +66,31 @@
fixer := NewFixer(tree)
// apply simplifications
- err := fixer.simplifyKnownPropertiesDuplicatingEachOther()
+ err := runPatchListMod(simplifyKnownPropertiesDuplicatingEachOther)(fixer)
if len(errs) > 0 {
t.Fatal(err)
}
// lookup legacy property
mod := fixer.tree.Defs[0].(*parser.Module)
- _, found := mod.GetProperty("local_include_dirs")
- if !found {
- t.Fatalf("failed to include key local_include_dirs in parse tree")
+
+ expectedResultString := fmt.Sprintf("%q", expectedResult)
+ if expectedResult == nil {
+ expectedResultString = "unset"
}
// check that the value for the legacy property was updated to the correct value
errorHeader := fmt.Sprintf("\nFailed to correctly simplify key 'local_include_dirs' in the presence of 'export_include_dirs.'\n"+
"original local_include_dirs: %q\n"+
"original export_include_dirs: %q\n"+
- "expected result: %q\n"+
+ "expected result: %s\n"+
"actual result: ",
- local_include_dirs, export_include_dirs, expectedResult)
- result, ok := mod.GetProperty("local_include_dirs")
- if !ok {
+ local_include_dirs, export_include_dirs, expectedResultString)
+ result, found := mod.GetProperty("local_include_dirs")
+ if !found {
+ if expectedResult == nil {
+ return
+ }
t.Fatal(errorHeader + "property not found")
}
@@ -95,6 +99,10 @@
t.Fatalf("%sproperty is not a list: %v", errorHeader, listResult)
}
+ if expectedResult == nil {
+ t.Fatalf("%sproperty exists: %v", errorHeader, listResult)
+ }
+
actualExpressions := listResult.Values
actualValues := make([]string, 0)
for _, expr := range actualExpressions {
@@ -109,7 +117,7 @@
func TestSimplifyKnownVariablesDuplicatingEachOther(t *testing.T) {
// TODO use []Expression{} once buildTree above can support it (which is after b/38325146 is done)
- implFilterListTest(t, []string{"include"}, []string{"include"}, []string{})
+ implFilterListTest(t, []string{"include"}, []string{"include"}, nil)
implFilterListTest(t, []string{"include1"}, []string{"include2"}, []string{"include1"})
implFilterListTest(t, []string{"include1", "include2", "include3", "include4"}, []string{"include2"},
[]string{"include1", "include3", "include4"})
@@ -117,6 +125,49 @@
implFilterListTest(t, []string{}, []string{}, []string{})
}
+func runPass(t *testing.T, in, out string, innerTest func(*Fixer) error) {
+ expected, err := Reformat(out)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ in, err = Reformat(in)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ tree, errs := parser.Parse("<testcase>", bytes.NewBufferString(in), parser.NewScope(nil))
+ if errs != nil {
+ t.Fatal(errs)
+ }
+
+ fixer := NewFixer(tree)
+
+ got := ""
+ prev := "foo"
+ passes := 0
+ for got != prev && passes < 10 {
+ err := innerTest(fixer)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ out, err := parser.Print(fixer.tree)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ prev = got
+ got = string(out)
+ passes++
+ }
+
+ if got != expected {
+ t.Errorf("output didn't match:\ninput:\n%s\n\nexpected:\n%s\ngot:\n%s\n",
+ in, expected, got)
+ }
+}
+
func TestMergeMatchingProperties(t *testing.T) {
tests := []struct {
name string
@@ -199,47 +250,308 @@
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
- expected, err := Reformat(test.out)
- if err != nil {
- t.Error(err)
- }
+ runPass(t, test.in, test.out, func(fixer *Fixer) error {
+ return runPatchListMod(mergeMatchingModuleProperties)(fixer)
+ })
+ })
+ }
+}
- in, err := Reformat(test.in)
- if err != nil {
- t.Error(err)
- }
-
- tree, errs := parser.Parse("<testcase>", bytes.NewBufferString(in), parser.NewScope(nil))
- if errs != nil {
- t.Fatal(errs)
- }
-
- fixer := NewFixer(tree)
-
- got := ""
- prev := "foo"
- passes := 0
- for got != prev && passes < 10 {
- err := fixer.mergeMatchingModuleProperties()
- if err != nil {
- t.Fatal(err)
+func TestReorderCommonProperties(t *testing.T) {
+ var tests = []struct {
+ name string
+ in string
+ out string
+ }{
+ {
+ name: "empty",
+ in: `cc_library {}`,
+ out: `cc_library {}`,
+ },
+ {
+ name: "only priority",
+ in: `
+ cc_library {
+ name: "foo",
}
-
- out, err := parser.Print(fixer.tree)
- if err != nil {
- t.Fatal(err)
+ `,
+ out: `
+ cc_library {
+ name: "foo",
}
+ `,
+ },
+ {
+ name: "already in order",
+ in: `
+ cc_library {
+ name: "foo",
+ defaults: ["bar"],
+ }
+ `,
+ out: `
+ cc_library {
+ name: "foo",
+ defaults: ["bar"],
+ }
+ `,
+ },
+ {
+ name: "reorder only priority",
+ in: `
+ cc_library {
+ defaults: ["bar"],
+ name: "foo",
+ }
+ `,
+ out: `
+ cc_library {
+ name: "foo",
+ defaults: ["bar"],
+ }
+ `,
+ },
+ {
+ name: "reorder",
+ in: `
+ cc_library {
+ name: "foo",
+ srcs: ["a.c"],
+ host_supported: true,
+ defaults: ["bar"],
+ shared_libs: ["baz"],
+ }
+ `,
+ out: `
+ cc_library {
+ name: "foo",
+ defaults: ["bar"],
+ host_supported: true,
+ srcs: ["a.c"],
+ shared_libs: ["baz"],
+ }
+ `,
+ },
+ }
- prev = got
- got = string(out)
- passes++
- }
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ runPass(t, test.in, test.out, func(fixer *Fixer) error {
+ return runPatchListMod(reorderCommonProperties)(fixer)
+ })
+ })
+ }
+}
- if got != expected {
- t.Errorf("failed testcase '%s'\ninput:\n%s\n\nexpected:\n%s\ngot:\n%s\n",
- test.name, in, expected, got)
- }
+func TestRemoveMatchingModuleListProperties(t *testing.T) {
+ var tests = []struct {
+ name string
+ in string
+ out string
+ }{
+ {
+ name: "simple",
+ in: `
+ cc_library {
+ name: "foo",
+ foo: ["a"],
+ bar: ["a"],
+ }
+ `,
+ out: `
+ cc_library {
+ name: "foo",
+ bar: ["a"],
+ }
+ `,
+ },
+ {
+ name: "long",
+ in: `
+ cc_library {
+ name: "foo",
+ foo: [
+ "a",
+ "b",
+ ],
+ bar: ["a"],
+ }
+ `,
+ out: `
+ cc_library {
+ name: "foo",
+ foo: [
+ "b",
+ ],
+ bar: ["a"],
+ }
+ `,
+ },
+ {
+ name: "long fully removed",
+ in: `
+ cc_library {
+ name: "foo",
+ foo: [
+ "a",
+ ],
+ bar: ["a"],
+ }
+ `,
+ out: `
+ cc_library {
+ name: "foo",
+ bar: ["a"],
+ }
+ `,
+ },
+ {
+ name: "comment",
+ in: `
+ cc_library {
+ name: "foo",
+ // comment
+ foo: ["a"],
+
+ bar: ["a"],
+ }
+ `,
+ out: `
+ cc_library {
+ name: "foo",
+
+ // comment
+
+ bar: ["a"],
+ }
+ `,
+ },
+ {
+ name: "inner comment",
+ in: `
+ cc_library {
+ name: "foo",
+ foo: [
+ // comment
+ "a",
+ ],
+ bar: ["a"],
+ }
+ `,
+ out: `
+ cc_library {
+ name: "foo",
+ bar: ["a"],
+ }
+ `,
+ },
+ {
+ name: "eol comment",
+ in: `
+ cc_library {
+ name: "foo",
+ foo: ["a"], // comment
+ bar: ["a"],
+ }
+ `,
+ out: `
+ cc_library {
+ name: "foo",
+ // comment
+ bar: ["a"],
+ }
+ `,
+ },
+ {
+ name: "eol comment with blank lines",
+ in: `
+ cc_library {
+ name: "foo",
+
+ foo: ["a"], // comment
+
+ // bar
+ bar: ["a"],
+ }
+ `,
+ out: `
+ cc_library {
+ name: "foo",
+
+ // comment
+
+ // bar
+ bar: ["a"],
+ }
+ `,
+ },
+ }
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ runPass(t, test.in, test.out, func(fixer *Fixer) error {
+ return runPatchListMod(func(mod *parser.Module, buf []byte, patchList *parser.PatchList) error {
+ return removeMatchingModuleListProperties(mod, patchList, "bar", "foo")
+ })(fixer)
+ })
+ })
+ }
+}
+
+func TestReplaceJavaStaticLibs(t *testing.T) {
+ tests := []struct {
+ name string
+ in string
+ out string
+ }{
+ {
+ name: "static lib",
+ in: `
+ java_library_static {
+ name: "foo",
+ }
+ `,
+ out: `
+ java_library {
+ name: "foo",
+ }
+ `,
+ },
+ {
+ name: "java lib",
+ in: `
+ java_library {
+ name: "foo",
+ }
+ `,
+ out: `
+ java_library {
+ name: "foo",
+ }
+ `,
+ },
+ {
+ name: "java installable lib",
+ in: `
+ java_library {
+ name: "foo",
+ installable: true,
+ }
+ `,
+ out: `
+ java_library {
+ name: "foo",
+ installable: true,
+ }
+ `,
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ runPass(t, test.in, test.out, func(fixer *Fixer) error {
+ return rewriteJavaStaticLibs(fixer)
+ })
})
}
}
diff --git a/bpfix/cmd/bpfix.go b/bpfix/cmd/bpfix.go
index 2fde383..ccdae16 100644
--- a/bpfix/cmd/bpfix.go
+++ b/bpfix/cmd/bpfix.go
@@ -65,7 +65,7 @@
if err != nil {
return err
}
- r := bytes.NewBuffer(src)
+ r := bytes.NewBuffer(append([]byte(nil), src...))
file, errs := parser.Parse(filename, r, parser.NewScope(nil))
if len(errs) > 0 {
for _, err := range errs {
diff --git a/cc/androidmk.go b/cc/androidmk.go
index cdd4a5a..263f0f3 100644
--- a/cc/androidmk.go
+++ b/cc/androidmk.go
@@ -24,7 +24,8 @@
)
var (
- vendorSuffix = ".vendor"
+ vendorSuffix = ".vendor"
+ recoverySuffix = ".recovery"
)
type AndroidMkContext interface {
@@ -70,7 +71,7 @@
fmt.Fprintln(w, "LOCAL_SHARED_LIBRARIES := "+strings.Join(c.Properties.AndroidMkSharedLibs, " "))
}
if c.Target().Os == android.Android &&
- String(c.Properties.Sdk_version) != "" && !c.useVndk() {
+ String(c.Properties.Sdk_version) != "" && !c.useVndk() && !c.inRecovery() {
fmt.Fprintln(w, "LOCAL_SDK_VERSION := "+String(c.Properties.Sdk_version))
fmt.Fprintln(w, "LOCAL_NDK_STL_VARIANT := none")
} else {
@@ -99,6 +100,8 @@
// .vendor suffix is added only when we will have two variants: core and vendor.
// The suffix is not added for vendor-only module.
ret.SubName += vendorSuffix
+ } else if c.inRecovery() && !c.onlyInRecovery() {
+ ret.SubName += recoverySuffix
}
return ret
@@ -345,7 +348,7 @@
func (c *llndkStubDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
ret.Class = "SHARED_LIBRARIES"
- ret.SubName = ".vendor"
+ ret.SubName = vendorSuffix
ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
c.libraryDecorator.androidMkWriteExportedFlags(w)
diff --git a/cc/binary.go b/cc/binary.go
index 9e7b70b..82e1941 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -41,9 +41,6 @@
// extension (if any) appended
Symlinks []string `android:"arch_variant"`
- // do not pass -pie
- No_pie *bool `android:"arch_variant"`
-
DynamicLinker string `blueprint:"mutated"`
// Names of modules to be overridden. Listed modules can only be other binaries
@@ -216,9 +213,6 @@
if ctx.Host() && !binary.static() {
if !ctx.Config().IsEnvTrue("DISABLE_HOST_PIE") {
flags.LdFlags = append(flags.LdFlags, "-pie")
- if ctx.Windows() {
- flags.LdFlags = append(flags.LdFlags, "-Wl,-e_mainCRTStartup")
- }
}
}
@@ -318,6 +312,10 @@
builderFlags := flagsToBuilderFlags(flags)
if binary.stripper.needsStrip(ctx) {
+ // b/80093681, GNU strip/objcopy bug.
+ // Use llvm-{strip,objcopy} when clang lld is used.
+ builderFlags.stripUseLlvmStrip =
+ flags.Clang && binary.baseLinker.useClangLld(ctx)
strippedOutputFile := outputFile
outputFile = android.PathForModuleOut(ctx, "unstripped", fileName)
binary.stripper.strip(ctx, outputFile, strippedOutputFile, builderFlags)
diff --git a/cc/builder.go b/cc/builder.go
index cb09d09..51d3195 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -117,7 +117,7 @@
blueprint.RuleParams{
Depfile: "${out}.d",
Deps: blueprint.DepsGCC,
- Command: "CROSS_COMPILE=$crossCompile XZ=$xzCmd $stripPath ${args} -i ${in} -o ${out} -d ${out}.d",
+ Command: "CROSS_COMPILE=$crossCompile XZ=$xzCmd CLANG_BIN=${config.ClangBin} $stripPath ${args} -i ${in} -o ${out} -d ${out}.d",
CommandDeps: []string{"$stripPath", "$xzCmd"},
},
"args", "crossCompile")
@@ -200,8 +200,8 @@
sAbiDiff = pctx.AndroidRuleFunc("sAbiDiff",
func(ctx android.PackageRuleContext) blueprint.RuleParams {
-
- commandStr := "($sAbiDiffer $allowFlags -lib $libName -arch $arch -check-all-apis -o ${out} -new $in -old $referenceDump)"
+ // TODO(b/78139997): Add -check-all-apis back
+ commandStr := "($sAbiDiffer $allowFlags -lib $libName -arch $arch -o ${out} -new $in -old $referenceDump)"
distAbiDiffDir := android.PathForDist(ctx, "abidiffs")
commandStr += "|| (echo ' ---- Please update abi references by running $$ANDROID_BUILD_TOP/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l ${libName} ----'"
if distAbiDiffDir.Valid() {
@@ -266,6 +266,7 @@
stripKeepSymbols bool
stripKeepMiniDebugInfo bool
stripAddGnuDebuglink bool
+ stripUseLlvmStrip bool
}
type Objects struct {
@@ -725,11 +726,14 @@
baseName, exportedHeaderFlags string, isVndkExt bool) android.OptionalPath {
outputFile := android.PathForModuleOut(ctx, baseName+".abidiff")
-
+ libName := strings.TrimSuffix(baseName, filepath.Ext(baseName))
localAbiCheckAllowFlags := append([]string(nil), abiCheckAllowFlags...)
if exportedHeaderFlags == "" {
localAbiCheckAllowFlags = append(localAbiCheckAllowFlags, "-advice-only")
}
+ if inList(libName, llndkLibraries) {
+ localAbiCheckAllowFlags = append(localAbiCheckAllowFlags, "-consider-opaque-types-different")
+ }
if isVndkExt {
localAbiCheckAllowFlags = append(localAbiCheckAllowFlags, "-allow-extensions")
}
@@ -742,7 +746,7 @@
Implicit: referenceDump,
Args: map[string]string{
"referenceDump": referenceDump.String(),
- "libName": baseName[0:(len(baseName) - len(filepath.Ext(baseName)))],
+ "libName": libName,
"arch": ctx.Arch().ArchType.Name,
"allowFlags": strings.Join(localAbiCheckAllowFlags, " "),
},
@@ -822,6 +826,9 @@
if flags.stripKeepSymbols {
args += " --keep-symbols"
}
+ if flags.stripUseLlvmStrip {
+ args += " --use-llvm-strip"
+ }
ctx.Build(pctx, android.BuildParams{
Rule: strip,
diff --git a/cc/cc.go b/cc/cc.go
index 76e6645..89d45a9 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -34,7 +34,7 @@
android.RegisterModuleType("cc_defaults", defaultsFactory)
android.PreDepsMutators(func(ctx android.RegisterMutatorsContext) {
- ctx.BottomUp("image", vendorMutator).Parallel()
+ ctx.BottomUp("image", imageMutator).Parallel()
ctx.BottomUp("link", linkageMutator).Parallel()
ctx.BottomUp("vndk", vndkMutator).Parallel()
ctx.BottomUp("ndk_api", ndkApiMutator).Parallel()
@@ -52,7 +52,7 @@
ctx.TopDown("tsan_deps", sanitizerDepsMutator(tsan))
ctx.BottomUp("tsan", sanitizerMutator(tsan)).Parallel()
- ctx.TopDown("sanitize_runtime_deps", sanitizerRuntimeDepsMutator())
+ ctx.TopDown("sanitize_runtime_deps", sanitizerRuntimeDepsMutator)
ctx.BottomUp("coverage", coverageLinkingMutator).Parallel()
ctx.TopDown("vndk_deps", sabiDepsMutator)
@@ -175,6 +175,11 @@
// *.logtags files, to combine together in order to generate the /system/etc/event-log-tags
// file
Logtags []string
+
+ // Make this module available when building for recovery
+ Recovery_available *bool
+
+ InRecovery bool `blueprint:"mutated"`
}
type VendorProperties struct {
@@ -206,10 +211,6 @@
Double_loadable *bool
}
-type UnusedProperties struct {
- Tags []string
-}
-
type ModuleContextIntf interface {
static() bool
staticBinary() bool
@@ -221,7 +222,8 @@
isVndk() bool
isVndkSp() bool
isVndkExt() bool
- createVndkSourceAbiDump() bool
+ inRecovery() bool
+ shouldCreateVndkSourceAbiDump() bool
selectedStl() string
baseModuleName() string
getVndkExtendsModuleName() string
@@ -320,7 +322,6 @@
Properties BaseProperties
VendorProperties VendorProperties
- unused UnusedProperties
// initialize before calling Init
hod android.HostOrDeviceSupported
@@ -360,7 +361,7 @@
}
func (c *Module) Init() android.Module {
- c.AddProperties(&c.Properties, &c.VendorProperties, &c.unused)
+ c.AddProperties(&c.Properties, &c.VendorProperties)
if c.compiler != nil {
c.AddProperties(c.compiler.compilerProps()...)
}
@@ -458,6 +459,14 @@
return c.isVndk() || Bool(c.VendorProperties.Vendor_available)
}
+func (c *Module) inRecovery() bool {
+ return c.Properties.InRecovery || c.ModuleBase.InstallInRecovery()
+}
+
+func (c *Module) onlyInRecovery() bool {
+ return c.ModuleBase.InstallInRecovery()
+}
+
type baseModuleContext struct {
android.BaseContext
moduleContextImpl
@@ -505,7 +514,7 @@
}
func (ctx *moduleContextImpl) useSdk() bool {
- if ctx.ctx.Device() && !ctx.useVndk() {
+ if ctx.ctx.Device() && !ctx.useVndk() && !ctx.inRecovery() {
return String(ctx.mod.Properties.Sdk_version) != ""
}
return false
@@ -549,16 +558,33 @@
return ctx.mod.isVndkExt()
}
-// Create source abi dumps if the module belongs to the list of VndkLibraries.
-func (ctx *moduleContextImpl) createVndkSourceAbiDump() bool {
- skipAbiChecks := ctx.ctx.Config().IsEnvTrue("SKIP_ABI_CHECKS")
- isUnsanitizedVariant := true
- sanitize := ctx.mod.sanitize
- if sanitize != nil {
- isUnsanitizedVariant = sanitize.isUnsanitizedVariant()
+func (ctx *moduleContextImpl) inRecovery() bool {
+ return ctx.mod.inRecovery()
+}
+
+// Check whether ABI dumps should be created for this module.
+func (ctx *moduleContextImpl) shouldCreateVndkSourceAbiDump() bool {
+ if ctx.ctx.Config().IsEnvTrue("SKIP_ABI_CHECKS") {
+ return false
}
- vendorAvailable := Bool(ctx.mod.VendorProperties.Vendor_available)
- return !skipAbiChecks && isUnsanitizedVariant && ctx.ctx.Device() && ((ctx.useVndk() && ctx.isVndk() && vendorAvailable) || inList(ctx.baseModuleName(), llndkLibraries))
+ if sanitize := ctx.mod.sanitize; sanitize != nil {
+ if !sanitize.isVariantOnProductionDevice() {
+ return false
+ }
+ }
+ if !ctx.ctx.Device() {
+ // Host modules do not need ABI dumps.
+ return false
+ }
+ if inList(ctx.baseModuleName(), llndkLibraries) {
+ return true
+ }
+ if ctx.useVndk() && ctx.isVndk() {
+ // Return true if this is VNDK-core, VNDK-SP, or VNDK-Ext and this is not
+ // VNDK-private.
+ return Bool(ctx.mod.VendorProperties.Vendor_available) || ctx.isVndkExt()
+ }
+ return false
}
func (ctx *moduleContextImpl) selectedStl() string {
@@ -1074,6 +1100,10 @@
// Platform code can link to anything
return
}
+ if from.inRecovery() {
+ // Recovery code is not NDK
+ return
+ }
if _, ok := to.linker.(*toolchainLibraryDecorator); ok {
// These are always allowed
return
@@ -1185,8 +1215,6 @@
if ccDep == nil {
// handling for a few module types that aren't cc Module but that are also supported
switch depTag {
- case android.DefaultsDepTag, android.SourceDepTag:
- // Nothing to do
case genSourceDepTag:
if genRule, ok := dep.(genrule.SourceFileGenerator); ok {
depPaths.GeneratedSources = append(depPaths.GeneratedSources,
@@ -1224,8 +1252,6 @@
} else {
ctx.ModuleErrorf("module %q is not a genrule", depName)
}
- default:
- ctx.ModuleErrorf("depends on non-cc module %q", depName)
}
return
}
@@ -1365,6 +1391,8 @@
return libName + vendorSuffix
} else if (ctx.Platform() || ctx.ProductSpecific()) && isVendorPublicLib {
return libName + vendorPublicLibrarySuffix
+ } else if ccDep.inRecovery() && !ccDep.onlyInRecovery() {
+ return libName + recoverySuffix
} else {
return libName
}
@@ -1416,6 +1444,10 @@
return c.installer.inSanitizerDir()
}
+func (c *Module) InstallInRecovery() bool {
+ return c.inRecovery()
+}
+
func (c *Module) HostToolPath() android.OptionalPath {
if c.installer == nil {
return android.OptionalPath{}
@@ -1470,12 +1502,12 @@
&VendorProperties{},
&BaseCompilerProperties{},
&BaseLinkerProperties{},
+ &MoreBaseLinkerProperties{},
&LibraryProperties{},
&FlagExporterProperties{},
&BinaryLinkerProperties{},
&TestProperties{},
&TestBinaryProperties{},
- &UnusedProperties{},
&StlProperties{},
&SanitizeProperties{},
&StripProperties{},
@@ -1502,6 +1534,8 @@
// vendorMode is the variant used for /vendor code that compiles
// against the VNDK.
vendorMode = "vendor"
+
+ recoveryMode = "recovery"
)
func squashVendorSrcs(m *Module) {
@@ -1514,22 +1548,59 @@
}
}
-func vendorMutator(mctx android.BottomUpMutatorContext) {
+func squashRecoverySrcs(m *Module) {
+ if lib, ok := m.compiler.(*libraryDecorator); ok {
+ lib.baseCompiler.Properties.Srcs = append(lib.baseCompiler.Properties.Srcs,
+ lib.baseCompiler.Properties.Target.Recovery.Srcs...)
+
+ lib.baseCompiler.Properties.Exclude_srcs = append(lib.baseCompiler.Properties.Exclude_srcs,
+ lib.baseCompiler.Properties.Target.Recovery.Exclude_srcs...)
+ }
+}
+
+func imageMutator(mctx android.BottomUpMutatorContext) {
if mctx.Os() != android.Android {
return
}
if genrule, ok := mctx.Module().(*genrule.Module); ok {
- if props, ok := genrule.Extra.(*VendorProperties); ok {
+ if props, ok := genrule.Extra.(*GenruleExtraProperties); ok {
+ var coreVariantNeeded bool = false
+ var vendorVariantNeeded bool = false
+ var recoveryVariantNeeded bool = false
if mctx.DeviceConfig().VndkVersion() == "" {
- mctx.CreateVariations(coreMode)
+ coreVariantNeeded = true
} else if Bool(props.Vendor_available) {
- mctx.CreateVariations(coreMode, vendorMode)
+ coreVariantNeeded = true
+ vendorVariantNeeded = true
} else if mctx.SocSpecific() || mctx.DeviceSpecific() {
- mctx.CreateVariations(vendorMode)
+ vendorVariantNeeded = true
} else {
- mctx.CreateVariations(coreMode)
+ coreVariantNeeded = true
}
+ if Bool(props.Recovery_available) {
+ recoveryVariantNeeded = true
+ }
+
+ if recoveryVariantNeeded {
+ primaryArch := mctx.Config().DevicePrimaryArchType()
+ moduleArch := genrule.Target().Arch.ArchType
+ if moduleArch != primaryArch {
+ recoveryVariantNeeded = false
+ }
+ }
+
+ var variants []string
+ if coreVariantNeeded {
+ variants = append(variants, coreMode)
+ }
+ if vendorVariantNeeded {
+ variants = append(variants, vendorMode)
+ }
+ if recoveryVariantNeeded {
+ variants = append(variants, recoveryMode)
+ }
+ mctx.CreateVariations(variants...)
}
}
@@ -1583,43 +1654,78 @@
}
}
+ var coreVariantNeeded bool = false
+ var vendorVariantNeeded bool = false
+ var recoveryVariantNeeded bool = false
+
if mctx.DeviceConfig().VndkVersion() == "" {
// If the device isn't compiling against the VNDK, we always
// use the core mode.
- mctx.CreateVariations(coreMode)
+ coreVariantNeeded = true
} 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)
+ vendorVariantNeeded = true
} else if _, ok := m.linker.(*llndkHeadersDecorator); ok {
// ... and LL-NDK headers as well
- mod := mctx.CreateVariations(vendorMode)
- vendor := mod[0].(*Module)
- vendor.Properties.UseVndk = true
+ vendorVariantNeeded = true
} else if _, ok := m.linker.(*vndkPrebuiltLibraryDecorator); ok {
// Make vendor variants only for the versions in BOARD_VNDK_VERSION and
// PRODUCT_EXTRA_VNDK_VERSIONS.
- mod := mctx.CreateVariations(vendorMode)
- vendor := mod[0].(*Module)
- vendor.Properties.UseVndk = true
+ vendorVariantNeeded = true
} else if m.hasVendorVariant() && !vendorSpecific {
// This will be available in both /system and /vendor
// or a /system directory that is available to vendor.
- mod := mctx.CreateVariations(coreMode, vendorMode)
- vendor := mod[1].(*Module)
- vendor.Properties.UseVndk = true
- squashVendorSrcs(vendor)
+ coreVariantNeeded = true
+ vendorVariantNeeded = true
} else if vendorSpecific && String(m.Properties.Sdk_version) == "" {
// This will be available in /vendor (or /odm) only
- mod := mctx.CreateVariations(vendorMode)
- vendor := mod[0].(*Module)
- vendor.Properties.UseVndk = true
- squashVendorSrcs(vendor)
+ vendorVariantNeeded = 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)
+ coreVariantNeeded = true
+ }
+
+ if Bool(m.Properties.Recovery_available) {
+ recoveryVariantNeeded = true
+ }
+
+ if m.ModuleBase.InstallInRecovery() {
+ recoveryVariantNeeded = true
+ coreVariantNeeded = false
+ }
+
+ if recoveryVariantNeeded {
+ primaryArch := mctx.Config().DevicePrimaryArchType()
+ moduleArch := m.Target().Arch.ArchType
+ if moduleArch != primaryArch {
+ recoveryVariantNeeded = false
+ }
+ }
+
+ var variants []string
+ if coreVariantNeeded {
+ variants = append(variants, coreMode)
+ }
+ if vendorVariantNeeded {
+ variants = append(variants, vendorMode)
+ }
+ if recoveryVariantNeeded {
+ variants = append(variants, recoveryMode)
+ }
+ mod := mctx.CreateVariations(variants...)
+ for i, v := range variants {
+ if v == vendorMode {
+ m := mod[i].(*Module)
+ m.Properties.UseVndk = true
+ squashVendorSrcs(m)
+ } else if v == recoveryMode {
+ m := mod[i].(*Module)
+ m.Properties.InRecovery = true
+ squashRecoverySrcs(m)
+ }
}
}
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 4f26827..3d162e7 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -63,7 +63,7 @@
ctx.RegisterModuleType("cc_object", android.ModuleFactoryAdaptor(objectFactory))
ctx.RegisterModuleType("filegroup", android.ModuleFactoryAdaptor(android.FileGroupFactory))
ctx.PreDepsMutators(func(ctx android.RegisterMutatorsContext) {
- ctx.BottomUp("image", vendorMutator).Parallel()
+ ctx.BottomUp("image", imageMutator).Parallel()
ctx.BottomUp("link", linkageMutator).Parallel()
ctx.BottomUp("vndk", vndkMutator).Parallel()
ctx.BottomUp("begin", beginMutator).Parallel()
@@ -75,16 +75,19 @@
toolchain_library {
name: "libatomic",
vendor_available: true,
+ recovery_available: true,
}
toolchain_library {
name: "libcompiler_rt-extras",
vendor_available: true,
+ recovery_available: true,
}
toolchain_library {
name: "libgcc",
vendor_available: true,
+ recovery_available: true,
}
cc_library {
@@ -92,6 +95,7 @@
no_libgcc: true,
nocrt: true,
system_shared_libs: [],
+ recovery_available: true,
}
llndk_library {
name: "libc",
@@ -102,6 +106,7 @@
no_libgcc: true,
nocrt: true,
system_shared_libs: [],
+ recovery_available: true,
}
llndk_library {
name: "libm",
@@ -112,6 +117,7 @@
no_libgcc: true,
nocrt: true,
system_shared_libs: [],
+ recovery_available: true,
}
llndk_library {
name: "libdl",
@@ -124,6 +130,7 @@
system_shared_libs: [],
stl: "none",
vendor_available: true,
+ recovery_available: true,
}
cc_library {
name: "libc++",
@@ -132,6 +139,7 @@
system_shared_libs: [],
stl: "none",
vendor_available: true,
+ recovery_available: true,
vndk: {
enabled: true,
support_system_process: true,
@@ -144,14 +152,17 @@
system_shared_libs: [],
stl: "none",
vendor_available: true,
+ recovery_available: true,
}
cc_object {
name: "crtbegin_so",
+ recovery_available: true,
}
cc_object {
name: "crtend_so",
+ recovery_available: true,
}
cc_library {
@@ -1651,3 +1662,28 @@
}
}
+
+func TestRecovery(t *testing.T) {
+ ctx := testCc(t, `
+ cc_library_shared {
+ name: "librecovery",
+ recovery: true,
+ }
+ cc_library_shared {
+ name: "librecovery32",
+ recovery: true,
+ compile_multilib:"32",
+ }
+ `)
+
+ variants := ctx.ModuleVariantsForTests("librecovery")
+ const arm64 = "android_arm64_armv8-a_recovery_shared"
+ if len(variants) != 1 || !android.InList(arm64, variants) {
+ t.Errorf("variants of librecovery must be \"%s\" only, but was %#v", arm64, variants)
+ }
+
+ variants = ctx.ModuleVariantsForTests("librecovery32")
+ if android.InList(arm64, variants) {
+ t.Errorf("multilib was set to 32 for librecovery32, but its variants has %s.", arm64)
+ }
+}
diff --git a/cc/cmakelists.go b/cc/cmakelists.go
index c25578e..a2f46cd 100644
--- a/cc/cmakelists.go
+++ b/cc/cmakelists.go
@@ -62,10 +62,14 @@
outputDebugInfo = (getEnvVariable(envVariableGenerateDebugInfo, ctx) == envVariableTrue)
+ // Track which projects have already had CMakeLists.txt generated to keep the first
+ // variant for each project.
+ seenProjects := map[string]bool{}
+
ctx.VisitAllModules(func(module android.Module) {
if ccModule, ok := module.(*Module); ok {
if compiledModule, ok := ccModule.compiler.(CompiledInterface); ok {
- generateCLionProject(compiledModule, ctx, ccModule)
+ generateCLionProject(compiledModule, ctx, ccModule, seenProjects)
}
}
})
@@ -114,14 +118,22 @@
return nil
}
-func generateCLionProject(compiledModule CompiledInterface, ctx android.SingletonContext, ccModule *Module) {
+func generateCLionProject(compiledModule CompiledInterface, ctx android.SingletonContext, ccModule *Module,
+ seenProjects map[string]bool) {
srcs := compiledModule.Srcs()
if len(srcs) == 0 {
return
}
- // Ensure the directory hosting the cmakelists.txt exists
+ // Only write CMakeLists.txt for the first variant of each architecture of each module
clionproject_location := getCMakeListsForModule(ccModule, ctx)
+ if seenProjects[clionproject_location] {
+ return
+ }
+
+ seenProjects[clionproject_location] = true
+
+ // Ensure the directory hosting the cmakelists.txt exists
projectDir := path.Dir(clionproject_location)
os.MkdirAll(projectDir, os.ModePerm)
diff --git a/cc/compiler.go b/cc/compiler.go
index 2ba19f1..8d034c9 100644
--- a/cc/compiler.go
+++ b/cc/compiler.go
@@ -142,6 +142,19 @@
// variant of the C/C++ module.
Cflags []string
}
+ Recovery struct {
+ // list of source files that should only be used in the
+ // recovery variant of the C/C++ module.
+ Srcs []string
+
+ // list of source files that should not be used to
+ // build the recovery variant of the C/C++ module.
+ Exclude_srcs []string
+
+ // List of additional cflags that should be used to build the recovery
+ // variant of the C/C++ module.
+ Cflags []string
+ }
}
Proto struct {
@@ -245,6 +258,8 @@
CheckBadCompilerFlags(ctx, "cppflags", compiler.Properties.Cppflags)
CheckBadCompilerFlags(ctx, "conlyflags", compiler.Properties.Conlyflags)
CheckBadCompilerFlags(ctx, "asflags", compiler.Properties.Asflags)
+ CheckBadCompilerFlags(ctx, "vendor.cflags", compiler.Properties.Target.Vendor.Cflags)
+ CheckBadCompilerFlags(ctx, "recovery.cflags", compiler.Properties.Target.Recovery.Cflags)
esc := proptools.NinjaAndShellEscape
@@ -317,6 +332,10 @@
"-D__ANDROID_API__="+version, "-D__ANDROID_VNDK__")
}
+ if ctx.inRecovery() {
+ flags.GlobalFlags = append(flags.GlobalFlags, "-D__ANDROID_RECOVERY__")
+ }
+
instructionSet := String(compiler.Properties.Instruction_set)
if flags.RequiredInstructionSet != "" {
instructionSet = flags.RequiredInstructionSet
@@ -377,6 +396,12 @@
fmt.Sprintf("${config.%sGlobalCflags}", hod))
}
+ if flags.Clang {
+ if strings.HasPrefix(android.PathForModuleSrc(ctx).String(), "external/") {
+ flags.GlobalFlags = append([]string{"${config.ClangExternalCflags}"}, flags.GlobalFlags...)
+ }
+ }
+
if ctx.Device() {
if Bool(compiler.Properties.Rtti) {
flags.CppFlags = append(flags.CppFlags, "-frtti")
@@ -441,6 +466,10 @@
flags.CFlags = append(flags.CFlags, esc(compiler.Properties.Target.Vendor.Cflags)...)
}
+ if ctx.inRecovery() {
+ flags.CFlags = append(flags.CFlags, esc(compiler.Properties.Target.Recovery.Cflags)...)
+ }
+
// We can enforce some rules more strictly in the code we own. strict
// indicates if this is code that we can be stricter with. If we have
// rules that we want to apply to *our* code (but maybe can't for
diff --git a/cc/config/arm64_device.go b/cc/config/arm64_device.go
index 74c936f..172784a 100644
--- a/cc/config/arm64_device.go
+++ b/cc/config/arm64_device.go
@@ -27,10 +27,18 @@
"-Werror=implicit-function-declaration",
}
+ arm64ArchVariantCflags = map[string][]string{
+ "armv8-a": []string{
+ "-march=armv8-a",
+ },
+ "armv8-2a": []string{
+ "-march=armv8.2a",
+ },
+ }
+
arm64Ldflags = []string{
"-Wl,-m,aarch64_elf64_le_vec",
"-Wl,--hash-style=gnu",
- "-Wl,--fix-cortex-a53-843419",
"-fuse-ld=gold",
"-Wl,--icf=safe",
}
@@ -44,6 +52,14 @@
"cortex-a53": []string{
"-mcpu=cortex-a53",
},
+ "cortex-a55": []string{
+ "-mcpu=cortex-a55",
+ },
+ "cortex-a75": []string{
+ // Use the cortex-a55 since it is similar to the little
+ // core (cortex-a55) and is sensitive to ordering.
+ "-mcpu=cortex-a55",
+ },
"kryo": []string{
// Use the cortex-a57 cpu since some compilers
// don't support a Kryo specific target yet.
@@ -67,8 +83,11 @@
func init() {
android.RegisterArchVariants(android.Arm64,
"armv8_a",
+ "armv8_2a",
"cortex-a53",
+ "cortex-a55",
"cortex-a73",
+ "cortex-a75",
"kryo",
"exynos-m1",
"exynos-m2",
@@ -93,11 +112,19 @@
pctx.StaticVariable("Arm64ClangLldflags", strings.Join(ClangFilterUnknownCflags(arm64Lldflags), " "))
pctx.StaticVariable("Arm64ClangCppflags", strings.Join(ClangFilterUnknownCflags(arm64Cppflags), " "))
+ pctx.StaticVariable("Arm64ClangArmv8ACflags", strings.Join(arm64ArchVariantCflags["armv8-a"], " "))
+ pctx.StaticVariable("Arm64ClangArmv82ACflags", strings.Join(arm64ArchVariantCflags["armv8-2a"], " "))
+
pctx.StaticVariable("Arm64CortexA53Cflags",
strings.Join(arm64CpuVariantCflags["cortex-a53"], " "))
pctx.StaticVariable("Arm64ClangCortexA53Cflags",
strings.Join(arm64ClangCpuVariantCflags["cortex-a53"], " "))
+ pctx.StaticVariable("Arm64CortexA55Cflags",
+ strings.Join(arm64CpuVariantCflags["cortex-a55"], " "))
+ pctx.StaticVariable("Arm64ClangCortexA55Cflags",
+ strings.Join(arm64ClangCpuVariantCflags["cortex-a55"], " "))
+
pctx.StaticVariable("Arm64KryoCflags",
strings.Join(arm64CpuVariantCflags["kryo"], " "))
pctx.StaticVariable("Arm64ClangKryoCflags",
@@ -118,16 +145,25 @@
arm64CpuVariantCflagsVar = map[string]string{
"": "",
"cortex-a53": "${config.Arm64CortexA53Cflags}",
+ "cortex-a55": "${config.Arm64CortexA55Cflags}",
"cortex-a73": "${config.Arm64CortexA53Cflags}",
+ "cortex-a75": "${config.Arm64CortexA55Cflags}",
"kryo": "${config.Arm64KryoCflags}",
"exynos-m1": "${config.Arm64ExynosM1Cflags}",
"exynos-m2": "${config.Arm64ExynosM2Cflags}",
}
+ arm64ClangArchVariantCflagsVar = map[string]string{
+ "armv8-a": "${config.Arm64ClangArmv8ACflags}",
+ "armv8-2a": "${config.Arm64ClangArmv82ACflags}",
+ }
+
arm64ClangCpuVariantCflagsVar = map[string]string{
"": "",
"cortex-a53": "${config.Arm64ClangCortexA53Cflags}",
+ "cortex-a55": "${config.Arm64ClangCortexA55Cflags}",
"cortex-a73": "${config.Arm64ClangCortexA53Cflags}",
+ "cortex-a75": "${config.Arm64ClangCortexA55Cflags}",
"kryo": "${config.Arm64ClangKryoCflags}",
"exynos-m1": "${config.Arm64ClangExynosM1Cflags}",
"exynos-m2": "${config.Arm64ClangExynosM2Cflags}",
@@ -137,6 +173,8 @@
type toolchainArm64 struct {
toolchain64Bit
+ ldflags string
+ lldflags string
toolchainCflags string
toolchainClangCflags string
}
@@ -170,7 +208,7 @@
}
func (t *toolchainArm64) Ldflags() string {
- return "${config.Arm64Ldflags}"
+ return t.ldflags
}
func (t *toolchainArm64) IncludeFlags() string {
@@ -190,11 +228,11 @@
}
func (t *toolchainArm64) ClangLdflags() string {
- return "${config.Arm64Ldflags}"
+ return t.ldflags
}
func (t *toolchainArm64) ClangLldflags() string {
- return "${config.Arm64Lldflags}"
+ return t.lldflags
}
func (t *toolchainArm64) ToolchainClangCflags() string {
@@ -206,13 +244,38 @@
}
func arm64ToolchainFactory(arch android.Arch) Toolchain {
- if arch.ArchVariant != "armv8-a" {
+ switch arch.ArchVariant {
+ case "armv8-a":
+ case "armv8-2a":
+ // Nothing extra for armv8-a/armv8-2a
+ default:
panic(fmt.Sprintf("Unknown ARM architecture version: %q", arch.ArchVariant))
}
+ toolchainClangCflags := []string{arm64ClangArchVariantCflagsVar[arch.ArchVariant]}
+ toolchainClangCflags = append(toolchainClangCflags,
+ variantOrDefault(arm64ClangCpuVariantCflagsVar, arch.CpuVariant))
+
+ var extraLdflags string
+ switch arch.CpuVariant {
+ case "cortex-a53", "cortex-a73", "kryo", "exynos-m1", "exynos-m2",
+ // This variant might not need the workaround but leave it
+ // in the list since it has had the workaround on before.
+ "denver64":
+ extraLdflags = "-Wl,--fix-cortex-a53-843419"
+ }
+
return &toolchainArm64{
+ ldflags: strings.Join([]string{
+ "${config.Arm64Ldflags}",
+ extraLdflags,
+ }, " "),
+ lldflags: strings.Join([]string{
+ "${config.Arm64Lldflags}",
+ extraLdflags,
+ }, " "),
toolchainCflags: variantOrDefault(arm64CpuVariantCflagsVar, arch.CpuVariant),
- toolchainClangCflags: variantOrDefault(arm64ClangCpuVariantCflagsVar, arch.CpuVariant),
+ toolchainClangCflags: strings.Join(toolchainClangCflags, " "),
}
}
diff --git a/cc/config/arm_device.go b/cc/config/arm_device.go
index 02bd9eb..4719fb7 100644
--- a/cc/config/arm_device.go
+++ b/cc/config/arm_device.go
@@ -99,6 +99,24 @@
// better solution comes around. See Bug 27340895
"-D__ARM_FEATURE_LPAE=1",
},
+ "cortex-a55": []string{
+ "-mcpu=cortex-a55",
+ "-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
+ // better solution comes around. See Bug 27340895
+ "-D__ARM_FEATURE_LPAE=1",
+ },
+ "cortex-a75": []string{
+ "-mcpu=cortex-a55",
+ "-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
+ // better solution comes around. See Bug 27340895
+ "-D__ARM_FEATURE_LPAE=1",
+ },
"krait": []string{
"-mcpu=cortex-a15",
"-mfpu=neon-vfpv4",
@@ -143,7 +161,9 @@
"cortex-a15",
"cortex-a53",
"cortex-a53-a57",
+ "cortex-a55",
"cortex-a73",
+ "cortex-a75",
"krait",
"kryo",
"exynos-m1",
@@ -192,6 +212,7 @@
pctx.StaticVariable("ArmCortexA8Cflags", strings.Join(armCpuVariantCflags["cortex-a8"], " "))
pctx.StaticVariable("ArmCortexA15Cflags", strings.Join(armCpuVariantCflags["cortex-a15"], " "))
pctx.StaticVariable("ArmCortexA53Cflags", strings.Join(armCpuVariantCflags["cortex-a53"], " "))
+ pctx.StaticVariable("ArmCortexA55Cflags", strings.Join(armCpuVariantCflags["cortex-a55"], " "))
pctx.StaticVariable("ArmKraitCflags", strings.Join(armCpuVariantCflags["krait"], " "))
pctx.StaticVariable("ArmKryoCflags", strings.Join(armCpuVariantCflags["kryo"], " "))
@@ -225,6 +246,8 @@
strings.Join(armClangCpuVariantCflags["cortex-a15"], " "))
pctx.StaticVariable("ArmClangCortexA53Cflags",
strings.Join(armClangCpuVariantCflags["cortex-a53"], " "))
+ pctx.StaticVariable("ArmClangCortexA55Cflags",
+ strings.Join(armClangCpuVariantCflags["cortex-a55"], " "))
pctx.StaticVariable("ArmClangKraitCflags",
strings.Join(armClangCpuVariantCflags["krait"], " "))
pctx.StaticVariable("ArmClangKryoCflags",
@@ -245,7 +268,9 @@
"cortex-a15": "${config.ArmCortexA15Cflags}",
"cortex-a53": "${config.ArmCortexA53Cflags}",
"cortex-a53.a57": "${config.ArmCortexA53Cflags}",
+ "cortex-a55": "${config.ArmCortexA55Cflags}",
"cortex-a73": "${config.ArmCortexA53Cflags}",
+ "cortex-a75": "${config.ArmCortexA55Cflags}",
"krait": "${config.ArmKraitCflags}",
"kryo": "${config.ArmKryoCflags}",
"exynos-m1": "${config.ArmCortexA53Cflags}",
@@ -266,7 +291,9 @@
"cortex-a15": "${config.ArmClangCortexA15Cflags}",
"cortex-a53": "${config.ArmClangCortexA53Cflags}",
"cortex-a53.a57": "${config.ArmClangCortexA53Cflags}",
+ "cortex-a55": "${config.ArmClangCortexA55Cflags}",
"cortex-a73": "${config.ArmClangCortexA53Cflags}",
+ "cortex-a75": "${config.ArmClangCortexA55Cflags}",
"krait": "${config.ArmClangKraitCflags}",
"kryo": "${config.ArmClangKryoCflags}",
"exynos-m1": "${config.ArmClangCortexA53Cflags}",
diff --git a/cc/config/clang.go b/cc/config/clang.go
index ba1cd3c..36afc68 100644
--- a/cc/config/clang.go
+++ b/cc/config/clang.go
@@ -173,15 +173,17 @@
// this new warning are fixed.
"-Wno-null-pointer-arithmetic",
- // http://b/72330874 Disable -Wenum-compare until the instances detected by this new
- // warning are fixed.
- "-Wno-enum-compare",
- "-Wno-enum-compare-switch",
-
// Disable c++98-specific warning since Android is not concerned with C++98
// compatibility.
"-Wno-c++98-compat-extra-semi",
}, " "))
+
+ // Extra cflags for projects under external/ directory to disable warnings that are infeasible
+ // to fix in all the external projects and their upstream repos.
+ pctx.StaticVariable("ClangExtraExternalCflags", strings.Join([]string{
+ "-Wno-enum-compare",
+ "-Wno-enum-compare-switch",
+ }, " "))
}
func ClangFilterUnknownCflags(cflags []string) []string {
diff --git a/cc/config/global.go b/cc/config/global.go
index 06f6f9a..dee7640 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -89,6 +89,9 @@
deviceGlobalLldflags = append(ClangFilterUnknownLldflags(deviceGlobalLdflags),
[]string{
+ // TODO(b/109657296): needs --no-rosegment until Android
+ // stack unwinder can handle the read-only segment.
+ "-Wl,--no-rosegment",
"-Wl,--pack-dyn-relocs=android",
"-fuse-ld=lld",
}...)
@@ -170,6 +173,8 @@
pctx.StaticVariable("CommonClangGlobalCppflags",
strings.Join(append(ClangFilterUnknownCflags(commonGlobalCppflags), "${ClangExtraCppflags}"), " "))
+ pctx.StaticVariable("ClangExternalCflags", "${ClangExtraExternalCflags}")
+
// Everything in these lists is a crime against abstraction and dependency tracking.
// Do not add anything to this list.
pctx.PrefixedExistentPathsForSourcesVariable("CommonGlobalIncludes", "-I",
diff --git a/cc/config/tidy.go b/cc/config/tidy.go
index a20d556..5d53a8c 100644
--- a/cc/config/tidy.go
+++ b/cc/config/tidy.go
@@ -19,6 +19,13 @@
"strings"
)
+// clang-tidy doesn't recognize every flag that clang does. This is unlikely to
+// be a complete list, but we can populate this with the ones we know to avoid
+// issues with clang-diagnostic-unused-command-line-argument.
+var ClangTidyUnknownCflags = sorted([]string{
+ "-Wa,%",
+})
+
func init() {
// Most Android source files are not clang-tidy clean yet.
// Global tidy checks include only google*, performance*,
diff --git a/cc/config/toolchain.go b/cc/config/toolchain.go
index ca863a7..ea8397d 100644
--- a/cc/config/toolchain.go
+++ b/cc/config/toolchain.go
@@ -243,6 +243,10 @@
return SanitizerRuntimeLibrary(t, "profile")
}
+func ScudoRuntimeLibrary(t Toolchain) string {
+ return SanitizerRuntimeLibrary(t, "scudo")
+}
+
func ToolPath(t Toolchain) string {
if p := t.ToolPath(); p != "" {
return p
diff --git a/cc/config/x86_windows_host.go b/cc/config/x86_windows_host.go
index 6fbff9f..4cb8fa4 100644
--- a/cc/config/x86_windows_host.go
+++ b/cc/config/x86_windows_host.go
@@ -45,7 +45,6 @@
windowsIncludeFlags = []string{
"-isystem ${WindowsGccRoot}/${WindowsGccTriple}/include",
- "-isystem ${WindowsGccRoot}/lib/gcc/${WindowsGccTriple}/4.8.3/include",
}
windowsClangCppflags = []string{
@@ -79,22 +78,34 @@
"-m32",
"-Wl,--large-address-aware",
"-L${WindowsGccRoot}/${WindowsGccTriple}/lib32",
+ "-static-libgcc",
}
windowsX86ClangLdflags = append(ClangFilterUnknownCflags(windowsX86Ldflags), []string{
+ "-B${WindowsGccRoot}/${WindowsGccTriple}/bin",
"-B${WindowsGccRoot}/lib/gcc/${WindowsGccTriple}/4.8.3/32",
"-L${WindowsGccRoot}/lib/gcc/${WindowsGccTriple}/4.8.3/32",
"-B${WindowsGccRoot}/${WindowsGccTriple}/lib32",
+ "-pthread",
+ // Bug: http://b/109759970 - WAR until issue with ld.bfd's
+ // inability to handle Clang-generated section names is fixed.
+ "-Wl,--allow-multiple-definition",
}...)
windowsX86ClangLldflags = ClangFilterUnknownLldflags(windowsX86ClangLdflags)
windowsX8664Ldflags = []string{
"-m64",
"-L${WindowsGccRoot}/${WindowsGccTriple}/lib64",
+ "-static-libgcc",
}
windowsX8664ClangLdflags = append(ClangFilterUnknownCflags(windowsX8664Ldflags), []string{
+ "-B${WindowsGccRoot}/${WindowsGccTriple}/bin",
"-B${WindowsGccRoot}/lib/gcc/${WindowsGccTriple}/4.8.3",
"-L${WindowsGccRoot}/lib/gcc/${WindowsGccTriple}/4.8.3",
"-B${WindowsGccRoot}/${WindowsGccTriple}/lib64",
+ "-pthread",
+ // Bug: http://b/109759970 - WAR until issue with ld.bfd's
+ // inability to handle Clang-generated section names is fixed.
+ "-Wl,--allow-multiple-definition",
}...)
windowsX8664ClangLldflags = ClangFilterUnknownLldflags(windowsX8664ClangLdflags)
@@ -220,7 +231,7 @@
}
func (t *toolchainWindows) ClangSupported() bool {
- return false
+ return true
}
func (t *toolchainWindowsX86) ClangTriple() string {
diff --git a/cc/gen.go b/cc/gen.go
index f22a783..487f662 100644
--- a/cc/gen.go
+++ b/cc/gen.go
@@ -25,7 +25,7 @@
)
func init() {
- pctx.SourcePathVariable("lexCmd", "prebuilts/misc/${config.HostPrebuiltTag}/flex/flex-2.5.39")
+ pctx.SourcePathVariable("lexCmd", "prebuilts/build-tools/${config.HostPrebuiltTag}/bin/flex")
pctx.SourcePathVariable("yaccCmd", "prebuilts/build-tools/${config.HostPrebuiltTag}/bin/bison")
pctx.SourcePathVariable("yaccDataDir", "prebuilts/build-tools/common/bison")
@@ -48,7 +48,7 @@
aidl = pctx.AndroidStaticRule("aidl",
blueprint.RuleParams{
- Command: "$aidlCmd -d${out}.d -ninja $aidlFlags $in $outDir $out",
+ Command: "$aidlCmd -d${out}.d --ninja $aidlFlags $in $outDir $out",
CommandDeps: []string{"$aidlCmd"},
Depfile: "${out}.d",
Deps: blueprint.DepsGCC,
diff --git a/cc/genrule.go b/cc/genrule.go
index 51c0d16..a672992 100644
--- a/cc/genrule.go
+++ b/cc/genrule.go
@@ -23,13 +23,18 @@
android.RegisterModuleType("cc_genrule", genRuleFactory)
}
+type GenruleExtraProperties struct {
+ Vendor_available *bool
+ Recovery_available *bool
+}
+
// cc_genrule is a genrule that can depend on other cc_* objects.
// The cmd may be run multiple times, once for each of the different arch/etc
// variations.
func genRuleFactory() android.Module {
module := genrule.NewGenRule()
- module.Extra = &VendorProperties{}
+ module.Extra = &GenruleExtraProperties{}
module.AddProperties(module.Extra)
android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibBoth)
diff --git a/cc/libbuildversion/Android.bp b/cc/libbuildversion/Android.bp
index fd563e6..825b920 100644
--- a/cc/libbuildversion/Android.bp
+++ b/cc/libbuildversion/Android.bp
@@ -1,6 +1,7 @@
cc_library_static {
name: "libbuildversion",
host_supported: true,
+ recovery_available: true,
srcs: ["libbuildversion.cpp"],
export_include_dirs: ["include"],
cflags: ["-fvisibility=hidden"],
diff --git a/cc/library.go b/cc/library.go
index b31fee2..e92cf9d 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -362,7 +362,7 @@
}
return Objects{}
}
- if ctx.createVndkSourceAbiDump() || library.sabi.Properties.CreateSAbiDumps {
+ if ctx.shouldCreateVndkSourceAbiDump() || library.sabi.Properties.CreateSAbiDumps {
exportIncludeDirs := library.flagExporter.exportedIncludes(ctx)
var SourceAbiFlags []string
for _, dir := range exportIncludeDirs.Strings() {
@@ -477,6 +477,11 @@
deps.SharedLibs = removeListFromList(deps.SharedLibs, library.baseLinker.Properties.Target.Vendor.Exclude_shared_libs)
deps.StaticLibs = removeListFromList(deps.StaticLibs, library.baseLinker.Properties.Target.Vendor.Exclude_static_libs)
}
+ if ctx.inRecovery() {
+ deps.WholeStaticLibs = removeListFromList(deps.WholeStaticLibs, library.baseLinker.Properties.Target.Recovery.Exclude_static_libs)
+ deps.SharedLibs = removeListFromList(deps.SharedLibs, library.baseLinker.Properties.Target.Recovery.Exclude_shared_libs)
+ deps.StaticLibs = removeListFromList(deps.StaticLibs, library.baseLinker.Properties.Target.Recovery.Exclude_static_libs)
+ }
android.ExtractSourceDeps(ctx, library.Properties.Version_script)
android.ExtractSourceDeps(ctx, library.Properties.Unexported_symbols_list)
@@ -588,6 +593,10 @@
}
if library.stripper.needsStrip(ctx) {
+ // b/80093681, GNU strip/objcopy bug.
+ // Use llvm-{strip,objcopy} when clang lld is used.
+ builderFlags.stripUseLlvmStrip =
+ flags.Clang && library.baseLinker.useClangLld(ctx)
strippedOutputFile := outputFile
outputFile = android.PathForModuleOut(ctx, "unstripped", fileName)
library.stripper.strip(ctx, outputFile, strippedOutputFile, builderFlags)
@@ -623,14 +632,12 @@
}
func (library *libraryDecorator) linkSAbiDumpFiles(ctx ModuleContext, objs Objects, fileName string, soFile android.Path) {
- //Also take into account object re-use.
- if len(objs.sAbiDumpFiles) > 0 && ctx.createVndkSourceAbiDump() {
+ if len(objs.sAbiDumpFiles) > 0 && ctx.shouldCreateVndkSourceAbiDump() {
vndkVersion := ctx.DeviceConfig().PlatformVndkVersion()
if ver := ctx.DeviceConfig().VndkVersion(); ver != "" && ver != "current" {
vndkVersion = ver
}
- refSourceDumpFile := android.PathForVndkRefAbiDump(ctx, vndkVersion, fileName, vndkVsNdk(ctx), true)
exportIncludeDirs := library.flagExporter.exportedIncludes(ctx)
var SourceAbiFlags []string
for _, dir := range exportIncludeDirs.Strings() {
@@ -641,6 +648,8 @@
}
exportedHeaderFlags := strings.Join(SourceAbiFlags, " ")
library.sAbiOutputFile = TransformDumpToLinkedDump(ctx, objs.sAbiDumpFiles, soFile, fileName, exportedHeaderFlags)
+
+ refSourceDumpFile := android.PathForVndkRefAbiDump(ctx, vndkVersion, fileName, vndkVsNdk(ctx), true)
if refSourceDumpFile.Valid() {
unzippedRefDump := UnzipRefDump(ctx, refSourceDumpFile.Path(), fileName)
library.sAbiDiff = SourceAbiDiff(ctx, library.sAbiOutputFile.Path(),
@@ -745,7 +754,7 @@
}
if Bool(library.Properties.Static_ndk_lib) && library.static() &&
- !ctx.useVndk() && ctx.Device() &&
+ !ctx.useVndk() && !ctx.inRecovery() && ctx.Device() &&
library.sanitize.isUnsanitizedVariant() {
installPath := getNdkSysrootBase(ctx).Join(
ctx, "usr/lib", config.NDKTriple(ctx.toolchain()), file.Base())
diff --git a/cc/linker.go b/cc/linker.go
index 71da09e..6bbf015 100644
--- a/cc/linker.go
+++ b/cc/linker.go
@@ -107,12 +107,28 @@
// variant of the C/C++ module.
Exclude_runtime_libs []string
}
+ Recovery struct {
+ // list of shared libs that should not be used to build
+ // the recovery variant of the C/C++ module.
+ Exclude_shared_libs []string
+
+ // list of static libs that should not be used to build
+ // the recovery variant of the C/C++ module.
+ Exclude_static_libs []string
+ }
}
// make android::build:GetBuildNumber() available containing the build ID.
Use_version_lib *bool `android:"arch_variant"`
}
+// TODO(http://b/80437643): BaseLinkerProperties is getting too big,
+// more than 2^16 bytes. New properties are defined in MoreBaseLinkerProperties.
+type MoreBaseLinkerProperties struct {
+ // Generate compact dynamic relocation table, default true.
+ Pack_relocations *bool `android:"arch_variant"`
+}
+
func NewBaseLinker() *baseLinker {
return &baseLinker{}
}
@@ -120,6 +136,7 @@
// baseLinker provides support for shared_libs, static_libs, and whole_static_libs properties
type baseLinker struct {
Properties BaseLinkerProperties
+ MoreProperties MoreBaseLinkerProperties
dynamicProperties struct {
RunPaths []string `blueprint:"mutated"`
}
@@ -138,7 +155,7 @@
}
func (linker *baseLinker) linkerProps() []interface{} {
- return []interface{}{&linker.Properties, &linker.dynamicProperties}
+ return []interface{}{&linker.Properties, &linker.MoreProperties, &linker.dynamicProperties}
}
func (linker *baseLinker) linkerDeps(ctx BaseModuleContext, deps Deps) Deps {
@@ -166,6 +183,14 @@
deps.RuntimeLibs = removeListFromList(deps.RuntimeLibs, linker.Properties.Target.Vendor.Exclude_runtime_libs)
}
+ if ctx.inRecovery() {
+ deps.SharedLibs = removeListFromList(deps.SharedLibs, linker.Properties.Target.Recovery.Exclude_shared_libs)
+ deps.ReexportSharedLibHeaders = removeListFromList(deps.ReexportSharedLibHeaders, linker.Properties.Target.Recovery.Exclude_shared_libs)
+ deps.StaticLibs = removeListFromList(deps.StaticLibs, linker.Properties.Target.Recovery.Exclude_static_libs)
+ deps.ReexportStaticLibHeaders = removeListFromList(deps.ReexportStaticLibHeaders, linker.Properties.Target.Recovery.Exclude_static_libs)
+ deps.WholeStaticLibs = removeListFromList(deps.WholeStaticLibs, linker.Properties.Target.Recovery.Exclude_static_libs)
+ }
+
if ctx.ModuleName() != "libcompiler_rt-extras" {
deps.LateStaticLibs = append(deps.LateStaticLibs, "libcompiler_rt-extras")
}
@@ -221,6 +246,10 @@
if ctx.Darwin() {
return false
}
+ // http://b/110800681 - lld cannot link Android's Windows modules yet.
+ if ctx.Windows() {
+ return false
+ }
if linker.Properties.Use_clang_lld != nil {
return Bool(linker.Properties.Use_clang_lld)
}
@@ -239,6 +268,9 @@
if flags.Clang && linker.useClangLld(ctx) {
flags.LdFlags = append(flags.LdFlags, fmt.Sprintf("${config.%sGlobalLldflags}", hod))
+ if !BoolDefault(linker.MoreProperties.Pack_relocations, true) {
+ flags.LdFlags = append(flags.LdFlags, "-Wl,--pack-dyn-relocs=none")
+ }
} else {
flags.LdFlags = append(flags.LdFlags, fmt.Sprintf("${config.%sGlobalLdflags}", hod))
}
diff --git a/cc/makevars.go b/cc/makevars.go
index bb81dcb..88d4639 100644
--- a/cc/makevars.go
+++ b/cc/makevars.go
@@ -72,7 +72,10 @@
ctx.Strict("CLANG_CXX", "${config.ClangBin}/clang++")
ctx.Strict("LLVM_AS", "${config.ClangBin}/llvm-as")
ctx.Strict("LLVM_LINK", "${config.ClangBin}/llvm-link")
+ ctx.Strict("LLVM_OBJCOPY", "${config.ClangBin}/llvm-objcopy")
+ ctx.Strict("LLVM_STRIP", "${config.ClangBin}/llvm-strip")
ctx.Strict("PATH_TO_CLANG_TIDY", "${config.ClangBin}/clang-tidy")
+ ctx.Strict("CLANG_TIDY_UNKNOWN_CFLAGS", strings.Join(config.ClangTidyUnknownCflags, " "))
ctx.StrictSorted("CLANG_CONFIG_UNKNOWN_CFLAGS", strings.Join(config.ClangUnknownCflags, " "))
ctx.Strict("RS_LLVM_PREBUILTS_VERSION", "${config.RSClangVersion}")
@@ -83,6 +86,7 @@
ctx.Strict("RS_LLVM_AS", "${config.RSLLVMPrebuiltsPath}/llvm-as")
ctx.Strict("RS_LLVM_LINK", "${config.RSLLVMPrebuiltsPath}/llvm-link")
+ ctx.Strict("CLANG_EXTERNAL_CFLAGS", "${config.ClangExternalCflags}")
ctx.Strict("GLOBAL_CFLAGS_NO_OVERRIDE", "${config.NoOverrideGlobalCflags}")
ctx.Strict("GLOBAL_CLANG_CFLAGS_NO_OVERRIDE", "${config.ClangExtraNoOverrideCflags}")
ctx.Strict("GLOBAL_CPPFLAGS_NO_OVERRIDE", "")
@@ -299,6 +303,7 @@
ctx.Strict(secondPrefix+"UBSAN_RUNTIME_LIBRARY", strings.TrimSuffix(config.UndefinedBehaviorSanitizerRuntimeLibrary(toolchain), ".so"))
ctx.Strict(secondPrefix+"UBSAN_MINIMAL_RUNTIME_LIBRARY", strings.TrimSuffix(config.UndefinedBehaviorSanitizerMinimalRuntimeLibrary(toolchain), ".a"))
ctx.Strict(secondPrefix+"TSAN_RUNTIME_LIBRARY", strings.TrimSuffix(config.ThreadSanitizerRuntimeLibrary(toolchain), ".so"))
+ ctx.Strict(secondPrefix+"SCUDO_RUNTIME_LIBRARY", strings.TrimSuffix(config.ScudoRuntimeLibrary(toolchain), ".so"))
}
// This is used by external/gentoo/...
@@ -315,10 +320,14 @@
if target.Os == android.Darwin {
ctx.Strict(makePrefix+"AR", "${config.MacArPath}")
+ ctx.Strict(makePrefix+"NM", "${config.MacToolPath}/nm")
+ ctx.Strict(makePrefix+"OTOOL", "${config.MacToolPath}/otool")
+ ctx.Strict(makePrefix+"STRIP", "${config.MacStripPath}")
} else {
ctx.Strict(makePrefix+"AR", "${config.ClangBin}/llvm-ar")
ctx.Strict(makePrefix+"READELF", gccCmd(toolchain, "readelf"))
ctx.Strict(makePrefix+"NM", gccCmd(toolchain, "nm"))
+ ctx.Strict(makePrefix+"STRIP", gccCmd(toolchain, "strip"))
}
if target.Os == android.Windows {
@@ -328,7 +337,6 @@
if target.Os.Class == android.Device {
ctx.Strict(makePrefix+"OBJCOPY", gccCmd(toolchain, "objcopy"))
ctx.Strict(makePrefix+"LD", gccCmd(toolchain, "ld"))
- ctx.Strict(makePrefix+"STRIP", gccCmd(toolchain, "strip"))
ctx.Strict(makePrefix+"GCC_VERSION", toolchain.GccVersion())
ctx.Strict(makePrefix+"NDK_GCC_VERSION", toolchain.GccVersion())
ctx.Strict(makePrefix+"NDK_TRIPLE", config.NDKTriple(toolchain))
diff --git a/cc/ndk_headers.go b/cc/ndk_headers.go
index 9fabc97..1cd4829 100644
--- a/cc/ndk_headers.go
+++ b/cc/ndk_headers.go
@@ -26,7 +26,7 @@
)
var (
- preprocessBionicHeaders = pctx.AndroidStaticRule("preprocessBionicHeaders",
+ versionBionicHeaders = pctx.AndroidStaticRule("versionBionicHeaders",
blueprint.RuleParams{
// The `&& touch $out` isn't really necessary, but Blueprint won't
// let us have only implicit outputs.
@@ -34,6 +34,13 @@
CommandDeps: []string{"$versionerCmd"},
},
"depsPath", "srcDir", "outDir")
+
+ preprocessNdkHeader = pctx.AndroidStaticRule("preprocessNdkHeader",
+ blueprint.RuleParams{
+ Command: "$preprocessor -o $out $in",
+ CommandDeps: []string{"$preprocessor"},
+ },
+ "preprocessor")
)
func init() {
@@ -45,7 +52,7 @@
return getNdkSysrootBase(ctx).Join(ctx, "usr/include")
}
-type headerProperies struct {
+type headerProperties struct {
// Base directory of the headers being installed. As an example:
//
// ndk_headers {
@@ -65,6 +72,9 @@
// List of headers to install. Glob compatible. Common case is "include/**/*.h".
Srcs []string
+ // Source paths that should be excluded from the srcs glob.
+ Exclude_srcs []string
+
// Path to the NOTICE file associated with the headers.
License *string
}
@@ -72,7 +82,7 @@
type headerModule struct {
android.ModuleBase
- properties headerProperies
+ properties headerProperties
installPaths android.Paths
licensePath android.ModuleSrcPath
@@ -128,7 +138,7 @@
return
}
- srcFiles := ctx.ExpandSources(m.properties.Srcs, nil)
+ srcFiles := ctx.ExpandSources(m.properties.Srcs, m.properties.Exclude_srcs)
for _, header := range srcFiles {
installDir := getHeaderInstallDir(ctx, header, String(m.properties.From),
String(m.properties.To))
@@ -154,10 +164,10 @@
return module
}
-type preprocessedHeaderProperies struct {
+type versionedHeaderProperties struct {
// Base directory of the headers being installed. As an example:
//
- // preprocessed_ndk_headers {
+ // versioned_ndk_headers {
// name: "foo",
// from: "include",
// to: "",
@@ -181,19 +191,19 @@
// 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 {
+type versionedHeaderModule struct {
android.ModuleBase
- properties preprocessedHeaderProperies
+ properties versionedHeaderProperties
installPaths android.Paths
licensePath android.ModuleSrcPath
}
-func (m *preprocessedHeaderModule) DepsMutator(ctx android.BottomUpMutatorContext) {
+func (m *versionedHeaderModule) DepsMutator(ctx android.BottomUpMutatorContext) {
}
-func (m *preprocessedHeaderModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+func (m *versionedHeaderModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
if String(m.properties.License) == "" {
ctx.PropertyErrorf("license", "field is required")
}
@@ -248,7 +258,7 @@
timestampFile := android.PathForModuleOut(ctx, "versioner.timestamp")
ctx.Build(pctx, android.BuildParams{
- Rule: preprocessBionicHeaders,
+ Rule: versionBionicHeaders,
Description: "versioner preprocess " + srcDir.Rel(),
Output: timestampFile,
Implicits: append(srcFiles, depsGlob...),
@@ -263,8 +273,92 @@
return timestampFile
}
+func versionedNdkHeadersFactory() android.Module {
+ module := &versionedHeaderModule{}
+
+ module.AddProperties(&module.properties)
+
+ // 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
+ // a host module.
+ android.InitAndroidArchModule(module, android.HostSupportedNoCross, android.MultilibFirst)
+
+ return module
+}
+
+// preprocessed_ndk_header {
+// name: "foo",
+// preprocessor: "foo.sh",
+// srcs: [...],
+// to: "android",
+// }
+//
+// Will invoke the preprocessor as:
+// $preprocessor -o $SYSROOT/usr/include/android/needs_preproc.h $src
+// For each src in srcs.
+type preprocessedHeadersProperties struct {
+ // The preprocessor to run. Must be a program inside the source directory
+ // with no dependencies.
+ Preprocessor *string
+
+ // Source path to the files to be preprocessed.
+ Srcs []string
+
+ // Source paths that should be excluded from the srcs glob.
+ Exclude_srcs []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
+}
+
+type preprocessedHeadersModule struct {
+ android.ModuleBase
+
+ properties preprocessedHeadersProperties
+
+ installPaths android.Paths
+ licensePath android.ModuleSrcPath
+}
+
+func (m *preprocessedHeadersModule) DepsMutator(ctx android.BottomUpMutatorContext) {
+}
+
+func (m *preprocessedHeadersModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+ if String(m.properties.License) == "" {
+ ctx.PropertyErrorf("license", "field is required")
+ }
+
+ preprocessor := android.PathForModuleSrc(ctx, String(m.properties.Preprocessor))
+ m.licensePath = android.PathForModuleSrc(ctx, String(m.properties.License))
+
+ srcFiles := ctx.ExpandSources(m.properties.Srcs, m.properties.Exclude_srcs)
+ installDir := getCurrentIncludePath(ctx).Join(ctx, String(m.properties.To))
+ for _, src := range srcFiles {
+ installPath := installDir.Join(ctx, src.Base())
+ m.installPaths = append(m.installPaths, installPath)
+
+ ctx.Build(pctx, android.BuildParams{
+ Rule: preprocessNdkHeader,
+ Description: "preprocess " + src.Rel(),
+ Input: src,
+ Output: installPath,
+ Args: map[string]string{
+ "preprocessor": preprocessor.String(),
+ },
+ })
+ }
+
+ if len(m.installPaths) == 0 {
+ ctx.ModuleErrorf("srcs %q matched zero files", m.properties.Srcs)
+ }
+}
+
func preprocessedNdkHeadersFactory() android.Module {
- module := &preprocessedHeaderModule{}
+ module := &preprocessedHeadersModule{}
module.AddProperties(&module.properties)
diff --git a/cc/ndk_sysroot.go b/cc/ndk_sysroot.go
index c7ba588..144fc09 100644
--- a/cc/ndk_sysroot.go
+++ b/cc/ndk_sysroot.go
@@ -59,6 +59,7 @@
func init() {
android.RegisterModuleType("ndk_headers", ndkHeadersFactory)
android.RegisterModuleType("ndk_library", ndkLibraryFactory)
+ android.RegisterModuleType("versioned_ndk_headers", versionedNdkHeadersFactory)
android.RegisterModuleType("preprocessed_ndk_headers", preprocessedNdkHeadersFactory)
android.RegisterSingletonType("ndk", NdkSingleton)
@@ -107,7 +108,12 @@
licensePaths = append(licensePaths, m.licensePath)
}
- if m, ok := module.(*preprocessedHeaderModule); ok {
+ if m, ok := module.(*versionedHeaderModule); ok {
+ installPaths = append(installPaths, m.installPaths...)
+ licensePaths = append(licensePaths, m.licensePath)
+ }
+
+ if m, ok := module.(*preprocessedHeadersModule); ok {
installPaths = append(installPaths, m.installPaths...)
licensePaths = append(licensePaths, m.licensePath)
}
diff --git a/cc/pgo.go b/cc/pgo.go
index d39e429..a341ab9 100644
--- a/cc/pgo.go
+++ b/cc/pgo.go
@@ -19,6 +19,8 @@
"path/filepath"
"strings"
+ "github.com/google/blueprint/proptools"
+
"android/soong/android"
"android/soong/cc/config"
)
@@ -160,13 +162,8 @@
return flags
}
- // Skip -fprofile-use if 'enable_profile_use' property is set
- if props.Pgo.Enable_profile_use != nil && *props.Pgo.Enable_profile_use == false {
- return flags
- }
-
- // If the profile file is found, add flags to use the profile
- if profileFile := props.getPgoProfileFile(ctx); profileFile.Valid() {
+ if props.PgoCompile {
+ profileFile := props.getPgoProfileFile(ctx)
profileFilePath := profileFile.Path()
profileUseFlags := props.profileUseFlags(ctx, profileFilePath.String())
@@ -257,7 +254,8 @@
}
}
- if !ctx.Config().IsEnvTrue("ANDROID_PGO_NO_PROFILE_USE") {
+ if !ctx.Config().IsEnvTrue("ANDROID_PGO_NO_PROFILE_USE") &&
+ proptools.BoolDefault(pgo.Properties.Pgo.Enable_profile_use, true) {
if profileFile := pgo.Properties.getPgoProfileFile(ctx); profileFile.Valid() {
pgo.Properties.PgoCompile = true
}
diff --git a/cc/proto.go b/cc/proto.go
index 22e50ab..6e6f95e 100644
--- a/cc/proto.go
+++ b/cc/proto.go
@@ -25,13 +25,17 @@
func init() {
pctx.HostBinToolVariable("protocCmd", "aprotoc")
+ pctx.HostBinToolVariable("depFixCmd", "dep_fixer")
}
var (
proto = pctx.AndroidStaticRule("protoc",
blueprint.RuleParams{
- Command: "$protocCmd --cpp_out=$protoOutParams:$outDir -I $protoBase $protoFlags $in",
- CommandDeps: []string{"$protocCmd"},
+ Command: "$protocCmd --cpp_out=$protoOutParams:$outDir --dependency_out=$out.d -I $protoBase $protoFlags $in && " +
+ `$depFixCmd $out.d`,
+ CommandDeps: []string{"$protocCmd", "$depFixCmd"},
+ Depfile: "${out}.d",
+ Deps: blueprint.DepsGCC,
}, "protoFlags", "protoOutParams", "protoBase", "outDir")
)
@@ -53,10 +57,11 @@
}
ctx.Build(pctx, android.BuildParams{
- Rule: proto,
- Description: "protoc " + protoFile.Rel(),
- Outputs: android.WritablePaths{ccFile, headerFile},
- Input: protoFile,
+ Rule: proto,
+ Description: "protoc " + protoFile.Rel(),
+ Output: ccFile,
+ ImplicitOutput: headerFile,
+ Input: protoFile,
Args: map[string]string{
"outDir": android.ProtoDir(ctx).String(),
"protoFlags": protoFlags,
diff --git a/cc/relocation_packer.go b/cc/relocation_packer.go
index f6a1fe4..8989b29 100644
--- a/cc/relocation_packer.go
+++ b/cc/relocation_packer.go
@@ -33,6 +33,7 @@
})
type RelocationPackerProperties struct {
+ // Generate compact dynamic relocation table, default true.
Pack_relocations *bool `android:"arch_variant"`
// This will be true even if we're embedded in Make, in which case
diff --git a/cc/sabi.go b/cc/sabi.go
index f5a7c77..42b2f35 100644
--- a/cc/sabi.go
+++ b/cc/sabi.go
@@ -74,8 +74,13 @@
// RSClang does not support recent mcpu option likes exynos-m2.
// So we need overriding mcpu option when we want to use it.
- if ctx.Arch().CpuVariant == "exynos-m2" {
- flags.ToolingCFlags = append(flags.ToolingCFlags, "-mcpu=cortex-a53")
+ mappedArch := map[string]string{
+ "exynos-m2": "cortex-a53",
+ "cortex-a55": "cortex-a53",
+ "cortex-a75": "cortex-a57",
+ }
+ if arch, ok := mappedArch[ctx.Arch().CpuVariant]; ok {
+ flags.ToolingCFlags = append(flags.ToolingCFlags, "-mcpu="+arch)
}
return flags
diff --git a/cc/sanitize.go b/cc/sanitize.go
index 859d876..4c8a611 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -21,6 +21,8 @@
"strings"
"sync"
+ "github.com/google/blueprint"
+
"android/soong/android"
"android/soong/cc/config"
)
@@ -94,6 +96,7 @@
Safestack *bool `android:"arch_variant"`
Cfi *bool `android:"arch_variant"`
Integer_overflow *bool `android:"arch_variant"`
+ Scudo *bool `android:"arch_variant"`
// Sanitizers to run in the diagnostic mode (as opposed to the release mode).
// Replaces abort() on error with a human-readable error message.
@@ -152,7 +155,9 @@
if ctx.clang() {
if ctx.Host() {
- globalSanitizers = ctx.Config().SanitizeHost()
+ if !ctx.Windows() {
+ globalSanitizers = ctx.Config().SanitizeHost()
+ }
} else {
arches := ctx.Config().SanitizeDeviceArch()
if len(arches) == 0 || inList(ctx.Arch().ArchType.Name, arches) {
@@ -207,6 +212,10 @@
}
}
+ if found, globalSanitizers = removeFromList("scudo", globalSanitizers); found && s.Scudo == nil {
+ s.Scudo = boolPtr(true)
+ }
+
if len(globalSanitizers) > 0 {
ctx.ModuleErrorf("unknown global sanitizer option %s", globalSanitizers[0])
}
@@ -281,10 +290,16 @@
}
if ctx.Os() != android.Windows && (Bool(s.All_undefined) || Bool(s.Undefined) || Bool(s.Address) || Bool(s.Thread) ||
- Bool(s.Coverage) || Bool(s.Safestack) || Bool(s.Cfi) || Bool(s.Integer_overflow) || len(s.Misc_undefined) > 0) {
+ Bool(s.Coverage) || Bool(s.Safestack) || Bool(s.Cfi) || Bool(s.Integer_overflow) || len(s.Misc_undefined) > 0 ||
+ Bool(s.Scudo)) {
sanitize.Properties.SanitizerEnabled = true
}
+ // Disable Scudo if ASan or TSan is enabled.
+ if Bool(s.Address) || Bool(s.Thread) {
+ s.Scudo = nil
+ }
+
if Bool(s.Coverage) {
if !Bool(s.Address) {
ctx.ModuleErrorf(`Use of "coverage" also requires "address"`)
@@ -307,10 +322,12 @@
}
func (sanitize *sanitize) flags(ctx ModuleContext, flags Flags) Flags {
- minimalRuntimePath := "${config.ClangAsanLibDir}/" + config.UndefinedBehaviorSanitizerMinimalRuntimeLibrary(ctx.toolchain()) + ".a"
+ minimalRuntimeLib := config.UndefinedBehaviorSanitizerMinimalRuntimeLibrary(ctx.toolchain()) + ".a"
+ minimalRuntimePath := "${config.ClangAsanLibDir}/" + minimalRuntimeLib
if ctx.Device() && sanitize.Properties.MinimalRuntimeDep {
flags.LdFlags = append(flags.LdFlags, minimalRuntimePath)
+ flags.LdFlags = append(flags.LdFlags, "-Wl,--exclude-libs,"+minimalRuntimeLib)
}
if !sanitize.Properties.SanitizerEnabled && !sanitize.Properties.UbsanRuntimeDep {
return flags
@@ -432,6 +449,10 @@
}
}
+ if Bool(sanitize.Properties.Sanitize.Scudo) {
+ sanitizers = append(sanitizers, "scudo")
+ }
+
if len(sanitizers) > 0 {
sanitizeArg := "-fsanitize=" + strings.Join(sanitizers, ",")
@@ -448,6 +469,7 @@
if enableMinimalRuntime(sanitize) {
flags.CFlags = append(flags.CFlags, strings.Join(minimalRuntimeFlags, " "))
flags.libFlags = append([]string{minimalRuntimePath}, flags.libFlags...)
+ flags.LdFlags = append(flags.LdFlags, "-Wl,--exclude-libs,"+minimalRuntimeLib)
}
}
}
@@ -468,6 +490,8 @@
runtimeLibrary = config.AddressSanitizerRuntimeLibrary(ctx.toolchain())
} else if Bool(sanitize.Properties.Sanitize.Thread) {
runtimeLibrary = config.ThreadSanitizerRuntimeLibrary(ctx.toolchain())
+ } else if Bool(sanitize.Properties.Sanitize.Scudo) {
+ runtimeLibrary = config.ScudoRuntimeLibrary(ctx.toolchain())
} else if len(diagSanitizers) > 0 || sanitize.Properties.UbsanRuntimeDep {
runtimeLibrary = config.UndefinedBehaviorSanitizerRuntimeLibrary(ctx.toolchain())
}
@@ -539,6 +563,11 @@
!sanitize.isSanitizerEnabled(cfi)
}
+func (sanitize *sanitize) isVariantOnProductionDevice() bool {
+ return !sanitize.isSanitizerEnabled(asan) &&
+ !sanitize.isSanitizerEnabled(tsan)
+}
+
func (sanitize *sanitize) SetSanitizer(t sanitizerType, b bool) {
switch t {
case asan:
@@ -552,7 +581,6 @@
sanitize.Properties.Sanitize.Integer_overflow = boolPtr(b)
case cfi:
sanitize.Properties.Sanitize.Cfi = boolPtr(b)
- sanitize.Properties.Sanitize.Diag.Cfi = boolPtr(b)
default:
panic(fmt.Errorf("unknown sanitizerType %d", t))
}
@@ -586,43 +614,54 @@
return sanitizerVal != nil && *sanitizerVal == true
}
+func isSanitizableDependencyTag(tag blueprint.DependencyTag) bool {
+ t, ok := tag.(dependencyTag)
+ return ok && t.library || t == reuseObjTag
+}
+
// Propagate asan requirements down from binaries
func sanitizerDepsMutator(t sanitizerType) func(android.TopDownMutatorContext) {
return func(mctx android.TopDownMutatorContext) {
if c, ok := mctx.Module().(*Module); ok && c.sanitize.isSanitizerEnabled(t) {
- mctx.VisitDepsDepthFirst(func(module android.Module) {
- if d, ok := module.(*Module); ok && d.sanitize != nil &&
+ mctx.WalkDeps(func(child, parent android.Module) bool {
+ if !isSanitizableDependencyTag(mctx.OtherModuleDependencyTag(child)) {
+ return false
+ }
+ if d, ok := child.(*Module); ok && d.sanitize != nil &&
!Bool(d.sanitize.Properties.Sanitize.Never) &&
!d.sanitize.isSanitizerExplicitlyDisabled(t) {
if (t == cfi && d.static()) || t != cfi {
d.sanitize.Properties.SanitizeDep = true
}
}
+ return true
})
}
}
}
// Propagate the ubsan minimal runtime dependency when there are integer overflow sanitized static dependencies.
-func sanitizerRuntimeDepsMutator() func(android.TopDownMutatorContext) {
- return func(mctx android.TopDownMutatorContext) {
- if c, ok := mctx.Module().(*Module); ok && c.sanitize != nil {
- mctx.VisitDepsDepthFirst(func(module android.Module) {
- if d, ok := module.(*Module); ok && d.static() && d.sanitize != nil {
+func sanitizerRuntimeDepsMutator(mctx android.TopDownMutatorContext) {
+ if c, ok := mctx.Module().(*Module); ok && c.sanitize != nil {
+ mctx.WalkDeps(func(child, parent android.Module) bool {
+ if !isSanitizableDependencyTag(mctx.OtherModuleDependencyTag(child)) {
+ return false
+ }
+ if d, ok := child.(*Module); ok && d.static() && d.sanitize != nil {
- if enableMinimalRuntime(d.sanitize) {
- // If a static dependency is built with the minimal runtime,
- // make sure we include the ubsan minimal runtime.
- c.sanitize.Properties.MinimalRuntimeDep = true
- } else if Bool(d.sanitize.Properties.Sanitize.Diag.Integer_overflow) ||
- len(d.sanitize.Properties.Sanitize.Diag.Misc_undefined) > 0 {
- // If a static dependency runs with full ubsan diagnostics,
- // make sure we include the ubsan runtime.
- c.sanitize.Properties.UbsanRuntimeDep = true
- }
+ if enableMinimalRuntime(d.sanitize) {
+ // If a static dependency is built with the minimal runtime,
+ // make sure we include the ubsan minimal runtime.
+ c.sanitize.Properties.MinimalRuntimeDep = true
+ } else if Bool(d.sanitize.Properties.Sanitize.Diag.Integer_overflow) ||
+ len(d.sanitize.Properties.Sanitize.Diag.Misc_undefined) > 0 {
+ // If a static dependency runs with full ubsan diagnostics,
+ // make sure we include the ubsan runtime.
+ c.sanitize.Properties.UbsanRuntimeDep = true
}
- })
- }
+ }
+ return true
+ })
}
}
@@ -698,6 +737,7 @@
func enableMinimalRuntime(sanitize *sanitize) bool {
if !Bool(sanitize.Properties.Sanitize.Address) &&
+ !Bool(sanitize.Properties.Sanitize.Scudo) &&
(Bool(sanitize.Properties.Sanitize.Integer_overflow) ||
len(sanitize.Properties.Sanitize.Misc_undefined) > 0) &&
!(Bool(sanitize.Properties.Sanitize.Diag.Integer_overflow) ||
diff --git a/cc/test.go b/cc/test.go
index fef6367..5d0ef20 100644
--- a/cc/test.go
+++ b/cc/test.go
@@ -168,13 +168,17 @@
}
func (test *testDecorator) linkerInit(ctx BaseModuleContext, linker *baseLinker) {
- // add ../../lib[64] to rpath so that out/host/linux-x86/nativetest/<test dir>/<test> can
+ // 1. Add ../../lib[64] to rpath so that out/host/linux-x86/nativetest/<test dir>/<test> can
// find out/host/linux-x86/lib[64]/library.so
- runpath := "../../lib"
- if ctx.toolchain().Is64Bit() {
- runpath += "64"
+ // 2. Add ../../../lib[64] to rpath so that out/host/linux-x86/testcases/<test dir>/<CPU>/<test> can
+ // also find out/host/linux-x86/lib[64]/library.so
+ runpaths := []string{"../../lib", "../../../lib"}
+ for _, runpath := range runpaths {
+ if ctx.toolchain().Is64Bit() {
+ runpath += "64"
+ }
+ linker.dynamicProperties.RunPaths = append(linker.dynamicProperties.RunPaths, runpath)
}
- linker.dynamicProperties.RunPaths = append(linker.dynamicProperties.RunPaths, runpath)
// add "" to rpath so that test binaries can find libraries in their own test directory
linker.dynamicProperties.RunPaths = append(linker.dynamicProperties.RunPaths, "")
diff --git a/cc/tidy.go b/cc/tidy.go
index 8ca94ef..491cc22 100644
--- a/cc/tidy.go
+++ b/cc/tidy.go
@@ -83,9 +83,21 @@
flags.TidyFlags = append(flags.TidyFlags, "-extra-arg-before=-fno-caret-diagnostics")
}
- // We might be using the static analyzer through clang tidy.
- // https://bugs.llvm.org/show_bug.cgi?id=32914
- flags.TidyFlags = append(flags.TidyFlags, "-extra-arg-before=-D__clang_analyzer__")
+ extraArgFlags := []string{
+ // We might be using the static analyzer through clang tidy.
+ // https://bugs.llvm.org/show_bug.cgi?id=32914
+ "-D__clang_analyzer__",
+
+ // A recent change in clang-tidy (r328258) enabled destructor inlining, which
+ // appears to cause a number of false positives. Until that's resolved, this turns
+ // off the effects of r328258.
+ // https://bugs.llvm.org/show_bug.cgi?id=37459
+ "-Xclang", "-analyzer-config", "-Xclang", "c++-temp-dtor-inlining=false",
+ }
+
+ for _, f := range extraArgFlags {
+ flags.TidyFlags = append(flags.TidyFlags, "-extra-arg-before="+f)
+ }
tidyChecks := "-checks="
if checks := ctx.Config().TidyChecks(); len(checks) > 0 {
diff --git a/cc/vndk_prebuilt.go b/cc/vndk_prebuilt.go
index 9c9545d..849bb3f 100644
--- a/cc/vndk_prebuilt.go
+++ b/cc/vndk_prebuilt.go
@@ -21,7 +21,8 @@
)
var (
- vndkSuffix = ".vndk."
+ vndkSuffix = ".vndk."
+ binder32Suffix = ".binder32"
)
// Creates vndk prebuilts that include the VNDK version.
@@ -53,6 +54,10 @@
// Target arch name of the snapshot (e.g. 'arm64' for variant 'aosp_arm64_ab')
Target_arch *string
+ // If the prebuilt snapshot lib is built with 32 bit binder, this must be set to true.
+ // The lib with 64 bit binder does not need to set this property.
+ Binder32bit *bool
+
// Prebuilt files for each arch.
Srcs []string `android:"arch_variant"`
}
@@ -67,10 +72,14 @@
}
func (p *vndkPrebuiltLibraryDecorator) NameSuffix() string {
+ suffix := p.version()
if p.arch() != "" {
- return vndkSuffix + p.version() + "." + p.arch()
+ suffix += "." + p.arch()
}
- return vndkSuffix + p.version()
+ if Bool(p.properties.Binder32bit) {
+ suffix += binder32Suffix
+ }
+ return vndkSuffix + suffix
}
func (p *vndkPrebuiltLibraryDecorator) version() string {
@@ -81,6 +90,13 @@
return String(p.properties.Target_arch)
}
+func (p *vndkPrebuiltLibraryDecorator) binderBit() string {
+ if Bool(p.properties.Binder32bit) {
+ return "32"
+ }
+ return "64"
+}
+
func (p *vndkPrebuiltLibraryDecorator) linkerFlags(ctx ModuleContext, flags Flags) Flags {
p.libraryDecorator.libName = strings.TrimSuffix(ctx.ModuleName(), p.NameSuffix())
return p.libraryDecorator.linkerFlags(ctx, flags)
@@ -114,6 +130,9 @@
if len(arches) == 0 || arches[0].ArchType.String() != p.arch() {
return
}
+ if ctx.DeviceConfig().BinderBitness() != p.binderBit() {
+ return
+ }
if p.shared() {
if ctx.isVndkSp() {
p.baseInstaller.subDir = "vndk-sp-" + p.version()
diff --git a/ui/build/util_linux.go b/cmd/dep_fixer/Android.bp
similarity index 71%
copy from ui/build/util_linux.go
copy to cmd/dep_fixer/Android.bp
index 0a4e1d2..d2d1113 100644
--- a/ui/build/util_linux.go
+++ b/cmd/dep_fixer/Android.bp
@@ -1,4 +1,4 @@
-// Copyright 2017 Google Inc. All rights reserved.
+// Copyright 2018 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.
@@ -12,10 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package build
-
-import (
- "syscall"
-)
-
-const ioctlGetTermios = syscall.TCGETS
+blueprint_go_binary {
+ name: "dep_fixer",
+ deps: ["androidmk-parser"],
+ srcs: [
+ "main.go",
+ "deps.go",
+ ],
+ testSrcs: ["deps_test.go"],
+}
diff --git a/cmd/dep_fixer/deps.go b/cmd/dep_fixer/deps.go
new file mode 100644
index 0000000..64c97f5
--- /dev/null
+++ b/cmd/dep_fixer/deps.go
@@ -0,0 +1,95 @@
+// Copyright 2018 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"
+ "fmt"
+ "io"
+ "strings"
+
+ "android/soong/androidmk/parser"
+)
+
+type Deps struct {
+ Output string
+ Inputs []string
+}
+
+func Parse(filename string, r io.Reader) (*Deps, error) {
+ p := parser.NewParser(filename, r)
+ nodes, errs := p.Parse()
+
+ if len(errs) == 1 {
+ return nil, errs[0]
+ } else if len(errs) > 1 {
+ return nil, fmt.Errorf("many errors: %v", errs)
+ }
+
+ pos := func(node parser.Node) string {
+ return p.Unpack(node.Pos()).String() + ": "
+ }
+
+ ret := &Deps{}
+
+ for _, node := range nodes {
+ switch x := node.(type) {
+ case *parser.Comment:
+ // Do nothing
+ case *parser.Rule:
+ if x.Recipe != "" {
+ return nil, fmt.Errorf("%sunexpected recipe in rule: %v", pos(node), x)
+ }
+
+ if !x.Target.Const() {
+ return nil, fmt.Errorf("%sunsupported variable expansion: %v", pos(node), x.Target.Dump())
+ }
+ outputs := x.Target.Words()
+ if len(outputs) == 0 {
+ return nil, fmt.Errorf("%smissing output: %v", pos(node), x)
+ }
+ ret.Output = outputs[0].Value(nil)
+
+ if !x.Prerequisites.Const() {
+ return nil, fmt.Errorf("%sunsupported variable expansion: %v", pos(node), x.Prerequisites.Dump())
+ }
+ for _, input := range x.Prerequisites.Words() {
+ ret.Inputs = append(ret.Inputs, input.Value(nil))
+ }
+ default:
+ return nil, fmt.Errorf("%sunexpected line: %#v", pos(node), node)
+ }
+ }
+
+ return ret, nil
+}
+
+func (d *Deps) Print() []byte {
+ // We don't really have to escape every \, but it's simpler,
+ // and ninja will handle it.
+ replacer := strings.NewReplacer(" ", "\\ ",
+ ":", "\\:",
+ "#", "\\#",
+ "$", "$$",
+ "\\", "\\\\")
+
+ b := &bytes.Buffer{}
+ fmt.Fprintf(b, "%s:", replacer.Replace(d.Output))
+ for _, input := range d.Inputs {
+ fmt.Fprintf(b, " %s", replacer.Replace(input))
+ }
+ fmt.Fprintln(b)
+ return b.Bytes()
+}
diff --git a/cmd/dep_fixer/deps_test.go b/cmd/dep_fixer/deps_test.go
new file mode 100644
index 0000000..0a779b7
--- /dev/null
+++ b/cmd/dep_fixer/deps_test.go
@@ -0,0 +1,389 @@
+// Copyright 2018 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"
+ "io"
+ "io/ioutil"
+ "os"
+ "testing"
+)
+
+func TestParse(t *testing.T) {
+ testCases := []struct {
+ name string
+ input string
+ output Deps
+ err error
+ }{
+ // These come from the ninja test suite
+ {
+ name: "Basic",
+ input: "build/ninja.o: ninja.cc ninja.h eval_env.h manifest_parser.h",
+ output: Deps{
+ Output: "build/ninja.o",
+ Inputs: []string{
+ "ninja.cc",
+ "ninja.h",
+ "eval_env.h",
+ "manifest_parser.h",
+ },
+ },
+ },
+ {
+ name: "EarlyNewlineAndWhitespace",
+ input: ` \
+ out: in`,
+ output: Deps{
+ Output: "out",
+ Inputs: []string{"in"},
+ },
+ },
+ {
+ name: "Continuation",
+ input: `foo.o: \
+ bar.h baz.h
+`,
+ output: Deps{
+ Output: "foo.o",
+ Inputs: []string{"bar.h", "baz.h"},
+ },
+ },
+ {
+ name: "CarriageReturnContinuation",
+ input: "foo.o: \\\r\n bar.h baz.h\r\n",
+ output: Deps{
+ Output: "foo.o",
+ Inputs: []string{"bar.h", "baz.h"},
+ },
+ },
+ {
+ name: "BackSlashes",
+ input: `Project\Dir\Build\Release8\Foo\Foo.res : \
+ Dir\Library\Foo.rc \
+ Dir\Library\Version\Bar.h \
+ Dir\Library\Foo.ico \
+ Project\Thing\Bar.tlb \
+`,
+ output: Deps{
+ Output: `Project\Dir\Build\Release8\Foo\Foo.res`,
+ Inputs: []string{
+ `Dir\Library\Foo.rc`,
+ `Dir\Library\Version\Bar.h`,
+ `Dir\Library\Foo.ico`,
+ `Project\Thing\Bar.tlb`,
+ },
+ },
+ },
+ {
+ name: "Spaces",
+ input: `a\ bc\ def: a\ b c d`,
+ output: Deps{
+ Output: `a bc def`,
+ Inputs: []string{"a b", "c", "d"},
+ },
+ },
+ {
+ name: "Escapes",
+ input: `\!\@\#$$\%\^\&\\:`,
+ output: Deps{
+ Output: `\!\@#$\%\^\&\`,
+ },
+ },
+ {
+ name: "SpecialChars",
+ // Ninja includes a number of '=', but our parser can't handle that,
+ // since it sees the equals and switches over to assuming it's an
+ // assignment.
+ //
+ // We don't have any files in our tree that contain an '=' character,
+ // and Kati can't handle parsing this either, so for now I'm just
+ // going to remove all the '=' characters below.
+ //
+ // It looks like make will only do this for the first
+ // dependency, but not later dependencies.
+ input: `C\:/Program\ Files\ (x86)/Microsoft\ crtdefs.h: \
+ en@quot.header~ t+t-x!1 \
+ openldap/slapd.d/cnconfig/cnschema/cn{0}core.ldif \
+ Fu` + "\303\244ball",
+ output: Deps{
+ Output: "C:/Program Files (x86)/Microsoft crtdefs.h",
+ Inputs: []string{
+ "en@quot.header~",
+ "t+t-x!1",
+ "openldap/slapd.d/cnconfig/cnschema/cn{0}core.ldif",
+ "Fu\303\244ball",
+ },
+ },
+ },
+ // Ninja's UnifyMultipleOutputs and RejectMultipleDifferentOutputs tests have been omitted,
+ // since we don't want the same behavior.
+
+ // Our own tests
+ {
+ name: "Multiple outputs",
+ input: `a b: c
+a: d
+b: e`,
+ output: Deps{
+ Output: "b",
+ Inputs: []string{
+ "c",
+ "d",
+ "e",
+ },
+ },
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ out, err := Parse("test.d", bytes.NewBufferString(tc.input))
+ if err != tc.err {
+ t.Fatalf("Unexpected error: %v (expected %v)", err, tc.err)
+ }
+
+ if out.Output != tc.output.Output {
+ t.Errorf("output file doesn't match:\n"+
+ " str: %#v\n"+
+ "want: %#v\n"+
+ " got: %#v", tc.input, tc.output.Output, out.Output)
+ }
+
+ matches := true
+ if len(out.Inputs) != len(tc.output.Inputs) {
+ matches = false
+ } else {
+ for i := range out.Inputs {
+ if out.Inputs[i] != tc.output.Inputs[i] {
+ matches = false
+ }
+ }
+ }
+ if !matches {
+ t.Errorf("input files don't match:\n"+
+ " str: %#v\n"+
+ "want: %#v\n"+
+ " got: %#v", tc.input, tc.output.Inputs, out.Inputs)
+ }
+ })
+ }
+}
+
+func BenchmarkParsing(b *testing.B) {
+ // Write it out to a file to most closely match ninja's perftest
+ tmpfile, err := ioutil.TempFile("", "depfile")
+ if err != nil {
+ b.Fatal("Failed to create temp file:", err)
+ }
+ defer os.Remove(tmpfile.Name())
+ _, err = io.WriteString(tmpfile, `out/soong/.intermediates/external/ninja/ninja/linux_glibc_x86_64/obj/external/ninja/src/ninja.o: \
+ external/ninja/src/ninja.cc external/libcxx/include/errno.h \
+ external/libcxx/include/__config \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/features.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/predefs.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/sys/cdefs.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/wordsize.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/gnu/stubs.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/gnu/stubs-64.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/errno.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/errno.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/linux/errno.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/asm/errno.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/asm-generic/errno.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/asm-generic/errno-base.h \
+ external/libcxx/include/limits.h \
+ prebuilts/clang/host/linux-x86/clang-4639204/lib64/clang/6.0.1/include/limits.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/limits.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/posix1_lim.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/local_lim.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/linux/limits.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/posix2_lim.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/xopen_lim.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/stdio_lim.h \
+ external/libcxx/include/stdio.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/stdio.h \
+ external/libcxx/include/stddef.h \
+ prebuilts/clang/host/linux-x86/clang-4639204/lib64/clang/6.0.1/include/stddef.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/types.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/typesizes.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/libio.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/_G_config.h \
+ external/libcxx/include/wchar.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/wchar.h \
+ prebuilts/clang/host/linux-x86/clang-4639204/lib64/clang/6.0.1/include/stdarg.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/sys_errlist.h \
+ external/libcxx/include/stdlib.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/stdlib.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/waitflags.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/waitstatus.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/endian.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/endian.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/byteswap.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/xlocale.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/sys/types.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/time.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/sys/select.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/select.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/sigset.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/time.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/select2.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/sys/sysmacros.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/pthreadtypes.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/alloca.h \
+ external/libcxx/include/string.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/string.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/getopt.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/unistd.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/posix_opt.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/environments.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/confname.h \
+ external/ninja/src/browse.h external/ninja/src/build.h \
+ external/libcxx/include/cstdio external/libcxx/include/map \
+ external/libcxx/include/__tree external/libcxx/include/iterator \
+ external/libcxx/include/iosfwd \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/wchar.h \
+ external/libcxx/include/__functional_base \
+ external/libcxx/include/type_traits external/libcxx/include/cstddef \
+ prebuilts/clang/host/linux-x86/clang-4639204/lib64/clang/6.0.1/include/__stddef_max_align_t.h \
+ external/libcxx/include/__nullptr external/libcxx/include/typeinfo \
+ external/libcxx/include/exception external/libcxx/include/cstdlib \
+ external/libcxx/include/cstdint external/libcxx/include/stdint.h \
+ prebuilts/clang/host/linux-x86/clang-4639204/lib64/clang/6.0.1/include/stdint.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/stdint.h \
+ external/libcxx/include/new external/libcxx/include/utility \
+ external/libcxx/include/__tuple \
+ external/libcxx/include/initializer_list \
+ external/libcxx/include/cstring external/libcxx/include/__debug \
+ external/libcxx/include/memory external/libcxx/include/limits \
+ external/libcxx/include/__undef_macros external/libcxx/include/tuple \
+ external/libcxx/include/stdexcept external/libcxx/include/cassert \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/assert.h \
+ external/libcxx/include/atomic external/libcxx/include/algorithm \
+ external/libcxx/include/functional external/libcxx/include/queue \
+ external/libcxx/include/deque external/libcxx/include/__split_buffer \
+ external/libcxx/include/vector external/libcxx/include/__bit_reference \
+ external/libcxx/include/climits external/libcxx/include/set \
+ external/libcxx/include/string external/libcxx/include/string_view \
+ external/libcxx/include/__string external/libcxx/include/cwchar \
+ external/libcxx/include/cwctype external/libcxx/include/cctype \
+ external/libcxx/include/ctype.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/ctype.h \
+ external/libcxx/include/wctype.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/wctype.h \
+ external/ninja/src/graph.h external/ninja/src/eval_env.h \
+ external/ninja/src/string_piece.h external/ninja/src/timestamp.h \
+ external/ninja/src/util.h external/ninja/src/exit_status.h \
+ external/ninja/src/line_printer.h external/ninja/src/metrics.h \
+ external/ninja/src/build_log.h external/ninja/src/hash_map.h \
+ external/libcxx/include/unordered_map \
+ external/libcxx/include/__hash_table external/libcxx/include/cmath \
+ external/libcxx/include/math.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/math.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/huge_val.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/huge_valf.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/huge_vall.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/inf.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/nan.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/mathdef.h \
+ prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/sysroot/usr/include/x86_64-linux-gnu/bits/mathcalls.h \
+ external/ninja/src/deps_log.h external/ninja/src/clean.h \
+ external/ninja/src/debug_flags.h external/ninja/src/disk_interface.h \
+ external/ninja/src/graphviz.h external/ninja/src/manifest_parser.h \
+ external/ninja/src/lexer.h external/ninja/src/state.h \
+ external/ninja/src/version.h`)
+ tmpfile.Close()
+ if err != nil {
+ b.Fatal("Failed to write dep file:", err)
+ }
+ b.ResetTimer()
+
+ for n := 0; n < b.N; n++ {
+ depfile, err := ioutil.ReadFile(tmpfile.Name())
+ if err != nil {
+ b.Fatal("Failed to read dep file:", err)
+ }
+
+ _, err = Parse(tmpfile.Name(), bytes.NewBuffer(depfile))
+ if err != nil {
+ b.Fatal("Failed to parse:", err)
+ }
+ }
+}
+
+func TestDepPrint(t *testing.T) {
+ testCases := []struct {
+ name string
+ input Deps
+ output string
+ }{
+ {
+ name: "Empty",
+ input: Deps{
+ Output: "a",
+ },
+ output: "a:",
+ },
+ {
+ name: "Basic",
+ input: Deps{
+ Output: "a",
+ Inputs: []string{"b", "c"},
+ },
+ output: "a: b c",
+ },
+ {
+ name: "Escapes",
+ input: Deps{
+ Output: `\!\@#$\%\^\&\`,
+ },
+ output: `\\!\\@\#$$\\%\\^\\&\\:`,
+ },
+ {
+ name: "Spaces",
+ input: Deps{
+ Output: "a b",
+ Inputs: []string{"c d", "e f "},
+ },
+ output: `a\ b: c\ d e\ f\ `,
+ },
+ {
+ name: "SpecialChars",
+ input: Deps{
+ Output: "C:/Program Files (x86)/Microsoft crtdefs.h",
+ Inputs: []string{
+ "en@quot.header~",
+ "t+t-x!1",
+ "openldap/slapd.d/cnconfig/cnschema/cn{0}core.ldif",
+ "Fu\303\244ball",
+ },
+ },
+ output: `C\:/Program\ Files\ (x86)/Microsoft\ crtdefs.h: en@quot.header~ t+t-x!1 openldap/slapd.d/cnconfig/cnschema/cn{0}core.ldif Fu` + "\303\244ball",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ out := tc.input.Print()
+ outStr := string(out)
+ want := tc.output + "\n"
+
+ if outStr != want {
+ t.Errorf("output doesn't match:\nwant:%q\n got:%q", want, outStr)
+ }
+ })
+ }
+}
diff --git a/cmd/dep_fixer/main.go b/cmd/dep_fixer/main.go
new file mode 100644
index 0000000..0647fb2
--- /dev/null
+++ b/cmd/dep_fixer/main.go
@@ -0,0 +1,67 @@
+// Copyright 2018 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 tool reads "make"-like dependency files, and outputs a canonical version
+// that can be used by ninja. Ninja doesn't support multiple output files (even
+// though it doesn't care what the output file is, or whether it matches what is
+// expected).
+package main
+
+import (
+ "bytes"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+)
+
+func main() {
+ flag.Usage = func() {
+ fmt.Fprintf(os.Stderr, "Usage: %s <depfile.d>", os.Args[0])
+ flag.PrintDefaults()
+ }
+ output := flag.String("o", "", "Optional output file (defaults to rewriting source if necessary)")
+ flag.Parse()
+
+ if flag.NArg() != 1 {
+ log.Fatal("Expected a single file as an argument")
+ }
+
+ old, err := ioutil.ReadFile(flag.Arg(0))
+ if err != nil {
+ log.Fatalf("Error opening %q: %v", flag.Arg(0), err)
+ }
+
+ deps, err := Parse(flag.Arg(0), bytes.NewBuffer(append([]byte(nil), old...)))
+ if err != nil {
+ log.Fatalf("Failed to parse: %v", err)
+ }
+
+ new := deps.Print()
+
+ if *output == "" || *output == flag.Arg(0) {
+ if !bytes.Equal(old, new) {
+ err := ioutil.WriteFile(flag.Arg(0), new, 0666)
+ if err != nil {
+ log.Fatalf("Failed to write: %v", err)
+ }
+ }
+ } else {
+ err := ioutil.WriteFile(*output, new, 0666)
+ if err != nil {
+ log.Fatalf("Failed to write to %q: %v", *output, err)
+ }
+ }
+}
diff --git a/cmd/merge_zips/Android.bp b/cmd/merge_zips/Android.bp
index ace079d..ab658fd 100644
--- a/cmd/merge_zips/Android.bp
+++ b/cmd/merge_zips/Android.bp
@@ -16,10 +16,14 @@
name: "merge_zips",
deps: [
"android-archive-zip",
+ "blueprint-pathtools",
"soong-jar",
],
srcs: [
"merge_zips.go",
],
+ testSrcs: [
+ "merge_zips_test.go",
+ ],
}
diff --git a/cmd/merge_zips/merge_zips.go b/cmd/merge_zips/merge_zips.go
index 1057655..95ff70b 100644
--- a/cmd/merge_zips/merge_zips.go
+++ b/cmd/merge_zips/merge_zips.go
@@ -24,7 +24,8 @@
"os"
"path/filepath"
"sort"
- "strings"
+
+ "github.com/google/blueprint/pathtools"
"android/soong/jar"
"android/soong/third_party/zip"
@@ -69,8 +70,8 @@
)
func init() {
- flag.Var(&stripDirs, "stripDir", "the prefix of file path to be excluded from the output zip")
- flag.Var(&stripFiles, "stripFile", "filenames to be excluded from the output zip, accepts wildcards")
+ flag.Var(&stripDirs, "stripDir", "directories to be excluded from the output zip, accepts wildcards")
+ flag.Var(&stripFiles, "stripFile", "files to be excluded from the output zip, accepts wildcards")
flag.Var(&zipsToNotStrip, "zipToNotStrip", "the input zip file which is not applicable for stripping")
}
@@ -114,7 +115,7 @@
log.Fatal(err)
}
defer reader.Close()
- namedReader := namedZipReader{path: input, reader: reader}
+ namedReader := namedZipReader{path: input, reader: &reader.Reader}
readers = append(readers, namedReader)
}
@@ -132,7 +133,7 @@
// do merge
err = mergeZips(readers, writer, *manifest, *entrypoint, *pyMain, *sortEntries, *emulateJar, *emulatePar,
- *stripDirEntries, *ignoreDuplicates)
+ *stripDirEntries, *ignoreDuplicates, []string(stripFiles), []string(stripDirs), map[string]bool(zipsToNotStrip))
if err != nil {
log.Fatal(err)
}
@@ -141,7 +142,7 @@
// a namedZipReader reads a .zip file and can say which file it's reading
type namedZipReader struct {
path string
- reader *zip.ReadCloser
+ reader *zip.Reader
}
// a zipEntryPath refers to a file contained in a zip
@@ -224,7 +225,8 @@
}
func mergeZips(readers []namedZipReader, writer *zip.Writer, manifest, entrypoint, pyMain string,
- sortEntries, emulateJar, emulatePar, stripDirEntries, ignoreDuplicates bool) error {
+ sortEntries, emulateJar, emulatePar, stripDirEntries, ignoreDuplicates bool,
+ stripFiles, stripDirs []string, zipsToNotStrip map[string]bool) error {
sourceByDest := make(map[string]zipSource, 0)
orderedMappings := []fileMapping{}
@@ -338,8 +340,12 @@
for _, namedReader := range readers {
_, skipStripThisZip := zipsToNotStrip[namedReader.path]
for _, file := range namedReader.reader.File {
- if !skipStripThisZip && shouldStripFile(emulateJar, file.Name) {
- continue
+ if !skipStripThisZip {
+ if skip, err := shouldStripEntry(emulateJar, stripFiles, stripDirs, file.Name); err != nil {
+ return err
+ } else if skip {
+ continue
+ }
}
if stripDirEntries && file.FileInfo().IsDir() {
@@ -419,26 +425,41 @@
return ret
}
-func shouldStripFile(emulateJar bool, name string) bool {
+func shouldStripEntry(emulateJar bool, stripFiles, stripDirs []string, name string) (bool, error) {
for _, dir := range stripDirs {
- if strings.HasPrefix(name, dir+"/") {
- if emulateJar {
- if name != jar.MetaDir && name != jar.ManifestFile {
- return true
+ dir = filepath.Clean(dir)
+ patterns := []string{
+ dir + "/", // the directory itself
+ dir + "/**/*", // files recursively in the directory
+ dir + "/**/*/", // directories recursively in the directory
+ }
+
+ for _, pattern := range patterns {
+ match, err := pathtools.Match(pattern, name)
+ if err != nil {
+ return false, fmt.Errorf("%s: %s", err.Error(), pattern)
+ } else if match {
+ if emulateJar {
+ // When merging jar files, don't strip META-INF/MANIFEST.MF even if stripping META-INF is
+ // requested.
+ // TODO(ccross): which files does this affect?
+ if name != jar.MetaDir && name != jar.ManifestFile {
+ return true, nil
+ }
}
- } else {
- return true
+ return true, nil
}
}
}
+
for _, pattern := range stripFiles {
- if match, err := filepath.Match(pattern, filepath.Base(name)); err != nil {
- panic(fmt.Errorf("%s: %s", err.Error(), pattern))
+ if match, err := pathtools.Match(pattern, name); err != nil {
+ return false, fmt.Errorf("%s: %s", err.Error(), pattern)
} else if match {
- return true
+ return true, nil
}
}
- return false
+ return false, nil
}
func jarSort(files []fileMapping) {
diff --git a/cmd/merge_zips/merge_zips_test.go b/cmd/merge_zips/merge_zips_test.go
new file mode 100644
index 0000000..f91111f
--- /dev/null
+++ b/cmd/merge_zips/merge_zips_test.go
@@ -0,0 +1,298 @@
+// Copyright 2018 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"
+ "fmt"
+ "os"
+ "strconv"
+ "strings"
+ "testing"
+
+ "android/soong/jar"
+ "android/soong/third_party/zip"
+)
+
+type testZipEntry struct {
+ name string
+ mode os.FileMode
+ data []byte
+}
+
+var (
+ A = testZipEntry{"A", 0755, []byte("foo")}
+ a = testZipEntry{"a", 0755, []byte("foo")}
+ a2 = testZipEntry{"a", 0755, []byte("FOO2")}
+ a3 = testZipEntry{"a", 0755, []byte("Foo3")}
+ bDir = testZipEntry{"b/", os.ModeDir | 0755, nil}
+ bbDir = testZipEntry{"b/b/", os.ModeDir | 0755, nil}
+ bbb = testZipEntry{"b/b/b", 0755, nil}
+ ba = testZipEntry{"b/a", 0755, []byte("foob")}
+ bc = testZipEntry{"b/c", 0755, []byte("bar")}
+ bd = testZipEntry{"b/d", 0700, []byte("baz")}
+ be = testZipEntry{"b/e", 0700, []byte("")}
+
+ metainfDir = testZipEntry{jar.MetaDir, os.ModeDir | 0755, nil}
+ manifestFile = testZipEntry{jar.ManifestFile, 0755, []byte("manifest")}
+ manifestFile2 = testZipEntry{jar.ManifestFile, 0755, []byte("manifest2")}
+ moduleInfoFile = testZipEntry{jar.ModuleInfoClass, 0755, []byte("module-info")}
+)
+
+func TestMergeZips(t *testing.T) {
+ testCases := []struct {
+ name string
+ in [][]testZipEntry
+ stripFiles []string
+ stripDirs []string
+ jar bool
+ sort bool
+ ignoreDuplicates bool
+ stripDirEntries bool
+ zipsToNotStrip map[string]bool
+
+ out []testZipEntry
+ err string
+ }{
+ {
+ name: "duplicates error",
+ in: [][]testZipEntry{
+ {a},
+ {a2},
+ {a3},
+ },
+ out: []testZipEntry{a},
+ err: "duplicate",
+ },
+ {
+ name: "duplicates take first",
+ in: [][]testZipEntry{
+ {a},
+ {a2},
+ {a3},
+ },
+ out: []testZipEntry{a},
+
+ ignoreDuplicates: true,
+ },
+ {
+ name: "sort",
+ in: [][]testZipEntry{
+ {be, bc, bDir, bbDir, bbb, A, metainfDir, manifestFile},
+ },
+ out: []testZipEntry{A, metainfDir, manifestFile, bDir, bbDir, bbb, bc, be},
+
+ sort: true,
+ },
+ {
+ name: "jar sort",
+ in: [][]testZipEntry{
+ {be, bc, bDir, A, metainfDir, manifestFile},
+ },
+ out: []testZipEntry{metainfDir, manifestFile, A, bDir, bc, be},
+
+ jar: true,
+ },
+ {
+ name: "jar merge",
+ in: [][]testZipEntry{
+ {metainfDir, manifestFile, bDir, be},
+ {metainfDir, manifestFile2, bDir, bc},
+ {metainfDir, manifestFile2, A},
+ },
+ out: []testZipEntry{metainfDir, manifestFile, A, bDir, bc, be},
+
+ jar: true,
+ },
+ {
+ name: "merge",
+ in: [][]testZipEntry{
+ {bDir, be},
+ {bDir, bc},
+ {A},
+ },
+ out: []testZipEntry{bDir, be, bc, A},
+ },
+ {
+ name: "strip dir entries",
+ in: [][]testZipEntry{
+ {a, bDir, bbDir, bbb, bc, bd, be},
+ },
+ out: []testZipEntry{a, bbb, bc, bd, be},
+
+ stripDirEntries: true,
+ },
+ {
+ name: "strip files",
+ in: [][]testZipEntry{
+ {a, bDir, bbDir, bbb, bc, bd, be},
+ },
+ out: []testZipEntry{a, bDir, bbDir, bbb, bc},
+
+ stripFiles: []string{"b/d", "b/e"},
+ },
+ {
+ // merge_zips used to treat -stripFile a as stripping any file named a, it now only strips a in the
+ // root of the zip.
+ name: "strip file name",
+ in: [][]testZipEntry{
+ {a, bDir, ba},
+ },
+ out: []testZipEntry{bDir, ba},
+
+ stripFiles: []string{"a"},
+ },
+ {
+ name: "strip files glob",
+ in: [][]testZipEntry{
+ {a, bDir, ba},
+ },
+ out: []testZipEntry{bDir},
+
+ stripFiles: []string{"**/a"},
+ },
+ {
+ name: "strip dirs",
+ in: [][]testZipEntry{
+ {a, bDir, bbDir, bbb, bc, bd, be},
+ },
+ out: []testZipEntry{a},
+
+ stripDirs: []string{"b"},
+ },
+ {
+ name: "strip dirs glob",
+ in: [][]testZipEntry{
+ {a, bDir, bbDir, bbb, bc, bd, be},
+ },
+ out: []testZipEntry{a, bDir, bc, bd, be},
+
+ stripDirs: []string{"b/*"},
+ },
+ {
+ name: "zips to not strip",
+ in: [][]testZipEntry{
+ {a, bDir, bc},
+ {bDir, bd},
+ {bDir, be},
+ },
+ out: []testZipEntry{a, bDir, bd},
+
+ stripDirs: []string{"b"},
+ zipsToNotStrip: map[string]bool{
+ "in1": true,
+ },
+ },
+ }
+
+ for _, test := range testCases {
+ t.Run(test.name, func(t *testing.T) {
+ var readers []namedZipReader
+ for i, in := range test.in {
+ r := testZipEntriesToZipReader(in)
+ readers = append(readers, namedZipReader{
+ path: "in" + strconv.Itoa(i),
+ reader: r,
+ })
+ }
+
+ want := testZipEntriesToBuf(test.out)
+
+ out := &bytes.Buffer{}
+ writer := zip.NewWriter(out)
+
+ err := mergeZips(readers, writer, "", "", "",
+ test.sort, test.jar, false, test.stripDirEntries, test.ignoreDuplicates,
+ test.stripFiles, test.stripDirs, test.zipsToNotStrip)
+
+ closeErr := writer.Close()
+ if closeErr != nil {
+ t.Fatal(err)
+ }
+
+ if test.err != "" {
+ if err == nil {
+ t.Fatal("missing err, expected: ", test.err)
+ } else if !strings.Contains(strings.ToLower(err.Error()), strings.ToLower(test.err)) {
+ t.Fatal("incorrect err, want:", test.err, "got:", err)
+ }
+ return
+ }
+
+ if !bytes.Equal(want, out.Bytes()) {
+ t.Error("incorrect zip output")
+ t.Errorf("want:\n%s", dumpZip(want))
+ t.Errorf("got:\n%s", dumpZip(out.Bytes()))
+ }
+ })
+ }
+}
+
+func testZipEntriesToBuf(entries []testZipEntry) []byte {
+ b := &bytes.Buffer{}
+ zw := zip.NewWriter(b)
+
+ for _, e := range entries {
+ fh := zip.FileHeader{
+ Name: e.name,
+ }
+ fh.SetMode(e.mode)
+
+ w, err := zw.CreateHeader(&fh)
+ if err != nil {
+ panic(err)
+ }
+
+ _, err = w.Write(e.data)
+ if err != nil {
+ panic(err)
+ }
+ }
+
+ err := zw.Close()
+ if err != nil {
+ panic(err)
+ }
+
+ return b.Bytes()
+}
+
+func testZipEntriesToZipReader(entries []testZipEntry) *zip.Reader {
+ b := testZipEntriesToBuf(entries)
+ r := bytes.NewReader(b)
+
+ zr, err := zip.NewReader(r, int64(len(b)))
+ if err != nil {
+ panic(err)
+ }
+
+ return zr
+}
+
+func dumpZip(buf []byte) string {
+ r := bytes.NewReader(buf)
+ zr, err := zip.NewReader(r, int64(len(buf)))
+ if err != nil {
+ panic(err)
+ }
+
+ var ret string
+
+ for _, f := range zr.File {
+ ret += fmt.Sprintf("%v: %v %v %08x\n", f.Name, f.Mode(), f.UncompressedSize64, f.CRC32)
+ }
+
+ return ret
+}
diff --git a/cmd/multiproduct_kati/Android.bp b/cmd/multiproduct_kati/Android.bp
index 04a5802..13b3679 100644
--- a/cmd/multiproduct_kati/Android.bp
+++ b/cmd/multiproduct_kati/Android.bp
@@ -17,6 +17,7 @@
deps: [
"soong-ui-build",
"soong-ui-logger",
+ "soong-ui-terminal",
"soong-ui-tracer",
"soong-zip",
],
diff --git a/cmd/multiproduct_kati/main.go b/cmd/multiproduct_kati/main.go
index 06c5626..237d384 100644
--- a/cmd/multiproduct_kati/main.go
+++ b/cmd/multiproduct_kati/main.go
@@ -29,6 +29,8 @@
"android/soong/ui/build"
"android/soong/ui/logger"
+ "android/soong/ui/status"
+ "android/soong/ui/terminal"
"android/soong/ui/tracer"
"android/soong/zip"
)
@@ -66,98 +68,34 @@
ctx build.Context
config build.Config
logFile string
+ action *status.Action
}
-type Status struct {
- cur int
- total int
- failed int
-
- ctx build.Context
- haveBlankLine bool
- smartTerminal bool
-
- lock sync.Mutex
-}
-
-func NewStatus(ctx build.Context) *Status {
- return &Status{
- ctx: ctx,
- haveBlankLine: true,
- smartTerminal: ctx.IsTerminal(),
- }
-}
-
-func (s *Status) SetTotal(total int) {
- s.total = total
-}
-
-func (s *Status) Fail(product string, err error, logFile string) {
- s.Finish(product)
-
- s.lock.Lock()
- defer s.lock.Unlock()
-
- if s.smartTerminal && !s.haveBlankLine {
- fmt.Fprintln(s.ctx.Stdout())
- s.haveBlankLine = true
+func errMsgFromLog(filename string) string {
+ if filename == "" {
+ return ""
}
- s.failed++
- fmt.Fprintln(s.ctx.Stderr(), "FAILED:", product)
- s.ctx.Verboseln("FAILED:", product)
-
- if logFile != "" {
- data, err := ioutil.ReadFile(logFile)
- if err == nil {
- lines := strings.Split(strings.TrimSpace(string(data)), "\n")
- if len(lines) > errorLeadingLines+errorTrailingLines+1 {
- lines[errorLeadingLines] = fmt.Sprintf("... skipping %d lines ...",
- len(lines)-errorLeadingLines-errorTrailingLines)
-
- lines = append(lines[:errorLeadingLines+1],
- lines[len(lines)-errorTrailingLines:]...)
- }
- for _, line := range lines {
- fmt.Fprintln(s.ctx.Stderr(), "> ", line)
- s.ctx.Verboseln(line)
- }
- }
+ data, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return ""
}
- s.ctx.Print(err)
-}
+ lines := strings.Split(strings.TrimSpace(string(data)), "\n")
+ if len(lines) > errorLeadingLines+errorTrailingLines+1 {
+ lines[errorLeadingLines] = fmt.Sprintf("... skipping %d lines ...",
+ len(lines)-errorLeadingLines-errorTrailingLines)
-func (s *Status) Finish(product string) {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- s.cur++
- line := fmt.Sprintf("[%d/%d] %s", s.cur, s.total, product)
-
- if s.smartTerminal {
- if max, ok := s.ctx.TermWidth(); ok {
- if len(line) > max {
- line = line[:max]
- }
- }
-
- fmt.Fprint(s.ctx.Stdout(), "\r", line, "\x1b[K")
- s.haveBlankLine = false
- } else {
- s.ctx.Println(line)
+ lines = append(lines[:errorLeadingLines+1],
+ lines[len(lines)-errorTrailingLines:]...)
}
-}
-
-func (s *Status) Finished() int {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- if !s.haveBlankLine {
- fmt.Fprintln(s.ctx.Stdout())
- s.haveBlankLine = true
+ var buf strings.Builder
+ for _, line := range lines {
+ buf.WriteString("> ")
+ buf.WriteString(line)
+ buf.WriteString("\n")
}
- return s.failed
+ return buf.String()
}
// TODO(b/70370883): This tool uses a lot of open files -- over the default
@@ -194,6 +132,9 @@
}
func main() {
+ writer := terminal.NewWriter(terminal.StdioImpl{})
+ defer writer.Finish()
+
log := logger.New(os.Stderr)
defer log.Cleanup()
@@ -205,20 +146,24 @@
trace := tracer.New(log)
defer trace.Close()
+ stat := &status.Status{}
+ defer stat.Finish()
+ stat.AddOutput(terminal.NewStatusOutput(writer, ""))
+
build.SetupSignals(log, cancel, func() {
trace.Close()
log.Cleanup()
+ stat.Finish()
})
buildCtx := build.Context{&build.ContextImpl{
- Context: ctx,
- Logger: log,
- Tracer: trace,
- StdioInterface: build.StdioImpl{},
+ Context: ctx,
+ Logger: log,
+ Tracer: trace,
+ Writer: writer,
+ Status: stat,
}}
- status := NewStatus(buildCtx)
-
config := build.NewConfig(buildCtx)
if *outDir == "" {
name := "multiproduct-" + time.Now().Format("20060102150405")
@@ -255,6 +200,11 @@
setMaxFiles(log)
+ finder := build.NewSourceFinder(buildCtx, config)
+ defer finder.Shutdown()
+
+ build.FindSources(buildCtx, config, finder)
+
vars, err := build.DumpMakeVars(buildCtx, config, nil, []string{"all_named_products"})
if err != nil {
log.Fatal(err)
@@ -298,14 +248,12 @@
log.Verbose("Got product list: ", products)
- status.SetTotal(len(products))
+ s := buildCtx.Status.StartTool()
+ s.SetTotalActions(len(products))
var wg sync.WaitGroup
productConfigs := make(chan Product, len(products))
- finder := build.NewSourceFinder(buildCtx, config)
- defer finder.Shutdown()
-
// Run the product config for every product in parallel
for _, product := range products {
wg.Add(1)
@@ -313,8 +261,18 @@
var stdLog string
defer wg.Done()
+
+ action := &status.Action{
+ Description: product,
+ Outputs: []string{product},
+ }
+ s.StartAction(action)
defer logger.Recover(func(err error) {
- status.Fail(product, err, stdLog)
+ s.FinishAction(status.ActionResult{
+ Action: action,
+ Error: err,
+ Output: errMsgFromLog(stdLog),
+ })
})
productOutDir := filepath.Join(config.OutDir(), product)
@@ -337,12 +295,14 @@
productLog.SetOutput(filepath.Join(productLogDir, "soong.log"))
productCtx := build.Context{&build.ContextImpl{
- Context: ctx,
- Logger: productLog,
- Tracer: trace,
- StdioInterface: build.NewCustomStdio(nil, f, f),
- Thread: trace.NewThread(product),
+ Context: ctx,
+ Logger: productLog,
+ Tracer: trace,
+ Writer: terminal.NewWriter(terminal.NewCustomStdio(nil, f, f)),
+ Thread: trace.NewThread(product),
+ Status: &status.Status{},
}}
+ productCtx.Status.AddOutput(terminal.NewStatusOutput(productCtx.Writer, ""))
productConfig := build.NewConfig(productCtx)
productConfig.Environment().Set("OUT_DIR", productOutDir)
@@ -350,7 +310,7 @@
productConfig.Lunch(productCtx, product, *buildVariant)
build.Build(productCtx, productConfig, build.BuildProductConfig)
- productConfigs <- Product{productCtx, productConfig, stdLog}
+ productConfigs <- Product{productCtx, productConfig, stdLog, action}
}(product)
}
go func() {
@@ -367,7 +327,11 @@
for product := range productConfigs {
func() {
defer logger.Recover(func(err error) {
- status.Fail(product.config.TargetProduct(), err, product.logFile)
+ s.FinishAction(status.ActionResult{
+ Action: product.action,
+ Error: err,
+ Output: errMsgFromLog(product.logFile),
+ })
})
defer func() {
@@ -398,7 +362,9 @@
}
}
build.Build(product.ctx, product.config, buildWhat)
- status.Finish(product.config.TargetProduct())
+ s.FinishAction(status.ActionResult{
+ Action: product.action,
+ })
}()
}
}()
@@ -419,7 +385,5 @@
}
}
- if count := status.Finished(); count > 0 {
- log.Fatalln(count, "products failed")
- }
+ s.Finish()
}
diff --git a/ui/build/util_linux.go b/cmd/path_interposer/Android.bp
similarity index 73%
copy from ui/build/util_linux.go
copy to cmd/path_interposer/Android.bp
index 0a4e1d2..41a219f 100644
--- a/ui/build/util_linux.go
+++ b/cmd/path_interposer/Android.bp
@@ -1,4 +1,4 @@
-// Copyright 2017 Google Inc. All rights reserved.
+// Copyright 2018 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.
@@ -12,10 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package build
-
-import (
- "syscall"
-)
-
-const ioctlGetTermios = syscall.TCGETS
+blueprint_go_binary {
+ name: "path_interposer",
+ deps: ["soong-ui-build-paths"],
+ srcs: ["main.go"],
+ testSrcs: ["main_test.go"],
+}
diff --git a/cmd/path_interposer/main.go b/cmd/path_interposer/main.go
new file mode 100644
index 0000000..cd28b96
--- /dev/null
+++ b/cmd/path_interposer/main.go
@@ -0,0 +1,247 @@
+// Copyright 2018 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"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strconv"
+ "syscall"
+
+ "android/soong/ui/build/paths"
+)
+
+func main() {
+ interposer, err := os.Executable()
+ if err != nil {
+ fmt.Fprintln(os.Stderr, "Unable to locate interposer executable:", err)
+ os.Exit(1)
+ }
+
+ if fi, err := os.Lstat(interposer); err == nil {
+ if fi.Mode()&os.ModeSymlink != 0 {
+ link, err := os.Readlink(interposer)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, "Unable to read link to interposer executable:", err)
+ os.Exit(1)
+ }
+ if filepath.IsAbs(link) {
+ interposer = link
+ } else {
+ interposer = filepath.Join(filepath.Dir(interposer), link)
+ }
+ }
+ } else {
+ fmt.Fprintln(os.Stderr, "Unable to stat interposer executable:", err)
+ os.Exit(1)
+ }
+
+ disableError := false
+ if e, ok := os.LookupEnv("TEMPORARY_DISABLE_PATH_RESTRICTIONS"); ok {
+ disableError = e == "1" || e == "y" || e == "yes" || e == "on" || e == "true"
+ }
+
+ exitCode, err := Main(os.Stdout, os.Stderr, interposer, os.Args, mainOpts{
+ disableError: disableError,
+
+ sendLog: paths.SendLog,
+ config: paths.GetConfig,
+ lookupParents: lookupParents,
+ })
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err.Error())
+ }
+ os.Exit(exitCode)
+}
+
+var usage = fmt.Errorf(`To use the PATH interposer:
+ * Write the original PATH variable to <interposer>_origpath
+ * Set up a directory of symlinks to the PATH interposer, and use that in PATH
+
+If a tool isn't in the allowed list, a log will be posted to the unix domain
+socket at <interposer>_log.`)
+
+type mainOpts struct {
+ disableError bool
+
+ sendLog func(logSocket string, entry *paths.LogEntry, done chan interface{})
+ config func(name string) paths.PathConfig
+ lookupParents func() []paths.LogProcess
+}
+
+func Main(stdout, stderr io.Writer, interposer string, args []string, opts mainOpts) (int, error) {
+ base := filepath.Base(args[0])
+
+ origPathFile := interposer + "_origpath"
+ if base == filepath.Base(interposer) {
+ return 1, usage
+ }
+
+ origPath, err := ioutil.ReadFile(origPathFile)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return 1, usage
+ } else {
+ return 1, fmt.Errorf("Failed to read original PATH: %v", err)
+ }
+ }
+
+ cmd := &exec.Cmd{
+ Args: args,
+ Env: os.Environ(),
+
+ Stdin: os.Stdin,
+ Stdout: stdout,
+ Stderr: stderr,
+ }
+
+ if err := os.Setenv("PATH", string(origPath)); err != nil {
+ return 1, fmt.Errorf("Failed to set PATH env: %v", err)
+ }
+
+ if config := opts.config(base); config.Log || config.Error {
+ var procs []paths.LogProcess
+ if opts.lookupParents != nil {
+ procs = opts.lookupParents()
+ }
+
+ if opts.sendLog != nil {
+ waitForLog := make(chan interface{})
+ opts.sendLog(interposer+"_log", &paths.LogEntry{
+ Basename: base,
+ Args: args,
+ Parents: procs,
+ }, waitForLog)
+ defer func() { <-waitForLog }()
+ }
+ if config.Error && !opts.disableError {
+ return 1, fmt.Errorf("%q is not allowed to be used. See https://android.googlesource.com/platform/build/+/master/Changes.md#PATH_Tools for more information.", base)
+ }
+ }
+
+ cmd.Path, err = exec.LookPath(base)
+ if err != nil {
+ return 1, err
+ }
+
+ if err = cmd.Run(); err != nil {
+ if exitErr, ok := err.(*exec.ExitError); ok {
+ if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
+ if status.Exited() {
+ return status.ExitStatus(), nil
+ } else if status.Signaled() {
+ exitCode := 128 + int(status.Signal())
+ return exitCode, nil
+ } else {
+ return 1, exitErr
+ }
+ } else {
+ return 1, nil
+ }
+ }
+ }
+
+ return 0, nil
+}
+
+type procEntry struct {
+ Pid int
+ Ppid int
+ Command string
+}
+
+func readProcs() map[int]procEntry {
+ cmd := exec.Command("ps", "-o", "pid,ppid,command")
+ data, err := cmd.Output()
+ if err != nil {
+ return nil
+ }
+
+ return parseProcs(data)
+}
+
+func parseProcs(data []byte) map[int]procEntry {
+ lines := bytes.Split(data, []byte("\n"))
+ if len(lines) < 2 {
+ return nil
+ }
+ // Remove the header
+ lines = lines[1:]
+
+ ret := make(map[int]procEntry, len(lines))
+ for _, line := range lines {
+ fields := bytes.SplitN(line, []byte(" "), 2)
+ if len(fields) != 2 {
+ continue
+ }
+
+ pid, err := strconv.Atoi(string(fields[0]))
+ if err != nil {
+ continue
+ }
+
+ line = bytes.TrimLeft(fields[1], " ")
+
+ fields = bytes.SplitN(line, []byte(" "), 2)
+ if len(fields) != 2 {
+ continue
+ }
+
+ ppid, err := strconv.Atoi(string(fields[0]))
+ if err != nil {
+ continue
+ }
+
+ ret[pid] = procEntry{
+ Pid: pid,
+ Ppid: ppid,
+ Command: string(bytes.TrimLeft(fields[1], " ")),
+ }
+ }
+
+ return ret
+}
+
+func lookupParents() []paths.LogProcess {
+ procs := readProcs()
+ if procs == nil {
+ return nil
+ }
+
+ list := []paths.LogProcess{}
+ pid := os.Getpid()
+ for {
+ entry, ok := procs[pid]
+ if !ok {
+ break
+ }
+
+ list = append([]paths.LogProcess{
+ {
+ Pid: pid,
+ Command: entry.Command,
+ },
+ }, list...)
+
+ pid = entry.Ppid
+ }
+
+ return list
+}
diff --git a/cmd/path_interposer/main_test.go b/cmd/path_interposer/main_test.go
new file mode 100644
index 0000000..c89d623
--- /dev/null
+++ b/cmd/path_interposer/main_test.go
@@ -0,0 +1,196 @@
+// Copyright 2018 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 (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "android/soong/ui/build/paths"
+)
+
+var tmpDir string
+var origPATH string
+
+func TestMain(m *testing.M) {
+ os.Exit(func() int {
+ var err error
+ tmpDir, err = ioutil.TempDir("", "interposer_test")
+ if err != nil {
+ panic(err)
+ }
+ defer os.RemoveAll(tmpDir)
+
+ origPATH = os.Getenv("PATH")
+ err = os.Setenv("PATH", "")
+ if err != nil {
+ panic(err)
+ }
+
+ return m.Run()
+ }())
+}
+
+func setup(t *testing.T) string {
+ f, err := ioutil.TempFile(tmpDir, "interposer")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer f.Close()
+
+ err = ioutil.WriteFile(f.Name()+"_origpath", []byte(origPATH), 0666)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return f.Name()
+}
+
+func TestInterposer(t *testing.T) {
+ interposer := setup(t)
+
+ logConfig := func(name string) paths.PathConfig {
+ if name == "true" {
+ return paths.PathConfig{
+ Log: false,
+ Error: false,
+ }
+ } else if name == "path_interposer_test_not_allowed" {
+ return paths.PathConfig{
+ Log: false,
+ Error: true,
+ }
+ }
+ return paths.PathConfig{
+ Log: true,
+ Error: false,
+ }
+ }
+
+ testCases := []struct {
+ name string
+ args []string
+
+ exitCode int
+ err error
+ logEntry string
+ }{
+ {
+ name: "direct call",
+ args: []string{interposer},
+
+ exitCode: 1,
+ err: usage,
+ },
+ {
+ name: "relative call",
+ args: []string{filepath.Base(interposer)},
+
+ exitCode: 1,
+ err: usage,
+ },
+ {
+ name: "true",
+ args: []string{"/my/path/true"},
+ },
+ {
+ name: "relative true",
+ args: []string{"true"},
+ },
+ {
+ name: "exit code",
+ args: []string{"bash", "-c", "exit 42"},
+
+ exitCode: 42,
+ logEntry: "bash",
+ },
+ {
+ name: "signal",
+ args: []string{"bash", "-c", "kill -9 $$"},
+
+ exitCode: 137,
+ logEntry: "bash",
+ },
+ {
+ name: "does not exist",
+ args: []string{"path_interposer_test_does_not_exist"},
+
+ exitCode: 1,
+ err: fmt.Errorf(`exec: "path_interposer_test_does_not_exist": executable file not found in $PATH`),
+ logEntry: "path_interposer_test_does_not_exist",
+ },
+ {
+ name: "not allowed",
+ args: []string{"path_interposer_test_not_allowed"},
+
+ exitCode: 1,
+ err: fmt.Errorf(`"path_interposer_test_not_allowed" is not allowed to be used. See https://android.googlesource.com/platform/build/+/master/Changes.md#PATH_Tools for more information.`),
+ logEntry: "path_interposer_test_not_allowed",
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ logged := false
+ logFunc := func(logSocket string, entry *paths.LogEntry, done chan interface{}) {
+ defer close(done)
+
+ logged = true
+ if entry.Basename != testCase.logEntry {
+ t.Errorf("unexpected log entry:\nwant: %q\n got: %q", testCase.logEntry, entry.Basename)
+ }
+ }
+
+ exitCode, err := Main(ioutil.Discard, ioutil.Discard, interposer, testCase.args, mainOpts{
+ sendLog: logFunc,
+ config: logConfig,
+ })
+
+ errstr := func(err error) string {
+ if err == nil {
+ return ""
+ }
+ return err.Error()
+ }
+ if errstr(testCase.err) != errstr(err) {
+ t.Errorf("unexpected error:\nwant: %v\n got: %v", testCase.err, err)
+ }
+ if testCase.exitCode != exitCode {
+ t.Errorf("expected exit code %d, got %d", testCase.exitCode, exitCode)
+ }
+ if !logged && testCase.logEntry != "" {
+ t.Errorf("no log entry, but expected %q", testCase.logEntry)
+ }
+ })
+ }
+}
+
+func TestMissingPath(t *testing.T) {
+ interposer := setup(t)
+ err := os.Remove(interposer + "_origpath")
+ if err != nil {
+ t.Fatal("Failed to remove:", err)
+ }
+
+ exitCode, err := Main(ioutil.Discard, ioutil.Discard, interposer, []string{"true"}, mainOpts{})
+ if err != usage {
+ t.Errorf("Unexpected error:\n got: %v\nwant: %v", err, usage)
+ }
+ if exitCode != 1 {
+ t.Errorf("expected exit code %d, got %d", 1, exitCode)
+ }
+}
diff --git a/cmd/pom2bp/pom2bp.go b/cmd/pom2bp/pom2bp.go
index 078a07d..9ce6b50 100644
--- a/cmd/pom2bp/pom2bp.go
+++ b/cmd/pom2bp/pom2bp.go
@@ -15,6 +15,7 @@
package main
import (
+ "archive/zip"
"bufio"
"bytes"
"encoding/xml"
@@ -138,9 +139,10 @@
type Pom struct {
XMLName xml.Name `xml:"http://maven.apache.org/POM/4.0.0 project"`
- PomFile string `xml:"-"`
- ArtifactFile string `xml:"-"`
- BpTarget string `xml:"-"`
+ PomFile string `xml:"-"`
+ ArtifactFile string `xml:"-"`
+ BpTarget string `xml:"-"`
+ MinSdkVersion string `xml:"-"`
GroupId string `xml:"groupId"`
ArtifactId string `xml:"artifactId"`
@@ -215,11 +217,61 @@
}
}
+// ExtractMinSdkVersion extracts the minSdkVersion from the AndroidManifest.xml file inside an aar file, or sets it
+// to "current" if it is not present.
+func (p *Pom) ExtractMinSdkVersion() error {
+ aar, err := zip.OpenReader(p.ArtifactFile)
+ if err != nil {
+ return err
+ }
+ defer aar.Close()
+
+ var manifest *zip.File
+ for _, f := range aar.File {
+ if f.Name == "AndroidManifest.xml" {
+ manifest = f
+ break
+ }
+ }
+
+ if manifest == nil {
+ return fmt.Errorf("failed to find AndroidManifest.xml in %s", p.ArtifactFile)
+ }
+
+ r, err := manifest.Open()
+ if err != nil {
+ return err
+ }
+ defer r.Close()
+
+ decoder := xml.NewDecoder(r)
+
+ manifestData := struct {
+ XMLName xml.Name `xml:"manifest"`
+ Uses_sdk struct {
+ MinSdkVersion string `xml:"http://schemas.android.com/apk/res/android minSdkVersion,attr"`
+ } `xml:"uses-sdk"`
+ }{}
+
+ err = decoder.Decode(&manifestData)
+ if err != nil {
+ return err
+ }
+
+ p.MinSdkVersion = manifestData.Uses_sdk.MinSdkVersion
+ if p.MinSdkVersion == "" {
+ p.MinSdkVersion = "current"
+ }
+
+ return nil
+}
+
var bpTemplate = template.Must(template.New("bp").Parse(`
{{if .IsAar}}android_library_import{{else}}java_import{{end}} {
name: "{{.BpName}}-nodeps",
{{if .IsAar}}aars{{else}}jars{{end}}: ["{{.ArtifactFile}}"],
sdk_version: "{{.SdkVersion}}",{{if .IsAar}}
+ min_sdk_version: "{{.MinSdkVersion}}",
static_libs: [{{range .BpAarDeps}}
"{{.}}",{{end}}{{range .BpExtraDeps}}
"{{.}}",{{end}}
@@ -229,6 +281,7 @@
{{if .IsAar}}android_library{{else}}java_library_static{{end}} {
name: "{{.BpName}}",
sdk_version: "{{.SdkVersion}}",{{if .IsAar}}
+ min_sdk_version: "{{.MinSdkVersion}}",
manifest: "manifests/{{.BpName}}/AndroidManifest.xml",{{end}}
static_libs: [
"{{.BpName}}-nodeps",{{range .BpJarDeps}}
@@ -302,7 +355,7 @@
// Append all current command line args except -regen <file> to the ones from the file
for i := 1; i < len(os.Args); i++ {
- if os.Args[i] == "-regen" {
+ if os.Args[i] == "-regen" || os.Args[i] == "--regen" {
i++
} else {
args = append(args, os.Args[i])
@@ -468,6 +521,13 @@
}
for _, pom := range poms {
+ if pom.IsAar() {
+ err := pom.ExtractMinSdkVersion()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error reading manifest for %s: %s", pom.ArtifactFile, err)
+ os.Exit(1)
+ }
+ }
pom.FixDeps(modules)
}
diff --git a/cmd/soong_ui/Android.bp b/cmd/soong_ui/Android.bp
index f09e42e..4e57bef 100644
--- a/cmd/soong_ui/Android.bp
+++ b/cmd/soong_ui/Android.bp
@@ -17,6 +17,7 @@
deps: [
"soong-ui-build",
"soong-ui-logger",
+ "soong-ui-terminal",
"soong-ui-tracer",
],
srcs: [
diff --git a/cmd/soong_ui/main.go b/cmd/soong_ui/main.go
index 2ca7ebf..47682ff 100644
--- a/cmd/soong_ui/main.go
+++ b/cmd/soong_ui/main.go
@@ -26,6 +26,8 @@
"android/soong/ui/build"
"android/soong/ui/logger"
+ "android/soong/ui/status"
+ "android/soong/ui/terminal"
"android/soong/ui/tracer"
)
@@ -44,7 +46,18 @@
}
func main() {
- log := logger.New(os.Stderr)
+ var stdio terminal.StdioInterface
+ stdio = terminal.StdioImpl{}
+
+ // dumpvar uses stdout, everything else should be in stderr
+ if os.Args[1] == "--dumpvar-mode" || os.Args[1] == "--dumpvars-mode" {
+ stdio = terminal.NewCustomStdio(os.Stdin, os.Stderr, os.Stderr)
+ }
+
+ writer := terminal.NewWriter(stdio)
+ defer writer.Finish()
+
+ log := logger.New(writer)
defer log.Cleanup()
if len(os.Args) < 2 || !(inList("--make-mode", os.Args) ||
@@ -60,16 +73,23 @@
trace := tracer.New(log)
defer trace.Close()
+ stat := &status.Status{}
+ defer stat.Finish()
+ stat.AddOutput(terminal.NewStatusOutput(writer, os.Getenv("NINJA_STATUS")))
+ stat.AddOutput(trace.StatusTracer())
+
build.SetupSignals(log, cancel, func() {
trace.Close()
log.Cleanup()
+ stat.Finish()
})
buildCtx := build.Context{&build.ContextImpl{
- Context: ctx,
- Logger: log,
- Tracer: trace,
- StdioInterface: build.StdioImpl{},
+ Context: ctx,
+ Logger: log,
+ Tracer: trace,
+ Writer: writer,
+ Status: stat,
}}
var config build.Config
if os.Args[1] == "--dumpvars-mode" || os.Args[1] == "--dumpvar-mode" {
@@ -78,19 +98,19 @@
config = build.NewConfig(buildCtx, os.Args[1:]...)
}
- log.SetVerbose(config.IsVerbose())
build.SetupOutDir(buildCtx, config)
+ logsDir := config.OutDir()
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"))
+ logsDir = filepath.Join(config.DistDir(), "logs")
}
+ os.MkdirAll(logsDir, 0777)
+ log.SetOutput(filepath.Join(logsDir, "soong.log"))
+ trace.SetOutput(filepath.Join(logsDir, "build.trace"))
+ stat.AddOutput(status.NewVerboseLog(log, filepath.Join(logsDir, "verbose.log")))
+ stat.AddOutput(status.NewErrorLog(log, filepath.Join(logsDir, "error.log")))
+
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 {
@@ -114,6 +134,17 @@
} else if os.Args[1] == "--dumpvars-mode" {
dumpVars(buildCtx, config, os.Args[2:])
} else {
+ if config.IsVerbose() {
+ writer.Print("! The argument `showcommands` is no longer supported.")
+ writer.Print("! Instead, the verbose log is always written to a compressed file in the output dir:")
+ writer.Print("!")
+ writer.Print(fmt.Sprintf("! gzip -cd %s/verbose.log.gz | less -R", logsDir))
+ writer.Print("!")
+ writer.Print("! Older versions are saved in verbose.log.#.gz files")
+ writer.Print("")
+ time.Sleep(5 * time.Second)
+ }
+
toBuild := build.BuildAll
if config.Checkbuild() {
toBuild |= build.RunBuildTests
diff --git a/genrule/genrule.go b/genrule/genrule.go
index 42be88f..03d4ea6 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -163,8 +163,6 @@
if len(g.properties.Tools) > 0 {
ctx.VisitDirectDepsBlueprint(func(module blueprint.Module) {
switch ctx.OtherModuleDependencyTag(module) {
- case android.SourceDepTag:
- // Nothing to do
case hostToolDepTag:
tool := ctx.OtherModuleName(module)
var path android.OptionalPath
@@ -201,8 +199,6 @@
} else {
ctx.ModuleErrorf("host tool %q missing output file", tool)
}
- default:
- ctx.ModuleErrorf("unknown dependency on %q", ctx.OtherModuleName(module))
}
})
}
@@ -227,13 +223,18 @@
task := g.taskGenerator(ctx, String(g.properties.Cmd), srcFiles)
rawCommand, err := android.Expand(task.cmd, func(name string) (string, error) {
+ // report the error directly without returning an error to android.Expand to catch multiple errors in a
+ // single run
+ reportError := func(fmt string, args ...interface{}) (string, error) {
+ ctx.PropertyErrorf("cmd", fmt, args...)
+ return "SOONG_ERROR", nil
+ }
+
switch name {
case "location":
if len(g.properties.Tools) == 0 && len(toolFiles) == 0 {
- return "", fmt.Errorf("at least one `tools` or `tool_files` is required if $(location) is used")
- }
-
- if len(g.properties.Tools) > 0 {
+ return reportError("at least one `tools` or `tool_files` is required if $(location) is used")
+ } else if len(g.properties.Tools) > 0 {
return tools[g.properties.Tools[0]].String(), nil
} else {
return tools[toolFiles[0].Rel()].String(), nil
@@ -245,7 +246,7 @@
case "depfile":
referencedDepfile = true
if !Bool(g.properties.Depfile) {
- return "", fmt.Errorf("$(depfile) used without depfile property")
+ return reportError("$(depfile) used without depfile property")
}
return "__SBOX_DEPFILE__", nil
case "genDir":
@@ -256,22 +257,22 @@
if tool, ok := tools[label]; ok {
return tool.String(), nil
} else {
- return "", fmt.Errorf("unknown location label %q", label)
+ return reportError("unknown location label %q", label)
}
}
- return "", fmt.Errorf("unknown variable '$(%s)'", name)
+ return reportError("unknown variable '$(%s)'", name)
}
})
- if Bool(g.properties.Depfile) && !referencedDepfile {
- ctx.PropertyErrorf("cmd", "specified depfile=true but did not include a reference to '${depfile}' in cmd")
- }
-
if err != nil {
ctx.PropertyErrorf("cmd", "%s", err.Error())
return
}
+ if Bool(g.properties.Depfile) && !referencedDepfile {
+ ctx.PropertyErrorf("cmd", "specified depfile=true but did not include a reference to '${depfile}' in cmd")
+ }
+
// tell the sbox command which directory to use as its sandbox root
buildDir := android.PathForOutput(ctx).String()
sandboxPath := shared.TempDirForOutDir(buildDir)
diff --git a/java/aar.go b/java/aar.go
index 9e5cddb..0cfc585 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -27,6 +27,7 @@
ExportPackage() android.Path
ExportedProguardFlagFiles() android.Paths
ExportedStaticPackages() android.Paths
+ ExportedManifest() android.Path
}
func init() {
@@ -74,8 +75,8 @@
return a.exportPackage
}
-func (a *aapt) aapt2Flags(ctx android.ModuleContext, sdkVersion string) (flags []string, deps android.Paths,
- resDirs, overlayDirs []globbedResourceDir, overlayFiles, rroDirs android.Paths, manifestPath android.Path) {
+func (a *aapt) aapt2Flags(ctx android.ModuleContext, sdkContext sdkContext, manifestPath android.Path) (flags []string,
+ deps android.Paths, resDirs, overlayDirs []globbedResourceDir, rroDirs android.Paths) {
hasVersionCode := false
hasVersionName := false
@@ -116,29 +117,17 @@
assetFiles = append(assetFiles, androidResourceGlob(ctx, dir)...)
}
- // App manifest file
- manifestFile := proptools.StringDefault(a.aaptProperties.Manifest, "AndroidManifest.xml")
- manifestPath = android.PathForModuleSrc(ctx, manifestFile)
linkFlags = append(linkFlags, "--manifest "+manifestPath.String())
linkDeps = append(linkDeps, manifestPath)
linkFlags = append(linkFlags, android.JoinWithPrefix(assetDirs.Strings(), "-A "))
linkDeps = append(linkDeps, assetFiles...)
- transitiveStaticLibs, libDeps, libFlags := aaptLibs(ctx, sdkVersion)
-
- overlayFiles = append(overlayFiles, transitiveStaticLibs...)
- linkDeps = append(linkDeps, libDeps...)
- linkFlags = append(linkFlags, libFlags...)
-
// SDK version flags
- switch sdkVersion {
- case "", "current", "system_current", "test_current":
- sdkVersion = proptools.NinjaEscape([]string{ctx.Config().DefaultAppTargetSdk()})[0]
- }
+ minSdkVersion := sdkVersionOrDefault(ctx, sdkContext.minSdkVersion())
- linkFlags = append(linkFlags, "--min-sdk-version "+sdkVersion)
- linkFlags = append(linkFlags, "--target-sdk-version "+sdkVersion)
+ linkFlags = append(linkFlags, "--min-sdk-version "+minSdkVersion)
+ linkFlags = append(linkFlags, "--target-sdk-version "+minSdkVersion)
// Version code
if !hasVersionCode {
@@ -150,8 +139,8 @@
if ctx.ModuleName() == "framework-res" {
// Some builds set AppsDefaultVersionName() to include the build number ("O-123456"). aapt2 copies the
// version name of framework-res into app manifests as compileSdkVersionCodename, which confuses things
- // if it contains the build number. Use the DefaultAppTargetSdk instead.
- versionName = ctx.Config().DefaultAppTargetSdk()
+ // if it contains the build number. Use the PlatformVersionName instead.
+ versionName = ctx.Config().PlatformVersionName()
} else {
versionName = ctx.Config().AppsDefaultVersionName()
}
@@ -159,21 +148,31 @@
linkFlags = append(linkFlags, "--version-name ", versionName)
}
- return linkFlags, linkDeps, resDirs, overlayDirs, overlayFiles, rroDirs, manifestPath
+ return linkFlags, linkDeps, resDirs, overlayDirs, rroDirs
}
-func (a *aapt) deps(ctx android.BottomUpMutatorContext, sdkVersion string) {
+func (a *aapt) deps(ctx android.BottomUpMutatorContext, sdkContext sdkContext) {
if !ctx.Config().UnbundledBuild() {
- sdkDep := decodeSdkDep(ctx, sdkVersion)
+ sdkDep := decodeSdkDep(ctx, sdkContext)
if sdkDep.frameworkResModule != "" {
ctx.AddDependency(ctx.Module(), frameworkResTag, sdkDep.frameworkResModule)
}
}
}
-func (a *aapt) buildActions(ctx android.ModuleContext, sdkVersion string, extraLinkFlags ...string) {
- linkFlags, linkDeps, resDirs, overlayDirs, overlayFiles, rroDirs, manifestPath := a.aapt2Flags(ctx, sdkVersion)
+func (a *aapt) buildActions(ctx android.ModuleContext, sdkContext sdkContext, extraLinkFlags ...string) {
+ transitiveStaticLibs, staticLibManifests, libDeps, libFlags := aaptLibs(ctx, sdkContext)
+ // App manifest file
+ manifestFile := proptools.StringDefault(a.aaptProperties.Manifest, "AndroidManifest.xml")
+ manifestSrcPath := android.PathForModuleSrc(ctx, manifestFile)
+
+ manifestPath := manifestMerger(ctx, manifestSrcPath, sdkContext, staticLibManifests)
+
+ linkFlags, linkDeps, resDirs, overlayDirs, rroDirs := a.aapt2Flags(ctx, sdkContext, manifestPath)
+
+ linkFlags = append(linkFlags, libFlags...)
+ linkDeps = append(linkDeps, libDeps...)
linkFlags = append(linkFlags, extraLinkFlags...)
packageRes := android.PathForModuleOut(ctx, "package-res.apk")
@@ -191,7 +190,7 @@
compiledOverlay = append(compiledOverlay, aapt2Compile(ctx, dir.dir, dir.files).Paths()...)
}
- compiledOverlay = append(compiledOverlay, overlayFiles...)
+ compiledOverlay = append(compiledOverlay, transitiveStaticLibs...)
aapt2Link(ctx, packageRes, srcJar, proguardOptionsFile, rTxt, extraPackages,
linkFlags, linkDeps, compiledRes, compiledOverlay)
@@ -206,14 +205,14 @@
}
// aaptLibs collects libraries from dependencies and sdk_version and converts them into paths
-func aaptLibs(ctx android.ModuleContext, sdkVersion string) (transitiveStaticLibs, deps android.Paths,
- flags []string) {
+func aaptLibs(ctx android.ModuleContext, sdkContext sdkContext) (transitiveStaticLibs, staticLibManifests,
+ deps android.Paths, flags []string) {
var sharedLibs android.Paths
- sdkDep := decodeSdkDep(ctx, sdkVersion)
+ sdkDep := decodeSdkDep(ctx, sdkContext)
if sdkDep.useFiles {
- sharedLibs = append(sharedLibs, sdkDep.jar)
+ sharedLibs = append(sharedLibs, sdkDep.jars...)
}
ctx.VisitDirectDeps(func(module android.Module) {
@@ -232,6 +231,7 @@
if exportPackage != nil {
transitiveStaticLibs = append(transitiveStaticLibs, exportPackage)
transitiveStaticLibs = append(transitiveStaticLibs, aarDep.ExportedStaticPackages()...)
+ staticLibManifests = append(staticLibManifests, aarDep.ExportedManifest())
}
}
})
@@ -249,7 +249,7 @@
transitiveStaticLibs = android.FirstUniquePaths(transitiveStaticLibs)
- return transitiveStaticLibs, deps, flags
+ return transitiveStaticLibs, staticLibManifests, deps, flags
}
type AndroidLibrary struct {
@@ -272,17 +272,21 @@
return a.exportedStaticPackages
}
+func (a *AndroidLibrary) ExportedManifest() android.Path {
+ return a.manifestPath
+}
+
var _ AndroidLibraryDependency = (*AndroidLibrary)(nil)
func (a *AndroidLibrary) DepsMutator(ctx android.BottomUpMutatorContext) {
a.Module.deps(ctx)
if !Bool(a.properties.No_framework_libs) && !Bool(a.properties.No_standard_libs) {
- a.aapt.deps(ctx, String(a.deviceProperties.Sdk_version))
+ a.aapt.deps(ctx, sdkContext(a))
}
}
func (a *AndroidLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
- a.aapt.buildActions(ctx, String(a.deviceProperties.Sdk_version), "--static-lib")
+ a.aapt.buildActions(ctx, sdkContext(a), "--static-lib")
ctx.CheckbuildFile(a.proguardOptionsFile)
ctx.CheckbuildFile(a.exportPackage)
@@ -338,7 +342,8 @@
type AARImportProperties struct {
Aars []string
- Sdk_version *string
+ Sdk_version *string
+ Min_sdk_version *string
Static_libs []string
Libs []string
@@ -354,10 +359,22 @@
proguardFlags android.WritablePath
exportPackage android.WritablePath
extraAaptPackagesFile android.WritablePath
+ manifest android.WritablePath
exportedStaticPackages android.Paths
}
+func (a *AARImport) sdkVersion() string {
+ return String(a.properties.Sdk_version)
+}
+
+func (a *AARImport) minSdkVersion() string {
+ if a.properties.Min_sdk_version != nil {
+ return *a.properties.Min_sdk_version
+ }
+ return a.sdkVersion()
+}
+
var _ AndroidLibraryDependency = (*AARImport)(nil)
func (a *AARImport) ExportPackage() android.Path {
@@ -372,6 +389,10 @@
return a.exportedStaticPackages
}
+func (a *AARImport) ExportedManifest() android.Path {
+ return a.manifest
+}
+
func (a *AARImport) Prebuilt() *android.Prebuilt {
return &a.prebuilt
}
@@ -382,7 +403,7 @@
func (a *AARImport) DepsMutator(ctx android.BottomUpMutatorContext) {
if !ctx.Config().UnbundledBuild() {
- sdkDep := decodeSdkDep(ctx, String(a.properties.Sdk_version))
+ sdkDep := decodeSdkDep(ctx, sdkContext(a))
if sdkDep.useModule && sdkDep.frameworkResModule != "" {
ctx.AddDependency(ctx.Module(), frameworkResTag, sdkDep.frameworkResModule)
}
@@ -413,12 +434,12 @@
extractedResDir := extractedAARDir.Join(ctx, "res")
a.classpathFile = extractedAARDir.Join(ctx, "classes.jar")
a.proguardFlags = extractedAARDir.Join(ctx, "proguard.txt")
- manifest := extractedAARDir.Join(ctx, "AndroidManifest.xml")
+ a.manifest = extractedAARDir.Join(ctx, "AndroidManifest.xml")
ctx.Build(pctx, android.BuildParams{
Rule: unzipAAR,
Input: aar,
- Outputs: android.WritablePaths{a.classpathFile, a.proguardFlags, manifest},
+ Outputs: android.WritablePaths{a.classpathFile, a.proguardFlags, a.manifest},
Description: "unzip AAR",
Args: map[string]string{
"expectedDirs": extractedResDir.String(),
@@ -446,10 +467,12 @@
"--auto-add-overlay",
}
- linkFlags = append(linkFlags, "--manifest "+manifest.String())
- linkDeps = append(linkDeps, manifest)
+ linkFlags = append(linkFlags, "--manifest "+a.manifest.String())
+ linkDeps = append(linkDeps, a.manifest)
- transitiveStaticLibs, libDeps, libFlags := aaptLibs(ctx, String(a.properties.Sdk_version))
+ transitiveStaticLibs, staticLibManifests, libDeps, libFlags := aaptLibs(ctx, sdkContext(a))
+
+ _ = staticLibManifests
linkDeps = append(linkDeps, libDeps...)
linkFlags = append(linkFlags, libFlags...)
@@ -474,6 +497,10 @@
return nil
}
+func (a *AARImport) ExportedSdkLibs() []string {
+ return nil
+}
+
var _ android.PrebuiltInterface = (*Import)(nil)
func AARImportFactory() android.Module {
diff --git a/java/android_manifest.go b/java/android_manifest.go
new file mode 100644
index 0000000..8fcdcba
--- /dev/null
+++ b/java/android_manifest.go
@@ -0,0 +1,71 @@
+// Copyright 2018 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 java
+
+import (
+ "android/soong/java/config"
+
+ "github.com/google/blueprint"
+
+ "android/soong/android"
+)
+
+var manifestFixerRule = pctx.AndroidStaticRule("manifestFixer",
+ blueprint.RuleParams{
+ Command: `${config.ManifestFixerCmd} --minSdkVersion ${minSdkVersion} $usesLibraries $in $out`,
+ CommandDeps: []string{"${config.ManifestFixerCmd}"},
+ },
+ "minSdkVersion", "usesLibraries")
+
+var manifestMergerRule = pctx.AndroidStaticRule("manifestMerger",
+ blueprint.RuleParams{
+ Command: `${config.JavaCmd} -classpath ${config.ManifestMergerClasspath} com.android.manifmerger.Merger ` +
+ `--main $in $libs --out $out`,
+ CommandDeps: config.ManifestMergerClasspath,
+ },
+ "libs")
+
+func manifestMerger(ctx android.ModuleContext, manifest android.Path, sdkContext sdkContext,
+ staticLibManifests android.Paths) android.Path {
+
+ // Inject minSdkVersion into the manifest
+ fixedManifest := android.PathForModuleOut(ctx, "manifest_fixer", "AndroidManifest.xml")
+ ctx.Build(pctx, android.BuildParams{
+ Rule: manifestFixerRule,
+ Input: manifest,
+ Output: fixedManifest,
+ Args: map[string]string{
+ "minSdkVersion": sdkVersionOrDefault(ctx, sdkContext.minSdkVersion()),
+ },
+ })
+ manifest = fixedManifest
+
+ // Merge static aar dependency manifests if necessary
+ if len(staticLibManifests) > 0 {
+ mergedManifest := android.PathForModuleOut(ctx, "manifest_merger", "AndroidManifest.xml")
+ ctx.Build(pctx, android.BuildParams{
+ Rule: manifestMergerRule,
+ Input: manifest,
+ Implicits: staticLibManifests,
+ Output: mergedManifest,
+ Args: map[string]string{
+ "libs": android.JoinWithPrefix(staticLibManifests.Strings(), "--uses-library "),
+ },
+ })
+ manifest = mergedManifest
+ }
+
+ return manifest
+}
diff --git a/java/androidmk.go b/java/androidmk.go
index b168f2c..5740eca 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -37,7 +37,7 @@
fmt.Fprintln(w, "LOCAL_LOGTAGS_FILES :=", strings.Join(logtags, " "))
}
- if library.properties.Installable != nil && *library.properties.Installable == false {
+ if library.installFile == nil {
fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := true")
}
if library.dexJarFile != nil {
@@ -56,13 +56,17 @@
fmt.Fprintln(w, "LOCAL_DEX_PREOPT_PROFILE_CLASS_LISTING := $(LOCAL_PATH)/"+*library.deviceProperties.Dex_preopt.Profile)
}
}
- fmt.Fprintln(w, "LOCAL_SDK_VERSION :=", String(library.deviceProperties.Sdk_version))
+ fmt.Fprintln(w, "LOCAL_SDK_VERSION :=", library.sdkVersion())
fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", library.headerJarFile.String())
if library.jacocoReportClassesFile != nil {
fmt.Fprintln(w, "LOCAL_SOONG_JACOCO_REPORT_CLASSES_JAR :=", library.jacocoReportClassesFile.String())
}
+ if len(library.exportedSdkLibs) != 0 {
+ fmt.Fprintln(w, "LOCAL_EXPORT_SDK_LIBRARIES :=", strings.Join(library.exportedSdkLibs, " "))
+ }
+
// Temporary hack: export sources used to compile framework.jar to Make
// to be used for droiddoc
// TODO(ccross): remove this once droiddoc is in soong
@@ -81,7 +85,7 @@
fmt.Fprintln(w, "LOCAL_IS_HOST_MODULE := true")
fmt.Fprintln(w, "LOCAL_MODULE_CLASS := JAVA_LIBRARIES")
fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", library.implementationJarFile.String())
- if library.properties.Installable != nil && *library.properties.Installable == false {
+ if library.installFile == nil {
fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := true")
}
if library.dexJarFile != nil {
@@ -117,7 +121,7 @@
func(w io.Writer, outputFile android.Path) {
fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := ", !Bool(prebuilt.properties.Installable))
fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", prebuilt.combinedClasspathFile.String())
- fmt.Fprintln(w, "LOCAL_SDK_VERSION :=", String(prebuilt.properties.Sdk_version))
+ fmt.Fprintln(w, "LOCAL_SDK_VERSION :=", prebuilt.sdkVersion())
},
},
}
@@ -136,7 +140,8 @@
fmt.Fprintln(w, "LOCAL_SOONG_RESOURCE_EXPORT_PACKAGE :=", prebuilt.exportPackage.String())
fmt.Fprintln(w, "LOCAL_SOONG_EXPORT_PROGUARD_FLAGS :=", prebuilt.proguardFlags.String())
fmt.Fprintln(w, "LOCAL_SOONG_STATIC_LIBRARY_EXTRA_PACKAGES :=", prebuilt.extraAaptPackagesFile.String())
- fmt.Fprintln(w, "LOCAL_SDK_VERSION :=", String(prebuilt.properties.Sdk_version))
+ fmt.Fprintln(w, "LOCAL_FULL_MANIFEST_FILE :=", prebuilt.manifest.String())
+ fmt.Fprintln(w, "LOCAL_SDK_VERSION :=", prebuilt.sdkVersion())
},
},
}
@@ -228,6 +233,19 @@
}
}
+func (a *AndroidTest) AndroidMk() android.AndroidMkData {
+ data := a.AndroidApp.AndroidMk()
+ data.Extra = append(data.Extra, func(w io.Writer, outputFile android.Path) {
+ fmt.Fprintln(w, "LOCAL_MODULE_TAGS := tests")
+ if len(a.testProperties.Test_suites) > 0 {
+ fmt.Fprintln(w, "LOCAL_COMPATIBILITY_SUITE :=",
+ strings.Join(a.testProperties.Test_suites, " "))
+ }
+ })
+
+ return data
+}
+
func (a *AndroidLibrary) AndroidMk() android.AndroidMkData {
data := a.Library.AndroidMk()
@@ -286,26 +304,55 @@
if ddoc.Javadoc.stubsSrcJar != nil {
fmt.Fprintln(w, "LOCAL_DROIDDOC_STUBS_SRCJAR := ", ddoc.Javadoc.stubsSrcJar.String())
}
+ if ddoc.checkCurrentApiTimestamp != nil {
+ fmt.Fprintln(w, ".PHONY:", ddoc.Name()+"-check-current-api")
+ fmt.Fprintln(w, ddoc.Name()+"-check-current-api:",
+ ddoc.checkCurrentApiTimestamp.String())
+
+ fmt.Fprintln(w, ".PHONY: checkapi")
+ fmt.Fprintln(w, "checkapi:",
+ ddoc.checkCurrentApiTimestamp.String())
+
+ fmt.Fprintln(w, ".PHONY: droidcore")
+ fmt.Fprintln(w, "droidcore: checkapi")
+ }
+ if ddoc.updateCurrentApiTimestamp != nil {
+ fmt.Fprintln(w, ".PHONY:", ddoc.Name(), "-update-current-api")
+ fmt.Fprintln(w, ddoc.Name()+"-update-current-api:",
+ ddoc.updateCurrentApiTimestamp.String())
+
+ fmt.Fprintln(w, ".PHONY: update-api")
+ fmt.Fprintln(w, "update-api:",
+ ddoc.updateCurrentApiTimestamp.String())
+ }
+ if ddoc.checkLastReleasedApiTimestamp != nil {
+ fmt.Fprintln(w, ".PHONY:", ddoc.Name()+"-check-last-released-api")
+ fmt.Fprintln(w, ddoc.Name()+"-check-last-released-api:",
+ ddoc.checkLastReleasedApiTimestamp.String())
+ }
apiFilePrefix := "INTERNAL_PLATFORM_"
if String(ddoc.properties.Api_tag_name) != "" {
apiFilePrefix += String(ddoc.properties.Api_tag_name) + "_"
}
- if String(ddoc.properties.Api_filename) != "" {
+ if ddoc.apiFile != nil {
fmt.Fprintln(w, apiFilePrefix+"API_FILE := ", ddoc.apiFile.String())
}
- if String(ddoc.properties.Private_api_filename) != "" {
+ if ddoc.dexApiFile != nil {
+ fmt.Fprintln(w, apiFilePrefix+"DEX_API_FILE := ", ddoc.dexApiFile.String())
+ }
+ if ddoc.privateApiFile != nil {
fmt.Fprintln(w, apiFilePrefix+"PRIVATE_API_FILE := ", ddoc.privateApiFile.String())
}
- if String(ddoc.properties.Private_dex_api_filename) != "" {
+ if ddoc.privateDexApiFile != nil {
fmt.Fprintln(w, apiFilePrefix+"PRIVATE_DEX_API_FILE := ", ddoc.privateDexApiFile.String())
}
- if String(ddoc.properties.Removed_api_filename) != "" {
+ if ddoc.removedApiFile != nil {
fmt.Fprintln(w, apiFilePrefix+"REMOVED_API_FILE := ", ddoc.removedApiFile.String())
}
- if String(ddoc.properties.Removed_dex_api_filename) != "" {
+ if ddoc.removedDexApiFile != nil {
fmt.Fprintln(w, apiFilePrefix+"REMOVED_DEX_API_FILE := ", ddoc.removedDexApiFile.String())
}
- if String(ddoc.properties.Exact_api_filename) != "" {
+ if ddoc.exactApiFile != nil {
fmt.Fprintln(w, apiFilePrefix+"EXACT_API_FILE := ", ddoc.exactApiFile.String())
}
},
diff --git a/java/app.go b/java/app.go
index ae0592a..9e7530e 100644
--- a/java/app.go
+++ b/java/app.go
@@ -26,6 +26,7 @@
func init() {
android.RegisterModuleType("android_app", AndroidAppFactory)
+ android.RegisterModuleType("android_test", AndroidTestFactory)
}
// AndroidManifest.xml merging
@@ -50,8 +51,6 @@
// list of resource labels to generate individual resource packages
Package_splits []string
-
- Instrumentation_for *string
}
type AndroidApp struct {
@@ -61,6 +60,8 @@
certificate certificate
appProperties appProperties
+
+ extraLinkFlags []string
}
func (a *AndroidApp) ExportedProguardFlagFiles() android.Paths {
@@ -71,6 +72,10 @@
return nil
}
+func (a *AndroidApp) ExportedManifest() android.Path {
+ return a.manifestPath
+}
+
var _ AndroidLibraryDependency = (*AndroidApp)(nil)
type certificate struct {
@@ -80,19 +85,16 @@
func (a *AndroidApp) DepsMutator(ctx android.BottomUpMutatorContext) {
a.Module.deps(ctx)
if !Bool(a.properties.No_framework_libs) && !Bool(a.properties.No_standard_libs) {
- a.aapt.deps(ctx, String(a.deviceProperties.Sdk_version))
+ a.aapt.deps(ctx, sdkContext(a))
}
}
func (a *AndroidApp) GenerateAndroidBuildActions(ctx android.ModuleContext) {
- var linkFlags []string
- if String(a.appProperties.Instrumentation_for) != "" {
- linkFlags = append(linkFlags,
- "--rename-instrumentation-target-package",
- String(a.appProperties.Instrumentation_for))
- } else {
- a.properties.Instrument = true
- }
+ a.generateAndroidBuildActions(ctx)
+}
+
+func (a *AndroidApp) generateAndroidBuildActions(ctx android.ModuleContext) {
+ linkFlags := append([]string(nil), a.extraLinkFlags...)
hasProduct := false
for _, f := range a.aaptProperties.Aaptflags {
@@ -119,7 +121,7 @@
// TODO: LOCAL_PACKAGE_OVERRIDES
// $(addprefix --rename-manifest-package , $(PRIVATE_MANIFEST_PACKAGE_NAME)) \
- a.aapt.buildActions(ctx, String(a.deviceProperties.Sdk_version), linkFlags...)
+ a.aapt.buildActions(ctx, sdkContext(a), linkFlags...)
// apps manifests are handled by aapt, don't let Module see them
a.properties.Manifest = nil
@@ -188,6 +190,9 @@
module.Module.deviceProperties.Optimize.Enabled = proptools.BoolPtr(true)
module.Module.deviceProperties.Optimize.Shrink = proptools.BoolPtr(true)
+ module.Module.properties.Instrument = true
+ module.Module.properties.Installable = proptools.BoolPtr(true)
+
module.AddProperties(
&module.Module.properties,
&module.Module.deviceProperties,
@@ -198,3 +203,45 @@
android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
return module
}
+
+type appTestProperties struct {
+ Instrumentation_for *string
+}
+
+type AndroidTest struct {
+ AndroidApp
+
+ appTestProperties appTestProperties
+
+ testProperties testProperties
+}
+
+func (a *AndroidTest) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+ if String(a.appTestProperties.Instrumentation_for) != "" {
+ a.AndroidApp.extraLinkFlags = append(a.AndroidApp.extraLinkFlags,
+ "--rename-instrumentation-target-package",
+ String(a.appTestProperties.Instrumentation_for))
+ }
+
+ a.generateAndroidBuildActions(ctx)
+}
+
+func AndroidTestFactory() android.Module {
+ module := &AndroidTest{}
+
+ module.Module.deviceProperties.Optimize.Enabled = proptools.BoolPtr(true)
+ module.Module.properties.Installable = proptools.BoolPtr(true)
+
+ module.AddProperties(
+ &module.Module.properties,
+ &module.Module.deviceProperties,
+ &module.Module.protoProperties,
+ &module.aaptProperties,
+ &module.appProperties,
+ &module.appTestProperties,
+ &module.testProperties)
+
+ android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+
+ return module
+}
diff --git a/java/app_test.go b/java/app_test.go
index 6770119..c7c94ec 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -71,7 +71,10 @@
foo := ctx.ModuleForTests("foo", "android_common")
- expectedLinkImplicits := []string{"AndroidManifest.xml"}
+ var expectedLinkImplicits []string
+
+ manifestFixer := foo.Output("manifest_fixer/AndroidManifest.xml")
+ expectedLinkImplicits = append(expectedLinkImplicits, manifestFixer.Output.String())
frameworkRes := ctx.ModuleForTests("framework-res", "android_common")
expectedLinkImplicits = append(expectedLinkImplicits,
diff --git a/java/builder.go b/java/builder.go
index d36e0dc..55be3a6 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -26,7 +26,6 @@
"github.com/google/blueprint"
"android/soong/android"
- "android/soong/java/config"
)
var (
@@ -42,10 +41,11 @@
blueprint.RuleParams{
Command: `rm -rf "$outDir" "$annoDir" "$srcJarDir" && mkdir -p "$outDir" "$annoDir" "$srcJarDir" && ` +
`${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` +
+ `(if [ -s $srcJarDir/list ] || [ -s $out.rsp ] ; then ` +
`${config.SoongJavacWrapper} ${config.JavacWrapper}${config.JavacCmd} ${config.JavacHeapFlags} ${config.CommonJdkFlags} ` +
- `$javacFlags $bootClasspath $classpath ` +
+ `$processorpath $javacFlags $bootClasspath $classpath ` +
`-source $javaVersion -target $javaVersion ` +
- `-d $outDir -s $annoDir @$out.rsp @$srcJarDir/list && ` +
+ `-d $outDir -s $annoDir @$out.rsp @$srcJarDir/list ; fi ) && ` +
`${config.SoongZipCmd} -jar -o $out -C $outDir -D $outDir`,
CommandDeps: []string{
"${config.JavacCmd}",
@@ -56,7 +56,7 @@
Rspfile: "$out.rsp",
RspfileContent: "$in",
},
- "javacFlags", "bootClasspath", "classpath", "srcJars", "srcJarDir",
+ "javacFlags", "bootClasspath", "classpath", "processorpath", "srcJars", "srcJarDir",
"outDir", "annoDir", "javaVersion")
kotlinc = pctx.AndroidGomaStaticRule("kotlinc",
@@ -80,29 +80,6 @@
},
"kotlincFlags", "classpath", "srcJars", "srcJarDir", "outDir", "kotlinJvmTarget")
- errorprone = pctx.AndroidStaticRule("errorprone",
- blueprint.RuleParams{
- Command: `rm -rf "$outDir" "$annoDir" "$srcJarDir" && mkdir -p "$outDir" "$annoDir" "$srcJarDir" && ` +
- `${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` +
- `${config.SoongJavacWrapper} ${config.ErrorProneCmd} ` +
- `$javacFlags $bootClasspath $classpath ` +
- `-source $javaVersion -target $javaVersion ` +
- `-d $outDir -s $annoDir @$out.rsp @$srcJarDir/list && ` +
- `${config.SoongZipCmd} -jar -o $out -C $outDir -D $outDir`,
- CommandDeps: []string{
- "${config.JavaCmd}",
- "${config.ErrorProneJavacJar}",
- "${config.ErrorProneJar}",
- "${config.SoongZipCmd}",
- "${config.ZipSyncCmd}",
- },
- CommandOrderOnly: []string{"${config.SoongJavacWrapper}"},
- Rspfile: "$out.rsp",
- RspfileContent: "$in",
- },
- "javacFlags", "bootClasspath", "classpath", "srcJars", "srcJarDir",
- "outDir", "annoDir", "javaVersion")
-
turbine = pctx.AndroidStaticRule("turbine",
blueprint.RuleParams{
Command: `rm -rf "$outDir" && mkdir -p "$outDir" && ` +
@@ -155,11 +132,13 @@
javacFlags string
bootClasspath classpath
classpath classpath
+ processorPath classpath
systemModules classpath
aidlFlags string
javaVersion string
errorProneExtraJavacFlags string
+ errorProneProcessorPath classpath
kotlincFlags string
kotlincClasspath classpath
@@ -207,26 +186,24 @@
desc += strconv.Itoa(shardIdx)
}
- transformJavaToClasses(ctx, outputFile, shardIdx, srcFiles, srcJars, flags, deps, "javac", desc, javac)
+ transformJavaToClasses(ctx, outputFile, shardIdx, srcFiles, srcJars, flags, deps, "javac", desc)
}
func RunErrorProne(ctx android.ModuleContext, outputFile android.WritablePath,
srcFiles, srcJars android.Paths, flags javaBuilderFlags) {
- if config.ErrorProneJar == "" {
- ctx.ModuleErrorf("cannot build with Error Prone, missing external/error_prone?")
- }
+ flags.processorPath = append(flags.errorProneProcessorPath, flags.processorPath...)
if len(flags.errorProneExtraJavacFlags) > 0 {
if len(flags.javacFlags) > 0 {
- flags.javacFlags = flags.errorProneExtraJavacFlags + " " + flags.javacFlags
+ flags.javacFlags += " " + flags.errorProneExtraJavacFlags
} else {
flags.javacFlags = flags.errorProneExtraJavacFlags
}
}
transformJavaToClasses(ctx, outputFile, -1, srcFiles, srcJars, flags, nil,
- "errorprone", "errorprone", errorprone)
+ "errorprone", "errorprone")
}
func TransformJavaToHeaderClasses(ctx android.ModuleContext, outputFile android.WritablePath,
@@ -243,7 +220,7 @@
// ensure java does not fall back to the default bootclasspath.
bootClasspath = `--bootclasspath ""`
} else {
- bootClasspath = strings.Join(flags.bootClasspath.FormDesugarClasspath("--bootclasspath"), " ")
+ bootClasspath = strings.Join(flags.bootClasspath.FormTurbineClasspath("--bootclasspath"), " ")
}
ctx.Build(pctx, android.BuildParams{
@@ -256,7 +233,7 @@
"javacFlags": flags.javacFlags,
"bootClasspath": bootClasspath,
"srcJars": strings.Join(srcJars.Strings(), " "),
- "classpath": strings.Join(flags.classpath.FormDesugarClasspath("--classpath"), " "),
+ "classpath": strings.Join(flags.classpath.FormTurbineClasspath("--classpath"), " "),
"outDir": android.PathForModuleOut(ctx, "turbine", "classes").String(),
"javaVersion": flags.javaVersion,
},
@@ -275,7 +252,7 @@
func transformJavaToClasses(ctx android.ModuleContext, outputFile android.WritablePath,
shardIdx int, srcFiles, srcJars android.Paths,
flags javaBuilderFlags, deps android.Paths,
- intermediatesDir, desc string, rule blueprint.Rule) {
+ intermediatesDir, desc string) {
deps = append(deps, srcJars...)
@@ -295,6 +272,7 @@
}
deps = append(deps, flags.classpath...)
+ deps = append(deps, flags.processorPath...)
srcJarDir := "srcjars"
outDir := "classes"
@@ -306,7 +284,7 @@
annoDir = filepath.Join(shardDir, annoDir)
}
ctx.Build(pctx, android.BuildParams{
- Rule: rule,
+ Rule: javac,
Description: desc,
Output: outputFile,
Inputs: srcFiles,
@@ -315,6 +293,7 @@
"javacFlags": flags.javacFlags,
"bootClasspath": bootClasspath,
"classpath": flags.classpath.FormJavaClassPath("-classpath"),
+ "processorpath": flags.processorPath.FormJavaClassPath("-processorpath"),
"srcJars": strings.Join(srcJars.Strings(), " "),
"srcJarDir": android.PathForModuleOut(ctx, intermediatesDir, srcJarDir).String(),
"outDir": android.PathForModuleOut(ctx, intermediatesDir, outDir).String(),
@@ -339,7 +318,8 @@
}
func TransformJarsToJar(ctx android.ModuleContext, outputFile android.WritablePath, desc string,
- jars android.Paths, manifest android.OptionalPath, stripDirs bool, dirsToStrip []string) {
+ jars android.Paths, manifest android.OptionalPath, stripDirEntries bool, filesToStrip []string,
+ dirsToStrip []string) {
var deps android.Paths
@@ -349,22 +329,19 @@
deps = append(deps, manifest.Path())
}
- if dirsToStrip != nil {
- for _, dir := range dirsToStrip {
- jarArgs = append(jarArgs, "-stripDir ", dir)
- }
+ for _, dir := range dirsToStrip {
+ jarArgs = append(jarArgs, "-stripDir ", dir)
+ }
+
+ for _, file := range filesToStrip {
+ jarArgs = append(jarArgs, "-stripFile ", file)
}
// Remove any module-info.class files that may have come from prebuilt jars, they cause problems
// for downstream tools like desugar.
jarArgs = append(jarArgs, "-stripFile module-info.class")
- // Remove any kotlin-reflect related files
- // TODO(pszczepaniak): Support kotlin-reflect
- jarArgs = append(jarArgs, "-stripFile \"*.kotlin_module\"")
- jarArgs = append(jarArgs, "-stripFile \"*.kotlin_builtin\"")
-
- if stripDirs {
+ if stripDirEntries {
jarArgs = append(jarArgs, "-D")
}
@@ -419,7 +396,7 @@
}
}
-func (x *classpath) FormDesugarClasspath(optName string) []string {
+func (x *classpath) FormTurbineClasspath(optName string) []string {
if x == nil || *x == nil {
return nil
}
diff --git a/java/config/config.go b/java/config/config.go
index 6633f79..ae497a6 100644
--- a/java/config/config.go
+++ b/java/config/config.go
@@ -30,6 +30,8 @@
DefaultBootclasspathLibraries = []string{"core-oj", "core-libart"}
DefaultSystemModules = "core-system-modules"
DefaultLibraries = []string{"ext", "framework", "okhttp"}
+ DefaultLambdaStubsLibrary = "core-lambda-stubs"
+ SdkLambdaStubsPath = "prebuilts/sdk/tools/core-lambda-stubs.jar"
DefaultJacocoExcludeFilter = []string{"org.junit.*", "org.jacoco.*", "org.mockito.*"}
@@ -40,6 +42,16 @@
"android.car",
"android.car7",
}
+
+ ManifestMergerClasspath = []string{
+ "prebuilts/gradle-plugin/com/android/tools/build/manifest-merger/26.1.0/manifest-merger-26.1.0.jar",
+ "prebuilts/gradle-plugin/com/android/tools/common/26.1.0/common-26.1.0.jar",
+ "prebuilts/gradle-plugin/com/android/tools/sdk-common/26.1.0/sdk-common-26.1.0.jar",
+ "prebuilts/gradle-plugin/com/android/tools/sdklib/26.1.0/sdklib-26.1.0.jar",
+ "prebuilts/gradle-plugin/org/jetbrains/kotlin/kotlin-runtime/1.0.5/kotlin-runtime-1.0.5.jar",
+ "prebuilts/gradle-plugin/org/jetbrains/kotlin/kotlin-stdlib/1.1.3/kotlin-stdlib-1.1.3.jar",
+ "prebuilts/misc/common/guava/guava-21.0.jar",
+ }
)
func init() {
@@ -90,18 +102,7 @@
pctx.HostBinToolVariable("MergeZipsCmd", "merge_zips")
pctx.HostBinToolVariable("Zip2ZipCmd", "zip2zip")
pctx.HostBinToolVariable("ZipSyncCmd", "zipsync")
- pctx.VariableFunc("DxCmd", func(ctx android.PackageVarContext) string {
- config := ctx.Config()
- if config.IsEnvFalse("USE_D8") {
- if config.UnbundledBuild() || config.IsPdkBuild() {
- return "prebuilts/build-tools/common/bin/dx"
- } else {
- return pctx.HostBinToolPath(ctx, "dx").String()
- }
- } else {
- return pctx.HostBinToolPath(ctx, "d8-compat-dx").String()
- }
- })
+ pctx.HostBinToolVariable("ApiCheckCmd", "apicheck")
pctx.HostBinToolVariable("D8Cmd", "d8")
pctx.HostBinToolVariable("R8Cmd", "r8-compat-proguard")
@@ -115,9 +116,9 @@
})
pctx.HostJavaToolVariable("JarjarCmd", "jarjar.jar")
- pctx.HostJavaToolVariable("DesugarJar", "desugar.jar")
pctx.HostJavaToolVariable("JsilverJar", "jsilver.jar")
pctx.HostJavaToolVariable("DoclavaJar", "doclava.jar")
+ pctx.HostJavaToolVariable("MetalavaJar", "metalava.jar")
pctx.HostBinToolVariable("SoongJavacWrapper", "soong_javac_wrapper")
@@ -141,4 +142,9 @@
}
hostBinToolVariableWithPrebuilt("Aapt2Cmd", "prebuilts/sdk/tools", "aapt2")
+
+ pctx.SourcePathVariable("ManifestFixerCmd", "build/soong/scripts/manifest_fixer.py")
+
+ pctx.SourcePathsVariable("ManifestMergerJars", " ", ManifestMergerClasspath...)
+ pctx.SourcePathsVariable("ManifestMergerClasspath", ":", ManifestMergerClasspath...)
}
diff --git a/java/config/error_prone.go b/java/config/error_prone.go
index f203234..48681b5 100644
--- a/java/config/error_prone.go
+++ b/java/config/error_prone.go
@@ -14,34 +14,40 @@
package config
-import "android/soong/android"
+import (
+ "strings"
+
+ "android/soong/android"
+)
var (
// These will be filled out by external/error_prone/soong/error_prone.go if it is available
- ErrorProneJavacJar string
- ErrorProneJar string
- ErrorProneClasspath string
- ErrorProneChecksError string
- ErrorProneFlags string
+ ErrorProneClasspath []string
+ ErrorProneChecksError []string
+ ErrorProneChecksWarning []string
+ ErrorProneChecksDefaultDisabled []string
+ ErrorProneChecksOff []string
+ ErrorProneFlags []string
)
// Wrapper that grabs value of val late so it can be initialized by a later module's init function
-func errorProneVar(name string, val *string) {
+func errorProneVar(name string, val *[]string, sep string) {
pctx.VariableFunc(name, func(android.PackageVarContext) string {
- return *val
+ return strings.Join(*val, sep)
})
}
func init() {
- errorProneVar("ErrorProneJar", &ErrorProneJar)
- errorProneVar("ErrorProneJavacJar", &ErrorProneJavacJar)
- errorProneVar("ErrorProneClasspath", &ErrorProneClasspath)
- errorProneVar("ErrorProneChecksError", &ErrorProneChecksError)
- errorProneVar("ErrorProneFlags", &ErrorProneFlags)
-
- pctx.StaticVariable("ErrorProneCmd",
- "${JavaCmd} -Xmx${JavacHeapSize} -Xbootclasspath/p:${ErrorProneJavacJar} "+
- "-cp ${ErrorProneJar}:${ErrorProneClasspath} "+
- "${ErrorProneFlags} ${CommonJdkFlags} ${ErrorProneChecksError}")
-
+ errorProneVar("ErrorProneClasspath", &ErrorProneClasspath, ":")
+ errorProneVar("ErrorProneChecksError", &ErrorProneChecksError, " ")
+ errorProneVar("ErrorProneChecksWarning", &ErrorProneChecksWarning, " ")
+ errorProneVar("ErrorProneChecksDefaultDisabled", &ErrorProneChecksDefaultDisabled, " ")
+ errorProneVar("ErrorProneChecksOff", &ErrorProneChecksOff, " ")
+ errorProneVar("ErrorProneFlags", &ErrorProneFlags, " ")
+ pctx.StaticVariable("ErrorProneChecks", strings.Join([]string{
+ "${ErrorProneChecksOff}",
+ "${ErrorProneChecksError}",
+ "${ErrorProneChecksWarning}",
+ "${ErrorProneChecksDefaultDisabled}",
+ }, " "))
}
diff --git a/java/config/makevars.go b/java/config/makevars.go
index 27c7daa..275f496 100644
--- a/java/config/makevars.go
+++ b/java/config/makevars.go
@@ -46,31 +46,23 @@
ctx.Strict("JAVADOC", "${JavadocCmd}")
ctx.Strict("COMMON_JDK_FLAGS", "${CommonJdkFlags}")
- if ctx.Config().UseD8Desugar() {
- ctx.Strict("DX", "${D8Cmd}")
- ctx.Strict("DX_COMMAND", "${D8Cmd} -JXms16M -JXmx2048M")
- ctx.Strict("USE_D8_DESUGAR", "true")
- } else {
- ctx.Strict("DX", "${DxCmd}")
- ctx.Strict("DX_COMMAND", "${DxCmd} -JXms16M -JXmx2048M")
- ctx.Strict("USE_D8_DESUGAR", "false")
- }
+ ctx.Strict("DX", "${D8Cmd}")
+ ctx.Strict("DX_COMMAND", "${D8Cmd} -JXms16M -JXmx2048M")
ctx.Strict("R8_COMPAT_PROGUARD", "${R8Cmd}")
ctx.Strict("TURBINE", "${TurbineJar}")
- if ctx.Config().IsEnvTrue("RUN_ERROR_PRONE") {
- ctx.Strict("TARGET_JAVAC", "${ErrorProneCmd}")
- ctx.Strict("HOST_JAVAC", "${ErrorProneCmd}")
- } else {
- ctx.Strict("TARGET_JAVAC", "${JavacCmd} ${CommonJdkFlags}")
- ctx.Strict("HOST_JAVAC", "${JavacCmd} ${CommonJdkFlags}")
+ if ctx.Config().RunErrorProne() {
+ ctx.Strict("ERROR_PRONE_JARS", strings.Join(ErrorProneClasspath, " "))
+ ctx.Strict("ERROR_PRONE_FLAGS", "${ErrorProneFlags}")
+ ctx.Strict("ERROR_PRONE_CHECKS", "${ErrorProneChecks}")
}
- if ctx.Config().UseOpenJDK9() {
- ctx.Strict("JLINK", "${JlinkCmd}")
- ctx.Strict("JMOD", "${JmodCmd}")
- }
+ ctx.Strict("TARGET_JAVAC", "${JavacCmd} ${CommonJdkFlags}")
+ ctx.Strict("HOST_JAVAC", "${JavacCmd} ${CommonJdkFlags}")
+
+ ctx.Strict("JLINK", "${JlinkCmd}")
+ ctx.Strict("JMOD", "${JmodCmd}")
ctx.Strict("SOONG_JAVAC_WRAPPER", "${SoongJavacWrapper}")
ctx.Strict("ZIPSYNC", "${ZipSyncCmd}")
@@ -79,4 +71,10 @@
ctx.Strict("DEFAULT_JACOCO_EXCLUDE_FILTER", strings.Join(DefaultJacocoExcludeFilter, ","))
ctx.Strict("EXTRACT_JAR_PACKAGES", "${ExtractJarPackagesCmd}")
+
+ ctx.Strict("MANIFEST_FIXER", "${ManifestFixerCmd}")
+
+ ctx.Strict("ANDROID_MANIFEST_MERGER_DEPS", "${ManifestMergerJars}")
+ ctx.Strict("ANDROID_MANIFEST_MERGER",
+ "${JavaCmd} -classpath ${ManifestMergerClasspath} com.android.manifmerger.Merger")
}
diff --git a/java/dex.go b/java/dex.go
index 66e71b5..db240fc 100644
--- a/java/dex.go
+++ b/java/dex.go
@@ -22,84 +22,12 @@
"android/soong/android"
)
-var desugar = pctx.AndroidStaticRule("desugar",
- blueprint.RuleParams{
- Command: `rm -rf $dumpDir && mkdir -p $dumpDir && ` +
- `${config.JavaCmd} ` +
- `-Djdk.internal.lambda.dumpProxyClasses=$$(cd $dumpDir && pwd) ` +
- `$javaFlags ` +
- `-jar ${config.DesugarJar} $classpathFlags $desugarFlags ` +
- `-i $in -o $out`,
- CommandDeps: []string{"${config.DesugarJar}", "${config.JavaCmd}"},
- },
- "javaFlags", "classpathFlags", "desugarFlags", "dumpDir")
-
-func (j *Module) desugar(ctx android.ModuleContext, flags javaBuilderFlags,
- classesJar android.Path, jarName string) android.Path {
-
- desugarFlags := []string{
- "--min_sdk_version " + j.minSdkVersionNumber(ctx),
- "--desugar_try_with_resources_if_needed=false",
- "--allow_empty_bootclasspath",
- }
-
- if inList("--core-library", j.deviceProperties.Dxflags) {
- desugarFlags = append(desugarFlags, "--core_library")
- }
-
- desugarJar := android.PathForModuleOut(ctx, "desugar", jarName)
- dumpDir := android.PathForModuleOut(ctx, "desugar", "classes")
-
- javaFlags := ""
- if ctx.Config().UseOpenJDK9() {
- javaFlags = "--add-opens java.base/java.lang.invoke=ALL-UNNAMED"
- }
-
- var classpathFlags []string
- classpathFlags = append(classpathFlags, flags.bootClasspath.FormDesugarClasspath("--bootclasspath_entry")...)
- classpathFlags = append(classpathFlags, flags.classpath.FormDesugarClasspath("--classpath_entry")...)
-
- var deps android.Paths
- deps = append(deps, flags.bootClasspath...)
- deps = append(deps, flags.classpath...)
-
- ctx.Build(pctx, android.BuildParams{
- Rule: desugar,
- Description: "desugar",
- Output: desugarJar,
- Input: classesJar,
- Implicits: deps,
- Args: map[string]string{
- "dumpDir": dumpDir.String(),
- "javaFlags": javaFlags,
- "classpathFlags": strings.Join(classpathFlags, " "),
- "desugarFlags": strings.Join(desugarFlags, " "),
- },
- })
-
- return desugarJar
-}
-
-var dx = pctx.AndroidStaticRule("dx",
- blueprint.RuleParams{
- Command: `rm -rf "$outDir" && mkdir -p "$outDir" && ` +
- `${config.DxCmd} --dex --output=$outDir $dxFlags $in && ` +
- `${config.SoongZipCmd} -o $outDir/classes.dex.jar -C $outDir -D $outDir && ` +
- `${config.MergeZipsCmd} -D -stripFile "*.class" $out $outDir/classes.dex.jar $in`,
- CommandDeps: []string{
- "${config.DxCmd}",
- "${config.SoongZipCmd}",
- "${config.MergeZipsCmd}",
- },
- },
- "outDir", "dxFlags")
-
var d8 = pctx.AndroidStaticRule("d8",
blueprint.RuleParams{
Command: `rm -rf "$outDir" && mkdir -p "$outDir" && ` +
`${config.D8Cmd} --output $outDir $dxFlags $in && ` +
`${config.SoongZipCmd} -o $outDir/classes.dex.jar -C $outDir -D $outDir && ` +
- `${config.MergeZipsCmd} -D -stripFile "*.class" $out $outDir/classes.dex.jar $in`,
+ `${config.MergeZipsCmd} -D -stripFile "**/*.class" $out $outDir/classes.dex.jar $in`,
CommandDeps: []string{
"${config.D8Cmd}",
"${config.SoongZipCmd}",
@@ -116,7 +44,7 @@
`-printmapping $outDict ` +
`$dxFlags $r8Flags && ` +
`${config.SoongZipCmd} -o $outDir/classes.dex.jar -C $outDir -D $outDir && ` +
- `${config.MergeZipsCmd} -D -stripFile "*.class" $out $outDir/classes.dex.jar $in`,
+ `${config.MergeZipsCmd} -D -stripFile "**/*.class" $out $outDir/classes.dex.jar $in`,
CommandDeps: []string{
"${config.R8Cmd}",
"${config.SoongZipCmd}",
@@ -125,39 +53,29 @@
},
"outDir", "outDict", "dxFlags", "r8Flags")
-func (j *Module) dxFlags(ctx android.ModuleContext, fullD8 bool) []string {
+func (j *Module) dxFlags(ctx android.ModuleContext) []string {
flags := j.deviceProperties.Dxflags
- if fullD8 {
- // Translate all the DX flags to D8 ones until all the build files have been migrated
- // to D8 flags. See: b/69377755
- flags = android.RemoveListFromList(flags,
- []string{"--core-library", "--dex", "--multi-dex"})
- }
+ // Translate all the DX flags to D8 ones until all the build files have been migrated
+ // to D8 flags. See: b/69377755
+ flags = android.RemoveListFromList(flags,
+ []string{"--core-library", "--dex", "--multi-dex"})
if ctx.Config().Getenv("NO_OPTIMIZE_DX") != "" {
- if fullD8 {
- flags = append(flags, "--debug")
- } else {
- flags = append(flags, "--no-optimize")
- }
+ flags = append(flags, "--debug")
}
if ctx.Config().Getenv("GENERATE_DEX_DEBUG") != "" {
flags = append(flags,
"--debug",
"--verbose")
- if !fullD8 {
- flags = append(flags,
- "--dump-to="+android.PathForModuleOut(ctx, "classes.lst").String(),
- "--dump-width=1000")
- }
}
- if fullD8 {
- flags = append(flags, "--min-api "+j.minSdkVersionNumber(ctx))
- } else {
- flags = append(flags, "--min-sdk-version="+j.minSdkVersionNumber(ctx))
+ minSdkVersion, err := sdkVersionToNumberAsString(ctx, j.minSdkVersion())
+ if err != nil {
+ ctx.PropertyErrorf("min_sdk_version", "%s", err)
}
+
+ flags = append(flags, "--min-api "+minSdkVersion)
return flags
}
@@ -166,7 +84,7 @@
// When an app contains references to APIs that are not in the SDK specified by
// its LOCAL_SDK_VERSION for example added by support library or by runtime
- // classes added by desugar, we artifically raise the "SDK version" "linked" by
+ // classes added by desugaring, we artifically raise the "SDK version" "linked" by
// ProGuard, to
// - suppress ProGuard warnings of referencing symbols unknown to the lower SDK version.
// - prevent ProGuard stripping subclass in the support library that extends class added in the higher SDK version.
@@ -223,13 +141,8 @@
classesJar android.Path, jarName string) android.Path {
useR8 := Bool(j.deviceProperties.Optimize.Enabled)
- fullD8 := useR8 || ctx.Config().UseD8Desugar()
- if !fullD8 {
- classesJar = j.desugar(ctx, flags, classesJar, jarName)
- }
-
- dxFlags := j.dxFlags(ctx, fullD8)
+ dxFlags := j.dxFlags(ctx)
// Compile classes.jar into classes.dex and then javalib.jar
javalibJar := android.PathForModuleOut(ctx, "dex", jarName)
@@ -238,14 +151,16 @@
if useR8 {
// TODO(ccross): if this is an instrumentation test of an obfuscated app, use the
// dictionary of the app and move the app from libraryjars to injars.
- j.proguardDictionary = android.PathForModuleOut(ctx, "proguard_dictionary")
+ proguardDictionary := android.PathForModuleOut(ctx, "proguard_dictionary")
+ j.proguardDictionary = proguardDictionary
r8Flags, r8Deps := j.r8Flags(ctx, flags)
ctx.Build(pctx, android.BuildParams{
- Rule: r8,
- Description: "r8",
- Output: javalibJar,
- Input: classesJar,
- Implicits: r8Deps,
+ Rule: r8,
+ Description: "r8",
+ Output: javalibJar,
+ ImplicitOutput: proguardDictionary,
+ Input: classesJar,
+ Implicits: r8Deps,
Args: map[string]string{
"dxFlags": strings.Join(dxFlags, " "),
"r8Flags": strings.Join(r8Flags, " "),
@@ -254,15 +169,9 @@
},
})
} else {
- rule := dx
- desc := "dx"
- if fullD8 {
- rule = d8
- desc = "d8"
- }
ctx.Build(pctx, android.BuildParams{
- Rule: rule,
- Description: desc,
+ Rule: d8,
+ Description: "d8",
Output: javalibJar,
Input: classesJar,
Args: map[string]string{
diff --git a/java/droiddoc.go b/java/droiddoc.go
index 07042a1..9ec2be8 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -19,6 +19,7 @@
"android/soong/java/config"
"fmt"
"path/filepath"
+ "runtime"
"strings"
"github.com/google/blueprint"
@@ -33,7 +34,7 @@
`$opts $bootclasspathArgs $classpathArgs -sourcepath $sourcepath ` +
`-d $outDir -quiet && ` +
`${config.SoongZipCmd} -write_if_changed -d -o $docZip -C $outDir -D $outDir && ` +
- `${config.SoongZipCmd} -write_if_changed -jar -o $out -C $stubsDir -D $stubsDir`,
+ `${config.SoongZipCmd} -write_if_changed -jar -o $out -C $stubsDir -D $stubsDir $postDoclavaCmds`,
CommandDeps: []string{
"${config.ZipSyncCmd}",
"${config.JavadocCmd}",
@@ -44,7 +45,48 @@
Restat: true,
},
"outDir", "srcJarDir", "stubsDir", "srcJars", "opts",
- "bootclasspathArgs", "classpathArgs", "sourcepath", "docZip")
+ "bootclasspathArgs", "classpathArgs", "sourcepath", "docZip", "postDoclavaCmds")
+
+ apiCheck = pctx.AndroidStaticRule("apiCheck",
+ blueprint.RuleParams{
+ Command: `( ${config.ApiCheckCmd} -JXmx1024m -J"classpath $classpath" $opts ` +
+ `$apiFile $apiFileToCheck $removedApiFile $removedApiFileToCheck ` +
+ `&& touch $out ) || (echo -e "$msg" ; exit 38)`,
+ CommandDeps: []string{
+ "${config.ApiCheckCmd}",
+ },
+ },
+ "classpath", "opts", "apiFile", "apiFileToCheck", "removedApiFile", "removedApiFileToCheck", "msg")
+
+ updateApi = pctx.AndroidStaticRule("updateApi",
+ blueprint.RuleParams{
+ Command: `( ( cp -f $apiFileToCheck $apiFile && cp -f $removedApiFileToCheck $removedApiFile ) ` +
+ `&& touch $out ) || (echo failed to update public API ; exit 38)`,
+ },
+ "apiFile", "apiFileToCheck", "removedApiFile", "removedApiFileToCheck")
+
+ metalava = pctx.AndroidStaticRule("metalava",
+ blueprint.RuleParams{
+ Command: `rm -rf "$outDir" "$srcJarDir" "$stubsDir" && mkdir -p "$outDir" "$srcJarDir" "$stubsDir" && ` +
+ `${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` +
+ `${config.JavaCmd} -jar ${config.MetalavaJar} -encoding UTF-8 -source $javaVersion @$out.rsp @$srcJarDir/list ` +
+ `$bootclasspathArgs $classpathArgs -sourcepath $sourcepath --no-banner --color --quiet ` +
+ `--stubs $stubsDir $opts && ` +
+ `${config.SoongZipCmd} -write_if_changed -d -o $docZip -C $outDir -D $outDir && ` +
+ `${config.SoongZipCmd} -write_if_changed -jar -o $out -C $stubsDir -D $stubsDir`,
+ CommandDeps: []string{
+ "${config.ZipSyncCmd}",
+ "${config.JavaCmd}",
+ "${config.MetalavaJar}",
+ "${config.JavadocCmd}",
+ "${config.SoongZipCmd}",
+ },
+ Rspfile: "$out.rsp",
+ RspfileContent: "$in",
+ Restat: true,
+ },
+ "outDir", "srcJarDir", "stubsDir", "srcJars", "javaVersion", "bootclasspathArgs",
+ "classpathArgs", "sourcepath", "opts", "docZip")
)
func init() {
@@ -57,6 +99,10 @@
android.RegisterModuleType("javadoc_host", JavadocHostFactory)
}
+var (
+ srcsLibTag = dependencyTag{name: "sources from javalib"}
+)
+
type JavadocProperties struct {
// list of source files used to compile the Java module. May be .java, .logtags, .proto,
// or .aidl files.
@@ -92,6 +138,31 @@
// if not blank, set to the version of the sdk to compile against
Sdk_version *string `android:"arch_variant"`
+
+ Aidl struct {
+ // Top level directories to pass to aidl tool
+ Include_dirs []string
+
+ // Directories rooted at the Android.bp file to pass to aidl tool
+ Local_include_dirs []string
+ }
+
+ // If not blank, set the java version passed to javadoc as -source
+ Java_version *string
+}
+
+type ApiToCheck struct {
+ // path to the API txt file that the new API extracted from source code is checked
+ // against. The path can be local to the module or from other module (via :module syntax).
+ Api_file *string
+
+ // path to the API txt file that the new @removed API extractd from source code is
+ // checked against. The path can be local to the module or from other module (via
+ // :module syntax).
+ Removed_api_file *string
+
+ // Arguments to the apicheck tool.
+ Args *string
}
type DroiddocProperties struct {
@@ -130,6 +201,16 @@
// names of the output files used in args that will be generated
Out []string
+ // if set to true, collect the values used by the Dev tools and
+ // write them in files packaged with the SDK. Defaults to false.
+ Write_sdk_values *bool
+
+ // index.html under current module will be copied to docs out dir, if not null.
+ Static_doc_index_redirect *string
+
+ // source.properties under current module will be copied to docs out dir, if not null.
+ Static_doc_properties *string
+
// a list of files under current module source dir which contains known tags in Java sources.
// filegroup or genrule can be included within this property.
Knowntags []string
@@ -140,6 +221,9 @@
// the generated public API filename by Doclava.
Api_filename *string
+ // the generated public Dex API filename by Doclava.
+ Dex_api_filename *string
+
// the generated private API filename by Doclava.
Private_api_filename *string
@@ -152,11 +236,34 @@
// the generated removed Dex API filename by Doclava.
Removed_dex_api_filename *string
+ // mapping of dex signatures to source file and line number. This is a temporary property and
+ // will be deleted; you probably shouldn't be using it.
+ Dex_mapping_filename *string
+
// the generated exact API filename by Doclava.
Exact_api_filename *string
// if set to false, don't allow droiddoc to generate stubs source files. Defaults to true.
Create_stubs *bool
+
+ Check_api struct {
+ Last_released ApiToCheck
+
+ Current ApiToCheck
+ }
+
+ // if set to true, create stubs through Metalava instead of Doclava. Javadoc/Doclava is
+ // currently still used for documentation generation, and will be replaced by Dokka soon.
+ Metalava_enabled *bool
+
+ // user can specify the version of previous released API file in order to do compatibility check.
+ Metalava_previous_api *string
+
+ // is set to true, Metalava will allow framework SDK to contain annotations.
+ Metalava_annotations_enabled *bool
+
+ // a top level directory contains XML files set to merge annotations.
+ Metalava_merge_annotations_dir *string
}
type Javadoc struct {
@@ -184,11 +291,17 @@
properties DroiddocProperties
apiFile android.WritablePath
+ dexApiFile android.WritablePath
privateApiFile android.WritablePath
privateDexApiFile android.WritablePath
removedApiFile android.WritablePath
removedDexApiFile android.WritablePath
exactApiFile android.WritablePath
+ apiMappingFile android.WritablePath
+
+ checkCurrentApiTimestamp android.WritablePath
+ updateCurrentApiTimestamp android.WritablePath
+ checkLastReleasedApiTimestamp android.WritablePath
}
func InitDroiddocModule(module android.DefaultableModule, hod android.HostOrDeviceSupported) {
@@ -234,20 +347,37 @@
return module
}
+func (j *Javadoc) sdkVersion() string {
+ return String(j.properties.Sdk_version)
+}
+
+func (j *Javadoc) minSdkVersion() string {
+ return j.sdkVersion()
+}
+
func (j *Javadoc) addDeps(ctx android.BottomUpMutatorContext) {
if ctx.Device() {
- sdkDep := decodeSdkDep(ctx, String(j.properties.Sdk_version))
+ sdkDep := decodeSdkDep(ctx, sdkContext(j))
if sdkDep.useDefaultLibs {
ctx.AddDependency(ctx.Module(), bootClasspathTag, config.DefaultBootclasspathLibraries...)
+ if ctx.Config().TargetOpenJDK9() {
+ ctx.AddDependency(ctx.Module(), systemModulesTag, config.DefaultSystemModules)
+ }
if !Bool(j.properties.No_framework_libs) {
ctx.AddDependency(ctx.Module(), libTag, []string{"ext", "framework"}...)
}
} else if sdkDep.useModule {
- ctx.AddDependency(ctx.Module(), bootClasspathTag, sdkDep.module)
+ if ctx.Config().TargetOpenJDK9() {
+ ctx.AddDependency(ctx.Module(), systemModulesTag, sdkDep.systemModules)
+ }
+ ctx.AddDependency(ctx.Module(), bootClasspathTag, sdkDep.modules...)
}
}
ctx.AddDependency(ctx.Module(), libTag, j.properties.Libs...)
+ if j.properties.Srcs_lib != nil {
+ ctx.AddDependency(ctx.Module(), srcsLibTag, *j.properties.Srcs_lib)
+ }
android.ExtractSourcesDeps(ctx, j.properties.Srcs)
@@ -268,52 +398,87 @@
}
}
+func (j *Javadoc) collectBuilderFlags(ctx android.ModuleContext, deps deps) javaBuilderFlags {
+ var flags javaBuilderFlags
+
+ // aidl flags.
+ aidlFlags := j.aidlFlags(ctx, deps.aidlPreprocess, deps.aidlIncludeDirs)
+ if len(aidlFlags) > 0 {
+ // optimization.
+ ctx.Variable(pctx, "aidlFlags", strings.Join(aidlFlags, " "))
+ flags.aidlFlags = "$aidlFlags"
+ }
+
+ return flags
+}
+
+func (j *Javadoc) aidlFlags(ctx android.ModuleContext, aidlPreprocess android.OptionalPath,
+ aidlIncludeDirs android.Paths) []string {
+
+ aidlIncludes := android.PathsForModuleSrc(ctx, j.properties.Aidl.Local_include_dirs)
+ aidlIncludes = append(aidlIncludes, android.PathsForSource(ctx, j.properties.Aidl.Include_dirs)...)
+
+ var flags []string
+ if aidlPreprocess.Valid() {
+ flags = append(flags, "-p"+aidlPreprocess.String())
+ } else {
+ flags = append(flags, android.JoinWithPrefix(aidlIncludeDirs.Strings(), "-I"))
+ }
+
+ flags = append(flags, android.JoinWithPrefix(aidlIncludes.Strings(), "-I"))
+ flags = append(flags, "-I"+android.PathForModuleSrc(ctx).String())
+ if src := android.ExistentPathForSource(ctx, ctx.ModuleDir(), "src"); src.Valid() {
+ flags = append(flags, "-I"+src.String())
+ }
+
+ return flags
+}
+
+func (j *Javadoc) genSources(ctx android.ModuleContext, srcFiles android.Paths,
+ flags javaBuilderFlags) android.Paths {
+
+ outSrcFiles := make(android.Paths, 0, len(srcFiles))
+
+ for _, srcFile := range srcFiles {
+ switch srcFile.Ext() {
+ case ".aidl":
+ javaFile := genAidl(ctx, srcFile, flags.aidlFlags)
+ outSrcFiles = append(outSrcFiles, javaFile)
+ default:
+ outSrcFiles = append(outSrcFiles, srcFile)
+ }
+ }
+
+ return outSrcFiles
+}
+
func (j *Javadoc) collectDeps(ctx android.ModuleContext) deps {
var deps deps
- sdkDep := decodeSdkDep(ctx, String(j.properties.Sdk_version))
+ sdkDep := decodeSdkDep(ctx, sdkContext(j))
if sdkDep.invalidVersion {
- ctx.AddMissingDependencies([]string{sdkDep.module})
+ ctx.AddMissingDependencies(sdkDep.modules)
} else if sdkDep.useFiles {
- deps.bootClasspath = append(deps.bootClasspath, sdkDep.jar)
+ deps.bootClasspath = append(deps.bootClasspath, sdkDep.jars...)
}
ctx.VisitDirectDeps(func(module android.Module) {
otherName := ctx.OtherModuleName(module)
tag := ctx.OtherModuleDependencyTag(module)
- switch dep := module.(type) {
- case Dependency:
- switch tag {
- case bootClasspathTag:
+ switch tag {
+ case bootClasspathTag:
+ if dep, ok := module.(Dependency); ok {
deps.bootClasspath = append(deps.bootClasspath, dep.ImplementationJars()...)
- case libTag:
- deps.classpath = append(deps.classpath, dep.ImplementationJars()...)
- if otherName == String(j.properties.Srcs_lib) {
- srcs := dep.(SrcDependency).CompiledSrcs()
- whitelistPathPrefixes := make(map[string]bool)
- j.genWhitelistPathPrefixes(whitelistPathPrefixes)
- for _, src := range srcs {
- if _, ok := src.(android.WritablePath); ok { // generated sources
- deps.srcs = append(deps.srcs, src)
- } else { // select source path for documentation based on whitelist path prefixs.
- for k, _ := range whitelistPathPrefixes {
- if strings.HasPrefix(src.Rel(), k) {
- deps.srcs = append(deps.srcs, src)
- break
- }
- }
- }
- }
- deps.srcJars = append(deps.srcJars, dep.(SrcDependency).CompiledSrcJars()...)
- }
- default:
+ } else {
panic(fmt.Errorf("unknown dependency %q for %q", otherName, ctx.ModuleName()))
}
- case SdkLibraryDependency:
- switch tag {
- case libTag:
- sdkVersion := String(j.properties.Sdk_version)
+ case libTag:
+ switch dep := module.(type) {
+ case Dependency:
+ deps.classpath = append(deps.classpath, dep.ImplementationJars()...)
+ case SdkLibraryDependency:
+ sdkVersion := j.sdkVersion()
linkType := javaSdk
if strings.HasPrefix(sdkVersion, "system_") || strings.HasPrefix(sdkVersion, "test_") {
linkType = javaSystem
@@ -321,31 +486,50 @@
linkType = javaPlatform
}
deps.classpath = append(deps.classpath, dep.HeaderJars(linkType)...)
- default:
- ctx.ModuleErrorf("dependency on java_sdk_library %q can only be in libs", otherName)
- }
- case android.SourceFileProducer:
- switch tag {
- case libTag:
+ case android.SourceFileProducer:
checkProducesJars(ctx, dep)
deps.classpath = append(deps.classpath, dep.Srcs()...)
- case android.DefaultsDepTag, android.SourceDepTag:
- // Nothing to do
- default:
- ctx.ModuleErrorf("dependency on genrule %q may only be in srcs, libs", otherName)
- }
- default:
- switch tag {
- case android.DefaultsDepTag, android.SourceDepTag, droiddocTemplateTag:
- // Nothing to do
default:
ctx.ModuleErrorf("depends on non-java module %q", otherName)
}
+ case srcsLibTag:
+ switch dep := module.(type) {
+ case Dependency:
+ srcs := dep.(SrcDependency).CompiledSrcs()
+ whitelistPathPrefixes := make(map[string]bool)
+ j.genWhitelistPathPrefixes(whitelistPathPrefixes)
+ for _, src := range srcs {
+ if _, ok := src.(android.WritablePath); ok { // generated sources
+ deps.srcs = append(deps.srcs, src)
+ } else { // select source path for documentation based on whitelist path prefixs.
+ for k, _ := range whitelistPathPrefixes {
+ if strings.HasPrefix(src.Rel(), k) {
+ deps.srcs = append(deps.srcs, src)
+ break
+ }
+ }
+ }
+ }
+ deps.srcJars = append(deps.srcJars, dep.(SrcDependency).CompiledSrcJars()...)
+ default:
+ ctx.ModuleErrorf("depends on non-java module %q", otherName)
+ }
+ case systemModulesTag:
+ if deps.systemModules != nil {
+ panic("Found two system module dependencies")
+ }
+ sm := module.(*SystemModules)
+ if sm.outputFile == nil {
+ panic("Missing directory for system module dependency")
+ }
+ deps.systemModules = sm.outputFile
}
})
// do not pass exclude_srcs directly when expanding srcFiles since exclude_srcs
// may contain filegroup or genrule.
srcFiles := ctx.ExpandSources(j.properties.Srcs, j.properties.Exclude_srcs)
+ flags := j.collectBuilderFlags(ctx, deps)
+ srcFiles = j.genSources(ctx, srcFiles, flags)
// srcs may depend on some genrule output.
j.srcJars = srcFiles.FilterByExt(".srcjar")
@@ -377,19 +561,15 @@
implicits = append(implicits, deps.classpath...)
var bootClasspathArgs, classpathArgs string
- if ctx.Config().UseOpenJDK9() {
- if len(deps.bootClasspath) > 0 {
- // For OpenJDK 9 we use --patch-module to define the core libraries code.
- // TODO(tobiast): Reorganize this when adding proper support for OpenJDK 9
- // modules. Here we treat all code in core libraries as being in java.base
- // to work around the OpenJDK 9 module system. http://b/62049770
- bootClasspathArgs = "--patch-module=java.base=" + strings.Join(deps.bootClasspath.Strings(), ":")
+
+ javaVersion := getJavaVersion(ctx, String(j.properties.Java_version), sdkContext(j))
+ if len(deps.bootClasspath) > 0 {
+ var systemModules classpath
+ if deps.systemModules != nil {
+ systemModules = append(systemModules, deps.systemModules)
}
- } else {
- if len(deps.bootClasspath.Strings()) > 0 {
- // For OpenJDK 8 we can use -bootclasspath to define the core libraries code.
- bootClasspathArgs = deps.bootClasspath.FormJavaClassPath("-bootclasspath")
- }
+ bootClasspathArgs = systemModules.FormJavaSystemModulesPath("--system ", ctx.Device())
+ bootClasspathArgs = bootClasspathArgs + " --patch-module java.base=."
}
if len(deps.classpath.Strings()) > 0 {
classpathArgs = "-classpath " + strings.Join(deps.classpath.Strings(), ":")
@@ -397,7 +577,7 @@
implicits = append(implicits, j.srcJars...)
- opts := "-J-Xmx1024m -XDignore.symbol.file -Xdoclint:none"
+ opts := "-source " + javaVersion + " -J-Xmx1024m -XDignore.symbol.file -Xdoclint:none"
ctx.Build(pctx, android.BuildParams{
Rule: javadoc,
@@ -420,13 +600,36 @@
})
}
+func (d *Droiddoc) checkCurrentApi() bool {
+ if String(d.properties.Check_api.Current.Api_file) != "" &&
+ String(d.properties.Check_api.Current.Removed_api_file) != "" {
+ return true
+ } else if String(d.properties.Check_api.Current.Api_file) != "" {
+ panic("check_api.current.removed_api_file: has to be non empty!")
+ } else if String(d.properties.Check_api.Current.Removed_api_file) != "" {
+ panic("check_api.current.api_file: has to be non empty!")
+ }
+
+ return false
+}
+
+func (d *Droiddoc) checkLastReleasedApi() bool {
+ if String(d.properties.Check_api.Last_released.Api_file) != "" &&
+ String(d.properties.Check_api.Last_released.Removed_api_file) != "" {
+ return true
+ } else if String(d.properties.Check_api.Last_released.Api_file) != "" {
+ panic("check_api.last_released.removed_api_file: has to be non empty!")
+ } else if String(d.properties.Check_api.Last_released.Removed_api_file) != "" {
+ panic("check_api.last_released.api_file: has to be non empty!")
+ }
+
+ return false
+}
+
func (d *Droiddoc) DepsMutator(ctx android.BottomUpMutatorContext) {
d.Javadoc.addDeps(ctx)
- if String(d.properties.Custom_template) == "" {
- // TODO: This is almost always droiddoc-templates-sdk
- ctx.PropertyErrorf("custom_template", "must specify a template")
- } else {
+ if String(d.properties.Custom_template) != "" {
ctx.AddDependency(ctx.Module(), droiddocTemplateTag, String(d.properties.Custom_template))
}
@@ -435,6 +638,28 @@
// knowntags may contain filegroup or genrule.
android.ExtractSourcesDeps(ctx, d.properties.Knowntags)
+
+ if String(d.properties.Static_doc_index_redirect) != "" {
+ android.ExtractSourceDeps(ctx, d.properties.Static_doc_index_redirect)
+ }
+
+ if String(d.properties.Static_doc_properties) != "" {
+ android.ExtractSourceDeps(ctx, d.properties.Static_doc_properties)
+ }
+
+ if d.checkCurrentApi() {
+ android.ExtractSourceDeps(ctx, d.properties.Check_api.Current.Api_file)
+ android.ExtractSourceDeps(ctx, d.properties.Check_api.Current.Removed_api_file)
+ }
+
+ if d.checkLastReleasedApi() {
+ android.ExtractSourceDeps(ctx, d.properties.Check_api.Last_released.Api_file)
+ android.ExtractSourceDeps(ctx, d.properties.Check_api.Last_released.Removed_api_file)
+ }
+
+ if String(d.properties.Metalava_previous_api) != "" {
+ android.ExtractSourceDeps(ctx, d.properties.Metalava_previous_api)
+ }
}
func (d *Droiddoc) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -444,6 +669,22 @@
implicits = append(implicits, deps.bootClasspath...)
implicits = append(implicits, deps.classpath...)
+ var bootClasspathArgs string
+ javaVersion := getJavaVersion(ctx, String(d.Javadoc.properties.Java_version), sdkContext(d))
+ // Doclava has problem with "-source 1.9", so override javaVersion when Doclava
+ // is running with EXPERIMENTAL_USE_OPENJDK9=true. And eventually Doclava will be
+ // replaced by Metalava.
+ if !Bool(d.properties.Metalava_enabled) {
+ javaVersion = "1.8"
+ }
+ // continue to use -bootclasspath even if Metalava under -source 1.9 is enabled
+ // since it doesn't support system modules yet.
+ if len(deps.bootClasspath.Strings()) > 0 {
+ // For OpenJDK 8 we can use -bootclasspath to define the core libraries code.
+ bootClasspathArgs = deps.bootClasspath.FormJavaClassPath("-bootclasspath")
+ }
+ classpathArgs := deps.classpath.FormJavaClassPath("-classpath")
+
argFiles := ctx.ExpandSources(d.properties.Arg_files, nil)
argFilesMap := map[string]android.Path{}
@@ -472,158 +713,378 @@
})
if err != nil {
- ctx.PropertyErrorf("extra_args", "%s", err.Error())
+ ctx.PropertyErrorf("args", "%s", err.Error())
return
}
- var bootClasspathArgs, classpathArgs string
- if len(deps.bootClasspath.Strings()) > 0 {
- bootClasspathArgs = "-bootclasspath " + strings.Join(deps.bootClasspath.Strings(), ":")
- }
- if len(deps.classpath.Strings()) > 0 {
- classpathArgs = "-classpath " + strings.Join(deps.classpath.Strings(), ":")
- }
-
- var templateDir string
- ctx.VisitDirectDepsWithTag(droiddocTemplateTag, func(m android.Module) {
- if t, ok := m.(*DroiddocTemplate); ok {
- implicits = append(implicits, t.deps...)
- templateDir = t.dir.String()
+ genDocsForMetalava := false
+ var metalavaArgs string
+ if Bool(d.properties.Metalava_enabled) {
+ if strings.Contains(args, "--generate-documentation") {
+ if !strings.Contains(args, "-nodocs") {
+ genDocsForMetalava = true
+ }
+ // TODO(nanzhang): Add a Soong property to handle documentation args.
+ metalavaArgs = strings.Split(args, "--generate-documentation")[0]
} else {
- ctx.PropertyErrorf("custom_template", "module %q is not a droiddoc_template", ctx.OtherModuleName(m))
+ metalavaArgs = args
}
- })
-
- var htmlDirArgs string
- if len(d.properties.Html_dirs) > 0 {
- htmlDir := android.PathForModuleSrc(ctx, d.properties.Html_dirs[0])
- implicits = append(implicits, ctx.Glob(htmlDir.Join(ctx, "**/*").String(), nil)...)
- htmlDirArgs = "-htmldir " + htmlDir.String()
}
- var htmlDir2Args string
- if len(d.properties.Html_dirs) > 1 {
- htmlDir2 := android.PathForModuleSrc(ctx, d.properties.Html_dirs[1])
- implicits = append(implicits, ctx.Glob(htmlDir2.Join(ctx, "**/*").String(), nil)...)
- htmlDir2Args = "-htmldir2 " + htmlDir2.String()
+ var templateDir, htmlDirArgs, htmlDir2Args string
+ if !Bool(d.properties.Metalava_enabled) || genDocsForMetalava {
+ if String(d.properties.Custom_template) == "" {
+ // TODO: This is almost always droiddoc-templates-sdk
+ ctx.PropertyErrorf("custom_template", "must specify a template")
+ }
+
+ ctx.VisitDirectDepsWithTag(droiddocTemplateTag, func(m android.Module) {
+ if t, ok := m.(*DroiddocTemplate); ok {
+ implicits = append(implicits, t.deps...)
+ templateDir = t.dir.String()
+ } else {
+ ctx.PropertyErrorf("custom_template", "module %q is not a droiddoc_template", ctx.OtherModuleName(m))
+ }
+ })
+
+ if len(d.properties.Html_dirs) > 0 {
+ htmlDir := android.PathForModuleSrc(ctx, d.properties.Html_dirs[0])
+ implicits = append(implicits, ctx.Glob(htmlDir.Join(ctx, "**/*").String(), nil)...)
+ htmlDirArgs = "-htmldir " + htmlDir.String()
+ }
+
+ if len(d.properties.Html_dirs) > 1 {
+ htmlDir2 := android.PathForModuleSrc(ctx, d.properties.Html_dirs[1])
+ implicits = append(implicits, ctx.Glob(htmlDir2.Join(ctx, "**/*").String(), nil)...)
+ htmlDir2Args = "-htmldir2 " + htmlDir2.String()
+ }
+
+ if len(d.properties.Html_dirs) > 2 {
+ ctx.PropertyErrorf("html_dirs", "Droiddoc only supports up to 2 html dirs")
+ }
+
+ knownTags := ctx.ExpandSources(d.properties.Knowntags, nil)
+ implicits = append(implicits, knownTags...)
+
+ for _, kt := range knownTags {
+ args = args + " -knowntags " + kt.String()
+ }
+
+ for _, hdf := range d.properties.Hdf {
+ args = args + " -hdf " + hdf
+ }
+
+ if String(d.properties.Proofread_file) != "" {
+ proofreadFile := android.PathForModuleOut(ctx, String(d.properties.Proofread_file))
+ args = args + " -proofread " + proofreadFile.String()
+ }
+
+ if String(d.properties.Todo_file) != "" {
+ // tricky part:
+ // we should not compute full path for todo_file through PathForModuleOut().
+ // the non-standard doclet will get the full path relative to "-o".
+ args = args + " -todo " + String(d.properties.Todo_file)
+ }
+
+ if String(d.properties.Resourcesdir) != "" {
+ // TODO: should we add files under resourcesDir to the implicits? It seems that
+ // resourcesDir is one sub dir of htmlDir
+ resourcesDir := android.PathForModuleSrc(ctx, String(d.properties.Resourcesdir))
+ args = args + " -resourcesdir " + resourcesDir.String()
+ }
+
+ if String(d.properties.Resourcesoutdir) != "" {
+ // TODO: it seems -resourceoutdir reference/android/images/ didn't get generated anywhere.
+ args = args + " -resourcesoutdir " + String(d.properties.Resourcesoutdir)
+ }
}
- if len(d.properties.Html_dirs) > 2 {
- ctx.PropertyErrorf("html_dirs", "Droiddoc only supports up to 2 html dirs")
- }
-
- knownTags := ctx.ExpandSources(d.properties.Knowntags, nil)
- implicits = append(implicits, knownTags...)
-
- for _, kt := range knownTags {
- args = args + " -knowntags " + kt.String()
- }
- for _, hdf := range d.properties.Hdf {
- args = args + " -hdf " + hdf
- }
-
- if String(d.properties.Proofread_file) != "" {
- proofreadFile := android.PathForModuleOut(ctx, String(d.properties.Proofread_file))
- args = args + " -proofread " + proofreadFile.String()
- }
-
- if String(d.properties.Todo_file) != "" {
- // tricky part:
- // we should not compute full path for todo_file through PathForModuleOut().
- // the non-standard doclet will get the full path relative to "-o".
- args = args + " -todo " + String(d.properties.Todo_file)
- }
-
- if String(d.properties.Resourcesdir) != "" {
- // TODO: should we add files under resourcesDir to the implicits? It seems that
- // resourcesDir is one sub dir of htmlDir
- resourcesDir := android.PathForModuleSrc(ctx, String(d.properties.Resourcesdir))
- args = args + " -resourcesdir " + resourcesDir.String()
- }
-
- if String(d.properties.Resourcesoutdir) != "" {
- // TODO: it seems -resourceoutdir reference/android/images/ didn't get generated anywhere.
- args = args + " -resourcesoutdir " + String(d.properties.Resourcesoutdir)
+ var docArgsForMetalava string
+ if Bool(d.properties.Metalava_enabled) && genDocsForMetalava {
+ docArgsForMetalava = strings.Split(args, "--generate-documentation")[1]
}
var implicitOutputs android.WritablePaths
- if String(d.properties.Api_filename) != "" {
- d.apiFile = android.PathForModuleOut(ctx, String(d.properties.Api_filename))
+
+ if d.checkCurrentApi() || d.checkLastReleasedApi() || String(d.properties.Api_filename) != "" {
+ d.apiFile = android.PathForModuleOut(ctx, ctx.ModuleName()+"_api.txt")
args = args + " -api " + d.apiFile.String()
+ metalavaArgs = metalavaArgs + " --api " + d.apiFile.String()
implicitOutputs = append(implicitOutputs, d.apiFile)
}
+ if d.checkCurrentApi() || d.checkLastReleasedApi() || String(d.properties.Removed_api_filename) != "" {
+ d.removedApiFile = android.PathForModuleOut(ctx, ctx.ModuleName()+"_removed.txt")
+ args = args + " -removedApi " + d.removedApiFile.String()
+ metalavaArgs = metalavaArgs + " --removed-api " + d.removedApiFile.String()
+ implicitOutputs = append(implicitOutputs, d.removedApiFile)
+ }
+
if String(d.properties.Private_api_filename) != "" {
d.privateApiFile = android.PathForModuleOut(ctx, String(d.properties.Private_api_filename))
args = args + " -privateApi " + d.privateApiFile.String()
+ metalavaArgs = metalavaArgs + " --private-api " + d.privateApiFile.String()
implicitOutputs = append(implicitOutputs, d.privateApiFile)
}
+ if String(d.properties.Dex_api_filename) != "" {
+ d.dexApiFile = android.PathForModuleOut(ctx, String(d.properties.Dex_api_filename))
+ args = args + " -dexApi " + d.dexApiFile.String()
+ implicitOutputs = append(implicitOutputs, d.dexApiFile)
+ }
+
if String(d.properties.Private_dex_api_filename) != "" {
d.privateDexApiFile = android.PathForModuleOut(ctx, String(d.properties.Private_dex_api_filename))
args = args + " -privateDexApi " + d.privateDexApiFile.String()
+ metalavaArgs = metalavaArgs + " --private-dex-api " + d.privateDexApiFile.String()
implicitOutputs = append(implicitOutputs, d.privateDexApiFile)
}
- if String(d.properties.Removed_api_filename) != "" {
- d.removedApiFile = android.PathForModuleOut(ctx, String(d.properties.Removed_api_filename))
- args = args + " -removedApi " + d.removedApiFile.String()
- implicitOutputs = append(implicitOutputs, d.removedApiFile)
- }
-
if String(d.properties.Removed_dex_api_filename) != "" {
d.removedDexApiFile = android.PathForModuleOut(ctx, String(d.properties.Removed_dex_api_filename))
args = args + " -removedDexApi " + d.removedDexApiFile.String()
+ metalavaArgs = metalavaArgs + " --removed-dex-api " + d.removedDexApiFile.String()
implicitOutputs = append(implicitOutputs, d.removedDexApiFile)
}
if String(d.properties.Exact_api_filename) != "" {
d.exactApiFile = android.PathForModuleOut(ctx, String(d.properties.Exact_api_filename))
args = args + " -exactApi " + d.exactApiFile.String()
+ metalavaArgs = metalavaArgs + " --exact-api " + d.exactApiFile.String()
implicitOutputs = append(implicitOutputs, d.exactApiFile)
}
- implicits = append(implicits, d.Javadoc.srcJars...)
-
- jsilver := android.PathForOutput(ctx, "host", ctx.Config().PrebuiltOS(), "framework", "jsilver.jar")
- doclava := android.PathForOutput(ctx, "host", ctx.Config().PrebuiltOS(), "framework", "doclava.jar")
- implicits = append(implicits, jsilver)
- implicits = append(implicits, doclava)
-
- opts := "-source 1.8 -J-Xmx1600m -J-XX:-OmitStackTraceInFastThrow -XDignore.symbol.file " +
- "-doclet com.google.doclava.Doclava -docletpath " + jsilver.String() + ":" + doclava.String() + " " +
- "-templatedir " + templateDir + " " + htmlDirArgs + " " + htmlDir2Args + " " +
- "-hdf page.build " + ctx.Config().BuildId() + "-" + ctx.Config().BuildNumberFromFile() + " " +
- "-hdf page.now " + `"$$(date -d @$$(cat ` + ctx.Config().Getenv("BUILD_DATETIME_FILE") + `) "+%d %b %Y %k:%M")"` +
- " " + args
- if BoolDefault(d.properties.Create_stubs, true) {
- opts += " -stubs " + android.PathForModuleOut(ctx, "docs", "stubsDir").String()
+ if String(d.properties.Dex_mapping_filename) != "" {
+ d.apiMappingFile = android.PathForModuleOut(ctx, String(d.properties.Dex_mapping_filename))
+ args = args + " -apiMapping " + d.apiMappingFile.String()
+ // Omitted: metalava support
+ implicitOutputs = append(implicitOutputs, d.apiMappingFile)
}
+ implicits = append(implicits, d.Javadoc.srcJars...)
+
implicitOutputs = append(implicitOutputs, d.Javadoc.docZip)
for _, o := range d.properties.Out {
implicitOutputs = append(implicitOutputs, android.PathForModuleGen(ctx, o))
}
- ctx.Build(pctx, android.BuildParams{
- Rule: javadoc,
- Description: "Droiddoc",
- Output: d.Javadoc.stubsSrcJar,
- Inputs: d.Javadoc.srcFiles,
- Implicits: implicits,
- ImplicitOutputs: implicitOutputs,
- Args: map[string]string{
+ jsilver := android.PathForOutput(ctx, "host", ctx.Config().PrebuiltOS(), "framework", "jsilver.jar")
+ doclava := android.PathForOutput(ctx, "host", ctx.Config().PrebuiltOS(), "framework", "doclava.jar")
+
+ var date string
+ if runtime.GOOS == "darwin" {
+ date = `date -r`
+ } else {
+ date = `date -d`
+ }
+
+ doclavaOpts := "-source " + javaVersion + " -J-Xmx1600m -J-XX:-OmitStackTraceInFastThrow -XDignore.symbol.file " +
+ "-doclet com.google.doclava.Doclava -docletpath " + jsilver.String() + ":" + doclava.String() + " " +
+ "-templatedir " + templateDir + " " + htmlDirArgs + " " + htmlDir2Args + " " +
+ "-hdf page.build " + ctx.Config().BuildId() + "-" + ctx.Config().BuildNumberFromFile() + " " +
+ `-hdf page.now "$$(` + date + ` @$$(cat ` + ctx.Config().Getenv("BUILD_DATETIME_FILE") + `) "+%d %b %Y %k:%M")" `
+
+ if !Bool(d.properties.Metalava_enabled) {
+ opts := doclavaOpts + args
+
+ implicits = append(implicits, jsilver)
+ implicits = append(implicits, doclava)
+
+ if BoolDefault(d.properties.Create_stubs, true) {
+ opts += " -stubs " + android.PathForModuleOut(ctx, "docs", "stubsDir").String()
+ }
+
+ if Bool(d.properties.Write_sdk_values) {
+ opts += " -sdkvalues " + android.PathForModuleOut(ctx, "docs").String()
+ }
+
+ var postDoclavaCmds string
+ if String(d.properties.Static_doc_index_redirect) != "" {
+ static_doc_index_redirect := ctx.ExpandSource(String(d.properties.Static_doc_index_redirect),
+ "static_doc_index_redirect")
+ implicits = append(implicits, static_doc_index_redirect)
+ postDoclavaCmds += " && cp " + static_doc_index_redirect.String() + " " +
+ android.PathForModuleOut(ctx, "docs", "out", "index.html").String()
+ }
+
+ if String(d.properties.Static_doc_properties) != "" {
+ static_doc_properties := ctx.ExpandSource(String(d.properties.Static_doc_properties),
+ "static_doc_properties")
+ implicits = append(implicits, static_doc_properties)
+ postDoclavaCmds += " && cp " + static_doc_properties.String() + " " +
+ android.PathForModuleOut(ctx, "docs", "out", "source.properties").String()
+ }
+
+ ctx.Build(pctx, android.BuildParams{
+ Rule: javadoc,
+ Description: "Droiddoc",
+ Output: d.Javadoc.stubsSrcJar,
+ Inputs: d.Javadoc.srcFiles,
+ Implicits: implicits,
+ ImplicitOutputs: implicitOutputs,
+ Args: map[string]string{
+ "outDir": android.PathForModuleOut(ctx, "docs", "out").String(),
+ "srcJarDir": android.PathForModuleOut(ctx, "docs", "srcjars").String(),
+ "stubsDir": android.PathForModuleOut(ctx, "docs", "stubsDir").String(),
+ "srcJars": strings.Join(d.Javadoc.srcJars.Strings(), " "),
+ "opts": opts,
+ "bootclasspathArgs": bootClasspathArgs,
+ "classpathArgs": classpathArgs,
+ "sourcepath": strings.Join(d.Javadoc.sourcepaths.Strings(), ":"),
+ "docZip": d.Javadoc.docZip.String(),
+ "postDoclavaCmds": postDoclavaCmds,
+ },
+ })
+ } else {
+ opts := metalavaArgs
+
+ buildArgs := map[string]string{
"outDir": android.PathForModuleOut(ctx, "docs", "out").String(),
"srcJarDir": android.PathForModuleOut(ctx, "docs", "srcjars").String(),
"stubsDir": android.PathForModuleOut(ctx, "docs", "stubsDir").String(),
"srcJars": strings.Join(d.Javadoc.srcJars.Strings(), " "),
- "opts": opts,
+ "javaVersion": javaVersion,
"bootclasspathArgs": bootClasspathArgs,
"classpathArgs": classpathArgs,
"sourcepath": strings.Join(d.Javadoc.sourcepaths.Strings(), ":"),
"docZip": d.Javadoc.docZip.String(),
- },
- })
+ }
+
+ var previousApi android.Path
+ if String(d.properties.Metalava_previous_api) != "" {
+ previousApi = ctx.ExpandSource(String(d.properties.Metalava_previous_api),
+ "metalava_previous_api")
+ opts += " --previous-api " + previousApi.String()
+ implicits = append(implicits, previousApi)
+ }
+
+ if Bool(d.properties.Metalava_annotations_enabled) {
+ if String(d.properties.Metalava_previous_api) == "" {
+ ctx.PropertyErrorf("metalava_previous_api",
+ "has to be non-empty if annotations was enabled!")
+ }
+ opts += " --include-annotations --migrate-nullness"
+
+ annotationsZip := android.PathForModuleOut(ctx, ctx.ModuleName()+"_annotations.zip")
+ implicitOutputs = append(implicitOutputs, annotationsZip)
+
+ if String(d.properties.Metalava_merge_annotations_dir) == "" {
+ ctx.PropertyErrorf("metalava_merge_annotations",
+ "has to be non-empty if annotations was enabled!")
+ }
+
+ mergeAnnotationsDir := android.PathForSource(ctx, String(d.properties.Metalava_merge_annotations_dir))
+
+ opts += " --extract-annotations " + annotationsZip.String() + " --merge-annotations " + mergeAnnotationsDir.String()
+ // TODO(tnorbye): find owners to fix these warnings when annotation was enabled.
+ opts += " --hide HiddenTypedefConstant --hide SuperfluousPrefix --hide AnnotationExtraction"
+ }
+
+ if genDocsForMetalava {
+ opts += " --doc-stubs " + android.PathForModuleOut(ctx, "docs", "docStubsDir").String() +
+ " --write-doc-stubs-source-list $outDir/doc_stubs_src_list " +
+ " --generate-documentation ${config.JavadocCmd} -encoding UTF-8 DOC_STUBS_SOURCE_LIST " +
+ doclavaOpts + docArgsForMetalava + bootClasspathArgs + " " + classpathArgs + " " + " -sourcepath " +
+ android.PathForModuleOut(ctx, "docs", "docStubsDir").String() + " -quiet -d $outDir "
+ implicits = append(implicits, jsilver)
+ implicits = append(implicits, doclava)
+ }
+
+ buildArgs["opts"] = opts
+ ctx.Build(pctx, android.BuildParams{
+ Rule: metalava,
+ Description: "Metalava",
+ Output: d.Javadoc.stubsSrcJar,
+ Inputs: d.Javadoc.srcFiles,
+ Implicits: implicits,
+ ImplicitOutputs: implicitOutputs,
+ Args: buildArgs,
+ })
+ }
+
+ java8Home := ctx.Config().Getenv("ANDROID_JAVA8_HOME")
+
+ checkApiClasspath := classpath{jsilver, doclava, android.PathForSource(ctx, java8Home, "lib/tools.jar")}
+
+ if d.checkCurrentApi() && !ctx.Config().IsPdkBuild() {
+ d.checkCurrentApiTimestamp = android.PathForModuleOut(ctx, "check_current_api.timestamp")
+
+ apiFile := ctx.ExpandSource(String(d.properties.Check_api.Current.Api_file),
+ "check_api.current.api_file")
+ removedApiFile := ctx.ExpandSource(String(d.properties.Check_api.Current.Removed_api_file),
+ "check_api.current_removed_api_file")
+
+ ctx.Build(pctx, android.BuildParams{
+ Rule: apiCheck,
+ Description: "Current API check",
+ Output: d.checkCurrentApiTimestamp,
+ Inputs: nil,
+ Implicits: append(android.Paths{apiFile, removedApiFile, d.apiFile, d.removedApiFile},
+ checkApiClasspath...),
+ Args: map[string]string{
+ "classpath": checkApiClasspath.FormJavaClassPath(""),
+ "opts": String(d.properties.Check_api.Current.Args),
+ "apiFile": apiFile.String(),
+ "apiFileToCheck": d.apiFile.String(),
+ "removedApiFile": removedApiFile.String(),
+ "removedApiFileToCheck": d.removedApiFile.String(),
+ "msg": fmt.Sprintf(`\n******************************\n`+
+ `You have tried to change the API from what has been previously approved.\n\n`+
+ `To make these errors go away, you have two choices:\n`+
+ ` 1. You can add '@hide' javadoc comments to the methods, etc. listed in the\n`+
+ ` errors above.\n\n`+
+ ` 2. You can update current.txt by executing the following command:\n`+
+ ` make %s-update-current-api\n\n`+
+ ` To submit the revised current.txt to the main Android repository,\n`+
+ ` you will need approval.\n`+
+ `******************************\n`, ctx.ModuleName()),
+ },
+ })
+
+ d.updateCurrentApiTimestamp = android.PathForModuleOut(ctx, "update_current_api.timestamp")
+
+ ctx.Build(pctx, android.BuildParams{
+ Rule: updateApi,
+ Description: "update current API",
+ Output: d.updateCurrentApiTimestamp,
+ Implicits: append(android.Paths{}, apiFile, removedApiFile, d.apiFile, d.removedApiFile),
+ Args: map[string]string{
+ "apiFile": apiFile.String(),
+ "apiFileToCheck": d.apiFile.String(),
+ "removedApiFile": removedApiFile.String(),
+ "removedApiFileToCheck": d.removedApiFile.String(),
+ },
+ })
+ }
+ if d.checkLastReleasedApi() && !ctx.Config().IsPdkBuild() {
+ d.checkLastReleasedApiTimestamp = android.PathForModuleOut(ctx, "check_last_released_api.timestamp")
+
+ apiFile := ctx.ExpandSource(String(d.properties.Check_api.Last_released.Api_file),
+ "check_api.last_released.api_file")
+ removedApiFile := ctx.ExpandSource(String(d.properties.Check_api.Last_released.Removed_api_file),
+ "check_api.last_released.removed_api_file")
+
+ ctx.Build(pctx, android.BuildParams{
+ Rule: apiCheck,
+ Description: "Last Released API check",
+ Output: d.checkLastReleasedApiTimestamp,
+ Inputs: nil,
+ Implicits: append(android.Paths{apiFile, removedApiFile, d.apiFile, d.removedApiFile},
+ checkApiClasspath...),
+ Args: map[string]string{
+ "classpath": checkApiClasspath.FormJavaClassPath(""),
+ "opts": String(d.properties.Check_api.Last_released.Args),
+ "apiFile": apiFile.String(),
+ "apiFileToCheck": d.apiFile.String(),
+ "removedApiFile": removedApiFile.String(),
+ "removedApiFileToCheck": d.removedApiFile.String(),
+ "msg": `\n******************************\n` +
+ `You have tried to change the API from what has been previously released in\n` +
+ `an SDK. Please fix the errors listed above.\n` +
+ `******************************\n`,
+ },
+ })
+ }
}
var droiddocTemplateTag = dependencyTag{name: "droiddoc-template"}
diff --git a/java/java.go b/java/java.go
index 8c23124..06d3564 100644
--- a/java/java.go
+++ b/java/java.go
@@ -34,8 +34,8 @@
func init() {
android.RegisterModuleType("java_defaults", defaultsFactory)
- android.RegisterModuleType("java_library", LibraryFactory(true))
- android.RegisterModuleType("java_library_static", LibraryFactory(false))
+ android.RegisterModuleType("java_library", LibraryFactory)
+ android.RegisterModuleType("java_library_static", LibraryFactory)
android.RegisterModuleType("java_library_host", LibraryHostFactory)
android.RegisterModuleType("java_binary", BinaryFactory)
android.RegisterModuleType("java_binary_host", BinaryHostFactory)
@@ -107,7 +107,8 @@
// If not blank, set the java version passed to javac as -source and -target
Java_version *string
- // If set to false, don't allow this module to be installed. Defaults to true.
+ // If set to true, allow this module to be dexed and installed on devices. Has no
+ // effect on host modules, which are always considered installable.
Installable *bool
// If set to true, include sources used to compile the module in to the final jar
@@ -168,9 +169,17 @@
// list of module-specific flags that will be used for dex compiles
Dxflags []string `android:"arch_variant"`
- // if not blank, set to the version of the sdk to compile against
+ // if not blank, set to the version of the sdk to compile against. Defaults to compiling against the current
+ // sdk if platform_apis is not set.
Sdk_version *string
+ // if not blank, set the minimum version of the sdk that the compiled artifacts will run against.
+ // Defaults to sdk_version if not set.
+ Min_sdk_version *string
+
+ // if true, compile against the platform APIs instead of an SDK.
+ Platform_apis *bool
+
Aidl struct {
// Top level directories to pass to aidl tool
Include_dirs []string
@@ -184,11 +193,17 @@
// whether to generate traces (for systrace) for this interface
Generate_traces *bool
+
+ // whether to generate Binder#GetTransaction name method.
+ Generate_get_transaction_name *bool
}
// If true, export a copy of the module as a -hostdex module for host testing.
Hostdex *bool
+ // If set to true, compile dex regardless of installable. Defaults to false.
+ Compile_dex *bool
+
Dex_preopt struct {
// If false, prevent dexpreopting and stripping the dex file from the final jar. Defaults to
// true.
@@ -209,8 +224,8 @@
}
Optimize struct {
- // If false, disable all optimization. Defaults to true for apps, false for
- // libraries and tests.
+ // If false, disable all optimization. Defaults to true for android_app and android_test
+ // modules, false for java_library and java_test modules.
Enabled *bool
// If true, optimize for size by removing unused code. Defaults to true for apps,
@@ -278,6 +293,9 @@
// list of extra progurad flag files
extraProguardFlagFiles android.Paths
+
+ // list of SDK lib names that this java moudule is exporting
+ exportedSdkLibs []string
}
func (j *Module) Srcs() android.Paths {
@@ -290,6 +308,7 @@
HeaderJars() android.Paths
ImplementationJars() android.Paths
AidlIncludeDirs() android.Paths
+ ExportedSdkLibs() []string
}
type SdkLibraryDependency interface {
@@ -324,6 +343,7 @@
var (
staticLibTag = dependencyTag{name: "staticlib"}
libTag = dependencyTag{name: "javalib"}
+ annoTag = dependencyTag{name: "annotation processor"}
bootClasspathTag = dependencyTag{name: "bootclasspath"}
systemModulesTag = dependencyTag{name: "system modules"}
frameworkResTag = dependencyTag{name: "framework-res"}
@@ -335,29 +355,15 @@
type sdkDep struct {
useModule, useFiles, useDefaultLibs, invalidVersion bool
- module string
+ modules []string
systemModules string
frameworkResModule string
- jar android.Path
+ jars android.Paths
aidl android.Path
}
-func sdkStringToNumber(ctx android.BaseContext, v string) int {
- switch v {
- case "", "current", "system_current", "test_current", "core_current":
- return android.FutureApiLevel
- default:
- if i, err := strconv.Atoi(android.GetNumericSdkVersion(v)); err != nil {
- ctx.PropertyErrorf("sdk_version", "invalid sdk version")
- return -1
- } else {
- return i
- }
- }
-}
-
func (j *Module) shouldInstrument(ctx android.BaseContext) bool {
return j.properties.Instrument && ctx.Config().IsEnvTrue("EMMA_INSTRUMENT")
}
@@ -368,10 +374,62 @@
ctx.Config().UnbundledBuild())
}
-func decodeSdkDep(ctx android.BaseContext, v string) sdkDep {
- i := sdkStringToNumber(ctx, v)
- if i == -1 {
- // Invalid sdk version, error handled by sdkStringToNumber.
+func (j *Module) sdkVersion() string {
+ return String(j.deviceProperties.Sdk_version)
+}
+
+func (j *Module) minSdkVersion() string {
+ if j.deviceProperties.Min_sdk_version != nil {
+ return *j.deviceProperties.Min_sdk_version
+ }
+ return j.sdkVersion()
+}
+
+type sdkContext interface {
+ // sdkVersion eturns the sdk_version property of the current module, or an empty string if it is not set.
+ sdkVersion() string
+ // minSdkVersion returns the min_sdk_version property of the current module, or sdkVersion() if it is not set.
+ minSdkVersion() string
+}
+
+func sdkVersionOrDefault(ctx android.BaseContext, v string) string {
+ switch v {
+ case "", "current", "system_current", "test_current", "core_current":
+ return ctx.Config().DefaultAppTargetSdk()
+ default:
+ return v
+ }
+}
+
+// Returns a sdk version as a number. For modules targeting an unreleased SDK (meaning it does not yet have a number)
+// it returns android.FutureApiLevel (10000).
+func sdkVersionToNumber(ctx android.BaseContext, v string) (int, error) {
+ switch v {
+ case "", "current", "test_current", "system_current", "core_current":
+ return ctx.Config().DefaultAppTargetSdkInt(), nil
+ default:
+ n := android.GetNumericSdkVersion(v)
+ if i, err := strconv.Atoi(n); err != nil {
+ return -1, fmt.Errorf("invalid sdk version %q", n)
+ } else {
+ return i, nil
+ }
+ }
+}
+
+func sdkVersionToNumberAsString(ctx android.BaseContext, v string) (string, error) {
+ n, err := sdkVersionToNumber(ctx, v)
+ if err != nil {
+ return "", err
+ }
+ return strconv.Itoa(n), nil
+}
+
+func decodeSdkDep(ctx android.BaseContext, sdkContext sdkContext) sdkDep {
+ v := sdkContext.sdkVersion()
+ i, err := sdkVersionToNumber(ctx, v)
+ if err != nil {
+ ctx.PropertyErrorf("sdk_version", "%s", err)
return sdkDep{}
}
@@ -409,11 +467,12 @@
aidl := filepath.Join(public_dir, "framework.aidl")
jarPath := android.ExistentPathForSource(ctx, jar)
aidlPath := android.ExistentPathForSource(ctx, aidl)
+ lambdaStubsPath := android.PathForSource(ctx, config.SdkLambdaStubsPath)
if (!jarPath.Valid() || !aidlPath.Valid()) && ctx.Config().AllowMissingDependencies() {
return sdkDep{
invalidVersion: true,
- module: fmt.Sprintf("sdk_%s_%s_android", api, v),
+ modules: []string{fmt.Sprintf("sdk_%s_%s_android", api, v)},
}
}
@@ -429,7 +488,7 @@
return sdkDep{
useFiles: true,
- jar: jarPath.Path(),
+ jars: android.Paths{jarPath.Path(), lambdaStubsPath},
aidl: aidlPath.Path(),
}
}
@@ -437,7 +496,7 @@
toModule := func(m, r string) sdkDep {
ret := sdkDep{
useModule: true,
- module: m,
+ modules: []string{m, config.DefaultLambdaStubsLibrary},
systemModules: m + "_system_modules",
frameworkResModule: r,
}
@@ -473,7 +532,7 @@
func (j *Module) deps(ctx android.BottomUpMutatorContext) {
if ctx.Device() {
if !Bool(j.properties.No_standard_libs) {
- sdkDep := decodeSdkDep(ctx, String(j.deviceProperties.Sdk_version))
+ sdkDep := decodeSdkDep(ctx, sdkContext(j))
if sdkDep.useDefaultLibs {
ctx.AddDependency(ctx.Module(), bootClasspathTag, config.DefaultBootclasspathLibraries...)
if ctx.Config().TargetOpenJDK9() {
@@ -486,7 +545,7 @@
if ctx.Config().TargetOpenJDK9() {
ctx.AddDependency(ctx.Module(), systemModulesTag, sdkDep.systemModules)
}
- ctx.AddDependency(ctx.Module(), bootClasspathTag, sdkDep.module)
+ ctx.AddDependency(ctx.Module(), bootClasspathTag, sdkDep.modules...)
if Bool(j.deviceProperties.Optimize.Enabled) {
ctx.AddDependency(ctx.Module(), proguardRaiseTag, config.DefaultBootclasspathLibraries...)
ctx.AddDependency(ctx.Module(), proguardRaiseTag, config.DefaultLibraries...)
@@ -503,15 +562,19 @@
}
if ctx.ModuleName() == "android_stubs_current" ||
ctx.ModuleName() == "android_system_stubs_current" ||
- ctx.ModuleName() == "android_test_stubs_current" {
+ ctx.ModuleName() == "android_test_stubs_current" ||
+ ctx.ModuleName() == "metalava_android_stubs_current" ||
+ ctx.ModuleName() == "metalava_android_system_stubs_current" ||
+ ctx.ModuleName() == "metalava_android_test_stubs_current" {
ctx.AddDependency(ctx.Module(), frameworkApkTag, "framework-res")
}
}
ctx.AddDependency(ctx.Module(), libTag, j.properties.Libs...)
ctx.AddDependency(ctx.Module(), staticLibTag, j.properties.Static_libs...)
- ctx.AddDependency(ctx.Module(), libTag, j.properties.Annotation_processors...)
-
+ ctx.AddFarVariationDependencies([]blueprint.Variation{
+ {"arch", ctx.Config().BuildOsCommonVariant},
+ }, annoTag, j.properties.Annotation_processors...)
android.ExtractSourcesDeps(ctx, j.properties.Srcs)
android.ExtractSourcesDeps(ctx, j.properties.Exclude_srcs)
android.ExtractSourcesDeps(ctx, j.properties.Java_resources)
@@ -585,12 +648,17 @@
flags = append(flags, "-t")
}
+ if Bool(j.deviceProperties.Aidl.Generate_get_transaction_name) {
+ flags = append(flags, "--transaction_names")
+ }
+
return flags
}
type deps struct {
classpath classpath
bootClasspath classpath
+ processorPath classpath
staticJars android.Paths
staticHeaderJars android.Paths
staticJarResources android.Paths
@@ -621,15 +689,16 @@
)
func getLinkType(m *Module, name string) linkType {
- ver := String(m.deviceProperties.Sdk_version)
+ ver := m.sdkVersion()
+ noStdLibs := Bool(m.properties.No_standard_libs)
switch {
- case name == "core.current.stubs" || ver == "core_current":
+ case name == "core.current.stubs" || ver == "core_current" || noStdLibs || name == "stub-annotations":
return javaCore
- case name == "android_system_stubs_current" || strings.HasPrefix(ver, "system_"):
+ case name == "android_system_stubs_current" || strings.HasPrefix(ver, "system_") || name == "metalava_android_system_stubs_current":
return javaSystem
- case name == "android_test_stubs_current" || strings.HasPrefix(ver, "test_"):
+ case name == "android_test_stubs_current" || strings.HasPrefix(ver, "test_") || name == "metalava_android_test_stubs_current":
return javaPlatform
- case name == "android_stubs_current" || ver == "current":
+ case name == "android_stubs_current" || ver == "current" || name == "metalava_android_stubs_current":
return javaSdk
case ver == "":
return javaPlatform
@@ -679,12 +748,12 @@
var deps deps
if ctx.Device() {
- sdkDep := decodeSdkDep(ctx, String(j.deviceProperties.Sdk_version))
+ sdkDep := decodeSdkDep(ctx, sdkContext(j))
if sdkDep.invalidVersion {
- ctx.AddMissingDependencies([]string{sdkDep.module})
+ ctx.AddMissingDependencies(sdkDep.modules)
} else if sdkDep.useFiles {
// sdkDep.jar is actually equivalent to turbine header.jar.
- deps.classpath = append(deps.classpath, sdkDep.jar)
+ deps.classpath = append(deps.classpath, sdkDep.jars...)
deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, sdkDep.aidl)
}
}
@@ -706,10 +775,16 @@
deps.bootClasspath = append(deps.bootClasspath, dep.HeaderJars()...)
case libTag:
deps.classpath = append(deps.classpath, dep.HeaderJars()...)
+ // sdk lib names from dependencies are re-exported
+ j.exportedSdkLibs = append(j.exportedSdkLibs, dep.ExportedSdkLibs()...)
case staticLibTag:
deps.classpath = append(deps.classpath, dep.HeaderJars()...)
deps.staticJars = append(deps.staticJars, dep.ImplementationJars()...)
deps.staticHeaderJars = append(deps.staticHeaderJars, dep.HeaderJars()...)
+ // sdk lib names from dependencies are re-exported
+ j.exportedSdkLibs = append(j.exportedSdkLibs, dep.ExportedSdkLibs()...)
+ case annoTag:
+ deps.processorPath = append(deps.processorPath, dep.ImplementationJars()...)
case frameworkResTag:
if ctx.ModuleName() == "framework" {
// framework.jar has a one-off dependency on the R.java and Manifest.java files
@@ -719,7 +794,10 @@
case frameworkApkTag:
if ctx.ModuleName() == "android_stubs_current" ||
ctx.ModuleName() == "android_system_stubs_current" ||
- ctx.ModuleName() == "android_test_stubs_current" {
+ ctx.ModuleName() == "android_test_stubs_current" ||
+ ctx.ModuleName() == "metalava_android_stubs_current" ||
+ ctx.ModuleName() == "metalava_android_system_stubs_current" ||
+ ctx.ModuleName() == "metalava_android_test_stubs_current" {
// framework stubs.jar need to depend on framework-res.apk, in order to pull the
// resource files out of there for aapt.
//
@@ -737,6 +815,8 @@
switch tag {
case libTag:
deps.classpath = append(deps.classpath, dep.HeaderJars(getLinkType(j, ctx.ModuleName()))...)
+ // names of sdk libs that are directly depended are exported
+ j.exportedSdkLibs = append(j.exportedSdkLibs, otherName)
default:
ctx.ModuleErrorf("dependency on java_sdk_library %q can only be in libs", otherName)
}
@@ -774,9 +854,33 @@
}
})
+ j.exportedSdkLibs = android.FirstUniqueStrings(j.exportedSdkLibs)
+
return deps
}
+func getJavaVersion(ctx android.ModuleContext, javaVersion string, sdkContext sdkContext) string {
+ var ret string
+ sdk, err := sdkVersionToNumber(ctx, sdkContext.sdkVersion())
+ if err != nil {
+ ctx.PropertyErrorf("sdk_version", "%s", err)
+ }
+ if javaVersion != "" {
+ ret = javaVersion
+ } else if ctx.Device() && sdk <= 23 {
+ ret = "1.7"
+ } else if ctx.Device() && sdk <= 26 || !ctx.Config().TargetOpenJDK9() {
+ ret = "1.8"
+ } else if ctx.Device() && sdkContext.sdkVersion() != "" && sdk == android.FutureApiLevel {
+ // TODO(ccross): once we generate stubs we should be able to use 1.9 for sdk_version: "current"
+ ret = "1.8"
+ } else {
+ ret = "1.9"
+ }
+
+ return ret
+}
+
func (j *Module) collectBuilderFlags(ctx android.ModuleContext, deps deps) javaBuilderFlags {
var flags javaBuilderFlags
@@ -797,28 +901,29 @@
flags.javacFlags = "$javacFlags"
}
- if len(j.properties.Errorprone.Javacflags) > 0 {
- flags.errorProneExtraJavacFlags = strings.Join(j.properties.Errorprone.Javacflags, " ")
+ if ctx.Config().RunErrorProne() {
+ if config.ErrorProneClasspath == nil {
+ ctx.ModuleErrorf("cannot build with Error Prone, missing external/error_prone?")
+ }
+
+ errorProneFlags := []string{
+ "-Xplugin:ErrorProne",
+ "${config.ErrorProneChecks}",
+ }
+ errorProneFlags = append(errorProneFlags, j.properties.Errorprone.Javacflags...)
+
+ flags.errorProneExtraJavacFlags = "${config.ErrorProneFlags} " +
+ "'" + strings.Join(errorProneFlags, " ") + "'"
+ flags.errorProneProcessorPath = classpath(android.PathsForSource(ctx, config.ErrorProneClasspath))
}
// javaVersion flag.
- sdk := sdkStringToNumber(ctx, String(j.deviceProperties.Sdk_version))
- if j.properties.Java_version != nil {
- flags.javaVersion = *j.properties.Java_version
- } else if ctx.Device() && sdk <= 23 {
- flags.javaVersion = "1.7"
- } else if ctx.Device() && sdk <= 26 || !ctx.Config().TargetOpenJDK9() {
- flags.javaVersion = "1.8"
- } else if ctx.Device() && String(j.deviceProperties.Sdk_version) != "" && sdk == android.FutureApiLevel {
- // TODO(ccross): once we generate stubs we should be able to use 1.9 for sdk_version: "current"
- flags.javaVersion = "1.8"
- } else {
- flags.javaVersion = "1.9"
- }
+ flags.javaVersion = getJavaVersion(ctx, String(j.properties.Java_version), sdkContext(j))
// classpath
flags.bootClasspath = append(flags.bootClasspath, deps.bootClasspath...)
flags.classpath = append(flags.classpath, deps.classpath...)
+ flags.processorPath = append(flags.processorPath, deps.processorPath...)
if len(flags.bootClasspath) == 0 && ctx.Host() && !ctx.Config().TargetOpenJDK9() &&
!Bool(j.properties.No_standard_libs) &&
@@ -897,6 +1002,8 @@
}
}
+ var stripFiles []string
+
if srcFiles.HasExt(".kt") {
// If there are kotlin files, compile them first but pass all the kotlin and java files
// kotlinc will use the java files to resolve types referenced by the kotlin files, but
@@ -923,14 +1030,21 @@
}
// Make javac rule depend on the kotlinc rule
+ flags.classpath = append(flags.classpath, deps.kotlinStdlib...)
flags.classpath = append(flags.classpath, kotlinJar)
// Jar kotlin classes into the final jar after javac
jars = append(jars, kotlinJar)
- // Don't add kotlin-stdlib if using (on-device) renamed stdlib
- // (it's expected to be on device bootclasspath)
- if !Bool(j.properties.Renamed_kotlin_stdlib) {
+ if Bool(j.properties.Renamed_kotlin_stdlib) {
+ // Remove any kotlin-reflect related files
+ // TODO(pszczepaniak): Support kotlin-reflect
+ stripFiles = append(stripFiles,
+ "**/*.kotlin_module",
+ "**/*.kotlin_builtins")
+ } else {
+ // Only add kotlin-stdlib if not using (on-device) renamed stdlib
+ // (it's expected to be on device bootclasspath)
jars = append(jars, deps.kotlinStdlib...)
}
}
@@ -957,7 +1071,7 @@
}
if len(uniqueSrcFiles) > 0 || len(srcJars) > 0 {
var extraJarDeps android.Paths
- if ctx.Config().IsEnvTrue("RUN_ERROR_PRONE") {
+ if ctx.Config().RunErrorProne() {
// If error-prone is enabled, add an additional rule to compile the java files into
// a separate set of classes (so that they don't overwrite the normal ones and require
// a rebuild when error-prone is turned off).
@@ -1044,7 +1158,8 @@
outputFile = jars[0]
} else {
combinedJar := android.PathForModuleOut(ctx, "combined", jarName)
- TransformJarsToJar(ctx, combinedJar, "for javac", jars, manifest, false, nil)
+ TransformJarsToJar(ctx, combinedJar, "for javac", jars, manifest,
+ false, stripFiles, nil)
outputFile = combinedJar
}
@@ -1084,11 +1199,15 @@
outputFile = j.instrument(ctx, flags, outputFile, jarName)
}
- if ctx.Device() && j.installable() {
- outputFile = j.compileDex(ctx, flags, outputFile, jarName)
+ if ctx.Device() && (Bool(j.properties.Installable) || Bool(j.deviceProperties.Compile_dex)) {
+ var dexOutputFile android.Path
+ dexOutputFile = j.compileDex(ctx, flags, outputFile, jarName)
if ctx.Failed() {
return
}
+ if Bool(j.properties.Installable) {
+ outputFile = dexOutputFile
+ }
}
ctx.CheckbuildFile(outputFile)
j.outputFile = outputFile
@@ -1116,7 +1235,8 @@
// we cannot skip the combine step for now if there is only one jar
// since we have to strip META-INF/TRANSITIVE dir from turbine.jar
combinedJar := android.PathForModuleOut(ctx, "turbine-combined", jarName)
- TransformJarsToJar(ctx, combinedJar, "for turbine", jars, android.OptionalPath{}, false, []string{"META-INF"})
+ TransformJarsToJar(ctx, combinedJar, "for turbine", jars, android.OptionalPath{},
+ false, nil, []string{"META-INF"})
headerJar = combinedJar
if j.properties.Jarjar_rules != nil {
@@ -1148,21 +1268,6 @@
return instrumentedJar
}
-// Returns a sdk version as a string that is guaranteed to be a parseable as a number. For
-// modules targeting an unreleased SDK (meaning it does not yet have a number) it returns "10000".
-func (j *Module) minSdkVersionNumber(ctx android.ModuleContext) string {
- switch String(j.deviceProperties.Sdk_version) {
- case "", "current", "test_current", "system_current", "core_current":
- return strconv.Itoa(ctx.Config().DefaultAppTargetSdkInt())
- default:
- return android.GetNumericSdkVersion(String(j.deviceProperties.Sdk_version))
- }
-}
-
-func (j *Module) installable() bool {
- return BoolDefault(j.properties.Installable, true)
-}
-
var _ Dependency = (*Library)(nil)
func (j *Module) HeaderJars() android.Paths {
@@ -1177,6 +1282,10 @@
return j.exportAidlIncludeDirs
}
+func (j *Module) ExportedSdkLibs() []string {
+ return j.exportedSdkLibs
+}
+
var _ logtagsProducer = (*Module)(nil)
func (j *Module) logtags() android.Paths {
@@ -1194,7 +1303,7 @@
func (j *Library) GenerateAndroidBuildActions(ctx android.ModuleContext) {
j.compile(ctx)
- if j.installable() {
+ if Bool(j.properties.Installable) || ctx.Host() {
j.installFile = ctx.InstallFile(android.PathForModuleInstall(ctx, "framework"),
ctx.ModuleName()+".jar", j.outputFile)
}
@@ -1204,22 +1313,16 @@
j.deps(ctx)
}
-func LibraryFactory(installable bool) func() android.Module {
- return func() android.Module {
- module := &Library{}
+func LibraryFactory() android.Module {
+ module := &Library{}
- if !installable {
- module.properties.Installable = proptools.BoolPtr(false)
- }
+ module.AddProperties(
+ &module.Module.properties,
+ &module.Module.deviceProperties,
+ &module.Module.protoProperties)
- module.AddProperties(
- &module.Module.properties,
- &module.Module.deviceProperties,
- &module.Module.protoProperties)
-
- InitJavaModule(module, android.HostAndDeviceSupported)
- return module
- }
+ InitJavaModule(module, android.HostAndDeviceSupported)
+ return module
}
func LibraryHostFactory() android.Module {
@@ -1229,6 +1332,8 @@
&module.Module.properties,
&module.Module.protoProperties)
+ module.Module.properties.Installable = proptools.BoolPtr(true)
+
InitJavaModule(module, android.HostSupported)
return module
}
@@ -1268,6 +1373,8 @@
&module.Module.protoProperties,
&module.testProperties)
+ module.Module.properties.Installable = proptools.BoolPtr(true)
+
InitJavaModule(module, android.HostAndDeviceSupported)
android.InitDefaultableModule(module)
return module
@@ -1281,6 +1388,8 @@
&module.Module.protoProperties,
&module.testProperties)
+ module.Module.properties.Installable = proptools.BoolPtr(true)
+
InitJavaModule(module, android.HostSupported)
android.InitDefaultableModule(module)
return module
@@ -1350,6 +1459,8 @@
&module.Module.protoProperties,
&module.binaryProperties)
+ module.Module.properties.Installable = proptools.BoolPtr(true)
+
android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibCommonFirst)
android.InitDefaultableModule(module)
return module
@@ -1363,6 +1474,8 @@
&module.Module.protoProperties,
&module.binaryProperties)
+ module.Module.properties.Installable = proptools.BoolPtr(true)
+
android.InitAndroidArchModule(module, android.HostSupported, android.MultilibCommonFirst)
android.InitDefaultableModule(module)
return module
@@ -1378,6 +1491,15 @@
Sdk_version *string
Installable *bool
+
+ // List of shared java libs that this module has dependencies to
+ Libs []string
+
+ // List of files to remove from the jar file(s)
+ Exclude_files []string
+
+ // List of directories to remove from the jar file(s)
+ Exclude_dirs []string
}
type Import struct {
@@ -1386,8 +1508,16 @@
properties ImportProperties
- classpathFiles android.Paths
combinedClasspathFile android.Path
+ exportedSdkLibs []string
+}
+
+func (j *Import) sdkVersion() string {
+ return String(j.properties.Sdk_version)
+}
+
+func (j *Import) minSdkVersion() string {
+ return j.sdkVersion()
}
func (j *Import) Prebuilt() *android.Prebuilt {
@@ -1403,30 +1533,59 @@
}
func (j *Import) DepsMutator(ctx android.BottomUpMutatorContext) {
+ android.ExtractSourcesDeps(ctx, j.properties.Jars)
+ ctx.AddDependency(ctx.Module(), libTag, j.properties.Libs...)
}
func (j *Import) GenerateAndroidBuildActions(ctx android.ModuleContext) {
- j.classpathFiles = android.PathsForModuleSrc(ctx, j.properties.Jars)
+ jars := ctx.ExpandSources(j.properties.Jars, nil)
outputFile := android.PathForModuleOut(ctx, "classes.jar")
- TransformJarsToJar(ctx, outputFile, "for prebuilts", j.classpathFiles, android.OptionalPath{}, false, nil)
+ TransformJarsToJar(ctx, outputFile, "for prebuilts", jars, android.OptionalPath{},
+ false, j.properties.Exclude_files, j.properties.Exclude_dirs)
j.combinedClasspathFile = outputFile
+
+ ctx.VisitDirectDeps(func(module android.Module) {
+ otherName := ctx.OtherModuleName(module)
+ tag := ctx.OtherModuleDependencyTag(module)
+
+ switch dep := module.(type) {
+ case Dependency:
+ switch tag {
+ case libTag, staticLibTag:
+ // sdk lib names from dependencies are re-exported
+ j.exportedSdkLibs = append(j.exportedSdkLibs, dep.ExportedSdkLibs()...)
+ }
+ case SdkLibraryDependency:
+ switch tag {
+ case libTag:
+ // names of sdk libs that are directly depended are exported
+ j.exportedSdkLibs = append(j.exportedSdkLibs, otherName)
+ }
+ }
+ })
+
+ j.exportedSdkLibs = android.FirstUniqueStrings(j.exportedSdkLibs)
}
var _ Dependency = (*Import)(nil)
func (j *Import) HeaderJars() android.Paths {
- return j.classpathFiles
+ return android.Paths{j.combinedClasspathFile}
}
func (j *Import) ImplementationJars() android.Paths {
- return j.classpathFiles
+ return android.Paths{j.combinedClasspathFile}
}
func (j *Import) AidlIncludeDirs() android.Paths {
return nil
}
+func (j *Import) ExportedSdkLibs() []string {
+ return j.exportedSdkLibs
+}
+
var _ android.PrebuiltInterface = (*Import)(nil)
func ImportFactory() android.Module {
diff --git a/java/java_test.go b/java/java_test.go
index ea52496..6bba29b 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -25,6 +25,8 @@
"strconv"
"strings"
"testing"
+
+ "github.com/google/blueprint/proptools"
)
var buildDir string
@@ -72,7 +74,7 @@
ctx.RegisterModuleType("android_app", android.ModuleFactoryAdaptor(AndroidAppFactory))
ctx.RegisterModuleType("android_library", android.ModuleFactoryAdaptor(AndroidLibraryFactory))
ctx.RegisterModuleType("java_binary_host", android.ModuleFactoryAdaptor(BinaryHostFactory))
- ctx.RegisterModuleType("java_library", android.ModuleFactoryAdaptor(LibraryFactory(true)))
+ ctx.RegisterModuleType("java_library", android.ModuleFactoryAdaptor(LibraryFactory))
ctx.RegisterModuleType("java_library_host", android.ModuleFactoryAdaptor(LibraryHostFactory))
ctx.RegisterModuleType("java_import", android.ModuleFactoryAdaptor(ImportFactory))
ctx.RegisterModuleType("java_defaults", android.ModuleFactoryAdaptor(defaultsFactory))
@@ -84,10 +86,12 @@
ctx.RegisterModuleType("droiddoc_host", android.ModuleFactoryAdaptor(DroiddocHostFactory))
ctx.RegisterModuleType("droiddoc_template", android.ModuleFactoryAdaptor(DroiddocTemplateFactory))
ctx.RegisterModuleType("java_sdk_library", android.ModuleFactoryAdaptor(sdkLibraryFactory))
+ ctx.RegisterModuleType("prebuilt_apis", android.ModuleFactoryAdaptor(prebuiltApisFactory))
ctx.PreArchMutators(android.RegisterPrebuiltsPreArchMutators)
ctx.PreArchMutators(android.RegisterPrebuiltsPostDepsMutators)
ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
ctx.PreArchMutators(func(ctx android.RegisterMutatorsContext) {
+ ctx.TopDown("prebuilt_apis", prebuiltApisMutator).Parallel()
ctx.TopDown("java_sdk_library", sdkLibraryMutator).Parallel()
})
ctx.RegisterPreSingletonType("overlay", android.SingletonFactoryAdaptor(OverlaySingletonFactory))
@@ -96,6 +100,7 @@
extraModules := []string{
"core-oj",
"core-libart",
+ "core-lambda-stubs",
"framework",
"ext",
"okhttp",
@@ -141,28 +146,49 @@
}
mockFS := map[string][]byte{
- "Android.bp": []byte(bp),
- "a.java": nil,
- "b.java": nil,
- "c.java": nil,
- "b.kt": nil,
- "a.jar": nil,
- "b.jar": nil,
- "java-res/a/a": nil,
- "java-res/b/b": nil,
- "java-res2/a": nil,
- "java-fg/a.java": nil,
- "java-fg/b.java": nil,
- "java-fg/c.java": nil,
+ "Android.bp": []byte(bp),
+ "a.java": nil,
+ "b.java": nil,
+ "c.java": nil,
+ "b.kt": nil,
+ "a.jar": nil,
+ "b.jar": nil,
+ "java-res/a/a": nil,
+ "java-res/b/b": nil,
+ "java-res2/a": nil,
+ "java-fg/a.java": nil,
+ "java-fg/b.java": nil,
+ "java-fg/c.java": nil,
+ "api/current.txt": nil,
+ "api/removed.txt": nil,
+ "api/system-current.txt": nil,
+ "api/system-removed.txt": nil,
+ "api/test-current.txt": nil,
+ "api/test-removed.txt": nil,
"prebuilts/sdk/14/public/android.jar": nil,
"prebuilts/sdk/14/public/framework.aidl": nil,
"prebuilts/sdk/14/system/android.jar": nil,
+ "prebuilts/sdk/current/core/android.jar": nil,
"prebuilts/sdk/current/public/android.jar": nil,
"prebuilts/sdk/current/public/framework.aidl": nil,
"prebuilts/sdk/current/public/core.jar": nil,
"prebuilts/sdk/current/system/android.jar": nil,
"prebuilts/sdk/current/test/android.jar": nil,
+ "prebuilts/sdk/28/public/api/foo.txt": nil,
+ "prebuilts/sdk/28/system/api/foo.txt": nil,
+ "prebuilts/sdk/28/test/api/foo.txt": nil,
+ "prebuilts/sdk/28/public/api/foo-removed.txt": nil,
+ "prebuilts/sdk/28/system/api/foo-removed.txt": nil,
+ "prebuilts/sdk/28/test/api/foo-removed.txt": nil,
+ "prebuilts/sdk/28/public/api/bar.txt": nil,
+ "prebuilts/sdk/28/system/api/bar.txt": nil,
+ "prebuilts/sdk/28/test/api/bar.txt": nil,
+ "prebuilts/sdk/28/public/api/bar-removed.txt": nil,
+ "prebuilts/sdk/28/system/api/bar-removed.txt": nil,
+ "prebuilts/sdk/28/test/api/bar-removed.txt": nil,
+ "prebuilts/sdk/tools/core-lambda-stubs.jar": nil,
+ "prebuilts/sdk/Android.bp": []byte(`prebuilt_apis { name: "sdk", api_dirs: ["14", "28", "current"],}`),
// For framework-res, which is an implicit dependency for framework
"AndroidManifest.xml": nil,
@@ -175,9 +201,11 @@
"jdk8/jre/lib/jce.jar": nil,
"jdk8/jre/lib/rt.jar": nil,
+ "jdk8/lib/tools.jar": nil,
"bar-doc/a.java": nil,
"bar-doc/b.java": nil,
+ "bar-doc/IFoo.aidl": nil,
"bar-doc/known_oj_tags.txt": nil,
"external/doclava/templates-sdk": nil,
@@ -195,7 +223,7 @@
func run(t *testing.T, ctx *android.TestContext, config android.Config) {
t.Helper()
- _, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+ _, errs := ctx.ParseFileList(".", []string{"Android.bp", "prebuilts/sdk/Android.bp"})
android.FailIfErrored(t, errs)
_, errs = ctx.PrepareBuildActions(config)
android.FailIfErrored(t, errs)
@@ -315,6 +343,7 @@
var classpathTestcases = []struct {
name string
+ unbundled bool
moduleType string
host android.OsClass
properties string
@@ -341,20 +370,20 @@
properties: `sdk_version: "14",`,
bootclasspath: []string{`""`},
system: "bootclasspath", // special value to tell 1.9 test to expect bootclasspath
- classpath: []string{"prebuilts/sdk/14/public/android.jar"},
+ classpath: []string{"prebuilts/sdk/14/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
},
{
name: "current",
properties: `sdk_version: "current",`,
- bootclasspath: []string{"android_stubs_current"},
+ bootclasspath: []string{"android_stubs_current", "core-lambda-stubs"},
system: "bootclasspath", // special value to tell 1.9 test to expect bootclasspath
},
{
name: "system_current",
properties: `sdk_version: "system_current",`,
- bootclasspath: []string{"android_system_stubs_current"},
+ bootclasspath: []string{"android_system_stubs_current", "core-lambda-stubs"},
system: "bootclasspath", // special value to tell 1.9 test to expect bootclasspath
},
{
@@ -363,20 +392,20 @@
properties: `sdk_version: "system_14",`,
bootclasspath: []string{`""`},
system: "bootclasspath", // special value to tell 1.9 test to expect bootclasspath
- classpath: []string{"prebuilts/sdk/14/system/android.jar"},
+ classpath: []string{"prebuilts/sdk/14/system/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
},
{
name: "test_current",
properties: `sdk_version: "test_current",`,
- bootclasspath: []string{"android_test_stubs_current"},
+ bootclasspath: []string{"android_test_stubs_current", "core-lambda-stubs"},
system: "bootclasspath", // special value to tell 1.9 test to expect bootclasspath
},
{
name: "core_current",
properties: `sdk_version: "core_current",`,
- bootclasspath: []string{"core.current.stubs"},
+ bootclasspath: []string{"core.current.stubs", "core-lambda-stubs"},
system: "bootclasspath", // special value to tell 1.9 test to expect bootclasspath
},
{
@@ -425,6 +454,24 @@
properties: `host_supported: true, no_standard_libs: true, system_modules: "none"`,
classpath: []string{},
},
+ {
+
+ name: "unbundled sdk v14",
+ unbundled: true,
+ properties: `sdk_version: "14",`,
+ bootclasspath: []string{`""`},
+ system: "bootclasspath", // special value to tell 1.9 test to expect bootclasspath
+ classpath: []string{"prebuilts/sdk/14/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+ },
+ {
+
+ name: "unbundled current",
+ unbundled: true,
+ properties: `sdk_version: "current",`,
+ bootclasspath: []string{`""`},
+ system: "bootclasspath", // special value to tell 1.9 test to expect bootclasspath
+ classpath: []string{"prebuilts/sdk/current/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+ },
}
func TestClasspath(t *testing.T) {
@@ -475,7 +522,12 @@
t.Run("1.8", func(t *testing.T) {
// Test default javac 1.8
- ctx := testJava(t, bp)
+ config := testConfig(nil)
+ if testcase.unbundled {
+ config.TestProductVariables.Unbundled_build = proptools.BoolPtr(true)
+ }
+ ctx := testContext(config, bp, nil)
+ run(t, ctx, config)
javac := ctx.ModuleForTests("foo", variant).Rule("javac")
@@ -503,6 +555,9 @@
// Test again with javac 1.9
t.Run("1.9", func(t *testing.T) {
config := testConfig(map[string]string{"EXPERIMENTAL_USE_OPENJDK9": "true"})
+ if testcase.unbundled {
+ config.TestProductVariables.Unbundled_build = proptools.BoolPtr(true)
+ }
ctx := testContext(config, bp, nil)
run(t, ctx, config)
@@ -543,14 +598,15 @@
javac := ctx.ModuleForTests("foo", "android_common").Rule("javac")
combineJar := ctx.ModuleForTests("foo", "android_common").Description("for javac")
+ barJar := ctx.ModuleForTests("bar", "android_common").Rule("combineJar").Output
+ bazJar := ctx.ModuleForTests("baz", "android_common").Rule("combineJar").Output
- bar := "a.jar"
- if !strings.Contains(javac.Args["classpath"], bar) {
- t.Errorf("foo classpath %v does not contain %q", javac.Args["classpath"], bar)
+ if !strings.Contains(javac.Args["classpath"], barJar.String()) {
+ t.Errorf("foo classpath %v does not contain %q", javac.Args["classpath"], barJar.String())
}
- if len(combineJar.Inputs) != 2 || combineJar.Inputs[1].String() != "b.jar" {
- t.Errorf("foo combineJar inputs %v does not contain %q", combineJar.Inputs, "b.jar")
+ if len(combineJar.Inputs) != 2 || combineJar.Inputs[1].String() != bazJar.String() {
+ t.Errorf("foo combineJar inputs %v does not contain %q", combineJar.Inputs, bazJar.String())
}
}
@@ -899,6 +955,7 @@
name: "bar-doc",
srcs: [
"bar-doc/*.java",
+ "bar-doc/IFoo.aidl",
],
exclude_srcs: [
"bar-doc/b.java"
@@ -921,6 +978,14 @@
if stubsJar != barDoc.Output.String() {
t.Errorf("expected stubs Jar [%q], got %q", stubsJar, barDoc.Output.String())
}
+ inputs := ctx.ModuleForTests("bar-doc", "android_common").Rule("javadoc").Inputs
+ var javaSrcs []string
+ for _, i := range inputs {
+ javaSrcs = append(javaSrcs, i.Base())
+ }
+ if len(javaSrcs) != 2 || javaSrcs[0] != "a.java" || javaSrcs[1] != "IFoo.java" {
+ t.Errorf("inputs of bar-doc must be []string{\"a.java\", \"IFoo.java\", but was %#v.", javaSrcs)
+ }
}
func TestJarGenrules(t *testing.T) {
@@ -1031,16 +1096,27 @@
libs: ["foo", "bar"],
sdk_version: "system_current",
}
+ java_library {
+ name: "qux",
+ srcs: ["c.java"],
+ libs: ["baz"],
+ sdk_version: "system_current",
+ }
`)
// check the existence of the internal modules
ctx.ModuleForTests("foo", "android_common")
ctx.ModuleForTests("foo"+sdkStubsLibrarySuffix, "android_common")
ctx.ModuleForTests("foo"+sdkStubsLibrarySuffix+sdkSystemApiSuffix, "android_common")
+ ctx.ModuleForTests("foo"+sdkStubsLibrarySuffix+sdkTestApiSuffix, "android_common")
ctx.ModuleForTests("foo"+sdkDocsSuffix, "android_common")
ctx.ModuleForTests("foo"+sdkDocsSuffix+sdkSystemApiSuffix, "android_common")
+ ctx.ModuleForTests("foo"+sdkDocsSuffix+sdkTestApiSuffix, "android_common")
ctx.ModuleForTests("foo"+sdkImplLibrarySuffix, "android_common")
ctx.ModuleForTests("foo"+sdkXmlFileSuffix, "android_common")
+ ctx.ModuleForTests("foo.api.public.28", "")
+ ctx.ModuleForTests("foo.api.system.28", "")
+ ctx.ModuleForTests("foo.api.test.28", "")
bazJavac := ctx.ModuleForTests("baz", "android_common").Rule("javac")
// tests if baz is actually linked to the stubs lib
@@ -1058,4 +1134,13 @@
t.Errorf("baz javac classpath %v should not contain %q", bazJavac.Args["classpath"],
"foo.stubs.jar")
}
+
+ // test if baz has exported SDK lib names foo and bar to qux
+ qux := ctx.ModuleForTests("qux", "android_common")
+ if quxLib, ok := qux.Module().(*Library); ok {
+ sdkLibs := quxLib.ExportedSdkLibs()
+ if len(sdkLibs) != 2 || !android.InList("foo", sdkLibs) || !android.InList("bar", sdkLibs) {
+ t.Errorf("qux should export \"foo\" and \"bar\" but exports %v", sdkLibs)
+ }
+ }
}
diff --git a/java/prebuilt_apis.go b/java/prebuilt_apis.go
new file mode 100644
index 0000000..59b2092
--- /dev/null
+++ b/java/prebuilt_apis.go
@@ -0,0 +1,200 @@
+// Copyright 2018 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 java
+
+import (
+ "android/soong/android"
+ "sort"
+ "strconv"
+ "strings"
+
+ "github.com/google/blueprint/proptools"
+)
+
+// prebuilt_apis is a meta-module that generates filegroup modules for all
+// API txt files found under the directory where the Android.bp is located.
+// Specificaly, an API file located at ./<ver>/<scope>/api/<module>.txt
+// generates a filegroup module named <module>-api.<scope>.<ver>.
+//
+// It also creates <module>-api.<scope>.latest for the lastest <ver>.
+//
+func init() {
+ android.RegisterModuleType("prebuilt_apis", prebuiltApisFactory)
+
+ android.PreArchMutators(func(ctx android.RegisterMutatorsContext) {
+ ctx.TopDown("prebuilt_apis", prebuiltApisMutator).Parallel()
+ })
+}
+
+type prebuiltApisProperties struct {
+ // list of api version directories
+ Api_dirs []string
+}
+
+type prebuiltApis struct {
+ android.ModuleBase
+ properties prebuiltApisProperties
+}
+
+func (module *prebuiltApis) DepsMutator(ctx android.BottomUpMutatorContext) {
+ // no need to implement
+}
+
+func (module *prebuiltApis) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+ // no need to implement
+}
+
+func parseJarPath(ctx android.BaseModuleContext, path string) (module string, apiver string, scope string) {
+ elements := strings.Split(path, "/")
+
+ apiver = elements[0]
+ scope = elements[1]
+
+ module = strings.TrimSuffix(elements[2], ".jar")
+ return
+}
+
+func parseApiFilePath(ctx android.BaseModuleContext, path string) (module string, apiver int, scope string) {
+ elements := strings.Split(path, "/")
+ ver, err := strconv.Atoi(elements[0])
+ if err != nil {
+ ctx.ModuleErrorf("invalid version %q found in path: %q", elements[0], path)
+ return
+ }
+ apiver = ver
+
+ scope = elements[1]
+ if scope != "public" && scope != "system" && scope != "test" {
+ ctx.ModuleErrorf("invalid scope %q found in path: %q", scope, path)
+ return
+ }
+
+ // elements[2] is string literal "api". skipping.
+ module = strings.TrimSuffix(elements[3], ".txt")
+ return
+}
+
+func createImport(mctx android.TopDownMutatorContext, module string, scope string, apiver string, path string) {
+ props := struct {
+ Name *string
+ Jars []string
+ Sdk_version *string
+ Installable *bool
+ }{}
+ props.Name = proptools.StringPtr(mctx.ModuleName() + "_" + scope + "_" + apiver + "_" + module)
+ props.Jars = append(props.Jars, path)
+ // TODO(hansson): change to scope after migration is done.
+ props.Sdk_version = proptools.StringPtr("current")
+ props.Installable = proptools.BoolPtr(false)
+
+ mctx.CreateModule(android.ModuleFactoryAdaptor(ImportFactory), &props)
+}
+
+func createFilegroup(mctx android.TopDownMutatorContext, module string, scope string, apiver string, path string) {
+ fgName := module + ".api." + scope + "." + apiver
+ filegroupProps := struct {
+ Name *string
+ Srcs []string
+ }{}
+ filegroupProps.Name = proptools.StringPtr(fgName)
+ filegroupProps.Srcs = []string{path}
+ mctx.CreateModule(android.ModuleFactoryAdaptor(android.FileGroupFactory), &filegroupProps)
+}
+
+func prebuiltSdkStubs(mctx android.TopDownMutatorContext) {
+ mydir := mctx.ModuleDir() + "/"
+ // <apiver>/<scope>/<module>.jar
+ var files []string
+ for _, apiver := range mctx.Module().(*prebuiltApis).properties.Api_dirs {
+ for _, scope := range []string{"public", "system", "test", "core"} {
+ vfiles, err := mctx.GlobWithDeps(mydir+apiver+"/"+scope+"*/*.jar", nil)
+ if err != nil {
+ mctx.ModuleErrorf("failed to glob jar files under %q: %s", mydir+apiver+"/"+scope, err)
+ }
+ files = append(files, vfiles...)
+ }
+ }
+
+ for _, f := range files {
+ // create a Import module for each jar file
+ localPath := strings.TrimPrefix(f, mydir)
+ module, apiver, scope := parseJarPath(mctx, localPath)
+ createImport(mctx, module, scope, apiver, localPath)
+ }
+}
+
+func prebuiltApiFiles(mctx android.TopDownMutatorContext) {
+ mydir := mctx.ModuleDir() + "/"
+ // <apiver>/<scope>/api/<module>.txt
+ files, err := mctx.GlobWithDeps(mydir+"*/*/api/*.txt", nil)
+ if err != nil {
+ mctx.ModuleErrorf("failed to glob api txt files under %q: %s", mydir, err)
+ }
+ if len(files) == 0 {
+ mctx.ModuleErrorf("no api file found under %q", mydir)
+ }
+
+ // construct a map to find out the latest api file path
+ // for each (<module>, <scope>) pair.
+ type latestApiInfo struct {
+ module string
+ scope string
+ apiver int
+ path string
+ }
+ m := make(map[string]latestApiInfo)
+
+ for _, f := range files {
+ // create a filegroup for each api txt file
+ localPath := strings.TrimPrefix(f, mydir)
+ module, apiver, scope := parseApiFilePath(mctx, localPath)
+ createFilegroup(mctx, module, scope, strconv.Itoa(apiver), localPath)
+
+ // find the latest apiver
+ key := module + "." + scope
+ info, ok := m[key]
+ if !ok {
+ m[key] = latestApiInfo{module, scope, apiver, localPath}
+ } else if apiver > info.apiver {
+ info.apiver = apiver
+ info.path = localPath
+ }
+ }
+ // create filegroups for the latest version of (<module>, <scope>) pairs
+ // sort the keys in order to make build.ninja stable
+ keys := make([]string, 0, len(m))
+ for k := range m {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ for _, k := range keys {
+ info := m[k]
+ createFilegroup(mctx, info.module, info.scope, "latest", info.path)
+ }
+}
+
+func prebuiltApisMutator(mctx android.TopDownMutatorContext) {
+ if _, ok := mctx.Module().(*prebuiltApis); ok {
+ prebuiltApiFiles(mctx)
+ prebuiltSdkStubs(mctx)
+ }
+}
+
+func prebuiltApisFactory() android.Module {
+ module := &prebuiltApis{}
+ module.AddProperties(&module.properties)
+ android.InitAndroidModule(module)
+ return module
+}
diff --git a/java/proto.go b/java/proto.go
index cfd733a..58b039e 100644
--- a/java/proto.go
+++ b/java/proto.go
@@ -24,18 +24,23 @@
func init() {
pctx.HostBinToolVariable("protocCmd", "aprotoc")
+ pctx.HostBinToolVariable("depFixCmd", "dep_fixer")
}
var (
proto = pctx.AndroidStaticRule("protoc",
blueprint.RuleParams{
Command: `rm -rf $out.tmp && mkdir -p $out.tmp && ` +
- `$protocCmd $protoOut=$protoOutParams:$out.tmp -I $protoBase $protoFlags $in && ` +
+ `$protocCmd $protoOut=$protoOutParams:$out.tmp --dependency_out=$out.d -I $protoBase $protoFlags $in && ` +
+ `$depFixCmd $out.d && ` +
`${config.SoongZipCmd} -jar -o $out -C $out.tmp -D $out.tmp && rm -rf $out.tmp`,
CommandDeps: []string{
"$protocCmd",
+ "$depFixCmd",
"${config.SoongZipCmd}",
},
+ Depfile: "${out}.d",
+ Deps: blueprint.DepsGCC,
}, "protoBase", "protoFlags", "protoOut", "protoOutParams")
)
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 703401c..e65af65 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -46,6 +46,7 @@
publicApiStubsTag = dependencyTag{name: "public"}
systemApiStubsTag = dependencyTag{name: "system"}
testApiStubsTag = dependencyTag{name: "test"}
+ implLibTag = dependencyTag{name: "platform"}
)
type apiScope int
@@ -67,11 +68,8 @@
// classpath at runtime if requested via <uses-library>.
//
// TODO: these are big features that are currently missing
-// 1) check for API consistency
-// 2) install stubs libs as the dist artifacts
-// 3) ensuring that apps have appropriate <uses-library> tag
-// 4) disallowing linking to the runtime shared lib
-// 5) HTML generation
+// 1) disallowing linking to the runtime shared lib
+// 2) HTML generation
func init() {
android.RegisterModuleType("java_sdk_library", sdkLibraryFactory)
@@ -102,12 +100,20 @@
// These libraries are not compiled into the stubs jar.
Static_libs []string `android:"arch_variant"`
+ // List of Java libraries that will be in the classpath when building stubs
+ Stub_only_libs []string `android:"arch_variant"`
+
// list of package names that will be documented and publicized as API
Api_packages []string
// list of package names that must be hidden from the API
Hidden_api_packages []string
+ Errorprone struct {
+ // List of javac flags that should only be used when running errorprone.
+ Javacflags []string
+ }
+
// TODO: determines whether to create HTML doc or not
//Html_doc *bool
}
@@ -122,6 +128,7 @@
publicApiStubsPath android.Paths
systemApiStubsPath android.Paths
testApiStubsPath android.Paths
+ implLibPath android.Paths
}
func (module *sdkLibrary) DepsMutator(ctx android.BottomUpMutatorContext) {
@@ -129,24 +136,27 @@
ctx.AddDependency(ctx.Module(), publicApiStubsTag, module.stubsName(apiScopePublic))
ctx.AddDependency(ctx.Module(), systemApiStubsTag, module.stubsName(apiScopeSystem))
ctx.AddDependency(ctx.Module(), testApiStubsTag, module.stubsName(apiScopeTest))
+ ctx.AddDependency(ctx.Module(), implLibTag, module.implName())
}
func (module *sdkLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
- // Record the paths to the header jars of the stubs library.
+ // Record the paths to the header jars of the library (stubs and impl).
// When this java_sdk_library is dependened from others via "libs" property,
// the recorded paths will be returned depending on the link type of the caller.
ctx.VisitDirectDeps(func(to android.Module) {
otherName := ctx.OtherModuleName(to)
tag := ctx.OtherModuleDependencyTag(to)
- if stubs, ok := to.(Dependency); ok {
+ if lib, ok := to.(Dependency); ok {
switch tag {
case publicApiStubsTag:
- module.publicApiStubsPath = stubs.HeaderJars()
+ module.publicApiStubsPath = lib.HeaderJars()
case systemApiStubsTag:
- module.systemApiStubsPath = stubs.HeaderJars()
+ module.systemApiStubsPath = lib.HeaderJars()
case testApiStubsTag:
- module.testApiStubsPath = stubs.HeaderJars()
+ module.testApiStubsPath = lib.HeaderJars()
+ case implLibTag:
+ module.implLibPath = lib.HeaderJars()
default:
ctx.ModuleErrorf("depends on module %q of unknown tag %q", otherName, tag)
}
@@ -155,15 +165,31 @@
}
func (module *sdkLibrary) AndroidMk() android.AndroidMkData {
- // Create a phony module that installs the impl library, for the case when this lib is
- // in PRODUCT_PACKAGES.
return android.AndroidMkData{
Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
+ // Create a phony module that installs the impl library, for the case when this lib is
+ // in PRODUCT_PACKAGES.
fmt.Fprintln(w, "\ninclude $(CLEAR_VARS)")
fmt.Fprintln(w, "LOCAL_PATH :=", moduleDir)
fmt.Fprintln(w, "LOCAL_MODULE :=", name)
fmt.Fprintln(w, "LOCAL_REQUIRED_MODULES := "+module.implName())
fmt.Fprintln(w, "include $(BUILD_PHONY_PACKAGE)")
+ // Create dist rules to install the stubs libs to the dist dir
+ if len(module.publicApiStubsPath) == 1 {
+ fmt.Fprintln(w, "$(call dist-for-goals,sdk win_sdk,"+
+ module.publicApiStubsPath.Strings()[0]+
+ ":"+path.Join("apistubs", "public", module.BaseModuleName()+".jar")+")")
+ }
+ if len(module.systemApiStubsPath) == 1 {
+ fmt.Fprintln(w, "$(call dist-for-goals,sdk win_sdk,"+
+ module.systemApiStubsPath.Strings()[0]+
+ ":"+path.Join("apistubs", "system", module.BaseModuleName()+".jar")+")")
+ }
+ if len(module.testApiStubsPath) == 1 {
+ fmt.Fprintln(w, "$(call dist-for-goals,sdk win_sdk,"+
+ module.testApiStubsPath.Strings()[0]+
+ ":"+path.Join("apistubs", "test", module.BaseModuleName()+".jar")+")")
+ }
},
}
}
@@ -245,21 +271,32 @@
return apiTagName
}
-// returns the path (relative to this module) to the API txt file. Files are located
-// ./<api_dir>/<api_level>.txt where <api_level> is either current, system-current, removed,
-// or system-removed.
-func (module *sdkLibrary) apiFilePath(apiLevel string, apiScope apiScope) string {
- apiDir := "api"
- apiFile := apiLevel
+func (module *sdkLibrary) latestApiFilegroupName(apiScope apiScope) string {
+ name := ":" + module.BaseModuleName() + ".api."
switch apiScope {
+ case apiScopePublic:
+ name = name + "public"
case apiScopeSystem:
- apiFile = "system-" + apiFile
+ name = name + "system"
case apiScopeTest:
- apiFile = "test-" + apiFile
+ name = name + "test"
}
- apiFile = apiFile + ".txt"
+ name = name + ".latest"
+ return name
+}
- return path.Join(apiDir, apiFile)
+func (module *sdkLibrary) latestRemovedApiFilegroupName(apiScope apiScope) string {
+ name := ":" + module.BaseModuleName() + "-removed.api."
+ switch apiScope {
+ case apiScopePublic:
+ name = name + "public"
+ case apiScopeSystem:
+ name = name + "system"
+ case apiScopeTest:
+ name = name + "test"
+ }
+ name = name + ".latest"
+ return name
}
// Creates a static java library that has API stubs
@@ -268,6 +305,7 @@
Name *string
Srcs []string
Sdk_version *string
+ Libs []string
Soc_specific *bool
Device_specific *bool
Product_specific *bool
@@ -285,6 +323,7 @@
// sources are generated from the droiddoc
props.Srcs = []string{":" + module.docsName(apiScope)}
props.Sdk_version = proptools.StringPtr(module.sdkVersion(apiScope))
+ props.Libs = module.properties.Stub_only_libs
// Unbundled apps will use the prebult one from /prebuilts/sdk
props.Product_variables.Unbundled_build.Enabled = proptools.BoolPtr(false)
props.Product_variables.Pdk.Enabled = proptools.BoolPtr(false)
@@ -297,7 +336,7 @@
props.Product_specific = proptools.BoolPtr(true)
}
- mctx.CreateModule(android.ModuleFactoryAdaptor(LibraryFactory(false)), &props)
+ mctx.CreateModule(android.ModuleFactoryAdaptor(LibraryFactory), &props)
}
// Creates a droiddoc module that creates stubs source files from the given full source
@@ -316,6 +355,14 @@
Api_tag_name *string
Api_filename *string
Removed_api_filename *string
+ Check_api struct {
+ Current ApiToCheck
+ Last_released ApiToCheck
+ }
+ Aidl struct {
+ Include_dirs []string
+ Local_include_dirs []string
+ }
}{}
props.Name = proptools.StringPtr(module.docsName(apiScope))
@@ -323,7 +370,12 @@
props.Srcs = append(props.Srcs, module.properties.Api_srcs...)
props.Custom_template = proptools.StringPtr("droiddoc-templates-sdk")
props.Installable = proptools.BoolPtr(false)
+ // A droiddoc module has only one Libs property and doesn't distinguish between
+ // shared libs and static libs. So we need to add both of these libs to Libs property.
props.Libs = module.properties.Libs
+ props.Libs = append(props.Libs, module.properties.Static_libs...)
+ props.Aidl.Include_dirs = module.deviceProperties.Aidl.Include_dirs
+ props.Aidl.Local_include_dirs = module.deviceProperties.Aidl.Local_include_dirs
droiddocArgs := " -hide 110 -hide 111 -hide 113 -hide 121 -hide 125 -hide 126 -hide 127 -hide 128" +
" -stubpackages " + strings.Join(module.properties.Api_packages, ":") +
@@ -340,7 +392,6 @@
// List of APIs identified from the provided source files are created. They are later
// compared against to the not-yet-released (a.k.a current) list of APIs and to the
// last-released (a.k.a numbered) list of API.
- // TODO: If any incompatible change is detected, break the build
currentApiFileName := "current.txt"
removedApiFileName := "removed.txt"
switch apiScope {
@@ -353,12 +404,31 @@
}
currentApiFileName = path.Join("api", currentApiFileName)
removedApiFileName = path.Join("api", removedApiFileName)
+ // TODO(jiyong): remove these three props
props.Api_tag_name = proptools.StringPtr(module.apiTagName(apiScope))
- // Note: the exact names of these two are not important because they are always
- // referenced by the make variable $(INTERNAL_PLATFORM_<TAG_NAME>_API_FILE)
props.Api_filename = proptools.StringPtr(currentApiFileName)
props.Removed_api_filename = proptools.StringPtr(removedApiFileName)
+ // check against the not-yet-release API
+ props.Check_api.Current.Api_file = proptools.StringPtr(currentApiFileName)
+ props.Check_api.Current.Removed_api_file = proptools.StringPtr(removedApiFileName)
+ // any change is reported as error
+ props.Check_api.Current.Args = proptools.StringPtr("-error 2 -error 3 -error 4 -error 5 " +
+ "-error 6 -error 7 -error 8 -error 9 -error 10 -error 11 -error 12 -error 13 " +
+ "-error 14 -error 15 -error 16 -error 17 -error 18 -error 19 -error 20 " +
+ "-error 21 -error 23 -error 24 -error 25 -error 26 -error 27")
+
+ // check against the latest released API
+ props.Check_api.Last_released.Api_file = proptools.StringPtr(
+ module.latestApiFilegroupName(apiScope))
+ props.Check_api.Last_released.Removed_api_file = proptools.StringPtr(
+ module.latestRemovedApiFilegroupName(apiScope))
+ // backward incompatible changes are reported as error
+ props.Check_api.Last_released.Args = proptools.StringPtr("-hide 2 -hide 3 -hide 4 -hide 5 " +
+ "-hide 6 -hide 24 -hide 25 -hide 26 -hide 27 " +
+ "-error 7 -error 8 -error 9 -error 10 -error 11 -error 12 -error 13 -error 14 " +
+ "-error 15 -error 16 -error 17 -error 18")
+
// Include the part of the framework source. This is required for the case when
// API class is extending from the framework class. In that case, doclava needs
// to know whether the base class is hidden or not. Since that information is
@@ -388,15 +458,21 @@
Soc_specific *bool
Device_specific *bool
Product_specific *bool
+ Installable *bool
Required []string
+ Errorprone struct {
+ Javacflags []string
+ }
}{}
props.Name = proptools.StringPtr(module.implName())
props.Srcs = module.properties.Srcs
props.Libs = module.properties.Libs
props.Static_libs = module.properties.Static_libs
+ props.Installable = proptools.BoolPtr(true)
// XML file is installed along with the impl lib
props.Required = []string{module.xmlFileName()}
+ props.Errorprone.Javacflags = module.properties.Errorprone.Javacflags
if module.SocSpecific() {
props.Soc_specific = proptools.BoolPtr(true)
@@ -406,7 +482,7 @@
props.Product_specific = proptools.BoolPtr(true)
}
- mctx.CreateModule(android.ModuleFactoryAdaptor(LibraryFactory(true)), &props, &module.deviceProperties)
+ mctx.CreateModule(android.ModuleFactoryAdaptor(LibraryFactory), &props, &module.deviceProperties)
}
// Creates the xml file that publicizes the runtime library
@@ -472,8 +548,10 @@
// to satisfy SdkLibraryDependency interface
func (module *sdkLibrary) HeaderJars(linkType linkType) android.Paths {
// This module is just a wrapper for the stubs.
- if linkType == javaSystem || linkType == javaPlatform {
+ if linkType == javaSystem {
return module.systemApiStubsPath
+ } else if linkType == javaPlatform {
+ return module.implLibPath
} else {
return module.publicApiStubsPath
}
diff --git a/java/support_libraries.go b/java/support_libraries.go
new file mode 100644
index 0000000..320afae
--- /dev/null
+++ b/java/support_libraries.go
@@ -0,0 +1,66 @@
+// Copyright 2018 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 java
+
+import (
+ "sort"
+ "strings"
+
+ "android/soong/android"
+)
+
+func init() {
+ android.RegisterMakeVarsProvider(pctx, supportLibrariesMakeVarsProvider)
+}
+
+func supportLibrariesMakeVarsProvider(ctx android.MakeVarsContext) {
+ var supportAars, supportJars []string
+
+ sctx := ctx.SingletonContext()
+ sctx.VisitAllModules(func(module android.Module) {
+ dir := sctx.ModuleDir(module)
+ switch {
+ case strings.HasPrefix(dir, "prebuilts/sdk/current/extras"),
+ dir == "prebuilts/sdk/current/androidx",
+ dir == "prebuilts/sdk/current/car",
+ dir == "prebuilts/sdk/current/optional",
+ dir == "prebuilts/sdk/current/support":
+ // Support library
+ default:
+ // Not a support library
+ return
+ }
+
+ name := sctx.ModuleName(module)
+ if strings.HasSuffix(name, "-nodeps") {
+ return
+ }
+
+ switch module.(type) {
+ case *AndroidLibrary, *AARImport:
+ supportAars = append(supportAars, name)
+ case *Library, *Import:
+ supportJars = append(supportJars, name)
+ default:
+ sctx.ModuleErrorf(module, "unknown module type %t", module)
+ }
+ })
+
+ sort.Strings(supportAars)
+ sort.Strings(supportJars)
+
+ ctx.Strict("SUPPORT_LIBRARIES_AARS", strings.Join(supportAars, " "))
+ ctx.Strict("SUPPORT_LIBRARIES_JARS", strings.Join(supportJars, " "))
+}
diff --git a/java/system_modules.go b/java/system_modules.go
index 943eaeb..73a5131 100644
--- a/java/system_modules.go
+++ b/java/system_modules.go
@@ -119,9 +119,7 @@
jars = append(jars, android.PathsForModuleSrc(ctx, system.properties.Jars)...)
- if ctx.Config().TargetOpenJDK9() {
- system.outputFile = TransformJarsToSystemModules(ctx, "java.base", jars)
- }
+ system.outputFile = TransformJarsToSystemModules(ctx, "java.base", jars)
}
func (system *SystemModules) DepsMutator(ctx android.BottomUpMutatorContext) {
@@ -131,17 +129,15 @@
func (system *SystemModules) AndroidMk() android.AndroidMkData {
return android.AndroidMkData{
Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
- if system.outputFile != nil {
- makevar := "SOONG_SYSTEM_MODULES_" + name
- fmt.Fprintln(w)
- fmt.Fprintln(w, makevar, ":=", system.outputFile.String())
- fmt.Fprintln(w, ".KATI_READONLY", ":=", makevar)
- fmt.Fprintln(w, name+":", "$("+makevar+")")
- fmt.Fprintln(w)
- makevar = "SOONG_SYSTEM_MODULES_LIBS_" + name
- fmt.Fprintln(w, makevar, ":=", strings.Join(system.properties.Libs, " "))
- fmt.Fprintln(w, ".KATI_READONLY :=", makevar)
- }
+ makevar := "SOONG_SYSTEM_MODULES_" + name
+ fmt.Fprintln(w)
+ fmt.Fprintln(w, makevar, ":=", system.outputFile.String())
+ fmt.Fprintln(w, ".KATI_READONLY", ":=", makevar)
+ fmt.Fprintln(w, name+":", "$("+makevar+")")
+ fmt.Fprintln(w)
+ makevar = "SOONG_SYSTEM_MODULES_LIBS_" + name
+ fmt.Fprintln(w, makevar, ":=", strings.Join(system.properties.Libs, " "))
+ fmt.Fprintln(w, ".KATI_READONLY :=", makevar)
},
}
}
diff --git a/phony/phony.go b/phony/phony.go
index a39b5d5..0c62e8a 100644
--- a/phony/phony.go
+++ b/phony/phony.go
@@ -23,7 +23,7 @@
)
func init() {
- android.RegisterModuleType("phony", phonyFactory)
+ android.RegisterModuleType("phony", PhonyFactory)
}
type phony struct {
@@ -31,7 +31,7 @@
requiredModuleNames []string
}
-func phonyFactory() android.Module {
+func PhonyFactory() android.Module {
module := &phony{}
android.InitAndroidModule(module)
diff --git a/python/androidmk.go b/python/androidmk.go
index 5fa01ab..365b422 100644
--- a/python/androidmk.go
+++ b/python/androidmk.go
@@ -77,6 +77,7 @@
ret.OutputFile = android.OptionalPathForPath(installer.path)
}
+ ret.Required = append(ret.Required, "libc++")
ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
path := installer.path.RelPathString()
dir, file := filepath.Split(path)
diff --git a/python/builder.go b/python/builder.go
index 969f9ef..ec4cb4e 100644
--- a/python/builder.go
+++ b/python/builder.go
@@ -96,11 +96,8 @@
Output: binFile,
Implicits: implicits,
Args: map[string]string{
- "interp": strings.Replace(interpreter, "/", `\/`, -1),
- // we need remove "runfiles/" suffix since stub script starts
- // searching for main file in each sub-dir of "runfiles" directory tree.
- "main": strings.Replace(strings.TrimPrefix(main, runFiles+"/"),
- "/", `\/`, -1),
+ "interp": strings.Replace(interpreter, "/", `\/`, -1),
+ "main": strings.Replace(main, "/", `\/`, -1),
"template": template.String(),
"stub": stub,
"mergedZip": mergedZip.String(),
diff --git a/python/proto.go b/python/proto.go
index 82ee3cb..2370cd2 100644
--- a/python/proto.go
+++ b/python/proto.go
@@ -29,13 +29,15 @@
proto = pctx.AndroidStaticRule("protoc",
blueprint.RuleParams{
Command: `rm -rf $out.tmp && mkdir -p $out.tmp && ` +
- `$protocCmd --python_out=$out.tmp -I $protoBase $protoFlags $in && ` +
- `$parCmd -o $out -P $pkgPath -C $out.tmp -D $out.tmp && rm -rf $out.tmp`,
+ `$protocCmd --python_out=$out.tmp --dependency_out=$out.d -I $protoBase $protoFlags $in && ` +
+ `$parCmd -o $out $pkgPathArgs -C $out.tmp -D $out.tmp && rm -rf $out.tmp`,
CommandDeps: []string{
"$protocCmd",
"$parCmd",
},
- }, "protoBase", "protoFlags", "pkgPath")
+ Depfile: "${out}.d",
+ Deps: blueprint.DepsGCC,
+ }, "protoBase", "protoFlags", "pkgPathArgs")
)
func genProto(ctx android.ModuleContext, p *android.ProtoProperties,
@@ -51,15 +53,19 @@
protoBase = strings.TrimSuffix(protoFile.String(), protoFile.Rel())
}
+ var pkgPathArgs string
+ if pkgPath != "" {
+ pkgPathArgs = "-P " + pkgPath
+ }
ctx.Build(pctx, android.BuildParams{
Rule: proto,
Description: "protoc " + protoFile.Rel(),
Output: srcJarFile,
Input: protoFile,
Args: map[string]string{
- "protoBase": protoBase,
- "protoFlags": strings.Join(protoFlags, " "),
- "pkgPath": pkgPath,
+ "protoBase": protoBase,
+ "protoFlags": strings.Join(protoFlags, " "),
+ "pkgPathArgs": pkgPathArgs,
},
})
diff --git a/python/python.go b/python/python.go
index a277988..feb17da 100644
--- a/python/python.go
+++ b/python/python.go
@@ -63,8 +63,7 @@
// files of the current module.
// eg. Pkg_path = "a/b/c"; Other packages can reference this module by using
// (from a.b.c import ...) statement.
- // if left unspecified, all the source/data files of current module are copied to
- // "runfiles/" tree directory directly.
+ // if left unspecified, all the source/data files path is unchanged within zip file.
Pkg_path *string `android:"arch_variant"`
// true, if the Python module is used internally, eg, Python std libs.
@@ -206,7 +205,7 @@
var (
pythonLibTag = dependencyTag{name: "pythonLib"}
launcherTag = dependencyTag{name: "launcher"}
- pyIdentifierRegexp = regexp.MustCompile(`^([a-z]|[A-Z]|_)([a-z]|[A-Z]|[0-9]|_)*$`)
+ pyIdentifierRegexp = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_-]*$`)
pyExt = ".py"
protoExt = ".proto"
pyVersion2 = "PY2"
@@ -215,7 +214,6 @@
mainFileName = "__main__.py"
entryPointFile = "entry_point.txt"
parFileExt = ".zip"
- runFiles = "runfiles"
internal = "internal"
)
@@ -417,19 +415,11 @@
return
}
if p.properties.Is_internal != nil && *p.properties.Is_internal {
- // pkg_path starts from "internal/" implicitly.
pkgPath = filepath.Join(internal, pkgPath)
- } else {
- // pkg_path starts from "runfiles/" implicitly.
- pkgPath = filepath.Join(runFiles, pkgPath)
}
} else {
if p.properties.Is_internal != nil && *p.properties.Is_internal {
- // pkg_path starts from "runfiles/" implicitly.
pkgPath = internal
- } else {
- // pkg_path starts from "runfiles/" implicitly.
- pkgPath = runFiles
}
}
@@ -520,7 +510,9 @@
sort.Strings(keys)
parArgs := []string{}
- parArgs = append(parArgs, `-P `+pkgPath)
+ if pkgPath != "" {
+ parArgs = append(parArgs, `-P `+pkgPath)
+ }
implicits := android.Paths{}
for _, k := range keys {
parArgs = append(parArgs, `-C `+k)
@@ -582,39 +574,46 @@
destToPyData[path.dest] = path.src.String()
}
+ seen := make(map[android.Module]bool)
+
// visit all its dependencies in depth first.
- ctx.VisitDepsDepthFirst(func(module android.Module) {
- if ctx.OtherModuleDependencyTag(module) != pythonLibTag {
- return
+ ctx.WalkDeps(func(child, parent android.Module) bool {
+ if ctx.OtherModuleDependencyTag(child) != pythonLibTag {
+ return false
}
+ if seen[child] {
+ return false
+ }
+ seen[child] = true
// Python modules only can depend on Python libraries.
- if !isPythonLibModule(module) {
+ if !isPythonLibModule(child) {
panic(fmt.Errorf(
"the dependency %q of module %q is not Python library!",
- ctx.ModuleName(), ctx.OtherModuleName(module)))
+ ctx.ModuleName(), ctx.OtherModuleName(child)))
}
- if dep, ok := module.(PythonDependency); ok {
+ if dep, ok := child.(PythonDependency); ok {
srcs := dep.GetSrcsPathMappings()
for _, path := range srcs {
if !fillInMap(ctx, destToPySrcs,
- path.dest, path.src.String(), ctx.ModuleName(), ctx.OtherModuleName(module)) {
+ path.dest, path.src.String(), ctx.ModuleName(), ctx.OtherModuleName(child)) {
continue
}
}
data := dep.GetDataPathMappings()
for _, path := range data {
fillInMap(ctx, destToPyData,
- path.dest, path.src.String(), ctx.ModuleName(), ctx.OtherModuleName(module))
+ path.dest, path.src.String(), ctx.ModuleName(), ctx.OtherModuleName(child))
}
p.depsSrcsZips = append(p.depsSrcsZips, dep.GetSrcsZip())
}
+ return true
})
}
func fillInMap(ctx android.ModuleContext, m map[string]string,
key, value, curModule, otherModule string) bool {
if oldValue, found := m[key]; found {
- ctx.ModuleErrorf("found two files to be placed at the same runfiles location %q."+
+ ctx.ModuleErrorf("found two files to be placed at the same location within zip %q."+
" First file: in module %s at path %q."+
" Second file: in module %s at path %q.",
key, curModule, oldValue, otherModule, value)
diff --git a/python/python_test.go b/python/python_test.go
index 60a1c82..e5fe126 100644
--- a/python/python_test.go
+++ b/python/python_test.go
@@ -44,7 +44,7 @@
badIdentifierErrTemplate = moduleVariantErrTemplate +
"srcs: the path %q contains invalid token %q."
dupRunfileErrTemplate = moduleVariantErrTemplate +
- "found two files to be placed at the same runfiles location %q." +
+ "found two files to be placed at the same location within zip %q." +
" First file: in module %s at path %q." +
" Second file: in module %s at path %q."
noSrcFileErr = moduleVariantErrTemplate + "doesn't have any source files!"
@@ -175,11 +175,11 @@
},
errors: []string{
fmt.Sprintf(badIdentifierErrTemplate, "dir/Blueprints:4:11",
- "lib1", "PY3", "runfiles/a/b/c/-e/f/file1.py", "-e"),
+ "lib1", "PY3", "a/b/c/-e/f/file1.py", "-e"),
fmt.Sprintf(badIdentifierErrTemplate, "dir/Blueprints:4:11",
- "lib1", "PY3", "runfiles/a/b/c/.file1.py", ".file1"),
+ "lib1", "PY3", "a/b/c/.file1.py", ".file1"),
fmt.Sprintf(badIdentifierErrTemplate, "dir/Blueprints:4:11",
- "lib1", "PY3", "runfiles/a/b/c/123/file1.py", "123"),
+ "lib1", "PY3", "a/b/c/123/file1.py", "123"),
},
},
{
@@ -212,7 +212,7 @@
},
errors: []string{
fmt.Sprintf(dupRunfileErrTemplate, "dir/Blueprints:9:6",
- "lib2", "PY3", "runfiles/a/b/c/file1.py", "lib2", "dir/file1.py",
+ "lib2", "PY3", "a/b/c/file1.py", "lib2", "dir/file1.py",
"lib1", "dir/c/file1.py"),
},
},
@@ -307,10 +307,10 @@
name: "bin",
actualVersion: "PY3",
pyRunfiles: []string{
- "runfiles/e/default.py",
- "runfiles/e/bin.py",
- "runfiles/e/default_py3.py",
- "runfiles/e/file4.py",
+ "e/default.py",
+ "e/bin.py",
+ "e/default_py3.py",
+ "e/file4.py",
},
srcsZip: "@prefix@/.intermediates/dir/bin/PY3/bin.py.srcszip",
depsSrcsZips: []string{
diff --git a/python/scripts/stub_template_host.txt b/python/scripts/stub_template_host.txt
index b90a28b..e686211 100644
--- a/python/scripts/stub_template_host.txt
+++ b/python/scripts/stub_template_host.txt
@@ -11,15 +11,12 @@
PYTHON_BINARY = '%interpreter%'
MAIN_FILE = '%main%'
PYTHON_PATH = 'PYTHONPATH'
-ZIP_RUNFILES_DIRECTORY_NAME = 'runfiles'
def SearchPathEnv(name):
search_path = os.getenv('PATH', os.defpath).split(os.pathsep)
for directory in search_path:
if directory == '': continue
path = os.path.join(directory, name)
- if os.path.islink(path):
- path = os.path.realpath(path)
# Check if path is actual executable file.
if os.path.isfile(path) and os.access(path, os.X_OK):
return path
@@ -38,7 +35,7 @@
temp_dir = tempfile.mkdtemp("", "Soong.python_")
zf = zipfile.ZipFile(os.path.dirname(__file__))
zf.extractall(temp_dir)
- return os.path.join(temp_dir, ZIP_RUNFILES_DIRECTORY_NAME)
+ return temp_dir
def Main():
args = sys.argv[1:]
@@ -85,7 +82,7 @@
except:
raise
finally:
- shutil.rmtree(os.path.dirname(runfiles_path), True)
+ shutil.rmtree(runfiles_path, True)
if __name__ == '__main__':
Main()
diff --git a/scripts/build-ndk-prebuilts.sh b/scripts/build-ndk-prebuilts.sh
index d150451..e3552a0 100755
--- a/scripts/build-ndk-prebuilts.sh
+++ b/scripts/build-ndk-prebuilts.sh
@@ -48,7 +48,7 @@
"Platform_sdk_version": ${PLATFORM_SDK_VERSION},
"Platform_version_active_codenames": ${PLATFORM_VERSION_ALL_CODENAMES},
- "DeviceName": "flounder",
+ "DeviceName": "generic_arm64",
"DeviceArch": "arm64",
"DeviceArchVariant": "armv8-a",
"DeviceCpuVariant": "denver64",
diff --git a/scripts/gen-kotlin-build-file.sh b/scripts/gen-kotlin-build-file.sh
index f077a0c..1e03f72 100755
--- a/scripts/gen-kotlin-build-file.sh
+++ b/scripts/gen-kotlin-build-file.sh
@@ -16,7 +16,7 @@
# Generates kotlinc module xml file to standard output based on rsp files
-if [ -z "$1" ]; then
+if [[ -z "$1" ]]; then
echo "usage: $0 <classpath> <outDir> <rspFiles>..." >&2
exit 1
fi
@@ -30,24 +30,36 @@
out_dir=$2
shift 2
-# Path in the build file are relative to the build file, we need to make them absolute.
-prefix=`pwd`
+# Path in the build file may be relative to the build file, we need to make them
+# absolute
+prefix="$(pwd)"
+
+get_abs_path () {
+ local file="$1"
+ if [[ "${file:0:1}" == '/' ]] ; then
+ echo "${file}"
+ else
+ echo "${prefix}/${file}"
+ fi
+}
# Print preamble
echo "<modules><module name=\"name\" type=\"java-production\" outputDir=\"${out_dir}\">"
# Print classpath entries
-for file in $(echo $classpath | tr ":" "\n"); do
- echo " <classpath path=\"${prefix}/${file}\"/>"
+for file in $(echo "$classpath" | tr ":" "\n"); do
+ path="$(get_abs_path "$file")"
+ echo " <classpath path=\"${path}\"/>"
done
# For each rsp file, print source entries
while (( "$#" )); do
- for file in $(cat $1); do
+ for file in $(cat "$1"); do
+ path="$(get_abs_path "$file")"
if [[ $file == *.java ]]; then
- echo " <javaSourceRoots path=\"${prefix}/${file}\"/>"
+ echo " <javaSourceRoots path=\"${path}\"/>"
elif [[ $file == *.kt ]]; then
- echo " <sources path=\"${prefix}/${file}\"/>"
+ echo " <sources path=\"${path}\"/>"
else
echo "Unknown source file type ${file}"
exit 1
diff --git a/scripts/manifest_fixer.py b/scripts/manifest_fixer.py
new file mode 100755
index 0000000..6af0ca9
--- /dev/null
+++ b/scripts/manifest_fixer.py
@@ -0,0 +1,247 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2018 The Android Open Source Project
+#
+# 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.
+#
+"""A tool for inserting values from the build system into a manifest."""
+
+from __future__ import print_function
+import argparse
+import sys
+from xml.dom import minidom
+
+
+android_ns = 'http://schemas.android.com/apk/res/android'
+
+
+def get_children_with_tag(parent, tag_name):
+ children = []
+ for child in parent.childNodes:
+ if child.nodeType == minidom.Node.ELEMENT_NODE and \
+ child.tagName == tag_name:
+ children.append(child)
+ return children
+
+
+def find_child_with_attribute(element, tag_name, namespace_uri,
+ attr_name, value):
+ for child in get_children_with_tag(element, tag_name):
+ attr = child.getAttributeNodeNS(namespace_uri, attr_name)
+ if attr is not None and attr.value == value:
+ return child
+ return None
+
+
+def parse_args():
+ """Parse commandline arguments."""
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--minSdkVersion', default='', dest='min_sdk_version',
+ help='specify minSdkVersion used by the build system')
+ parser.add_argument('--uses-library', dest='uses_libraries', action='append',
+ help='specify additional <uses-library> tag to add')
+ parser.add_argument('input', help='input AndroidManifest.xml file')
+ parser.add_argument('output', help='output AndroidManifest.xml file')
+ return parser.parse_args()
+
+
+def parse_manifest(doc):
+ """Get the manifest element."""
+
+ manifest = doc.documentElement
+ if manifest.tagName != 'manifest':
+ raise RuntimeError('expected manifest tag at root')
+ return manifest
+
+
+def ensure_manifest_android_ns(doc):
+ """Make sure the manifest tag defines the android namespace."""
+
+ manifest = parse_manifest(doc)
+
+ ns = manifest.getAttributeNodeNS(minidom.XMLNS_NAMESPACE, 'android')
+ if ns is None:
+ attr = doc.createAttributeNS(minidom.XMLNS_NAMESPACE, 'xmlns:android')
+ attr.value = android_ns
+ manifest.setAttributeNode(attr)
+ elif ns.value != android_ns:
+ raise RuntimeError('manifest tag has incorrect android namespace ' +
+ ns.value)
+
+
+def as_int(s):
+ try:
+ i = int(s)
+ except ValueError:
+ return s, False
+ return i, True
+
+
+def compare_version_gt(a, b):
+ """Compare two SDK versions.
+
+ Compares a and b, treating codenames like 'Q' as higher
+ than numerical versions like '28'.
+
+ Returns True if a > b
+
+ Args:
+ a: value to compare
+ b: value to compare
+ Returns:
+ True if a is a higher version than b
+ """
+
+ a, a_is_int = as_int(a.upper())
+ b, b_is_int = as_int(b.upper())
+
+ if a_is_int == b_is_int:
+ # Both are codenames or both are versions, compare directly
+ return a > b
+ else:
+ # One is a codename, the other is not. Return true if
+ # b is an integer version
+ return b_is_int
+
+
+def get_indent(element, default_level):
+ indent = ''
+ if element is not None and element.nodeType == minidom.Node.TEXT_NODE:
+ text = element.nodeValue
+ indent = text[:len(text)-len(text.lstrip())]
+ if not indent or indent == '\n':
+ # 1 indent = 4 space
+ indent = '\n' + (' ' * default_level * 4)
+ return indent
+
+
+def raise_min_sdk_version(doc, requested):
+ """Ensure the manifest contains a <uses-sdk> tag with a minSdkVersion.
+
+ Args:
+ doc: The XML document. May be modified by this function.
+ requested: The requested minSdkVersion attribute.
+ Raises:
+ RuntimeError: invalid manifest
+ """
+
+ manifest = parse_manifest(doc)
+
+ # Get or insert the uses-sdk element
+ uses_sdk = get_children_with_tag(manifest, 'uses-sdk')
+ if len(uses_sdk) > 1:
+ raise RuntimeError('found multiple uses-sdk elements')
+ elif len(uses_sdk) == 1:
+ element = uses_sdk[0]
+ else:
+ element = doc.createElement('uses-sdk')
+ indent = get_indent(manifest.firstChild, 1)
+ manifest.insertBefore(element, manifest.firstChild)
+
+ # Insert an indent before uses-sdk to line it up with the indentation of the
+ # other children of the <manifest> tag.
+ manifest.insertBefore(doc.createTextNode(indent), manifest.firstChild)
+
+ # Get or insert the minSdkVersion attribute
+ min_attr = element.getAttributeNodeNS(android_ns, 'minSdkVersion')
+ if min_attr is None:
+ min_attr = doc.createAttributeNS(android_ns, 'android:minSdkVersion')
+ min_attr.value = '1'
+ element.setAttributeNode(min_attr)
+
+ # Update the value of the minSdkVersion attribute if necessary
+ if compare_version_gt(requested, min_attr.value):
+ min_attr.value = requested
+
+
+def add_uses_libraries(doc, new_uses_libraries):
+ """Add additional <uses-library> tags with android:required=true.
+
+ Args:
+ doc: The XML document. May be modified by this function.
+ new_uses_libraries: The names of libraries to be added by this function.
+ Raises:
+ RuntimeError: Invalid manifest
+ """
+
+ manifest = parse_manifest(doc)
+ elems = get_children_with_tag(manifest, 'application')
+ application = elems[0] if len(elems) == 1 else None
+ if len(elems) > 1:
+ raise RuntimeError('found multiple <application> tags')
+ elif not elems:
+ application = doc.createElement('application')
+ indent = get_indent(manifest.firstChild, 1)
+ first = manifest.firstChild
+ manifest.insertBefore(doc.createTextNode(indent), first)
+ manifest.insertBefore(application, first)
+
+ indent = get_indent(application.firstChild, 2)
+
+ last = application.lastChild
+ if last is not None and last.nodeType != minidom.Node.TEXT_NODE:
+ last = None
+
+ for name in new_uses_libraries:
+ if find_child_with_attribute(application, 'uses-library', android_ns,
+ 'name', name) is not None:
+ # If the uses-library tag of the same 'name' attribute value exists,
+ # respect it.
+ continue
+
+ ul = doc.createElement('uses-library')
+ ul.setAttributeNS(android_ns, 'android:name', name)
+ ul.setAttributeNS(android_ns, 'android:required', 'true')
+
+ application.insertBefore(doc.createTextNode(indent), last)
+ application.insertBefore(ul, last)
+
+ # align the closing tag with the opening tag if it's not
+ # indented
+ if application.lastChild.nodeType != minidom.Node.TEXT_NODE:
+ indent = get_indent(application.previousSibling, 1)
+ application.appendChild(doc.createTextNode(indent))
+
+
+def write_xml(f, doc):
+ f.write('<?xml version="1.0" encoding="utf-8"?>\n')
+ for node in doc.childNodes:
+ f.write(node.toxml(encoding='utf-8') + '\n')
+
+
+def main():
+ """Program entry point."""
+ try:
+ args = parse_args()
+
+ doc = minidom.parse(args.input)
+
+ ensure_manifest_android_ns(doc)
+
+ if args.min_sdk_version:
+ raise_min_sdk_version(doc, args.min_sdk_version)
+
+ if args.uses_libraries:
+ add_uses_libraries(doc, args.uses_libraries)
+
+ with open(args.output, 'wb') as f:
+ write_xml(f, doc)
+
+ # pylint: disable=broad-except
+ except Exception as err:
+ print('error: ' + str(err), file=sys.stderr)
+ sys.exit(-1)
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/manifest_fixer_test.py b/scripts/manifest_fixer_test.py
new file mode 100755
index 0000000..54a3784
--- /dev/null
+++ b/scripts/manifest_fixer_test.py
@@ -0,0 +1,249 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2018 The Android Open Source Project
+#
+# 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.
+#
+"""Unit tests for manifest_fixer_test.py."""
+
+import StringIO
+import sys
+import unittest
+from xml.dom import minidom
+
+import manifest_fixer
+
+sys.dont_write_bytecode = True
+
+
+class CompareVersionGtTest(unittest.TestCase):
+ """Unit tests for compare_version_gt function."""
+
+ def test_sdk(self):
+ """Test comparing sdk versions."""
+ self.assertTrue(manifest_fixer.compare_version_gt('28', '27'))
+ self.assertFalse(manifest_fixer.compare_version_gt('27', '28'))
+ self.assertFalse(manifest_fixer.compare_version_gt('28', '28'))
+
+ def test_codename(self):
+ """Test comparing codenames."""
+ self.assertTrue(manifest_fixer.compare_version_gt('Q', 'P'))
+ self.assertFalse(manifest_fixer.compare_version_gt('P', 'Q'))
+ self.assertFalse(manifest_fixer.compare_version_gt('Q', 'Q'))
+
+ def test_sdk_codename(self):
+ """Test comparing sdk versions with codenames."""
+ self.assertTrue(manifest_fixer.compare_version_gt('Q', '28'))
+ self.assertFalse(manifest_fixer.compare_version_gt('28', 'Q'))
+
+ def test_compare_numeric(self):
+ """Test that numbers are compared in numeric and not lexicographic order."""
+ self.assertTrue(manifest_fixer.compare_version_gt('18', '8'))
+
+
+class RaiseMinSdkVersionTest(unittest.TestCase):
+ """Unit tests for raise_min_sdk_version function."""
+
+ def raise_min_sdk_version_test(self, input_manifest, min_sdk_version):
+ doc = minidom.parseString(input_manifest)
+ manifest_fixer.raise_min_sdk_version(doc, min_sdk_version)
+ output = StringIO.StringIO()
+ manifest_fixer.write_xml(output, doc)
+ return output.getvalue()
+
+ manifest_tmpl = (
+ '<?xml version="1.0" encoding="utf-8"?>\n'
+ '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n'
+ '%s'
+ '</manifest>\n')
+
+ def uses_sdk(self, v, extra=''):
+ if extra:
+ extra = ' ' + extra
+ return ' <uses-sdk android:minSdkVersion="%s"%s/>\n' % (v, extra)
+
+ def test_no_uses_sdk(self):
+ """Tests inserting a uses-sdk element into a manifest."""
+
+ manifest_input = self.manifest_tmpl % ''
+ expected = self.manifest_tmpl % self.uses_sdk('28')
+ output = self.raise_min_sdk_version_test(manifest_input, '28')
+ self.assertEqual(output, expected)
+
+ def test_no_min(self):
+ """Tests inserting a minSdkVersion attribute into a uses-sdk element."""
+
+ manifest_input = self.manifest_tmpl % ' <uses-sdk extra="foo"/>\n'
+ expected = self.manifest_tmpl % self.uses_sdk('28', 'extra="foo"')
+ output = self.raise_min_sdk_version_test(manifest_input, '28')
+ self.assertEqual(output, expected)
+
+ def test_raise_min(self):
+ """Tests inserting a minSdkVersion attribute into a uses-sdk element."""
+
+ manifest_input = self.manifest_tmpl % self.uses_sdk('27')
+ expected = self.manifest_tmpl % self.uses_sdk('28')
+ output = self.raise_min_sdk_version_test(manifest_input, '28')
+ self.assertEqual(output, expected)
+
+ def test_raise(self):
+ """Tests raising a minSdkVersion attribute."""
+
+ manifest_input = self.manifest_tmpl % self.uses_sdk('27')
+ expected = self.manifest_tmpl % self.uses_sdk('28')
+ output = self.raise_min_sdk_version_test(manifest_input, '28')
+ self.assertEqual(output, expected)
+
+ def test_no_raise_min(self):
+ """Tests a minSdkVersion that doesn't need raising."""
+
+ manifest_input = self.manifest_tmpl % self.uses_sdk('28')
+ expected = manifest_input
+ output = self.raise_min_sdk_version_test(manifest_input, '27')
+ self.assertEqual(output, expected)
+
+ def test_raise_codename(self):
+ """Tests raising a minSdkVersion attribute to a codename."""
+
+ manifest_input = self.manifest_tmpl % self.uses_sdk('28')
+ expected = self.manifest_tmpl % self.uses_sdk('P')
+ output = self.raise_min_sdk_version_test(manifest_input, 'P')
+ self.assertEqual(output, expected)
+
+ def test_no_raise_codename(self):
+ """Tests a minSdkVersion codename that doesn't need raising."""
+
+ manifest_input = self.manifest_tmpl % self.uses_sdk('P')
+ expected = manifest_input
+ output = self.raise_min_sdk_version_test(manifest_input, '28')
+ self.assertEqual(output, expected)
+
+ def test_extra(self):
+ """Tests that extra attributes and elements are maintained."""
+
+ manifest_input = self.manifest_tmpl % (
+ ' <!-- comment -->\n'
+ ' <uses-sdk android:minSdkVersion="27" extra="foo"/>\n'
+ ' <application/>\n')
+
+ expected = self.manifest_tmpl % (
+ ' <!-- comment -->\n'
+ ' <uses-sdk android:minSdkVersion="28" extra="foo"/>\n'
+ ' <application/>\n')
+
+ output = self.raise_min_sdk_version_test(manifest_input, '28')
+
+ self.assertEqual(output, expected)
+
+ def test_indent(self):
+ """Tests that an inserted element copies the existing indentation."""
+
+ manifest_input = self.manifest_tmpl % ' <!-- comment -->\n'
+
+ expected = self.manifest_tmpl % (
+ ' <uses-sdk android:minSdkVersion="28"/>\n'
+ ' <!-- comment -->\n')
+
+ output = self.raise_min_sdk_version_test(manifest_input, '28')
+
+ self.assertEqual(output, expected)
+
+
+class AddUsesLibrariesTest(unittest.TestCase):
+ """Unit tests for add_uses_libraries function."""
+
+ def run_test(self, input_manifest, new_uses_libraries):
+ doc = minidom.parseString(input_manifest)
+ manifest_fixer.add_uses_libraries(doc, new_uses_libraries)
+ output = StringIO.StringIO()
+ manifest_fixer.write_xml(output, doc)
+ return output.getvalue()
+
+ manifest_tmpl = (
+ '<?xml version="1.0" encoding="utf-8"?>\n'
+ '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n'
+ ' <application>\n'
+ '%s'
+ ' </application>\n'
+ '</manifest>\n')
+
+ def uses_libraries(self, name_required_pairs):
+ ret = ''
+ for name, required in name_required_pairs:
+ ret += (
+ ' <uses-library android:name="%s" android:required="%s"/>\n'
+ ) % (name, required)
+
+ return ret
+
+ def test_empty(self):
+ """Empty new_uses_libraries must not touch the manifest."""
+ manifest_input = self.manifest_tmpl % self.uses_libraries([
+ ('foo', 'true'),
+ ('bar', 'false')])
+ expected = manifest_input
+ output = self.run_test(manifest_input, [])
+ self.assertEqual(output, expected)
+
+ def test_not_overwrite(self):
+ """new_uses_libraries must not overwrite existing tags."""
+ manifest_input = self.manifest_tmpl % self.uses_libraries([
+ ('foo', 'true'),
+ ('bar', 'false')])
+ expected = manifest_input
+ output = self.run_test(manifest_input, ['foo', 'bar'])
+ self.assertEqual(output, expected)
+
+ def test_add(self):
+ """New names are added with 'required:true'."""
+ manifest_input = self.manifest_tmpl % self.uses_libraries([
+ ('foo', 'true'),
+ ('bar', 'false')])
+ expected = self.manifest_tmpl % self.uses_libraries([
+ ('foo', 'true'),
+ ('bar', 'false'),
+ ('baz', 'true'),
+ ('qux', 'true')])
+ output = self.run_test(manifest_input, ['bar', 'baz', 'qux'])
+ self.assertEqual(output, expected)
+
+ def test_no_application(self):
+ """When there is no <application> tag, the tag is added."""
+ manifest_input = (
+ '<?xml version="1.0" encoding="utf-8"?>\n'
+ '<manifest xmlns:android='
+ '"http://schemas.android.com/apk/res/android">\n'
+ '</manifest>\n')
+ expected = self.manifest_tmpl % self.uses_libraries([
+ ('foo', 'true'),
+ ('bar', 'true')])
+ output = self.run_test(manifest_input, ['foo', 'bar'])
+ self.assertEqual(output, expected)
+
+ def test_empty_application(self):
+ """Even when here is an empty <application/> tag, the libs are added."""
+ manifest_input = (
+ '<?xml version="1.0" encoding="utf-8"?>\n'
+ '<manifest xmlns:android='
+ '"http://schemas.android.com/apk/res/android">\n'
+ ' <application/>\n'
+ '</manifest>\n')
+ expected = self.manifest_tmpl % self.uses_libraries([
+ ('foo', 'true'),
+ ('bar', 'true')])
+ output = self.run_test(manifest_input, ['foo', 'bar'])
+ self.assertEqual(output, expected)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/scripts/microfactory.bash b/scripts/microfactory.bash
index 65ba55d..4bb6058 100644
--- a/scripts/microfactory.bash
+++ b/scripts/microfactory.bash
@@ -59,7 +59,7 @@
BUILDDIR=$(getoutdir) \
SRCDIR=${TOP} \
BLUEPRINTDIR=${TOP}/build/blueprint \
- EXTRA_ARGS="-pkg-path android/soong=${TOP}/build/soong" \
+ EXTRA_ARGS="-pkg-path android/soong=${TOP}/build/soong -pkg-path github.com/golang/protobuf=${TOP}/external/golang-protobuf" \
build_go $@
}
diff --git a/scripts/strip.sh b/scripts/strip.sh
index 318c7ad..432582f 100755
--- a/scripts/strip.sh
+++ b/scripts/strip.sh
@@ -17,15 +17,17 @@
# Script to handle the various ways soong may need to strip binaries
# Inputs:
# Environment:
+# CLANG_BIN: path to the clang bin directory
# CROSS_COMPILE: prefix added to readelf, objcopy tools
# XZ: path to the xz binary
# Arguments:
# -i ${file}: input file (required)
# -o ${file}: output file (required)
# -d ${file}: deps file (required)
-# --keep-symbols
-# --keep-mini-debug-info
# --add-gnu-debuglink
+# --keep-mini-debug-info
+# --keep-symbols
+# --use-llvm-strip
OPTSTRING=d:i:o:-:
@@ -33,25 +35,52 @@
cat <<EOF
Usage: strip.sh [options] -i in-file -o out-file -d deps-file
Options:
- --keep-symbols Keep symbols in out-file
- --keep-mini-debug-info Keep compressed debug info in out-file
--add-gnu-debuglink Add a gnu-debuglink section to out-file
+ --keep-mini-debug-info Keep compressed debug info in out-file
+ --keep-symbols Keep symbols in out-file
+ --use-llvm-strip Use llvm-{strip,objcopy} instead of strip/objcopy
EOF
exit 1
}
+# With --use-llvm-strip, GNU strip is replaced with llvm-strip to work around
+# old GNU strip bug on lld output files, b/80093681.
+# Similary, calls to objcopy are replaced with llvm-objcopy,
+# with some exceptions.
+
do_strip() {
- "${CROSS_COMPILE}strip" --strip-all "${infile}" -o "${outfile}.tmp"
+ # ${CROSS_COMPILE}strip --strip-all does not strip .ARM.attributes,
+ # so we tell llvm-strip to keep it too.
+ if [ ! -z "${use_llvm_strip}" ]; then
+ "${CLANG_BIN}/llvm-strip" --strip-all -keep=.ARM.attributes "${infile}" "${outfile}.tmp"
+ else
+ "${CROSS_COMPILE}strip" --strip-all "${infile}" -o "${outfile}.tmp"
+ fi
}
do_strip_keep_symbols() {
- "${CROSS_COMPILE}objcopy" "${infile}" "${outfile}.tmp" \
- `"${CROSS_COMPILE}readelf" -S "${infile}" | awk '/.debug_/ {print "-R " $2}' | xargs`
+ # Maybe we should replace this objcopy with llvm-objcopy, but
+ # we have not found a use case that is broken by objcopy yet.
+ REMOVE_SECTIONS=`"${CROSS_COMPILE}readelf" -S "${infile}" | awk '/.debug_/ {print "--remove-section " $2}' | xargs`
+ if [ ! -z "${use_llvm_strip}" ]; then
+ "${CROSS_COMPILE}objcopy" "${infile}" "${outfile}.tmp" ${REMOVE_SECTIONS}
+ else
+ "${CLANG_BIN}/llvm-objcopy" "${infile}" "${outfile}.tmp" ${REMOVE_SECTIONS}
+ fi
}
do_strip_keep_mini_debug_info() {
rm -f "${outfile}.dynsyms" "${outfile}.funcsyms" "${outfile}.keep_symbols" "${outfile}.debug" "${outfile}.mini_debuginfo" "${outfile}.mini_debuginfo.xz"
- if "${CROSS_COMPILE}strip" --strip-all -R .comment "${infile}" -o "${outfile}.tmp"; then
+ if [ ! -z "${use_llvm_strip}" ]; then
+ "${CLANG_BIN}/llvm-strip" --strip-all -keep=.ARM.attributes -remove-section=.comment "${infile}" "${outfile}.tmp"
+ else
+ "${CROSS_COMPILE}strip" --strip-all -R .comment "${infile}" -o "${outfile}.tmp"
+ fi
+ if [ "$?" == "0" ]; then
+ # Current prebult llvm-objcopy does not support the following flags:
+ # --only-keep-debug --rename-section --keep-symbols
+ # For the following use cases, ${CROSS_COMPILE}objcopy does fine with lld linked files,
+ # except the --add-section flag.
"${CROSS_COMPILE}objcopy" --only-keep-debug "${infile}" "${outfile}.debug"
"${CROSS_COMPILE}nm" -D "${infile}" --format=posix --defined-only | awk '{ print $$1 }' | sort >"${outfile}.dynsyms"
"${CROSS_COMPILE}nm" "${infile}" --format=posix --defined-only | awk '{ if ($$2 == "T" || $$2 == "t" || $$2 == "D") print $$1 }' | sort > "${outfile}.funcsyms"
@@ -61,14 +90,22 @@
"${CROSS_COMPILE}objcopy" -S --remove-section .gdb_index --remove-section .comment --keep-symbols="${outfile}.keep_symbols" "${outfile}.mini_debuginfo"
"${CROSS_COMPILE}objcopy" --rename-section saved_debug_frame=.debug_frame "${outfile}.mini_debuginfo"
"${XZ}" "${outfile}.mini_debuginfo"
- "${CROSS_COMPILE}objcopy" --add-section .gnu_debugdata="${outfile}.mini_debuginfo.xz" "${outfile}.tmp"
+ if [ ! -z "${use_llvm_strip}" ]; then
+ "${CLANG_BIN}/llvm-objcopy" --add-section .gnu_debugdata="${outfile}.mini_debuginfo.xz" "${outfile}.tmp"
+ else
+ "${CROSS_COMPILE}objcopy" --add-section .gnu_debugdata="${outfile}.mini_debuginfo.xz" "${outfile}.tmp"
+ fi
else
cp -f "${infile}" "${outfile}.tmp"
fi
}
do_add_gnu_debuglink() {
- "${CROSS_COMPILE}objcopy" --add-gnu-debuglink="${infile}" "${outfile}.tmp"
+ if [ ! -z "${use_llvm_strip}" ]; then
+ "${CLANG_BIN}/llvm-objcopy" --add-gnu-debuglink="${infile}" "${outfile}.tmp"
+ else
+ "${CROSS_COMPILE}objcopy" --add-gnu-debuglink="${infile}" "${outfile}.tmp"
+ fi
}
while getopts $OPTSTRING opt; do
@@ -78,9 +115,10 @@
o) outfile="${OPTARG}" ;;
-)
case "${OPTARG}" in
- keep-symbols) keep_symbols=true ;;
- keep-mini-debug-info) keep_mini_debug_info=true ;;
add-gnu-debuglink) add_gnu_debuglink=true ;;
+ keep-mini-debug-info) keep_mini_debug_info=true ;;
+ keep-symbols) keep_symbols=true ;;
+ use-llvm-strip) use_llvm_strip=true ;;
*) echo "Unknown option --${OPTARG}"; usage ;;
esac;;
?) usage ;;
@@ -130,12 +168,18 @@
rm -f "${outfile}"
mv "${outfile}.tmp" "${outfile}"
+if [ ! -z "${use_llvm_strip}" ]; then
+ USED_STRIP_OBJCOPY="${CLANG_BIN}/llvm-strip ${CLANG_BIN}/llvm-objcopy"
+else
+ USED_STRIP_OBJCOPY="${CROSS_COMPILE}strip"
+fi
+
cat <<EOF > "${depsfile}"
${outfile}: \
${infile} \
${CROSS_COMPILE}nm \
${CROSS_COMPILE}objcopy \
${CROSS_COMPILE}readelf \
- ${CROSS_COMPILE}strip
+ ${USED_STRIP_OBJCOPY}
EOF
diff --git a/scripts/toc.sh b/scripts/toc.sh
index 7b2224c..bd6425b 100755
--- a/scripts/toc.sh
+++ b/scripts/toc.sh
@@ -39,8 +39,8 @@
}
do_macho() {
- otool -l "${infile}" | grep LC_ID_DYLIB -A 5 > "${outfile}.tmp"
- nm -gP "${infile}" | cut -f1-2 -d" " | grep -v 'U$' >> "${outfile}.tmp"
+ "${CROSS_COMPILE}/otool" -l "${infile}" | grep LC_ID_DYLIB -A 5 > "${outfile}.tmp"
+ "${CROSS_COMPILE}/nm" -gP "${infile}" | cut -f1-2 -d" " | grep -v 'U$' >> "${outfile}.tmp"
}
diff --git a/ui/build/Android.bp b/ui/build/Android.bp
index 5809894..a48a314 100644
--- a/ui/build/Android.bp
+++ b/ui/build/Android.bp
@@ -13,10 +13,25 @@
// limitations under the License.
bootstrap_go_package {
+ name: "soong-ui-build-paths",
+ pkgPath: "android/soong/ui/build/paths",
+ srcs: [
+ "paths/config.go",
+ "paths/logs.go",
+ ],
+ testSrcs: [
+ "paths/logs_test.go",
+ ],
+}
+
+bootstrap_go_package {
name: "soong-ui-build",
pkgPath: "android/soong/ui/build",
deps: [
+ "soong-ui-build-paths",
"soong-ui-logger",
+ "soong-ui-status",
+ "soong-ui-terminal",
"soong-ui-tracer",
"soong-shared",
"soong-finder",
@@ -33,6 +48,7 @@
"finder.go",
"kati.go",
"ninja.go",
+ "path.go",
"proc_sync.go",
"signal.go",
"soong.go",
@@ -48,13 +64,11 @@
darwin: {
srcs: [
"sandbox_darwin.go",
- "util_darwin.go"
],
},
linux: {
srcs: [
"sandbox_linux.go",
- "util_linux.go"
],
},
}
diff --git a/ui/build/build.go b/ui/build/build.go
index 78eb6a3..96cfdbb 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -40,7 +40,6 @@
{{if .HasKatiSuffix}}include {{.KatiNinjaFile}}
{{end -}}
include {{.SoongNinjaFile}}
-build {{.CombinedNinjaFile}}: phony {{.SoongNinjaFile}}
`))
func createCombinedBuildNinjaFile(ctx Context, config Config) {
@@ -106,9 +105,7 @@
func help(ctx Context, config Config, what int) {
cmd := Command(ctx, config, "help.sh", "build/make/help.sh")
cmd.Sandbox = dumpvarsSandbox
- cmd.Stdout = ctx.Stdout()
- cmd.Stderr = ctx.Stderr()
- cmd.RunOrFatal()
+ cmd.RunAndPrintOrFatal()
}
// Build the tree. The 'what' argument can be used to chose which components of
@@ -140,6 +137,8 @@
ensureEmptyDirectoriesExist(ctx, config.TempDir())
+ SetupPath(ctx, config)
+
if what&BuildProductConfig != 0 {
// Run make for product config
runMakeProductConfig(ctx, config)
diff --git a/ui/build/cleanbuild.go b/ui/build/cleanbuild.go
index f2de2cd..24a8c7a 100644
--- a/ui/build/cleanbuild.go
+++ b/ui/build/cleanbuild.go
@@ -107,6 +107,7 @@
productOut("system"),
productOut("system_other"),
productOut("vendor"),
+ productOut("product"),
productOut("oem"),
productOut("obj/FAKE"),
productOut("breakpad"),
diff --git a/ui/build/config.go b/ui/build/config.go
index 5622dff..1f7656e 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -50,7 +50,10 @@
targetDevice string
targetDeviceDir string
+ pdkBuild bool
brokenDupRules bool
+
+ pathReplaced bool
}
const srcDirFileCheck = "build/soong/root.bp"
@@ -115,6 +118,18 @@
// Set in envsetup.sh, reset in makefiles
"ANDROID_JAVA_TOOLCHAIN",
+
+ // Set by envsetup.sh, but shouldn't be used inside the build because envsetup.sh is optional
+ "ANDROID_BUILD_TOP",
+ "ANDROID_HOST_OUT",
+ "ANDROID_PRODUCT_OUT",
+ "ANDROID_HOST_OUT_TESTCASES",
+ "ANDROID_TARGET_OUT_TESTCASES",
+ "ANDROID_TOOLCHAIN",
+ "ANDROID_TOOLCHAIN_2ND_ARCH",
+ "ANDROID_DEV_SCRIPTS",
+ "ANDROID_EMULATOR_PREBUILTS",
+ "ANDROID_PRE_BUILD_PATHS",
)
// Tell python not to spam the source tree with .pyc files.
@@ -161,19 +176,7 @@
if override, ok := ret.environ.Get("OVERRIDE_ANDROID_JAVA_HOME"); ok {
return override
}
- v, ok := ret.environ.Get("EXPERIMENTAL_USE_OPENJDK9")
- if !ok {
- v2, ok2 := ret.environ.Get("RUN_ERROR_PRONE")
- if ok2 && (v2 == "true") {
- v = "false"
- } else {
- v = "1.8"
- }
- }
- if v != "false" {
- return java9Home
- }
- return java8Home
+ return java9Home
}()
absJavaHome := absPath(ctx, javaHome)
@@ -577,3 +580,11 @@
func (c *configImpl) TargetDeviceDir() string {
return c.targetDeviceDir
}
+
+func (c *configImpl) SetPdkBuild(pdk bool) {
+ c.pdkBuild = pdk
+}
+
+func (c *configImpl) IsPdkBuild() bool {
+ return c.pdkBuild
+}
diff --git a/ui/build/config_test.go b/ui/build/config_test.go
index e4eab94..242e3af 100644
--- a/ui/build/config_test.go
+++ b/ui/build/config_test.go
@@ -22,13 +22,14 @@
"testing"
"android/soong/ui/logger"
+ "android/soong/ui/terminal"
)
func testContext() Context {
return Context{&ContextImpl{
- Context: context.Background(),
- Logger: logger.New(&bytes.Buffer{}),
- StdioInterface: NewCustomStdio(&bytes.Buffer{}, &bytes.Buffer{}, &bytes.Buffer{}),
+ Context: context.Background(),
+ Logger: logger.New(&bytes.Buffer{}),
+ Writer: terminal.NewWriter(terminal.NewCustomStdio(&bytes.Buffer{}, &bytes.Buffer{}, &bytes.Buffer{})),
}}
}
diff --git a/ui/build/context.go b/ui/build/context.go
index 0636631..c8b00c3 100644
--- a/ui/build/context.go
+++ b/ui/build/context.go
@@ -16,45 +16,14 @@
import (
"context"
- "io"
- "os"
- "time"
"android/soong/ui/logger"
+ "android/soong/ui/status"
+ "android/soong/ui/terminal"
"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.
+// Context combines a context.Context, logger.Logger, and terminal.Writer.
// 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 }
@@ -62,7 +31,8 @@
context.Context
logger.Logger
- StdioInterface
+ Writer terminal.Writer
+ Status *status.Status
Thread tracer.Thread
Tracer tracer.Tracer
@@ -88,28 +58,3 @@
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
-}
-
-func (c ContextImpl) IsErrTerminal() bool {
- if term, ok := os.LookupEnv("TERM"); ok {
- return term != "dumb" && isTerminal(c.Stderr())
- }
- return false
-}
-
-func (c ContextImpl) TermWidth() (int, bool) {
- return termWidth(c.Stdout())
-}
diff --git a/ui/build/dumpvars.go b/ui/build/dumpvars.go
index cc9e742..06bd74f 100644
--- a/ui/build/dumpvars.go
+++ b/ui/build/dumpvars.go
@@ -18,6 +18,8 @@
"bytes"
"fmt"
"strings"
+
+ "android/soong/ui/status"
)
// DumpMakeVars can be used to extract the values of Make variables after the
@@ -60,7 +62,7 @@
}
cmd.StartOrFatal()
// TODO: error out when Stderr contains any content
- katiRewriteOutput(ctx, pipe)
+ status.KatiReader(ctx.Status.StartTool(), pipe)
cmd.WaitOrFatal()
ret := make(map[string]string, len(vars))
@@ -162,6 +164,11 @@
// Whether --werror_overriding_commands will work
"BUILD_BROKEN_DUP_RULES",
+
+ // Not used, but useful to be in the soong.log
+ "BUILD_BROKEN_ANDROIDMK_EXPORTS",
+ "BUILD_BROKEN_DUP_COPY_HEADERS",
+ "BUILD_BROKEN_PHONY_TARGETS",
}, exportEnvVars...), BannerVars...)
make_vars, err := dumpMakeVars(ctx, config, config.Arguments(), allVars, true)
@@ -170,7 +177,7 @@
}
// Print the banner like make does
- fmt.Fprintln(ctx.Stdout(), Banner(make_vars))
+ ctx.Writer.Print(Banner(make_vars))
// Populate the environment
env := config.Environment()
@@ -187,5 +194,6 @@
config.SetTargetDevice(make_vars["TARGET_DEVICE"])
config.SetTargetDeviceDir(make_vars["TARGET_DEVICE_DIR"])
+ config.SetPdkBuild(make_vars["TARGET_BUILD_PDK"] == "true")
config.SetBuildBrokenDupRules(make_vars["BUILD_BROKEN_DUP_RULES"] != "false")
}
diff --git a/ui/build/exec.go b/ui/build/exec.go
index 90fb19d..5c312bc 100644
--- a/ui/build/exec.go
+++ b/ui/build/exec.go
@@ -122,3 +122,20 @@
c.reportError(err)
return ret
}
+
+// RunAndPrintOrFatal will run the command, then after finishing
+// print any output, then handling any errors with a call to
+// ctx.Fatal
+func (c *Cmd) RunAndPrintOrFatal() {
+ ret, err := c.CombinedOutput()
+ st := c.ctx.Status.StartTool()
+ if len(ret) > 0 {
+ if err != nil {
+ st.Error(string(ret))
+ } else {
+ st.Print(string(ret))
+ }
+ }
+ st.Finish()
+ c.reportError(err)
+}
diff --git a/ui/build/kati.go b/ui/build/kati.go
index 81edd32..7cfa1cf 100644
--- a/ui/build/kati.go
+++ b/ui/build/kati.go
@@ -15,15 +15,14 @@
package build
import (
- "bufio"
"crypto/md5"
"fmt"
- "io"
"io/ioutil"
"path/filepath"
- "regexp"
"strconv"
"strings"
+
+ "android/soong/ui/status"
)
var spaceSlashReplacer = strings.NewReplacer("/", "_", " ", "_")
@@ -77,10 +76,17 @@
"--color_warnings",
"--gen_all_targets",
"--werror_find_emulator",
+ "--no_builtin_rules",
+ "--werror_suffix_rules",
"--kati_stats",
"-f", "build/make/core/main.mk",
}
+ // PDK builds still uses a few implicit rules
+ if !config.IsPdkBuild() {
+ args = append(args, "--werror_implicit_rules")
+ }
+
if !config.BuildBrokenDupRules() {
args = append(args, "--werror_overriding_commands")
}
@@ -110,77 +116,10 @@
cmd.Stderr = cmd.Stdout
cmd.StartOrFatal()
- katiRewriteOutput(ctx, pipe)
+ status.KatiReader(ctx.Status.StartTool(), pipe)
cmd.WaitOrFatal()
}
-var katiIncludeRe = regexp.MustCompile(`^(\[\d+/\d+] )?including [^ ]+ ...$`)
-var katiLogRe = regexp.MustCompile(`^\*kati\*: `)
-
-func katiRewriteOutput(ctx Context, pipe io.ReadCloser) {
- haveBlankLine := true
- smartTerminal := ctx.IsTerminal()
- errSmartTerminal := ctx.IsErrTerminal()
-
- scanner := bufio.NewScanner(pipe)
- for scanner.Scan() {
- line := scanner.Text()
- verbose := katiIncludeRe.MatchString(line)
-
- // Only put kati debug/stat lines in our verbose log
- if katiLogRe.MatchString(line) {
- ctx.Verbose(line)
- continue
- }
-
- // 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 !errSmartTerminal {
- // 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())
- }
-
- if err := scanner.Err(); err != nil {
- ctx.Println("Error from kati parser:", err)
- io.Copy(ctx.Stderr(), pipe)
- }
-}
-
func runKatiCleanSpec(ctx Context, config Config) {
ctx.BeginTrace("kati cleanspec")
defer ctx.EndTrace()
@@ -213,6 +152,6 @@
cmd.Stderr = cmd.Stdout
cmd.StartOrFatal()
- katiRewriteOutput(ctx, pipe)
+ status.KatiReader(ctx.Status.StartTool(), pipe)
cmd.WaitOrFatal()
}
diff --git a/ui/build/ninja.go b/ui/build/ninja.go
index 96b5e9d..c48fe0f 100644
--- a/ui/build/ninja.go
+++ b/ui/build/ninja.go
@@ -21,15 +21,21 @@
"strconv"
"strings"
"time"
+
+ "android/soong/ui/status"
)
func runNinja(ctx Context, config Config) {
ctx.BeginTrace("ninja")
defer ctx.EndTrace()
+ fifo := filepath.Join(config.OutDir(), ".ninja_fifo")
+ status.NinjaReader(ctx, ctx.Status.StartTool(), fifo)
+
executable := config.PrebuiltBuildTool("ninja")
args := []string{
"-d", "keepdepfile",
+ fmt.Sprintf("--frontend=cat <&3 >%s", fifo),
}
args = append(args, config.NinjaArgs()...)
@@ -47,9 +53,6 @@
args = append(args, "-f", config.CombinedNinjaFile())
- if config.IsVerbose() {
- args = append(args, "-v")
- }
args = append(args, "-w", "dupbuild=err")
cmd := Command(ctx, config, "ninja", executable, args...)
@@ -66,13 +69,6 @@
cmd.Args = append(cmd.Args, strings.Fields(extra)...)
}
- if _, ok := cmd.Environment.Get("NINJA_STATUS"); !ok {
- cmd.Environment.Set("NINJA_STATUS", "[%p %f/%t] ")
- }
-
- cmd.Stdin = ctx.Stdin()
- cmd.Stdout = ctx.Stdout()
- cmd.Stderr = ctx.Stderr()
logPath := filepath.Join(config.OutDir(), ".ninja_log")
ninjaHeartbeatDuration := time.Minute * 5
if overrideText, ok := cmd.Environment.Get("NINJA_HEARTBEAT_INTERVAL"); ok {
@@ -99,10 +95,7 @@
}
}()
- startTime := time.Now()
- defer ctx.ImportNinjaLog(logPath, startTime)
-
- cmd.RunOrFatal()
+ cmd.RunAndPrintOrFatal()
}
type statusChecker struct {
diff --git a/ui/build/path.go b/ui/build/path.go
new file mode 100644
index 0000000..52658ef
--- /dev/null
+++ b/ui/build/path.go
@@ -0,0 +1,149 @@
+// Copyright 2018 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"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/google/blueprint/microfactory"
+
+ "android/soong/ui/build/paths"
+)
+
+func parsePathDir(dir string) []string {
+ f, err := os.Open(dir)
+ if err != nil {
+ return nil
+ }
+ defer f.Close()
+
+ if s, err := f.Stat(); err != nil || !s.IsDir() {
+ return nil
+ }
+
+ infos, err := f.Readdir(-1)
+ if err != nil {
+ return nil
+ }
+
+ ret := make([]string, 0, len(infos))
+ for _, info := range infos {
+ if m := info.Mode(); !m.IsDir() && m&0111 != 0 {
+ ret = append(ret, info.Name())
+ }
+ }
+ return ret
+}
+
+func SetupPath(ctx Context, config Config) {
+ if config.pathReplaced {
+ return
+ }
+
+ ctx.BeginTrace("path")
+ defer ctx.EndTrace()
+
+ origPath, _ := config.Environment().Get("PATH")
+ myPath := filepath.Join(config.OutDir(), ".path")
+ interposer := myPath + "_interposer"
+
+ var cfg microfactory.Config
+ cfg.Map("android/soong", "build/soong")
+ cfg.TrimPath, _ = filepath.Abs(".")
+ if _, err := microfactory.Build(&cfg, interposer, "android/soong/cmd/path_interposer"); err != nil {
+ ctx.Fatalln("Failed to build path interposer:", err)
+ }
+
+ if err := ioutil.WriteFile(interposer+"_origpath", []byte(origPath), 0777); err != nil {
+ ctx.Fatalln("Failed to write original path:", err)
+ }
+
+ entries, err := paths.LogListener(ctx.Context, interposer+"_log")
+ if err != nil {
+ ctx.Fatalln("Failed to listen for path logs:", err)
+ }
+
+ go func() {
+ for log := range entries {
+ curPid := os.Getpid()
+ for i, proc := range log.Parents {
+ if proc.Pid == curPid {
+ log.Parents = log.Parents[i:]
+ break
+ }
+ }
+ procPrints := []string{
+ "See https://android.googlesource.com/platform/build/+/master/Changes.md#PATH_Tools for more information.",
+ }
+ if len(log.Parents) > 0 {
+ procPrints = append(procPrints, "Process tree:")
+ for i, proc := range log.Parents {
+ procPrints = append(procPrints, fmt.Sprintf("%s→ %s", strings.Repeat(" ", i), proc.Command))
+ }
+ }
+
+ config := paths.GetConfig(log.Basename)
+ if config.Error {
+ ctx.Printf("Disallowed PATH tool %q used: %#v", log.Basename, log.Args)
+ for _, line := range procPrints {
+ ctx.Println(line)
+ }
+ } else {
+ ctx.Verbosef("Unknown PATH tool %q used: %#v", log.Basename, log.Args)
+ for _, line := range procPrints {
+ ctx.Verboseln(line)
+ }
+ }
+ }
+ }()
+
+ ensureEmptyDirectoriesExist(ctx, myPath)
+
+ var execs []string
+ for _, pathEntry := range filepath.SplitList(origPath) {
+ if pathEntry == "" {
+ // Ignore the current directory
+ continue
+ }
+ // TODO(dwillemsen): remove path entries under TOP? or anything
+ // that looks like an android source dir? They won't exist on
+ // the build servers, since they're added by envsetup.sh.
+ // (Except for the JDK, which is configured in ui/build/config.go)
+
+ execs = append(execs, parsePathDir(pathEntry)...)
+ }
+
+ allowAllSymlinks := config.Environment().IsEnvTrue("TEMPORARY_DISABLE_PATH_RESTRICTIONS")
+ for _, name := range execs {
+ if !paths.GetConfig(name).Symlink && !allowAllSymlinks {
+ continue
+ }
+
+ err := os.Symlink("../.path_interposer", filepath.Join(myPath, name))
+ // Intentionally ignore existing files -- that means that we
+ // just created it, and the first one should win.
+ if err != nil && !os.IsExist(err) {
+ ctx.Fatalln("Failed to create symlink:", err)
+ }
+ }
+
+ myPath, _ = filepath.Abs(myPath)
+ config.Environment().Set("PATH", myPath)
+ config.pathReplaced = true
+}
diff --git a/ui/build/paths/config.go b/ui/build/paths/config.go
new file mode 100644
index 0000000..4275601
--- /dev/null
+++ b/ui/build/paths/config.go
@@ -0,0 +1,168 @@
+// Copyright 2018 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 paths
+
+import "runtime"
+
+type PathConfig struct {
+ // Whether to create the symlink in the new PATH for this tool.
+ Symlink bool
+
+ // Whether to log about usages of this tool to the soong.log
+ Log bool
+
+ // Whether to exit with an error instead of invoking the underlying tool.
+ Error bool
+}
+
+var Allowed = PathConfig{
+ Symlink: true,
+ Log: false,
+ Error: false,
+}
+
+var Forbidden = PathConfig{
+ Symlink: false,
+ Log: true,
+ Error: true,
+}
+
+// The configuration used if the tool is not listed in the config below.
+// Currently this will create the symlink, but log a warning. In the future,
+// I expect this to move closer to Forbidden.
+var Missing = PathConfig{
+ Symlink: true,
+ Log: true,
+ Error: false,
+}
+
+func GetConfig(name string) PathConfig {
+ if config, ok := Configuration[name]; ok {
+ return config
+ }
+ return Missing
+}
+
+var Configuration = map[string]PathConfig{
+ "awk": Allowed,
+ "basename": Allowed,
+ "bash": Allowed,
+ "bc": Allowed,
+ "bzip2": Allowed,
+ "cat": Allowed,
+ "chmod": Allowed,
+ "cmp": Allowed,
+ "comm": Allowed,
+ "cp": Allowed,
+ "cut": Allowed,
+ "date": Allowed,
+ "dd": Allowed,
+ "diff": Allowed,
+ "dirname": Allowed,
+ "echo": Allowed,
+ "egrep": Allowed,
+ "env": Allowed,
+ "expr": Allowed,
+ "find": Allowed,
+ "getconf": Allowed,
+ "getopt": Allowed,
+ "git": Allowed,
+ "grep": Allowed,
+ "gzip": Allowed,
+ "head": Allowed,
+ "hexdump": Allowed,
+ "hostname": Allowed,
+ "id": Allowed,
+ "jar": Allowed,
+ "java": Allowed,
+ "javap": Allowed,
+ "ln": Allowed,
+ "ls": Allowed,
+ "m4": Allowed,
+ "make": Allowed,
+ "md5sum": Allowed,
+ "mkdir": Allowed,
+ "mktemp": Allowed,
+ "mv": Allowed,
+ "openssl": Allowed,
+ "patch": Allowed,
+ "perl": Allowed,
+ "pgrep": Allowed,
+ "pkill": Allowed,
+ "pstree": Allowed,
+ "pwd": Allowed,
+ "python": Allowed,
+ "python2.7": Allowed,
+ "python3": Allowed,
+ "readlink": Allowed,
+ "realpath": Allowed,
+ "rm": Allowed,
+ "rmdir": Allowed,
+ "rsync": Allowed,
+ "runalarm": Allowed,
+ "sed": Allowed,
+ "setsid": Allowed,
+ "sh": Allowed,
+ "sha1sum": Allowed,
+ "sha256sum": Allowed,
+ "sha512sum": Allowed,
+ "sleep": Allowed,
+ "sort": Allowed,
+ "stat": Allowed,
+ "sum": Allowed,
+ "tar": Allowed,
+ "tail": Allowed,
+ "touch": Allowed,
+ "tr": Allowed,
+ "true": Allowed,
+ "uname": Allowed,
+ "uniq": Allowed,
+ "unzip": Allowed,
+ "wc": Allowed,
+ "which": Allowed,
+ "whoami": Allowed,
+ "xargs": Allowed,
+ "xmllint": Allowed,
+ "xz": Allowed,
+ "zip": Allowed,
+ "zipinfo": Allowed,
+
+ // Host toolchain is removed. In-tree toolchain should be used instead.
+ // GCC also can't find cc1 with this implementation.
+ "ar": Forbidden,
+ "as": Forbidden,
+ "cc": Forbidden,
+ "clang": Forbidden,
+ "clang++": Forbidden,
+ "gcc": Forbidden,
+ "g++": Forbidden,
+ "ld": Forbidden,
+ "ld.bfd": Forbidden,
+ "ld.gold": Forbidden,
+ "pkg-config": Forbidden,
+
+ // We've got prebuilts of these
+ //"dtc": Forbidden,
+ //"lz4": Forbidden,
+ //"lz4c": Forbidden,
+}
+
+func init() {
+ if runtime.GOOS == "darwin" {
+ Configuration["md5"] = Allowed
+ Configuration["sw_vers"] = Allowed
+ Configuration["xcrun"] = Allowed
+ }
+}
diff --git a/ui/build/paths/logs.go b/ui/build/paths/logs.go
new file mode 100644
index 0000000..6c24968
--- /dev/null
+++ b/ui/build/paths/logs.go
@@ -0,0 +1,211 @@
+// Copyright 2018 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 paths
+
+import (
+ "context"
+ "encoding/gob"
+ "fmt"
+ "io/ioutil"
+ "net"
+ "os"
+ "path/filepath"
+ "runtime"
+ "sync"
+ "syscall"
+ "time"
+)
+
+type LogProcess struct {
+ Pid int
+ Command string
+}
+
+type LogEntry struct {
+ Basename string
+ Args []string
+ Parents []LogProcess
+}
+
+const timeoutDuration = time.Duration(100) * time.Millisecond
+
+type socketAddrFunc func(string) (string, func(), error)
+
+func procFallback(name string) (string, func(), error) {
+ d, err := os.Open(filepath.Dir(name))
+ if err != nil {
+ return "", func() {}, err
+ }
+
+ return fmt.Sprintf("/proc/self/fd/%d/%s", d.Fd(), filepath.Base(name)), func() {
+ d.Close()
+ }, nil
+}
+
+func tmpFallback(name string) (addr string, cleanup func(), err error) {
+ d, err := ioutil.TempDir("/tmp", "log_sock")
+ if err != nil {
+ cleanup = func() {}
+ return
+ }
+ cleanup = func() {
+ os.RemoveAll(d)
+ }
+
+ dir := filepath.Dir(name)
+
+ absDir, err := filepath.Abs(dir)
+ if err != nil {
+ return
+ }
+
+ err = os.Symlink(absDir, filepath.Join(d, "d"))
+ if err != nil {
+ return
+ }
+
+ addr = filepath.Join(d, "d", filepath.Base(name))
+
+ return
+}
+
+func getSocketAddr(name string) (string, func(), error) {
+ maxNameLen := len(syscall.RawSockaddrUnix{}.Path)
+
+ if len(name) < maxNameLen {
+ return name, func() {}, nil
+ }
+
+ if runtime.GOOS == "linux" {
+ addr, cleanup, err := procFallback(name)
+ if err == nil {
+ if len(addr) < maxNameLen {
+ return addr, cleanup, nil
+ }
+ }
+ cleanup()
+ }
+
+ addr, cleanup, err := tmpFallback(name)
+ if err == nil {
+ if len(addr) < maxNameLen {
+ return addr, cleanup, nil
+ }
+ }
+ cleanup()
+
+ return name, func() {}, fmt.Errorf("Path to socket is still over size limit, fallbacks failed.")
+}
+
+func dial(name string, lookup socketAddrFunc, timeout time.Duration) (net.Conn, error) {
+ socket, cleanup, err := lookup(name)
+ defer cleanup()
+ if err != nil {
+ return nil, err
+ }
+
+ dialer := &net.Dialer{
+ Timeout: timeout,
+ }
+ return dialer.Dial("unix", socket)
+}
+
+func listen(name string, lookup socketAddrFunc) (net.Listener, error) {
+ socket, cleanup, err := lookup(name)
+ defer cleanup()
+ if err != nil {
+ return nil, err
+ }
+
+ return net.Listen("unix", socket)
+}
+
+func SendLog(logSocket string, entry *LogEntry, done chan interface{}) {
+ sendLog(logSocket, getSocketAddr, timeoutDuration, entry, done)
+}
+
+func sendLog(logSocket string, lookup socketAddrFunc, timeout time.Duration, entry *LogEntry, done chan interface{}) {
+ defer close(done)
+
+ conn, err := dial(logSocket, lookup, timeout)
+ if err != nil {
+ return
+ }
+ defer conn.Close()
+
+ if timeout != 0 {
+ conn.SetDeadline(time.Now().Add(timeout))
+ }
+
+ enc := gob.NewEncoder(conn)
+ enc.Encode(entry)
+}
+
+func LogListener(ctx context.Context, logSocket string) (chan *LogEntry, error) {
+ return logListener(ctx, logSocket, getSocketAddr)
+}
+
+func logListener(ctx context.Context, logSocket string, lookup socketAddrFunc) (chan *LogEntry, error) {
+ ret := make(chan *LogEntry, 5)
+
+ if err := os.Remove(logSocket); err != nil && !os.IsNotExist(err) {
+ return nil, err
+ }
+
+ ln, err := listen(logSocket, lookup)
+ if err != nil {
+ return nil, err
+ }
+
+ go func() {
+ for {
+ select {
+ case <-ctx.Done():
+ ln.Close()
+ }
+ }
+ }()
+
+ go func() {
+ var wg sync.WaitGroup
+ defer func() {
+ wg.Wait()
+ close(ret)
+ }()
+
+ for {
+ conn, err := ln.Accept()
+ if err != nil {
+ ln.Close()
+ break
+ }
+ conn.SetDeadline(time.Now().Add(timeoutDuration))
+ wg.Add(1)
+
+ go func() {
+ defer wg.Done()
+ defer conn.Close()
+
+ dec := gob.NewDecoder(conn)
+ entry := &LogEntry{}
+ if err := dec.Decode(entry); err != nil {
+ return
+ }
+ ret <- entry
+ }()
+ }
+ }()
+ return ret, nil
+}
diff --git a/ui/build/paths/logs_test.go b/ui/build/paths/logs_test.go
new file mode 100644
index 0000000..3b1005f
--- /dev/null
+++ b/ui/build/paths/logs_test.go
@@ -0,0 +1,154 @@
+// Copyright 2018 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 paths
+
+import (
+ "context"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "reflect"
+ "runtime"
+ "strings"
+ "testing"
+)
+
+func TestSendLog(t *testing.T) {
+ t.Run("Short name", func(t *testing.T) {
+ d, err := ioutil.TempDir("", "s")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(d)
+ f := filepath.Join(d, "s")
+
+ testSendLog(t, f, getSocketAddr)
+ })
+
+ testLongName := func(t *testing.T, lookup socketAddrFunc) {
+ d, err := ioutil.TempDir("", strings.Repeat("s", 150))
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(d)
+ f := filepath.Join(d, strings.Repeat("s", 10))
+
+ testSendLog(t, f, lookup)
+ }
+
+ // Using a name longer than the ~100 limit of the underlying calls to bind, etc
+ t.Run("Long name", func(t *testing.T) {
+ testLongName(t, getSocketAddr)
+ })
+
+ if runtime.GOOS == "linux" {
+ t.Run("Long name proc fallback", func(t *testing.T) {
+ testLongName(t, procFallback)
+ })
+ }
+
+ t.Run("Long name tmp fallback", func(t *testing.T) {
+ testLongName(t, tmpFallback)
+ })
+}
+
+func testSendLog(t *testing.T, socket string, lookup socketAddrFunc) {
+ recv, err := logListener(context.Background(), socket, lookup)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ go func() {
+ for i := 0; i < 10; i++ {
+ sendLog(socket, lookup, 0, &LogEntry{
+ Basename: "test",
+ Args: []string{"foo", "bar"},
+ }, make(chan interface{}))
+ }
+ }()
+
+ count := 0
+ for {
+ entry := <-recv
+ if entry == nil {
+ if count != 10 {
+ t.Errorf("Expected 10 logs, got %d", count)
+ }
+ return
+ }
+
+ ref := LogEntry{
+ Basename: "test",
+ Args: []string{"foo", "bar"},
+ }
+ if !reflect.DeepEqual(ref, *entry) {
+ t.Fatalf("Bad log entry: %v", entry)
+ }
+ count++
+
+ if count == 10 {
+ return
+ }
+ }
+}
+
+func TestSendLogError(t *testing.T) {
+ d, err := ioutil.TempDir("", "log_socket")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(d)
+
+ // Missing log sockets should not block waiting for the timeout to elapse
+ t.Run("Missing file", func(t *testing.T) {
+ sendLog(filepath.Join(d, "missing"), getSocketAddr, 0, &LogEntry{}, make(chan interface{}))
+ })
+
+ // Non-sockets should not block waiting for the timeout to elapse
+ t.Run("Regular file", func(t *testing.T) {
+ f := filepath.Join(d, "file")
+ if fp, err := os.Create(f); err == nil {
+ fp.Close()
+ } else {
+ t.Fatal(err)
+ }
+
+ sendLog(f, getSocketAddr, 0, &LogEntry{}, make(chan interface{}))
+ })
+
+ // If the reader is stuck, we should be able to make progress
+ t.Run("Reader not reading", func(t *testing.T) {
+ f := filepath.Join(d, "sock1")
+
+ ln, err := listen(f, getSocketAddr)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer ln.Close()
+
+ done := make(chan bool, 1)
+ go func() {
+ for i := 0; i < 10; i++ {
+ sendLog(f, getSocketAddr, timeoutDuration, &LogEntry{
+ // Ensure a relatively large payload
+ Basename: strings.Repeat(" ", 100000),
+ }, make(chan interface{}))
+ }
+ done <- true
+ }()
+
+ <-done
+ })
+}
diff --git a/ui/build/sandbox/darwin/global.sb b/ui/build/sandbox/darwin/global.sb
index 47d0c43..e32b64b 100644
--- a/ui/build/sandbox/darwin/global.sb
+++ b/ui/build/sandbox/darwin/global.sb
@@ -35,6 +35,12 @@
(global-name-regex #"^com\.apple\.distributed_notifications") ; xcodebuild in Soong
)
+; Allow suid /bin/ps to function
+(allow process-exec (literal "/bin/ps") (with no-sandbox))
+
+; Allow path_interposer unix domain socket without logging
+(allow network-outbound (literal (string-append (param "OUT_DIR") "/.path_interposer_log")))
+
; Allow executing any file
(allow process-exec*)
(allow process-fork)
diff --git a/ui/build/soong.go b/ui/build/soong.go
index cbb75c7..6c94079 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -15,12 +15,15 @@
package build
import (
+ "fmt"
"os"
"path/filepath"
"strconv"
- "time"
+ "strings"
"github.com/google/blueprint/microfactory"
+
+ "android/soong/ui/status"
)
func runSoong(ctx Context, config Config) {
@@ -41,9 +44,8 @@
cmd.Environment.Set("SRCDIR", ".")
cmd.Environment.Set("TOPNAME", "Android.bp")
cmd.Sandbox = soongSandbox
- cmd.Stdout = ctx.Stdout()
- cmd.Stderr = ctx.Stderr()
- cmd.RunOrFatal()
+
+ cmd.RunAndPrintOrFatal()
}()
func() {
@@ -56,12 +58,18 @@
if _, err := os.Stat(envTool); err == nil {
cmd := Command(ctx, config, "soong_env", envTool, envFile)
cmd.Sandbox = soongSandbox
- cmd.Stdout = ctx.Stdout()
- cmd.Stderr = ctx.Stderr()
+
+ var buf strings.Builder
+ cmd.Stdout = &buf
+ cmd.Stderr = &buf
if err := cmd.Run(); err != nil {
ctx.Verboseln("soong_env failed, forcing manifest regeneration")
os.Remove(envFile)
}
+
+ if buf.Len() > 0 {
+ ctx.Verboseln(buf.String())
+ }
} else {
ctx.Verboseln("Missing soong_env tool, forcing manifest regeneration")
os.Remove(envFile)
@@ -71,41 +79,47 @@
}
}()
+ var cfg microfactory.Config
+ cfg.Map("github.com/google/blueprint", "build/blueprint")
+
+ cfg.TrimPath = absPath(ctx, ".")
+
func() {
ctx.BeginTrace("minibp")
defer ctx.EndTrace()
- var cfg microfactory.Config
- cfg.Map("github.com/google/blueprint", "build/blueprint")
-
- cfg.TrimPath = absPath(ctx, ".")
-
minibp := filepath.Join(config.SoongOutDir(), ".minibootstrap/minibp")
if _, err := microfactory.Build(&cfg, minibp, "github.com/google/blueprint/bootstrap/minibp"); err != nil {
ctx.Fatalln("Failed to build minibp:", err)
}
}()
+ func() {
+ ctx.BeginTrace("bpglob")
+ defer ctx.EndTrace()
+
+ bpglob := filepath.Join(config.SoongOutDir(), ".minibootstrap/bpglob")
+ if _, err := microfactory.Build(&cfg, bpglob, "github.com/google/blueprint/bootstrap/bpglob"); err != nil {
+ ctx.Fatalln("Failed to build bpglob:", err)
+ }
+ }()
+
ninja := func(name, file string) {
ctx.BeginTrace(name)
defer ctx.EndTrace()
+ fifo := filepath.Join(config.OutDir(), ".ninja_fifo")
+ status.NinjaReader(ctx, ctx.Status.StartTool(), fifo)
+
cmd := Command(ctx, config, "soong "+name,
config.PrebuiltBuildTool("ninja"),
"-d", "keepdepfile",
"-w", "dupbuild=err",
"-j", strconv.Itoa(config.Parallel()),
+ fmt.Sprintf("--frontend=cat <&3 >%s", fifo),
"-f", filepath.Join(config.SoongOutDir(), file))
- if config.IsVerbose() {
- cmd.Args = append(cmd.Args, "-v")
- }
cmd.Sandbox = soongSandbox
- cmd.Stdin = ctx.Stdin()
- cmd.Stdout = ctx.Stdout()
- cmd.Stderr = ctx.Stderr()
-
- defer ctx.ImportNinjaLog(filepath.Join(config.OutDir(), ".ninja_log"), time.Now())
- cmd.RunOrFatal()
+ cmd.RunAndPrintOrFatal()
}
ninja("minibootstrap", ".minibootstrap/build.ninja")
diff --git a/ui/build/test_build.go b/ui/build/test_build.go
index 940f0c8..4bc4c97 100644
--- a/ui/build/test_build.go
+++ b/ui/build/test_build.go
@@ -18,6 +18,7 @@
"bufio"
"path/filepath"
"runtime"
+ "sort"
"strings"
)
@@ -56,7 +57,7 @@
bootstrapDir := filepath.Join(outDir, "soong", ".bootstrap")
miniBootstrapDir := filepath.Join(outDir, "soong", ".minibootstrap")
- var danglingRules []string
+ danglingRules := make(map[string]bool)
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
@@ -70,16 +71,22 @@
// full build rules in the primary build.ninja file.
continue
}
- danglingRules = append(danglingRules, line)
+ danglingRules[line] = true
}
cmd.WaitOrFatal()
- if len(danglingRules) > 0 {
+ var danglingRulesList []string
+ for rule := range danglingRules {
+ danglingRulesList = append(danglingRulesList, rule)
+ }
+ sort.Strings(danglingRulesList)
+
+ if len(danglingRulesList) > 0 {
ctx.Println("Dependencies in out found with no rule to create them:")
- for _, dep := range danglingRules {
- ctx.Println(dep)
+ for _, dep := range danglingRulesList {
+ ctx.Println(" ", dep)
}
- ctx.Fatal("")
+ ctx.Fatal("stopping")
}
}
diff --git a/ui/build/util.go b/ui/build/util.go
index f698ccd..0676a86 100644
--- a/ui/build/util.go
+++ b/ui/build/util.go
@@ -15,13 +15,9 @@
package build
import (
- "bytes"
- "io"
"os"
"path/filepath"
"strings"
- "syscall"
- "unsafe"
)
func absPath(ctx Context, p string) string {
@@ -62,9 +58,24 @@
func ensureEmptyDirectoriesExist(ctx Context, dirs ...string) {
// remove all the directories
for _, dir := range dirs {
- err := os.RemoveAll(dir)
- if err != nil {
- ctx.Fatalf("Error removing %s: %q\n", dir, err)
+ seenErr := map[string]bool{}
+ for {
+ err := os.RemoveAll(dir)
+ if err == nil {
+ break
+ }
+
+ if pathErr, ok := err.(*os.PathError); !ok ||
+ dir == pathErr.Path || seenErr[pathErr.Path] {
+
+ ctx.Fatalf("Error removing %s: %q\n", dir, err)
+ } else {
+ seenErr[pathErr.Path] = true
+ err = os.Chmod(filepath.Dir(pathErr.Path), 0700)
+ if err != nil {
+ ctx.Fatal(err)
+ }
+ }
}
}
// recreate all the directories
@@ -102,81 +113,3 @@
}
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_test.go b/ui/build/util_test.go
index e85eada..89bfc77 100644
--- a/ui/build/util_test.go
+++ b/ui/build/util_test.go
@@ -14,49 +14,38 @@
package build
-import "testing"
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "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]",
- },
+ "android/soong/ui/logger"
+)
+
+func TestEnsureEmptyDirs(t *testing.T) {
+ ctx := testContext()
+ defer logger.Recover(func(err error) {
+ t.Error(err)
+ })
+
+ tmpDir, err := ioutil.TempDir("", "")
+ if err != nil {
+ t.Fatal(err)
}
- 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)
+ defer func() {
+ err := os.RemoveAll(tmpDir)
+ if err != nil {
+ t.Errorf("Error removing tmpDir: %v", err)
}
+ }()
+
+ ensureEmptyDirectoriesExist(ctx, filepath.Join(tmpDir, "a/b"))
+
+ err = os.Chmod(filepath.Join(tmpDir, "a"), 0555)
+ if err != nil {
+ t.Fatalf("Failed to chown: %v", err)
}
+
+ ensureEmptyDirectoriesExist(ctx, filepath.Join(tmpDir, "a"))
}
diff --git a/ui/status/Android.bp b/ui/status/Android.bp
new file mode 100644
index 0000000..76caaef
--- /dev/null
+++ b/ui/status/Android.bp
@@ -0,0 +1,42 @@
+// Copyright 2018 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-status",
+ pkgPath: "android/soong/ui/status",
+ deps: [
+ "golang-protobuf-proto",
+ "soong-ui-logger",
+ "soong-ui-status-ninja_frontend",
+ ],
+ srcs: [
+ "kati.go",
+ "log.go",
+ "ninja.go",
+ "status.go",
+ ],
+ testSrcs: [
+ "kati_test.go",
+ "status_test.go",
+ ],
+}
+
+bootstrap_go_package {
+ name: "soong-ui-status-ninja_frontend",
+ pkgPath: "android/soong/ui/status/ninja_frontend",
+ deps: ["golang-protobuf-proto"],
+ srcs: [
+ "ninja_frontend/frontend.pb.go",
+ ],
+}
diff --git a/ui/status/kati.go b/ui/status/kati.go
new file mode 100644
index 0000000..552a9e9
--- /dev/null
+++ b/ui/status/kati.go
@@ -0,0 +1,138 @@
+// Copyright 2018 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 status
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "regexp"
+ "strconv"
+ "strings"
+)
+
+var katiError = regexp.MustCompile(`^(\033\[1m)?[^ ]+:[0-9]+: (\033\[31m)?error:`)
+var katiIncludeRe = regexp.MustCompile(`^(\[(\d+)/(\d+)] )?((including [^ ]+|initializing build system|finishing build rules|writing build rules) ...)$`)
+var katiLogRe = regexp.MustCompile(`^\*kati\*: `)
+var katiNinjaMissing = regexp.MustCompile("^[^ ]+ is missing, regenerating...$")
+
+type katiOutputParser struct {
+ st ToolStatus
+
+ count int
+ total int
+ extra int
+
+ action *Action
+ buf strings.Builder
+ hasError bool
+}
+
+func (k *katiOutputParser) flushAction() {
+ if k.action == nil {
+ return
+ }
+
+ var err error
+ if k.hasError {
+ err = fmt.Errorf("makefile error")
+ }
+
+ k.st.FinishAction(ActionResult{
+ Action: k.action,
+ Output: k.buf.String(),
+ Error: err,
+ })
+
+ k.buf.Reset()
+ k.hasError = false
+}
+
+func (k *katiOutputParser) parseLine(line string) {
+ // Only put kati debug/stat lines in our verbose log
+ if katiLogRe.MatchString(line) {
+ k.st.Verbose(line)
+ return
+ }
+
+ if matches := katiIncludeRe.FindStringSubmatch(line); len(matches) > 0 {
+ k.flushAction()
+ k.count += 1
+
+ matches := katiIncludeRe.FindStringSubmatch(line)
+ if matches[2] != "" {
+ idx, err := strconv.Atoi(matches[2])
+
+ if err == nil && idx+k.extra != k.count {
+ k.extra = k.count - idx
+ k.st.SetTotalActions(k.total + k.extra)
+ }
+ } else {
+ k.extra += 1
+ k.st.SetTotalActions(k.total + k.extra)
+ }
+
+ if matches[3] != "" {
+ tot, err := strconv.Atoi(matches[3])
+
+ if err == nil && tot != k.total {
+ k.total = tot
+ k.st.SetTotalActions(k.total + k.extra)
+ }
+ }
+
+ k.action = &Action{
+ Description: matches[4],
+ }
+ k.st.StartAction(k.action)
+ } else if k.action != nil {
+ if katiError.MatchString(line) {
+ k.hasError = true
+ }
+ k.buf.WriteString(line)
+ k.buf.WriteString("\n")
+ } else {
+ // Before we've started executing actions from Kati
+ if line == "No need to regenerate ninja file" || katiNinjaMissing.MatchString(line) {
+ k.st.Status(line)
+ } else {
+ k.st.Print(line)
+ }
+ }
+}
+
+// KatiReader reads the output from Kati, and turns it into Actions and
+// messages that are passed into the ToolStatus API.
+func KatiReader(st ToolStatus, pipe io.ReadCloser) {
+ parser := &katiOutputParser{
+ st: st,
+ }
+
+ scanner := bufio.NewScanner(pipe)
+ for scanner.Scan() {
+ parser.parseLine(scanner.Text())
+ }
+
+ parser.flushAction()
+
+ if err := scanner.Err(); err != nil {
+ var buf strings.Builder
+ io.Copy(&buf, pipe)
+ st.Print(fmt.Sprintf("Error from kati parser: %s", err))
+ st.Print(buf.String())
+ }
+
+ st.Finish()
+}
diff --git a/ui/status/kati_test.go b/ui/status/kati_test.go
new file mode 100644
index 0000000..f2cb813
--- /dev/null
+++ b/ui/status/kati_test.go
@@ -0,0 +1,175 @@
+// Copyright 2018 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 status
+
+import (
+ "testing"
+)
+
+type lastOutput struct {
+ counterOutput
+
+ action *Action
+ result ActionResult
+
+ msgLevel MsgLevel
+ msg string
+}
+
+func (l *lastOutput) StartAction(a *Action, c Counts) {
+ l.action = a
+ l.counterOutput.StartAction(a, c)
+}
+func (l *lastOutput) FinishAction(r ActionResult, c Counts) {
+ l.result = r
+ l.counterOutput.FinishAction(r, c)
+}
+func (l *lastOutput) Message(level MsgLevel, msg string) {
+ l.msgLevel = level
+ l.msg = msg
+}
+func (l *lastOutput) Flush() {}
+
+func TestKatiNormalCase(t *testing.T) {
+ status := &Status{}
+ output := &lastOutput{}
+ status.AddOutput(output)
+
+ parser := &katiOutputParser{
+ st: status.StartTool(),
+ }
+
+ msg := "*kati*: verbose msg"
+ parser.parseLine(msg)
+ output.Expect(t, Counts{})
+
+ if output.msgLevel != VerboseLvl {
+ t.Errorf("Expected verbose message, but got %d", output.msgLevel)
+ }
+ if output.msg != msg {
+ t.Errorf("unexpected message contents:\nwant: %q\n got: %q\n", msg, output.msg)
+ }
+
+ parser.parseLine("out/build-aosp_arm.ninja is missing, regenerating...")
+ output.Expect(t, Counts{})
+
+ parser.parseLine("[1/1] initializing build system ...")
+ output.Expect(t, Counts{
+ TotalActions: 1,
+ RunningActions: 1,
+ StartedActions: 1,
+ FinishedActions: 0,
+ })
+
+ parser.parseLine("[2/5] including out/soong/Android-aosp_arm.mk ...")
+ output.Expect(t, Counts{
+ TotalActions: 5,
+ RunningActions: 1,
+ StartedActions: 2,
+ FinishedActions: 1,
+ })
+
+ parser.parseLine("[3/5] including a ...")
+ msg = "a random message"
+ parser.parseLine(msg)
+
+ // Start the next line to flush the previous result
+ parser.parseLine("[4/5] finishing build rules ...")
+
+ msg += "\n"
+ if output.result.Output != msg {
+ t.Errorf("output for action did not match:\nwant: %q\n got: %q\n", msg, output.result.Output)
+ }
+
+ parser.parseLine("[5/5] writing build rules ...")
+ parser.parseLine("*kati*: verbose msg")
+ parser.flushAction()
+
+ if output.result.Output != "" {
+ t.Errorf("expected no output for last action, but got %q", output.result.Output)
+ }
+
+ output.Expect(t, Counts{
+ TotalActions: 5,
+ RunningActions: 0,
+ StartedActions: 5,
+ FinishedActions: 5,
+ })
+}
+
+func TestKatiExtraIncludes(t *testing.T) {
+ status := &Status{}
+ output := &lastOutput{}
+ status.AddOutput(output)
+
+ parser := &katiOutputParser{
+ st: status.StartTool(),
+ }
+
+ parser.parseLine("[1/1] initializing build system ...")
+ parser.parseLine("[2/5] including out/soong/Android-aosp_arm.mk ...")
+ output.Expect(t, Counts{
+ TotalActions: 5,
+ RunningActions: 1,
+ StartedActions: 2,
+ FinishedActions: 1,
+ })
+
+ parser.parseLine("including a ...")
+
+ output.Expect(t, Counts{
+ TotalActions: 6,
+ RunningActions: 1,
+ StartedActions: 3,
+ FinishedActions: 2,
+ })
+
+ parser.parseLine("including b ...")
+
+ output.Expect(t, Counts{
+ TotalActions: 7,
+ RunningActions: 1,
+ StartedActions: 4,
+ FinishedActions: 3,
+ })
+
+ parser.parseLine("[3/5] finishing build rules ...")
+
+ output.Expect(t, Counts{
+ TotalActions: 7,
+ RunningActions: 1,
+ StartedActions: 5,
+ FinishedActions: 4,
+ })
+}
+
+func TestKatiFailOnError(t *testing.T) {
+ status := &Status{}
+ output := &lastOutput{}
+ status.AddOutput(output)
+
+ parser := &katiOutputParser{
+ st: status.StartTool(),
+ }
+
+ parser.parseLine("[1/1] initializing build system ...")
+ parser.parseLine("[2/5] inclduing out/soong/Android-aosp_arm.mk ...")
+ parser.parseLine("build/make/tools/Android.mk:19: error: testing")
+ parser.flushAction()
+
+ if output.result.Error == nil {
+ t.Errorf("Expected the last action to be marked as an error")
+ }
+}
diff --git a/ui/status/log.go b/ui/status/log.go
new file mode 100644
index 0000000..921aa44
--- /dev/null
+++ b/ui/status/log.go
@@ -0,0 +1,136 @@
+// Copyright 2018 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 status
+
+import (
+ "android/soong/ui/logger"
+ "compress/gzip"
+ "fmt"
+ "io"
+ "strings"
+)
+
+type verboseLog struct {
+ w io.WriteCloser
+}
+
+func NewVerboseLog(log logger.Logger, filename string) StatusOutput {
+ if !strings.HasSuffix(filename, ".gz") {
+ filename += ".gz"
+ }
+
+ f, err := logger.CreateFileWithRotation(filename, 5)
+ if err != nil {
+ log.Println("Failed to create verbose log file:", err)
+ return nil
+ }
+
+ w := gzip.NewWriter(f)
+
+ return &verboseLog{
+ w: w,
+ }
+}
+
+func (v *verboseLog) StartAction(action *Action, counts Counts) {}
+
+func (v *verboseLog) FinishAction(result ActionResult, counts Counts) {
+ cmd := result.Command
+ if cmd == "" {
+ cmd = result.Description
+ }
+
+ fmt.Fprintf(v.w, "[%d/%d] %s\n", counts.FinishedActions, counts.TotalActions, cmd)
+
+ if result.Error != nil {
+ fmt.Fprintf(v.w, "FAILED: %s\n", strings.Join(result.Outputs, " "))
+ }
+
+ if result.Output != "" {
+ fmt.Fprintln(v.w, result.Output)
+ }
+}
+
+func (v *verboseLog) Flush() {
+ v.w.Close()
+}
+
+func (v *verboseLog) Message(level MsgLevel, message string) {
+ fmt.Fprintf(v.w, "%s%s\n", level.Prefix(), message)
+}
+
+type errorLog struct {
+ w io.WriteCloser
+
+ empty bool
+}
+
+func NewErrorLog(log logger.Logger, filename string) StatusOutput {
+ f, err := logger.CreateFileWithRotation(filename, 5)
+ if err != nil {
+ log.Println("Failed to create error log file:", err)
+ return nil
+ }
+
+ return &errorLog{
+ w: f,
+ empty: true,
+ }
+}
+
+func (e *errorLog) StartAction(action *Action, counts Counts) {}
+
+func (e *errorLog) FinishAction(result ActionResult, counts Counts) {
+ if result.Error == nil {
+ return
+ }
+
+ cmd := result.Command
+ if cmd == "" {
+ cmd = result.Description
+ }
+
+ if !e.empty {
+ fmt.Fprintf(e.w, "\n\n")
+ }
+ e.empty = false
+
+ fmt.Fprintf(e.w, "FAILED: %s\n", result.Description)
+ if len(result.Outputs) > 0 {
+ fmt.Fprintf(e.w, "Outputs: %s\n", strings.Join(result.Outputs, " "))
+ }
+ fmt.Fprintf(e.w, "Error: %s\n", result.Error)
+ if result.Command != "" {
+ fmt.Fprintf(e.w, "Command: %s\n", result.Command)
+ }
+ fmt.Fprintf(e.w, "Output:\n%s\n", result.Output)
+}
+
+func (e *errorLog) Flush() {
+ e.w.Close()
+}
+
+func (e *errorLog) Message(level MsgLevel, message string) {
+ if level < ErrorLvl {
+ return
+ }
+
+ if !e.empty {
+ fmt.Fprintf(e.w, "\n\n")
+ }
+ e.empty = false
+
+ fmt.Fprintf(e.w, "error: %s\n", message)
+}
diff --git a/ui/status/ninja.go b/ui/status/ninja.go
new file mode 100644
index 0000000..7d330f9
--- /dev/null
+++ b/ui/status/ninja.go
@@ -0,0 +1,151 @@
+// Copyright 2018 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 status
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "os"
+ "syscall"
+
+ "github.com/golang/protobuf/proto"
+
+ "android/soong/ui/logger"
+ "android/soong/ui/status/ninja_frontend"
+)
+
+// NinjaReader reads the protobuf frontend format from ninja and translates it
+// into calls on the ToolStatus API.
+func NinjaReader(ctx logger.Logger, status ToolStatus, fifo string) {
+ os.Remove(fifo)
+
+ err := syscall.Mkfifo(fifo, 0666)
+ if err != nil {
+ ctx.Fatalf("Failed to mkfifo(%q): %v", fifo, err)
+ }
+
+ go ninjaReader(status, fifo)
+}
+
+func ninjaReader(status ToolStatus, fifo string) {
+ f, err := os.Open(fifo)
+ if err != nil {
+ status.Error(fmt.Sprintf("Failed to open fifo:", err))
+ }
+ defer f.Close()
+
+ r := bufio.NewReader(f)
+
+ running := map[uint32]*Action{}
+
+ for {
+ size, err := readVarInt(r)
+ if err != nil {
+ if err != io.EOF {
+ status.Error(fmt.Sprintf("Got error reading from ninja: %s", err))
+ }
+ return
+ }
+
+ buf := make([]byte, size)
+ _, err = io.ReadFull(r, buf)
+ if err != nil {
+ if err == io.EOF {
+ status.Print(fmt.Sprintf("Missing message of size %d from ninja\n", size))
+ } else {
+ status.Error(fmt.Sprintf("Got error reading from ninja: %s", err))
+ }
+ return
+ }
+
+ msg := &ninja_frontend.Status{}
+ err = proto.Unmarshal(buf, msg)
+ if err != nil {
+ status.Print(fmt.Sprintf("Error reading message from ninja: %v", err))
+ continue
+ }
+
+ // Ignore msg.BuildStarted
+ if msg.TotalEdges != nil {
+ status.SetTotalActions(int(msg.TotalEdges.GetTotalEdges()))
+ }
+ if msg.EdgeStarted != nil {
+ action := &Action{
+ Description: msg.EdgeStarted.GetDesc(),
+ Outputs: msg.EdgeStarted.Outputs,
+ Command: msg.EdgeStarted.GetCommand(),
+ }
+ status.StartAction(action)
+ running[msg.EdgeStarted.GetId()] = action
+ }
+ if msg.EdgeFinished != nil {
+ if started, ok := running[msg.EdgeFinished.GetId()]; ok {
+ delete(running, msg.EdgeFinished.GetId())
+
+ var err error
+ exitCode := int(msg.EdgeFinished.GetStatus())
+ if exitCode != 0 {
+ err = fmt.Errorf("exited with code: %d", exitCode)
+ }
+
+ status.FinishAction(ActionResult{
+ Action: started,
+ Output: msg.EdgeFinished.GetOutput(),
+ Error: err,
+ })
+ }
+ }
+ if msg.Message != nil {
+ message := "ninja: " + msg.Message.GetMessage()
+ switch msg.Message.GetLevel() {
+ case ninja_frontend.Status_Message_INFO:
+ status.Status(message)
+ case ninja_frontend.Status_Message_WARNING:
+ status.Print("warning: " + message)
+ case ninja_frontend.Status_Message_ERROR:
+ status.Error(message)
+ default:
+ status.Print(message)
+ }
+ }
+ if msg.BuildFinished != nil {
+ status.Finish()
+ }
+ }
+}
+
+func readVarInt(r *bufio.Reader) (int, error) {
+ ret := 0
+ shift := uint(0)
+
+ for {
+ b, err := r.ReadByte()
+ if err != nil {
+ return 0, err
+ }
+
+ ret += int(b&0x7f) << (shift * 7)
+ if b&0x80 == 0 {
+ break
+ }
+ shift += 1
+ if shift > 4 {
+ return 0, fmt.Errorf("Expected varint32 length-delimited message")
+ }
+ }
+
+ return ret, nil
+}
diff --git a/ui/status/ninja_frontend/README b/ui/status/ninja_frontend/README
new file mode 100644
index 0000000..8c4b451
--- /dev/null
+++ b/ui/status/ninja_frontend/README
@@ -0,0 +1,3 @@
+This comes from https://android.googlesource.com/platform/external/ninja/+/master/src/frontend.proto
+
+The only difference is the specification of a go_package. To regenerate frontend.pb.go, run regen.sh.
diff --git a/ui/status/ninja_frontend/frontend.pb.go b/ui/status/ninja_frontend/frontend.pb.go
new file mode 100644
index 0000000..7c05eed
--- /dev/null
+++ b/ui/status/ninja_frontend/frontend.pb.go
@@ -0,0 +1,510 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: frontend.proto
+
+package ninja_frontend
+
+import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
+
+type Status_Message_Level int32
+
+const (
+ Status_Message_INFO Status_Message_Level = 0
+ Status_Message_WARNING Status_Message_Level = 1
+ Status_Message_ERROR Status_Message_Level = 2
+)
+
+var Status_Message_Level_name = map[int32]string{
+ 0: "INFO",
+ 1: "WARNING",
+ 2: "ERROR",
+}
+var Status_Message_Level_value = map[string]int32{
+ "INFO": 0,
+ "WARNING": 1,
+ "ERROR": 2,
+}
+
+func (x Status_Message_Level) Enum() *Status_Message_Level {
+ p := new(Status_Message_Level)
+ *p = x
+ return p
+}
+func (x Status_Message_Level) String() string {
+ return proto.EnumName(Status_Message_Level_name, int32(x))
+}
+func (x *Status_Message_Level) UnmarshalJSON(data []byte) error {
+ value, err := proto.UnmarshalJSONEnum(Status_Message_Level_value, data, "Status_Message_Level")
+ if err != nil {
+ return err
+ }
+ *x = Status_Message_Level(value)
+ return nil
+}
+func (Status_Message_Level) EnumDescriptor() ([]byte, []int) {
+ return fileDescriptor_frontend_5a49d9b15a642005, []int{0, 5, 0}
+}
+
+type Status struct {
+ TotalEdges *Status_TotalEdges `protobuf:"bytes,1,opt,name=total_edges,json=totalEdges" json:"total_edges,omitempty"`
+ BuildStarted *Status_BuildStarted `protobuf:"bytes,2,opt,name=build_started,json=buildStarted" json:"build_started,omitempty"`
+ BuildFinished *Status_BuildFinished `protobuf:"bytes,3,opt,name=build_finished,json=buildFinished" json:"build_finished,omitempty"`
+ EdgeStarted *Status_EdgeStarted `protobuf:"bytes,4,opt,name=edge_started,json=edgeStarted" json:"edge_started,omitempty"`
+ EdgeFinished *Status_EdgeFinished `protobuf:"bytes,5,opt,name=edge_finished,json=edgeFinished" json:"edge_finished,omitempty"`
+ Message *Status_Message `protobuf:"bytes,6,opt,name=message" json:"message,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Status) Reset() { *m = Status{} }
+func (m *Status) String() string { return proto.CompactTextString(m) }
+func (*Status) ProtoMessage() {}
+func (*Status) Descriptor() ([]byte, []int) {
+ return fileDescriptor_frontend_5a49d9b15a642005, []int{0}
+}
+func (m *Status) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Status.Unmarshal(m, b)
+}
+func (m *Status) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Status.Marshal(b, m, deterministic)
+}
+func (dst *Status) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Status.Merge(dst, src)
+}
+func (m *Status) XXX_Size() int {
+ return xxx_messageInfo_Status.Size(m)
+}
+func (m *Status) XXX_DiscardUnknown() {
+ xxx_messageInfo_Status.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Status proto.InternalMessageInfo
+
+func (m *Status) GetTotalEdges() *Status_TotalEdges {
+ if m != nil {
+ return m.TotalEdges
+ }
+ return nil
+}
+
+func (m *Status) GetBuildStarted() *Status_BuildStarted {
+ if m != nil {
+ return m.BuildStarted
+ }
+ return nil
+}
+
+func (m *Status) GetBuildFinished() *Status_BuildFinished {
+ if m != nil {
+ return m.BuildFinished
+ }
+ return nil
+}
+
+func (m *Status) GetEdgeStarted() *Status_EdgeStarted {
+ if m != nil {
+ return m.EdgeStarted
+ }
+ return nil
+}
+
+func (m *Status) GetEdgeFinished() *Status_EdgeFinished {
+ if m != nil {
+ return m.EdgeFinished
+ }
+ return nil
+}
+
+func (m *Status) GetMessage() *Status_Message {
+ if m != nil {
+ return m.Message
+ }
+ return nil
+}
+
+type Status_TotalEdges struct {
+ // New value for total edges in the build.
+ TotalEdges *uint32 `protobuf:"varint,1,opt,name=total_edges,json=totalEdges" json:"total_edges,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Status_TotalEdges) Reset() { *m = Status_TotalEdges{} }
+func (m *Status_TotalEdges) String() string { return proto.CompactTextString(m) }
+func (*Status_TotalEdges) ProtoMessage() {}
+func (*Status_TotalEdges) Descriptor() ([]byte, []int) {
+ return fileDescriptor_frontend_5a49d9b15a642005, []int{0, 0}
+}
+func (m *Status_TotalEdges) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Status_TotalEdges.Unmarshal(m, b)
+}
+func (m *Status_TotalEdges) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Status_TotalEdges.Marshal(b, m, deterministic)
+}
+func (dst *Status_TotalEdges) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Status_TotalEdges.Merge(dst, src)
+}
+func (m *Status_TotalEdges) XXX_Size() int {
+ return xxx_messageInfo_Status_TotalEdges.Size(m)
+}
+func (m *Status_TotalEdges) XXX_DiscardUnknown() {
+ xxx_messageInfo_Status_TotalEdges.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Status_TotalEdges proto.InternalMessageInfo
+
+func (m *Status_TotalEdges) GetTotalEdges() uint32 {
+ if m != nil && m.TotalEdges != nil {
+ return *m.TotalEdges
+ }
+ return 0
+}
+
+type Status_BuildStarted struct {
+ // Number of jobs Ninja will run in parallel.
+ Parallelism *uint32 `protobuf:"varint,1,opt,name=parallelism" json:"parallelism,omitempty"`
+ // Verbose value passed to ninja.
+ Verbose *bool `protobuf:"varint,2,opt,name=verbose" json:"verbose,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Status_BuildStarted) Reset() { *m = Status_BuildStarted{} }
+func (m *Status_BuildStarted) String() string { return proto.CompactTextString(m) }
+func (*Status_BuildStarted) ProtoMessage() {}
+func (*Status_BuildStarted) Descriptor() ([]byte, []int) {
+ return fileDescriptor_frontend_5a49d9b15a642005, []int{0, 1}
+}
+func (m *Status_BuildStarted) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Status_BuildStarted.Unmarshal(m, b)
+}
+func (m *Status_BuildStarted) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Status_BuildStarted.Marshal(b, m, deterministic)
+}
+func (dst *Status_BuildStarted) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Status_BuildStarted.Merge(dst, src)
+}
+func (m *Status_BuildStarted) XXX_Size() int {
+ return xxx_messageInfo_Status_BuildStarted.Size(m)
+}
+func (m *Status_BuildStarted) XXX_DiscardUnknown() {
+ xxx_messageInfo_Status_BuildStarted.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Status_BuildStarted proto.InternalMessageInfo
+
+func (m *Status_BuildStarted) GetParallelism() uint32 {
+ if m != nil && m.Parallelism != nil {
+ return *m.Parallelism
+ }
+ return 0
+}
+
+func (m *Status_BuildStarted) GetVerbose() bool {
+ if m != nil && m.Verbose != nil {
+ return *m.Verbose
+ }
+ return false
+}
+
+type Status_BuildFinished struct {
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Status_BuildFinished) Reset() { *m = Status_BuildFinished{} }
+func (m *Status_BuildFinished) String() string { return proto.CompactTextString(m) }
+func (*Status_BuildFinished) ProtoMessage() {}
+func (*Status_BuildFinished) Descriptor() ([]byte, []int) {
+ return fileDescriptor_frontend_5a49d9b15a642005, []int{0, 2}
+}
+func (m *Status_BuildFinished) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Status_BuildFinished.Unmarshal(m, b)
+}
+func (m *Status_BuildFinished) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Status_BuildFinished.Marshal(b, m, deterministic)
+}
+func (dst *Status_BuildFinished) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Status_BuildFinished.Merge(dst, src)
+}
+func (m *Status_BuildFinished) XXX_Size() int {
+ return xxx_messageInfo_Status_BuildFinished.Size(m)
+}
+func (m *Status_BuildFinished) XXX_DiscardUnknown() {
+ xxx_messageInfo_Status_BuildFinished.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Status_BuildFinished proto.InternalMessageInfo
+
+type Status_EdgeStarted struct {
+ // Edge identification number, unique to a Ninja run.
+ Id *uint32 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"`
+ // Edge start time in milliseconds since Ninja started.
+ StartTime *uint32 `protobuf:"varint,2,opt,name=start_time,json=startTime" json:"start_time,omitempty"`
+ // List of edge inputs.
+ Inputs []string `protobuf:"bytes,3,rep,name=inputs" json:"inputs,omitempty"`
+ // List of edge outputs.
+ Outputs []string `protobuf:"bytes,4,rep,name=outputs" json:"outputs,omitempty"`
+ // Description field from the edge.
+ Desc *string `protobuf:"bytes,5,opt,name=desc" json:"desc,omitempty"`
+ // Command field from the edge.
+ Command *string `protobuf:"bytes,6,opt,name=command" json:"command,omitempty"`
+ // Edge uses console.
+ Console *bool `protobuf:"varint,7,opt,name=console" json:"console,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Status_EdgeStarted) Reset() { *m = Status_EdgeStarted{} }
+func (m *Status_EdgeStarted) String() string { return proto.CompactTextString(m) }
+func (*Status_EdgeStarted) ProtoMessage() {}
+func (*Status_EdgeStarted) Descriptor() ([]byte, []int) {
+ return fileDescriptor_frontend_5a49d9b15a642005, []int{0, 3}
+}
+func (m *Status_EdgeStarted) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Status_EdgeStarted.Unmarshal(m, b)
+}
+func (m *Status_EdgeStarted) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Status_EdgeStarted.Marshal(b, m, deterministic)
+}
+func (dst *Status_EdgeStarted) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Status_EdgeStarted.Merge(dst, src)
+}
+func (m *Status_EdgeStarted) XXX_Size() int {
+ return xxx_messageInfo_Status_EdgeStarted.Size(m)
+}
+func (m *Status_EdgeStarted) XXX_DiscardUnknown() {
+ xxx_messageInfo_Status_EdgeStarted.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Status_EdgeStarted proto.InternalMessageInfo
+
+func (m *Status_EdgeStarted) GetId() uint32 {
+ if m != nil && m.Id != nil {
+ return *m.Id
+ }
+ return 0
+}
+
+func (m *Status_EdgeStarted) GetStartTime() uint32 {
+ if m != nil && m.StartTime != nil {
+ return *m.StartTime
+ }
+ return 0
+}
+
+func (m *Status_EdgeStarted) GetInputs() []string {
+ if m != nil {
+ return m.Inputs
+ }
+ return nil
+}
+
+func (m *Status_EdgeStarted) GetOutputs() []string {
+ if m != nil {
+ return m.Outputs
+ }
+ return nil
+}
+
+func (m *Status_EdgeStarted) GetDesc() string {
+ if m != nil && m.Desc != nil {
+ return *m.Desc
+ }
+ return ""
+}
+
+func (m *Status_EdgeStarted) GetCommand() string {
+ if m != nil && m.Command != nil {
+ return *m.Command
+ }
+ return ""
+}
+
+func (m *Status_EdgeStarted) GetConsole() bool {
+ if m != nil && m.Console != nil {
+ return *m.Console
+ }
+ return false
+}
+
+type Status_EdgeFinished struct {
+ // Edge identification number, unique to a Ninja run.
+ Id *uint32 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"`
+ // Edge end time in milliseconds since Ninja started.
+ EndTime *uint32 `protobuf:"varint,2,opt,name=end_time,json=endTime" json:"end_time,omitempty"`
+ // Exit status (0 for success).
+ Status *int32 `protobuf:"zigzag32,3,opt,name=status" json:"status,omitempty"`
+ // Edge output, may contain ANSI codes.
+ Output *string `protobuf:"bytes,4,opt,name=output" json:"output,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Status_EdgeFinished) Reset() { *m = Status_EdgeFinished{} }
+func (m *Status_EdgeFinished) String() string { return proto.CompactTextString(m) }
+func (*Status_EdgeFinished) ProtoMessage() {}
+func (*Status_EdgeFinished) Descriptor() ([]byte, []int) {
+ return fileDescriptor_frontend_5a49d9b15a642005, []int{0, 4}
+}
+func (m *Status_EdgeFinished) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Status_EdgeFinished.Unmarshal(m, b)
+}
+func (m *Status_EdgeFinished) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Status_EdgeFinished.Marshal(b, m, deterministic)
+}
+func (dst *Status_EdgeFinished) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Status_EdgeFinished.Merge(dst, src)
+}
+func (m *Status_EdgeFinished) XXX_Size() int {
+ return xxx_messageInfo_Status_EdgeFinished.Size(m)
+}
+func (m *Status_EdgeFinished) XXX_DiscardUnknown() {
+ xxx_messageInfo_Status_EdgeFinished.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Status_EdgeFinished proto.InternalMessageInfo
+
+func (m *Status_EdgeFinished) GetId() uint32 {
+ if m != nil && m.Id != nil {
+ return *m.Id
+ }
+ return 0
+}
+
+func (m *Status_EdgeFinished) GetEndTime() uint32 {
+ if m != nil && m.EndTime != nil {
+ return *m.EndTime
+ }
+ return 0
+}
+
+func (m *Status_EdgeFinished) GetStatus() int32 {
+ if m != nil && m.Status != nil {
+ return *m.Status
+ }
+ return 0
+}
+
+func (m *Status_EdgeFinished) GetOutput() string {
+ if m != nil && m.Output != nil {
+ return *m.Output
+ }
+ return ""
+}
+
+type Status_Message struct {
+ // Message priority level (INFO, WARNING, or ERROR).
+ Level *Status_Message_Level `protobuf:"varint,1,opt,name=level,enum=ninja.Status_Message_Level,def=0" json:"level,omitempty"`
+ // Info/warning/error message from Ninja.
+ Message *string `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Status_Message) Reset() { *m = Status_Message{} }
+func (m *Status_Message) String() string { return proto.CompactTextString(m) }
+func (*Status_Message) ProtoMessage() {}
+func (*Status_Message) Descriptor() ([]byte, []int) {
+ return fileDescriptor_frontend_5a49d9b15a642005, []int{0, 5}
+}
+func (m *Status_Message) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Status_Message.Unmarshal(m, b)
+}
+func (m *Status_Message) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Status_Message.Marshal(b, m, deterministic)
+}
+func (dst *Status_Message) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Status_Message.Merge(dst, src)
+}
+func (m *Status_Message) XXX_Size() int {
+ return xxx_messageInfo_Status_Message.Size(m)
+}
+func (m *Status_Message) XXX_DiscardUnknown() {
+ xxx_messageInfo_Status_Message.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Status_Message proto.InternalMessageInfo
+
+const Default_Status_Message_Level Status_Message_Level = Status_Message_INFO
+
+func (m *Status_Message) GetLevel() Status_Message_Level {
+ if m != nil && m.Level != nil {
+ return *m.Level
+ }
+ return Default_Status_Message_Level
+}
+
+func (m *Status_Message) GetMessage() string {
+ if m != nil && m.Message != nil {
+ return *m.Message
+ }
+ return ""
+}
+
+func init() {
+ proto.RegisterType((*Status)(nil), "ninja.Status")
+ proto.RegisterType((*Status_TotalEdges)(nil), "ninja.Status.TotalEdges")
+ proto.RegisterType((*Status_BuildStarted)(nil), "ninja.Status.BuildStarted")
+ proto.RegisterType((*Status_BuildFinished)(nil), "ninja.Status.BuildFinished")
+ proto.RegisterType((*Status_EdgeStarted)(nil), "ninja.Status.EdgeStarted")
+ proto.RegisterType((*Status_EdgeFinished)(nil), "ninja.Status.EdgeFinished")
+ proto.RegisterType((*Status_Message)(nil), "ninja.Status.Message")
+ proto.RegisterEnum("ninja.Status_Message_Level", Status_Message_Level_name, Status_Message_Level_value)
+}
+
+func init() { proto.RegisterFile("frontend.proto", fileDescriptor_frontend_5a49d9b15a642005) }
+
+var fileDescriptor_frontend_5a49d9b15a642005 = []byte{
+ // 496 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x53, 0xd1, 0x6e, 0xd3, 0x30,
+ 0x14, 0xa5, 0x69, 0xd3, 0x34, 0x37, 0x6d, 0x28, 0x96, 0x40, 0x59, 0x10, 0xa2, 0xda, 0xd3, 0x78,
+ 0x20, 0x48, 0xbc, 0x20, 0x10, 0x12, 0xa2, 0xd2, 0x06, 0x43, 0xd0, 0x49, 0xde, 0x24, 0x24, 0x5e,
+ 0xaa, 0x74, 0xf6, 0x86, 0x51, 0xe2, 0x54, 0xb1, 0xbb, 0x5f, 0xe0, 0x7f, 0x78, 0xe0, 0xfb, 0x90,
+ 0xaf, 0xed, 0x2c, 0x65, 0x7b, 0xcb, 0xf1, 0x3d, 0xe7, 0xde, 0x73, 0x8f, 0x1d, 0x48, 0xaf, 0xda,
+ 0x46, 0x6a, 0x2e, 0x59, 0xb1, 0x6d, 0x1b, 0xdd, 0x90, 0x50, 0x0a, 0xf9, 0xab, 0x3c, 0xfc, 0x13,
+ 0xc1, 0xf8, 0x5c, 0x97, 0x7a, 0xa7, 0xc8, 0x5b, 0x48, 0x74, 0xa3, 0xcb, 0x6a, 0xcd, 0xd9, 0x35,
+ 0x57, 0xd9, 0x60, 0x31, 0x38, 0x4a, 0x5e, 0x67, 0x05, 0xf2, 0x0a, 0xcb, 0x29, 0x2e, 0x0c, 0xe1,
+ 0xd8, 0xd4, 0x29, 0xe8, 0xee, 0x9b, 0x7c, 0x80, 0xd9, 0x66, 0x27, 0x2a, 0xb6, 0x56, 0xba, 0x6c,
+ 0x35, 0x67, 0x59, 0x80, 0xe2, 0x7c, 0x5f, 0xbc, 0x34, 0x94, 0x73, 0xcb, 0xa0, 0xd3, 0x4d, 0x0f,
+ 0x91, 0x25, 0xa4, 0xb6, 0xc1, 0x95, 0x90, 0x42, 0xfd, 0xe4, 0x2c, 0x1b, 0x62, 0x87, 0xa7, 0xf7,
+ 0x74, 0x38, 0x71, 0x14, 0x6a, 0x67, 0x7a, 0x48, 0xde, 0xc3, 0xd4, 0x38, 0xef, 0x3c, 0x8c, 0xb0,
+ 0xc3, 0xc1, 0x7e, 0x07, 0xe3, 0xd7, 0x5b, 0x48, 0xf8, 0x2d, 0x30, 0x2b, 0xa0, 0xba, 0x33, 0x10,
+ 0xde, 0xb7, 0x82, 0x91, 0x77, 0xf3, 0x71, 0x5c, 0x37, 0xfe, 0x15, 0x44, 0x35, 0x57, 0xaa, 0xbc,
+ 0xe6, 0xd9, 0x18, 0xa5, 0x8f, 0xf7, 0xa5, 0xdf, 0x6c, 0x91, 0x7a, 0x56, 0xfe, 0x12, 0xe0, 0x36,
+ 0x4e, 0xf2, 0xfc, 0x6e, 0xfa, 0xb3, 0x7e, 0xc6, 0xf9, 0x17, 0x98, 0xf6, 0x03, 0x24, 0x0b, 0x48,
+ 0xb6, 0x65, 0x5b, 0x56, 0x15, 0xaf, 0x84, 0xaa, 0x9d, 0xa0, 0x7f, 0x44, 0x32, 0x88, 0x6e, 0x78,
+ 0xbb, 0x69, 0x14, 0xc7, 0xfb, 0x98, 0x50, 0x0f, 0xf3, 0x87, 0x30, 0xdb, 0x8b, 0x32, 0xff, 0x3b,
+ 0x80, 0xa4, 0x17, 0x0d, 0x49, 0x21, 0x10, 0xcc, 0xf5, 0x0c, 0x04, 0x23, 0xcf, 0x00, 0x30, 0xd6,
+ 0xb5, 0x16, 0xb5, 0xed, 0x36, 0xa3, 0x31, 0x9e, 0x5c, 0x88, 0x9a, 0x93, 0x27, 0x30, 0x16, 0x72,
+ 0xbb, 0xd3, 0x2a, 0x1b, 0x2e, 0x86, 0x47, 0x31, 0x75, 0xc8, 0x38, 0x68, 0x76, 0x1a, 0x0b, 0x23,
+ 0x2c, 0x78, 0x48, 0x08, 0x8c, 0x18, 0x57, 0x97, 0x98, 0x72, 0x4c, 0xf1, 0xdb, 0xb0, 0x2f, 0x9b,
+ 0xba, 0x2e, 0x25, 0xc3, 0x04, 0x63, 0xea, 0xa1, 0xad, 0x48, 0xd5, 0x54, 0x3c, 0x8b, 0xec, 0x26,
+ 0x0e, 0xe6, 0x02, 0xa6, 0xfd, 0x3b, 0xb9, 0x63, 0xfc, 0x00, 0x26, 0x5c, 0xb2, 0xbe, 0xed, 0x88,
+ 0x4b, 0xe6, 0x4d, 0x2b, 0xbc, 0x1a, 0x7c, 0x6b, 0x8f, 0xa8, 0x43, 0xe6, 0xdc, 0xba, 0xc4, 0x17,
+ 0x14, 0x53, 0x87, 0xf2, 0xdf, 0x03, 0x88, 0xdc, 0x25, 0x92, 0x37, 0x10, 0x56, 0xfc, 0x86, 0x57,
+ 0x38, 0x29, 0xfd, 0xff, 0x99, 0x3a, 0x56, 0xf1, 0xd5, 0x50, 0xde, 0x8d, 0x4e, 0x57, 0x27, 0x67,
+ 0xd4, 0xf2, 0xcd, 0x26, 0xfe, 0x95, 0x04, 0x76, 0x47, 0x07, 0x0f, 0x5f, 0x40, 0x88, 0x7c, 0x32,
+ 0x01, 0x54, 0xcc, 0x1f, 0x90, 0x04, 0xa2, 0xef, 0x1f, 0xe9, 0xea, 0x74, 0xf5, 0x69, 0x3e, 0x20,
+ 0x31, 0x84, 0xc7, 0x94, 0x9e, 0xd1, 0x79, 0xb0, 0x24, 0x9f, 0x87, 0x3f, 0x52, 0x9c, 0xb8, 0xf6,
+ 0x7f, 0xf5, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x2e, 0x8c, 0xef, 0xcb, 0xe0, 0x03, 0x00, 0x00,
+}
diff --git a/ui/status/ninja_frontend/frontend.proto b/ui/status/ninja_frontend/frontend.proto
new file mode 100644
index 0000000..13fd535
--- /dev/null
+++ b/ui/status/ninja_frontend/frontend.proto
@@ -0,0 +1,84 @@
+// 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.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package ninja;
+option go_package = "ninja_frontend";
+
+message Status {
+ message TotalEdges {
+ // New value for total edges in the build.
+ optional uint32 total_edges = 1;
+ }
+
+ message BuildStarted {
+ // Number of jobs Ninja will run in parallel.
+ optional uint32 parallelism = 1;
+ // Verbose value passed to ninja.
+ optional bool verbose = 2;
+ }
+
+ message BuildFinished {
+ }
+
+ message EdgeStarted {
+ // Edge identification number, unique to a Ninja run.
+ optional uint32 id = 1;
+ // Edge start time in milliseconds since Ninja started.
+ optional uint32 start_time = 2;
+ // List of edge inputs.
+ repeated string inputs = 3;
+ // List of edge outputs.
+ repeated string outputs = 4;
+ // Description field from the edge.
+ optional string desc = 5;
+ // Command field from the edge.
+ optional string command = 6;
+ // Edge uses console.
+ optional bool console = 7;
+ }
+
+ message EdgeFinished {
+ // Edge identification number, unique to a Ninja run.
+ optional uint32 id = 1;
+ // Edge end time in milliseconds since Ninja started.
+ optional uint32 end_time = 2;
+ // Exit status (0 for success).
+ optional sint32 status = 3;
+ // Edge output, may contain ANSI codes.
+ optional string output = 4;
+ }
+
+ message Message {
+ enum Level {
+ INFO = 0;
+ WARNING = 1;
+ ERROR = 2;
+ }
+ // Message priority level (INFO, WARNING, or ERROR).
+ optional Level level = 1 [default = INFO];
+ // Info/warning/error message from Ninja.
+ optional string message = 2;
+ }
+
+ optional TotalEdges total_edges = 1;
+ optional BuildStarted build_started = 2;
+ optional BuildFinished build_finished = 3;
+ optional EdgeStarted edge_started = 4;
+ optional EdgeFinished edge_finished = 5;
+ optional Message message = 6;
+}
diff --git a/ui/status/ninja_frontend/regen.sh b/ui/status/ninja_frontend/regen.sh
new file mode 100755
index 0000000..d270731
--- /dev/null
+++ b/ui/status/ninja_frontend/regen.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+aprotoc --go_out=paths=source_relative:. frontend.proto
diff --git a/ui/status/status.go b/ui/status/status.go
new file mode 100644
index 0000000..c851d7f
--- /dev/null
+++ b/ui/status/status.go
@@ -0,0 +1,340 @@
+// Copyright 2018 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 status tracks actions run by various tools, combining the counts
+// (total actions, currently running, started, finished), and giving that to
+// multiple outputs.
+package status
+
+import (
+ "sync"
+)
+
+// Action describes an action taken (or as Ninja calls them, Edges).
+type Action struct {
+ // Description is a shorter, more readable form of the command, meant
+ // for users. It's optional, but one of either Description or Command
+ // should be set.
+ Description string
+
+ // Outputs is the (optional) list of outputs. Usually these are files,
+ // but they can be any string.
+ Outputs []string
+
+ // Command is the actual command line executed to perform the action.
+ // It's optional, but one of either Description or Command should be
+ // set.
+ Command string
+}
+
+// ActionResult describes the result of running an Action.
+type ActionResult struct {
+ // Action is a pointer to the original Action struct.
+ *Action
+
+ // Output is the output produced by the command (usually stdout&stderr
+ // for Actions that run commands)
+ Output string
+
+ // Error is nil if the Action succeeded, or set to an error if it
+ // failed.
+ Error error
+}
+
+// Counts describes the number of actions in each state
+type Counts struct {
+ // TotalActions is the total number of expected changes. This can
+ // generally change up or down during a build, but it should never go
+ // below the number of StartedActions
+ TotalActions int
+
+ // RunningActions are the number of actions that are currently running
+ // -- the number that have called StartAction, but not FinishAction.
+ RunningActions int
+
+ // StartedActions are the number of actions that have been started with
+ // StartAction.
+ StartedActions int
+
+ // FinishedActions are the number of actions that have been finished
+ // with FinishAction.
+ FinishedActions int
+}
+
+// ToolStatus is the interface used by tools to report on their Actions, and to
+// present other information through a set of messaging functions.
+type ToolStatus interface {
+ // SetTotalActions sets the expected total number of actions that will
+ // be started by this tool.
+ //
+ // This call be will ignored if it sets a number that is less than the
+ // current number of started actions.
+ SetTotalActions(total int)
+
+ // StartAction specifies that the associated action has been started by
+ // the tool.
+ //
+ // A specific *Action should not be specified to StartAction more than
+ // once, even if the previous action has already been finished, and the
+ // contents rewritten.
+ //
+ // Do not re-use *Actions between different ToolStatus interfaces
+ // either.
+ StartAction(action *Action)
+
+ // FinishAction specifies the result of a particular Action.
+ //
+ // The *Action embedded in the ActionResult structure must have already
+ // been passed to StartAction (on this interface).
+ //
+ // Do not call FinishAction twice for the same *Action.
+ FinishAction(result ActionResult)
+
+ // Verbose takes a non-important message that is never printed to the
+ // screen, but is in the verbose build log, etc
+ Verbose(msg string)
+ // Status takes a less important message that may be printed to the
+ // screen, but overwritten by another status message. The full message
+ // will still appear in the verbose build log.
+ Status(msg string)
+ // Print takes an message and displays it to the screen and other
+ // output logs, etc.
+ Print(msg string)
+ // Error is similar to Print, but treats it similarly to a failed
+ // action, showing it in the error logs, etc.
+ Error(msg string)
+
+ // Finish marks the end of all Actions being run by this tool.
+ //
+ // SetTotalEdges, StartAction, and FinishAction should not be called
+ // after Finish.
+ Finish()
+}
+
+// MsgLevel specifies the importance of a particular log message. See the
+// descriptions in ToolStatus: Verbose, Status, Print, Error.
+type MsgLevel int
+
+const (
+ VerboseLvl MsgLevel = iota
+ StatusLvl
+ PrintLvl
+ ErrorLvl
+)
+
+func (l MsgLevel) Prefix() string {
+ switch l {
+ case VerboseLvl:
+ return "verbose: "
+ case StatusLvl:
+ return "status: "
+ case PrintLvl:
+ return ""
+ case ErrorLvl:
+ return "error: "
+ default:
+ panic("Unknown message level")
+ }
+}
+
+// StatusOutput is the interface used to get status information as a Status
+// output.
+//
+// All of the functions here are guaranteed to be called by Status while
+// holding it's internal lock, so it's safe to assume a single caller at any
+// time, and that the ordering of calls will be correct. It is not safe to call
+// back into the Status, or one of its ToolStatus interfaces.
+type StatusOutput interface {
+ // StartAction will be called once every time ToolStatus.StartAction is
+ // called. counts will include the current counters across all
+ // ToolStatus instances, including ones that have been finished.
+ StartAction(action *Action, counts Counts)
+
+ // FinishAction will be called once every time ToolStatus.FinishAction
+ // is called. counts will include the current counters across all
+ // ToolStatus instances, including ones that have been finished.
+ FinishAction(result ActionResult, counts Counts)
+
+ // Message is the equivalent of ToolStatus.Verbose/Status/Print/Error,
+ // but the level is specified as an argument.
+ Message(level MsgLevel, msg string)
+
+ // Flush is called when your outputs should be flushed / closed. No
+ // output is expected after this call.
+ Flush()
+}
+
+// Status is the multiplexer / accumulator between ToolStatus instances (via
+// StartTool) and StatusOutputs (via AddOutput). There's generally one of these
+// per build process (though tools like multiproduct_kati may have multiple
+// independent versions).
+type Status struct {
+ counts Counts
+ outputs []StatusOutput
+
+ // Protects counts and outputs, and allows each output to
+ // expect only a single caller at a time.
+ lock sync.Mutex
+}
+
+// AddOutput attaches an output to this object. It's generally expected that an
+// output is attached to a single Status instance.
+func (s *Status) AddOutput(output StatusOutput) {
+ if output == nil {
+ return
+ }
+
+ s.lock.Lock()
+ defer s.lock.Unlock()
+
+ s.outputs = append(s.outputs, output)
+}
+
+// StartTool returns a new ToolStatus instance to report the status of a tool.
+func (s *Status) StartTool() ToolStatus {
+ return &toolStatus{
+ status: s,
+ }
+}
+
+// Finish will call Flush on all the outputs, generally flushing or closing all
+// of their outputs. Do not call any other functions on this instance or any
+// associated ToolStatus instances after this has been called.
+func (s *Status) Finish() {
+ s.lock.Lock()
+ defer s.lock.Unlock()
+
+ for _, o := range s.outputs {
+ o.Flush()
+ }
+}
+
+func (s *Status) updateTotalActions(diff int) {
+ s.lock.Lock()
+ defer s.lock.Unlock()
+
+ s.counts.TotalActions += diff
+}
+
+func (s *Status) startAction(action *Action) {
+ s.lock.Lock()
+ defer s.lock.Unlock()
+
+ s.counts.RunningActions += 1
+ s.counts.StartedActions += 1
+
+ for _, o := range s.outputs {
+ o.StartAction(action, s.counts)
+ }
+}
+
+func (s *Status) finishAction(result ActionResult) {
+ s.lock.Lock()
+ defer s.lock.Unlock()
+
+ s.counts.RunningActions -= 1
+ s.counts.FinishedActions += 1
+
+ for _, o := range s.outputs {
+ o.FinishAction(result, s.counts)
+ }
+}
+
+func (s *Status) message(level MsgLevel, msg string) {
+ s.lock.Lock()
+ defer s.lock.Unlock()
+
+ for _, o := range s.outputs {
+ o.Message(level, msg)
+ }
+}
+
+type toolStatus struct {
+ status *Status
+
+ counts Counts
+ // Protects counts
+ lock sync.Mutex
+}
+
+var _ ToolStatus = (*toolStatus)(nil)
+
+func (d *toolStatus) SetTotalActions(total int) {
+ diff := 0
+
+ d.lock.Lock()
+ if total >= d.counts.StartedActions && total != d.counts.TotalActions {
+ diff = total - d.counts.TotalActions
+ d.counts.TotalActions = total
+ }
+ d.lock.Unlock()
+
+ if diff != 0 {
+ d.status.updateTotalActions(diff)
+ }
+}
+
+func (d *toolStatus) StartAction(action *Action) {
+ totalDiff := 0
+
+ d.lock.Lock()
+ d.counts.RunningActions += 1
+ d.counts.StartedActions += 1
+
+ if d.counts.StartedActions > d.counts.TotalActions {
+ totalDiff = d.counts.StartedActions - d.counts.TotalActions
+ d.counts.TotalActions = d.counts.StartedActions
+ }
+ d.lock.Unlock()
+
+ if totalDiff != 0 {
+ d.status.updateTotalActions(totalDiff)
+ }
+ d.status.startAction(action)
+}
+
+func (d *toolStatus) FinishAction(result ActionResult) {
+ d.lock.Lock()
+ d.counts.RunningActions -= 1
+ d.counts.FinishedActions += 1
+ d.lock.Unlock()
+
+ d.status.finishAction(result)
+}
+
+func (d *toolStatus) Verbose(msg string) {
+ d.status.message(VerboseLvl, msg)
+}
+func (d *toolStatus) Status(msg string) {
+ d.status.message(StatusLvl, msg)
+}
+func (d *toolStatus) Print(msg string) {
+ d.status.message(PrintLvl, msg)
+}
+func (d *toolStatus) Error(msg string) {
+ d.status.message(ErrorLvl, msg)
+}
+
+func (d *toolStatus) Finish() {
+ d.lock.Lock()
+ defer d.lock.Unlock()
+
+ if d.counts.TotalActions != d.counts.StartedActions {
+ d.status.updateTotalActions(d.counts.StartedActions - d.counts.TotalActions)
+ }
+
+ // TODO: update status to correct running/finished edges?
+ d.counts.RunningActions = 0
+ d.counts.TotalActions = d.counts.StartedActions
+}
diff --git a/ui/status/status_test.go b/ui/status/status_test.go
new file mode 100644
index 0000000..e62785f
--- /dev/null
+++ b/ui/status/status_test.go
@@ -0,0 +1,166 @@
+// Copyright 2018 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 status
+
+import "testing"
+
+type counterOutput Counts
+
+func (c *counterOutput) StartAction(action *Action, counts Counts) {
+ *c = counterOutput(counts)
+}
+func (c *counterOutput) FinishAction(result ActionResult, counts Counts) {
+ *c = counterOutput(counts)
+}
+func (c counterOutput) Message(level MsgLevel, msg string) {}
+func (c counterOutput) Flush() {}
+
+func (c counterOutput) Expect(t *testing.T, counts Counts) {
+ if Counts(c) == counts {
+ return
+ }
+ t.Helper()
+
+ if c.TotalActions != counts.TotalActions {
+ t.Errorf("Expected %d total edges, but got %d", counts.TotalActions, c.TotalActions)
+ }
+ if c.RunningActions != counts.RunningActions {
+ t.Errorf("Expected %d running edges, but got %d", counts.RunningActions, c.RunningActions)
+ }
+ if c.StartedActions != counts.StartedActions {
+ t.Errorf("Expected %d started edges, but got %d", counts.StartedActions, c.StartedActions)
+ }
+ if c.FinishedActions != counts.FinishedActions {
+ t.Errorf("Expected %d finished edges, but got %d", counts.FinishedActions, c.FinishedActions)
+ }
+}
+
+func TestBasicUse(t *testing.T) {
+ status := &Status{}
+ counts := &counterOutput{}
+ status.AddOutput(counts)
+ s := status.StartTool()
+
+ s.SetTotalActions(2)
+
+ a := &Action{}
+ s.StartAction(a)
+
+ counts.Expect(t, Counts{
+ TotalActions: 2,
+ RunningActions: 1,
+ StartedActions: 1,
+ FinishedActions: 0,
+ })
+
+ s.FinishAction(ActionResult{Action: a})
+
+ counts.Expect(t, Counts{
+ TotalActions: 2,
+ RunningActions: 0,
+ StartedActions: 1,
+ FinishedActions: 1,
+ })
+
+ a = &Action{}
+ s.StartAction(a)
+
+ counts.Expect(t, Counts{
+ TotalActions: 2,
+ RunningActions: 1,
+ StartedActions: 2,
+ FinishedActions: 1,
+ })
+
+ s.FinishAction(ActionResult{Action: a})
+
+ counts.Expect(t, Counts{
+ TotalActions: 2,
+ RunningActions: 0,
+ StartedActions: 2,
+ FinishedActions: 2,
+ })
+}
+
+// For when a tool claims to have 2 actions, but finishes after one.
+func TestFinishEarly(t *testing.T) {
+ status := &Status{}
+ counts := &counterOutput{}
+ status.AddOutput(counts)
+ s := status.StartTool()
+
+ s.SetTotalActions(2)
+
+ a := &Action{}
+ s.StartAction(a)
+ s.FinishAction(ActionResult{Action: a})
+ s.Finish()
+
+ s = status.StartTool()
+ s.SetTotalActions(2)
+
+ a = &Action{}
+ s.StartAction(a)
+
+ counts.Expect(t, Counts{
+ TotalActions: 3,
+ RunningActions: 1,
+ StartedActions: 2,
+ FinishedActions: 1,
+ })
+}
+
+// For when a tool claims to have 1 action, but starts two.
+func TestExtraActions(t *testing.T) {
+ status := &Status{}
+ counts := &counterOutput{}
+ status.AddOutput(counts)
+ s := status.StartTool()
+
+ s.SetTotalActions(1)
+
+ s.StartAction(&Action{})
+ s.StartAction(&Action{})
+
+ counts.Expect(t, Counts{
+ TotalActions: 2,
+ RunningActions: 2,
+ StartedActions: 2,
+ FinishedActions: 0,
+ })
+}
+
+// When a tool calls Finish() with a running Action
+func TestRunningWhenFinished(t *testing.T) {
+ status := &Status{}
+ counts := &counterOutput{}
+ status.AddOutput(counts)
+
+ s := status.StartTool()
+ s.SetTotalActions(1)
+ s.StartAction(&Action{})
+ s.Finish()
+
+ s = status.StartTool()
+ s.SetTotalActions(1)
+ s.StartAction(&Action{})
+
+ counts.Expect(t, Counts{
+ TotalActions: 2,
+ RunningActions: 2,
+ StartedActions: 2,
+ FinishedActions: 0,
+ })
+}
diff --git a/ui/terminal/Android.bp b/ui/terminal/Android.bp
new file mode 100644
index 0000000..7104a50
--- /dev/null
+++ b/ui/terminal/Android.bp
@@ -0,0 +1,37 @@
+// Copyright 2018 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-terminal",
+ pkgPath: "android/soong/ui/terminal",
+ deps: ["soong-ui-status"],
+ srcs: [
+ "status.go",
+ "writer.go",
+ "util.go",
+ ],
+ testSrcs: [
+ "util_test.go",
+ ],
+ darwin: {
+ srcs: [
+ "util_darwin.go",
+ ],
+ },
+ linux: {
+ srcs: [
+ "util_linux.go",
+ ],
+ },
+}
diff --git a/ui/terminal/status.go b/ui/terminal/status.go
new file mode 100644
index 0000000..c8eb382
--- /dev/null
+++ b/ui/terminal/status.go
@@ -0,0 +1,144 @@
+// Copyright 2018 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 terminal
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "android/soong/ui/status"
+)
+
+type statusOutput struct {
+ writer Writer
+ format string
+
+ start time.Time
+}
+
+// NewStatusOutput returns a StatusOutput that represents the
+// current build status similarly to Ninja's built-in terminal
+// output.
+//
+// statusFormat takes nearly all the same options as NINJA_STATUS.
+// %c is currently unsupported.
+func NewStatusOutput(w Writer, statusFormat string) status.StatusOutput {
+ return &statusOutput{
+ writer: w,
+ format: statusFormat,
+
+ start: time.Now(),
+ }
+}
+
+func (s *statusOutput) Message(level status.MsgLevel, message string) {
+ if level >= status.ErrorLvl {
+ s.writer.Print(fmt.Sprintf("FAILED: %s", message))
+ } else if level > status.StatusLvl {
+ s.writer.Print(fmt.Sprintf("%s%s", level.Prefix(), message))
+ } else if level == status.StatusLvl {
+ s.writer.StatusLine(message)
+ }
+}
+
+func (s *statusOutput) StartAction(action *status.Action, counts status.Counts) {
+ if !s.writer.isSmartTerminal() {
+ return
+ }
+
+ str := action.Description
+ if str == "" {
+ str = action.Command
+ }
+
+ s.writer.StatusLine(s.progress(counts) + str)
+}
+
+func (s *statusOutput) FinishAction(result status.ActionResult, counts status.Counts) {
+ str := result.Description
+ if str == "" {
+ str = result.Command
+ }
+
+ progress := s.progress(counts) + str
+
+ if result.Error != nil {
+ hasCommand := ""
+ if result.Command != "" {
+ hasCommand = "\n"
+ }
+
+ s.writer.StatusAndMessage(progress, fmt.Sprintf("FAILED: %s\n%s%s%s",
+ strings.Join(result.Outputs, " "), result.Command, hasCommand, result.Output))
+ } else if result.Output != "" {
+ s.writer.StatusAndMessage(progress, result.Output)
+ } else {
+ s.writer.StatusLine(progress)
+ }
+}
+
+func (s *statusOutput) Flush() {}
+
+func (s *statusOutput) progress(counts status.Counts) string {
+ if s.format == "" {
+ return fmt.Sprintf("[%3d%% %d/%d] ", 100*counts.FinishedActions/counts.TotalActions, counts.FinishedActions, counts.TotalActions)
+ }
+
+ buf := &strings.Builder{}
+ for i := 0; i < len(s.format); i++ {
+ c := s.format[i]
+ if c != '%' {
+ buf.WriteByte(c)
+ continue
+ }
+
+ i = i + 1
+ if i == len(s.format) {
+ buf.WriteByte(c)
+ break
+ }
+
+ c = s.format[i]
+ switch c {
+ case '%':
+ buf.WriteByte(c)
+ case 's':
+ fmt.Fprintf(buf, "%d", counts.StartedActions)
+ case 't':
+ fmt.Fprintf(buf, "%d", counts.TotalActions)
+ case 'r':
+ fmt.Fprintf(buf, "%d", counts.RunningActions)
+ case 'u':
+ fmt.Fprintf(buf, "%d", counts.TotalActions-counts.StartedActions)
+ case 'f':
+ fmt.Fprintf(buf, "%d", counts.FinishedActions)
+ case 'o':
+ fmt.Fprintf(buf, "%.1f", float64(counts.FinishedActions)/time.Since(s.start).Seconds())
+ case 'c':
+ // TODO: implement?
+ buf.WriteRune('?')
+ case 'p':
+ fmt.Fprintf(buf, "%3d%%", 100*counts.FinishedActions/counts.TotalActions)
+ case 'e':
+ fmt.Fprintf(buf, "%.3f", time.Since(s.start).Seconds())
+ default:
+ buf.WriteString("unknown placeholder '")
+ buf.WriteByte(c)
+ buf.WriteString("'")
+ }
+ }
+ return buf.String()
+}
diff --git a/ui/terminal/util.go b/ui/terminal/util.go
new file mode 100644
index 0000000..a85a517
--- /dev/null
+++ b/ui/terminal/util.go
@@ -0,0 +1,101 @@
+// 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 terminal
+
+import (
+ "bytes"
+ "io"
+ "os"
+ "syscall"
+ "unsafe"
+)
+
+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/terminal/util_darwin.go
similarity index 97%
rename from ui/build/util_darwin.go
rename to ui/terminal/util_darwin.go
index 254a9b8..109a37f 100644
--- a/ui/build/util_darwin.go
+++ b/ui/terminal/util_darwin.go
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package build
+package terminal
import (
"syscall"
diff --git a/ui/build/util_linux.go b/ui/terminal/util_linux.go
similarity index 97%
rename from ui/build/util_linux.go
rename to ui/terminal/util_linux.go
index 0a4e1d2..0a3d9dd 100644
--- a/ui/build/util_linux.go
+++ b/ui/terminal/util_linux.go
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package build
+package terminal
import (
"syscall"
diff --git a/ui/terminal/util_test.go b/ui/terminal/util_test.go
new file mode 100644
index 0000000..82bde7c
--- /dev/null
+++ b/ui/terminal/util_test.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 terminal
+
+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/terminal/writer.go b/ui/terminal/writer.go
new file mode 100644
index 0000000..ebe4b2a
--- /dev/null
+++ b/ui/terminal/writer.go
@@ -0,0 +1,229 @@
+// Copyright 2018 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 terminal provides a set of interfaces that can be used to interact
+// with the terminal (including falling back when the terminal is detected to
+// be a redirect or other dumb terminal)
+package terminal
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "strings"
+ "sync"
+)
+
+// Writer provides an interface to write temporary and permanent messages to
+// the terminal.
+//
+// The terminal is considered to be a dumb terminal if TERM==dumb, or if a
+// terminal isn't detected on stdout/stderr (generally because it's a pipe or
+// file). Dumb terminals will strip out all ANSI escape sequences, including
+// colors.
+type Writer interface {
+ // Print prints the string to the terminal, overwriting any current
+ // status being displayed.
+ //
+ // On a dumb terminal, the status messages will be kept.
+ Print(str string)
+
+ // Status prints the first line of the string to the terminal,
+ // overwriting any previous status line. Strings longer than the width
+ // of the terminal will be cut off.
+ //
+ // On a dumb terminal, previous status messages will remain, and the
+ // entire first line of the string will be printed.
+ StatusLine(str string)
+
+ // StatusAndMessage prints the first line of status to the terminal,
+ // similarly to StatusLine(), then prints the full msg below that. The
+ // status line is retained.
+ //
+ // There is guaranteed to be no other output in between the status and
+ // message.
+ StatusAndMessage(status, msg string)
+
+ // Finish ensures that the output ends with a newline (preserving any
+ // current status line that is current displayed).
+ //
+ // This does nothing on dumb terminals.
+ Finish()
+
+ // Write implements the io.Writer interface. This is primarily so that
+ // the logger can use this interface to print to stderr without
+ // breaking the other semantics of this interface.
+ //
+ // Try to use any of the other functions if possible.
+ Write(p []byte) (n int, err error)
+
+ isSmartTerminal() bool
+}
+
+// NewWriter creates a new Writer based on the stdio and the TERM
+// environment variable.
+func NewWriter(stdio StdioInterface) Writer {
+ w := &writerImpl{
+ stdio: stdio,
+
+ haveBlankLine: true,
+ }
+
+ if term, ok := os.LookupEnv("TERM"); ok && term != "dumb" {
+ w.smartTerminal = isTerminal(stdio.Stdout())
+ }
+ w.stripEscapes = !w.smartTerminal
+
+ return w
+}
+
+type writerImpl struct {
+ stdio StdioInterface
+
+ haveBlankLine bool
+
+ // Protecting the above, we assume that smartTerminal and stripEscapes
+ // does not change after initial setup.
+ lock sync.Mutex
+
+ smartTerminal bool
+ stripEscapes bool
+}
+
+func (w *writerImpl) isSmartTerminal() bool {
+ return w.smartTerminal
+}
+
+func (w *writerImpl) requestLine() {
+ if !w.haveBlankLine {
+ fmt.Fprintln(w.stdio.Stdout())
+ w.haveBlankLine = true
+ }
+}
+
+func (w *writerImpl) Print(str string) {
+ if w.stripEscapes {
+ str = string(stripAnsiEscapes([]byte(str)))
+ }
+
+ w.lock.Lock()
+ defer w.lock.Unlock()
+ w.print(str)
+}
+
+func (w *writerImpl) print(str string) {
+ if !w.haveBlankLine {
+ fmt.Fprint(w.stdio.Stdout(), "\r", "\x1b[K")
+ w.haveBlankLine = true
+ }
+ fmt.Fprint(w.stdio.Stdout(), str)
+ if len(str) == 0 || str[len(str)-1] != '\n' {
+ fmt.Fprint(w.stdio.Stdout(), "\n")
+ }
+}
+
+func (w *writerImpl) StatusLine(str string) {
+ w.lock.Lock()
+ defer w.lock.Unlock()
+
+ w.statusLine(str)
+}
+
+func (w *writerImpl) statusLine(str string) {
+ if !w.smartTerminal {
+ fmt.Fprintln(w.stdio.Stdout(), str)
+ return
+ }
+
+ idx := strings.IndexRune(str, '\n')
+ if idx != -1 {
+ str = str[0:idx]
+ }
+
+ // 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(w.stdio.Stdout()); ok {
+ if len(str) > max {
+ // TODO: Just do a max. Ninja elides the middle, but that's
+ // more complicated and these lines aren't that important.
+ str = str[:max]
+ }
+ }
+
+ // Move to the beginning on the line, print the output, then clear
+ // the rest of the line.
+ fmt.Fprint(w.stdio.Stdout(), "\r", str, "\x1b[K")
+ w.haveBlankLine = false
+}
+
+func (w *writerImpl) StatusAndMessage(status, msg string) {
+ if w.stripEscapes {
+ msg = string(stripAnsiEscapes([]byte(msg)))
+ }
+
+ w.lock.Lock()
+ defer w.lock.Unlock()
+
+ w.statusLine(status)
+ w.requestLine()
+ w.print(msg)
+}
+
+func (w *writerImpl) Finish() {
+ w.lock.Lock()
+ defer w.lock.Unlock()
+
+ w.requestLine()
+}
+
+func (w *writerImpl) Write(p []byte) (n int, err error) {
+ w.Print(string(p))
+ return len(p), nil
+}
+
+// StdioInterface represents a set of stdin/stdout/stderr Reader/Writers
+type StdioInterface interface {
+ Stdin() io.Reader
+ Stdout() io.Writer
+ Stderr() io.Writer
+}
+
+// StdioImpl uses the OS stdin/stdout/stderr to implement StdioInterface
+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{}
diff --git a/ui/tracer/Android.bp b/ui/tracer/Android.bp
index 9729c7e..af588f1 100644
--- a/ui/tracer/Android.bp
+++ b/ui/tracer/Android.bp
@@ -15,10 +15,13 @@
bootstrap_go_package {
name: "soong-ui-tracer",
pkgPath: "android/soong/ui/tracer",
- deps: ["soong-ui-logger"],
+ deps: [
+ "soong-ui-logger",
+ "soong-ui-status",
+ ],
srcs: [
"microfactory.go",
- "ninja.go",
+ "status.go",
"tracer.go",
],
}
diff --git a/ui/tracer/microfactory.go b/ui/tracer/microfactory.go
index acb9be4..c4c37c2 100644
--- a/ui/tracer/microfactory.go
+++ b/ui/tracer/microfactory.go
@@ -17,10 +17,48 @@
import (
"bufio"
"os"
+ "sort"
"strconv"
"strings"
)
+type eventEntry struct {
+ Name string
+ Begin uint64
+ End uint64
+}
+
+func (t *tracerImpl) importEvents(entries []*eventEntry) {
+ sort.Slice(entries, func(i, j int) bool {
+ return entries[i].Begin < entries[j].Begin
+ })
+
+ cpus := []uint64{}
+ 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: entry.Begin,
+ Dur: entry.End - entry.Begin,
+ Pid: 1,
+ Tid: uint64(tid),
+ })
+ }
+}
+
func (t *tracerImpl) ImportMicrofactoryLog(filename string) {
if _, err := os.Stat(filename); err != nil {
return
diff --git a/ui/tracer/ninja.go b/ui/tracer/ninja.go
deleted file mode 100644
index 1980559..0000000
--- a/ui/tracer/ninja.go
+++ /dev/null
@@ -1,131 +0,0 @@
-// 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 eventEntry struct {
- Name string
- Begin uint64
- End uint64
-}
-
-func (t *tracerImpl) importEvents(entries []*eventEntry) {
- sort.Slice(entries, func(i, j int) bool {
- return entries[i].Begin < entries[j].Begin
- })
-
- cpus := []uint64{}
- 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: entry.Begin,
- Dur: entry.End - entry.Begin,
- Pid: 1,
- Tid: uint64(tid),
- })
- }
-}
-
-// 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 := []*eventEntry{}
- prevEnd := 0
- offset := uint64(startOffset.UnixNano()) / 1000
- 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, &eventEntry{
- Name: fields[3],
- Begin: offset + uint64(begin)*1000,
- End: offset + uint64(end)*1000,
- })
- }
- if err := s.Err(); err != nil {
- t.log.Println("Unable to parse ninja log:", err)
- return
- }
-
- t.importEvents(entries)
-}
diff --git a/ui/tracer/status.go b/ui/tracer/status.go
new file mode 100644
index 0000000..af50e2d
--- /dev/null
+++ b/ui/tracer/status.go
@@ -0,0 +1,87 @@
+// Copyright 2018 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 (
+ "android/soong/ui/status"
+ "time"
+)
+
+func (t *tracerImpl) StatusTracer() status.StatusOutput {
+ return &statusOutput{
+ tracer: t,
+
+ running: map[*status.Action]actionStatus{},
+ }
+}
+
+type actionStatus struct {
+ cpu int
+ start time.Time
+}
+
+type statusOutput struct {
+ tracer *tracerImpl
+
+ cpus []bool
+ running map[*status.Action]actionStatus
+}
+
+func (s *statusOutput) StartAction(action *status.Action, counts status.Counts) {
+ cpu := -1
+ for i, busy := range s.cpus {
+ if !busy {
+ cpu = i
+ s.cpus[i] = true
+ break
+ }
+ }
+
+ if cpu == -1 {
+ cpu = len(s.cpus)
+ s.cpus = append(s.cpus, true)
+ }
+
+ s.running[action] = actionStatus{
+ cpu: cpu,
+ start: time.Now(),
+ }
+}
+
+func (s *statusOutput) FinishAction(result status.ActionResult, counts status.Counts) {
+ start, ok := s.running[result.Action]
+ if !ok {
+ return
+ }
+ delete(s.running, result.Action)
+ s.cpus[start.cpu] = false
+
+ str := result.Action.Description
+ if len(result.Action.Outputs) > 0 {
+ str = result.Action.Outputs[0]
+ }
+
+ s.tracer.writeEvent(&viewerEvent{
+ Name: str,
+ Phase: "X",
+ Time: uint64(start.start.UnixNano()) / 1000,
+ Dur: uint64(time.Since(start.start).Nanoseconds()) / 1000,
+ Pid: 1,
+ Tid: uint64(start.cpu),
+ })
+}
+
+func (s *statusOutput) Flush() {}
+func (s *statusOutput) Message(level status.MsgLevel, message string) {}
diff --git a/ui/tracer/tracer.go b/ui/tracer/tracer.go
index 8705040..b8fc87b 100644
--- a/ui/tracer/tracer.go
+++ b/ui/tracer/tracer.go
@@ -31,6 +31,7 @@
"time"
"android/soong/ui/logger"
+ "android/soong/ui/status"
)
type Thread uint64
@@ -46,7 +47,8 @@
Complete(name string, thread Thread, begin, end uint64)
ImportMicrofactoryLog(filename string)
- ImportNinjaLog(thread Thread, filename string, startOffset time.Time)
+
+ StatusTracer() status.StatusOutput
NewThread(name string) Thread
}
diff --git a/zip/zip.go b/zip/zip.go
index b7e3764..a89fa9f 100644
--- a/zip/zip.go
+++ b/zip/zip.go
@@ -788,13 +788,16 @@
Name: rel,
}
fileHeader.SetModTime(z.time)
- fileHeader.SetMode(0700 | os.ModeSymlink)
+ fileHeader.SetMode(0777 | os.ModeSymlink)
dest, err := os.Readlink(file)
if err != nil {
return err
}
+ fileHeader.UncompressedSize64 = uint64(len(dest))
+ fileHeader.CRC32 = crc32.ChecksumIEEE([]byte(dest))
+
ze := make(chan *zipEntry, 1)
futureReaders := make(chan chan io.Reader, 1)
futureReader := make(chan io.Reader, 1)