blob: e39f8d765a3284239c5315f9817a35fe95e42129 [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)
79 if _, err := rc.Read(bytes); err != io.EOF {
80 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
135func (m apkDescriptionMatcher) matches(config TargetConfig) bool {
136 return m.ApkDescription == nil || (apkTargetingMatcher{m.Targeting}).matches(config)
137}
138
139type apkTargetingMatcher struct {
140 *android_bundle_proto.ApkTargeting
141}
142
143func (m apkTargetingMatcher) matches(config TargetConfig) bool {
144 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) &&
149 multiAbiTargetingMatcher{m.MultiAbiTargeting}.matches(config))
150}
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
218 m = append(multiAbiValue{}, m...)
219 sort.Slice(m, sortAbis(m))
220 other = append(multiAbiValue{}, other...)
221 sort.Slice(other, sortAbis(other))
222
223 for i := 0; i < min(len(m), len(other)); i++ {
224 if multiAbiPriorities[m[i].Alias] > multiAbiPriorities[other[i].Alias] {
225 return 1
226 }
227 if multiAbiPriorities[m[i].Alias] < multiAbiPriorities[other[i].Alias] {
228 return -1
229 }
230 }
231
232 if len(m) == len(other) {
233 return 0
234 }
235 if len(m) > len(other) {
236 return 1
237 }
238 return -1
239}
240
241// this logic should match the logic in bundletool at
242// https://github.com/google/bundletool/blob/ae0fc0162fd80d92ef8f4ef4527c066f0106942f/src/main/java/com/android/tools/build/bundletool/device/MultiAbiMatcher.java#L43
243// (note link is the commit at time of writing; but logic should always match the latest)
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700244func (t multiAbiTargetingMatcher) matches(config TargetConfig) bool {
Sasha Smundak7a894a62020-05-06 21:23:08 -0700245 if t.MultiAbiTargeting == nil {
246 return true
247 }
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700248 if _, ok := config.abis[android_bundle_proto.Abi_UNSPECIFIED_CPU_ARCHITECTURE]; ok {
249 return true
250 }
Sam Delmericob74f0a02022-09-16 11:59:56 -0400251
252 multiAbiIsValid := func(m multiAbiValue) bool {
253 for _, abi := range m {
254 if _, ok := config.abis[abi.Alias]; !ok {
255 return false
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700256 }
257 }
Sam Delmericob74f0a02022-09-16 11:59:56 -0400258 return true
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700259 }
Sam Delmericob74f0a02022-09-16 11:59:56 -0400260
261 // ensure that the current value is valid for our config
262 valueSetContainsViableAbi := false
263 multiAbiSet := t.GetValue()
264 for _, multiAbi := range multiAbiSet {
265 if multiAbiIsValid(multiAbi.GetAbi()) {
266 valueSetContainsViableAbi = true
267 }
268 }
269
270 if !valueSetContainsViableAbi {
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700271 return false
272 }
Sam Delmericob74f0a02022-09-16 11:59:56 -0400273
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700274 // See if there are any matching alternatives with a higher priority.
Sam Delmericob74f0a02022-09-16 11:59:56 -0400275 for _, altMultiAbi := range t.GetAlternatives() {
276 if !multiAbiIsValid(altMultiAbi.GetAbi()) {
277 continue
278 }
279
280 for _, multiAbi := range multiAbiSet {
281 valueAbis := multiAbiValue(multiAbi.GetAbi())
282 altAbis := multiAbiValue(altMultiAbi.GetAbi())
283 if valueAbis.compare(altAbis) < 0 {
284 // An alternative has a higher priority, don't use this one
285 return false
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700286 }
287 }
288 }
Sam Delmericob74f0a02022-09-16 11:59:56 -0400289
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700290 return true
Sasha Smundak7a894a62020-05-06 21:23:08 -0700291}
292
293type screenDensityTargetingMatcher struct {
294 *android_bundle_proto.ScreenDensityTargeting
295}
296
297func (m screenDensityTargetingMatcher) matches(config TargetConfig) bool {
298 if m.ScreenDensityTargeting == nil {
299 return true
300 }
301 if _, ok := config.screenDpi[android_bundle_proto.ScreenDensity_DENSITY_UNSPECIFIED]; ok {
302 return true
303 }
304 for _, v := range m.GetValue() {
305 switch x := v.GetDensityOneof().(type) {
306 case *android_bundle_proto.ScreenDensity_DensityAlias_:
307 if _, ok := config.screenDpi[x.DensityAlias]; ok {
308 return true
309 }
310 default:
311 log.Fatal("For screen density, only DPI name based entry selection (e.g. HDPI, XHDPI) is implemented")
312 }
313 }
314 return false
315}
316
317type sdkVersionTargetingMatcher struct {
318 *android_bundle_proto.SdkVersionTargeting
319}
320
321func (m sdkVersionTargetingMatcher) matches(config TargetConfig) bool {
322 const preReleaseVersion = 10000
323 if m.SdkVersionTargeting == nil {
324 return true
325 }
326 if len(m.Value) > 1 {
327 log.Fatal(fmt.Sprintf("sdk_version_targeting should not have multiple values:%#v", m.Value))
328 }
329 // Inspect only sdkVersionTargeting.Value.
330 // Even though one of the SdkVersionTargeting.Alternatives values may be
331 // better matching, we will select all of them
332 return m.Value[0].Min == nil ||
333 m.Value[0].Min.Value <= config.sdkVersion ||
334 (config.allowPrereleased && m.Value[0].Min.Value == preReleaseVersion)
335}
336
337type textureCompressionFormatTargetingMatcher struct {
338 *android_bundle_proto.TextureCompressionFormatTargeting
339}
340
341func (m textureCompressionFormatTargetingMatcher) matches(_ TargetConfig) bool {
342 if m.TextureCompressionFormatTargeting == nil {
343 return true
344 }
345 log.Fatal("texture based entry selection is not implemented")
346 return false
347}
348
349type userCountriesTargetingMatcher struct {
350 *android_bundle_proto.UserCountriesTargeting
351}
352
353func (m userCountriesTargetingMatcher) matches(_ TargetConfig) bool {
354 if m.UserCountriesTargeting == nil {
355 return true
356 }
357 log.Fatal("country based entry selection is not implemented")
358 return false
359}
360
361type variantTargetingMatcher struct {
362 *android_bundle_proto.VariantTargeting
363}
364
365func (m variantTargetingMatcher) matches(config TargetConfig) bool {
366 if m.VariantTargeting == nil {
367 return true
368 }
369 return sdkVersionTargetingMatcher{m.SdkVersionTargeting}.matches(config) &&
370 abiTargetingMatcher{m.AbiTargeting}.matches(config) &&
371 multiAbiTargetingMatcher{m.MultiAbiTargeting}.matches(config) &&
372 screenDensityTargetingMatcher{m.ScreenDensityTargeting}.matches(config) &&
373 textureCompressionFormatTargetingMatcher{m.TextureCompressionFormatTargeting}.matches(config)
374}
375
376type SelectionResult struct {
377 moduleName string
378 entries []string
379}
380
381// Return all entries matching target configuration
382func selectApks(toc Toc, targetConfig TargetConfig) SelectionResult {
383 var result SelectionResult
384 for _, variant := range (*toc).GetVariant() {
385 if !(variantTargetingMatcher{variant.GetTargeting()}.matches(targetConfig)) {
386 continue
387 }
388 for _, as := range variant.GetApkSet() {
389 if !(moduleMetadataMatcher{as.ModuleMetadata}.matches(targetConfig)) {
390 continue
391 }
392 for _, apkdesc := range as.GetApkDescription() {
393 if (apkDescriptionMatcher{apkdesc}).matches(targetConfig) {
394 result.entries = append(result.entries, apkdesc.GetPath())
395 // TODO(asmundak): As it turns out, moduleName which we get from
396 // the ModuleMetadata matches the module names of the generated
397 // entry paths just by coincidence, only for the split APKs. We
398 // need to discuss this with bundletool folks.
399 result.moduleName = as.GetModuleMetadata().GetName()
400 }
401 }
402 // we allow only a single module, so bail out here if we found one
403 if result.moduleName != "" {
404 return result
405 }
406 }
407 }
408 return result
409}
410
411type Zip2ZipWriter interface {
412 CopyFrom(file *zip.File, name string) error
413}
414
415// Writes out selected entries, renaming them as needed
416func (apkSet *ApkSet) writeApks(selected SelectionResult, config TargetConfig,
Colin Crossffbcd1d2021-11-12 12:19:42 -0800417 outFile io.Writer, zipWriter Zip2ZipWriter, partition string) ([]string, error) {
Sasha Smundak7a894a62020-05-06 21:23:08 -0700418 // Renaming rules:
419 // splits/MODULE-master.apk to STEM.apk
420 // else
421 // splits/MODULE-*.apk to STEM>-$1.apk
422 // TODO(asmundak):
423 // add more rules, for .apex files
424 renameRules := []struct {
425 rex *regexp.Regexp
426 repl string
427 }{
428 {
429 regexp.MustCompile(`^.*/` + selected.moduleName + `-master\.apk$`),
430 config.stem + `.apk`,
431 },
432 {
433 regexp.MustCompile(`^.*/` + selected.moduleName + `(-.*\.apk)$`),
434 config.stem + `$1`,
435 },
Sasha Smundak827c55f2020-05-20 13:10:59 -0700436 {
437 regexp.MustCompile(`^universal\.apk$`),
438 config.stem + ".apk",
439 },
Sasha Smundak7a894a62020-05-06 21:23:08 -0700440 }
441 renamer := func(path string) (string, bool) {
442 for _, rr := range renameRules {
443 if rr.rex.MatchString(path) {
444 return rr.rex.ReplaceAllString(path, rr.repl), true
445 }
446 }
447 return "", false
448 }
449
450 entryOrigin := make(map[string]string) // output entry to input entry
Jaewoong Jung11c1e0f2020-06-29 19:18:44 -0700451 var apkcerts []string
Sasha Smundak7a894a62020-05-06 21:23:08 -0700452 for _, apk := range selected.entries {
453 apkFile, ok := apkSet.entries[apk]
454 if !ok {
Jaewoong Jung11c1e0f2020-06-29 19:18:44 -0700455 return nil, fmt.Errorf("TOC refers to an entry %s which does not exist", apk)
Sasha Smundak7a894a62020-05-06 21:23:08 -0700456 }
457 inName := apkFile.Name
458 outName, ok := renamer(inName)
459 if !ok {
460 log.Fatalf("selected an entry with unexpected name %s", inName)
461 }
462 if origin, ok := entryOrigin[inName]; ok {
463 log.Fatalf("selected entries %s and %s will have the same output name %s",
464 origin, inName, outName)
465 }
466 entryOrigin[outName] = inName
Colin Crossffbcd1d2021-11-12 12:19:42 -0800467 if outName == config.stem+".apk" {
468 if err := writeZipEntryToFile(outFile, apkFile); err != nil {
469 return nil, err
470 }
471 } else {
472 if err := zipWriter.CopyFrom(apkFile, outName); err != nil {
473 return nil, err
474 }
Jaewoong Jung11c1e0f2020-06-29 19:18:44 -0700475 }
476 if partition != "" {
477 apkcerts = append(apkcerts, fmt.Sprintf(
478 `name="%s" certificate="PRESIGNED" private_key="" partition="%s"`, outName, partition))
Sasha Smundak7a894a62020-05-06 21:23:08 -0700479 }
480 }
Jaewoong Jung11c1e0f2020-06-29 19:18:44 -0700481 sort.Strings(apkcerts)
482 return apkcerts, nil
Sasha Smundak7a894a62020-05-06 21:23:08 -0700483}
484
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700485func (apkSet *ApkSet) extractAndCopySingle(selected SelectionResult, outFile *os.File) error {
486 if len(selected.entries) != 1 {
487 return fmt.Errorf("Too many matching entries for extract-single:\n%v", selected.entries)
488 }
489 apk, ok := apkSet.entries[selected.entries[0]]
490 if !ok {
491 return fmt.Errorf("Couldn't find apk path %s", selected.entries[0])
492 }
Colin Crossffbcd1d2021-11-12 12:19:42 -0800493 return writeZipEntryToFile(outFile, apk)
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700494}
495
Sasha Smundak7a894a62020-05-06 21:23:08 -0700496// Arguments parsing
497var (
Colin Crossffbcd1d2021-11-12 12:19:42 -0800498 outputFile = flag.String("o", "", "output file for primary entry")
499 zipFile = flag.String("zip", "", "output file containing additional extracted entries")
Sasha Smundak7a894a62020-05-06 21:23:08 -0700500 targetConfig = TargetConfig{
501 screenDpi: map[android_bundle_proto.ScreenDensity_DensityAlias]bool{},
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700502 abis: map[android_bundle_proto.Abi_AbiAlias]int{},
Sasha Smundak7a894a62020-05-06 21:23:08 -0700503 }
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700504 extractSingle = flag.Bool("extract-single", false,
505 "extract a single target and output it uncompressed. only available for standalone apks and apexes.")
Jaewoong Jung11c1e0f2020-06-29 19:18:44 -0700506 apkcertsOutput = flag.String("apkcerts", "",
507 "optional apkcerts.txt output file containing signing info of all outputted apks")
508 partition = flag.String("partition", "", "partition string. required when -apkcerts is used.")
Sasha Smundak7a894a62020-05-06 21:23:08 -0700509)
510
511// Parse abi values
512type abiFlagValue struct {
513 targetConfig *TargetConfig
514}
515
516func (a abiFlagValue) String() string {
517 return "all"
518}
519
520func (a abiFlagValue) Set(abiList string) error {
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700521 for i, abi := range strings.Split(abiList, ",") {
Sasha Smundak7a894a62020-05-06 21:23:08 -0700522 v, ok := android_bundle_proto.Abi_AbiAlias_value[abi]
523 if !ok {
524 return fmt.Errorf("bad ABI value: %q", abi)
525 }
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700526 targetConfig.abis[android_bundle_proto.Abi_AbiAlias(v)] = i
Sasha Smundak7a894a62020-05-06 21:23:08 -0700527 }
528 return nil
529}
530
531// Parse screen density values
532type screenDensityFlagValue struct {
533 targetConfig *TargetConfig
534}
535
536func (s screenDensityFlagValue) String() string {
537 return "none"
538}
539
540func (s screenDensityFlagValue) Set(densityList string) error {
541 if densityList == "none" {
542 return nil
543 }
544 if densityList == "all" {
545 targetConfig.screenDpi[android_bundle_proto.ScreenDensity_DENSITY_UNSPECIFIED] = true
546 return nil
547 }
548 for _, density := range strings.Split(densityList, ",") {
549 v, found := android_bundle_proto.ScreenDensity_DensityAlias_value[density]
550 if !found {
551 return fmt.Errorf("bad screen density value: %q", density)
552 }
553 targetConfig.screenDpi[android_bundle_proto.ScreenDensity_DensityAlias(v)] = true
554 }
555 return nil
556}
557
558func processArgs() {
559 flag.Usage = func() {
Colin Crossffbcd1d2021-11-12 12:19:42 -0800560 fmt.Fprintln(os.Stderr, `usage: extract_apks -o <output-file> [-zip <output-zip-file>] `+
561 `-sdk-version value -abis value `+
Jaewoong Jung11c1e0f2020-06-29 19:18:44 -0700562 `-screen-densities value {-stem value | -extract-single} [-allow-prereleased] `+
563 `[-apkcerts <apkcerts output file> -partition <partition>] <APK set>`)
Sasha Smundak7a894a62020-05-06 21:23:08 -0700564 flag.PrintDefaults()
565 os.Exit(2)
566 }
567 version := flag.Uint("sdk-version", 0, "SDK version")
568 flag.Var(abiFlagValue{&targetConfig}, "abis",
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700569 "comma-separated ABIs list of ARMEABI ARMEABI_V7A ARM64_V8A X86 X86_64 MIPS MIPS64")
Sasha Smundak7a894a62020-05-06 21:23:08 -0700570 flag.Var(screenDensityFlagValue{&targetConfig}, "screen-densities",
571 "'all' or comma-separated list of screen density names (NODPI LDPI MDPI TVDPI HDPI XHDPI XXHDPI XXXHDPI)")
572 flag.BoolVar(&targetConfig.allowPrereleased, "allow-prereleased", false,
573 "allow prereleased")
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700574 flag.StringVar(&targetConfig.stem, "stem", "", "output entries base name in the output zip file")
Sasha Smundak7a894a62020-05-06 21:23:08 -0700575 flag.Parse()
Jaewoong Jung11c1e0f2020-06-29 19:18:44 -0700576 if (*outputFile == "") || len(flag.Args()) != 1 || *version == 0 ||
Colin Crossffbcd1d2021-11-12 12:19:42 -0800577 ((targetConfig.stem == "" || *zipFile == "") && !*extractSingle) ||
578 (*apkcertsOutput != "" && *partition == "") {
Sasha Smundak7a894a62020-05-06 21:23:08 -0700579 flag.Usage()
580 }
581 targetConfig.sdkVersion = int32(*version)
582
583}
584
585func main() {
586 processArgs()
587 var toc Toc
588 apkSet, err := newApkSet(flag.Arg(0))
589 if err == nil {
590 defer apkSet.close()
591 toc, err = apkSet.getToc()
592 }
593 if err != nil {
594 log.Fatal(err)
595 }
596 sel := selectApks(toc, targetConfig)
597 if len(sel.entries) == 0 {
598 log.Fatalf("there are no entries for the target configuration: %#v", targetConfig)
599 }
600
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700601 outFile, err := os.Create(*outputFile)
Sasha Smundak7a894a62020-05-06 21:23:08 -0700602 if err != nil {
603 log.Fatal(err)
604 }
605 defer outFile.Close()
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700606
607 if *extractSingle {
608 err = apkSet.extractAndCopySingle(sel, outFile)
609 } else {
Colin Crossffbcd1d2021-11-12 12:19:42 -0800610 zipOutputFile, err := os.Create(*zipFile)
611 if err != nil {
612 log.Fatal(err)
613 }
614 defer zipOutputFile.Close()
615
616 zipWriter := zip.NewWriter(zipOutputFile)
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700617 defer func() {
Colin Crossffbcd1d2021-11-12 12:19:42 -0800618 if err := zipWriter.Close(); err != nil {
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700619 log.Fatal(err)
620 }
621 }()
Colin Crossffbcd1d2021-11-12 12:19:42 -0800622
623 apkcerts, err := apkSet.writeApks(sel, targetConfig, outFile, zipWriter, *partition)
Jaewoong Jung11c1e0f2020-06-29 19:18:44 -0700624 if err == nil && *apkcertsOutput != "" {
625 apkcertsFile, err := os.Create(*apkcertsOutput)
626 if err != nil {
627 log.Fatal(err)
628 }
629 defer apkcertsFile.Close()
630 for _, a := range apkcerts {
631 _, err = apkcertsFile.WriteString(a + "\n")
632 if err != nil {
633 log.Fatal(err)
634 }
635 }
636 }
Jaewoong Jungfa00c062020-05-14 14:15:24 -0700637 }
638 if err != nil {
Sasha Smundak7a894a62020-05-06 21:23:08 -0700639 log.Fatal(err)
640 }
641}
Colin Crossffbcd1d2021-11-12 12:19:42 -0800642
643func writeZipEntryToFile(outFile io.Writer, zipEntry *zip.File) error {
644 reader, err := zipEntry.Open()
645 if err != nil {
646 return err
647 }
648 defer reader.Close()
649 _, err = io.Copy(outFile, reader)
650 return err
651}