xref: /aosp_15_r20/build/soong/cmd/extract_apks/main.go (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
1*333d2b36SAndroid Build Coastguard Worker// Copyright 2020 Google Inc. All rights reserved.
2*333d2b36SAndroid Build Coastguard Worker//
3*333d2b36SAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License");
4*333d2b36SAndroid Build Coastguard Worker// you may not use this file except in compliance with the License.
5*333d2b36SAndroid Build Coastguard Worker// You may obtain a copy of the License at
6*333d2b36SAndroid Build Coastguard Worker//
7*333d2b36SAndroid Build Coastguard Worker//     http://www.apache.org/licenses/LICENSE-2.0
8*333d2b36SAndroid Build Coastguard Worker//
9*333d2b36SAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software
10*333d2b36SAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS,
11*333d2b36SAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*333d2b36SAndroid Build Coastguard Worker// See the License for the specific language governing permissions and
13*333d2b36SAndroid Build Coastguard Worker// limitations under the License.
14*333d2b36SAndroid Build Coastguard Worker
15*333d2b36SAndroid Build Coastguard Worker// Copies all the entries (APKs/APEXes) matching the target configuration from the given
16*333d2b36SAndroid Build Coastguard Worker// APK set into a zip file. Run it without arguments to see usage details.
17*333d2b36SAndroid Build Coastguard Workerpackage main
18*333d2b36SAndroid Build Coastguard Worker
19*333d2b36SAndroid Build Coastguard Workerimport (
20*333d2b36SAndroid Build Coastguard Worker	"flag"
21*333d2b36SAndroid Build Coastguard Worker	"fmt"
22*333d2b36SAndroid Build Coastguard Worker	"io"
23*333d2b36SAndroid Build Coastguard Worker	"log"
24*333d2b36SAndroid Build Coastguard Worker	"math"
25*333d2b36SAndroid Build Coastguard Worker	"os"
26*333d2b36SAndroid Build Coastguard Worker	"regexp"
27*333d2b36SAndroid Build Coastguard Worker	"sort"
28*333d2b36SAndroid Build Coastguard Worker	"strings"
29*333d2b36SAndroid Build Coastguard Worker
30*333d2b36SAndroid Build Coastguard Worker	"google.golang.org/protobuf/proto"
31*333d2b36SAndroid Build Coastguard Worker
32*333d2b36SAndroid Build Coastguard Worker	"android/soong/cmd/extract_apks/bundle_proto"
33*333d2b36SAndroid Build Coastguard Worker	android_bundle_proto "android/soong/cmd/extract_apks/bundle_proto"
34*333d2b36SAndroid Build Coastguard Worker	"android/soong/third_party/zip"
35*333d2b36SAndroid Build Coastguard Worker)
36*333d2b36SAndroid Build Coastguard Worker
37*333d2b36SAndroid Build Coastguard Workertype TargetConfig struct {
38*333d2b36SAndroid Build Coastguard Worker	sdkVersion int32
39*333d2b36SAndroid Build Coastguard Worker	screenDpi  map[android_bundle_proto.ScreenDensity_DensityAlias]bool
40*333d2b36SAndroid Build Coastguard Worker	// Map holding <ABI alias>:<its sequence number in the flag> info.
41*333d2b36SAndroid Build Coastguard Worker	abis             map[android_bundle_proto.Abi_AbiAlias]int
42*333d2b36SAndroid Build Coastguard Worker	allowPrereleased bool
43*333d2b36SAndroid Build Coastguard Worker	stem             string
44*333d2b36SAndroid Build Coastguard Worker	skipSdkCheck     bool
45*333d2b36SAndroid Build Coastguard Worker}
46*333d2b36SAndroid Build Coastguard Worker
47*333d2b36SAndroid Build Coastguard Worker// An APK set is a zip archive. An entry 'toc.pb' describes its contents.
48*333d2b36SAndroid Build Coastguard Worker// It is a protobuf message BuildApkResult.
49*333d2b36SAndroid Build Coastguard Workertype Toc *android_bundle_proto.BuildApksResult
50*333d2b36SAndroid Build Coastguard Worker
51*333d2b36SAndroid Build Coastguard Workertype ApkSet struct {
52*333d2b36SAndroid Build Coastguard Worker	path    string
53*333d2b36SAndroid Build Coastguard Worker	reader  *zip.ReadCloser
54*333d2b36SAndroid Build Coastguard Worker	entries map[string]*zip.File
55*333d2b36SAndroid Build Coastguard Worker}
56*333d2b36SAndroid Build Coastguard Worker
57*333d2b36SAndroid Build Coastguard Workerfunc newApkSet(path string) (*ApkSet, error) {
58*333d2b36SAndroid Build Coastguard Worker	apkSet := &ApkSet{path: path, entries: make(map[string]*zip.File)}
59*333d2b36SAndroid Build Coastguard Worker	var err error
60*333d2b36SAndroid Build Coastguard Worker	if apkSet.reader, err = zip.OpenReader(apkSet.path); err != nil {
61*333d2b36SAndroid Build Coastguard Worker		return nil, err
62*333d2b36SAndroid Build Coastguard Worker	}
63*333d2b36SAndroid Build Coastguard Worker	for _, f := range apkSet.reader.File {
64*333d2b36SAndroid Build Coastguard Worker		apkSet.entries[f.Name] = f
65*333d2b36SAndroid Build Coastguard Worker	}
66*333d2b36SAndroid Build Coastguard Worker	return apkSet, nil
67*333d2b36SAndroid Build Coastguard Worker}
68*333d2b36SAndroid Build Coastguard Worker
69*333d2b36SAndroid Build Coastguard Workerfunc (apkSet *ApkSet) getToc() (Toc, error) {
70*333d2b36SAndroid Build Coastguard Worker	var err error
71*333d2b36SAndroid Build Coastguard Worker	tocFile, ok := apkSet.entries["toc.pb"]
72*333d2b36SAndroid Build Coastguard Worker	if !ok {
73*333d2b36SAndroid Build Coastguard Worker		return nil, fmt.Errorf("%s: APK set should have toc.pb entry", apkSet.path)
74*333d2b36SAndroid Build Coastguard Worker	}
75*333d2b36SAndroid Build Coastguard Worker	rc, err := tocFile.Open()
76*333d2b36SAndroid Build Coastguard Worker	if err != nil {
77*333d2b36SAndroid Build Coastguard Worker		return nil, err
78*333d2b36SAndroid Build Coastguard Worker	}
79*333d2b36SAndroid Build Coastguard Worker	bytes, err := io.ReadAll(rc)
80*333d2b36SAndroid Build Coastguard Worker	if err != nil {
81*333d2b36SAndroid Build Coastguard Worker		return nil, err
82*333d2b36SAndroid Build Coastguard Worker	}
83*333d2b36SAndroid Build Coastguard Worker	rc.Close()
84*333d2b36SAndroid Build Coastguard Worker	buildApksResult := new(android_bundle_proto.BuildApksResult)
85*333d2b36SAndroid Build Coastguard Worker	if err = proto.Unmarshal(bytes, buildApksResult); err != nil {
86*333d2b36SAndroid Build Coastguard Worker		return nil, err
87*333d2b36SAndroid Build Coastguard Worker	}
88*333d2b36SAndroid Build Coastguard Worker	return buildApksResult, nil
89*333d2b36SAndroid Build Coastguard Worker}
90*333d2b36SAndroid Build Coastguard Worker
91*333d2b36SAndroid Build Coastguard Workerfunc (apkSet *ApkSet) close() {
92*333d2b36SAndroid Build Coastguard Worker	apkSet.reader.Close()
93*333d2b36SAndroid Build Coastguard Worker}
94*333d2b36SAndroid Build Coastguard Worker
95*333d2b36SAndroid Build Coastguard Worker// Matchers for selection criteria
96*333d2b36SAndroid Build Coastguard Worker
97*333d2b36SAndroid Build Coastguard Workertype abiTargetingMatcher struct {
98*333d2b36SAndroid Build Coastguard Worker	*android_bundle_proto.AbiTargeting
99*333d2b36SAndroid Build Coastguard Worker}
100*333d2b36SAndroid Build Coastguard Worker
101*333d2b36SAndroid Build Coastguard Workerfunc (m abiTargetingMatcher) matches(config TargetConfig) bool {
102*333d2b36SAndroid Build Coastguard Worker	if m.AbiTargeting == nil {
103*333d2b36SAndroid Build Coastguard Worker		return true
104*333d2b36SAndroid Build Coastguard Worker	}
105*333d2b36SAndroid Build Coastguard Worker	if _, ok := config.abis[android_bundle_proto.Abi_UNSPECIFIED_CPU_ARCHITECTURE]; ok {
106*333d2b36SAndroid Build Coastguard Worker		return true
107*333d2b36SAndroid Build Coastguard Worker	}
108*333d2b36SAndroid Build Coastguard Worker	// Find the one that appears first in the abis flags.
109*333d2b36SAndroid Build Coastguard Worker	abiIdx := math.MaxInt32
110*333d2b36SAndroid Build Coastguard Worker	for _, v := range m.GetValue() {
111*333d2b36SAndroid Build Coastguard Worker		if i, ok := config.abis[v.Alias]; ok {
112*333d2b36SAndroid Build Coastguard Worker			if i < abiIdx {
113*333d2b36SAndroid Build Coastguard Worker				abiIdx = i
114*333d2b36SAndroid Build Coastguard Worker			}
115*333d2b36SAndroid Build Coastguard Worker		}
116*333d2b36SAndroid Build Coastguard Worker	}
117*333d2b36SAndroid Build Coastguard Worker	if abiIdx == math.MaxInt32 {
118*333d2b36SAndroid Build Coastguard Worker		return false
119*333d2b36SAndroid Build Coastguard Worker	}
120*333d2b36SAndroid Build Coastguard Worker	// See if any alternatives appear before the above one.
121*333d2b36SAndroid Build Coastguard Worker	for _, a := range m.GetAlternatives() {
122*333d2b36SAndroid Build Coastguard Worker		if i, ok := config.abis[a.Alias]; ok {
123*333d2b36SAndroid Build Coastguard Worker			if i < abiIdx {
124*333d2b36SAndroid Build Coastguard Worker				// There is a better alternative. Skip this one.
125*333d2b36SAndroid Build Coastguard Worker				return false
126*333d2b36SAndroid Build Coastguard Worker			}
127*333d2b36SAndroid Build Coastguard Worker		}
128*333d2b36SAndroid Build Coastguard Worker	}
129*333d2b36SAndroid Build Coastguard Worker	return true
130*333d2b36SAndroid Build Coastguard Worker}
131*333d2b36SAndroid Build Coastguard Worker
132*333d2b36SAndroid Build Coastguard Workertype apkDescriptionMatcher struct {
133*333d2b36SAndroid Build Coastguard Worker	*android_bundle_proto.ApkDescription
134*333d2b36SAndroid Build Coastguard Worker}
135*333d2b36SAndroid Build Coastguard Worker
136*333d2b36SAndroid Build Coastguard Workerfunc (m apkDescriptionMatcher) matches(config TargetConfig, allAbisMustMatch bool) bool {
137*333d2b36SAndroid Build Coastguard Worker	return m.ApkDescription == nil || (apkTargetingMatcher{m.Targeting}).matches(config, allAbisMustMatch)
138*333d2b36SAndroid Build Coastguard Worker}
139*333d2b36SAndroid Build Coastguard Worker
140*333d2b36SAndroid Build Coastguard Workertype apkTargetingMatcher struct {
141*333d2b36SAndroid Build Coastguard Worker	*android_bundle_proto.ApkTargeting
142*333d2b36SAndroid Build Coastguard Worker}
143*333d2b36SAndroid Build Coastguard Worker
144*333d2b36SAndroid Build Coastguard Workerfunc (m apkTargetingMatcher) matches(config TargetConfig, allAbisMustMatch bool) bool {
145*333d2b36SAndroid Build Coastguard Worker	return m.ApkTargeting == nil ||
146*333d2b36SAndroid Build Coastguard Worker		(abiTargetingMatcher{m.AbiTargeting}.matches(config) &&
147*333d2b36SAndroid Build Coastguard Worker			languageTargetingMatcher{m.LanguageTargeting}.matches(config) &&
148*333d2b36SAndroid Build Coastguard Worker			screenDensityTargetingMatcher{m.ScreenDensityTargeting}.matches(config) &&
149*333d2b36SAndroid Build Coastguard Worker			sdkVersionTargetingMatcher{m.SdkVersionTargeting}.matches(config) &&
150*333d2b36SAndroid Build Coastguard Worker			multiAbiTargetingMatcher{m.MultiAbiTargeting}.matches(config, allAbisMustMatch))
151*333d2b36SAndroid Build Coastguard Worker}
152*333d2b36SAndroid Build Coastguard Worker
153*333d2b36SAndroid Build Coastguard Workertype languageTargetingMatcher struct {
154*333d2b36SAndroid Build Coastguard Worker	*android_bundle_proto.LanguageTargeting
155*333d2b36SAndroid Build Coastguard Worker}
156*333d2b36SAndroid Build Coastguard Worker
157*333d2b36SAndroid Build Coastguard Workerfunc (m languageTargetingMatcher) matches(_ TargetConfig) bool {
158*333d2b36SAndroid Build Coastguard Worker	if m.LanguageTargeting == nil {
159*333d2b36SAndroid Build Coastguard Worker		return true
160*333d2b36SAndroid Build Coastguard Worker	}
161*333d2b36SAndroid Build Coastguard Worker	log.Fatal("language based entry selection is not implemented")
162*333d2b36SAndroid Build Coastguard Worker	return false
163*333d2b36SAndroid Build Coastguard Worker}
164*333d2b36SAndroid Build Coastguard Worker
165*333d2b36SAndroid Build Coastguard Workertype moduleMetadataMatcher struct {
166*333d2b36SAndroid Build Coastguard Worker	*android_bundle_proto.ModuleMetadata
167*333d2b36SAndroid Build Coastguard Worker}
168*333d2b36SAndroid Build Coastguard Worker
169*333d2b36SAndroid Build Coastguard Workerfunc (m moduleMetadataMatcher) matches(config TargetConfig) bool {
170*333d2b36SAndroid Build Coastguard Worker	return m.ModuleMetadata == nil ||
171*333d2b36SAndroid Build Coastguard Worker		(m.GetDeliveryType() == android_bundle_proto.DeliveryType_INSTALL_TIME &&
172*333d2b36SAndroid Build Coastguard Worker			moduleTargetingMatcher{m.Targeting}.matches(config) &&
173*333d2b36SAndroid Build Coastguard Worker			!m.IsInstant)
174*333d2b36SAndroid Build Coastguard Worker}
175*333d2b36SAndroid Build Coastguard Worker
176*333d2b36SAndroid Build Coastguard Workertype moduleTargetingMatcher struct {
177*333d2b36SAndroid Build Coastguard Worker	*android_bundle_proto.ModuleTargeting
178*333d2b36SAndroid Build Coastguard Worker}
179*333d2b36SAndroid Build Coastguard Worker
180*333d2b36SAndroid Build Coastguard Workerfunc (m moduleTargetingMatcher) matches(config TargetConfig) bool {
181*333d2b36SAndroid Build Coastguard Worker	return m.ModuleTargeting == nil ||
182*333d2b36SAndroid Build Coastguard Worker		(sdkVersionTargetingMatcher{m.SdkVersionTargeting}.matches(config) &&
183*333d2b36SAndroid Build Coastguard Worker			userCountriesTargetingMatcher{m.UserCountriesTargeting}.matches(config))
184*333d2b36SAndroid Build Coastguard Worker}
185*333d2b36SAndroid Build Coastguard Worker
186*333d2b36SAndroid Build Coastguard Worker// A higher number means a higher priority.
187*333d2b36SAndroid Build Coastguard Worker// This order must be kept identical to bundletool's.
188*333d2b36SAndroid Build Coastguard Workervar multiAbiPriorities = map[android_bundle_proto.Abi_AbiAlias]int{
189*333d2b36SAndroid Build Coastguard Worker	android_bundle_proto.Abi_ARMEABI:     1,
190*333d2b36SAndroid Build Coastguard Worker	android_bundle_proto.Abi_ARMEABI_V7A: 2,
191*333d2b36SAndroid Build Coastguard Worker	android_bundle_proto.Abi_ARM64_V8A:   3,
192*333d2b36SAndroid Build Coastguard Worker	android_bundle_proto.Abi_X86:         4,
193*333d2b36SAndroid Build Coastguard Worker	android_bundle_proto.Abi_X86_64:      5,
194*333d2b36SAndroid Build Coastguard Worker	android_bundle_proto.Abi_MIPS:        6,
195*333d2b36SAndroid Build Coastguard Worker	android_bundle_proto.Abi_MIPS64:      7,
196*333d2b36SAndroid Build Coastguard Worker}
197*333d2b36SAndroid Build Coastguard Worker
198*333d2b36SAndroid Build Coastguard Workertype multiAbiTargetingMatcher struct {
199*333d2b36SAndroid Build Coastguard Worker	*android_bundle_proto.MultiAbiTargeting
200*333d2b36SAndroid Build Coastguard Worker}
201*333d2b36SAndroid Build Coastguard Worker
202*333d2b36SAndroid Build Coastguard Workertype multiAbiValue []*bundle_proto.Abi
203*333d2b36SAndroid Build Coastguard Worker
204*333d2b36SAndroid Build Coastguard Workerfunc (m multiAbiValue) compare(other multiAbiValue) int {
205*333d2b36SAndroid Build Coastguard Worker	min := func(a, b int) int {
206*333d2b36SAndroid Build Coastguard Worker		if a < b {
207*333d2b36SAndroid Build Coastguard Worker			return a
208*333d2b36SAndroid Build Coastguard Worker		}
209*333d2b36SAndroid Build Coastguard Worker		return b
210*333d2b36SAndroid Build Coastguard Worker	}
211*333d2b36SAndroid Build Coastguard Worker
212*333d2b36SAndroid Build Coastguard Worker	sortAbis := func(abiSlice multiAbiValue) func(i, j int) bool {
213*333d2b36SAndroid Build Coastguard Worker		return func(i, j int) bool {
214*333d2b36SAndroid Build Coastguard Worker			// sort priorities greatest to least
215*333d2b36SAndroid Build Coastguard Worker			return multiAbiPriorities[abiSlice[i].Alias] > multiAbiPriorities[abiSlice[j].Alias]
216*333d2b36SAndroid Build Coastguard Worker		}
217*333d2b36SAndroid Build Coastguard Worker	}
218*333d2b36SAndroid Build Coastguard Worker
219*333d2b36SAndroid Build Coastguard Worker	sortedM := append(multiAbiValue{}, m...)
220*333d2b36SAndroid Build Coastguard Worker	sort.Slice(sortedM, sortAbis(sortedM))
221*333d2b36SAndroid Build Coastguard Worker	sortedOther := append(multiAbiValue{}, other...)
222*333d2b36SAndroid Build Coastguard Worker	sort.Slice(sortedOther, sortAbis(sortedOther))
223*333d2b36SAndroid Build Coastguard Worker
224*333d2b36SAndroid Build Coastguard Worker	for i := 0; i < min(len(sortedM), len(sortedOther)); i++ {
225*333d2b36SAndroid Build Coastguard Worker		if multiAbiPriorities[sortedM[i].Alias] > multiAbiPriorities[sortedOther[i].Alias] {
226*333d2b36SAndroid Build Coastguard Worker			return 1
227*333d2b36SAndroid Build Coastguard Worker		}
228*333d2b36SAndroid Build Coastguard Worker		if multiAbiPriorities[sortedM[i].Alias] < multiAbiPriorities[sortedOther[i].Alias] {
229*333d2b36SAndroid Build Coastguard Worker			return -1
230*333d2b36SAndroid Build Coastguard Worker		}
231*333d2b36SAndroid Build Coastguard Worker	}
232*333d2b36SAndroid Build Coastguard Worker
233*333d2b36SAndroid Build Coastguard Worker	return len(sortedM) - len(sortedOther)
234*333d2b36SAndroid Build Coastguard Worker}
235*333d2b36SAndroid Build Coastguard Worker
236*333d2b36SAndroid Build Coastguard Worker// this logic should match the logic in bundletool at
237*333d2b36SAndroid Build Coastguard Worker// https://github.com/google/bundletool/blob/ae0fc0162fd80d92ef8f4ef4527c066f0106942f/src/main/java/com/android/tools/build/bundletool/device/MultiAbiMatcher.java#L43
238*333d2b36SAndroid Build Coastguard Worker// (note link is the commit at time of writing; but logic should always match the latest)
239*333d2b36SAndroid Build Coastguard Workerfunc (t multiAbiTargetingMatcher) matches(config TargetConfig, allAbisMustMatch bool) bool {
240*333d2b36SAndroid Build Coastguard Worker	if t.MultiAbiTargeting == nil {
241*333d2b36SAndroid Build Coastguard Worker		return true
242*333d2b36SAndroid Build Coastguard Worker	}
243*333d2b36SAndroid Build Coastguard Worker	if _, ok := config.abis[android_bundle_proto.Abi_UNSPECIFIED_CPU_ARCHITECTURE]; ok {
244*333d2b36SAndroid Build Coastguard Worker		return true
245*333d2b36SAndroid Build Coastguard Worker	}
246*333d2b36SAndroid Build Coastguard Worker
247*333d2b36SAndroid Build Coastguard Worker	multiAbiIsValid := func(m multiAbiValue) bool {
248*333d2b36SAndroid Build Coastguard Worker		numValid := 0
249*333d2b36SAndroid Build Coastguard Worker		for _, abi := range m {
250*333d2b36SAndroid Build Coastguard Worker			if _, ok := config.abis[abi.Alias]; ok {
251*333d2b36SAndroid Build Coastguard Worker				numValid += 1
252*333d2b36SAndroid Build Coastguard Worker			}
253*333d2b36SAndroid Build Coastguard Worker		}
254*333d2b36SAndroid Build Coastguard Worker		if numValid == 0 {
255*333d2b36SAndroid Build Coastguard Worker			return false
256*333d2b36SAndroid Build Coastguard Worker		} else if numValid > 0 && !allAbisMustMatch {
257*333d2b36SAndroid Build Coastguard Worker			return true
258*333d2b36SAndroid Build Coastguard Worker		} else {
259*333d2b36SAndroid Build Coastguard Worker			return numValid == len(m)
260*333d2b36SAndroid Build Coastguard Worker		}
261*333d2b36SAndroid Build Coastguard Worker	}
262*333d2b36SAndroid Build Coastguard Worker
263*333d2b36SAndroid Build Coastguard Worker	// ensure that the current value is valid for our config
264*333d2b36SAndroid Build Coastguard Worker	valueSetContainsViableAbi := false
265*333d2b36SAndroid Build Coastguard Worker	multiAbiSet := t.GetValue()
266*333d2b36SAndroid Build Coastguard Worker	for _, multiAbi := range multiAbiSet {
267*333d2b36SAndroid Build Coastguard Worker		if multiAbiIsValid(multiAbi.GetAbi()) {
268*333d2b36SAndroid Build Coastguard Worker			valueSetContainsViableAbi = true
269*333d2b36SAndroid Build Coastguard Worker			break
270*333d2b36SAndroid Build Coastguard Worker		}
271*333d2b36SAndroid Build Coastguard Worker	}
272*333d2b36SAndroid Build Coastguard Worker
273*333d2b36SAndroid Build Coastguard Worker	if !valueSetContainsViableAbi {
274*333d2b36SAndroid Build Coastguard Worker		return false
275*333d2b36SAndroid Build Coastguard Worker	}
276*333d2b36SAndroid Build Coastguard Worker
277*333d2b36SAndroid Build Coastguard Worker	// See if there are any matching alternatives with a higher priority.
278*333d2b36SAndroid Build Coastguard Worker	for _, altMultiAbi := range t.GetAlternatives() {
279*333d2b36SAndroid Build Coastguard Worker		if !multiAbiIsValid(altMultiAbi.GetAbi()) {
280*333d2b36SAndroid Build Coastguard Worker			continue
281*333d2b36SAndroid Build Coastguard Worker		}
282*333d2b36SAndroid Build Coastguard Worker
283*333d2b36SAndroid Build Coastguard Worker		for _, multiAbi := range multiAbiSet {
284*333d2b36SAndroid Build Coastguard Worker			valueAbis := multiAbiValue(multiAbi.GetAbi())
285*333d2b36SAndroid Build Coastguard Worker			altAbis := multiAbiValue(altMultiAbi.GetAbi())
286*333d2b36SAndroid Build Coastguard Worker			if valueAbis.compare(altAbis) < 0 {
287*333d2b36SAndroid Build Coastguard Worker				// An alternative has a higher priority, don't use this one
288*333d2b36SAndroid Build Coastguard Worker				return false
289*333d2b36SAndroid Build Coastguard Worker			}
290*333d2b36SAndroid Build Coastguard Worker		}
291*333d2b36SAndroid Build Coastguard Worker	}
292*333d2b36SAndroid Build Coastguard Worker
293*333d2b36SAndroid Build Coastguard Worker	return true
294*333d2b36SAndroid Build Coastguard Worker}
295*333d2b36SAndroid Build Coastguard Worker
296*333d2b36SAndroid Build Coastguard Workertype screenDensityTargetingMatcher struct {
297*333d2b36SAndroid Build Coastguard Worker	*android_bundle_proto.ScreenDensityTargeting
298*333d2b36SAndroid Build Coastguard Worker}
299*333d2b36SAndroid Build Coastguard Worker
300*333d2b36SAndroid Build Coastguard Workerfunc (m screenDensityTargetingMatcher) matches(config TargetConfig) bool {
301*333d2b36SAndroid Build Coastguard Worker	if m.ScreenDensityTargeting == nil {
302*333d2b36SAndroid Build Coastguard Worker		return true
303*333d2b36SAndroid Build Coastguard Worker	}
304*333d2b36SAndroid Build Coastguard Worker	if _, ok := config.screenDpi[android_bundle_proto.ScreenDensity_DENSITY_UNSPECIFIED]; ok {
305*333d2b36SAndroid Build Coastguard Worker		return true
306*333d2b36SAndroid Build Coastguard Worker	}
307*333d2b36SAndroid Build Coastguard Worker	for _, v := range m.GetValue() {
308*333d2b36SAndroid Build Coastguard Worker		switch x := v.GetDensityOneof().(type) {
309*333d2b36SAndroid Build Coastguard Worker		case *android_bundle_proto.ScreenDensity_DensityAlias_:
310*333d2b36SAndroid Build Coastguard Worker			if _, ok := config.screenDpi[x.DensityAlias]; ok {
311*333d2b36SAndroid Build Coastguard Worker				return true
312*333d2b36SAndroid Build Coastguard Worker			}
313*333d2b36SAndroid Build Coastguard Worker		default:
314*333d2b36SAndroid Build Coastguard Worker			log.Fatal("For screen density, only DPI name based entry selection (e.g. HDPI, XHDPI) is implemented")
315*333d2b36SAndroid Build Coastguard Worker		}
316*333d2b36SAndroid Build Coastguard Worker	}
317*333d2b36SAndroid Build Coastguard Worker	return false
318*333d2b36SAndroid Build Coastguard Worker}
319*333d2b36SAndroid Build Coastguard Worker
320*333d2b36SAndroid Build Coastguard Workertype sdkVersionTargetingMatcher struct {
321*333d2b36SAndroid Build Coastguard Worker	*android_bundle_proto.SdkVersionTargeting
322*333d2b36SAndroid Build Coastguard Worker}
323*333d2b36SAndroid Build Coastguard Worker
324*333d2b36SAndroid Build Coastguard Workerfunc (m sdkVersionTargetingMatcher) matches(config TargetConfig) bool {
325*333d2b36SAndroid Build Coastguard Worker	const preReleaseVersion = 10000
326*333d2b36SAndroid Build Coastguard Worker	// TODO (b274518686) This check should only be used while SHA based targeting is active
327*333d2b36SAndroid Build Coastguard Worker	// Once we have switched to an SDK version, this can be changed to throw an error if
328*333d2b36SAndroid Build Coastguard Worker	// it was accidentally set
329*333d2b36SAndroid Build Coastguard Worker	if config.skipSdkCheck == true {
330*333d2b36SAndroid Build Coastguard Worker		return true
331*333d2b36SAndroid Build Coastguard Worker	}
332*333d2b36SAndroid Build Coastguard Worker	if m.SdkVersionTargeting == nil {
333*333d2b36SAndroid Build Coastguard Worker		return true
334*333d2b36SAndroid Build Coastguard Worker	}
335*333d2b36SAndroid Build Coastguard Worker	if len(m.Value) > 1 {
336*333d2b36SAndroid Build Coastguard Worker		log.Fatal(fmt.Sprintf("sdk_version_targeting should not have multiple values:%#v", m.Value))
337*333d2b36SAndroid Build Coastguard Worker	}
338*333d2b36SAndroid Build Coastguard Worker	// Inspect only sdkVersionTargeting.Value.
339*333d2b36SAndroid Build Coastguard Worker	// Even though one of the SdkVersionTargeting.Alternatives values may be
340*333d2b36SAndroid Build Coastguard Worker	// better matching, we will select all of them
341*333d2b36SAndroid Build Coastguard Worker	return m.Value[0].Min == nil ||
342*333d2b36SAndroid Build Coastguard Worker		m.Value[0].Min.Value <= config.sdkVersion ||
343*333d2b36SAndroid Build Coastguard Worker		(config.allowPrereleased && m.Value[0].Min.Value == preReleaseVersion)
344*333d2b36SAndroid Build Coastguard Worker}
345*333d2b36SAndroid Build Coastguard Worker
346*333d2b36SAndroid Build Coastguard Workertype textureCompressionFormatTargetingMatcher struct {
347*333d2b36SAndroid Build Coastguard Worker	*android_bundle_proto.TextureCompressionFormatTargeting
348*333d2b36SAndroid Build Coastguard Worker}
349*333d2b36SAndroid Build Coastguard Worker
350*333d2b36SAndroid Build Coastguard Workerfunc (m textureCompressionFormatTargetingMatcher) matches(_ TargetConfig) bool {
351*333d2b36SAndroid Build Coastguard Worker	if m.TextureCompressionFormatTargeting == nil {
352*333d2b36SAndroid Build Coastguard Worker		return true
353*333d2b36SAndroid Build Coastguard Worker	}
354*333d2b36SAndroid Build Coastguard Worker	log.Fatal("texture based entry selection is not implemented")
355*333d2b36SAndroid Build Coastguard Worker	return false
356*333d2b36SAndroid Build Coastguard Worker}
357*333d2b36SAndroid Build Coastguard Worker
358*333d2b36SAndroid Build Coastguard Workertype userCountriesTargetingMatcher struct {
359*333d2b36SAndroid Build Coastguard Worker	*android_bundle_proto.UserCountriesTargeting
360*333d2b36SAndroid Build Coastguard Worker}
361*333d2b36SAndroid Build Coastguard Worker
362*333d2b36SAndroid Build Coastguard Workerfunc (m userCountriesTargetingMatcher) matches(_ TargetConfig) bool {
363*333d2b36SAndroid Build Coastguard Worker	if m.UserCountriesTargeting == nil {
364*333d2b36SAndroid Build Coastguard Worker		return true
365*333d2b36SAndroid Build Coastguard Worker	}
366*333d2b36SAndroid Build Coastguard Worker	log.Fatal("country based entry selection is not implemented")
367*333d2b36SAndroid Build Coastguard Worker	return false
368*333d2b36SAndroid Build Coastguard Worker}
369*333d2b36SAndroid Build Coastguard Worker
370*333d2b36SAndroid Build Coastguard Workertype variantTargetingMatcher struct {
371*333d2b36SAndroid Build Coastguard Worker	*android_bundle_proto.VariantTargeting
372*333d2b36SAndroid Build Coastguard Worker}
373*333d2b36SAndroid Build Coastguard Worker
374*333d2b36SAndroid Build Coastguard Workerfunc (m variantTargetingMatcher) matches(config TargetConfig, allAbisMustMatch bool) bool {
375*333d2b36SAndroid Build Coastguard Worker	if m.VariantTargeting == nil {
376*333d2b36SAndroid Build Coastguard Worker		return true
377*333d2b36SAndroid Build Coastguard Worker	}
378*333d2b36SAndroid Build Coastguard Worker	return sdkVersionTargetingMatcher{m.SdkVersionTargeting}.matches(config) &&
379*333d2b36SAndroid Build Coastguard Worker		abiTargetingMatcher{m.AbiTargeting}.matches(config) &&
380*333d2b36SAndroid Build Coastguard Worker		multiAbiTargetingMatcher{m.MultiAbiTargeting}.matches(config, allAbisMustMatch) &&
381*333d2b36SAndroid Build Coastguard Worker		screenDensityTargetingMatcher{m.ScreenDensityTargeting}.matches(config) &&
382*333d2b36SAndroid Build Coastguard Worker		textureCompressionFormatTargetingMatcher{m.TextureCompressionFormatTargeting}.matches(config)
383*333d2b36SAndroid Build Coastguard Worker}
384*333d2b36SAndroid Build Coastguard Worker
385*333d2b36SAndroid Build Coastguard Workertype SelectionResult struct {
386*333d2b36SAndroid Build Coastguard Worker	moduleName string
387*333d2b36SAndroid Build Coastguard Worker	entries    []string
388*333d2b36SAndroid Build Coastguard Worker}
389*333d2b36SAndroid Build Coastguard Worker
390*333d2b36SAndroid Build Coastguard Worker// Return all entries matching target configuration
391*333d2b36SAndroid Build Coastguard Workerfunc selectApks(toc Toc, targetConfig TargetConfig) SelectionResult {
392*333d2b36SAndroid Build Coastguard Worker	checkMatching := func(allAbisMustMatch bool) SelectionResult {
393*333d2b36SAndroid Build Coastguard Worker		var result SelectionResult
394*333d2b36SAndroid Build Coastguard Worker		for _, variant := range (*toc).GetVariant() {
395*333d2b36SAndroid Build Coastguard Worker			if !(variantTargetingMatcher{variant.GetTargeting()}.matches(targetConfig, allAbisMustMatch)) {
396*333d2b36SAndroid Build Coastguard Worker				continue
397*333d2b36SAndroid Build Coastguard Worker			}
398*333d2b36SAndroid Build Coastguard Worker			for _, as := range variant.GetApkSet() {
399*333d2b36SAndroid Build Coastguard Worker				if !(moduleMetadataMatcher{as.ModuleMetadata}.matches(targetConfig)) {
400*333d2b36SAndroid Build Coastguard Worker					continue
401*333d2b36SAndroid Build Coastguard Worker				}
402*333d2b36SAndroid Build Coastguard Worker				for _, apkdesc := range as.GetApkDescription() {
403*333d2b36SAndroid Build Coastguard Worker					if (apkDescriptionMatcher{apkdesc}).matches(targetConfig, allAbisMustMatch) {
404*333d2b36SAndroid Build Coastguard Worker						result.entries = append(result.entries, apkdesc.GetPath())
405*333d2b36SAndroid Build Coastguard Worker						// TODO(asmundak): As it turns out, moduleName which we get from
406*333d2b36SAndroid Build Coastguard Worker						// the ModuleMetadata matches the module names of the generated
407*333d2b36SAndroid Build Coastguard Worker						// entry paths just by coincidence, only for the split APKs. We
408*333d2b36SAndroid Build Coastguard Worker						// need to discuss this with bundletool folks.
409*333d2b36SAndroid Build Coastguard Worker						result.moduleName = as.GetModuleMetadata().GetName()
410*333d2b36SAndroid Build Coastguard Worker					}
411*333d2b36SAndroid Build Coastguard Worker				}
412*333d2b36SAndroid Build Coastguard Worker				// we allow only a single module, so bail out here if we found one
413*333d2b36SAndroid Build Coastguard Worker				if result.moduleName != "" {
414*333d2b36SAndroid Build Coastguard Worker					return result
415*333d2b36SAndroid Build Coastguard Worker				}
416*333d2b36SAndroid Build Coastguard Worker			}
417*333d2b36SAndroid Build Coastguard Worker		}
418*333d2b36SAndroid Build Coastguard Worker		return result
419*333d2b36SAndroid Build Coastguard Worker	}
420*333d2b36SAndroid Build Coastguard Worker	result := checkMatching(true)
421*333d2b36SAndroid Build Coastguard Worker	if result.moduleName == "" {
422*333d2b36SAndroid Build Coastguard Worker		// if there are no matches where all of the ABIs are available in the
423*333d2b36SAndroid Build Coastguard Worker		// TargetConfig, then search again with a looser requirement of at
424*333d2b36SAndroid Build Coastguard Worker		// least one matching ABI
425*333d2b36SAndroid Build Coastguard Worker		// NOTE(b/260130686): this logic diverges from the logic in bundletool
426*333d2b36SAndroid Build Coastguard Worker		// https://github.com/google/bundletool/blob/ae0fc0162fd80d92ef8f4ef4527c066f0106942f/src/main/java/com/android/tools/build/bundletool/device/MultiAbiMatcher.java#L43
427*333d2b36SAndroid Build Coastguard Worker		result = checkMatching(false)
428*333d2b36SAndroid Build Coastguard Worker	}
429*333d2b36SAndroid Build Coastguard Worker	return result
430*333d2b36SAndroid Build Coastguard Worker}
431*333d2b36SAndroid Build Coastguard Worker
432*333d2b36SAndroid Build Coastguard Workertype Zip2ZipWriter interface {
433*333d2b36SAndroid Build Coastguard Worker	CopyFrom(file *zip.File, name string) error
434*333d2b36SAndroid Build Coastguard Worker}
435*333d2b36SAndroid Build Coastguard Worker
436*333d2b36SAndroid Build Coastguard Worker// Writes out selected entries, renaming them as needed
437*333d2b36SAndroid Build Coastguard Workerfunc (apkSet *ApkSet) writeApks(selected SelectionResult, config TargetConfig,
438*333d2b36SAndroid Build Coastguard Worker	outFile io.Writer, zipWriter Zip2ZipWriter, partition string) ([]string, error) {
439*333d2b36SAndroid Build Coastguard Worker	// Renaming rules:
440*333d2b36SAndroid Build Coastguard Worker	//  splits/MODULE-master.apk to STEM.apk
441*333d2b36SAndroid Build Coastguard Worker	// else
442*333d2b36SAndroid Build Coastguard Worker	//  splits/MODULE-*.apk to STEM>-$1.apk
443*333d2b36SAndroid Build Coastguard Worker	// TODO(asmundak):
444*333d2b36SAndroid Build Coastguard Worker	//  add more rules, for .apex files
445*333d2b36SAndroid Build Coastguard Worker	renameRules := []struct {
446*333d2b36SAndroid Build Coastguard Worker		rex  *regexp.Regexp
447*333d2b36SAndroid Build Coastguard Worker		repl string
448*333d2b36SAndroid Build Coastguard Worker	}{
449*333d2b36SAndroid Build Coastguard Worker		{
450*333d2b36SAndroid Build Coastguard Worker			regexp.MustCompile(`^.*/` + selected.moduleName + `-master\.apk$`),
451*333d2b36SAndroid Build Coastguard Worker			config.stem + `.apk`,
452*333d2b36SAndroid Build Coastguard Worker		},
453*333d2b36SAndroid Build Coastguard Worker		{
454*333d2b36SAndroid Build Coastguard Worker			regexp.MustCompile(`^.*/` + selected.moduleName + `(-.*\.apk)$`),
455*333d2b36SAndroid Build Coastguard Worker			config.stem + `$1`,
456*333d2b36SAndroid Build Coastguard Worker		},
457*333d2b36SAndroid Build Coastguard Worker		{
458*333d2b36SAndroid Build Coastguard Worker			regexp.MustCompile(`^universal\.apk$`),
459*333d2b36SAndroid Build Coastguard Worker			config.stem + ".apk",
460*333d2b36SAndroid Build Coastguard Worker		},
461*333d2b36SAndroid Build Coastguard Worker	}
462*333d2b36SAndroid Build Coastguard Worker	renamer := func(path string) (string, bool) {
463*333d2b36SAndroid Build Coastguard Worker		for _, rr := range renameRules {
464*333d2b36SAndroid Build Coastguard Worker			if rr.rex.MatchString(path) {
465*333d2b36SAndroid Build Coastguard Worker				return rr.rex.ReplaceAllString(path, rr.repl), true
466*333d2b36SAndroid Build Coastguard Worker			}
467*333d2b36SAndroid Build Coastguard Worker		}
468*333d2b36SAndroid Build Coastguard Worker		return "", false
469*333d2b36SAndroid Build Coastguard Worker	}
470*333d2b36SAndroid Build Coastguard Worker
471*333d2b36SAndroid Build Coastguard Worker	entryOrigin := make(map[string]string) // output entry to input entry
472*333d2b36SAndroid Build Coastguard Worker	var apkcerts []string
473*333d2b36SAndroid Build Coastguard Worker	for _, apk := range selected.entries {
474*333d2b36SAndroid Build Coastguard Worker		apkFile, ok := apkSet.entries[apk]
475*333d2b36SAndroid Build Coastguard Worker		if !ok {
476*333d2b36SAndroid Build Coastguard Worker			return nil, fmt.Errorf("TOC refers to an entry %s which does not exist", apk)
477*333d2b36SAndroid Build Coastguard Worker		}
478*333d2b36SAndroid Build Coastguard Worker		inName := apkFile.Name
479*333d2b36SAndroid Build Coastguard Worker		outName, ok := renamer(inName)
480*333d2b36SAndroid Build Coastguard Worker		if !ok {
481*333d2b36SAndroid Build Coastguard Worker			log.Fatalf("selected an entry with unexpected name %s", inName)
482*333d2b36SAndroid Build Coastguard Worker		}
483*333d2b36SAndroid Build Coastguard Worker		if origin, ok := entryOrigin[inName]; ok {
484*333d2b36SAndroid Build Coastguard Worker			log.Fatalf("selected entries %s and %s will have the same output name %s",
485*333d2b36SAndroid Build Coastguard Worker				origin, inName, outName)
486*333d2b36SAndroid Build Coastguard Worker		}
487*333d2b36SAndroid Build Coastguard Worker		entryOrigin[outName] = inName
488*333d2b36SAndroid Build Coastguard Worker		if outName == config.stem+".apk" {
489*333d2b36SAndroid Build Coastguard Worker			if err := writeZipEntryToFile(outFile, apkFile); err != nil {
490*333d2b36SAndroid Build Coastguard Worker				return nil, err
491*333d2b36SAndroid Build Coastguard Worker			}
492*333d2b36SAndroid Build Coastguard Worker		} else {
493*333d2b36SAndroid Build Coastguard Worker			if err := zipWriter.CopyFrom(apkFile, outName); err != nil {
494*333d2b36SAndroid Build Coastguard Worker				return nil, err
495*333d2b36SAndroid Build Coastguard Worker			}
496*333d2b36SAndroid Build Coastguard Worker		}
497*333d2b36SAndroid Build Coastguard Worker		if partition != "" {
498*333d2b36SAndroid Build Coastguard Worker			apkcerts = append(apkcerts, fmt.Sprintf(
499*333d2b36SAndroid Build Coastguard Worker				`name="%s" certificate="PRESIGNED" private_key="" partition="%s"`, outName, partition))
500*333d2b36SAndroid Build Coastguard Worker		}
501*333d2b36SAndroid Build Coastguard Worker	}
502*333d2b36SAndroid Build Coastguard Worker	sort.Strings(apkcerts)
503*333d2b36SAndroid Build Coastguard Worker	return apkcerts, nil
504*333d2b36SAndroid Build Coastguard Worker}
505*333d2b36SAndroid Build Coastguard Worker
506*333d2b36SAndroid Build Coastguard Workerfunc (apkSet *ApkSet) extractAndCopySingle(selected SelectionResult, outFile *os.File) error {
507*333d2b36SAndroid Build Coastguard Worker	if len(selected.entries) != 1 {
508*333d2b36SAndroid Build Coastguard Worker		return fmt.Errorf("Too many matching entries for extract-single:\n%v", selected.entries)
509*333d2b36SAndroid Build Coastguard Worker	}
510*333d2b36SAndroid Build Coastguard Worker	apk, ok := apkSet.entries[selected.entries[0]]
511*333d2b36SAndroid Build Coastguard Worker	if !ok {
512*333d2b36SAndroid Build Coastguard Worker		return fmt.Errorf("Couldn't find apk path %s", selected.entries[0])
513*333d2b36SAndroid Build Coastguard Worker	}
514*333d2b36SAndroid Build Coastguard Worker	return writeZipEntryToFile(outFile, apk)
515*333d2b36SAndroid Build Coastguard Worker}
516*333d2b36SAndroid Build Coastguard Worker
517*333d2b36SAndroid Build Coastguard Worker// Arguments parsing
518*333d2b36SAndroid Build Coastguard Workervar (
519*333d2b36SAndroid Build Coastguard Worker	outputFile   = flag.String("o", "", "output file for primary entry")
520*333d2b36SAndroid Build Coastguard Worker	zipFile      = flag.String("zip", "", "output file containing additional extracted entries")
521*333d2b36SAndroid Build Coastguard Worker	targetConfig = TargetConfig{
522*333d2b36SAndroid Build Coastguard Worker		screenDpi: map[android_bundle_proto.ScreenDensity_DensityAlias]bool{},
523*333d2b36SAndroid Build Coastguard Worker		abis:      map[android_bundle_proto.Abi_AbiAlias]int{},
524*333d2b36SAndroid Build Coastguard Worker	}
525*333d2b36SAndroid Build Coastguard Worker	extractSingle = flag.Bool("extract-single", false,
526*333d2b36SAndroid Build Coastguard Worker		"extract a single target and output it uncompressed. only available for standalone apks and apexes.")
527*333d2b36SAndroid Build Coastguard Worker	apkcertsOutput = flag.String("apkcerts", "",
528*333d2b36SAndroid Build Coastguard Worker		"optional apkcerts.txt output file containing signing info of all outputted apks")
529*333d2b36SAndroid Build Coastguard Worker	partition = flag.String("partition", "", "partition string. required when -apkcerts is used.")
530*333d2b36SAndroid Build Coastguard Worker)
531*333d2b36SAndroid Build Coastguard Worker
532*333d2b36SAndroid Build Coastguard Worker// Parse abi values
533*333d2b36SAndroid Build Coastguard Workertype abiFlagValue struct {
534*333d2b36SAndroid Build Coastguard Worker	targetConfig *TargetConfig
535*333d2b36SAndroid Build Coastguard Worker}
536*333d2b36SAndroid Build Coastguard Worker
537*333d2b36SAndroid Build Coastguard Workerfunc (a abiFlagValue) String() string {
538*333d2b36SAndroid Build Coastguard Worker	return "all"
539*333d2b36SAndroid Build Coastguard Worker}
540*333d2b36SAndroid Build Coastguard Worker
541*333d2b36SAndroid Build Coastguard Workerfunc (a abiFlagValue) Set(abiList string) error {
542*333d2b36SAndroid Build Coastguard Worker	for i, abi := range strings.Split(abiList, ",") {
543*333d2b36SAndroid Build Coastguard Worker		v, ok := android_bundle_proto.Abi_AbiAlias_value[abi]
544*333d2b36SAndroid Build Coastguard Worker		if !ok {
545*333d2b36SAndroid Build Coastguard Worker			return fmt.Errorf("bad ABI value: %q", abi)
546*333d2b36SAndroid Build Coastguard Worker		}
547*333d2b36SAndroid Build Coastguard Worker		targetConfig.abis[android_bundle_proto.Abi_AbiAlias(v)] = i
548*333d2b36SAndroid Build Coastguard Worker	}
549*333d2b36SAndroid Build Coastguard Worker	return nil
550*333d2b36SAndroid Build Coastguard Worker}
551*333d2b36SAndroid Build Coastguard Worker
552*333d2b36SAndroid Build Coastguard Worker// Parse screen density values
553*333d2b36SAndroid Build Coastguard Workertype screenDensityFlagValue struct {
554*333d2b36SAndroid Build Coastguard Worker	targetConfig *TargetConfig
555*333d2b36SAndroid Build Coastguard Worker}
556*333d2b36SAndroid Build Coastguard Worker
557*333d2b36SAndroid Build Coastguard Workerfunc (s screenDensityFlagValue) String() string {
558*333d2b36SAndroid Build Coastguard Worker	return "none"
559*333d2b36SAndroid Build Coastguard Worker}
560*333d2b36SAndroid Build Coastguard Worker
561*333d2b36SAndroid Build Coastguard Workerfunc (s screenDensityFlagValue) Set(densityList string) error {
562*333d2b36SAndroid Build Coastguard Worker	if densityList == "none" {
563*333d2b36SAndroid Build Coastguard Worker		return nil
564*333d2b36SAndroid Build Coastguard Worker	}
565*333d2b36SAndroid Build Coastguard Worker	if densityList == "all" {
566*333d2b36SAndroid Build Coastguard Worker		targetConfig.screenDpi[android_bundle_proto.ScreenDensity_DENSITY_UNSPECIFIED] = true
567*333d2b36SAndroid Build Coastguard Worker		return nil
568*333d2b36SAndroid Build Coastguard Worker	}
569*333d2b36SAndroid Build Coastguard Worker	for _, density := range strings.Split(densityList, ",") {
570*333d2b36SAndroid Build Coastguard Worker		v, found := android_bundle_proto.ScreenDensity_DensityAlias_value[density]
571*333d2b36SAndroid Build Coastguard Worker		if !found {
572*333d2b36SAndroid Build Coastguard Worker			return fmt.Errorf("bad screen density value: %q", density)
573*333d2b36SAndroid Build Coastguard Worker		}
574*333d2b36SAndroid Build Coastguard Worker		targetConfig.screenDpi[android_bundle_proto.ScreenDensity_DensityAlias(v)] = true
575*333d2b36SAndroid Build Coastguard Worker	}
576*333d2b36SAndroid Build Coastguard Worker	return nil
577*333d2b36SAndroid Build Coastguard Worker}
578*333d2b36SAndroid Build Coastguard Worker
579*333d2b36SAndroid Build Coastguard Workerfunc processArgs() {
580*333d2b36SAndroid Build Coastguard Worker	flag.Usage = func() {
581*333d2b36SAndroid Build Coastguard Worker		fmt.Fprintln(os.Stderr, `usage: extract_apks -o <output-file> [-zip <output-zip-file>] `+
582*333d2b36SAndroid Build Coastguard Worker			`-sdk-version value -abis value [-skip-sdk-check]`+
583*333d2b36SAndroid Build Coastguard Worker			`-screen-densities value {-stem value | -extract-single} [-allow-prereleased] `+
584*333d2b36SAndroid Build Coastguard Worker			`[-apkcerts <apkcerts output file> -partition <partition>] <APK set>`)
585*333d2b36SAndroid Build Coastguard Worker		flag.PrintDefaults()
586*333d2b36SAndroid Build Coastguard Worker		os.Exit(2)
587*333d2b36SAndroid Build Coastguard Worker	}
588*333d2b36SAndroid Build Coastguard Worker	version := flag.Uint("sdk-version", 0, "SDK version")
589*333d2b36SAndroid Build Coastguard Worker	flag.Var(abiFlagValue{&targetConfig}, "abis",
590*333d2b36SAndroid Build Coastguard Worker		"comma-separated ABIs list of ARMEABI ARMEABI_V7A ARM64_V8A X86 X86_64 MIPS MIPS64")
591*333d2b36SAndroid Build Coastguard Worker	flag.Var(screenDensityFlagValue{&targetConfig}, "screen-densities",
592*333d2b36SAndroid Build Coastguard Worker		"'all' or comma-separated list of screen density names (NODPI LDPI MDPI TVDPI HDPI XHDPI XXHDPI XXXHDPI)")
593*333d2b36SAndroid Build Coastguard Worker	flag.BoolVar(&targetConfig.allowPrereleased, "allow-prereleased", false,
594*333d2b36SAndroid Build Coastguard Worker		"allow prereleased")
595*333d2b36SAndroid Build Coastguard Worker	flag.BoolVar(&targetConfig.skipSdkCheck, "skip-sdk-check", false, "Skip the SDK version check")
596*333d2b36SAndroid Build Coastguard Worker	flag.StringVar(&targetConfig.stem, "stem", "", "output entries base name in the output zip file")
597*333d2b36SAndroid Build Coastguard Worker	flag.Parse()
598*333d2b36SAndroid Build Coastguard Worker	if (*outputFile == "") || len(flag.Args()) != 1 || *version == 0 ||
599*333d2b36SAndroid Build Coastguard Worker		((targetConfig.stem == "" || *zipFile == "") && !*extractSingle) ||
600*333d2b36SAndroid Build Coastguard Worker		(*apkcertsOutput != "" && *partition == "") {
601*333d2b36SAndroid Build Coastguard Worker		flag.Usage()
602*333d2b36SAndroid Build Coastguard Worker	}
603*333d2b36SAndroid Build Coastguard Worker	targetConfig.sdkVersion = int32(*version)
604*333d2b36SAndroid Build Coastguard Worker
605*333d2b36SAndroid Build Coastguard Worker}
606*333d2b36SAndroid Build Coastguard Worker
607*333d2b36SAndroid Build Coastguard Workerfunc main() {
608*333d2b36SAndroid Build Coastguard Worker	processArgs()
609*333d2b36SAndroid Build Coastguard Worker	var toc Toc
610*333d2b36SAndroid Build Coastguard Worker	apkSet, err := newApkSet(flag.Arg(0))
611*333d2b36SAndroid Build Coastguard Worker	if err == nil {
612*333d2b36SAndroid Build Coastguard Worker		defer apkSet.close()
613*333d2b36SAndroid Build Coastguard Worker		toc, err = apkSet.getToc()
614*333d2b36SAndroid Build Coastguard Worker	}
615*333d2b36SAndroid Build Coastguard Worker	if err != nil {
616*333d2b36SAndroid Build Coastguard Worker		log.Fatal(err)
617*333d2b36SAndroid Build Coastguard Worker	}
618*333d2b36SAndroid Build Coastguard Worker	sel := selectApks(toc, targetConfig)
619*333d2b36SAndroid Build Coastguard Worker	if len(sel.entries) == 0 {
620*333d2b36SAndroid Build Coastguard Worker		log.Fatalf("there are no entries for the target configuration: %#v", targetConfig)
621*333d2b36SAndroid Build Coastguard Worker	}
622*333d2b36SAndroid Build Coastguard Worker
623*333d2b36SAndroid Build Coastguard Worker	outFile, err := os.Create(*outputFile)
624*333d2b36SAndroid Build Coastguard Worker	if err != nil {
625*333d2b36SAndroid Build Coastguard Worker		log.Fatal(err)
626*333d2b36SAndroid Build Coastguard Worker	}
627*333d2b36SAndroid Build Coastguard Worker	defer outFile.Close()
628*333d2b36SAndroid Build Coastguard Worker
629*333d2b36SAndroid Build Coastguard Worker	if *extractSingle {
630*333d2b36SAndroid Build Coastguard Worker		err = apkSet.extractAndCopySingle(sel, outFile)
631*333d2b36SAndroid Build Coastguard Worker	} else {
632*333d2b36SAndroid Build Coastguard Worker		zipOutputFile, err := os.Create(*zipFile)
633*333d2b36SAndroid Build Coastguard Worker		if err != nil {
634*333d2b36SAndroid Build Coastguard Worker			log.Fatal(err)
635*333d2b36SAndroid Build Coastguard Worker		}
636*333d2b36SAndroid Build Coastguard Worker		defer zipOutputFile.Close()
637*333d2b36SAndroid Build Coastguard Worker
638*333d2b36SAndroid Build Coastguard Worker		zipWriter := zip.NewWriter(zipOutputFile)
639*333d2b36SAndroid Build Coastguard Worker		defer func() {
640*333d2b36SAndroid Build Coastguard Worker			if err := zipWriter.Close(); err != nil {
641*333d2b36SAndroid Build Coastguard Worker				log.Fatal(err)
642*333d2b36SAndroid Build Coastguard Worker			}
643*333d2b36SAndroid Build Coastguard Worker		}()
644*333d2b36SAndroid Build Coastguard Worker
645*333d2b36SAndroid Build Coastguard Worker		apkcerts, err := apkSet.writeApks(sel, targetConfig, outFile, zipWriter, *partition)
646*333d2b36SAndroid Build Coastguard Worker		if err == nil && *apkcertsOutput != "" {
647*333d2b36SAndroid Build Coastguard Worker			apkcertsFile, err := os.Create(*apkcertsOutput)
648*333d2b36SAndroid Build Coastguard Worker			if err != nil {
649*333d2b36SAndroid Build Coastguard Worker				log.Fatal(err)
650*333d2b36SAndroid Build Coastguard Worker			}
651*333d2b36SAndroid Build Coastguard Worker			defer apkcertsFile.Close()
652*333d2b36SAndroid Build Coastguard Worker			for _, a := range apkcerts {
653*333d2b36SAndroid Build Coastguard Worker				_, err = apkcertsFile.WriteString(a + "\n")
654*333d2b36SAndroid Build Coastguard Worker				if err != nil {
655*333d2b36SAndroid Build Coastguard Worker					log.Fatal(err)
656*333d2b36SAndroid Build Coastguard Worker				}
657*333d2b36SAndroid Build Coastguard Worker			}
658*333d2b36SAndroid Build Coastguard Worker		}
659*333d2b36SAndroid Build Coastguard Worker	}
660*333d2b36SAndroid Build Coastguard Worker	if err != nil {
661*333d2b36SAndroid Build Coastguard Worker		log.Fatal(err)
662*333d2b36SAndroid Build Coastguard Worker	}
663*333d2b36SAndroid Build Coastguard Worker}
664*333d2b36SAndroid Build Coastguard Worker
665*333d2b36SAndroid Build Coastguard Workerfunc writeZipEntryToFile(outFile io.Writer, zipEntry *zip.File) error {
666*333d2b36SAndroid Build Coastguard Worker	reader, err := zipEntry.Open()
667*333d2b36SAndroid Build Coastguard Worker	if err != nil {
668*333d2b36SAndroid Build Coastguard Worker		return err
669*333d2b36SAndroid Build Coastguard Worker	}
670*333d2b36SAndroid Build Coastguard Worker	defer reader.Close()
671*333d2b36SAndroid Build Coastguard Worker	_, err = io.Copy(outFile, reader)
672*333d2b36SAndroid Build Coastguard Worker	return err
673*333d2b36SAndroid Build Coastguard Worker}
674