summaryrefslogtreecommitdiff
path: root/src/cmd/go/get.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/go/get.go')
-rw-r--r--src/cmd/go/get.go428
1 files changed, 428 insertions, 0 deletions
diff --git a/src/cmd/go/get.go b/src/cmd/go/get.go
new file mode 100644
index 000000000..f70b6761d
--- /dev/null
+++ b/src/cmd/go/get.go
@@ -0,0 +1,428 @@
+// 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.
+
+// TODO: Dashboard upload
+
+package main
+
+import (
+ "fmt"
+ "go/build"
+ "os"
+ "path/filepath"
+ "regexp"
+ "runtime"
+ "strconv"
+ "strings"
+)
+
+var cmdGet = &Command{
+ UsageLine: "get [-a] [-d] [-fix] [-n] [-p n] [-u] [-v] [-x] [packages]",
+ Short: "download and install packages and dependencies",
+ Long: `
+Get downloads and installs the packages named by the import paths,
+along with their dependencies.
+
+The -a, -n, -v, -x, and -p flags have the same meaning as in 'go build'
+and 'go install'. See 'go help build'.
+
+The -d flag instructs get to stop after downloading the packages; that is,
+it instructs get not to install the packages.
+
+The -fix flag instructs get to run the fix tool on the downloaded packages
+before resolving dependencies or building the code.
+
+The -u flag instructs get to use the network to update the named packages
+and their dependencies. By default, get uses the network to check out
+missing packages but does not use it to look for updates to existing packages.
+
+When checking out or updating a package, get looks for a branch or
+tag that matches the locally installed version of Go. If the local
+version "is release.rNN", it searches for "go.rNN". (For an
+installation using Go version "weekly.YYYY-MM-DD", it searches for a
+package version labeled "go.YYYY-MM-DD".) If the desired version
+cannot be found but others exist with labels in the correct format,
+get retrieves the most recent version before the desired label.
+Finally, if all else fails it retrieves the most recent version of
+the package.
+
+For more about specifying packages, see 'go help packages'.
+
+For more about how 'go get' finds source code to
+download, see 'go help remote'.
+
+See also: go build, go install, go clean.
+ `,
+}
+
+var getD = cmdGet.Flag.Bool("d", false, "")
+var getU = cmdGet.Flag.Bool("u", false, "")
+var getFix = cmdGet.Flag.Bool("fix", false, "")
+
+func init() {
+ addBuildFlags(cmdGet)
+ cmdGet.Run = runGet // break init loop
+}
+
+func runGet(cmd *Command, args []string) {
+ // Phase 1. Download/update.
+ var stk importStack
+ for _, arg := range downloadPaths(args) {
+ download(arg, &stk)
+ }
+ exitIfErrors()
+
+ // Phase 2. Rescan packages and reevaluate args list.
+
+ // Code we downloaded and all code that depends on it
+ // needs to be evicted from the package cache so that
+ // the information will be recomputed. Instead of keeping
+ // track of the reverse dependency information, evict
+ // everything.
+ for name := range packageCache {
+ delete(packageCache, name)
+ }
+
+ args = importPaths(args)
+
+ // Phase 3. Install.
+ if *getD {
+ // Download only.
+ // Check delayed until now so that importPaths
+ // has a chance to print errors.
+ return
+ }
+
+ runInstall(cmd, args)
+}
+
+// downloadPath prepares the list of paths to pass to download.
+// It expands ... patterns that can be expanded. If there is no match
+// for a particular pattern, downloadPaths leaves it in the result list,
+// in the hope that we can figure out the repository from the
+// initial ...-free prefix.
+func downloadPaths(args []string) []string {
+ args = importPathsNoDotExpansion(args)
+ var out []string
+ for _, a := range args {
+ if strings.Contains(a, "...") {
+ var expand []string
+ // Use matchPackagesInFS to avoid printing
+ // warnings. They will be printed by the
+ // eventual call to importPaths instead.
+ if build.IsLocalImport(a) {
+ expand = matchPackagesInFS(a)
+ } else {
+ expand = matchPackages(a)
+ }
+ if len(expand) > 0 {
+ out = append(out, expand...)
+ continue
+ }
+ }
+ out = append(out, a)
+ }
+ return out
+}
+
+// downloadCache records the import paths we have already
+// considered during the download, to avoid duplicate work when
+// there is more than one dependency sequence leading to
+// a particular package.
+var downloadCache = map[string]bool{}
+
+// downloadRootCache records the version control repository
+// root directories we have already considered during the download.
+// For example, all the packages in the code.google.com/p/codesearch repo
+// share the same root (the directory for that path), and we only need
+// to run the hg commands to consider each repository once.
+var downloadRootCache = map[string]bool{}
+
+// download runs the download half of the get command
+// for the package named by the argument.
+func download(arg string, stk *importStack) {
+ p := loadPackage(arg, stk)
+
+ // There's nothing to do if this is a package in the standard library.
+ if p.Standard {
+ return
+ }
+
+ // Only process each package once.
+ if downloadCache[arg] {
+ return
+ }
+ downloadCache[arg] = true
+
+ pkgs := []*Package{p}
+ wildcardOkay := len(*stk) == 0
+
+ // Download if the package is missing, or update if we're using -u.
+ if p.Dir == "" || *getU {
+ // The actual download.
+ stk.push(p.ImportPath)
+ err := downloadPackage(p)
+ if err != nil {
+ errorf("%s", &PackageError{ImportStack: stk.copy(), Err: err.Error()})
+ stk.pop()
+ return
+ }
+
+ args := []string{arg}
+ // If the argument has a wildcard in it, re-evaluate the wildcard.
+ // We delay this until after reloadPackage so that the old entry
+ // for p has been replaced in the package cache.
+ if wildcardOkay && strings.Contains(arg, "...") {
+ if build.IsLocalImport(arg) {
+ args = matchPackagesInFS(arg)
+ } else {
+ args = matchPackages(arg)
+ }
+ }
+
+ // Clear all relevant package cache entries before
+ // doing any new loads.
+ for _, arg := range args {
+ p := packageCache[arg]
+ if p != nil {
+ delete(packageCache, p.Dir)
+ delete(packageCache, p.ImportPath)
+ }
+ }
+
+ pkgs = pkgs[:0]
+ for _, arg := range args {
+ stk.push(arg)
+ p := loadPackage(arg, stk)
+ stk.pop()
+ if p.Error != nil {
+ errorf("%s", p.Error)
+ continue
+ }
+ pkgs = append(pkgs, p)
+ }
+ }
+
+ // Process package, which might now be multiple packages
+ // due to wildcard expansion.
+ for _, p := range pkgs {
+ if *getFix {
+ run(stringList(tool("fix"), relPaths(p.gofiles)))
+
+ // The imports might have changed, so reload again.
+ p = reloadPackage(arg, stk)
+ if p.Error != nil {
+ errorf("%s", p.Error)
+ return
+ }
+ }
+
+ // Process dependencies, now that we know what they are.
+ for _, dep := range p.deps {
+ download(dep.ImportPath, stk)
+ }
+ }
+}
+
+// downloadPackage runs the create or download command
+// to make the first copy of or update a copy of the given package.
+func downloadPackage(p *Package) error {
+ var (
+ vcs *vcsCmd
+ repo, rootPath string
+ err error
+ )
+ if p.build.SrcRoot != "" {
+ // Directory exists. Look for checkout along path to src.
+ vcs, rootPath, err = vcsForDir(p)
+ if err != nil {
+ return err
+ }
+ repo = "<local>" // should be unused; make distinctive
+ } else {
+ // Analyze the import path to determine the version control system,
+ // repository, and the import path for the root of the repository.
+ rr, err := repoRootForImportPath(p.ImportPath)
+ if err != nil {
+ return err
+ }
+ vcs, repo, rootPath = rr.vcs, rr.repo, rr.root
+ }
+
+ if p.build.SrcRoot == "" {
+ // Package not found. Put in first directory of $GOPATH or else $GOROOT.
+ // Guard against people setting GOPATH=$GOROOT. We have to use
+ // $GOROOT's directory hierarchy (src/pkg, not just src) in that case.
+ if list := filepath.SplitList(buildContext.GOPATH); len(list) > 0 && list[0] != goroot {
+ p.build.SrcRoot = filepath.Join(list[0], "src")
+ p.build.PkgRoot = filepath.Join(list[0], "pkg")
+ } else {
+ p.build.SrcRoot = filepath.Join(goroot, "src", "pkg")
+ p.build.PkgRoot = filepath.Join(goroot, "pkg")
+ }
+ }
+ root := filepath.Join(p.build.SrcRoot, rootPath)
+ // If we've considered this repository already, don't do it again.
+ if downloadRootCache[root] {
+ return nil
+ }
+ downloadRootCache[root] = true
+
+ if buildV {
+ fmt.Fprintf(os.Stderr, "%s (download)\n", rootPath)
+ }
+
+ // Check that this is an appropriate place for the repo to be checked out.
+ // The target directory must either not exist or have a repo checked out already.
+ meta := filepath.Join(root, "."+vcs.cmd)
+ st, err := os.Stat(meta)
+ if err == nil && !st.IsDir() {
+ return fmt.Errorf("%s exists but is not a directory", meta)
+ }
+ if err != nil {
+ // Metadata directory does not exist. Prepare to checkout new copy.
+ // Some version control tools require the target directory not to exist.
+ // We require that too, just to avoid stepping on existing work.
+ if _, err := os.Stat(root); err == nil {
+ return fmt.Errorf("%s exists but %s does not - stale checkout?", root, meta)
+ }
+ // Some version control tools require the parent of the target to exist.
+ parent, _ := filepath.Split(root)
+ if err := os.MkdirAll(parent, 0777); err != nil {
+ return err
+ }
+ if err = vcs.create(root, repo); err != nil {
+ return err
+ }
+ } else {
+ // Metadata directory does exist; download incremental updates.
+ if err = vcs.download(root); err != nil {
+ return err
+ }
+ }
+
+ if buildN {
+ // Do not show tag sync in -n; it's noise more than anything,
+ // and since we're not running commands, no tag will be found.
+ // But avoid printing nothing.
+ fmt.Fprintf(os.Stderr, "# cd %s; %s sync/update\n", root, vcs.cmd)
+ return nil
+ }
+
+ // Select and sync to appropriate version of the repository.
+ tags, err := vcs.tags(root)
+ if err != nil {
+ return err
+ }
+ vers := runtime.Version()
+ if i := strings.Index(vers, " "); i >= 0 {
+ vers = vers[:i]
+ }
+ if err := vcs.tagSync(root, selectTag(vers, tags)); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// goTag matches go release tags such as go1 and go1.2.3.
+// The numbers involved must be small (at most 4 digits),
+// have no unnecessary leading zeros, and the version cannot
+// end in .0 - it is go1, not go1.0 or go1.0.0.
+var goTag = regexp.MustCompile(
+ `^go((0|[1-9][0-9]{0,3})\.)*([1-9][0-9]{0,3})$`,
+)
+
+// selectTag returns the closest matching tag for a given version.
+// Closest means the latest one that is not after the current release.
+// Version "goX" (or "goX.Y" or "goX.Y.Z") matches tags of the same form.
+// Version "release.rN" matches tags of the form "go.rN" (N being a floating-point number).
+// 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
+ }
+ }
+ }
+
+ if goTag.MatchString(goVersion) {
+ v := goVersion
+ for _, t := range tags {
+ if !goTag.MatchString(t) {
+ continue
+ }
+ if cmpGoVersion(match, t) < 0 && cmpGoVersion(t, v) <= 0 {
+ match = t
+ }
+ }
+ }
+
+ return match
+}
+
+// cmpGoVersion returns -1, 0, +1 reporting whether
+// x < y, x == y, or x > y.
+func cmpGoVersion(x, y string) int {
+ // Malformed strings compare less than well-formed strings.
+ if !goTag.MatchString(x) {
+ return -1
+ }
+ if !goTag.MatchString(y) {
+ return +1
+ }
+
+ // Compare numbers in sequence.
+ xx := strings.Split(x[len("go"):], ".")
+ yy := strings.Split(y[len("go"):], ".")
+
+ for i := 0; i < len(xx) && i < len(yy); i++ {
+ // The Atoi are guaranteed to succeed
+ // because the versions match goTag.
+ xi, _ := strconv.Atoi(xx[i])
+ yi, _ := strconv.Atoi(yy[i])
+ if xi < yi {
+ return -1
+ } else if xi > yi {
+ return +1
+ }
+ }
+
+ if len(xx) < len(yy) {
+ return -1
+ }
+ if len(xx) > len(yy) {
+ return +1
+ }
+ return 0
+}