blob: 631311ed63db069bfbd6f01a6c7ae7cdbf37186f [file] [log] [blame]
// Copyright 2015 The Vanadium 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 android_profile
import (
"flag"
"fmt"
"os"
"path"
"path/filepath"
"runtime"
"v.io/jiri"
"v.io/jiri/collect"
"v.io/jiri/profiles"
"v.io/jiri/profiles/profilesmanager"
"v.io/jiri/profiles/profilesreader"
"v.io/jiri/profiles/profilesutil"
"v.io/jiri/runutil"
"v.io/x/lib/envvar"
)
const (
ndkDownloadBaseURL = "https://dl.google.com/android/ndk"
platformToolsBaseURL = "http://tools.android.com/download"
)
type versionSpec struct {
ndkDownloadURL string
// seq's chain may be already in progress.
ndkExtract func(seq runutil.Sequence, src, dst string) runutil.Sequence
ndkAPILevel int
platformToolsVersion map[string]string
baseVersion string // Version of the base profile that this requires
}
func ndkArch(goArch string) (string, error) {
switch goArch {
case "386":
return "x86", nil
case "amd64":
return "x86_64", nil
case "arm":
return "arm", nil
default:
return "", fmt.Errorf("NDK unsupported for GOARCH %s", goArch)
}
}
func tarExtract(seq runutil.Sequence, src, dst string) runutil.Sequence {
return seq.Run("tar", "-C", dst, "-xjf", src)
}
func selfExtract(seq runutil.Sequence, src, dst string) runutil.Sequence {
return seq.Chmod(src, profilesutil.DefaultDirPerm).Run(src, "-y", "-o"+dst)
}
func Register(installer, profile string) {
arch, err := ndkArch(runtime.GOARCH)
if err != nil {
fmt.Fprintf(os.Stderr, "WARNING: android profile not supported: %v\n", err)
return
}
m := &Manager{
profileInstaller: installer,
profileName: profile,
qualifiedName: profiles.QualifiedProfileName(installer, profile),
versionInfo: profiles.NewVersionInfo(profile, map[string]interface{}{
"3": &versionSpec{
ndkDownloadURL: fmt.Sprintf("%s/android-ndk-r9d-%s-%s.tar.bz2", ndkDownloadBaseURL, runtime.GOOS, arch),
ndkExtract: tarExtract,
ndkAPILevel: 9,
baseVersion: "4",
},
"4": &versionSpec{
ndkDownloadURL: fmt.Sprintf("%s/android-ndk-r10e-%s-%s.bin", ndkDownloadBaseURL, runtime.GOOS, arch),
ndkExtract: selfExtract,
ndkAPILevel: 16,
baseVersion: "4",
},
"5": &versionSpec{
ndkDownloadURL: fmt.Sprintf("%s/android-ndk-r10e-%s-%s.bin", ndkDownloadBaseURL, runtime.GOOS, arch),
ndkExtract: selfExtract,
ndkAPILevel: 21,
baseVersion: "4",
},
"7": &versionSpec{
ndkDownloadURL: fmt.Sprintf("%s/android-ndk-r10e-%s-%s.bin", ndkDownloadBaseURL, runtime.GOOS, arch),
ndkExtract: selfExtract,
ndkAPILevel: 21,
baseVersion: "4",
},
"8": &versionSpec{
ndkDownloadURL: fmt.Sprintf("%s/android-ndk-r10e-%s-%s.bin", ndkDownloadBaseURL, runtime.GOOS, arch),
ndkExtract: selfExtract,
ndkAPILevel: 21,
platformToolsVersion: map[string]string{
"darwin": "sdk-repo-darwin-platform-tools-2219242",
"linux": "sdk-repo-linux-platform-tools-2219198",
},
baseVersion: "4",
},
"9": &versionSpec{
ndkDownloadURL: fmt.Sprintf("%s/android-ndk-r10e-%s-%s.bin", ndkDownloadBaseURL, runtime.GOOS, arch),
ndkExtract: selfExtract,
ndkAPILevel: 21,
platformToolsVersion: map[string]string{
"darwin": "sdk-repo-darwin-platform-tools-2219242",
"linux": "sdk-repo-linux-platform-tools-2219198",
},
baseVersion: "5",
},
}, "9"),
}
profilesmanager.Register(m)
}
type Manager struct {
profileInstaller string
profileName string
qualifiedName string
root jiri.RelPath
androidRoot jiri.RelPath
ndkRoot jiri.RelPath
platformRoot jiri.RelPath
versionInfo *profiles.VersionInfo
spec versionSpec
}
func (m Manager) Name() string {
return m.profileName
}
func (m Manager) Installer() string {
return m.profileInstaller
}
func (m Manager) String() string {
return fmt.Sprintf("%s[%s]", m.qualifiedName, m.versionInfo.Default())
}
func (m Manager) Info() string {
return `
The android profile provides support for cross-compiling from linux or darwin
to android. It only supports one target 'arm-android' and will assume that
as the default value if one is not supplied. It installs the android NDK
and a go compiler configured to use it.`
}
func (m Manager) VersionInfo() *profiles.VersionInfo {
return m.versionInfo
}
func (m *Manager) AddFlags(flags *flag.FlagSet, action profiles.Action) {
}
func (m *Manager) initForTarget(jirix *jiri.X, action string, root jiri.RelPath, target *profiles.Target) error {
if !target.IsSet() {
def := *target
target.Set("arm-android")
fmt.Fprintf(jirix.Stdout(), "Default target %v reinterpreted as: %v\n", def, target)
} else {
if (target.Arch() != "amd64" && target.Arch() != "arm") || target.OS() != "android" {
return fmt.Errorf("this profile can only be %v as arm-android or amd64-android and not as %v", action, target)
}
}
if err := m.versionInfo.Lookup(target.Version(), &m.spec); err != nil {
return err
}
m.root = root
m.androidRoot = root.Join("android")
archName, err := ndkArch(target.Arch())
if err != nil {
return err
}
m.ndkRoot = m.androidRoot.Join("ndk-toolchain", fmt.Sprintf("%s-%d", archName, m.spec.ndkAPILevel))
m.platformRoot = m.androidRoot.Join("platform-tools", m.spec.platformToolsVersion[runtime.GOOS])
return nil
}
func (m *Manager) OSPackages(jirix *jiri.X, pdb *profiles.DB, root jiri.RelPath, target profiles.Target) ([]string, error) {
var pkgs []string
switch runtime.GOOS {
case "linux":
pkgs = []string{"ant", "autoconf", "bzip2", "default-jdk", "gawk", "lib32z1", "lib32stdc++6"}
case "darwin":
pkgs = []string{"ant", "autoconf", "gawk"}
default:
return nil, fmt.Errorf("unsupported OS: %s", runtime.GOOS)
}
return pkgs, nil
}
func (m *Manager) Install(jirix *jiri.X, pdb *profiles.DB, root jiri.RelPath, target profiles.Target) error {
var err error
var baseEnv []string
if err = m.initForTarget(jirix, "installed", root, &target); err != nil {
return err
}
if p := pdb.LookupProfileTarget(m.profileInstaller, m.profileName, target); p != nil {
fmt.Fprintf(jirix.Stdout(), "%v %v is already installed as %v\n", m.profileName, target, p)
return nil
}
if err = m.installAndroidNDK(jirix, target); err != nil {
return err
}
// Note that the ordering is important here. Installing base depends upon the NDK
// being installed. Essentially there is a circular dependency between base and android.
if baseEnv, err = m.installBase(jirix, pdb, root, target); err != nil {
return err
}
if target, err = m.installAndroidPlatformTools(jirix, target); err != nil {
return err
}
// Merge the target and baseProfile environments.
env := &envvar.Vars{}
if target.Arch() == "amd64" {
ldflags := env.GetTokens("CGO_LDFLAGS", " ")
ldflags = append(ldflags, m.ndkRoot.Join("x86_64-linux-android", "lib64", "libstdc++.a").Symbolic())
env.SetTokens("CGO_LDFLAGS", ldflags, " ")
}
profilesreader.MergeEnv(profilesreader.ProfileMergePolicies(), env, target.Env.Vars, baseEnv)
target.Env.Vars = env.ToSlice()
target.InstallationDir = string(m.androidRoot)
pdb.InstallProfile(m.profileInstaller, m.profileName, string(m.androidRoot))
return pdb.AddProfileTarget(m.profileInstaller, m.profileName, target)
}
func (m *Manager) Uninstall(jirix *jiri.X, pdb *profiles.DB, root jiri.RelPath, target profiles.Target) error {
pdb.RemoveProfileTarget(m.profileInstaller, m.profileName, target)
return nil
}
func (m *Manager) installBase(jirix *jiri.X, pdb *profiles.DB, root jiri.RelPath, target profiles.Target) ([]string, error) {
// It turns out that you can't install base for *-android without setting
// at least one of these variables.
env := fmt.Sprintf("ANDROID_NDK_DIR=%s,GOARM=7", m.ndkRoot.Symbolic())
// target.String() only preserves the arch, opsys and version fields.
// So this is a good way to copy the arch/opsys and we just have to set
// the version.
baseTarget, err := profiles.NewTarget(target.String(), env)
baseTarget.SetVersion(m.spec.baseVersion)
if err != nil {
return nil, err
}
if err := profilesmanager.EnsureProfileTargetIsInstalled(jirix, pdb, m.profileInstaller, "base", root, baseTarget); err != nil {
return nil, err
}
return pdb.EnvFromProfile(m.profileInstaller, "base", baseTarget), nil
}
// installAndroidNDK installs the android NDK toolchain.
func (m *Manager) installAndroidNDK(jirix *jiri.X, target profiles.Target) (e error) {
// Download Android NDK.
installNdkFn := func() error {
s := jirix.NewSeq()
tmpDir, err := s.TempDir("", "")
if err != nil {
return fmt.Errorf("TempDir() failed: %v", err)
}
defer collect.Error(func() error { return jirix.NewSeq().RemoveAll(tmpDir).Done() }, &e)
extractDir, err := s.TempDir(tmpDir, "extract")
if err != nil {
return err
}
local := filepath.Join(tmpDir, path.Base(m.spec.ndkDownloadURL))
s.Run("curl", "-Lo", local, m.spec.ndkDownloadURL)
files, err := m.spec.ndkExtract(s, local, extractDir).ReadDir(extractDir)
if err != nil {
return err
}
if len(files) != 1 {
return fmt.Errorf("expected one directory under %s, got: %v", extractDir, files)
}
ndkBin := filepath.Join(extractDir, files[0].Name(), "build", "tools", "make-standalone-toolchain.sh")
archOption, err := ndkArch(target.Arch())
if err != nil {
return err
}
ndkArgs := []string{ndkBin, fmt.Sprintf("--platform=android-%d", m.spec.ndkAPILevel), fmt.Sprintf("--arch=%s", archOption), "--install-dir=" + m.ndkRoot.Abs(jirix)}
return s.Last("bash", ndkArgs...)
}
return profilesutil.AtomicAction(jirix, installNdkFn, m.ndkRoot.Abs(jirix), "Download Android NDK")
}
// installAndroidPlatformTools installs the android platform tools.
func (m *Manager) installAndroidPlatformTools(jirix *jiri.X, target profiles.Target) (profiles.Target, error) {
suffix := m.spec.platformToolsVersion[runtime.GOOS]
if suffix == "" {
return target, nil
}
tmpDir, err := jirix.NewSeq().TempDir("", "")
if err != nil {
return target, err
}
defer jirix.NewSeq().RemoveAll(tmpDir)
outDir := m.platformRoot.Abs(jirix)
target.Env.Set("PATH=" + m.platformRoot.Symbolic())
fn := func() error {
androidPlatformToolsZipFile := filepath.Join(tmpDir, "platform-tools.zip")
return jirix.NewSeq().
Call(func() error {
url := platformToolsBaseURL + "/" + suffix + ".zip"
return profilesutil.Fetch(jirix, androidPlatformToolsZipFile, url)
}, "fetch android platform tools").
Call(func() error {
return profilesutil.Unzip(jirix, androidPlatformToolsZipFile, tmpDir)
}, "unzip android platform tools").
MkdirAll(filepath.Dir(outDir), profilesutil.DefaultDirPerm).
Rename(filepath.Join(tmpDir, "platform-tools"), outDir).
Done()
}
return target, profilesutil.AtomicAction(jirix, fn, outDir, "Install Android Platform Tools")
}