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