// Copyright 2013 gopm authors. // // Licensed under the Apache License, Version 2.0 (the "License"): you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package cmd import ( "errors" "fmt" "os" "path/filepath" "strings" "github.com/Unknwon/com" "github.com/codegangsta/cli" "github.com/gpmgo/gopm/doc" "github.com/gpmgo/gopm/log" ) var ( installRepoPath string downloadCache map[string]bool // Saves packages that have been downloaded. downloadCount int failConut int ) var CmdGet = cli.Command{ Name: "get", Usage: "fetch remote package(s) and dependencies to local repository", Description: `Command get fetches a package, and any pakcages that it depents on. If the package has a gopmfile, the fetch process will be driven by that. gopm get gopm get @[:] gopm get @[:] Can specify one or more: gopm get beego@tag:v0.9.0 github.com/beego/bee If no argument is supplied, then gopmfile must be present`, Action: runGet, Flags: []cli.Flag{ cli.BoolFlag{"force", "force to update pakcage(s) and dependencies"}, cli.BoolFlag{"example", "download dependencies for example(s)"}, }, } func init() { downloadCache = make(map[string]bool) } func runGet(ctx *cli.Context) { hd, err := com.HomeDir() if err != nil { log.Error("get", "Fail to get current user") log.Fatal("", err.Error()) } installRepoPath = strings.Replace(reposDir, "~", hd, -1) log.Log("Local repository path: %s", installRepoPath) // Check number of arguments. switch len(ctx.Args()) { case 0: getByGopmfile(ctx) default: getByPath(ctx) } } func getByGopmfile(ctx *cli.Context) { if !com.IsFile(".gopmfile") { log.Fatal("get", "No argument is supplied and no gopmfile exist") } gf := doc.NewGopmfile(".") absPath, err := filepath.Abs(".") if err != nil { log.Error("", "Fail to get absolute path of work directory") log.Fatal("", err.Error()) } log.Log("Work directory: %s", absPath) // Get dependencies. imports := doc.GetAllImports([]string{absPath}, gf.MustValue("target", "path"), ctx.Bool("example")) nodes := make([]*doc.Node, 0, len(imports)) for _, p := range imports { node := doc.NewNode(p, p, doc.BRANCH, "", true) // Check if user specified the version. if v, err := gf.GetValue("deps", p); err == nil { tp, ver, err := validPath(v) if err != nil { log.Error("", "Fail to parse version") log.Fatal("", err.Error()) } node.Type = tp node.Value = ver } nodes = append(nodes, node) } downloadPackages(ctx, nodes) log.Log("%d package(s) downloaded, %d failed", downloadCount, failConut) } func getByPath(ctx *cli.Context) { nodes := make([]*doc.Node, 0, len(ctx.Args())) for _, info := range ctx.Args() { pkg := info node := doc.NewNode(pkg, pkg, doc.BRANCH, "", true) if i := strings.Index(info, "@"); i > -1 { pkg = info[:i] tp, ver, err := validPath(info[i+1:]) if err != nil { log.Error("", "Fail to parse version") log.Fatal("", err.Error()) } node = doc.NewNode(pkg, pkg, tp, ver, true) } // Cheeck package name. if !strings.Contains(pkg, "/") { name, ok := doc.PackageNameList[pkg] if !ok { log.Error("", "Invalid package name: "+pkg) log.Fatal("", "No match in the package name list") } pkg = name } nodes = append(nodes, node) } downloadPackages(ctx, nodes) log.Log("%d package(s) downloaded, %d failed", downloadCount, failConut) } // downloadPackages downloads packages with certain commit, // if the commit is empty string, then it downloads all dependencies, // otherwise, it only downloada package with specific commit only. func downloadPackages(ctx *cli.Context, nodes []*doc.Node) { // Check all packages, they may be raw packages path. for _, n := range nodes { // Check if it is a valid remote path. if doc.IsValidRemotePath(n.ImportPath) { if !ctx.Bool("force") { // Check if package has been downloaded. installPath := installRepoPath + "/" + doc.GetProjectPath(n.ImportPath) if len(n.Value) > 0 { installPath += "." + n.Value } if com.IsExist(installPath) { log.Trace("Skipped installed package: %s@%s:%s", n.ImportPath, n.Type, doc.CheckNodeValue(n.Value)) continue } } if !downloadCache[n.ImportPath] { // Download package. nod, imports := downloadPackage(n) if len(imports) > 0 { // TODO: 检查是否有 gopmfile // Need to download dependencies. // Generate temporary nodes. nodes := make([]*doc.Node, len(imports)) for i := range nodes { nodes[i] = doc.NewNode(imports[i], imports[i], doc.BRANCH, "", true) } downloadPackages(ctx, nodes) } // Only save package information with specific commit. if nod != nil { // Save record in local nodes. log.Success("SUCC", "GET", fmt.Sprintf("%s@%s:%s", n.ImportPath, n.Type, doc.CheckNodeValue(n.Value))) downloadCount++ // TODO: 保存包信息 //saveNode(nod) } } else { log.Trace("Skipped downloaded package: %s@%s:%s", n.ImportPath, n.Type, doc.CheckNodeValue(n.Value)) } } else if n.ImportPath == "C" { continue } else { // Invalid import path. com.ColorLog("[WARN] Skipped invalid package path( %s => %s:%s )\n", n.ImportPath, n.Type, doc.CheckNodeValue(n.Value)) failConut++ } } } // downloadPackage downloads package either use version control tools or not. func downloadPackage(nod *doc.Node) (*doc.Node, []string) { log.Message("Downloading", fmt.Sprintf("package: %s@%s:%s", nod.ImportPath, nod.Type, doc.CheckNodeValue(nod.Value))) // Mark as donwloaded. downloadCache[nod.ImportPath] = true imports, err := doc.PureDownload(nod, installRepoPath, nil) //CmdGet.Flags) if err != nil { log.Error("Get", "Fail to download pakage: "+nod.ImportPath) log.Fatal("", err.Error()) failConut++ os.RemoveAll(installRepoPath + "/" + doc.GetProjectPath(nod.ImportPath) + "/") return nil, nil } return nod, imports } // validPath checks if the information of the package is valid. func validPath(info string) (string, string, error) { infos := strings.SplitN(info, ":", 2) l := len(infos) switch { case l == 1: return doc.BRANCH, "", nil case l == 2: switch infos[1] { case doc.TRUNK, doc.MASTER, doc.DEFAULT: infos[1] = "" } return infos[0], infos[1], nil default: return "", "", errors.New("Invalid version information") } }