1// Copyright 2017 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
17import (
18	"path/filepath"
19	"sort"
20	"strconv"
21	"strings"
22
23	"github.com/google/blueprint"
24
25	"android/soong/android"
26)
27
28const AAPT2_SHARD_SIZE = 100
29
30// Convert input resource file path to output file path.
31// values-[config]/<file>.xml -> values-[config]_<file>.arsc.flat;
32// For other resource file, just replace the last "/" with "_" and
33// add .flat extension.
34func pathToAapt2Path(ctx android.ModuleContext, res android.Path) android.WritablePath {
35
36	name := res.Base()
37	subDir := filepath.Dir(res.String())
38	subDir, lastDir := filepath.Split(subDir)
39	if strings.HasPrefix(lastDir, "values") {
40		name = strings.TrimSuffix(name, ".xml") + ".arsc"
41	}
42	name = lastDir + "_" + name + ".flat"
43	return android.PathForModuleOut(ctx, "aapt2", subDir, name)
44}
45
46func pathsToAapt2Paths(ctx android.ModuleContext, resPaths android.Paths) android.WritablePaths {
47	outPaths := make(android.WritablePaths, len(resPaths))
48
49	for i, res := range resPaths {
50		outPaths[i] = pathToAapt2Path(ctx, res)
51	}
52
53	return outPaths
54}
55
56var aapt2CompileRule = pctx.AndroidStaticRule("aapt2Compile",
57	blueprint.RuleParams{
58		Command:     `${config.Aapt2Cmd} compile -o $outDir $cFlags $in`,
59		CommandDeps: []string{"${config.Aapt2Cmd}"},
60	},
61	"outDir", "cFlags")
62
63func aapt2Compile(ctx android.ModuleContext, dir android.Path, paths android.Paths,
64	flags []string) android.WritablePaths {
65
66	shards := android.ShardPaths(paths, AAPT2_SHARD_SIZE)
67
68	ret := make(android.WritablePaths, 0, len(paths))
69
70	for i, shard := range shards {
71		outPaths := pathsToAapt2Paths(ctx, shard)
72		ret = append(ret, outPaths...)
73
74		shardDesc := ""
75		if i != 0 {
76			shardDesc = " " + strconv.Itoa(i+1)
77		}
78
79		ctx.Build(pctx, android.BuildParams{
80			Rule:        aapt2CompileRule,
81			Description: "aapt2 compile " + dir.String() + shardDesc,
82			Inputs:      shard,
83			Outputs:     outPaths,
84			Args: map[string]string{
85				"outDir": android.PathForModuleOut(ctx, "aapt2", dir.String()).String(),
86				"cFlags": strings.Join(flags, " "),
87			},
88		})
89	}
90
91	sort.Slice(ret, func(i, j int) bool {
92		return ret[i].String() < ret[j].String()
93	})
94	return ret
95}
96
97var aapt2CompileZipRule = pctx.AndroidStaticRule("aapt2CompileZip",
98	blueprint.RuleParams{
99		Command: `${config.ZipSyncCmd} -d $resZipDir $zipSyncFlags $in && ` +
100			`${config.Aapt2Cmd} compile -o $out $cFlags --dir $resZipDir`,
101		CommandDeps: []string{
102			"${config.Aapt2Cmd}",
103			"${config.ZipSyncCmd}",
104		},
105	}, "cFlags", "resZipDir", "zipSyncFlags")
106
107func aapt2CompileZip(ctx android.ModuleContext, flata android.WritablePath, zip android.Path, zipPrefix string,
108	flags []string) {
109
110	if zipPrefix != "" {
111		zipPrefix = "--zip-prefix " + zipPrefix
112	}
113	ctx.Build(pctx, android.BuildParams{
114		Rule:        aapt2CompileZipRule,
115		Description: "aapt2 compile zip",
116		Input:       zip,
117		Output:      flata,
118		Args: map[string]string{
119			"cFlags":       strings.Join(flags, " "),
120			"resZipDir":    android.PathForModuleOut(ctx, "aapt2", "reszip", flata.Base()).String(),
121			"zipSyncFlags": zipPrefix,
122		},
123	})
124}
125
126var aapt2LinkRule = pctx.AndroidStaticRule("aapt2Link",
127	blueprint.RuleParams{
128		Command: `rm -rf $genDir && ` +
129			`${config.Aapt2Cmd} link -o $out $flags --java $genDir --proguard $proguardOptions ` +
130			`--output-text-symbols ${rTxt} $inFlags && ` +
131			`${config.SoongZipCmd} -write_if_changed -jar -o $genJar -C $genDir -D $genDir &&` +
132			`${config.ExtractJarPackagesCmd} -i $genJar -o $extraPackages --prefix '--extra-packages '`,
133
134		CommandDeps: []string{
135			"${config.Aapt2Cmd}",
136			"${config.SoongZipCmd}",
137			"${config.ExtractJarPackagesCmd}",
138		},
139		Restat: true,
140	},
141	"flags", "inFlags", "proguardOptions", "genDir", "genJar", "rTxt", "extraPackages")
142
143var fileListToFileRule = pctx.AndroidStaticRule("fileListToFile",
144	blueprint.RuleParams{
145		Command:        `cp $out.rsp $out`,
146		Rspfile:        "$out.rsp",
147		RspfileContent: "$in",
148	})
149
150var mergeAssetsRule = pctx.AndroidStaticRule("mergeAssets",
151	blueprint.RuleParams{
152		Command:     `${config.MergeZipsCmd} ${out} ${in}`,
153		CommandDeps: []string{"${config.MergeZipsCmd}"},
154	})
155
156func aapt2Link(ctx android.ModuleContext,
157	packageRes, genJar, proguardOptions, rTxt, extraPackages android.WritablePath,
158	flags []string, deps android.Paths,
159	compiledRes, compiledOverlay, assetPackages android.Paths, splitPackages android.WritablePaths) {
160
161	genDir := android.PathForModuleGen(ctx, "aapt2", "R")
162
163	var inFlags []string
164
165	if len(compiledRes) > 0 {
166		resFileList := android.PathForModuleOut(ctx, "aapt2", "res.list")
167		// Write out file lists to files
168		ctx.Build(pctx, android.BuildParams{
169			Rule:        fileListToFileRule,
170			Description: "resource file list",
171			Inputs:      compiledRes,
172			Output:      resFileList,
173		})
174
175		deps = append(deps, compiledRes...)
176		deps = append(deps, resFileList)
177		inFlags = append(inFlags, "@"+resFileList.String())
178	}
179
180	if len(compiledOverlay) > 0 {
181		overlayFileList := android.PathForModuleOut(ctx, "aapt2", "overlay.list")
182		ctx.Build(pctx, android.BuildParams{
183			Rule:        fileListToFileRule,
184			Description: "overlay resource file list",
185			Inputs:      compiledOverlay,
186			Output:      overlayFileList,
187		})
188
189		deps = append(deps, compiledOverlay...)
190		deps = append(deps, overlayFileList)
191		inFlags = append(inFlags, "-R", "@"+overlayFileList.String())
192	}
193
194	implicitOutputs := append(splitPackages, proguardOptions, genJar, rTxt, extraPackages)
195	linkOutput := packageRes
196
197	// AAPT2 ignores assets in overlays. Merge them after linking.
198	if len(assetPackages) > 0 {
199		linkOutput = android.PathForModuleOut(ctx, "aapt2", "package-res.apk")
200		inputZips := append(android.Paths{linkOutput}, assetPackages...)
201		ctx.Build(pctx, android.BuildParams{
202			Rule:        mergeAssetsRule,
203			Inputs:      inputZips,
204			Output:      packageRes,
205			Description: "merge assets from dependencies",
206		})
207	}
208
209	ctx.Build(pctx, android.BuildParams{
210		Rule:            aapt2LinkRule,
211		Description:     "aapt2 link",
212		Implicits:       deps,
213		Output:          linkOutput,
214		ImplicitOutputs: implicitOutputs,
215		Args: map[string]string{
216			"flags":           strings.Join(flags, " "),
217			"inFlags":         strings.Join(inFlags, " "),
218			"proguardOptions": proguardOptions.String(),
219			"genDir":          genDir.String(),
220			"genJar":          genJar.String(),
221			"rTxt":            rTxt.String(),
222			"extraPackages":   extraPackages.String(),
223		},
224	})
225}
226
227var aapt2ConvertRule = pctx.AndroidStaticRule("aapt2Convert",
228	blueprint.RuleParams{
229		Command:     `${config.Aapt2Cmd} convert --output-format proto $in -o $out`,
230		CommandDeps: []string{"${config.Aapt2Cmd}"},
231	})
232
233func aapt2Convert(ctx android.ModuleContext, out android.WritablePath, in android.Path) {
234	ctx.Build(pctx, android.BuildParams{
235		Rule:        aapt2ConvertRule,
236		Input:       in,
237		Output:      out,
238		Description: "convert to proto",
239	})
240}
241