| // 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/timing" |
| ) |
| |
| const ( |
| RootEnv = "JIRI_ROOT" |
| RootMetaDir = ".jiri_root" |
| ProjectMetaDir = ".jiri" |
| ProjectMetaFile = "metadata.v2" |
| legacyProfilesDBFile = ".jiri_v23_profiles" |
| ProfilesDBDir = RootMetaDir + string(filepath.Separator) + "profile_db" |
| ProfilesRootDir = RootMetaDir + string(filepath.Separator) + "profiles" |
| JiriManifestFile = ".jiri_manifest" |
| ) |
| |
| // 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. |
| func NewX(env *cmdline.Env) (*X, error) { |
| ctx := tool.NewContextFromEnv(env) |
| root, err := findJiriRoot(ctx.Timer()) |
| if err != nil { |
| return nil, err |
| } |
| return &X{ |
| Context: ctx, |
| Root: root, |
| Usage: env.UsageErrorf, |
| }, 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 |
| } |
| |
| // DefaultProfilesDBPath returns the appropriate profiles database to use |
| // based on whether profile-v23 has been run. It will be removed once |
| // transition to profile-v23 is complete. |
| // TODO(cnicolaou): delete this and use ProfilesDBDir in the client apps. |
| func DefaultProfilesDBPath() string { |
| root := FindRoot() |
| fi, err := os.Stat(filepath.Join(root, ProfilesDBDir)) |
| if err == nil && fi.IsDir() { |
| return ProfilesDBDir |
| } |
| return legacyProfilesDBFile |
| } |
| |
| // 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) |
| } |
| |
| // UsingOldManifests returns true iff the JIRI_ROOT/.jiri_manifest file does not |
| // exist. This is the one signal used to decide whether to use the old manifest |
| // logic (checking .local_manifest and the -manifest flag), or the new logic. |
| func (x *X) UsingOldManifests() bool { |
| _, err := os.Stat(x.JiriManifestFile()) |
| return os.IsNotExist(err) |
| } |
| |
| // BinDir returns the path to the bin directory. |
| func (x *X) BinDir() string { |
| return filepath.Join(x.RootMetaDir(), "bin") |
| } |
| |
| // 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") |
| } |
| |
| // 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") |
| } |
| |
| // ResolveManifestPath resolves the given manifest name to an absolute path in |
| // the local filesystem. |
| // |
| // TODO(toddw): Remove this once the transition to new manifests is done. In |
| // the new world, we always start with the JiriManifestFile. |
| func (x *X) ResolveManifestPath(name string) (string, error) { |
| if x.UsingOldManifests() { |
| return x.resolveManifestPathDeprecated(name) |
| } |
| if name != "" { |
| return "", fmt.Errorf("-manifest flag isn't supported with .jiri_manifest") |
| } |
| return x.JiriManifestFile(), nil |
| } |
| |
| // Deprecated logic, only run if JIRI_ROOT/.jiri_manifest doesn't exist. |
| // |
| // TODO(toddw): Remove this logic when the transition to .jiri_manifest is done. |
| func (x *X) resolveManifestPathDeprecated(name string) (string, error) { |
| manifestDir := filepath.Join(x.Root, ".manifest", "v2") |
| if name != "" { |
| if filepath.IsAbs(name) { |
| return name, nil |
| } |
| return filepath.Join(manifestDir, name), nil |
| } |
| path := filepath.Join(x.Root, ".local_manifest") |
| switch _, err := os.Stat(path); { |
| case err == nil: |
| return path, nil |
| case os.IsNotExist(err): |
| return filepath.Join(manifestDir, "default"), nil |
| default: |
| return "", fmt.Errorf("Stat(%v) failed: %v", path, err) |
| } |
| } |
| |
| // 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) |
| } |