xref: /aosp_15_r20/external/bazelbuild-rules_android/src/tools/ak/generatemanifest/generatemanifest.go (revision 9e965d6fece27a77de5377433c2f7e6999b8cc0b)
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