[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)
|
||||
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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
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
|
||||
|
|
|
@ -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 (
|
||||
|
@ -38,8 +35,13 @@ var startTime = time.Now()
|
|||
|
||||
var (
|
||||
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,26 +62,21 @@ 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 != "" {
|
||||
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},
|
||||
}
|
||||
if runHTTPS == nil {
|
||||
errc <- errors.New("can't use --autocert without building binary with the autocert build tag")
|
||||
} else {
|
||||
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 {
|
||||
p.stop()
|
||||
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:
|
||||
|
||||
```
|
||||
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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -146,13 +146,6 @@ information about Go releases.
|
|||
</div>
|
||||
{{end}}
|
||||
|
||||
|
||||
<!-- Disabled for now; there's no admin functionality yet.
|
||||
<p>
|
||||
<small><a href="{{.LoginURL}}">π</a></small>
|
||||
</p>
|
||||
-->
|
||||
|
||||
<div id="footer">
|
||||
<p>
|
||||
Except as
|
||||
|
|
|
@ -423,7 +423,7 @@ func sanitizeFunc(src string) string {
|
|||
type PageInfo struct {
|
||||
Dirname string // directory containing the package
|
||||
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
|
||||
|
||||
|
@ -461,17 +461,14 @@ func pkgLinkFunc(path string) string {
|
|||
return "pkg/" + path
|
||||
}
|
||||
|
||||
// srcToPkgLinkFunc builds an <a> tag linking to
|
||||
// the package documentation of relpath.
|
||||
// srcToPkgLinkFunc builds an <a> tag linking to the package
|
||||
// documentation of relpath.
|
||||
func srcToPkgLinkFunc(relpath string) string {
|
||||
relpath = pkgLinkFunc(relpath)
|
||||
if relpath == "pkg/" {
|
||||
relpath = pathpkg.Dir(relpath)
|
||||
if relpath == "pkg" {
|
||||
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/"):])
|
||||
}
|
||||
|
||||
|
@ -683,8 +680,8 @@ func (p *Presentation) example_htmlFunc(info *PageInfo, funcName string) string
|
|||
|
||||
err := p.ExampleHTML.Execute(&buf, struct {
|
||||
Name, Doc, Code, Play, Output string
|
||||
Share bool
|
||||
}{eg.Name, eg.Doc, code, play, out, info.Share})
|
||||
GoogleCN bool
|
||||
}{eg.Name, eg.Doc, code, play, out, info.GoogleCN})
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
|
|
|
@ -313,6 +313,8 @@ func TestSrcToPkgLinkFunc(t *testing.T) {
|
|||
}{
|
||||
{"src/", `<a href="/pkg">Index</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 {
|
||||
t.Errorf("srcToPkgLinkFunc(%v) = %v; want %v", tc.path, got, tc.want)
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Page describes the contents of the top-level godoc webpage.
|
||||
|
@ -19,7 +20,7 @@ type Page struct {
|
|||
SrcPath string
|
||||
Query string
|
||||
Body []byte
|
||||
Share bool
|
||||
GoogleCN bool // page is being served from golang.google.cn
|
||||
|
||||
// filled in by servePage
|
||||
SearchBox bool
|
||||
|
@ -51,19 +52,25 @@ func (p *Presentation) ServeError(w http.ResponseWriter, r *http.Request, relpat
|
|||
Title: "File " + relpath,
|
||||
Subtitle: relpath,
|
||||
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 {
|
||||
return false
|
||||
}
|
||||
if strings.HasSuffix(r.Host, ".cn") {
|
||||
return true
|
||||
}
|
||||
switch r.Header.Get("X-AppEngine-Country") {
|
||||
case "", "ZZ", "CN":
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
@ -147,8 +148,8 @@ func cacheKey(body string) string {
|
|||
}
|
||||
|
||||
func share(w http.ResponseWriter, r *http.Request) {
|
||||
if !allowShare(r) {
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
if googleCN(r) {
|
||||
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
target, _ := url.Parse(playgroundURL)
|
||||
|
@ -157,13 +158,19 @@ func share(w http.ResponseWriter, r *http.Request) {
|
|||
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() {
|
||||
return false
|
||||
}
|
||||
if strings.HasSuffix(r.Host, ".cn") {
|
||||
return true
|
||||
}
|
||||
switch r.Header.Get("X-AppEngine-Country") {
|
||||
case "", "ZZ", "CN":
|
||||
return false
|
||||
}
|
||||
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 {
|
||||
target = "https://codereview.appspot.com/" + id
|
||||
} else {
|
||||
target = "https://go-review.googlesource.com/r/" + id
|
||||
target = "https://go-review.googlesource.com/" + id
|
||||
}
|
||||
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/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/r/1"},
|
||||
"/cl/1": {302, "https://go-review.googlesource.com/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"},
|
||||
}
|
||||
|
|
|
@ -126,7 +126,7 @@ func (p *Presentation) HandleSearch(w http.ResponseWriter, r *http.Request) {
|
|||
Tabtitle: query,
|
||||
Query: query,
|
||||
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.Share = allowShare(r)
|
||||
info.GoogleCN = googleCN(r)
|
||||
h.p.ServePage(w, Page{
|
||||
Title: title,
|
||||
Tabtitle: tabtitle,
|
||||
Subtitle: subtitle,
|
||||
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,
|
||||
Tabtitle: relpath,
|
||||
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,
|
||||
Tabtitle: relpath,
|
||||
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{
|
||||
Title: meta.Title,
|
||||
Subtitle: meta.Subtitle,
|
||||
Share: allowShare(r),
|
||||
GoogleCN: googleCN(r),
|
||||
}
|
||||
|
||||
// evaluate as template if indicated
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<div class="buttons">
|
||||
<a class="run" title="Run this code [shift-enter]">Run</a>
|
||||
<a class="fmt" title="Format this code">Format</a>
|
||||
{{if $.Share}}
|
||||
{{if not $.GoogleCN}}
|
||||
<a class="share" title="Share this code">Share</a>
|
||||
{{end}}
|
||||
</div>
|
||||
|
|
|
@ -55,7 +55,7 @@ func main() {
|
|||
<div class="buttons">
|
||||
<a class="run" title="Run this code [shift-enter]">Run</a>
|
||||
<a class="fmt" title="Format this code">Format</a>
|
||||
{{if $.Share}}
|
||||
{{if not $.GoogleCN}}
|
||||
<a class="share" title="Share this code">Share</a>
|
||||
{{end}}
|
||||
</div>
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue