Skip to content

[bin wrappers] fixes for only-path-without-wrappers call #1163

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion devbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type Devbox interface {
Install(ctx context.Context) error
IsEnvEnabled() bool
ListScripts() []string
PrintEnv(opts *devopt.PrintEnv) (string, error)
PrintEnv(ctx context.Context, includeHooks bool) (string, error)
PrintEnvVars(ctx context.Context) ([]string, error)
PrintGlobalList() error
Pull(ctx context.Context, overwrite bool, path string) error
Expand Down Expand Up @@ -74,3 +74,12 @@ func GlobalDataPath() (string, error) {
func PrintEnvrcContent(w io.Writer) error {
return impl.PrintEnvrcContent(w)
}

// ExportifySystemPathWithoutWrappers reads $PATH, removes `virtenv/.wrappers/bin` paths,
// and returns a string of the form `export PATH=....`
//
// This small utility function could have been inlined in the boxcli caller, but
// needed the impl.exportify functionality. It does not depend on core-devbox.
func ExportifySystemPathWithoutWrappers() string {
return impl.ExportifySystemPathWithoutWrappers()
}
2 changes: 1 addition & 1 deletion internal/boxcli/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func runShellCmd(cmd *cobra.Command, flags shellCmdFlags) error {
if flags.printEnv {
// false for includeHooks is because init hooks is not compatible with .envrc files generated
// by versions older than 0.4.6
script, err := box.PrintEnv(&devopt.PrintEnv{Ctx: cmd.Context()})
script, err := box.PrintEnv(cmd.Context(), false /*includeHooks*/)
if err != nil {
return err
}
Expand Down
43 changes: 5 additions & 38 deletions internal/boxcli/shellenv.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,63 +77,30 @@ func shellEnvFunc(cmd *cobra.Command, flags shellEnvCmdFlags) (string, error) {
}
}

opts := &devopt.PrintEnv{
Ctx: cmd.Context(),
IncludeHooks: flags.runInitHook,
}
envStr, err := box.PrintEnv(opts)
envStr, err := box.PrintEnv(cmd.Context(), flags.runInitHook)
if err != nil {
return "", err
}

return envStr, nil
}

type shellEnvOnlyPathWithoutWrappersCmdFlags struct {
config configFlags
}

func shellEnvOnlyPathWithoutWrappersCmd() *cobra.Command {
flags := shellEnvOnlyPathWithoutWrappersCmdFlags{}
command := &cobra.Command{
Use: "only-path-without-wrappers",
Hidden: true,
Short: "Print shell commands that export PATH without the bin-wrappers",
Short: "[internal] Print shell command that exports the system $PATH without the bin-wrappers paths.",
Args: cobra.ExactArgs(0),
PreRunE: ensureNixInstalled,
RunE: func(cmd *cobra.Command, args []string) error {
s, err := shellEnvOnlyPathWithoutWrappersFunc(cmd, &flags)
if err != nil {
return err
}
s := shellEnvOnlyPathWithoutWrappersFunc()
fmt.Fprintln(cmd.OutOrStdout(), s)
fmt.Fprintln(cmd.OutOrStdout(), "hash -r")
return nil
},
}
flags.config.register(command)
return command
}

func shellEnvOnlyPathWithoutWrappersFunc(cmd *cobra.Command, flags *shellEnvOnlyPathWithoutWrappersCmdFlags) (string, error) {

box, err := devbox.Open(&devopt.Opts{
Dir: flags.config.path,
Writer: cmd.ErrOrStderr(),
Pure: false,
})
if err != nil {
return "", err
}

opts := &devopt.PrintEnv{
Ctx: cmd.Context(),
OnlyPathWithoutWrappers: true,
}
envStr, err := box.PrintEnv(opts)
if err != nil {
return "", err
}

return envStr, nil
func shellEnvOnlyPathWithoutWrappersFunc() string {
return devbox.ExportifySystemPathWithoutWrappers()
}
41 changes: 23 additions & 18 deletions internal/impl/devbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ func (d *Devbox) RunScript(ctx context.Context, cmdName string, cmdArgs []string
// creates all wrappers, but does not run init hooks. It is used to power
// devbox install cli command.
func (d *Devbox) Install(ctx context.Context) error {
if _, err := d.PrintEnv(&devopt.PrintEnv{Ctx: ctx}); err != nil {
if _, err := d.PrintEnv(ctx, false /*includeHooks*/); err != nil {
return err
}
return wrapnix.CreateWrappers(ctx, d)
Expand All @@ -309,8 +309,8 @@ func (d *Devbox) ListScripts() []string {
return keys
}

func (d *Devbox) PrintEnv(opts *devopt.PrintEnv) (string, error) {
ctx, task := trace.NewTask(opts.Ctx, "devboxPrintEnv")
func (d *Devbox) PrintEnv(ctx context.Context, includeHooks bool) (string, error) {
ctx, task := trace.NewTask(ctx, "devboxPrintEnv")
defer task.End()

if err := d.ensurePackagesAreInstalled(ctx, ensure); err != nil {
Expand All @@ -322,23 +322,9 @@ func (d *Devbox) PrintEnv(opts *devopt.PrintEnv) (string, error) {
return "", err
}

if opts.OnlyPathWithoutWrappers {
path := []string{}
for _, p := range strings.Split(envs["PATH"], string(filepath.ListSeparator)) {
if !strings.Contains(p, plugin.WrapperPath) {
path = append(path, p)
}
}

// reset envs to be PATH only!
envs = map[string]string{
"PATH": strings.Join(path, string(filepath.ListSeparator)),
}
}

envStr := exportify(envs)

if opts.IncludeHooks {
if includeHooks {
hooksStr := ". " + d.scriptPath(hooksFilename)
envStr = fmt.Sprintf("%s\n%s;\n", envStr, hooksStr)
}
Expand Down Expand Up @@ -1242,3 +1228,22 @@ func (d *Devbox) convertEnvToMap(currentEnv []string) (map[string]string, error)
}
return env, nil
}

// ExportifySystemPathWithoutWrappers is a small utility to filter WrapperBin paths from PATH
func ExportifySystemPathWithoutWrappers() string {

path := []string{}
for _, p := range strings.Split(os.Getenv("PATH"), string(filepath.ListSeparator)) {
// Intentionally do not include projectDir with plugin.WrapperBinPath so that
// we filter out bin-wrappers for devbox-global and devbox-project.
if !strings.Contains(p, plugin.WrapperBinPath) {
path = append(path, p)
}
}

envs := map[string]string{
"PATH": strings.Join(path, string(filepath.ListSeparator)),
}

return exportify(envs)
}
7 changes: 0 additions & 7 deletions internal/impl/devopt/devboxopts.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package devopt

import (
"context"
"io"
)

Expand All @@ -11,9 +10,3 @@ type Opts struct {
IgnoreWarnings bool
Writer io.Writer
}

type PrintEnv struct {
Ctx context.Context
IncludeHooks bool
OnlyPathWithoutWrappers bool
}
13 changes: 8 additions & 5 deletions internal/wrapnix/wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"text/template"

"github.com/pkg/errors"

"go.jetpack.io/devbox/internal/cmdutil"
"go.jetpack.io/devbox/internal/nix"
"go.jetpack.io/devbox/internal/plugin"
Expand Down Expand Up @@ -48,7 +47,7 @@ func CreateWrappers(ctx context.Context, devbox devboxer) error {
_ = os.RemoveAll(filepath.Join(devbox.ProjectDir(), plugin.WrapperPath))

// Recreate the bin wrapper directory
destPath := filepath.Join(devbox.ProjectDir(), plugin.WrapperBinPath)
destPath := filepath.Join(wrapperBinPath(devbox))
_ = os.MkdirAll(destPath, 0755)

bashPath := cmdutil.GetPathOrDefault("bash", "/bin/bash")
Expand Down Expand Up @@ -91,7 +90,7 @@ func CreateWrappers(ctx context.Context, devbox devboxer) error {
return errors.WithStack(err)
}
}
if err = createDevboxSymlink(devbox.ProjectDir()); err != nil {
if err = createDevboxSymlink(devbox); err != nil {
return err
}

Expand Down Expand Up @@ -168,17 +167,21 @@ func createSymlinksForSupportDirs(projectDir string) error {

// Creates a symlink for devbox in .devbox/virtenv/.wrappers/bin
// so that devbox can be available inside a pure shell
func createDevboxSymlink(projectDir string) error {
func createDevboxSymlink(devbox devboxer) error {

// Get absolute path for where devbox is called
devboxPath, err := filepath.Abs(os.Args[0])
if err != nil {
return errors.Wrap(err, "failed to create devbox symlink. Devbox command won't be available inside the shell")
}
// Create a symlink between devbox in .wrappers/bin
err = os.Symlink(devboxPath, filepath.Join(projectDir, plugin.WrapperBinPath, "devbox"))
err = os.Symlink(devboxPath, filepath.Join(wrapperBinPath(devbox), "devbox"))
if err != nil && !errors.Is(err, fs.ErrExist) {
return errors.Wrap(err, "failed to create devbox symlink. Devbox command won't be available inside the shell")
}
return nil
}

func wrapperBinPath(devbox devboxer) string {
return filepath.Join(devbox.ProjectDir(), plugin.WrapperBinPath)
}
14 changes: 9 additions & 5 deletions internal/wrapnix/wrapper.sh.tmpl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!{{ .BashPath }}

{{/*
{{/*
# If env variable has never been set by devbox we set it, but also
# default to env value set by user. This means plugin env variables behave a bit
# differently than devbox.json env variables which are always set once.
Expand Down Expand Up @@ -29,9 +29,13 @@ export {{ .ShellEnvHashKey }}_GUARD=true
eval "$(DO_NOT_TRACK=1 devbox shellenv -c {{ .ProjectDir }})"
fi

# So that we do not invoke other bin-wrappers from
# this bin-wrapper. Instead, we directly invoke the binary from the nix store, which
# should be in PATH.
eval "$(devbox shellenv only-path-without-wrappers)"
{{/*
We call only-path-without-wrappers so that we do not invoke other bin-wrappers from
this bin-wrapper. Instead, we directly invoke the binary from the nix store, which
should be in PATH.

DO_NOT_TRACK=1 can be removed once we optimize segment to queue events.
*/ -}}
eval "$(DO_NOT_TRACK=1 devbox shellenv only-path-without-wrappers)"

exec {{ .Command }} "$@"