// Copyright 2018 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 manifestutils provides common methods to interact with and modify AndroidManifest.xml files. package manifestutils import ( "encoding/xml" "io" "log" "strings" "src/common/golang/xml2" ) // Constant attribute names used in an AndroidManifest. const ( NameSpace = "http://schemas.android.com/apk/res/android" ElemManifest = "manifest" AttrPackage = "package" AttrSplit = "split" AttrFeatureName = "featureName" AttrSharedUserID = "sharedUserId" AttrSharedUserLabel = "sharedUserLabel" AttrVersionCode = "versionCode" AttrVersionName = "versionName" ) var ( // NoNSAttrs contains attributes that are not namespaced. NoNSAttrs = map[string]bool{ AttrPackage: true, AttrSplit: true, AttrFeatureName: true} ) // Manifest is the XML root that we want to parse. type Manifest struct { XMLName xml.Name `xml:"manifest"` Package string `xml:"package,attr"` SharedUserID string `xml:"sharedUserId,attr"` SharedUserLabel string `xml:"sharedUserLabel,attr"` VersionCode string `xml:"versionCode,attr"` VersionName string `xml:"versionName,attr"` Application Application `xml:"application"` } // Application is the XML tag that we want to parse. type Application struct { XMLName xml.Name `xml:"application"` Name string `xml:"http://schemas.android.com/apk/res/android name,attr"` } // Encoder takes the xml.Token and encodes it, interface allows us to use xml2.Encoder. type Encoder interface { EncodeToken(xml.Token) error } // Patch updates an AndroidManifest by patching the attributes of existing elements. // // Attributes that are already defined on the element are updated, while missing // attributes are added to the element's attributes. Elements in patchElems that are // missing from the manifest are ignored. func Patch(dec *xml.Decoder, enc Encoder, patchElems map[string]map[string]xml.Attr) error { for { t, err := dec.Token() if err != nil { if err == io.EOF { break } return err } switch tt := t.(type) { case xml.StartElement: elem := tt.Name.Local if attrs, ok := patchElems[elem]; ok { found := make(map[string]bool) for i, a := range tt.Attr { if attr, ok := attrs[a.Name.Local]; a.Name.Space == attr.Name.Space && ok { found[a.Name.Local] = true tt.Attr[i] = attr } } for _, attr := range attrs { if found[attr.Name.Local] { continue } tt.Attr = append(tt.Attr, attr) } } enc.EncodeToken(tt) default: enc.EncodeToken(tt) } } return nil } // WriteManifest writes an AndroidManifest with updates to patched elements. func WriteManifest(dst io.Writer, src io.Reader, patchElems map[string]map[string]xml.Attr) error { e := xml2.NewEncoder(dst) if err := Patch(xml.NewDecoder(src), e, patchElems); err != nil { return err } return e.Flush() } // CreatePatchElements creates an element map from a string array of "element:attr:attr_value" entries. func CreatePatchElements(attr []string) map[string]map[string]xml.Attr { patchElems := make(map[string]map[string]xml.Attr) for _, a := range attr { pts := strings.Split(a, ":") if len(pts) < 3 { log.Fatalf("Failed to parse attr to replace %s", a) } elem := pts[0] attr := pts[1] ns := NameSpace // https://developer.android.com/guide/topics/manifest/manifest-element if elem == ElemManifest && NoNSAttrs[attr] { ns = "" } if ais, ok := patchElems[elem]; ok { ais[attr] = xml.Attr{ Name: xml.Name{Space: ns, Local: attr}, Value: pts[2]} } else { patchElems[elem] = map[string]xml.Attr{ attr: xml.Attr{ Name: xml.Name{Space: ns, Local: attr}, Value: pts[2]}} } } return patchElems }