func goAndroidBuild()

in cmd/gomobile/build_androidapp.go [27:286]


func goAndroidBuild(pkg *packages.Package, targets []targetInfo) (map[string]bool, error) {
	ndkRoot, err := ndkRoot()
	if err != nil {
		return nil, err
	}
	appName := path.Base(pkg.PkgPath)
	libName := androidPkgName(appName)

	// TODO(hajimehoshi): This works only with Go tools that assume all source files are in one directory.
	// Fix this to work with other Go tools.
	dir := filepath.Dir(pkg.GoFiles[0])

	manifestPath := filepath.Join(dir, "AndroidManifest.xml")
	manifestData, err := ioutil.ReadFile(manifestPath)
	if err != nil {
		if !os.IsNotExist(err) {
			return nil, err
		}

		buf := new(bytes.Buffer)
		buf.WriteString(`<?xml version="1.0" encoding="utf-8"?>`)
		err := manifestTmpl.Execute(buf, manifestTmplData{
			// TODO(crawshaw): a better package path.
			JavaPkgPath: "org.golang.todo." + libName,
			Name:        strings.Title(appName),
			LibName:     libName,
		})
		if err != nil {
			return nil, err
		}
		manifestData = buf.Bytes()
		if buildV {
			fmt.Fprintf(os.Stderr, "generated AndroidManifest.xml:\n%s\n", manifestData)
		}
	} else {
		libName, err = manifestLibName(manifestData)
		if err != nil {
			return nil, fmt.Errorf("error parsing %s: %v", manifestPath, err)
		}
	}

	libFiles := []string{}
	nmpkgs := make(map[string]map[string]bool) // map: arch -> extractPkgs' output

	for _, t := range targets {
		toolchain := ndk.Toolchain(t.arch)
		libPath := "lib/" + toolchain.abi + "/lib" + libName + ".so"
		libAbsPath := filepath.Join(tmpdir, libPath)
		if err := mkdir(filepath.Dir(libAbsPath)); err != nil {
			return nil, err
		}
		err = goBuild(
			pkg.PkgPath,
			androidEnv[t.arch],
			"-buildmode=c-shared",
			"-o", libAbsPath,
		)
		if err != nil {
			return nil, err
		}
		nmpkgs[t.arch], err = extractPkgs(toolchain.Path(ndkRoot, "nm"), libAbsPath)
		if err != nil {
			return nil, err
		}
		libFiles = append(libFiles, libPath)
	}

	block, _ := pem.Decode([]byte(debugCert))
	if block == nil {
		return nil, errors.New("no debug cert")
	}
	privKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		return nil, err
	}

	if buildO == "" {
		buildO = androidPkgName(path.Base(pkg.PkgPath)) + ".apk"
	}
	if !strings.HasSuffix(buildO, ".apk") {
		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 nil, err
		}
		defer func() {
			if cerr := f.Close(); err == nil {
				err = cerr
			}
		}()
		out = f
	}

	var apkw *Writer
	if !buildN {
		apkw = NewWriter(out, privKey)
	}
	apkwCreate := func(name string) (io.Writer, error) {
		if buildV {
			fmt.Fprintf(os.Stderr, "apk: %s\n", name)
		}
		if buildN {
			return ioutil.Discard, nil
		}
		return apkw.Create(name)
	}
	apkwWriteFile := func(dst, src string) error {
		w, err := apkwCreate(dst)
		if err != nil {
			return err
		}
		if !buildN {
			f, err := os.Open(src)
			if err != nil {
				return err
			}
			defer f.Close()
			if _, err := io.Copy(w, f); err != nil {
				return err
			}
		}
		return nil
	}

	w, err := apkwCreate("classes.dex")
	if err != nil {
		return nil, err
	}
	dexData, err := base64.StdEncoding.DecodeString(dexStr)
	if err != nil {
		log.Fatalf("internal error bad dexStr: %v", err)
	}
	if _, err := w.Write(dexData); err != nil {
		return nil, err
	}

	for _, libFile := range libFiles {
		if err := apkwWriteFile(libFile, filepath.Join(tmpdir, libFile)); err != nil {
			return nil, err
		}
	}

	for _, t := range targets {
		toolchain := ndk.Toolchain(t.arch)
		if nmpkgs[t.arch]["golang.org/x/mobile/exp/audio/al"] {
			dst := "lib/" + toolchain.abi + "/libopenal.so"
			src := filepath.Join(gomobilepath, dst)
			if _, err := os.Stat(src); err != nil {
				return nil, errors.New("the Android requires the golang.org/x/mobile/exp/audio/al, but the OpenAL libraries was not found. Please run gomobile init with the -openal flag pointing to an OpenAL source directory.")
			}
			if err := apkwWriteFile(dst, src); err != nil {
				return nil, err
			}
		}
	}

	// Add any assets.
	var arsc struct {
		iconPath string
	}
	assetsDir := filepath.Join(dir, "assets")
	assetsDirExists := true
	fi, err := os.Stat(assetsDir)
	if err != nil {
		if os.IsNotExist(err) {
			assetsDirExists = false
		} else {
			return nil, err
		}
	} else {
		assetsDirExists = fi.IsDir()
	}
	if assetsDirExists {
		// if assets is a symlink, follow the symlink.
		assetsDir, err = filepath.EvalSymlinks(assetsDir)
		if err != nil {
			return nil, err
		}
		err = filepath.Walk(assetsDir, func(path string, info os.FileInfo, err error) error {
			if err != nil {
				return err
			}
			if name := filepath.Base(path); strings.HasPrefix(name, ".") {
				// Do not include the hidden files.
				return nil
			}
			if info.IsDir() {
				return nil
			}

			if rel, err := filepath.Rel(assetsDir, path); rel == "icon.png" && err == nil {
				arsc.iconPath = path
				// TODO returning here does not write the assets/icon.png to the final assets output,
				// making it unavailable via the assets API. Should the file be duplicated into assets
				// or should assets API be able to retrieve files from the generated resource table?
				return nil
			}

			name := "assets/" + path[len(assetsDir)+1:]
			return apkwWriteFile(name, path)
		})
		if err != nil {
			return nil, fmt.Errorf("asset %v", err)
		}
	}

	bxml, err := binres.UnmarshalXML(bytes.NewReader(manifestData), arsc.iconPath != "")
	if err != nil {
		return nil, err
	}

	// generate resources.arsc identifying single xxxhdpi icon resource.
	if arsc.iconPath != "" {
		pkgname, err := bxml.RawValueByName("manifest", xml.Name{Local: "package"})
		if err != nil {
			return nil, err
		}
		tbl, name := binres.NewMipmapTable(pkgname)
		if err := apkwWriteFile(name, arsc.iconPath); err != nil {
			return nil, err
		}
		w, err := apkwCreate("resources.arsc")
		if err != nil {
			return nil, err
		}
		bin, err := tbl.MarshalBinary()
		if err != nil {
			return nil, err
		}
		if _, err := w.Write(bin); err != nil {
			return nil, err
		}
	}

	w, err = apkwCreate("AndroidManifest.xml")
	if err != nil {
		return nil, err
	}
	bin, err := bxml.MarshalBinary()
	if err != nil {
		return nil, err
	}
	if _, err := w.Write(bin); err != nil {
		return nil, err
	}

	// TODO: add gdbserver to apk?

	if !buildN {
		if err := apkw.Close(); err != nil {
			return nil, err
		}
	}

	// TODO: return nmpkgs
	return nmpkgs[targets[0].arch], nil
}