blob: bee09dbed92ccaebf201d8e0dac89a013d70f9cf [file] [log] [blame] [edit]
// 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 java_profile
import (
"bufio"
"bytes"
"errors"
"flag"
"fmt"
"os"
"path/filepath"
"strings"
"v.io/jiri"
"v.io/jiri/profiles"
"v.io/jiri/profiles/profilesmanager"
"v.io/jiri/profiles/profilesreader"
"v.io/x/lib/envvar"
)
type versionSpec struct {
jdkVersion string
}
func Register(installer, profile string) {
m := &Manager{
profileInstaller: installer,
profileName: profile,
qualifiedName: profiles.QualifiedProfileName(installer, profile),
versionInfo: profiles.NewVersionInfo(profile, map[string]interface{}{
"1.7+": versionSpec{"1.7+"},
"1.8+": versionSpec{"1.8+"},
}, "1.8+"),
}
profilesmanager.Register(m)
}
type Manager struct {
profileInstaller, profileName, qualifiedName string
root, javaRoot 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 java profile provides support for Java and in particular installs java related
tools such as gradle. It does not install a jre, but rather attempts to locate one
on the current system and prompts the user to install it if not present. It also
installs the android profile since android is the primary use of Java. It only
supports a single target of 'arm-android' and assumes it as the default.`
}
func (m Manager) VersionInfo() *profiles.VersionInfo {
return m.versionInfo
}
func (m *Manager) AddFlags(flags *flag.FlagSet, action profiles.Action) {
}
func (m *Manager) initForTarget(root jiri.RelPath, target profiles.Target) error {
m.root = root
m.javaRoot = root.Join("java")
if err := m.versionInfo.Lookup(target.Version(), &m.spec); err != nil {
return err
}
return nil
}
func (m *Manager) OSPackages(jirix *jiri.X, pdb *profiles.DB, root jiri.RelPath, target profiles.Target) ([]string, error) {
switch target.OS() {
case "darwin", "linux":
return []string{"gradle"}, nil
default:
return nil, fmt.Errorf("OS %q is not supported", target.OS())
}
}
func (m *Manager) Install(jirix *jiri.X, pdb *profiles.DB, root jiri.RelPath, target profiles.Target) error {
if err := m.initForTarget(root, target); err != nil {
return err
}
javaHome, err := m.install(jirix, target)
if err != nil {
return err
}
baseTarget := target
baseTarget.SetVersion("")
if err := profilesmanager.EnsureProfileTargetIsInstalled(jirix, pdb, m.profileInstaller, "base", root, baseTarget); err != nil {
return err
}
// NOTE(spetrovic): For now, we install android profile along with Java,
// as the two are bundled up for ease of development.
androidArmTarget, err := profiles.NewTarget("arm-android", "")
if err != nil {
return err
}
if err := profilesmanager.EnsureProfileTargetIsInstalled(jirix, pdb, m.profileInstaller, "android", root, androidArmTarget); err != nil {
return err
}
androidAmd64Target, err := profiles.NewTarget("amd64-android", "")
if err != nil {
return err
}
if err := profilesmanager.EnsureProfileTargetIsInstalled(jirix, pdb, m.profileInstaller, "android", root, androidAmd64Target); err != nil {
return err
}
// Merge the environments using those in the target as the base
// with those from the base profile and then the java ones
// we want to set here.
env := envvar.VarsFromSlice(target.Env.Vars)
javaProfileEnv := []string{
fmt.Sprintf("CGO_CFLAGS=-I%s -I%s", filepath.Join(javaHome, "include"),
filepath.Join(javaHome, "include", target.OS())),
"JAVA_HOME=" + javaHome,
}
baseProfileEnv := pdb.EnvFromProfile(m.profileInstaller, "base", baseTarget)
profilesreader.MergeEnv(profilesreader.ProfileMergePolicies(), env, baseProfileEnv, javaProfileEnv)
target.Env.Vars = env.ToSlice()
target.InstallationDir = javaHome
pdb.InstallProfile(m.profileInstaller, m.profileName, string(m.javaRoot))
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 {
if err := m.initForTarget(root, target); err != nil {
return err
}
if err := jirix.NewSeq().RemoveAll(m.javaRoot.Abs(jirix)).Done(); err != nil {
return err
}
pdb.RemoveProfileTarget(m.profileInstaller, m.profileName, target)
return nil
}
func (m *Manager) install(jirix *jiri.X, target profiles.Target) (string, error) {
switch target.OS() {
case "darwin":
javaHome, err := getJDKDarwin(jirix, m.spec)
if err == nil {
return javaHome, nil
}
fmt.Fprintf(os.Stderr, "Couldn't find an existing Java installation: %v", err)
// Prompt the user to install JDK.
// (Note that JDK cannot be installed via Homebrew.)
javaHomeBin := "/usr/libexec/java_home"
jirix.NewSeq().Last(javaHomeBin, "-t", "CommandLine", "--request")
return "", fmt.Errorf("Please follow the OS X prompt instructions to install JDK, then set JAVA_HOME and re-run the profile installation command.")
case "linux":
javaHome, err := getJDKLinux(jirix, m.spec)
if err == nil {
return javaHome, nil
}
fmt.Fprintf(os.Stderr, "Couldn't find an existing Java installation: %v", err)
// Prompt the user to install JDK.
// (Note that Oracle JDKs cannot be installed via apt-get.)
dlURL := "http://www.oracle.com/technetwork/java/javase/downloads/index.html"
jirix.NewSeq().Last("xdg-open", dlURL)
return "", fmt.Errorf("Please follow the instructions in the browser to install JDK, then set JAVA_HOME and re-run the profile installation command")
default:
return "", fmt.Errorf("OS %q is not supported", target.OS())
}
}
func checkInstall(jirix *jiri.X, home, version string) error {
s := jirix.NewSeq()
if _, err := s.Stat(filepath.Join(home, "include", "jni.h")); err != nil {
return err
}
var out bytes.Buffer
javacPath := filepath.Join(home, "bin", "javac")
if err := s.Capture(&out, &out).Last(javacPath, "-version"); err != nil {
return err
}
if out.Len() == 0 {
return errors.New("couldn't find a valid javac at: " + javacPath)
}
javacVersion := strings.TrimPrefix(out.String(), "javac ")
if !strings.HasPrefix(javacVersion, strings.TrimSuffix(version, "+")) {
return fmt.Errorf("want javac version %v, got %v from output %v.", version, javacVersion, out.String())
}
return nil
}
func getJDKLinux(jirix *jiri.X, spec versionSpec) (string, error) {
if javaHome := os.Getenv("JAVA_HOME"); len(javaHome) > 0 {
err := checkInstall(jirix, javaHome, spec.jdkVersion)
if err == nil {
return javaHome, nil
}
fmt.Fprintf(os.Stderr, "JAVA_HOME (%s) is incompatible with required profile version: %v; trying to find a compatible system installation.", javaHome, err)
defer fmt.Fprint(os.Stderr, "Done looking for system installation.")
}
// JAVA_HOME doesn't point to the right version: check the system installation.
javacBin := "/usr/bin/javac"
var out bytes.Buffer
if err := jirix.NewSeq().Capture(&out, &out).Last("readlink", "-f", javacBin); err != nil {
return "", err
}
if out.Len() == 0 {
return "", errors.New("No Java installed under /usr/bin/javac")
}
// Strip "/bin/javac" from the returned path.
javaHome := strings.TrimSuffix(out.String(), "/bin/javac\n")
if err := checkInstall(jirix, javaHome, spec.jdkVersion); err != nil {
return "", errors.New("Java installed in /usr/bin/javac is incompatible with profile version: " + spec.jdkVersion)
}
return javaHome, nil
}
func getJDKDarwin(jirix *jiri.X, spec versionSpec) (string, error) {
if javaHome := os.Getenv("JAVA_HOME"); len(javaHome) > 0 {
err := checkInstall(jirix, javaHome, spec.jdkVersion)
if err == nil {
return javaHome, nil
}
fmt.Fprintf(os.Stderr, "JAVA_HOME (%s) is incompatible with required profile version %v; trying to find a compatible system installation", javaHome, err)
defer fmt.Fprint(os.Stderr, "Done looking for system installation.")
}
// JAVA_HOME doesn't point to the right version: check the system installation.
javaHomeBin := "/usr/libexec/java_home"
var out bytes.Buffer
if err := jirix.NewSeq().Capture(&out, &out).Last(javaHomeBin, "-t", "CommandLine", "-v", spec.jdkVersion); err != nil {
return "", err
}
if out.Len() == 0 {
return "", errors.New("Couldn't find a valid Java system installation.")
}
jdkLoc, _, err := bufio.NewReader(strings.NewReader(out.String())).ReadLine()
if err != nil {
return "", fmt.Errorf("Couldn't find a valid Java system installation: %v", err)
}
if err := checkInstall(jirix, string(jdkLoc), spec.jdkVersion); err != nil {
return "", fmt.Errorf("Java system installation is incompatible with profile version %s: %v", spec.jdkVersion, err)
}
return string(jdkLoc), nil
}