blob: a7308b471059f657cf7def0cb7be2918f25ca834 [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
Pranav Gupta51645ff2023-03-20 16:19:53 -070044 skipSdkCheck bool
Sasha Smundak7a894a62020-05-06 21:23:08 -070045}
46
47// An APK set is a zip archive. An entry 'toc.pb' describes its contents.
48// It is a protobuf message BuildApkResult.
49type Toc *android_bundle_proto.BuildApksResult
50
51type ApkSet struct {
52 path string
53 reader *zip.ReadCloser
54 entries map[string]*zip.File
55}
56
57func newApkSet(path string) (*ApkSet, error) {
58 apkSet := &ApkSet{path: path, entries: make(map[string]*zip.File)}
59 var err error
60 if apkSet.reader, err = zip.OpenReader(apkSet.path); err != nil {
61 return nil, err
62 }
63 for _, f := range apkSet.reader.File {
64 apkSet.entries[f.Name] = f
65 }
66 return apkSet, nil
67}
68
69func (apkSet *ApkSet) getToc() (Toc, error) {
70 var err error
71 tocFile, ok := apkSet.entries["toc.pb"]
72 if !ok {
73 return nil, fmt.Errorf("%s: APK set should have toc.pb entry", apkSet.path)
74 }
75 rc, err := tocFile.Open()
76 if err != nil {
77 return nil, err
78 }
Ray Chin0523ee82023-09-21 20:32:06 +080079 bytes, err := io.ReadAll(rc)
80 if err != nil {
Sasha Smundak7a894a62020-05-06 21:23:08 -070081 return nil, err
82 }
83 rc.Close()
84 buildApksResult := new(android_bundle_proto.BuildApksResult)
85 if err = proto.Unmarshal(bytes, buildApksResult); err != nil {
86 return nil, err
87 }
88 return buildApksResult, nil
89}
90
91func (apkSet *ApkSet) close() {
92 apkSet.reader.Close()
93}
94
95// Matchers for selection criteria
Jaewoong Jungfa00c062020-05-14 14:15:24 -070096
Sasha Smundak7a894a62020-05-06 21:23:08 -070097type abiTargetingMatcher struct {
98 *android_bundle_proto.AbiTargeting
99}
100
101func (m abiTargetingMatcher) matches(config TargetConfig) bool {
102 if m.AbiTargeting == nil {
103 return true
104 }
105 if _, ok := config.abis[android_bundle_proto.Abi_UNSPECIFIED_CPU_ARCHITECTURE]; ok {
106 return true
107 }
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700108 // Find the one that appears first in the abis flags.
109 abiIdx := math.MaxInt32
Sasha Smundak7a894a62020-05-06 21:23:08 -0700110 for _, v := range m.GetValue() {
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700111 if i, ok := config.abis[v.Alias]; ok {
112 if i < abiIdx {
113 abiIdx = i
114 }
Sasha Smundak7a894a62020-05-06 21:23:08 -0700115 }
116 }
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700117 if abiIdx == math.MaxInt32 {
118 return false
119 }
120 // See if any alternatives appear before the above one.
121 for _, a := range m.GetAlternatives() {
122 if i, ok := config.abis[a.Alias]; ok {
123 if i < abiIdx {
124 // There is a better alternative. Skip this one.
125 return false
126 }
127 }
128 }
129 return true
Sasha Smundak7a894a62020-05-06 21:23:08 -0700130}
131
132type apkDescriptionMatcher struct {
133 *android_bundle_proto.ApkDescription
134}
135
Sam Delmericob48d57b2022-11-22 17:47:59 -0500136func (m apkDescriptionMatcher) matches(config TargetConfig, allAbisMustMatch bool) bool {
137 return m.ApkDescription == nil || (apkTargetingMatcher{m.Targeting}).matches(config, allAbisMustMatch)
Sasha Smundak7a894a62020-05-06 21:23:08 -0700138}
139
140type apkTargetingMatcher struct {
141 *android_bundle_proto.ApkTargeting
142}
143
Sam Delmericob48d57b2022-11-22 17:47:59 -0500144func (m apkTargetingMatcher) matches(config TargetConfig, allAbisMustMatch bool) bool {
Sasha Smundak7a894a62020-05-06 21:23:08 -0700145 return m.ApkTargeting == nil ||
146 (abiTargetingMatcher{m.AbiTargeting}.matches(config) &&
147 languageTargetingMatcher{m.LanguageTargeting}.matches(config) &&
148 screenDensityTargetingMatcher{m.ScreenDensityTargeting}.matches(config) &&
149 sdkVersionTargetingMatcher{m.SdkVersionTargeting}.matches(config) &&
Sam Delmericob48d57b2022-11-22 17:47:59 -0500150 multiAbiTargetingMatcher{m.MultiAbiTargeting}.matches(config, allAbisMustMatch))
Sasha Smundak7a894a62020-05-06 21:23:08 -0700151}
152
153type languageTargetingMatcher struct {
154 *android_bundle_proto.LanguageTargeting
155}
156
157func (m languageTargetingMatcher) matches(_ TargetConfig) bool {
158 if m.LanguageTargeting == nil {
159 return true
160 }
161 log.Fatal("language based entry selection is not implemented")
162 return false
163}
164
165type moduleMetadataMatcher struct {
166 *android_bundle_proto.ModuleMetadata
167}
168
169func (m moduleMetadataMatcher) matches(config TargetConfig) bool {
170 return m.ModuleMetadata == nil ||
171 (m.GetDeliveryType() == android_bundle_proto.DeliveryType_INSTALL_TIME &&
172 moduleTargetingMatcher{m.Targeting}.matches(config) &&
173 !m.IsInstant)
174}
175
176type moduleTargetingMatcher struct {
177 *android_bundle_proto.ModuleTargeting
178}
179
180func (m moduleTargetingMatcher) matches(config TargetConfig) bool {
181 return m.ModuleTargeting == nil ||
182 (sdkVersionTargetingMatcher{m.SdkVersionTargeting}.matches(config) &&
183 userCountriesTargetingMatcher{m.UserCountriesTargeting}.matches(config))
184}
185
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700186// A higher number means a higher priority.
187// This order must be kept identical to bundletool's.
188var multiAbiPriorities = map[android_bundle_proto.Abi_AbiAlias]int{
189 android_bundle_proto.Abi_ARMEABI: 1,
190 android_bundle_proto.Abi_ARMEABI_V7A: 2,
191 android_bundle_proto.Abi_ARM64_V8A: 3,
192 android_bundle_proto.Abi_X86: 4,
193 android_bundle_proto.Abi_X86_64: 5,
194 android_bundle_proto.Abi_MIPS: 6,
195 android_bundle_proto.Abi_MIPS64: 7,
196}
197
Sasha Smundak7a894a62020-05-06 21:23:08 -0700198type multiAbiTargetingMatcher struct {
199 *android_bundle_proto.MultiAbiTargeting
200}
201
Sam Delmericob74f0a02022-09-16 11:59:56 -0400202type multiAbiValue []*bundle_proto.Abi
203
204func (m multiAbiValue) compare(other multiAbiValue) int {
205 min := func(a, b int) int {
206 if a < b {
207 return a
208 }
209 return b
210 }
211
212 sortAbis := func(abiSlice multiAbiValue) func(i, j int) bool {
213 return func(i, j int) bool {
214 // sort priorities greatest to least
215 return multiAbiPriorities[abiSlice[i].Alias] > multiAbiPriorities[abiSlice[j].Alias]
216 }
217 }
218
Sam Delmericob48d57b2022-11-22 17:47:59 -0500219 sortedM := append(multiAbiValue{}, m...)
220 sort.Slice(sortedM, sortAbis(sortedM))
221 sortedOther := append(multiAbiValue{}, other...)
222 sort.Slice(sortedOther, sortAbis(sortedOther))
Sam Delmericob74f0a02022-09-16 11:59:56 -0400223
Sam Delmericob48d57b2022-11-22 17:47:59 -0500224 for i := 0; i < min(len(sortedM), len(sortedOther)); i++ {
225 if multiAbiPriorities[sortedM[i].Alias] > multiAbiPriorities[sortedOther[i].Alias] {
Sam Delmericob74f0a02022-09-16 11:59:56 -0400226 return 1
227 }
Sam Delmericob48d57b2022-11-22 17:47:59 -0500228 if multiAbiPriorities[sortedM[i].Alias] < multiAbiPriorities[sortedOther[i].Alias] {
Sam Delmericob74f0a02022-09-16 11:59:56 -0400229 return -1
230 }
231 }
232
Sam Delmericob48d57b2022-11-22 17:47:59 -0500233 return len(sortedM) - len(sortedOther)
Sam Delmericob74f0a02022-09-16 11:59:56 -0400234}
235
236// this logic should match the logic in bundletool at
237// https://github.com/google/bundletool/blob/ae0fc0162fd80d92ef8f4ef4527c066f0106942f/src/main/java/com/android/tools/build/bundletool/device/MultiAbiMatcher.java#L43
238// (note link is the commit at time of writing; but logic should always match the latest)
Sam Delmericob48d57b2022-11-22 17:47:59 -0500239func (t multiAbiTargetingMatcher) matches(config TargetConfig, allAbisMustMatch bool) bool {
Sasha Smundak7a894a62020-05-06 21:23:08 -0700240 if t.MultiAbiTargeting == nil {
241 return true
242 }
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700243 if _, ok := config.abis[android_bundle_proto.Abi_UNSPECIFIED_CPU_ARCHITECTURE]; ok {
244 return true
245 }
Sam Delmericob74f0a02022-09-16 11:59:56 -0400246
247 multiAbiIsValid := func(m multiAbiValue) bool {
Sam Delmericob48d57b2022-11-22 17:47:59 -0500248 numValid := 0
Sam Delmericob74f0a02022-09-16 11:59:56 -0400249 for _, abi := range m {
Sam Delmericob48d57b2022-11-22 17:47:59 -0500250 if _, ok := config.abis[abi.Alias]; ok {
251 numValid += 1
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700252 }
253 }
Sam Delmericob48d57b2022-11-22 17:47:59 -0500254 if numValid == 0 {
255 return false
256 } else if numValid > 0 && !allAbisMustMatch {
257 return true
258 } else {
259 return numValid == len(m)
260 }
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700261 }
Sam Delmericob74f0a02022-09-16 11:59:56 -0400262
263 // ensure that the current value is valid for our config
264 valueSetContainsViableAbi := false
265 multiAbiSet := t.GetValue()
266 for _, multiAbi := range multiAbiSet {
267 if multiAbiIsValid(multiAbi.GetAbi()) {
268 valueSetContainsViableAbi = true
Sam Delmericob48d57b2022-11-22 17:47:59 -0500269 break
Sam Delmericob74f0a02022-09-16 11:59:56 -0400270 }
271 }
272
273 if !valueSetContainsViableAbi {
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700274 return false
275 }
Sam Delmericob74f0a02022-09-16 11:59:56 -0400276
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700277 // See if there are any matching alternatives with a higher priority.
Sam Delmericob74f0a02022-09-16 11:59:56 -0400278 for _, altMultiAbi := range t.GetAlternatives() {
279 if !multiAbiIsValid(altMultiAbi.GetAbi()) {
280 continue
281 }
282
283 for _, multiAbi := range multiAbiSet {
284 valueAbis := multiAbiValue(multiAbi.GetAbi())
285 altAbis := multiAbiValue(altMultiAbi.GetAbi())
286 if valueAbis.compare(altAbis) < 0 {
287 // An alternative has a higher priority, don't use this one
288 return false
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700289 }
290 }
291 }
Sam Delmericob74f0a02022-09-16 11:59:56 -0400292
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700293 return true
Sasha Smundak7a894a62020-05-06 21:23:08 -0700294}
295
296type screenDensityTargetingMatcher struct {
297 *android_bundle_proto.ScreenDensityTargeting
298}
299
300func (m screenDensityTargetingMatcher) matches(config TargetConfig) bool {
301 if m.ScreenDensityTargeting == nil {
302 return true
303 }
304 if _, ok := config.screenDpi[android_bundle_proto.ScreenDensity_DENSITY_UNSPECIFIED]; ok {
305 return true
306 }
307 for _, v := range m.GetValue() {
308 switch x := v.GetDensityOneof().(type) {
309 case *android_bundle_proto.ScreenDensity_DensityAlias_:
310 if _, ok := config.screenDpi[x.DensityAlias]; ok {
311 return true
312 }
313 default:
314 log.Fatal("For screen density, only DPI name based entry selection (e.g. HDPI, XHDPI) is implemented")
315 }
316 }
317 return false
318}
319
320type sdkVersionTargetingMatcher struct {
321 *android_bundle_proto.SdkVersionTargeting
322}
323
324func (m sdkVersionTargetingMatcher) matches(config TargetConfig) bool {
325 const preReleaseVersion = 10000
Pranav Gupta51645ff2023-03-20 16:19:53 -0700326 // TODO (b274518686) This check should only be used while SHA based targeting is active
327 // Once we have switched to an SDK version, this can be changed to throw an error if
328 // it was accidentally set
329 if config.skipSdkCheck == true {
330 return true
331 }
Sasha Smundak7a894a62020-05-06 21:23:08 -0700332 if m.SdkVersionTargeting == nil {
333 return true
334 }
335 if len(m.Value) > 1 {
336 log.Fatal(fmt.Sprintf("sdk_version_targeting should not have multiple values:%#v", m.Value))
337 }
338 // Inspect only sdkVersionTargeting.Value.
339 // Even though one of the SdkVersionTargeting.Alternatives values may be
340 // better matching, we will select all of them
341 return m.Value[0].Min == nil ||
342 m.Value[0].Min.Value <= config.sdkVersion ||
343 (config.allowPrereleased && m.Value[0].Min.Value == preReleaseVersion)
344}
345
346type textureCompressionFormatTargetingMatcher struct {
347 *android_bundle_proto.TextureCompressionFormatTargeting
348}
349
350func (m textureCompressionFormatTargetingMatcher) matches(_ TargetConfig) bool {
351 if m.TextureCompressionFormatTargeting == nil {
352 return true
353 }
354 log.Fatal("texture based entry selection is not implemented")
355 return false
356}
357
358type userCountriesTargetingMatcher struct {
359 *android_bundle_proto.UserCountriesTargeting
360}
361
362func (m userCountriesTargetingMatcher) matches(_ TargetConfig) bool {
363 if m.UserCountriesTargeting == nil {
364 return true
365 }
366 log.Fatal("country based entry selection is not implemented")
367 return false
368}
369
370type variantTargetingMatcher struct {
371 *android_bundle_proto.VariantTargeting
372}
373
Sam Delmericob48d57b2022-11-22 17:47:59 -0500374func (m variantTargetingMatcher) matches(config TargetConfig, allAbisMustMatch bool) bool {
Sasha Smundak7a894a62020-05-06 21:23:08 -0700375 if m.VariantTargeting == nil {
376 return true
377 }
378 return sdkVersionTargetingMatcher{m.SdkVersionTargeting}.matches(config) &&
379 abiTargetingMatcher{m.AbiTargeting}.matches(config) &&
Sam Delmericob48d57b2022-11-22 17:47:59 -0500380 multiAbiTargetingMatcher{m.MultiAbiTargeting}.matches(config, allAbisMustMatch) &&
Sasha Smundak7a894a62020-05-06 21:23:08 -0700381 screenDensityTargetingMatcher{m.ScreenDensityTargeting}.matches(config) &&
382 textureCompressionFormatTargetingMatcher{m.TextureCompressionFormatTargeting}.matches(config)
383}
384
385type SelectionResult struct {
386 moduleName string
387 entries []string
388}
389
390// Return all entries matching target configuration
391func selectApks(toc Toc, targetConfig TargetConfig) SelectionResult {
Sam Delmericob48d57b2022-11-22 17:47:59 -0500392 checkMatching := func(allAbisMustMatch bool) SelectionResult {
393 var result SelectionResult
394 for _, variant := range (*toc).GetVariant() {
395 if !(variantTargetingMatcher{variant.GetTargeting()}.matches(targetConfig, allAbisMustMatch)) {
Sasha Smundak7a894a62020-05-06 21:23:08 -0700396 continue
397 }
Sam Delmericob48d57b2022-11-22 17:47:59 -0500398 for _, as := range variant.GetApkSet() {
399 if !(moduleMetadataMatcher{as.ModuleMetadata}.matches(targetConfig)) {
400 continue
401 }
402 for _, apkdesc := range as.GetApkDescription() {
403 if (apkDescriptionMatcher{apkdesc}).matches(targetConfig, allAbisMustMatch) {
404 result.entries = append(result.entries, apkdesc.GetPath())
405 // TODO(asmundak): As it turns out, moduleName which we get from
406 // the ModuleMetadata matches the module names of the generated
407 // entry paths just by coincidence, only for the split APKs. We
408 // need to discuss this with bundletool folks.
409 result.moduleName = as.GetModuleMetadata().GetName()
410 }
411 }
412 // we allow only a single module, so bail out here if we found one
413 if result.moduleName != "" {
414 return result
Sasha Smundak7a894a62020-05-06 21:23:08 -0700415 }
416 }
Sasha Smundak7a894a62020-05-06 21:23:08 -0700417 }
Sam Delmericob48d57b2022-11-22 17:47:59 -0500418 return result
419 }
420 result := checkMatching(true)
421 if result.moduleName == "" {
422 // if there are no matches where all of the ABIs are available in the
423 // TargetConfig, then search again with a looser requirement of at
424 // least one matching ABI
425 // NOTE(b/260130686): this logic diverges from the logic in bundletool
426 // https://github.com/google/bundletool/blob/ae0fc0162fd80d92ef8f4ef4527c066f0106942f/src/main/java/com/android/tools/build/bundletool/device/MultiAbiMatcher.java#L43
427 result = checkMatching(false)
Sasha Smundak7a894a62020-05-06 21:23:08 -0700428 }
429 return result
430}
431
432type Zip2ZipWriter interface {
433 CopyFrom(file *zip.File, name string) error
434}
435
436// Writes out selected entries, renaming them as needed
437func (apkSet *ApkSet) writeApks(selected SelectionResult, config TargetConfig,
Colin Crossffbcd1d2021-11-12 12:19:42 -0800438 outFile io.Writer, zipWriter Zip2ZipWriter, partition string) ([]string, error) {
Sasha Smundak7a894a62020-05-06 21:23:08 -0700439 // Renaming rules:
440 // splits/MODULE-master.apk to STEM.apk
441 // else
442 // splits/MODULE-*.apk to STEM>-$1.apk
443 // TODO(asmundak):
444 // add more rules, for .apex files
445 renameRules := []struct {
446 rex *regexp.Regexp
447 repl string
448 }{
449 {
450 regexp.MustCompile(`^.*/` + selected.moduleName + `-master\.apk$`),
451 config.stem + `.apk`,
452 },
453 {
454 regexp.MustCompile(`^.*/` + selected.moduleName + `(-.*\.apk)$`),
455 config.stem + `$1`,
456 },
Sasha Smundak827c55f2020-05-20 13:10:59 -0700457 {
458 regexp.MustCompile(`^universal\.apk$`),
459 config.stem + ".apk",
460 },
Sasha Smundak7a894a62020-05-06 21:23:08 -0700461 }
462 renamer := func(path string) (string, bool) {
463 for _, rr := range renameRules {
464 if rr.rex.MatchString(path) {
465 return rr.rex.ReplaceAllString(path, rr.repl), true
466 }
467 }
468 return "", false
469 }
470
471 entryOrigin := make(map[string]string) // output entry to input entry
Jaewoong Jung11c1e0f2020-06-29 19:18:44 -0700472 var apkcerts []string
Sasha Smundak7a894a62020-05-06 21:23:08 -0700473 for _, apk := range selected.entries {
474 apkFile, ok := apkSet.entries[apk]
475 if !ok {
Jaewoong Jung11c1e0f2020-06-29 19:18:44 -0700476 return nil, fmt.Errorf("TOC refers to an entry %s which does not exist", apk)
Sasha Smundak7a894a62020-05-06 21:23:08 -0700477 }
478 inName := apkFile.Name
479 outName, ok := renamer(inName)
480 if !ok {
481 log.Fatalf("selected an entry with unexpected name %s", inName)
482 }
483 if origin, ok := entryOrigin[inName]; ok {
484 log.Fatalf("selected entries %s and %s will have the same output name %s",
485 origin, inName, outName)
486 }
487 entryOrigin[outName] = inName
Colin Crossffbcd1d2021-11-12 12:19:42 -0800488 if outName == config.stem+".apk" {
489 if err := writeZipEntryToFile(outFile, apkFile); err != nil {
490 return nil, err
491 }
492 } else {
493 if err := zipWriter.CopyFrom(apkFile, outName); err != nil {
494 return nil, err
495 }
Jaewoong Jung11c1e0f2020-06-29 19:18:44 -0700496 }
497 if partition != "" {
498 apkcerts = append(apkcerts, fmt.Sprintf(
499 `name="%s" certificate="PRESIGNED" private_key="" partition="%s"`, outName, partition))
Sasha Smundak7a894a62020-05-06 21:23:08 -0700500 }
501 }
Jaewoong Jung11c1e0f2020-06-29 19:18:44 -0700502 sort.Strings(apkcerts)
503 return apkcerts, nil
Sasha Smundak7a894a62020-05-06 21:23:08 -0700504}
505
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700506func (apkSet *ApkSet) extractAndCopySingle(selected SelectionResult, outFile *os.File) error {
507 if len(selected.entries) != 1 {
508 return fmt.Errorf("Too many matching entries for extract-single:\n%v", selected.entries)
509 }
510 apk, ok := apkSet.entries[selected.entries[0]]
511 if !ok {
512 return fmt.Errorf("Couldn't find apk path %s", selected.entries[0])
513 }
Colin Crossffbcd1d2021-11-12 12:19:42 -0800514 return writeZipEntryToFile(outFile, apk)
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700515}
516
Sasha Smundak7a894a62020-05-06 21:23:08 -0700517// Arguments parsing
518var (
Colin Crossffbcd1d2021-11-12 12:19:42 -0800519 outputFile = flag.String("o", "", "output file for primary entry")
520 zipFile = flag.String("zip", "", "output file containing additional extracted entries")
Sasha Smundak7a894a62020-05-06 21:23:08 -0700521 targetConfig = TargetConfig{
522 screenDpi: map[android_bundle_proto.ScreenDensity_DensityAlias]bool{},
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700523 abis: map[android_bundle_proto.Abi_AbiAlias]int{},
Sasha Smundak7a894a62020-05-06 21:23:08 -0700524 }
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700525 extractSingle = flag.Bool("extract-single", false,
526 "extract a single target and output it uncompressed. only available for standalone apks and apexes.")
Jaewoong Jung11c1e0f2020-06-29 19:18:44 -0700527 apkcertsOutput = flag.String("apkcerts", "",
528 "optional apkcerts.txt output file containing signing info of all outputted apks")
529 partition = flag.String("partition", "", "partition string. required when -apkcerts is used.")
Sasha Smundak7a894a62020-05-06 21:23:08 -0700530)
531
532// Parse abi values
533type abiFlagValue struct {
534 targetConfig *TargetConfig
535}
536
537func (a abiFlagValue) String() string {
538 return "all"
539}
540
541func (a abiFlagValue) Set(abiList string) error {
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700542 for i, abi := range strings.Split(abiList, ",") {
Sasha Smundak7a894a62020-05-06 21:23:08 -0700543 v, ok := android_bundle_proto.Abi_AbiAlias_value[abi]
544 if !ok {
545 return fmt.Errorf("bad ABI value: %q", abi)
546 }
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700547 targetConfig.abis[android_bundle_proto.Abi_AbiAlias(v)] = i
Sasha Smundak7a894a62020-05-06 21:23:08 -0700548 }
549 return nil
550}
551
552// Parse screen density values
553type screenDensityFlagValue struct {
554 targetConfig *TargetConfig
555}
556
557func (s screenDensityFlagValue) String() string {
558 return "none"
559}
560
561func (s screenDensityFlagValue) Set(densityList string) error {
562 if densityList == "none" {
563 return nil
564 }
565 if densityList == "all" {
566 targetConfig.screenDpi[android_bundle_proto.ScreenDensity_DENSITY_UNSPECIFIED] = true
567 return nil
568 }
569 for _, density := range strings.Split(densityList, ",") {
570 v, found := android_bundle_proto.ScreenDensity_DensityAlias_value[density]
571 if !found {
572 return fmt.Errorf("bad screen density value: %q", density)
573 }
574 targetConfig.screenDpi[android_bundle_proto.ScreenDensity_DensityAlias(v)] = true
575 }
576 return nil
577}
578
579func processArgs() {
580 flag.Usage = func() {
Colin Crossffbcd1d2021-11-12 12:19:42 -0800581 fmt.Fprintln(os.Stderr, `usage: extract_apks -o <output-file> [-zip <output-zip-file>] `+
Pranav Gupta51645ff2023-03-20 16:19:53 -0700582 `-sdk-version value -abis value [-skip-sdk-check]`+
Jaewoong Jung11c1e0f2020-06-29 19:18:44 -0700583 `-screen-densities value {-stem value | -extract-single} [-allow-prereleased] `+
584 `[-apkcerts <apkcerts output file> -partition <partition>] <APK set>`)
Sasha Smundak7a894a62020-05-06 21:23:08 -0700585 flag.PrintDefaults()
586 os.Exit(2)
587 }
588 version := flag.Uint("sdk-version", 0, "SDK version")
589 flag.Var(abiFlagValue{&targetConfig}, "abis",
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700590 "comma-separated ABIs list of ARMEABI ARMEABI_V7A ARM64_V8A X86 X86_64 MIPS MIPS64")
Sasha Smundak7a894a62020-05-06 21:23:08 -0700591 flag.Var(screenDensityFlagValue{&targetConfig}, "screen-densities",
592 "'all' or comma-separated list of screen density names (NODPI LDPI MDPI TVDPI HDPI XHDPI XXHDPI XXXHDPI)")
593 flag.BoolVar(&targetConfig.allowPrereleased, "allow-prereleased", false,
594 "allow prereleased")
Pranav Gupta51645ff2023-03-20 16:19:53 -0700595 flag.BoolVar(&targetConfig.skipSdkCheck, "skip-sdk-check", false, "Skip the SDK version check")
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700596 flag.StringVar(&targetConfig.stem, "stem", "", "output entries base name in the output zip file")
Sasha Smundak7a894a62020-05-06 21:23:08 -0700597 flag.Parse()
Jaewoong Jung11c1e0f2020-06-29 19:18:44 -0700598 if (*outputFile == "") || len(flag.Args()) != 1 || *version == 0 ||
Colin Crossffbcd1d2021-11-12 12:19:42 -0800599 ((targetConfig.stem == "" || *zipFile == "") && !*extractSingle) ||
600 (*apkcertsOutput != "" && *partition == "") {
Sasha Smundak7a894a62020-05-06 21:23:08 -0700601 flag.Usage()
602 }
603 targetConfig.sdkVersion = int32(*version)
604
605}
606
607func main() {
608 processArgs()
609 var toc Toc
610 apkSet, err := newApkSet(flag.Arg(0))
611 if err == nil {
612 defer apkSet.close()
613 toc, err = apkSet.getToc()
614 }
615 if err != nil {
616 log.Fatal(err)
617 }
618 sel := selectApks(toc, targetConfig)
619 if len(sel.entries) == 0 {
620 log.Fatalf("there are no entries for the target configuration: %#v", targetConfig)
621 }
622
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700623 outFile, err := os.Create(*outputFile)
Sasha Smundak7a894a62020-05-06 21:23:08 -0700624 if err != nil {
625 log.Fatal(err)
626 }
627 defer outFile.Close()
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700628
629 if *extractSingle {
630 err = apkSet.extractAndCopySingle(sel, outFile)
631 } else {
Colin Crossffbcd1d2021-11-12 12:19:42 -0800632 zipOutputFile, err := os.Create(*zipFile)
633 if err != nil {
634 log.Fatal(err)
635 }
636 defer zipOutputFile.Close()
637
638 zipWriter := zip.NewWriter(zipOutputFile)
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700639 defer func() {
Colin Crossffbcd1d2021-11-12 12:19:42 -0800640 if err := zipWriter.Close(); err != nil {
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700641 log.Fatal(err)
642 }
643 }()
Colin Crossffbcd1d2021-11-12 12:19:42 -0800644
645 apkcerts, err := apkSet.writeApks(sel, targetConfig, outFile, zipWriter, *partition)
Jaewoong Jung11c1e0f2020-06-29 19:18:44 -0700646 if err == nil && *apkcertsOutput != "" {
647 apkcertsFile, err := os.Create(*apkcertsOutput)
648 if err != nil {
649 log.Fatal(err)
650 }
651 defer apkcertsFile.Close()
652 for _, a := range apkcerts {
653 _, err = apkcertsFile.WriteString(a + "\n")
654 if err != nil {
655 log.Fatal(err)
656 }
657 }
658 }
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700659 }
660 if err != nil {
Sasha Smundak7a894a62020-05-06 21:23:08 -0700661 log.Fatal(err)
662 }
663}
Colin Crossffbcd1d2021-11-12 12:19:42 -0800664
665func writeZipEntryToFile(outFile io.Writer, zipEntry *zip.File) error {
666 reader, err := zipEntry.Open()
667 if err != nil {
668 return err
669 }
670 defer reader.Close()
671 _, err = io.Copy(outFile, reader)
672 return err
673}