summaryrefslogtreecommitdiff
path: root/src/cmd/goinstall
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/goinstall')
-rw-r--r--src/cmd/goinstall/Makefile17
-rw-r--r--src/cmd/goinstall/doc.go74
-rw-r--r--src/cmd/goinstall/download.go248
-rw-r--r--src/cmd/goinstall/main.go92
-rw-r--r--src/cmd/goinstall/make.go60
-rw-r--r--src/cmd/goinstall/parse.go172
-rw-r--r--src/cmd/goinstall/path.go149
-rw-r--r--src/cmd/goinstall/syslist_test.go61
8 files changed, 316 insertions, 557 deletions
diff --git a/src/cmd/goinstall/Makefile b/src/cmd/goinstall/Makefile
index 202797cd5..f61354f39 100644
--- a/src/cmd/goinstall/Makefile
+++ b/src/cmd/goinstall/Makefile
@@ -9,22 +9,5 @@ GOFILES=\
download.go\
main.go\
make.go\
- parse.go\
- path.go\
- syslist.go\
-
-CLEANFILES+=syslist.go
include ../../Make.cmd
-
-syslist.go:
- echo '// Generated automatically by make.' >$@
- echo 'package main' >>$@
- echo 'const goosList = "$(GOOS_LIST)"' >>$@
- echo 'const goarchList = "$(GOARCH_LIST)"' >>$@
-
-test:
- gotest
-
-testshort:
- gotest -test.short
diff --git a/src/cmd/goinstall/doc.go b/src/cmd/goinstall/doc.go
index 13c37d0a2..a5df7b3bd 100644
--- a/src/cmd/goinstall/doc.go
+++ b/src/cmd/goinstall/doc.go
@@ -5,7 +5,8 @@
/*
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.
+It maintains a list of public Go packages at
+http://godashboard.appspot.com/package.
Usage:
goinstall [flags] importpath...
@@ -15,7 +16,9 @@ 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
-log=true log installed packages to $GOROOT/goinstall.log for use by -a
+ -nuke=false remove the target object and clean before installing
-u=false update already-downloaded packages
-v=false verbose operation
@@ -39,9 +42,22 @@ Another common idiom is to use
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
-path refers to a code hosting site, goinstall will download the code
-if necessary. The recognized code hosting sites are:
+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 (Mercurial)
@@ -61,7 +77,7 @@ if necessary. The recognized code hosting sites are:
import "project.googlecode.com/svn/trunk"
import "project.googlecode.com/svn/trunk/sub/directory"
- Launchpad
+ Launchpad (Bazaar)
import "launchpad.net/project"
import "launchpad.net/project/series"
@@ -70,7 +86,6 @@ if necessary. The recognized code hosting sites are:
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,
@@ -82,23 +97,42 @@ named "release". If there is one, it 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 a publicly accessible
-remote package, 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.
+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.
-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.
+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.
-Goinstall does not attempt to be a replacement for make.
-Instead, it invokes "make install" after locating the package sources.
-For local packages without a Makefile and all remote packages,
-goinstall creates and uses a temporary Makefile constructed from
-the import path and the list of Go files in the package.
+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
diff --git a/src/cmd/goinstall/download.go b/src/cmd/goinstall/download.go
index 2edf85efd..da892a69d 100644
--- a/src/cmd/goinstall/download.go
+++ b/src/cmd/goinstall/download.go
@@ -7,6 +7,8 @@
package main
import (
+ "exec"
+ "fmt"
"http"
"os"
"path/filepath"
@@ -31,74 +33,10 @@ func maybeReportToDashboard(path string) {
}
}
-var vcsPatterns = map[string]*regexp.Regexp{
- "googlecode": regexp.MustCompile(`^([a-z0-9\-]+\.googlecode\.com/(svn|hg))(/[a-z0-9A-Z_.\-/]*)?$`),
- "github": regexp.MustCompile(`^(github\.com/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]*)?$`),
- "bitbucket": regexp.MustCompile(`^(bitbucket\.org/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]*)?$`),
- "launchpad": 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_.\-/]+)?$`),
-}
-
-// isRemote returns true if the provided package path
-// matches one of the supported remote repositories.
-func isRemote(pkg string) bool {
- for _, r := range vcsPatterns {
- if r.MatchString(pkg) {
- return true
- }
- }
- return false
-}
-
-// download checks out or updates pkg from the remote server.
-func download(pkg, srcDir string) os.Error {
- if strings.Contains(pkg, "..") {
- return os.ErrorString("invalid path (contains ..)")
- }
- if m := vcsPatterns["bitbucket"].FindStringSubmatch(pkg); m != nil {
- if err := vcsCheckout(&hg, srcDir, m[1], "http://"+m[1], m[1]); err != nil {
- return err
- }
- return nil
- }
- if m := vcsPatterns["googlecode"].FindStringSubmatch(pkg); m != nil {
- var v *vcs
- switch m[2] {
- case "hg":
- v = &hg
- case "svn":
- v = &svn
- default:
- // regexp only allows hg, svn to get through
- panic("missing case in download: " + pkg)
- }
- if err := vcsCheckout(v, srcDir, m[1], "https://"+m[1], m[1]); err != nil {
- return err
- }
- return nil
- }
- if m := vcsPatterns["github"].FindStringSubmatch(pkg); m != nil {
- if strings.HasSuffix(m[1], ".git") {
- return os.ErrorString("repository " + pkg + " should not have .git suffix")
- }
- if err := vcsCheckout(&git, srcDir, m[1], "http://"+m[1]+".git", m[1]); err != nil {
- return err
- }
- return nil
- }
- if m := vcsPatterns["launchpad"].FindStringSubmatch(pkg); m != nil {
- // Either lp.net/<project>[/<series>[/<path>]]
- // or lp.net/~<user or team>/<project>/<branch>[/<path>]
- if err := vcsCheckout(&bzr, srcDir, m[1], "https://"+m[1], m[1]); err != nil {
- return err
- }
- return nil
- }
- return os.ErrorString("unknown repository: " + pkg)
-}
-
// a vcs represents a version control system
// like Mercurial, Git, or Subversion.
type vcs struct {
+ name string
cmd string
metadir string
checkout string
@@ -110,9 +48,20 @@ type vcs struct {
log string
logLimitFlag string
logReleaseFlag string
+ check string
+ protocols []string
+ suffix string
+ defaultHosts []host
+}
+
+type host struct {
+ pattern *regexp.Regexp
+ protocol string
+ suffix string
}
var hg = vcs{
+ name: "Mercurial",
cmd: "hg",
metadir: ".hg",
checkout: "checkout",
@@ -123,9 +72,17 @@ var hg = vcs{
log: "log",
logLimitFlag: "-l1",
logReleaseFlag: "-rrelease",
+ check: "identify",
+ protocols: []string{"https", "http"},
+ suffix: ".hg",
+ defaultHosts: []host{
+ {regexp.MustCompile(`^([a-z0-9\-]+\.googlecode\.com/hg)(/[a-z0-9A-Z_.\-/]*)?$`), "https", ""},
+ {regexp.MustCompile(`^(bitbucket\.org/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]*)?$`), "http", ""},
+ },
}
var git = vcs{
+ name: "Git",
cmd: "git",
metadir: ".git",
checkout: "checkout",
@@ -136,9 +93,16 @@ var git = vcs{
log: "show-ref",
logLimitFlag: "",
logReleaseFlag: "release",
+ check: "ls-remote",
+ protocols: []string{"git", "https", "http"},
+ suffix: ".git",
+ defaultHosts: []host{
+ {regexp.MustCompile(`^(github\.com/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]*)?$`), "http", ".git"},
+ },
}
var svn = vcs{
+ name: "Subversion",
cmd: "svn",
metadir: ".svn",
checkout: "checkout",
@@ -148,9 +112,16 @@ var svn = vcs{
log: "log",
logLimitFlag: "-l1",
logReleaseFlag: "release",
+ check: "info",
+ protocols: []string{"https", "http", "svn"},
+ suffix: ".svn",
+ defaultHosts: []host{
+ {regexp.MustCompile(`^([a-z0-9\-]+\.googlecode\.com/svn)(/[a-z0-9A-Z_.\-/]*)?$`), "https", ""},
+ },
}
var bzr = vcs{
+ name: "Bazaar",
cmd: "bzr",
metadir: ".bzr",
checkout: "update",
@@ -162,6 +133,116 @@ var bzr = vcs{
log: "log",
logLimitFlag: "-l1",
logReleaseFlag: "-rrelease",
+ check: "info",
+ protocols: []string{"https", "http", "bzr"},
+ suffix: ".bzr",
+ defaultHosts: []host{
+ {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_.\-/]+)?$`), "https", ""},
+ },
+}
+
+var vcsList = []*vcs{&git, &hg, &bzr, &svn}
+
+type vcsMatch struct {
+ *vcs
+ prefix, repo string
+}
+
+// findHostedRepo checks whether pkg is located at one of
+// the supported code hosting sites and, if so, returns a match.
+func findHostedRepo(pkg string) (*vcsMatch, os.Error) {
+ for _, v := range vcsList {
+ for _, host := range v.defaultHosts {
+ if hm := host.pattern.FindStringSubmatch(pkg); hm != nil {
+ if host.suffix != "" && strings.HasSuffix(hm[1], host.suffix) {
+ return nil, os.NewError("repository " + pkg + " should not have " + v.suffix + " suffix")
+ }
+ repo := host.protocol + "://" + hm[1] + host.suffix
+ return &vcsMatch{v, hm[1], repo}, nil
+ }
+ }
+ }
+ return nil, nil
+}
+
+// findAnyRepo looks for a vcs suffix in pkg (.git, etc) and returns a match.
+func findAnyRepo(pkg string) (*vcsMatch, os.Error) {
+ for _, v := range vcsList {
+ i := strings.Index(pkg+"/", v.suffix+"/")
+ if i < 0 {
+ continue
+ }
+ if !strings.Contains(pkg[:i], "/") {
+ continue // don't match vcs suffix in the host name
+ }
+ if m := v.find(pkg[:i]); m != nil {
+ return m, nil
+ }
+ return nil, fmt.Errorf("couldn't find %s repository", v.name)
+ }
+ return nil, nil
+}
+
+func (v *vcs) find(pkg string) *vcsMatch {
+ for _, proto := range v.protocols {
+ for _, suffix := range []string{"", v.suffix} {
+ repo := proto + "://" + pkg + suffix
+ out, err := exec.Command(v.cmd, v.check, repo).CombinedOutput()
+ if err == nil {
+ printf("find %s: found %s\n", pkg, repo)
+ return &vcsMatch{v, pkg + v.suffix, repo}
+ }
+ printf("find %s: %s %s %s: %v\n%s\n", pkg, v.cmd, v.check, repo, err, out)
+ }
+ }
+ 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
+}
+
+// download checks out or updates pkg from the remote server.
+func download(pkg, srcDir string) (dashReport bool, err os.Error) {
+ if strings.Contains(pkg, "..") {
+ err = os.NewError("invalid path (contains ..)")
+ return
+ }
+ m, err := findHostedRepo(pkg)
+ if err != nil {
+ return
+ }
+ if m != nil {
+ dashReport = true // only report public code hosting sites
+ } else {
+ m, err = findAnyRepo(pkg)
+ if err != nil {
+ return
+ }
+ }
+ if m == nil {
+ err = os.NewError("cannot download: " + pkg)
+ return
+ }
+ installed, err := m.checkoutRepo(srcDir, m.prefix, m.repo)
+ if err != nil {
+ return
+ }
+ if !installed {
+ dashReport = false
+ }
+ return
}
// Try to detect if a "release" tag exists. If it does, update
@@ -180,47 +261,46 @@ func (v *vcs) updateRepo(dst string) os.Error {
return nil
}
-// vcsCheckout checks out repo into dst using vcs.
+// checkoutRepo 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)
// the repository at tag/branch "release". If there is no
// such tag or branch, it falls back to the repository tip.
-func vcsCheckout(vcs *vcs, srcDir, pkgprefix, repo, dashpath string) os.Error {
+func (vcs *vcs) checkoutRepo(srcDir, pkgprefix, repo string) (installed bool, err os.Error) {
dst := filepath.Join(srcDir, filepath.FromSlash(pkgprefix))
dir, err := os.Stat(filepath.Join(dst, vcs.metadir))
if err == nil && !dir.IsDirectory() {
- return os.ErrorString("not a directory: " + dst)
+ err = os.NewError("not a directory: " + dst)
+ return
}
if err != nil {
parent, _ := filepath.Split(dst)
- if err := os.MkdirAll(parent, 0777); err != nil {
- return err
+ if err = os.MkdirAll(parent, 0777); err != nil {
+ return
}
- if err := run(string(filepath.Separator), nil, vcs.cmd, vcs.clone, repo, dst); err != nil {
- return err
+ if err = run(string(filepath.Separator), nil, vcs.cmd, vcs.clone, repo, dst); err != nil {
+ return
}
- if err := vcs.updateRepo(dst); err != nil {
- return err
+ if err = vcs.updateRepo(dst); err != nil {
+ return
}
- // success on first installation - report
- maybeReportToDashboard(dashpath)
+ installed = true
} else if *update {
// 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(dst, nil, vcs.cmd, vcs.pull, vcs.pullForceFlag); err != nil {
- return err
+ if err = run(dst, nil, vcs.cmd, vcs.pull, vcs.pullForceFlag); err != nil {
+ return
}
- } else if err := run(dst, nil, vcs.cmd, vcs.pull); err != nil {
- return err
+ } else if err = run(dst, nil, vcs.cmd, vcs.pull); err != nil {
+ return
}
}
-
// Update to release or latest revision
- if err := vcs.updateRepo(dst); err != nil {
- return err
+ if err = vcs.updateRepo(dst); err != nil {
+ return
}
}
- return nil
+ return
}
diff --git a/src/cmd/goinstall/main.go b/src/cmd/goinstall/main.go
index 721e719d2..5cdf0f18e 100644
--- a/src/cmd/goinstall/main.go
+++ b/src/cmd/goinstall/main.go
@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// Experimental Go package installer; see doc.go.
-
package main
import (
@@ -11,6 +9,7 @@ import (
"exec"
"flag"
"fmt"
+ "go/build"
"go/token"
"io/ioutil"
"os"
@@ -39,7 +38,10 @@ var (
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")
+ 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")
verbose = flag.Bool("v", false, "verbose")
)
@@ -56,7 +58,7 @@ func logf(format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, format, args...)
}
-func vlogf(format string, args ...interface{}) {
+func printf(format string, args ...interface{}) {
if *verbose {
logf(format, args...)
}
@@ -160,66 +162,96 @@ func install(pkg, parent string) {
fmt.Fprintf(os.Stderr, "\t%s\n", pkg)
os.Exit(2)
}
- visit[pkg] = visiting
parents[pkg] = parent
-
- vlogf("%s: visit\n", pkg)
+ visit[pkg] = visiting
+ defer func() {
+ visit[pkg] = done
+ }()
// Check whether package is local or remote.
// If remote, download or update it.
- proot, pkg, err := findPackageRoot(pkg)
+ tree, pkg, err := build.FindTree(pkg)
// Don't build the standard library.
- if err == nil && proot.goroot && isStandardPath(pkg) {
+ if err == nil && tree.Goroot && isStandardPath(pkg) {
if parent == "" {
errorf("%s: can not goinstall the standard library\n", pkg)
} else {
- vlogf("%s: skipping standard library\n", pkg)
+ printf("%s: skipping standard library\n", pkg)
}
- visit[pkg] = done
return
}
// Download remote packages if not found or forced with -u flag.
remote := isRemote(pkg)
- if remote && (err == ErrPackageNotFound || (err == nil && *update)) {
- vlogf("%s: download\n", pkg)
- err = download(pkg, proot.srcDir())
+ dashReport := false
+ if remote && (err == build.ErrNotFound || (err == nil && *update)) {
+ printf("%s: download\n", pkg)
+ dashReport, err = download(pkg, tree.SrcDir())
}
if err != nil {
errorf("%s: %v\n", pkg, err)
- visit[pkg] = done
return
}
- dir := filepath.Join(proot.srcDir(), pkg)
+ dir := filepath.Join(tree.SrcDir(), pkg)
// Install prerequisites.
- dirInfo, err := scanDir(dir, parent == "")
+ dirInfo, err := build.ScanDir(dir, parent == "")
if err != nil {
errorf("%s: %v\n", pkg, err)
- visit[pkg] = done
return
}
- if len(dirInfo.goFiles) == 0 {
+ if len(dirInfo.GoFiles)+len(dirInfo.CgoFiles) == 0 {
errorf("%s: package has no files\n", pkg)
- visit[pkg] = done
return
}
- for _, p := range dirInfo.imports {
+ for _, p := range dirInfo.Imports {
if p != "C" {
install(p, pkg)
}
}
+ if errors {
+ return
+ }
// Install this package.
- if !errors {
- isCmd := dirInfo.pkgName == "main"
- if err := domake(dir, pkg, proot, isCmd); err != nil {
- errorf("installing: %v\n", err)
- } else if remote && *logPkgs {
- // mark package as installed in $GOROOT/goinstall.log
- logPackage(pkg)
+ if *useMake {
+ err := domake(dir, pkg, tree, dirInfo.IsCommand())
+ if err != nil {
+ errorf("%s: install: %v\n", pkg, err)
+ return
+ }
+ } else {
+ script, err := build.Build(tree, pkg, dirInfo)
+ if err != nil {
+ errorf("%s: install: %v\n", pkg, err)
+ return
+ }
+ if *nuke {
+ printf("%s: nuke\n", pkg)
+ script.Nuke()
+ } else if *clean {
+ printf("%s: clean\n", pkg)
+ script.Clean()
}
+ if *doInstall {
+ if script.Stale() {
+ printf("%s: install\n", pkg)
+ if err := script.Run(); err != nil {
+ errorf("%s: install: %v\n", pkg, err)
+ return
+ }
+ } else {
+ printf("%s: up-to-date\n", pkg)
+ }
+ }
+ }
+ if dashReport {
+ maybeReportToDashboard(pkg)
+ }
+ if remote {
+ // mark package as installed in $GOROOT/goinstall.log
+ logPackage(pkg)
}
- visit[pkg] = done
+ return
}
@@ -249,7 +281,7 @@ func genRun(dir string, stdin []byte, arg []string, quiet bool) os.Error {
cmd := exec.Command(arg[0], arg[1:]...)
cmd.Stdin = bytes.NewBuffer(stdin)
cmd.Dir = dir
- vlogf("%s: %s %s\n", dir, cmd.Path, strings.Join(arg[1:], " "))
+ printf("%s: %s %s\n", dir, cmd.Path, strings.Join(arg[1:], " "))
out, err := cmd.CombinedOutput()
if err != nil {
if !quiet || *verbose {
@@ -260,7 +292,7 @@ func genRun(dir string, stdin []byte, arg []string, quiet bool) os.Error {
os.Stderr.Write(out)
fmt.Fprintf(os.Stderr, "--- %s\n", err)
}
- return os.ErrorString("running " + arg[0] + ": " + err.String())
+ return os.NewError("running " + arg[0] + ": " + err.String())
}
return nil
}
diff --git a/src/cmd/goinstall/make.go b/src/cmd/goinstall/make.go
index 0c44481d7..0fd9b02a8 100644
--- a/src/cmd/goinstall/make.go
+++ b/src/cmd/goinstall/make.go
@@ -1,4 +1,4 @@
-// Copyright 2010 The Go Authors. All rights reserved.
+// 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.
@@ -8,21 +8,25 @@ package main
import (
"bytes"
+ "go/build"
"os"
"path/filepath"
+ "strings"
"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, root *pkgroot, isCmd bool) (err os.Error) {
- makefile, err := makeMakefile(dir, pkg, root, isCmd)
+func domake(dir, pkg string, tree *build.Tree, isCmd bool) (err os.Error) {
+ makefile, err := makeMakefile(dir, pkg, tree, isCmd)
if err != nil {
return err
}
cmd := []string{"bash", "gomake", "-f-"}
- if *clean {
+ if *nuke {
+ cmd = append(cmd, "nuke")
+ } else if *clean {
cmd = append(cmd, "clean")
}
cmd = append(cmd, "install")
@@ -32,46 +36,46 @@ func domake(dir, pkg string, root *pkgroot, isCmd bool) (err os.Error) {
// 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, root *pkgroot, isCmd bool) ([]byte, os.Error) {
+func makeMakefile(dir, pkg string, tree *build.Tree, isCmd bool) ([]byte, os.Error) {
if !safeName(pkg) {
- return nil, os.ErrorString("unsafe name: " + pkg)
+ return nil, os.NewError("unsafe name: " + pkg)
}
targ := pkg
- targDir := root.pkgDir()
+ targDir := tree.PkgDir()
if isCmd {
// use the last part of the package name for targ
_, targ = filepath.Split(pkg)
- targDir = root.binDir()
+ targDir = tree.BinDir()
}
- dirInfo, err := scanDir(dir, isCmd)
+ dirInfo, err := build.ScanDir(dir, isCmd)
if err != nil {
return nil, err
}
- cgoFiles := dirInfo.cgoFiles
+ cgoFiles := dirInfo.CgoFiles
isCgo := make(map[string]bool, len(cgoFiles))
for _, file := range cgoFiles {
if !safeName(file) {
- return nil, os.ErrorString("bad name: " + file)
+ return nil, os.NewError("bad name: " + file)
}
isCgo[file] = true
}
- goFiles := make([]string, 0, len(dirInfo.goFiles))
- for _, file := range dirInfo.goFiles {
+ goFiles := make([]string, 0, len(dirInfo.GoFiles))
+ for _, file := range dirInfo.GoFiles {
if !safeName(file) {
- return nil, os.ErrorString("unsafe name: " + file)
+ return nil, os.NewError("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 {
+ 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, os.ErrorString("unsafe name: " + file)
+ return nil, os.NewError("unsafe name: " + file)
}
// When cgo is in use, C files are compiled with gcc,
// otherwise they're compiled with gc.
@@ -82,13 +86,18 @@ func makeMakefile(dir, pkg string, root *pkgroot, isCmd bool) ([]byte, os.Error)
}
}
- for _, file := range dirInfo.sFiles {
+ for _, file := range dirInfo.SFiles {
if !safeName(file) {
- return nil, os.ErrorString("unsafe name: " + file)
+ return nil, os.NewError("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 {
@@ -106,6 +115,9 @@ func safeName(s string) bool {
if s == "" {
return false
}
+ if strings.Contains(s, "..") {
+ return false
+ }
for i := 0; i < len(s); i++ {
if c := s[i]; c < 0x80 && bytes.IndexByte(safeBytes, c) < 0 {
return false
@@ -134,28 +146,28 @@ TARGDIR={TargDir}
{.section GoFiles}
GOFILES=\
-{.repeated section GoFiles}
+{.repeated section @}
{@}\
{.end}
{.end}
{.section OFiles}
OFILES=\
-{.repeated section OFiles}
+{.repeated section @}
{@}\
{.end}
{.end}
{.section CgoFiles}
CGOFILES=\
-{.repeated section CgoFiles}
+{.repeated section @}
{@}\
{.end}
{.end}
{.section CgoOFiles}
CGO_OFILES=\
-{.repeated section CgoOFiles}
+{.repeated section @}
{@}\
{.end}
diff --git a/src/cmd/goinstall/parse.go b/src/cmd/goinstall/parse.go
deleted file mode 100644
index a4bb761f2..000000000
--- a/src/cmd/goinstall/parse.go
+++ /dev/null
@@ -1,172 +0,0 @@
-// 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.
-
-// Wrappers for Go parser.
-
-package main
-
-import (
- "go/ast"
- "go/parser"
- "log"
- "os"
- "path/filepath"
- "strconv"
- "strings"
- "runtime"
-)
-
-
-type dirInfo struct {
- goFiles []string // .go files within dir (including cgoFiles)
- cgoFiles []string // .go files that import "C"
- cFiles []string // .c files within dir
- sFiles []string // .s 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)
- if err != nil {
- return nil, err
- }
- dirs, err := f.Readdir(-1)
- f.Close()
- if err != nil {
- return nil, err
- }
-
- goFiles := make([]string, 0, len(dirs))
- cgoFiles := make([]string, 0, len(dirs))
- cFiles := make([]string, 0, len(dirs))
- sFiles := 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 !goodOSArch(d.Name) {
- continue
- }
-
- switch filepath.Ext(d.Name) {
- case ".go":
- if strings.HasSuffix(d.Name, "_test.go") {
- continue
- }
- case ".c":
- cFiles = append(cFiles, d.Name)
- continue
- case ".s":
- sFiles = append(sFiles, d.Name)
- continue
- default:
- continue
- }
-
- filename := filepath.Join(dir, d.Name)
- pf, err := parser.ParseFile(fset, filename, nil, parser.ImportsOnly)
- if err != nil {
- return nil, err
- }
- s := string(pf.Name.Name)
- if s == "main" && !allowMain {
- continue
- }
- if s == "documentation" {
- continue
- }
- if pkgName == "" {
- pkgName = s
- } else if pkgName != s {
- // Only if all files in the directory are in package main
- // do we return pkgName=="main".
- // A mix of main and another package reverts
- // to the original (allowMain=false) behaviour.
- if s == "main" || pkgName == "main" {
- return scanDir(dir, false)
- }
- return nil, os.ErrorString("multiple package names in " + dir)
- }
- 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.Panicf("%s: parser returned invalid quoted string: <%s>", filename, quoted)
- }
- importsm[unquoted] = true
- if unquoted == "C" {
- cgoFiles = append(cgoFiles, d.Name)
- }
- }
- }
- }
- imports := make([]string, len(importsm))
- i := 0
- for p := range importsm {
- imports[i] = p
- i++
- }
- return &dirInfo{goFiles, cgoFiles, cFiles, sFiles, imports, pkgName}, nil
-}
-
-// goodOSArch returns false if the filename contains a $GOOS or $GOARCH
-// suffix which does not match the current system.
-// The recognized filename formats are:
-//
-// name_$(GOOS).*
-// name_$(GOARCH).*
-// name_$(GOOS)_$(GOARCH).*
-//
-func goodOSArch(filename string) bool {
- if dot := strings.Index(filename, "."); dot != -1 {
- filename = filename[:dot]
- }
- l := strings.Split(filename, "_", -1)
- n := len(l)
- if n == 0 {
- return true
- }
- if good, known := goodOS[l[n-1]]; known {
- return good
- }
- if good, known := goodArch[l[n-1]]; known {
- if !good || n < 2 {
- return false
- }
- good, known = goodOS[l[n-2]]
- return good || !known
- }
- return true
-}
-
-var goodOS = make(map[string]bool)
-var goodArch = make(map[string]bool)
-
-func init() {
- goodOS = make(map[string]bool)
- goodArch = make(map[string]bool)
- for _, v := range strings.Fields(goosList) {
- goodOS[v] = v == runtime.GOOS
- }
- for _, v := range strings.Fields(goarchList) {
- goodArch[v] = v == runtime.GOARCH
- }
-}
diff --git a/src/cmd/goinstall/path.go b/src/cmd/goinstall/path.go
deleted file mode 100644
index b8c392931..000000000
--- a/src/cmd/goinstall/path.go
+++ /dev/null
@@ -1,149 +0,0 @@
-// 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 (
- "fmt"
- "log"
- "os"
- "path/filepath"
- "runtime"
- "strings"
-)
-
-var (
- gopath []*pkgroot
- imports []string
- defaultRoot *pkgroot // default root for remote packages
-)
-
-// set up gopath: parse and validate GOROOT and GOPATH variables
-func init() {
- root := runtime.GOROOT()
- p, err := newPkgroot(root)
- if err != nil {
- log.Fatalf("Invalid GOROOT %q: %v", root, err)
- }
- p.goroot = true
- gopath = []*pkgroot{p}
-
- for _, p := range filepath.SplitList(os.Getenv("GOPATH")) {
- if p == "" {
- continue
- }
- r, err := newPkgroot(p)
- if err != nil {
- log.Printf("Invalid GOPATH %q: %v", p, err)
- continue
- }
- gopath = append(gopath, r)
- imports = append(imports, r.pkgDir())
-
- // select first GOPATH entry as default
- if defaultRoot == nil {
- defaultRoot = r
- }
- }
-
- // use GOROOT if no valid GOPATH specified
- if defaultRoot == nil {
- defaultRoot = gopath[0]
- }
-}
-
-type pkgroot struct {
- path string
- goroot bool // TODO(adg): remove this once Go tree re-organized
-}
-
-func newPkgroot(p string) (*pkgroot, os.Error) {
- if !filepath.IsAbs(p) {
- return nil, os.NewError("must be absolute")
- }
- ep, err := filepath.EvalSymlinks(p)
- if err != nil {
- return nil, err
- }
- return &pkgroot{path: ep}, nil
-}
-
-func (r *pkgroot) srcDir() string {
- if r.goroot {
- return filepath.Join(r.path, "src", "pkg")
- }
- return filepath.Join(r.path, "src")
-}
-
-func (r *pkgroot) pkgDir() string {
- goos, goarch := runtime.GOOS, runtime.GOARCH
- if e := os.Getenv("GOOS"); e != "" {
- goos = e
- }
- if e := os.Getenv("GOARCH"); e != "" {
- goarch = e
- }
- return filepath.Join(r.path, "pkg", goos+"_"+goarch)
-}
-
-func (r *pkgroot) binDir() string {
- return filepath.Join(r.path, "bin")
-}
-
-func (r *pkgroot) hasSrcDir(name string) bool {
- fi, err := os.Stat(filepath.Join(r.srcDir(), name))
- if err != nil {
- return false
- }
- return fi.IsDirectory()
-}
-
-func (r *pkgroot) hasPkg(name string) bool {
- fi, err := os.Stat(filepath.Join(r.pkgDir(), name+".a"))
- if err != nil {
- return false
- }
- return fi.IsRegular()
- // TODO(adg): check object version is consistent
-}
-
-
-var ErrPackageNotFound = os.NewError("package could not be found locally")
-
-// findPackageRoot takes an import or filesystem path and returns the
-// root where the package source should be and the package import path.
-func findPackageRoot(path string) (root *pkgroot, pkg string, err os.Error) {
- if isLocalPath(path) {
- if path, err = filepath.Abs(path); err != nil {
- return
- }
- for _, r := range gopath {
- rpath := r.srcDir() + string(filepath.Separator)
- if !strings.HasPrefix(path, rpath) {
- continue
- }
- root = r
- pkg = path[len(rpath):]
- return
- }
- err = fmt.Errorf("path %q not inside a GOPATH", path)
- return
- }
- root = defaultRoot
- pkg = path
- for _, r := range gopath {
- if r.hasSrcDir(path) {
- root = r
- return
- }
- }
- err = ErrPackageNotFound
- return
-}
-
-// Is this a local path? /foo ./foo ../foo . ..
-func isLocalPath(s string) bool {
- const sep = string(filepath.Separator)
- return strings.HasPrefix(s, sep) || strings.HasPrefix(s, "."+sep) || strings.HasPrefix(s, ".."+sep) || s == "." || s == ".."
-}
diff --git a/src/cmd/goinstall/syslist_test.go b/src/cmd/goinstall/syslist_test.go
deleted file mode 100644
index 795cd293a..000000000
--- a/src/cmd/goinstall/syslist_test.go
+++ /dev/null
@@ -1,61 +0,0 @@
-// 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 (
- "runtime"
- "testing"
-)
-
-var (
- thisOS = runtime.GOOS
- thisArch = runtime.GOARCH
- otherOS = anotherOS()
- otherArch = anotherArch()
-)
-
-func anotherOS() string {
- if thisOS != "darwin" {
- return "darwin"
- }
- return "linux"
-}
-
-func anotherArch() string {
- if thisArch != "amd64" {
- return "amd64"
- }
- return "386"
-}
-
-type GoodFileTest struct {
- name string
- result bool
-}
-
-var tests = []GoodFileTest{
- {"file.go", true},
- {"file.c", true},
- {"file_foo.go", true},
- {"file_" + thisArch + ".go", true},
- {"file_" + otherArch + ".go", false},
- {"file_" + thisOS + ".go", true},
- {"file_" + otherOS + ".go", false},
- {"file_" + thisOS + "_" + thisArch + ".go", true},
- {"file_" + otherOS + "_" + thisArch + ".go", false},
- {"file_" + thisOS + "_" + otherArch + ".go", false},
- {"file_" + otherOS + "_" + otherArch + ".go", false},
- {"file_foo_" + thisArch + ".go", true},
- {"file_foo_" + otherArch + ".go", false},
- {"file_" + thisOS + ".c", true},
- {"file_" + otherOS + ".c", false},
-}
-
-func TestGoodOSArch(t *testing.T) {
- for _, test := range tests {
- if goodOSArch(test.name) != test.result {
- t.Fatalf("goodOSArch(%q) != %v", test.name, test.result)
- }
- }
-}