Commit 1e5b7e70 authored by Gustavo Niemeyer's avatar Gustavo Niemeyer

cmd/goinstall: remove now that 'go get' works

The changes to builder were not tested.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5572083
parent d91ade02
......@@ -40,7 +40,6 @@ CLEANDIRS=\
godoc\
fix\
gofmt\
goinstall\
gotest\
vet\
yacc\
......
# Copyright 2009 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.
include ../../Make.inc
TARG=goinstall
GOFILES=\
download.go\
main.go\
make.go\
include ../../Make.cmd
test:
gotest
testshort:
gotest -test.short
// Copyright 2010 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.
/*
Goinstall is an experiment in automatic package installation.
It installs packages, possibly downloading them from the internet.
It maintains a list of public Go packages at
http://godashboard.appspot.com/package.
Usage:
goinstall [flags] importpath...
goinstall [flags] -a
Flags and default settings:
-a=false install all previously installed packages
-clean=false clean the package directory before installing
-dashboard=true tally public packages on godashboard.appspot.com
-install=true build and install the package and its dependencies
-nuke=false remove the target object and clean before installing
-u=false update already-downloaded packages
-v=false verbose operation
Goinstall installs each of the packages identified on the command line. It
installs a package's prerequisites before trying to install the package
itself. Unless -log=false is specified, goinstall logs the import path of each
installed package to $GOROOT/goinstall.log for use by goinstall -a.
If the -a flag is given, goinstall reinstalls all previously installed
packages, reading the list from $GOROOT/goinstall.log. After updating to a
new Go release, which deletes all package binaries, running
goinstall -a
will recompile and reinstall goinstalled packages.
Another common idiom is to use
goinstall -a -u
to update, recompile, and reinstall all goinstalled packages.
The source code for a package with import path foo/bar is expected
to be in the directory $GOROOT/src/pkg/foo/bar/ or $GOPATH/src/foo/bar/.
See "The GOPATH Environment Variable" for more about GOPATH.
By default, goinstall prints output only when it encounters an error.
The -v flag causes goinstall to print information about packages
being considered and installed.
Goinstall ignores Makefiles.
Remote Repositories
If a package import path refers to a remote repository, goinstall will
download the code if necessary.
Goinstall recognizes packages from a few common code hosting sites:
BitBucket (Git, Mercurial)
import "bitbucket.org/user/project"
import "bitbucket.org/user/project/sub/directory"
GitHub (Git)
import "github.com/user/project"
import "github.com/user/project/sub/directory"
Google Code Project Hosting (Git, Mercurial, Subversion)
import "project.googlecode.com/git"
import "project.googlecode.com/git/sub/directory"
import "project.googlecode.com/hg"
import "project.googlecode.com/hg/sub/directory"
import "project.googlecode.com/svn/trunk"
import "project.googlecode.com/svn/trunk/sub/directory"
Google Code Project Hosting sub-repositories:
import "code.google.com/p/project.subrepo/sub/directory
Launchpad (Bazaar)
import "launchpad.net/project"
import "launchpad.net/project/series"
import "launchpad.net/project/series/sub/directory"
import "launchpad.net/~user/project/branch"
import "launchpad.net/~user/project/branch/sub/directory"
If the destination directory (e.g., $GOROOT/src/pkg/bitbucket.org/user/project)
already exists and contains an appropriate checkout, goinstall will not
attempt to fetch updates. The -u flag changes this behavior,
causing goinstall to update all remote packages encountered during
the installation.
When downloading or updating, goinstall looks for a tag with the "go." prefix
that corresponds to the local Go version. For Go "release.r58" it looks for a
tag named "go.r58". For "weekly.2011-06-03" it looks for "go.weekly.2011-06-03".
If the specific "go.X" tag is not found, it chooses the closest earlier version.
If an appropriate tag is found, goinstall uses that version of the code.
Otherwise it uses the default version selected by the version control
system, typically HEAD for git, tip for Mercurial.
After a successful download and installation of one of these import paths,
goinstall reports the installation to godashboard.appspot.com, which
increments a count associated with the package and the time of its most
recent installation. This mechanism powers the package list at
http://godashboard.appspot.com/package, allowing Go programmers to learn about
popular packages that might be worth looking at.
The -dashboard=false flag disables this reporting.
For code hosted on other servers, goinstall recognizes the general form
repository.vcs/path
as denoting the given repository, with or without the .vcs suffix, using
the named version control system, and then the path inside that repository.
The supported version control systems are:
Bazaar .bzr
Git .git
Mercurial .hg
Subversion .svn
For example,
import "example.org/user/foo.hg"
denotes the root directory of the Mercurial repository at example.org/user/foo
or foo.hg, and
import "example.org/repo.git/foo/bar"
denotes the foo/bar directory of the Git repository at example.com/repo or
repo.git.
When a version control system supports multiple protocols, goinstall tries each
in turn.
For example, for Git it tries git://, then https://, then http://.
The GOPATH Environment Variable
GOPATH may be set to a colon-separated list of paths inside which Go code,
package objects, and executables may be found.
Set a GOPATH to use goinstall to build and install your own code and
external libraries outside of the Go tree (and to avoid writing Makefiles).
The top-level directory structure of a GOPATH is prescribed:
The 'src' directory is for source code. The directory naming inside 'src'
determines the package import path or executable name.
The 'pkg' directory is for package objects. Like the Go tree, package objects
are stored inside a directory named after the target operating system and
processor architecture ('pkg/$GOOS_$GOARCH').
A package whose source is located at '$GOPATH/src/foo/bar' would be imported
as 'foo/bar' and installed as '$GOPATH/pkg/$GOOS_$GOARCH/foo/bar.a'.
The 'bin' directory is for executable files.
Goinstall installs program binaries using the name of the source folder.
A binary whose source is at 'src/foo/qux' would be built and installed to
'$GOPATH/bin/qux'. (Note 'bin/qux', not 'bin/foo/qux' - this is such that
you can put the bin directory in your PATH.)
Here's an example directory layout:
GOPATH=/home/user/gocode
/home/user/gocode/
src/foo/
bar/ (go code in package bar)
qux/ (go code in package main)
bin/qux (executable file)
pkg/linux_amd64/foo/bar.a (object file)
Run 'goinstall foo/bar' to build and install the package 'foo/bar'
(and its dependencies).
Goinstall will search each GOPATH (in order) for 'src/foo/bar'.
If the directory cannot be found, goinstall will attempt to fetch the
source from a remote repository and write it to the 'src' directory of the
first GOPATH (or $GOROOT/src/pkg if GOPATH is not set).
Goinstall recognizes relative and absolute paths (paths beginning with / or .).
The following commands would build our example packages:
goinstall /home/user/gocode/src/foo/bar # build and install foo/bar
cd /home/user/gocode/src/foo
goinstall ./bar # build and install foo/bar (again)
cd qux
goinstall . # build and install foo/qux
*/
package documentation
// Copyright 2010 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.
// Download remote packages.
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
)
const dashboardURL = "http://godashboard.appspot.com/package"
// maybeReportToDashboard reports path to dashboard unless
// -dashboard=false is on command line. It ignores errors.
func maybeReportToDashboard(path string) {
// if -dashboard=false was on command line, do nothing
if !*reportToDashboard {
return
}
// otherwise lob url to dashboard
r, _ := http.Post(dashboardURL, "application/x-www-form-urlencoded", strings.NewReader("path="+path))
if r != nil && r.Body != nil {
r.Body.Close()
}
}
// a vcs represents a version control system
// like Mercurial, Git, or Subversion.
type vcs struct {
name string
cmd string
metadir string
checkout string
clone string
update string
updateRevFlag string
pull string
pullForceFlag string
tagList string
tagListRe *regexp.Regexp
check string
protocols []string
suffix string
}
func (v *vcs) String() string {
return v.name
}
var vcsMap = map[string]*vcs{
"hg": {
name: "Mercurial",
cmd: "hg",
metadir: ".hg",
checkout: "checkout",
clone: "clone",
update: "update",
pull: "pull",
tagList: "tags",
tagListRe: regexp.MustCompile("([^ ]+)[^\n]+\n"),
check: "identify",
protocols: []string{"https", "http"},
suffix: ".hg",
},
"git": {
name: "Git",
cmd: "git",
metadir: ".git",
checkout: "checkout",
clone: "clone",
update: "pull",
pull: "fetch",
tagList: "tag",
tagListRe: regexp.MustCompile("([^\n]+)\n"),
check: "ls-remote",
protocols: []string{"git", "https", "http"},
suffix: ".git",
},
"svn": {
name: "Subversion",
cmd: "svn",
metadir: ".svn",
checkout: "checkout",
clone: "checkout",
update: "update",
check: "info",
protocols: []string{"https", "http", "svn"},
suffix: ".svn",
},
"bzr": {
name: "Bazaar",
cmd: "bzr",
metadir: ".bzr",
checkout: "update",
clone: "branch",
update: "update",
updateRevFlag: "-r",
pull: "pull",
pullForceFlag: "--overwrite",
tagList: "tags",
tagListRe: regexp.MustCompile("([^ ]+)[^\n]+\n"),
check: "info",
protocols: []string{"https", "http", "bzr"},
suffix: ".bzr",
},
}
type RemoteRepo interface {
// IsCheckedOut returns whether this repository is checked
// out inside the given srcDir (eg, $GOPATH/src).
IsCheckedOut(srcDir string) bool
// Repo returns the information about this repository: its url,
// the part of the import path that forms the repository root,
// and the version control system it uses. It may discover this
// information by using the supplied client to make HTTP requests.
Repo(*http.Client) (url, root string, vcs *vcs, err error)
}
type host struct {
pattern *regexp.Regexp
repo func(repo string) (RemoteRepo, error)
}
var knownHosts = []host{
{
regexp.MustCompile(`^code\.google\.com/p/([a-z0-9\-]+(\.[a-z0-9\-]+)?)(/[a-z0-9A-Z_.\-/]+)?$`),
matchGoogleRepo,
},
{
regexp.MustCompile(`^(github\.com/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]+)?$`),
matchGithubRepo,
},
{
regexp.MustCompile(`^(bitbucket\.org/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]+)?$`),
matchBitbucketRepo,
},
{
regexp.MustCompile(`^(launchpad\.net/([a-z0-9A-Z_.\-]+(/[a-z0-9A-Z_.\-]+)?|~[a-z0-9A-Z_.\-]+/(\+junk|[a-z0-9A-Z_.\-]+)/[a-z0-9A-Z_.\-]+))(/[a-z0-9A-Z_.\-/]+)?$`),
matchLaunchpadRepo,
},
}
// baseRepo is the base implementation of RemoteRepo.
type baseRepo struct {
url, root string
vcs *vcs
}
func (r *baseRepo) Repo(*http.Client) (url, root string, vcs *vcs, err error) {
return r.url, r.root, r.vcs, nil
}
// IsCheckedOut reports whether the repo root inside srcDir contains a
// repository metadir. It updates the baseRepo's vcs field if necessary.
func (r *baseRepo) IsCheckedOut(srcDir string) bool {
pkgPath := filepath.Join(srcDir, r.root)
if r.vcs == nil {
for _, vcs := range vcsMap {
if isDir(filepath.Join(pkgPath, vcs.metadir)) {
r.vcs = vcs
return true
}
}
return false
}
return isDir(filepath.Join(pkgPath, r.vcs.metadir))
}
// matchGithubRepo handles matches for github.com repositories.
func matchGithubRepo(root string) (RemoteRepo, error) {
if strings.HasSuffix(root, ".git") {
return nil, errors.New("path must not include .git suffix")
}
return &baseRepo{"http://" + root + ".git", root, vcsMap["git"]}, nil
}
// matchLaunchpadRepo handles matches for launchpad.net repositories.
func matchLaunchpadRepo(root string) (RemoteRepo, error) {
return &baseRepo{"https://" + root, root, vcsMap["bzr"]}, nil
}
// matchGoogleRepo matches repos like "code.google.com/p/repo.subrepo/path".
func matchGoogleRepo(id string) (RemoteRepo, error) {
root := "code.google.com/p/" + id
return &googleRepo{baseRepo{"https://" + root, root, nil}}, nil
}
// googleRepo implements a RemoteRepo that discovers a Google Code
// repository's VCS type by scraping the code.google.com source checkout page.
type googleRepo struct{ baseRepo }
var googleRepoRe = regexp.MustCompile(`id="checkoutcmd">(hg|git|svn)`)
func (r *googleRepo) Repo(client *http.Client) (url, root string, vcs *vcs, err error) {
if r.vcs != nil {
return r.url, r.root, r.vcs, nil
}
// Use the code.google.com source checkout page to find the VCS type.
const prefix = "code.google.com/p/"
p := strings.SplitN(r.root[len(prefix):], ".", 2)
u := fmt.Sprintf("https://%s%s/source/checkout", prefix, p[0])
if len(p) == 2 {
u += fmt.Sprintf("?repo=%s", p[1])
}
resp, err := client.Get(u)
if err != nil {
return "", "", nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return "", "", nil, fmt.Errorf("fetching %s: %v", u, resp.Status)
}
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", "", nil, fmt.Errorf("fetching %s: %v", u, err)
}
// Scrape result for vcs details.
if m := googleRepoRe.FindSubmatch(b); len(m) == 2 {
s := string(m[1])
if v := vcsMap[s]; v != nil {
if s == "svn" {
// Subversion still uses the old-style URL.
r.url = fmt.Sprintf("http://%s.googlecode.com/svn", p[0])
}
r.vcs = v
return r.url, r.root, r.vcs, nil
}
}
return "", "", nil, errors.New("could not detect googlecode vcs")
}
// matchBitbucketRepo handles matches for all bitbucket.org repositories.
func matchBitbucketRepo(root string) (RemoteRepo, error) {
if strings.HasSuffix(root, ".git") {
return nil, errors.New("path must not include .git suffix")
}
return &bitbucketRepo{baseRepo{root: root}}, nil
}
// bitbucketRepo implements a RemoteRepo that uses the BitBucket API to
// discover the repository's VCS type.
type bitbucketRepo struct{ baseRepo }
func (r *bitbucketRepo) Repo(client *http.Client) (url, root string, vcs *vcs, err error) {
if r.vcs != nil && r.url != "" {
return r.url, r.root, r.vcs, nil
}
// Use the BitBucket API to find which kind of repository this is.
const apiUrl = "https://api.bitbucket.org/1.0/repositories/"
resp, err := client.Get(apiUrl + strings.SplitN(r.root, "/", 2)[1])
if err != nil {
return "", "", nil, fmt.Errorf("BitBucket API: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return "", "", nil, fmt.Errorf("BitBucket API: %v", resp.Status)
}
var response struct {
Vcs string `json:"scm"`
}
err = json.NewDecoder(resp.Body).Decode(&response)
if err != nil {
return "", "", nil, fmt.Errorf("BitBucket API: %v", err)
}
switch response.Vcs {
case "git":
r.url = "http://" + r.root + ".git"
case "hg":
r.url = "http://" + r.root
default:
return "", "", nil, errors.New("unsupported bitbucket vcs: " + response.Vcs)
}
if r.vcs = vcsMap[response.Vcs]; r.vcs == nil {
panic("vcs is nil when it should not be")
}
return r.url, r.root, r.vcs, nil
}
// findPublicRepo checks whether importPath is a well-formed path for one of
// the supported code hosting sites and, if so, returns a RemoteRepo.
func findPublicRepo(importPath string) (RemoteRepo, error) {
for _, host := range knownHosts {
if hm := host.pattern.FindStringSubmatch(importPath); hm != nil {
return host.repo(hm[1])
}
}
return nil, nil
}
// findAnyRepo matches import paths with a repo suffix (.git, etc).
func findAnyRepo(importPath string) RemoteRepo {
for _, v := range vcsMap {
i := strings.Index(importPath+"/", v.suffix+"/")
if i < 0 {
continue
}
if !strings.Contains(importPath[:i], "/") {
continue // don't match vcs suffix in the host name
}
return &anyRepo{
baseRepo{
root: importPath[:i] + v.suffix,
vcs: v,
},
importPath[:i],
}
}
return nil
}
// anyRepo implements an discoverable remote repo with a suffix (.git, etc).
type anyRepo struct {
baseRepo
rootWithoutSuffix string
}
func (r *anyRepo) Repo(*http.Client) (url, root string, vcs *vcs, err error) {
if r.url != "" {
return r.url, r.root, r.vcs, nil
}
url, err = r.vcs.findURL(r.rootWithoutSuffix)
if url == "" && err == nil {
err = fmt.Errorf("couldn't find %s repository", r.vcs.name)
}
if err != nil {
return "", "", nil, err
}
r.url = url
return r.url, r.root, r.vcs, nil
}
// findURL finds the URL for a given repo root by trying each combination of
// protocol and suffix in series.
func (v *vcs) findURL(root string) (string, error) {
for _, proto := range v.protocols {
for _, suffix := range []string{"", v.suffix} {
url := proto + "://" + root + suffix
out, err := exec.Command(v.cmd, v.check, url).CombinedOutput()
if err == nil {
printf("find %s: found %s\n", root, url)
return url, nil
}
printf("findURL(%s): %s %s %s: %v\n%s\n", root, v.cmd, v.check, url, err, out)
}
}
return "", nil
}
var oldGoogleRepo = regexp.MustCompile(`^([a-z0-9\-]+)\.googlecode\.com/(svn|git|hg)(/[a-z0-9A-Z_.\-/]+)?$`)
type errOldGoogleRepo struct {
fixedPath string
}
func (e *errOldGoogleRepo) Error() string {
return fmt.Sprintf("unsupported import path; should be %q", e.fixedPath)
}
// download checks out or updates the specified package from the remote server.
func download(importPath, srcDir string) (public bool, err error) {
if strings.Contains(importPath, "..") {
err = errors.New("invalid path (contains ..)")
return
}
if m := oldGoogleRepo.FindStringSubmatch(importPath); m != nil {
fixedPath := "code.google.com/p/" + m[1] + m[3]
err = &errOldGoogleRepo{fixedPath}
return
}
repo, err := findPublicRepo(importPath)
if err != nil {
return false, err
}
if repo != nil {
public = true
} else {
repo = findAnyRepo(importPath)
}
if repo == nil {
err = errors.New("cannot download: " + importPath)
return
}
err = checkoutRepo(srcDir, repo)
return
}
// checkoutRepo checks out repo into srcDir (if it's not checked out already)
// and, if the -u flag is set, updates the repository.
func checkoutRepo(srcDir string, repo RemoteRepo) error {
if !repo.IsCheckedOut(srcDir) {
// do checkout
url, root, vcs, err := repo.Repo(http.DefaultClient)
if err != nil {
return err
}
repoPath := filepath.Join(srcDir, root)
parent, _ := filepath.Split(repoPath)
if err = os.MkdirAll(parent, 0777); err != nil {
return err
}
if err = run(string(filepath.Separator), nil, vcs.cmd, vcs.clone, url, repoPath); err != nil {
return err
}
return vcs.updateRepo(repoPath)
}
if *update {
// do update
_, root, vcs, err := repo.Repo(http.DefaultClient)
if err != nil {
return err
}
repoPath := filepath.Join(srcDir, root)
// Retrieve new revisions from the remote branch, if the VCS
// supports this operation independently (e.g. svn doesn't)
if vcs.pull != "" {
if vcs.pullForceFlag != "" {
if err = run(repoPath, nil, vcs.cmd, vcs.pull, vcs.pullForceFlag); err != nil {
return err
}
} else if err = run(repoPath, nil, vcs.cmd, vcs.pull); err != nil {
return err
}
}
// Update to release or latest revision
return vcs.updateRepo(repoPath)
}
return nil
}
// updateRepo gets a list of tags in the repository and
// checks out the tag closest to the current runtime.Version.
// If no matching tag is found, it just updates to tip.
func (v *vcs) updateRepo(repoPath string) error {
if v.tagList == "" || v.tagListRe == nil {
// TODO(adg): fix for svn
return run(repoPath, nil, v.cmd, v.update)
}
// Get tag list.
stderr := new(bytes.Buffer)
cmd := exec.Command(v.cmd, v.tagList)
cmd.Dir = repoPath
cmd.Stderr = stderr
out, err := cmd.Output()
if err != nil {
return &RunError{strings.Join(cmd.Args, " "), repoPath, out, err}
}
var tags []string
for _, m := range v.tagListRe.FindAllStringSubmatch(string(out), -1) {
tags = append(tags, m[1])
}
// Only use the tag component of runtime.Version.
ver := strings.Split(runtime.Version(), " ")[0]
// Select tag.
if tag := selectTag(ver, tags); tag != "" {
printf("selecting revision %q\n", tag)
return run(repoPath, nil, v.cmd, v.checkout, v.updateRevFlag+tag)
}
// No matching tag found, make default selection.
printf("selecting tip\n")
return run(repoPath, nil, v.cmd, v.update)
}
// selectTag returns the closest matching tag for a given version.
// Closest means the latest one that is not after the current release.
// Version "release.rN" matches tags of the form "go.rN" (N being a decimal).
// Version "weekly.YYYY-MM-DD" matches tags like "go.weekly.YYYY-MM-DD".
func selectTag(goVersion string, tags []string) (match string) {
const rPrefix = "release.r"
if strings.HasPrefix(goVersion, rPrefix) {
p := "go.r"
v, err := strconv.ParseFloat(goVersion[len(rPrefix):], 64)
if err != nil {
return ""
}
var matchf float64
for _, t := range tags {
if !strings.HasPrefix(t, p) {
continue
}
tf, err := strconv.ParseFloat(t[len(p):], 64)
if err != nil {
continue
}
if matchf < tf && tf <= v {
match, matchf = t, tf
}
}
}
const wPrefix = "weekly."
if strings.HasPrefix(goVersion, wPrefix) {
p := "go.weekly."
v := goVersion[len(wPrefix):]
for _, t := range tags {
if !strings.HasPrefix(t, p) {
continue
}
if match < t && t[len(p):] <= v {
match = t
}
}
}
return match
}
func isDir(dir string) bool {
fi, err := os.Stat(dir)
return err == nil && fi.IsDir()
}
// Copyright 2011 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"
"errors"
"io/ioutil"
"net/http"
"testing"
)
var FindPublicRepoTests = []struct {
pkg string
vcs, root, url string
transport *testTransport
}{
{
"code.google.com/p/repo/path/foo",
"hg",
"code.google.com/p/repo",
"https://code.google.com/p/repo",
&testTransport{
"https://code.google.com/p/repo/source/checkout",
`<tt id="checkoutcmd">hg clone https://...`,
},
},
{
"code.google.com/p/repo/path/foo",
"svn",
"code.google.com/p/repo",
"http://repo.googlecode.com/svn",
&testTransport{
"https://code.google.com/p/repo/source/checkout",
`<tt id="checkoutcmd">svn checkout https://...`,
},
},
{
"code.google.com/p/repo/path/foo",
"git",
"code.google.com/p/repo",
"https://code.google.com/p/repo",
&testTransport{
"https://code.google.com/p/repo/source/checkout",
`<tt id="checkoutcmd">git clone https://...`,
},
},
{
"code.google.com/p/repo.sub/path",
"hg",
"code.google.com/p/repo.sub",
"https://code.google.com/p/repo.sub",
&testTransport{
"https://code.google.com/p/repo/source/checkout?repo=sub",
`<tt id="checkoutcmd">hg clone https://...`,
},
},
{
"bitbucket.org/user/repo/path/foo",
"hg",
"bitbucket.org/user/repo",
"http://bitbucket.org/user/repo",
&testTransport{
"https://api.bitbucket.org/1.0/repositories/user/repo",
`{"scm": "hg"}`,
},
},
{
"bitbucket.org/user/repo/path/foo",
"git",
"bitbucket.org/user/repo",
"http://bitbucket.org/user/repo.git",
&testTransport{
"https://api.bitbucket.org/1.0/repositories/user/repo",
`{"scm": "git"}`,
},
},
{
"github.com/user/repo/path/foo",
"git",
"github.com/user/repo",
"http://github.com/user/repo.git",
nil,
},
{
"launchpad.net/project/series/path",
"bzr",
"launchpad.net/project/series",
"https://launchpad.net/project/series",
nil,
},
{
"launchpad.net/~user/project/branch/path",
"bzr",
"launchpad.net/~user/project/branch",
"https://launchpad.net/~user/project/branch",
nil,
},
}
func TestFindPublicRepo(t *testing.T) {
for _, test := range FindPublicRepoTests {
client := http.DefaultClient
if test.transport != nil {
client = &http.Client{Transport: test.transport}
}
repo, err := findPublicRepo(test.pkg)
if err != nil {
t.Errorf("findPublicRepo(%s): error: %v", test.pkg, err)
continue
}
if repo == nil {
t.Errorf("%s: got nil match", test.pkg)
continue
}
url, root, vcs, err := repo.Repo(client)
if err != nil {
t.Errorf("%s: repo.Repo error: %v", test.pkg, err)
continue
}
if v := vcsMap[test.vcs]; vcs != v {
t.Errorf("%s: got vcs=%v, want %v", test.pkg, vcs, v)
}
if root != test.root {
t.Errorf("%s: got root=%v, want %v", test.pkg, root, test.root)
}
if url != test.url {
t.Errorf("%s: got url=%v, want %v", test.pkg, url, test.url)
}
}
}
type testTransport struct {
expectURL string
responseBody string
}
func (t *testTransport) RoundTrip(req *http.Request) (*http.Response, error) {
if g, e := req.URL.String(), t.expectURL; g != e {
return nil, errors.New("want " + e)
}
body := ioutil.NopCloser(bytes.NewBufferString(t.responseBody))
return &http.Response{
StatusCode: http.StatusOK,
Body: body,
}, nil
}
// Copyright 2010 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"
"errors"
"flag"
"fmt"
"go/build"
"go/token"
"io/ioutil"
"os"
"os/exec"
"path/filepath" // use for file system paths
"regexp"
"runtime"
"strings"
)
func usage() {
fmt.Fprintln(os.Stderr, "usage: goinstall [flags] importpath...")
fmt.Fprintln(os.Stderr, " goinstall [flags] -a")
flag.PrintDefaults()
os.Exit(2)
}
const logfile = "goinstall.log"
var (
fset = token.NewFileSet()
argv0 = os.Args[0]
parents = make(map[string]string)
visit = make(map[string]status)
installedPkgs = make(map[string]map[string]bool)
schemeRe = regexp.MustCompile(`^[a-z]+://`)
allpkg = flag.Bool("a", false, "install all previously installed packages")
reportToDashboard = flag.Bool("dashboard", true, "report public packages at "+dashboardURL)
update = flag.Bool("u", false, "update already-downloaded packages")
doGofix = flag.Bool("fix", false, "gofix each package before building it")
doInstall = flag.Bool("install", true, "build and install")
clean = flag.Bool("clean", false, "clean the package directory before installing")
nuke = flag.Bool("nuke", false, "clean the package directory and target before installing")
useMake = flag.Bool("make", true, "use make to build and install (obsolete, always true)")
verbose = flag.Bool("v", false, "verbose")
)
type status int // status for visited map
const (
unvisited status = iota
visiting
done
)
type PackageError struct {
pkg string
err error
}
func (e *PackageError) Error() string {
return fmt.Sprintf("%s: %v", e.pkg, e.err)
}
type DownloadError struct {
pkg string
goroot bool
err error
}
func (e *DownloadError) Error() string {
s := fmt.Sprintf("%s: download failed: %v", e.pkg, e.err)
if e.goroot && os.Getenv("GOPATH") == "" {
s += " ($GOPATH is not set)"
}
return s
}
type DependencyError PackageError
func (e *DependencyError) Error() string {
return fmt.Sprintf("%s: depends on failing packages:\n\t%v", e.pkg, e.err)
}
type BuildError PackageError
func (e *BuildError) Error() string {
return fmt.Sprintf("%s: build failed: %v", e.pkg, e.err)
}
type RunError struct {
cmd, dir string
out []byte
err error
}
func (e *RunError) Error() string {
return fmt.Sprintf("%v\ncd %q && %q\n%s", e.err, e.dir, e.cmd, e.out)
}
func logf(format string, args ...interface{}) {
format = "%s: " + format
args = append([]interface{}{argv0}, args...)
fmt.Fprintf(os.Stderr, format, args...)
}
func printf(format string, args ...interface{}) {
if *verbose {
logf(format, args...)
}
}
func main() {
flag.Usage = usage
flag.Parse()
if runtime.GOROOT() == "" {
fmt.Fprintf(os.Stderr, "%s: no $GOROOT\n", argv0)
os.Exit(1)
}
readPackageList()
// special case - "unsafe" is already installed
visit["unsafe"] = done
args := flag.Args()
if *allpkg {
if len(args) != 0 {
usage() // -a and package list both provided
}
// install all packages that were ever installed
n := 0
for _, pkgs := range installedPkgs {
for pkg := range pkgs {
args = append(args, pkg)
n++
}
}
if n == 0 {
logf("no installed packages\n")
os.Exit(1)
}
}
if len(args) == 0 {
usage()
}
errs := false
for _, path := range args {
if err := install(path, ""); err != nil {
errs = true
fmt.Fprintln(os.Stderr, err)
}
}
if errs {
os.Exit(1)
}
}
// printDeps prints the dependency path that leads to pkg.
func printDeps(pkg string) {
if pkg == "" {
return
}
if visit[pkg] != done {
printDeps(parents[pkg])
}
fmt.Fprintf(os.Stderr, "\t%s ->\n", pkg)
}
// readPackageList reads the list of installed packages from the
// goinstall.log files in GOROOT and the GOPATHs and initializes
// the installedPkgs variable.
func readPackageList() {
for _, t := range build.Path {
installedPkgs[t.Path] = make(map[string]bool)
name := filepath.Join(t.Path, logfile)
pkglistdata, err := ioutil.ReadFile(name)
if err != nil {
printf("%s\n", err)
continue
}
pkglist := strings.Fields(string(pkglistdata))
for _, pkg := range pkglist {
installedPkgs[t.Path][pkg] = true
}
}
}
// logPackage logs the named package as installed in the goinstall.log file
// in the given tree if the package is not already in that file.
func logPackage(pkg string, tree *build.Tree) (logged bool) {
if installedPkgs[tree.Path][pkg] {
return false
}
name := filepath.Join(tree.Path, logfile)
fout, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
printf("package log: %s\n", err)
return false
}
fmt.Fprintf(fout, "%s\n", pkg)
fout.Close()
return true
}
// install installs the package named by path, which is needed by parent.
func install(pkg, parent string) error {
// Basic validation of import path string.
if s := schemeRe.FindString(pkg); s != "" {
return fmt.Errorf("%q used in import path, try %q\n", s, pkg[len(s):])
}
if strings.HasSuffix(pkg, "/") {
return fmt.Errorf("%q should not have trailing '/'\n", pkg)
}
// Make sure we're not already trying to install pkg.
switch visit[pkg] {
case done:
return nil
case visiting:
fmt.Fprintf(os.Stderr, "%s: package dependency cycle\n", argv0)
printDeps(parent)
fmt.Fprintf(os.Stderr, "\t%s\n", pkg)
os.Exit(2)
}
parents[pkg] = parent
visit[pkg] = visiting
defer func() {
visit[pkg] = done
}()
// Check whether package is local or remote.
// If remote, download or update it.
tree, pkg, err := build.FindTree(pkg)
// Don't build the standard library.
if err == nil && tree.Goroot && isStandardPath(pkg) {
if parent == "" {
return &PackageError{pkg, errors.New("cannot goinstall the standard library")}
}
return nil
}
// Download remote packages if not found or forced with -u flag.
remote, public := isRemote(pkg), false
if remote {
if err == build.ErrNotFound || (err == nil && *update) {
// Download remote package.
printf("%s: download\n", pkg)
public, err = download(pkg, tree.SrcDir())
if err != nil {
// only suggest -fix if the bad import was not on the command line
if e, ok := err.(*errOldGoogleRepo); ok && parent != "" {
err = fmt.Errorf("%v\nRun goinstall with -fix to gofix the code.", e)
}
return &DownloadError{pkg, tree.Goroot, err}
}
} else {
// Test if this is a public repository
// (for reporting to dashboard).
repo, e := findPublicRepo(pkg)
public = repo != nil
err = e
}
}
if err != nil {
return &PackageError{pkg, err}
}
// Install the package and its dependencies.
if err := installPackage(pkg, parent, tree, false); err != nil {
return err
}
if remote {
// mark package as installed in goinstall.log
logged := logPackage(pkg, tree)
// report installation to the dashboard if this is the first
// install from a public repository.
if logged && public {
maybeReportToDashboard(pkg)
}
}
return nil
}
// installPackage installs the specified package and its dependencies.
func installPackage(pkg, parent string, tree *build.Tree, retry bool) (installErr error) {
printf("%s: install\n", pkg)
// Read package information.
dir := filepath.Join(tree.SrcDir(), filepath.FromSlash(pkg))
dirInfo, err := build.ScanDir(dir)
if err != nil {
return &PackageError{pkg, err}
}
// We reserve package main to identify commands.
if parent != "" && dirInfo.Package == "main" {
return &PackageError{pkg, fmt.Errorf("found only package main in %s; cannot import", dir)}
}
// Run gofix if we fail to build and -fix is set.
defer func() {
if retry || installErr == nil || !*doGofix {
return
}
if e, ok := (installErr).(*DependencyError); ok {
// If this package failed to build due to a
// DependencyError, only attempt to gofix it if its
// dependency failed for some reason other than a
// DependencyError or BuildError.
// (If a dep or one of its deps doesn't build there's
// no way that gofixing this package can help.)
switch e.err.(type) {
case *DependencyError:
return
case *BuildError:
return
}
}
gofix(pkg, dir, dirInfo)
installErr = installPackage(pkg, parent, tree, true) // retry
}()
// Install prerequisites.
for _, p := range dirInfo.Imports {
if p == "C" {
continue
}
if err := install(p, pkg); err != nil {
return &DependencyError{pkg, err}
}
}
// Install this package.
err = domake(dir, pkg, tree, dirInfo.IsCommand())
if err != nil {
return &BuildError{pkg, err}
}
return nil
}
// gofix runs gofix against the GoFiles and CgoFiles of dirInfo in dir.
func gofix(pkg, dir string, dirInfo *build.DirInfo) {
printf("%s: gofix\n", pkg)
files := append([]string{}, dirInfo.GoFiles...)
files = append(files, dirInfo.CgoFiles...)
for i, file := range files {
files[i] = filepath.Join(dir, file)
}
cmd := exec.Command("gofix", files...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
logf("%s: gofix: %v", pkg, err)
}
}
// Is this a standard package path? strings container/list etc.
// Assume that if the first element has a dot, it's a domain name
// and is not the standard package path.
func isStandardPath(s string) bool {
dot := strings.Index(s, ".")
slash := strings.Index(s, "/")
return dot < 0 || 0 < slash && slash < dot
}
// run runs the command cmd in directory dir with standard input stdin.
// If verbose is set and the command fails it prints the output to stderr.
func run(dir string, stdin []byte, arg ...string) error {
cmd := exec.Command(arg[0], arg[1:]...)
cmd.Stdin = bytes.NewBuffer(stdin)
cmd.Dir = dir
printf("cd %s && %s %s\n", dir, cmd.Path, strings.Join(arg[1:], " "))
if out, err := cmd.CombinedOutput(); err != nil {
if *verbose {
fmt.Fprintf(os.Stderr, "%v\n%s\n", err, out)
}
return &RunError{strings.Join(arg, " "), dir, out, err}
}
return nil
}
// isRemote returns true if the first part of the package name looks like a
// hostname - i.e. contains at least one '.' and the last part is at least 2
// characters.
func isRemote(pkg string) bool {
parts := strings.SplitN(pkg, "/", 2)
if len(parts) != 2 {
return false
}
parts = strings.Split(parts[0], ".")
if len(parts) < 2 || len(parts[len(parts)-1]) < 2 {
return false
}
return true
}
// Copyright 2011 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.
// Run "make install" to build package.
package main
import (
"bytes"
"errors"
"go/build"
"path" // use for import paths
"strings"
"text/template"
)
// domake builds the package in dir.
// domake generates a standard Makefile and passes it
// to make on standard input.
func domake(dir, pkg string, tree *build.Tree, isCmd bool) (err error) {
makefile, err := makeMakefile(dir, pkg, tree, isCmd)
if err != nil {
return err
}
cmd := []string{"bash", "gomake", "-f-"}
if *nuke {
cmd = append(cmd, "nuke")
} else if *clean {
cmd = append(cmd, "clean")
}
if *doInstall {
cmd = append(cmd, "install")
}
if len(cmd) <= 3 { // nothing to do
return nil
}
return run(dir, makefile, cmd...)
}
// makeMakefile computes the standard Makefile for the directory dir
// installing as package pkg. It includes all *.go files in the directory
// except those in package main and those ending in _test.go.
func makeMakefile(dir, pkg string, tree *build.Tree, isCmd bool) ([]byte, error) {
if !safeName(pkg) {
return nil, errors.New("unsafe name: " + pkg)
}
targ := pkg
targDir := tree.PkgDir()
if isCmd {
// use the last part of the package name for targ
_, targ = path.Split(pkg)
targDir = tree.BinDir()
}
dirInfo, err := build.ScanDir(dir)
if err != nil {
return nil, err
}
cgoFiles := dirInfo.CgoFiles
isCgo := make(map[string]bool, len(cgoFiles))
for _, file := range cgoFiles {
if !safeName(file) {
return nil, errors.New("bad name: " + file)
}
isCgo[file] = true
}
goFiles := make([]string, 0, len(dirInfo.GoFiles))
for _, file := range dirInfo.GoFiles {
if !safeName(file) {
return nil, errors.New("unsafe name: " + file)
}
if !isCgo[file] {
goFiles = append(goFiles, file)
}
}
oFiles := make([]string, 0, len(dirInfo.CFiles)+len(dirInfo.SFiles))
cgoOFiles := make([]string, 0, len(dirInfo.CFiles))
for _, file := range dirInfo.CFiles {
if !safeName(file) {
return nil, errors.New("unsafe name: " + file)
}
// When cgo is in use, C files are compiled with gcc,
// otherwise they're compiled with gc.
if len(cgoFiles) > 0 {
cgoOFiles = append(cgoOFiles, file[:len(file)-2]+".o")
} else {
oFiles = append(oFiles, file[:len(file)-2]+".$O")
}
}
for _, file := range dirInfo.SFiles {
if !safeName(file) {
return nil, errors.New("unsafe name: " + file)
}
oFiles = append(oFiles, file[:len(file)-2]+".$O")
}
var imports []string
for _, t := range build.Path {
imports = append(imports, t.PkgDir())
}
var buf bytes.Buffer
md := makedata{targ, targDir, "pkg", goFiles, oFiles, cgoFiles, cgoOFiles, imports}
if isCmd {
md.Type = "cmd"
}
if err := makefileTemplate.Execute(&buf, &md); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
var safeBytes = []byte("+-~./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz")
func safeName(s string) bool {
if s == "" {
return false
}
if strings.Contains(s, "..") {
return false
}
if s[0] == '~' {
return false
}
for i := 0; i < len(s); i++ {
if c := s[i]; c < 0x80 && bytes.IndexByte(safeBytes, c) < 0 {
return false
}
}
return true
}
// makedata is the data type for the makefileTemplate.
type makedata struct {
Targ string // build target
TargDir string // build target directory
Type string // build type: "pkg" or "cmd"
GoFiles []string // list of non-cgo .go files
OFiles []string // list of .$O files
CgoFiles []string // list of cgo .go files
CgoOFiles []string // list of cgo .o files, without extension
Imports []string // gc/ld import paths
}
var makefileTemplate = template.Must(template.New("Makefile").Parse(`
include $(GOROOT)/src/Make.inc
TARG={{.Targ}}
TARGDIR={{.TargDir}}
{{with .GoFiles}}
GOFILES=\
{{range .}} {{.}}\
{{end}}
{{end}}
{{with .OFiles}}
OFILES=\
{{range .}} {{.}}\
{{end}}
{{end}}
{{with .CgoFiles}}
CGOFILES=\
{{range .}} {{.}}\
{{end}}
{{end}}
{{with .CgoOFiles}}
CGO_OFILES=\
{{range .}} {{.}}\
{{end}}
{{end}}
GCIMPORTS={{range .Imports}}-I "{{.}}" {{end}}
LDIMPORTS={{range .Imports}}-L "{{.}}" {{end}}
include $(GOROOT)/src/Make.{{.Type}}
`))
// Copyright 2011 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 "testing"
var selectTagTestTags = []string{
"go.r58",
"go.r58.1",
"go.r59",
"go.r59.1",
"go.r61",
"go.r61.1",
"go.weekly.2010-01-02",
"go.weekly.2011-10-12",
"go.weekly.2011-10-12.1",
"go.weekly.2011-10-14",
"go.weekly.2011-11-01",
// these should be ignored:
"release.r59",
"release.r59.1",
"release",
"weekly.2011-10-12",
"weekly.2011-10-12.1",
"weekly",
"foo",
"bar",
"go.f00",
"go!r60",
"go.1999-01-01",
}
var selectTagTests = []struct {
version string
selected string
}{
{"release.r57", ""},
{"release.r58.2", "go.r58.1"},
{"release.r59", "go.r59"},
{"release.r59.1", "go.r59.1"},
{"release.r60", "go.r59.1"},
{"release.r60.1", "go.r59.1"},
{"release.r61", "go.r61"},
{"release.r66", "go.r61.1"},
{"weekly.2010-01-01", ""},
{"weekly.2010-01-02", "go.weekly.2010-01-02"},
{"weekly.2010-01-02.1", "go.weekly.2010-01-02"},
{"weekly.2010-01-03", "go.weekly.2010-01-02"},
{"weekly.2011-10-12", "go.weekly.2011-10-12"},
{"weekly.2011-10-12.1", "go.weekly.2011-10-12.1"},
{"weekly.2011-10-13", "go.weekly.2011-10-12.1"},
{"weekly.2011-10-14", "go.weekly.2011-10-14"},
{"weekly.2011-10-14.1", "go.weekly.2011-10-14"},
{"weekly.2011-11-01", "go.weekly.2011-11-01"},
{"weekly.2014-01-01", "go.weekly.2011-11-01"},
{"weekly.3000-01-01", "go.weekly.2011-11-01"},
// faulty versions:
{"release.f00", ""},
{"weekly.1999-01-01", ""},
{"junk", ""},
{"", ""},
}
func TestSelectTag(t *testing.T) {
for _, c := range selectTagTests {
selected := selectTag(c.version, selectTagTestTags)
if selected != c.selected {
t.Errorf("selectTag(%q) = %q, want %q", c.version, selected, c.selected)
}
}
}
......@@ -158,7 +158,6 @@ DIRS=\
../cmd/godoc\
../cmd/fix\
../cmd/gofmt\
../cmd/goinstall\
../cmd/gotest\
../cmd/vet\
../cmd/yacc\
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment