[release-branch.go1.9] all: merge master into release-branch.go1.9
0f5d61c4
imports: print dir of candidates in addition to import patha237aba5
godoc: fix out-of-bounds panic when serving top-level files4e70a1b2
godoc: add GoogleCN property to pagesfcc44a63
cmd/getgo: add a user-agent to download requests3fd990c6
cmd/tip: fix the buildd07a458d
cmd/tip: add a cert cache, clean up Kubernetes config, use update-deps9badcbe4
cmd/getgo: prompt warning if an earlier installation exists6fdd948b
godoc: remove disabled admin codef2b3bb00
cmd/getgo: fix builds001b4ec8
cmd/getgo: have consistent messages29518d98
cmd/getgo: display that -i stands for interactive mode5724bdc2
x/tools/godoc: fix redirect to Gerritac1e4b19
cmd/getgo: initial commit Change-Id: Ie58293a2621bbabbadea4f9431c6fe7bc4aa329f
This commit is contained in:
commit
fa615f793b
|
@ -0,0 +1,5 @@
|
||||||
|
.git
|
||||||
|
.dockerignore
|
||||||
|
LICENSE
|
||||||
|
README.md
|
||||||
|
.gitignore
|
|
@ -0,0 +1,3 @@
|
||||||
|
build
|
||||||
|
testgetgo
|
||||||
|
getgo
|
|
@ -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
|
|
@ -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.
|
|
@ -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`.
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
# getgo server
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
```
|
||||||
|
gcloud app deploy --promote --project golang-org
|
||||||
|
```
|
|
@ -0,0 +1,7 @@
|
||||||
|
runtime: go
|
||||||
|
service: get
|
||||||
|
api_version: go1
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
- url: /.*
|
||||||
|
script: _go_app
|
|
@ -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())
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
|
@ -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)
|
# golang puts its go install here (weird but true)
|
||||||
ENV GOROOT_BOOTSTRAP /usr/local/go
|
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
|
# golang sets GOPATH=/go
|
||||||
ADD . /go/src/tip
|
ADD . /go/src/tip
|
||||||
RUN go install tip
|
RUN go install --tags=autocert tip
|
||||||
ENTRYPOINT ["/go/bin/tip"]
|
ENTRYPOINT ["/go/bin/tip"]
|
||||||
# App Engine expects us to listen on port 8080
|
# App Engine expects us to listen on port 8080
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
|
@ -2,7 +2,11 @@
|
||||||
# Use of this source code is governed by a BSD-style
|
# Use of this source code is governed by a BSD-style
|
||||||
# license that can be found in the LICENSE file.
|
# 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-prod: Dockerfile
|
||||||
docker build -f Dockerfile --tag=gcr.io/symbolic-datum-552/tip:$(VERSION) .
|
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) .
|
docker build -f Dockerfile --tag=gcr.io/go-dashboard-dev/tip:$(VERSION) .
|
||||||
|
|
||||||
push-prod: docker-prod
|
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
|
push-dev: docker-dev
|
||||||
gcloud docker push -- gcr.io/go-dashboard-dev/tip:$(VERSION)
|
gcloud docker -- push gcr.io/go-dashboard-dev/tip:$(VERSION)
|
||||||
|
|
|
@ -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("", "")
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ReplicationController
|
kind: ReplicationController
|
||||||
metadata:
|
metadata:
|
||||||
name: tipgodoc-v1
|
name: tipgodoc
|
||||||
spec:
|
spec:
|
||||||
replicas: 1
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
|
@ -17,9 +17,9 @@ spec:
|
||||||
emptyDir: {}
|
emptyDir: {}
|
||||||
containers:
|
containers:
|
||||||
- name: gitmirror
|
- name: gitmirror
|
||||||
image: gcr.io/symbolic-datum-552/tip:v1
|
image: gcr.io/symbolic-datum-552/tip:v2
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
command: ["/go/bin/tip", "--autocert=tip.golang.org"]
|
command: ["/go/bin/tip", "--autocert=tip.golang.org", "--autocert-bucket=golang-tip-autocert"]
|
||||||
env:
|
env:
|
||||||
- name: TMPDIR
|
- name: TMPDIR
|
||||||
value: /build
|
value: /build
|
||||||
|
|
|
@ -8,7 +8,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"crypto/tls"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
|
@ -24,8 +23,6 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/crypto/acme/autocert"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -38,8 +35,13 @@ var startTime = time.Now()
|
||||||
|
|
||||||
var (
|
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() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
@ -60,26 +62,21 @@ func main() {
|
||||||
|
|
||||||
log.Printf("Starting up tip server for builder %q", os.Getenv(k))
|
log.Printf("Starting up tip server for builder %q", os.Getenv(k))
|
||||||
|
|
||||||
errc := make(chan error)
|
errc := make(chan error, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
errc <- http.ListenAndServe(":8080", mux)
|
errc <- http.ListenAndServe(":8080", mux)
|
||||||
}()
|
}()
|
||||||
if *autoCertDomain != "" {
|
if *autoCertDomain != "" {
|
||||||
log.Printf("Listening on port 443 with LetsEncrypt support on domain %q", *autoCertDomain)
|
if runHTTPS == nil {
|
||||||
m := autocert.Manager{
|
errc <- errors.New("can't use --autocert without building binary with the autocert build tag")
|
||||||
Prompt: autocert.AcceptTOS,
|
} else {
|
||||||
HostPolicy: autocert.HostWhitelist(*autoCertDomain),
|
|
||||||
}
|
|
||||||
s := &http.Server{
|
|
||||||
Addr: ":https",
|
|
||||||
Handler: mux,
|
|
||||||
TLSConfig: &tls.Config{GetCertificate: m.GetCertificate},
|
|
||||||
}
|
|
||||||
go func() {
|
go func() {
|
||||||
errc <- s.ListenAndServeTLS("", "")
|
errc <- runHTTPS(mux)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
log.Printf("Listening on port 443 with LetsEncrypt support on domain %q", *autoCertDomain)
|
||||||
|
}
|
||||||
if err := <-errc; err != nil {
|
if err := <-errc; err != nil {
|
||||||
p.stop()
|
p.stop()
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
|
|
@ -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:
|
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
|
## Recompiling static assets
|
||||||
|
|
|
@ -32,7 +32,6 @@ import (
|
||||||
"google.golang.org/appengine/datastore"
|
"google.golang.org/appengine/datastore"
|
||||||
"google.golang.org/appengine/log"
|
"google.golang.org/appengine/log"
|
||||||
"google.golang.org/appengine/memcache"
|
"google.golang.org/appengine/memcache"
|
||||||
"google.golang.org/appengine/user"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -183,7 +182,6 @@ var featuredFiles = []Feature{
|
||||||
type listTemplateData struct {
|
type listTemplateData struct {
|
||||||
Featured []Feature
|
Featured []Feature
|
||||||
Stable, Unstable, Archive []Release
|
Stable, Unstable, Archive []Release
|
||||||
LoginURL string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -218,11 +216,6 @@ func listHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
d.Featured = filesToFeatured(d.Stable[0].Files)
|
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}
|
item := &memcache.Item{Key: cacheKey, Object: &d, Expiration: cacheDuration}
|
||||||
if err := memcache.Gob.Set(c, item); err != nil {
|
if err := memcache.Gob.Set(c, item); err != nil {
|
||||||
log.Errorf(c, "cache set error: %v", err)
|
log.Errorf(c, "cache set error: %v", err)
|
||||||
|
|
|
@ -146,13 +146,6 @@ information about Go releases.
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
|
||||||
<!-- Disabled for now; there's no admin functionality yet.
|
|
||||||
<p>
|
|
||||||
<small><a href="{{.LoginURL}}">π</a></small>
|
|
||||||
</p>
|
|
||||||
-->
|
|
||||||
|
|
||||||
<div id="footer">
|
<div id="footer">
|
||||||
<p>
|
<p>
|
||||||
Except as
|
Except as
|
||||||
|
|
|
@ -423,7 +423,7 @@ func sanitizeFunc(src string) string {
|
||||||
type PageInfo struct {
|
type PageInfo struct {
|
||||||
Dirname string // directory containing the package
|
Dirname string // directory containing the package
|
||||||
Err error // error or nil
|
Err error // error or nil
|
||||||
Share bool // show share button on examples
|
GoogleCN bool // page is being served from golang.google.cn
|
||||||
|
|
||||||
Mode PageInfoMode // display metadata from query string
|
Mode PageInfoMode // display metadata from query string
|
||||||
|
|
||||||
|
@ -461,17 +461,14 @@ func pkgLinkFunc(path string) string {
|
||||||
return "pkg/" + path
|
return "pkg/" + path
|
||||||
}
|
}
|
||||||
|
|
||||||
// srcToPkgLinkFunc builds an <a> tag linking to
|
// srcToPkgLinkFunc builds an <a> tag linking to the package
|
||||||
// the package documentation of relpath.
|
// documentation of relpath.
|
||||||
func srcToPkgLinkFunc(relpath string) string {
|
func srcToPkgLinkFunc(relpath string) string {
|
||||||
relpath = pkgLinkFunc(relpath)
|
relpath = pkgLinkFunc(relpath)
|
||||||
if relpath == "pkg/" {
|
relpath = pathpkg.Dir(relpath)
|
||||||
|
if relpath == "pkg" {
|
||||||
return `<a href="/pkg">Index</a>`
|
return `<a href="/pkg">Index</a>`
|
||||||
}
|
}
|
||||||
if i := strings.LastIndex(relpath, "/"); i != -1 {
|
|
||||||
// Remove filename after last slash.
|
|
||||||
relpath = relpath[:i]
|
|
||||||
}
|
|
||||||
return fmt.Sprintf(`<a href="/%s">%s</a>`, relpath, relpath[len("pkg/"):])
|
return fmt.Sprintf(`<a href="/%s">%s</a>`, relpath, relpath[len("pkg/"):])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -683,8 +680,8 @@ func (p *Presentation) example_htmlFunc(info *PageInfo, funcName string) string
|
||||||
|
|
||||||
err := p.ExampleHTML.Execute(&buf, struct {
|
err := p.ExampleHTML.Execute(&buf, struct {
|
||||||
Name, Doc, Code, Play, Output string
|
Name, Doc, Code, Play, Output string
|
||||||
Share bool
|
GoogleCN bool
|
||||||
}{eg.Name, eg.Doc, code, play, out, info.Share})
|
}{eg.Name, eg.Doc, code, play, out, info.GoogleCN})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -313,6 +313,8 @@ func TestSrcToPkgLinkFunc(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{"src/", `<a href="/pkg">Index</a>`},
|
{"src/", `<a href="/pkg">Index</a>`},
|
||||||
{"src/fmt/", `<a href="/pkg/fmt">fmt</a>`},
|
{"src/fmt/", `<a href="/pkg/fmt">fmt</a>`},
|
||||||
|
{"pkg/", `<a href="/pkg">Index</a>`},
|
||||||
|
{"pkg/LICENSE", `<a href="/pkg">Index</a>`},
|
||||||
} {
|
} {
|
||||||
if got := srcToPkgLinkFunc(tc.path); got != tc.want {
|
if got := srcToPkgLinkFunc(tc.path); got != tc.want {
|
||||||
t.Errorf("srcToPkgLinkFunc(%v) = %v; want %v", tc.path, got, tc.want)
|
t.Errorf("srcToPkgLinkFunc(%v) = %v; want %v", tc.path, got, tc.want)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Page describes the contents of the top-level godoc webpage.
|
// Page describes the contents of the top-level godoc webpage.
|
||||||
|
@ -19,7 +20,7 @@ type Page struct {
|
||||||
SrcPath string
|
SrcPath string
|
||||||
Query string
|
Query string
|
||||||
Body []byte
|
Body []byte
|
||||||
Share bool
|
GoogleCN bool // page is being served from golang.google.cn
|
||||||
|
|
||||||
// filled in by servePage
|
// filled in by servePage
|
||||||
SearchBox bool
|
SearchBox bool
|
||||||
|
@ -51,19 +52,25 @@ func (p *Presentation) ServeError(w http.ResponseWriter, r *http.Request, relpat
|
||||||
Title: "File " + relpath,
|
Title: "File " + relpath,
|
||||||
Subtitle: relpath,
|
Subtitle: relpath,
|
||||||
Body: applyTemplate(p.ErrorHTML, "errorHTML", err),
|
Body: applyTemplate(p.ErrorHTML, "errorHTML", err),
|
||||||
Share: allowShare(r),
|
GoogleCN: googleCN(r),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
var onAppengine = false // overriden in appengine.go when on app engine
|
var onAppengine = false // overridden in appengine.go when on app engine
|
||||||
|
|
||||||
func allowShare(r *http.Request) bool {
|
func googleCN(r *http.Request) bool {
|
||||||
|
if r.FormValue("googlecn") != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
if !onAppengine {
|
if !onAppengine {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(r.Host, ".cn") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
switch r.Header.Get("X-AppEngine-Country") {
|
switch r.Header.Get("X-AppEngine-Country") {
|
||||||
case "", "ZZ", "CN":
|
case "", "ZZ", "CN":
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
@ -147,8 +148,8 @@ func cacheKey(body string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func share(w http.ResponseWriter, r *http.Request) {
|
func share(w http.ResponseWriter, r *http.Request) {
|
||||||
if !allowShare(r) {
|
if googleCN(r) {
|
||||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
target, _ := url.Parse(playgroundURL)
|
target, _ := url.Parse(playgroundURL)
|
||||||
|
@ -157,13 +158,19 @@ func share(w http.ResponseWriter, r *http.Request) {
|
||||||
p.ServeHTTP(w, r)
|
p.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func allowShare(r *http.Request) bool {
|
func googleCN(r *http.Request) bool {
|
||||||
|
if r.FormValue("googlecn") != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
if appengine.IsDevAppServer() {
|
if appengine.IsDevAppServer() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(r.Host, ".cn") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
switch r.Header.Get("X-AppEngine-Country") {
|
switch r.Header.Get("X-AppEngine-Country") {
|
||||||
case "", "ZZ", "CN":
|
case "", "ZZ", "CN":
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -196,7 +196,7 @@ func clHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if n, err := strconv.Atoi(id); err == nil && n > 150000 {
|
if n, err := strconv.Atoi(id); err == nil && n > 150000 {
|
||||||
target = "https://codereview.appspot.com/" + id
|
target = "https://codereview.appspot.com/" + id
|
||||||
} else {
|
} else {
|
||||||
target = "https://go-review.googlesource.com/r/" + id
|
target = "https://go-review.googlesource.com/" + id
|
||||||
}
|
}
|
||||||
http.Redirect(w, r, target, http.StatusFound)
|
http.Redirect(w, r, target, http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,8 +58,8 @@ func TestRedirects(t *testing.T) {
|
||||||
"/design/123-foo": {302, "https://github.com/golang/proposal/blob/master/design/123-foo.md"},
|
"/design/123-foo": {302, "https://github.com/golang/proposal/blob/master/design/123-foo.md"},
|
||||||
"/design/text/123-foo": {302, "https://github.com/golang/proposal/blob/master/design/text/123-foo.md"},
|
"/design/text/123-foo": {302, "https://github.com/golang/proposal/blob/master/design/text/123-foo.md"},
|
||||||
|
|
||||||
"/cl/1": {302, "https://go-review.googlesource.com/r/1"},
|
"/cl/1": {302, "https://go-review.googlesource.com/1"},
|
||||||
"/cl/1/": {302, "https://go-review.googlesource.com/r/1"},
|
"/cl/1/": {302, "https://go-review.googlesource.com/1"},
|
||||||
"/cl/267120043": {302, "https://codereview.appspot.com/267120043"},
|
"/cl/267120043": {302, "https://codereview.appspot.com/267120043"},
|
||||||
"/cl/267120043/": {302, "https://codereview.appspot.com/267120043"},
|
"/cl/267120043/": {302, "https://codereview.appspot.com/267120043"},
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,7 +126,7 @@ func (p *Presentation) HandleSearch(w http.ResponseWriter, r *http.Request) {
|
||||||
Tabtitle: query,
|
Tabtitle: query,
|
||||||
Query: query,
|
Query: query,
|
||||||
Body: body.Bytes(),
|
Body: body.Bytes(),
|
||||||
Share: allowShare(r),
|
GoogleCN: googleCN(r),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -312,13 +312,13 @@ func (h *handlerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
info.TypeInfoIndex[ti.Name] = i
|
info.TypeInfoIndex[ti.Name] = i
|
||||||
}
|
}
|
||||||
|
|
||||||
info.Share = allowShare(r)
|
info.GoogleCN = googleCN(r)
|
||||||
h.p.ServePage(w, Page{
|
h.p.ServePage(w, Page{
|
||||||
Title: title,
|
Title: title,
|
||||||
Tabtitle: tabtitle,
|
Tabtitle: tabtitle,
|
||||||
Subtitle: subtitle,
|
Subtitle: subtitle,
|
||||||
Body: applyTemplate(h.p.PackageHTML, "packageHTML", info),
|
Body: applyTemplate(h.p.PackageHTML, "packageHTML", info),
|
||||||
Share: info.Share,
|
GoogleCN: info.GoogleCN,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -583,7 +583,7 @@ func (p *Presentation) serveTextFile(w http.ResponseWriter, r *http.Request, abs
|
||||||
SrcPath: relpath,
|
SrcPath: relpath,
|
||||||
Tabtitle: relpath,
|
Tabtitle: relpath,
|
||||||
Body: buf.Bytes(),
|
Body: buf.Bytes(),
|
||||||
Share: allowShare(r),
|
GoogleCN: googleCN(r),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -654,7 +654,7 @@ func (p *Presentation) serveDirectory(w http.ResponseWriter, r *http.Request, ab
|
||||||
SrcPath: relpath,
|
SrcPath: relpath,
|
||||||
Tabtitle: relpath,
|
Tabtitle: relpath,
|
||||||
Body: applyTemplate(p.DirlistHTML, "dirlistHTML", list),
|
Body: applyTemplate(p.DirlistHTML, "dirlistHTML", list),
|
||||||
Share: allowShare(r),
|
GoogleCN: googleCN(r),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -683,7 +683,7 @@ func (p *Presentation) ServeHTMLDoc(w http.ResponseWriter, r *http.Request, absp
|
||||||
page := Page{
|
page := Page{
|
||||||
Title: meta.Title,
|
Title: meta.Title,
|
||||||
Subtitle: meta.Subtitle,
|
Subtitle: meta.Subtitle,
|
||||||
Share: allowShare(r),
|
GoogleCN: googleCN(r),
|
||||||
}
|
}
|
||||||
|
|
||||||
// evaluate as template if indicated
|
// evaluate as template if indicated
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<a class="run" title="Run this code [shift-enter]">Run</a>
|
<a class="run" title="Run this code [shift-enter]">Run</a>
|
||||||
<a class="fmt" title="Format this code">Format</a>
|
<a class="fmt" title="Format this code">Format</a>
|
||||||
{{if $.Share}}
|
{{if not $.GoogleCN}}
|
||||||
<a class="share" title="Share this code">Share</a>
|
<a class="share" title="Share this code">Share</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -55,7 +55,7 @@ func main() {
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<a class="run" title="Run this code [shift-enter]">Run</a>
|
<a class="run" title="Run this code [shift-enter]">Run</a>
|
||||||
<a class="fmt" title="Format this code">Format</a>
|
<a class="fmt" title="Format this code">Format</a>
|
||||||
{{if $.Share}}
|
{{if not $.GoogleCN}}
|
||||||
<a class="share" title="Share this code">Share</a>
|
<a class="share" title="Share this code">Share</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -776,7 +776,7 @@ func findImportGoPath(pkgName string, symbols map[string]bool, filename string)
|
||||||
sort.Sort(byImportPathShortLength(candidates))
|
sort.Sort(byImportPathShortLength(candidates))
|
||||||
if Debug {
|
if Debug {
|
||||||
for i, pkg := range candidates {
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue