blob: 48a442de4702e0e5c8b9f98d8b39dda0b3c470f4 [file] [log] [blame] [edit]
// 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 sh
import (
"path/filepath"
"strings"
"android/soong/testing"
"github.com/google/blueprint"
"github.com/google/blueprint/proptools"
"android/soong/android"
"android/soong/cc"
"android/soong/tradefed"
)
// sh_binary is for shell scripts (and batch files) that are installed as
// executable files into .../bin/
//
// Do not use them for prebuilt C/C++/etc files. Use cc_prebuilt_binary
// instead.
var pctx = android.NewPackageContext("android/soong/sh")
func init() {
pctx.Import("android/soong/android")
registerShBuildComponents(android.InitRegistrationContext)
}
func registerShBuildComponents(ctx android.RegistrationContext) {
ctx.RegisterModuleType("sh_binary", ShBinaryFactory)
ctx.RegisterModuleType("sh_binary_host", ShBinaryHostFactory)
ctx.RegisterModuleType("sh_test", ShTestFactory)
ctx.RegisterModuleType("sh_test_host", ShTestHostFactory)
}
// Test fixture preparer that will register most sh build components.
//
// Singletons and mutators should only be added here if they are needed for a majority of sh
// module types, otherwise they should be added under a separate preparer to allow them to be
// selected only when needed to reduce test execution time.
//
// Module types do not have much of an overhead unless they are used so this should include as many
// module types as possible. The exceptions are those module types that require mutators and/or
// singletons in order to function in which case they should be kept together in a separate
// preparer.
var PrepareForTestWithShBuildComponents = android.GroupFixturePreparers(
android.FixtureRegisterWithContext(registerShBuildComponents),
)
type shBinaryProperties struct {
// Source file of this prebuilt.
Src *string `android:"path,arch_variant"`
// optional subdirectory under which this file is installed into
Sub_dir *string `android:"arch_variant"`
// optional name for the installed file. If unspecified, name of the module is used as the file name
Filename *string `android:"arch_variant"`
// when set to true, and filename property is not set, the name for the installed file
// is the same as the file name of the source file.
Filename_from_src *bool `android:"arch_variant"`
// Whether this module is directly installable to one of the partitions. Default: true.
Installable *bool
// install symlinks to the binary
Symlinks []string `android:"arch_variant"`
// Make this module available when building for ramdisk.
// On device without a dedicated recovery partition, the module is only
// available after switching root into
// /first_stage_ramdisk. To expose the module before switching root, install
// the recovery variant instead.
Ramdisk_available *bool
// Make this module available when building for vendor ramdisk.
// On device without a dedicated recovery partition, the module is only
// available after switching root into
// /first_stage_ramdisk. To expose the module before switching root, install
// the recovery variant instead.
Vendor_ramdisk_available *bool
// Make this module available when building for recovery.
Recovery_available *bool
// The name of the image this module is built for
ImageVariation string `blueprint:"mutated"`
// Suffix for the name of Android.mk entries generated by this module
SubName string `blueprint:"mutated"`
}
type TestProperties struct {
// list of compatibility suites (for example "cts", "vts") that the module should be
// installed into.
Test_suites []string `android:"arch_variant"`
// the name of the test configuration (for example "AndroidTest.xml") that should be
// installed with the module.
Test_config *string `android:"path,arch_variant"`
// list of files or filegroup modules that provide data that should be installed alongside
// the test.
Data []string `android:"path,arch_variant"`
// Add RootTargetPreparer to auto generated test config. This guarantees the test to run
// with root permission.
Require_root *bool
// the name of the test configuration template (for example "AndroidTestTemplate.xml") that
// should be installed with the module.
Test_config_template *string `android:"path,arch_variant"`
// Flag to indicate whether or not to create test config automatically. If AndroidTest.xml
// doesn't exist next to the Android.bp, this attribute doesn't need to be set to true
// explicitly.
Auto_gen_config *bool
// list of binary modules that should be installed alongside the test
Data_bins []string `android:"path,arch_variant"`
// list of library modules that should be installed alongside the test
Data_libs []string `android:"path,arch_variant"`
// list of device binary modules that should be installed alongside the test.
// Only available for host sh_test modules.
Data_device_bins []string `android:"path,arch_variant"`
// list of device library modules that should be installed alongside the test.
// Only available for host sh_test modules.
Data_device_libs []string `android:"path,arch_variant"`
// list of java modules that provide data that should be installed alongside the test.
Java_data []string
// Install the test into a folder named for the module in all test suites.
Per_testcase_directory *bool
// Test options.
Test_options android.CommonTestOptions
}
type ShBinary struct {
android.ModuleBase
properties shBinaryProperties
sourceFilePath android.Path
outputFilePath android.OutputPath
installedFile android.InstallPath
}
var _ android.HostToolProvider = (*ShBinary)(nil)
type ShTest struct {
ShBinary
testProperties TestProperties
installDir android.InstallPath
data []android.DataPath
testConfig android.Path
dataModules map[string]android.Path
}
func (s *ShBinary) HostToolPath() android.OptionalPath {
return android.OptionalPathForPath(s.installedFile)
}
func (s *ShBinary) DepsMutator(ctx android.BottomUpMutatorContext) {
}
func (s *ShBinary) OutputFile() android.OutputPath {
return s.outputFilePath
}
func (s *ShBinary) SubDir() string {
return proptools.String(s.properties.Sub_dir)
}
func (s *ShBinary) RelativeInstallPath() string {
return s.SubDir()
}
func (s *ShBinary) Installable() bool {
return s.properties.Installable == nil || proptools.Bool(s.properties.Installable)
}
func (s *ShBinary) Symlinks() []string {
return s.properties.Symlinks
}
var _ android.ImageInterface = (*ShBinary)(nil)
func (s *ShBinary) ImageMutatorBegin(ctx android.BaseModuleContext) {}
func (s *ShBinary) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
return !s.InstallInRecovery() && !s.InstallInRamdisk() && !s.InstallInVendorRamdisk() && !s.ModuleBase.InstallInVendor()
}
func (s *ShBinary) RamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
return proptools.Bool(s.properties.Ramdisk_available) || s.InstallInRamdisk()
}
func (s *ShBinary) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
return proptools.Bool(s.properties.Vendor_ramdisk_available) || s.InstallInVendorRamdisk()
}
func (s *ShBinary) DebugRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
return false
}
func (s *ShBinary) RecoveryVariantNeeded(ctx android.BaseModuleContext) bool {
return proptools.Bool(s.properties.Recovery_available) || s.InstallInRecovery()
}
func (s *ShBinary) ExtraImageVariations(ctx android.BaseModuleContext) []string {
extraVariations := []string{}
if s.InstallInProduct() {
extraVariations = append(extraVariations, cc.ProductVariation)
}
if s.InstallInVendor() {
extraVariations = append(extraVariations, cc.VendorVariation)
}
return extraVariations
}
func (s *ShBinary) SetImageVariation(ctx android.BaseModuleContext, variation string) {
s.properties.ImageVariation = variation
}
// Overrides ModuleBase.InstallInRamdisk() so that the install rule respects
// Ramdisk_available property for ramdisk variant
func (s *ShBinary) InstallInRamdisk() bool {
return s.ModuleBase.InstallInRamdisk() ||
(proptools.Bool(s.properties.Ramdisk_available) && s.properties.ImageVariation == android.RamdiskVariation)
}
// Overrides ModuleBase.InstallInVendorRamdisk() so that the install rule respects
// Vendor_ramdisk_available property for vendor ramdisk variant
func (s *ShBinary) InstallInVendorRamdisk() bool {
return s.ModuleBase.InstallInVendorRamdisk() ||
(proptools.Bool(s.properties.Vendor_ramdisk_available) && s.properties.ImageVariation == android.VendorRamdiskVariation)
}
// Overrides ModuleBase.InstallInRecovery() so that the install rule respects
// Recovery_available property for recovery variant
func (s *ShBinary) InstallInRecovery() bool {
return s.ModuleBase.InstallInRecovery() ||
(proptools.Bool(s.properties.Recovery_available) && s.properties.ImageVariation == android.RecoveryVariation)
}
func (s *ShBinary) generateAndroidBuildActions(ctx android.ModuleContext) {
if s.properties.Src == nil {
ctx.PropertyErrorf("src", "missing prebuilt source file")
}
s.sourceFilePath = android.PathForModuleSrc(ctx, proptools.String(s.properties.Src))
filename := proptools.String(s.properties.Filename)
filenameFromSrc := proptools.Bool(s.properties.Filename_from_src)
if filename == "" {
if filenameFromSrc {
filename = s.sourceFilePath.Base()
} else {
filename = ctx.ModuleName()
}
} else if filenameFromSrc {
ctx.PropertyErrorf("filename_from_src", "filename is set. filename_from_src can't be true")
return
}
s.outputFilePath = android.PathForModuleOut(ctx, filename).OutputPath
// This ensures that outputFilePath has the correct name for others to
// use, as the source file may have a different name.
ctx.Build(pctx, android.BuildParams{
Rule: android.CpExecutable,
Output: s.outputFilePath,
Input: s.sourceFilePath,
})
s.properties.SubName = s.GetSubname(ctx)
android.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: []string{s.sourceFilePath.String()}})
ctx.SetOutputFiles(android.Paths{s.outputFilePath}, "")
}
func (s *ShBinary) GetSubname(ctx android.ModuleContext) string {
ret := ""
if s.properties.ImageVariation != "" {
if s.properties.ImageVariation != cc.VendorVariation {
ret = "." + s.properties.ImageVariation
}
}
return ret
}
func (s *ShBinary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
s.generateAndroidBuildActions(ctx)
installDir := android.PathForModuleInstall(ctx, "bin", proptools.String(s.properties.Sub_dir))
if !s.Installable() {
s.SkipInstall()
}
s.installedFile = ctx.InstallExecutable(installDir, s.outputFilePath.Base(), s.outputFilePath)
for _, symlink := range s.Symlinks() {
ctx.InstallSymlink(installDir, symlink, s.installedFile)
}
}
func (s *ShBinary) AndroidMkEntries() []android.AndroidMkEntries {
return []android.AndroidMkEntries{{
Class: "EXECUTABLES",
OutputFile: android.OptionalPathForPath(s.outputFilePath),
Include: "$(BUILD_SYSTEM)/soong_cc_rust_prebuilt.mk",
ExtraEntries: []android.AndroidMkExtraEntriesFunc{
func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
s.customAndroidMkEntries(entries)
entries.SetString("LOCAL_MODULE_RELATIVE_PATH", proptools.String(s.properties.Sub_dir))
entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", !s.Installable())
},
},
SubName: s.properties.SubName,
}}
}
func (s *ShBinary) customAndroidMkEntries(entries *android.AndroidMkEntries) {
entries.SetString("LOCAL_MODULE_SUFFIX", "")
entries.SetString("LOCAL_MODULE_STEM", s.outputFilePath.Rel())
if len(s.properties.Symlinks) > 0 {
entries.SetString("LOCAL_MODULE_SYMLINKS", strings.Join(s.properties.Symlinks, " "))
}
}
type dependencyTag struct {
blueprint.BaseDependencyTag
name string
}
var (
shTestDataBinsTag = dependencyTag{name: "dataBins"}
shTestDataLibsTag = dependencyTag{name: "dataLibs"}
shTestDataDeviceBinsTag = dependencyTag{name: "dataDeviceBins"}
shTestDataDeviceLibsTag = dependencyTag{name: "dataDeviceLibs"}
shTestJavaDataTag = dependencyTag{name: "javaData"}
)
var sharedLibVariations = []blueprint.Variation{{Mutator: "link", Variation: "shared"}}
func (s *ShTest) DepsMutator(ctx android.BottomUpMutatorContext) {
s.ShBinary.DepsMutator(ctx)
ctx.AddFarVariationDependencies(ctx.Target().Variations(), shTestDataBinsTag, s.testProperties.Data_bins...)
ctx.AddFarVariationDependencies(append(ctx.Target().Variations(), sharedLibVariations...),
shTestDataLibsTag, s.testProperties.Data_libs...)
if ctx.Target().Os.Class == android.Host && len(ctx.Config().Targets[android.Android]) > 0 {
deviceVariations := ctx.Config().AndroidFirstDeviceTarget.Variations()
ctx.AddFarVariationDependencies(deviceVariations, shTestDataDeviceBinsTag, s.testProperties.Data_device_bins...)
ctx.AddFarVariationDependencies(append(deviceVariations, sharedLibVariations...),
shTestDataDeviceLibsTag, s.testProperties.Data_device_libs...)
javaDataVariation := []blueprint.Variation{{"arch", android.Common.String()}}
ctx.AddVariationDependencies(javaDataVariation, shTestJavaDataTag, s.testProperties.Java_data...)
} else if ctx.Target().Os.Class != android.Host {
if len(s.testProperties.Data_device_bins) > 0 {
ctx.PropertyErrorf("data_device_bins", "only available for host modules")
}
if len(s.testProperties.Data_device_libs) > 0 {
ctx.PropertyErrorf("data_device_libs", "only available for host modules")
}
if len(s.testProperties.Java_data) > 0 {
ctx.PropertyErrorf("Java_data", "only available for host modules")
}
}
}
func (s *ShTest) addToDataModules(ctx android.ModuleContext, relPath string, path android.Path) {
if _, exists := s.dataModules[relPath]; exists {
ctx.ModuleErrorf("data modules have a conflicting installation path, %v - %s, %s",
relPath, s.dataModules[relPath].String(), path.String())
return
}
s.dataModules[relPath] = path
s.data = append(s.data, android.DataPath{SrcPath: path})
}
func (s *ShTest) GenerateAndroidBuildActions(ctx android.ModuleContext) {
s.ShBinary.generateAndroidBuildActions(ctx)
expandedData := android.PathsForModuleSrc(ctx, s.testProperties.Data)
// Emulate the data property for java_data dependencies.
for _, javaData := range ctx.GetDirectDepsWithTag(shTestJavaDataTag) {
expandedData = append(expandedData, android.OutputFilesForModule(ctx, javaData, "")...)
}
for _, d := range expandedData {
s.data = append(s.data, android.DataPath{SrcPath: d})
}
testDir := "nativetest"
if ctx.Target().Arch.ArchType.Multilib == "lib64" {
testDir = "nativetest64"
}
if ctx.Target().NativeBridge == android.NativeBridgeEnabled {
testDir = filepath.Join(testDir, ctx.Target().NativeBridgeRelativePath)
} else if !ctx.Host() && ctx.Config().HasMultilibConflict(ctx.Arch().ArchType) {
testDir = filepath.Join(testDir, ctx.Arch().ArchType.String())
}
if s.SubDir() != "" {
// Don't add the module name to the installation path if sub_dir is specified for backward
// compatibility.
s.installDir = android.PathForModuleInstall(ctx, testDir, s.SubDir())
} else {
s.installDir = android.PathForModuleInstall(ctx, testDir, s.Name())
}
var configs []tradefed.Config
if Bool(s.testProperties.Require_root) {
configs = append(configs, tradefed.Object{"target_preparer", "com.android.tradefed.targetprep.RootTargetPreparer", nil})
} else {
options := []tradefed.Option{{Name: "force-root", Value: "false"}}
configs = append(configs, tradefed.Object{"target_preparer", "com.android.tradefed.targetprep.RootTargetPreparer", options})
}
if len(s.testProperties.Data_device_bins) > 0 {
moduleName := s.Name()
remoteDir := "/data/local/tests/unrestricted/" + moduleName + "/"
options := []tradefed.Option{{Name: "cleanup", Value: "true"}}
for _, bin := range s.testProperties.Data_device_bins {
options = append(options, tradefed.Option{Name: "push-file", Key: bin, Value: remoteDir + bin})
}
configs = append(configs, tradefed.Object{"target_preparer", "com.android.tradefed.targetprep.PushFilePreparer", options})
}
s.testConfig = tradefed.AutoGenTestConfig(ctx, tradefed.AutoGenTestConfigOptions{
TestConfigProp: s.testProperties.Test_config,
TestConfigTemplateProp: s.testProperties.Test_config_template,
TestSuites: s.testProperties.Test_suites,
Config: configs,
AutoGenConfig: s.testProperties.Auto_gen_config,
OutputFileName: s.outputFilePath.Base(),
DeviceTemplate: "${ShellTestConfigTemplate}",
HostTemplate: "${ShellTestConfigTemplate}",
})
s.dataModules = make(map[string]android.Path)
ctx.VisitDirectDeps(func(dep android.Module) {
depTag := ctx.OtherModuleDependencyTag(dep)
switch depTag {
case shTestDataBinsTag, shTestDataDeviceBinsTag:
path := android.OutputFileForModule(ctx, dep, "")
s.addToDataModules(ctx, path.Base(), path)
case shTestDataLibsTag, shTestDataDeviceLibsTag:
if cc, isCc := dep.(*cc.Module); isCc {
// Copy to an intermediate output directory to append "lib[64]" to the path,
// so that it's compatible with the default rpath values.
var relPath string
if cc.Arch().ArchType.Multilib == "lib64" {
relPath = filepath.Join("lib64", cc.OutputFile().Path().Base())
} else {
relPath = filepath.Join("lib", cc.OutputFile().Path().Base())
}
if _, exist := s.dataModules[relPath]; exist {
return
}
relocatedLib := android.PathForModuleOut(ctx, "relocated").Join(ctx, relPath)
ctx.Build(pctx, android.BuildParams{
Rule: android.Cp,
Input: cc.OutputFile().Path(),
Output: relocatedLib,
})
s.addToDataModules(ctx, relPath, relocatedLib)
return
}
property := "data_libs"
if depTag == shTestDataDeviceBinsTag {
property = "data_device_libs"
}
ctx.PropertyErrorf(property, "%q of type %q is not supported", dep.Name(), ctx.OtherModuleType(dep))
}
})
installedData := ctx.InstallTestData(s.installDir, s.data)
s.installedFile = ctx.InstallExecutable(s.installDir, s.outputFilePath.Base(), s.outputFilePath, installedData...)
android.SetProvider(ctx, testing.TestModuleProviderKey, testing.TestModuleProviderData{})
}
func (s *ShTest) InstallInData() bool {
return true
}
func (s *ShTest) AndroidMkEntries() []android.AndroidMkEntries {
return []android.AndroidMkEntries{android.AndroidMkEntries{
Class: "NATIVE_TESTS",
OutputFile: android.OptionalPathForPath(s.outputFilePath),
Include: "$(BUILD_SYSTEM)/soong_cc_rust_prebuilt.mk",
ExtraEntries: []android.AndroidMkExtraEntriesFunc{
func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
s.customAndroidMkEntries(entries)
entries.SetPath("LOCAL_MODULE_PATH", s.installDir)
entries.AddCompatibilityTestSuites(s.testProperties.Test_suites...)
if s.testConfig != nil {
entries.SetPath("LOCAL_FULL_TEST_CONFIG", s.testConfig)
}
if s.testProperties.Data_bins != nil {
entries.AddStrings("LOCAL_TEST_DATA_BINS", s.testProperties.Data_bins...)
}
entries.SetBoolIfTrue("LOCAL_COMPATIBILITY_PER_TESTCASE_DIRECTORY", Bool(s.testProperties.Per_testcase_directory))
s.testProperties.Test_options.SetAndroidMkEntries(entries)
},
},
}}
}
func initShBinaryModule(s *ShBinary) {
s.AddProperties(&s.properties)
}
// sh_binary is for a shell script or batch file to be installed as an
// executable binary to <partition>/bin.
func ShBinaryFactory() android.Module {
module := &ShBinary{}
initShBinaryModule(module)
android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibFirst)
return module
}
// sh_binary_host is for a shell script to be installed as an executable binary
// to $(HOST_OUT)/bin.
func ShBinaryHostFactory() android.Module {
module := &ShBinary{}
initShBinaryModule(module)
android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst)
return module
}
// sh_test defines a shell script based test module.
func ShTestFactory() android.Module {
module := &ShTest{}
initShBinaryModule(&module.ShBinary)
module.AddProperties(&module.testProperties)
android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibFirst)
return module
}
// sh_test_host defines a shell script based test module that runs on a host.
func ShTestHostFactory() android.Module {
module := &ShTest{}
initShBinaryModule(&module.ShBinary)
module.AddProperties(&module.testProperties)
// Default sh_test_host to unit_tests = true
if module.testProperties.Test_options.Unit_test == nil {
module.testProperties.Test_options.Unit_test = proptools.BoolPtr(true)
}
android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst)
return module
}
var Bool = proptools.Bool