blob: 82db634d06bfa92e3f608a19d04bf4654083c454 [file] [log] [blame]
Sasha Smundak7a894a62020-05-06 21:23:08 -07001// Copyright 2020 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
15// Copies all the entries (APKs/APEXes) matching the target configuration from the given
16// APK set into a zip file. Run it without arguments to see usage details.
17package main
18
19import (
20 "flag"
21 "fmt"
22 "io"
23 "log"
Jaewoong Jungfa00c062020-05-14 14:15:24 -070024 "math"
Sasha Smundak7a894a62020-05-06 21:23:08 -070025 "os"
26 "regexp"
Jaewoong Jung11c1e0f2020-06-29 19:18:44 -070027 "sort"
Sasha Smundak7a894a62020-05-06 21:23:08 -070028 "strings"
29
Dan Willemsen4591b642021-05-24 14:24:12 -070030 "google.golang.org/protobuf/proto"
Sasha Smundak7a894a62020-05-06 21:23:08 -070031
Sam Delmericob74f0a02022-09-16 11:59:56 -040032 "android/soong/cmd/extract_apks/bundle_proto"
Dan Willemsen4591b642021-05-24 14:24:12 -070033 android_bundle_proto "android/soong/cmd/extract_apks/bundle_proto"
Sasha Smundak7a894a62020-05-06 21:23:08 -070034 "android/soong/third_party/zip"
35)
36
37type TargetConfig struct {
Jaewoong Jungfa00c062020-05-14 14:15:24 -070038 sdkVersion int32
39 screenDpi map[android_bundle_proto.ScreenDensity_DensityAlias]bool
40 // Map holding <ABI alias>:<its sequence number in the flag> info.
41 abis map[android_bundle_proto.Abi_AbiAlias]int
Sasha Smundak7a894a62020-05-06 21:23:08 -070042 allowPrereleased bool
43 stem string
44}
45
46// An APK set is a zip archive. An entry 'toc.pb' describes its contents.
47// It is a protobuf message BuildApkResult.
48type Toc *android_bundle_proto.BuildApksResult
49
50type ApkSet struct {
51 path string
52 reader *zip.ReadCloser
53 entries map[string]*zip.File
54}
55
56func newApkSet(path string) (*ApkSet, error) {
57 apkSet := &ApkSet{path: path, entries: make(map[string]*zip.File)}
58 var err error
59 if apkSet.reader, err = zip.OpenReader(apkSet.path); err != nil {
60 return nil, err
61 }
62 for _, f := range apkSet.reader.File {
63 apkSet.entries[f.Name] = f
64 }
65 return apkSet, nil
66}
67
68func (apkSet *ApkSet) getToc() (Toc, error) {
69 var err error
70 tocFile, ok := apkSet.entries["toc.pb"]
71 if !ok {
72 return nil, fmt.Errorf("%s: APK set should have toc.pb entry", apkSet.path)
73 }
74 rc, err := tocFile.Open()
75 if err != nil {
76 return nil, err
77 }
78 bytes := make([]byte, tocFile.FileHeader.UncompressedSize64)
Martin Stjernholm56d691e2022-09-16 00:22:52 +010079 if _, err := rc.Read(bytes); err != nil && err != io.EOF {
Sasha Smundak7a894a62020-05-06 21:23:08 -070080 return nil, err
81 }
82 rc.Close()
83 buildApksResult := new(android_bundle_proto.BuildApksResult)
84 if err = proto.Unmarshal(bytes, buildApksResult); err != nil {
85 return nil, err
86 }
87 return buildApksResult, nil
88}
89
90func (apkSet *ApkSet) close() {
91 apkSet.reader.Close()
92}
93
94// Matchers for selection criteria
Jaewoong Jungfa00c062020-05-14 14:15:24 -070095
Sasha Smundak7a894a62020-05-06 21:23:08 -070096type abiTargetingMatcher struct {
97 *android_bundle_proto.AbiTargeting
98}
99
100func (m abiTargetingMatcher) matches(config TargetConfig) bool {
101 if m.AbiTargeting == nil {
102 return true
103 }
104 if _, ok := config.abis[android_bundle_proto.Abi_UNSPECIFIED_CPU_ARCHITECTURE]; ok {
105 return true
106 }
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700107 // Find the one that appears first in the abis flags.
108 abiIdx := math.MaxInt32
Sasha Smundak7a894a62020-05-06 21:23:08 -0700109 for _, v := range m.GetValue() {
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700110 if i, ok := config.abis[v.Alias]; ok {
111 if i < abiIdx {
112 abiIdx = i
113 }
Sasha Smundak7a894a62020-05-06 21:23:08 -0700114 }
115 }
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700116 if abiIdx == math.MaxInt32 {
117 return false
118 }
119 // See if any alternatives appear before the above one.
120 for _, a := range m.GetAlternatives() {
121 if i, ok := config.abis[a.Alias]; ok {
122 if i < abiIdx {
123 // There is a better alternative. Skip this one.
124 return false
125 }
126 }
127 }
128 return true
Sasha Smundak7a894a62020-05-06 21:23:08 -0700129}
130
131type apkDescriptionMatcher struct {
132 *android_bundle_proto.ApkDescription
133}
134
Sam Delmericob48d57b2022-11-22 17:47:59 -0500135func (m apkDescriptionMatcher) matches(config TargetConfig, allAbisMustMatch bool) bool {
136 return m.ApkDescription == nil || (apkTargetingMatcher{m.Targeting}).matches(config, allAbisMustMatch)
Sasha Smundak7a894a62020-05-06 21:23:08 -0700137}
138
139type apkTargetingMatcher struct {
140 *android_bundle_proto.ApkTargeting
141}
142
Sam Delmericob48d57b2022-11-22 17:47:59 -0500143func (m apkTargetingMatcher) matches(config TargetConfig, allAbisMustMatch bool) bool {
Sasha Smundak7a894a62020-05-06 21:23:08 -0700144 return m.ApkTargeting == nil ||
145 (abiTargetingMatcher{m.AbiTargeting}.matches(config) &&
146 languageTargetingMatcher{m.LanguageTargeting}.matches(config) &&
147 screenDensityTargetingMatcher{m.ScreenDensityTargeting}.matches(config) &&
148 sdkVersionTargetingMatcher{m.SdkVersionTargeting}.matches(config) &&
Sam Delmericob48d57b2022-11-22 17:47:59 -0500149 multiAbiTargetingMatcher{m.MultiAbiTargeting}.matches(config, allAbisMustMatch))
Sasha Smundak7a894a62020-05-06 21:23:08 -0700150}
151
152type languageTargetingMatcher struct {
153 *android_bundle_proto.LanguageTargeting
154}
155
156func (m languageTargetingMatcher) matches(_ TargetConfig) bool {
157 if m.LanguageTargeting == nil {
158 return true
159 }
160 log.Fatal("language based entry selection is not implemented")
161 return false
162}
163
164type moduleMetadataMatcher struct {
165 *android_bundle_proto.ModuleMetadata
166}
167
168func (m moduleMetadataMatcher) matches(config TargetConfig) bool {
169 return m.ModuleMetadata == nil ||
170 (m.GetDeliveryType() == android_bundle_proto.DeliveryType_INSTALL_TIME &&
171 moduleTargetingMatcher{m.Targeting}.matches(config) &&
172 !m.IsInstant)
173}
174
175type moduleTargetingMatcher struct {
176 *android_bundle_proto.ModuleTargeting
177}
178
179func (m moduleTargetingMatcher) matches(config TargetConfig) bool {
180 return m.ModuleTargeting == nil ||
181 (sdkVersionTargetingMatcher{m.SdkVersionTargeting}.matches(config) &&
182 userCountriesTargetingMatcher{m.UserCountriesTargeting}.matches(config))
183}
184
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700185// A higher number means a higher priority.
186// This order must be kept identical to bundletool's.
187var multiAbiPriorities = map[android_bundle_proto.Abi_AbiAlias]int{
188 android_bundle_proto.Abi_ARMEABI: 1,
189 android_bundle_proto.Abi_ARMEABI_V7A: 2,
190 android_bundle_proto.Abi_ARM64_V8A: 3,
191 android_bundle_proto.Abi_X86: 4,
192 android_bundle_proto.Abi_X86_64: 5,
193 android_bundle_proto.Abi_MIPS: 6,
194 android_bundle_proto.Abi_MIPS64: 7,
195}
196
Sasha Smundak7a894a62020-05-06 21:23:08 -0700197type multiAbiTargetingMatcher struct {
198 *android_bundle_proto.MultiAbiTargeting
199}
200
Sam Delmericob74f0a02022-09-16 11:59:56 -0400201type multiAbiValue []*bundle_proto.Abi
202
203func (m multiAbiValue) compare(other multiAbiValue) int {
204 min := func(a, b int) int {
205 if a < b {
206 return a
207 }
208 return b
209 }
210
211 sortAbis := func(abiSlice multiAbiValue) func(i, j int) bool {
212 return func(i, j int) bool {
213 // sort priorities greatest to least
214 return multiAbiPriorities[abiSlice[i].Alias] > multiAbiPriorities[abiSlice[j].Alias]
215 }
216 }
217
Sam Delmericob48d57b2022-11-22 17:47:59 -0500218 sortedM := append(multiAbiValue{}, m...)
219 sort.Slice(sortedM, sortAbis(sortedM))
220 sortedOther := append(multiAbiValue{}, other...)
221 sort.Slice(sortedOther, sortAbis(sortedOther))
Sam Delmericob74f0a02022-09-16 11:59:56 -0400222
Sam Delmericob48d57b2022-11-22 17:47:59 -0500223 for i := 0; i < min(len(sortedM), len(sortedOther)); i++ {
224 if multiAbiPriorities[sortedM[i].Alias] > multiAbiPriorities[sortedOther[i].Alias] {
Sam Delmericob74f0a02022-09-16 11:59:56 -0400225 return 1
226 }
Sam Delmericob48d57b2022-11-22 17:47:59 -0500227 if multiAbiPriorities[sortedM[i].Alias] < multiAbiPriorities[sortedOther[i].Alias] {
Sam Delmericob74f0a02022-09-16 11:59:56 -0400228 return -1
229 }
230 }
231
Sam Delmericob48d57b2022-11-22 17:47:59 -0500232 return len(sortedM) - len(sortedOther)
Sam Delmericob74f0a02022-09-16 11:59:56 -0400233}
234
235// this logic should match the logic in bundletool at
236// https://github.com/google/bundletool/blob/ae0fc0162fd80d92ef8f4ef4527c066f0106942f/src/main/java/com/android/tools/build/bundletool/device/MultiAbiMatcher.java#L43
237// (note link is the commit at time of writing; but logic should always match the latest)
Sam Delmericob48d57b2022-11-22 17:47:59 -0500238func (t multiAbiTargetingMatcher) matches(config TargetConfig, allAbisMustMatch bool) bool {
Sasha Smundak7a894a62020-05-06 21:23:08 -0700239 if t.MultiAbiTargeting == nil {
240 return true
241 }
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700242 if _, ok := config.abis[android_bundle_proto.Abi_UNSPECIFIED_CPU_ARCHITECTURE]; ok {
243 return true
244 }
Sam Delmericob74f0a02022-09-16 11:59:56 -0400245
246 multiAbiIsValid := func(m multiAbiValue) bool {
Sam Delmericob48d57b2022-11-22 17:47:59 -0500247 numValid := 0
Sam Delmericob74f0a02022-09-16 11:59:56 -0400248 for _, abi := range m {
Sam Delmericob48d57b2022-11-22 17:47:59 -0500249 if _, ok := config.abis[abi.Alias]; ok {
250 numValid += 1
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700251 }
252 }
Sam Delmericob48d57b2022-11-22 17:47:59 -0500253 if numValid == 0 {
254 return false
255 } else if numValid > 0 && !allAbisMustMatch {
256 return true
257 } else {
258 return numValid == len(m)
259 }
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700260 }
Sam Delmericob74f0a02022-09-16 11:59:56 -0400261
262 // ensure that the current value is valid for our config
263 valueSetContainsViableAbi := false
264 multiAbiSet := t.GetValue()
265 for _, multiAbi := range multiAbiSet {
266 if multiAbiIsValid(multiAbi.GetAbi()) {
267 valueSetContainsViableAbi = true
Sam Delmericob48d57b2022-11-22 17:47:59 -0500268 break
Sam Delmericob74f0a02022-09-16 11:59:56 -0400269 }
270 }
271
272 if !valueSetContainsViableAbi {
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700273 return false
274 }
Sam Delmericob74f0a02022-09-16 11:59:56 -0400275
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700276 // See if there are any matching alternatives with a higher priority.
Sam Delmericob74f0a02022-09-16 11:59:56 -0400277 for _, altMultiAbi := range t.GetAlternatives() {
278 if !multiAbiIsValid(altMultiAbi.GetAbi()) {
279 continue
280 }
281
282 for _, multiAbi := range multiAbiSet {
283 valueAbis := multiAbiValue(multiAbi.GetAbi())
284 altAbis := multiAbiValue(altMultiAbi.GetAbi())
285 if valueAbis.compare(altAbis) < 0 {
286 // An alternative has a higher priority, don't use this one
287 return false
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700288 }
289 }
290 }
Sam Delmericob74f0a02022-09-16 11:59:56 -0400291
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700292 return true
Sasha Smundak7a894a62020-05-06 21:23:08 -0700293}
294
295type screenDensityTargetingMatcher struct {
296 *android_bundle_proto.ScreenDensityTargeting
297}
298
299func (m screenDensityTargetingMatcher) matches(config TargetConfig) bool {
300 if m.ScreenDensityTargeting == nil {
301 return true
302 }
303 if _, ok := config.screenDpi[android_bundle_proto.ScreenDensity_DENSITY_UNSPECIFIED]; ok {
304 return true
305 }
306 for _, v := range m.GetValue() {
307 switch x := v.GetDensityOneof().(type) {
308 case *android_bundle_proto.ScreenDensity_DensityAlias_:
309 if _, ok := config.screenDpi[x.DensityAlias]; ok {
310 return true
311 }
312 default:
313 log.Fatal("For screen density, only DPI name based entry selection (e.g. HDPI, XHDPI) is implemented")
314 }
315 }
316 return false
317}
318
319type sdkVersionTargetingMatcher struct {
320 *android_bundle_proto.SdkVersionTargeting
321}
322
323func (m sdkVersionTargetingMatcher) matches(config TargetConfig) bool {
324 const preReleaseVersion = 10000
325 if m.SdkVersionTargeting == nil {
326 return true
327 }
328 if len(m.Value) > 1 {
329 log.Fatal(fmt.Sprintf("sdk_version_targeting should not have multiple values:%#v", m.Value))
330 }
331 // Inspect only sdkVersionTargeting.Value.
332 // Even though one of the SdkVersionTargeting.Alternatives values may be
333 // better matching, we will select all of them
334 return m.Value[0].Min == nil ||
335 m.Value[0].Min.Value <= config.sdkVersion ||
336 (config.allowPrereleased && m.Value[0].Min.Value == preReleaseVersion)
337}
338
339type textureCompressionFormatTargetingMatcher struct {
340 *android_bundle_proto.TextureCompressionFormatTargeting
341}
342
343func (m textureCompressionFormatTargetingMatcher) matches(_ TargetConfig) bool {
344 if m.TextureCompressionFormatTargeting == nil {
345 return true
346 }
347 log.Fatal("texture based entry selection is not implemented")
348 return false
349}
350
351type userCountriesTargetingMatcher struct {
352 *android_bundle_proto.UserCountriesTargeting
353}
354
355func (m userCountriesTargetingMatcher) matches(_ TargetConfig) bool {
356 if m.UserCountriesTargeting == nil {
357 return true
358 }
359 log.Fatal("country based entry selection is not implemented")
360 return false
361}
362
363type variantTargetingMatcher struct {
364 *android_bundle_proto.VariantTargeting
365}
366
Sam Delmericob48d57b2022-11-22 17:47:59 -0500367func (m variantTargetingMatcher) matches(config TargetConfig, allAbisMustMatch bool) bool {
Sasha Smundak7a894a62020-05-06 21:23:08 -0700368 if m.VariantTargeting == nil {
369 return true
370 }
371 return sdkVersionTargetingMatcher{m.SdkVersionTargeting}.matches(config) &&
372 abiTargetingMatcher{m.AbiTargeting}.matches(config) &&
Sam Delmericob48d57b2022-11-22 17:47:59 -0500373 multiAbiTargetingMatcher{m.MultiAbiTargeting}.matches(config, allAbisMustMatch) &&
Sasha Smundak7a894a62020-05-06 21:23:08 -0700374 screenDensityTargetingMatcher{m.ScreenDensityTargeting}.matches(config) &&
375 textureCompressionFormatTargetingMatcher{m.TextureCompressionFormatTargeting}.matches(config)
376}
377
378type SelectionResult struct {
379 moduleName string
380 entries []string
381}
382
383// Return all entries matching target configuration
384func selectApks(toc Toc, targetConfig TargetConfig) SelectionResult {
Sam Delmericob48d57b2022-11-22 17:47:59 -0500385 checkMatching := func(allAbisMustMatch bool) SelectionResult {
386 var result SelectionResult
387 for _, variant := range (*toc).GetVariant() {
388 if !(variantTargetingMatcher{variant.GetTargeting()}.matches(targetConfig, allAbisMustMatch)) {
Sasha Smundak7a894a62020-05-06 21:23:08 -0700389 continue
390 }
Sam Delmericob48d57b2022-11-22 17:47:59 -0500391 for _, as := range variant.GetApkSet() {
392 if !(moduleMetadataMatcher{as.ModuleMetadata}.matches(targetConfig)) {
393 continue
394 }
395 for _, apkdesc := range as.GetApkDescription() {
396 if (apkDescriptionMatcher{apkdesc}).matches(targetConfig, allAbisMustMatch) {
397 result.entries = append(result.entries, apkdesc.GetPath())
398 // TODO(asmundak): As it turns out, moduleName which we get from
399 // the ModuleMetadata matches the module names of the generated
400 // entry paths just by coincidence, only for the split APKs. We
401 // need to discuss this with bundletool folks.
402 result.moduleName = as.GetModuleMetadata().GetName()
403 }
404 }
405 // we allow only a single module, so bail out here if we found one
406 if result.moduleName != "" {
407 return result
Sasha Smundak7a894a62020-05-06 21:23:08 -0700408 }
409 }
Sasha Smundak7a894a62020-05-06 21:23:08 -0700410 }
Sam Delmericob48d57b2022-11-22 17:47:59 -0500411 return result
412 }
413 result := checkMatching(true)
414 if result.moduleName == "" {
415 // if there are no matches where all of the ABIs are available in the
416 // TargetConfig, then search again with a looser requirement of at
417 // least one matching ABI
418 // NOTE(b/260130686): this logic diverges from the logic in bundletool
419 // https://github.com/google/bundletool/blob/ae0fc0162fd80d92ef8f4ef4527c066f0106942f/src/main/java/com/android/tools/build/bundletool/device/MultiAbiMatcher.java#L43
420 result = checkMatching(false)
Sasha Smundak7a894a62020-05-06 21:23:08 -0700421 }
422 return result
423}
424
425type Zip2ZipWriter interface {
426 CopyFrom(file *zip.File, name string) error
427}
428
429// Writes out selected entries, renaming them as needed
430func (apkSet *ApkSet) writeApks(selected SelectionResult, config TargetConfig,
Colin Crossffbcd1d2021-11-12 12:19:42 -0800431 outFile io.Writer, zipWriter Zip2ZipWriter, partition string) ([]string, error) {
Sasha Smundak7a894a62020-05-06 21:23:08 -0700432 // Renaming rules:
433 // splits/MODULE-master.apk to STEM.apk
434 // else
435 // splits/MODULE-*.apk to STEM>-$1.apk
436 // TODO(asmundak):
437 // add more rules, for .apex files
438 renameRules := []struct {
439 rex *regexp.Regexp
440 repl string
441 }{
442 {
443 regexp.MustCompile(`^.*/` + selected.moduleName + `-master\.apk$`),
444 config.stem + `.apk`,
445 },
446 {
447 regexp.MustCompile(`^.*/` + selected.moduleName + `(-.*\.apk)$`),
448 config.stem + `$1`,
449 },
Sasha Smundak827c55f2020-05-20 13:10:59 -0700450 {
451 regexp.MustCompile(`^universal\.apk$`),
452 config.stem + ".apk",
453 },
Sasha Smundak7a894a62020-05-06 21:23:08 -0700454 }
455 renamer := func(path string) (string, bool) {
456 for _, rr := range renameRules {
457 if rr.rex.MatchString(path) {
458 return rr.rex.ReplaceAllString(path, rr.repl), true
459 }
460 }
461 return "", false
462 }
463
464 entryOrigin := make(map[string]string) // output entry to input entry
Jaewoong Jung11c1e0f2020-06-29 19:18:44 -0700465 var apkcerts []string
Sasha Smundak7a894a62020-05-06 21:23:08 -0700466 for _, apk := range selected.entries {
467 apkFile, ok := apkSet.entries[apk]
468 if !ok {
Jaewoong Jung11c1e0f2020-06-29 19:18:44 -0700469 return nil, fmt.Errorf("TOC refers to an entry %s which does not exist", apk)
Sasha Smundak7a894a62020-05-06 21:23:08 -0700470 }
471 inName := apkFile.Name
472 outName, ok := renamer(inName)
473 if !ok {
474 log.Fatalf("selected an entry with unexpected name %s", inName)
475 }
476 if origin, ok := entryOrigin[inName]; ok {
477 log.Fatalf("selected entries %s and %s will have the same output name %s",
478 origin, inName, outName)
479 }
480 entryOrigin[outName] = inName
Colin Crossffbcd1d2021-11-12 12:19:42 -0800481 if outName == config.stem+".apk" {
482 if err := writeZipEntryToFile(outFile, apkFile); err != nil {
483 return nil, err
484 }
485 } else {
486 if err := zipWriter.CopyFrom(apkFile, outName); err != nil {
487 return nil, err
488 }
Jaewoong Jung11c1e0f2020-06-29 19:18:44 -0700489 }
490 if partition != "" {
491 apkcerts = append(apkcerts, fmt.Sprintf(
492 `name="%s" certificate="PRESIGNED" private_key="" partition="%s"`, outName, partition))
Sasha Smundak7a894a62020-05-06 21:23:08 -0700493 }
494 }
Jaewoong Jung11c1e0f2020-06-29 19:18:44 -0700495 sort.Strings(apkcerts)
496 return apkcerts, nil
Sasha Smundak7a894a62020-05-06 21:23:08 -0700497}
498
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700499func (apkSet *ApkSet) extractAndCopySingle(selected SelectionResult, outFile *os.File) error {
500 if len(selected.entries) != 1 {
501 return fmt.Errorf("Too many matching entries for extract-single:\n%v", selected.entries)
502 }
503 apk, ok := apkSet.entries[selected.entries[0]]
504 if !ok {
505 return fmt.Errorf("Couldn't find apk path %s", selected.entries[0])
506 }
Colin Crossffbcd1d2021-11-12 12:19:42 -0800507 return writeZipEntryToFile(outFile, apk)
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700508}
509
Sasha Smundak7a894a62020-05-06 21:23:08 -0700510// Arguments parsing
511var (
Colin Crossffbcd1d2021-11-12 12:19:42 -0800512 outputFile = flag.String("o", "", "output file for primary entry")
513 zipFile = flag.String("zip", "", "output file containing additional extracted entries")
Sasha Smundak7a894a62020-05-06 21:23:08 -0700514 targetConfig = TargetConfig{
515 screenDpi: map[android_bundle_proto.ScreenDensity_DensityAlias]bool{},
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700516 abis: map[android_bundle_proto.Abi_AbiAlias]int{},
Sasha Smundak7a894a62020-05-06 21:23:08 -0700517 }
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700518 extractSingle = flag.Bool("extract-single", false,
519 "extract a single target and output it uncompressed. only available for standalone apks and apexes.")
Jaewoong Jung11c1e0f2020-06-29 19:18:44 -0700520 apkcertsOutput = flag.String("apkcerts", "",
521 "optional apkcerts.txt output file containing signing info of all outputted apks")
522 partition = flag.String("partition", "", "partition string. required when -apkcerts is used.")
Sasha Smundak7a894a62020-05-06 21:23:08 -0700523)
524
525// Parse abi values
526type abiFlagValue struct {
527 targetConfig *TargetConfig
528}
529
530func (a abiFlagValue) String() string {
531 return "all"
532}
533
534func (a abiFlagValue) Set(abiList string) error {
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700535 for i, abi := range strings.Split(abiList, ",") {
Sasha Smundak7a894a62020-05-06 21:23:08 -0700536 v, ok := android_bundle_proto.Abi_AbiAlias_value[abi]
537 if !ok {
538 return fmt.Errorf("bad ABI value: %q", abi)
539 }
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700540 targetConfig.abis[android_bundle_proto.Abi_AbiAlias(v)] = i
Sasha Smundak7a894a62020-05-06 21:23:08 -0700541 }
542 return nil
543}
544
545// Parse screen density values
546type screenDensityFlagValue struct {
547 targetConfig *TargetConfig
548}
549
550func (s screenDensityFlagValue) String() string {
551 return "none"
552}
553
554func (s screenDensityFlagValue) Set(densityList string) error {
555 if densityList == "none" {
556 return nil
557 }
558 if densityList == "all" {
559 targetConfig.screenDpi[android_bundle_proto.ScreenDensity_DENSITY_UNSPECIFIED] = true
560 return nil
561 }
562 for _, density := range strings.Split(densityList, ",") {
563 v, found := android_bundle_proto.ScreenDensity_DensityAlias_value[density]
564 if !found {
565 return fmt.Errorf("bad screen density value: %q", density)
566 }
567 targetConfig.screenDpi[android_bundle_proto.ScreenDensity_DensityAlias(v)] = true
568 }
569 return nil
570}
571
572func processArgs() {
573 flag.Usage = func() {
Colin Crossffbcd1d2021-11-12 12:19:42 -0800574 fmt.Fprintln(os.Stderr, `usage: extract_apks -o <output-file> [-zip <output-zip-file>] `+
575 `-sdk-version value -abis value `+
Jaewoong Jung11c1e0f2020-06-29 19:18:44 -0700576 `-screen-densities value {-stem value | -extract-single} [-allow-prereleased] `+
577 `[-apkcerts <apkcerts output file> -partition <partition>] <APK set>`)
Sasha Smundak7a894a62020-05-06 21:23:08 -0700578 flag.PrintDefaults()
579 os.Exit(2)
580 }
581 version := flag.Uint("sdk-version", 0, "SDK version")
582 flag.Var(abiFlagValue{&targetConfig}, "abis",
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700583 "comma-separated ABIs list of ARMEABI ARMEABI_V7A ARM64_V8A X86 X86_64 MIPS MIPS64")
Sasha Smundak7a894a62020-05-06 21:23:08 -0700584 flag.Var(screenDensityFlagValue{&targetConfig}, "screen-densities",
585 "'all' or comma-separated list of screen density names (NODPI LDPI MDPI TVDPI HDPI XHDPI XXHDPI XXXHDPI)")
586 flag.BoolVar(&targetConfig.allowPrereleased, "allow-prereleased", false,
587 "allow prereleased")
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700588 flag.StringVar(&targetConfig.stem, "stem", "", "output entries base name in the output zip file")
Sasha Smundak7a894a62020-05-06 21:23:08 -0700589 flag.Parse()
Jaewoong Jung11c1e0f2020-06-29 19:18:44 -0700590 if (*outputFile == "") || len(flag.Args()) != 1 || *version == 0 ||
Colin Crossffbcd1d2021-11-12 12:19:42 -0800591 ((targetConfig.stem == "" || *zipFile == "") && !*extractSingle) ||
592 (*apkcertsOutput != "" && *partition == "") {
Sasha Smundak7a894a62020-05-06 21:23:08 -0700593 flag.Usage()
594 }
595 targetConfig.sdkVersion = int32(*version)
596
597}
598
599func main() {
600 processArgs()
601 var toc Toc
602 apkSet, err := newApkSet(flag.Arg(0))
603 if err == nil {
604 defer apkSet.close()
605 toc, err = apkSet.getToc()
606 }
607 if err != nil {
608 log.Fatal(err)
609 }
610 sel := selectApks(toc, targetConfig)
611 if len(sel.entries) == 0 {
612 log.Fatalf("there are no entries for the target configuration: %#v", targetConfig)
613 }
614
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700615 outFile, err := os.Create(*outputFile)
Sasha Smundak7a894a62020-05-06 21:23:08 -0700616 if err != nil {
617 log.Fatal(err)
618 }
619 defer outFile.Close()
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700620
621 if *extractSingle {
622 err = apkSet.extractAndCopySingle(sel, outFile)
623 } else {
Colin Crossffbcd1d2021-11-12 12:19:42 -0800624 zipOutputFile, err := os.Create(*zipFile)
625 if err != nil {
626 log.Fatal(err)
627 }
628 defer zipOutputFile.Close()
629
630 zipWriter := zip.NewWriter(zipOutputFile)
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700631 defer func() {
Colin Crossffbcd1d2021-11-12 12:19:42 -0800632 if err := zipWriter.Close(); err != nil {
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700633 log.Fatal(err)
634 }
635 }()
Colin Crossffbcd1d2021-11-12 12:19:42 -0800636
637 apkcerts, err := apkSet.writeApks(sel, targetConfig, outFile, zipWriter, *partition)
Jaewoong Jung11c1e0f2020-06-29 19:18:44 -0700638 if err == nil && *apkcertsOutput != "" {
639 apkcertsFile, err := os.Create(*apkcertsOutput)
640 if err != nil {
641 log.Fatal(err)
642 }
643 defer apkcertsFile.Close()
644 for _, a := range apkcerts {
645 _, err = apkcertsFile.WriteString(a + "\n")
646 if err != nil {
647 log.Fatal(err)
648 }
649 }
650 }
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700651 }
652 if err != nil {
Sasha Smundak7a894a62020-05-06 21:23:08 -0700653 log.Fatal(err)
654 }
655}
Colin Crossffbcd1d2021-11-12 12:19:42 -0800656
657func writeZipEntryToFile(outFile io.Writer, zipEntry *zip.File) error {
658 reader, err := zipEntry.Open()
659 if err != nil {
660 return err
661 }
662 defer reader.Close()
663 _, err = io.Copy(outFile, reader)
664 return err
665}