github.com/vanadium-archive/go.jiri@v0.0.0-20160715023856-abfb8b131290/x.go (about)

     1  // Copyright 2015 The Vanadium Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package jiri provides utilities used by the jiri tool and related tools.
     6  package jiri
     7  
     8  // TODO(toddw): Rename this package to v.io/jiri, and rename the tool itself to
     9  // v.io/jiri/cmd/jiri
    10  
    11  import (
    12  	"fmt"
    13  	"os"
    14  	"path/filepath"
    15  
    16  	"v.io/jiri/tool"
    17  	"v.io/x/lib/cmdline"
    18  	"v.io/x/lib/envvar"
    19  	"v.io/x/lib/timing"
    20  )
    21  
    22  const (
    23  	RootEnv          = "JIRI_ROOT"
    24  	RootMetaDir      = ".jiri_root"
    25  	ProjectMetaDir   = ".jiri"
    26  	ProjectMetaFile  = "metadata.v2"
    27  	ProfilesDBDir    = RootMetaDir + string(filepath.Separator) + "profile_db"
    28  	ProfilesRootDir  = RootMetaDir + string(filepath.Separator) + "profiles"
    29  	JiriManifestFile = ".jiri_manifest"
    30  
    31  	// PreservePathEnv is the name of the environment variable that, when set to a
    32  	// non-empty value, causes jiri tools to use the existing PATH variable,
    33  	// rather than mutating it.
    34  	PreservePathEnv = "JIRI_PRESERVE_PATH"
    35  )
    36  
    37  // X holds the execution environment for the jiri tool and related tools.  This
    38  // includes the jiri filesystem root directory.
    39  //
    40  // TODO(toddw): Other jiri state should be transitioned to this struct,
    41  // including the manifest and related operations.
    42  type X struct {
    43  	*tool.Context
    44  	Root  string
    45  	Usage func(format string, args ...interface{}) error
    46  }
    47  
    48  // NewX returns a new execution environment, given a cmdline env.
    49  // It also prepends $JIRI_ROOT/.jiri_root/bin to the PATH.
    50  func NewX(env *cmdline.Env) (*X, error) {
    51  	ctx := tool.NewContextFromEnv(env)
    52  	root, err := findJiriRoot(ctx.Timer())
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  	x := &X{
    57  		Context: ctx,
    58  		Root:    root,
    59  		Usage:   env.UsageErrorf,
    60  	}
    61  	if ctx.Env()[PreservePathEnv] == "" {
    62  		// Prepend $JIRI_ROOT/.jiri_root/bin to the PATH, so execing a binary will
    63  		// invoke the one in that directory, if it exists.  This is crucial for jiri
    64  		// subcommands, where we want to invoke the binary that jiri installed, not
    65  		// whatever is in the user's PATH.
    66  		//
    67  		// Note that we must modify the actual os env variable with os.SetEnv and
    68  		// also the ctx.env, so that execing a binary through the os/exec package
    69  		// and with ctx.Run both have the correct behavior.
    70  		newPath := envvar.PrependUniqueToken(ctx.Env()["PATH"], string(os.PathListSeparator), x.BinDir())
    71  		ctx.Env()["PATH"] = newPath
    72  		if err := os.Setenv("PATH", newPath); err != nil {
    73  			return nil, err
    74  		}
    75  	}
    76  	return x, nil
    77  }
    78  
    79  func findJiriRoot(timer *timing.Timer) (string, error) {
    80  	if timer != nil {
    81  		timer.Push("find JIRI_ROOT")
    82  		defer timer.Pop()
    83  	}
    84  	if root := os.Getenv(RootEnv); root != "" {
    85  		// Always use JIRI_ROOT if it's set.
    86  		result, err := filepath.EvalSymlinks(root)
    87  		if err != nil {
    88  			return "", fmt.Errorf("%v EvalSymlinks(%v) failed: %v", RootEnv, root, err)
    89  		}
    90  		if !filepath.IsAbs(result) {
    91  			return "", fmt.Errorf("%v isn't an absolute path: %v", RootEnv, result)
    92  		}
    93  		return filepath.Clean(result), nil
    94  	}
    95  	// TODO(toddw): Try to find the root by walking up the filesystem.
    96  	return "", fmt.Errorf("%v is not set", RootEnv)
    97  }
    98  
    99  // FindRoot returns the root directory of the jiri environment.  All state
   100  // managed by jiri resides under this root.
   101  //
   102  // If the RootEnv environment variable is non-empty, we always attempt to use
   103  // it.  It must point to an absolute path, after symlinks are evaluated.
   104  // TODO(toddw): Walk up the filesystem too.
   105  //
   106  // Returns an empty string if the root directory cannot be determined, or if any
   107  // errors are encountered.
   108  //
   109  // FindRoot should be rarely used; typically you should use NewX to create a new
   110  // execution environment, and handle errors.  An example of a valid usage is to
   111  // initialize default flag values in an init func before main.
   112  func FindRoot() string {
   113  	root, _ := findJiriRoot(nil)
   114  	return root
   115  }
   116  
   117  // Clone returns a clone of the environment.
   118  func (x *X) Clone(opts tool.ContextOpts) *X {
   119  	return &X{
   120  		Context: x.Context.Clone(opts),
   121  		Root:    x.Root,
   122  		Usage:   x.Usage,
   123  	}
   124  }
   125  
   126  // UsageErrorf prints the error message represented by the printf-style format
   127  // and args, followed by the usage output.  The implementation typically calls
   128  // cmdline.Env.UsageErrorf.
   129  func (x *X) UsageErrorf(format string, args ...interface{}) error {
   130  	if x.Usage != nil {
   131  		return x.Usage(format, args...)
   132  	}
   133  	return fmt.Errorf(format, args...)
   134  }
   135  
   136  // RootMetaDir returns the path to the root metadata directory.
   137  func (x *X) RootMetaDir() string {
   138  	return filepath.Join(x.Root, RootMetaDir)
   139  }
   140  
   141  // JiriManifestFile returns the path to the .jiri_manifest file.
   142  func (x *X) JiriManifestFile() string {
   143  	return filepath.Join(x.Root, JiriManifestFile)
   144  }
   145  
   146  // BinDir returns the path to the bin directory.
   147  func (x *X) BinDir() string {
   148  	return filepath.Join(x.RootMetaDir(), "bin")
   149  }
   150  
   151  // ScriptsDir returns the path to the scripts directory.
   152  func (x *X) ScriptsDir() string {
   153  	return filepath.Join(x.RootMetaDir(), "scripts")
   154  }
   155  
   156  // UpdateHistoryDir returns the path to the update history directory.
   157  func (x *X) UpdateHistoryDir() string {
   158  	return filepath.Join(x.RootMetaDir(), "update_history")
   159  }
   160  
   161  // ProfilesDBDir returns the path to the profiles data base directory.
   162  func (x *X) ProfilesDBDir() string {
   163  	return filepath.Join(x.RootMetaDir(), "profile_db")
   164  }
   165  
   166  // ProfilesRootDir returns the path to the root of the profiles installation.
   167  func (x *X) ProfilesRootDir() string {
   168  	return filepath.Join(x.RootMetaDir(), "profiles")
   169  }
   170  
   171  // UpdateHistoryLatestLink returns the path to a symlink that points to the
   172  // latest update in the update history directory.
   173  func (x *X) UpdateHistoryLatestLink() string {
   174  	return filepath.Join(x.UpdateHistoryDir(), "latest")
   175  }
   176  
   177  // UpdateHistorySecondLatestLink returns the path to a symlink that points to
   178  // the second latest update in the update history directory.
   179  func (x *X) UpdateHistorySecondLatestLink() string {
   180  	return filepath.Join(x.UpdateHistoryDir(), "second-latest")
   181  }
   182  
   183  // RunnerFunc is an adapter that turns regular functions into cmdline.Runner.
   184  // This is similar to cmdline.RunnerFunc, but the first function argument is
   185  // jiri.X, rather than cmdline.Env.
   186  func RunnerFunc(run func(*X, []string) error) cmdline.Runner {
   187  	return runner(run)
   188  }
   189  
   190  type runner func(*X, []string) error
   191  
   192  func (r runner) Run(env *cmdline.Env, args []string) error {
   193  	x, err := NewX(env)
   194  	if err != nil {
   195  		return err
   196  	}
   197  	return r(x, args)
   198  }