1// Copyright 2022 The Bazel Authors. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// Package generatemanifest is a command line tool to generate an empty AndroidManifest 16package generatemanifest 17 18import ( 19 "bufio" 20 "encoding/xml" 21 "flag" 22 "fmt" 23 "io" 24 "io/ioutil" 25 "log" 26 "os" 27 "strconv" 28 "sync" 29 30 "src/common/golang/flags" 31 "src/tools/ak/types" 32) 33 34// Structs used for reading the manifest xml file 35type manifestTag struct { 36 XMLName xml.Name `xml:"manifest"` 37 UsesSdk usesSdkTag `xml:"uses-sdk"` 38} 39 40type usesSdkTag struct { 41 XMLName xml.Name `xml:"uses-sdk"` 42 MinSdk string `xml:"minSdkVersion,attr"` 43} 44 45type result struct { 46 minSdk int 47 err error 48} 49 50const manifestContent string = `<?xml version="1.0" encoding="utf-8"?> 51<manifest xmlns:android="http://schemas.android.com/apk/res/android" 52 package="%s"> 53 <uses-sdk android:minSdkVersion="%d" /> 54 <application/> 55</manifest> 56` 57 58var ( 59 // Cmd defines the command to run 60 Cmd = types.Command{ 61 Init: Init, 62 Run: Run, 63 Desc: desc, 64 Flags: []string{ 65 "out", 66 "java_package", 67 "manifests", 68 "minsdk", 69 }, 70 } 71 72 // Flag variables 73 out, javaPackage string 74 minSdk int 75 manifests flags.StringList 76 77 initOnce sync.Once 78) 79 80// Init initializes manifest flags 81func Init() { 82 initOnce.Do(func() { 83 flag.StringVar(&out, "out", "", "Path to output manifest generated with the max min sdk value found from --manifests.") 84 flag.StringVar(&javaPackage, "java_package", "com.default", "(optional) Java package to use for the manifest.") 85 flag.IntVar(&minSdk, "minsdk", 14, "(optional) Default min sdk to support.") 86 flag.Var(&manifests, "manifests", "(optional) Manifests(s) to get min sdk from.") 87 }) 88} 89 90func desc() string { 91 return "Generates an empty AndroidManifest.xml with a minSdk value. The min sdk is selected " + 92 "by taking the max value found between the manifests and the minsdk flag." 93} 94 95// Run is the main entry point 96func Run() { 97 if out == "" { 98 log.Fatal("Missing required flag. Must specify --out") 99 } 100 101 var manifestFiles []io.ReadCloser 102 for _, manifest := range manifests { 103 manifestFile, err := os.Open(manifest) 104 if err != nil { 105 log.Fatalf("error opening manifest %s: %v", manifest, err) 106 } 107 manifestFiles = append(manifestFiles, manifestFile) 108 } 109 defer func(manifestFiles []io.ReadCloser) { 110 for _, manifestFile := range manifestFiles { 111 manifestFile.Close() 112 } 113 }(manifestFiles) 114 115 extractedMinSdk, err := extractMinSdk(manifestFiles, minSdk) 116 if err != nil { 117 log.Fatalf("error extracting min sdk from manifests: %v", err) 118 } 119 120 outFile, err := os.Create(out) 121 if err != nil { 122 log.Fatalf("error opening output manifest: %v", err) 123 } 124 defer outFile.Close() 125 if err := writeManifest(outFile, javaPackage, extractedMinSdk); err != nil { 126 log.Fatalf("error writing output manifest: %v", err) 127 } 128} 129 130// The min sdk is selected by taking the max value found 131// between the manifests and the minsdk flag 132func extractMinSdk(manifests []io.ReadCloser, defaultSdk int) (int, error) { 133 // Extracting minSdk values in goroutines 134 results := make(chan result, len(manifests)) 135 var wg sync.WaitGroup 136 wg.Add(len(manifests)) 137 for _, manifestFile := range manifests { 138 go func(manifestFile io.Reader) { 139 res := extractMinSdkFromManifest(manifestFile) 140 results <- res 141 wg.Done() 142 }(manifestFile) 143 } 144 wg.Wait() 145 close(results) 146 147 // Finding max value from channel 148 minSdk := defaultSdk 149 for result := range results { 150 if result.err != nil { 151 return 0, result.err 152 } 153 minSdk = max(minSdk, result.minSdk) 154 } 155 return minSdk, nil 156} 157 158func extractMinSdkFromManifest(reader io.Reader) result { 159 manifestBytes, err := ioutil.ReadAll(reader) 160 if err != nil { 161 return result{minSdk: 0, err: err} 162 } 163 usesSdk := usesSdkTag{MinSdk: ""} 164 manifest := manifestTag{UsesSdk: usesSdk} 165 if err := xml.Unmarshal(manifestBytes, &manifest); err != nil { 166 return result{minSdk: 0, err: err} 167 } 168 169 // MinSdk value could be a placeholder, we ignore it if that's the case 170 value, err := strconv.Atoi(manifest.UsesSdk.MinSdk) 171 if err != nil { 172 return result{minSdk: 0, err: nil} 173 } 174 return result{minSdk: value, err: nil} 175} 176 177func writeManifest(outManifest io.Writer, javaPackage string, minSdk int) error { 178 manifestWriter := bufio.NewWriter(outManifest) 179 manifestWriter.WriteString(fmt.Sprintf(manifestContent, javaPackage, minSdk)) 180 return manifestWriter.Flush() 181} 182 183func max(a, b int) int { 184 if a < b { 185 return b 186 } 187 return a 188} 189