diff --git a/cmd/getgo/.dockerignore b/cmd/getgo/.dockerignore new file mode 100644 index 00000000..2b87ad9c --- /dev/null +++ b/cmd/getgo/.dockerignore @@ -0,0 +1,5 @@ +.git +.dockerignore +LICENSE +README.md +.gitignore diff --git a/cmd/getgo/.gitignore b/cmd/getgo/.gitignore new file mode 100644 index 00000000..d4984ab9 --- /dev/null +++ b/cmd/getgo/.gitignore @@ -0,0 +1,3 @@ +build +testgetgo +getgo diff --git a/cmd/getgo/Dockerfile b/cmd/getgo/Dockerfile new file mode 100644 index 00000000..78fd9566 --- /dev/null +++ b/cmd/getgo/Dockerfile @@ -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 diff --git a/cmd/getgo/LICENSE b/cmd/getgo/LICENSE new file mode 100644 index 00000000..32017f8f --- /dev/null +++ b/cmd/getgo/LICENSE @@ -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. diff --git a/cmd/getgo/README.md b/cmd/getgo/README.md new file mode 100644 index 00000000..e62a6c2b --- /dev/null +++ b/cmd/getgo/README.md @@ -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`. diff --git a/cmd/getgo/download.go b/cmd/getgo/download.go new file mode 100644 index 00000000..ae46549b --- /dev/null +++ b/cmd/getgo/download.go @@ -0,0 +1,182 @@ +// 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) + + req, err := http.NewRequest("GET", uri, nil) + if err != nil { + return err + } + req.Header.Add("User-Agent", fmt.Sprintf("golang.org-getgo/%s", version)) + + resp, err := http.DefaultClient.Do(req) + 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 +} diff --git a/cmd/getgo/download_test.go b/cmd/getgo/download_test.go new file mode 100644 index 00000000..1a47823c --- /dev/null +++ b/cmd/getgo/download_test.go @@ -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) + } +} diff --git a/cmd/getgo/main.go b/cmd/getgo/main.go new file mode 100644 index 00000000..8d9fbd41 --- /dev/null +++ b/cmd/getgo/main.go @@ -0,0 +1,115 @@ +// 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, "Interactive mode, 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(checkOthers) + 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 +} diff --git a/cmd/getgo/main_test.go b/cmd/getgo/main_test.go new file mode 100644 index 00000000..932a7397 --- /dev/null +++ b/cmd/getgo/main_test.go @@ -0,0 +1,171 @@ +// 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) { + if os.Getenv("GOGET_INTEGRATION") == "" { + fmt.Fprintln(os.Stderr, "main_test: Skipping integration tests with GOGET_INTEGRATION unset") + return + } + + 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)) + } +} diff --git a/cmd/getgo/make.bash b/cmd/getgo/make.bash new file mode 100755 index 00000000..cbc36857 --- /dev/null +++ b/cmd/getgo/make.bash @@ -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" diff --git a/cmd/getgo/path.go b/cmd/getgo/path.go new file mode 100644 index 00000000..551ac42e --- /dev/null +++ b/cmd/getgo/path.go @@ -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()) + } +} diff --git a/cmd/getgo/path_test.go b/cmd/getgo/path_test.go new file mode 100644 index 00000000..4cf66474 --- /dev/null +++ b/cmd/getgo/path_test.go @@ -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))) + } +} diff --git a/cmd/getgo/server/README.md b/cmd/getgo/server/README.md new file mode 100644 index 00000000..0cf629d6 --- /dev/null +++ b/cmd/getgo/server/README.md @@ -0,0 +1,7 @@ +# getgo server + +## Deployment + +``` +gcloud app deploy --promote --project golang-org +``` diff --git a/cmd/getgo/server/app.yaml b/cmd/getgo/server/app.yaml new file mode 100644 index 00000000..0502c4e0 --- /dev/null +++ b/cmd/getgo/server/app.yaml @@ -0,0 +1,7 @@ +runtime: go +service: get +api_version: go1 + +handlers: +- url: /.* + script: _go_app diff --git a/cmd/getgo/server/main.go b/cmd/getgo/server/main.go new file mode 100644 index 00000000..b5995065 --- /dev/null +++ b/cmd/getgo/server/main.go @@ -0,0 +1,61 @@ +// 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 server + +import ( + "fmt" + "net/http" + "strings" + "time" +) + +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 init() { + http.HandleFunc("/", handler) +} + +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()) +} diff --git a/cmd/getgo/steps.go b/cmd/getgo/steps.go new file mode 100644 index 00000000..eac6517d --- /dev/null +++ b/cmd/getgo/steps.go @@ -0,0 +1,131 @@ +// 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 checkOthers(ctx context.Context) error { + // TODO: if go is currently installed install new version over that + path, err := whichGo(ctx) + if err != nil { + fmt.Printf("Cannot check if Go is already installed:\n%v\n", err) + } + if path == "" { + return nil + } + if path != installPath { + fmt.Printf("Go is already installed at %v; remove it from your PATH.\n", path) + } + return nil +} + +func chooseVersion(ctx context.Context) error { + 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() +} diff --git a/cmd/getgo/system.go b/cmd/getgo/system.go new file mode 100644 index 00000000..8b5b024d --- /dev/null +++ b/cmd/getgo/system.go @@ -0,0 +1,36 @@ +// 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" + "context" + "os/exec" + "runtime" + "strings" +) + +// 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" +}() + +func findGo(ctx context.Context, cmd string) (string, error) { + out, err := exec.CommandContext(ctx, cmd, "go").CombinedOutput() + return strings.TrimSpace(string(out)), err +} diff --git a/cmd/getgo/system_unix.go b/cmd/getgo/system_unix.go new file mode 100644 index 00000000..d2405c5c --- /dev/null +++ b/cmd/getgo/system_unix.go @@ -0,0 +1,55 @@ +// 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 ( + "context" + "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 whichGo(ctx context.Context) (string, error) { + return findGo(ctx, "which") +} + +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 +} diff --git a/cmd/getgo/system_windows.go b/cmd/getgo/system_windows.go new file mode 100644 index 00000000..d8a61917 --- /dev/null +++ b/cmd/getgo/system_windows.go @@ -0,0 +1,86 @@ +// 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 ( + "context" + "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 +} + +func whichGo(ctx context.Context) (string, error) { + return findGo(ctx, "where") +} + +// 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 +} diff --git a/cmd/getgo/upload.bash b/cmd/getgo/upload.bash new file mode 100755 index 00000000..f52bb23c --- /dev/null +++ b/cmd/getgo/upload.bash @@ -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 diff --git a/cmd/tip/Dockerfile b/cmd/tip/Dockerfile index 9c80137f..86dfe364 100644 --- a/cmd/tip/Dockerfile +++ b/cmd/tip/Dockerfile @@ -5,11 +5,128 @@ RUN apt-get update && apt-get install --no-install-recommends -y -q build-essent # golang puts its go install here (weird but true) ENV GOROOT_BOOTSTRAP /usr/local/go -RUN go get -d golang.org/x/crypto/acme/autocert +# BEGIN deps (run `make update-deps` to update) + +# Repo cloud.google.com/go at 76d607c (2017-07-20) +ENV REV=76d607c4e7a2b9df49f1d1a58a3f3d2dd2614704 +RUN go get -d cloud.google.com/go/compute/metadata `#and 6 other pkgs` &&\ + (cd /go/src/cloud.google.com/go && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV) + +# Repo github.com/golang/protobuf at 0a4f71a (2017-07-11) +ENV REV=0a4f71a498b7c4812f64969510bcb4eca251e33a +RUN go get -d github.com/golang/protobuf/proto `#and 6 other pkgs` &&\ + (cd /go/src/github.com/golang/protobuf && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV) + +# Repo github.com/googleapis/gax-go at 84ed267 (2017-06-10) +ENV REV=84ed26760e7f6f80887a2fbfb50db3cc415d2cea +RUN go get -d github.com/googleapis/gax-go &&\ + (cd /go/src/github.com/googleapis/gax-go && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV) + +# Repo golang.org/x/build at da1460b (2017-07-31) +ENV REV=da1460b7c9c9b65383d1336593ed9ad346f6a1c5 +RUN go get -d golang.org/x/build/autocertcache &&\ + (cd /go/src/golang.org/x/build && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV) + +# Repo golang.org/x/crypto at 6914964 (2017-07-20) +ENV REV=6914964337150723782436d56b3f21610a74ce7b +RUN go get -d golang.org/x/crypto/acme `#and 2 other pkgs` &&\ + (cd /go/src/golang.org/x/crypto && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV) + +# Repo golang.org/x/net at ab54850 (2017-07-21) +ENV REV=ab5485076ff3407ad2d02db054635913f017b0ed +RUN go get -d golang.org/x/net/context `#and 8 other pkgs` &&\ + (cd /go/src/golang.org/x/net && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV) + +# Repo golang.org/x/oauth2 at b53b38a (2017-07-19) +ENV REV=b53b38ad8a6435bd399ea76d0fa74f23149cca4e +RUN go get -d golang.org/x/oauth2 `#and 5 other pkgs` &&\ + (cd /go/src/golang.org/x/oauth2 && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV) + +# Repo golang.org/x/text at 836efe4 (2017-07-14) +ENV REV=836efe42bb4aa16aaa17b9c155d8813d336ed720 +RUN go get -d golang.org/x/text/secure/bidirule `#and 4 other pkgs` &&\ + (cd /go/src/golang.org/x/text && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV) + +# Repo google.golang.org/api at 295e4bb (2017-07-18) +ENV REV=295e4bb0ade057ae2cfb9876ab0b54635dbfcea4 +RUN go get -d google.golang.org/api/gensupport `#and 9 other pkgs` &&\ + (cd /go/src/google.golang.org/api && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV) + +# Repo google.golang.org/genproto at b0a3dcf (2017-07-12) +ENV REV=b0a3dcfcd1a9bd48e63634bd8802960804cf8315 +RUN go get -d google.golang.org/genproto/googleapis/api/annotations `#and 3 other pkgs` &&\ + (cd /go/src/google.golang.org/genproto && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV) + +# Repo google.golang.org/grpc at fa1cb32 (2017-07-31) +ENV REV=fa1cb32dc4f81e23ab862dd5e7ac4f2920a33088 +RUN go get -d google.golang.org/grpc `#and 14 other pkgs` &&\ + (cd /go/src/google.golang.org/grpc && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV) + +# Optimization to speed up iterative development, not necessary for correctness: +RUN go install cloud.google.com/go/compute/metadata \ + cloud.google.com/go/iam \ + cloud.google.com/go/internal \ + cloud.google.com/go/internal/optional \ + cloud.google.com/go/internal/version \ + cloud.google.com/go/storage \ + github.com/golang/protobuf/proto \ + github.com/golang/protobuf/protoc-gen-go/descriptor \ + github.com/golang/protobuf/ptypes \ + github.com/golang/protobuf/ptypes/any \ + github.com/golang/protobuf/ptypes/duration \ + github.com/golang/protobuf/ptypes/timestamp \ + github.com/googleapis/gax-go \ + golang.org/x/build/autocertcache \ + golang.org/x/crypto/acme \ + golang.org/x/crypto/acme/autocert \ + golang.org/x/net/context \ + golang.org/x/net/context/ctxhttp \ + golang.org/x/net/http2 \ + golang.org/x/net/http2/hpack \ + golang.org/x/net/idna \ + golang.org/x/net/internal/timeseries \ + golang.org/x/net/lex/httplex \ + golang.org/x/net/trace \ + golang.org/x/oauth2 \ + golang.org/x/oauth2/google \ + golang.org/x/oauth2/internal \ + golang.org/x/oauth2/jws \ + golang.org/x/oauth2/jwt \ + golang.org/x/text/secure/bidirule \ + golang.org/x/text/transform \ + golang.org/x/text/unicode/bidi \ + golang.org/x/text/unicode/norm \ + google.golang.org/api/gensupport \ + google.golang.org/api/googleapi \ + google.golang.org/api/googleapi/internal/uritemplates \ + google.golang.org/api/googleapi/transport \ + google.golang.org/api/internal \ + google.golang.org/api/iterator \ + google.golang.org/api/option \ + google.golang.org/api/storage/v1 \ + google.golang.org/api/transport/http \ + google.golang.org/genproto/googleapis/api/annotations \ + google.golang.org/genproto/googleapis/iam/v1 \ + google.golang.org/genproto/googleapis/rpc/status \ + google.golang.org/grpc \ + google.golang.org/grpc/codes \ + google.golang.org/grpc/credentials \ + google.golang.org/grpc/grpclb/grpc_lb_v1 \ + google.golang.org/grpc/grpclog \ + google.golang.org/grpc/internal \ + google.golang.org/grpc/keepalive \ + google.golang.org/grpc/metadata \ + google.golang.org/grpc/naming \ + google.golang.org/grpc/peer \ + google.golang.org/grpc/stats \ + google.golang.org/grpc/status \ + google.golang.org/grpc/tap \ + google.golang.org/grpc/transport +# END deps. # golang sets GOPATH=/go ADD . /go/src/tip -RUN go install tip +RUN go install --tags=autocert tip ENTRYPOINT ["/go/bin/tip"] # App Engine expects us to listen on port 8080 EXPOSE 8080 diff --git a/cmd/tip/Makefile b/cmd/tip/Makefile index 5844307b..7d2f6ed3 100644 --- a/cmd/tip/Makefile +++ b/cmd/tip/Makefile @@ -2,7 +2,11 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. -VERSION=v1 +VERSION=v2 + +update-deps: + go install golang.org/x/build/cmd/gitlock + gitlock --update=Dockerfile --ignore=NONE golang.org/x/tools/cmd/tip docker-prod: Dockerfile docker build -f Dockerfile --tag=gcr.io/symbolic-datum-552/tip:$(VERSION) . @@ -10,6 +14,6 @@ docker-dev: Dockerfile docker build -f Dockerfile --tag=gcr.io/go-dashboard-dev/tip:$(VERSION) . push-prod: docker-prod - gcloud docker push -- gcr.io/symbolic-datum-552/tip:$(VERSION) + gcloud docker -- push gcr.io/symbolic-datum-552/tip:$(VERSION) push-dev: docker-dev - gcloud docker push -- gcr.io/go-dashboard-dev/tip:$(VERSION) + gcloud docker -- push gcr.io/go-dashboard-dev/tip:$(VERSION) diff --git a/cmd/tip/cert.go b/cmd/tip/cert.go new file mode 100644 index 00000000..e00777b2 --- /dev/null +++ b/cmd/tip/cert.go @@ -0,0 +1,50 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by the Apache 2.0 +// license that can be found in the LICENSE file. + +// +build autocert + +// This file contains autocert and cloud.google.com/go/storage +// dependencies we want to hide by default from the Go build system, +// which currently doesn't know how to fetch non-golang.org/x/* +// dependencies. The Dockerfile builds the production binary +// with this code using --tags=autocert. + +package main + +import ( + "context" + "crypto/tls" + "log" + "net/http" + + "cloud.google.com/go/storage" + "golang.org/x/build/autocertcache" + "golang.org/x/crypto/acme/autocert" +) + +func init() { + runHTTPS = runHTTPSAutocert +} + +func runHTTPSAutocert(h http.Handler) error { + var cache autocert.Cache + if b := *autoCertCacheBucket; b != "" { + sc, err := storage.NewClient(context.Background()) + if err != nil { + log.Fatalf("storage.NewClient: %v", err) + } + cache = autocertcache.NewGoogleCloudStorageCache(sc, b) + } + m := autocert.Manager{ + Prompt: autocert.AcceptTOS, + HostPolicy: autocert.HostWhitelist(*autoCertDomain), + Cache: cache, + } + s := &http.Server{ + Addr: ":https", + Handler: h, + TLSConfig: &tls.Config{GetCertificate: m.GetCertificate}, + } + return s.ListenAndServeTLS("", "") +} diff --git a/cmd/tip/tip-rc.yaml b/cmd/tip/tip-rc.yaml index 139e614c..82af3b70 100644 --- a/cmd/tip/tip-rc.yaml +++ b/cmd/tip/tip-rc.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: ReplicationController metadata: - name: tipgodoc-v1 + name: tipgodoc spec: replicas: 1 selector: @@ -17,9 +17,9 @@ spec: emptyDir: {} containers: - name: gitmirror - image: gcr.io/symbolic-datum-552/tip:v1 + image: gcr.io/symbolic-datum-552/tip:v2 imagePullPolicy: Always - command: ["/go/bin/tip", "--autocert=tip.golang.org"] + command: ["/go/bin/tip", "--autocert=tip.golang.org", "--autocert-bucket=golang-tip-autocert"] env: - name: TMPDIR value: /build diff --git a/cmd/tip/tip.go b/cmd/tip/tip.go index 428bcf09..6e0fd47a 100644 --- a/cmd/tip/tip.go +++ b/cmd/tip/tip.go @@ -8,7 +8,6 @@ package main import ( "bufio" - "crypto/tls" "encoding/json" "errors" "flag" @@ -24,8 +23,6 @@ import ( "path/filepath" "sync" "time" - - "golang.org/x/crypto/acme/autocert" ) const ( @@ -37,9 +34,14 @@ const ( var startTime = time.Now() var ( - autoCertDomain = flag.String("autocert", "", "if non-empty, listen on port 443 and serve a LetsEncrypt cert for this hostname") + autoCertDomain = flag.String("autocert", "", "if non-empty, listen on port 443 and serve a LetsEncrypt cert for this hostname") + autoCertCacheBucket = flag.String("autocert-bucket", "", "if non-empty, the Google Cloud Storage bucket in which to store the LetsEncrypt cache") ) +// runHTTPS, if non-nil, specifies the function to serve HTTPS. +// It is set non-nil in cert.go with the "autocert" build tag. +var runHTTPS func(http.Handler) error + func main() { flag.Parse() @@ -60,25 +62,20 @@ func main() { log.Printf("Starting up tip server for builder %q", os.Getenv(k)) - errc := make(chan error) + errc := make(chan error, 1) go func() { errc <- http.ListenAndServe(":8080", mux) }() if *autoCertDomain != "" { + if runHTTPS == nil { + errc <- errors.New("can't use --autocert without building binary with the autocert build tag") + } else { + go func() { + errc <- runHTTPS(mux) + }() + } log.Printf("Listening on port 443 with LetsEncrypt support on domain %q", *autoCertDomain) - m := autocert.Manager{ - Prompt: autocert.AcceptTOS, - HostPolicy: autocert.HostWhitelist(*autoCertDomain), - } - s := &http.Server{ - Addr: ":https", - Handler: mux, - TLSConfig: &tls.Config{GetCertificate: m.GetCertificate}, - } - go func() { - errc <- s.ListenAndServeTLS("", "") - }() } if err := <-errc; err != nil { p.stop() diff --git a/godoc/README.md b/godoc/README.md index 1c495c8d..52bc8a4e 100644 --- a/godoc/README.md +++ b/godoc/README.md @@ -10,7 +10,7 @@ binary. It can be tedious to recompile assets every time, but you can pass a flag to load CSS/JS/templates from disk every time a page loads: ``` -godoc --templates=$GOPATH/src/golang.org/x/tools/godoc/static --http=:6060 +godoc -templates=$GOPATH/src/golang.org/x/tools/godoc/static -http=:6060 ``` ## Recompiling static assets diff --git a/godoc/dl/dl.go b/godoc/dl/dl.go index b4230b61..3d2f6c46 100644 --- a/godoc/dl/dl.go +++ b/godoc/dl/dl.go @@ -32,7 +32,6 @@ import ( "google.golang.org/appengine/datastore" "google.golang.org/appengine/log" "google.golang.org/appengine/memcache" - "google.golang.org/appengine/user" ) const ( @@ -183,7 +182,6 @@ var featuredFiles = []Feature{ type listTemplateData struct { Featured []Feature Stable, Unstable, Archive []Release - LoginURL string } var ( @@ -218,11 +216,6 @@ func listHandler(w http.ResponseWriter, r *http.Request) { d.Featured = filesToFeatured(d.Stable[0].Files) } - d.LoginURL, _ = user.LoginURL(c, "/dl") - if user.Current(c) != nil { - d.LoginURL, _ = user.LogoutURL(c, "/dl") - } - item := &memcache.Item{Key: cacheKey, Object: &d, Expiration: cacheDuration} if err := memcache.Gob.Set(c, item); err != nil { log.Errorf(c, "cache set error: %v", err) diff --git a/godoc/dl/tmpl.go b/godoc/dl/tmpl.go index e92f9b76..440917ab 100644 --- a/godoc/dl/tmpl.go +++ b/godoc/dl/tmpl.go @@ -106,7 +106,7 @@ please follow the installation instructions.

-If you are building from source, +If you are building from source, follow the source installation instructions.

@@ -146,13 +146,6 @@ information about Go releases. {{end}} - - - diff --git a/imports/fix.go b/imports/fix.go index ac7f4b00..e2460849 100644 --- a/imports/fix.go +++ b/imports/fix.go @@ -776,7 +776,7 @@ func findImportGoPath(pkgName string, symbols map[string]bool, filename string) sort.Sort(byImportPathShortLength(candidates)) if Debug { for i, pkg := range candidates { - log.Printf("%s candidate %d/%d: %v", pkgName, i+1, len(candidates), pkg.importPathShort) + log.Printf("%s candidate %d/%d: %v in %v", pkgName, i+1, len(candidates), pkg.importPathShort, pkg.dir) } }