xref: /aosp_15_r20/external/bazelbuild-rules_android/src/tools/ak/manifestutils.go (revision 9e965d6fece27a77de5377433c2f7e6999b8cc0b)
1// Copyright 2018 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 manifestutils provides common methods to interact with and modify AndroidManifest.xml files.
16package manifestutils
17
18import (
19	"encoding/xml"
20	"io"
21	"log"
22	"strings"
23
24	"src/common/golang/xml2"
25)
26
27// Constant attribute names used in an AndroidManifest.
28const (
29	NameSpace           = "http://schemas.android.com/apk/res/android"
30	ElemManifest        = "manifest"
31	AttrPackage         = "package"
32	AttrSplit           = "split"
33	AttrFeatureName     = "featureName"
34	AttrSharedUserID    = "sharedUserId"
35	AttrSharedUserLabel = "sharedUserLabel"
36	AttrVersionCode     = "versionCode"
37	AttrVersionName     = "versionName"
38)
39
40var (
41	// NoNSAttrs contains attributes that are not namespaced.
42	NoNSAttrs = map[string]bool{
43		AttrPackage:     true,
44		AttrSplit:       true,
45		AttrFeatureName: true}
46)
47
48// Manifest is the XML root that we want to parse.
49type Manifest struct {
50	XMLName         xml.Name    `xml:"manifest"`
51	Package         string      `xml:"package,attr"`
52	SharedUserID    string      `xml:"sharedUserId,attr"`
53	SharedUserLabel string      `xml:"sharedUserLabel,attr"`
54	VersionCode     string      `xml:"versionCode,attr"`
55	VersionName     string      `xml:"versionName,attr"`
56	Application     Application `xml:"application"`
57}
58
59// Application is the XML tag that we want to parse.
60type Application struct {
61	XMLName xml.Name `xml:"application"`
62	Name    string   `xml:"http://schemas.android.com/apk/res/android name,attr"`
63}
64
65// Encoder takes the xml.Token and encodes it, interface allows us to use xml2.Encoder.
66type Encoder interface {
67	EncodeToken(xml.Token) error
68}
69
70// Patch updates an AndroidManifest by patching the attributes of existing elements.
71//
72// Attributes that are already defined on the element are updated, while missing
73// attributes are added to the element's attributes. Elements in patchElems that are
74// missing from the manifest are ignored.
75func Patch(dec *xml.Decoder, enc Encoder, patchElems map[string]map[string]xml.Attr) error {
76	for {
77		t, err := dec.Token()
78		if err != nil {
79			if err == io.EOF {
80				break
81			}
82			return err
83		}
84		switch tt := t.(type) {
85		case xml.StartElement:
86			elem := tt.Name.Local
87			if attrs, ok := patchElems[elem]; ok {
88				found := make(map[string]bool)
89				for i, a := range tt.Attr {
90					if attr, ok := attrs[a.Name.Local]; a.Name.Space == attr.Name.Space && ok {
91						found[a.Name.Local] = true
92						tt.Attr[i] = attr
93					}
94				}
95				for _, attr := range attrs {
96					if found[attr.Name.Local] {
97						continue
98					}
99
100					tt.Attr = append(tt.Attr, attr)
101				}
102			}
103			enc.EncodeToken(tt)
104		default:
105			enc.EncodeToken(tt)
106		}
107	}
108	return nil
109}
110
111// WriteManifest writes an AndroidManifest with updates to patched elements.
112func WriteManifest(dst io.Writer, src io.Reader, patchElems map[string]map[string]xml.Attr) error {
113	e := xml2.NewEncoder(dst)
114	if err := Patch(xml.NewDecoder(src), e, patchElems); err != nil {
115		return err
116	}
117	return e.Flush()
118}
119
120// CreatePatchElements creates an element map from a string array of "element:attr:attr_value" entries.
121func CreatePatchElements(attr []string) map[string]map[string]xml.Attr {
122	patchElems := make(map[string]map[string]xml.Attr)
123	for _, a := range attr {
124		pts := strings.Split(a, ":")
125		if len(pts) < 3 {
126			log.Fatalf("Failed to parse attr to replace %s", a)
127		}
128
129		elem := pts[0]
130		attr := pts[1]
131		ns := NameSpace
132
133		// https://developer.android.com/guide/topics/manifest/manifest-element
134		if elem == ElemManifest && NoNSAttrs[attr] {
135			ns = ""
136		}
137
138		if ais, ok := patchElems[elem]; ok {
139			ais[attr] = xml.Attr{
140				Name: xml.Name{Space: ns, Local: attr}, Value: pts[2]}
141		} else {
142			patchElems[elem] = map[string]xml.Attr{
143				attr: xml.Attr{
144					Name: xml.Name{Space: ns, Local: attr}, Value: pts[2]}}
145		}
146	}
147	return patchElems
148}
149