blob: a42a227e25c05fd0491814431fe1168fe587272b [file]
// Copyright 2022 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package generatemanifest is a command line tool to generate an empty AndroidManifest
package generatemanifest
import (
"bufio"
"encoding/xml"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"strconv"
"sync"
"src/common/golang/flags"
"src/tools/ak/types"
)
// Structs used for reading the manifest xml file
type manifestTag struct {
XMLName xml.Name `xml:"manifest"`
UsesSdk usesSdkTag `xml:"uses-sdk"`
}
type usesSdkTag struct {
XMLName xml.Name `xml:"uses-sdk"`
MinSdk string `xml:"minSdkVersion,attr"`
}
type result struct {
minSdk int
err error
}
const manifestContent string = `<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="%s">
<uses-sdk android:minSdkVersion="%d" />
<application/>
</manifest>
`
var (
// Cmd defines the command to run
Cmd = types.Command{
Init: Init,
Run: Run,
Desc: desc,
Flags: []string{
"out",
"java_package",
"manifests",
"minsdk",
},
}
// Flag variables
out, javaPackage string
minSdk int
manifests flags.StringList
initOnce sync.Once
)
// Init initializes manifest flags
func Init() {
initOnce.Do(func() {
flag.StringVar(&out, "out", "", "Path to output manifest generated with the max min sdk value found from --manifests.")
flag.StringVar(&javaPackage, "java_package", "com.default", "(optional) Java package to use for the manifest.")
flag.IntVar(&minSdk, "minsdk", 14, "(optional) Default min sdk to support.")
flag.Var(&manifests, "manifests", "(optional) Manifests(s) to get min sdk from.")
})
}
func desc() string {
return "Generates an empty AndroidManifest.xml with a minSdk value. The min sdk is selected " +
"by taking the max value found between the manifests and the minsdk flag."
}
// Run is the main entry point
func Run() {
if out == "" {
log.Fatal("Missing required flag. Must specify --out")
}
var manifestFiles []io.ReadCloser
for _, manifest := range manifests {
manifestFile, err := os.Open(manifest)
if err != nil {
log.Fatalf("error opening manifest %s: %v", manifest, err)
}
manifestFiles = append(manifestFiles, manifestFile)
}
defer func(manifestFiles []io.ReadCloser) {
for _, manifestFile := range manifestFiles {
manifestFile.Close()
}
}(manifestFiles)
extractedMinSdk, err := extractMinSdk(manifestFiles, minSdk)
if err != nil {
log.Fatalf("error extracting min sdk from manifests: %v", err)
}
outFile, err := os.Create(out)
if err != nil {
log.Fatalf("error opening output manifest: %v", err)
}
defer outFile.Close()
if err := writeManifest(outFile, javaPackage, extractedMinSdk); err != nil {
log.Fatalf("error writing output manifest: %v", err)
}
}
// The min sdk is selected by taking the max value found
// between the manifests and the minsdk flag
func extractMinSdk(manifests []io.ReadCloser, defaultSdk int) (int, error) {
// Extracting minSdk values in goroutines
results := make(chan result, len(manifests))
var wg sync.WaitGroup
wg.Add(len(manifests))
for _, manifestFile := range manifests {
go func(manifestFile io.Reader) {
res := extractMinSdkFromManifest(manifestFile)
results <- res
wg.Done()
}(manifestFile)
}
wg.Wait()
close(results)
// Finding max value from channel
minSdk := defaultSdk
for result := range results {
if result.err != nil {
return 0, result.err
}
minSdk = max(minSdk, result.minSdk)
}
return minSdk, nil
}
func extractMinSdkFromManifest(reader io.Reader) result {
manifestBytes, err := ioutil.ReadAll(reader)
if err != nil {
return result{minSdk: 0, err: err}
}
usesSdk := usesSdkTag{MinSdk: ""}
manifest := manifestTag{UsesSdk: usesSdk}
if err := xml.Unmarshal(manifestBytes, &manifest); err != nil {
return result{minSdk: 0, err: err}
}
// MinSdk value could be a placeholder, we ignore it if that's the case
value, err := strconv.Atoi(manifest.UsesSdk.MinSdk)
if err != nil {
return result{minSdk: 0, err: nil}
}
return result{minSdk: value, err: nil}
}
func writeManifest(outManifest io.Writer, javaPackage string, minSdk int) error {
manifestWriter := bufio.NewWriter(outManifest)
manifestWriter.WriteString(fmt.Sprintf(manifestContent, javaPackage, minSdk))
return manifestWriter.Flush()
}
func max(a, b int) int {
if a < b {
return b
}
return a
}