platform_build_soong/java/app.go
Colin Cross 68a7023436 Fix ordering of overlays
Device overlays are passed to Soong in highest priority to lowest
priority order, but aapt2 keeps the last value provided on the
command line.  Reverse the order that overlayGlobResults are
collected.

Bug: 71595164
Test: app_test.go
Change-Id: Ibc6b8297f1548766c5c92f3be969b89736dc8dff
2018-01-04 14:52:45 -08:00

451 lines
14 KiB
Go

// Copyright 2015 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
// This file contains the module types for compiling Android apps.
import (
"path/filepath"
"strings"
"github.com/google/blueprint/proptools"
"android/soong/android"
)
func init() {
android.RegisterPreSingletonType("overlay", OverlaySingletonFactory)
android.RegisterModuleType("android_app", AndroidAppFactory)
}
// AAR prebuilts
// AndroidManifest.xml merging
// package splits
type androidAppProperties struct {
// path to a certificate, or the name of a certificate in the default
// certificate directory, or blank to use the default product certificate
Certificate *string
// paths to extra certificates to sign the apk with
Additional_certificates []string
// If set, create package-export.apk, which other packages can
// use to get PRODUCT-agnostic resource data like IDs and type definitions.
Export_package_resources *bool
// flags passed to aapt when creating the apk
Aaptflags []string
// list of resource labels to generate individual resource packages
Package_splits []string
// list of directories relative to the Blueprints file containing assets.
// Defaults to "assets"
Asset_dirs []string
// list of directories relative to the Blueprints file containing
// Android resources
Resource_dirs []string
Instrumentation_for *string
// Specifies that this app should be installed to the priv-app directory,
// where the system will grant it additional privileges not available to
// normal apps.
Privileged *bool
}
type AndroidApp struct {
Module
appProperties androidAppProperties
aaptSrcJar android.Path
exportPackage android.Path
rroDirs android.Paths
manifestPath android.Path
certificate certificate
}
type certificate struct {
pem, key android.Path
}
func (a *AndroidApp) DepsMutator(ctx android.BottomUpMutatorContext) {
a.Module.deps(ctx)
if !Bool(a.properties.No_framework_libs) && !Bool(a.properties.No_standard_libs) {
switch String(a.deviceProperties.Sdk_version) { // TODO: Res_sdk_version?
case "current", "system_current", "test_current", "":
ctx.AddDependency(ctx.Module(), frameworkResTag, "framework-res")
default:
// We'll already have a dependency on an sdk prebuilt android.jar
}
}
}
func (a *AndroidApp) GenerateAndroidBuildActions(ctx android.ModuleContext) {
linkFlags, linkDeps, resDirs, overlayDirs, rroDirs, manifestPath := a.aapt2Flags(ctx)
packageRes := android.PathForModuleOut(ctx, "package-res.apk")
srcJar := android.PathForModuleGen(ctx, "R.jar")
proguardOptionsFile := android.PathForModuleGen(ctx, "proguard.options")
var compiledRes, compiledOverlay android.Paths
for _, dir := range resDirs {
compiledRes = append(compiledRes, aapt2Compile(ctx, dir.dir, dir.files).Paths()...)
}
for _, dir := range overlayDirs {
compiledOverlay = append(compiledOverlay, aapt2Compile(ctx, dir.dir, dir.files).Paths()...)
}
aapt2Link(ctx, packageRes, srcJar, proguardOptionsFile,
linkFlags, linkDeps, compiledRes, compiledOverlay)
a.exportPackage = packageRes
a.aaptSrcJar = srcJar
ctx.CheckbuildFile(proguardOptionsFile)
ctx.CheckbuildFile(a.exportPackage)
ctx.CheckbuildFile(a.aaptSrcJar)
// apps manifests are handled by aapt, don't let Module see them
a.properties.Manifest = nil
if String(a.appProperties.Instrumentation_for) == "" {
a.properties.Instrument = true
}
a.Module.extraProguardFlagFiles = append(a.Module.extraProguardFlagFiles,
proguardOptionsFile)
if ctx.ModuleName() != "framework-res" {
a.Module.compile(ctx, a.aaptSrcJar)
}
c := String(a.appProperties.Certificate)
switch {
case c == "":
pem, key := ctx.Config().DefaultAppCertificate(ctx)
a.certificate = certificate{pem, key}
case strings.ContainsRune(c, '/'):
a.certificate = certificate{
android.PathForSource(ctx, c+".x509.pem"),
android.PathForSource(ctx, c+".pk8"),
}
default:
defaultDir := ctx.Config().DefaultAppCertificateDir(ctx)
a.certificate = certificate{
defaultDir.Join(ctx, c+".x509.pem"),
defaultDir.Join(ctx, c+".pk8"),
}
}
certificates := []certificate{a.certificate}
for _, c := range a.appProperties.Additional_certificates {
certificates = append(certificates, certificate{
android.PathForSource(ctx, c+".x509.pem"),
android.PathForSource(ctx, c+".pk8"),
})
}
packageFile := android.PathForModuleOut(ctx, "package.apk")
CreateAppPackage(ctx, packageFile, a.exportPackage, a.outputFile, certificates)
a.outputFile = packageFile
a.rroDirs = rroDirs
a.manifestPath = manifestPath
if ctx.ModuleName() == "framework-res" {
// framework-res.apk is installed as system/framework/framework-res.apk
ctx.InstallFile(android.PathForModuleInstall(ctx, "framework"), ctx.ModuleName()+".apk", a.outputFile)
} else if Bool(a.appProperties.Privileged) {
ctx.InstallFile(android.PathForModuleInstall(ctx, "priv-app"), ctx.ModuleName()+".apk", a.outputFile)
} else {
ctx.InstallFile(android.PathForModuleInstall(ctx, "app"), ctx.ModuleName()+".apk", a.outputFile)
}
}
var aaptIgnoreFilenames = []string{
".svn",
".git",
".ds_store",
"*.scc",
".*",
"CVS",
"thumbs.db",
"picasa.ini",
"*~",
}
type globbedResourceDir struct {
dir android.Path
files android.Paths
}
func (a *AndroidApp) aapt2Flags(ctx android.ModuleContext) (flags []string, deps android.Paths,
resDirs, overlayDirs []globbedResourceDir, rroDirs android.Paths, manifestPath android.Path) {
hasVersionCode := false
hasVersionName := false
hasProduct := false
for _, f := range a.appProperties.Aaptflags {
if strings.HasPrefix(f, "--version-code") {
hasVersionCode = true
} else if strings.HasPrefix(f, "--version-name") {
hasVersionName = true
} else if strings.HasPrefix(f, "--product") {
hasProduct = true
}
}
var linkFlags []string
// Flags specified in Android.bp
linkFlags = append(linkFlags, a.appProperties.Aaptflags...)
linkFlags = append(linkFlags, "--no-static-lib-packages")
// Find implicit or explicit asset and resource dirs
assetDirs := android.PathsWithOptionalDefaultForModuleSrc(ctx, a.appProperties.Asset_dirs, "assets")
resourceDirs := android.PathsWithOptionalDefaultForModuleSrc(ctx, a.appProperties.Resource_dirs, "res")
var linkDeps android.Paths
// Glob directories into lists of paths
for _, dir := range resourceDirs {
resDirs = append(resDirs, globbedResourceDir{
dir: dir,
files: resourceGlob(ctx, dir),
})
resOverlayDirs, resRRODirs := overlayResourceGlob(ctx, dir)
overlayDirs = append(overlayDirs, resOverlayDirs...)
rroDirs = append(rroDirs, resRRODirs...)
}
var assetFiles android.Paths
for _, dir := range assetDirs {
assetFiles = append(assetFiles, resourceGlob(ctx, dir)...)
}
// App manifest file
var manifestFile string
if a.properties.Manifest == nil {
manifestFile = "AndroidManifest.xml"
} else {
manifestFile = *a.properties.Manifest
}
manifestPath = android.PathForModuleSrc(ctx, manifestFile)
linkFlags = append(linkFlags, "--manifest "+manifestPath.String())
linkDeps = append(linkDeps, manifestPath)
linkFlags = append(linkFlags, android.JoinWithPrefix(assetDirs.Strings(), "-A "))
linkDeps = append(linkDeps, assetFiles...)
// Include dirs
ctx.VisitDirectDeps(func(module android.Module) {
var depFiles android.Paths
if javaDep, ok := module.(Dependency); ok {
// TODO: shared android libraries
if ctx.OtherModuleName(module) == "framework-res" {
depFiles = android.Paths{javaDep.(*AndroidApp).exportPackage}
}
}
for _, dep := range depFiles {
linkFlags = append(linkFlags, "-I "+dep.String())
}
linkDeps = append(linkDeps, depFiles...)
})
// SDK version flags
sdkVersion := String(a.deviceProperties.Sdk_version)
switch sdkVersion {
case "", "current", "system_current", "test_current":
sdkVersion = proptools.NinjaEscape([]string{ctx.Config().AppsDefaultVersionName()})[0]
}
linkFlags = append(linkFlags, "--min-sdk-version "+sdkVersion)
linkFlags = append(linkFlags, "--target-sdk-version "+sdkVersion)
// Product characteristics
if !hasProduct && len(ctx.Config().ProductAAPTCharacteristics()) > 0 {
linkFlags = append(linkFlags, "--product", ctx.Config().ProductAAPTCharacteristics())
}
// Product AAPT config
for _, aaptConfig := range ctx.Config().ProductAAPTConfig() {
linkFlags = append(linkFlags, "-c", aaptConfig)
}
// Product AAPT preferred config
if len(ctx.Config().ProductAAPTPreferredConfig()) > 0 {
linkFlags = append(linkFlags, "--preferred-density", ctx.Config().ProductAAPTPreferredConfig())
}
// Version code
if !hasVersionCode {
linkFlags = append(linkFlags, "--version-code", ctx.Config().PlatformSdkVersion())
}
if !hasVersionName {
versionName := proptools.NinjaEscape([]string{ctx.Config().AppsDefaultVersionName()})[0]
linkFlags = append(linkFlags, "--version-name ", versionName)
}
if String(a.appProperties.Instrumentation_for) != "" {
linkFlags = append(linkFlags,
"--rename-instrumentation-target-package",
String(a.appProperties.Instrumentation_for))
}
// TODO: LOCAL_PACKAGE_OVERRIDES
// $(addprefix --rename-manifest-package , $(PRIVATE_MANIFEST_PACKAGE_NAME)) \
return linkFlags, linkDeps, resDirs, overlayDirs, rroDirs, manifestPath
}
func AndroidAppFactory() android.Module {
module := &AndroidApp{}
module.Module.deviceProperties.Optimize.Enabled = proptools.BoolPtr(true)
module.Module.deviceProperties.Optimize.Shrink = proptools.BoolPtr(true)
module.AddProperties(
&module.Module.properties,
&module.Module.deviceProperties,
&module.appProperties)
android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
return module
}
func resourceGlob(ctx android.ModuleContext, dir android.Path) android.Paths {
var ret android.Paths
files := ctx.Glob(filepath.Join(dir.String(), "**/*"), aaptIgnoreFilenames)
for _, f := range files {
if isDir, err := ctx.Fs().IsDir(f.String()); err != nil {
ctx.ModuleErrorf("error in IsDir(%s): %s", f.String(), err.Error())
return nil
} else if !isDir {
ret = append(ret, f)
}
}
return ret
}
type overlayGlobResult struct {
dir string
paths android.DirectorySortedPaths
// Set to true of the product has selected that values in this overlay should not be moved to
// Runtime Resource Overlay (RRO) packages.
excludeFromRRO bool
}
const overlayDataKey = "overlayDataKey"
func overlayResourceGlob(ctx android.ModuleContext, dir android.Path) (res []globbedResourceDir,
rroDirs android.Paths) {
overlayData := ctx.Config().Get(overlayDataKey).([]overlayGlobResult)
// Runtime resource overlays (RRO) may be turned on by the product config for some modules
rroEnabled := false
enforceRROTargets := ctx.Config().ProductVariables.EnforceRROTargets
if enforceRROTargets != nil {
if len(*enforceRROTargets) == 1 && (*enforceRROTargets)[0] == "*" {
rroEnabled = true
} else if inList(ctx.ModuleName(), *enforceRROTargets) {
rroEnabled = true
}
}
for _, data := range overlayData {
files := data.paths.PathsInDirectory(filepath.Join(data.dir, dir.String()))
if len(files) > 0 {
overlayModuleDir := android.PathForSource(ctx, data.dir, dir.String())
// If enforce RRO is enabled for this module and this overlay is not in the
// exclusion list, ignore the overlay. The list of ignored overlays will be
// passed to Make to be turned into an RRO package.
if rroEnabled && !data.excludeFromRRO {
rroDirs = append(rroDirs, overlayModuleDir)
} else {
res = append(res, globbedResourceDir{
dir: overlayModuleDir,
files: files,
})
}
}
}
return res, rroDirs
}
func OverlaySingletonFactory() android.Singleton {
return overlaySingleton{}
}
type overlaySingleton struct{}
func (overlaySingleton) GenerateBuildActions(ctx android.SingletonContext) {
// Specific overlays may be excluded from Runtime Resource Overlays by the product config
var rroExcludedOverlays []string
if ctx.Config().ProductVariables.EnforceRROExcludedOverlays != nil {
rroExcludedOverlays = *ctx.Config().ProductVariables.EnforceRROExcludedOverlays
}
var overlayData []overlayGlobResult
overlayDirs := ctx.Config().ResourceOverlays()
for i := range overlayDirs {
// Iterate backwards through the list of overlay directories so that the later, lower-priority
// directories in the list show up earlier in the command line to aapt2.
overlay := overlayDirs[len(overlayDirs)-1-i]
var result overlayGlobResult
result.dir = overlay
// Mark overlays that will not have Runtime Resource Overlays enforced on them
for _, exclude := range rroExcludedOverlays {
if strings.HasPrefix(overlay, exclude) {
result.excludeFromRRO = true
}
}
files, err := ctx.GlobWithDeps(filepath.Join(overlay, "**/*"), aaptIgnoreFilenames)
if err != nil {
ctx.Errorf("failed to glob resource dir %q: %s", overlay, err.Error())
continue
}
var paths android.Paths
for _, f := range files {
if isDir, err := ctx.Fs().IsDir(f); err != nil {
ctx.Errorf("error in IsDir(%s): %s", f, err.Error())
return
} else if !isDir {
paths = append(paths, android.PathForSource(ctx, f))
}
}
result.paths = android.PathsToDirectorySortedPaths(paths)
overlayData = append(overlayData, result)
}
ctx.Config().Once(overlayDataKey, func() interface{} {
return overlayData
})
}