1// Copyright 2015 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package java
16
17// This file contains the module types for compiling Android apps.
18
19import (
20	"path/filepath"
21	"strings"
22
23	"github.com/google/blueprint"
24
25	"android/soong/common"
26)
27
28// AAR prebuilts
29// AndroidManifest.xml merging
30// package splits
31
32type androidAppProperties struct {
33	// path to a certificate, or the name of a certificate in the default
34	// certificate directory, or blank to use the default product certificate
35	Certificate string
36
37	// paths to extra certificates to sign the apk with
38	Additional_certificates []string
39
40	// If set, create package-export.apk, which other packages can
41	// use to get PRODUCT-agnostic resource data like IDs and type definitions.
42	Export_package_resources bool
43
44	// flags passed to aapt when creating the apk
45	Aaptflags []string
46
47	// list of resource labels to generate individual resource packages
48	Package_splits []string
49
50	// list of directories relative to the Blueprints file containing assets.
51	// Defaults to "assets"
52	Asset_dirs []string
53
54	// list of directories relative to the Blueprints file containing
55	// Java resources
56	Android_resource_dirs []string
57}
58
59type AndroidApp struct {
60	javaBase
61
62	appProperties androidAppProperties
63
64	aaptJavaFileList common.Path
65	exportPackage    common.Path
66}
67
68func (a *AndroidApp) JavaDependencies(ctx AndroidJavaModuleContext) []string {
69	deps := a.javaBase.JavaDependencies(ctx)
70
71	if !a.properties.No_standard_libraries {
72		switch a.properties.Sdk_version { // TODO: Res_sdk_version?
73		case "current", "system_current", "":
74			deps = append(deps, "framework-res")
75		default:
76			// We'll already have a dependency on an sdk prebuilt android.jar
77		}
78	}
79
80	return deps
81}
82
83func (a *AndroidApp) GenerateJavaBuildActions(ctx common.AndroidModuleContext) {
84	aaptFlags, aaptDeps, hasResources := a.aaptFlags(ctx)
85
86	if hasResources {
87		// First generate R.java so we can build the .class files
88		aaptRJavaFlags := append([]string(nil), aaptFlags...)
89
90		publicResourcesFile, proguardOptionsFile, aaptJavaFileList :=
91			CreateResourceJavaFiles(ctx, aaptRJavaFlags, aaptDeps)
92		a.aaptJavaFileList = aaptJavaFileList
93		a.ExtraSrcLists = append(a.ExtraSrcLists, aaptJavaFileList)
94
95		if a.appProperties.Export_package_resources {
96			aaptPackageFlags := append([]string(nil), aaptFlags...)
97			var hasProduct bool
98			for _, f := range aaptPackageFlags {
99				if strings.HasPrefix(f, "--product") {
100					hasProduct = true
101					break
102				}
103			}
104
105			if !hasProduct {
106				aaptPackageFlags = append(aaptPackageFlags,
107					"--product "+ctx.AConfig().ProductAaptCharacteristics())
108			}
109			a.exportPackage = CreateExportPackage(ctx, aaptPackageFlags, aaptDeps)
110			ctx.CheckbuildFile(a.exportPackage)
111		}
112		ctx.CheckbuildFile(publicResourcesFile)
113		ctx.CheckbuildFile(proguardOptionsFile)
114		ctx.CheckbuildFile(aaptJavaFileList)
115	}
116
117	// apps manifests are handled by aapt, don't let javaBase see them
118	a.properties.Manifest = nil
119
120	//if !ctx.ContainsProperty("proguard.enabled") {
121	//	a.properties.Proguard.Enabled = true
122	//}
123
124	a.javaBase.GenerateJavaBuildActions(ctx)
125
126	aaptPackageFlags := append([]string(nil), aaptFlags...)
127	var hasProduct bool
128	for _, f := range aaptPackageFlags {
129		if strings.HasPrefix(f, "--product") {
130			hasProduct = true
131			break
132		}
133	}
134
135	if !hasProduct {
136		aaptPackageFlags = append(aaptPackageFlags,
137			"--product "+ctx.AConfig().ProductAaptCharacteristics())
138	}
139
140	certificate := a.appProperties.Certificate
141	if certificate == "" {
142		certificate = ctx.AConfig().DefaultAppCertificate(ctx).String()
143	} else if dir, _ := filepath.Split(certificate); dir == "" {
144		certificate = filepath.Join(ctx.AConfig().DefaultAppCertificateDir(ctx).String(), certificate)
145	} else {
146		certificate = filepath.Join(common.PathForSource(ctx).String(), certificate)
147	}
148
149	certificates := []string{certificate}
150	for _, c := range a.appProperties.Additional_certificates {
151		certificates = append(certificates, filepath.Join(common.PathForSource(ctx).String(), c))
152	}
153
154	a.outputFile = CreateAppPackage(ctx, aaptPackageFlags, a.outputFile, certificates)
155	ctx.InstallFileName(common.PathForModuleInstall(ctx, "app"), ctx.ModuleName()+".apk", a.outputFile)
156}
157
158var aaptIgnoreFilenames = []string{
159	".svn",
160	".git",
161	".ds_store",
162	"*.scc",
163	".*",
164	"CVS",
165	"thumbs.db",
166	"picasa.ini",
167	"*~",
168}
169
170func (a *AndroidApp) aaptFlags(ctx common.AndroidModuleContext) ([]string, common.Paths, bool) {
171	aaptFlags := a.appProperties.Aaptflags
172	hasVersionCode := false
173	hasVersionName := false
174	for _, f := range aaptFlags {
175		if strings.HasPrefix(f, "--version-code") {
176			hasVersionCode = true
177		} else if strings.HasPrefix(f, "--version-name") {
178			hasVersionName = true
179		}
180	}
181
182	if true /* is not a test */ {
183		aaptFlags = append(aaptFlags, "-z")
184	}
185
186	assetDirs := common.PathsWithOptionalDefaultForModuleSrc(ctx, a.appProperties.Asset_dirs, "assets")
187	resourceDirs := common.PathsWithOptionalDefaultForModuleSrc(ctx, a.appProperties.Android_resource_dirs, "res")
188
189	var overlayResourceDirs common.Paths
190	// For every resource directory, check if there is an overlay directory with the same path.
191	// If found, it will be prepended to the list of resource directories.
192	for _, overlayDir := range ctx.AConfig().ResourceOverlays() {
193		for _, resourceDir := range resourceDirs {
194			overlay := overlayDir.OverlayPath(ctx, resourceDir)
195			if overlay.Valid() {
196				overlayResourceDirs = append(overlayResourceDirs, overlay.Path())
197			}
198		}
199	}
200
201	if len(overlayResourceDirs) > 0 {
202		resourceDirs = append(overlayResourceDirs, resourceDirs...)
203	}
204
205	// aapt needs to rerun if any files are added or modified in the assets or resource directories,
206	// use glob to create a filelist.
207	var aaptDeps common.Paths
208	var hasResources bool
209	for _, d := range resourceDirs {
210		newDeps := ctx.Glob("app_resources", filepath.Join(d.String(), "**/*"), aaptIgnoreFilenames)
211		aaptDeps = append(aaptDeps, newDeps...)
212		if len(newDeps) > 0 {
213			hasResources = true
214		}
215	}
216	for _, d := range assetDirs {
217		newDeps := ctx.Glob("app_assets", filepath.Join(d.String(), "**/*"), aaptIgnoreFilenames)
218		aaptDeps = append(aaptDeps, newDeps...)
219	}
220
221	var manifestFile string
222	if a.properties.Manifest == nil {
223		manifestFile = "AndroidManifest.xml"
224	} else {
225		manifestFile = *a.properties.Manifest
226	}
227
228	manifestPath := common.PathForModuleSrc(ctx, manifestFile)
229	aaptDeps = append(aaptDeps, manifestPath)
230
231	aaptFlags = append(aaptFlags, "-M "+manifestPath.String())
232	aaptFlags = append(aaptFlags, common.JoinWithPrefix(assetDirs.Strings(), "-A "))
233	aaptFlags = append(aaptFlags, common.JoinWithPrefix(resourceDirs.Strings(), "-S "))
234
235	ctx.VisitDirectDeps(func(module blueprint.Module) {
236		var depFile common.OptionalPath
237		if sdkDep, ok := module.(sdkDependency); ok {
238			depFile = common.OptionalPathForPath(sdkDep.ClasspathFile())
239		} else if javaDep, ok := module.(JavaDependency); ok {
240			if ctx.OtherModuleName(module) == "framework-res" {
241				depFile = common.OptionalPathForPath(javaDep.(*javaBase).module.(*AndroidApp).exportPackage)
242			}
243		}
244		if depFile.Valid() {
245			aaptFlags = append(aaptFlags, "-I "+depFile.String())
246			aaptDeps = append(aaptDeps, depFile.Path())
247		}
248	})
249
250	sdkVersion := a.properties.Sdk_version
251	if sdkVersion == "" {
252		sdkVersion = ctx.AConfig().PlatformSdkVersion()
253	}
254
255	aaptFlags = append(aaptFlags, "--min-sdk-version "+sdkVersion)
256	aaptFlags = append(aaptFlags, "--target-sdk-version "+sdkVersion)
257
258	if !hasVersionCode {
259		aaptFlags = append(aaptFlags, "--version-code "+ctx.AConfig().PlatformSdkVersion())
260	}
261
262	if !hasVersionName {
263		aaptFlags = append(aaptFlags,
264			"--version-name "+ctx.AConfig().PlatformVersion()+"-"+ctx.AConfig().BuildNumber())
265	}
266
267	// TODO: LOCAL_PACKAGE_OVERRIDES
268	//    $(addprefix --rename-manifest-package , $(PRIVATE_MANIFEST_PACKAGE_NAME)) \
269
270	// TODO: LOCAL_INSTRUMENTATION_FOR
271	//    $(addprefix --rename-instrumentation-target-package , $(PRIVATE_MANIFEST_INSTRUMENTATION_FOR))
272
273	return aaptFlags, aaptDeps, hasResources
274}
275
276func AndroidAppFactory() (blueprint.Module, []interface{}) {
277	module := &AndroidApp{}
278
279	module.properties.Dex = true
280
281	return NewJavaBase(&module.javaBase, module, common.DeviceSupported, &module.appProperties)
282}
283