1
0
mirror of https://github.com/golang/go synced 2024-11-18 09:04:49 -07:00

cmd/getgo: initial commit

Initial commit of getgo, a "one-line installer".

Example use from bash:

  curl -LO https://get.golang.org/$(uname)/go_installer && chmod +x go_installer && ./go_installer && rm go_installer

It's comprised of two parts:

cmd/getgo/server: an App Engine application that redirects users to an
appropriate installer based on the request path, which identifies the
user's operating system. It's deployed to get.golang.org.

cmd/getgo: a cross-compiled binary that does the heavy lifting of
downloading and installing the latest Go version (including setting up
the environment) to the user's system. The installers are served from
the golang GCS bucket.

Currently supported systems:
  linux, darwin, windows / amd64, 386

Authored by Jess Frazelle, Steve Francia, Chris Broadfoot.

Change-Id: I615de86e198d3bd93e418fa23055d00ddbdd99fb
Reviewed-on: https://go-review.googlesource.com/51115
Reviewed-by: Jaana Burcu Dogan <jbd@google.com>
This commit is contained in:
Chris Broadfoot 2017-07-25 14:46:40 -07:00
parent 3da34b1b52
commit ac1e4b1998
20 changed files with 1214 additions and 0 deletions

5
cmd/getgo/.dockerignore Normal file
View File

@ -0,0 +1,5 @@
.git
.dockerignore
LICENSE
README.md
.gitignore

3
cmd/getgo/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
build
testgetgo
getgo

20
cmd/getgo/Dockerfile Normal file
View File

@ -0,0 +1,20 @@
FROM golang:latest
ENV SHELL /bin/bash
ENV HOME /root
WORKDIR $HOME
COPY . /go/src/golang.org/x/tools/cmd/getgo
RUN ( \
cd /go/src/golang.org/x/tools/cmd/getgo \
&& go build \
&& mv getgo /usr/local/bin/getgo \
)
# undo the adding of GOPATH to env for testing
ENV PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ENV GOPATH ""
# delete /go and /usr/local/go for testing
RUN rm -rf /go /usr/local/go

27
cmd/getgo/LICENSE Normal file
View File

@ -0,0 +1,27 @@
Copyright (c) 2017 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

71
cmd/getgo/README.md Normal file
View File

@ -0,0 +1,71 @@
# getgo
A proof-of-concept command-line installer for Go.
This installer is designed to both install Go as well as do the initial configuration
of setting up the right environment variables and paths.
It will install the Go distribution (tools & stdlib) to "/.go" inside your home directory by default.
It will setup "$HOME/go" as your GOPATH.
This is where third party libraries and apps will be installed as well as where you will write your Go code.
If Go is already installed via this installer it will upgrade it to the latest version of Go.
Currently the installer supports Windows, \*nix and macOS on x86 & x64.
It supports Bash and Zsh on all of these platforms as well as powershell & cmd.exe on Windows.
## Usage
Windows Powershell/cmd.exe:
`(New-Object System.Net.WebClient).DownloadFile('https://get.golang.org/installer.exe', 'installer.exe'); Start-Process -Wait -NonewWindow installer.exe; Remove-Item installer.exe`
Shell (Linux/macOS/Windows):
`curl -LO https://get.golang.org/$(uname)/go_installer && chmod +x go_installer && ./go_installer && rm go_installer`
## To Do
* Check if Go is already installed (via a different method) and update it in place or at least notify the user
* Lots of testing. It's only had limited testing so far.
* Add support for additional shells.
## Development instructions
### Testing
There are integration tests in [`main_test.go`](main_test.go). Please add more
tests there.
#### On unix/linux with the Dockerfile
The Dockerfile automatically builds the binary, moves it to
`/usr/local/bin/getgo` and then unsets `$GOPATH` and removes all `$GOPATH` from
`$PATH`.
```bash
$ docker build --rm --force-rm -t getgo .
...
$ docker run --rm -it getgo bash
root@78425260fad0:~# getgo -v
Welcome to the Go installer!
Downloading Go version go1.8.3 to /usr/local/go
This may take a bit of time...
Adding "export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin" to /root/.bashrc
Downloaded!
Setting up GOPATH
Adding "export GOPATH=/root/go" to /root/.bashrc
Adding "export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin:/root/go/bin" to /root/.bashrc
GOPATH has been setup!
root@78425260fad0:~# which go
/usr/local/go/bin/go
root@78425260fad0:~# echo $GOPATH
/root/go
root@78425260fad0:~# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin:/root/go/bin
```
## Release instructions
To upload a new release of getgo, run `./make.bash && ./upload.bash`.

176
cmd/getgo/download.go Normal file
View File

@ -0,0 +1,176 @@
// 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.
package main
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"crypto/sha256"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strings"
)
const (
currentVersionURL = "https://golang.org/VERSION?m=text"
downloadURLPrefix = "https://storage.googleapis.com/golang"
)
// downloadGoVersion downloads and upacks the specific go version to dest/go.
func downloadGoVersion(version, ops, arch, dest string) error {
suffix := "tar.gz"
if ops == "windows" {
suffix = "zip"
}
uri := fmt.Sprintf("%s/%s.%s-%s.%s", downloadURLPrefix, version, ops, arch, suffix)
verbosef("Downloading %s", uri)
resp, err := http.Get(uri)
if err != nil {
return fmt.Errorf("Downloading Go from %s failed: %v", uri, err)
}
if resp.StatusCode > 299 {
return fmt.Errorf("Downloading Go from %s failed with HTTP status %s", resp.Status)
}
defer resp.Body.Close()
tmpf, err := ioutil.TempFile("", "go")
if err != nil {
return err
}
defer os.Remove(tmpf.Name())
h := sha256.New()
w := io.MultiWriter(tmpf, h)
if _, err := io.Copy(w, resp.Body); err != nil {
return err
}
verbosef("Downloading SHA %s.sha256", uri)
sresp, err := http.Get(uri + ".sha256")
if err != nil {
return fmt.Errorf("Downloading Go sha256 from %s.sha256 failed: %v", uri, err)
}
defer sresp.Body.Close()
if sresp.StatusCode > 299 {
return fmt.Errorf("Downloading Go sha256 from %s.sha256 failed with HTTP status %s", sresp.Status)
}
shasum, err := ioutil.ReadAll(sresp.Body)
if err != nil {
return err
}
// Check the shasum.
sum := fmt.Sprintf("%x", h.Sum(nil))
if sum != string(shasum) {
return fmt.Errorf("Shasum mismatch %s vs. %s", sum, string(shasum))
}
unpackFunc := unpackTar
if ops == "windows" {
unpackFunc = unpackZip
}
if err := unpackFunc(tmpf.Name(), dest); err != nil {
return fmt.Errorf("Unpacking Go to %s failed: %v", dest, err)
}
return nil
}
func unpack(dest, name string, fi os.FileInfo, r io.Reader) error {
if strings.HasPrefix(name, "go/") {
name = name[len("go/"):]
}
path := filepath.Join(dest, name)
if fi.IsDir() {
return os.MkdirAll(path, fi.Mode())
}
f, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, fi.Mode())
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, r)
return err
}
func unpackTar(src, dest string) error {
r, err := os.Open(src)
if err != nil {
return err
}
defer r.Close()
archive, err := gzip.NewReader(r)
if err != nil {
return err
}
defer archive.Close()
tarReader := tar.NewReader(archive)
for {
header, err := tarReader.Next()
if err == io.EOF {
break
} else if err != nil {
return err
}
if err := unpack(dest, header.Name, header.FileInfo(), tarReader); err != nil {
return err
}
}
return nil
}
func unpackZip(src, dest string) error {
zr, err := zip.OpenReader(src)
if err != nil {
return err
}
for _, f := range zr.File {
fr, err := f.Open()
if err != nil {
return err
}
if err := unpack(dest, f.Name, f.FileInfo(), fr); err != nil {
return err
}
fr.Close()
}
return nil
}
func getLatestGoVersion() (string, error) {
resp, err := http.Get(currentVersionURL)
if err != nil {
return "", fmt.Errorf("Getting current Go version failed: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode > 299 {
b, _ := ioutil.ReadAll(io.LimitReader(resp.Body, 1024))
return "", fmt.Errorf("Could not get current Go version: HTTP %d: %q", resp.StatusCode, b)
}
version, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
return strings.TrimSpace(string(version)), nil
}

View File

@ -0,0 +1,34 @@
// 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.
package main
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
)
func TestDownloadGoVersion(t *testing.T) {
if testing.Short() {
t.Skipf("Skipping download in short mode")
}
tmpd, err := ioutil.TempDir("", "go")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpd)
if err := downloadGoVersion("go1.8.1", "linux", "amd64", filepath.Join(tmpd, "go")); err != nil {
t.Fatal(err)
}
// Ensure the VERSION file exists.
vf := filepath.Join(tmpd, "go", "VERSION")
if _, err := os.Stat(vf); os.IsNotExist(err) {
t.Fatalf("file %s does not exist and should", vf)
}
}

114
cmd/getgo/main.go Normal file
View File

@ -0,0 +1,114 @@
// 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.
// The getgo command installs Go to the user's system.
package main
import (
"bufio"
"context"
"errors"
"flag"
"fmt"
"os"
"os/exec"
"strings"
)
var (
interactive = flag.Bool("i", false, "Prompt for inputs.")
verbose = flag.Bool("v", false, "Verbose.")
setupOnly = flag.Bool("skip-dl", false, "Don't download - only set up environment variables")
goVersion = flag.String("version", "", `Version of Go to install (e.g. "1.8.3"). If empty, uses the latest version.`)
version = "devel"
)
var exitCleanly error = errors.New("exit cleanly sentinel value")
func main() {
flag.Parse()
if *goVersion != "" && !strings.HasPrefix(*goVersion, "go") {
*goVersion = "go" + *goVersion
}
ctx := context.Background()
verbosef("version " + version)
runStep := func(s step) {
err := s(ctx)
if err == exitCleanly {
os.Exit(0)
}
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
}
if !*setupOnly {
runStep(welcome)
runStep(chooseVersion)
runStep(downloadGo)
}
runStep(setupGOPATH)
}
func verbosef(format string, v ...interface{}) {
if !*verbose {
return
}
fmt.Printf(format+"\n", v...)
}
func prompt(ctx context.Context, query, defaultAnswer string) (string, error) {
if !*interactive {
return defaultAnswer, nil
}
fmt.Printf("%s [%s]: ", query, defaultAnswer)
type result struct {
answer string
err error
}
ch := make(chan result, 1)
go func() {
s := bufio.NewScanner(os.Stdin)
if !s.Scan() {
ch <- result{"", s.Err()}
return
}
answer := s.Text()
if answer == "" {
answer = defaultAnswer
}
ch <- result{answer, nil}
}()
select {
case r := <-ch:
return r.answer, r.err
case <-ctx.Done():
return "", ctx.Err()
}
}
func runCommand(ctx context.Context, prog string, args ...string) ([]byte, error) {
verbosef("Running command: %s %v", prog, args)
cmd := exec.CommandContext(ctx, prog, args...)
out, err := cmd.CombinedOutput()
if err != nil {
return nil, fmt.Errorf("running cmd '%s %s' failed: %s err: %v", prog, strings.Join(args, " "), string(out), err)
}
if out != nil && err == nil && len(out) != 0 {
verbosef("%s", out)
}
return out, nil
}

166
cmd/getgo/main_test.go Normal file
View File

@ -0,0 +1,166 @@
// 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.
package main
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"os/exec"
"runtime"
"testing"
)
const (
testbin = "testgetgo"
)
var (
exeSuffix string // ".exe" on Windows
)
func init() {
if runtime.GOOS == "windows" {
exeSuffix = ".exe"
}
}
// TestMain creates a getgo command for testing purposes and
// deletes it after the tests have been run.
func TestMain(m *testing.M) {
args := []string{"build", "-tags", testbin, "-o", testbin + exeSuffix}
out, err := exec.Command("go", args...).CombinedOutput()
if err != nil {
fmt.Fprintf(os.Stderr, "building %s failed: %v\n%s", testbin, err, out)
os.Exit(2)
}
// Don't let these environment variables confuse the test.
os.Unsetenv("GOBIN")
os.Unsetenv("GOPATH")
os.Unsetenv("GIT_ALLOW_PROTOCOL")
os.Unsetenv("PATH")
r := m.Run()
os.Remove(testbin + exeSuffix)
os.Exit(r)
}
func createTmpHome(t *testing.T) string {
tmpd, err := ioutil.TempDir("", "testgetgo")
if err != nil {
t.Fatalf("creating test tempdir failed: %v", err)
}
os.Setenv("HOME", tmpd)
return tmpd
}
// doRun runs the test getgo command, recording stdout and stderr and
// returning exit status.
func doRun(t *testing.T, args ...string) error {
var stdout, stderr bytes.Buffer
t.Logf("running %s %v", testbin, args)
cmd := exec.Command("./"+testbin+exeSuffix, args...)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
cmd.Env = os.Environ()
status := cmd.Run()
if stdout.Len() > 0 {
t.Log("standard output:")
t.Log(stdout.String())
}
if stderr.Len() > 0 {
t.Log("standard error:")
t.Log(stderr.String())
}
return status
}
func TestCommandVerbose(t *testing.T) {
tmpd := createTmpHome(t)
defer os.RemoveAll(tmpd)
err := doRun(t, "-v")
if err != nil {
t.Fatal(err)
}
// make sure things are in path
shellConfig, err := shellConfigFile()
if err != nil {
t.Fatal(err)
}
b, err := ioutil.ReadFile(shellConfig)
if err != nil {
t.Fatal(err)
}
home, err := getHomeDir()
if err != nil {
t.Fatal(err)
}
expected := fmt.Sprintf(`
export PATH=$PATH:%s/.go/bin
export GOPATH=%s/go
export PATH=$PATH:%s/go/bin
`, home, home, home)
if string(b) != expected {
t.Fatalf("%s expected %q, got %q", shellConfig, expected, string(b))
}
}
func TestCommandPathExists(t *testing.T) {
tmpd := createTmpHome(t)
defer os.RemoveAll(tmpd)
// run once
err := doRun(t, "-skip-dl")
if err != nil {
t.Fatal(err)
}
// make sure things are in path
shellConfig, err := shellConfigFile()
if err != nil {
t.Fatal(err)
}
b, err := ioutil.ReadFile(shellConfig)
if err != nil {
t.Fatal(err)
}
home, err := getHomeDir()
if err != nil {
t.Fatal(err)
}
expected := fmt.Sprintf(`
export GOPATH=%s/go
export PATH=$PATH:%s/go/bin
`, home, home)
if string(b) != expected {
t.Fatalf("%s expected %q, got %q", shellConfig, expected, string(b))
}
// run twice
if err := doRun(t, "-skip-dl"); err != nil {
t.Fatal(err)
}
b, err = ioutil.ReadFile(shellConfig)
if err != nil {
t.Fatal(err)
}
if string(b) != expected {
t.Fatalf("%s expected %q, got %q", shellConfig, expected, string(b))
}
}

13
cmd/getgo/make.bash Executable file
View File

@ -0,0 +1,13 @@
#!/bin/bash
# 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.
set -e -o -x
LDFLAGS="-X main.version=$(git describe --always --dirty='*')"
GOOS=windows GOARCH=386 go build -o build/installer.exe -ldflags="$LDFLAGS"
GOOS=linux GOARCH=386 go build -o build/installer_linux -ldflags="$LDFLAGS"
GOOS=darwin GOARCH=386 go build -o build/installer_darwin -ldflags="$LDFLAGS"

153
cmd/getgo/path.go Normal file
View File

@ -0,0 +1,153 @@
// 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.
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())
}
}

56
cmd/getgo/path_test.go Normal file
View File

@ -0,0 +1,56 @@
// 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.
package main
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
)
func TestAppendPath(t *testing.T) {
tmpd, err := ioutil.TempDir("", "go")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpd)
if err := os.Setenv("HOME", tmpd); err != nil {
t.Fatal(err)
}
GOPATH := os.Getenv("GOPATH")
if err := appendToPATH(filepath.Join(GOPATH, "bin")); err != nil {
t.Fatal(err)
}
shellConfig, err := shellConfigFile()
if err != nil {
t.Fatal(err)
}
b, err := ioutil.ReadFile(shellConfig)
if err != nil {
t.Fatal(err)
}
expected := "export PATH=" + pathVar + envSeparator + filepath.Join(GOPATH, "bin")
if strings.TrimSpace(string(b)) != expected {
t.Fatalf("expected: %q, got %q", expected, strings.TrimSpace(string(b)))
}
// Check that appendToPATH is idempotent.
if err := appendToPATH(filepath.Join(GOPATH, "bin")); err != nil {
t.Fatal(err)
}
b, err = ioutil.ReadFile(shellConfig)
if err != nil {
t.Fatal(err)
}
if strings.TrimSpace(string(b)) != expected {
t.Fatalf("expected: %q, got %q", expected, strings.TrimSpace(string(b)))
}
}

View File

@ -0,0 +1,7 @@
# getgo server
## Deployment
```
gcloud app deploy --promote --project golang-org
```

View File

@ -0,0 +1,7 @@
runtime: go
service: get
api_version: go1
handlers:
- url: /.*
script: _go_app

64
cmd/getgo/server/main.go Normal file
View File

@ -0,0 +1,64 @@
// 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.
// Command server serves get.golang.org, redirecting users to the appropriate
// getgo installer based on the request path.
package main
import (
"fmt"
"net/http"
"strings"
"time"
"google.golang.org/appengine"
)
const (
base = "https://storage.googleapis.com/golang/getgo/"
windowsInstaller = base + "installer.exe"
linuxInstaller = base + "installer_linux"
macInstaller = base + "installer_darwin"
)
// substring-based redirects.
var stringMatch = map[string]string{
// via uname, from bash
"MINGW": windowsInstaller, // Reported as MINGW64_NT-10.0 in git bash
"Linux": linuxInstaller,
"Darwin": macInstaller,
}
func main() {
http.HandleFunc("/", handler)
appengine.Main()
}
func handler(w http.ResponseWriter, r *http.Request) {
if containsIgnoreCase(r.URL.Path, "installer.exe") {
// cache bust
http.Redirect(w, r, windowsInstaller+cacheBust(), http.StatusFound)
return
}
for match, redirect := range stringMatch {
if containsIgnoreCase(r.URL.Path, match) {
http.Redirect(w, r, redirect, http.StatusFound)
return
}
}
http.NotFound(w, r)
}
func containsIgnoreCase(s, substr string) bool {
return strings.Contains(
strings.ToLower(s),
strings.ToLower(substr),
)
}
func cacheBust() string {
return fmt.Sprintf("?%d", time.Now().Nanosecond())
}

119
cmd/getgo/steps.go Normal file
View File

@ -0,0 +1,119 @@
// 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.
package main
import (
"context"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
)
type step func(context.Context) error
func welcome(ctx context.Context) error {
fmt.Println("Welcome to the Go installer!")
answer, err := prompt(ctx, "Would you like to install Go? Y/n", "Y")
if err != nil {
return err
}
if strings.ToLower(answer) != "y" {
fmt.Println("Exiting install.")
return exitCleanly
}
return nil
}
func chooseVersion(ctx context.Context) error {
// TODO: check if go is currently installed
// TODO: if go is currently installed install new version over that
if *goVersion != "" {
return nil
}
var err error
*goVersion, err = getLatestGoVersion()
if err != nil {
return err
}
answer, err := prompt(ctx, fmt.Sprintf("The latest go version is %s, install that? Y/n", *goVersion), "Y")
if err != nil {
return err
}
if strings.ToLower(answer) != "y" {
// TODO: handle passing a version
fmt.Println("Aborting install.")
return exitCleanly
}
return nil
}
func downloadGo(ctx context.Context) error {
answer, err := prompt(ctx, fmt.Sprintf("Download go version %s to %s? Y/n", *goVersion, installPath), "Y")
if err != nil {
return err
}
if strings.ToLower(answer) != "y" {
fmt.Println("Aborting install.")
return exitCleanly
}
fmt.Printf("Downloading Go version %s to %s\n", *goVersion, installPath)
fmt.Println("This may take a bit of time...")
if err := downloadGoVersion(*goVersion, runtime.GOOS, arch, installPath); err != nil {
return err
}
if err := appendToPATH(filepath.Join(installPath, "bin")); err != nil {
return err
}
fmt.Println("Downloaded!")
return nil
}
func setupGOPATH(ctx context.Context) error {
answer, err := prompt(ctx, "Would you like us to setup your GOPATH? Y/n", "Y")
if err != nil {
return err
}
if strings.ToLower(answer) != "y" {
fmt.Println("Exiting and not setting up GOPATH.")
return exitCleanly
}
fmt.Println("Setting up GOPATH")
home, err := getHomeDir()
if err != nil {
return err
}
gopath := os.Getenv("GOPATH")
if gopath == "" {
// set $GOPATH
gopath = filepath.Join(home, "go")
if err := persistEnvVar("GOPATH", gopath); err != nil {
return err
}
fmt.Println("GOPATH has been set up!")
} else {
verbosef("GOPATH is already set to %s", gopath)
}
if err := appendToPATH(filepath.Join(gopath, "bin")); err != nil {
return err
}
return persistEnvChangesForSession()
}

29
cmd/getgo/system.go Normal file
View File

@ -0,0 +1,29 @@
// 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.
package main
import (
"bytes"
"os/exec"
"runtime"
)
// arch contains either amd64 or 386.
var arch = func() string {
cmd := exec.Command("uname", "-m") // "x86_64"
if runtime.GOOS == "windows" {
cmd = exec.Command("powershell", "-command", "(Get-WmiObject -Class Win32_ComputerSystem).SystemType") // "x64-based PC"
}
out, err := cmd.Output()
if err != nil {
// a sensible default?
return "amd64"
}
if bytes.Contains(out, []byte("64")) {
return "amd64"
}
return "386"
}()

50
cmd/getgo/system_unix.go Normal file
View File

@ -0,0 +1,50 @@
// 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 darwin dragonfly freebsd linux nacl netbsd openbsd solaris
package main
import (
"fmt"
"os"
"path/filepath"
)
const (
envSeparator = ":"
homeKey = "HOME"
lineEnding = "\n"
pathVar = "$PATH"
)
var installPath = func() string {
home, err := getHomeDir()
if err != nil {
return "/usr/local/go"
}
return filepath.Join(home, ".go")
}()
func isWindowsXP() bool {
return false
}
func currentShell() string {
return os.Getenv("SHELL")
}
func persistEnvChangesForSession() error {
shellConfig, err := shellConfigFile()
if err != nil {
return err
}
fmt.Println()
fmt.Printf("One more thing! Run `source %s` to persist the\n", shellConfig)
fmt.Println("new environment variables to your current session, or open a")
fmt.Println("new shell prompt.")
return nil
}

View File

@ -0,0 +1,81 @@
// 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 windows
package main
import (
"log"
"os"
"syscall"
"unsafe"
)
const (
envSeparator = ";"
homeKey = "USERPROFILE"
lineEnding = "/r/n"
pathVar = "$env:Path"
)
var installPath = `c:\go`
func isWindowsXP() bool {
v, err := syscall.GetVersion()
if err != nil {
log.Fatalf("GetVersion failed: %v", err)
}
major := byte(v)
return major < 6
}
// currentShell reports the current shell.
// It might be "powershell.exe", "cmd.exe" or any of the *nix shells.
//
// Returns empty string if the shell is unknown.
func currentShell() string {
shell := os.Getenv("SHELL")
if shell != "" {
return shell
}
pid := os.Getppid()
pe, err := getProcessEntry(pid)
if err != nil {
verbosef("getting shell from process entry failed: %v", err)
return ""
}
return syscall.UTF16ToString(pe.ExeFile[:])
}
func getProcessEntry(pid int) (*syscall.ProcessEntry32, error) {
// From https://go.googlesource.com/go/+/go1.8.3/src/syscall/syscall_windows.go#941
snapshot, err := syscall.CreateToolhelp32Snapshot(syscall.TH32CS_SNAPPROCESS, 0)
if err != nil {
return nil, err
}
defer syscall.CloseHandle(snapshot)
var procEntry syscall.ProcessEntry32
procEntry.Size = uint32(unsafe.Sizeof(procEntry))
if err = syscall.Process32First(snapshot, &procEntry); err != nil {
return nil, err
}
for {
if procEntry.ProcessID == uint32(pid) {
return &procEntry, nil
}
if err := syscall.Process32Next(snapshot, &procEntry); err != nil {
return nil, err
}
}
}
func persistEnvChangesForSession() error {
return nil
}

19
cmd/getgo/upload.bash Executable file
View File

@ -0,0 +1,19 @@
#!/bin/bash
# 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.
if ! command -v gsutil 2>&1 > /dev/null; then
echo "Install gsutil:"
echo
echo " https://cloud.google.com/storage/docs/gsutil_install#sdk-install"
fi
if [ ! -d build ]; then
echo "Run make.bash first"
fi
set -e -o -x
gsutil -m cp -a public-read build/* gs://golang/getgo