Adding upstream version 0.0~git20250520.a1d9079+dfsg.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
590ac7ff5f
commit
20149b7f3a
456 changed files with 70406 additions and 0 deletions
9
internal/binres/arsc.go
Normal file
9
internal/binres/arsc.go
Normal file
File diff suppressed because one or more lines are too long
963
internal/binres/binres.go
Normal file
963
internal/binres/binres.go
Normal file
|
@ -0,0 +1,963 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:generate go run genarsc.go
|
||||
//go:generate stringer -output binres_string.go -type ResType,DataType
|
||||
|
||||
// Package binres implements encoding and decoding of android binary resources.
|
||||
//
|
||||
// Binary resource structs support unmarshalling the binary output of aapt.
|
||||
// Implementations of marshalling for each struct must produce the exact input
|
||||
// sent to unmarshalling. This allows tests to validate each struct representation
|
||||
// of the binary format as follows:
|
||||
//
|
||||
// - unmarshal the output of aapt
|
||||
// - marshal the struct representation
|
||||
// - perform byte-to-byte comparison with aapt output per chunk header and body
|
||||
//
|
||||
// This process should strive to make structs idiomatic to make parsing xml text
|
||||
// into structs trivial.
|
||||
//
|
||||
// Once the struct representation is validated, tests for parsing xml text
|
||||
// into structs can become self-referential as the following holds true:
|
||||
//
|
||||
// - the unmarshalled input of aapt output is the only valid target
|
||||
// - the unmarshalled input of xml text may be compared to the unmarshalled
|
||||
// input of aapt output to identify errors, e.g. text-trims, wrong flags, etc
|
||||
//
|
||||
// This provides validation, byte-for-byte, for producing binary xml resources.
|
||||
//
|
||||
// It should be made clear that unmarshalling binary resources is currently only
|
||||
// in scope for proving that the BinaryMarshaler works correctly. Any other use
|
||||
// is currently out of scope.
|
||||
//
|
||||
// A simple view of binary xml document structure:
|
||||
//
|
||||
// XML
|
||||
// Pool
|
||||
// Map
|
||||
// Namespace
|
||||
// [...node]
|
||||
//
|
||||
// Additional resources:
|
||||
// https://android.googlesource.com/platform/frameworks/base/+/master/libs/androidfw/include/androidfw/ResourceTypes.h
|
||||
// https://justanapplication.wordpress.com/2011/09/13/ (a series of articles, increment date)
|
||||
package binres
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"encoding/binary"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
func errWrongType(have ResType, want ...ResType) error {
|
||||
return fmt.Errorf("wrong resource type %s, want one of %v", have, want)
|
||||
}
|
||||
|
||||
type ResType uint16
|
||||
|
||||
func (t ResType) IsSupported() bool {
|
||||
return t != ResNull
|
||||
}
|
||||
|
||||
// explicitly defined for clarity and resolvability with apt source
|
||||
const (
|
||||
ResNull ResType = 0x0000
|
||||
ResStringPool ResType = 0x0001
|
||||
ResTable ResType = 0x0002
|
||||
ResXML ResType = 0x0003
|
||||
|
||||
ResXMLStartNamespace ResType = 0x0100
|
||||
ResXMLEndNamespace ResType = 0x0101
|
||||
ResXMLStartElement ResType = 0x0102
|
||||
ResXMLEndElement ResType = 0x0103
|
||||
ResXMLCharData ResType = 0x0104
|
||||
|
||||
ResXMLResourceMap ResType = 0x0180
|
||||
|
||||
ResTablePackage ResType = 0x0200
|
||||
ResTableType ResType = 0x0201
|
||||
ResTableTypeSpec ResType = 0x0202
|
||||
ResTableLibrary ResType = 0x0203
|
||||
ResTableOverlayable ResType = 0x0204
|
||||
ResTableOverlayablePolicy ResType = 0x0205
|
||||
ResTableStagedAlias ResType = 0x0206
|
||||
)
|
||||
|
||||
var (
|
||||
btou16 = binary.LittleEndian.Uint16
|
||||
btou32 = binary.LittleEndian.Uint32
|
||||
putu16 = binary.LittleEndian.PutUint16
|
||||
putu32 = binary.LittleEndian.PutUint32
|
||||
)
|
||||
|
||||
// unmarshaler wraps BinaryUnmarshaler to provide byte size of decoded chunks.
|
||||
type unmarshaler interface {
|
||||
encoding.BinaryUnmarshaler
|
||||
|
||||
// size returns the byte size unmarshalled after a call to
|
||||
// UnmarshalBinary, or otherwise zero.
|
||||
size() int
|
||||
}
|
||||
|
||||
// chunkHeader appears at the front of every data chunk in a resource.
|
||||
type chunkHeader struct {
|
||||
// Type of data that follows this header.
|
||||
typ ResType
|
||||
|
||||
// Advance slice index by this value to find its associated data, if any.
|
||||
headerByteSize uint16
|
||||
|
||||
// This is the header size plus the size of any data associated with the chunk.
|
||||
// Advance slice index by this value to completely skip its contents, including
|
||||
// any child chunks. If this value is the same as headerByteSize, there is
|
||||
// no data associated with the chunk.
|
||||
byteSize uint32
|
||||
}
|
||||
|
||||
// size implements unmarshaler.
|
||||
func (hdr chunkHeader) size() int { return int(hdr.byteSize) }
|
||||
|
||||
func (hdr *chunkHeader) UnmarshalBinary(bin []byte) error {
|
||||
hdr.typ = ResType(btou16(bin))
|
||||
if !hdr.typ.IsSupported() {
|
||||
return fmt.Errorf("%s not supported", hdr.typ)
|
||||
}
|
||||
hdr.headerByteSize = btou16(bin[2:])
|
||||
hdr.byteSize = btou32(bin[4:])
|
||||
if len(bin) < int(hdr.byteSize) {
|
||||
return fmt.Errorf("too few bytes to unmarshal chunk body, have %v, need at-least %v", len(bin), hdr.byteSize)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hdr chunkHeader) MarshalBinary() ([]byte, error) {
|
||||
if !hdr.typ.IsSupported() {
|
||||
return nil, fmt.Errorf("%s not supported", hdr.typ)
|
||||
}
|
||||
|
||||
bin := make([]byte, 8)
|
||||
putu16(bin, uint16(hdr.typ))
|
||||
putu16(bin[2:], hdr.headerByteSize)
|
||||
putu32(bin[4:], hdr.byteSize)
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
type XML struct {
|
||||
chunkHeader
|
||||
|
||||
Pool *Pool
|
||||
Map *Map
|
||||
|
||||
Namespace *Namespace
|
||||
Children []*Element
|
||||
|
||||
// tmp field used when unmarshalling binary
|
||||
stack []*Element
|
||||
}
|
||||
|
||||
// RawValueByName returns the original raw string value of first matching element attribute, or error if not exists.
|
||||
// Given <manifest package="VAL" ...> then RawValueByName("manifest", xml.Name{Local: "package"}) returns "VAL".
|
||||
func (bx *XML) RawValueByName(elname string, attrname xml.Name) (string, error) {
|
||||
elref, err := bx.Pool.RefByName(elname)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
nref, err := bx.Pool.RefByName(attrname.Local)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
nsref := PoolRef(NoEntry)
|
||||
if attrname.Space != "" {
|
||||
nsref, err = bx.Pool.RefByName(attrname.Space)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
for el := range bx.iterElements() {
|
||||
if el.Name == elref {
|
||||
for _, attr := range el.attrs {
|
||||
// TODO enforce TypedValue DataString constraint?
|
||||
if nsref == attr.NS && nref == attr.Name {
|
||||
return bx.Pool.strings[int(attr.RawValue)], nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("no matching element %q for attribute %+v found", elname, attrname)
|
||||
}
|
||||
|
||||
const (
|
||||
androidSchema = "http://schemas.android.com/apk/res/android"
|
||||
toolsSchema = "http://schemas.android.com/tools"
|
||||
)
|
||||
|
||||
// skipSynthesize is set true for tests to avoid synthesis of additional nodes and attributes.
|
||||
var skipSynthesize bool
|
||||
|
||||
// UnmarshalXML decodes an AndroidManifest.xml document returning type XML
|
||||
// containing decoded resources.
|
||||
func UnmarshalXML(r io.Reader, withIcon bool) (*XML, error) {
|
||||
tbl, err := OpenTable()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lr := &lineReader{r: r}
|
||||
dec := xml.NewDecoder(lr)
|
||||
bx := new(XML)
|
||||
|
||||
// temporary pool to resolve real poolref later
|
||||
pool := new(Pool)
|
||||
|
||||
type ltoken struct {
|
||||
xml.Token
|
||||
line int
|
||||
}
|
||||
var q []ltoken
|
||||
|
||||
for {
|
||||
line := lr.line(dec.InputOffset())
|
||||
tkn, err := dec.Token()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
tkn = xml.CopyToken(tkn)
|
||||
|
||||
switch tkn := tkn.(type) {
|
||||
case xml.StartElement:
|
||||
switch tkn.Name.Local {
|
||||
default:
|
||||
q = append(q, ltoken{tkn, line})
|
||||
case "uses-sdk":
|
||||
return nil, fmt.Errorf("manual declaration of uses-sdk in AndroidManifest.xml not supported")
|
||||
case "manifest":
|
||||
// synthesize additional attributes and nodes for use during encode.
|
||||
tkn.Attr = append(tkn.Attr,
|
||||
xml.Attr{
|
||||
Name: xml.Name{
|
||||
Space: "",
|
||||
Local: "platformBuildVersionCode",
|
||||
},
|
||||
Value: "16",
|
||||
},
|
||||
xml.Attr{
|
||||
Name: xml.Name{
|
||||
Space: "",
|
||||
Local: "platformBuildVersionName",
|
||||
},
|
||||
Value: "4.1.2-1425332",
|
||||
})
|
||||
|
||||
q = append(q, ltoken{tkn, line})
|
||||
|
||||
if !skipSynthesize {
|
||||
s := xml.StartElement{
|
||||
Name: xml.Name{
|
||||
Space: "",
|
||||
Local: "uses-sdk",
|
||||
},
|
||||
Attr: []xml.Attr{
|
||||
xml.Attr{
|
||||
Name: xml.Name{
|
||||
Space: androidSchema,
|
||||
Local: "minSdkVersion",
|
||||
},
|
||||
Value: fmt.Sprintf("%v", MinSDK),
|
||||
},
|
||||
},
|
||||
}
|
||||
e := xml.EndElement{Name: xml.Name{Local: "uses-sdk"}}
|
||||
|
||||
q = append(q, ltoken{s, line}, ltoken{e, line})
|
||||
}
|
||||
case "application":
|
||||
if !skipSynthesize {
|
||||
for _, attr := range tkn.Attr {
|
||||
if attr.Name.Space == androidSchema && attr.Name.Local == "icon" {
|
||||
return nil, fmt.Errorf("manual declaration of android:icon in AndroidManifest.xml not supported")
|
||||
}
|
||||
}
|
||||
if withIcon {
|
||||
tkn.Attr = append(tkn.Attr,
|
||||
xml.Attr{
|
||||
Name: xml.Name{
|
||||
Space: androidSchema,
|
||||
Local: "icon",
|
||||
},
|
||||
Value: "@mipmap/icon",
|
||||
})
|
||||
}
|
||||
}
|
||||
q = append(q, ltoken{tkn, line})
|
||||
}
|
||||
default:
|
||||
q = append(q, ltoken{tkn, line})
|
||||
}
|
||||
}
|
||||
|
||||
for _, ltkn := range q {
|
||||
tkn, line := ltkn.Token, ltkn.line
|
||||
|
||||
switch tkn := tkn.(type) {
|
||||
case xml.StartElement:
|
||||
el := &Element{
|
||||
NodeHeader: NodeHeader{
|
||||
LineNumber: uint32(line),
|
||||
Comment: 0xFFFFFFFF,
|
||||
},
|
||||
NS: NoEntry,
|
||||
Name: pool.ref(tkn.Name.Local),
|
||||
}
|
||||
if len(bx.stack) == 0 {
|
||||
bx.Children = append(bx.Children, el)
|
||||
} else {
|
||||
n := len(bx.stack)
|
||||
var p *Element
|
||||
p, bx.stack = bx.stack[n-1], bx.stack[:n-1]
|
||||
p.Children = append(p.Children, el)
|
||||
bx.stack = append(bx.stack, p)
|
||||
}
|
||||
bx.stack = append(bx.stack, el)
|
||||
|
||||
for _, attr := range tkn.Attr {
|
||||
if (attr.Name.Space == "xmlns" && attr.Name.Local == "tools") || attr.Name.Space == toolsSchema {
|
||||
continue // TODO can tbl be queried for schemas to determine validity instead?
|
||||
}
|
||||
|
||||
if attr.Name.Space == "xmlns" && attr.Name.Local == "android" {
|
||||
if bx.Namespace != nil {
|
||||
return nil, fmt.Errorf("multiple declarations of xmlns:android encountered")
|
||||
}
|
||||
bx.Namespace = &Namespace{
|
||||
NodeHeader: NodeHeader{
|
||||
LineNumber: uint32(line),
|
||||
Comment: NoEntry,
|
||||
},
|
||||
prefix: 0,
|
||||
uri: 0,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
nattr := &Attribute{
|
||||
NS: pool.ref(attr.Name.Space),
|
||||
Name: pool.ref(attr.Name.Local),
|
||||
RawValue: NoEntry,
|
||||
}
|
||||
el.attrs = append(el.attrs, nattr)
|
||||
|
||||
if attr.Name.Space == "" {
|
||||
nattr.NS = NoEntry
|
||||
// TODO it's unclear how to query these
|
||||
switch attr.Name.Local {
|
||||
case "platformBuildVersionCode":
|
||||
nattr.TypedValue.Type = DataIntDec
|
||||
i, err := strconv.Atoi(attr.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nattr.TypedValue.Value = uint32(i)
|
||||
default: // "package", "platformBuildVersionName", and any invalid
|
||||
nattr.RawValue = pool.ref(attr.Value)
|
||||
nattr.TypedValue.Type = DataString
|
||||
}
|
||||
} else {
|
||||
// get type spec and value data type
|
||||
ref, err := tbl.RefByName("attr/" + attr.Name.Local)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nt, err := ref.Resolve(tbl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(nt.values) == 0 {
|
||||
panic("encountered empty values slice")
|
||||
}
|
||||
|
||||
if len(nt.values) == 1 {
|
||||
val := nt.values[0]
|
||||
if val.data.Type != DataIntDec {
|
||||
panic("TODO only know how to handle DataIntDec type here")
|
||||
}
|
||||
|
||||
t := DataType(val.data.Value)
|
||||
switch t {
|
||||
case DataString, DataAttribute, DataType(0x3e):
|
||||
// TODO identify 0x3e, in bootstrap.xml this is the native lib name
|
||||
nattr.RawValue = pool.ref(attr.Value)
|
||||
nattr.TypedValue.Type = DataString
|
||||
nattr.TypedValue.Value = uint32(nattr.RawValue)
|
||||
case DataIntBool, DataType(0x08):
|
||||
nattr.TypedValue.Type = DataIntBool
|
||||
switch attr.Value {
|
||||
case "true":
|
||||
nattr.TypedValue.Value = 0xFFFFFFFF
|
||||
case "false":
|
||||
nattr.TypedValue.Value = 0
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid bool value %q", attr.Value)
|
||||
}
|
||||
case DataIntDec, DataFloat, DataFraction:
|
||||
// TODO DataFraction needs it's own case statement. minSdkVersion identifies as DataFraction
|
||||
// but has accepted input in the past such as android:minSdkVersion="L"
|
||||
// Other use-cases for DataFraction are currently unknown as applicable to manifest generation
|
||||
// but this provides minimum support for writing out minSdkVersion="15" correctly.
|
||||
nattr.TypedValue.Type = DataIntDec
|
||||
i, err := strconv.Atoi(attr.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nattr.TypedValue.Value = uint32(i)
|
||||
case DataReference:
|
||||
nattr.TypedValue.Type = DataReference
|
||||
dref, err := tbl.RefByName(attr.Value)
|
||||
if err != nil {
|
||||
if strings.HasPrefix(attr.Value, "@mipmap") {
|
||||
// firstDrawableId is a TableRef matching first entry of mipmap spec initialized by NewMipmapTable.
|
||||
// 7f is default package, 02 is mipmap spec, 0000 is first entry; e.g. R.drawable.icon
|
||||
// TODO resource table should generate ids as required.
|
||||
const firstDrawableId = 0x7f020000
|
||||
nattr.TypedValue.Value = firstDrawableId
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
nattr.TypedValue.Value = uint32(dref)
|
||||
default:
|
||||
return nil, fmt.Errorf("unhandled data type %0#2x: %s", uint8(t), t)
|
||||
}
|
||||
} else {
|
||||
// 0x01000000 is an unknown ref that doesn't point to anything, typically
|
||||
// located at the start of entry value lists, peek at last value to determine type.
|
||||
t := nt.values[len(nt.values)-1].data.Type
|
||||
switch t {
|
||||
case DataIntDec:
|
||||
for _, val := range nt.values {
|
||||
if val.name == 0x01000000 {
|
||||
continue
|
||||
}
|
||||
nr, err := val.name.Resolve(tbl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if attr.Value == nr.key.Resolve(tbl.pkgs[0].keyPool) { // TODO hard-coded pkg ref
|
||||
nattr.TypedValue = *val.data
|
||||
break
|
||||
}
|
||||
}
|
||||
case DataIntHex:
|
||||
nattr.TypedValue.Type = t
|
||||
for _, x := range strings.Split(attr.Value, "|") {
|
||||
for _, val := range nt.values {
|
||||
if val.name == 0x01000000 {
|
||||
continue
|
||||
}
|
||||
nr, err := val.name.Resolve(tbl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if x == nr.key.Resolve(tbl.pkgs[0].keyPool) { // TODO hard-coded pkg ref
|
||||
nattr.TypedValue.Value |= val.data.Value
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unhandled data type for configuration %0#2x: %s", uint8(t), t)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case xml.CharData:
|
||||
if s := poolTrim(string(tkn)); s != "" {
|
||||
cdt := &CharData{
|
||||
NodeHeader: NodeHeader{
|
||||
LineNumber: uint32(line),
|
||||
Comment: NoEntry,
|
||||
},
|
||||
RawData: pool.ref(s),
|
||||
}
|
||||
el := bx.stack[len(bx.stack)-1]
|
||||
if el.head == nil {
|
||||
el.head = cdt
|
||||
} else if el.tail == nil {
|
||||
el.tail = cdt
|
||||
} else {
|
||||
return nil, fmt.Errorf("element head and tail already contain chardata")
|
||||
}
|
||||
}
|
||||
case xml.EndElement:
|
||||
if tkn.Name.Local == "manifest" {
|
||||
bx.Namespace.end = &Namespace{
|
||||
NodeHeader: NodeHeader{
|
||||
LineNumber: uint32(line),
|
||||
Comment: NoEntry,
|
||||
},
|
||||
prefix: 0,
|
||||
uri: 0,
|
||||
}
|
||||
}
|
||||
n := len(bx.stack)
|
||||
var el *Element
|
||||
el, bx.stack = bx.stack[n-1], bx.stack[:n-1]
|
||||
if el.end != nil {
|
||||
return nil, fmt.Errorf("element end already exists")
|
||||
}
|
||||
el.end = &ElementEnd{
|
||||
NodeHeader: NodeHeader{
|
||||
LineNumber: uint32(line),
|
||||
Comment: NoEntry,
|
||||
},
|
||||
NS: el.NS,
|
||||
Name: el.Name,
|
||||
}
|
||||
case xml.Comment, xml.ProcInst:
|
||||
// discard
|
||||
default:
|
||||
panic(fmt.Errorf("unhandled token type: %T %+v", tkn, tkn))
|
||||
}
|
||||
}
|
||||
|
||||
// pools appear to be sorted as follows:
|
||||
// * attribute names prefixed with android:
|
||||
// * "android", [schema-url], [empty-string]
|
||||
// * for each node:
|
||||
// * attribute names with no prefix
|
||||
// * node name
|
||||
// * attribute value if data type of name is DataString, DataAttribute, or 0x3e (an unknown)
|
||||
bx.Pool = new(Pool)
|
||||
|
||||
var arecurse func(*Element)
|
||||
arecurse = func(el *Element) {
|
||||
for _, attr := range el.attrs {
|
||||
if attr.NS == NoEntry {
|
||||
continue
|
||||
}
|
||||
if attr.NS.Resolve(pool) == androidSchema {
|
||||
bx.Pool.strings = append(bx.Pool.strings, attr.Name.Resolve(pool))
|
||||
}
|
||||
}
|
||||
for _, child := range el.Children {
|
||||
arecurse(child)
|
||||
}
|
||||
}
|
||||
for _, el := range bx.Children {
|
||||
arecurse(el)
|
||||
}
|
||||
|
||||
// TODO encoding/xml does not enforce namespace prefix and manifest encoding in aapt
|
||||
// appears to ignore all other prefixes. Inserting this manually is not strictly correct
|
||||
// for the general case, but the effort to do otherwise currently offers nothing.
|
||||
bx.Pool.strings = append(bx.Pool.strings, "android", androidSchema)
|
||||
|
||||
// there always appears to be an empty string located after schema, even if one is
|
||||
// not present in manifest.
|
||||
bx.Pool.strings = append(bx.Pool.strings, "")
|
||||
|
||||
var brecurse func(*Element)
|
||||
brecurse = func(el *Element) {
|
||||
for _, attr := range el.attrs {
|
||||
if attr.NS == NoEntry {
|
||||
bx.Pool.strings = append(bx.Pool.strings, attr.Name.Resolve(pool))
|
||||
}
|
||||
}
|
||||
|
||||
bx.Pool.strings = append(bx.Pool.strings, el.Name.Resolve(pool))
|
||||
|
||||
for _, attr := range el.attrs {
|
||||
if attr.RawValue != NoEntry {
|
||||
bx.Pool.strings = append(bx.Pool.strings, attr.RawValue.Resolve(pool))
|
||||
} else if attr.NS == NoEntry {
|
||||
bx.Pool.strings = append(bx.Pool.strings, fmt.Sprintf("%+v", attr.TypedValue.Value))
|
||||
}
|
||||
}
|
||||
|
||||
if el.head != nil {
|
||||
bx.Pool.strings = append(bx.Pool.strings, el.head.RawData.Resolve(pool))
|
||||
}
|
||||
if el.tail != nil {
|
||||
bx.Pool.strings = append(bx.Pool.strings, el.tail.RawData.Resolve(pool))
|
||||
}
|
||||
|
||||
for _, child := range el.Children {
|
||||
brecurse(child)
|
||||
}
|
||||
}
|
||||
for _, el := range bx.Children {
|
||||
brecurse(el)
|
||||
}
|
||||
|
||||
// do not eliminate duplicates until the entire slice has been composed.
|
||||
// consider <activity android:label="label" .../>
|
||||
// all attribute names come first followed by values; in such a case, the value "label"
|
||||
// would be a reference to the same "android:label" in the string pool which will occur
|
||||
// within the beginning of the pool where other attr names are located.
|
||||
bx.Pool.strings = asSet(bx.Pool.strings)
|
||||
|
||||
// TODO consider cases of multiple declarations of the same attr name that should return error
|
||||
// before ever reaching this point.
|
||||
bx.Map = new(Map)
|
||||
for _, s := range bx.Pool.strings {
|
||||
ref, err := tbl.RefByName("attr/" + s)
|
||||
if err != nil {
|
||||
break // break after first non-ref as all strings after are also non-refs.
|
||||
}
|
||||
bx.Map.rs = append(bx.Map.rs, ref)
|
||||
}
|
||||
|
||||
// resolve tmp pool refs to final pool refs
|
||||
// TODO drop this in favor of sort directly on Table
|
||||
var resolve func(el *Element)
|
||||
resolve = func(el *Element) {
|
||||
if el.NS != NoEntry {
|
||||
el.NS = bx.Pool.ref(el.NS.Resolve(pool))
|
||||
el.end.NS = el.NS
|
||||
}
|
||||
el.Name = bx.Pool.ref(el.Name.Resolve(pool))
|
||||
el.end.Name = el.Name
|
||||
for _, attr := range el.attrs {
|
||||
if attr.NS != NoEntry {
|
||||
attr.NS = bx.Pool.ref(attr.NS.Resolve(pool))
|
||||
}
|
||||
attr.Name = bx.Pool.ref(attr.Name.Resolve(pool))
|
||||
if attr.RawValue != NoEntry {
|
||||
attr.RawValue = bx.Pool.ref(attr.RawValue.Resolve(pool))
|
||||
if attr.TypedValue.Type == DataString {
|
||||
attr.TypedValue.Value = uint32(attr.RawValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, child := range el.Children {
|
||||
resolve(child)
|
||||
}
|
||||
}
|
||||
for _, el := range bx.Children {
|
||||
resolve(el)
|
||||
}
|
||||
|
||||
var asort func(*Element)
|
||||
asort = func(el *Element) {
|
||||
sort.Sort(byType(el.attrs))
|
||||
sort.Sort(byNamespace(el.attrs))
|
||||
sort.Sort(byName(el.attrs))
|
||||
for _, child := range el.Children {
|
||||
asort(child)
|
||||
}
|
||||
}
|
||||
for _, el := range bx.Children {
|
||||
asort(el)
|
||||
}
|
||||
|
||||
for i, s := range bx.Pool.strings {
|
||||
switch s {
|
||||
case androidSchema:
|
||||
bx.Namespace.uri = PoolRef(i)
|
||||
bx.Namespace.end.uri = PoolRef(i)
|
||||
case "android":
|
||||
bx.Namespace.prefix = PoolRef(i)
|
||||
bx.Namespace.end.prefix = PoolRef(i)
|
||||
}
|
||||
}
|
||||
|
||||
return bx, nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary decodes all resource chunks in buf returning any error encountered.
|
||||
func (bx *XML) UnmarshalBinary(buf []byte) error {
|
||||
if err := (&bx.chunkHeader).UnmarshalBinary(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
buf = buf[8:]
|
||||
for len(buf) > 0 {
|
||||
k, err := bx.unmarshalBinaryKind(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buf = buf[k.size():]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// unmarshalBinaryKind decodes and stores the first resource chunk of bin.
|
||||
// It returns the unmarshaler interface and any error encountered.
|
||||
// If k.size() < len(bin), subsequent chunks can be decoded at bin[k.size():].
|
||||
func (bx *XML) unmarshalBinaryKind(bin []byte) (k unmarshaler, err error) {
|
||||
k, err = bx.kind(ResType(btou16(bin)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = k.UnmarshalBinary(bin); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return k, nil
|
||||
}
|
||||
|
||||
func (bx *XML) kind(t ResType) (unmarshaler, error) {
|
||||
switch t {
|
||||
case ResStringPool:
|
||||
if bx.Pool != nil {
|
||||
return nil, fmt.Errorf("pool already exists")
|
||||
}
|
||||
bx.Pool = new(Pool)
|
||||
return bx.Pool, nil
|
||||
case ResXMLResourceMap:
|
||||
if bx.Map != nil {
|
||||
return nil, fmt.Errorf("resource map already exists")
|
||||
}
|
||||
bx.Map = new(Map)
|
||||
return bx.Map, nil
|
||||
case ResXMLStartNamespace:
|
||||
if bx.Namespace != nil {
|
||||
return nil, fmt.Errorf("namespace start already exists")
|
||||
}
|
||||
bx.Namespace = new(Namespace)
|
||||
return bx.Namespace, nil
|
||||
case ResXMLEndNamespace:
|
||||
if bx.Namespace.end != nil {
|
||||
return nil, fmt.Errorf("namespace end already exists")
|
||||
}
|
||||
bx.Namespace.end = new(Namespace)
|
||||
return bx.Namespace.end, nil
|
||||
case ResXMLStartElement:
|
||||
el := new(Element)
|
||||
if len(bx.stack) == 0 {
|
||||
bx.Children = append(bx.Children, el)
|
||||
} else {
|
||||
n := len(bx.stack)
|
||||
var p *Element
|
||||
p, bx.stack = bx.stack[n-1], bx.stack[:n-1]
|
||||
p.Children = append(p.Children, el)
|
||||
bx.stack = append(bx.stack, p)
|
||||
}
|
||||
bx.stack = append(bx.stack, el)
|
||||
return el, nil
|
||||
case ResXMLEndElement:
|
||||
n := len(bx.stack)
|
||||
var el *Element
|
||||
el, bx.stack = bx.stack[n-1], bx.stack[:n-1]
|
||||
if el.end != nil {
|
||||
return nil, fmt.Errorf("element end already exists")
|
||||
}
|
||||
el.end = new(ElementEnd)
|
||||
return el.end, nil
|
||||
case ResXMLCharData: // TODO assure correctness
|
||||
cdt := new(CharData)
|
||||
el := bx.stack[len(bx.stack)-1]
|
||||
if el.head == nil {
|
||||
el.head = cdt
|
||||
} else if el.tail == nil {
|
||||
el.tail = cdt
|
||||
} else {
|
||||
return nil, fmt.Errorf("element head and tail already contain chardata")
|
||||
}
|
||||
return cdt, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected type %s", t)
|
||||
}
|
||||
}
|
||||
|
||||
func (bx *XML) MarshalBinary() ([]byte, error) {
|
||||
bx.typ = ResXML
|
||||
bx.headerByteSize = 8
|
||||
|
||||
var (
|
||||
bin, b []byte
|
||||
err error
|
||||
)
|
||||
b, err = bx.chunkHeader.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bin = append(bin, b...)
|
||||
|
||||
b, err = bx.Pool.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bin = append(bin, b...)
|
||||
|
||||
b, err = bx.Map.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bin = append(bin, b...)
|
||||
|
||||
b, err = bx.Namespace.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bin = append(bin, b...)
|
||||
|
||||
for _, child := range bx.Children {
|
||||
if err := marshalRecurse(child, &bin); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
b, err = bx.Namespace.end.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bin = append(bin, b...)
|
||||
|
||||
putu32(bin[4:], uint32(len(bin)))
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
func marshalRecurse(el *Element, bin *[]byte) error {
|
||||
b, err := el.MarshalBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*bin = append(*bin, b...)
|
||||
|
||||
if el.head != nil {
|
||||
b, err := el.head.MarshalBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*bin = append(*bin, b...)
|
||||
}
|
||||
|
||||
for _, child := range el.Children {
|
||||
if err := marshalRecurse(child, bin); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
b, err = el.end.MarshalBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*bin = append(*bin, b...)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bx *XML) iterElements() <-chan *Element {
|
||||
ch := make(chan *Element, 1)
|
||||
go func() {
|
||||
for _, el := range bx.Children {
|
||||
iterElementsRecurse(el, ch)
|
||||
}
|
||||
close(ch)
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
func iterElementsRecurse(el *Element, ch chan *Element) {
|
||||
ch <- el
|
||||
for _, e := range el.Children {
|
||||
iterElementsRecurse(e, ch)
|
||||
}
|
||||
}
|
||||
|
||||
// asSet returns a set from a slice of strings.
|
||||
func asSet(xs []string) []string {
|
||||
m := make(map[string]bool)
|
||||
fo := xs[:0]
|
||||
for _, x := range xs {
|
||||
if !m[x] {
|
||||
m[x] = true
|
||||
fo = append(fo, x)
|
||||
}
|
||||
}
|
||||
return fo
|
||||
}
|
||||
|
||||
// poolTrim trims all but immediately surrounding space.
|
||||
// \n\t\tfoobar\n\t\t becomes \tfoobar\n
|
||||
func poolTrim(s string) string {
|
||||
var start, end int
|
||||
for i, r := range s {
|
||||
if !unicode.IsSpace(r) {
|
||||
if i != 0 {
|
||||
start = i - 1 // preserve preceding space
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for i := len(s) - 1; i >= 0; i-- {
|
||||
r := rune(s[i])
|
||||
if !unicode.IsSpace(r) {
|
||||
if i != len(s)-1 {
|
||||
end = i + 2
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if start == 0 && end == 0 {
|
||||
return "" // every char was a space
|
||||
}
|
||||
|
||||
return s[start:end]
|
||||
}
|
||||
|
||||
// byNamespace sorts attributes based on string pool position of namespace.
|
||||
// Given that "android" always proceeds "" in the pool, this results in the
|
||||
// correct ordering of attributes.
|
||||
type byNamespace []*Attribute
|
||||
|
||||
func (a byNamespace) Len() int { return len(a) }
|
||||
func (a byNamespace) Less(i, j int) bool {
|
||||
return a[i].NS < a[j].NS
|
||||
}
|
||||
func (a byNamespace) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
// byType sorts attributes by the uint8 value of the type.
|
||||
type byType []*Attribute
|
||||
|
||||
func (a byType) Len() int { return len(a) }
|
||||
func (a byType) Less(i, j int) bool {
|
||||
return a[i].TypedValue.Type < a[j].TypedValue.Type
|
||||
}
|
||||
func (a byType) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
// byName sorts attributes that have matching types based on string pool position of name.
|
||||
type byName []*Attribute
|
||||
|
||||
func (a byName) Len() int { return len(a) }
|
||||
func (a byName) Less(i, j int) bool {
|
||||
return (a[i].TypedValue.Type == DataString || a[i].TypedValue.Type == DataIntDec) &&
|
||||
(a[j].TypedValue.Type == DataString || a[j].TypedValue.Type == DataIntDec) &&
|
||||
a[i].Name < a[j].Name
|
||||
}
|
||||
func (a byName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
type lineReader struct {
|
||||
off int64
|
||||
lines []int64
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
func (r *lineReader) Read(p []byte) (n int, err error) {
|
||||
n, err = r.r.Read(p)
|
||||
for i := 0; i < n; i++ {
|
||||
if p[i] == '\n' {
|
||||
r.lines = append(r.lines, r.off+int64(i))
|
||||
}
|
||||
}
|
||||
r.off += int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (r *lineReader) line(pos int64) int {
|
||||
return sort.Search(len(r.lines), func(i int) bool {
|
||||
return pos < r.lines[i]
|
||||
}) + 1
|
||||
}
|
62
internal/binres/binres_string.go
Normal file
62
internal/binres/binres_string.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
// Code generated by "stringer -output binres_string.go -type ResType,DataType"; DO NOT EDIT.
|
||||
|
||||
package binres
|
||||
|
||||
import "strconv"
|
||||
|
||||
const (
|
||||
_ResType_name_0 = "ResNullResStringPoolResTableResXML"
|
||||
_ResType_name_1 = "ResXMLStartNamespaceResXMLEndNamespaceResXMLStartElementResXMLEndElementResXMLCharData"
|
||||
_ResType_name_2 = "ResXMLResourceMap"
|
||||
_ResType_name_3 = "ResTablePackageResTableTypeResTableTypeSpecResTableLibrary"
|
||||
)
|
||||
|
||||
var (
|
||||
_ResType_index_0 = [...]uint8{0, 7, 20, 28, 34}
|
||||
_ResType_index_1 = [...]uint8{0, 20, 38, 56, 72, 86}
|
||||
_ResType_index_3 = [...]uint8{0, 15, 27, 43, 58}
|
||||
)
|
||||
|
||||
func (i ResType) String() string {
|
||||
switch {
|
||||
case 0 <= i && i <= 3:
|
||||
return _ResType_name_0[_ResType_index_0[i]:_ResType_index_0[i+1]]
|
||||
case 256 <= i && i <= 260:
|
||||
i -= 256
|
||||
return _ResType_name_1[_ResType_index_1[i]:_ResType_index_1[i+1]]
|
||||
case i == 384:
|
||||
return _ResType_name_2
|
||||
case 512 <= i && i <= 515:
|
||||
i -= 512
|
||||
return _ResType_name_3[_ResType_index_3[i]:_ResType_index_3[i+1]]
|
||||
default:
|
||||
return "ResType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
_DataType_name_0 = "DataNullDataReferenceDataAttributeDataStringDataFloatDataDimensionDataFractionDataDynamicReference"
|
||||
_DataType_name_1 = "DataIntDecDataIntHexDataIntBool"
|
||||
_DataType_name_2 = "DataIntColorARGB8DataIntColorRGB8DataIntColorARGB4DataIntColorRGB4"
|
||||
)
|
||||
|
||||
var (
|
||||
_DataType_index_0 = [...]uint8{0, 8, 21, 34, 44, 53, 66, 78, 98}
|
||||
_DataType_index_1 = [...]uint8{0, 10, 20, 31}
|
||||
_DataType_index_2 = [...]uint8{0, 17, 33, 50, 66}
|
||||
)
|
||||
|
||||
func (i DataType) String() string {
|
||||
switch {
|
||||
case 0 <= i && i <= 7:
|
||||
return _DataType_name_0[_DataType_index_0[i]:_DataType_index_0[i+1]]
|
||||
case 16 <= i && i <= 18:
|
||||
i -= 16
|
||||
return _DataType_name_1[_DataType_index_1[i]:_DataType_index_1[i+1]]
|
||||
case 28 <= i && i <= 31:
|
||||
i -= 28
|
||||
return _DataType_name_2[_DataType_index_2[i]:_DataType_index_2[i+1]]
|
||||
default:
|
||||
return "DataType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
}
|
663
internal/binres/binres_test.go
Normal file
663
internal/binres/binres_test.go
Normal file
|
@ -0,0 +1,663 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package binres
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/mobile/internal/sdkpath"
|
||||
)
|
||||
|
||||
func init() {
|
||||
skipSynthesize = true
|
||||
}
|
||||
|
||||
func printrecurse(t *testing.T, pl *Pool, el *Element, ws string) {
|
||||
|
||||
t.Logf("%s+elem:ns(%v) name(%s)", ws, el.NS, el.Name.Resolve(pl))
|
||||
|
||||
for _, attr := range el.attrs {
|
||||
ns := ""
|
||||
if attr.NS != math.MaxUint32 {
|
||||
ns = pl.strings[int(attr.NS)]
|
||||
nss := strings.Split(ns, "/")
|
||||
ns = nss[len(nss)-1]
|
||||
}
|
||||
|
||||
val := ""
|
||||
if attr.RawValue != NoEntry {
|
||||
val = pl.strings[int(attr.RawValue)]
|
||||
} else {
|
||||
switch attr.TypedValue.Type {
|
||||
case DataIntDec:
|
||||
val = fmt.Sprintf("%v", attr.TypedValue.Value)
|
||||
case DataIntBool:
|
||||
val = fmt.Sprintf("%v", attr.TypedValue.Value == 0xFFFFFFFF)
|
||||
default:
|
||||
val = fmt.Sprintf("0x%08X", attr.TypedValue.Value)
|
||||
}
|
||||
}
|
||||
dt := attr.TypedValue.Type
|
||||
|
||||
t.Logf("%s|attr:ns(%v) name(%s) val(%s) valtyp(%s)\n", ws, ns, pl.strings[int(attr.Name)], val, dt)
|
||||
}
|
||||
t.Logf("\n")
|
||||
for _, e := range el.Children {
|
||||
printrecurse(t, pl, e, ws+" ")
|
||||
}
|
||||
}
|
||||
|
||||
func compareBytes(a, b []byte) error {
|
||||
if bytes.Equal(a, b) {
|
||||
return nil
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
x, y := len(a), len(b)
|
||||
if x != y {
|
||||
fmt.Fprintf(buf, "byte length does not match, have %v, want %v\n", y, x)
|
||||
}
|
||||
if x > y {
|
||||
x, y = y, x
|
||||
}
|
||||
mismatch := false
|
||||
for i := 0; i < x; i++ {
|
||||
if mismatch = a[i] != b[i]; mismatch {
|
||||
fmt.Fprintf(buf, "first byte mismatch at %v\n", i)
|
||||
break
|
||||
}
|
||||
}
|
||||
if mismatch {
|
||||
// print out a reasonable amount of data to help identify issues
|
||||
truncate := x > 3300
|
||||
if truncate {
|
||||
x = 3300
|
||||
}
|
||||
buf.WriteString(" HAVE WANT\n")
|
||||
for i := 0; i < x; i += 4 {
|
||||
he, we := 4, 4
|
||||
if i+he >= x {
|
||||
he = x - i
|
||||
}
|
||||
if i+we >= y {
|
||||
we = y - i
|
||||
}
|
||||
notequal := ""
|
||||
if !bytes.Equal(b[i:i+he], a[i:i+we]) {
|
||||
notequal = "***"
|
||||
}
|
||||
fmt.Fprintf(buf, "%3v | % X % X %s\n", i, b[i:i+he], a[i:i+we], notequal)
|
||||
}
|
||||
if truncate {
|
||||
fmt.Fprint(buf, "... output truncated.\n")
|
||||
}
|
||||
}
|
||||
return errors.New(buf.String())
|
||||
}
|
||||
|
||||
func TestBootstrap(t *testing.T) {
|
||||
bin, err := os.ReadFile("testdata/bootstrap.bin")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// unmarshal binary xml and store byte indices of decoded resources.
|
||||
debugIndices := make(map[encoding.BinaryMarshaler]int)
|
||||
trackUnmarshal := func(buf []byte) (*XML, error) {
|
||||
bx := new(XML)
|
||||
if err := (&bx.chunkHeader).UnmarshalBinary(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = buf[8:]
|
||||
debugIndex := 8
|
||||
for len(buf) > 0 {
|
||||
k, err := bx.unmarshalBinaryKind(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
debugIndices[k.(encoding.BinaryMarshaler)] = debugIndex
|
||||
debugIndex += k.size()
|
||||
buf = buf[k.size():]
|
||||
}
|
||||
return bx, nil
|
||||
}
|
||||
|
||||
checkMarshal := func(res encoding.BinaryMarshaler, bsize int) {
|
||||
b, err := res.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
idx := debugIndices[res]
|
||||
a := bin[idx : idx+bsize]
|
||||
if !bytes.Equal(a, b) {
|
||||
x, y := len(a), len(b)
|
||||
if x != y {
|
||||
t.Errorf("%v: %T: byte length does not match, have %v, want %v", idx, res, y, x)
|
||||
}
|
||||
if x > y {
|
||||
x, y = y, x
|
||||
}
|
||||
mismatch := false
|
||||
for i := 0; i < x; i++ {
|
||||
if mismatch = a[i] != b[i]; mismatch {
|
||||
t.Errorf("%v: %T: first byte mismatch at %v of %v", idx, res, i, bsize)
|
||||
break
|
||||
}
|
||||
}
|
||||
if mismatch {
|
||||
// print out a reasonable amount of data to help identify issues
|
||||
truncate := x > 1300
|
||||
if truncate {
|
||||
x = 1300
|
||||
}
|
||||
t.Log(" HAVE WANT")
|
||||
for i := 0; i < x; i += 4 {
|
||||
he, we := 4, 4
|
||||
if i+he >= x {
|
||||
he = x - i
|
||||
}
|
||||
if i+we >= y {
|
||||
we = y - i
|
||||
}
|
||||
t.Logf("%3v | % X % X\n", i, b[i:i+he], a[i:i+we])
|
||||
}
|
||||
if truncate {
|
||||
t.Log("... output truncated.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bxml, err := trackUnmarshal(bin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i, x := range bxml.Pool.strings {
|
||||
t.Logf("Pool(%v): %q\n", i, x)
|
||||
}
|
||||
|
||||
for _, e := range bxml.Children {
|
||||
printrecurse(t, bxml.Pool, e, "")
|
||||
}
|
||||
|
||||
checkMarshal(&bxml.chunkHeader, int(bxml.headerByteSize))
|
||||
checkMarshal(bxml.Pool, bxml.Pool.size())
|
||||
checkMarshal(bxml.Map, bxml.Map.size())
|
||||
checkMarshal(bxml.Namespace, bxml.Namespace.size())
|
||||
|
||||
for el := range bxml.iterElements() {
|
||||
checkMarshal(el, el.size())
|
||||
checkMarshal(el.end, el.end.size())
|
||||
}
|
||||
|
||||
checkMarshal(bxml.Namespace.end, bxml.Namespace.end.size())
|
||||
checkMarshal(bxml, bxml.size())
|
||||
}
|
||||
|
||||
func TestEncode(t *testing.T) {
|
||||
f, err := os.Open("testdata/bootstrap.xml")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
bx, err := UnmarshalXML(f, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
bin, err := os.ReadFile("testdata/bootstrap.bin")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
bxml := new(XML)
|
||||
if err := bxml.UnmarshalBinary(bin); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := compareStrings(t, bxml.Pool.strings, bx.Pool.strings); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if err := compareUint32s(t, rtou(bxml.Map.rs), rtou(bx.Map.rs)); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if err := compareNamespaces(bx.Namespace, bxml.Namespace); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if err := compareElements(bx, bxml); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// Current output byte-for-byte of pkg binres is close, but not exact, to output of aapt.
|
||||
// The current exceptions to this are as follows:
|
||||
// * sort order of certain attributes
|
||||
// * typed value of minSdkVersion
|
||||
// The below check will produce an error, listing differences in the byte output of each.
|
||||
// have, err := bx.MarshalBinary()
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// if err := compareBytes(bin, have); err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
}
|
||||
|
||||
func TestRawValueByName(t *testing.T) {
|
||||
f, err := os.Open("testdata/bootstrap.xml")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
bx, err := UnmarshalXML(f, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pkgname, err := bx.RawValueByName("manifest", xml.Name{Local: "package"})
|
||||
if want := "com.zentus.balloon"; err != nil || pkgname != want {
|
||||
t.Fatalf("have (%q, %v), want (%q, nil)", pkgname, err, want)
|
||||
}
|
||||
}
|
||||
|
||||
type byAttrName []*Attribute
|
||||
|
||||
func (a byAttrName) Len() int { return len(a) }
|
||||
func (a byAttrName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a byAttrName) Less(i, j int) bool { return a[i].Name < a[j].Name }
|
||||
|
||||
func compareElements(have, want *XML) error {
|
||||
h, w := have.iterElements(), want.iterElements()
|
||||
buf := new(bytes.Buffer)
|
||||
for {
|
||||
a, b := <-h, <-w
|
||||
if a == nil || b == nil {
|
||||
break
|
||||
}
|
||||
if a.Name.Resolve(have.Pool) == "uses-sdk" {
|
||||
a = <-h // discard uses-sdk token from tests since it's synthesized internally
|
||||
}
|
||||
if a.NS != b.NS ||
|
||||
a.Name != b.Name {
|
||||
return fmt.Errorf("elements don't match, have %+v, want %+v", a, b)
|
||||
}
|
||||
if a.end.NS != b.end.NS ||
|
||||
a.end.Name != b.end.Name {
|
||||
return fmt.Errorf("element ends don't match, have %+v, want %+v", a.end, b.end)
|
||||
}
|
||||
if len(a.attrs) != len(b.attrs) {
|
||||
return fmt.Errorf("element attribute lengths don't match, have %v, want %v", len(a.attrs), len(b.attrs))
|
||||
}
|
||||
|
||||
// discards order of aapt and binres as some sorting details of apt have eluded this package but do not
|
||||
// affect final output from functioning correctly
|
||||
sort.Sort(byAttrName(a.attrs))
|
||||
sort.Sort(byAttrName(b.attrs))
|
||||
|
||||
for i, attr := range a.attrs {
|
||||
bttr := b.attrs[i]
|
||||
if attr.NS != bttr.NS ||
|
||||
attr.Name != bttr.Name ||
|
||||
attr.RawValue != bttr.RawValue ||
|
||||
attr.TypedValue.Type != bttr.TypedValue.Type ||
|
||||
attr.TypedValue.Value != bttr.TypedValue.Value {
|
||||
// single exception to check for minSdkVersion which has peculiar output from aapt
|
||||
// but following same format of all other like-types appears to work correctly.
|
||||
// BUG(dskinner) this check is brittle as it will skip over any attribute in
|
||||
// bootstrap.xml that has value == MinSDK.
|
||||
if attr.TypedValue.Value == MinSDK {
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(buf, "attrs don't match\nhave: %+v\nwant: %+v\n", attr, bttr)
|
||||
}
|
||||
}
|
||||
if buf.Len() > 0 {
|
||||
buf.WriteString("-------------\n")
|
||||
}
|
||||
}
|
||||
if buf.Len() > 0 {
|
||||
return errors.New(buf.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func compareNamespaces(have, want *Namespace) error {
|
||||
if have == nil || want == nil ||
|
||||
have.LineNumber != want.LineNumber ||
|
||||
have.Comment != want.Comment ||
|
||||
have.prefix != want.prefix ||
|
||||
have.uri != want.uri {
|
||||
return fmt.Errorf("namespaces don't match, have %+v, want %+v", have, want)
|
||||
}
|
||||
if have.end != nil || want.end != nil {
|
||||
return compareNamespaces(have.end, want.end)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func rtou(a []TableRef) (b []uint32) {
|
||||
for _, x := range a {
|
||||
b = append(b, uint32(x))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func compareUint32s(t *testing.T, a, b []uint32) error {
|
||||
var err error
|
||||
if len(a) != len(b) {
|
||||
err = fmt.Errorf("lengths do not match")
|
||||
}
|
||||
|
||||
n := len(a)
|
||||
if n < len(b) {
|
||||
n = len(b)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("a.Map.rs b.Map.rs\n")
|
||||
for i := 0; i < n; i++ {
|
||||
var c, d string
|
||||
if i < len(a) {
|
||||
c = fmt.Sprintf("%0#8x ", a[i])
|
||||
} else {
|
||||
c = "__________ "
|
||||
}
|
||||
if i < len(b) {
|
||||
d = fmt.Sprintf("%0#8x ", b[i])
|
||||
} else {
|
||||
d = "__________ "
|
||||
}
|
||||
if err == nil && c != d {
|
||||
err = fmt.Errorf("has missing/incorrect values")
|
||||
}
|
||||
buf.WriteString(c + " " + d + "\n")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%s\n%s", err, buf.String())
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func compareStrings(t *testing.T, a, b []string) error {
|
||||
var err error
|
||||
if len(a) != len(b) {
|
||||
err = fmt.Errorf("lengths do not match")
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
for i, x := range a {
|
||||
v := "__"
|
||||
for j, y := range b {
|
||||
if x == y {
|
||||
v = fmt.Sprintf("%2v", j)
|
||||
break
|
||||
}
|
||||
}
|
||||
if err == nil && v == "__" {
|
||||
if !strings.HasPrefix(x, "4.1.") {
|
||||
// as of the time of this writing, the current version of build tools being targeted
|
||||
// reports 4.1.2-1425332.
|
||||
//
|
||||
// TODO this check has the potential to hide real errors but can be fixed once more
|
||||
// of the xml document is unmarshalled and XML can be queried to assure this is related
|
||||
// to platformBuildVersionName.
|
||||
err = fmt.Errorf("has missing/incorrect values")
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(buf, "Pool(%2v, %s) %q\n", i, v, x)
|
||||
}
|
||||
|
||||
contains := func(xs []string, a string) bool {
|
||||
for _, x := range xs {
|
||||
if x == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
buf.WriteString("\n## only in var a\n")
|
||||
for i, x := range a {
|
||||
if !contains(b, x) {
|
||||
fmt.Fprintf(buf, "Pool(%2v) %q\n", i, x)
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString("\n## only in var b\n")
|
||||
for i, x := range b {
|
||||
if !contains(a, x) {
|
||||
fmt.Fprintf(buf, "Pool(%2v) %q\n", i, x)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%s\n%s", err, buf.String())
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func TestOpenTable(t *testing.T) {
|
||||
if _, err := sdkpath.AndroidHome(); err != nil {
|
||||
t.Skipf("Could not locate Android SDK: %v", err)
|
||||
}
|
||||
tbl, err := OpenTable()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(tbl.pkgs) == 0 {
|
||||
t.Fatal("failed to decode any resource packages")
|
||||
}
|
||||
|
||||
pkg := tbl.pkgs[0]
|
||||
|
||||
t.Log("package name:", pkg.name)
|
||||
|
||||
for i, x := range pkg.typePool.strings {
|
||||
t.Logf("typePool[i=%v]: %s\n", i, x)
|
||||
}
|
||||
|
||||
for i, spec := range pkg.specs {
|
||||
t.Logf("spec[i=%v]: %v %q\n", i, spec.id, pkg.typePool.strings[spec.id-1])
|
||||
for j, typ := range spec.types {
|
||||
t.Logf("\ttype[i=%v]: %v\n", j, typ.id)
|
||||
for k, nt := range typ.entries {
|
||||
if nt == nil { // NoEntry
|
||||
continue
|
||||
}
|
||||
t.Logf("\t\tentry[i=%v]: %v %q\n", k, nt.key, pkg.keyPool.strings[nt.key])
|
||||
if k > 5 {
|
||||
t.Logf("\t\t... truncating output")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTableRefByName(t *testing.T) {
|
||||
checkResources(t)
|
||||
tbl, err := OpenSDKTable()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(tbl.pkgs) == 0 {
|
||||
t.Fatal("failed to decode any resource packages")
|
||||
}
|
||||
|
||||
ref, err := tbl.RefByName("@android:style/Theme.NoTitleBar.Fullscreen")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if want := uint32(0x01030007); uint32(ref) != want {
|
||||
t.Fatalf("RefByName does not match expected result, have %0#8x, want %0#8x", ref, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTableMarshal(t *testing.T) {
|
||||
checkResources(t)
|
||||
tbl, err := OpenSDKTable()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
bin, err := tbl.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
xtbl := new(Table)
|
||||
if err := xtbl.UnmarshalBinary(bin); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(tbl.pool.strings) != len(xtbl.pool.strings) {
|
||||
t.Fatal("tbl.pool lengths don't match")
|
||||
}
|
||||
if len(tbl.pkgs) != len(xtbl.pkgs) {
|
||||
t.Fatal("tbl.pkgs lengths don't match")
|
||||
}
|
||||
|
||||
pkg, xpkg := tbl.pkgs[0], xtbl.pkgs[0]
|
||||
if err := compareStrings(t, pkg.typePool.strings, xpkg.typePool.strings); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := compareStrings(t, pkg.keyPool.strings, xpkg.keyPool.strings); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(pkg.specs) != len(xpkg.specs) {
|
||||
t.Fatal("pkg.specs lengths don't match")
|
||||
}
|
||||
|
||||
for i, spec := range pkg.specs {
|
||||
xspec := xpkg.specs[i]
|
||||
if spec.id != xspec.id {
|
||||
t.Fatal("spec.id doesn't match")
|
||||
}
|
||||
if spec.entryCount != xspec.entryCount {
|
||||
t.Fatal("spec.entryCount doesn't match")
|
||||
}
|
||||
if len(spec.entries) != len(xspec.entries) {
|
||||
t.Fatal("spec.entries lengths don't match")
|
||||
}
|
||||
for j, mask := range spec.entries {
|
||||
xmask := xspec.entries[j]
|
||||
if mask != xmask {
|
||||
t.Fatal("entry mask doesn't match")
|
||||
}
|
||||
}
|
||||
if len(spec.types) != len(xspec.types) {
|
||||
t.Fatal("spec.types length don't match")
|
||||
}
|
||||
for j, typ := range spec.types {
|
||||
xtyp := xspec.types[j]
|
||||
if typ.id != xtyp.id {
|
||||
t.Fatal("typ.id doesn't match")
|
||||
}
|
||||
if typ.entryCount != xtyp.entryCount {
|
||||
t.Fatal("typ.entryCount doesn't match")
|
||||
}
|
||||
// Config size can differ after serialization due to the loss of extended fields
|
||||
// during reserialization, but the fixed portions of the Type header must not change.
|
||||
if uint32(typ.headerByteSize)-typ.config.size != uint32(xtyp.headerByteSize)-uint32(xtyp.config.size) {
|
||||
t.Fatal("fixed size header portions don't match")
|
||||
}
|
||||
if len(typ.indices) != len(xtyp.indices) {
|
||||
t.Fatal("typ.indices length don't match")
|
||||
}
|
||||
for k, index := range typ.indices {
|
||||
xindex := xtyp.indices[k]
|
||||
if index != xindex {
|
||||
t.Errorf("type index doesn't match at %v, have %v, want %v", k, xindex, index)
|
||||
}
|
||||
}
|
||||
if len(typ.entries) != len(xtyp.entries) {
|
||||
t.Fatal("typ.entries lengths don't match")
|
||||
}
|
||||
for k, nt := range typ.entries {
|
||||
xnt := xtyp.entries[k]
|
||||
if nt == nil {
|
||||
if xnt != nil {
|
||||
t.Fatal("nt is nil but xnt is not")
|
||||
}
|
||||
continue
|
||||
}
|
||||
if nt.size != xnt.size {
|
||||
t.Fatal("entry.size doesn't match")
|
||||
}
|
||||
if nt.flags != xnt.flags {
|
||||
t.Fatal("entry.flags don't match")
|
||||
}
|
||||
if nt.key != xnt.key {
|
||||
t.Fatal("entry.key doesn't match")
|
||||
}
|
||||
|
||||
if nt.parent != xnt.parent {
|
||||
t.Fatal("entry.parent doesn't match")
|
||||
}
|
||||
if nt.count != xnt.count {
|
||||
t.Fatal("entry.count doesn't match")
|
||||
}
|
||||
for l, val := range nt.values {
|
||||
xval := xnt.values[l]
|
||||
if val.name != xval.name {
|
||||
t.Fatal("value.name doesn't match")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkResources(t *testing.T) {
|
||||
t.Helper()
|
||||
if _, err := sdkpath.AndroidHome(); err != nil {
|
||||
t.Skip("Could not locate Android SDK")
|
||||
}
|
||||
rscPath, err := apiResourcesPath()
|
||||
if err != nil {
|
||||
t.Skipf("failed to find resources: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(rscPath); err != nil {
|
||||
t.Skipf("failed to find resources: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTableRefByName(b *testing.B) {
|
||||
if _, err := sdkpath.AndroidHome(); err != nil {
|
||||
b.Fatal("Could not locate Android SDK")
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
tbl, err := OpenTable()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
_, err = tbl.RefByName("@android:style/Theme.NoTitleBar.Fullscreen")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
40
internal/binres/genarsc.go
Normal file
40
internal/binres/genarsc.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
// Genarsc generates stripped down version of android.jar resources used
|
||||
// for validation of manifest entries.
|
||||
//
|
||||
// Requires the selected Android SDK to support the MinSDK platform version.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/mobile/internal/binres"
|
||||
)
|
||||
|
||||
const tmpl = `// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Code generated by genarsc.go. DO NOT EDIT.
|
||||
|
||||
package binres
|
||||
|
||||
var arsc = []byte(%s)`
|
||||
|
||||
func main() {
|
||||
arsc, err := binres.PackResources()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile("arsc.go", []byte(fmt.Sprintf(tmpl, strconv.Quote(string(arsc)))), 0644); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
252
internal/binres/node.go
Normal file
252
internal/binres/node.go
Normal file
|
@ -0,0 +1,252 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package binres
|
||||
|
||||
// NodeHeader is header all xml node types have, providing additional
|
||||
// information regarding an xml node over binChunkHeader.
|
||||
type NodeHeader struct {
|
||||
chunkHeader
|
||||
LineNumber uint32 // line number in source file this element appears
|
||||
Comment PoolRef // optional xml comment associated with element, MaxUint32 if none
|
||||
}
|
||||
|
||||
func (hdr *NodeHeader) UnmarshalBinary(bin []byte) error {
|
||||
if err := (&hdr.chunkHeader).UnmarshalBinary(bin); err != nil {
|
||||
return err
|
||||
}
|
||||
hdr.LineNumber = btou32(bin[8:])
|
||||
hdr.Comment = PoolRef(btou32(bin[12:]))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hdr *NodeHeader) MarshalBinary() ([]byte, error) {
|
||||
bin := make([]byte, 16)
|
||||
b, err := hdr.chunkHeader.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(bin, b)
|
||||
putu32(bin[8:], hdr.LineNumber)
|
||||
putu32(bin[12:], uint32(hdr.Comment))
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
type Namespace struct {
|
||||
NodeHeader
|
||||
prefix PoolRef
|
||||
uri PoolRef
|
||||
|
||||
end *Namespace // TODO don't let this type be recursive
|
||||
}
|
||||
|
||||
func (ns *Namespace) UnmarshalBinary(bin []byte) error {
|
||||
if err := (&ns.NodeHeader).UnmarshalBinary(bin); err != nil {
|
||||
return err
|
||||
}
|
||||
buf := bin[ns.headerByteSize:]
|
||||
ns.prefix = PoolRef(btou32(buf))
|
||||
ns.uri = PoolRef(btou32(buf[4:]))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *Namespace) MarshalBinary() ([]byte, error) {
|
||||
if ns.end == nil {
|
||||
ns.typ = ResXMLEndNamespace
|
||||
} else {
|
||||
ns.typ = ResXMLStartNamespace
|
||||
}
|
||||
ns.headerByteSize = 16
|
||||
ns.byteSize = 24
|
||||
|
||||
bin := make([]byte, ns.byteSize)
|
||||
b, err := ns.NodeHeader.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(bin, b)
|
||||
putu32(bin[16:], uint32(ns.prefix))
|
||||
putu32(bin[20:], uint32(ns.uri))
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
type Element struct {
|
||||
NodeHeader
|
||||
NS PoolRef
|
||||
Name PoolRef // name of node if element, otherwise chardata if CDATA
|
||||
AttributeStart uint16 // byte offset where attrs start
|
||||
AttributeSize uint16 // byte size of attrs
|
||||
AttributeCount uint16 // length of attrs
|
||||
IdIndex uint16 // Index (1-based) of the "id" attribute. 0 if none.
|
||||
ClassIndex uint16 // Index (1-based) of the "class" attribute. 0 if none.
|
||||
StyleIndex uint16 // Index (1-based) of the "style" attribute. 0 if none.
|
||||
|
||||
attrs []*Attribute
|
||||
Children []*Element
|
||||
end *ElementEnd
|
||||
|
||||
head, tail *CharData
|
||||
}
|
||||
|
||||
func (el *Element) UnmarshalBinary(buf []byte) error {
|
||||
if err := (&el.NodeHeader).UnmarshalBinary(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
buf = buf[el.headerByteSize:]
|
||||
el.NS = PoolRef(btou32(buf))
|
||||
el.Name = PoolRef(btou32(buf[4:]))
|
||||
|
||||
el.AttributeStart = btou16(buf[8:])
|
||||
el.AttributeSize = btou16(buf[10:])
|
||||
el.AttributeCount = btou16(buf[12:])
|
||||
el.IdIndex = btou16(buf[14:])
|
||||
el.ClassIndex = btou16(buf[16:])
|
||||
el.StyleIndex = btou16(buf[18:])
|
||||
|
||||
buf = buf[el.AttributeStart:]
|
||||
el.attrs = make([]*Attribute, int(el.AttributeCount))
|
||||
for i := range el.attrs {
|
||||
attr := new(Attribute)
|
||||
if err := attr.UnmarshalBinary(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
el.attrs[i] = attr
|
||||
buf = buf[el.AttributeSize:]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (el *Element) MarshalBinary() ([]byte, error) {
|
||||
el.typ = ResXMLStartElement
|
||||
el.headerByteSize = 16
|
||||
el.AttributeSize = 20
|
||||
el.AttributeStart = 20
|
||||
el.AttributeCount = uint16(len(el.attrs))
|
||||
el.IdIndex = 0
|
||||
el.ClassIndex = 0
|
||||
el.StyleIndex = 0
|
||||
el.byteSize = uint32(el.headerByteSize) + uint32(el.AttributeStart) + uint32(len(el.attrs)*int(el.AttributeSize))
|
||||
|
||||
bin := make([]byte, el.byteSize)
|
||||
b, err := el.NodeHeader.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(bin, b)
|
||||
putu32(bin[16:], uint32(el.NS))
|
||||
putu32(bin[20:], uint32(el.Name))
|
||||
putu16(bin[24:], el.AttributeStart)
|
||||
putu16(bin[26:], el.AttributeSize)
|
||||
putu16(bin[28:], el.AttributeCount)
|
||||
putu16(bin[30:], el.IdIndex)
|
||||
putu16(bin[32:], el.ClassIndex)
|
||||
putu16(bin[34:], el.StyleIndex)
|
||||
|
||||
buf := bin[36:]
|
||||
for _, attr := range el.attrs {
|
||||
b, err := attr.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(buf, b)
|
||||
buf = buf[int(el.AttributeSize):]
|
||||
}
|
||||
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
// ElementEnd marks the end of an element node, either Element or CharData.
|
||||
type ElementEnd struct {
|
||||
NodeHeader
|
||||
NS PoolRef
|
||||
Name PoolRef // name of node if binElement, raw chardata if binCharData
|
||||
}
|
||||
|
||||
func (el *ElementEnd) UnmarshalBinary(bin []byte) error {
|
||||
(&el.NodeHeader).UnmarshalBinary(bin)
|
||||
buf := bin[el.headerByteSize:]
|
||||
el.NS = PoolRef(btou32(buf))
|
||||
el.Name = PoolRef(btou32(buf[4:]))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (el *ElementEnd) MarshalBinary() ([]byte, error) {
|
||||
el.typ = ResXMLEndElement
|
||||
el.headerByteSize = 16
|
||||
el.byteSize = 24
|
||||
|
||||
bin := make([]byte, 24)
|
||||
b, err := el.NodeHeader.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(bin, b)
|
||||
putu32(bin[16:], uint32(el.NS))
|
||||
putu32(bin[20:], uint32(el.Name))
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
type Attribute struct {
|
||||
NS PoolRef
|
||||
Name PoolRef
|
||||
RawValue PoolRef // The original raw string value of this attribute.
|
||||
TypedValue Data // Processesd typed value of this attribute.
|
||||
}
|
||||
|
||||
func (attr *Attribute) UnmarshalBinary(bin []byte) error {
|
||||
attr.NS = PoolRef(btou32(bin))
|
||||
attr.Name = PoolRef(btou32(bin[4:]))
|
||||
attr.RawValue = PoolRef(btou32(bin[8:]))
|
||||
return (&attr.TypedValue).UnmarshalBinary(bin[12:])
|
||||
}
|
||||
|
||||
func (attr *Attribute) MarshalBinary() ([]byte, error) {
|
||||
bin := make([]byte, 20)
|
||||
putu32(bin, uint32(attr.NS))
|
||||
putu32(bin[4:], uint32(attr.Name))
|
||||
putu32(bin[8:], uint32(attr.RawValue))
|
||||
b, err := attr.TypedValue.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(bin[12:], b)
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
// CharData represents a CDATA node and includes ref to node's text value.
|
||||
type CharData struct {
|
||||
NodeHeader
|
||||
RawData PoolRef // raw character data
|
||||
TypedData Data // typed value of character data
|
||||
}
|
||||
|
||||
func (cdt *CharData) UnmarshalBinary(bin []byte) error {
|
||||
if err := (&cdt.NodeHeader).UnmarshalBinary(bin); err != nil {
|
||||
return err
|
||||
}
|
||||
buf := bin[cdt.headerByteSize:]
|
||||
cdt.RawData = PoolRef(btou32(buf))
|
||||
return (&cdt.TypedData).UnmarshalBinary(buf[4:])
|
||||
}
|
||||
|
||||
func (cdt *CharData) MarshalBinary() ([]byte, error) {
|
||||
cdt.typ = ResXMLCharData
|
||||
cdt.headerByteSize = 16
|
||||
cdt.byteSize = 28
|
||||
|
||||
bin := make([]byte, cdt.byteSize)
|
||||
b, err := cdt.NodeHeader.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(bin, b)
|
||||
putu32(bin[16:], uint32(cdt.RawData))
|
||||
b, err = cdt.TypedData.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(bin[20:], b)
|
||||
return bin, nil
|
||||
}
|
293
internal/binres/pool.go
Normal file
293
internal/binres/pool.go
Normal file
|
@ -0,0 +1,293 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package binres
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unicode/utf16"
|
||||
)
|
||||
|
||||
const (
|
||||
SortedFlag uint32 = 1 << 0
|
||||
UTF8Flag = 1 << 8
|
||||
)
|
||||
|
||||
// PoolRef is the i'th string in a pool.
|
||||
type PoolRef uint32
|
||||
|
||||
// Resolve returns the string entry of PoolRef in pl.
|
||||
func (ref PoolRef) Resolve(pl *Pool) string {
|
||||
return pl.strings[ref]
|
||||
}
|
||||
|
||||
// Pool is a container for string and style span collections.
|
||||
//
|
||||
// Pool has the following structure marshalled:
|
||||
//
|
||||
// chunkHeader
|
||||
// uint32 number of strings in this pool
|
||||
// uint32 number of style spans in pool
|
||||
// uint32 SortedFlag, UTF8Flag
|
||||
// uint32 index of string data from header
|
||||
// uint32 index of style data from header
|
||||
// []uint32 string indices starting at zero
|
||||
// []uint16 or []uint8 concatenation of string entries
|
||||
//
|
||||
// UTF-16 entries are as follows:
|
||||
//
|
||||
// uint16 string length, exclusive
|
||||
// uint16 [optional] low word if high bit of length set
|
||||
// [n]byte data
|
||||
// uint16 0x0000 terminator
|
||||
//
|
||||
// UTF-8 entries are as follows:
|
||||
//
|
||||
// uint8 character length, exclusive
|
||||
// uint8 [optional] low word if high bit of character length set
|
||||
// uint8 byte length, exclusive
|
||||
// uint8 [optional] low word if high bit of byte length set
|
||||
// [n]byte data
|
||||
// uint8 0x00 terminator
|
||||
type Pool struct {
|
||||
chunkHeader
|
||||
strings []string
|
||||
styles []*Span
|
||||
flags uint32 // SortedFlag, UTF8Flag
|
||||
}
|
||||
|
||||
// ref returns the PoolRef of s, inserting s if it doesn't exist.
|
||||
func (pl *Pool) ref(s string) PoolRef {
|
||||
for i, x := range pl.strings {
|
||||
if s == x {
|
||||
return PoolRef(i)
|
||||
}
|
||||
}
|
||||
pl.strings = append(pl.strings, s)
|
||||
return PoolRef(len(pl.strings) - 1)
|
||||
}
|
||||
|
||||
// RefByName returns the PoolRef of s, or error if not exists.
|
||||
func (pl *Pool) RefByName(s string) (PoolRef, error) {
|
||||
for i, x := range pl.strings {
|
||||
if s == x {
|
||||
return PoolRef(i), nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("PoolRef by name %q does not exist", s)
|
||||
}
|
||||
|
||||
func (pl *Pool) IsSorted() bool { return pl.flags&SortedFlag == SortedFlag }
|
||||
func (pl *Pool) IsUTF8() bool { return pl.flags&UTF8Flag == UTF8Flag }
|
||||
|
||||
func (pl *Pool) UnmarshalBinary(bin []byte) error {
|
||||
if err := (&pl.chunkHeader).UnmarshalBinary(bin); err != nil {
|
||||
return err
|
||||
}
|
||||
if pl.typ != ResStringPool {
|
||||
return fmt.Errorf("have type %s, want %s", pl.typ, ResStringPool)
|
||||
}
|
||||
|
||||
pl.strings = make([]string, btou32(bin[8:]))
|
||||
pl.styles = make([]*Span, btou32(bin[12:]))
|
||||
pl.flags = btou32(bin[16:])
|
||||
|
||||
offstrings := btou32(bin[20:])
|
||||
offstyle := btou32(bin[24:])
|
||||
hdrlen := 28
|
||||
|
||||
if pl.IsUTF8() {
|
||||
for i := range pl.strings {
|
||||
offset := int(offstrings + btou32(bin[hdrlen+i*4:]))
|
||||
|
||||
// if leading bit set for nchars and nbytes,
|
||||
// treat first byte as 7-bit high word and next as low word.
|
||||
nchars := int(bin[offset])
|
||||
offset++
|
||||
if nchars&(1<<7) != 0 {
|
||||
n0 := nchars ^ (1 << 7) // high word
|
||||
n1 := int(bin[offset]) // low word
|
||||
nchars = n0*(1<<8) + n1
|
||||
offset++
|
||||
}
|
||||
|
||||
// TODO(d) At least one case in android.jar (api-10) resource table has only
|
||||
// highbit set, making 7-bit highword zero. The reason for this is currently
|
||||
// unknown but would make tests that unmarshal-marshal to match bytes impossible.
|
||||
// The values noted were high-word: 0 (after highbit unset), low-word: 141
|
||||
// The size may be treated as an int8 triggering the use of two bytes to store size
|
||||
// even though the implementation uses uint8.
|
||||
nbytes := int(bin[offset])
|
||||
offset++
|
||||
if nbytes&(1<<7) != 0 {
|
||||
n0 := nbytes ^ (1 << 7) // high word
|
||||
n1 := int(bin[offset]) // low word
|
||||
nbytes = n0*(1<<8) + n1
|
||||
offset++
|
||||
}
|
||||
|
||||
data := bin[offset : offset+nbytes]
|
||||
if x := uint8(bin[offset+nbytes]); x != 0 {
|
||||
return fmt.Errorf("expected zero terminator, got 0x%02X for nchars=%v nbytes=%v data=%q", x, nchars, nbytes, data)
|
||||
}
|
||||
pl.strings[i] = string(data)
|
||||
}
|
||||
} else {
|
||||
for i := range pl.strings {
|
||||
offset := int(offstrings + btou32(bin[hdrlen+i*4:])) // read index of string
|
||||
|
||||
// if leading bit set for nchars, treat first byte as 7-bit high word and next as low word.
|
||||
nchars := int(btou16(bin[offset:]))
|
||||
offset += 2
|
||||
if nchars&(1<<15) != 0 { // TODO(d) this is untested
|
||||
n0 := nchars ^ (1 << 15) // high word
|
||||
n1 := int(btou16(bin[offset:])) // low word
|
||||
nchars = n0*(1<<16) + n1
|
||||
offset += 2
|
||||
}
|
||||
|
||||
data := make([]uint16, nchars)
|
||||
for i := range data {
|
||||
data[i] = btou16(bin[offset+2*i:])
|
||||
}
|
||||
|
||||
if x := btou16(bin[offset+nchars*2:]); x != 0 {
|
||||
return fmt.Errorf("expected zero terminator, got 0x%04X for nchars=%v data=%q", x, nchars, data)
|
||||
}
|
||||
pl.strings[i] = string(utf16.Decode(data))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO decode styles
|
||||
_ = offstyle
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pl *Pool) MarshalBinary() ([]byte, error) {
|
||||
if pl.IsUTF8() {
|
||||
return nil, fmt.Errorf("encode utf8 not supported")
|
||||
}
|
||||
|
||||
var (
|
||||
hdrlen = 28
|
||||
// indices of string indices
|
||||
iis = make([]uint32, len(pl.strings))
|
||||
iislen = len(iis) * 4
|
||||
// utf16 encoded strings concatenated together
|
||||
strs []uint16
|
||||
)
|
||||
for i, x := range pl.strings {
|
||||
if len(x)>>16 > 0 {
|
||||
panic(fmt.Errorf("string lengths over 1<<15 not yet supported, got len %d", len(x)))
|
||||
}
|
||||
p := utf16.Encode([]rune(x))
|
||||
if len(p) == 0 {
|
||||
strs = append(strs, 0x0000, 0x0000)
|
||||
} else {
|
||||
strs = append(strs, uint16(len(p))) // string length (implicitly includes zero terminator to follow)
|
||||
strs = append(strs, p...)
|
||||
strs = append(strs, 0) // zero terminated
|
||||
}
|
||||
// indices start at zero
|
||||
if i+1 != len(iis) {
|
||||
iis[i+1] = uint32(len(strs) * 2) // utf16 byte index
|
||||
}
|
||||
}
|
||||
|
||||
// check strings is 4-byte aligned, pad with zeros if not.
|
||||
for x := (len(strs) * 2) % 4; x != 0; x -= 2 {
|
||||
strs = append(strs, 0x0000)
|
||||
}
|
||||
|
||||
strslen := len(strs) * 2
|
||||
|
||||
hdr := chunkHeader{
|
||||
typ: ResStringPool,
|
||||
headerByteSize: 28,
|
||||
byteSize: uint32(28 + iislen + strslen),
|
||||
}
|
||||
|
||||
bin := make([]byte, hdr.byteSize)
|
||||
|
||||
hdrbin, err := hdr.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(bin, hdrbin)
|
||||
|
||||
putu32(bin[8:], uint32(len(pl.strings)))
|
||||
putu32(bin[12:], uint32(len(pl.styles)))
|
||||
putu32(bin[16:], pl.flags)
|
||||
putu32(bin[20:], uint32(hdrlen+iislen))
|
||||
putu32(bin[24:], 0) // index of styles start, is 0 when styles length is 0
|
||||
|
||||
buf := bin[28:]
|
||||
for _, x := range iis {
|
||||
putu32(buf, x)
|
||||
buf = buf[4:]
|
||||
}
|
||||
|
||||
for _, x := range strs {
|
||||
putu16(buf, x)
|
||||
buf = buf[2:]
|
||||
}
|
||||
|
||||
if len(buf) != 0 {
|
||||
panic(fmt.Errorf("failed to fill allocated buffer, %v bytes left over", len(buf)))
|
||||
}
|
||||
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
type Span struct {
|
||||
name PoolRef
|
||||
|
||||
firstChar, lastChar uint32
|
||||
}
|
||||
|
||||
func (spn *Span) UnmarshalBinary(bin []byte) error {
|
||||
const end = 0xFFFFFFFF
|
||||
spn.name = PoolRef(btou32(bin))
|
||||
if spn.name == end {
|
||||
return nil
|
||||
}
|
||||
spn.firstChar = btou32(bin[4:])
|
||||
spn.lastChar = btou32(bin[8:])
|
||||
return nil
|
||||
}
|
||||
|
||||
// Map contains a uint32 slice mapping strings in the string
|
||||
// pool back to resource identifiers. The i'th element of the slice
|
||||
// is also the same i'th element of the string pool.
|
||||
type Map struct {
|
||||
chunkHeader
|
||||
rs []TableRef
|
||||
}
|
||||
|
||||
func (m *Map) UnmarshalBinary(bin []byte) error {
|
||||
(&m.chunkHeader).UnmarshalBinary(bin)
|
||||
buf := bin[m.headerByteSize:m.byteSize]
|
||||
m.rs = make([]TableRef, len(buf)/4)
|
||||
for i := range m.rs {
|
||||
m.rs[i] = TableRef(btou32(buf[i*4:]))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Map) MarshalBinary() ([]byte, error) {
|
||||
m.typ = ResXMLResourceMap
|
||||
m.headerByteSize = 8
|
||||
m.byteSize = uint32(m.headerByteSize) + uint32(len(m.rs)*4)
|
||||
bin := make([]byte, m.byteSize)
|
||||
b, err := m.chunkHeader.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(bin, b)
|
||||
for i, r := range m.rs {
|
||||
putu32(bin[8+i*4:], uint32(r))
|
||||
}
|
||||
return bin, nil
|
||||
}
|
153
internal/binres/sdk.go
Normal file
153
internal/binres/sdk.go
Normal file
|
@ -0,0 +1,153 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package binres
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/mobile/internal/sdkpath"
|
||||
)
|
||||
|
||||
// MinSDK is the targeted sdk version for support by package binres.
|
||||
const MinSDK = 16
|
||||
|
||||
func apiResources() ([]byte, error) {
|
||||
apiResPath, err := apiResourcesPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
zr, err := zip.OpenReader(apiResPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf(`%v; consider installing with "android update sdk --all --no-ui --filter android-%d"`, err, MinSDK)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
defer zr.Close()
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
for _, f := range zr.File {
|
||||
if f.Name == "resources.arsc" {
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = io.Copy(buf, rc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rc.Close()
|
||||
break
|
||||
}
|
||||
}
|
||||
if buf.Len() == 0 {
|
||||
return nil, fmt.Errorf("failed to read resources.arsc")
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func apiResourcesPath() (string, error) {
|
||||
platformDir, err := sdkpath.AndroidAPIPath(MinSDK)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(platformDir, "android.jar"), nil
|
||||
}
|
||||
|
||||
// PackResources produces a stripped down gzip version of the resources.arsc from api jar.
|
||||
func PackResources() ([]byte, error) {
|
||||
tbl, err := OpenSDKTable()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tbl.pool.strings = []string{} // should not be needed
|
||||
pkg := tbl.pkgs[0]
|
||||
|
||||
// drop language string entries
|
||||
for _, typ := range pkg.specs[3].types {
|
||||
if typ.config.locale.language != 0 {
|
||||
for j, nt := range typ.entries {
|
||||
if nt == nil { // NoEntry
|
||||
continue
|
||||
}
|
||||
pkg.keyPool.strings[nt.key] = ""
|
||||
typ.indices[j] = NoEntry
|
||||
typ.entries[j] = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// drop strings from pool for specs to be dropped
|
||||
for _, spec := range pkg.specs[4:] {
|
||||
for _, typ := range spec.types {
|
||||
for _, nt := range typ.entries {
|
||||
if nt == nil { // NoEntry
|
||||
continue
|
||||
}
|
||||
// don't drop if there's a collision
|
||||
var collision bool
|
||||
for _, xspec := range pkg.specs[:4] {
|
||||
for _, xtyp := range xspec.types {
|
||||
for _, xnt := range xtyp.entries {
|
||||
if xnt == nil {
|
||||
continue
|
||||
}
|
||||
if collision = nt.key == xnt.key; collision {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !collision {
|
||||
pkg.keyPool.strings[nt.key] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// entries are densely packed but probably safe to drop nil entries off the end
|
||||
for _, spec := range pkg.specs[:4] {
|
||||
for _, typ := range spec.types {
|
||||
var last int
|
||||
for i, nt := range typ.entries {
|
||||
if nt != nil {
|
||||
last = i
|
||||
}
|
||||
}
|
||||
typ.entries = typ.entries[:last+1]
|
||||
typ.indices = typ.indices[:last+1]
|
||||
}
|
||||
}
|
||||
|
||||
// keeping 0:attr, 1:id, 2:style, 3:string
|
||||
pkg.typePool.strings = pkg.typePool.strings[:4]
|
||||
pkg.specs = pkg.specs[:4]
|
||||
|
||||
bin, err := tbl.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
zw := gzip.NewWriter(buf)
|
||||
if _, err := zw.Write(bin); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := zw.Flush(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := zw.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
833
internal/binres/table.go
Normal file
833
internal/binres/table.go
Normal file
|
@ -0,0 +1,833 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package binres
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"unicode/utf16"
|
||||
)
|
||||
|
||||
const NoEntry = 0xFFFFFFFF
|
||||
|
||||
// TableRef uniquely identifies entries within a resource table.
|
||||
type TableRef uint32
|
||||
|
||||
// Resolve returns the Entry of TableRef in the given table.
|
||||
//
|
||||
// A TableRef is structured as follows:
|
||||
//
|
||||
// 0xpptteeee
|
||||
// pp: package index
|
||||
// tt: type spec index in package
|
||||
// eeee: entry index in type spec
|
||||
//
|
||||
// The package and type spec values start at 1 for the first item,
|
||||
// to help catch cases where they have not been supplied.
|
||||
func (ref TableRef) Resolve(tbl *Table) (*Entry, error) {
|
||||
pkg := tbl.pkgs[uint8(ref>>24)-1]
|
||||
spec := pkg.specs[uint8(ref>>16)-1]
|
||||
idx := uint16(ref)
|
||||
|
||||
for _, typ := range spec.types {
|
||||
if idx < uint16(len(typ.entries)) {
|
||||
nt := typ.entries[idx]
|
||||
if nt == nil {
|
||||
return nil, errors.New("nil entry match")
|
||||
}
|
||||
return nt, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New("failed to resolve table reference")
|
||||
}
|
||||
|
||||
// Table is a container for packaged resources. Resource values within a package
|
||||
// are obtained through pool while resource names and identifiers are obtained
|
||||
// through each package's type and key pools respectively.
|
||||
type Table struct {
|
||||
chunkHeader
|
||||
pool *Pool
|
||||
pkgs []*Package
|
||||
}
|
||||
|
||||
// NewMipmapTable returns a resource table initialized for a single xxxhdpi mipmap resource
|
||||
// and the path to write resource data to.
|
||||
func NewMipmapTable(pkgname string) (*Table, string) {
|
||||
pkg := &Package{id: 127, name: pkgname, typePool: &Pool{}, keyPool: &Pool{}}
|
||||
|
||||
attr := pkg.typePool.ref("attr")
|
||||
mipmap := pkg.typePool.ref("mipmap")
|
||||
icon := pkg.keyPool.ref("icon")
|
||||
|
||||
nt := &Entry{values: []*Value{{data: &Data{Type: DataString}}}}
|
||||
typ := &Type{id: 2, indices: []uint32{0}, entries: []*Entry{nt}}
|
||||
typ.config.screenType.density = 640
|
||||
typ.config.version.sdk = 4
|
||||
|
||||
pkg.specs = append(pkg.specs,
|
||||
&TypeSpec{
|
||||
id: uint8(attr) + 1, //1,
|
||||
},
|
||||
&TypeSpec{
|
||||
id: uint8(mipmap) + 1, //2,
|
||||
entryCount: 1,
|
||||
entries: []uint32{uint32(icon)}, // {0}
|
||||
types: []*Type{typ},
|
||||
})
|
||||
|
||||
pkg.lastPublicType = uint32(len(pkg.typePool.strings)) // 2
|
||||
pkg.lastPublicKey = uint32(len(pkg.keyPool.strings)) // 1
|
||||
|
||||
name := "res/mipmap-xxxhdpi-v4/icon.png"
|
||||
tbl := &Table{pool: &Pool{}, pkgs: []*Package{pkg}}
|
||||
tbl.pool.ref(name)
|
||||
return tbl, name
|
||||
}
|
||||
|
||||
// OpenSDKTable decodes resources.arsc from sdk platform jar.
|
||||
func OpenSDKTable() (*Table, error) {
|
||||
bin, err := apiResources()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tbl := new(Table)
|
||||
if err := tbl.UnmarshalBinary(bin); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tbl, nil
|
||||
}
|
||||
|
||||
// OpenTable decodes the prepacked resources.arsc for the supported sdk platform.
|
||||
func OpenTable() (*Table, error) {
|
||||
zr, err := gzip.NewReader(bytes.NewReader(arsc))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("gzip: %v", err)
|
||||
}
|
||||
defer zr.Close()
|
||||
|
||||
var buf bytes.Buffer
|
||||
if _, err := io.Copy(&buf, zr); err != nil {
|
||||
return nil, fmt.Errorf("io: %v", err)
|
||||
}
|
||||
tbl := new(Table)
|
||||
if err := tbl.UnmarshalBinary(buf.Bytes()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tbl, nil
|
||||
}
|
||||
|
||||
// SpecByName parses the spec name from an entry string if necessary and returns
|
||||
// the Package and TypeSpec associated with that name along with their respective
|
||||
// indices.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// tbl.SpecByName("@android:style/Theme.NoTitleBar")
|
||||
// tbl.SpecByName("style")
|
||||
//
|
||||
// Both locate the spec by name "style".
|
||||
func (tbl *Table) SpecByName(name string) (int, *Package, int, *TypeSpec, error) {
|
||||
n := strings.TrimPrefix(name, "@android:")
|
||||
n = strings.Split(n, "/")[0]
|
||||
for pp, pkg := range tbl.pkgs {
|
||||
for tt, spec := range pkg.specs {
|
||||
if n == pkg.typePool.strings[spec.id-1] {
|
||||
return pp, pkg, tt, spec, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0, nil, 0, nil, fmt.Errorf("spec by name not found: %q", name)
|
||||
}
|
||||
|
||||
// RefByName returns the TableRef by a given name. The ref may be used to resolve the
|
||||
// associated Entry and is used for the generation of binary manifest files.
|
||||
func (tbl *Table) RefByName(name string) (TableRef, error) {
|
||||
pp, pkg, tt, spec, err := tbl.SpecByName(name)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
q := strings.Split(name, "/")
|
||||
if len(q) != 2 {
|
||||
return 0, fmt.Errorf("invalid entry format, missing forward-slash: %q", name)
|
||||
}
|
||||
n := q[1]
|
||||
|
||||
for _, t := range spec.types {
|
||||
for eeee, nt := range t.entries {
|
||||
if nt == nil { // NoEntry
|
||||
continue
|
||||
}
|
||||
if n == pkg.keyPool.strings[nt.key] {
|
||||
return TableRef(uint32(eeee) | uint32(tt+1)<<16 | uint32(pp+1)<<24), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("failed to find table ref by %q", name)
|
||||
}
|
||||
|
||||
func (tbl *Table) UnmarshalBinary(bin []byte) error {
|
||||
if err := (&tbl.chunkHeader).UnmarshalBinary(bin); err != nil {
|
||||
return err
|
||||
}
|
||||
if tbl.typ != ResTable {
|
||||
return fmt.Errorf("unexpected resource type %s, want %s", tbl.typ, ResTable)
|
||||
}
|
||||
|
||||
npkgs := btou32(bin[8:])
|
||||
tbl.pkgs = make([]*Package, npkgs)
|
||||
|
||||
buf := bin[tbl.headerByteSize:]
|
||||
|
||||
tbl.pool = new(Pool)
|
||||
if err := tbl.pool.UnmarshalBinary(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
buf = buf[tbl.pool.size():]
|
||||
for i := range tbl.pkgs {
|
||||
pkg := new(Package)
|
||||
if err := pkg.UnmarshalBinary(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
tbl.pkgs[i] = pkg
|
||||
buf = buf[pkg.byteSize:]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tbl *Table) MarshalBinary() ([]byte, error) {
|
||||
bin := make([]byte, 12)
|
||||
putu16(bin, uint16(ResTable))
|
||||
putu16(bin[2:], 12)
|
||||
putu32(bin[8:], uint32(len(tbl.pkgs)))
|
||||
|
||||
if tbl.pool.IsUTF8() {
|
||||
tbl.pool.flags ^= UTF8Flag
|
||||
defer func() {
|
||||
tbl.pool.flags |= UTF8Flag
|
||||
}()
|
||||
}
|
||||
|
||||
b, err := tbl.pool.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bin = append(bin, b...)
|
||||
|
||||
for _, pkg := range tbl.pkgs {
|
||||
b, err = pkg.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bin = append(bin, b...)
|
||||
}
|
||||
|
||||
putu32(bin[4:], uint32(len(bin)))
|
||||
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
// Package contains a collection of resource data types.
|
||||
type Package struct {
|
||||
chunkHeader
|
||||
|
||||
id uint32
|
||||
name string
|
||||
|
||||
lastPublicType uint32 // last index into typePool that is for public use
|
||||
lastPublicKey uint32 // last index into keyPool that is for public use
|
||||
|
||||
typePool *Pool // type names; e.g. theme
|
||||
keyPool *Pool // resource names; e.g. Theme.NoTitleBar
|
||||
|
||||
aliases []*StagedAlias
|
||||
specs []*TypeSpec
|
||||
}
|
||||
|
||||
func (pkg *Package) UnmarshalBinary(bin []byte) error {
|
||||
if err := (&pkg.chunkHeader).UnmarshalBinary(bin); err != nil {
|
||||
return err
|
||||
}
|
||||
if pkg.typ != ResTablePackage {
|
||||
return errWrongType(pkg.typ, ResTablePackage)
|
||||
}
|
||||
|
||||
pkg.id = btou32(bin[8:])
|
||||
|
||||
var name []uint16
|
||||
for i := 0; i < 128; i++ {
|
||||
x := btou16(bin[12+i*2:])
|
||||
if x == 0 {
|
||||
break
|
||||
}
|
||||
name = append(name, x)
|
||||
}
|
||||
pkg.name = string(utf16.Decode(name))
|
||||
|
||||
typeOffset := btou32(bin[268:]) // 0 if inheriting from another package
|
||||
pkg.lastPublicType = btou32(bin[272:])
|
||||
keyOffset := btou32(bin[276:]) // 0 if inheriting from another package
|
||||
pkg.lastPublicKey = btou32(bin[280:])
|
||||
|
||||
var idOffset uint32 // value determined by either typePool or keyPool below
|
||||
|
||||
if typeOffset != 0 {
|
||||
pkg.typePool = new(Pool)
|
||||
if err := pkg.typePool.UnmarshalBinary(bin[typeOffset:]); err != nil {
|
||||
return err
|
||||
}
|
||||
idOffset = typeOffset + pkg.typePool.byteSize
|
||||
}
|
||||
|
||||
if keyOffset != 0 {
|
||||
pkg.keyPool = new(Pool)
|
||||
if err := pkg.keyPool.UnmarshalBinary(bin[keyOffset:]); err != nil {
|
||||
return err
|
||||
}
|
||||
idOffset = keyOffset + pkg.keyPool.byteSize
|
||||
}
|
||||
|
||||
if idOffset == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
buf := bin[idOffset:pkg.byteSize]
|
||||
for len(buf) > 0 {
|
||||
t := ResType(btou16(buf))
|
||||
switch t {
|
||||
case ResTableTypeSpec:
|
||||
spec := new(TypeSpec)
|
||||
if err := spec.UnmarshalBinary(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
pkg.specs = append(pkg.specs, spec)
|
||||
buf = buf[spec.byteSize:]
|
||||
case ResTableType:
|
||||
typ := new(Type)
|
||||
if err := typ.UnmarshalBinary(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
last := pkg.specs[len(pkg.specs)-1]
|
||||
last.types = append(last.types, typ)
|
||||
buf = buf[typ.byteSize:]
|
||||
case ResTableStagedAlias:
|
||||
alias := new(StagedAlias)
|
||||
if err := alias.UnmarshalBinary(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
pkg.aliases = append(pkg.aliases, alias)
|
||||
buf = buf[alias.byteSize:]
|
||||
default:
|
||||
return errWrongType(t, ResTableTypeSpec, ResTableType, ResTableStagedAlias)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pkg *Package) MarshalBinary() ([]byte, error) {
|
||||
// Package header size is determined by C++ struct ResTable_package
|
||||
// see frameworks/base/include/ResourceTypes.h
|
||||
bin := make([]byte, 288)
|
||||
putu16(bin, uint16(ResTablePackage))
|
||||
putu16(bin[2:], 288)
|
||||
|
||||
putu32(bin[8:], pkg.id)
|
||||
p := utf16.Encode([]rune(pkg.name))
|
||||
for i, x := range p {
|
||||
putu16(bin[12+i*2:], x)
|
||||
}
|
||||
|
||||
if pkg.typePool != nil {
|
||||
if pkg.typePool.IsUTF8() {
|
||||
pkg.typePool.flags ^= UTF8Flag
|
||||
defer func() {
|
||||
pkg.typePool.flags |= UTF8Flag
|
||||
}()
|
||||
}
|
||||
|
||||
b, err := pkg.typePool.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
putu32(bin[268:], uint32(len(bin)))
|
||||
putu32(bin[272:], pkg.lastPublicType)
|
||||
bin = append(bin, b...)
|
||||
}
|
||||
|
||||
if pkg.keyPool != nil {
|
||||
if pkg.keyPool.IsUTF8() {
|
||||
pkg.keyPool.flags ^= UTF8Flag
|
||||
defer func() {
|
||||
pkg.keyPool.flags |= UTF8Flag
|
||||
}()
|
||||
}
|
||||
b, err := pkg.keyPool.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
putu32(bin[276:], uint32(len(bin)))
|
||||
putu32(bin[280:], pkg.lastPublicKey)
|
||||
bin = append(bin, b...)
|
||||
}
|
||||
|
||||
for _, alias := range pkg.aliases {
|
||||
b, err := alias.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bin = append(bin, b...)
|
||||
}
|
||||
|
||||
for _, spec := range pkg.specs {
|
||||
b, err := spec.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bin = append(bin, b...)
|
||||
}
|
||||
|
||||
putu32(bin[4:], uint32(len(bin)))
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
// TypeSpec provides a specification for the resources defined by a particular type.
|
||||
type TypeSpec struct {
|
||||
chunkHeader
|
||||
id uint8 // id-1 is name index in Package.typePool
|
||||
res0 uint8 // must be 0
|
||||
res1 uint16 // must be 0
|
||||
entryCount uint32 // number of uint32 entry configuration masks that follow
|
||||
|
||||
entries []uint32 // entry configuration masks
|
||||
types []*Type
|
||||
}
|
||||
|
||||
func (spec *TypeSpec) UnmarshalBinary(bin []byte) error {
|
||||
if err := (&spec.chunkHeader).UnmarshalBinary(bin); err != nil {
|
||||
return err
|
||||
}
|
||||
if spec.typ != ResTableTypeSpec {
|
||||
return errWrongType(spec.typ, ResTableTypeSpec)
|
||||
}
|
||||
spec.id = uint8(bin[8])
|
||||
spec.res0 = uint8(bin[9])
|
||||
spec.res1 = btou16(bin[10:])
|
||||
spec.entryCount = btou32(bin[12:])
|
||||
|
||||
spec.entries = make([]uint32, spec.entryCount)
|
||||
for i := range spec.entries {
|
||||
spec.entries[i] = btou32(bin[16+i*4:])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (spec *TypeSpec) MarshalBinary() ([]byte, error) {
|
||||
bin := make([]byte, 16+len(spec.entries)*4)
|
||||
putu16(bin, uint16(ResTableTypeSpec))
|
||||
putu16(bin[2:], 16)
|
||||
putu32(bin[4:], uint32(len(bin)))
|
||||
|
||||
bin[8] = byte(spec.id)
|
||||
// [9] = 0
|
||||
// [10:12] = 0
|
||||
putu32(bin[12:], uint32(len(spec.entries)))
|
||||
for i, x := range spec.entries {
|
||||
putu32(bin[16+i*4:], x)
|
||||
}
|
||||
|
||||
for _, typ := range spec.types {
|
||||
b, err := typ.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bin = append(bin, b...)
|
||||
}
|
||||
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
// Type provides a collection of entries for a specific device configuration.
|
||||
type Type struct {
|
||||
chunkHeader
|
||||
id uint8
|
||||
res0 uint8 // must be 0
|
||||
res1 uint16 // must be 0
|
||||
entryCount uint32 // number of uint32 entry configuration masks that follow
|
||||
entriesStart uint32 // offset from header where Entry data starts
|
||||
|
||||
// configuration this collection of entries is designed for
|
||||
config struct {
|
||||
size uint32
|
||||
imsi struct {
|
||||
mcc uint16 // mobile country code
|
||||
mnc uint16 // mobile network code
|
||||
}
|
||||
locale struct {
|
||||
language uint16
|
||||
country uint16
|
||||
}
|
||||
screenType struct {
|
||||
orientation uint8
|
||||
touchscreen uint8
|
||||
density uint16
|
||||
}
|
||||
input struct {
|
||||
keyboard uint8
|
||||
navigation uint8
|
||||
inputFlags uint8
|
||||
inputPad0 uint8
|
||||
}
|
||||
screenSize struct {
|
||||
width uint16
|
||||
height uint16
|
||||
}
|
||||
version struct {
|
||||
sdk uint16
|
||||
minor uint16 // always 0
|
||||
}
|
||||
screenConfig struct {
|
||||
layout uint8
|
||||
uiMode uint8
|
||||
smallestWidthDP uint16
|
||||
}
|
||||
screenSizeDP struct {
|
||||
width uint16
|
||||
height uint16
|
||||
}
|
||||
}
|
||||
|
||||
indices []uint32 // values that map to typePool
|
||||
entries []*Entry
|
||||
}
|
||||
|
||||
func (typ *Type) UnmarshalBinary(bin []byte) error {
|
||||
if err := (&typ.chunkHeader).UnmarshalBinary(bin); err != nil {
|
||||
return err
|
||||
}
|
||||
if typ.typ != ResTableType {
|
||||
return errWrongType(typ.typ, ResTableType)
|
||||
}
|
||||
|
||||
typ.id = uint8(bin[8])
|
||||
typ.res0 = uint8(bin[9])
|
||||
typ.res1 = btou16(bin[10:])
|
||||
typ.entryCount = btou32(bin[12:])
|
||||
typ.entriesStart = btou32(bin[16:])
|
||||
|
||||
if typ.res0 != 0 || typ.res1 != 0 {
|
||||
return fmt.Errorf("res0 res1 not zero")
|
||||
}
|
||||
|
||||
typ.config.size = btou32(bin[20:])
|
||||
typ.config.imsi.mcc = btou16(bin[24:])
|
||||
typ.config.imsi.mnc = btou16(bin[26:])
|
||||
typ.config.locale.language = btou16(bin[28:])
|
||||
typ.config.locale.country = btou16(bin[30:])
|
||||
typ.config.screenType.orientation = uint8(bin[32])
|
||||
typ.config.screenType.touchscreen = uint8(bin[33])
|
||||
typ.config.screenType.density = btou16(bin[34:])
|
||||
typ.config.input.keyboard = uint8(bin[36])
|
||||
typ.config.input.navigation = uint8(bin[37])
|
||||
typ.config.input.inputFlags = uint8(bin[38])
|
||||
typ.config.input.inputPad0 = uint8(bin[39])
|
||||
typ.config.screenSize.width = btou16(bin[40:])
|
||||
typ.config.screenSize.height = btou16(bin[42:])
|
||||
typ.config.version.sdk = btou16(bin[44:])
|
||||
typ.config.version.minor = btou16(bin[46:])
|
||||
typ.config.screenConfig.layout = uint8(bin[48])
|
||||
typ.config.screenConfig.uiMode = uint8(bin[49])
|
||||
typ.config.screenConfig.smallestWidthDP = btou16(bin[50:])
|
||||
typ.config.screenSizeDP.width = btou16(bin[52:])
|
||||
typ.config.screenSizeDP.height = btou16(bin[54:])
|
||||
|
||||
// fmt.Println("language/country:", u16tos(typ.config.locale.language), u16tos(typ.config.locale.country))
|
||||
|
||||
buf := bin[typ.headerByteSize:typ.entriesStart]
|
||||
if len(buf) != 4*int(typ.entryCount) {
|
||||
return fmt.Errorf("index buffer len[%v] doesn't match entryCount[%v]", len(buf), typ.entryCount)
|
||||
}
|
||||
|
||||
typ.indices = make([]uint32, typ.entryCount)
|
||||
for i := range typ.indices {
|
||||
typ.indices[i] = btou32(buf[i*4:])
|
||||
}
|
||||
|
||||
typ.entries = make([]*Entry, typ.entryCount)
|
||||
|
||||
for i, x := range typ.indices {
|
||||
if x == NoEntry {
|
||||
continue
|
||||
}
|
||||
nt := &Entry{}
|
||||
if err := nt.UnmarshalBinary(bin[typ.entriesStart+x:]); err != nil {
|
||||
return err
|
||||
}
|
||||
typ.entries[i] = nt
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (typ *Type) MarshalBinary() ([]byte, error) {
|
||||
bin := make([]byte, 56+len(typ.entries)*4)
|
||||
putu16(bin, uint16(ResTableType))
|
||||
putu16(bin[2:], 56)
|
||||
|
||||
bin[8] = byte(typ.id)
|
||||
// [9] = 0
|
||||
// [10:12] = 0
|
||||
putu32(bin[12:], uint32(len(typ.entries)))
|
||||
putu32(bin[16:], uint32(56+len(typ.entries)*4))
|
||||
|
||||
// assure typ.config.size is always written as 36; extended configuration beyond supported
|
||||
// API level is not supported by this marshal implementation but will be forward-compatible.
|
||||
putu32(bin[20:], 36)
|
||||
|
||||
putu16(bin[24:], typ.config.imsi.mcc)
|
||||
putu16(bin[26:], typ.config.imsi.mnc)
|
||||
putu16(bin[28:], typ.config.locale.language)
|
||||
putu16(bin[30:], typ.config.locale.country)
|
||||
bin[32] = typ.config.screenType.orientation
|
||||
bin[33] = typ.config.screenType.touchscreen
|
||||
putu16(bin[34:], typ.config.screenType.density)
|
||||
bin[36] = typ.config.input.keyboard
|
||||
bin[37] = typ.config.input.navigation
|
||||
bin[38] = typ.config.input.inputFlags
|
||||
bin[39] = typ.config.input.inputPad0
|
||||
putu16(bin[40:], typ.config.screenSize.width)
|
||||
putu16(bin[42:], typ.config.screenSize.height)
|
||||
putu16(bin[44:], typ.config.version.sdk)
|
||||
putu16(bin[46:], typ.config.version.minor)
|
||||
bin[48] = typ.config.screenConfig.layout
|
||||
bin[49] = typ.config.screenConfig.uiMode
|
||||
putu16(bin[50:], typ.config.screenConfig.smallestWidthDP)
|
||||
putu16(bin[52:], typ.config.screenSizeDP.width)
|
||||
putu16(bin[54:], typ.config.screenSizeDP.height)
|
||||
|
||||
var ntbin []byte
|
||||
for i, nt := range typ.entries {
|
||||
if nt == nil { // NoEntry
|
||||
putu32(bin[56+i*4:], NoEntry)
|
||||
continue
|
||||
}
|
||||
putu32(bin[56+i*4:], uint32(len(ntbin)))
|
||||
b, err := nt.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ntbin = append(ntbin, b...)
|
||||
}
|
||||
bin = append(bin, ntbin...)
|
||||
|
||||
putu32(bin[4:], uint32(len(bin)))
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
type StagedAliasEntry struct {
|
||||
stagedID uint32
|
||||
finalizedID uint32
|
||||
}
|
||||
|
||||
func (ae *StagedAliasEntry) MarshalBinary() ([]byte, error) {
|
||||
bin := make([]byte, 8)
|
||||
putu32(bin, ae.stagedID)
|
||||
putu32(bin[4:], ae.finalizedID)
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
func (ae *StagedAliasEntry) UnmarshalBinary(bin []byte) error {
|
||||
ae.stagedID = btou32(bin)
|
||||
ae.finalizedID = btou32(bin[4:])
|
||||
return nil
|
||||
}
|
||||
|
||||
type StagedAlias struct {
|
||||
chunkHeader
|
||||
count uint32
|
||||
entries []StagedAliasEntry
|
||||
}
|
||||
|
||||
func (a *StagedAlias) UnmarshalBinary(bin []byte) error {
|
||||
if err := (&a.chunkHeader).UnmarshalBinary(bin); err != nil {
|
||||
return err
|
||||
}
|
||||
if a.typ != ResTableStagedAlias {
|
||||
return errWrongType(a.typ, ResTableStagedAlias)
|
||||
}
|
||||
a.count = btou32(bin[8:])
|
||||
a.entries = make([]StagedAliasEntry, a.count)
|
||||
for i := range a.entries {
|
||||
if err := a.entries[i].UnmarshalBinary(bin[12+i*8:]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *StagedAlias) MarshalBinary() ([]byte, error) {
|
||||
chunkHeaderBin, err := a.chunkHeader.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
countBin := make([]byte, 4)
|
||||
putu32(countBin, a.count)
|
||||
bin := append(chunkHeaderBin, countBin...)
|
||||
for _, entry := range a.entries {
|
||||
entryBin, err := entry.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bin = append(bin, entryBin...)
|
||||
}
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
// Entry is a resource key typically followed by a value or resource map.
|
||||
type Entry struct {
|
||||
size uint16
|
||||
flags uint16
|
||||
key PoolRef // ref into key pool
|
||||
|
||||
// only filled if this is a map entry; when size is 16
|
||||
parent TableRef // id of parent mapping or zero if none
|
||||
count uint32 // name and value pairs that follow for FlagComplex
|
||||
|
||||
values []*Value
|
||||
}
|
||||
|
||||
func (nt *Entry) UnmarshalBinary(bin []byte) error {
|
||||
nt.size = btou16(bin)
|
||||
nt.flags = btou16(bin[2:])
|
||||
nt.key = PoolRef(btou32(bin[4:]))
|
||||
|
||||
if nt.size == 16 {
|
||||
nt.parent = TableRef(btou32(bin[8:]))
|
||||
nt.count = btou32(bin[12:])
|
||||
nt.values = make([]*Value, nt.count)
|
||||
for i := range nt.values {
|
||||
val := &Value{}
|
||||
if err := val.UnmarshalBinary(bin[16+i*12:]); err != nil {
|
||||
return err
|
||||
}
|
||||
nt.values[i] = val
|
||||
}
|
||||
} else {
|
||||
data := &Data{}
|
||||
if err := data.UnmarshalBinary(bin[8:]); err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO boxing data not strictly correct as binary repr isn't of Value.
|
||||
nt.values = append(nt.values, &Value{0, data})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nt *Entry) MarshalBinary() ([]byte, error) {
|
||||
bin := make([]byte, 8)
|
||||
sz := nt.size
|
||||
if sz == 0 {
|
||||
sz = 8
|
||||
}
|
||||
putu16(bin, sz)
|
||||
putu16(bin[2:], nt.flags)
|
||||
putu32(bin[4:], uint32(nt.key))
|
||||
|
||||
if sz == 16 {
|
||||
bin = append(bin, make([]byte, 8+len(nt.values)*12)...)
|
||||
putu32(bin[8:], uint32(nt.parent))
|
||||
putu32(bin[12:], uint32(len(nt.values)))
|
||||
for i, val := range nt.values {
|
||||
b, err := val.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(bin[16+i*12:], b)
|
||||
}
|
||||
} else {
|
||||
b, err := nt.values[0].data.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bin = append(bin, b...)
|
||||
}
|
||||
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
type Value struct {
|
||||
name TableRef
|
||||
data *Data
|
||||
}
|
||||
|
||||
func (val *Value) UnmarshalBinary(bin []byte) error {
|
||||
val.name = TableRef(btou32(bin))
|
||||
val.data = &Data{}
|
||||
return val.data.UnmarshalBinary(bin[4:])
|
||||
}
|
||||
|
||||
func (val *Value) MarshalBinary() ([]byte, error) {
|
||||
bin := make([]byte, 12)
|
||||
putu32(bin, uint32(val.name))
|
||||
b, err := val.data.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(bin[4:], b)
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
type DataType uint8
|
||||
|
||||
// explicitly defined for clarity and resolvability with apt source
|
||||
const (
|
||||
DataNull DataType = 0x00 // either 0 or 1 for resource undefined or empty
|
||||
DataReference DataType = 0x01 // ResTable_ref, a reference to another resource table entry
|
||||
DataAttribute DataType = 0x02 // attribute resource identifier
|
||||
DataString DataType = 0x03 // index into the containing resource table's global value string pool
|
||||
DataFloat DataType = 0x04 // single-precision floating point number
|
||||
DataDimension DataType = 0x05 // complex number encoding a dimension value, such as "100in"
|
||||
DataFraction DataType = 0x06 // complex number encoding a fraction of a container
|
||||
DataDynamicReference DataType = 0x07 // dynamic ResTable_ref, which needs to be resolved before it can be used like a TYPE_REFERENCE.
|
||||
DataIntDec DataType = 0x10 // raw integer value of the form n..n
|
||||
DataIntHex DataType = 0x11 // raw integer value of the form 0xn..n
|
||||
DataIntBool DataType = 0x12 // either 0 or 1, for input "false" or "true"
|
||||
DataIntColorARGB8 DataType = 0x1c // raw integer value of the form #aarrggbb
|
||||
DataIntColorRGB8 DataType = 0x1d // raw integer value of the form #rrggbb
|
||||
DataIntColorARGB4 DataType = 0x1e // raw integer value of the form #argb
|
||||
DataIntColorRGB4 DataType = 0x1f // raw integer value of the form #rgb
|
||||
)
|
||||
|
||||
type Data struct {
|
||||
ByteSize uint16
|
||||
Res0 uint8 // always 0, useful for debugging bad read offsets
|
||||
Type DataType
|
||||
Value uint32
|
||||
}
|
||||
|
||||
func (d *Data) UnmarshalBinary(bin []byte) error {
|
||||
d.ByteSize = btou16(bin)
|
||||
d.Res0 = uint8(bin[2])
|
||||
d.Type = DataType(bin[3])
|
||||
d.Value = btou32(bin[4:])
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Data) MarshalBinary() ([]byte, error) {
|
||||
bin := make([]byte, 8)
|
||||
putu16(bin, 8)
|
||||
bin[2] = byte(d.Res0)
|
||||
bin[3] = byte(d.Type)
|
||||
putu32(bin[4:], d.Value)
|
||||
return bin, nil
|
||||
}
|
BIN
internal/binres/testdata/bootstrap-res/mipmap-xxxhdpi/icon.png
vendored
Normal file
BIN
internal/binres/testdata/bootstrap-res/mipmap-xxxhdpi/icon.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 486 B |
BIN
internal/binres/testdata/bootstrap.arsc
vendored
Normal file
BIN
internal/binres/testdata/bootstrap.arsc
vendored
Normal file
Binary file not shown.
BIN
internal/binres/testdata/bootstrap.bin
vendored
Normal file
BIN
internal/binres/testdata/bootstrap.bin
vendored
Normal file
Binary file not shown.
38
internal/binres/testdata/bootstrap.xml
vendored
Normal file
38
internal/binres/testdata/bootstrap.xml
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2014 The Go Authors. All rights reserved.
|
||||
Use of this source code is governed by a BSD-style
|
||||
license that can be found in the LICENSE file.
|
||||
-->
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.zentus.balloon"
|
||||
android:versionCode="42"
|
||||
android:versionName=""
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:label="Balloon世界"
|
||||
android:allowBackup="true"
|
||||
android:hasCode="false"
|
||||
android:icon="@mipmap/icon"
|
||||
foo="bar"
|
||||
android:debuggable="true"
|
||||
baz="bar"
|
||||
tools:strict="label">
|
||||
<activity android:name="android.app.NativeActivity"
|
||||
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
|
||||
android:label="Balloon"
|
||||
android:screenOrientation="portrait"
|
||||
android:configChanges="orientation|keyboardHidden">
|
||||
<meta-data android:name="android.app.lib_name" android:value="balloon" />
|
||||
<intent-filter>
|
||||
here is some text
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
20
internal/binres/testdata/gen.sh
vendored
Executable file
20
internal/binres/testdata/gen.sh
vendored
Executable file
|
@ -0,0 +1,20 @@
|
|||
#! /usr/bin/env bash
|
||||
|
||||
# version of build-tools tests run against
|
||||
AAPT=${ANDROID_HOME:-${HOME}/Android/Sdk}/build-tools/32.0.0/aapt
|
||||
|
||||
# minimum version of android api for resource identifiers supported
|
||||
APIJAR=${ANDROID_HOME:-${HOME}/Android/Sdk}/platforms/android-16/android.jar
|
||||
|
||||
for f in *.xml; do
|
||||
RES=""
|
||||
if [ -d "${f:0:-4}-res" ]; then
|
||||
RES="-S ${f:0:-4}-res"
|
||||
fi
|
||||
cp "$f" AndroidManifest.xml
|
||||
"$AAPT" p -M AndroidManifest.xml $RES -I "$APIJAR" -F tmp.apk
|
||||
unzip -qq -o tmp.apk AndroidManifest.xml resources.arsc
|
||||
mv AndroidManifest.xml "${f:0:-3}bin"
|
||||
mv resources.arsc "${f:0:-3}arsc"
|
||||
rm tmp.apk
|
||||
done
|
248
internal/importers/ast.go
Normal file
248
internal/importers/ast.go
Normal file
|
@ -0,0 +1,248 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// The importers package uses go/ast to analyze Go packages or Go files
|
||||
// and collect references to types whose package has a package prefix.
|
||||
// It is used by the language specific importers to determine the set of
|
||||
// wrapper types to be generated.
|
||||
//
|
||||
// # For example, in the Go file
|
||||
//
|
||||
// package javaprogram
|
||||
//
|
||||
// import "Java/java/lang"
|
||||
//
|
||||
// func F() {
|
||||
//
|
||||
// o := lang.Object.New()
|
||||
// ...
|
||||
//
|
||||
// }
|
||||
//
|
||||
// the java importer uses this package to determine that the "java/lang"
|
||||
// package and the wrapper interface, lang.Object, needs to be generated.
|
||||
// After calling AnalyzeFile or AnalyzePackages, the References result
|
||||
// contains the reference to lang.Object and the names set will contain
|
||||
// "New".
|
||||
package importers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"path"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
// References is the result of analyzing a Go file or set of Go packages.
|
||||
//
|
||||
// # For example, the Go file
|
||||
//
|
||||
// package pkg
|
||||
//
|
||||
// import "Prefix/some/Package"
|
||||
//
|
||||
// var A = Package.Identifier
|
||||
//
|
||||
// Will result in a single PkgRef with the "some/Package" package and
|
||||
// the Identifier name. The Names set will contain the single name,
|
||||
// "Identifier".
|
||||
type References struct {
|
||||
// The list of references to identifiers in packages that are
|
||||
// identified by a package prefix.
|
||||
Refs []PkgRef
|
||||
// The list of names used in at least one selector expression.
|
||||
// Useful as a conservative upper bound on the set of identifiers
|
||||
// referenced from a set of packages.
|
||||
Names map[string]struct{}
|
||||
// Embedders is a list of struct types with prefixed types
|
||||
// embedded.
|
||||
Embedders []Struct
|
||||
}
|
||||
|
||||
// Struct is a representation of a struct type with embedded
|
||||
// types.
|
||||
type Struct struct {
|
||||
Name string
|
||||
Pkg string
|
||||
PkgPath string
|
||||
Refs []PkgRef
|
||||
}
|
||||
|
||||
// PkgRef is a reference to an identifier in a package.
|
||||
type PkgRef struct {
|
||||
Name string
|
||||
Pkg string
|
||||
}
|
||||
|
||||
type refsSaver struct {
|
||||
pkgPrefix string
|
||||
*References
|
||||
refMap map[PkgRef]struct{}
|
||||
insideStruct bool
|
||||
}
|
||||
|
||||
// AnalyzeFile scans the provided file for references to packages with the given
|
||||
// package prefix. The list of unique (package, identifier) pairs is returned
|
||||
func AnalyzeFile(file *ast.File, pkgPrefix string) (*References, error) {
|
||||
visitor := newRefsSaver(pkgPrefix)
|
||||
fset := token.NewFileSet()
|
||||
files := map[string]*ast.File{file.Name.Name: file}
|
||||
// Ignore errors (from unknown packages)
|
||||
pkg, _ := ast.NewPackage(fset, files, visitor.importer(), nil)
|
||||
ast.Walk(visitor, pkg)
|
||||
visitor.findEmbeddingStructs("", pkg)
|
||||
return visitor.References, nil
|
||||
}
|
||||
|
||||
// AnalyzePackages scans the provided packages for references to packages with the given
|
||||
// package prefix. The list of unique (package, identifier) pairs is returned
|
||||
func AnalyzePackages(pkgs []*packages.Package, pkgPrefix string) (*References, error) {
|
||||
visitor := newRefsSaver(pkgPrefix)
|
||||
imp := visitor.importer()
|
||||
fset := token.NewFileSet()
|
||||
for _, pkg := range pkgs {
|
||||
files := make(map[string]*ast.File)
|
||||
for i, name := range pkg.GoFiles {
|
||||
files[name] = pkg.Syntax[i]
|
||||
}
|
||||
// Ignore errors (from unknown packages)
|
||||
astpkg, _ := ast.NewPackage(fset, files, imp, nil)
|
||||
ast.Walk(visitor, astpkg)
|
||||
visitor.findEmbeddingStructs(pkg.PkgPath, astpkg)
|
||||
}
|
||||
return visitor.References, nil
|
||||
}
|
||||
|
||||
// findEmbeddingStructs finds all top level declarations embedding a prefixed type.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// import "Prefix/some/Package"
|
||||
//
|
||||
// type T struct {
|
||||
//
|
||||
// Package.Class
|
||||
//
|
||||
// }
|
||||
func (v *refsSaver) findEmbeddingStructs(pkgpath string, pkg *ast.Package) {
|
||||
var names []string
|
||||
for _, obj := range pkg.Scope.Objects {
|
||||
if obj.Kind != ast.Typ || !ast.IsExported(obj.Name) {
|
||||
continue
|
||||
}
|
||||
names = append(names, obj.Name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
obj := pkg.Scope.Objects[name]
|
||||
|
||||
t, ok := obj.Decl.(*ast.TypeSpec).Type.(*ast.StructType)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
var refs []PkgRef
|
||||
for _, f := range t.Fields.List {
|
||||
sel, ok := f.Type.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
ref, ok := v.addRef(sel)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if len(f.Names) > 0 && !f.Names[0].IsExported() {
|
||||
continue
|
||||
}
|
||||
refs = append(refs, ref)
|
||||
}
|
||||
if len(refs) > 0 {
|
||||
v.Embedders = append(v.Embedders, Struct{
|
||||
Name: obj.Name,
|
||||
Pkg: pkg.Name,
|
||||
PkgPath: pkgpath,
|
||||
|
||||
Refs: refs,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newRefsSaver(pkgPrefix string) *refsSaver {
|
||||
s := &refsSaver{
|
||||
pkgPrefix: pkgPrefix,
|
||||
refMap: make(map[PkgRef]struct{}),
|
||||
References: &References{},
|
||||
}
|
||||
s.Names = make(map[string]struct{})
|
||||
return s
|
||||
}
|
||||
|
||||
func (v *refsSaver) importer() ast.Importer {
|
||||
return func(imports map[string]*ast.Object, pkgPath string) (*ast.Object, error) {
|
||||
if pkg, exists := imports[pkgPath]; exists {
|
||||
return pkg, nil
|
||||
}
|
||||
if !strings.HasPrefix(pkgPath, v.pkgPrefix) {
|
||||
return nil, errors.New("ignored")
|
||||
}
|
||||
pkg := ast.NewObj(ast.Pkg, path.Base(pkgPath))
|
||||
imports[pkgPath] = pkg
|
||||
return pkg, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (v *refsSaver) addRef(sel *ast.SelectorExpr) (PkgRef, bool) {
|
||||
x, ok := sel.X.(*ast.Ident)
|
||||
if !ok || x.Obj == nil {
|
||||
return PkgRef{}, false
|
||||
}
|
||||
imp, ok := x.Obj.Decl.(*ast.ImportSpec)
|
||||
if !ok {
|
||||
return PkgRef{}, false
|
||||
}
|
||||
pkgPath, err := strconv.Unquote(imp.Path.Value)
|
||||
if err != nil {
|
||||
return PkgRef{}, false
|
||||
}
|
||||
if !strings.HasPrefix(pkgPath, v.pkgPrefix) {
|
||||
return PkgRef{}, false
|
||||
}
|
||||
pkgPath = pkgPath[len(v.pkgPrefix):]
|
||||
ref := PkgRef{Pkg: pkgPath, Name: sel.Sel.Name}
|
||||
if _, exists := v.refMap[ref]; !exists {
|
||||
v.refMap[ref] = struct{}{}
|
||||
v.Refs = append(v.Refs, ref)
|
||||
}
|
||||
return ref, true
|
||||
}
|
||||
|
||||
func (v *refsSaver) Visit(n ast.Node) ast.Visitor {
|
||||
switch n := n.(type) {
|
||||
case *ast.StructType:
|
||||
// Use a copy of refsSaver that only accepts exported fields. It refers
|
||||
// to the original refsSaver for collecting references.
|
||||
v2 := *v
|
||||
v2.insideStruct = true
|
||||
return &v2
|
||||
case *ast.Field:
|
||||
if v.insideStruct && len(n.Names) == 1 && !n.Names[0].IsExported() {
|
||||
return nil
|
||||
}
|
||||
case *ast.SelectorExpr:
|
||||
v.Names[n.Sel.Name] = struct{}{}
|
||||
if _, ok := v.addRef(n); ok {
|
||||
return nil
|
||||
}
|
||||
case *ast.FuncDecl:
|
||||
if n.Recv != nil { // Methods
|
||||
v.Names[n.Name.Name] = struct{}{}
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
69
internal/importers/ast_test.go
Normal file
69
internal/importers/ast_test.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package importers
|
||||
|
||||
import (
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAnalyzer(t *testing.T) {
|
||||
file := `package ast_test
|
||||
|
||||
import "Prefix/some/pkg/Name"
|
||||
import "Prefix/some/pkg/Name2"
|
||||
|
||||
const c = Name.Constant
|
||||
|
||||
type T struct {
|
||||
Name.Type
|
||||
unexported Name.Type2
|
||||
}
|
||||
|
||||
func f() {
|
||||
Name2.Func().Func().Func()
|
||||
}
|
||||
`
|
||||
fset := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset, "ast_test.go", file, parser.AllErrors)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
refs, err := AnalyzeFile(f, "Prefix/")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
exps := []PkgRef{
|
||||
{Pkg: "some/pkg/Name", Name: "Constant"},
|
||||
{Pkg: "some/pkg/Name", Name: "Type"},
|
||||
{Pkg: "some/pkg/Name2", Name: "Func"},
|
||||
{Pkg: "some/pkg/Name", Name: "Type2"},
|
||||
}
|
||||
if len(refs.Refs) != len(exps) {
|
||||
t.Fatalf("expected %d references; got %d", len(exps), len(refs.Refs))
|
||||
}
|
||||
for i, exp := range exps {
|
||||
if got := refs.Refs[i]; exp != got {
|
||||
t.Errorf("expected ref %v; got %v", exp, got)
|
||||
}
|
||||
}
|
||||
if _, exists := refs.Names["Constant"]; !exists {
|
||||
t.Errorf("expected \"Constant\" in the names set")
|
||||
}
|
||||
if len(refs.Embedders) != 1 {
|
||||
t.Fatalf("expected 1 struct; got %d", len(refs.Embedders))
|
||||
}
|
||||
s := refs.Embedders[0]
|
||||
exp := Struct{
|
||||
Name: "T",
|
||||
Pkg: "ast_test",
|
||||
Refs: []PkgRef{{Pkg: "some/pkg/Name", Name: "Type"}},
|
||||
}
|
||||
if !reflect.DeepEqual(exp, s) {
|
||||
t.Errorf("expected struct %v; got %v", exp, s)
|
||||
}
|
||||
}
|
1183
internal/importers/java/java.go
Normal file
1183
internal/importers/java/java.go
Normal file
File diff suppressed because it is too large
Load diff
206
internal/importers/java/java_test.go
Normal file
206
internal/importers/java/java_test.go
Normal file
|
@ -0,0 +1,206 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package java
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/mobile/internal/importers"
|
||||
)
|
||||
|
||||
func TestImport(t *testing.T) {
|
||||
if !IsAvailable() {
|
||||
t.Skipf("javap not available")
|
||||
}
|
||||
tests := []struct {
|
||||
ref importers.PkgRef
|
||||
name string
|
||||
methods []*FuncSet
|
||||
}{
|
||||
{
|
||||
ref: importers.PkgRef{Pkg: "java/lang/Object", Name: "equals"},
|
||||
name: "java.lang.Object",
|
||||
methods: []*FuncSet{
|
||||
&FuncSet{
|
||||
Name: "equals",
|
||||
GoName: "Equals",
|
||||
CommonSig: CommonSig{
|
||||
Params: []*Type{&Type{Kind: Object, Class: "java.lang.Object"}}, Ret: &Type{Kind: Boolean}, HasRet: true,
|
||||
},
|
||||
Funcs: []*Func{&Func{FuncSig: FuncSig{Name: "equals", Desc: "(Ljava/lang/Object;)Z"}, ArgDesc: "Ljava/lang/Object;", JNIName: "equals", Public: true, Params: []*Type{&Type{Kind: Object, Class: "java.lang.Object"}}, Ret: &Type{Kind: Boolean}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ref: importers.PkgRef{Pkg: "java/lang/Runnable", Name: "run"},
|
||||
name: "java.lang.Runnable",
|
||||
methods: []*FuncSet{
|
||||
&FuncSet{
|
||||
Name: "run",
|
||||
GoName: "Run",
|
||||
CommonSig: CommonSig{},
|
||||
Funcs: []*Func{&Func{FuncSig: FuncSig{Name: "run", Desc: "()V"}, ArgDesc: "", JNIName: "run", Public: true, Abstract: true}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
toString := &FuncSet{
|
||||
Name: "toString",
|
||||
GoName: "ToString",
|
||||
CommonSig: CommonSig{
|
||||
Ret: &Type{Kind: String}, HasRet: true,
|
||||
},
|
||||
Funcs: []*Func{&Func{FuncSig: FuncSig{Name: "toString", Desc: "()Ljava/lang/String;"}, ArgDesc: "", JNIName: "toString", Public: true, Ret: &Type{Kind: String}}},
|
||||
}
|
||||
for _, test := range tests {
|
||||
refs := &importers.References{
|
||||
Refs: []importers.PkgRef{test.ref},
|
||||
Names: make(map[string]struct{}),
|
||||
}
|
||||
for _, m := range test.methods {
|
||||
refs.Names[m.GoName] = struct{}{}
|
||||
}
|
||||
classes, err := (&Importer{}).Import(refs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(classes) != 1 {
|
||||
t.Fatalf("got %d classes, expected 1", len(classes))
|
||||
}
|
||||
cls := classes[0]
|
||||
if cls.Name != test.name {
|
||||
t.Errorf("got class name %s, expected %s", cls.Name, test.name)
|
||||
}
|
||||
methods := test.methods
|
||||
if !cls.Interface {
|
||||
methods = append(methods, toString)
|
||||
}
|
||||
loop:
|
||||
for _, exp := range methods {
|
||||
for _, got := range cls.AllMethods {
|
||||
if reflect.DeepEqual(exp, got) {
|
||||
continue loop
|
||||
}
|
||||
}
|
||||
t.Errorf("failed to find method: %+v", exp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testClsMap() map[string]*Class {
|
||||
//
|
||||
// A--
|
||||
// / \ \
|
||||
// B C \
|
||||
// \ / \ \
|
||||
// D E F
|
||||
//
|
||||
return map[string]*Class{
|
||||
"A": &Class{},
|
||||
"B": &Class{
|
||||
Supers: []string{"A"},
|
||||
},
|
||||
"C": &Class{
|
||||
Supers: []string{"A"},
|
||||
},
|
||||
"D": &Class{
|
||||
Supers: []string{"B", "C"},
|
||||
},
|
||||
"E": &Class{
|
||||
Supers: []string{"C"},
|
||||
},
|
||||
"F": &Class{
|
||||
Supers: []string{"A"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommonTypes(t *testing.T) {
|
||||
clsMap := testClsMap()
|
||||
tests := [][3]*Type{
|
||||
{nil, nil, nil},
|
||||
{&Type{Kind: Int}, nil, nil},
|
||||
{&Type{Kind: Int}, &Type{Kind: Float}, nil},
|
||||
{&Type{Kind: Int}, &Type{Kind: Int}, &Type{Kind: Int}},
|
||||
{&Type{Kind: Object, Class: "D"}, &Type{Kind: Object, Class: "D"}, &Type{Kind: Object, Class: "D"}},
|
||||
{&Type{Kind: Object, Class: "D"}, &Type{Kind: Object, Class: "E"}, &Type{Kind: Object, Class: "C"}},
|
||||
{&Type{Kind: Object, Class: "D"}, &Type{Kind: Object, Class: "F"}, &Type{Kind: Object, Class: "A"}},
|
||||
{&Type{Kind: Object, Class: "B"}, &Type{Kind: Object, Class: "E"}, &Type{Kind: Object, Class: "A"}},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t1, t2, exp := test[0], test[1], test[2]
|
||||
got := commonType(clsMap, t1, t2)
|
||||
if !reflect.DeepEqual(got, exp) {
|
||||
t.Errorf("commonType(%+v, %+v) = %+v, expected %+v", t1, t2, got, exp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommonSig(t *testing.T) {
|
||||
tests := []struct {
|
||||
Sigs []CommonSig
|
||||
CommonSig
|
||||
}{
|
||||
{
|
||||
Sigs: []CommonSig{
|
||||
CommonSig{}, // f()
|
||||
},
|
||||
CommonSig: CommonSig{}, // f()
|
||||
},
|
||||
{
|
||||
Sigs: []CommonSig{
|
||||
CommonSig{Throws: true, HasRet: true, Ret: &Type{Kind: Int}}, // int f() throws
|
||||
},
|
||||
// int f() throws
|
||||
CommonSig: CommonSig{Throws: true, HasRet: true, Ret: &Type{Kind: Int}},
|
||||
},
|
||||
{
|
||||
Sigs: []CommonSig{
|
||||
CommonSig{}, // f()
|
||||
CommonSig{Params: []*Type{&Type{Kind: Int}}}, // f(int)
|
||||
},
|
||||
CommonSig: CommonSig{ // f(int...)
|
||||
Variadic: true,
|
||||
Params: []*Type{&Type{Kind: Int}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Sigs: []CommonSig{
|
||||
CommonSig{Params: []*Type{&Type{Kind: Int}}}, // f(int)
|
||||
CommonSig{Params: []*Type{&Type{Kind: Float}}}, // f(float)
|
||||
},
|
||||
CommonSig: CommonSig{ // f(interface{})
|
||||
Params: []*Type{nil},
|
||||
},
|
||||
},
|
||||
{
|
||||
Sigs: []CommonSig{
|
||||
CommonSig{Params: []*Type{&Type{Kind: Int}}}, // f(int)
|
||||
CommonSig{Params: []*Type{&Type{Kind: Int}, &Type{Kind: Int}}}, // f(int, int)
|
||||
},
|
||||
CommonSig: CommonSig{ // f(int, int...)
|
||||
Variadic: true,
|
||||
Params: []*Type{&Type{Kind: Int}, &Type{Kind: Int}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Sigs: []CommonSig{
|
||||
CommonSig{Params: []*Type{&Type{Kind: Object, Class: "A"}}}, // f(A)
|
||||
CommonSig{Params: []*Type{&Type{Kind: Object, Class: "B"}}}, // f(B)
|
||||
},
|
||||
CommonSig: CommonSig{ // f(A)
|
||||
Params: []*Type{&Type{Kind: Object, Class: "A"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
clsMap := testClsMap()
|
||||
for _, test := range tests {
|
||||
got := combineSigs(clsMap, test.Sigs...)
|
||||
if !reflect.DeepEqual(got, test.CommonSig) {
|
||||
t.Errorf("commonSig(%+v) = %+v, expected %+v", test.Sigs, got, test.CommonSig)
|
||||
}
|
||||
}
|
||||
}
|
868
internal/importers/objc/objc.go
Normal file
868
internal/importers/objc/objc.go
Normal file
|
@ -0,0 +1,868 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// The objc package takes the result of an AST traversal by the
|
||||
// importers package and uses the clang command to dump the type
|
||||
// information for the referenced ObjC classes and protocols.
|
||||
//
|
||||
// It is the of go/types for ObjC types and is used by the bind
|
||||
// package to generate Go wrappers for ObjC API on iOS.
|
||||
package objc
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/mobile/internal/importers"
|
||||
)
|
||||
|
||||
type parser struct {
|
||||
sdkPath string
|
||||
sc *bufio.Scanner
|
||||
|
||||
decl string
|
||||
indent int
|
||||
last string
|
||||
// Current module as parsed from the AST tree.
|
||||
module string
|
||||
}
|
||||
|
||||
type TypeKind int
|
||||
|
||||
// Named represents ObjC classes and protocols.
|
||||
type Named struct {
|
||||
Name string
|
||||
GoName string
|
||||
Module string
|
||||
Funcs []*Func
|
||||
Methods []*Func
|
||||
AllMethods []*Func
|
||||
Supers []Super
|
||||
// For deduplication of function or method
|
||||
// declarations.
|
||||
funcMap map[string]struct{}
|
||||
Protocol bool
|
||||
// Generated is true if the type is wrapper of a
|
||||
// generated Go struct.
|
||||
Generated bool
|
||||
}
|
||||
|
||||
// Super denotes a super class or protocol.
|
||||
type Super struct {
|
||||
Name string
|
||||
Protocol bool
|
||||
}
|
||||
|
||||
// Func is a ObjC method, static functions as well as
|
||||
// instance methods.
|
||||
type Func struct {
|
||||
Sig string
|
||||
GoName string
|
||||
Params []*Param
|
||||
Ret *Type
|
||||
Static bool
|
||||
// Method whose name start with "init"
|
||||
Constructor bool
|
||||
}
|
||||
|
||||
type Param struct {
|
||||
Name string
|
||||
Type *Type
|
||||
}
|
||||
|
||||
type Type struct {
|
||||
Kind TypeKind
|
||||
// For Interface and Protocol types.
|
||||
Name string
|
||||
// For 'id' types.
|
||||
instanceType bool
|
||||
// The declared type raw from the AST.
|
||||
Decl string
|
||||
// Set if the type is a pointer to its kind. For classes
|
||||
// Indirect is true if the type is a double pointer, e.g.
|
||||
// NSObject **.
|
||||
Indirect bool
|
||||
}
|
||||
|
||||
const (
|
||||
Unknown TypeKind = iota
|
||||
Protocol
|
||||
Class
|
||||
String
|
||||
Data
|
||||
Int
|
||||
Uint
|
||||
Short
|
||||
Ushort
|
||||
Bool
|
||||
Char
|
||||
Uchar
|
||||
Float
|
||||
Double
|
||||
)
|
||||
|
||||
// Import returns descriptors for a list of references to
|
||||
// ObjC protocols and classes.
|
||||
//
|
||||
// The type information is parsed from the output of clang -cc1
|
||||
// -ast-dump.
|
||||
func Import(refs *importers.References) ([]*Named, error) {
|
||||
var modules []string
|
||||
modMap := make(map[string]struct{})
|
||||
typeNames := make(map[string][]string)
|
||||
typeSet := make(map[string]struct{})
|
||||
genMods := make(map[string]struct{})
|
||||
for _, emb := range refs.Embedders {
|
||||
genMods[initialUpper(emb.Pkg)] = struct{}{}
|
||||
}
|
||||
for _, ref := range refs.Refs {
|
||||
var module, name string
|
||||
if idx := strings.Index(ref.Pkg, "/"); idx != -1 {
|
||||
// ref is a static method reference.
|
||||
module = ref.Pkg[:idx]
|
||||
name = ref.Pkg[idx+1:]
|
||||
} else {
|
||||
// ref is a type name.
|
||||
module = ref.Pkg
|
||||
name = ref.Name
|
||||
}
|
||||
if _, exists := typeSet[name]; !exists {
|
||||
typeNames[module] = append(typeNames[module], name)
|
||||
typeSet[name] = struct{}{}
|
||||
}
|
||||
if _, exists := modMap[module]; !exists {
|
||||
// Include the module only if it is generated.
|
||||
if _, exists := genMods[module]; !exists {
|
||||
modMap[module] = struct{}{}
|
||||
modules = append(modules, module)
|
||||
}
|
||||
}
|
||||
}
|
||||
sdkPathOut, err := exec.Command("xcrun", "--sdk", "iphonesimulator", "--show-sdk-path").CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sdkPath := strings.TrimSpace(string(sdkPathOut))
|
||||
var allTypes []*Named
|
||||
typeMap := make(map[string]*Named)
|
||||
for _, module := range modules {
|
||||
types, err := importModule(string(sdkPath), module, typeNames[module], typeMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %v", module, err)
|
||||
}
|
||||
allTypes = append(allTypes, types...)
|
||||
}
|
||||
// Embedders refer to every exported Go struct that will have its class
|
||||
// generated. Allow Go code to reverse bind to those classes by synthesizing
|
||||
// their descriptors.
|
||||
for _, emb := range refs.Embedders {
|
||||
module := initialUpper(emb.Pkg)
|
||||
named := &Named{
|
||||
Name: module + emb.Name,
|
||||
GoName: emb.Name,
|
||||
Module: module,
|
||||
Generated: true,
|
||||
}
|
||||
for _, ref := range emb.Refs {
|
||||
t, exists := typeMap[ref.Name]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("type not found: %q", ref.Name)
|
||||
}
|
||||
named.Supers = append(named.Supers, Super{
|
||||
Name: t.Name,
|
||||
Protocol: t.Protocol,
|
||||
})
|
||||
}
|
||||
typeMap[emb.Name] = named
|
||||
allTypes = append(allTypes, named)
|
||||
}
|
||||
initTypes(allTypes, refs, typeMap)
|
||||
// Include implicit types that are used in parameter or return values.
|
||||
newTypes := allTypes
|
||||
for len(newTypes) > 0 {
|
||||
var impTypes []*Named
|
||||
for _, t := range newTypes {
|
||||
for _, funcs := range [][]*Func{t.Funcs, t.AllMethods} {
|
||||
for _, f := range funcs {
|
||||
types := implicitFuncTypes(f)
|
||||
for _, name := range types {
|
||||
if _, exists := typeSet[name]; exists {
|
||||
continue
|
||||
}
|
||||
typeSet[name] = struct{}{}
|
||||
t, exists := typeMap[name]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("implicit type %q not found", name)
|
||||
}
|
||||
impTypes = append(impTypes, t)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
initTypes(impTypes, refs, typeMap)
|
||||
allTypes = append(allTypes, impTypes...)
|
||||
newTypes = impTypes
|
||||
}
|
||||
return allTypes, nil
|
||||
}
|
||||
|
||||
func implicitFuncTypes(f *Func) []string {
|
||||
var types []string
|
||||
if rt := f.Ret; rt != nil && !rt.instanceType && (rt.Kind == Class || rt.Kind == Protocol) {
|
||||
types = append(types, rt.Name)
|
||||
}
|
||||
for _, p := range f.Params {
|
||||
if t := p.Type; !t.instanceType && (t.Kind == Class || t.Kind == Protocol) {
|
||||
types = append(types, t.Name)
|
||||
}
|
||||
}
|
||||
return types
|
||||
}
|
||||
|
||||
func initTypes(types []*Named, refs *importers.References, typeMap map[string]*Named) {
|
||||
for _, t := range types {
|
||||
fillAllMethods(t, typeMap)
|
||||
}
|
||||
// Move constructors to functions. They are represented in Go
|
||||
// as functions.
|
||||
for _, t := range types {
|
||||
var methods []*Func
|
||||
for _, f := range t.AllMethods {
|
||||
if f.Constructor {
|
||||
f.Static = true
|
||||
t.Funcs = append(t.Funcs, f)
|
||||
} else {
|
||||
methods = append(methods, f)
|
||||
}
|
||||
}
|
||||
t.AllMethods = methods
|
||||
}
|
||||
for _, t := range types {
|
||||
mangleMethodNames(t.AllMethods)
|
||||
mangleMethodNames(t.Funcs)
|
||||
}
|
||||
filterReferences(types, refs, typeMap)
|
||||
for _, t := range types {
|
||||
resolveInstanceTypes(t, t.Funcs)
|
||||
resolveInstanceTypes(t, t.AllMethods)
|
||||
}
|
||||
}
|
||||
|
||||
func filterReferences(types []*Named, refs *importers.References, typeMap map[string]*Named) {
|
||||
refFuncs := make(map[[2]string]struct{})
|
||||
for _, ref := range refs.Refs {
|
||||
if sep := strings.Index(ref.Pkg, "/"); sep != -1 {
|
||||
pkgName := ref.Pkg[sep+1:]
|
||||
n := typeMap[pkgName]
|
||||
if n == nil {
|
||||
continue
|
||||
}
|
||||
refFuncs[[...]string{pkgName, ref.Name}] = struct{}{}
|
||||
}
|
||||
}
|
||||
for _, t := range types {
|
||||
var filtered []*Func
|
||||
for _, f := range t.Funcs {
|
||||
if _, exists := refFuncs[[...]string{t.GoName, f.GoName}]; exists {
|
||||
filtered = append(filtered, f)
|
||||
}
|
||||
}
|
||||
t.Funcs = filtered
|
||||
filtered = nil
|
||||
for _, m := range t.Methods {
|
||||
if _, exists := refs.Names[m.GoName]; exists {
|
||||
filtered = append(filtered, m)
|
||||
}
|
||||
}
|
||||
t.Methods = filtered
|
||||
filtered = nil
|
||||
for _, m := range t.AllMethods {
|
||||
if _, exists := refs.Names[m.GoName]; exists {
|
||||
filtered = append(filtered, m)
|
||||
}
|
||||
}
|
||||
t.AllMethods = filtered
|
||||
}
|
||||
}
|
||||
|
||||
// mangleMethodNames assigns unique Go names to ObjC methods. If a method name is unique
|
||||
// within the same method list, its name is used with its first letter in upper case.
|
||||
// Multiple methods with the same name have their full signature appended, with : removed.
|
||||
func mangleMethodNames(allFuncs []*Func) {
|
||||
goName := func(n string, constructor bool) string {
|
||||
if constructor {
|
||||
n = "new" + n[len("init"):]
|
||||
}
|
||||
return initialUpper(n)
|
||||
}
|
||||
overloads := make(map[string][]*Func)
|
||||
for i, f := range allFuncs {
|
||||
// Copy function so each class can have its own
|
||||
// name mangling.
|
||||
f := *f
|
||||
allFuncs[i] = &f
|
||||
f.GoName = goName(f.Sig, f.Constructor)
|
||||
if colon := strings.Index(f.GoName, ":"); colon != -1 {
|
||||
f.GoName = f.GoName[:colon]
|
||||
}
|
||||
overloads[f.GoName] = append(overloads[f.GoName], &f)
|
||||
}
|
||||
fallbacks := make(map[string][]*Func)
|
||||
for _, funcs := range overloads {
|
||||
if len(funcs) == 1 {
|
||||
continue
|
||||
}
|
||||
for _, f := range funcs {
|
||||
sig := f.Sig
|
||||
if strings.HasSuffix(sig, ":") {
|
||||
sig = sig[:len(sig)-1]
|
||||
}
|
||||
sigElems := strings.Split(f.Sig, ":")
|
||||
for i := 0; i < len(sigElems); i++ {
|
||||
sigElems[i] = initialUpper(sigElems[i])
|
||||
}
|
||||
name := strings.Join(sigElems, "")
|
||||
f.GoName = goName(name, f.Constructor)
|
||||
fallbacks[f.GoName] = append(fallbacks[f.GoName], f)
|
||||
}
|
||||
}
|
||||
for _, funcs := range fallbacks {
|
||||
if len(funcs) == 1 {
|
||||
continue
|
||||
}
|
||||
for _, f := range funcs {
|
||||
name := strings.Replace(f.Sig, ":", "_", -1)
|
||||
f.GoName = goName(name, f.Constructor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func resolveInstanceType(n *Named, t *Type) *Type {
|
||||
if !t.instanceType || t.Kind != Protocol {
|
||||
return t
|
||||
}
|
||||
// Copy and update the type name for instancetype types
|
||||
ct := *t
|
||||
ct.instanceType = false
|
||||
ct.Decl = n.Name + " *"
|
||||
if n.Name == "NSString" {
|
||||
ct.Kind = String
|
||||
ct.Name = ""
|
||||
} else {
|
||||
ct.Kind = Class
|
||||
ct.Name = n.Name
|
||||
}
|
||||
return &ct
|
||||
}
|
||||
|
||||
func resolveInstanceTypes(n *Named, funcs []*Func) {
|
||||
for _, f := range funcs {
|
||||
for _, p := range f.Params {
|
||||
p.Type = resolveInstanceType(n, p.Type)
|
||||
}
|
||||
if f.Ret != nil {
|
||||
f.Ret = resolveInstanceType(n, f.Ret)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fillAllMethods(n *Named, typeMap map[string]*Named) {
|
||||
if len(n.AllMethods) > 0 {
|
||||
return
|
||||
}
|
||||
if len(n.Supers) == 0 {
|
||||
n.AllMethods = n.Methods
|
||||
return
|
||||
}
|
||||
for _, sup := range n.Supers {
|
||||
super := lookup(sup.Name, sup.Protocol, typeMap)
|
||||
fillAllMethods(super, typeMap)
|
||||
}
|
||||
methods := make(map[string]struct{})
|
||||
for _, sup := range n.Supers {
|
||||
super := lookup(sup.Name, sup.Protocol, typeMap)
|
||||
for _, f := range super.AllMethods {
|
||||
if _, exists := methods[f.Sig]; !exists {
|
||||
methods[f.Sig] = struct{}{}
|
||||
n.AllMethods = append(n.AllMethods, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, f := range n.Methods {
|
||||
if _, exists := methods[f.Sig]; !exists {
|
||||
n.AllMethods = append(n.AllMethods, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
frameworksPath = "/System/Library/Frameworks/"
|
||||
)
|
||||
|
||||
// importModule parses ObjC type information with clang -cc1 -ast-dump.
|
||||
//
|
||||
// TODO: Use module.map files to precisely model the @import Module.Identifier
|
||||
// directive. For now, importModules assumes the single umbrella header
|
||||
// file Module.framework/Headers/Module.h contains every declaration.
|
||||
func importModule(sdkPath, module string, identifiers []string, typeMap map[string]*Named) ([]*Named, error) {
|
||||
hFile := fmt.Sprintf(sdkPath+frameworksPath+"%s.framework/Headers/%[1]s.h", module)
|
||||
clang := exec.Command("xcrun", "--sdk", "iphonesimulator", "clang", "-cc1", "-triple", "x86_64-apple-ios8.0.0-simulator", "-isysroot", sdkPath, "-ast-dump", "-fblocks", "-fobjc-arc", "-x", "objective-c", hFile)
|
||||
out, err := clang.CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("clang failed to parse module: %v: %s", err, out)
|
||||
}
|
||||
p := &parser{
|
||||
sdkPath: sdkPath,
|
||||
sc: bufio.NewScanner(bytes.NewBuffer(out)),
|
||||
}
|
||||
if err := p.parseModule(module, typeMap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var types []*Named
|
||||
for _, ident := range identifiers {
|
||||
named, exists := typeMap[ident]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("no such type: %s", ident)
|
||||
}
|
||||
types = append(types, named)
|
||||
}
|
||||
return types, nil
|
||||
}
|
||||
|
||||
func (p *parser) scanLine() bool {
|
||||
for {
|
||||
l := p.last
|
||||
if l == "" {
|
||||
if !p.sc.Scan() {
|
||||
return false
|
||||
}
|
||||
l = p.sc.Text()
|
||||
} else {
|
||||
p.last = ""
|
||||
}
|
||||
indent := (strings.Index(l, "-") + 1) / 2
|
||||
switch {
|
||||
case indent > p.indent:
|
||||
// Skip
|
||||
case indent < p.indent:
|
||||
p.indent--
|
||||
p.last = l
|
||||
return false
|
||||
case indent == p.indent:
|
||||
p.decl = l[p.indent*2:]
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) parseModule(module string, typeMap map[string]*Named) (err error) {
|
||||
defer func() {
|
||||
if rerr := recover(); rerr != nil {
|
||||
err = rerr.(error)
|
||||
}
|
||||
}()
|
||||
if !p.scanLine() {
|
||||
return nil
|
||||
}
|
||||
// A header file AST starts with
|
||||
//
|
||||
// TranslationUnitDecl 0x103833ad0 <<invalid sloc>> <invalid sloc>
|
||||
if w := p.scanWord(); w != "TranslationUnitDecl" {
|
||||
return fmt.Errorf("unexpected AST root: %q", w)
|
||||
}
|
||||
p.indent++
|
||||
for {
|
||||
if !p.scanLine() {
|
||||
break
|
||||
}
|
||||
switch w := p.scanWord(); w {
|
||||
case "ObjCCategoryDecl":
|
||||
// ObjCCategoryDecl 0x103d9bdb8 <line:48:1, line:63:2> line:48:12 NSDateCreation
|
||||
// |-ObjCInterface 0x103d9a788 'NSDate'
|
||||
// Skip the node address, the source code range, position.
|
||||
p.scanWord()
|
||||
p.parseLocation()
|
||||
catName := p.scanWord()
|
||||
p.indent++
|
||||
if !p.scanLine() {
|
||||
return fmt.Errorf("no interface for category %s", catName)
|
||||
}
|
||||
if w := p.scanWord(); w != "ObjCInterface" {
|
||||
return fmt.Errorf("unexpected declaaration %s for category %s", w, catName)
|
||||
}
|
||||
p.scanWord()
|
||||
clsName := p.scanWord()
|
||||
clsName = clsName[1 : len(clsName)-1]
|
||||
named := lookup(clsName, false, typeMap)
|
||||
if named == nil {
|
||||
return fmt.Errorf("category %s references unknown class %s", catName, clsName)
|
||||
}
|
||||
p.parseInterface(named)
|
||||
case "ObjCInterfaceDecl", "ObjCProtocolDecl":
|
||||
// ObjCProtocolDecl 0x104116450 <line:15:1, line:47:2> line:15:11 NSObject
|
||||
// or
|
||||
// ObjCInterfaceDecl 0x1041ca480 <line:17:29, line:64:2> line:17:40 UIResponder
|
||||
|
||||
prot := w == "ObjCProtocolDecl"
|
||||
|
||||
// Skip the node address, the source code range, position.
|
||||
p.scanWord()
|
||||
if strings.HasPrefix(p.decl, "prev ") {
|
||||
p.scanWord()
|
||||
p.scanWord()
|
||||
}
|
||||
p.parseLocation()
|
||||
if strings.HasPrefix(p.decl, "implicit ") {
|
||||
p.scanWord()
|
||||
}
|
||||
name := p.decl
|
||||
named := p.lookupOrCreate(name, prot, typeMap)
|
||||
p.indent++
|
||||
p.parseInterface(named)
|
||||
default:
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func lookup(name string, prot bool, typeMap map[string]*Named) *Named {
|
||||
var mangled string
|
||||
if prot {
|
||||
mangled = name + "P"
|
||||
} else {
|
||||
mangled = name + "C"
|
||||
}
|
||||
if n := typeMap[mangled]; n != nil {
|
||||
return n
|
||||
}
|
||||
return typeMap[name]
|
||||
}
|
||||
|
||||
// lookupOrCreate looks up the type name in the type map. If it doesn't exist, it creates
|
||||
// and returns a new type. If it does exist, it returns the existing type. If there are both
|
||||
// a class and a protocol with the same name, their type names are mangled by prefixing
|
||||
// 'C' or 'P' and then re-inserted into the type map.
|
||||
func (p *parser) lookupOrCreate(name string, prot bool, typeMap map[string]*Named) *Named {
|
||||
mangled := name + "C"
|
||||
otherMangled := name + "P"
|
||||
if prot {
|
||||
mangled, otherMangled = otherMangled, mangled
|
||||
}
|
||||
named, exists := typeMap[mangled]
|
||||
if exists {
|
||||
return named
|
||||
}
|
||||
named, exists = typeMap[name]
|
||||
if exists {
|
||||
if named.Protocol == prot {
|
||||
return named
|
||||
}
|
||||
// Both a class and a protocol exists with the same name.
|
||||
delete(typeMap, name)
|
||||
named.GoName = otherMangled
|
||||
typeMap[otherMangled] = named
|
||||
named = &Named{
|
||||
GoName: mangled,
|
||||
}
|
||||
} else {
|
||||
named = &Named{
|
||||
GoName: name,
|
||||
}
|
||||
}
|
||||
named.Name = name
|
||||
named.Protocol = prot
|
||||
named.funcMap = make(map[string]struct{})
|
||||
named.Module = p.module
|
||||
typeMap[named.GoName] = named
|
||||
return named
|
||||
}
|
||||
|
||||
func (p *parser) parseInterface(n *Named) {
|
||||
for {
|
||||
more := p.scanLine()
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
switch w := p.scanWord(); w {
|
||||
case "super":
|
||||
if w := p.scanWord(); w != "ObjCInterface" {
|
||||
panic(fmt.Errorf("unknown super type: %s", w))
|
||||
}
|
||||
// Skip node address.
|
||||
p.scanWord()
|
||||
super := p.scanWord()
|
||||
// Remove single quotes
|
||||
super = super[1 : len(super)-1]
|
||||
n.Supers = append(n.Supers, Super{super, false})
|
||||
case "ObjCProtocol":
|
||||
p.scanWord()
|
||||
super := p.scanWord()
|
||||
super = super[1 : len(super)-1]
|
||||
n.Supers = append(n.Supers, Super{super, true})
|
||||
case "ObjCMethodDecl":
|
||||
f := p.parseMethod()
|
||||
if f == nil {
|
||||
continue
|
||||
}
|
||||
var key string
|
||||
if f.Static {
|
||||
key = "+" + f.Sig
|
||||
} else {
|
||||
key = "-" + f.Sig
|
||||
}
|
||||
if _, exists := n.funcMap[key]; !exists {
|
||||
n.funcMap[key] = struct{}{}
|
||||
if f.Static {
|
||||
n.Funcs = append(n.Funcs, f)
|
||||
} else {
|
||||
n.Methods = append(n.Methods, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) parseMethod() *Func {
|
||||
// ObjCMethodDecl 0x103bdfb80 <line:17:1, col:27> col:1 - isEqual: 'BOOL':'_Bool'
|
||||
|
||||
// Skip the address, range, position.
|
||||
p.scanWord()
|
||||
p.parseLocation()
|
||||
if strings.HasPrefix(p.decl, "implicit") {
|
||||
p.scanWord()
|
||||
}
|
||||
f := new(Func)
|
||||
switch w := p.scanWord(); w {
|
||||
case "+":
|
||||
f.Static = true
|
||||
case "-":
|
||||
f.Static = false
|
||||
default:
|
||||
panic(fmt.Errorf("unknown method type for %q", w))
|
||||
}
|
||||
f.Sig = p.scanWord()
|
||||
if f.Sig == "dealloc" {
|
||||
// ARC forbids dealloc
|
||||
return nil
|
||||
}
|
||||
if strings.HasPrefix(f.Sig, "init") {
|
||||
f.Constructor = true
|
||||
}
|
||||
f.Ret = p.parseType()
|
||||
p.indent++
|
||||
for {
|
||||
more := p.scanLine()
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
switch p.scanWord() {
|
||||
case "UnavailableAttr":
|
||||
p.indent--
|
||||
return nil
|
||||
case "ParmVarDecl":
|
||||
f.Params = append(f.Params, p.parseParameter())
|
||||
}
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func (p *parser) parseParameter() *Param {
|
||||
// ParmVarDecl 0x1041caca8 <col:70, col:80> col:80 event 'UIEvent * _Nullable':'UIEvent *'
|
||||
|
||||
// Skip address, source range, position.
|
||||
p.scanWord()
|
||||
p.parseLocation()
|
||||
return &Param{Name: p.scanWord(), Type: p.parseType()}
|
||||
}
|
||||
|
||||
func (p *parser) parseType() *Type {
|
||||
// NSUInteger':'unsigned long'
|
||||
s := strings.SplitN(p.decl, ":", 2)
|
||||
decl := s[0]
|
||||
var canon string
|
||||
if len(s) == 2 {
|
||||
canon = s[1]
|
||||
} else {
|
||||
canon = decl
|
||||
}
|
||||
// unquote the type
|
||||
canon = canon[1 : len(canon)-1]
|
||||
if canon == "void" {
|
||||
return nil
|
||||
}
|
||||
decl = decl[1 : len(decl)-1]
|
||||
instancetype := strings.HasPrefix(decl, "instancetype")
|
||||
// Strip modifiers
|
||||
mods := []string{"__strong", "__unsafe_unretained", "const", "__strong", "_Nonnull", "_Nullable", "__autoreleasing"}
|
||||
for _, mod := range mods {
|
||||
if idx := strings.Index(canon, mod); idx != -1 {
|
||||
canon = canon[:idx] + canon[idx+len(mod):]
|
||||
}
|
||||
if idx := strings.Index(decl, mod); idx != -1 {
|
||||
decl = decl[:idx] + decl[idx+len(mod):]
|
||||
}
|
||||
}
|
||||
canon = strings.TrimSpace(canon)
|
||||
decl = strings.TrimSpace(decl)
|
||||
t := &Type{
|
||||
Decl: decl,
|
||||
instanceType: instancetype,
|
||||
}
|
||||
switch canon {
|
||||
case "int", "long", "long long":
|
||||
t.Kind = Int
|
||||
case "unsigned int", "unsigned long", "unsigned long long":
|
||||
t.Kind = Uint
|
||||
case "short":
|
||||
t.Kind = Short
|
||||
case "unsigned short":
|
||||
t.Kind = Ushort
|
||||
case "char":
|
||||
t.Kind = Char
|
||||
case "unsigned char":
|
||||
t.Kind = Uchar
|
||||
case "float":
|
||||
t.Kind = Float
|
||||
case "double":
|
||||
t.Kind = Double
|
||||
case "_Bool":
|
||||
t.Kind = Bool
|
||||
case "NSString *":
|
||||
t.Kind = String
|
||||
case "NSData *":
|
||||
t.Kind = Data
|
||||
default:
|
||||
switch {
|
||||
case strings.HasPrefix(canon, "enum"):
|
||||
t.Kind = Int
|
||||
case strings.HasPrefix(canon, "id"):
|
||||
_, gen := p.splitGeneric(canon)
|
||||
t.Kind = Protocol
|
||||
t.Name = gen
|
||||
default:
|
||||
if ind := strings.Count(canon, "*"); 1 <= ind && ind <= 2 {
|
||||
space := strings.Index(canon, " ")
|
||||
name := canon[:space]
|
||||
name, _ = p.splitGeneric(name)
|
||||
t.Kind = Class
|
||||
t.Name = name
|
||||
t.Indirect = ind > 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (p *parser) splitGeneric(decl string) (string, string) {
|
||||
// NSArray<KeyType>
|
||||
if br := strings.Index(decl, "<"); br != -1 {
|
||||
return decl[:br], decl[br+1 : len(decl)-1]
|
||||
} else {
|
||||
return decl, ""
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) parseSrcPos() {
|
||||
const invPref = "<invalid sloc>"
|
||||
if strings.HasPrefix(p.decl, invPref) {
|
||||
p.decl = p.decl[len(invPref):]
|
||||
return
|
||||
}
|
||||
var loc string
|
||||
const scrPref = "<scratch space>"
|
||||
if strings.HasPrefix(p.decl, scrPref) {
|
||||
// <scratch space>:130:1
|
||||
p.decl = p.decl[len(scrPref):]
|
||||
loc = "line" + p.scanWord()
|
||||
} else {
|
||||
// line:17:2, col:18 or, a file location:
|
||||
// /.../UIKit.framework/Headers/UISelectionFeedbackGenerator.h:16:1
|
||||
loc = p.scanWord()
|
||||
}
|
||||
locs := strings.SplitN(loc, ":", 2)
|
||||
if len(locs) != 2 && len(locs) != 3 {
|
||||
panic(fmt.Errorf("invalid source position: %q", loc))
|
||||
}
|
||||
switch loc := locs[0]; loc {
|
||||
case "line", "col":
|
||||
default:
|
||||
if !strings.HasPrefix(loc, p.sdkPath) {
|
||||
panic(fmt.Errorf("invalid source position: %q", loc))
|
||||
}
|
||||
loc = loc[len(p.sdkPath):]
|
||||
switch {
|
||||
case strings.HasPrefix(loc, "/usr/include/objc/"):
|
||||
p.module = "Foundation"
|
||||
case strings.HasPrefix(loc, frameworksPath):
|
||||
loc = loc[len(frameworksPath):]
|
||||
i := strings.Index(loc, ".framework")
|
||||
if i == -1 {
|
||||
panic(fmt.Errorf("invalid source position: %q", loc))
|
||||
}
|
||||
p.module = loc[:i]
|
||||
// Some types are declared in CoreFoundation.framework
|
||||
// even though they belong in Foundation in Objective-C.
|
||||
if p.module == "CoreFoundation" {
|
||||
p.module = "Foundation"
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) parseLocation() {
|
||||
// Source ranges are on the form: <line:17:29, line:64:2>.
|
||||
if !strings.HasPrefix(p.decl, "<") {
|
||||
panic(fmt.Errorf("1no source range first in %s", p.decl))
|
||||
}
|
||||
p.decl = p.decl[1:]
|
||||
p.parseSrcPos()
|
||||
if strings.HasPrefix(p.decl, ", ") {
|
||||
p.decl = p.decl[2:]
|
||||
p.parseSrcPos()
|
||||
}
|
||||
if !strings.HasPrefix(p.decl, "> ") {
|
||||
panic(fmt.Errorf("no source range first in %s", p.decl))
|
||||
}
|
||||
p.decl = p.decl[2:]
|
||||
p.parseSrcPos()
|
||||
}
|
||||
|
||||
func (p *parser) scanWord() string {
|
||||
i := 0
|
||||
loop:
|
||||
for ; i < len(p.decl); i++ {
|
||||
switch p.decl[i] {
|
||||
case ' ', '>', ',':
|
||||
break loop
|
||||
}
|
||||
}
|
||||
w := p.decl[:i]
|
||||
p.decl = p.decl[i:]
|
||||
for len(p.decl) > 0 && p.decl[0] == ' ' {
|
||||
p.decl = p.decl[1:]
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
func initialUpper(s string) string {
|
||||
if s == "" {
|
||||
return ""
|
||||
}
|
||||
r, n := utf8.DecodeRuneInString(s)
|
||||
return string(unicode.ToUpper(r)) + s[n:]
|
||||
}
|
||||
|
||||
func (t *Named) ObjcType() string {
|
||||
if t.Protocol {
|
||||
return fmt.Sprintf("id<%s> _Nullable", t.Name)
|
||||
} else {
|
||||
return t.Name + " * _Nullable"
|
||||
}
|
||||
}
|
83
internal/importers/objc/objc_test.go
Normal file
83
internal/importers/objc/objc_test.go
Normal file
|
@ -0,0 +1,83 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package objc
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/mobile/internal/importers"
|
||||
)
|
||||
|
||||
func TestImport(t *testing.T) {
|
||||
if runtime.GOOS != "darwin" {
|
||||
t.Skipf("can only parse objc types on darwin")
|
||||
}
|
||||
tests := []struct {
|
||||
ref importers.PkgRef
|
||||
name string
|
||||
methods []*Func
|
||||
}{
|
||||
{
|
||||
ref: importers.PkgRef{Pkg: "Foundation/NSObjectP", Name: "Hash"},
|
||||
name: "NSObject",
|
||||
methods: []*Func{
|
||||
&Func{Sig: "hash", GoName: "Hash", Ret: &Type{Kind: Uint, Decl: "NSUInteger"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
ref: importers.PkgRef{Pkg: "Foundation/NSString", Name: "StringWithContentsOfFileEncodingError"},
|
||||
name: "NSString",
|
||||
methods: []*Func{
|
||||
&Func{
|
||||
Sig: "stringWithContentsOfFile:encoding:error:",
|
||||
GoName: "StringWithContentsOfFileEncodingError",
|
||||
Params: []*Param{
|
||||
&Param{Name: "path", Type: &Type{Kind: String, Decl: "NSString *"}},
|
||||
&Param{Name: "enc", Type: &Type{Kind: Uint, Decl: "NSStringEncoding"}},
|
||||
&Param{Name: "error", Type: &Type{Kind: Class, Name: "NSError", Decl: "NSError * * _Nullable", Indirect: true}},
|
||||
},
|
||||
Ret: &Type{Kind: 3, Decl: "NSString *"},
|
||||
Static: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
refs := &importers.References{
|
||||
Refs: []importers.PkgRef{test.ref},
|
||||
Names: make(map[string]struct{}),
|
||||
}
|
||||
for _, m := range test.methods {
|
||||
refs.Names[m.GoName] = struct{}{}
|
||||
}
|
||||
types, err := Import(refs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(types) == 0 {
|
||||
t.Fatalf("got no types, expected at least 1")
|
||||
}
|
||||
n := types[0]
|
||||
if n.Name != test.name {
|
||||
t.Errorf("got class name %s, expected %s", n.Name, test.name)
|
||||
}
|
||||
loop:
|
||||
for _, exp := range test.methods {
|
||||
for _, got := range n.AllMethods {
|
||||
if reflect.DeepEqual(exp, got) {
|
||||
continue loop
|
||||
}
|
||||
}
|
||||
for _, got := range n.Funcs {
|
||||
if reflect.DeepEqual(exp, got) {
|
||||
continue loop
|
||||
}
|
||||
}
|
||||
t.Errorf("failed to find method: %+v", exp)
|
||||
}
|
||||
}
|
||||
}
|
124
internal/mobileinit/ctx_android.go
Normal file
124
internal/mobileinit/ctx_android.go
Normal file
|
@ -0,0 +1,124 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package mobileinit
|
||||
|
||||
/*
|
||||
#include <jni.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static char* lockJNI(JavaVM *vm, uintptr_t* envp, int* attachedp) {
|
||||
JNIEnv* env;
|
||||
|
||||
if (vm == NULL) {
|
||||
return "no current JVM";
|
||||
}
|
||||
|
||||
*attachedp = 0;
|
||||
switch ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6)) {
|
||||
case JNI_OK:
|
||||
break;
|
||||
case JNI_EDETACHED:
|
||||
if ((*vm)->AttachCurrentThread(vm, &env, 0) != 0) {
|
||||
return "cannot attach to JVM";
|
||||
}
|
||||
*attachedp = 1;
|
||||
break;
|
||||
case JNI_EVERSION:
|
||||
return "bad JNI version";
|
||||
default:
|
||||
return "unknown JNI error from GetEnv";
|
||||
}
|
||||
|
||||
*envp = (uintptr_t)env;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char* checkException(uintptr_t jnienv) {
|
||||
jthrowable exc;
|
||||
JNIEnv* env = (JNIEnv*)jnienv;
|
||||
|
||||
if (!(*env)->ExceptionCheck(env)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
exc = (*env)->ExceptionOccurred(env);
|
||||
(*env)->ExceptionClear(env);
|
||||
|
||||
jclass clazz = (*env)->FindClass(env, "java/lang/Throwable");
|
||||
jmethodID toString = (*env)->GetMethodID(env, clazz, "toString", "()Ljava/lang/String;");
|
||||
jobject msgStr = (*env)->CallObjectMethod(env, exc, toString);
|
||||
return (char*)(*env)->GetStringUTFChars(env, msgStr, 0);
|
||||
}
|
||||
|
||||
static void unlockJNI(JavaVM *vm) {
|
||||
(*vm)->DetachCurrentThread(vm);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// currentVM is stored to initialize other cgo packages.
|
||||
//
|
||||
// As all the Go packages in a program form a single shared library,
|
||||
// there can only be one JNI_OnLoad function for initialization. In
|
||||
// OpenJDK there is JNI_GetCreatedJavaVMs, but this is not available
|
||||
// on android.
|
||||
var currentVM *C.JavaVM
|
||||
|
||||
// currentCtx is Android's android.context.Context. May be NULL.
|
||||
var currentCtx C.jobject
|
||||
|
||||
// SetCurrentContext populates the global Context object with the specified
|
||||
// current JavaVM instance (vm) and android.context.Context object (ctx).
|
||||
// The android.context.Context object must be a global reference.
|
||||
func SetCurrentContext(vm unsafe.Pointer, ctx uintptr) {
|
||||
currentVM = (*C.JavaVM)(vm)
|
||||
currentCtx = (C.jobject)(ctx)
|
||||
}
|
||||
|
||||
// RunOnJVM runs fn on a new goroutine locked to an OS thread with a JNIEnv.
|
||||
//
|
||||
// RunOnJVM blocks until the call to fn is complete. Any Java
|
||||
// exception or failure to attach to the JVM is returned as an error.
|
||||
//
|
||||
// The function fn takes vm, the current JavaVM*,
|
||||
// env, the current JNIEnv*, and
|
||||
// ctx, a jobject representing the global android.context.Context.
|
||||
func RunOnJVM(fn func(vm, env, ctx uintptr) error) error {
|
||||
errch := make(chan error)
|
||||
go func() {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
env := C.uintptr_t(0)
|
||||
attached := C.int(0)
|
||||
if errStr := C.lockJNI(currentVM, &env, &attached); errStr != nil {
|
||||
errch <- errors.New(C.GoString(errStr))
|
||||
return
|
||||
}
|
||||
if attached != 0 {
|
||||
defer C.unlockJNI(currentVM)
|
||||
}
|
||||
|
||||
vm := uintptr(unsafe.Pointer(currentVM))
|
||||
if err := fn(vm, uintptr(env), uintptr(currentCtx)); err != nil {
|
||||
errch <- err
|
||||
return
|
||||
}
|
||||
|
||||
if exc := C.checkException(env); exc != nil {
|
||||
errch <- errors.New(C.GoString(exc))
|
||||
C.free(unsafe.Pointer(exc))
|
||||
return
|
||||
}
|
||||
errch <- nil
|
||||
}()
|
||||
return <-errch
|
||||
}
|
11
internal/mobileinit/mobileinit.go
Normal file
11
internal/mobileinit/mobileinit.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package mobileinit contains common initialization logic for mobile platforms
|
||||
// that is relevant to both all-Go apps and gobind-based apps.
|
||||
//
|
||||
// Long-term, some code in this package should consider moving into Go stdlib.
|
||||
package mobileinit
|
||||
|
||||
import "C"
|
93
internal/mobileinit/mobileinit_android.go
Normal file
93
internal/mobileinit/mobileinit_android.go
Normal file
|
@ -0,0 +1,93 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package mobileinit
|
||||
|
||||
/*
|
||||
To view the log output run:
|
||||
adb logcat GoLog:I *:S
|
||||
*/
|
||||
|
||||
// Android redirects stdout and stderr to /dev/null.
|
||||
// As these are common debugging utilities in Go,
|
||||
// we redirect them to logcat.
|
||||
//
|
||||
// Unfortunately, logcat is line oriented, so we must buffer.
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -landroid -llog
|
||||
|
||||
#include <android/log.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"log"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
ctag = C.CString("GoLog")
|
||||
// Store the writer end of the redirected stderr and stdout
|
||||
// so that they are not garbage collected and closed.
|
||||
stderr, stdout *os.File
|
||||
)
|
||||
|
||||
type infoWriter struct{}
|
||||
|
||||
func (infoWriter) Write(p []byte) (n int, err error) {
|
||||
cstr := C.CString(string(p))
|
||||
C.__android_log_write(C.ANDROID_LOG_INFO, ctag, cstr)
|
||||
C.free(unsafe.Pointer(cstr))
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func lineLog(f *os.File, priority C.int) {
|
||||
const logSize = 1024 // matches android/log.h.
|
||||
r := bufio.NewReaderSize(f, logSize)
|
||||
for {
|
||||
line, _, err := r.ReadLine()
|
||||
str := string(line)
|
||||
if err != nil {
|
||||
str += " " + err.Error()
|
||||
}
|
||||
cstr := C.CString(str)
|
||||
C.__android_log_write(priority, ctag, cstr)
|
||||
C.free(unsafe.Pointer(cstr))
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
log.SetOutput(infoWriter{})
|
||||
// android logcat includes all of log.LstdFlags
|
||||
log.SetFlags(log.Flags() &^ log.LstdFlags)
|
||||
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
stderr = w
|
||||
if err := syscall.Dup3(int(w.Fd()), int(os.Stderr.Fd()), 0); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
go lineLog(r, C.ANDROID_LOG_ERROR)
|
||||
|
||||
r, w, err = os.Pipe()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
stdout = w
|
||||
if err := syscall.Dup3(int(w.Fd()), int(os.Stdout.Fd()), 0); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
go lineLog(r, C.ANDROID_LOG_INFO)
|
||||
}
|
41
internal/mobileinit/mobileinit_ios.go
Normal file
41
internal/mobileinit/mobileinit_ios.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package mobileinit
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#include <os/log.h>
|
||||
|
||||
os_log_t create_os_log() {
|
||||
return os_log_create("org.golang.mobile", "os_log");
|
||||
}
|
||||
|
||||
void os_log_wrap(os_log_t log, const char *str) {
|
||||
os_log(log, "%s", str);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
type osWriter struct {
|
||||
w C.os_log_t
|
||||
}
|
||||
|
||||
func (o osWriter) Write(p []byte) (n int, err error) {
|
||||
cstr := C.CString(string(p))
|
||||
C.os_log_wrap(o.w, cstr)
|
||||
C.free(unsafe.Pointer(cstr))
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
log.SetOutput(io.MultiWriter(os.Stderr, osWriter{C.create_os_log()}))
|
||||
}
|
89
internal/sdkpath/sdkpath.go
Normal file
89
internal/sdkpath/sdkpath.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package sdkpath provides functions for locating the Android SDK.
|
||||
// These functions respect the ANDROID_HOME environment variable, and
|
||||
// otherwise use the default SDK location.
|
||||
package sdkpath
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AndroidHome returns the absolute path of the selected Android SDK,
|
||||
// if one can be found.
|
||||
func AndroidHome() (string, error) {
|
||||
androidHome := os.Getenv("ANDROID_HOME")
|
||||
if androidHome == "" {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
// See https://android.googlesource.com/platform/tools/adt/idea/+/85b4bfb7a10ad858a30ffa4003085b54f9424087/native/installer/win/setup_android_studio.nsi#100
|
||||
androidHome = filepath.Join(home, "AppData", "Local", "Android", "sdk")
|
||||
case "darwin":
|
||||
// See https://android.googlesource.com/platform/tools/asuite/+/67e0cd9604379e9663df57f16a318d76423c0aa8/aidegen/lib/ide_util.py#88
|
||||
androidHome = filepath.Join(home, "Library", "Android", "sdk")
|
||||
default: // Linux, BSDs, etc.
|
||||
// See LINUX_ANDROID_SDK_PATH in ide_util.py above.
|
||||
androidHome = filepath.Join(home, "Android", "Sdk")
|
||||
}
|
||||
}
|
||||
if info, err := os.Stat(androidHome); err != nil {
|
||||
return "", fmt.Errorf("%w; Android SDK was not found at %s", err, androidHome)
|
||||
} else if !info.IsDir() {
|
||||
return "", fmt.Errorf("%s is not a directory", androidHome)
|
||||
}
|
||||
return androidHome, nil
|
||||
}
|
||||
|
||||
// AndroidAPIPath returns an android SDK platform directory within the configured SDK.
|
||||
// If there are multiple platforms that satisfy the minimum version requirement,
|
||||
// AndroidAPIPath returns the latest one among them.
|
||||
func AndroidAPIPath(api int) (string, error) {
|
||||
sdk, err := AndroidHome()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sdkDir, err := os.Open(filepath.Join(sdk, "platforms"))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to find android SDK platform: %w", err)
|
||||
}
|
||||
defer sdkDir.Close()
|
||||
fis, err := sdkDir.Readdir(-1)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to find android SDK platform (API level: %d): %w", api, err)
|
||||
}
|
||||
|
||||
var apiPath string
|
||||
var apiVer int
|
||||
for _, fi := range fis {
|
||||
name := fi.Name()
|
||||
if !strings.HasPrefix(name, "android-") {
|
||||
continue
|
||||
}
|
||||
n, err := strconv.Atoi(name[len("android-"):])
|
||||
if err != nil || n < api {
|
||||
continue
|
||||
}
|
||||
p := filepath.Join(sdkDir.Name(), name)
|
||||
_, err = os.Stat(filepath.Join(p, "android.jar"))
|
||||
if err == nil && apiVer < n {
|
||||
apiPath = p
|
||||
apiVer = n
|
||||
}
|
||||
}
|
||||
if apiVer == 0 {
|
||||
return "", fmt.Errorf("failed to find android SDK platform (API level: %d) in %s",
|
||||
api, sdkDir.Name())
|
||||
}
|
||||
return apiPath, nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue