blob: c14d64a1a7e605b06aa715ee9984ae6410a4b33b [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 syncbase_profile
import (
"bufio"
"bytes"
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
"v.io/jiri"
"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 (
profileVersion = "1"
)
func Register(installer, profile string) {
m := &Manager{
profileInstaller: installer,
profileName: profile,
qualifiedName: profiles.QualifiedProfileName(installer, profile),
versionInfo: profiles.NewVersionInfo(profile, map[string]interface{}{
"1": "1",
"2": "2",
"3": "3",
}, "3"),
}
profilesmanager.Register(m)
}
type Manager struct {
profileInstaller, profileName, qualifiedName string
syncbaseRoot, syncbaseInstRoot jiri.RelPath
snappySrcDir, leveldbSrcDir jiri.RelPath
snappyInstDir, leveldbInstDir jiri.RelPath
versionInfo *profiles.VersionInfo
}
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 syncbase profile provides support for syncbase, in particular the snappy and
leveldb libraries.`
}
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, root jiri.RelPath, target profiles.Target) {
m.syncbaseRoot = root.Join("cout")
m.snappySrcDir = jiri.NewRelPath("third_party", "csrc", "snappy-1.1.2")
m.leveldbSrcDir = jiri.NewRelPath("third_party", "csrc", "leveldb")
targetDir := target.TargetSpecificDirname()
m.syncbaseInstRoot = m.syncbaseRoot.Join(targetDir)
m.snappyInstDir = m.syncbaseInstRoot.Join("snappy")
m.leveldbInstDir = m.syncbaseInstRoot.Join("leveldb")
if jirix.Verbose() {
fmt.Fprintf(jirix.Stdout(), "Installation Directories for: %s\n", target)
fmt.Fprintf(jirix.Stdout(), "Syncbase installation dir: %s\n", m.syncbaseInstRoot)
fmt.Fprintf(jirix.Stdout(), "Snappy: %s\n", m.snappyInstDir)
fmt.Fprintf(jirix.Stdout(), "Leveldb: %s\n", m.leveldbInstDir)
}
}
// setSyncbaseEnv adds the LevelDB third-party C++ libraries Vanadium
// Go code depends on to the CGO_CFLAGS and CGO_LDFLAGS variables.
func (m *Manager) syncbaseEnv(jirix *jiri.X, target profiles.Target) ([]string, error) {
env := envvar.VarsFromSlice([]string{})
for _, dir := range []jiri.RelPath{
m.leveldbInstDir,
m.snappyInstDir,
} {
cflags := env.GetTokens("CGO_CFLAGS", " ")
cxxflags := env.GetTokens("CGO_CXXFLAGS", " ")
ldflags := env.GetTokens("CGO_LDFLAGS", " ")
if _, err := jirix.NewSeq().Stat(dir.Abs(jirix)); err != nil {
if !runutil.IsNotExist(err) {
return nil, err
}
continue
}
cflags = append(cflags, filepath.Join("-I"+dir.Symbolic(), "include"))
cxxflags = append(cxxflags, filepath.Join("-I"+dir.Symbolic(), "include"))
ldflags = append(ldflags, filepath.Join("-L"+dir.Symbolic(), "lib"))
if target.Arch() == "linux" {
ldflags = append(ldflags, "-Wl,-rpath", filepath.Join(dir.Symbolic(), "lib"))
}
env.SetTokens("CGO_CFLAGS", cflags, " ")
env.SetTokens("CGO_CXXFLAGS", cxxflags, " ")
env.SetTokens("CGO_LDFLAGS", ldflags, " ")
}
return env.ToSlice(), nil
}
func (m *Manager) OSPackages(jirix *jiri.X, pdb *profiles.DB, root jiri.RelPath, target profiles.Target) ([]string, error) {
switch runtime.GOOS {
case "darwin":
return []string{"autoconf", "automake", "libtool", "pkg-config"}, nil
case "linux":
return []string{"autoconf", "automake", "g++", "g++-multilib",
"gcc-multilib", "libtool", "pkg-config"}, nil
default:
return nil, fmt.Errorf("%q is not supported", runtime.GOOS)
}
return nil, nil
}
func (m *Manager) Install(jirix *jiri.X, pdb *profiles.DB, root jiri.RelPath, target profiles.Target) error {
m.initForTarget(jirix, root, target)
if err := m.installCommon(jirix, pdb, root, target); err != nil {
return err
}
env := envvar.VarsFromSlice(target.Env.Vars)
syncbaseEnv, err := m.syncbaseEnv(jirix, target)
if err != nil {
return err
}
profilesreader.MergeEnv(profilesreader.ProfileMergePolicies(), env, syncbaseEnv)
target.Env.Vars = env.ToSlice()
target.InstallationDir = string(m.syncbaseInstRoot)
pdb.InstallProfile(m.profileInstaller, m.profileName, string(m.syncbaseRoot))
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 {
m.initForTarget(jirix, root, target)
if err := jirix.NewSeq().
RemoveAll(m.snappyInstDir.Abs(jirix)).
RemoveAll(m.leveldbInstDir.Abs(jirix)).Done(); err != nil {
return err
}
pdb.RemoveProfileTarget(m.profileInstaller, m.profileName, target)
return nil
}
// initXCC sets the environment variables in 'env' for use with cross-compilers.
func (m *Manager) initXCC(env map[string]string, pdb *profiles.DB, target profiles.Target) error {
target.SetVersion("")
goProfile := pdb.LookupProfileTarget(m.profileInstaller, "go", target)
if goProfile == nil {
return fmt.Errorf("go profile is not installed for %s", target)
}
goEnv := envvar.VarsFromSlice(goProfile.Env.Vars)
// TODO(ashankar): Change the go profile installation so it sets CC and CXX appropriately.
env["CC"] = goEnv.Get("CC_FOR_TARGET")
env["CXX"] = goEnv.Get("CXX_FOR_TARGET")
return nil
}
func (m *Manager) initClangEnv(jirix *jiri.X, pdb *profiles.DB, target profiles.Target) (map[string]string, error) {
target.SetVersion("")
goProfile := pdb.LookupProfileTarget(m.profileInstaller, m.profileName, target)
if goProfile == nil {
return nil, fmt.Errorf("go profile is not installed for %s", target)
}
goEnv := envvar.VarsFromSlice(goProfile.Env.Vars)
jiri.ExpandEnv(jirix, goEnv)
path := envvar.SplitTokens(jirix.Env()["PATH"], ":")
path = append([]string{goEnv.Get("BINUTILS_BIN")}, path...)
env := map[string]string{
"CC": goEnv.Get("CLANG"),
"CXX": goEnv.Get("CLANG++"),
"LDFLAGS": goEnv.Get("LDFLAGS"),
"AR": goEnv.Get("AR"),
"RANLIB": goEnv.Get("RANLIB"),
"PATH": envvar.JoinTokens(path, ":"),
"TARGET": goEnv.Get("TARGET"),
}
for k, v := range env {
if len(v) == 0 {
return nil, fmt.Errorf("variable %q is not set", k)
}
}
return env, nil
}
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)
}
}
// iosSDKName determines if we are using the simulator or device SDK for a given target architecture.
func iosSDKName(goArch string) (string, error) {
switch goArch {
case "386", "amd64":
return "iphonesimulator", nil
case "arm", "arm64":
return "iphoneos", nil
default:
return "", fmt.Errorf("Unsupported architecture for iOS: %v", goArch)
}
}
// iosSDKPath asks the system for the path to a given autodetected iOS SDK (device or simulator).
func iosSDKPath(jirix *jiri.X, target profiles.Target) (string, error) {
sdk, err := iosSDKName(target.Arch())
if err != nil {
return "", err
}
var out bytes.Buffer
outWriter := bufio.NewWriter(&out)
s := jirix.NewSeq()
if err := s.Capture(outWriter, outWriter).Last("xcrun", "--sdk", sdk, "--show-sdk-path"); err != nil {
return "", fmt.Errorf("Unable to get iOS SDK path from xcrun: %s", out.String())
}
outWriter.Flush()
return strings.TrimSpace(out.String()), nil
}
// iosToolPath asks the system for the path to a tool like clang for a given auto-detected SDK (device or simulator).
func iosToolPath(jirix *jiri.X, target profiles.Target, tool string) (string, error) {
sdk, err := iosSDKName(target.Arch())
if err != nil {
return "", err
}
var out bytes.Buffer
outWriter := bufio.NewWriter(&out)
s := jirix.NewSeq()
if err := s.Capture(outWriter, outWriter).Last("xcrun", "--sdk", sdk, "--find", tool); err != nil {
return "", fmt.Errorf("Unable to get %s path from xcrun: %s", tool, out.String())
}
outWriter.Flush()
return strings.TrimSpace(out.String()), nil
}
func iosArch(goArch string) (string, error) {
switch goArch {
case "arm":
return "armv7", nil
case "arm64":
return "arm64", nil
case "386":
return "i386", nil
case "amd64":
return "x86_64", nil
default:
return "", fmt.Errorf("Unsupported architecture for iOS: %v", goArch)
}
}
// initIOSEnv sets the appropriate environmental vars based on autodetecting the
// device or simulator environment (from the target architecture) to use the right clang and
// configure it for the iOS SDK. It returns the clang env flags or error.
func initIOSEnv(jirix *jiri.X, target profiles.Target) (map[string]string, error) {
sdkName, err := iosSDKName(target.Arch())
if err != nil {
return nil, err
}
sysroot, err := iosSDKPath(jirix, target)
if err != nil {
return nil, err
}
clangPath, err := iosToolPath(jirix, target, "clang")
if err != nil {
return nil, err
}
clangxxPath, err := iosToolPath(jirix, target, "clang++")
if err != nil {
return nil, err
}
iosArch, err := iosArch(target.Arch())
if err != nil {
return nil, err
}
// Currently we are setting a deployment target of 8 as it gives us the most APIs and the
// ability to load shared libraries while achieving a high usage rate (~96% as of Jan 15 2016).
deploymentTarget := "8.0"
// TODO(zinman): Enable bitcode via -fembed-bitcode as currently it errors with:
// ld: -bind_at_load and -bitcode_bundle (Xcode setting ENABLE_BITCODE=YES) cannot be used together
minVersionEnvFlag := sdkName
if minVersionEnvFlag == "iphonesimulator" {
minVersionEnvFlag = "ios-simulator"
}
// either -miphoneos-version-min or -mios-simulator-version-min
iosFlags := fmt.Sprintf("-m%v-version-min=%v -isysroot %v", minVersionEnvFlag, deploymentTarget, sysroot)
env := map[string]string{
"IPHONEOS_DEPLOYMENT_TARGET": deploymentTarget,
"CFLAGS": fmt.Sprintf("%v -arch %v", iosFlags, iosArch),
"CXXFLAGS": fmt.Sprintf("%v -arch %v", iosFlags, iosArch),
"LDFLAGS": iosFlags,
"CC": clangPath,
"CXX": clangxxPath,
}
if sdkName == "iphoneos" {
env["TARGET"] = "arm-apple-darwin" // this is true for 32 and 64-bits
}
return env, nil
}
// installSyncbaseCommon installs the syncbase profile.
func (m *Manager) installCommon(jirix *jiri.X, pdb *profiles.DB, root jiri.RelPath, target profiles.Target) (e error) {
if target.Arch() != runtime.GOARCH && target.Arch() != "386" && runtime.GOOS != "darwin" {
// In this special circumstance, old installations of version
// "1" had a bug - the code was built for the host
// architecture. So, uninstall the buggy, unusable V1 in this
// case.
if target.Version() != "1" {
// Check if V1 is installed and if so uninstall it.
v1 := target
v1.SetVersion("1")
if gotV1 := pdb.LookupProfileTarget(m.profileInstaller, m.profileName, v1); gotV1 != nil {
if jirix.Verbose() {
fmt.Fprintf(jirix.Stdout(), "Uninstalling bad %v target : %v", m.profileName, gotV1.String())
}
if err := m.Uninstall(jirix, pdb, root, *gotV1); err != nil {
return err
}
}
}
}
// Build and install Snappy.
installSnappyFn := func() error {
s := jirix.NewSeq()
snappySrcDir := m.snappySrcDir.Abs(jirix)
// Ignore errors from make distclean.
s.Pushd(snappySrcDir).Capture(ioutil.Discard, ioutil.Discard).Last("make", "distclean")
if err := s.Pushd(snappySrcDir).
Last("autoreconf", "--install", "--force", "--verbose"); err != nil {
return err
}
args := []string{
fmt.Sprintf("--prefix=%v", m.snappyInstDir.Abs(jirix)),
"--enable-shared=false",
}
env := map[string]string{
// NOTE(nlacasse): The -fPIC flag is needed to compile
// Syncbase Mojo service. This is set here since we don't
// currently have a specific target. Targets that don't
// require it should override it.
"CXXFLAGS": " -fPIC",
}
switch {
case target.OS() == "android":
ev := envvar.VarsFromSlice(target.CommandLineEnv().Vars)
jiri.ExpandEnv(jirix, ev)
ndk := ev.Get("ANDROID_NDK_DIR")
if len(ndk) == 0 {
return fmt.Errorf("ANDROID_NDK_DIR not specified in the command line environment")
}
abi, err := androidABI(target.Arch())
if err != nil {
return err
}
env["CC"] = filepath.Join(ndk, "bin", fmt.Sprintf("%s-gcc", abi))
env["CXX"] = filepath.Join(ndk, "bin", fmt.Sprintf("%s-g++", abi))
env["AR"] = filepath.Join(ndk, "bin", fmt.Sprintf("%s-ar", abi))
env["RANLIB"] = filepath.Join(ndk, "bin", fmt.Sprintf("%s-ranlib", abi))
args = append(args,
fmt.Sprintf("--host=%s", abi),
fmt.Sprintf("--target=%s", abi),
)
case target.OS() == "ios":
clangEnv, err := initIOSEnv(jirix, target)
if err != nil {
return err
}
env = envvar.MergeMaps(env, clangEnv)
if target, ok := clangEnv["TARGET"]; ok {
args = append(args, "--host="+target)
}
case target.OS() == "fnl" && target.Arch() == "amd64" && runtime.GOOS == "linux":
fnlRoot := os.Getenv("FNL_JIRI_ROOT")
if len(fnlRoot) == 0 {
return fmt.Errorf("FNL_JIRI_ROOT not specified in the command line environment")
}
muslBin := filepath.Join(fnlRoot, "out/root/tools/x86_64-fuchsia-linux-musl/bin")
env["CC"] = filepath.Join(muslBin, "x86_64-fuchsia-linux-musl-gcc")
env["CXX"] = filepath.Join(muslBin, "x86_64-fuchsia-linux-musl-g++")
args = append(args, "--host=amd64-linux")
case target.OS() == "linux" && target.Arch() == "arm" && runtime.GOOS == "darwin":
clangEnv, err := m.initClangEnv(jirix, pdb, target)
if err != nil {
return err
}
env = clangEnv
args = append(args,
"--host="+clangEnv["TARGET"],
"--target="+clangEnv["TARGET"],
)
case target.Arch() == "386":
env["CC"] = "gcc -m32"
env["CXX"] = "g++ -m32"
case target.Arch() != runtime.GOARCH:
if err := m.initXCC(env, pdb, target); err != nil {
return err
}
args = append(args,
"--host="+target.Arch(),
"--target="+target.Arch())
}
if jirix.Verbose() {
fmt.Fprintf(jirix.Stdout(), "Environment: %s\n", strings.Join(envvar.MapToSlice(env), " "))
}
return s.Pushd(snappySrcDir).
Env(env).Run("./configure", args...).
Run("make", "clean").
Env(env).Run("make", fmt.Sprintf("-j%d", runtime.NumCPU())).
Env(env).Run("make", "install").
Last("make", "distclean")
}
if err := profilesutil.AtomicAction(jirix, installSnappyFn, m.snappyInstDir.Abs(jirix), "Build and install Snappy"); err != nil {
return err
}
// Build and install LevelDB.
installLeveldbFn := func() error {
leveldbIncludeDir := m.leveldbInstDir.Join("include").Abs(jirix)
leveldbLibDir := m.leveldbInstDir.Join("lib").Abs(jirix)
s := jirix.NewSeq()
err := s.Chdir(m.leveldbSrcDir.Abs(jirix)).
Run("mkdir", "-p", m.leveldbInstDir.Abs(jirix)).
Run("cp", "-R", "include", leveldbIncludeDir).
Last("mkdir", leveldbLibDir)
if err != nil {
return err
}
env := map[string]string{
"PREFIX": leveldbLibDir,
// NOTE(nlacasse): The -fPIC flag is needed to compile Syncbase Mojo service.
"CXXFLAGS": "-I" + filepath.Join(m.snappyInstDir.Abs(jirix), "include") + " -fPIC",
"LDFLAGS": "-L" + filepath.Join(m.snappyInstDir.Abs(jirix), "lib"),
}
switch {
case target.OS() == "android":
ev := envvar.VarsFromSlice(target.CommandLineEnv().Vars)
jiri.ExpandEnv(jirix, ev)
ndk := ev.Get("ANDROID_NDK_DIR")
if len(ndk) == 0 {
return fmt.Errorf("ANDROID_NDK_DIR not specified in the command line environment")
}
abi, err := androidABI(target.Arch())
if err != nil {
return err
}
env["CC"] = filepath.Join(ndk, "bin", fmt.Sprintf("%s-gcc", abi))
env["CXX"] = filepath.Join(ndk, "bin", fmt.Sprintf("%s-g++", abi))
env["TARGET_OS"] = "OS_ANDROID_CROSSCOMPILE"
env["AR"] = filepath.Join(ndk, "bin", fmt.Sprintf("%s-ar", abi))
env["RANLIB"] = filepath.Join(ndk, "bin", fmt.Sprintf("%s-ranlib", abi))
case target.OS() == "ios":
// NOTE(zinman): LevelDB has its own ability to prepare for the iOS platform by setting TARGET_OS,
// but we still want to use our existing minimum iOS deployment target.
clangEnv, err := initIOSEnv(jirix, target)
if err != nil {
return err
}
env["TARGET_OS"] = "IOS"
env["IPHONEOS_DEPLOYMENT_TARGET"] = clangEnv["IPHONEOS_DEPLOYMENT_TARGET"]
case target.OS() == "fnl" && target.Arch() == "amd64" && runtime.GOOS == "linux":
fnlRoot := os.Getenv("FNL_JIRI_ROOT")
if len(fnlRoot) == 0 {
return fmt.Errorf("FNL_JIRI_ROOT not specified in the command line environment")
}
muslBin := filepath.Join(fnlRoot, "out/root/tools/x86_64-fuchsia-linux-musl/bin")
env["CC"] = filepath.Join(muslBin, "x86_64-fuchsia-linux-musl-gcc")
env["CXX"] = filepath.Join(muslBin, "x86_64-fuchsia-linux-musl-g++")
env["AR"] = filepath.Join(muslBin, "x86_64-fuchsia-linux-musl-ar")
case target.OS() == "linux" && target.Arch() == "arm" && runtime.GOOS == "darwin":
clangEnv, err := m.initClangEnv(jirix, pdb, target)
if err != nil {
return err
}
env["CXXFLAGS"] = "-I" + filepath.Join(m.snappyInstDir.Abs(jirix), "include")
env["TARGET_OS"] = "Linux"
env = envvar.MergeMaps(env, clangEnv)
case target.Arch() == "386":
env["CC"] = "gcc -m32"
env["CXX"] = "g++ -m32"
case target.Arch() != runtime.GOARCH:
if err := m.initXCC(env, pdb, target); err != nil {
return err
}
}
if jirix.Verbose() {
fmt.Fprintf(jirix.Stdout(), "Environment: %s\n", strings.Join(envvar.MapToSlice(env), " "))
}
err = s.Run("make", "clean").
Env(env).Last("make", "static")
if err != nil {
return err
}
if target.OS() == "ios" {
// Clean up the iOS binary to trim for this target architecture. The leveldb makefile will be
// produce VERY fat binaries (i386, x86_64, armv6, armv7, armv7s, arm64). As we eventually combine
// all our libraries at a future point into a fat binary for distribution, we seek to minimize
// conflicts by removing unnecessary architectures here at this build juncture.
// N.B. Apple's "standard architectures" are only armv7 (pre-iPhone 5) and arm64 (future)
// at this point (Jan 15 2016). iOS 8, our current minimum, runs on armv7.
leveldbStaticLibPath := filepath.Join(leveldbLibDir, "libleveldb.a")
targetIosArch, err := iosArch(target.Arch())
if err != nil {
return err
}
if err := s.Last("lipo", leveldbStaticLibPath, "-output", leveldbStaticLibPath, "-thin", targetIosArch); err != nil {
return err
}
}
return nil
}
if err := profilesutil.AtomicAction(jirix, installLeveldbFn, m.leveldbInstDir.Abs(jirix), "Build and install LevelDB"); err != nil {
return err
}
return nil
}
func androidABI(targetArch string) (string, error) {
switch targetArch {
case "amd64":
return "x86_64-linux-android", nil
case "arm":
return "arm-linux-androideabi", nil
default:
return "", fmt.Errorf("could not locate android abi for target arch %s", targetArch)
}
}