/
x.go
198 lines (174 loc) · 6.25 KB
/
x.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
// 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)
}