diff options
Diffstat (limited to 'src/cmd/goinstall')
-rw-r--r-- | src/cmd/goinstall/Makefile | 2 | ||||
-rw-r--r-- | src/cmd/goinstall/doc.go | 41 | ||||
-rw-r--r-- | src/cmd/goinstall/download.go | 57 | ||||
-rw-r--r-- | src/cmd/goinstall/main.go | 91 | ||||
-rw-r--r-- | src/cmd/goinstall/make.go | 80 | ||||
-rw-r--r-- | src/cmd/goinstall/parse.go | 77 |
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 } |