Merge "Renderscript: rename .rs extension to .rscript"
diff --git a/Android.bp b/Android.bp
index fff17ef..4893de6 100644
--- a/Android.bp
+++ b/Android.bp
@@ -55,8 +55,10 @@
"android/mutator.go",
"android/namespace.go",
"android/neverallow.go",
+ "android/notices.go",
"android/onceper.go",
"android/override_module.go",
+ "android/package.go",
"android/package_ctx.go",
"android/path_properties.go",
"android/paths.go",
@@ -79,6 +81,7 @@
],
testSrcs: [
"android/android_test.go",
+ "android/androidmk_test.go",
"android/arch_test.go",
"android/config_test.go",
"android/expand_test.go",
@@ -87,6 +90,7 @@
"android/namespace_test.go",
"android/neverallow_test.go",
"android/onceper_test.go",
+ "android/package_test.go",
"android/path_properties_test.go",
"android/paths_test.go",
"android/prebuilt_test.go",
@@ -288,6 +292,7 @@
"java/testing.go",
],
testSrcs: [
+ "java/androidmk_test.go",
"java/app_test.go",
"java/device_host_converter_test.go",
"java/dexpreopt_test.go",
diff --git a/README.md b/README.md
index 8fdce4b..531ef4c 100644
--- a/README.md
+++ b/README.md
@@ -133,6 +133,25 @@
This is based on the Bazel package concept.
+The `package` module type allows information to be specified about a package. Only a single
+`package` module can be specified per package and in the case where there are multiple `.bp` files
+in the same package directory it is highly recommended that the `package` module (if required) is
+specified in the `Android.bp` file.
+
+Unlike most module type `package` does not have a `name` property. Instead the name is set to the
+name of the package, e.g. if the package is in `top/intermediate/package` then the package name is
+`//top/intermediate/package`.
+
+E.g. The following will set the default visibility for all the modules defined in the package and
+any subpackages that do not set their own default visibility (irrespective of whether they are in
+the same `.bp` file as the `package` module) to be visible to all the subpackages by default.
+
+```
+package {
+ default_visibility: [":__subpackages"]
+}
+```
+
### Name resolution
Soong provides the ability for modules in different directories to specify
@@ -191,13 +210,13 @@
`//independent:evil`)
* `["//project"]`: This is shorthand for `["//project:__pkg__"]`
* `[":__subpackages__"]`: This is shorthand for `["//project:__subpackages__"]`
-where `//project` is the module's package. e.g. using `[":__subpackages__"]` in
+where `//project` is the module's package, e.g. using `[":__subpackages__"]` in
`packages/apps/Settings/Android.bp` is equivalent to
`//packages/apps/Settings:__subpackages__`.
* `["//visibility:legacy_public"]`: The default visibility, behaves as
`//visibility:public` for now. It is an error if it is used in a module.
-The visibility rules of `//visibility:public` and `//visibility:private` can not
+The visibility rules of `//visibility:public` and `//visibility:private` cannot
be combined with any other visibility specifications, except
`//visibility:public` is allowed to override visibility specifications imported
through the `defaults` property.
@@ -207,13 +226,22 @@
say `vendor/google`, instead it must make itself visible to all packages within
`vendor/` using `//vendor:__subpackages__`.
-If a module does not specify the `visibility` property the module is
-`//visibility:legacy_public`. Once the build has been completely switched over to
-soong it is possible that a global refactoring will be done to change this to
-`//visibility:private` at which point all modules that do not currently specify
-a `visibility` property will be updated to have
-`visibility = [//visibility:legacy_public]` added. It will then be the owner's
-responsibility to replace that with a more appropriate visibility.
+If a module does not specify the `visibility` property then it uses the
+`default_visibility` property of the `package` module in the module's package.
+
+If the `default_visibility` property is not set for the module's package then
+it will use the `default_visibility` of its closest ancestor package for which
+a `default_visibility` property is specified.
+
+If no `default_visibility` property can be found then the module uses the
+global default of `//visibility:legacy_public`.
+
+Once the build has been completely switched over to soong it is possible that a
+global refactoring will be done to change this to `//visibility:private` at
+which point all packages that do not currently specify a `default_visibility`
+property will be updated to have
+`default_visibility = [//visibility:legacy_public]` added. It will then be the
+owner's responsibility to replace that with a more appropriate visibility.
### Formatter
@@ -309,6 +337,19 @@
This will bind mount the Soong source directories into the directory in the layout expected by
the IDE.
+### Running Soong in a debugger
+
+To run the soong_build process in a debugger, install `dlv` and then start the build with
+`SOONG_DELVE=<listen addr>` in the environment.
+For examle:
+```bash
+SOONG_DELVE=:1234 m nothing
+```
+and then in another terminal:
+```
+dlv connect :1234
+```
+
## Contact
Email android-building@googlegroups.com (external) for any questions, or see
diff --git a/android/androidmk.go b/android/androidmk.go
index 7d0aa3b..1f1bd70 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -191,15 +191,7 @@
// Make cannot identify LOCAL_MODULE_TARGET_ARCH:= common.
if archStr != "common" {
if amod.Target().NativeBridge {
- // TODO: Unhardcode these rules.
- guestArchStr := archStr
- hostArchStr := ""
- if guestArchStr == "arm" {
- hostArchStr = "x86"
- } else if guestArchStr == "arm64" {
- hostArchStr = "x86_64"
- }
-
+ hostArchStr := amod.Target().NativeBridgeHostArchName
if hostArchStr != "" {
a.SetString("LOCAL_MODULE_TARGET_ARCH", hostArchStr)
}
@@ -216,7 +208,10 @@
}
a.SetBoolIfTrue("LOCAL_ODM_MODULE", Bool(amod.commonProperties.Device_specific))
a.SetBoolIfTrue("LOCAL_PRODUCT_MODULE", Bool(amod.commonProperties.Product_specific))
- a.SetBoolIfTrue("LOCAL_PRODUCT_SERVICES_MODULE", Bool(amod.commonProperties.Product_services_specific))
+ // TODO(b/135957588) product_services_specific is matched to LOCAL_PRODUCT_MODULE
+ // as a workaround. Remove this after clearing all Android.bp
+ a.SetBoolIfTrue("LOCAL_PRODUCT_MODULE", Bool(amod.commonProperties.Product_services_specific))
+ a.SetBoolIfTrue("LOCAL_SYSTEM_EXT_MODULE", Bool(amod.commonProperties.System_ext_specific))
if amod.commonProperties.Owner != nil {
a.SetString("LOCAL_MODULE_OWNER", *amod.commonProperties.Owner)
}
@@ -388,6 +383,31 @@
return nil
}
+func (data *AndroidMkData) fillInData(config Config, bpPath string, mod blueprint.Module) {
+ // Get the preamble content through AndroidMkEntries logic.
+ entries := AndroidMkEntries{
+ Class: data.Class,
+ SubName: data.SubName,
+ DistFile: data.DistFile,
+ OutputFile: data.OutputFile,
+ Disabled: data.Disabled,
+ Include: data.Include,
+ Required: data.Required,
+ Host_required: data.Host_required,
+ Target_required: data.Target_required,
+ }
+ entries.fillInEntries(config, bpPath, mod)
+
+ // preamble doesn't need the footer content.
+ entries.footer = bytes.Buffer{}
+ entries.write(&data.preamble)
+
+ // copy entries back to data since it is used in Custom
+ data.Required = entries.Required
+ data.Host_required = entries.Host_required
+ data.Target_required = entries.Target_required
+}
+
func translateAndroidModule(ctx SingletonContext, w io.Writer, mod blueprint.Module,
provider AndroidMkDataProvider) error {
@@ -401,22 +421,7 @@
data.Include = "$(BUILD_PREBUILT)"
}
- // Get the preamble content through AndroidMkEntries logic.
- entries := AndroidMkEntries{
- Class: data.Class,
- SubName: data.SubName,
- DistFile: data.DistFile,
- OutputFile: data.OutputFile,
- Disabled: data.Disabled,
- Include: data.Include,
- Required: data.Required,
- Host_required: data.Host_required,
- Target_required: data.Target_required,
- }
- entries.fillInEntries(ctx.Config(), ctx.BlueprintFile(mod), mod)
- // preamble doesn't need the footer content.
- entries.footer = bytes.Buffer{}
- entries.write(&data.preamble)
+ data.fillInData(ctx.Config(), ctx.BlueprintFile(mod), mod)
prefix := ""
if amod.ArchSpecific() {
diff --git a/android/androidmk_test.go b/android/androidmk_test.go
new file mode 100644
index 0000000..0bb455b
--- /dev/null
+++ b/android/androidmk_test.go
@@ -0,0 +1,82 @@
+// Copyright 2019 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import (
+ "io"
+ "reflect"
+ "testing"
+)
+
+type customModule struct {
+ ModuleBase
+ data AndroidMkData
+}
+
+func (m *customModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+}
+
+func (m *customModule) AndroidMk() AndroidMkData {
+ return AndroidMkData{
+ Custom: func(w io.Writer, name, prefix, moduleDir string, data AndroidMkData) {
+ m.data = data
+ },
+ }
+}
+
+func customModuleFactory() Module {
+ module := &customModule{}
+ InitAndroidModule(module)
+ return module
+}
+
+func TestAndroidMkSingleton_PassesUpdatedAndroidMkDataToCustomCallback(t *testing.T) {
+ config := TestConfig(buildDir, nil)
+ config.inMake = true // Enable androidmk Singleton
+
+ ctx := NewTestContext()
+ ctx.RegisterSingletonType("androidmk", SingletonFactoryAdaptor(AndroidMkSingleton))
+ ctx.RegisterModuleType("custom", ModuleFactoryAdaptor(customModuleFactory))
+ ctx.Register()
+
+ bp := `
+ custom {
+ name: "foo",
+ required: ["bar"],
+ host_required: ["baz"],
+ target_required: ["qux"],
+ }
+ `
+
+ ctx.MockFileSystem(map[string][]byte{
+ "Android.bp": []byte(bp),
+ })
+
+ _, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+ FailIfErrored(t, errs)
+ _, errs = ctx.PrepareBuildActions(config)
+ FailIfErrored(t, errs)
+
+ m := ctx.ModuleForTests("foo", "").Module().(*customModule)
+
+ assertEqual := func(expected interface{}, actual interface{}) {
+ if !reflect.DeepEqual(expected, actual) {
+ t.Errorf("%q expected, but got %q", expected, actual)
+ }
+ }
+ assertEqual([]string{"bar"}, m.data.Required)
+ assertEqual([]string{"baz"}, m.data.Host_required)
+ assertEqual([]string{"qux"}, m.data.Target_required)
+}
diff --git a/android/arch.go b/android/arch.go
index 46e582c..44c3f48 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -691,9 +691,11 @@
)
type Target struct {
- Os OsType
- Arch Arch
- NativeBridge NativeBridgeSupport
+ Os OsType
+ Arch Arch
+ NativeBridge NativeBridgeSupport
+ NativeBridgeHostArchName string
+ NativeBridgeRelativePath string
}
func (target Target) String() string {
@@ -1403,7 +1405,8 @@
var targetErr error
addTarget := func(os OsType, archName string, archVariant, cpuVariant *string, abi []string,
- nativeBridgeEnabled NativeBridgeSupport) {
+ nativeBridgeEnabled NativeBridgeSupport, nativeBridgeHostArchName *string,
+ nativeBridgeRelativePath *string) {
if targetErr != nil {
return
}
@@ -1413,12 +1416,21 @@
targetErr = err
return
}
+ nativeBridgeRelativePathStr := String(nativeBridgeRelativePath)
+ nativeBridgeHostArchNameStr := String(nativeBridgeHostArchName)
+
+ // Use guest arch as relative install path by default
+ if nativeBridgeEnabled && nativeBridgeRelativePathStr == "" {
+ nativeBridgeRelativePathStr = arch.ArchType.String()
+ }
targets[os] = append(targets[os],
Target{
- Os: os,
- Arch: arch,
- NativeBridge: nativeBridgeEnabled,
+ Os: os,
+ Arch: arch,
+ NativeBridge: nativeBridgeEnabled,
+ NativeBridgeHostArchName: nativeBridgeHostArchNameStr,
+ NativeBridgeRelativePath: nativeBridgeRelativePathStr,
})
}
@@ -1426,14 +1438,14 @@
return nil, fmt.Errorf("No host primary architecture set")
}
- addTarget(BuildOs, *variables.HostArch, nil, nil, nil, NativeBridgeDisabled)
+ addTarget(BuildOs, *variables.HostArch, nil, nil, nil, NativeBridgeDisabled, nil, nil)
if variables.HostSecondaryArch != nil && *variables.HostSecondaryArch != "" {
- addTarget(BuildOs, *variables.HostSecondaryArch, nil, nil, nil, NativeBridgeDisabled)
+ addTarget(BuildOs, *variables.HostSecondaryArch, nil, nil, nil, NativeBridgeDisabled, nil, nil)
}
if Bool(config.Host_bionic) {
- addTarget(LinuxBionic, "x86_64", nil, nil, nil, NativeBridgeDisabled)
+ addTarget(LinuxBionic, "x86_64", nil, nil, nil, NativeBridgeDisabled, nil, nil)
}
if String(variables.CrossHost) != "" {
@@ -1446,10 +1458,10 @@
return nil, fmt.Errorf("No cross-host primary architecture set")
}
- addTarget(crossHostOs, *variables.CrossHostArch, nil, nil, nil, NativeBridgeDisabled)
+ addTarget(crossHostOs, *variables.CrossHostArch, nil, nil, nil, NativeBridgeDisabled, nil, nil)
if variables.CrossHostSecondaryArch != nil && *variables.CrossHostSecondaryArch != "" {
- addTarget(crossHostOs, *variables.CrossHostSecondaryArch, nil, nil, nil, NativeBridgeDisabled)
+ addTarget(crossHostOs, *variables.CrossHostSecondaryArch, nil, nil, nil, NativeBridgeDisabled, nil, nil)
}
}
@@ -1460,12 +1472,12 @@
}
addTarget(target, *variables.DeviceArch, variables.DeviceArchVariant,
- variables.DeviceCpuVariant, variables.DeviceAbi, NativeBridgeDisabled)
+ variables.DeviceCpuVariant, variables.DeviceAbi, NativeBridgeDisabled, nil, nil)
if variables.DeviceSecondaryArch != nil && *variables.DeviceSecondaryArch != "" {
addTarget(Android, *variables.DeviceSecondaryArch,
variables.DeviceSecondaryArchVariant, variables.DeviceSecondaryCpuVariant,
- variables.DeviceSecondaryAbi, NativeBridgeDisabled)
+ variables.DeviceSecondaryAbi, NativeBridgeDisabled, nil, nil)
deviceArches := targets[Android]
if deviceArches[0].Arch.ArchType.Multilib == deviceArches[1].Arch.ArchType.Multilib {
@@ -1476,7 +1488,8 @@
if variables.NativeBridgeArch != nil && *variables.NativeBridgeArch != "" {
addTarget(Android, *variables.NativeBridgeArch,
variables.NativeBridgeArchVariant, variables.NativeBridgeCpuVariant,
- variables.NativeBridgeAbi, NativeBridgeEnabled)
+ variables.NativeBridgeAbi, NativeBridgeEnabled, variables.DeviceArch,
+ variables.NativeBridgeRelativePath)
}
if variables.DeviceSecondaryArch != nil && *variables.DeviceSecondaryArch != "" &&
@@ -1484,7 +1497,10 @@
addTarget(Android, *variables.NativeBridgeSecondaryArch,
variables.NativeBridgeSecondaryArchVariant,
variables.NativeBridgeSecondaryCpuVariant,
- variables.NativeBridgeSecondaryAbi, NativeBridgeEnabled)
+ variables.NativeBridgeSecondaryAbi,
+ NativeBridgeEnabled,
+ variables.DeviceSecondaryArch,
+ variables.NativeBridgeSecondaryRelativePath)
}
}
diff --git a/android/config.go b/android/config.go
index b0d8b7f..8ced93a 100644
--- a/android/config.go
+++ b/android/config.go
@@ -216,6 +216,7 @@
AAPTPreferredConfig: stringPtr("xhdpi"),
AAPTCharacteristics: stringPtr("nosdcard"),
AAPTPrebuiltDPI: []string{"xhdpi", "xxhdpi"},
+ UncompressPrivAppDex: boolPtr(true),
},
buildDir: buildDir,
@@ -239,10 +240,10 @@
config := testConfig.config
config.Targets[Android] = []Target{
- {Android, Arch{ArchType: X86_64, ArchVariant: "silvermont", Native: true, Abi: []string{"arm64-v8a"}}, NativeBridgeDisabled},
- {Android, Arch{ArchType: X86, ArchVariant: "silvermont", Native: true, Abi: []string{"armeabi-v7a"}}, NativeBridgeDisabled},
- {Android, Arch{ArchType: Arm64, ArchVariant: "armv8-a", Native: true, Abi: []string{"arm64-v8a"}}, NativeBridgeEnabled},
- {Android, Arch{ArchType: Arm, ArchVariant: "armv7-a-neon", Native: true, Abi: []string{"armeabi-v7a"}}, NativeBridgeEnabled},
+ {Android, Arch{ArchType: X86_64, ArchVariant: "silvermont", Native: true, Abi: []string{"arm64-v8a"}}, NativeBridgeDisabled, "", ""},
+ {Android, Arch{ArchType: X86, ArchVariant: "silvermont", Native: true, Abi: []string{"armeabi-v7a"}}, NativeBridgeDisabled, "", ""},
+ {Android, Arch{ArchType: Arm64, ArchVariant: "armv8-a", Native: true, Abi: []string{"arm64-v8a"}}, NativeBridgeEnabled, "x86_64", "arm64"},
+ {Android, Arch{ArchType: Arm, ArchVariant: "armv7-a-neon", Native: true, Abi: []string{"armeabi-v7a"}}, NativeBridgeEnabled, "x86", "arm"},
}
return testConfig
@@ -254,10 +255,10 @@
config.Targets = map[OsType][]Target{
Fuchsia: []Target{
- {Fuchsia, Arch{ArchType: Arm64, ArchVariant: "", Native: true}, NativeBridgeDisabled},
+ {Fuchsia, Arch{ArchType: Arm64, ArchVariant: "", Native: true}, NativeBridgeDisabled, "", ""},
},
BuildOs: []Target{
- {BuildOs, Arch{ArchType: X86_64}, NativeBridgeDisabled},
+ {BuildOs, Arch{ArchType: X86_64}, NativeBridgeDisabled, "", ""},
},
}
@@ -271,12 +272,12 @@
config.Targets = map[OsType][]Target{
Android: []Target{
- {Android, Arch{ArchType: Arm64, ArchVariant: "armv8-a", Native: true, Abi: []string{"arm64-v8a"}}, NativeBridgeDisabled},
- {Android, Arch{ArchType: Arm, ArchVariant: "armv7-a-neon", Native: true, Abi: []string{"armeabi-v7a"}}, NativeBridgeDisabled},
+ {Android, Arch{ArchType: Arm64, ArchVariant: "armv8-a", Native: true, Abi: []string{"arm64-v8a"}}, NativeBridgeDisabled, "", ""},
+ {Android, Arch{ArchType: Arm, ArchVariant: "armv7-a-neon", Native: true, Abi: []string{"armeabi-v7a"}}, NativeBridgeDisabled, "", ""},
},
BuildOs: []Target{
- {BuildOs, Arch{ArchType: X86_64}, NativeBridgeDisabled},
- {BuildOs, Arch{ArchType: X86}, NativeBridgeDisabled},
+ {BuildOs, Arch{ArchType: X86_64}, NativeBridgeDisabled, "", ""},
+ {BuildOs, Arch{ArchType: X86}, NativeBridgeDisabled, "", ""},
},
}
@@ -900,11 +901,11 @@
return "product"
}
-func (c *deviceConfig) ProductServicesPath() string {
- if c.config.productVariables.ProductServicesPath != nil {
- return *c.config.productVariables.ProductServicesPath
+func (c *deviceConfig) SystemExtPath() string {
+ if c.config.productVariables.SystemExtPath != nil {
+ return *c.config.productVariables.SystemExtPath
}
- return "product_services"
+ return "system_ext"
}
func (c *deviceConfig) BtConfigIncludeDir() string {
diff --git a/android/defaults.go b/android/defaults.go
index 844b4d4..ae2c820 100644
--- a/android/defaults.go
+++ b/android/defaults.go
@@ -63,9 +63,28 @@
type DefaultsModuleBase struct {
DefaultableModuleBase
- defaultProperties []interface{}
}
+// The common pattern for defaults modules is to register separate instances of
+// the xxxProperties structs in the AddProperties calls, rather than reusing the
+// ones inherited from Module.
+//
+// The effect is that e.g. myDefaultsModuleInstance.base().xxxProperties won't
+// contain the values that have been set for the defaults module. Rather, to
+// retrieve the values it is necessary to iterate over properties(). E.g. to get
+// the commonProperties instance that have the real values:
+//
+// d := myModule.(Defaults)
+// for _, props := range d.properties() {
+// if cp, ok := props.(*commonProperties); ok {
+// ... access property values in cp ...
+// }
+// }
+//
+// The rationale is that the properties on a defaults module apply to the
+// defaultable modules using it, not to the defaults module itself. E.g. setting
+// the "enabled" property false makes inheriting modules disabled by default,
+// rather than disabling the defaults module itself.
type Defaults interface {
Defaultable
isDefaults() bool
diff --git a/android/env.go b/android/env.go
index 469dfff..d9f2db2 100644
--- a/android/env.go
+++ b/android/env.go
@@ -16,6 +16,7 @@
import (
"os"
+ "os/exec"
"strings"
"android/soong/env"
@@ -29,8 +30,16 @@
// a manifest regeneration.
var originalEnv map[string]string
+var SoongDelveListen string
+var SoongDelvePath string
func init() {
+ // Delve support needs to read this environment variable very early, before NewConfig has created a way to
+ // access originalEnv with dependencies. Store the value where soong_build can find it, it will manually
+ // ensure the dependencies are created.
+ SoongDelveListen = os.Getenv("SOONG_DELVE")
+ SoongDelvePath, _ = exec.LookPath("dlv")
+
originalEnv = make(map[string]string)
for _, env := range os.Environ() {
idx := strings.IndexRune(env, '=')
@@ -38,6 +47,8 @@
originalEnv[env[:idx]] = env[idx+1:]
}
}
+ // Clear the environment to prevent use of os.Getenv(), which would not provide dependencies on environment
+ // variable values. The environment is available through ctx.Config().Getenv, ctx.Config().IsEnvTrue, etc.
os.Clearenv()
}
diff --git a/android/expand.go b/android/expand.go
index 527c4ac..67fb4ee 100644
--- a/android/expand.go
+++ b/android/expand.go
@@ -18,12 +18,30 @@
"fmt"
"strings"
"unicode"
+
+ "github.com/google/blueprint/proptools"
)
+// ExpandNinjaEscaped substitutes $() variables in a string
+// $(var) is passed to mapping(var), which should return the expanded value, a bool for whether the result should
+// be left unescaped when using in a ninja value (generally false, true if the expanded value is a ninja variable like
+// '${in}'), and an error.
+// $$ is converted to $, which is escaped back to $$.
+func ExpandNinjaEscaped(s string, mapping func(string) (string, bool, error)) (string, error) {
+ return expand(s, true, mapping)
+}
+
// Expand substitutes $() variables in a string
-// $(var) is passed to Expander(var)
-// $$ is converted to $
+// $(var) is passed to mapping(var), which should return the expanded value and an error.
+// $$ is converted to $.
func Expand(s string, mapping func(string) (string, error)) (string, error) {
+ return expand(s, false, func(s string) (string, bool, error) {
+ s, err := mapping(s)
+ return s, false, err
+ })
+}
+
+func expand(s string, ninjaEscape bool, mapping func(string) (string, bool, error)) (string, error) {
// based on os.Expand
buf := make([]byte, 0, 2*len(s))
i := 0
@@ -33,10 +51,13 @@
return "", fmt.Errorf("expected character after '$'")
}
buf = append(buf, s[i:j]...)
- value, w, err := getMapping(s[j+1:], mapping)
+ value, ninjaVariable, w, err := getMapping(s[j+1:], mapping)
if err != nil {
return "", err
}
+ if !ninjaVariable && ninjaEscape {
+ value = proptools.NinjaEscape(value)
+ }
buf = append(buf, value...)
j += w
i = j + 1
@@ -45,26 +66,26 @@
return string(buf) + s[i:], nil
}
-func getMapping(s string, mapping func(string) (string, error)) (string, int, error) {
+func getMapping(s string, mapping func(string) (string, bool, error)) (string, bool, int, error) {
switch s[0] {
case '(':
// Scan to closing brace
for i := 1; i < len(s); i++ {
if s[i] == ')' {
- ret, err := mapping(strings.TrimSpace(s[1:i]))
- return ret, i + 1, err
+ ret, ninjaVariable, err := mapping(strings.TrimSpace(s[1:i]))
+ return ret, ninjaVariable, i + 1, err
}
}
- return "", len(s), fmt.Errorf("missing )")
+ return "", false, len(s), fmt.Errorf("missing )")
case '$':
- return "$$", 1, nil
+ return "$", false, 1, nil
default:
i := strings.IndexFunc(s, unicode.IsSpace)
if i == 0 {
- return "", 0, fmt.Errorf("unexpected character '%c' after '$'", s[0])
+ return "", false, 0, fmt.Errorf("unexpected character '%c' after '$'", s[0])
} else if i == -1 {
i = len(s)
}
- return "", 0, fmt.Errorf("expected '(' after '$', did you mean $(%s)?", s[:i])
+ return "", false, 0, fmt.Errorf("expected '(' after '$', did you mean $(%s)?", s[:i])
}
}
diff --git a/android/expand_test.go b/android/expand_test.go
index 128de8a..12179ed 100644
--- a/android/expand_test.go
+++ b/android/expand_test.go
@@ -20,88 +20,111 @@
)
var vars = map[string]string{
- "var1": "abc",
- "var2": "",
- "var3": "def",
- "💩": "😃",
+ "var1": "abc",
+ "var2": "",
+ "var3": "def",
+ "💩": "😃",
+ "escape": "${in}",
}
-func expander(s string) (string, error) {
+func expander(s string) (string, bool, error) {
if val, ok := vars[s]; ok {
- return val, nil
+ return val, s == "escape", nil
} else {
- return "", fmt.Errorf("unknown variable %q", s)
+ return "", false, fmt.Errorf("unknown variable %q", s)
}
}
var expandTestCases = []struct {
- in string
- out string
- err bool
+ in string
+ out string
+ out_escaped string
+ err bool
}{
{
- in: "$(var1)",
- out: "abc",
+ in: "$(var1)",
+ out: "abc",
+ out_escaped: "abc",
},
{
- in: "$( var1 )",
- out: "abc",
+ in: "$( var1 )",
+ out: "abc",
+ out_escaped: "abc",
},
{
- in: "def$(var1)",
- out: "defabc",
+ in: "def$(var1)",
+ out: "defabc",
+ out_escaped: "defabc",
},
{
- in: "$(var1)def",
- out: "abcdef",
+ in: "$(var1)def",
+ out: "abcdef",
+ out_escaped: "abcdef",
},
{
- in: "def$(var1)def",
- out: "defabcdef",
+ in: "def$(var1)def",
+ out: "defabcdef",
+ out_escaped: "defabcdef",
},
{
- in: "$(var2)",
- out: "",
+ in: "$(var2)",
+ out: "",
+ out_escaped: "",
},
{
- in: "def$(var2)",
- out: "def",
+ in: "def$(var2)",
+ out: "def",
+ out_escaped: "def",
},
{
- in: "$(var2)def",
- out: "def",
+ in: "$(var2)def",
+ out: "def",
+ out_escaped: "def",
},
{
- in: "def$(var2)def",
- out: "defdef",
+ in: "def$(var2)def",
+ out: "defdef",
+ out_escaped: "defdef",
},
{
- in: "$(var1)$(var3)",
- out: "abcdef",
+ in: "$(var1)$(var3)",
+ out: "abcdef",
+ out_escaped: "abcdef",
},
{
- in: "$(var1)g$(var3)",
- out: "abcgdef",
+ in: "$(var1)g$(var3)",
+ out: "abcgdef",
+ out_escaped: "abcgdef",
},
{
- in: "$$",
- out: "$$",
+ in: "$$",
+ out: "$",
+ out_escaped: "$$",
},
{
- in: "$$(var1)",
- out: "$$(var1)",
+ in: "$$(var1)",
+ out: "$(var1)",
+ out_escaped: "$$(var1)",
},
{
- in: "$$$(var1)",
- out: "$$abc",
+ in: "$$$(var1)",
+ out: "$abc",
+ out_escaped: "$$abc",
},
{
- in: "$(var1)$$",
- out: "abc$$",
+ in: "$(var1)$$",
+ out: "abc$",
+ out_escaped: "abc$$",
},
{
- in: "$(💩)",
- out: "😃",
+ in: "$(💩)",
+ out: "😃",
+ out_escaped: "😃",
+ },
+ {
+ in: "$$a$(escape)$$b",
+ out: "$a${in}$b",
+ out_escaped: "$$a${in}$$b",
},
// Errors
@@ -141,7 +164,10 @@
func TestExpand(t *testing.T) {
for _, test := range expandTestCases {
- got, err := Expand(test.in, expander)
+ got, err := Expand(test.in, func(s string) (string, error) {
+ s, _, err := expander(s)
+ return s, err
+ })
if err != nil && !test.err {
t.Errorf("%q: unexpected error %s", test.in, err.Error())
} else if err == nil && test.err {
@@ -151,3 +177,16 @@
}
}
}
+
+func TestExpandNinjaEscaped(t *testing.T) {
+ for _, test := range expandTestCases {
+ got, err := ExpandNinjaEscaped(test.in, expander)
+ if err != nil && !test.err {
+ t.Errorf("%q: unexpected error %s", test.in, err.Error())
+ } else if err == nil && test.err {
+ t.Errorf("%q: expected error, got %q", test.in, got)
+ } else if !test.err && got != test.out_escaped {
+ t.Errorf("%q: expected %q, got %q", test.in, test.out, got)
+ }
+ }
+}
diff --git a/android/hooks.go b/android/hooks.go
index 2d2f797..5810996 100644
--- a/android/hooks.go
+++ b/android/hooks.go
@@ -129,6 +129,8 @@
func LoadHookMutator(ctx TopDownMutatorContext) {
if m, ok := ctx.Module().(Module); ok {
+ m.base().commonProperties.DebugName = ctx.ModuleName()
+
// Cast through *topDownMutatorContext because AppendProperties is implemented
// on *topDownMutatorContext but not exposed through TopDownMutatorContext
var loadHookCtx LoadHookContext = ctx.(*topDownMutatorContext)
diff --git a/android/module.go b/android/module.go
index 87e2ca7..adb9454 100644
--- a/android/module.go
+++ b/android/module.go
@@ -126,7 +126,7 @@
DeviceSpecific() bool
SocSpecific() bool
ProductSpecific() bool
- ProductServicesSpecific() bool
+ SystemExtSpecific() bool
AConfig() Config
DeviceConfig() DeviceConfig
}
@@ -202,6 +202,58 @@
BuildParamsForTests() []BuildParams
RuleParamsForTests() map[blueprint.Rule]blueprint.RuleParams
VariablesForTests() map[string]string
+
+ // String returns a string that includes the module name and variants for printing during debugging.
+ String() string
+
+ // Get the qualified module id for this module.
+ qualifiedModuleId(ctx BaseModuleContext) qualifiedModuleName
+
+ // Get information about the properties that can contain visibility rules.
+ visibilityProperties() []visibilityProperty
+}
+
+// Qualified id for a module
+type qualifiedModuleName struct {
+ // The package (i.e. directory) in which the module is defined, without trailing /
+ pkg string
+
+ // The name of the module, empty string if package.
+ name string
+}
+
+func (q qualifiedModuleName) String() string {
+ if q.name == "" {
+ return "//" + q.pkg
+ }
+ return "//" + q.pkg + ":" + q.name
+}
+
+func (q qualifiedModuleName) isRootPackage() bool {
+ return q.pkg == "" && q.name == ""
+}
+
+// Get the id for the package containing this module.
+func (q qualifiedModuleName) getContainingPackageId() qualifiedModuleName {
+ pkg := q.pkg
+ if q.name == "" {
+ if pkg == "" {
+ panic(fmt.Errorf("Cannot get containing package id of root package"))
+ }
+
+ index := strings.LastIndex(pkg, "/")
+ if index == -1 {
+ pkg = ""
+ } else {
+ pkg = pkg[:index]
+ }
+ }
+ return newPackageId(pkg)
+}
+
+func newPackageId(pkg string) qualifiedModuleName {
+ // A qualified id for a package module has no name.
+ return qualifiedModuleName{pkg: pkg, name: ""}
}
type nameProperties struct {
@@ -236,6 +288,20 @@
// //packages/apps/Settings:__subpackages__.
// ["//visibility:legacy_public"]: The default visibility, behaves as //visibility:public
// for now. It is an error if it is used in a module.
+ //
+ // If a module does not specify the `visibility` property then it uses the
+ // `default_visibility` property of the `package` module in the module's package.
+ //
+ // If a module does not specify the `visibility` property then it uses the
+ // `default_visibility` property of the `package` module in the module's package.
+ //
+ // If the `default_visibility` property is not set for the module's package then
+ // it will use the `default_visibility` of its closest ancestor package for which
+ // a `default_visibility` property is specified.
+ //
+ // If no `default_visibility` property can be found then the module uses the
+ // global default of `//visibility:legacy_public`.
+ //
// See https://android.googlesource.com/platform/build/soong/+/master/README.md#visibility for
// more details.
Visibility []string
@@ -284,11 +350,15 @@
// /system/product if product partition does not exist).
Product_specific *bool
- // whether this module provides services owned by the OS provider to the core platform. When set
- // to true, it is installed into /product_services (or /system/product_services if
- // product_services partition does not exist).
+ // TODO(b/135957588) Product_services_specific will be removed once we clear all Android.bp
+ // files that have 'product_services_specific: true'. This will be converted to
+ // Product_speicific as a workaround.
Product_services_specific *bool
+ // whether this module extends system. When set to true, it is installed into /system_ext
+ // (or /system/system_ext if system_ext partition does not exist).
+ System_ext_specific *bool
+
// Whether this module is installed to recovery partition
Recovery *bool
@@ -345,6 +415,11 @@
NamespaceExportedToMake bool `blueprint:"mutated"`
MissingDeps []string `blueprint:"mutated"`
+
+ // Name and variant strings stored by mutators to enable Module.String()
+ DebugName string `blueprint:"mutated"`
+ DebugMutators []string `blueprint:"mutated"`
+ DebugVariations []string `blueprint:"mutated"`
}
type hostAndDeviceProperties struct {
@@ -398,7 +473,7 @@
deviceSpecificModule
socSpecificModule
productSpecificModule
- productServicesSpecificModule
+ systemExtSpecificModule
)
func (k moduleKind) String() string {
@@ -411,8 +486,8 @@
return "soc-specific"
case productSpecificModule:
return "product-specific"
- case productServicesSpecificModule:
- return "productservices-specific"
+ case systemExtSpecificModule:
+ return "systemext-specific"
default:
panic(fmt.Errorf("unknown module kind %d", k))
}
@@ -562,6 +637,23 @@
return String(m.nameProperties.Name)
}
+// String returns a string that includes the module name and variants for printing during debugging.
+func (m *ModuleBase) String() string {
+ sb := strings.Builder{}
+ sb.WriteString(m.commonProperties.DebugName)
+ sb.WriteString("{")
+ for i := range m.commonProperties.DebugMutators {
+ if i != 0 {
+ sb.WriteString(",")
+ }
+ sb.WriteString(m.commonProperties.DebugMutators[i])
+ sb.WriteString(":")
+ sb.WriteString(m.commonProperties.DebugVariations[i])
+ }
+ sb.WriteString("}")
+ return sb.String()
+}
+
// BaseModuleName returns the name of the module as specified in the blueprints file.
func (m *ModuleBase) BaseModuleName() string {
return String(m.nameProperties.Name)
@@ -571,6 +663,18 @@
return m
}
+func (m *ModuleBase) qualifiedModuleId(ctx BaseModuleContext) qualifiedModuleName {
+ return qualifiedModuleName{pkg: ctx.ModuleDir(), name: ctx.ModuleName()}
+}
+
+func (m *ModuleBase) visibilityProperties() []visibilityProperty {
+ return []visibilityProperty{
+ newVisibilityProperty("visibility", func() []string {
+ return m.base().commonProperties.Visibility
+ }),
+ }
+}
+
func (m *ModuleBase) SetTarget(target Target, multiTargets []Target, primary bool) {
m.commonProperties.CompileTarget = target
m.commonProperties.CompileMultiTargets = multiTargets
@@ -638,7 +742,7 @@
}
func (m *ModuleBase) Platform() bool {
- return !m.DeviceSpecific() && !m.SocSpecific() && !m.ProductSpecific() && !m.ProductServicesSpecific()
+ return !m.DeviceSpecific() && !m.SocSpecific() && !m.ProductSpecific() && !m.SystemExtSpecific()
}
func (m *ModuleBase) DeviceSpecific() bool {
@@ -653,8 +757,8 @@
return Bool(m.commonProperties.Product_specific)
}
-func (m *ModuleBase) ProductServicesSpecific() bool {
- return Bool(m.commonProperties.Product_services_specific)
+func (m *ModuleBase) SystemExtSpecific() bool {
+ return Bool(m.commonProperties.System_ext_specific)
}
func (m *ModuleBase) Enabled() bool {
@@ -775,7 +879,7 @@
var socSpecific = Bool(m.commonProperties.Vendor) || Bool(m.commonProperties.Proprietary) || Bool(m.commonProperties.Soc_specific)
var deviceSpecific = Bool(m.commonProperties.Device_specific)
var productSpecific = Bool(m.commonProperties.Product_specific)
- var productServicesSpecific = Bool(m.commonProperties.Product_services_specific)
+ var systemExtSpecific = Bool(m.commonProperties.System_ext_specific)
msg := "conflicting value set here"
if socSpecific && deviceSpecific {
@@ -791,16 +895,16 @@
}
}
- if productSpecific && productServicesSpecific {
- ctx.PropertyErrorf("product_specific", "a module cannot be specific to product and product_services at the same time.")
- ctx.PropertyErrorf("product_services_specific", msg)
+ if productSpecific && systemExtSpecific {
+ ctx.PropertyErrorf("product_specific", "a module cannot be specific to product and system_ext at the same time.")
+ ctx.PropertyErrorf("system_ext_specific", msg)
}
- if (socSpecific || deviceSpecific) && (productSpecific || productServicesSpecific) {
+ if (socSpecific || deviceSpecific) && (productSpecific || systemExtSpecific) {
if productSpecific {
ctx.PropertyErrorf("product_specific", "a module cannot be specific to SoC or device and product at the same time.")
} else {
- ctx.PropertyErrorf("product_services_specific", "a module cannot be specific to SoC or device and product_services at the same time.")
+ ctx.PropertyErrorf("system_ext_specific", "a module cannot be specific to SoC or device and system_ext at the same time.")
}
if deviceSpecific {
ctx.PropertyErrorf("device_specific", msg)
@@ -819,8 +923,8 @@
if productSpecific {
return productSpecificModule
- } else if productServicesSpecific {
- return productServicesSpecificModule
+ } else if systemExtSpecific {
+ return systemExtSpecificModule
} else if deviceSpecific {
return deviceSpecificModule
} else if socSpecific {
@@ -901,14 +1005,6 @@
}
if m.Enabled() {
- m.module.GenerateAndroidBuildActions(ctx)
- if ctx.Failed() {
- return
- }
-
- m.installFiles = append(m.installFiles, ctx.installFiles...)
- m.checkbuildFiles = append(m.checkbuildFiles, ctx.checkbuildFiles...)
-
notice := proptools.StringDefault(m.commonProperties.Notice, "NOTICE")
if module := SrcIsModule(notice); module != "" {
m.noticeFile = ctx.ExpandOptionalSource(¬ice, "notice")
@@ -916,6 +1012,14 @@
noticePath := filepath.Join(ctx.ModuleDir(), notice)
m.noticeFile = ExistentPathForSource(ctx, noticePath)
}
+
+ m.module.GenerateAndroidBuildActions(ctx)
+ if ctx.Failed() {
+ return
+ }
+
+ m.installFiles = append(m.installFiles, ctx.installFiles...)
+ m.checkbuildFiles = append(m.checkbuildFiles, ctx.checkbuildFiles...)
} else if ctx.Config().AllowMissingDependencies() {
// If the module is not enabled it will not create any build rules, nothing will call
// ctx.GetMissingDependencies(), and blueprint will consider the missing dependencies to be unhandled
@@ -1333,18 +1437,18 @@
return b.kind == productSpecificModule
}
-func (b *baseModuleContext) ProductServicesSpecific() bool {
- return b.kind == productServicesSpecificModule
+func (b *baseModuleContext) SystemExtSpecific() bool {
+ return b.kind == systemExtSpecificModule
}
// Makes this module a platform module, i.e. not specific to soc, device,
-// product, or product_services.
+// product, or system_ext.
func (m *ModuleBase) MakeAsPlatform() {
m.commonProperties.Vendor = boolPtr(false)
m.commonProperties.Proprietary = boolPtr(false)
m.commonProperties.Soc_specific = boolPtr(false)
m.commonProperties.Product_specific = boolPtr(false)
- m.commonProperties.Product_services_specific = boolPtr(false)
+ m.commonProperties.System_ext_specific = boolPtr(false)
}
func (m *ModuleBase) EnableNativeBridgeSupportByDefault() {
diff --git a/android/mutator.go b/android/mutator.go
index 081c2b2..b799432 100644
--- a/android/mutator.go
+++ b/android/mutator.go
@@ -75,6 +75,8 @@
var preArch = []RegisterMutatorFunc{
registerLoadHookMutator,
RegisterNamespaceMutator,
+ // Rename package module types.
+ registerPackageRenamer,
RegisterPrebuiltsPreArchMutators,
registerVisibilityRuleChecker,
RegisterDefaultsPreArchMutators,
@@ -115,6 +117,8 @@
type TopDownMutatorContext interface {
BaseModuleContext
+ MutatorName() string
+
Rename(name string)
CreateModule(blueprint.ModuleFactory, ...interface{})
@@ -130,6 +134,8 @@
type BottomUpMutatorContext interface {
BaseModuleContext
+ MutatorName() string
+
Rename(name string)
AddDependency(module blueprint.Module, tag blueprint.DependencyTag, name ...string)
@@ -227,16 +233,26 @@
// non-overridden method has to be forwarded. There are fewer non-overridden methods, so use the latter. The following
// methods forward to the identical blueprint versions for topDownMutatorContext and bottomUpMutatorContext.
+func (t *topDownMutatorContext) MutatorName() string {
+ return t.bp.MutatorName()
+}
+
func (t *topDownMutatorContext) Rename(name string) {
t.bp.Rename(name)
+ t.Module().base().commonProperties.DebugName = name
}
func (t *topDownMutatorContext) CreateModule(factory blueprint.ModuleFactory, props ...interface{}) {
t.bp.CreateModule(factory, props...)
}
+func (b *bottomUpMutatorContext) MutatorName() string {
+ return b.bp.MutatorName()
+}
+
func (b *bottomUpMutatorContext) Rename(name string) {
b.bp.Rename(name)
+ b.Module().base().commonProperties.DebugName = name
}
func (b *bottomUpMutatorContext) AddDependency(module blueprint.Module, tag blueprint.DependencyTag, name ...string) {
@@ -248,11 +264,27 @@
}
func (b *bottomUpMutatorContext) CreateVariations(variations ...string) []blueprint.Module {
- return b.bp.CreateVariations(variations...)
+ modules := b.bp.CreateVariations(variations...)
+
+ for i := range variations {
+ base := modules[i].(Module).base()
+ base.commonProperties.DebugMutators = append(base.commonProperties.DebugMutators, b.MutatorName())
+ base.commonProperties.DebugVariations = append(base.commonProperties.DebugVariations, variations[i])
+ }
+
+ return modules
}
func (b *bottomUpMutatorContext) CreateLocalVariations(variations ...string) []blueprint.Module {
- return b.bp.CreateLocalVariations(variations...)
+ modules := b.bp.CreateLocalVariations(variations...)
+
+ for i := range variations {
+ base := modules[i].(Module).base()
+ base.commonProperties.DebugMutators = append(base.commonProperties.DebugMutators, b.MutatorName())
+ base.commonProperties.DebugVariations = append(base.commonProperties.DebugVariations, variations[i])
+ }
+
+ return modules
}
func (b *bottomUpMutatorContext) SetDependencyVariation(variation string) {
diff --git a/android/mutator_test.go b/android/mutator_test.go
index 4cef400..0b23434 100644
--- a/android/mutator_test.go
+++ b/android/mutator_test.go
@@ -15,8 +15,6 @@
package android
import (
- "io/ioutil"
- "os"
"reflect"
"testing"
@@ -26,6 +24,8 @@
type mutatorTestModule struct {
ModuleBase
props struct {
+ Deps_missing_deps []string
+ Mutator_missing_deps []string
}
missingDeps []string
@@ -48,20 +48,14 @@
}
func (m *mutatorTestModule) DepsMutator(ctx BottomUpMutatorContext) {
- ctx.AddDependency(ctx.Module(), nil, "regular_missing_dep")
+ ctx.AddDependency(ctx.Module(), nil, m.props.Deps_missing_deps...)
}
func addMissingDependenciesMutator(ctx TopDownMutatorContext) {
- ctx.AddMissingDependencies([]string{"added_missing_dep"})
+ ctx.AddMissingDependencies(ctx.Module().(*mutatorTestModule).props.Mutator_missing_deps)
}
func TestMutatorAddMissingDependencies(t *testing.T) {
- buildDir, err := ioutil.TempDir("", "soong_mutator_test")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(buildDir)
-
config := TestConfig(buildDir, nil)
config.TestProductVariables.Allow_missing_dependencies = proptools.BoolPtr(true)
@@ -76,6 +70,8 @@
bp := `
test {
name: "foo",
+ deps_missing_deps: ["regular_missing_dep"],
+ mutator_missing_deps: ["added_missing_dep"],
}
`
@@ -97,3 +93,107 @@
t.Errorf("want foo missing deps %q, got %q", w, g)
}
}
+
+func TestModuleString(t *testing.T) {
+ ctx := NewTestContext()
+
+ var moduleStrings []string
+
+ ctx.PreArchMutators(func(ctx RegisterMutatorsContext) {
+ ctx.BottomUp("pre_arch", func(ctx BottomUpMutatorContext) {
+ moduleStrings = append(moduleStrings, ctx.Module().String())
+ ctx.CreateVariations("a", "b")
+ })
+ ctx.TopDown("rename_top_down", func(ctx TopDownMutatorContext) {
+ moduleStrings = append(moduleStrings, ctx.Module().String())
+ ctx.Rename(ctx.Module().base().Name() + "_renamed1")
+ })
+ })
+
+ ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
+ ctx.BottomUp("pre_deps", func(ctx BottomUpMutatorContext) {
+ moduleStrings = append(moduleStrings, ctx.Module().String())
+ ctx.CreateVariations("c", "d")
+ })
+ })
+
+ ctx.PostDepsMutators(func(ctx RegisterMutatorsContext) {
+ ctx.BottomUp("post_deps", func(ctx BottomUpMutatorContext) {
+ moduleStrings = append(moduleStrings, ctx.Module().String())
+ ctx.CreateLocalVariations("e", "f")
+ })
+ ctx.BottomUp("rename_bottom_up", func(ctx BottomUpMutatorContext) {
+ moduleStrings = append(moduleStrings, ctx.Module().String())
+ ctx.Rename(ctx.Module().base().Name() + "_renamed2")
+ })
+ ctx.BottomUp("final", func(ctx BottomUpMutatorContext) {
+ moduleStrings = append(moduleStrings, ctx.Module().String())
+ })
+ })
+
+ ctx.RegisterModuleType("test", ModuleFactoryAdaptor(mutatorTestModuleFactory))
+
+ bp := `
+ test {
+ name: "foo",
+ }
+ `
+
+ mockFS := map[string][]byte{
+ "Android.bp": []byte(bp),
+ }
+
+ ctx.MockFileSystem(mockFS)
+
+ ctx.Register()
+
+ config := TestConfig(buildDir, nil)
+
+ _, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+ FailIfErrored(t, errs)
+ _, errs = ctx.PrepareBuildActions(config)
+ FailIfErrored(t, errs)
+
+ want := []string{
+ // Initial name.
+ "foo{}",
+
+ // After pre_arch (reversed because rename_top_down is TopDown so it visits in reverse order).
+ "foo{pre_arch:b}",
+ "foo{pre_arch:a}",
+
+ // After rename_top_down.
+ "foo_renamed1{pre_arch:a}",
+ "foo_renamed1{pre_arch:b}",
+
+ // After pre_deps.
+ "foo_renamed1{pre_arch:a,pre_deps:c}",
+ "foo_renamed1{pre_arch:a,pre_deps:d}",
+ "foo_renamed1{pre_arch:b,pre_deps:c}",
+ "foo_renamed1{pre_arch:b,pre_deps:d}",
+
+ // After post_deps.
+ "foo_renamed1{pre_arch:a,pre_deps:c,post_deps:e}",
+ "foo_renamed1{pre_arch:a,pre_deps:c,post_deps:f}",
+ "foo_renamed1{pre_arch:a,pre_deps:d,post_deps:e}",
+ "foo_renamed1{pre_arch:a,pre_deps:d,post_deps:f}",
+ "foo_renamed1{pre_arch:b,pre_deps:c,post_deps:e}",
+ "foo_renamed1{pre_arch:b,pre_deps:c,post_deps:f}",
+ "foo_renamed1{pre_arch:b,pre_deps:d,post_deps:e}",
+ "foo_renamed1{pre_arch:b,pre_deps:d,post_deps:f}",
+
+ // After rename_bottom_up.
+ "foo_renamed2{pre_arch:a,pre_deps:c,post_deps:e}",
+ "foo_renamed2{pre_arch:a,pre_deps:c,post_deps:f}",
+ "foo_renamed2{pre_arch:a,pre_deps:d,post_deps:e}",
+ "foo_renamed2{pre_arch:a,pre_deps:d,post_deps:f}",
+ "foo_renamed2{pre_arch:b,pre_deps:c,post_deps:e}",
+ "foo_renamed2{pre_arch:b,pre_deps:c,post_deps:f}",
+ "foo_renamed2{pre_arch:b,pre_deps:d,post_deps:e}",
+ "foo_renamed2{pre_arch:b,pre_deps:d,post_deps:f}",
+ }
+
+ if !reflect.DeepEqual(moduleStrings, want) {
+ t.Errorf("want module String() values:\n%q\ngot:\n%q", want, moduleStrings)
+ }
+}
diff --git a/android/neverallow.go b/android/neverallow.go
index f35d1fe..1893e8b 100644
--- a/android/neverallow.go
+++ b/android/neverallow.go
@@ -31,61 +31,64 @@
// work regardless of these restrictions.
//
// A module is disallowed if all of the following are true:
-// - it is in one of the "in" paths
-// - it is not in one of the "notIn" paths
-// - it has all "with" properties matched
+// - it is in one of the "In" paths
+// - it is not in one of the "NotIn" paths
+// - it has all "With" properties matched
// - - values are matched in their entirety
// - - nil is interpreted as an empty string
// - - nested properties are separated with a '.'
// - - if the property is a list, any of the values in the list being matches
// counts as a match
-// - it has none of the "without" properties matched (same rules as above)
+// - it has none of the "Without" properties matched (same rules as above)
func registerNeverallowMutator(ctx RegisterMutatorsContext) {
ctx.BottomUp("neverallow", neverallowMutator).Parallel()
}
-var neverallows = createNeverAllows()
+var neverallows = []Rule{}
-func createNeverAllows() []*rule {
- rules := []*rule{}
- rules = append(rules, createTrebleRules()...)
- rules = append(rules, createLibcoreRules()...)
- rules = append(rules, createJavaDeviceForHostRules()...)
- return rules
+func init() {
+ AddNeverAllowRules(createTrebleRules()...)
+ AddNeverAllowRules(createLibcoreRules()...)
+ AddNeverAllowRules(createJavaDeviceForHostRules()...)
}
-func createTrebleRules() []*rule {
- return []*rule{
- neverallow().
- in("vendor", "device").
- with("vndk.enabled", "true").
- without("vendor", "true").
- because("the VNDK can never contain a library that is device dependent."),
- neverallow().
- with("vndk.enabled", "true").
- without("vendor", "true").
- without("owner", "").
- because("a VNDK module can never have an owner."),
+// Add a NeverAllow rule to the set of rules to apply.
+func AddNeverAllowRules(rules ...Rule) {
+ neverallows = append(neverallows, rules...)
+}
+
+func createTrebleRules() []Rule {
+ return []Rule{
+ NeverAllow().
+ In("vendor", "device").
+ With("vndk.enabled", "true").
+ Without("vendor", "true").
+ Because("the VNDK can never contain a library that is device dependent."),
+ NeverAllow().
+ With("vndk.enabled", "true").
+ Without("vendor", "true").
+ Without("owner", "").
+ Because("a VNDK module can never have an owner."),
// TODO(b/67974785): always enforce the manifest
- neverallow().
- without("name", "libhidltransport").
- with("product_variables.enforce_vintf_manifest.cflags", "*").
- because("manifest enforcement should be independent of ."),
+ NeverAllow().
+ Without("name", "libhidltransport").
+ With("product_variables.enforce_vintf_manifest.cflags", "*").
+ Because("manifest enforcement should be independent of ."),
// TODO(b/67975799): vendor code should always use /vendor/bin/sh
- neverallow().
- without("name", "libc_bionic_ndk").
- with("product_variables.treble_linker_namespaces.cflags", "*").
- because("nothing should care if linker namespaces are enabled or not"),
+ NeverAllow().
+ Without("name", "libc_bionic_ndk").
+ With("product_variables.treble_linker_namespaces.cflags", "*").
+ Because("nothing should care if linker namespaces are enabled or not"),
// Example:
- // *neverallow().with("Srcs", "main.cpp"))
+ // *NeverAllow().with("Srcs", "main.cpp"))
}
}
-func createLibcoreRules() []*rule {
+func createLibcoreRules() []Rule {
var coreLibraryProjects = []string{
"libcore",
"external/apache-harmony",
@@ -102,27 +105,27 @@
// Core library constraints. The sdk_version: "none" can only be used in core library projects.
// Access to core library targets is restricted using visibility rules.
- rules := []*rule{
- neverallow().
- notIn(coreLibraryProjects...).
- with("sdk_version", "none"),
+ rules := []Rule{
+ NeverAllow().
+ NotIn(coreLibraryProjects...).
+ With("sdk_version", "none"),
}
return rules
}
-func createJavaDeviceForHostRules() []*rule {
+func createJavaDeviceForHostRules() []Rule {
javaDeviceForHostProjectsWhitelist := []string{
"external/guava",
"external/robolectric-shadows",
"framework/layoutlib",
}
- return []*rule{
- neverallow().
- notIn(javaDeviceForHostProjectsWhitelist...).
- moduleType("java_device_for_host", "java_host_for_device").
- because("java_device_for_host can only be used in whitelisted projects"),
+ return []Rule{
+ NeverAllow().
+ NotIn(javaDeviceForHostProjectsWhitelist...).
+ ModuleType("java_device_for_host", "java_host_for_device").
+ Because("java_device_for_host can only be used in whitelisted projects"),
}
}
@@ -135,7 +138,8 @@
dir := ctx.ModuleDir() + "/"
properties := m.GetProperties()
- for _, n := range neverallows {
+ for _, r := range neverallows {
+ n := r.(*rule)
if !n.appliesToPath(dir) {
continue
}
@@ -157,6 +161,23 @@
value string // e.x.: true
}
+// A NeverAllow rule.
+type Rule interface {
+ In(path ...string) Rule
+
+ NotIn(path ...string) Rule
+
+ ModuleType(types ...string) Rule
+
+ NotModuleType(types ...string) Rule
+
+ With(properties, value string) Rule
+
+ Without(properties, value string) Rule
+
+ Because(reason string) Rule
+}
+
type rule struct {
// User string for why this is a thing.
reason string
@@ -171,31 +192,32 @@
unlessProps []ruleProperty
}
-func neverallow() *rule {
+// Create a new NeverAllow rule.
+func NeverAllow() Rule {
return &rule{}
}
-func (r *rule) in(path ...string) *rule {
+func (r *rule) In(path ...string) Rule {
r.paths = append(r.paths, cleanPaths(path)...)
return r
}
-func (r *rule) notIn(path ...string) *rule {
+func (r *rule) NotIn(path ...string) Rule {
r.unlessPaths = append(r.unlessPaths, cleanPaths(path)...)
return r
}
-func (r *rule) moduleType(types ...string) *rule {
+func (r *rule) ModuleType(types ...string) Rule {
r.moduleTypes = append(r.moduleTypes, types...)
return r
}
-func (r *rule) notModuleType(types ...string) *rule {
+func (r *rule) NotModuleType(types ...string) Rule {
r.unlessModuleTypes = append(r.unlessModuleTypes, types...)
return r
}
-func (r *rule) with(properties, value string) *rule {
+func (r *rule) With(properties, value string) Rule {
r.props = append(r.props, ruleProperty{
fields: fieldNamesForProperties(properties),
value: value,
@@ -203,7 +225,7 @@
return r
}
-func (r *rule) without(properties, value string) *rule {
+func (r *rule) Without(properties, value string) Rule {
r.unlessProps = append(r.unlessProps, ruleProperty{
fields: fieldNamesForProperties(properties),
value: value,
@@ -211,7 +233,7 @@
return r
}
-func (r *rule) because(reason string) *rule {
+func (r *rule) Because(reason string) Rule {
r.reason = reason
return r
}
diff --git a/android/notices.go b/android/notices.go
new file mode 100644
index 0000000..7b61d65
--- /dev/null
+++ b/android/notices.go
@@ -0,0 +1,102 @@
+// Copyright 2019 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import (
+ "path/filepath"
+
+ "github.com/google/blueprint"
+)
+
+func init() {
+ pctx.SourcePathVariable("merge_notices", "build/soong/scripts/mergenotice.py")
+ pctx.SourcePathVariable("generate_notice", "build/make/tools/generate-notice-files.py")
+
+ pctx.HostBinToolVariable("minigzip", "minigzip")
+}
+
+type NoticeOutputs struct {
+ Merged OptionalPath
+ TxtOutput OptionalPath
+ HtmlOutput OptionalPath
+ HtmlGzOutput OptionalPath
+}
+
+var (
+ mergeNoticesRule = pctx.AndroidStaticRule("mergeNoticesRule", blueprint.RuleParams{
+ Command: `${merge_notices} --output $out $in`,
+ CommandDeps: []string{"${merge_notices}"},
+ Description: "merge notice files into $out",
+ })
+
+ generateNoticeRule = pctx.AndroidStaticRule("generateNoticeRule", blueprint.RuleParams{
+ Command: `rm -rf $$(dirname $txtOut) $$(dirname $htmlOut) $$(dirname $out) && ` +
+ `mkdir -p $$(dirname $txtOut) $$(dirname $htmlOut) $$(dirname $out) && ` +
+ `${generate_notice} --text-output $txtOut --html-output $htmlOut -t "$title" -s $inputDir && ` +
+ `${minigzip} -c $htmlOut > $out`,
+ CommandDeps: []string{"${generate_notice}", "${minigzip}"},
+ Description: "produce notice file $out",
+ }, "txtOut", "htmlOut", "title", "inputDir")
+)
+
+func MergeNotices(ctx ModuleContext, mergedNotice WritablePath, noticePaths []Path) {
+ ctx.Build(pctx, BuildParams{
+ Rule: mergeNoticesRule,
+ Description: "merge notices",
+ Inputs: noticePaths,
+ Output: mergedNotice,
+ })
+}
+
+func BuildNoticeOutput(ctx ModuleContext, installPath OutputPath, installFilename string,
+ noticePaths []Path) NoticeOutputs {
+ // Merge all NOTICE files into one.
+ // TODO(jungjw): We should just produce a well-formatted NOTICE.html file in a single pass.
+ //
+ // generate-notice-files.py, which processes the merged NOTICE file, has somewhat strict rules
+ // about input NOTICE file paths.
+ // 1. Their relative paths to the src root become their NOTICE index titles. We want to use
+ // on-device paths as titles, and so output the merged NOTICE file the corresponding location.
+ // 2. They must end with .txt extension. Otherwise, they're ignored.
+ noticeRelPath := InstallPathToOnDevicePath(ctx, installPath.Join(ctx, installFilename+".txt"))
+ mergedNotice := PathForModuleOut(ctx, filepath.Join("NOTICE_FILES/src", noticeRelPath))
+ MergeNotices(ctx, mergedNotice, noticePaths)
+
+ // Transform the merged NOTICE file into a gzipped HTML file.
+ txtOuptut := PathForModuleOut(ctx, "NOTICE_txt", "NOTICE.txt")
+ htmlOutput := PathForModuleOut(ctx, "NOTICE_html", "NOTICE.html")
+ htmlGzOutput := PathForModuleOut(ctx, "NOTICE", "NOTICE.html.gz")
+ title := "Notices for " + ctx.ModuleName()
+ ctx.Build(pctx, BuildParams{
+ Rule: generateNoticeRule,
+ Description: "generate notice output",
+ Input: mergedNotice,
+ Output: htmlGzOutput,
+ ImplicitOutputs: WritablePaths{txtOuptut, htmlOutput},
+ Args: map[string]string{
+ "txtOut": txtOuptut.String(),
+ "htmlOut": htmlOutput.String(),
+ "title": title,
+ "inputDir": PathForModuleOut(ctx, "NOTICE_FILES/src").String(),
+ },
+ })
+
+ return NoticeOutputs{
+ Merged: OptionalPathForPath(mergedNotice),
+ TxtOutput: OptionalPathForPath(txtOuptut),
+ HtmlOutput: OptionalPathForPath(htmlOutput),
+ HtmlGzOutput: OptionalPathForPath(htmlGzOutput),
+ }
+}
diff --git a/android/package.go b/android/package.go
new file mode 100644
index 0000000..03f6a1e
--- /dev/null
+++ b/android/package.go
@@ -0,0 +1,194 @@
+// Copyright 2019 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import (
+ "fmt"
+ "sync/atomic"
+
+ "github.com/google/blueprint"
+)
+
+func init() {
+ RegisterModuleType("package", PackageFactory)
+}
+
+// The information maintained about each package.
+type packageInfo struct {
+ // The module from which this information was populated. If `duplicated` = true then this is the
+ // module that has been renamed and must be used to report errors.
+ module *packageModule
+
+ // If true this indicates that there are two package statements in the same package which is not
+ // allowed and will cause the build to fail. This flag is set by packageRenamer and checked in
+ // packageErrorReporter
+ duplicated bool
+}
+
+type packageProperties struct {
+ Name string `blueprint:"mutated"`
+
+ // Specifies the default visibility for all modules defined in this package.
+ Default_visibility []string
+}
+
+type packageModule struct {
+ ModuleBase
+
+ properties packageProperties
+ packageInfo *packageInfo
+}
+
+func (p *packageModule) GenerateAndroidBuildActions(ModuleContext) {
+ // Nothing to do.
+}
+
+func (p *packageModule) GenerateBuildActions(ctx blueprint.ModuleContext) {
+ // Nothing to do.
+}
+
+func (p *packageModule) qualifiedModuleId(ctx BaseModuleContext) qualifiedModuleName {
+ // Override to create a package id.
+ return newPackageId(ctx.ModuleDir())
+}
+
+// Override to ensure that the default_visibility rules are checked by the visibility module during
+// its checking phase.
+func (p *packageModule) visibilityProperties() []visibilityProperty {
+ return []visibilityProperty{
+ newVisibilityProperty("default_visibility", func() []string {
+ return p.properties.Default_visibility
+ }),
+ }
+}
+
+func (p *packageModule) Name() string {
+ return p.properties.Name
+}
+
+func (p *packageModule) setName(name string) {
+ p.properties.Name = name
+}
+
+// Counter to ensure package modules are created with a unique name within whatever namespace they
+// belong.
+var packageCount uint32 = 0
+
+func PackageFactory() Module {
+ module := &packageModule{}
+
+ // Get a unique if for the package. Has to be done atomically as the creation of the modules are
+ // done in parallel.
+ id := atomic.AddUint32(&packageCount, 1)
+ name := fmt.Sprintf("soong_package_%d", id)
+
+ module.properties.Name = name
+
+ module.AddProperties(&module.properties)
+ return module
+}
+
+// Registers the function that renames the packages.
+func registerPackageRenamer(ctx RegisterMutatorsContext) {
+ ctx.BottomUp("packageRenamer", packageRenamer).Parallel()
+ ctx.BottomUp("packageErrorReporter", packageErrorReporter).Parallel()
+}
+
+// Renames the package to match the package directory.
+//
+// This also creates a PackageInfo object for each package and uses that to detect and remember
+// duplicates for later error reporting.
+func packageRenamer(ctx BottomUpMutatorContext) {
+ m, ok := ctx.Module().(*packageModule)
+ if !ok {
+ return
+ }
+
+ packageName := "//" + ctx.ModuleDir()
+
+ pi := newPackageInfo(ctx, packageName, m)
+ if pi.module != m {
+ // Remember that the package was duplicated but do not rename as that will cause an error to
+ // be logged with the generated name. Similarly, reporting the error here will use the generated
+ // name as renames are only processed after this phase.
+ pi.duplicated = true
+ } else {
+ // This is the first package module in this package so rename it to match the package name.
+ m.setName(packageName)
+ ctx.Rename(packageName)
+
+ // Store a package info reference in the module.
+ m.packageInfo = pi
+ }
+}
+
+// Logs any deferred errors.
+func packageErrorReporter(ctx BottomUpMutatorContext) {
+ m, ok := ctx.Module().(*packageModule)
+ if !ok {
+ return
+ }
+
+ packageDir := ctx.ModuleDir()
+ packageName := "//" + packageDir
+
+ // Get the PackageInfo for the package. Should have been populated in the packageRenamer phase.
+ pi := findPackageInfo(ctx, packageName)
+ if pi == nil {
+ ctx.ModuleErrorf("internal error, expected package info to be present for package '%s'",
+ packageName)
+ return
+ }
+
+ if pi.module != m {
+ // The package module has been duplicated but this is not the module that has been renamed so
+ // ignore it. An error will be logged for the renamed module which will ensure that the error
+ // message uses the correct name.
+ return
+ }
+
+ // Check to see whether there are duplicate package modules in the package.
+ if pi.duplicated {
+ ctx.ModuleErrorf("package {...} specified multiple times")
+ return
+ }
+}
+
+type defaultPackageInfoKey string
+
+func newPackageInfo(
+ ctx BaseModuleContext, packageName string, module *packageModule) *packageInfo {
+ key := NewCustomOnceKey(defaultPackageInfoKey(packageName))
+
+ return ctx.Config().Once(key, func() interface{} {
+ return &packageInfo{module: module}
+ }).(*packageInfo)
+}
+
+// Get the PackageInfo for the package name (starts with //, no trailing /), is nil if no package
+// module type was specified.
+func findPackageInfo(ctx BaseModuleContext, packageName string) *packageInfo {
+ key := NewCustomOnceKey(defaultPackageInfoKey(packageName))
+
+ pi := ctx.Config().Once(key, func() interface{} {
+ return nil
+ })
+
+ if pi == nil {
+ return nil
+ } else {
+ return pi.(*packageInfo)
+ }
+}
diff --git a/android/package_test.go b/android/package_test.go
new file mode 100644
index 0000000..e5b0556
--- /dev/null
+++ b/android/package_test.go
@@ -0,0 +1,103 @@
+package android
+
+import (
+ "testing"
+)
+
+var packageTests = []struct {
+ name string
+ fs map[string][]byte
+ expectedErrors []string
+}{
+ // Package default_visibility handling is tested in visibility_test.go
+ {
+ name: "package must not accept visibility and name properties",
+ fs: map[string][]byte{
+ "top/Blueprints": []byte(`
+ package {
+ name: "package",
+ visibility: ["//visibility:private"],
+ }`),
+ },
+ expectedErrors: []string{
+ `top/Blueprints:3:10: mutated field name cannot be set in a Blueprint file`,
+ `top/Blueprints:4:16: unrecognized property "visibility"`,
+ },
+ },
+ {
+ name: "multiple packages in separate directories",
+ fs: map[string][]byte{
+ "top/Blueprints": []byte(`
+ package {
+ }`),
+ "other/Blueprints": []byte(`
+ package {
+ }`),
+ "other/nested/Blueprints": []byte(`
+ package {
+ }`),
+ },
+ },
+ {
+ name: "package must not be specified more than once per package",
+ fs: map[string][]byte{
+ "top/Blueprints": []byte(`
+ package {
+ default_visibility: ["//visibility:private"],
+ }
+
+ package {
+ }`),
+ },
+ expectedErrors: []string{
+ `module "//top": package {...} specified multiple times`,
+ },
+ },
+}
+
+func TestPackage(t *testing.T) {
+ for _, test := range packageTests {
+ t.Run(test.name, func(t *testing.T) {
+ _, errs := testPackage(test.fs)
+
+ expectedErrors := test.expectedErrors
+ if expectedErrors == nil {
+ FailIfErrored(t, errs)
+ } else {
+ for _, expectedError := range expectedErrors {
+ FailIfNoMatchingErrors(t, expectedError, errs)
+ }
+ if len(errs) > len(expectedErrors) {
+ t.Errorf("additional errors found, expected %d, found %d", len(expectedErrors), len(errs))
+ for i, expectedError := range expectedErrors {
+ t.Errorf("expectedErrors[%d] = %s", i, expectedError)
+ }
+ for i, err := range errs {
+ t.Errorf("errs[%d] = %s", i, err)
+ }
+ }
+ }
+ })
+ }
+}
+
+func testPackage(fs map[string][]byte) (*TestContext, []error) {
+
+ // Create a new config per test as visibility information is stored in the config.
+ config := TestArchConfig(buildDir, nil)
+
+ ctx := NewTestArchContext()
+ ctx.RegisterModuleType("package", ModuleFactoryAdaptor(PackageFactory))
+ ctx.PreArchMutators(registerPackageRenamer)
+ ctx.Register()
+
+ ctx.MockFileSystem(fs)
+
+ _, errs := ctx.ParseBlueprintsFiles(".")
+ if len(errs) > 0 {
+ return ctx, errs
+ }
+
+ _, errs = ctx.PrepareBuildActions(config)
+ return ctx, errs
+}
diff --git a/android/path_properties.go b/android/path_properties.go
index af7af59..6b1cdb3 100644
--- a/android/path_properties.go
+++ b/android/path_properties.go
@@ -35,16 +35,17 @@
props := m.base().generalProperties
+ var pathProperties []string
for _, ps := range props {
- pathProperties := pathPropertiesForPropertyStruct(ctx, ps)
- pathProperties = FirstUniqueStrings(pathProperties)
+ pathProperties = append(pathProperties, pathPropertiesForPropertyStruct(ctx, ps)...)
+ }
- for _, s := range pathProperties {
- if m, t := SrcIsModuleWithTag(s); m != "" {
- ctx.AddDependency(ctx.Module(), sourceOrOutputDepTag(t), m)
- }
+ pathProperties = FirstUniqueStrings(pathProperties)
+
+ for _, s := range pathProperties {
+ if m, t := SrcIsModuleWithTag(s); m != "" {
+ ctx.AddDependency(ctx.Module(), sourceOrOutputDepTag(t), m)
}
-
}
}
diff --git a/android/path_properties_test.go b/android/path_properties_test.go
index fa187fa..59bfa6c 100644
--- a/android/path_properties_test.go
+++ b/android/path_properties_test.go
@@ -15,8 +15,6 @@
package android
import (
- "io/ioutil"
- "os"
"reflect"
"testing"
)
@@ -30,12 +28,17 @@
Qux string
}
+ // A second property struct with a duplicate property name
+ props2 struct {
+ Foo string `android:"path"`
+ }
+
sourceDeps []string
}
func pathDepsMutatorTestModuleFactory() Module {
module := &pathDepsMutatorTestModule{}
- module.AddProperties(&module.props)
+ module.AddProperties(&module.props, &module.props2)
InitAndroidArchModule(module, DeviceSupported, MultilibBoth)
return module
}
@@ -46,6 +49,13 @@
p.sourceDeps = append(p.sourceDeps, ctx.OtherModuleName(dep))
}
})
+
+ if p.props.Foo != "" {
+ // Make sure there is only one dependency on a module listed in a property present in multiple property structs
+ if ctx.GetDirectDepWithTag(SrcIsModule(p.props.Foo), sourceOrOutputDepTag("")) == nil {
+ ctx.ModuleErrorf("GetDirectDepWithTag failed")
+ }
+ }
}
func TestPathDepsMutator(t *testing.T) {
@@ -85,12 +95,6 @@
},
}
- buildDir, err := ioutil.TempDir("", "soong_path_properties_test")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(buildDir)
-
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
config := TestArchConfig(buildDir, nil)
diff --git a/android/paths.go b/android/paths.go
index 20b8b82..0ea4447 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -1145,8 +1145,8 @@
partition = ctx.DeviceConfig().OdmPath()
} else if ctx.ProductSpecific() {
partition = ctx.DeviceConfig().ProductPath()
- } else if ctx.ProductServicesSpecific() {
- partition = ctx.DeviceConfig().ProductServicesPath()
+ } else if ctx.SystemExtSpecific() {
+ partition = ctx.DeviceConfig().SystemExtPath()
} else {
partition = "system"
}
diff --git a/android/paths_test.go b/android/paths_test.go
index 7bcfe41..8286e9a 100644
--- a/android/paths_test.go
+++ b/android/paths_test.go
@@ -294,15 +294,15 @@
out: "target/product/test_device/product/bin/my_test",
},
{
- name: "product_services binary",
+ name: "system_ext binary",
ctx: &moduleInstallPathContextImpl{
baseModuleContext: baseModuleContext{
target: deviceTarget,
- kind: productServicesSpecificModule,
+ kind: systemExtSpecificModule,
},
},
in: []string{"bin", "my_test"},
- out: "target/product/test_device/product_services/bin/my_test",
+ out: "target/product/test_device/system_ext/bin/my_test",
},
{
@@ -354,11 +354,11 @@
},
{
- name: "product_services native test binary",
+ name: "system_ext native test binary",
ctx: &moduleInstallPathContextImpl{
baseModuleContext: baseModuleContext{
target: deviceTarget,
- kind: productServicesSpecificModule,
+ kind: systemExtSpecificModule,
},
inData: true,
},
@@ -415,16 +415,16 @@
},
{
- name: "sanitized product_services binary",
+ name: "sanitized system_ext binary",
ctx: &moduleInstallPathContextImpl{
baseModuleContext: baseModuleContext{
target: deviceTarget,
- kind: productServicesSpecificModule,
+ kind: systemExtSpecificModule,
},
inSanitizerDir: true,
},
in: []string{"bin", "my_test"},
- out: "target/product/test_device/data/asan/product_services/bin/my_test",
+ out: "target/product/test_device/data/asan/system_ext/bin/my_test",
},
{
@@ -479,11 +479,11 @@
out: "target/product/test_device/data/asan/data/nativetest/my_test",
},
{
- name: "sanitized product_services native test binary",
+ name: "sanitized system_ext native test binary",
ctx: &moduleInstallPathContextImpl{
baseModuleContext: baseModuleContext{
target: deviceTarget,
- kind: productServicesSpecificModule,
+ kind: systemExtSpecificModule,
},
inData: true,
inSanitizerDir: true,
diff --git a/android/prebuilt.go b/android/prebuilt.go
index 5087b18..b674153 100644
--- a/android/prebuilt.go
+++ b/android/prebuilt.go
@@ -16,6 +16,7 @@
import (
"fmt"
+ "reflect"
"github.com/google/blueprint"
"github.com/google/blueprint/proptools"
@@ -43,7 +44,10 @@
properties PrebuiltProperties
module Module
srcs *[]string
- src *string
+
+ // Metadata for single source Prebuilt modules.
+ srcProps reflect.Value
+ srcField reflect.StructField
}
func (p *Prebuilt) Name(name string) string {
@@ -71,11 +75,16 @@
// sources.
return PathForModuleSrc(ctx, (*p.srcs)[0])
} else {
- if proptools.String(p.src) == "" {
- ctx.PropertyErrorf("src", "missing prebuilt source file")
+ if !p.srcProps.IsValid() {
+ ctx.ModuleErrorf("prebuilt source was not set")
+ }
+ src := p.getSingleSourceFieldValue()
+ if src == "" {
+ ctx.PropertyErrorf(proptools.FieldNameForProperty(p.srcField.Name),
+ "missing prebuilt source file")
return nil
}
- return PathForModuleSrc(ctx, *p.src)
+ return PathForModuleSrc(ctx, src)
}
}
@@ -89,10 +98,12 @@
p.srcs = srcs
}
-func InitSingleSourcePrebuiltModule(module PrebuiltInterface, src *string) {
+func InitSingleSourcePrebuiltModule(module PrebuiltInterface, srcProps interface{}, srcField string) {
p := module.Prebuilt()
module.AddProperties(&p.properties)
- p.src = src
+ p.srcProps = reflect.ValueOf(srcProps).Elem()
+ p.srcField, _ = p.srcProps.Type().FieldByName(srcField)
+ p.checkSingleSourceProperties()
}
type PrebuiltInterface interface {
@@ -129,7 +140,7 @@
func PrebuiltSelectModuleMutator(ctx TopDownMutatorContext) {
if m, ok := ctx.Module().(PrebuiltInterface); ok && m.Prebuilt() != nil {
p := m.Prebuilt()
- if p.srcs == nil && p.src == nil {
+ if p.srcs == nil && !p.srcProps.IsValid() {
panic(fmt.Errorf("prebuilt module did not have InitPrebuiltModule called on it"))
}
if !p.properties.SourceExists {
@@ -172,7 +183,7 @@
return false
}
- if p.src != nil && *p.src == "" {
+ if p.srcProps.IsValid() && p.getSingleSourceFieldValue() == "" {
return false
}
@@ -183,3 +194,28 @@
return source == nil || !source.Enabled()
}
+
+func (p *Prebuilt) checkSingleSourceProperties() {
+ if !p.srcProps.IsValid() || p.srcField.Name == "" {
+ panic(fmt.Errorf("invalid single source prebuilt %+v", p))
+ }
+
+ if p.srcProps.Kind() != reflect.Struct && p.srcProps.Kind() != reflect.Interface {
+ panic(fmt.Errorf("invalid single source prebuilt %+v", p.srcProps))
+ }
+}
+
+func (p *Prebuilt) getSingleSourceFieldValue() string {
+ value := p.srcProps.FieldByIndex(p.srcField.Index)
+ if value.Kind() == reflect.Ptr {
+ value = value.Elem()
+ }
+ if value.Kind() != reflect.String {
+ panic(fmt.Errorf("prebuilt src field %q should be a string or a pointer to one", p.srcField.Name))
+ }
+ return value.String()
+}
+
+func (p *Prebuilt) SourceExists() bool {
+ return p.properties.SourceExists
+}
diff --git a/android/proto.go b/android/proto.go
index 5247c68..c8ade45 100644
--- a/android/proto.go
+++ b/android/proto.go
@@ -135,7 +135,7 @@
}
rule.Command().
- Tool(ctx.Config().HostToolPath(ctx, "aprotoc")).
+ BuiltTool(ctx, "aprotoc").
FlagWithArg(flags.OutTypeFlag+"=", strings.Join(flags.OutParams, ",")+":"+outDir.String()).
FlagWithDepFile("--dependency_out=", depFile).
FlagWithArg("-I ", protoBase).
@@ -145,5 +145,5 @@
ImplicitOutputs(outputs)
rule.Command().
- Tool(ctx.Config().HostToolPath(ctx, "dep_fixer")).Flag(depFile.String())
+ BuiltTool(ctx, "dep_fixer").Flag(depFile.String())
}
diff --git a/android/rule_builder.go b/android/rule_builder.go
index 8d7e74b..1238ddc 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -263,11 +263,36 @@
return toolsList
}
-// Commands returns a slice containing a the built command line for each call to RuleBuilder.Command.
+// RspFileInputs returns the list of paths that were passed to the RuleBuilderCommand.FlagWithRspFileInputList method.
+func (r *RuleBuilder) RspFileInputs() Paths {
+ var rspFileInputs Paths
+ for _, c := range r.commands {
+ if c.rspFileInputs != nil {
+ if rspFileInputs != nil {
+ panic("Multiple commands in a rule may not have rsp file inputs")
+ }
+ rspFileInputs = c.rspFileInputs
+ }
+ }
+
+ return rspFileInputs
+}
+
+// Commands returns a slice containing the built command line for each call to RuleBuilder.Command.
func (r *RuleBuilder) Commands() []string {
var commands []string
for _, c := range r.commands {
- commands = append(commands, string(c.buf))
+ commands = append(commands, c.String())
+ }
+ return commands
+}
+
+// NinjaEscapedCommands returns a slice containin the built command line after ninja escaping for each call to
+// RuleBuilder.Command.
+func (r *RuleBuilder) NinjaEscapedCommands() []string {
+ var commands []string
+ for _, c := range r.commands {
+ commands = append(commands, c.NinjaEscapedString())
}
return commands
}
@@ -284,7 +309,7 @@
func (r *RuleBuilder) depFileMergerCmd(ctx PathContext, depFiles WritablePaths) *RuleBuilderCommand {
return r.Command().
- Tool(ctx.Config().HostToolPath(ctx, "dep_fixer")).
+ BuiltTool(ctx, "dep_fixer").
Inputs(depFiles.Paths())
}
@@ -324,7 +349,7 @@
}
tools := r.Tools()
- commands := r.Commands()
+ commands := r.NinjaEscapedCommands()
outputs := r.Outputs()
if len(commands) == 0 {
@@ -334,7 +359,7 @@
panic("No outputs specified from any Commands")
}
- commandString := strings.Join(proptools.NinjaEscapeList(commands), " && ")
+ commandString := strings.Join(commands, " && ")
if r.sbox {
sboxOutputs := make([]string, len(outputs))
@@ -352,28 +377,38 @@
}
sboxCmd := &RuleBuilderCommand{}
- sboxCmd.Tool(ctx.Config().HostToolPath(ctx, "sbox")).
+ sboxCmd.BuiltTool(ctx, "sbox").
Flag("-c").Text(commandString).
Flag("--sandbox-path").Text(shared.TempDirForOutDir(PathForOutput(ctx).String())).
Flag("--output-root").Text(r.sboxOutDir.String()).
Flags(sboxOutputs)
- commandString = string(sboxCmd.buf)
+ commandString = sboxCmd.buf.String()
tools = append(tools, sboxCmd.tools...)
}
// Ninja doesn't like multiple outputs when depfiles are enabled, move all but the first output to
- // ImplicitOutputs. RuleBuilder never uses "$out", so the distinction between Outputs and ImplicitOutputs
- // doesn't matter.
+ // ImplicitOutputs. RuleBuilder only uses "$out" for the rsp file location, so the distinction between Outputs and
+ // ImplicitOutputs doesn't matter.
output := outputs[0]
implicitOutputs := outputs[1:]
+ var rspFile, rspFileContent string
+ rspFileInputs := r.RspFileInputs()
+ if rspFileInputs != nil {
+ rspFile = "$out.rsp"
+ rspFileContent = "$in"
+ }
+
ctx.Build(pctx, BuildParams{
Rule: ctx.Rule(pctx, name, blueprint.RuleParams{
- Command: commandString,
- CommandDeps: tools.Strings(),
- Restat: r.restat,
+ Command: commandString,
+ CommandDeps: tools.Strings(),
+ Restat: r.restat,
+ Rspfile: rspFile,
+ RspfileContent: rspFileContent,
}),
+ Inputs: rspFileInputs,
Implicits: r.Inputs(),
Output: output,
ImplicitOutputs: implicitOutputs,
@@ -388,11 +423,15 @@
// RuleBuilderCommand, so they can be used chained or unchained. All methods that add text implicitly add a single
// space as a separator from the previous method.
type RuleBuilderCommand struct {
- buf []byte
- inputs Paths
- outputs WritablePaths
- depFiles WritablePaths
- tools Paths
+ buf strings.Builder
+ inputs Paths
+ outputs WritablePaths
+ depFiles WritablePaths
+ tools Paths
+ rspFileInputs Paths
+
+ // spans [start,end) of the command that should not be ninja escaped
+ unescapedSpans [][2]int
sbox bool
sboxOutDir WritablePath
@@ -420,10 +459,10 @@
// Text adds the specified raw text to the command line. The text should not contain input or output paths or the
// rule will not have them listed in its dependencies or outputs.
func (c *RuleBuilderCommand) Text(text string) *RuleBuilderCommand {
- if len(c.buf) > 0 {
- c.buf = append(c.buf, ' ')
+ if c.buf.Len() > 0 {
+ c.buf.WriteByte(' ')
}
- c.buf = append(c.buf, text...)
+ c.buf.WriteString(text)
return c
}
@@ -478,6 +517,24 @@
return c.Text(path.String())
}
+// BuiltTool adds the specified tool path that was built using a host Soong module to the command line. The path will
+// be also added to the dependencies returned by RuleBuilder.Tools.
+//
+// It is equivalent to:
+// cmd.Tool(ctx.Config().HostToolPath(ctx, tool))
+func (c *RuleBuilderCommand) BuiltTool(ctx PathContext, tool string) *RuleBuilderCommand {
+ return c.Tool(ctx.Config().HostToolPath(ctx, tool))
+}
+
+// PrebuiltBuildTool adds the specified tool path from prebuils/build-tools. The path will be also added to the
+// dependencies returned by RuleBuilder.Tools.
+//
+// It is equivalent to:
+// cmd.Tool(ctx.Config().PrebuiltBuildTool(ctx, tool))
+func (c *RuleBuilderCommand) PrebuiltBuildTool(ctx PathContext, tool string) *RuleBuilderCommand {
+ return c.Tool(ctx.Config().PrebuiltBuildTool(ctx, tool))
+}
+
// Input adds the specified input path to the command line. The path will also be added to the dependencies returned by
// RuleBuilder.Inputs.
func (c *RuleBuilderCommand) Input(path Path) *RuleBuilderCommand {
@@ -606,9 +663,54 @@
return c.Text(flag + c.outputStr(path))
}
+// FlagWithRspFileInputList adds the specified flag and path to an rspfile to the command line, with no separator
+// between them. The paths will be written to the rspfile.
+func (c *RuleBuilderCommand) FlagWithRspFileInputList(flag string, paths Paths) *RuleBuilderCommand {
+ if c.rspFileInputs != nil {
+ panic("FlagWithRspFileInputList cannot be called if rsp file inputs have already been provided")
+ }
+
+ // Use an empty slice if paths is nil, the non-nil slice is used as an indicator that the rsp file must be
+ // generated.
+ if paths == nil {
+ paths = Paths{}
+ }
+
+ c.rspFileInputs = paths
+
+ rspFile := "$out.rsp"
+ c.FlagWithArg(flag, rspFile)
+ c.unescapedSpans = append(c.unescapedSpans, [2]int{c.buf.Len() - len(rspFile), c.buf.Len()})
+ return c
+}
+
// String returns the command line.
func (c *RuleBuilderCommand) String() string {
- return string(c.buf)
+ return c.buf.String()
+}
+
+// String returns the command line.
+func (c *RuleBuilderCommand) NinjaEscapedString() string {
+ return ninjaEscapeExceptForSpans(c.String(), c.unescapedSpans)
+}
+
+func ninjaEscapeExceptForSpans(s string, spans [][2]int) string {
+ if len(spans) == 0 {
+ return proptools.NinjaEscape(s)
+ }
+
+ sb := strings.Builder{}
+ sb.Grow(len(s) * 11 / 10)
+
+ i := 0
+ for _, span := range spans {
+ sb.WriteString(proptools.NinjaEscape(s[i:span[0]]))
+ sb.WriteString(s[span[0]:span[1]])
+ i = span[1]
+ }
+ sb.WriteString(proptools.NinjaEscape(s[i:]))
+
+ return sb.String()
}
func ninjaNameEscape(s string) string {
diff --git a/android/rule_builder_test.go b/android/rule_builder_test.go
index cfbc2ab..6eba4f1 100644
--- a/android/rule_builder_test.go
+++ b/android/rule_builder_test.go
@@ -38,6 +38,7 @@
"ls": nil,
"turbine": nil,
"java": nil,
+ "javac": nil,
})
}
@@ -235,6 +236,34 @@
// ls --sort=time,size
}
+func ExampleRuleBuilderCommand_FlagWithRspFileInputList() {
+ ctx := pathContext()
+ fmt.Println(NewRuleBuilder().Command().
+ Tool(PathForSource(ctx, "javac")).
+ FlagWithRspFileInputList("@", PathsForTesting("a.java", "b.java")).
+ NinjaEscapedString())
+ // Output:
+ // javac @$out.rsp
+}
+
+func ExampleRuleBuilderCommand_String() {
+ fmt.Println(NewRuleBuilder().Command().
+ Text("FOO=foo").
+ Text("echo $FOO").
+ String())
+ // Output:
+ // FOO=foo echo $FOO
+}
+
+func ExampleRuleBuilderCommand_NinjaEscapedString() {
+ fmt.Println(NewRuleBuilder().Command().
+ Text("FOO=foo").
+ Text("echo $FOO").
+ NinjaEscapedString())
+ // Output:
+ // FOO=foo echo $$FOO
+}
+
func TestRuleBuilder(t *testing.T) {
fs := map[string][]byte{
"dep_fixer": nil,
@@ -503,3 +532,77 @@
"cp bar "+outFile, outFile, outFile+".d", true, nil)
})
}
+
+func Test_ninjaEscapeExceptForSpans(t *testing.T) {
+ type args struct {
+ s string
+ spans [][2]int
+ }
+ tests := []struct {
+ name string
+ args args
+ want string
+ }{
+ {
+ name: "empty",
+ args: args{
+ s: "",
+ },
+ want: "",
+ },
+ {
+ name: "unescape none",
+ args: args{
+ s: "$abc",
+ },
+ want: "$$abc",
+ },
+ {
+ name: "unescape all",
+ args: args{
+ s: "$abc",
+ spans: [][2]int{{0, 4}},
+ },
+ want: "$abc",
+ },
+ {
+ name: "unescape first",
+ args: args{
+ s: "$abc$",
+ spans: [][2]int{{0, 1}},
+ },
+ want: "$abc$$",
+ },
+ {
+ name: "unescape last",
+ args: args{
+ s: "$abc$",
+ spans: [][2]int{{4, 5}},
+ },
+ want: "$$abc$",
+ },
+ {
+ name: "unescape middle",
+ args: args{
+ s: "$a$b$c$",
+ spans: [][2]int{{2, 5}},
+ },
+ want: "$$a$b$c$$",
+ },
+ {
+ name: "unescape multiple",
+ args: args{
+ s: "$a$b$c$",
+ spans: [][2]int{{2, 3}, {4, 5}},
+ },
+ want: "$$a$b$c$$",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := ninjaEscapeExceptForSpans(tt.args.s, tt.args.spans); got != tt.want {
+ t.Errorf("ninjaEscapeExceptForSpans() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/android/sh_binary.go b/android/sh_binary.go
index fb7446d..2855aa0 100644
--- a/android/sh_binary.go
+++ b/android/sh_binary.go
@@ -29,6 +29,7 @@
RegisterModuleType("sh_binary", ShBinaryFactory)
RegisterModuleType("sh_binary_host", ShBinaryHostFactory)
RegisterModuleType("sh_test", ShTestFactory)
+ RegisterModuleType("sh_test_host", ShTestHostFactory)
}
type shBinaryProperties struct {
@@ -195,6 +196,7 @@
return module
}
+// sh_test defines a shell script based test module.
func ShTestFactory() Module {
module := &ShTest{}
InitShBinaryModule(&module.ShBinary)
@@ -203,3 +205,13 @@
InitAndroidArchModule(module, HostAndDeviceSupported, MultilibFirst)
return module
}
+
+// sh_test_host defines a shell script based test module that runs on a host.
+func ShTestHostFactory() Module {
+ module := &ShTest{}
+ InitShBinaryModule(&module.ShBinary)
+ module.AddProperties(&module.testProperties)
+
+ InitAndroidArchModule(module, HostSupported, MultilibFirst)
+ return module
+}
diff --git a/android/sh_binary_test.go b/android/sh_binary_test.go
index c99e18c..9df769c 100644
--- a/android/sh_binary_test.go
+++ b/android/sh_binary_test.go
@@ -10,6 +10,7 @@
ctx := NewTestArchContext()
ctx.RegisterModuleType("sh_test", ModuleFactoryAdaptor(ShTestFactory))
+ ctx.RegisterModuleType("sh_test_host", ModuleFactoryAdaptor(ShTestHostFactory))
ctx.Register()
mockFiles := map[string][]byte{
"Android.bp": []byte(bp),
@@ -48,3 +49,23 @@
t.Errorf("Unexpected test data expected: %q, actual: %q", expected, actual)
}
}
+
+func TestShTestHost(t *testing.T) {
+ ctx, _ := testShBinary(t, `
+ sh_test_host {
+ name: "foo",
+ src: "test.sh",
+ filename: "test.sh",
+ data: [
+ "testdata/data1",
+ "testdata/sub/data2",
+ ],
+ }
+ `)
+
+ buildOS := BuildOs.String()
+ mod := ctx.ModuleForTests("foo", buildOS+"_x86_64").Module().(*ShTest)
+ if !mod.Host() {
+ t.Errorf("host bit is not set for a sh_test_host module.")
+ }
+}
diff --git a/android/testing.go b/android/testing.go
index 44bee4b..12e30ec 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -382,3 +382,14 @@
entries.fillInEntries(config, bpPath, mod)
return entries
}
+
+func AndroidMkDataForTest(t *testing.T, config Config, bpPath string, mod blueprint.Module) AndroidMkData {
+ var p AndroidMkDataProvider
+ var ok bool
+ if p, ok = mod.(AndroidMkDataProvider); !ok {
+ t.Errorf("module does not implmement AndroidMkDataProvider: " + mod.Name())
+ }
+ data := p.AndroidMk()
+ data.fillInData(config, bpPath, mod)
+ return data
+}
diff --git a/android/util.go b/android/util.go
index 3b8bc78..97bec10 100644
--- a/android/util.go
+++ b/android/util.go
@@ -115,6 +115,17 @@
return false
}
+// IndexListPred returns the index of the element which in the given `list` satisfying the predicate, or -1 if there is no such element.
+func IndexListPred(pred func(s string) bool, list []string) int {
+ for i, l := range list {
+ if pred(l) {
+ return i
+ }
+ }
+
+ return -1
+}
+
func FilterList(list []string, filter []string) (remainder []string, filtered []string) {
for _, l := range list {
if InList(l, filter) {
diff --git a/android/variable.go b/android/variable.go
index b4f31c6..47586a7 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -165,15 +165,17 @@
DeviceSecondaryCpuVariant *string `json:",omitempty"`
DeviceSecondaryAbi []string `json:",omitempty"`
- NativeBridgeArch *string `json:",omitempty"`
- NativeBridgeArchVariant *string `json:",omitempty"`
- NativeBridgeCpuVariant *string `json:",omitempty"`
- NativeBridgeAbi []string `json:",omitempty"`
+ NativeBridgeArch *string `json:",omitempty"`
+ NativeBridgeArchVariant *string `json:",omitempty"`
+ NativeBridgeCpuVariant *string `json:",omitempty"`
+ NativeBridgeAbi []string `json:",omitempty"`
+ NativeBridgeRelativePath *string `json:",omitempty"`
- NativeBridgeSecondaryArch *string `json:",omitempty"`
- NativeBridgeSecondaryArchVariant *string `json:",omitempty"`
- NativeBridgeSecondaryCpuVariant *string `json:",omitempty"`
- NativeBridgeSecondaryAbi []string `json:",omitempty"`
+ NativeBridgeSecondaryArch *string `json:",omitempty"`
+ NativeBridgeSecondaryArchVariant *string `json:",omitempty"`
+ NativeBridgeSecondaryCpuVariant *string `json:",omitempty"`
+ NativeBridgeSecondaryAbi []string `json:",omitempty"`
+ NativeBridgeSecondaryRelativePath *string `json:",omitempty"`
HostArch *string `json:",omitempty"`
HostSecondaryArch *string `json:",omitempty"`
@@ -232,10 +234,10 @@
EnableXOM *bool `json:",omitempty"`
XOMExcludePaths []string `json:",omitempty"`
- VendorPath *string `json:",omitempty"`
- OdmPath *string `json:",omitempty"`
- ProductPath *string `json:",omitempty"`
- ProductServicesPath *string `json:",omitempty"`
+ VendorPath *string `json:",omitempty"`
+ OdmPath *string `json:",omitempty"`
+ ProductPath *string `json:",omitempty"`
+ SystemExtPath *string `json:",omitempty"`
ClangTidy *bool `json:",omitempty"`
TidyChecks *string `json:",omitempty"`
diff --git a/android/visibility.go b/android/visibility.go
index c7ef1da..94af343 100644
--- a/android/visibility.go
+++ b/android/visibility.go
@@ -23,14 +23,21 @@
// Enforces visibility rules between modules.
//
-// Two stage process:
-// * First stage works bottom up to extract visibility information from the modules, parse it,
+// Multi stage process:
+// * First stage works bottom up, before defaults expansion, to check the syntax of the visibility
+// rules that have been specified.
+//
+// * Second stage works bottom up to extract the package info for each package and store them in a
+// map by package name. See package.go for functionality for this.
+//
+// * Third stage works bottom up to extract visibility information from the modules, parse it,
// create visibilityRule structures and store them in a map keyed by the module's
// qualifiedModuleName instance, i.e. //<pkg>:<name>. The map is stored in the context rather
// than a global variable for testing. Each test has its own Config so they do not share a map
-// and so can be run in parallel.
+// and so can be run in parallel. If a module has no visibility specified then it uses the
+// default package visibility if specified.
//
-// * Second stage works top down and iterates over all the deps for each module. If the dep is in
+// * Fourth stage works top down and iterates over all the deps for each module. If the dep is in
// the same package then it is automatically visible. Otherwise, for each dep it first extracts
// its visibilityRule from the config map. If one could not be found then it assumes that it is
// publicly visible. Otherwise, it calls the visibility rule to check that the module can see
@@ -48,19 +55,6 @@
var visibilityRuleRegexp = regexp.MustCompile(visibilityRulePattern)
-// Qualified id for a module
-type qualifiedModuleName struct {
- // The package (i.e. directory) in which the module is defined, without trailing /
- pkg string
-
- // The name of the module.
- name string
-}
-
-func (q qualifiedModuleName) String() string {
- return fmt.Sprintf("//%s:%s", q.pkg, q.name)
-}
-
// A visibility rule is associated with a module and determines which other modules it is visible
// to, i.e. which other modules can depend on the rule's module.
type visibilityRule interface {
@@ -71,6 +65,32 @@
String() string
}
+// Describes the properties provided by a module that contain visibility rules.
+type visibilityPropertyImpl struct {
+ name string
+ stringsGetter func() []string
+}
+
+type visibilityProperty interface {
+ getName() string
+ getStrings() []string
+}
+
+func newVisibilityProperty(name string, stringsGetter func() []string) visibilityProperty {
+ return visibilityPropertyImpl{
+ name: name,
+ stringsGetter: stringsGetter,
+ }
+}
+
+func (p visibilityPropertyImpl) getName() string {
+ return p.name
+}
+
+func (p visibilityPropertyImpl) getStrings() []string {
+ return p.stringsGetter()
+}
+
// A compositeRule is a visibility rule composed from a list of atomic visibility rules.
//
// The list corresponds to the list of strings in the visibility property after defaults expansion.
@@ -96,9 +116,9 @@
return false
}
-func (r compositeRule) String() string {
- s := make([]string, 0, len(r))
- for _, r := range r {
+func (c compositeRule) String() string {
+ s := make([]string, 0, len(c))
+ for _, r := range c {
s = append(s, r.String())
}
@@ -173,9 +193,12 @@
ctx.BottomUp("visibilityRuleChecker", visibilityRuleChecker).Parallel()
}
+// Registers the function that gathers the visibility rules for each module.
+//
// Visibility is not dependent on arch so this must be registered before the arch phase to avoid
// having to process multiple variants for each module. This goes after defaults expansion to gather
-// the complete visibility lists from flat lists.
+// the complete visibility lists from flat lists and after the package info is gathered to ensure
+// that default_visibility is available.
func registerVisibilityRuleGatherer(ctx RegisterMutatorsContext) {
ctx.BottomUp("visibilityRuleGatherer", visibilityRuleGatherer).Parallel()
}
@@ -193,33 +216,36 @@
for _, props := range d.properties() {
if cp, ok := props.(*commonProperties); ok {
if visibility := cp.Visibility; visibility != nil {
- checkRules(ctx, qualified.pkg, visibility)
+ checkRules(ctx, qualified.pkg, "visibility", visibility)
}
}
}
} else if m, ok := ctx.Module().(Module); ok {
- if visibility := m.base().commonProperties.Visibility; visibility != nil {
- checkRules(ctx, qualified.pkg, visibility)
+ visibilityProperties := m.visibilityProperties()
+ for _, p := range visibilityProperties {
+ if visibility := p.getStrings(); visibility != nil {
+ checkRules(ctx, qualified.pkg, p.getName(), visibility)
+ }
}
}
}
-func checkRules(ctx BottomUpMutatorContext, currentPkg string, visibility []string) {
+func checkRules(ctx BaseModuleContext, currentPkg, property string, visibility []string) {
ruleCount := len(visibility)
if ruleCount == 0 {
// This prohibits an empty list as its meaning is unclear, e.g. it could mean no visibility and
// it could mean public visibility. Requiring at least one rule makes the owner's intent
// clearer.
- ctx.PropertyErrorf("visibility", "must contain at least one visibility rule")
+ ctx.PropertyErrorf(property, "must contain at least one visibility rule")
return
}
for _, v := range visibility {
- ok, pkg, name := splitRule(ctx, v, currentPkg)
+ ok, pkg, name := splitRule(v, currentPkg)
if !ok {
// Visibility rule is invalid so ignore it. Keep going rather than aborting straight away to
// ensure all the rules on this module are checked.
- ctx.PropertyErrorf("visibility",
+ ctx.PropertyErrorf(property,
"invalid visibility pattern %q must match"+
" //<package>:<module>, //<package> or :<module>",
v)
@@ -230,14 +256,14 @@
switch name {
case "private", "public":
case "legacy_public":
- ctx.PropertyErrorf("visibility", "//visibility:legacy_public must not be used")
+ ctx.PropertyErrorf(property, "//visibility:legacy_public must not be used")
continue
default:
- ctx.PropertyErrorf("visibility", "unrecognized visibility rule %q", v)
+ ctx.PropertyErrorf(property, "unrecognized visibility rule %q", v)
continue
}
if ruleCount != 1 {
- ctx.PropertyErrorf("visibility", "cannot mix %q with any other visibility rules", v)
+ ctx.PropertyErrorf(property, "cannot mix %q with any other visibility rules", v)
continue
}
}
@@ -246,7 +272,7 @@
// restrictions on the rules.
if !isAncestor("vendor", currentPkg) {
if !isAllowedFromOutsideVendor(pkg, name) {
- ctx.PropertyErrorf("visibility",
+ ctx.PropertyErrorf(property,
"%q is not allowed. Packages outside //vendor cannot make themselves visible to specific"+
" targets within //vendor, they can only use //vendor:__subpackages__.", v)
continue
@@ -265,23 +291,27 @@
return
}
- qualified := createQualifiedModuleName(ctx)
+ qualifiedModuleId := m.qualifiedModuleId(ctx)
+ currentPkg := qualifiedModuleId.pkg
- visibility := m.base().commonProperties.Visibility
- if visibility != nil {
- rule := parseRules(ctx, qualified.pkg, visibility)
- if rule != nil {
- moduleToVisibilityRuleMap(ctx).Store(qualified, rule)
+ // Parse all the properties into rules and store them.
+ visibilityProperties := m.visibilityProperties()
+ for _, p := range visibilityProperties {
+ if visibility := p.getStrings(); visibility != nil {
+ rule := parseRules(ctx, currentPkg, visibility)
+ if rule != nil {
+ moduleToVisibilityRuleMap(ctx).Store(qualifiedModuleId, rule)
+ }
}
}
}
-func parseRules(ctx BottomUpMutatorContext, currentPkg string, visibility []string) compositeRule {
+func parseRules(ctx BaseModuleContext, currentPkg string, visibility []string) compositeRule {
rules := make(compositeRule, 0, len(visibility))
hasPrivateRule := false
hasNonPrivateRule := false
for _, v := range visibility {
- ok, pkg, name := splitRule(ctx, v, currentPkg)
+ ok, pkg, name := splitRule(v, currentPkg)
if !ok {
continue
}
@@ -336,7 +366,7 @@
return !isAncestor("vendor", pkg)
}
-func splitRule(ctx BaseModuleContext, ruleExpression string, currentPkg string) (bool, string, string) {
+func splitRule(ruleExpression string, currentPkg string) (bool, string, string) {
// Make sure that the rule is of the correct format.
matches := visibilityRuleRegexp.FindStringSubmatch(ruleExpression)
if ruleExpression == "" || matches == nil {
@@ -378,11 +408,15 @@
return
}
- rule, ok := moduleToVisibilityRule.Load(depQualified)
+ value, ok := moduleToVisibilityRule.Load(depQualified)
+ var rule compositeRule
if ok {
- if !rule.(compositeRule).matches(qualified) {
- ctx.ModuleErrorf("depends on %s which is not visible to this module", depQualified)
- }
+ rule = value.(compositeRule)
+ } else {
+ rule = packageDefaultVisibility(ctx, depQualified)
+ }
+ if rule != nil && !rule.matches(qualified) {
+ ctx.ModuleErrorf("depends on %s which is not visible to this module", depQualified)
}
})
}
@@ -393,3 +427,20 @@
qualified := qualifiedModuleName{dir, moduleName}
return qualified
}
+
+func packageDefaultVisibility(ctx BaseModuleContext, moduleId qualifiedModuleName) compositeRule {
+ moduleToVisibilityRule := moduleToVisibilityRuleMap(ctx)
+ packageQualifiedId := moduleId.getContainingPackageId()
+ for {
+ value, ok := moduleToVisibilityRule.Load(packageQualifiedId)
+ if ok {
+ return value.(compositeRule)
+ }
+
+ if packageQualifiedId.isRootPackage() {
+ return nil
+ }
+
+ packageQualifiedId = packageQualifiedId.getContainingPackageId()
+ }
+}
diff --git a/android/visibility_test.go b/android/visibility_test.go
index 1a51495..af6acf4 100644
--- a/android/visibility_test.go
+++ b/android/visibility_test.go
@@ -658,6 +658,167 @@
` visible to this module`,
},
},
+ // Package default_visibility tests
+ {
+ name: "package default_visibility property is checked",
+ fs: map[string][]byte{
+ "top/Blueprints": []byte(`
+ package {
+ default_visibility: ["//visibility:invalid"],
+ }`),
+ },
+ expectedErrors: []string{`default_visibility: unrecognized visibility rule "//visibility:invalid"`},
+ },
+ {
+ // This test relies on the default visibility being legacy_public.
+ name: "package default_visibility property used when no visibility specified",
+ fs: map[string][]byte{
+ "top/Blueprints": []byte(`
+ package {
+ default_visibility: ["//visibility:private"],
+ }
+
+ mock_library {
+ name: "libexample",
+ }`),
+ "outsider/Blueprints": []byte(`
+ mock_library {
+ name: "liboutsider",
+ deps: ["libexample"],
+ }`),
+ },
+ expectedErrors: []string{
+ `module "liboutsider" variant "android_common": depends on //top:libexample which is not` +
+ ` visible to this module`,
+ },
+ },
+ {
+ name: "package default_visibility public does not override visibility private",
+ fs: map[string][]byte{
+ "top/Blueprints": []byte(`
+ package {
+ default_visibility: ["//visibility:public"],
+ }
+
+ mock_library {
+ name: "libexample",
+ visibility: ["//visibility:private"],
+ }`),
+ "outsider/Blueprints": []byte(`
+ mock_library {
+ name: "liboutsider",
+ deps: ["libexample"],
+ }`),
+ },
+ expectedErrors: []string{
+ `module "liboutsider" variant "android_common": depends on //top:libexample which is not` +
+ ` visible to this module`,
+ },
+ },
+ {
+ name: "package default_visibility private does not override visibility public",
+ fs: map[string][]byte{
+ "top/Blueprints": []byte(`
+ package {
+ default_visibility: ["//visibility:private"],
+ }
+
+ mock_library {
+ name: "libexample",
+ visibility: ["//visibility:public"],
+ }`),
+ "outsider/Blueprints": []byte(`
+ mock_library {
+ name: "liboutsider",
+ deps: ["libexample"],
+ }`),
+ },
+ },
+ {
+ name: "package default_visibility :__subpackages__",
+ fs: map[string][]byte{
+ "top/Blueprints": []byte(`
+ package {
+ default_visibility: [":__subpackages__"],
+ }
+
+ mock_library {
+ name: "libexample",
+ }`),
+ "top/nested/Blueprints": []byte(`
+ mock_library {
+ name: "libnested",
+ deps: ["libexample"],
+ }`),
+ "outsider/Blueprints": []byte(`
+ mock_library {
+ name: "liboutsider",
+ deps: ["libexample"],
+ }`),
+ },
+ expectedErrors: []string{
+ `module "liboutsider" variant "android_common": depends on //top:libexample which is not` +
+ ` visible to this module`,
+ },
+ },
+ {
+ name: "package default_visibility inherited to subpackages",
+ fs: map[string][]byte{
+ "top/Blueprints": []byte(`
+ package {
+ default_visibility: ["//outsider"],
+ }
+
+ mock_library {
+ name: "libexample",
+ visibility: [":__subpackages__"],
+ }`),
+ "top/nested/Blueprints": []byte(`
+ mock_library {
+ name: "libnested",
+ deps: ["libexample"],
+ }`),
+ "outsider/Blueprints": []byte(`
+ mock_library {
+ name: "liboutsider",
+ deps: ["libexample", "libnested"],
+ }`),
+ },
+ expectedErrors: []string{
+ `module "liboutsider" variant "android_common": depends on //top:libexample which is not` +
+ ` visible to this module`,
+ },
+ },
+ {
+ name: "package default_visibility inherited to subpackages",
+ fs: map[string][]byte{
+ "top/Blueprints": []byte(`
+ package {
+ default_visibility: ["//visibility:private"],
+ }`),
+ "top/nested/Blueprints": []byte(`
+ package {
+ default_visibility: ["//outsider"],
+ }
+
+ mock_library {
+ name: "libnested",
+ }`),
+ "top/other/Blueprints": []byte(`
+ mock_library {
+ name: "libother",
+ }`),
+ "outsider/Blueprints": []byte(`
+ mock_library {
+ name: "liboutsider",
+ deps: ["libother", "libnested"],
+ }`),
+ },
+ expectedErrors: []string{
+ `module "liboutsider" variant "android_common": depends on //top/other:libother which is` +
+ ` not visible to this module`,
+ },
+ },
}
func TestVisibility(t *testing.T) {
@@ -692,8 +853,10 @@
config := TestArchConfig(buildDir, nil)
ctx := NewTestArchContext()
+ ctx.RegisterModuleType("package", ModuleFactoryAdaptor(PackageFactory))
ctx.RegisterModuleType("mock_library", ModuleFactoryAdaptor(newMockLibraryModule))
ctx.RegisterModuleType("mock_defaults", ModuleFactoryAdaptor(defaultsFactory))
+ ctx.PreArchMutators(registerPackageRenamer)
ctx.PreArchMutators(registerVisibilityRuleChecker)
ctx.PreArchMutators(RegisterDefaultsPreArchMutators)
ctx.PreArchMutators(registerVisibilityRuleGatherer)
diff --git a/androidmk/cmd/androidmk/android.go b/androidmk/cmd/androidmk/android.go
index af81e43..2def179 100644
--- a/androidmk/cmd/androidmk/android.go
+++ b/androidmk/cmd/androidmk/android.go
@@ -192,7 +192,7 @@
"LOCAL_VENDOR_MODULE": "vendor",
"LOCAL_ODM_MODULE": "device_specific",
"LOCAL_PRODUCT_MODULE": "product_specific",
- "LOCAL_PRODUCT_SERVICES_MODULE": "product_services_specific",
+ "LOCAL_SYSTEM_EXT_MODULE": "system_ext_specific",
"LOCAL_EXPORT_PACKAGE_RESOURCES": "export_package_resources",
"LOCAL_PRIVILEGED_MODULE": "privileged",
"LOCAL_AAPT_INCLUDE_ALL_RESOURCES": "aapt_include_all_resources",
@@ -602,8 +602,8 @@
return fmt.Errorf("Cannot handle appending to LOCAL_MODULE_PATH")
}
// Analyze value in order to set the correct values for the 'device_specific',
- // 'product_specific', 'product_services_specific' 'vendor'/'soc_specific',
- // 'product_services_specific' attribute. Two cases are allowed:
+ // 'product_specific', 'system_ext_specific' 'vendor'/'soc_specific',
+ // 'system_ext_specific' attribute. Two cases are allowed:
// $(VAR)/<literal-value>
// $(PRODUCT_OUT)/$(TARGET_COPY_OUT_VENDOR)/<literal-value>
// The last case is equivalent to $(TARGET_OUT_VENDOR)/<literal-value>
diff --git a/androidmk/cmd/androidmk/androidmk_test.go b/androidmk/cmd/androidmk/androidmk_test.go
index 4d5180e..dbb7fde 100644
--- a/androidmk/cmd/androidmk/androidmk_test.go
+++ b/androidmk/cmd/androidmk/androidmk_test.go
@@ -957,37 +957,37 @@
`,
},
{
- desc: "prebuilt_etc_TARGET_OUT_PRODUCT_SERVICES/etc",
+ desc: "prebuilt_etc_TARGET_OUT_SYSTEM_EXT/etc",
in: `
include $(CLEAR_VARS)
LOCAL_MODULE := etc.test1
LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_PRODUCT_SERVICES)/etc/foo/bar
+LOCAL_MODULE_PATH := $(TARGET_OUT_SYSTEM_EXT)/etc/foo/bar
include $(BUILD_PREBUILT)
`,
expected: `
prebuilt_etc {
name: "etc.test1",
sub_dir: "foo/bar",
- product_services_specific: true,
+ system_ext_specific: true,
}
`,
},
{
- desc: "prebuilt_etc_TARGET_OUT_PRODUCT_SERVICES_ETC",
+ desc: "prebuilt_etc_TARGET_OUT_SYSTEM_EXT_ETC",
in: `
include $(CLEAR_VARS)
LOCAL_MODULE := etc.test1
LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_PRODUCT_SERVICES_ETC)/foo/bar
+LOCAL_MODULE_PATH := $(TARGET_OUT_SYSTEM_EXT_ETC)/foo/bar
include $(BUILD_PREBUILT)
`,
expected: `
prebuilt_etc {
name: "etc.test1",
sub_dir: "foo/bar",
- product_services_specific: true,
+ system_ext_specific: true,
}
diff --git a/apex/apex.go b/apex/apex.go
index ac0363b..9701e2a 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -90,12 +90,6 @@
CommandDeps: []string{"${zip2zip}"},
Description: "app bundle",
}, "abi")
-
- apexMergeNoticeRule = pctx.StaticRule("apexMergeNoticeRule", blueprint.RuleParams{
- Command: `${mergenotice} --output $out $inputs`,
- CommandDeps: []string{"${mergenotice}"},
- Description: "merge notice files into $out",
- }, "inputs")
)
var imageApexSuffix = ".apex"
@@ -114,8 +108,10 @@
executableTag = dependencyTag{name: "executable"}
javaLibTag = dependencyTag{name: "javaLib"}
prebuiltTag = dependencyTag{name: "prebuilt"}
+ testTag = dependencyTag{name: "test"}
keyTag = dependencyTag{name: "key"}
certificateTag = dependencyTag{name: "certificate"}
+ usesTag = dependencyTag{name: "uses"}
)
func init() {
@@ -144,8 +140,6 @@
pctx.HostBinToolVariable("zip2zip", "zip2zip")
pctx.HostBinToolVariable("zipalign", "zipalign")
- pctx.SourcePathVariable("mergenotice", "build/soong/scripts/mergenotice.py")
-
android.RegisterModuleType("apex", apexBundleFactory)
android.RegisterModuleType("apex_test", testApexBundleFactory)
android.RegisterModuleType("apex_defaults", defaultsFactory)
@@ -154,6 +148,7 @@
android.PostDepsMutators(func(ctx android.RegisterMutatorsContext) {
ctx.TopDown("apex_deps", apexDepsMutator)
ctx.BottomUp("apex", apexMutator).Parallel()
+ ctx.BottomUp("apex_uses", apexUsesMutator).Parallel()
})
}
@@ -194,12 +189,19 @@
mctx.CreateVariations(apexBundleName)
}
}
+func apexUsesMutator(mctx android.BottomUpMutatorContext) {
+ if ab, ok := mctx.Module().(*apexBundle); ok {
+ mctx.AddFarVariationDependencies(nil, usesTag, ab.properties.Uses...)
+ }
+}
type apexNativeDependencies struct {
// List of native libraries
Native_shared_libs []string
// List of native executables
Binaries []string
+ // List of native tests
+ Tests []string
}
type apexMultilibProperties struct {
// Native dependencies whose compile_multilib is "first"
@@ -240,7 +242,7 @@
// List of native shared libs that are embedded inside this APEX bundle
Native_shared_libs []string
- // List of native executables that are embedded inside this APEX bundle
+ // List of executables that are embedded inside this APEX bundle
Binaries []string
// List of java libraries that are embedded inside this APEX bundle
@@ -249,6 +251,9 @@
// List of prebuilt files that are embedded inside this APEX bundle
Prebuilts []string
+ // List of tests that are embedded inside this APEX bundle
+ Tests []string
+
// Name of the apex_key module that provides the private key to sign APEX
Key *string
@@ -274,6 +279,12 @@
// List of sanitizer names that this APEX is enabled for
SanitizerNames []string `blueprint:"mutated"`
+
+ // Indicates this APEX provides C++ shared libaries to other APEXes. Default: false.
+ Provide_cpp_shared_libs *bool
+
+ // List of providing APEXes' names so that this APEX can depend on provided shared libraries.
+ Uses []string
}
type apexTargetBundleProperties struct {
@@ -307,6 +318,7 @@
pyBinary
goBinary
javaSharedLib
+ nativeTest
)
type apexPackaging int
@@ -369,6 +381,8 @@
return "EXECUTABLES"
case javaSharedLib:
return "JAVA_LIBRARIES"
+ case nativeTest:
+ return "NATIVE_TESTS"
default:
panic(fmt.Errorf("unkonwn class %d", class))
}
@@ -402,8 +416,6 @@
container_certificate_file android.Path
container_private_key_file android.Path
- mergedNoticeFile android.WritablePath
-
// list of files to be included in this apex
filesInfo []apexFile
@@ -416,7 +428,8 @@
}
func addDependenciesForNativeModules(ctx android.BottomUpMutatorContext,
- native_shared_libs []string, binaries []string, arch string, imageVariation string) {
+ native_shared_libs []string, binaries []string, tests []string,
+ arch string, imageVariation string) {
// Use *FarVariation* to be able to depend on modules having
// conflicting variations with this module. This is required since
// arch variant of an APEX bundle is 'common' but it is 'arm' or 'arm64'
@@ -432,6 +445,11 @@
{Mutator: "arch", Variation: arch},
{Mutator: "image", Variation: imageVariation},
}, executableTag, binaries...)
+
+ ctx.AddFarVariationDependencies([]blueprint.Variation{
+ {Mutator: "arch", Variation: arch},
+ {Mutator: "image", Variation: imageVariation},
+ }, testTag, tests...)
}
func (a *apexBundle) combineProperties(ctx android.BottomUpMutatorContext) {
@@ -469,10 +487,19 @@
{Mutator: "link", Variation: "shared"},
}, sharedLibTag, a.properties.Native_shared_libs...)
+ // When multilib.* is omitted for tests, it implies
+ // multilib.both.
+ ctx.AddFarVariationDependencies([]blueprint.Variation{
+ {Mutator: "arch", Variation: target.String()},
+ {Mutator: "image", Variation: a.getImageVariation(config)},
+ }, testTag, a.properties.Tests...)
+
// Add native modules targetting both ABIs
addDependenciesForNativeModules(ctx,
a.properties.Multilib.Both.Native_shared_libs,
- a.properties.Multilib.Both.Binaries, target.String(),
+ a.properties.Multilib.Both.Binaries,
+ a.properties.Multilib.Both.Tests,
+ target.String(),
a.getImageVariation(config))
isPrimaryAbi := i == 0
@@ -487,7 +514,9 @@
// Add native modules targetting the first ABI
addDependenciesForNativeModules(ctx,
a.properties.Multilib.First.Native_shared_libs,
- a.properties.Multilib.First.Binaries, target.String(),
+ a.properties.Multilib.First.Binaries,
+ a.properties.Multilib.First.Tests,
+ target.String(),
a.getImageVariation(config))
// When multilib.* is omitted for prebuilts, it implies multilib.first.
@@ -501,24 +530,32 @@
// Add native modules targetting 32-bit ABI
addDependenciesForNativeModules(ctx,
a.properties.Multilib.Lib32.Native_shared_libs,
- a.properties.Multilib.Lib32.Binaries, target.String(),
+ a.properties.Multilib.Lib32.Binaries,
+ a.properties.Multilib.Lib32.Tests,
+ target.String(),
a.getImageVariation(config))
addDependenciesForNativeModules(ctx,
a.properties.Multilib.Prefer32.Native_shared_libs,
- a.properties.Multilib.Prefer32.Binaries, target.String(),
+ a.properties.Multilib.Prefer32.Binaries,
+ a.properties.Multilib.Prefer32.Tests,
+ target.String(),
a.getImageVariation(config))
case "lib64":
// Add native modules targetting 64-bit ABI
addDependenciesForNativeModules(ctx,
a.properties.Multilib.Lib64.Native_shared_libs,
- a.properties.Multilib.Lib64.Binaries, target.String(),
+ a.properties.Multilib.Lib64.Binaries,
+ a.properties.Multilib.Lib64.Tests,
+ target.String(),
a.getImageVariation(config))
if !has32BitTarget {
addDependenciesForNativeModules(ctx,
a.properties.Multilib.Prefer32.Native_shared_libs,
- a.properties.Multilib.Prefer32.Binaries, target.String(),
+ a.properties.Multilib.Prefer32.Binaries,
+ a.properties.Multilib.Prefer32.Tests,
+ target.String(),
a.getImageVariation(config))
}
@@ -527,7 +564,7 @@
if sanitizer == "hwaddress" {
addDependenciesForNativeModules(ctx,
[]string{"libclang_rt.hwasan-aarch64-android"},
- nil, target.String(), a.getImageVariation(config))
+ nil, nil, target.String(), a.getImageVariation(config))
break
}
}
@@ -621,6 +658,8 @@
dirInApex = filepath.Join(dirInApex, cc.RelativeInstallPath())
if !cc.Arch().Native {
dirInApex = filepath.Join(dirInApex, cc.Arch().ArchType.String())
+ } else if cc.Target().NativeBridge == android.NativeBridgeEnabled {
+ dirInApex = filepath.Join(dirInApex, cc.Target().NativeBridgeRelativePath)
}
if handleSpecialLibs {
switch cc.Name() {
@@ -644,6 +683,11 @@
func getCopyManifestForExecutable(cc *cc.Module) (fileToCopy android.Path, dirInApex string) {
dirInApex = filepath.Join("bin", cc.RelativeInstallPath())
+ if !cc.Arch().Native {
+ dirInApex = filepath.Join(dirInApex, cc.Arch().ArchType.String())
+ } else if cc.Target().NativeBridge == android.NativeBridgeEnabled {
+ dirInApex = filepath.Join(dirInApex, cc.Target().NativeBridgeRelativePath)
+ }
fileToCopy = cc.OutputFile().Path()
return
}
@@ -696,8 +740,37 @@
return
}
+ if len(a.properties.Tests) > 0 && !a.testApex {
+ ctx.PropertyErrorf("tests", "property not allowed in apex module type")
+ return
+ }
+
handleSpecialLibs := !android.Bool(a.properties.Ignore_system_library_special_case)
+ // Check if "uses" requirements are met with dependent apexBundles
+ var providedNativeSharedLibs []string
+ useVendor := proptools.Bool(a.properties.Use_vendor)
+ ctx.VisitDirectDepsBlueprint(func(m blueprint.Module) {
+ if ctx.OtherModuleDependencyTag(m) != usesTag {
+ return
+ }
+ otherName := ctx.OtherModuleName(m)
+ other, ok := m.(*apexBundle)
+ if !ok {
+ ctx.PropertyErrorf("uses", "%q is not a provider", otherName)
+ return
+ }
+ if proptools.Bool(other.properties.Use_vendor) != useVendor {
+ ctx.PropertyErrorf("use_vendor", "%q has different value of use_vendor", otherName)
+ return
+ }
+ if !proptools.Bool(other.properties.Provide_cpp_shared_libs) {
+ ctx.PropertyErrorf("uses", "%q does not provide native_shared_libs", otherName)
+ return
+ }
+ providedNativeSharedLibs = append(providedNativeSharedLibs, other.properties.Native_shared_libs...)
+ })
+
ctx.WalkDepsBlueprint(func(child, parent blueprint.Module) bool {
if _, ok := parent.(*apexBundle); ok {
// direct dependencies
@@ -714,11 +787,6 @@
}
case executableTag:
if cc, ok := child.(*cc.Module); ok {
- if !cc.Arch().Native {
- // There is only one 'bin' directory so we shouldn't bother copying in
- // native-bridge'd binaries and only use main ones.
- return true
- }
fileToCopy, dirInApex := getCopyManifestForExecutable(cc)
filesInfo = append(filesInfo, apexFile{fileToCopy, depName, dirInApex, nativeExecutable, cc, cc.Symlinks()})
return true
@@ -757,6 +825,14 @@
} else {
ctx.PropertyErrorf("prebuilts", "%q is not a prebuilt_etc module", depName)
}
+ case testTag:
+ if cc, ok := child.(*cc.Module); ok {
+ fileToCopy, dirInApex := getCopyManifestForExecutable(cc)
+ filesInfo = append(filesInfo, apexFile{fileToCopy, depName, dirInApex, nativeTest, cc, nil})
+ return true
+ } else {
+ ctx.PropertyErrorf("tests", "%q is not a cc module", depName)
+ }
case keyTag:
if key, ok := child.(*apexKey); ok {
a.private_key_file = key.private_key_file
@@ -778,6 +854,11 @@
// indirect dependencies
if am, ok := child.(android.ApexModule); ok && am.CanHaveApexVariants() && am.IsInstallableToApex() {
if cc, ok := child.(*cc.Module); ok {
+ if android.InList(cc.Name(), providedNativeSharedLibs) {
+ // If we're using a shared library which is provided from other APEX,
+ // don't include it in this APEX
+ return false
+ }
if !a.Host() && (cc.IsStubs() || cc.HasStubsVariants()) {
// If the dependency is a stubs lib, don't include it in this APEX,
// but make sure that the lib is installed on the device.
@@ -836,8 +917,6 @@
a.installDir = android.PathForModuleInstall(ctx, "apex")
a.filesInfo = filesInfo
- a.buildNoticeFile(ctx)
-
if a.apexTypes.zip() {
a.buildUnflattenedApex(ctx, zipApex)
}
@@ -851,35 +930,26 @@
}
}
-func (a *apexBundle) buildNoticeFile(ctx android.ModuleContext) {
+func (a *apexBundle) buildNoticeFile(ctx android.ModuleContext, apexFileName string) android.OptionalPath {
noticeFiles := []android.Path{}
- noticeFilesString := []string{}
for _, f := range a.filesInfo {
if f.module != nil {
notice := f.module.NoticeFile()
if notice.Valid() {
noticeFiles = append(noticeFiles, notice.Path())
- noticeFilesString = append(noticeFilesString, notice.Path().String())
}
}
}
// append the notice file specified in the apex module itself
if a.NoticeFile().Valid() {
noticeFiles = append(noticeFiles, a.NoticeFile().Path())
- noticeFilesString = append(noticeFilesString, a.NoticeFile().Path().String())
}
- if len(noticeFiles) > 0 {
- a.mergedNoticeFile = android.PathForModuleOut(ctx, "NOTICE")
- ctx.Build(pctx, android.BuildParams{
- Rule: apexMergeNoticeRule,
- Inputs: noticeFiles,
- Output: a.mergedNoticeFile,
- Args: map[string]string{
- "inputs": strings.Join(noticeFilesString, " "),
- },
- })
+ if len(noticeFiles) == 0 {
+ return android.OptionalPath{}
}
+
+ return android.BuildNoticeOutput(ctx, a.installDir, apexFileName, android.FirstUniquePaths(noticeFiles)).HtmlGzOutput
}
func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext, apexType apexPackaging) {
@@ -936,7 +1006,7 @@
var executablePaths []string // this also includes dirs
for _, f := range a.filesInfo {
pathInApex := filepath.Join(f.installDir, f.builtFile.Base())
- if f.installDir == "bin" {
+ if f.installDir == "bin" || strings.HasPrefix(f.installDir, "bin/") {
executablePaths = append(executablePaths, pathInApex)
for _, s := range f.symlinks {
executablePaths = append(executablePaths, filepath.Join("bin", s))
@@ -1004,6 +1074,13 @@
}
optFlags = append(optFlags, "--target_sdk_version "+targetSdkVersion)
+ noticeFile := a.buildNoticeFile(ctx, ctx.ModuleName()+suffix)
+ if noticeFile.Valid() {
+ // If there's a NOTICE file, embed it as an asset file in the APEX.
+ implicitInputs = append(implicitInputs, noticeFile.Path())
+ optFlags = append(optFlags, "--assets_dir "+filepath.Dir(noticeFile.String()))
+ }
+
ctx.Build(pctx, android.BuildParams{
Rule: apexRule,
Implicits: implicitInputs,
@@ -1250,9 +1327,6 @@
fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", filepath.Join("$(OUT_DIR)", a.installDir.RelPathString()))
fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", name+apexType.suffix())
fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE :=", !a.installable())
- if a.installable() && a.mergedNoticeFile != nil {
- fmt.Fprintln(w, "LOCAL_NOTICE_FILE :=", a.mergedNoticeFile.String())
- }
if len(moduleNames) > 0 {
fmt.Fprintln(w, "LOCAL_REQUIRED_MODULES +=", strings.Join(moduleNames, " "))
}
@@ -1269,11 +1343,11 @@
}
func testApexBundleFactory() android.Module {
- return ApexBundleFactory( /*testApex*/ true)
+ return ApexBundleFactory(true /*testApex*/)
}
func apexBundleFactory() android.Module {
- return ApexBundleFactory( /*testApex*/ false)
+ return ApexBundleFactory(false /*testApex*/)
}
func ApexBundleFactory(testApex bool) android.Module {
@@ -1333,7 +1407,8 @@
type PrebuiltProperties struct {
// the path to the prebuilt .apex file to import.
- Source string `blueprint:"mutated"`
+ Source string `blueprint:"mutated"`
+ ForceDisable bool `blueprint:"mutated"`
Src *string
Arch struct {
@@ -1362,6 +1437,23 @@
}
func (p *Prebuilt) DepsMutator(ctx android.BottomUpMutatorContext) {
+ // If the device is configured to use flattened APEX, force disable the prebuilt because
+ // the prebuilt is a non-flattened one.
+ forceDisable := ctx.Config().FlattenApex()
+
+ // Force disable the prebuilts when we are doing unbundled build. We do unbundled build
+ // to build the prebuilts themselves.
+ forceDisable = forceDisable || ctx.Config().UnbundledBuild()
+
+ // b/137216042 don't use prebuilts when address sanitizer is on
+ forceDisable = forceDisable || android.InList("address", ctx.Config().SanitizeDevice()) ||
+ android.InList("hwaddress", ctx.Config().SanitizeDevice())
+
+ if forceDisable && p.prebuilt.SourceExists() {
+ p.properties.ForceDisable = true
+ return
+ }
+
// This is called before prebuilt_select and prebuilt_postdeps mutators
// The mutators requires that src to be set correctly for each arch so that
// arch variants are disabled when src is not provided for the arch.
@@ -1403,6 +1495,10 @@
}
func (p *Prebuilt) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+ if p.properties.ForceDisable {
+ return
+ }
+
// TODO(jungjw): Check the key validity.
p.inputApex = p.Prebuilt().SingleSourcePath(ctx)
p.installDir = android.PathForModuleInstall(ctx, "apex")
@@ -1448,7 +1544,7 @@
func PrebuiltFactory() android.Module {
module := &Prebuilt{}
module.AddProperties(&module.properties)
- android.InitSingleSourcePrebuiltModule(module, &module.properties.Source)
+ android.InitSingleSourcePrebuiltModule(module, &module.properties, "Source")
android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
return module
}
diff --git a/apex/apex_test.go b/apex/apex_test.go
index f71abd7..94cf19d 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -27,9 +27,40 @@
"android/soong/java"
)
+var buildDir string
+
+func testApexError(t *testing.T, pattern, bp string) {
+ ctx, config := testApexContext(t, bp)
+ _, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+ if len(errs) > 0 {
+ android.FailIfNoMatchingErrors(t, pattern, errs)
+ return
+ }
+ _, errs = ctx.PrepareBuildActions(config)
+ if len(errs) > 0 {
+ android.FailIfNoMatchingErrors(t, pattern, errs)
+ return
+ }
+
+ t.Fatalf("missing expected error %q (0 errors are returned)", pattern)
+}
+
func testApex(t *testing.T, bp string) *android.TestContext {
- config, buildDir := setup(t)
- defer teardown(buildDir)
+ ctx, config := testApexContext(t, bp)
+ _, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+ android.FailIfErrored(t, errs)
+ _, errs = ctx.PrepareBuildActions(config)
+ android.FailIfErrored(t, errs)
+ return ctx
+}
+
+func testApexContext(t *testing.T, bp string) (*android.TestContext, android.Config) {
+ config := android.TestArchConfig(buildDir, nil)
+ config.TestProductVariables.DeviceVndkVersion = proptools.StringPtr("current")
+ config.TestProductVariables.DefaultAppCertificate = proptools.StringPtr("vendor/foo/devkeys/test")
+ config.TestProductVariables.CertificateOverrides = []string{"myapex_keytest:myapex.certificate.override"}
+ config.TestProductVariables.Platform_sdk_codename = proptools.StringPtr("Q")
+ config.TestProductVariables.Platform_sdk_final = proptools.BoolPtr(false)
ctx := android.NewTestArchContext()
ctx.RegisterModuleType("apex", android.ModuleFactoryAdaptor(apexBundleFactory))
@@ -42,6 +73,7 @@
ctx.PostDepsMutators(func(ctx android.RegisterMutatorsContext) {
ctx.TopDown("apex_deps", apexDepsMutator)
ctx.BottomUp("apex", apexMutator)
+ ctx.BottomUp("apex_uses", apexUsesMutator)
ctx.TopDown("prebuilt_select", android.PrebuiltSelectModuleMutator).Parallel()
ctx.BottomUp("prebuilt_postdeps", android.PrebuiltPostDepsMutator).Parallel()
})
@@ -51,6 +83,7 @@
ctx.RegisterModuleType("cc_library_headers", android.ModuleFactoryAdaptor(cc.LibraryHeaderFactory))
ctx.RegisterModuleType("cc_binary", android.ModuleFactoryAdaptor(cc.BinaryFactory))
ctx.RegisterModuleType("cc_object", android.ModuleFactoryAdaptor(cc.ObjectFactory))
+ ctx.RegisterModuleType("cc_test", android.ModuleFactoryAdaptor(cc.TestFactory))
ctx.RegisterModuleType("llndk_library", android.ModuleFactoryAdaptor(cc.LlndkLibraryFactory))
ctx.RegisterModuleType("toolchain_library", android.ModuleFactoryAdaptor(cc.ToolchainLibraryFactory))
ctx.RegisterModuleType("prebuilt_etc", android.ModuleFactoryAdaptor(android.PrebuiltEtcFactory))
@@ -161,7 +194,10 @@
"system/sepolicy/apex/myapex-file_contexts": nil,
"system/sepolicy/apex/myapex_keytest-file_contexts": nil,
"system/sepolicy/apex/otherapex-file_contexts": nil,
+ "system/sepolicy/apex/commonapex-file_contexts": nil,
"mylib.cpp": nil,
+ "mytest.cpp": nil,
+ "mylib_common.cpp": nil,
"myprebuilt": nil,
"my_include": nil,
"vendor/foo/devkeys/test.x509.pem": nil,
@@ -180,35 +216,25 @@
"myapex-arm.apex": nil,
"frameworks/base/api/current.txt": nil,
})
- _, errs := ctx.ParseFileList(".", []string{"Android.bp"})
- android.FailIfErrored(t, errs)
- _, errs = ctx.PrepareBuildActions(config)
- android.FailIfErrored(t, errs)
- return ctx
+ return ctx, config
}
-func setup(t *testing.T) (config android.Config, buildDir string) {
- buildDir, err := ioutil.TempDir("", "soong_apex_test")
+func setUp() {
+ var err error
+ buildDir, err = ioutil.TempDir("", "soong_apex_test")
if err != nil {
- t.Fatal(err)
+ panic(err)
}
-
- config = android.TestArchConfig(buildDir, nil)
- config.TestProductVariables.DeviceVndkVersion = proptools.StringPtr("current")
- config.TestProductVariables.DefaultAppCertificate = proptools.StringPtr("vendor/foo/devkeys/test")
- config.TestProductVariables.CertificateOverrides = []string{"myapex_keytest:myapex.certificate.override"}
- config.TestProductVariables.Platform_sdk_codename = proptools.StringPtr("Q")
- config.TestProductVariables.Platform_sdk_final = proptools.BoolPtr(false)
- return
}
-func teardown(buildDir string) {
+func tearDown() {
os.RemoveAll(buildDir)
}
// ensure that 'result' contains 'expected'
func ensureContains(t *testing.T, result string, expected string) {
+ t.Helper()
if !strings.Contains(result, expected) {
t.Errorf("%q is not found in %q", expected, result)
}
@@ -216,18 +242,21 @@
// ensures that 'result' does not contain 'notExpected'
func ensureNotContains(t *testing.T, result string, notExpected string) {
+ t.Helper()
if strings.Contains(result, notExpected) {
t.Errorf("%q is found in %q", notExpected, result)
}
}
func ensureListContains(t *testing.T, result []string, expected string) {
+ t.Helper()
if !android.InList(expected, result) {
t.Errorf("%q is not found in %v", expected, result)
}
}
func ensureListNotContains(t *testing.T, result []string, notExpected string) {
+ t.Helper()
if android.InList(notExpected, result) {
t.Errorf("%q is found in %v", notExpected, result)
}
@@ -310,6 +339,8 @@
optFlags := apexRule.Args["opt_flags"]
ensureContains(t, optFlags, "--pubkey vendor/foo/devkeys/testkey.avbpubkey")
+ // Ensure that the NOTICE output is being packaged as an asset.
+ ensureContains(t, optFlags, "--assets_dir "+buildDir+"/.intermediates/myapex/android_common_myapex/NOTICE")
copyCmds := apexRule.Args["copy_commands"]
@@ -347,10 +378,10 @@
t.Errorf("Could not find all expected symlinks! foo: %t, foo_link_64: %t. Command was %s", found_foo, found_foo_link_64, copyCmds)
}
- apexMergeNoticeRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexMergeNoticeRule")
- noticeInputs := strings.Split(apexMergeNoticeRule.Args["inputs"], " ")
- if len(noticeInputs) != 3 {
- t.Errorf("number of input notice files: expected = 3, actual = %d", len(noticeInputs))
+ mergeNoticesRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("mergeNoticesRule")
+ noticeInputs := mergeNoticesRule.Inputs.Strings()
+ if len(noticeInputs) != 2 {
+ t.Errorf("number of input notice files: expected = 2, actual = %q", len(noticeInputs))
}
ensureListContains(t, noticeInputs, "NOTICE")
ensureListContains(t, noticeInputs, "custom_notice")
@@ -786,6 +817,30 @@
ensureNotContains(t, inputsString, "android_arm64_armv8-a_core_shared_myapex/mylib2.so")
}
+func TestUseVendorFailsIfNotVendorAvailable(t *testing.T) {
+ testApexError(t, `dependency "mylib" of "myapex" missing variant:\n.*image:vendor`, `
+ apex {
+ name: "myapex",
+ key: "myapex.key",
+ native_shared_libs: ["mylib"],
+ use_vendor: true,
+ }
+
+ apex_key {
+ name: "myapex.key",
+ public_key: "testkey.avbpubkey",
+ private_key: "testkey.pem",
+ }
+
+ cc_library {
+ name: "mylib",
+ srcs: ["mylib.cpp"],
+ system_shared_libs: [],
+ stl: "none",
+ }
+ `)
+}
+
func TestStaticLinking(t *testing.T) {
ctx := testApex(t, `
apex {
@@ -1283,3 +1338,164 @@
t.Errorf("installFilename invalid. expected: %q, actual: %q", expected, p.installFilename)
}
}
+
+func TestApexWithTests(t *testing.T) {
+ ctx := testApex(t, `
+ apex_test {
+ name: "myapex",
+ key: "myapex.key",
+ tests: [
+ "mytest",
+ ],
+ }
+
+ apex_key {
+ name: "myapex.key",
+ public_key: "testkey.avbpubkey",
+ private_key: "testkey.pem",
+ }
+
+ cc_test {
+ name: "mytest",
+ gtest: false,
+ srcs: ["mytest.cpp"],
+ relative_install_path: "test",
+ system_shared_libs: [],
+ static_executable: true,
+ stl: "none",
+ }
+ `)
+
+ apexRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexRule")
+ copyCmds := apexRule.Args["copy_commands"]
+
+ // Ensure that test dep is copied into apex.
+ ensureContains(t, copyCmds, "image.apex/bin/test/mytest")
+}
+
+func TestApexUsesOtherApex(t *testing.T) {
+ ctx := testApex(t, `
+ apex {
+ name: "myapex",
+ key: "myapex.key",
+ native_shared_libs: ["mylib"],
+ uses: ["commonapex"],
+ }
+
+ apex {
+ name: "commonapex",
+ key: "myapex.key",
+ native_shared_libs: ["libcommon"],
+ provide_cpp_shared_libs: true,
+ }
+
+ apex_key {
+ name: "myapex.key",
+ public_key: "testkey.avbpubkey",
+ private_key: "testkey.pem",
+ }
+
+ cc_library {
+ name: "mylib",
+ srcs: ["mylib.cpp"],
+ shared_libs: ["libcommon"],
+ system_shared_libs: [],
+ stl: "none",
+ }
+
+ cc_library {
+ name: "libcommon",
+ srcs: ["mylib_common.cpp"],
+ system_shared_libs: [],
+ stl: "none",
+ }
+ `)
+
+ module1 := ctx.ModuleForTests("myapex", "android_common_myapex")
+ apexRule1 := module1.Rule("apexRule")
+ copyCmds1 := apexRule1.Args["copy_commands"]
+
+ module2 := ctx.ModuleForTests("commonapex", "android_common_commonapex")
+ apexRule2 := module2.Rule("apexRule")
+ copyCmds2 := apexRule2.Args["copy_commands"]
+
+ ensureListContains(t, ctx.ModuleVariantsForTests("mylib"), "android_arm64_armv8-a_core_shared_myapex")
+ ensureListContains(t, ctx.ModuleVariantsForTests("libcommon"), "android_arm64_armv8-a_core_shared_commonapex")
+ ensureContains(t, copyCmds1, "image.apex/lib64/mylib.so")
+ ensureContains(t, copyCmds2, "image.apex/lib64/libcommon.so")
+ ensureNotContains(t, copyCmds1, "image.apex/lib64/libcommon.so")
+}
+
+func TestApexUsesFailsIfNotProvided(t *testing.T) {
+ testApexError(t, `uses: "commonapex" does not provide native_shared_libs`, `
+ apex {
+ name: "myapex",
+ key: "myapex.key",
+ uses: ["commonapex"],
+ }
+
+ apex {
+ name: "commonapex",
+ key: "myapex.key",
+ }
+
+ apex_key {
+ name: "myapex.key",
+ public_key: "testkey.avbpubkey",
+ private_key: "testkey.pem",
+ }
+ `)
+ testApexError(t, `uses: "commonapex" is not a provider`, `
+ apex {
+ name: "myapex",
+ key: "myapex.key",
+ uses: ["commonapex"],
+ }
+
+ cc_library {
+ name: "commonapex",
+ system_shared_libs: [],
+ stl: "none",
+ }
+
+ apex_key {
+ name: "myapex.key",
+ public_key: "testkey.avbpubkey",
+ private_key: "testkey.pem",
+ }
+ `)
+}
+
+func TestApexUsesFailsIfUseVenderMismatch(t *testing.T) {
+ testApexError(t, `use_vendor: "commonapex" has different value of use_vendor`, `
+ apex {
+ name: "myapex",
+ key: "myapex.key",
+ use_vendor: true,
+ uses: ["commonapex"],
+ }
+
+ apex {
+ name: "commonapex",
+ key: "myapex.key",
+ provide_cpp_shared_libs: true,
+ }
+
+ apex_key {
+ name: "myapex.key",
+ public_key: "testkey.avbpubkey",
+ private_key: "testkey.pem",
+ }
+ `)
+}
+
+func TestMain(m *testing.M) {
+ run := func() int {
+ setUp()
+ defer tearDown()
+
+ return m.Run()
+ }
+
+ os.Exit(run())
+}
diff --git a/bpfix/bpfix/bpfix.go b/bpfix/bpfix/bpfix.go
index cac4d9a..5f1cce8 100644
--- a/bpfix/bpfix/bpfix.go
+++ b/bpfix/bpfix/bpfix.go
@@ -508,15 +508,15 @@
"TARGET_OUT": {{prefix: "/usr/share", modType: "prebuilt_usr_share"}, {prefix: "/fonts", modType: "prebuilt_font"},
{prefix: "/etc/firmware", modType: "prebuilt_firmware"}, {prefix: "/vendor/firmware", modType: "prebuilt_firmware", flags: []string{"proprietary"}},
{prefix: "/etc"}},
- "TARGET_OUT_ETC": {{prefix: "/firmware", modType: "prebuilt_firmware"}, {prefix: ""}},
- "TARGET_OUT_PRODUCT": {{prefix: "/etc", flags: []string{"product_specific"}}, {prefix: "/fonts", modType: "prebuilt_font", flags: []string{"product_specific"}}},
- "TARGET_OUT_PRODUCT_ETC": {{prefix: "", flags: []string{"product_specific"}}},
- "TARGET_OUT_ODM": {{prefix: "/etc", flags: []string{"device_specific"}}},
- "TARGET_OUT_PRODUCT_SERVICES": {{prefix: "/etc", flags: []string{"product_services_specific"}}},
- "TARGET_OUT_PRODUCT_SERVICES_ETC": {{prefix: "", flags: []string{"product_services_specific"}}},
- "TARGET_OUT_VENDOR": {{prefix: "/etc", flags: []string{"proprietary"}}, {prefix: "/firmware", modType: "prebuilt_firmware", flags: []string{"proprietary"}}},
- "TARGET_OUT_VENDOR_ETC": {{prefix: "", flags: []string{"proprietary"}}},
- "TARGET_RECOVERY_ROOT_OUT": {{prefix: "/system/etc", flags: []string{"recovery"}}},
+ "TARGET_OUT_ETC": {{prefix: "/firmware", modType: "prebuilt_firmware"}, {prefix: ""}},
+ "TARGET_OUT_PRODUCT": {{prefix: "/etc", flags: []string{"product_specific"}}, {prefix: "/fonts", modType: "prebuilt_font", flags: []string{"product_specific"}}},
+ "TARGET_OUT_PRODUCT_ETC": {{prefix: "", flags: []string{"product_specific"}}},
+ "TARGET_OUT_ODM": {{prefix: "/etc", flags: []string{"device_specific"}}},
+ "TARGET_OUT_SYSTEM_EXT": {{prefix: "/etc", flags: []string{"system_ext_specific"}}},
+ "TARGET_OUT_SYSTEM_EXT_ETC": {{prefix: "", flags: []string{"system_ext_specific"}}},
+ "TARGET_OUT_VENDOR": {{prefix: "/etc", flags: []string{"proprietary"}}, {prefix: "/firmware", modType: "prebuilt_firmware", flags: []string{"proprietary"}}},
+ "TARGET_OUT_VENDOR_ETC": {{prefix: "", flags: []string{"proprietary"}}},
+ "TARGET_RECOVERY_ROOT_OUT": {{prefix: "/system/etc", flags: []string{"recovery"}}},
}
// rewriteAndroidPrebuiltEtc fixes prebuilt_etc rule
diff --git a/cc/binary.go b/cc/binary.go
index 1757f1c..149a92e 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -444,7 +444,8 @@
// Bionic binaries (e.g. linker) is installed to the bootstrap subdirectory.
// The original path becomes a symlink to the corresponding file in the
// runtime APEX.
- if installToBootstrap(ctx.baseModuleName(), ctx.Config()) && ctx.Arch().Native && ctx.apexName() == "" && !ctx.inRecovery() {
+ translatedArch := ctx.Target().NativeBridge == android.NativeBridgeEnabled || !ctx.Arch().Native
+ if installToBootstrap(ctx.baseModuleName(), ctx.Config()) && !translatedArch && ctx.apexName() == "" && !ctx.inRecovery() {
if ctx.Device() && isBionic(ctx.baseModuleName()) {
binary.installSymlinkToRuntimeApex(ctx, file)
}
diff --git a/cc/builder.go b/cc/builder.go
index 1e12361..cbe2c88 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -78,7 +78,7 @@
blueprint.RuleParams{
// Without -no-pie, clang 7.0 adds -pie to link Android files,
// but -r and -pie cannot be used together.
- Command: "$ldCmd -nostdlib -no-pie -Wl,-r ${in} -o ${out} ${ldFlags}",
+ Command: "$ldCmd -fuse-ld=lld -nostdlib -no-pie -Wl,-r ${in} -o ${out} ${ldFlags}",
CommandDeps: []string{"$ldCmd"},
},
"ldCmd", "ldFlags")
@@ -416,7 +416,7 @@
ccCmd = "clang"
moduleCflags = cflags
moduleToolingCflags = toolingCflags
- case ".cpp", ".cc", ".mm":
+ case ".cpp", ".cc", ".cxx", ".mm":
ccCmd = "clang++"
moduleCflags = cppflags
moduleToolingCflags = toolingCppflags
diff --git a/cc/cc.go b/cc/cc.go
index 1100bac..a0ab255 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -368,6 +368,7 @@
ndkLateStubDepTag = dependencyTag{name: "ndk late stub", library: true}
vndkExtDepTag = dependencyTag{name: "vndk extends", library: true}
runtimeDepTag = dependencyTag{name: "runtime lib"}
+ coverageDepTag = dependencyTag{name: "coverage"}
)
// Module contains the properties and members used by all C/C++ module types, and implements
@@ -778,6 +779,10 @@
// APEX variants do not need ABI dumps.
return false
}
+ if ctx.isStubs() {
+ // Stubs do not need ABI dumps.
+ return false
+ }
if ctx.isNdk() {
return true
}
@@ -970,7 +975,7 @@
flags = c.sanitize.flags(ctx, flags)
}
if c.coverage != nil {
- flags = c.coverage.flags(ctx, flags)
+ flags, deps = c.coverage.flags(ctx, flags, deps)
}
if c.lto != nil {
flags = c.lto.flags(ctx, flags)
diff --git a/cc/compdb.go b/cc/compdb.go
index 1102651..ecc67b8 100644
--- a/cc/compdb.go
+++ b/cc/compdb.go
@@ -141,7 +141,7 @@
isAsm = false
isCpp = false
clangPath = ccPath
- case ".cpp", ".cc", ".mm":
+ case ".cpp", ".cc", ".cxx", ".mm":
isAsm = false
isCpp = true
clangPath = cxxPath
diff --git a/cc/config/x86_64_device.go b/cc/config/x86_64_device.go
index 0f0420f..bcfae5d 100644
--- a/cc/config/x86_64_device.go
+++ b/cc/config/x86_64_device.go
@@ -63,14 +63,20 @@
}
x86_64ArchFeatureCflags = map[string][]string{
- "ssse3": []string{"-DUSE_SSSE3", "-mssse3"},
+ "ssse3": []string{"-mssse3"},
"sse4": []string{"-msse4"},
"sse4_1": []string{"-msse4.1"},
"sse4_2": []string{"-msse4.2"},
+
+ // Not all cases there is performance gain by enabling -mavx -mavx2
+ // flags so these flags are not enabled by default.
+ // if there is performance gain in individual library components,
+ // the compiler flags can be set in corresponding bp files.
+ // "avx": []string{"-mavx"},
+ // "avx2": []string{"-mavx2"},
+ // "avx512": []string{"-mavx512"}
+
"popcnt": []string{"-mpopcnt"},
- "avx": []string{"-mavx"},
- "avx2": []string{"-mavx2"},
- "avx512": []string{"-mavx512"},
"aes_ni": []string{"-maes"},
}
)
diff --git a/cc/config/x86_64_fuchsia_device.go b/cc/config/x86_64_fuchsia_device.go
index 79af00c..0f2013b 100644
--- a/cc/config/x86_64_fuchsia_device.go
+++ b/cc/config/x86_64_fuchsia_device.go
@@ -92,7 +92,7 @@
}
func (t *toolchainFuchsiaX8664) ToolchainClangCflags() string {
- return "-DUSE_SSSE3 -mssse3"
+ return "-mssse3"
}
var toolchainFuchsiaSingleton Toolchain = &toolchainFuchsiaX8664{}
diff --git a/cc/config/x86_device.go b/cc/config/x86_device.go
index 500014e..64392dc 100644
--- a/cc/config/x86_device.go
+++ b/cc/config/x86_device.go
@@ -82,12 +82,19 @@
}
x86ArchFeatureCflags = map[string][]string{
- "ssse3": []string{"-DUSE_SSSE3", "-mssse3"},
+ "ssse3": []string{"-mssse3"},
"sse4": []string{"-msse4"},
"sse4_1": []string{"-msse4.1"},
"sse4_2": []string{"-msse4.2"},
- "avx": []string{"-mavx"},
- "avx2": []string{"-mavx2"},
+
+ // Not all cases there is performance gain by enabling -mavx -mavx2
+ // flags so these flags are not enabled by default.
+ // if there is performance gain in individual library components,
+ // the compiler flags can be set in corresponding bp files.
+ // "avx": []string{"-mavx"},
+ // "avx2": []string{"-mavx2"},
+ // "avx512": []string{"-mavx512"}
+
"aes_ni": []string{"-maes"},
}
)
diff --git a/cc/coverage.go b/cc/coverage.go
index 9dc7f06..0de0c1c 100644
--- a/cc/coverage.go
+++ b/cc/coverage.go
@@ -17,6 +17,8 @@
import (
"strconv"
+ "github.com/google/blueprint"
+
"android/soong/android"
)
@@ -41,30 +43,28 @@
return []interface{}{&cov.Properties}
}
-func (cov *coverage) deps(ctx BaseModuleContext, deps Deps) Deps {
- if cov.Properties.NeedCoverageBuild {
- // Link libprofile-extras/libprofile-extras_ndk when coverage
- // variant is required. This is a no-op unless coverage is
- // actually enabled during linking, when
- // '-uinit_profile_extras' is added (in flags()) to force the
- // setup code in libprofile-extras be linked into the
- // binary/library.
- //
- // We cannot narrow it further to only the 'cov' variant since
- // the mutator hasn't run (and we don't have the 'cov' variant
- // yet).
- if !ctx.useSdk() {
- deps.LateStaticLibs = append(deps.LateStaticLibs, "libprofile-extras")
- } else {
- deps.LateStaticLibs = append(deps.LateStaticLibs, "libprofile-extras_ndk")
- }
+func getProfileLibraryName(ctx ModuleContextIntf) string {
+ // This function should only ever be called for a cc.Module, so the
+ // following statement should always succeed.
+ if ctx.useSdk() {
+ return "libprofile-extras_ndk"
+ } else {
+ return "libprofile-extras"
+ }
+}
+
+func (cov *coverage) deps(ctx DepsContext, deps Deps) Deps {
+ if cov.Properties.NeedCoverageVariant {
+ ctx.AddVariationDependencies([]blueprint.Variation{
+ {Mutator: "link", Variation: "static"},
+ }, coverageDepTag, getProfileLibraryName(ctx))
}
return deps
}
-func (cov *coverage) flags(ctx ModuleContext, flags Flags) Flags {
+func (cov *coverage) flags(ctx ModuleContext, flags Flags, deps PathDeps) (Flags, PathDeps) {
if !ctx.DeviceConfig().NativeCoverageEnabled() {
- return flags
+ return flags, deps
}
if cov.Properties.CoverageEnabled {
@@ -114,11 +114,13 @@
if cov.linkCoverage {
flags.LdFlags = append(flags.LdFlags, "--coverage")
- // Force linking of constructor/setup code in libprofile-extras
- flags.LdFlags = append(flags.LdFlags, "-uinit_profile_extras")
+ coverage := ctx.GetDirectDepWithTag(getProfileLibraryName(ctx), coverageDepTag).(*Module)
+ deps.WholeStaticLibs = append(deps.WholeStaticLibs, coverage.OutputFile().Path())
+
+ flags.LdFlags = append(flags.LdFlags, "-Wl,--wrap,getenv")
}
- return flags
+ return flags, deps
}
func (cov *coverage) begin(ctx BaseModuleContext) {
diff --git a/cc/gen.go b/cc/gen.go
index 1d5cc48..f8007e6 100644
--- a/cc/gen.go
+++ b/cc/gen.go
@@ -98,7 +98,8 @@
}
cmd.Text("BISON_PKGDATADIR=prebuilts/build-tools/common/bison").
- Tool(ctx.Config().PrebuiltBuildTool(ctx, "bison")).
+ FlagWithInput("M4=", ctx.Config().PrebuiltBuildTool(ctx, "m4")).
+ PrebuiltBuildTool(ctx, "bison").
Flag("-d").
Flags(flags).
FlagWithOutput("--defines=", headerFile).
@@ -120,7 +121,7 @@
headerBp := outDir.Join(ctx, aidlPackage, "Bp"+shortName+".h")
cmd := rule.Command()
- cmd.Tool(ctx.Config().HostToolPath(ctx, "aidl-cpp")).
+ cmd.BuiltTool(ctx, "aidl-cpp").
FlagWithDepFile("-d", depFile).
Flag("--ninja").
Flag(aidlFlags).
diff --git a/cc/installer.go b/cc/installer.go
index cb261b7..a52ccf1 100644
--- a/cc/installer.go
+++ b/cc/installer.go
@@ -66,9 +66,12 @@
if ctx.toolchain().Is64Bit() && installer.dir64 != "" {
dir = installer.dir64
}
- if (!ctx.Host() && !ctx.Arch().Native) || ctx.Target().NativeBridge == android.NativeBridgeEnabled {
+ if !ctx.Host() && !ctx.Arch().Native {
dir = filepath.Join(dir, ctx.Arch().ArchType.String())
}
+ if ctx.Target().NativeBridge == android.NativeBridgeEnabled {
+ dir = filepath.Join(dir, ctx.Target().NativeBridgeRelativePath)
+ }
if installer.location == InstallInData && ctx.useVndk() {
dir = filepath.Join(dir, "vendor")
}
diff --git a/cc/library.go b/cc/library.go
index f98cd36..cb31979 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -113,8 +113,6 @@
}
type LibraryMutatedProperties struct {
- VariantName string `blueprint:"mutated"`
-
// Build a static variant
BuildStatic bool `blueprint:"mutated"`
// Build a shared variant
@@ -528,7 +526,7 @@
}
}
- return name + library.MutatedProperties.VariantName
+ return name
}
var versioningMacroNamesListMutex sync.Mutex
@@ -633,7 +631,7 @@
library.objects = deps.WholeStaticLibObjs.Copy()
library.objects = library.objects.Append(objs)
- fileName := ctx.ModuleName() + library.MutatedProperties.VariantName + staticLibraryExtension
+ fileName := ctx.ModuleName() + staticLibraryExtension
outputFile := android.PathForModuleOut(ctx, fileName)
builderFlags := flagsToBuilderFlags(flags)
@@ -651,8 +649,7 @@
TransformObjToStaticLib(ctx, library.objects.objFiles, builderFlags, outputFile, objs.tidyFiles)
- library.coverageOutputFile = TransformCoverageFilesToZip(ctx, library.objects,
- ctx.ModuleName()+library.MutatedProperties.VariantName)
+ library.coverageOutputFile = TransformCoverageFilesToZip(ctx, library.objects, ctx.ModuleName())
library.wholeStaticMissingDeps = ctx.GetMissingDependencies()
@@ -810,7 +807,7 @@
}
func (library *libraryDecorator) linkSAbiDumpFiles(ctx ModuleContext, objs Objects, fileName string, soFile android.Path) {
- if len(objs.sAbiDumpFiles) > 0 && library.shouldCreateVndkSourceAbiDump(ctx) {
+ if library.shouldCreateVndkSourceAbiDump(ctx) {
vndkVersion := ctx.DeviceConfig().PlatformVndkVersion()
if ver := ctx.DeviceConfig().VndkVersion(); ver != "" && ver != "current" {
vndkVersion = ver
@@ -955,7 +952,8 @@
// Bionic libraries (e.g. libc.so) is installed to the bootstrap subdirectory.
// The original path becomes a symlink to the corresponding file in the
// runtime APEX.
- if installToBootstrap(ctx.baseModuleName(), ctx.Config()) && !library.buildStubs() && ctx.Arch().Native && !ctx.inRecovery() {
+ translatedArch := ctx.Target().NativeBridge == android.NativeBridgeEnabled || !ctx.Arch().Native
+ if installToBootstrap(ctx.baseModuleName(), ctx.Config()) && !library.buildStubs() && !translatedArch && !ctx.inRecovery() {
if ctx.Device() && isBionic(ctx.baseModuleName()) {
library.installSymlinkToRuntimeApex(ctx, file)
}
diff --git a/cc/linker.go b/cc/linker.go
index dda2fcb..fa3b0a6 100644
--- a/cc/linker.go
+++ b/cc/linker.go
@@ -483,7 +483,7 @@
Input: in,
Output: out,
Args: map[string]string{
- "buildNumberFromFile": ctx.Config().BuildNumberFromFile(),
+ "buildNumberFromFile": proptools.NinjaEscape(ctx.Config().BuildNumberFromFile()),
},
})
}
diff --git a/cc/sanitize.go b/cc/sanitize.go
index 0eb9a74..c59f53a 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -445,7 +445,6 @@
// libraries needed with -fsanitize=address. http://b/18650275 (WAI)
flags.LdFlags = append(flags.LdFlags, "-Wl,--no-as-needed")
} else {
- flags.CFlags = append(flags.CFlags, "-mllvm", "-asan-globals=0")
if ctx.bootstrap() {
flags.DynamicLinker = "/system/bin/bootstrap/linker_asan"
} else {
diff --git a/cmd/diff_target_files/target_files.go b/cmd/diff_target_files/target_files.go
index 8705ca7..0fa04e8 100644
--- a/cmd/diff_target_files/target_files.go
+++ b/cmd/diff_target_files/target_files.go
@@ -28,7 +28,7 @@
"ODM/",
"OEM/",
"PRODUCT/",
- "PRODUCT_SERVICES/",
+ "SYSTEM_EXT/",
"ROOT/",
"SYSTEM/",
"SYSTEM_OTHER/",
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index 41c7d46..30381e0 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -18,7 +18,12 @@
"flag"
"fmt"
"os"
+ "os/exec"
"path/filepath"
+ "strconv"
+ "strings"
+ "syscall"
+ "time"
"github.com/google/blueprint/bootstrap"
@@ -50,6 +55,42 @@
}
func main() {
+ if android.SoongDelveListen != "" {
+ if android.SoongDelvePath == "" {
+ fmt.Fprintln(os.Stderr, "SOONG_DELVE is set but failed to find dlv")
+ os.Exit(1)
+ }
+ pid := strconv.Itoa(os.Getpid())
+ cmd := []string{android.SoongDelvePath,
+ "attach", pid,
+ "--headless",
+ "-l", android.SoongDelveListen,
+ "--api-version=2",
+ "--accept-multiclient",
+ "--log",
+ }
+
+ fmt.Println("Starting", strings.Join(cmd, " "))
+ dlv := exec.Command(cmd[0], cmd[1:]...)
+ dlv.Stdout = os.Stdout
+ dlv.Stderr = os.Stderr
+ dlv.Stdin = nil
+
+ // Put dlv into its own process group so we can kill it and the child process it starts.
+ dlv.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
+
+ err := dlv.Start()
+ if err != nil {
+ // Print the error starting dlv and continue.
+ fmt.Println(err)
+ } else {
+ // Kill the process group for dlv when soong_build exits.
+ defer syscall.Kill(-dlv.Process.Pid, syscall.SIGKILL)
+ // Wait to give dlv a chance to connect and pause the process.
+ time.Sleep(time.Second)
+ }
+ }
+
flag.Parse()
// The top-level Blueprints file is passed as the first argument.
@@ -72,7 +113,17 @@
ctx.SetAllowMissingDependencies(configuration.AllowMissingDependencies())
- bootstrap.Main(ctx.Context, configuration, configuration.ConfigFileName, configuration.ProductVariablesFileName)
+ extraNinjaDeps := []string{configuration.ConfigFileName, configuration.ProductVariablesFileName}
+
+ // Read the SOONG_DELVE again through configuration so that there is a dependency on the environment variable
+ // and soong_build will rerun when it is set for the first time.
+ if listen := configuration.Getenv("SOONG_DELVE"); listen != "" {
+ // Add a non-existent file to the dependencies so that soong_build will rerun when the debugger is
+ // enabled even if it completed successfully.
+ extraNinjaDeps = append(extraNinjaDeps, filepath.Join(configuration.BuildDir(), "always_rerun_for_delve"))
+ }
+
+ bootstrap.Main(ctx.Context, configuration, extraNinjaDeps...)
if docFile != "" {
if err := writeDocs(ctx, docFile); err != nil {
diff --git a/cmd/soong_ui/main.go b/cmd/soong_ui/main.go
index b63918f..4d59a39 100644
--- a/cmd/soong_ui/main.go
+++ b/cmd/soong_ui/main.go
@@ -161,6 +161,8 @@
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")))
+ stat.AddOutput(status.NewProtoErrorLog(log, filepath.Join(logsDir, "build_error")))
+ stat.AddOutput(status.NewCriticalPath(log))
defer met.Dump(filepath.Join(logsDir, "soong_metrics"))
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index a2f1af4..3e32958 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -79,13 +79,11 @@
InstructionSetFeatures map[android.ArchType]string // instruction set for each architecture
// Only used for boot image
- DirtyImageObjects android.OptionalPath // path to a dirty-image-objects file
- PreloadedClasses android.OptionalPath // path to a preloaded-classes file
- BootImageProfiles android.Paths // path to a boot-image-profile.txt file
- UseProfileForBootImage bool // whether a profile should be used to compile the boot image
- BootFlags string // extra flags to pass to dex2oat for the boot image
- Dex2oatImageXmx string // max heap size for dex2oat for the boot image
- Dex2oatImageXms string // initial heap size for dex2oat for the boot image
+ DirtyImageObjects android.OptionalPath // path to a dirty-image-objects file
+ BootImageProfiles android.Paths // path to a boot-image-profile.txt file
+ BootFlags string // extra flags to pass to dex2oat for the boot image
+ Dex2oatImageXmx string // max heap size for dex2oat for the boot image
+ Dex2oatImageXms string // initial heap size for dex2oat for the boot image
Tools Tools // paths to tools possibly used by the generated commands
}
@@ -183,7 +181,6 @@
// Copies of entries in GlobalConfig that are not constructable without extra parameters. They will be
// used to construct the real value manually below.
DirtyImageObjects string
- PreloadedClasses string
BootImageProfiles []string
Tools struct {
@@ -206,7 +203,6 @@
// Construct paths that require a PathContext.
config.GlobalConfig.DirtyImageObjects = android.OptionalPathForPath(constructPath(ctx, config.DirtyImageObjects))
- config.GlobalConfig.PreloadedClasses = android.OptionalPathForPath(constructPath(ctx, config.PreloadedClasses))
config.GlobalConfig.BootImageProfiles = constructPaths(ctx, config.BootImageProfiles)
config.GlobalConfig.Tools.Profman = constructPath(ctx, config.Tools.Profman)
@@ -321,9 +317,7 @@
CpuVariant: nil,
InstructionSetFeatures: nil,
DirtyImageObjects: android.OptionalPath{},
- PreloadedClasses: android.OptionalPath{},
BootImageProfiles: nil,
- UseProfileForBootImage: false,
BootFlags: "",
Dex2oatImageXmx: "",
Dex2oatImageXms: "",
diff --git a/genrule/genrule.go b/genrule/genrule.go
index b0657ff..411da05 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -284,12 +284,12 @@
referencedDepfile := false
- rawCommand, err := android.Expand(task.cmd, func(name string) (string, error) {
+ rawCommand, err := android.ExpandNinjaEscaped(task.cmd, func(name string) (string, bool, 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) {
+ reportError := func(fmt string, args ...interface{}) (string, bool, error) {
ctx.PropertyErrorf("cmd", fmt, args...)
- return "SOONG_ERROR", nil
+ return "SOONG_ERROR", false, nil
}
switch name {
@@ -304,19 +304,19 @@
return reportError("default label %q has multiple files, use $(locations %s) to reference it",
firstLabel, firstLabel)
}
- return locationLabels[firstLabel][0], nil
+ return locationLabels[firstLabel][0], false, nil
case "in":
- return "${in}", nil
+ return "${in}", true, nil
case "out":
- return "__SBOX_OUT_FILES__", nil
+ return "__SBOX_OUT_FILES__", false, nil
case "depfile":
referencedDepfile = true
if !Bool(g.properties.Depfile) {
return reportError("$(depfile) used without depfile property")
}
- return "__SBOX_DEPFILE__", nil
+ return "__SBOX_DEPFILE__", false, nil
case "genDir":
- return "__SBOX_OUT_DIR__", nil
+ return "__SBOX_OUT_DIR__", false, nil
default:
if strings.HasPrefix(name, "location ") {
label := strings.TrimSpace(strings.TrimPrefix(name, "location "))
@@ -327,7 +327,7 @@
return reportError("label %q has multiple files, use $(locations %s) to reference it",
label, label)
}
- return paths[0], nil
+ return paths[0], false, nil
} else {
return reportError("unknown location label %q", label)
}
@@ -337,7 +337,7 @@
if len(paths) == 0 {
return reportError("label %q has no files", label)
}
- return strings.Join(paths, " "), nil
+ return strings.Join(paths, " "), false, nil
} else {
return reportError("unknown locations label %q", label)
}
diff --git a/java/aapt2.go b/java/aapt2.go
index ad746f7..f0eb99c 100644
--- a/java/aapt2.go
+++ b/java/aapt2.go
@@ -55,7 +55,7 @@
var aapt2CompileRule = pctx.AndroidStaticRule("aapt2Compile",
blueprint.RuleParams{
- Command: `${config.Aapt2Cmd} compile -o $outDir $cFlags --legacy $in`,
+ Command: `${config.Aapt2Cmd} compile -o $outDir $cFlags $in`,
CommandDeps: []string{"${config.Aapt2Cmd}"},
},
"outDir", "cFlags")
@@ -97,7 +97,7 @@
var aapt2CompileZipRule = pctx.AndroidStaticRule("aapt2CompileZip",
blueprint.RuleParams{
Command: `${config.ZipSyncCmd} -d $resZipDir $zipSyncFlags $in && ` +
- `${config.Aapt2Cmd} compile -o $out $cFlags --legacy --dir $resZipDir`,
+ `${config.Aapt2Cmd} compile -o $out $cFlags --dir $resZipDir`,
CommandDeps: []string{
"${config.Aapt2Cmd}",
"${config.ZipSyncCmd}",
diff --git a/java/aar.go b/java/aar.go
index 51a6115..ce3d126 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -17,6 +17,7 @@
import (
"android/soong/android"
"fmt"
+ "path/filepath"
"strings"
"github.com/google/blueprint"
@@ -80,6 +81,7 @@
rTxt android.Path
extraAaptPackagesFile android.Path
mergedManifestFile android.Path
+ noticeFile android.OptionalPath
isLibrary bool
useEmbeddedNativeLibs bool
useEmbeddedDex bool
@@ -151,10 +153,16 @@
assetFiles = append(assetFiles, androidResourceGlob(ctx, dir)...)
}
+ assetDirStrings := assetDirs.Strings()
+ if a.noticeFile.Valid() {
+ assetDirStrings = append(assetDirStrings, filepath.Dir(a.noticeFile.Path().String()))
+ assetFiles = append(assetFiles, a.noticeFile.Path())
+ }
+
linkFlags = append(linkFlags, "--manifest "+manifestPath.String())
linkDeps = append(linkDeps, manifestPath)
- linkFlags = append(linkFlags, android.JoinWithPrefix(assetDirs.Strings(), "-A "))
+ linkFlags = append(linkFlags, android.JoinWithPrefix(assetDirStrings, "-A "))
linkDeps = append(linkDeps, assetFiles...)
// SDK version flags
@@ -502,7 +510,7 @@
}
func (a *AARImport) sdkVersion() string {
- return String(a.properties.Sdk_version)
+ return proptools.StringDefault(a.properties.Sdk_version, defaultSdkVersion(a))
}
func (a *AARImport) minSdkVersion() string {
@@ -516,10 +524,6 @@
return a.sdkVersion()
}
-func (a *AARImport) noFrameworkLibs() bool {
- return false
-}
-
var _ AndroidLibraryDependency = (*AARImport)(nil)
func (a *AARImport) ExportPackage() android.Path {
diff --git a/java/androidmk.go b/java/androidmk.go
index 39c2d13..90fdd0f 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -327,6 +327,18 @@
install := "$(LOCAL_MODULE_PATH)/" + strings.TrimSuffix(app.installApkName, ".apk") + split.suffix + ".apk"
fmt.Fprintln(w, "LOCAL_SOONG_BUILT_INSTALLED +=", split.path.String()+":"+install)
}
+ if app.noticeOutputs.Merged.Valid() {
+ fmt.Fprintf(w, "$(call dist-for-goals,%s,%s:%s)\n",
+ app.installApkName, app.noticeOutputs.Merged.String(), app.installApkName+"_NOTICE")
+ }
+ if app.noticeOutputs.TxtOutput.Valid() {
+ fmt.Fprintf(w, "$(call dist-for-goals,%s,%s:%s)\n",
+ app.installApkName, app.noticeOutputs.TxtOutput.String(), app.installApkName+"_NOTICE.txt")
+ }
+ if app.noticeOutputs.HtmlOutput.Valid() {
+ fmt.Fprintf(w, "$(call dist-for-goals,%s,%s:%s)\n",
+ app.installApkName, app.noticeOutputs.HtmlOutput.String(), app.installApkName+"_NOTICE.html")
+ }
},
},
}
diff --git a/java/androidmk_test.go b/java/androidmk_test.go
new file mode 100644
index 0000000..107837d
--- /dev/null
+++ b/java/androidmk_test.go
@@ -0,0 +1,184 @@
+// Copyright 2019 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"
+ "bytes"
+ "io"
+ "io/ioutil"
+ "strings"
+ "testing"
+)
+
+type testAndroidMk struct {
+ *testing.T
+ body []byte
+}
+type testAndroidMkModule struct {
+ *testing.T
+ props map[string]string
+}
+
+func newTestAndroidMk(t *testing.T, r io.Reader) *testAndroidMk {
+ t.Helper()
+ buf, err := ioutil.ReadAll(r)
+ if err != nil {
+ t.Fatal("failed to open read Android.mk.", err)
+ }
+ return &testAndroidMk{
+ T: t,
+ body: buf,
+ }
+}
+
+func parseAndroidMkProps(lines []string) map[string]string {
+ props := make(map[string]string)
+ for _, line := range lines {
+ line = strings.TrimLeft(line, " ")
+ if line == "" || strings.HasPrefix(line, "#") {
+ continue
+ }
+ tokens := strings.Split(line, " ")
+ if tokens[1] == "+=" {
+ props[tokens[0]] += " " + strings.Join(tokens[2:], " ")
+ } else {
+ props[tokens[0]] = strings.Join(tokens[2:], " ")
+ }
+ }
+ return props
+}
+
+func (t *testAndroidMk) moduleFor(moduleName string) *testAndroidMkModule {
+ t.Helper()
+ lines := strings.Split(string(t.body), "\n")
+ index := android.IndexList("LOCAL_MODULE := "+moduleName, lines)
+ if index == -1 {
+ t.Fatalf("%q is not found.", moduleName)
+ }
+ lines = lines[index:]
+ includeIndex := android.IndexListPred(func(line string) bool {
+ return strings.HasPrefix(line, "include")
+ }, lines)
+ if includeIndex == -1 {
+ t.Fatalf("%q is not properly defined. (\"include\" not found).", moduleName)
+ }
+ props := parseAndroidMkProps(lines[:includeIndex])
+ return &testAndroidMkModule{
+ T: t.T,
+ props: props,
+ }
+}
+
+func (t *testAndroidMkModule) hasRequired(dep string) {
+ t.Helper()
+ required, ok := t.props["LOCAL_REQUIRED_MODULES"]
+ if !ok {
+ t.Error("LOCAL_REQUIRED_MODULES is not found.")
+ return
+ }
+ if !android.InList(dep, strings.Split(required, " ")) {
+ t.Errorf("%q is expected in LOCAL_REQUIRED_MODULES, but not found in %q.", dep, required)
+ }
+}
+
+func (t *testAndroidMkModule) hasNoRequired(dep string) {
+ t.Helper()
+ required, ok := t.props["LOCAL_REQUIRED_MODULES"]
+ if !ok {
+ return
+ }
+ if android.InList(dep, strings.Split(required, " ")) {
+ t.Errorf("%q is not expected in LOCAL_REQUIRED_MODULES, but found.", dep)
+ }
+}
+
+func getAndroidMk(t *testing.T, ctx *android.TestContext, config android.Config, name string) *testAndroidMk {
+ t.Helper()
+ lib, _ := ctx.ModuleForTests(name, "android_common").Module().(*Library)
+ data := android.AndroidMkDataForTest(t, config, "", lib)
+ w := &bytes.Buffer{}
+ data.Custom(w, name, "", "", data)
+ return newTestAndroidMk(t, w)
+}
+
+func TestRequired(t *testing.T) {
+ config := testConfig(nil)
+ ctx := testContext(config, `
+ java_library {
+ name: "foo",
+ srcs: ["a.java"],
+ required: ["libfoo"],
+ }
+ `, nil)
+ run(t, ctx, config)
+
+ mk := getAndroidMk(t, ctx, config, "foo")
+ mk.moduleFor("foo").hasRequired("libfoo")
+}
+
+func TestHostdex(t *testing.T) {
+ config := testConfig(nil)
+ ctx := testContext(config, `
+ java_library {
+ name: "foo",
+ srcs: ["a.java"],
+ hostdex: true,
+ }
+ `, nil)
+ run(t, ctx, config)
+
+ mk := getAndroidMk(t, ctx, config, "foo")
+ mk.moduleFor("foo")
+ mk.moduleFor("foo-hostdex")
+}
+
+func TestHostdexRequired(t *testing.T) {
+ config := testConfig(nil)
+ ctx := testContext(config, `
+ java_library {
+ name: "foo",
+ srcs: ["a.java"],
+ hostdex: true,
+ required: ["libfoo"],
+ }
+ `, nil)
+ run(t, ctx, config)
+
+ mk := getAndroidMk(t, ctx, config, "foo")
+ mk.moduleFor("foo").hasRequired("libfoo")
+ mk.moduleFor("foo-hostdex").hasRequired("libfoo")
+}
+
+func TestHostdexSpecificRequired(t *testing.T) {
+ config := testConfig(nil)
+ ctx := testContext(config, `
+ java_library {
+ name: "foo",
+ srcs: ["a.java"],
+ hostdex: true,
+ target: {
+ hostdex: {
+ required: ["libfoo"],
+ },
+ },
+ }
+ `, nil)
+ run(t, ctx, config)
+
+ mk := getAndroidMk(t, ctx, config, "foo")
+ mk.moduleFor("foo").hasNoRequired("libfoo")
+ mk.moduleFor("foo-hostdex").hasRequired("libfoo")
+}
diff --git a/java/app.go b/java/app.go
index cab97de..a679e88 100644
--- a/java/app.go
+++ b/java/app.go
@@ -19,6 +19,7 @@
import (
"path/filepath"
"reflect"
+ "sort"
"strings"
"github.com/google/blueprint"
@@ -29,8 +30,7 @@
"android/soong/tradefed"
)
-var supportedDpis = [...]string{"Ldpi", "Mdpi", "Hdpi", "Xhdpi", "Xxhdpi", "Xxxhdpi"}
-var dpiVariantsStruct reflect.Type
+var supportedDpis = []string{"ldpi", "mdpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"}
func init() {
android.RegisterModuleType("android_app", AndroidAppFactory)
@@ -39,22 +39,6 @@
android.RegisterModuleType("android_app_certificate", AndroidAppCertificateFactory)
android.RegisterModuleType("override_android_app", OverrideAndroidAppModuleFactory)
android.RegisterModuleType("android_app_import", AndroidAppImportFactory)
-
- // Dynamically construct a struct for the dpi_variants property in android_app_import.
- perDpiStruct := reflect.StructOf([]reflect.StructField{
- {
- Name: "Apk",
- Type: reflect.TypeOf((*string)(nil)),
- },
- })
- dpiVariantsFields := make([]reflect.StructField, len(supportedDpis))
- for i, dpi := range supportedDpis {
- dpiVariantsFields[i] = reflect.StructField{
- Name: string(dpi),
- Type: perDpiStruct,
- }
- }
- dpiVariantsStruct = reflect.StructOf(dpiVariantsFields)
}
// AndroidManifest.xml merging
@@ -103,6 +87,10 @@
// Use_embedded_native_libs still selects whether they are stored uncompressed and aligned or compressed.
// True for android_test* modules.
AlwaysPackageNativeLibs bool `blueprint:"mutated"`
+
+ // If set, find and merge all NOTICE files that this module and its dependencies have and store
+ // it in the APK as an asset.
+ Embed_notices *bool
}
// android_app properties that can be overridden by override_android_app
@@ -136,6 +124,8 @@
installApkName string
additionalAaptFlags []string
+
+ noticeOutputs android.NoticeOutputs
}
func (a *AndroidApp) ExportedProguardFlagFiles() android.Paths {
@@ -224,15 +214,16 @@
return true
}
- if ctx.Config().UnbundledBuild() {
- return false
- }
-
- // Uncompress dex in APKs of privileged apps
+ // Uncompress dex in APKs of privileged apps (even for unbundled builds, they may
+ // be preinstalled as prebuilts).
if ctx.Config().UncompressPrivAppDex() && Bool(a.appProperties.Privileged) {
return true
}
+ if ctx.Config().UnbundledBuild() {
+ return false
+ }
+
return shouldUncompressDex(ctx, &a.dexpreopter)
}
@@ -351,6 +342,49 @@
return jniJarFile
}
+func (a *AndroidApp) noticeBuildActions(ctx android.ModuleContext, installDir android.OutputPath) {
+ // Collect NOTICE files from all dependencies.
+ seenModules := make(map[android.Module]bool)
+ noticePathSet := make(map[android.Path]bool)
+
+ ctx.WalkDeps(func(child android.Module, parent android.Module) bool {
+ // Have we already seen this?
+ if _, ok := seenModules[child]; ok {
+ return false
+ }
+ seenModules[child] = true
+
+ // Skip host modules.
+ if child.Target().Os.Class == android.Host || child.Target().Os.Class == android.HostCross {
+ return false
+ }
+
+ path := child.(android.Module).NoticeFile()
+ if path.Valid() {
+ noticePathSet[path.Path()] = true
+ }
+ return true
+ })
+
+ // If the app has one, add it too.
+ if a.NoticeFile().Valid() {
+ noticePathSet[a.NoticeFile().Path()] = true
+ }
+
+ if len(noticePathSet) == 0 {
+ return
+ }
+ var noticePaths []android.Path
+ for path := range noticePathSet {
+ noticePaths = append(noticePaths, path)
+ }
+ sort.Slice(noticePaths, func(i, j int) bool {
+ return noticePaths[i].String() < noticePaths[j].String()
+ })
+
+ a.noticeOutputs = android.BuildNoticeOutput(ctx, installDir, a.installApkName+".apk", noticePaths)
+}
+
// Reads and prepends a main cert from the default cert dir if it hasn't been set already, i.e. it
// isn't a cert module reference. Also checks and enforces system cert restriction if applicable.
func processMainCert(m android.ModuleBase, certPropValue string, certificates []Certificate, ctx android.ModuleContext) []Certificate {
@@ -391,6 +425,21 @@
// Check if the install APK name needs to be overridden.
a.installApkName = ctx.DeviceConfig().OverridePackageNameFor(a.Name())
+ var installDir android.OutputPath
+ if ctx.ModuleName() == "framework-res" {
+ // framework-res.apk is installed as system/framework/framework-res.apk
+ installDir = android.PathForModuleInstall(ctx, "framework")
+ } else if Bool(a.appProperties.Privileged) {
+ installDir = android.PathForModuleInstall(ctx, "priv-app", a.installApkName)
+ } else {
+ installDir = android.PathForModuleInstall(ctx, "app", a.installApkName)
+ }
+
+ a.noticeBuildActions(ctx, installDir)
+ if Bool(a.appProperties.Embed_notices) || ctx.Config().IsEnvTrue("ALWAYS_EMBED_NOTICES") {
+ a.aapt.noticeFile = a.noticeOutputs.HtmlGzOutput
+ }
+
// Process all building blocks, from AAPT to certificates.
a.aaptBuildActions(ctx)
@@ -432,16 +481,6 @@
a.bundleFile = bundleFile
// Install the app package.
- var installDir android.OutputPath
- if ctx.ModuleName() == "framework-res" {
- // framework-res.apk is installed as system/framework/framework-res.apk
- installDir = android.PathForModuleInstall(ctx, "framework")
- } else if Bool(a.appProperties.Privileged) {
- installDir = android.PathForModuleInstall(ctx, "priv-app", a.installApkName)
- } else {
- installDir = android.PathForModuleInstall(ctx, "app", a.installApkName)
- }
-
ctx.InstallFile(installDir, a.installApkName+".apk", a.outputFile)
for _, split := range a.aapt.splits {
ctx.InstallFile(installDir, a.installApkName+"_"+split.suffix+".apk", split.path)
@@ -688,7 +727,8 @@
android.DefaultableModuleBase
prebuilt android.Prebuilt
- properties AndroidAppImportProperties
+ properties AndroidAppImportProperties
+ dpiVariants interface{}
outputFile android.Path
certificate *Certificate
@@ -700,27 +740,7 @@
type AndroidAppImportProperties struct {
// A prebuilt apk to import
- Apk string
-
- // Per-DPI settings. This property makes it possible to specify a different source apk path for
- // each DPI.
- //
- // Example:
- //
- // android_app_import {
- // name: "example_import",
- // apk: "prebuilts/example.apk",
- // dpi_variants: {
- // mdpi: {
- // apk: "prebuilts/example_mdpi.apk",
- // },
- // xhdpi: {
- // apk: "prebuilts/example_xhdpi.apk",
- // },
- // },
- // certificate: "PRESIGNED",
- // }
- Dpi_variants interface{}
+ Apk *string
// The name of a certificate in the default certificate directory, blank to use the default
// product certificate, or an android_app_certificate module name in the form ":module".
@@ -743,39 +763,43 @@
Overrides []string
}
-func getApkPathForDpi(dpiVariantsValue reflect.Value, dpi string) string {
- dpiField := dpiVariantsValue.FieldByName(proptools.FieldNameForProperty(dpi))
- if !dpiField.IsValid() {
- return ""
+// Chooses a source APK path to use based on the module and product specs.
+func (a *AndroidAppImport) updateSrcApkPath(ctx android.LoadHookContext) {
+ config := ctx.Config()
+
+ dpiProps := reflect.ValueOf(a.dpiVariants).Elem().FieldByName("Dpi_variants")
+ // Try DPI variant matches in the reverse-priority order so that the highest priority match
+ // overwrites everything else.
+ // TODO(jungjw): Can we optimize this by making it priority order?
+ for i := len(config.ProductAAPTPrebuiltDPI()) - 1; i >= 0; i-- {
+ dpi := config.ProductAAPTPrebuiltDPI()[i]
+ if inList(dpi, supportedDpis) {
+ MergePropertiesFromVariant(ctx, &a.properties, dpiProps, dpi, "dpi_variants")
+ }
}
- apkValue := dpiField.FieldByName("Apk").Elem()
- if apkValue.IsValid() {
- return apkValue.String()
+ if config.ProductAAPTPreferredConfig() != "" {
+ dpi := config.ProductAAPTPreferredConfig()
+ if inList(dpi, supportedDpis) {
+ MergePropertiesFromVariant(ctx, &a.properties, dpiProps, dpi, "dpi_variants")
+ }
}
- return ""
}
-// Chooses a source APK path to use based on the module's per-DPI settings and the product config.
-func (a *AndroidAppImport) getSrcApkPath(ctx android.ModuleContext) string {
- config := ctx.Config()
- dpiVariantsValue := reflect.ValueOf(a.properties.Dpi_variants).Elem()
- if !dpiVariantsValue.IsValid() {
- return a.properties.Apk
- }
- // Match PRODUCT_AAPT_PREF_CONFIG first and then PRODUCT_AAPT_PREBUILT_DPI.
- if config.ProductAAPTPreferredConfig() != "" {
- if apk := getApkPathForDpi(dpiVariantsValue, config.ProductAAPTPreferredConfig()); apk != "" {
- return apk
- }
- }
- for _, dpi := range config.ProductAAPTPrebuiltDPI() {
- if apk := getApkPathForDpi(dpiVariantsValue, dpi); apk != "" {
- return apk
- }
+func MergePropertiesFromVariant(ctx android.BaseModuleContext,
+ dst interface{}, variantGroup reflect.Value, variant, variantGroupPath string) {
+ src := variantGroup.FieldByName(proptools.FieldNameForProperty(variant))
+ if !src.IsValid() {
+ ctx.ModuleErrorf("field %q does not exist", variantGroupPath+"."+variant)
}
- // No match. Use the generic one.
- return a.properties.Apk
+ err := proptools.ExtendMatchingProperties([]interface{}{dst}, src.Interface(), nil, proptools.OrderAppend)
+ if err != nil {
+ if propertyErr, ok := err.(*proptools.ExtendPropertyError); ok {
+ ctx.PropertyErrorf(propertyErr.Property, "%s", propertyErr.Err.Error())
+ } else {
+ panic(err)
+ }
+ }
}
func (a *AndroidAppImport) DepsMutator(ctx android.BottomUpMutatorContext) {
@@ -792,7 +816,7 @@
rule := android.NewRuleBuilder()
rule.Command().
Textf(`if (zipinfo %s 'lib/*.so' 2>/dev/null | grep -v ' stor ' >/dev/null) ; then`, inputPath).
- Tool(ctx.Config().HostToolPath(ctx, "zip2zip")).
+ BuiltTool(ctx, "zip2zip").
FlagWithInput("-i ", inputPath).
FlagWithOutput("-o ", outputPath).
FlagWithArg("-0 ", "'lib/**/*.so'").
@@ -819,7 +843,7 @@
rule := android.NewRuleBuilder()
rule.Command().
Textf(`if (zipinfo %s '*.dex' 2>/dev/null | grep -v ' stor ' >/dev/null) ; then`, inputPath).
- Tool(ctx.Config().HostToolPath(ctx, "zip2zip")).
+ BuiltTool(ctx, "zip2zip").
FlagWithInput("-i ", inputPath).
FlagWithOutput("-o ", outputPath).
FlagWithArg("-0 ", "'classes*.dex'").
@@ -840,8 +864,7 @@
// TODO: LOCAL_EXTRACT_APK/LOCAL_EXTRACT_DPI_APK
// TODO: LOCAL_PACKAGE_SPLITS
- var srcApk android.Path
- srcApk = android.PathForModuleSrc(ctx, a.getSrcApkPath(ctx))
+ srcApk := a.prebuilt.SingleSourcePath(ctx)
if a.usesLibrary.enforceUsesLibraries() {
srcApk = a.usesLibrary.verifyUsesLibrariesAPK(ctx, srcApk)
@@ -903,16 +926,56 @@
return a.prebuilt.Name(a.ModuleBase.Name())
}
+// Populates dpi_variants property and its fields at creation time.
+func (a *AndroidAppImport) addDpiVariants() {
+ // TODO(jungjw): Do we want to do some filtering here?
+ props := reflect.ValueOf(&a.properties).Type()
+
+ dpiFields := make([]reflect.StructField, len(supportedDpis))
+ for i, dpi := range supportedDpis {
+ dpiFields[i] = reflect.StructField{
+ Name: proptools.FieldNameForProperty(dpi),
+ Type: props,
+ }
+ }
+ dpiStruct := reflect.StructOf(dpiFields)
+ a.dpiVariants = reflect.New(reflect.StructOf([]reflect.StructField{
+ {
+ Name: "Dpi_variants",
+ Type: dpiStruct,
+ },
+ })).Interface()
+ a.AddProperties(a.dpiVariants)
+}
+
// android_app_import imports a prebuilt apk with additional processing specified in the module.
+// DPI-specific apk source files can be specified using dpi_variants. Example:
+//
+// android_app_import {
+// name: "example_import",
+// apk: "prebuilts/example.apk",
+// dpi_variants: {
+// mdpi: {
+// apk: "prebuilts/example_mdpi.apk",
+// },
+// xhdpi: {
+// apk: "prebuilts/example_xhdpi.apk",
+// },
+// },
+// certificate: "PRESIGNED",
+// }
func AndroidAppImportFactory() android.Module {
module := &AndroidAppImport{}
- module.properties.Dpi_variants = reflect.New(dpiVariantsStruct).Interface()
module.AddProperties(&module.properties)
module.AddProperties(&module.dexpreoptProperties)
module.AddProperties(&module.usesLibrary.usesLibraryProperties)
+ module.addDpiVariants()
+ android.AddLoadHook(module, func(ctx android.LoadHookContext) {
+ module.updateSrcApkPath(ctx)
+ })
InitJavaModule(module, android.DeviceSupported)
- android.InitSingleSourcePrebuiltModule(module, &module.properties.Apk)
+ android.InitSingleSourcePrebuiltModule(module, &module.properties, "Apk")
return module
}
@@ -1004,7 +1067,7 @@
outputFile := android.PathForModuleOut(ctx, "manifest_check", "AndroidManifest.xml")
rule := android.NewRuleBuilder()
- cmd := rule.Command().Tool(ctx.Config().HostToolPath(ctx, "manifest_check")).
+ cmd := rule.Command().BuiltTool(ctx, "manifest_check").
Flag("--enforce-uses-libraries").
Input(manifest).
FlagWithOutput("-o ", outputFile)
diff --git a/java/app_test.go b/java/app_test.go
index 27802cd..f08969d 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -546,79 +546,6 @@
}
}
-func TestJNIABI_no_framework_libs_true(t *testing.T) {
- ctx := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+`
- cc_library {
- name: "libjni",
- system_shared_libs: [],
- stl: "none",
- }
-
- android_test {
- name: "test",
- no_framework_libs: true,
- jni_libs: ["libjni"],
- }
-
- android_test {
- name: "test_first",
- no_framework_libs: true,
- compile_multilib: "first",
- jni_libs: ["libjni"],
- }
-
- android_test {
- name: "test_both",
- no_framework_libs: true,
- compile_multilib: "both",
- jni_libs: ["libjni"],
- }
-
- android_test {
- name: "test_32",
- no_framework_libs: true,
- compile_multilib: "32",
- jni_libs: ["libjni"],
- }
-
- android_test {
- name: "test_64",
- no_framework_libs: true,
- compile_multilib: "64",
- jni_libs: ["libjni"],
- }
- `)
-
- testCases := []struct {
- name string
- abis []string
- }{
- {"test", []string{"arm64-v8a"}},
- {"test_first", []string{"arm64-v8a"}},
- {"test_both", []string{"arm64-v8a", "armeabi-v7a"}},
- {"test_32", []string{"armeabi-v7a"}},
- {"test_64", []string{"arm64-v8a"}},
- }
-
- for _, test := range testCases {
- t.Run(test.name, func(t *testing.T) {
- app := ctx.ModuleForTests(test.name, "android_common")
- jniLibZip := app.Output("jnilibs.zip")
- var abis []string
- args := strings.Fields(jniLibZip.Args["jarArgs"])
- for i := 0; i < len(args); i++ {
- if args[i] == "-P" {
- abis = append(abis, filepath.Base(args[i+1]))
- i++
- }
- }
- if !reflect.DeepEqual(abis, test.abis) {
- t.Errorf("want abis %v, got %v", test.abis, abis)
- }
- })
- }
-}
-
func TestJNIABI(t *testing.T) {
ctx := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+`
cc_library {
@@ -692,89 +619,6 @@
}
}
-func TestJNIPackaging_no_framework_libs_true(t *testing.T) {
- ctx := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+`
- cc_library {
- name: "libjni",
- system_shared_libs: [],
- stl: "none",
- }
-
- android_app {
- name: "app",
- jni_libs: ["libjni"],
- }
-
- android_app {
- name: "app_noembed",
- jni_libs: ["libjni"],
- use_embedded_native_libs: false,
- }
-
- android_app {
- name: "app_embed",
- jni_libs: ["libjni"],
- use_embedded_native_libs: true,
- }
-
- android_test {
- name: "test",
- no_framework_libs: true,
- jni_libs: ["libjni"],
- }
-
- android_test {
- name: "test_noembed",
- no_framework_libs: true,
- jni_libs: ["libjni"],
- use_embedded_native_libs: false,
- }
-
- android_test_helper_app {
- name: "test_helper",
- no_framework_libs: true,
- jni_libs: ["libjni"],
- }
-
- android_test_helper_app {
- name: "test_helper_noembed",
- no_framework_libs: true,
- jni_libs: ["libjni"],
- use_embedded_native_libs: false,
- }
- `)
-
- testCases := []struct {
- name string
- packaged bool
- compressed bool
- }{
- {"app", false, false},
- {"app_noembed", false, false},
- {"app_embed", true, false},
- {"test", true, false},
- {"test_noembed", true, true},
- {"test_helper", true, false},
- {"test_helper_noembed", true, true},
- }
-
- for _, test := range testCases {
- t.Run(test.name, func(t *testing.T) {
- app := ctx.ModuleForTests(test.name, "android_common")
- jniLibZip := app.MaybeOutput("jnilibs.zip")
- if g, w := (jniLibZip.Rule != nil), test.packaged; g != w {
- t.Errorf("expected jni packaged %v, got %v", w, g)
- }
-
- if jniLibZip.Rule != nil {
- if g, w := !strings.Contains(jniLibZip.Args["jarArgs"], "-L 0"), test.compressed; g != w {
- t.Errorf("expected jni compressed %v, got %v", w, g)
- }
- }
- })
- }
-}
-
func TestJNIPackaging(t *testing.T) {
ctx := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+`
cc_library {
@@ -1294,7 +1138,7 @@
{
name: "AAPTPreferredConfig matches",
aaptPreferredConfig: proptools.StringPtr("xhdpi"),
- aaptPrebuiltDPI: []string{"xxhdpi", "lhdpi"},
+ aaptPrebuiltDPI: []string{"xxhdpi", "ldpi"},
expected: "prebuilts/apk/app_xhdpi.apk",
},
{
@@ -1552,3 +1396,185 @@
})
}
}
+
+func TestEmbedNotice(t *testing.T) {
+ ctx := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+`
+ android_app {
+ name: "foo",
+ srcs: ["a.java"],
+ static_libs: ["javalib"],
+ jni_libs: ["libjni"],
+ notice: "APP_NOTICE",
+ embed_notices: true,
+ }
+
+ // No embed_notice flag
+ android_app {
+ name: "bar",
+ srcs: ["a.java"],
+ jni_libs: ["libjni"],
+ notice: "APP_NOTICE",
+ }
+
+ // No NOTICE files
+ android_app {
+ name: "baz",
+ srcs: ["a.java"],
+ embed_notices: true,
+ }
+
+ cc_library {
+ name: "libjni",
+ system_shared_libs: [],
+ stl: "none",
+ notice: "LIB_NOTICE",
+ }
+
+ java_library {
+ name: "javalib",
+ srcs: [
+ ":gen",
+ ],
+ }
+
+ genrule {
+ name: "gen",
+ tools: ["gentool"],
+ out: ["gen.java"],
+ notice: "GENRULE_NOTICE",
+ }
+
+ java_binary_host {
+ name: "gentool",
+ srcs: ["b.java"],
+ notice: "TOOL_NOTICE",
+ }
+ `)
+
+ // foo has NOTICE files to process, and embed_notices is true.
+ foo := ctx.ModuleForTests("foo", "android_common")
+ // verify merge notices rule.
+ mergeNotices := foo.Rule("mergeNoticesRule")
+ noticeInputs := mergeNotices.Inputs.Strings()
+ // TOOL_NOTICE should be excluded as it's a host module.
+ if len(mergeNotices.Inputs) != 3 {
+ t.Errorf("number of input notice files: expected = 3, actual = %q", noticeInputs)
+ }
+ if !inList("APP_NOTICE", noticeInputs) {
+ t.Errorf("APP_NOTICE is missing from notice files, %q", noticeInputs)
+ }
+ if !inList("LIB_NOTICE", noticeInputs) {
+ t.Errorf("LIB_NOTICE is missing from notice files, %q", noticeInputs)
+ }
+ if !inList("GENRULE_NOTICE", noticeInputs) {
+ t.Errorf("GENRULE_NOTICE is missing from notice files, %q", noticeInputs)
+ }
+ // aapt2 flags should include -A <NOTICE dir> so that its contents are put in the APK's /assets.
+ res := foo.Output("package-res.apk")
+ aapt2Flags := res.Args["flags"]
+ e := "-A " + buildDir + "/.intermediates/foo/android_common/NOTICE"
+ if !strings.Contains(aapt2Flags, e) {
+ t.Errorf("asset dir flag for NOTICE, %q is missing in aapt2 link flags, %q", e, aapt2Flags)
+ }
+
+ // bar has NOTICE files to process, but embed_notices is not set.
+ bar := ctx.ModuleForTests("bar", "android_common")
+ res = bar.Output("package-res.apk")
+ aapt2Flags = res.Args["flags"]
+ e = "-A " + buildDir + "/.intermediates/bar/android_common/NOTICE"
+ if strings.Contains(aapt2Flags, e) {
+ t.Errorf("bar shouldn't have the asset dir flag for NOTICE: %q", e)
+ }
+
+ // baz's embed_notice is true, but it doesn't have any NOTICE files.
+ baz := ctx.ModuleForTests("baz", "android_common")
+ res = baz.Output("package-res.apk")
+ aapt2Flags = res.Args["flags"]
+ e = "-A " + buildDir + "/.intermediates/baz/android_common/NOTICE"
+ if strings.Contains(aapt2Flags, e) {
+ t.Errorf("baz shouldn't have the asset dir flag for NOTICE: %q", e)
+ }
+}
+
+func TestUncompressDex(t *testing.T) {
+ testCases := []struct {
+ name string
+ bp string
+
+ uncompressedPlatform bool
+ uncompressedUnbundled bool
+ }{
+ {
+ name: "normal",
+ bp: `
+ android_app {
+ name: "foo",
+ srcs: ["a.java"],
+ }
+ `,
+ uncompressedPlatform: true,
+ uncompressedUnbundled: false,
+ },
+ {
+ name: "use_embedded_dex",
+ bp: `
+ android_app {
+ name: "foo",
+ use_embedded_dex: true,
+ srcs: ["a.java"],
+ }
+ `,
+ uncompressedPlatform: true,
+ uncompressedUnbundled: true,
+ },
+ {
+ name: "privileged",
+ bp: `
+ android_app {
+ name: "foo",
+ privileged: true,
+ srcs: ["a.java"],
+ }
+ `,
+ uncompressedPlatform: true,
+ uncompressedUnbundled: true,
+ },
+ }
+
+ test := func(t *testing.T, bp string, want bool, unbundled bool) {
+ t.Helper()
+
+ config := testConfig(nil)
+ if unbundled {
+ config.TestProductVariables.Unbundled_build = proptools.BoolPtr(true)
+ }
+
+ ctx := testAppContext(config, bp, nil)
+
+ run(t, ctx, config)
+
+ foo := ctx.ModuleForTests("foo", "android_common")
+ dex := foo.Rule("r8")
+ uncompressedInDexJar := strings.Contains(dex.Args["zipFlags"], "-L 0")
+ aligned := foo.MaybeRule("zipalign").Rule != nil
+
+ if uncompressedInDexJar != want {
+ t.Errorf("want uncompressed in dex %v, got %v", want, uncompressedInDexJar)
+ }
+
+ if aligned != want {
+ t.Errorf("want aligned %v, got %v", want, aligned)
+ }
+ }
+
+ for _, tt := range testCases {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Run("platform", func(t *testing.T) {
+ test(t, tt.bp, tt.uncompressedPlatform, false)
+ })
+ t.Run("unbundled", func(t *testing.T) {
+ test(t, tt.bp, tt.uncompressedUnbundled, true)
+ })
+ })
+ }
+}
diff --git a/java/config/config.go b/java/config/config.go
index 6ade649..d017ae6 100644
--- a/java/config/config.go
+++ b/java/config/config.go
@@ -52,7 +52,7 @@
pctx.StaticVariable("JavacHeapSize", "2048M")
pctx.StaticVariable("JavacHeapFlags", "-J-Xmx${JavacHeapSize}")
- pctx.StaticVariable("DexFlags", "-JXX:+TieredCompilation -JXX:TieredStopAtLevel=1")
+ pctx.StaticVariable("DexFlags", "-JXX:OnError='cat hs_err_pid%p.log' -JXX:CICompilerCount=6 -JXX:+UseDynamicNumberOfGCThreads")
pctx.StaticVariable("CommonJdkFlags", strings.Join([]string{
`-Xmaxerrs 9999999`,
diff --git a/java/device_host_converter_test.go b/java/device_host_converter_test.go
index 146bf6f..9b9d0d8 100644
--- a/java/device_host_converter_test.go
+++ b/java/device_host_converter_test.go
@@ -126,7 +126,7 @@
java_library {
name: "device_module",
- no_framework_libs: true,
+ sdk_version: "core_platform",
srcs: ["b.java"],
java_resources: ["java-res/b/b"],
static_libs: ["host_for_device_module"],
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index eb735c1..4ef5bcf 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -229,7 +229,7 @@
if image.zip != nil {
rule := android.NewRuleBuilder()
rule.Command().
- Tool(ctx.Config().HostToolPath(ctx, "soong_zip")).
+ BuiltTool(ctx, "soong_zip").
FlagWithOutput("-o ", image.zip).
FlagWithArg("-C ", image.dir.String()).
FlagWithInputList("-f ", allFiles, " -f ")
@@ -286,8 +286,6 @@
if profile != nil {
cmd.FlagWithArg("--compiler-filter=", "speed-profile")
cmd.FlagWithInput("--profile-file=", profile)
- } else if global.PreloadedClasses.Valid() {
- cmd.FlagWithInput("--image-classes=", global.PreloadedClasses.Path())
}
if global.DirtyImageObjects.Valid() {
@@ -374,7 +372,7 @@
func bootImageProfileRule(ctx android.SingletonContext, image *bootImage, missingDeps []string) android.WritablePath {
global := dexpreoptGlobalConfig(ctx)
- if !global.UseProfileForBootImage || ctx.Config().IsPdkBuild() || ctx.Config().UnbundledBuild() {
+ if global.DisableGenerateProfile || ctx.Config().IsPdkBuild() || ctx.Config().UnbundledBuild() {
return nil
}
return ctx.Config().Once(bootImageProfileRuleKey, func() interface{} {
@@ -440,7 +438,7 @@
rule := android.NewRuleBuilder()
rule.Command().
// TODO: for now, use the debug version for better error reporting
- Tool(ctx.Config().HostToolPath(ctx, "oatdumpd")).
+ BuiltTool(ctx, "oatdumpd").
FlagWithInputList("--runtime-arg -Xbootclasspath:", image.dexPaths.Paths(), ":").
FlagWithList("--runtime-arg -Xbootclasspath-locations:", image.dexLocations, ":").
FlagWithArg("--image=", dexpreopt.PathToLocation(image.images[arch], arch)).Implicit(image.images[arch]).
diff --git a/java/dexpreopt_bootjars_test.go b/java/dexpreopt_bootjars_test.go
index cbb52f1..f91ff69 100644
--- a/java/dexpreopt_bootjars_test.go
+++ b/java/dexpreopt_bootjars_test.go
@@ -62,6 +62,7 @@
bootArt := dexpreoptBootJars.Output("boot.art")
expectedInputs := []string{
+ "dex_bootjars/boot.prof",
"dex_bootjars_input/foo.jar",
"dex_bootjars_input/bar.jar",
"dex_bootjars_input/baz.jar",
diff --git a/java/dexpreopt_config.go b/java/dexpreopt_config.go
index c396d3e..4a4d6d5 100644
--- a/java/dexpreopt_config.go
+++ b/java/dexpreopt_config.go
@@ -49,7 +49,8 @@
return ctx.Config().Once(dexpreoptTestGlobalConfigKey, func() interface{} {
// Nope, return a config with preopting disabled
return globalConfigAndRaw{dexpreopt.GlobalConfig{
- DisablePreopt: true,
+ DisablePreopt: true,
+ DisableGenerateProfile: true,
}, nil}
})
}).(globalConfigAndRaw)
diff --git a/java/droiddoc.go b/java/droiddoc.go
index be1b281..ae810e1 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -23,6 +23,7 @@
"strings"
"github.com/google/blueprint"
+ "github.com/google/blueprint/proptools"
)
var (
@@ -171,9 +172,6 @@
// list of java libraries that will be in the classpath.
Libs []string `android:"arch_variant"`
- // don't build against the framework libraries (ext, and framework for device targets)
- No_framework_libs *bool
-
// the java library (in classpath) for documentation that provides java srcs and srcjars.
Srcs_lib *string
@@ -502,6 +500,7 @@
}
}
+// javadoc converts .java source files to documentation using javadoc.
func JavadocFactory() android.Module {
module := &Javadoc{}
@@ -511,6 +510,7 @@
return module
}
+// javadoc_host converts .java source files to documentation using javadoc.
func JavadocHostFactory() android.Module {
module := &Javadoc{}
@@ -523,7 +523,7 @@
var _ android.OutputFileProducer = (*Javadoc)(nil)
func (j *Javadoc) sdkVersion() string {
- return String(j.properties.Sdk_version)
+ return proptools.StringDefault(j.properties.Sdk_version, defaultSdkVersion(j))
}
func (j *Javadoc) minSdkVersion() string {
@@ -534,10 +534,6 @@
return j.sdkVersion()
}
-func (j *Javadoc) noFrameworkLibs() bool {
- return Bool(j.properties.No_framework_libs)
-}
-
func (j *Javadoc) addDeps(ctx android.BottomUpMutatorContext) {
if ctx.Device() {
sdkDep := decodeSdkDep(ctx, sdkContext(j))
@@ -804,7 +800,7 @@
"srcJarDir": android.PathForModuleOut(ctx, "srcjars").String(),
"stubsDir": android.PathForModuleOut(ctx, "stubsDir").String(),
"srcJars": strings.Join(j.srcJars.Strings(), " "),
- "opts": opts,
+ "opts": proptools.NinjaEscape(opts),
"bootclasspathArgs": bootClasspathArgs,
"classpathArgs": classpathArgs,
"sourcepathArgs": sourcepathArgs,
@@ -837,6 +833,7 @@
apiFilePath android.Path
}
+// droiddoc converts .java source files to documentation using doclava or dokka.
func DroiddocFactory() android.Module {
module := &Droiddoc{}
@@ -847,6 +844,7 @@
return module
}
+// droiddoc_host converts .java source files to documentation using doclava or dokka.
func DroiddocHostFactory() android.Module {
module := &Droiddoc{}
@@ -924,7 +922,7 @@
args := " -source 1.8 -J-Xmx1600m -J-XX:-OmitStackTraceInFastThrow -XDignore.symbol.file " +
"-doclet com.google.doclava.Doclava -docletpath " + jsilver.String() + ":" + doclava.String() + " " +
"-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")" `
+ `-hdf page.now "$(` + date + ` @$(cat ` + ctx.Config().Getenv("BUILD_DATETIME_FILE") + `) "+%d %b %Y %k:%M")" `
if String(d.properties.Custom_template) == "" {
// TODO: This is almost always droiddoc-templates-sdk
@@ -1101,7 +1099,7 @@
"srcJarDir": android.PathForModuleOut(ctx, "srcjars").String(),
"stubsDir": android.PathForModuleOut(ctx, "stubsDir").String(),
"srcJars": strings.Join(d.Javadoc.srcJars.Strings(), " "),
- "opts": opts,
+ "opts": proptools.NinjaEscape(opts),
"bootclasspathArgs": bootclasspathArgs,
"classpathArgs": classpathArgs,
"sourcepathArgs": sourcepathArgs,
@@ -1123,7 +1121,7 @@
Args: map[string]string{
"msg": msg,
"classpath": checkApiClasspath.FormJavaClassPath(""),
- "opts": opts,
+ "opts": proptools.NinjaEscape(opts),
"apiFile": apiFile.String(),
"apiFileToCheck": d.apiFile.String(),
"removedApiFile": removedApiFile.String(),
@@ -1146,7 +1144,7 @@
"stubsDir": android.PathForModuleOut(ctx, "dokka-stubsDir").String(),
"srcJars": strings.Join(d.Javadoc.srcJars.Strings(), " "),
"classpathArgs": classpathArgs,
- "opts": opts,
+ "opts": proptools.NinjaEscape(opts),
"docZip": d.Javadoc.docZip.String(),
},
})
@@ -1264,6 +1262,9 @@
jdiffStubsSrcJar android.WritablePath
}
+// droidstubs passes sources files through Metalava to generate stub .java files that only contain the API to be
+// documented, filtering out hidden classes and methods. The resulting .java files are intended to be passed to
+// a droiddoc module to generate documentation.
func DroidstubsFactory() android.Module {
module := &Droidstubs{}
@@ -1274,6 +1275,10 @@
return module
}
+// droidstubs_host passes sources files through Metalava to generate stub .java files that only contain the API
+// to be documented, filtering out hidden classes and methods. The resulting .java files are intended to be
+// passed to a droiddoc_host module to generate documentation. Use a droidstubs_host instead of a droidstubs
+// module when symbols needed by the source files are provided by java_library_host modules.
func DroidstubsHostFactory() android.Module {
module := &Droidstubs{}
@@ -1564,7 +1569,7 @@
"bootclasspathArgs": bootclasspathArgs,
"classpathArgs": classpathArgs,
"sourcepathArgs": sourcepathArgs,
- "opts": opts,
+ "opts": proptools.NinjaEscape(opts),
},
})
}
@@ -1587,7 +1592,7 @@
"bootclasspathArgs": bootclasspathArgs,
"classpathArgs": classpathArgs,
"sourcepathArgs": sourcepathArgs,
- "opts": opts,
+ "opts": proptools.NinjaEscape(opts),
"msg": msg,
},
})
@@ -1608,7 +1613,7 @@
"srcJarDir": android.PathForModuleOut(ctx, "jdiff-srcjars").String(),
"stubsDir": android.PathForModuleOut(ctx, "jdiff-stubsDir").String(),
"srcJars": strings.Join(d.Javadoc.srcJars.Strings(), " "),
- "opts": opts,
+ "opts": proptools.NinjaEscape(opts),
"bootclasspathArgs": bootclasspathArgs,
"classpathArgs": classpathArgs,
"sourcepathArgs": sourcepathArgs,
@@ -1787,6 +1792,7 @@
dir android.Path
}
+// droiddoc_exported_dir exports a directory of html templates or nullability annotations for use by doclava.
func ExportedDroiddocDirFactory() android.Module {
module := &ExportedDroiddocDir{}
module.AddProperties(&module.properties)
diff --git a/java/hiddenapi_singleton.go b/java/hiddenapi_singleton.go
index cf9f492..c83dda1 100644
--- a/java/hiddenapi_singleton.go
+++ b/java/hiddenapi_singleton.go
@@ -64,7 +64,7 @@
stubFlagsRule(ctx)
// These rules depend on files located in frameworks/base, skip them if running in a tree that doesn't have them.
- if ctx.Config().FrameworksBaseDirExists(ctx) && !ctx.Config().UnbundledBuild() {
+ if ctx.Config().FrameworksBaseDirExists(ctx) {
h.flags = flagsRule(ctx)
h.metadata = metadataRule(ctx)
} else {
@@ -97,7 +97,7 @@
// Add the android.test.base to the set of stubs only if the android.test.base module is on
// the boot jars list as the runtime will only enforce hiddenapi access against modules on
// that list.
- if inList("android.test.base", ctx.Config().BootJars()) {
+ if inList("android.test.base", ctx.Config().BootJars()) && !ctx.Config().UnbundledBuildUsePrebuiltSdks() {
publicStubModules = append(publicStubModules, "android.test.base.stubs")
}
diff --git a/java/java.go b/java/java.go
index a2e9ab0..a49aad7 100644
--- a/java/java.go
+++ b/java/java.go
@@ -82,9 +82,6 @@
// list of files that should be excluded from java_resources and java_resource_dirs
Exclude_java_resources []string `android:"path,arch_variant"`
- // don't build against the framework libraries (ext, and framework for device targets)
- No_framework_libs *bool
-
// list of module-specific flags that will be used for javac compiles
Javacflags []string `android:"arch_variant"`
@@ -426,6 +423,18 @@
usesLibTag = dependencyTag{name: "uses-library"}
)
+func defaultSdkVersion(ctx checkVendorModuleContext) string {
+ if ctx.SocSpecific() || ctx.DeviceSpecific() {
+ return "system_current"
+ }
+ return ""
+}
+
+type checkVendorModuleContext interface {
+ SocSpecific() bool
+ DeviceSpecific() bool
+}
+
type sdkDep struct {
useModule, useFiles, useDefaultLibs, invalidVersion bool
@@ -465,7 +474,7 @@
}
func (j *Module) sdkVersion() string {
- return String(j.deviceProperties.Sdk_version)
+ return proptools.StringDefault(j.deviceProperties.Sdk_version, defaultSdkVersion(j))
}
func (j *Module) minSdkVersion() string {
@@ -482,10 +491,6 @@
return j.sdkVersion()
}
-func (j *Module) noFrameworkLibs() bool {
- return Bool(j.properties.No_framework_libs)
-}
-
func (j *Module) deps(ctx android.BottomUpMutatorContext) {
if ctx.Device() {
sdkDep := decodeSdkDep(ctx, sdkContext(j))
@@ -506,7 +511,7 @@
}
} else if j.deviceProperties.System_modules == nil {
ctx.PropertyErrorf("sdk_version",
- `system_modules is required to be set when sdk_version is "none", did you mean no_framework_libs?`)
+ `system_modules is required to be set when sdk_version is "none", did you mean "core_platform"`)
} else if *j.deviceProperties.System_modules != "none" {
ctx.AddVariationDependencies(nil, systemModulesTag, *j.deviceProperties.System_modules)
}
@@ -1317,11 +1322,9 @@
return
}
- if !ctx.Config().UnbundledBuild() {
- // Hidden API CSV generation and dex encoding
- dexOutputFile = j.hiddenAPI.hiddenAPI(ctx, dexOutputFile, j.implementationJarFile,
- j.deviceProperties.UncompressDex)
- }
+ // Hidden API CSV generation and dex encoding
+ dexOutputFile = j.hiddenAPI.hiddenAPI(ctx, dexOutputFile, j.implementationJarFile,
+ j.deviceProperties.UncompressDex)
// merge dex jar with resources if necessary
if j.resourceJar != nil {
@@ -1869,7 +1872,7 @@
}
func (j *Import) sdkVersion() string {
- return String(j.properties.Sdk_version)
+ return proptools.StringDefault(j.properties.Sdk_version, defaultSdkVersion(j))
}
func (j *Import) minSdkVersion() string {
@@ -2037,7 +2040,7 @@
// dex_import module
type DexImportProperties struct {
- Jars []string
+ Jars []string `android:"path"`
}
type DexImport struct {
@@ -2065,10 +2068,6 @@
return j.prebuilt.Name(j.ModuleBase.Name())
}
-func (j *DexImport) DepsMutator(ctx android.BottomUpMutatorContext) {
- android.ExtractSourcesDeps(ctx, j.properties.Jars)
-}
-
func (j *DexImport) GenerateAndroidBuildActions(ctx android.ModuleContext) {
if len(j.properties.Jars) != 1 {
ctx.PropertyErrorf("jars", "exactly one jar must be provided")
@@ -2089,14 +2088,14 @@
// use zip2zip to uncompress classes*.dex files
rule.Command().
- Tool(ctx.Config().HostToolPath(ctx, "zip2zip")).
+ BuiltTool(ctx, "zip2zip").
FlagWithInput("-i ", inputJar).
FlagWithOutput("-o ", temporary).
FlagWithArg("-0 ", "'classes*.dex'")
// use zipalign to align uncompressed classes*.dex files
rule.Command().
- Tool(ctx.Config().HostToolPath(ctx, "zipalign")).
+ BuiltTool(ctx, "zipalign").
Flag("-f").
Text("4").
Input(temporary).
diff --git a/java/java_test.go b/java/java_test.go
index 22dec07..677174d 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -121,6 +121,10 @@
"b.kt": nil,
"a.jar": nil,
"b.jar": nil,
+ "APP_NOTICE": nil,
+ "GENRULE_NOTICE": nil,
+ "LIB_NOTICE": nil,
+ "TOOL_NOTICE": nil,
"java-res/a/a": nil,
"java-res/b/b": nil,
"java-res2/a": nil,
@@ -282,6 +286,32 @@
}
}
+func TestSdkVersion(t *testing.T) {
+ ctx := testJava(t, `
+ java_library {
+ name: "foo",
+ srcs: ["a.java"],
+ vendor: true,
+ }
+
+ java_library {
+ name: "bar",
+ srcs: ["b.java"],
+ }
+ `)
+
+ foo := ctx.ModuleForTests("foo", "android_common").Module().(*Library)
+ bar := ctx.ModuleForTests("bar", "android_common").Module().(*Library)
+
+ if foo.sdkVersion() != "system_current" {
+ t.Errorf("If sdk version of vendor module is empty, it must change to system_current.")
+ }
+
+ if bar.sdkVersion() != "" {
+ t.Errorf("If sdk version of non-vendor module is empty, it keeps empty.")
+ }
+}
+
func TestArchSpecific(t *testing.T) {
ctx := testJava(t, `
java_library {
diff --git a/java/proto.go b/java/proto.go
index 37de1d2..0ec6499 100644
--- a/java/proto.go
+++ b/java/proto.go
@@ -34,7 +34,7 @@
// Proto generated java files have an unknown package name in the path, so package the entire output directory
// into a srcjar.
rule.Command().
- Tool(ctx.Config().HostToolPath(ctx, "soong_zip")).
+ BuiltTool(ctx, "soong_zip").
Flag("-jar").
FlagWithOutput("-o ", srcJarFile).
FlagWithArg("-C ", outDir.String()).
diff --git a/java/sdk.go b/java/sdk.go
index 6ffe399..d1e2ae4 100644
--- a/java/sdk.go
+++ b/java/sdk.go
@@ -44,9 +44,6 @@
minSdkVersion() string
// targetSdkVersion returns the target_sdk_version property of the current module, or sdkVersion() if it is not set.
targetSdkVersion() string
-
- // Temporarily provide access to the no_frameworks_libs property (where present).
- noFrameworkLibs() bool
}
func sdkVersionOrDefault(ctx android.BaseModuleContext, v string) string {
@@ -84,6 +81,7 @@
func decodeSdkDep(ctx android.BaseModuleContext, sdkContext sdkContext) sdkDep {
v := sdkContext.sdkVersion()
+
// For PDK builds, use the latest SDK version instead of "current"
if ctx.Config().IsPdkBuild() && (v == "" || v == "current") {
sdkVersions := ctx.Config().Get(sdkVersionsKey).([]int)
@@ -141,9 +139,6 @@
useFiles: true,
jars: android.Paths{jarPath.Path(), lambdaStubsPath},
aidl: android.OptionalPathForPath(aidlPath.Path()),
-
- // Pass value straight through for now to match previous behavior.
- noFrameworksLibs: sdkContext.noFrameworkLibs(),
}
}
@@ -154,15 +149,12 @@
systemModules: m + "_system_modules",
frameworkResModule: r,
aidl: android.OptionalPathForPath(aidl),
-
- // Pass value straight through for now to match previous behavior.
- noFrameworksLibs: sdkContext.noFrameworkLibs(),
}
if m == "core.current.stubs" {
ret.systemModules = "core-current-stubs-system-modules"
- } else if m == "core.platform.api.stubs" {
- ret.systemModules = "core-platform-api-stubs-system-modules"
+ // core_current does not include framework classes.
+ ret.noFrameworksLibs = true
}
return ret
}
@@ -192,9 +184,6 @@
return sdkDep{
useDefaultLibs: true,
frameworkResModule: "framework-res",
-
- // Pass value straight through for now to match previous behavior.
- noFrameworksLibs: sdkContext.noFrameworkLibs(),
}
case "none":
return sdkDep{
@@ -307,7 +296,7 @@
rule.Command().
Text("rm -f").Output(aidl)
rule.Command().
- Tool(ctx.Config().HostToolPath(ctx, "sdkparcelables")).
+ BuiltTool(ctx, "sdkparcelables").
Input(jar).
Output(aidl)
diff --git a/java/sdk_library.go b/java/sdk_library.go
index b4a3f29..d38088d 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -63,12 +63,6 @@
javaSdkLibrariesLock sync.Mutex
)
-// java_sdk_library is to make a Java library that implements optional platform APIs to apps.
-// It is actually a wrapper of several modules: 1) stubs library that clients are linked against
-// to, 2) droiddoc module that internally generates API stubs source files, 3) the real runtime
-// shared library that implements the APIs, and 4) XML file for adding the runtime lib to the
-// classpath at runtime if requested via <uses-library>.
-//
// TODO: these are big features that are currently missing
// 1) disallowing linking to the runtime shared lib
// 2) HTML generation
@@ -155,16 +149,21 @@
var _ SdkLibraryDependency = (*SdkLibrary)(nil)
func (module *SdkLibrary) DepsMutator(ctx android.BottomUpMutatorContext) {
+ useBuiltStubs := !ctx.Config().UnbundledBuildUsePrebuiltSdks()
// Add dependencies to the stubs library
- ctx.AddVariationDependencies(nil, publicApiStubsTag, module.stubsName(apiScopePublic))
+ if useBuiltStubs {
+ ctx.AddVariationDependencies(nil, publicApiStubsTag, module.stubsName(apiScopePublic))
+ }
ctx.AddVariationDependencies(nil, publicApiFileTag, module.docsName(apiScopePublic))
sdkDep := decodeSdkDep(ctx, sdkContext(&module.Library))
if sdkDep.hasStandardLibs() {
- ctx.AddVariationDependencies(nil, systemApiStubsTag, module.stubsName(apiScopeSystem))
+ if useBuiltStubs {
+ ctx.AddVariationDependencies(nil, systemApiStubsTag, module.stubsName(apiScopeSystem))
+ ctx.AddVariationDependencies(nil, testApiStubsTag, module.stubsName(apiScopeTest))
+ }
ctx.AddVariationDependencies(nil, systemApiFileTag, module.docsName(apiScopeSystem))
ctx.AddVariationDependencies(nil, testApiFileTag, module.docsName(apiScopeTest))
- ctx.AddVariationDependencies(nil, testApiStubsTag, module.stubsName(apiScopeTest))
}
module.Library.deps(ctx)
@@ -746,6 +745,11 @@
module.Library.Module.deviceProperties.IsSDKLibrary = true
}
+// java_sdk_library is a special Java library that provides optional platform APIs to apps.
+// In practice, it can be viewed as a combination of several modules: 1) stubs library that clients
+// are linked against to, 2) droiddoc module that internally generates API stubs source files,
+// 3) the real runtime shared library that implements the APIs, and 4) XML file for adding
+// the runtime lib to the classpath at runtime if requested via <uses-library>.
func SdkLibraryFactory() android.Module {
module := &SdkLibrary{}
module.InitSdkLibraryProperties()
@@ -787,6 +791,7 @@
var _ SdkLibraryDependency = (*sdkLibraryImport)(nil)
+// java_sdk_library_import imports a prebuilt java_sdk_library.
func sdkLibraryImportFactory() android.Module {
module := &sdkLibraryImport{}
diff --git a/java/sdk_test.go b/java/sdk_test.go
index 953c372..f82a4fb 100644
--- a/java/sdk_test.go
+++ b/java/sdk_test.go
@@ -47,14 +47,6 @@
aidl: "-Iframework/aidl",
},
{
- name: "no_framework_libs:true",
- properties: `no_framework_libs:true`,
- bootclasspath: config.DefaultBootclasspathLibraries,
- system: config.DefaultSystemModules,
- classpath: []string{},
- aidl: "",
- },
- {
name: `sdk_version:"core_platform"`,
properties: `sdk_version:"core_platform"`,
bootclasspath: config.DefaultBootclasspathLibraries,
diff --git a/python/proto.go b/python/proto.go
index 85ed1a5..b71e047 100644
--- a/python/proto.go
+++ b/python/proto.go
@@ -34,7 +34,7 @@
// Proto generated python files have an unknown package name in the path, so package the entire output directory
// into a srcszip.
zipCmd := rule.Command().
- Tool(ctx.Config().HostToolPath(ctx, "soong_zip")).
+ BuiltTool(ctx, "soong_zip").
FlagWithOutput("-o ", srcsZipFile)
if pkgPath != "" {
zipCmd.FlagWithArg("-P ", pkgPath)
diff --git a/scripts/jar-wrapper.sh b/scripts/jar-wrapper.sh
index 71c1d90..b468041 100644
--- a/scripts/jar-wrapper.sh
+++ b/scripts/jar-wrapper.sh
@@ -48,11 +48,11 @@
exit 1
fi
-javaOpts=""
+declare -a javaOpts=()
while expr "x$1" : 'x-J' >/dev/null; do
- opt=`expr "$1" : '-J\(.*\)'`
- javaOpts="${javaOpts} -${opt}"
+ opt=`expr "$1" : '-J-\{0,1\}\(.*\)'`
+ javaOpts+=("-${opt}")
shift
done
-exec java ${javaOpts} -jar ${jardir}/${jarfile} "$@"
+exec java "${javaOpts[@]}" -jar ${jardir}/${jarfile} "$@"
diff --git a/scripts/system-clang-format b/scripts/system-clang-format
index 14abd93..a7614d2 100644
--- a/scripts/system-clang-format
+++ b/scripts/system-clang-format
@@ -1,4 +1,5 @@
BasedOnStyle: Google
+Standard: Cpp11
AccessModifierOffset: -2
AllowShortFunctionsOnASingleLine: Inline
ColumnLimit: 100
diff --git a/scripts/system-clang-format-2 b/scripts/system-clang-format-2
index e28b379..a4e23f8 100644
--- a/scripts/system-clang-format-2
+++ b/scripts/system-clang-format-2
@@ -1,4 +1,5 @@
BasedOnStyle: Google
+Standard: Cpp11
AllowShortFunctionsOnASingleLine: Inline
ColumnLimit: 100
CommentPragmas: NOLINT:.*
diff --git a/ui/build/cleanbuild.go b/ui/build/cleanbuild.go
index c47f614..8e7f96a 100644
--- a/ui/build/cleanbuild.go
+++ b/ui/build/cleanbuild.go
@@ -111,7 +111,7 @@
productOut("system_other"),
productOut("vendor"),
productOut("product"),
- productOut("product_services"),
+ productOut("system_ext"),
productOut("oem"),
productOut("obj/FAKE"),
productOut("breakpad"),
diff --git a/ui/build/config.go b/ui/build/config.go
index 4a70f06..20bb3d1 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -61,6 +61,8 @@
const srcDirFileCheck = "build/soong/root.bp"
+var buildFiles = []string{"Android.mk", "Android.bp"}
+
type BuildAction uint
const (
@@ -266,6 +268,10 @@
if err != nil {
ctx.Fatalf("Error retrieving top directory: %v", err)
}
+ dir, err = filepath.EvalSymlinks(dir)
+ if err != nil {
+ ctx.Fatalf("Unable to evaluate symlink of %s: %v", dir, err)
+ }
dir, err = filepath.Abs(dir)
if err != nil {
ctx.Fatalf("Unable to find absolute path %s: %v", dir, err)
@@ -301,12 +307,10 @@
if topDir == dir {
break
}
- // Find the build file from the directory where the build action was triggered by traversing up
- // the source tree. If a blank build filename is returned, simply use the directory where the build
- // action was invoked.
+
buildFile := findBuildFile(ctx, relDir)
if buildFile == "" {
- buildFile = filepath.Join(relDir, "Android.mk")
+ ctx.Fatalf("Build file not found for %s directory", relDir)
}
buildFiles = []string{buildFile}
targets = []string{convertToTarget(filepath.Dir(buildFile), targetNamePrefix)}
@@ -340,26 +344,57 @@
return targetNamePrefix + strings.ReplaceAll(dir, "/", "-")
}
-// findBuildFile finds a build file (makefile or blueprint file) by looking at dir first. If not
-// found, go up one level and repeat again until one is found and the path of that build file
-// relative to the root directory of the source tree is returned. The returned filename of build
-// file is "Android.mk". If one was not found, a blank string is returned.
+// hasBuildFile returns true if dir contains an Android build file.
+func hasBuildFile(ctx Context, dir string) bool {
+ for _, buildFile := range buildFiles {
+ _, err := os.Stat(filepath.Join(dir, buildFile))
+ if err == nil {
+ return true
+ }
+ if !os.IsNotExist(err) {
+ ctx.Fatalf("Error retrieving the build file stats: %v", err)
+ }
+ }
+ return false
+}
+
+// findBuildFile finds a build file (makefile or blueprint file) by looking if there is a build file
+// in the current and any sub directory of dir. If a build file is not found, traverse the path
+// up by one directory and repeat again until either a build file is found or reached to the root
+// source tree. The returned filename of build file is "Android.mk". If one was not found, a blank
+// string is returned.
func findBuildFile(ctx Context, dir string) string {
- // If the string is empty, assume it is top directory of the source tree.
- if dir == "" {
+ // If the string is empty or ".", assume it is top directory of the source tree.
+ if dir == "" || dir == "." {
return ""
}
- for ; dir != "."; dir = filepath.Dir(dir) {
- for _, buildFile := range []string{"Android.bp", "Android.mk"} {
- _, err := os.Stat(filepath.Join(dir, buildFile))
- if err == nil {
- // Returning the filename Android.mk as it might be used for ONE_SHOT_MAKEFILE variable.
- return filepath.Join(dir, "Android.mk")
+ found := false
+ for buildDir := dir; buildDir != "."; buildDir = filepath.Dir(buildDir) {
+ err := filepath.Walk(buildDir, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
}
- if !os.IsNotExist(err) {
- ctx.Fatalf("Error retrieving the build file stats: %v", err)
+ if found {
+ return filepath.SkipDir
}
+ if info.IsDir() {
+ return nil
+ }
+ for _, buildFile := range buildFiles {
+ if info.Name() == buildFile {
+ found = true
+ return filepath.SkipDir
+ }
+ }
+ return nil
+ })
+ if err != nil {
+ ctx.Fatalf("Error finding Android build file: %v", err)
+ }
+
+ if found {
+ return filepath.Join(buildDir, "Android.mk")
}
}
@@ -424,24 +459,23 @@
}
}
- buildFile := findBuildFile(ctx, dir)
- if buildFile == "" {
- ctx.Fatalf("Build file not found for %s directory", dir)
- }
- buildFileDir := filepath.Dir(buildFile)
-
- // If there are specified targets, find the build file in the directory. If dir does not
- // contain the build file, bail out as it is required for one shot build. If there are no
- // target specified, build all the modules in dir (or the closest one in the dir path).
+ // If there are specified targets to build in dir, an android build file must exist for the one
+ // shot build. For the non-targets case, find the appropriate build file and build all the
+ // modules in dir (or the closest one in the dir path).
if len(newTargets) > 0 {
- if buildFileDir != dir {
+ if !hasBuildFile(ctx, dir) {
ctx.Fatalf("Couldn't locate a build file from %s directory", dir)
}
+ buildFiles = append(buildFiles, filepath.Join(dir, "Android.mk"))
} else {
- newTargets = []string{convertToTarget(buildFileDir, targetNamePrefix)}
+ buildFile := findBuildFile(ctx, dir)
+ if buildFile == "" {
+ ctx.Fatalf("Build file not found for %s directory", dir)
+ }
+ newTargets = []string{convertToTarget(filepath.Dir(buildFile), targetNamePrefix)}
+ buildFiles = append(buildFiles, buildFile)
}
- buildFiles = append(buildFiles, buildFile)
targets = append(targets, newTargets...)
}
@@ -604,7 +638,7 @@
func (c *configImpl) OutDir() string {
if outDir, ok := c.environ.Get("OUT_DIR"); ok {
- return filepath.Clean(outDir)
+ return outDir
}
return "out"
}
diff --git a/ui/build/config_test.go b/ui/build/config_test.go
index 856af11..463405a 100644
--- a/ui/build/config_test.go
+++ b/ui/build/config_test.go
@@ -441,7 +441,7 @@
buildFiles: []string{},
dirs: []string{"1/2/3:t1"},
curDir: "0",
- errStr: "Build file not found for 0/1/2/3 directory",
+ errStr: "Couldn't locate a build file from 0/1/2/3 directory",
}, {
description: "one target dir specified, one target specified, build file not in target dir",
dirsInTrees: []string{"0/1/2/3"},
@@ -545,8 +545,11 @@
// Array of build files to create in dir.
buildFiles []string
+ // Directories that exist in the source tree.
+ dirsInTrees []string
+
// ********* Action *********
- // Directory to create, also the base directory is where findBuildFile is invoked.
+ // The base directory is where findBuildFile is invoked.
dir string
// ********* Validation *********
@@ -555,38 +558,63 @@
}{{
description: "build file exists at leaf directory",
buildFiles: []string{"1/2/3/Android.bp"},
+ dirsInTrees: []string{"1/2/3"},
dir: "1/2/3",
expectedBuildFile: "1/2/3/Android.mk",
}, {
description: "build file exists in all directory paths",
buildFiles: []string{"1/Android.mk", "1/2/Android.mk", "1/2/3/Android.mk"},
+ dirsInTrees: []string{"1/2/3"},
dir: "1/2/3",
expectedBuildFile: "1/2/3/Android.mk",
}, {
description: "build file does not exist in all directory paths",
buildFiles: []string{},
+ dirsInTrees: []string{"1/2/3"},
dir: "1/2/3",
expectedBuildFile: "",
}, {
description: "build file exists only at top directory",
buildFiles: []string{"Android.bp"},
+ dirsInTrees: []string{"1/2/3"},
dir: "1/2/3",
expectedBuildFile: "",
}, {
description: "build file exist in a subdirectory",
buildFiles: []string{"1/2/Android.bp"},
+ dirsInTrees: []string{"1/2/3"},
dir: "1/2/3",
expectedBuildFile: "1/2/Android.mk",
}, {
description: "build file exists in a subdirectory",
buildFiles: []string{"1/Android.mk"},
+ dirsInTrees: []string{"1/2/3"},
dir: "1/2/3",
expectedBuildFile: "1/Android.mk",
}, {
description: "top directory",
buildFiles: []string{"Android.bp"},
+ dirsInTrees: []string{},
dir: ".",
expectedBuildFile: "",
+ }, {
+ description: "build file exists in subdirectory",
+ buildFiles: []string{"1/2/3/Android.bp", "1/2/4/Android.bp"},
+ dirsInTrees: []string{"1/2/3", "1/2/4"},
+ dir: "1/2",
+ expectedBuildFile: "1/2/Android.mk",
+ }, {
+ description: "build file exists in parent subdirectory",
+ buildFiles: []string{"1/5/Android.bp"},
+ dirsInTrees: []string{"1/2/3", "1/2/4", "1/5"},
+ dir: "1/2",
+ expectedBuildFile: "1/Android.mk",
+ }, {
+ description: "build file exists in deep parent's subdirectory.",
+ buildFiles: []string{"1/5/6/Android.bp"},
+ dirsInTrees: []string{"1/2/3", "1/2/4", "1/5/6", "1/5/7"},
+ dir: "1/2",
+ expectedBuildFile: "1/Android.mk",
}}
for _, tt := range tests {
@@ -601,10 +629,7 @@
}
defer os.RemoveAll(topDir)
- if tt.dir != "" {
- createDirectories(t, topDir, []string{tt.dir})
- }
-
+ createDirectories(t, topDir, tt.dirsInTrees)
createBuildFiles(t, topDir, tt.buildFiles)
curDir, err := os.Getwd()
@@ -690,6 +715,9 @@
// Build files that exists in the source tree.
buildFiles []string
+ // Create root symlink that points to topDir.
+ rootSymlink bool
+
// ********* Action *********
// Arguments passed in to soong_ui.
args []string
@@ -706,11 +734,23 @@
// Expected environment variables to be set.
expectedEnvVars []envVar
+
+ // Expecting error from running test case.
+ expectedErrStr string
}
func testGetConfigArgs(t *testing.T, tt buildActionTestCase, action BuildAction, buildDependencies bool) {
ctx := testContext()
+ defer logger.Recover(func(err error) {
+ if tt.expectedErrStr == "" {
+ t.Fatalf("Got unexpected error: %v", err)
+ }
+ if tt.expectedErrStr != err.Error() {
+ t.Errorf("expected %s, got %s", tt.expectedErrStr, err.Error())
+ }
+ })
+
// Environment variables to set it to blank on every test case run.
resetEnvVars := []string{
"ONE_SHOT_MAKEFILE",
@@ -738,6 +778,22 @@
createDirectories(t, topDir, tt.dirsInTrees)
createBuildFiles(t, topDir, tt.buildFiles)
+ if tt.rootSymlink {
+ // Create a secondary root source tree which points to the true root source tree.
+ symlinkTopDir, err := ioutil.TempDir("", "")
+ if err != nil {
+ t.Fatalf("failed to create symlink temp dir: %v", err)
+ }
+ defer os.RemoveAll(symlinkTopDir)
+
+ symlinkTopDir = filepath.Join(symlinkTopDir, "root")
+ err = os.Symlink(topDir, symlinkTopDir)
+ if err != nil {
+ t.Fatalf("failed to create symlink: %v", err)
+ }
+ topDir = symlinkTopDir
+ }
+
r := setTop(t, topDir)
defer r()
@@ -761,6 +817,11 @@
t.Errorf("expecting %s, got %s for environment variable %s", env.value, val, env.name)
}
}
+
+ // If the execution reached here and there was an expected error code, the unit test case failed.
+ if tt.expectedErrStr != "" {
+ t.Errorf("expecting error %s", tt.expectedErrStr)
+ }
}
func TestGetConfigArgsBuildModules(t *testing.T) {
@@ -796,7 +857,17 @@
dirsInTrees: []string{"0/1/2", "0/2", "0/3"},
buildFiles: []string{"0/1/2/Android.mk", "0/2/Android.bp"},
args: []string{},
- curDir: "1/2/3/4/5/6/7/8/9",
+ curDir: "0/2",
+ tidyOnly: "",
+ expectedArgs: []string{},
+ expectedEnvVars: []envVar{},
+ }, {
+ description: "normal execution in symlink root source tree, no args",
+ dirsInTrees: []string{"0/1/2", "0/2", "0/3"},
+ buildFiles: []string{"0/1/2/Android.mk", "0/2/Android.bp"},
+ rootSymlink: true,
+ args: []string{},
+ curDir: "0/2",
tidyOnly: "",
expectedArgs: []string{},
expectedEnvVars: []envVar{},
@@ -846,6 +917,7 @@
envVar{
name: "ONE_SHOT_MAKEFILE",
value: "0/1/2/Android.mk"}},
+ expectedErrStr: "Build file not found for 0/1/2 directory",
}, {
description: "build action executed at root directory",
dirsInTrees: []string{},
@@ -932,13 +1004,24 @@
description: "build action executed at root directory",
dirsInTrees: []string{},
buildFiles: []string{},
+ rootSymlink: false,
args: []string{},
curDir: ".",
tidyOnly: "",
expectedArgs: []string{},
expectedEnvVars: []envVar{},
}, {
- description: "build file not found - no error is expected to return",
+ description: "build action executed at root directory in symlink",
+ dirsInTrees: []string{},
+ buildFiles: []string{},
+ rootSymlink: true,
+ args: []string{},
+ curDir: ".",
+ tidyOnly: "",
+ expectedArgs: []string{},
+ expectedEnvVars: []envVar{},
+ }, {
+ description: "build file not found",
dirsInTrees: []string{"0/1/2"},
buildFiles: []string{},
args: []string{},
@@ -946,6 +1029,7 @@
tidyOnly: "",
expectedArgs: []string{"MODULES-IN-0-1-2"},
expectedEnvVars: []envVar{},
+ expectedErrStr: "Build file not found for 0/1/2 directory",
}, {
description: "GET-INSTALL-PATH specified,",
dirsInTrees: []string{"0/1/2"},
@@ -1080,6 +1164,20 @@
description: "normal execution from top dir directory",
dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3", "0/2"},
buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/3/Android.bp", "0/2/Android.bp"},
+ rootSymlink: false,
+ args: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3", "0/2"},
+ curDir: ".",
+ tidyOnly: "",
+ expectedArgs: []string{"MODULES-IN-0-1-2-3.1", "MODULES-IN-0-1-2-3.2", "MODULES-IN-0-1-3", "MODULES-IN-0-2"},
+ expectedEnvVars: []envVar{
+ envVar{
+ name: "ONE_SHOT_MAKEFILE",
+ value: ""}},
+ }, {
+ description: "normal execution from top dir directory in symlink",
+ dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3", "0/2"},
+ buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/3/Android.bp", "0/2/Android.bp"},
+ rootSymlink: true,
args: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3", "0/2"},
curDir: ".",
tidyOnly: "",
diff --git a/ui/build/exec.go b/ui/build/exec.go
index 5c312bc..e435c53 100644
--- a/ui/build/exec.go
+++ b/ui/build/exec.go
@@ -15,7 +15,10 @@
package build
import (
+ "bufio"
+ "io"
"os/exec"
+ "strings"
)
// Cmd is a wrapper of os/exec.Cmd that integrates with the build context for
@@ -139,3 +142,34 @@
st.Finish()
c.reportError(err)
}
+
+// RunAndStreamOrFatal will run the command, while running print
+// any output, then handle any errors with a call to ctx.Fatal
+func (c *Cmd) RunAndStreamOrFatal() {
+ out, err := c.StdoutPipe()
+ if err != nil {
+ c.ctx.Fatal(err)
+ }
+ c.Stderr = c.Stdout
+
+ st := c.ctx.Status.StartTool()
+
+ c.StartOrFatal()
+
+ buf := bufio.NewReaderSize(out, 2*1024*1024)
+ for {
+ // Attempt to read whole lines, but write partial lines that are too long to fit in the buffer or hit EOF
+ line, err := buf.ReadString('\n')
+ if line != "" {
+ st.Print(strings.TrimSuffix(line, "\n"))
+ } else if err == io.EOF {
+ break
+ } else if err != nil {
+ c.ctx.Fatal(err)
+ }
+ }
+
+ err = c.Wait()
+ st.Finish()
+ c.reportError(err)
+}
diff --git a/ui/build/ninja.go b/ui/build/ninja.go
index 7994f3a..b41ac20 100644
--- a/ui/build/ninja.go
+++ b/ui/build/ninja.go
@@ -103,7 +103,7 @@
}()
ctx.Status.Status("Starting ninja...")
- cmd.RunAndPrintOrFatal()
+ cmd.RunAndStreamOrFatal()
}
type statusChecker struct {
diff --git a/ui/build/paths/config.go b/ui/build/paths/config.go
index e2c5043..a4be2ac 100644
--- a/ui/build/paths/config.go
+++ b/ui/build/paths/config.go
@@ -81,6 +81,7 @@
"bzip2": Allowed,
"dd": Allowed,
"diff": Allowed,
+ "dlv": Allowed,
"egrep": Allowed,
"expr": Allowed,
"find": Allowed,
diff --git a/ui/build/sandbox_linux.go b/ui/build/sandbox_linux.go
index b94db74..11ff667 100644
--- a/ui/build/sandbox_linux.go
+++ b/ui/build/sandbox_linux.go
@@ -162,6 +162,10 @@
c.ctx.Printf("AllowBuildBrokenUsesNetwork: %v", c.Sandbox.AllowBuildBrokenUsesNetwork)
c.ctx.Printf("BuildBrokenUsesNetwork: %v", c.config.BuildBrokenUsesNetwork())
sandboxArgs = append(sandboxArgs, "-N")
+ } else if dlv, _ := c.config.Environment().Get("SOONG_DELVE"); dlv != "" {
+ // The debugger is enabled and soong_build will pause until a remote delve process connects, allow
+ // network connections.
+ sandboxArgs = append(sandboxArgs, "-N")
}
// Stop nsjail from parsing arguments
diff --git a/ui/build/soong.go b/ui/build/soong.go
index 2ce1ac9..3388417 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -120,7 +120,7 @@
"--frontend_file", fifo,
"-f", filepath.Join(config.SoongOutDir(), file))
cmd.Sandbox = soongSandbox
- cmd.RunAndPrintOrFatal()
+ cmd.RunAndStreamOrFatal()
}
ninja("minibootstrap", ".minibootstrap/build.ninja")
diff --git a/ui/status/Android.bp b/ui/status/Android.bp
index 901a713..ec929b3 100644
--- a/ui/status/Android.bp
+++ b/ui/status/Android.bp
@@ -19,14 +19,17 @@
"golang-protobuf-proto",
"soong-ui-logger",
"soong-ui-status-ninja_frontend",
+ "soong-ui-status-build_error_proto",
],
srcs: [
+ "critical_path.go",
"kati.go",
"log.go",
"ninja.go",
"status.go",
],
testSrcs: [
+ "critical_path_test.go",
"kati_test.go",
"ninja_test.go",
"status_test.go",
@@ -41,3 +44,12 @@
"ninja_frontend/frontend.pb.go",
],
}
+
+bootstrap_go_package {
+ name: "soong-ui-status-build_error_proto",
+ pkgPath: "android/soong/ui/status/build_error_proto",
+ deps: ["golang-protobuf-proto"],
+ srcs: [
+ "build_error_proto/build_error.pb.go",
+ ],
+}
diff --git a/ui/status/build_error_proto/build_error.pb.go b/ui/status/build_error_proto/build_error.pb.go
new file mode 100644
index 0000000..d4d0a6e
--- /dev/null
+++ b/ui/status/build_error_proto/build_error.pb.go
@@ -0,0 +1,175 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: build_error.proto
+
+package soong_build_error_proto
+
+import (
+ fmt "fmt"
+ proto "github.com/golang/protobuf/proto"
+ 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.ProtoPackageIsVersion3 // please upgrade the proto package
+
+type BuildError struct {
+ // List of error messages of the overall build. The error messages
+ // are not associated with a build action.
+ ErrorMessages []string `protobuf:"bytes,1,rep,name=error_messages,json=errorMessages" json:"error_messages,omitempty"`
+ // List of build action errors.
+ ActionErrors []*BuildActionError `protobuf:"bytes,2,rep,name=action_errors,json=actionErrors" json:"action_errors,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *BuildError) Reset() { *m = BuildError{} }
+func (m *BuildError) String() string { return proto.CompactTextString(m) }
+func (*BuildError) ProtoMessage() {}
+func (*BuildError) Descriptor() ([]byte, []int) {
+ return fileDescriptor_a2e15b05802a5501, []int{0}
+}
+
+func (m *BuildError) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_BuildError.Unmarshal(m, b)
+}
+func (m *BuildError) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_BuildError.Marshal(b, m, deterministic)
+}
+func (m *BuildError) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_BuildError.Merge(m, src)
+}
+func (m *BuildError) XXX_Size() int {
+ return xxx_messageInfo_BuildError.Size(m)
+}
+func (m *BuildError) XXX_DiscardUnknown() {
+ xxx_messageInfo_BuildError.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_BuildError proto.InternalMessageInfo
+
+func (m *BuildError) GetErrorMessages() []string {
+ if m != nil {
+ return m.ErrorMessages
+ }
+ return nil
+}
+
+func (m *BuildError) GetActionErrors() []*BuildActionError {
+ if m != nil {
+ return m.ActionErrors
+ }
+ return nil
+}
+
+// Build is composed of a list of build action. There can be a set of build
+// actions that can failed.
+type BuildActionError struct {
+ // Description of the command.
+ Description *string `protobuf:"bytes,1,opt,name=description" json:"description,omitempty"`
+ // The command name that raised the error.
+ Command *string `protobuf:"bytes,2,opt,name=command" json:"command,omitempty"`
+ // The command output stream.
+ Output *string `protobuf:"bytes,3,opt,name=output" json:"output,omitempty"`
+ // List of artifacts (i.e. files) that was produced by the command.
+ Artifacts []string `protobuf:"bytes,4,rep,name=artifacts" json:"artifacts,omitempty"`
+ // The error string produced by the build action.
+ Error *string `protobuf:"bytes,5,opt,name=error" json:"error,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *BuildActionError) Reset() { *m = BuildActionError{} }
+func (m *BuildActionError) String() string { return proto.CompactTextString(m) }
+func (*BuildActionError) ProtoMessage() {}
+func (*BuildActionError) Descriptor() ([]byte, []int) {
+ return fileDescriptor_a2e15b05802a5501, []int{1}
+}
+
+func (m *BuildActionError) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_BuildActionError.Unmarshal(m, b)
+}
+func (m *BuildActionError) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_BuildActionError.Marshal(b, m, deterministic)
+}
+func (m *BuildActionError) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_BuildActionError.Merge(m, src)
+}
+func (m *BuildActionError) XXX_Size() int {
+ return xxx_messageInfo_BuildActionError.Size(m)
+}
+func (m *BuildActionError) XXX_DiscardUnknown() {
+ xxx_messageInfo_BuildActionError.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_BuildActionError proto.InternalMessageInfo
+
+func (m *BuildActionError) GetDescription() string {
+ if m != nil && m.Description != nil {
+ return *m.Description
+ }
+ return ""
+}
+
+func (m *BuildActionError) GetCommand() string {
+ if m != nil && m.Command != nil {
+ return *m.Command
+ }
+ return ""
+}
+
+func (m *BuildActionError) GetOutput() string {
+ if m != nil && m.Output != nil {
+ return *m.Output
+ }
+ return ""
+}
+
+func (m *BuildActionError) GetArtifacts() []string {
+ if m != nil {
+ return m.Artifacts
+ }
+ return nil
+}
+
+func (m *BuildActionError) GetError() string {
+ if m != nil && m.Error != nil {
+ return *m.Error
+ }
+ return ""
+}
+
+func init() {
+ proto.RegisterType((*BuildError)(nil), "soong_build_error.BuildError")
+ proto.RegisterType((*BuildActionError)(nil), "soong_build_error.BuildActionError")
+}
+
+func init() { proto.RegisterFile("build_error.proto", fileDescriptor_a2e15b05802a5501) }
+
+var fileDescriptor_a2e15b05802a5501 = []byte{
+ // 229 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x90, 0xc1, 0x4a, 0xc3, 0x40,
+ 0x10, 0x86, 0x49, 0x63, 0x95, 0x4c, 0xad, 0xd8, 0x41, 0x74, 0x04, 0x0f, 0xa1, 0x22, 0xe4, 0x94,
+ 0x83, 0x6f, 0x60, 0x41, 0xf0, 0xe2, 0x25, 0x47, 0x2f, 0x61, 0xdd, 0xac, 0x65, 0xc1, 0x64, 0xc2,
+ 0xce, 0xe6, 0xe8, 0x8b, 0xf8, 0xb4, 0x92, 0x69, 0xa5, 0xa5, 0x39, 0x7e, 0xdf, 0x3f, 0xfb, 0xef,
+ 0xce, 0xc2, 0xea, 0x73, 0xf0, 0xdf, 0x4d, 0xed, 0x42, 0xe0, 0x50, 0xf6, 0x81, 0x23, 0xe3, 0x4a,
+ 0x98, 0xbb, 0x6d, 0x7d, 0x14, 0xac, 0x7f, 0x00, 0x36, 0x23, 0xbe, 0x8e, 0x84, 0x4f, 0x70, 0xa5,
+ 0xba, 0x6e, 0x9d, 0x88, 0xd9, 0x3a, 0xa1, 0x24, 0x4f, 0x8b, 0xac, 0x5a, 0xaa, 0x7d, 0xdf, 0x4b,
+ 0x7c, 0x83, 0xa5, 0xb1, 0xd1, 0x73, 0xb7, 0x2b, 0x11, 0x9a, 0xe5, 0x69, 0xb1, 0x78, 0x7e, 0x2c,
+ 0x27, 0xfd, 0xa5, 0x96, 0xbf, 0xe8, 0xb0, 0x5e, 0x51, 0x5d, 0x9a, 0x03, 0xc8, 0xfa, 0x37, 0x81,
+ 0xeb, 0xd3, 0x11, 0xcc, 0x61, 0xd1, 0x38, 0xb1, 0xc1, 0xf7, 0xa3, 0xa3, 0x24, 0x4f, 0x8a, 0xac,
+ 0x3a, 0x56, 0x48, 0x70, 0x61, 0xb9, 0x6d, 0x4d, 0xd7, 0xd0, 0x4c, 0xd3, 0x7f, 0xc4, 0x5b, 0x38,
+ 0xe7, 0x21, 0xf6, 0x43, 0xa4, 0x54, 0x83, 0x3d, 0xe1, 0x03, 0x64, 0x26, 0x44, 0xff, 0x65, 0x6c,
+ 0x14, 0x3a, 0xd3, 0xa5, 0x0e, 0x02, 0x6f, 0x60, 0xae, 0xcf, 0xa5, 0xb9, 0x1e, 0xda, 0xc1, 0xe6,
+ 0xfe, 0xe3, 0x6e, 0xb2, 0x50, 0xad, 0x3f, 0xf9, 0x17, 0x00, 0x00, 0xff, 0xff, 0xb6, 0x18, 0x9e,
+ 0x17, 0x5d, 0x01, 0x00, 0x00,
+}
diff --git a/ui/status/build_error_proto/build_error.proto b/ui/status/build_error_proto/build_error.proto
new file mode 100644
index 0000000..9c8470d
--- /dev/null
+++ b/ui/status/build_error_proto/build_error.proto
@@ -0,0 +1,46 @@
+// Copyright 2019 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";
+
+package soong_build_error;
+option go_package = "soong_build_error_proto";
+
+message BuildError {
+ // List of error messages of the overall build. The error messages
+ // are not associated with a build action.
+ repeated string error_messages = 1;
+
+ // List of build action errors.
+ repeated BuildActionError action_errors = 2;
+}
+
+// Build is composed of a list of build action. There can be a set of build
+// actions that can failed.
+message BuildActionError {
+ // Description of the command.
+ optional string description = 1;
+
+ // The command name that raised the error.
+ optional string command = 2;
+
+ // The command output stream.
+ optional string output = 3;
+
+ // List of artifacts (i.e. files) that was produced by the command.
+ repeated string artifacts = 4;
+
+ // The error string produced by the build action.
+ optional string error = 5;
+}
diff --git a/ui/status/build_error_proto/regen.sh b/ui/status/build_error_proto/regen.sh
new file mode 100755
index 0000000..7c3ec8f
--- /dev/null
+++ b/ui/status/build_error_proto/regen.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+aprotoc --go_out=paths=source_relative:. build_error.proto
diff --git a/ui/status/critical_path.go b/ui/status/critical_path.go
new file mode 100644
index 0000000..444327b
--- /dev/null
+++ b/ui/status/critical_path.go
@@ -0,0 +1,152 @@
+// Copyright 2019 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 (
+ "time"
+
+ "android/soong/ui/logger"
+)
+
+func NewCriticalPath(log logger.Logger) StatusOutput {
+ return &criticalPath{
+ log: log,
+ running: make(map[*Action]time.Time),
+ nodes: make(map[string]*node),
+ clock: osClock{},
+ }
+}
+
+type criticalPath struct {
+ log logger.Logger
+
+ nodes map[string]*node
+ running map[*Action]time.Time
+
+ start, end time.Time
+
+ clock clock
+}
+
+type clock interface {
+ Now() time.Time
+}
+
+type osClock struct{}
+
+func (osClock) Now() time.Time { return time.Now() }
+
+// A critical path node stores the critical path (the minimum time to build the node and all of its dependencies given
+// perfect parallelism) for an node.
+type node struct {
+ action *Action
+ cumulativeDuration time.Duration
+ duration time.Duration
+ input *node
+}
+
+func (cp *criticalPath) StartAction(action *Action, counts Counts) {
+ start := cp.clock.Now()
+ if cp.start.IsZero() {
+ cp.start = start
+ }
+ cp.running[action] = start
+}
+
+func (cp *criticalPath) FinishAction(result ActionResult, counts Counts) {
+ if start, ok := cp.running[result.Action]; ok {
+ delete(cp.running, result.Action)
+
+ // Determine the input to this edge with the longest cumulative duration
+ var criticalPathInput *node
+ for _, input := range result.Action.Inputs {
+ if x := cp.nodes[input]; x != nil {
+ if criticalPathInput == nil || x.cumulativeDuration > criticalPathInput.cumulativeDuration {
+ criticalPathInput = x
+ }
+ }
+ }
+
+ end := cp.clock.Now()
+ duration := end.Sub(start)
+
+ cumulativeDuration := duration
+ if criticalPathInput != nil {
+ cumulativeDuration += criticalPathInput.cumulativeDuration
+ }
+
+ node := &node{
+ action: result.Action,
+ cumulativeDuration: cumulativeDuration,
+ duration: duration,
+ input: criticalPathInput,
+ }
+
+ for _, output := range result.Action.Outputs {
+ cp.nodes[output] = node
+ }
+
+ cp.end = end
+ }
+}
+
+func (cp *criticalPath) Flush() {
+ criticalPath := cp.criticalPath()
+
+ if len(criticalPath) > 0 {
+ // Log the critical path to the verbose log
+ criticalTime := criticalPath[0].cumulativeDuration.Round(time.Second)
+ cp.log.Verbosef("critical path took %s", criticalTime.String())
+ if !cp.start.IsZero() {
+ elapsedTime := cp.end.Sub(cp.start).Round(time.Second)
+ cp.log.Verbosef("elapsed time %s", elapsedTime.String())
+ cp.log.Verbosef("perfect parallelism ratio %d%%",
+ int(float64(criticalTime)/float64(elapsedTime)*100))
+ }
+ cp.log.Verbose("critical path:")
+ for i := len(criticalPath) - 1; i >= 0; i-- {
+ duration := criticalPath[i].duration
+ duration = duration.Round(time.Second)
+ seconds := int(duration.Seconds())
+ cp.log.Verbosef(" %2d:%02d %s",
+ seconds/60, seconds%60, criticalPath[i].action.Description)
+ }
+ }
+}
+
+func (cp *criticalPath) Message(level MsgLevel, msg string) {}
+
+func (cp *criticalPath) Write(p []byte) (n int, err error) { return len(p), nil }
+
+func (cp *criticalPath) criticalPath() []*node {
+ var max *node
+
+ // Find the node with the longest critical path
+ for _, node := range cp.nodes {
+ if max == nil || node.cumulativeDuration > max.cumulativeDuration {
+ max = node
+ }
+ }
+
+ // Follow the critical path back to the leaf node
+ var criticalPath []*node
+ node := max
+ for node != nil {
+ criticalPath = append(criticalPath, node)
+ node = node.input
+ }
+
+ return criticalPath
+}
diff --git a/ui/status/critical_path_test.go b/ui/status/critical_path_test.go
new file mode 100644
index 0000000..965e0ad
--- /dev/null
+++ b/ui/status/critical_path_test.go
@@ -0,0 +1,166 @@
+// Copyright 2019 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 (
+ "reflect"
+ "testing"
+ "time"
+)
+
+type testCriticalPath struct {
+ *criticalPath
+ Counts
+
+ actions map[int]*Action
+}
+
+type testClock time.Time
+
+func (t testClock) Now() time.Time { return time.Time(t) }
+
+func (t *testCriticalPath) start(id int, startTime time.Duration, outputs, inputs []string) {
+ t.clock = testClock(time.Unix(0, 0).Add(startTime))
+ action := &Action{
+ Description: outputs[0],
+ Outputs: outputs,
+ Inputs: inputs,
+ }
+
+ t.actions[id] = action
+ t.StartAction(action, t.Counts)
+}
+
+func (t *testCriticalPath) finish(id int, endTime time.Duration) {
+ t.clock = testClock(time.Unix(0, 0).Add(endTime))
+ t.FinishAction(ActionResult{
+ Action: t.actions[id],
+ }, t.Counts)
+}
+
+func TestCriticalPath(t *testing.T) {
+ tests := []struct {
+ name string
+ msgs func(*testCriticalPath)
+ want []string
+ wantTime time.Duration
+ }{
+ {
+ name: "empty",
+ msgs: func(cp *testCriticalPath) {},
+ },
+ {
+ name: "duplicate",
+ msgs: func(cp *testCriticalPath) {
+ cp.start(0, 0, []string{"a"}, nil)
+ cp.start(1, 0, []string{"a"}, nil)
+ cp.finish(0, 1000)
+ cp.finish(0, 2000)
+ },
+ want: []string{"a"},
+ wantTime: 1000,
+ },
+ {
+ name: "linear",
+ // a
+ // |
+ // b
+ // |
+ // c
+ msgs: func(cp *testCriticalPath) {
+ cp.start(0, 0, []string{"a"}, nil)
+ cp.finish(0, 1000)
+ cp.start(1, 1000, []string{"b"}, []string{"a"})
+ cp.finish(1, 2000)
+ cp.start(2, 3000, []string{"c"}, []string{"b"})
+ cp.finish(2, 4000)
+ },
+ want: []string{"c", "b", "a"},
+ wantTime: 3000,
+ },
+ {
+ name: "diamond",
+ // a
+ // |\
+ // b c
+ // |/
+ // d
+ msgs: func(cp *testCriticalPath) {
+ cp.start(0, 0, []string{"a"}, nil)
+ cp.finish(0, 1000)
+ cp.start(1, 1000, []string{"b"}, []string{"a"})
+ cp.start(2, 1000, []string{"c"}, []string{"a"})
+ cp.finish(1, 2000)
+ cp.finish(2, 3000)
+ cp.start(3, 3000, []string{"d"}, []string{"b", "c"})
+ cp.finish(3, 4000)
+ },
+ want: []string{"d", "c", "a"},
+ wantTime: 4000,
+ },
+ {
+ name: "multiple",
+ // a d
+ // | |
+ // b e
+ // |
+ // c
+ msgs: func(cp *testCriticalPath) {
+ cp.start(0, 0, []string{"a"}, nil)
+ cp.start(3, 0, []string{"d"}, nil)
+ cp.finish(0, 1000)
+ cp.finish(3, 1000)
+ cp.start(1, 1000, []string{"b"}, []string{"a"})
+ cp.start(4, 1000, []string{"e"}, []string{"d"})
+ cp.finish(1, 2000)
+ cp.start(2, 2000, []string{"c"}, []string{"b"})
+ cp.finish(2, 3000)
+ cp.finish(4, 4000)
+
+ },
+ want: []string{"e", "d"},
+ wantTime: 4000,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ cp := &testCriticalPath{
+ criticalPath: NewCriticalPath(nil).(*criticalPath),
+ actions: make(map[int]*Action),
+ }
+
+ tt.msgs(cp)
+
+ criticalPath := cp.criticalPath.criticalPath()
+
+ var descs []string
+ for _, x := range criticalPath {
+ descs = append(descs, x.action.Description)
+ }
+
+ if !reflect.DeepEqual(descs, tt.want) {
+ t.Errorf("criticalPath.criticalPath() = %v, want %v", descs, tt.want)
+ }
+
+ var gotTime time.Duration
+ if len(criticalPath) > 0 {
+ gotTime = criticalPath[0].cumulativeDuration
+ }
+ if gotTime != tt.wantTime {
+ t.Errorf("cumulativeDuration[0].cumulativeDuration = %v, want %v", gotTime, tt.wantTime)
+ }
+ })
+ }
+}
diff --git a/ui/status/log.go b/ui/status/log.go
index 7badac7..9090f49 100644
--- a/ui/status/log.go
+++ b/ui/status/log.go
@@ -15,11 +15,17 @@
package status
import (
- "android/soong/ui/logger"
"compress/gzip"
+ "errors"
"fmt"
"io"
+ "io/ioutil"
"strings"
+
+ "github.com/golang/protobuf/proto"
+
+ "android/soong/ui/logger"
+ "android/soong/ui/status/build_error_proto"
)
type verboseLog struct {
@@ -77,8 +83,7 @@
}
type errorLog struct {
- w io.WriteCloser
-
+ w io.WriteCloser
empty bool
}
@@ -102,20 +107,17 @@
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)
@@ -144,3 +146,55 @@
fmt.Fprint(e.w, string(p))
return len(p), nil
}
+
+type errorProtoLog struct {
+ errorProto soong_build_error_proto.BuildError
+ filename string
+ log logger.Logger
+}
+
+func NewProtoErrorLog(log logger.Logger, filename string) StatusOutput {
+ return &errorProtoLog{
+ errorProto: soong_build_error_proto.BuildError{},
+ filename: filename,
+ log: log,
+ }
+}
+
+func (e *errorProtoLog) StartAction(action *Action, counts Counts) {}
+
+func (e *errorProtoLog) FinishAction(result ActionResult, counts Counts) {
+ if result.Error == nil {
+ return
+ }
+
+ e.errorProto.ActionErrors = append(e.errorProto.ActionErrors, &soong_build_error_proto.BuildActionError{
+ Description: proto.String(result.Description),
+ Command: proto.String(result.Command),
+ Output: proto.String(result.Output),
+ Artifacts: result.Outputs,
+ Error: proto.String(result.Error.Error()),
+ })
+}
+
+func (e *errorProtoLog) Flush() {
+ data, err := proto.Marshal(&e.errorProto)
+ if err != nil {
+ e.log.Println("Failed to marshal build status proto: %v", err)
+ return
+ }
+ err = ioutil.WriteFile(e.filename, []byte(data), 0644)
+ if err != nil {
+ e.log.Println("Failed to write file %s: %v", e.errorProto, err)
+ }
+}
+
+func (e *errorProtoLog) Message(level MsgLevel, message string) {
+ if level > ErrorLvl {
+ e.errorProto.ErrorMessages = append(e.errorProto.ErrorMessages, message)
+ }
+}
+
+func (e *errorProtoLog) Write(p []byte) (int, error) {
+ return 0, errors.New("not supported")
+}
diff --git a/ui/status/ninja.go b/ui/status/ninja.go
index ee2a2da..9cf2f6a 100644
--- a/ui/status/ninja.go
+++ b/ui/status/ninja.go
@@ -142,6 +142,7 @@
action := &Action{
Description: msg.EdgeStarted.GetDesc(),
Outputs: msg.EdgeStarted.Outputs,
+ Inputs: msg.EdgeStarted.Inputs,
Command: msg.EdgeStarted.GetCommand(),
}
n.status.StartAction(action)
diff --git a/ui/status/status.go b/ui/status/status.go
index 3d8cd7a..df33baa 100644
--- a/ui/status/status.go
+++ b/ui/status/status.go
@@ -32,6 +32,10 @@
// but they can be any string.
Outputs []string
+ // Inputs is the (optional) list of inputs. Usually these are files,
+ // but they can be any string.
+ Inputs []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.
diff --git a/ui/terminal/smart_status.go b/ui/terminal/smart_status.go
index 9638cdf..8659d4d 100644
--- a/ui/terminal/smart_status.go
+++ b/ui/terminal/smart_status.go
@@ -225,9 +225,7 @@
// Limit line width to the terminal width, otherwise we'll wrap onto
// another line and we won't delete the previous line.
- if s.termWidth > 0 {
- str = s.elide(str)
- }
+ str = elide(str, s.termWidth)
// Move to the beginning on the line, turn on bold, print the output,
// turn off bold, then clear the rest of the line.
@@ -237,11 +235,11 @@
s.haveBlankLine = false
}
-func (s *smartStatusOutput) elide(str string) string {
- if len(str) > s.termWidth {
+func elide(str string, width int) string {
+ if width > 0 && len(str) > width {
// TODO: Just do a max. Ninja elides the middle, but that's
// more complicated and these lines aren't that important.
- str = str[:s.termWidth]
+ str = str[:width]
}
return str
@@ -344,9 +342,18 @@
desc = runningAction.action.Command
}
- str := fmt.Sprintf(" %2d:%02d %s", seconds/60, seconds%60, desc)
- str = s.elide(str)
- fmt.Fprint(s.writer, str, ansi.clearToEndOfLine())
+ color := ""
+ if seconds >= 60 {
+ color = ansi.red() + ansi.bold()
+ } else if seconds >= 30 {
+ color = ansi.yellow() + ansi.bold()
+ }
+
+ durationStr := fmt.Sprintf(" %2d:%02d ", seconds/60, seconds%60)
+ desc = elide(desc, s.termWidth-len(durationStr))
+ durationStr = color + durationStr + ansi.regular()
+
+ fmt.Fprint(s.writer, durationStr, desc, ansi.clearToEndOfLine())
if tableLine < s.tableHeight-1 {
fmt.Fprint(s.writer, "\n")
}
@@ -387,6 +394,14 @@
return fmt.Sprintf("\x1b[r")
}
+func (ansiImpl) red() string {
+ return "\x1b[31m"
+}
+
+func (ansiImpl) yellow() string {
+ return "\x1b[33m"
+}
+
func (ansiImpl) bold() string {
return "\x1b[1m"
}
diff --git a/ui/terminal/util.go b/ui/terminal/util.go
index c9377f1..7a603d7 100644
--- a/ui/terminal/util.go
+++ b/ui/terminal/util.go
@@ -24,6 +24,9 @@
func isSmartTerminal(w io.Writer) bool {
if f, ok := w.(*os.File); ok {
+ if term, ok := os.LookupEnv("TERM"); ok && term == "dumb" {
+ return false
+ }
var termios syscall.Termios
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, f.Fd(),
ioctlGetTermios, uintptr(unsafe.Pointer(&termios)),