summaryrefslogtreecommitdiff
path: root/src/cmd/goinstall
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/goinstall')
-rw-r--r--src/cmd/goinstall/Makefile2
-rw-r--r--src/cmd/goinstall/doc.go41
-rw-r--r--src/cmd/goinstall/download.go57
-rw-r--r--src/cmd/goinstall/main.go91
-rw-r--r--src/cmd/goinstall/make.go80
-rw-r--r--src/cmd/goinstall/parse.go77
6 files changed, 261 insertions, 87 deletions
diff --git a/src/cmd/goinstall/Makefile b/src/cmd/goinstall/Makefile
index cf4728401..6ddb32be7 100644
--- a/src/cmd/goinstall/Makefile
+++ b/src/cmd/goinstall/Makefile
@@ -2,7 +2,7 @@
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
-include ../../Make.$(GOARCH)
+include ../../Make.inc
TARG=goinstall
GOFILES=\
diff --git a/src/cmd/goinstall/doc.go b/src/cmd/goinstall/doc.go
index 80b30d5ac..17cc06969 100644
--- a/src/cmd/goinstall/doc.go
+++ b/src/cmd/goinstall/doc.go
@@ -10,14 +10,33 @@ It maintains a list of public Go packages at http://godashboard.appspot.com/pack
Usage:
goinstall [flags] importpath...
+ goinstall [flags] -a
Flags and default settings:
+ -a=false install all previously installed packages
-dashboard=true tally public packages on godashboard.appspot.com
- -update=false update already-downloaded packages
+ -log=true log installed packages to $GOROOT/goinstall.log for use by -a
+ -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.
+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/. If the import
@@ -31,8 +50,8 @@ if necessary. The recognized code hosting sites are:
GitHub (Git)
- import "github.com/user/project.git"
- import "github.com/user/project.git/sub/directory"
+ import "github.com/user/project"
+ import "github.com/user/project/sub/directory"
Google Code Project Hosting (Mercurial, Subversion)
@@ -44,17 +63,17 @@ if necessary. The recognized code hosting sites are:
Launchpad
- import "launchpad.net/project
- import "launchpad.net/project/series
- import "launchpad.net/project/series/sub/directory
+ 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
+ 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 -update flag changes this behavior,
+attempt to fetch updates. The -u flag changes this behavior,
causing goinstall to update all remote packages encountered during
the installation.
diff --git a/src/cmd/goinstall/download.go b/src/cmd/goinstall/download.go
index 3422e8186..889f9d857 100644
--- a/src/cmd/goinstall/download.go
+++ b/src/cmd/goinstall/download.go
@@ -38,16 +38,16 @@ var launchpad = regexp.MustCompile(`^(launchpad\.net/([a-z0-9A-Z_.\-]+(/[a-z0-9A
// download checks out or updates pkg from the remote server.
func download(pkg string) (string, os.Error) {
- if strings.Index(pkg, "..") >= 0 {
+ if strings.Contains(pkg, "..") {
return "", os.ErrorString("invalid path (contains ..)")
}
- if m := bitbucket.MatchStrings(pkg); m != nil {
+ if m := bitbucket.FindStringSubmatch(pkg); m != nil {
if err := vcsCheckout(&hg, root+m[1], "http://"+m[1], m[1]); err != nil {
return "", err
}
return root + pkg, nil
}
- if m := googlecode.MatchStrings(pkg); m != nil {
+ if m := googlecode.FindStringSubmatch(pkg); m != nil {
var v *vcs
switch m[2] {
case "hg":
@@ -58,12 +58,12 @@ func download(pkg string) (string, os.Error) {
// regexp only allows hg, svn to get through
panic("missing case in download: " + pkg)
}
- if err := vcsCheckout(v, root+m[1], "http://"+m[1], m[1]); err != nil {
+ if err := vcsCheckout(v, root+m[1], "https://"+m[1], m[1]); err != nil {
return "", err
}
return root + pkg, nil
}
- if m := github.MatchStrings(pkg); m != nil {
+ if m := github.FindStringSubmatch(pkg); m != nil {
if strings.HasSuffix(m[1], ".git") {
return "", os.ErrorString("repository " + pkg + " should not have .git suffix")
}
@@ -72,7 +72,7 @@ func download(pkg string) (string, os.Error) {
}
return root + pkg, nil
}
- if m := launchpad.MatchStrings(pkg); m != nil {
+ if m := launchpad.FindStringSubmatch(pkg); m != nil {
// Either lp.net/<project>[/<series>[/<path>]]
// or lp.net/~<user or team>/<project>/<branch>[/<path>]
if err := vcsCheckout(&bzr, root+m[1], "https://"+m[1], m[1]); err != nil {
@@ -88,6 +88,7 @@ func download(pkg string) (string, os.Error) {
type vcs struct {
cmd string
metadir string
+ checkout string
clone string
update string
updateReleaseFlag string
@@ -101,6 +102,7 @@ type vcs struct {
var hg = vcs{
cmd: "hg",
metadir: ".hg",
+ checkout: "checkout",
clone: "clone",
update: "update",
updateReleaseFlag: "release",
@@ -113,18 +115,20 @@ var hg = vcs{
var git = vcs{
cmd: "git",
metadir: ".git",
+ checkout: "checkout",
clone: "clone",
update: "pull",
updateReleaseFlag: "release",
pull: "fetch",
- log: "log",
- logLimitFlag: "-n1",
+ log: "show-ref",
+ logLimitFlag: "",
logReleaseFlag: "release",
}
var svn = vcs{
cmd: "svn",
metadir: ".svn",
+ checkout: "checkout",
clone: "checkout",
update: "update",
updateReleaseFlag: "release",
@@ -136,6 +140,7 @@ var svn = vcs{
var bzr = vcs{
cmd: "bzr",
metadir: ".bzr",
+ checkout: "update",
clone: "branch",
update: "update",
updateReleaseFlag: "-rrelease",
@@ -146,6 +151,22 @@ var bzr = vcs{
logReleaseFlag: "-rrelease",
}
+// Try to detect if a "release" tag exists. If it does, update
+// to the tagged version, otherwise just update the current branch.
+// NOTE(_nil): svn will always fail because it is trying to get
+// the revision history of a file named "release" instead of
+// looking for a commit with a release tag
+func (v *vcs) updateRepo(dst string) os.Error {
+ if err := quietRun(dst, nil, v.cmd, v.log, v.logLimitFlag, v.logReleaseFlag); err == nil {
+ if err := run(dst, nil, v.cmd, v.checkout, v.updateReleaseFlag); err != nil {
+ return err
+ }
+ } else if err := run(dst, nil, v.cmd, v.update); err != nil {
+ return err
+ }
+ return nil
+}
+
// vcsCheckout checks out repo into dst using vcs.
// It tries to check out (or update, if the dst already
// exists and -u was specified on the command line)
@@ -164,8 +185,9 @@ func vcsCheckout(vcs *vcs, dst, repo, dashpath string) os.Error {
if err := run("/", nil, vcs.cmd, vcs.clone, repo, dst); err != nil {
return err
}
- quietRun(dst, nil, vcs.cmd, vcs.update, vcs.updateReleaseFlag)
-
+ if err := vcs.updateRepo(dst); err != nil {
+ return err
+ }
// success on first installation - report
maybeReportToDashboard(dashpath)
} else if *update {
@@ -181,19 +203,8 @@ func vcsCheckout(vcs *vcs, dst, repo, dashpath string) os.Error {
}
}
- // Try to detect if a "release" tag exists. If it does, update
- // to the tagged version. If no tag is found, then update to the
- // tip afterwards.
- // NOTE(gustavo@niemeyer.net): What is the expected behavior with
- // svn here? "svn log -l1 release" doesn't make sense in this
- // context and will probably fail.
- if err := quietRun(dst, nil, vcs.cmd, vcs.log, vcs.logLimitFlag, vcs.logReleaseFlag); err == nil {
- if err := run(dst, nil, vcs.cmd, vcs.update, vcs.updateReleaseFlag); err != nil {
- // The VCS supports tagging, has the "release" tag, but
- // something else went wrong. Report.
- return err
- }
- } else if err := run(dst, nil, vcs.cmd, vcs.update); err != nil {
+ // Update to release or latest revision
+ if err := vcs.updateRepo(dst); err != nil {
return err
}
}
diff --git a/src/cmd/goinstall/main.go b/src/cmd/goinstall/main.go
index 60efdf082..b0f08efdf 100644
--- a/src/cmd/goinstall/main.go
+++ b/src/cmd/goinstall/main.go
@@ -11,28 +11,37 @@ import (
"exec"
"flag"
"fmt"
+ "go/token"
"io"
+ "io/ioutil"
"os"
"path"
+ "runtime"
"strings"
)
func usage() {
fmt.Fprint(os.Stderr, "usage: goinstall importpath...\n")
+ fmt.Fprintf(os.Stderr, "\tgoinstall -a\n")
flag.PrintDefaults()
os.Exit(2)
}
var (
- argv0 = os.Args[0]
- errors = false
- gobin = os.Getenv("GOBIN")
- parents = make(map[string]string)
- root = os.Getenv("GOROOT")
- visit = make(map[string]status)
+ fset = token.NewFileSet()
+ argv0 = os.Args[0]
+ errors = false
+ parents = make(map[string]string)
+ root = runtime.GOROOT()
+ visit = make(map[string]status)
+ logfile = path.Join(root, "goinstall.log")
+ installedPkgs = make(map[string]bool)
+ allpkg = flag.Bool("a", false, "install all previously installed packages")
reportToDashboard = flag.Bool("dashboard", true, "report public packages at "+dashboardURL)
+ logPkgs = flag.Bool("log", true, "log installed packages to $GOROOT/goinstall.log for use by -a")
update = flag.Bool("u", false, "update already-downloaded packages")
+ clean = flag.Bool("clean", false, "clean the package directory before installing")
verbose = flag.Bool("v", false, "verbose")
)
@@ -51,15 +60,30 @@ func main() {
os.Exit(1)
}
root += "/src/pkg/"
- if gobin == "" {
- gobin = os.Getenv("HOME") + "/bin"
- }
// special case - "unsafe" is already installed
visit["unsafe"] = done
- // install command line arguments
args := flag.Args()
+ if *allpkg || *logPkgs {
+ readPackageList()
+ }
+ if *allpkg {
+ if len(args) != 0 {
+ usage() // -a and package list both provided
+ }
+ // install all packages that were ever installed
+ if len(installedPkgs) == 0 {
+ fmt.Fprintf(os.Stderr, "%s: no installed packages\n", argv0)
+ os.Exit(1)
+ }
+ args = make([]string, len(installedPkgs), len(installedPkgs))
+ i := 0
+ for pkg := range installedPkgs {
+ args[i] = pkg
+ i++
+ }
+ }
if len(args) == 0 {
usage()
}
@@ -82,6 +106,29 @@ func printDeps(pkg string) {
fmt.Fprintf(os.Stderr, "\t%s ->\n", pkg)
}
+// readPackageList reads the list of installed packages from goinstall.log
+func readPackageList() {
+ pkglistdata, _ := ioutil.ReadFile(logfile)
+ pkglist := strings.Fields(string(pkglistdata))
+ for _, pkg := range pkglist {
+ installedPkgs[pkg] = true
+ }
+}
+
+// logPackage logs the named package as installed in goinstall.log, if the package is not found in there
+func logPackage(pkg string) {
+ if installedPkgs[pkg] {
+ return
+ }
+ fout, err := os.Open(logfile, os.O_WRONLY|os.O_APPEND|os.O_CREAT, 0644)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "%s: %s\n", argv0, err)
+ return
+ }
+ fmt.Fprintf(fout, "%s\n", pkg)
+ fout.Close()
+}
+
// install installs the package named by path, which is needed by parent.
func install(pkg, parent string) {
// Make sure we're not already trying to install pkg.
@@ -104,6 +151,11 @@ func install(pkg, parent string) {
// If remote, download or update it.
var dir string
local := false
+ if strings.HasPrefix(pkg, "http://") {
+ fmt.Fprintf(os.Stderr, "%s: %s: 'http://' used in remote path, try '%s'\n", argv0, pkg, pkg[7:])
+ errors = true
+ return
+ }
if isLocalPath(pkg) {
dir = pkg
local = true
@@ -122,23 +174,25 @@ func install(pkg, parent string) {
}
// Install prerequisites.
- files, m, pkgname, err := goFiles(dir, parent == "")
+ dirInfo, err := scanDir(dir, parent == "")
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %s: %s\n", argv0, pkg, err)
errors = true
visit[pkg] = done
return
}
- if len(files) == 0 {
+ if len(dirInfo.goFiles) == 0 {
fmt.Fprintf(os.Stderr, "%s: %s: package has no files\n", argv0, pkg)
errors = true
visit[pkg] = done
return
}
- for p := range m {
- install(p, pkg)
+ for _, p := range dirInfo.imports {
+ if p != "C" {
+ install(p, pkg)
+ }
}
- if pkgname == "main" {
+ if dirInfo.pkgName == "main" {
if !errors {
fmt.Fprintf(os.Stderr, "%s: %s's dependencies are installed.\n", argv0, pkg)
}
@@ -152,9 +206,11 @@ func install(pkg, parent string) {
if err := domake(dir, pkg, local); err != nil {
fmt.Fprintf(os.Stderr, "%s: installing %s: %s\n", argv0, pkg, err)
errors = true
+ } else if !local && *logPkgs {
+ // mark this package as installed in $GOROOT/goinstall.log
+ logPackage(pkg)
}
}
-
visit[pkg] = done
}
@@ -207,6 +263,9 @@ func genRun(dir string, stdin []byte, cmd []string, quiet bool) os.Error {
io.Copy(&buf, p.Stdout)
w, err := p.Wait(0)
p.Close()
+ if err != nil {
+ return err
+ }
if !w.Exited() || w.ExitStatus() != 0 {
if !quiet || *verbose {
if dir != "" {
diff --git a/src/cmd/goinstall/make.go b/src/cmd/goinstall/make.go
index c15709b31..93a648b2b 100644
--- a/src/cmd/goinstall/make.go
+++ b/src/cmd/goinstall/make.go
@@ -17,30 +17,65 @@ import (
// For non-local packages or packages without Makefiles,
// domake generates a standard Makefile and passes it
// to make on standard input.
-func domake(dir, pkg string, local bool) os.Error {
+func domake(dir, pkg string, local bool) (err os.Error) {
+ needMakefile := true
if local {
_, err := os.Stat(dir + "/Makefile")
if err == nil {
- return run(dir, nil, gobin+"/gomake", "install")
+ needMakefile = false
}
}
- makefile, err := makeMakefile(dir, pkg)
- if err != nil {
- return err
+ cmd := []string{"gomake"}
+ var makefile []byte
+ if needMakefile {
+ if makefile, err = makeMakefile(dir, pkg); err != nil {
+ return err
+ }
+ cmd = append(cmd, "-f-")
+ }
+ if *clean {
+ cmd = append(cmd, "clean")
}
- return run(dir, makefile, gobin+"/gomake", "-f-", "install")
+ cmd = append(cmd, "install")
+ 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) ([]byte, os.Error) {
- files, _, _, err := goFiles(dir, false)
+ dirInfo, err := scanDir(dir, false)
if err != nil {
return nil, err
}
+
+ if len(dirInfo.cgoFiles) == 0 && len(dirInfo.cFiles) > 0 {
+ // When using cgo, .c files are compiled with gcc. Without cgo,
+ // they may be intended for 6c. Just error out for now.
+ return nil, os.ErrorString("C files found in non-cgo package")
+ }
+
+ cgoFiles := dirInfo.cgoFiles
+ isCgo := make(map[string]bool, len(cgoFiles))
+ for _, file := range cgoFiles {
+ isCgo[file] = true
+ }
+
+ oFiles := make([]string, 0, len(dirInfo.cFiles))
+ for _, file := range dirInfo.cFiles {
+ oFiles = append(oFiles, file[:len(file)-2]+".o")
+ }
+
+ goFiles := make([]string, 0, len(dirInfo.goFiles))
+ for _, file := range dirInfo.goFiles {
+ if !isCgo[file] {
+ goFiles = append(goFiles, file)
+ }
+ }
+
var buf bytes.Buffer
- if err := makefileTemplate.Execute(&makedata{pkg, files}, &buf); err != nil {
+ md := makedata{pkg, goFiles, cgoFiles, oFiles}
+ if err := makefileTemplate.Execute(&md, &buf); err != nil {
return nil, err
}
return buf.Bytes(), nil
@@ -48,19 +83,38 @@ func makeMakefile(dir, pkg string) ([]byte, os.Error) {
// makedata is the data type for the makefileTemplate.
type makedata struct {
- pkg string // package import path
- files []string // list of .go files
+ Pkg string // package import path
+ GoFiles []string // list of non-cgo .go files
+ CgoFiles []string // list of cgo .go files
+ OFiles []string // list of ofiles for cgo
}
var makefileTemplate = template.MustParse(`
-include $(GOROOT)/src/Make.$(GOARCH)
+include $(GOROOT)/src/Make.inc
+
+TARG={Pkg}
-TARG={pkg}
+{.section GoFiles}
GOFILES=\
-{.repeated section files}
+{.repeated section GoFiles}
+ {@}\
+{.end}
+
+{.end}
+{.section CgoFiles}
+CGOFILES=\
+{.repeated section CgoFiles}
{@}\
{.end}
+{.end}
+{.section OFiles}
+CGO_OFILES=\
+{.repeated section OFiles}
+ {@}\
+{.end}
+
+{.end}
include $(GOROOT)/src/Make.pkg
`,
nil)
diff --git a/src/cmd/goinstall/parse.go b/src/cmd/goinstall/parse.go
index ae391ed9a..679edfabc 100644
--- a/src/cmd/goinstall/parse.go
+++ b/src/cmd/goinstall/parse.go
@@ -16,36 +16,60 @@ import (
"go/parser"
)
-// goFiles returns a list of the *.go source files in dir, excluding
-// those in package main (unless allowMain is true) or ending in
-// _test.go. It also returns a map giving the packages imported by
-// those files, and the package name.
-// The map keys are the imported paths. The key's value
-// is one file that imports that path.
-func goFiles(dir string, allowMain bool) (files []string, imports map[string]string, pkgName string, err os.Error) {
+
+type dirInfo struct {
+ goFiles []string // .go files within dir (including cgoFiles)
+ cgoFiles []string // .go files that import "C"
+ cFiles []string // .c files within dir
+ imports []string // All packages imported by goFiles
+ pkgName string // Name of package within dir
+}
+
+// scanDir returns a structure with details about the Go content found
+// in the given directory. The list of files will NOT contain the
+// following entries:
+//
+// - Files in package main (unless allowMain is true)
+// - Files ending in _test.go
+// - Files starting with _ (temporary)
+// - Files containing .cgo in their names
+//
+// The imports map keys are package paths imported by listed Go files,
+// and the values are the Go files importing the respective package paths.
+func scanDir(dir string, allowMain bool) (info *dirInfo, err os.Error) {
f, err := os.Open(dir, os.O_RDONLY, 0)
if err != nil {
- return nil, nil, "", err
+ return nil, err
}
dirs, err := f.Readdir(-1)
f.Close()
if err != nil {
- return nil, nil, "", err
+ return nil, err
}
- files = make([]string, 0, len(dirs))
- imports = make(map[string]string)
+ goFiles := make([]string, 0, len(dirs))
+ cgoFiles := make([]string, 0, len(dirs))
+ cFiles := make([]string, 0, len(dirs))
+ importsm := make(map[string]bool)
+ pkgName := ""
for i := range dirs {
d := &dirs[i]
+ if strings.HasPrefix(d.Name, "_") || strings.Index(d.Name, ".cgo") != -1 {
+ continue
+ }
+ if strings.HasSuffix(d.Name, ".c") {
+ cFiles = append(cFiles, d.Name)
+ continue
+ }
if !strings.HasSuffix(d.Name, ".go") || strings.HasSuffix(d.Name, "_test.go") {
continue
}
filename := path.Join(dir, d.Name)
- pf, err := parser.ParseFile(filename, nil, nil, parser.ImportsOnly)
+ pf, err := parser.ParseFile(fset, filename, nil, parser.ImportsOnly)
if err != nil {
- return nil, nil, "", err
+ return nil, err
}
- s := string(pf.Name.Name())
+ s := string(pf.Name.Name)
if s == "main" && !allowMain {
continue
}
@@ -56,24 +80,31 @@ func goFiles(dir string, allowMain bool) (files []string, imports map[string]str
// do we return pkgName=="main".
// A mix of main and another package reverts
// to the original (allowMain=false) behaviour.
- if allowMain && pkgName == "main" {
- return goFiles(dir, false)
+ if s == "main" || pkgName == "main" {
+ return scanDir(dir, false)
}
- return nil, nil, "", os.ErrorString("multiple package names in " + dir)
+ return nil, os.ErrorString("multiple package names in " + dir)
}
- n := len(files)
- files = files[0 : n+1]
- files[n] = filename
+ goFiles = append(goFiles, d.Name)
for _, decl := range pf.Decls {
for _, spec := range decl.(*ast.GenDecl).Specs {
quoted := string(spec.(*ast.ImportSpec).Path.Value)
unquoted, err := strconv.Unquote(quoted)
if err != nil {
- log.Crashf("%s: parser returned invalid quoted string: <%s>", filename, quoted)
+ log.Panicf("%s: parser returned invalid quoted string: <%s>", filename, quoted)
+ }
+ importsm[unquoted] = true
+ if unquoted == "C" {
+ cgoFiles = append(cgoFiles, d.Name)
}
- imports[unquoted] = filename
}
}
}
- return files, imports, pkgName, nil
+ imports := make([]string, len(importsm))
+ i := 0
+ for p := range importsm {
+ imports[i] = p
+ i++
+ }
+ return &dirInfo{goFiles, cgoFiles, cFiles, imports, pkgName}, nil
}