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