1*9e965d6fSRomain Jobredeaux// Copyright 2018 The Bazel Authors. All rights reserved. 2*9e965d6fSRomain Jobredeaux// 3*9e965d6fSRomain Jobredeaux// Licensed under the Apache License, Version 2.0 (the "License"); 4*9e965d6fSRomain Jobredeaux// you may not use this file except in compliance with the License. 5*9e965d6fSRomain Jobredeaux// You may obtain a copy of the License at 6*9e965d6fSRomain Jobredeaux// 7*9e965d6fSRomain Jobredeaux// http://www.apache.org/licenses/LICENSE-2.0 8*9e965d6fSRomain Jobredeaux// 9*9e965d6fSRomain Jobredeaux// Unless required by applicable law or agreed to in writing, software 10*9e965d6fSRomain Jobredeaux// distributed under the License is distributed on an "AS IS" BASIS, 11*9e965d6fSRomain Jobredeaux// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*9e965d6fSRomain Jobredeaux// See the License for the specific language governing permissions and 13*9e965d6fSRomain Jobredeaux// limitations under the License. 14*9e965d6fSRomain Jobredeaux 15*9e965d6fSRomain Jobredeaux// Package manifestutils provides common methods to interact with and modify AndroidManifest.xml files. 16*9e965d6fSRomain Jobredeauxpackage manifestutils 17*9e965d6fSRomain Jobredeaux 18*9e965d6fSRomain Jobredeauximport ( 19*9e965d6fSRomain Jobredeaux "encoding/xml" 20*9e965d6fSRomain Jobredeaux "io" 21*9e965d6fSRomain Jobredeaux "log" 22*9e965d6fSRomain Jobredeaux "strings" 23*9e965d6fSRomain Jobredeaux 24*9e965d6fSRomain Jobredeaux "src/common/golang/xml2" 25*9e965d6fSRomain Jobredeaux) 26*9e965d6fSRomain Jobredeaux 27*9e965d6fSRomain Jobredeaux// Constant attribute names used in an AndroidManifest. 28*9e965d6fSRomain Jobredeauxconst ( 29*9e965d6fSRomain Jobredeaux NameSpace = "http://schemas.android.com/apk/res/android" 30*9e965d6fSRomain Jobredeaux ElemManifest = "manifest" 31*9e965d6fSRomain Jobredeaux AttrPackage = "package" 32*9e965d6fSRomain Jobredeaux AttrSplit = "split" 33*9e965d6fSRomain Jobredeaux AttrFeatureName = "featureName" 34*9e965d6fSRomain Jobredeaux AttrSharedUserID = "sharedUserId" 35*9e965d6fSRomain Jobredeaux AttrSharedUserLabel = "sharedUserLabel" 36*9e965d6fSRomain Jobredeaux AttrVersionCode = "versionCode" 37*9e965d6fSRomain Jobredeaux AttrVersionName = "versionName" 38*9e965d6fSRomain Jobredeaux) 39*9e965d6fSRomain Jobredeaux 40*9e965d6fSRomain Jobredeauxvar ( 41*9e965d6fSRomain Jobredeaux // NoNSAttrs contains attributes that are not namespaced. 42*9e965d6fSRomain Jobredeaux NoNSAttrs = map[string]bool{ 43*9e965d6fSRomain Jobredeaux AttrPackage: true, 44*9e965d6fSRomain Jobredeaux AttrSplit: true, 45*9e965d6fSRomain Jobredeaux AttrFeatureName: true} 46*9e965d6fSRomain Jobredeaux) 47*9e965d6fSRomain Jobredeaux 48*9e965d6fSRomain Jobredeaux// Manifest is the XML root that we want to parse. 49*9e965d6fSRomain Jobredeauxtype Manifest struct { 50*9e965d6fSRomain Jobredeaux XMLName xml.Name `xml:"manifest"` 51*9e965d6fSRomain Jobredeaux Package string `xml:"package,attr"` 52*9e965d6fSRomain Jobredeaux SharedUserID string `xml:"sharedUserId,attr"` 53*9e965d6fSRomain Jobredeaux SharedUserLabel string `xml:"sharedUserLabel,attr"` 54*9e965d6fSRomain Jobredeaux VersionCode string `xml:"versionCode,attr"` 55*9e965d6fSRomain Jobredeaux VersionName string `xml:"versionName,attr"` 56*9e965d6fSRomain Jobredeaux Application Application `xml:"application"` 57*9e965d6fSRomain Jobredeaux} 58*9e965d6fSRomain Jobredeaux 59*9e965d6fSRomain Jobredeaux// Application is the XML tag that we want to parse. 60*9e965d6fSRomain Jobredeauxtype Application struct { 61*9e965d6fSRomain Jobredeaux XMLName xml.Name `xml:"application"` 62*9e965d6fSRomain Jobredeaux Name string `xml:"http://schemas.android.com/apk/res/android name,attr"` 63*9e965d6fSRomain Jobredeaux} 64*9e965d6fSRomain Jobredeaux 65*9e965d6fSRomain Jobredeaux// Encoder takes the xml.Token and encodes it, interface allows us to use xml2.Encoder. 66*9e965d6fSRomain Jobredeauxtype Encoder interface { 67*9e965d6fSRomain Jobredeaux EncodeToken(xml.Token) error 68*9e965d6fSRomain Jobredeaux} 69*9e965d6fSRomain Jobredeaux 70*9e965d6fSRomain Jobredeaux// Patch updates an AndroidManifest by patching the attributes of existing elements. 71*9e965d6fSRomain Jobredeaux// 72*9e965d6fSRomain Jobredeaux// Attributes that are already defined on the element are updated, while missing 73*9e965d6fSRomain Jobredeaux// attributes are added to the element's attributes. Elements in patchElems that are 74*9e965d6fSRomain Jobredeaux// missing from the manifest are ignored. 75*9e965d6fSRomain Jobredeauxfunc Patch(dec *xml.Decoder, enc Encoder, patchElems map[string]map[string]xml.Attr) error { 76*9e965d6fSRomain Jobredeaux for { 77*9e965d6fSRomain Jobredeaux t, err := dec.Token() 78*9e965d6fSRomain Jobredeaux if err != nil { 79*9e965d6fSRomain Jobredeaux if err == io.EOF { 80*9e965d6fSRomain Jobredeaux break 81*9e965d6fSRomain Jobredeaux } 82*9e965d6fSRomain Jobredeaux return err 83*9e965d6fSRomain Jobredeaux } 84*9e965d6fSRomain Jobredeaux switch tt := t.(type) { 85*9e965d6fSRomain Jobredeaux case xml.StartElement: 86*9e965d6fSRomain Jobredeaux elem := tt.Name.Local 87*9e965d6fSRomain Jobredeaux if attrs, ok := patchElems[elem]; ok { 88*9e965d6fSRomain Jobredeaux found := make(map[string]bool) 89*9e965d6fSRomain Jobredeaux for i, a := range tt.Attr { 90*9e965d6fSRomain Jobredeaux if attr, ok := attrs[a.Name.Local]; a.Name.Space == attr.Name.Space && ok { 91*9e965d6fSRomain Jobredeaux found[a.Name.Local] = true 92*9e965d6fSRomain Jobredeaux tt.Attr[i] = attr 93*9e965d6fSRomain Jobredeaux } 94*9e965d6fSRomain Jobredeaux } 95*9e965d6fSRomain Jobredeaux for _, attr := range attrs { 96*9e965d6fSRomain Jobredeaux if found[attr.Name.Local] { 97*9e965d6fSRomain Jobredeaux continue 98*9e965d6fSRomain Jobredeaux } 99*9e965d6fSRomain Jobredeaux 100*9e965d6fSRomain Jobredeaux tt.Attr = append(tt.Attr, attr) 101*9e965d6fSRomain Jobredeaux } 102*9e965d6fSRomain Jobredeaux } 103*9e965d6fSRomain Jobredeaux enc.EncodeToken(tt) 104*9e965d6fSRomain Jobredeaux default: 105*9e965d6fSRomain Jobredeaux enc.EncodeToken(tt) 106*9e965d6fSRomain Jobredeaux } 107*9e965d6fSRomain Jobredeaux } 108*9e965d6fSRomain Jobredeaux return nil 109*9e965d6fSRomain Jobredeaux} 110*9e965d6fSRomain Jobredeaux 111*9e965d6fSRomain Jobredeaux// WriteManifest writes an AndroidManifest with updates to patched elements. 112*9e965d6fSRomain Jobredeauxfunc WriteManifest(dst io.Writer, src io.Reader, patchElems map[string]map[string]xml.Attr) error { 113*9e965d6fSRomain Jobredeaux e := xml2.NewEncoder(dst) 114*9e965d6fSRomain Jobredeaux if err := Patch(xml.NewDecoder(src), e, patchElems); err != nil { 115*9e965d6fSRomain Jobredeaux return err 116*9e965d6fSRomain Jobredeaux } 117*9e965d6fSRomain Jobredeaux return e.Flush() 118*9e965d6fSRomain Jobredeaux} 119*9e965d6fSRomain Jobredeaux 120*9e965d6fSRomain Jobredeaux// CreatePatchElements creates an element map from a string array of "element:attr:attr_value" entries. 121*9e965d6fSRomain Jobredeauxfunc CreatePatchElements(attr []string) map[string]map[string]xml.Attr { 122*9e965d6fSRomain Jobredeaux patchElems := make(map[string]map[string]xml.Attr) 123*9e965d6fSRomain Jobredeaux for _, a := range attr { 124*9e965d6fSRomain Jobredeaux pts := strings.Split(a, ":") 125*9e965d6fSRomain Jobredeaux if len(pts) < 3 { 126*9e965d6fSRomain Jobredeaux log.Fatalf("Failed to parse attr to replace %s", a) 127*9e965d6fSRomain Jobredeaux } 128*9e965d6fSRomain Jobredeaux 129*9e965d6fSRomain Jobredeaux elem := pts[0] 130*9e965d6fSRomain Jobredeaux attr := pts[1] 131*9e965d6fSRomain Jobredeaux ns := NameSpace 132*9e965d6fSRomain Jobredeaux 133*9e965d6fSRomain Jobredeaux // https://developer.android.com/guide/topics/manifest/manifest-element 134*9e965d6fSRomain Jobredeaux if elem == ElemManifest && NoNSAttrs[attr] { 135*9e965d6fSRomain Jobredeaux ns = "" 136*9e965d6fSRomain Jobredeaux } 137*9e965d6fSRomain Jobredeaux 138*9e965d6fSRomain Jobredeaux if ais, ok := patchElems[elem]; ok { 139*9e965d6fSRomain Jobredeaux ais[attr] = xml.Attr{ 140*9e965d6fSRomain Jobredeaux Name: xml.Name{Space: ns, Local: attr}, Value: pts[2]} 141*9e965d6fSRomain Jobredeaux } else { 142*9e965d6fSRomain Jobredeaux patchElems[elem] = map[string]xml.Attr{ 143*9e965d6fSRomain Jobredeaux attr: xml.Attr{ 144*9e965d6fSRomain Jobredeaux Name: xml.Name{Space: ns, Local: attr}, Value: pts[2]}} 145*9e965d6fSRomain Jobredeaux } 146*9e965d6fSRomain Jobredeaux } 147*9e965d6fSRomain Jobredeaux return patchElems 148*9e965d6fSRomain Jobredeaux} 149