452 lines
14 KiB
Go
452 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
|
|
})
|
|
}
|