blob: 726cdd83dc7b67ca6424dcb74703fc9bea620d14 [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 jiri provides utilities used by the jiri tool and related tools.
package jiri
// TODO(toddw): Rename this package to v.io/jiri, and rename the tool itself to
// v.io/jiri/cmd/jiri
import (
"fmt"
"os"
"path/filepath"
"v.io/jiri/tool"
"v.io/x/lib/cmdline"
"v.io/x/lib/envvar"
"v.io/x/lib/timing"
)
const (
RootEnv = "JIRI_ROOT"
RootMetaDir = ".jiri_root"
ProjectMetaDir = ".jiri"
ProjectMetaFile = "metadata.v2"
ProfilesDBDir = RootMetaDir + string(filepath.Separator) + "profile_db"
ProfilesRootDir = RootMetaDir + string(filepath.Separator) + "profiles"
JiriManifestFile = ".jiri_manifest"
// PreservePathEnv is the name of the environment variable that, when set to a
// non-empty value, causes jiri tools to use the existing PATH variable,
// rather than mutating it.
PreservePathEnv = "JIRI_PRESERVE_PATH"
)
// X holds the execution environment for the jiri tool and related tools. This
// includes the jiri filesystem root directory.
//
// TODO(toddw): Other jiri state should be transitioned to this struct,
// including the manifest and related operations.
type X struct {
*tool.Context
Root string
Usage func(format string, args ...interface{}) error
}
// NewX returns a new execution environment, given a cmdline env.
// It also prepends $JIRI_ROOT/.jiri_root/bin to the PATH.
func NewX(env *cmdline.Env) (*X, error) {
ctx := tool.NewContextFromEnv(env)
root, err := findJiriRoot(ctx.Timer())
if err != nil {
return nil, err
}
x := &X{
Context: ctx,
Root: root,
Usage: env.UsageErrorf,
}
if ctx.Env()[PreservePathEnv] == "" {
// Prepend $JIRI_ROOT/.jiri_root/bin to the PATH, so execing a binary will
// invoke the one in that directory, if it exists. This is crucial for jiri
// subcommands, where we want to invoke the binary that jiri installed, not
// whatever is in the user's PATH.
//
// Note that we must modify the actual os env variable with os.SetEnv and
// also the ctx.env, so that execing a binary through the os/exec package
// and with ctx.Run both have the correct behavior.
newPath := envvar.PrependUniqueToken(ctx.Env()["PATH"], string(os.PathListSeparator), x.BinDir())
ctx.Env()["PATH"] = newPath
if err := os.Setenv("PATH", newPath); err != nil {
return nil, err
}
}
return x, nil
}
func findJiriRoot(timer *timing.Timer) (string, error) {
if timer != nil {
timer.Push("find JIRI_ROOT")
defer timer.Pop()
}
if root := os.Getenv(RootEnv); root != "" {
// Always use JIRI_ROOT if it's set.
result, err := filepath.EvalSymlinks(root)
if err != nil {
return "", fmt.Errorf("%v EvalSymlinks(%v) failed: %v", RootEnv, root, err)
}
if !filepath.IsAbs(result) {
return "", fmt.Errorf("%v isn't an absolute path: %v", RootEnv, result)
}
return filepath.Clean(result), nil
}
// TODO(toddw): Try to find the root by walking up the filesystem.
return "", fmt.Errorf("%v is not set", RootEnv)
}
// FindRoot returns the root directory of the jiri environment. All state
// managed by jiri resides under this root.
//
// If the RootEnv environment variable is non-empty, we always attempt to use
// it. It must point to an absolute path, after symlinks are evaluated.
// TODO(toddw): Walk up the filesystem too.
//
// Returns an empty string if the root directory cannot be determined, or if any
// errors are encountered.
//
// FindRoot should be rarely used; typically you should use NewX to create a new
// execution environment, and handle errors. An example of a valid usage is to
// initialize default flag values in an init func before main.
func FindRoot() string {
root, _ := findJiriRoot(nil)
return root
}
// Clone returns a clone of the environment.
func (x *X) Clone(opts tool.ContextOpts) *X {
return &X{
Context: x.Context.Clone(opts),
Root: x.Root,
Usage: x.Usage,
}
}
// UsageErrorf prints the error message represented by the printf-style format
// and args, followed by the usage output. The implementation typically calls
// cmdline.Env.UsageErrorf.
func (x *X) UsageErrorf(format string, args ...interface{}) error {
if x.Usage != nil {
return x.Usage(format, args...)
}
return fmt.Errorf(format, args...)
}
// RootMetaDir returns the path to the root metadata directory.
func (x *X) RootMetaDir() string {
return filepath.Join(x.Root, RootMetaDir)
}
// JiriManifestFile returns the path to the .jiri_manifest file.
func (x *X) JiriManifestFile() string {
return filepath.Join(x.Root, JiriManifestFile)
}
// BinDir returns the path to the bin directory.
func (x *X) BinDir() string {
return filepath.Join(x.RootMetaDir(), "bin")
}
// ScriptsDir returns the path to the scripts directory.
func (x *X) ScriptsDir() string {
return filepath.Join(x.RootMetaDir(), "scripts")
}
// UpdateHistoryDir returns the path to the update history directory.
func (x *X) UpdateHistoryDir() string {
return filepath.Join(x.RootMetaDir(), "update_history")
}
// ProfilesDBDir returns the path to the profiles data base directory.
func (x *X) ProfilesDBDir() string {
return filepath.Join(x.RootMetaDir(), "profile_db")
}
// ProfilesRootDir returns the path to the root of the profiles installation.
func (x *X) ProfilesRootDir() string {
return filepath.Join(x.RootMetaDir(), "profiles")
}
// UpdateHistoryLatestLink returns the path to a symlink that points to the
// latest update in the update history directory.
func (x *X) UpdateHistoryLatestLink() string {
return filepath.Join(x.UpdateHistoryDir(), "latest")
}
// UpdateHistorySecondLatestLink returns the path to a symlink that points to
// the second latest update in the update history directory.
func (x *X) UpdateHistorySecondLatestLink() string {
return filepath.Join(x.UpdateHistoryDir(), "second-latest")
}
// RunnerFunc is an adapter that turns regular functions into cmdline.Runner.
// This is similar to cmdline.RunnerFunc, but the first function argument is
// jiri.X, rather than cmdline.Env.
func RunnerFunc(run func(*X, []string) error) cmdline.Runner {
return runner(run)
}
type runner func(*X, []string) error
func (r runner) Run(env *cmdline.Env, args []string) error {
x, err := NewX(env)
if err != nil {
return err
}
return r(x, args)
}