--- /dev/null
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !plan9
+
+package main
+
+import (
+ "bufio"
+ "context"
+ "fmt"
+ "os"
+ "os/user"
+ "path/filepath"
+ "runtime"
+ "strings"
+)
+
+const (
+ bashConfig = ".bash_profile"
+ zshConfig = ".zshrc"
+)
+
+// appendToPATH adds the given path to the PATH environment variable and
+// persists it for future sessions.
+func appendToPATH(value string) error {
+ if isInPATH(value) {
+ return nil
+ }
+ return persistEnvVar("PATH", pathVar+envSeparator+value)
+}
+
+func isInPATH(dir string) bool {
+ p := os.Getenv("PATH")
+
+ paths := strings.Split(p, envSeparator)
+ for _, d := range paths {
+ if d == dir {
+ return true
+ }
+ }
+
+ return false
+}
+
+func getHomeDir() (string, error) {
+ home := os.Getenv(homeKey)
+ if home != "" {
+ return home, nil
+ }
+
+ u, err := user.Current()
+ if err != nil {
+ return "", err
+ }
+ return u.HomeDir, nil
+}
+
+func checkStringExistsFile(filename, value string) (bool, error) {
+ file, err := os.OpenFile(filename, os.O_RDONLY, 0600)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return false, nil
+ }
+ return false, err
+ }
+ defer file.Close()
+
+ scanner := bufio.NewScanner(file)
+ for scanner.Scan() {
+ line := scanner.Text()
+ if line == value {
+ return true, nil
+ }
+ }
+
+ return false, scanner.Err()
+}
+
+func appendToFile(filename, value string) error {
+ verbosef("Adding %q to %s", value, filename)
+
+ ok, err := checkStringExistsFile(filename, value)
+ if err != nil {
+ return err
+ }
+ if ok {
+ // Nothing to do.
+ return nil
+ }
+
+ f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ _, err = f.WriteString(lineEnding + value + lineEnding)
+ return err
+}
+
+func isShell(name string) bool {
+ return strings.Contains(currentShell(), name)
+}
+
+// persistEnvVarWindows sets an environment variable in the Windows
+// registry.
+func persistEnvVarWindows(name, value string) error {
+ _, err := runCommand(context.Background(), "powershell", "-command",
+ fmt.Sprintf(`[Environment]::SetEnvironmentVariable("%s", "%s", "User")`, name, value))
+ return err
+}
+
+func persistEnvVar(name, value string) error {
+ if runtime.GOOS == "windows" {
+ if err := persistEnvVarWindows(name, value); err != nil {
+ return err
+ }
+
+ if isShell("cmd.exe") || isShell("powershell.exe") {
+ return os.Setenv(strings.ToUpper(name), value)
+ }
+ // User is in bash, zsh, etc.
+ // Also set the environment variable in their shell config.
+ }
+
+ rc, err := shellConfigFile()
+ if err != nil {
+ return err
+ }
+
+ line := fmt.Sprintf("export %s=%s", strings.ToUpper(name), value)
+ if err := appendToFile(rc, line); err != nil {
+ return err
+ }
+
+ return os.Setenv(strings.ToUpper(name), value)
+}
+
+func shellConfigFile() (string, error) {
+ home, err := getHomeDir()
+ if err != nil {
+ return "", err
+ }
+
+ switch {
+ case isShell("bash"):
+ return filepath.Join(home, bashConfig), nil
+ case isShell("zsh"):
+ return filepath.Join(home, zshConfig), nil
+ default:
+ return "", fmt.Errorf("%q is not a supported shell", currentShell())
+ }
+}