mirror of
https://github.com/go-i2p/gomobile-java.git
synced 2025-07-14 12:49:45 -04:00
cmd/gomobile: use nm to build list of packages
Today we only look at direct imports of package main for an import of golang.org/x/mobile/app, which is unfortunate. We also do a complete package tree load using go/build looking for the OpenAL import when building for android, which involves reading a lot of files. The compiler and linker have already done all of this work for us. Run nm on the output binary and extract package names from it. Change-Id: Ie4f07befede5017bbca7d24325062369d4b5c30d Reviewed-on: https://go-review.googlesource.com/12645 Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
This commit is contained in:
@ -7,11 +7,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -90,12 +92,14 @@ func runBuild(cmd *command) (err error) {
|
||||
return fmt.Errorf("cannot set -o when building non-main package")
|
||||
}
|
||||
|
||||
var nmpkgs map[string]bool
|
||||
switch buildTarget {
|
||||
case "android":
|
||||
if pkg.Name != "main" {
|
||||
return goBuild(pkg.ImportPath, androidArmEnv)
|
||||
}
|
||||
if err := goAndroidBuild(pkg); err != nil {
|
||||
nmpkgs, err = goAndroidBuild(pkg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "ios":
|
||||
@ -108,22 +112,53 @@ func runBuild(cmd *command) (err error) {
|
||||
}
|
||||
return goBuild(pkg.ImportPath, darwinArm64Env)
|
||||
}
|
||||
if err := goIOSBuild(pkg); err != nil {
|
||||
nmpkgs, err = goIOSBuild(pkg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(crawshaw): This is an incomplete package scan.
|
||||
// A complete package scan would be too expensive. Instead,
|
||||
// fake it. After the binary is built, scan its symbols
|
||||
// with nm and look for the app and al packages.
|
||||
if err := importsApp(pkg); err != nil {
|
||||
return err
|
||||
if !nmpkgs["golang.org/x/mobile/app"] {
|
||||
return fmt.Errorf(`%s does not import "golang.org/x/mobile/app"`, pkg.ImportPath)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var nmRE = regexp.MustCompile(`[0-9a-f]{8} t (golang.org/x.*/[^.]*)`)
|
||||
|
||||
func extractPkgs(nm string, path string) (map[string]bool, error) {
|
||||
if buildN {
|
||||
return map[string]bool{"golang.org/x/mobile/app": true}, nil
|
||||
}
|
||||
r, w := io.Pipe()
|
||||
cmd := exec.Command(nm, path)
|
||||
cmd.Stdout = w
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
nmpkgs := make(map[string]bool)
|
||||
errc := make(chan error, 1)
|
||||
go func() {
|
||||
s := bufio.NewScanner(r)
|
||||
for s.Scan() {
|
||||
if res := nmRE.FindStringSubmatch(s.Text()); res != nil {
|
||||
nmpkgs[res[1]] = true
|
||||
}
|
||||
}
|
||||
errc <- s.Err()
|
||||
}()
|
||||
|
||||
err := cmd.Run()
|
||||
w.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s %s: %v", nm, path, err)
|
||||
}
|
||||
if err := <-errc; err != nil {
|
||||
return nil, fmt.Errorf("%s %s: %v", nm, path, err)
|
||||
}
|
||||
return nmpkgs, nil
|
||||
}
|
||||
|
||||
func importsApp(pkg *build.Package) error {
|
||||
// Building a program, make sure it is appropriate for mobile.
|
||||
for _, path := range pkg.Imports {
|
||||
|
@ -21,12 +21,12 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func goAndroidBuild(pkg *build.Package) error {
|
||||
func goAndroidBuild(pkg *build.Package) (map[string]bool, error) {
|
||||
libName := path.Base(pkg.ImportPath)
|
||||
manifestData, err := ioutil.ReadFile(filepath.Join(pkg.Dir, "AndroidManifest.xml"))
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteString(`<?xml version="1.0" encoding="utf-8"?>`)
|
||||
@ -37,7 +37,7 @@ func goAndroidBuild(pkg *build.Package) error {
|
||||
LibName: libName,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
manifestData = buf.Bytes()
|
||||
if buildV {
|
||||
@ -46,7 +46,7 @@ func goAndroidBuild(pkg *build.Package) error {
|
||||
} else {
|
||||
libName, err = manifestLibName(manifestData)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
libPath := filepath.Join(tmpdir, "lib"+libName+".so")
|
||||
@ -58,28 +58,34 @@ func goAndroidBuild(pkg *build.Package) error {
|
||||
"-o", libPath,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nmpkgs, err := extractPkgs(androidArmNM, libPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, _ := pem.Decode([]byte(debugCert))
|
||||
if block == nil {
|
||||
return errors.New("no debug cert")
|
||||
return nil, errors.New("no debug cert")
|
||||
}
|
||||
privKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if buildO == "" {
|
||||
buildO = filepath.Base(pkg.Dir) + ".apk"
|
||||
}
|
||||
if !strings.HasSuffix(buildO, ".apk") {
|
||||
return fmt.Errorf("output file name %q does not end in '.apk'", buildO)
|
||||
return nil, fmt.Errorf("output file name %q does not end in '.apk'", buildO)
|
||||
}
|
||||
var out io.Writer
|
||||
if !buildN {
|
||||
f, err := os.Create(buildO)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if cerr := f.Close(); err == nil {
|
||||
@ -105,39 +111,39 @@ func goAndroidBuild(pkg *build.Package) error {
|
||||
|
||||
w, err := apkwcreate("AndroidManifest.xml")
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if _, err := w.Write(manifestData); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w, err = apkwcreate("classes.dex")
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
dexData, err := base64.StdEncoding.DecodeString(dexStr)
|
||||
if err != nil {
|
||||
log.Fatal("internal error bad dexStr: %v", err)
|
||||
}
|
||||
if _, err := w.Write(dexData); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w, err = apkwcreate("lib/armeabi/lib" + libName + ".so")
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if !buildN {
|
||||
r, err := os.Open(libPath)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if _, err := io.Copy(w, r); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if pkgImportsAL(pkg) {
|
||||
if nmpkgs["golang.org/x/mobile/exp/audio/al"] {
|
||||
alDir := filepath.Join(ndkccpath, "openal/lib")
|
||||
filepath.Walk(alDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
@ -171,7 +177,7 @@ func goAndroidBuild(pkg *build.Package) error {
|
||||
if os.IsNotExist(err) {
|
||||
assetsDirExists = false
|
||||
} else {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
assetsDirExists = fi.IsDir()
|
||||
@ -198,7 +204,7 @@ func goAndroidBuild(pkg *build.Package) error {
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("asset %v", err)
|
||||
return nil, fmt.Errorf("asset %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -206,39 +212,11 @@ func goAndroidBuild(pkg *build.Package) error {
|
||||
|
||||
if !buildN {
|
||||
if err := apkw.Close(); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var importsALPkg = make(map[string]struct{})
|
||||
|
||||
// pkgImportsAL returns true if the given package or one of its
|
||||
// dependencies imports the x/mobile/exp/audio/al package.
|
||||
func pkgImportsAL(pkg *build.Package) bool {
|
||||
for _, path := range pkg.Imports {
|
||||
if path == "C" {
|
||||
continue
|
||||
}
|
||||
if _, ok := importsALPkg[path]; ok {
|
||||
continue
|
||||
}
|
||||
importsALPkg[path] = struct{}{}
|
||||
if strings.HasPrefix(path, "golang.org/x/mobile/exp/audio/al") {
|
||||
return true
|
||||
}
|
||||
dPkg, err := ctx.Import(path, "", build.ImportComment)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error reading OpenAL library: %v", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
if pkgImportsAL(dPkg) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return nmpkgs, nil
|
||||
}
|
||||
|
||||
// A random uninteresting private key.
|
||||
|
@ -17,17 +17,17 @@ import (
|
||||
"text/template"
|
||||
)
|
||||
|
||||
func goIOSBuild(pkg *build.Package) error {
|
||||
func goIOSBuild(pkg *build.Package) (map[string]bool, error) {
|
||||
src := pkg.ImportPath
|
||||
if buildO != "" && !strings.HasSuffix(buildO, ".app") {
|
||||
return fmt.Errorf("-o must have an .app for target=ios")
|
||||
return nil, fmt.Errorf("-o must have an .app for target=ios")
|
||||
}
|
||||
|
||||
infoplist := new(bytes.Buffer)
|
||||
if err := infoplistTmpl.Execute(infoplist, manifestTmplData{
|
||||
Name: strings.Title(path.Base(pkg.ImportPath)),
|
||||
}); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
files := []struct {
|
||||
@ -41,26 +41,30 @@ func goIOSBuild(pkg *build.Package) error {
|
||||
|
||||
for _, file := range files {
|
||||
if err := mkdir(filepath.Dir(file.name)); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if buildX {
|
||||
printcmd("echo \"%s\" > %s", file.contents, file.name)
|
||||
}
|
||||
if !buildN {
|
||||
if err := ioutil.WriteFile(file.name, file.contents, 0644); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
armPath := filepath.Join(tmpdir, "arm")
|
||||
if err := goBuild(src, darwinArmEnv, "-tags=ios", "-o="+armPath); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
nmpkgs, err := extractPkgs(darwinArmNM, armPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
arm64Path := filepath.Join(tmpdir, "arm64")
|
||||
if err := goBuild(src, darwinArm64Env, "-tags=ios", "-o="+arm64Path); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Apple requires builds to target both darwin/arm and darwin/arm64.
|
||||
@ -73,12 +77,12 @@ func goIOSBuild(pkg *build.Package) error {
|
||||
"-o", filepath.Join(tmpdir, "main/main"),
|
||||
)
|
||||
if err := runCmd(cmd); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(jbd): Set the launcher icon.
|
||||
if err := iosCopyAssets(pkg, tmpdir); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Build and move the release build to the output directory.
|
||||
@ -88,7 +92,7 @@ func goIOSBuild(pkg *build.Package) error {
|
||||
"-project", tmpdir+"/main.xcodeproj",
|
||||
)
|
||||
if err := runCmd(cmd); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(jbd): Fallback to copying if renaming fails.
|
||||
@ -101,13 +105,13 @@ func goIOSBuild(pkg *build.Package) error {
|
||||
if !buildN {
|
||||
// if output already exists, remove.
|
||||
if err := os.RemoveAll(buildO); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if err := os.Rename(tmpdir+"/build/Release-iphoneos/main.app", buildO); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return nmpkgs, nil
|
||||
}
|
||||
|
||||
func iosCopyAssets(pkg *build.Package, xcodeProjDir string) error {
|
||||
|
@ -23,6 +23,9 @@ var (
|
||||
darwinArm64Env []string
|
||||
darwin386Env []string
|
||||
darwinAmd64Env []string
|
||||
|
||||
androidArmNM string
|
||||
darwinArmNM string
|
||||
)
|
||||
|
||||
func buildEnvInit() (cleanup func(), err error) {
|
||||
@ -104,6 +107,7 @@ func envInit() (err error) {
|
||||
"CXX=" + filepath.Join(ndkccbin, "arm-linux-androideabi-g++"+exe),
|
||||
"CGO_ENABLED=1",
|
||||
}
|
||||
androidArmNM = filepath.Join(ndkccbin, "arm-linux-androideabi-nm"+exe)
|
||||
|
||||
if runtime.GOOS != "darwin" {
|
||||
return nil
|
||||
@ -123,6 +127,7 @@ func envInit() (err error) {
|
||||
"CGO_LDFLAGS=" + cflags + " -arch " + archClang("arm"),
|
||||
"CGO_ENABLED=1",
|
||||
}
|
||||
darwinArmNM = "nm"
|
||||
darwinArm64Env = []string{
|
||||
"GOOS=darwin",
|
||||
"GOARCH=arm64",
|
||||
|
@ -430,7 +430,6 @@ func fetchFullNDK() error {
|
||||
}
|
||||
inflate.Dir = tmpdir
|
||||
return runCmd(inflate)
|
||||
return nil
|
||||
}
|
||||
|
||||
// fetch reads a URL into $GOPATH/pkg/gomobile/dl and returns the path
|
||||
|
Reference in New Issue
Block a user