func (ctx *Context) setPackage(dir, canonical, local, gopath string, status Status) *Package { at := 0 vMiddle := "/" + pathos.SlashToImportPath(ctx.VendorDiscoverFolder) + "/" vStart := pathos.SlashToImportPath(ctx.VendorDiscoverFolder) + "/" switch { case strings.Contains(canonical, vMiddle): at = strings.LastIndex(canonical, vMiddle) + len(vMiddle) case strings.HasPrefix(canonical, vStart): at = strings.LastIndex(canonical, vStart) + len(vStart) } if at > 0 { canonical = canonical[at:] if status == StatusUnknown { status = StatusVendor } } if status == StatusUnknown { if vp := ctx.VendorFilePackageLocal(local); vp != nil { status = StatusVendor canonical = vp.Path } } if status == StatusUnknown && strings.HasPrefix(canonical, ctx.RootImportPath) { status = StatusLocal } pkg := &Package{ Dir: dir, Canonical: canonical, Local: local, Gopath: gopath, Status: status, } ctx.Package[local] = pkg return pkg }
func (ctx *Context) setPackage(dir, canonical, local, gopath string, status Status) *Package { at := 0 vMiddle := "/" + pathos.SlashToImportPath(ctx.VendorDiscoverFolder) + "/" vStart := pathos.SlashToImportPath(ctx.VendorDiscoverFolder) + "/" switch { case strings.Contains(canonical, vMiddle): at = strings.LastIndex(canonical, vMiddle) + len(vMiddle) case strings.HasPrefix(canonical, vStart): at = strings.LastIndex(canonical, vStart) + len(vStart) } originDir := dir inVendor := false if at > 0 { canonical = canonical[at:] inVendor = true if status == StatusUnknown { p := path.Join(ctx.RootImportPath, ctx.VendorDiscoverFolder) if strings.HasPrefix(local, p) { status = StatusVendor od, _, err := ctx.findImportDir("", canonical) if err == nil { originDir = od } } } } if status == StatusUnknown && inVendor == false { if vp := ctx.VendorFilePackageLocal(local); vp != nil { // This will only be hit if the imported package is in the vendor // file, present in GOPATH, but not in vendor folder. status = StatusExternal inVendor = true canonical = vp.Path origin := vp.Origin if len(origin) == 0 { origin = canonical } od, _, err := ctx.findImportDir("", origin) if err == nil { originDir = od } } } if status == StatusUnknown && strings.HasPrefix(canonical, ctx.RootImportPath) { status = StatusLocal } pkg := &Package{ OriginDir: originDir, Dir: dir, Canonical: canonical, Local: local, Gopath: gopath, Status: status, inVendor: inVendor, } ctx.Package[local] = pkg return pkg }
// findImportPath takes a absolute directory and returns the import path and go path. func (ctx *Context) findImportPath(dir string) (importPath, gopath string, err error) { for _, gopath := range ctx.GopathList { if pathos.FileHasPrefix(dir, gopath) { importPath = pathos.FileTrimPrefix(dir, gopath) importPath = pathos.SlashToImportPath(importPath) return importPath, gopath, nil } } return "", "", ErrNotInGOPATH{dir} }
func vendorFileFindLocal(vf *vendorfile.File, root, gopath, importPath string) *vendorfile.Package { local := pathos.SlashToImportPath(pathos.FileTrimPrefix(root, gopath)) // "/co1" = /file/src/co1, /file/src local = strings.TrimPrefix(strings.TrimPrefix(importPath, local), "/") for _, pkg := range vf.Package { if pkg.Remove { continue } if pkg.Local == local { return pkg } } return nil }
func readVendorFile(vendorFilePath string) (*vendorfile.File, error) { vf := &vendorfile.File{} f, err := os.Open(vendorFilePath) if err != nil { return nil, err } defer f.Close() err = vf.Unmarshal(f) if err != nil { return nil, err } // Determine if local field is relative to GOPATH or vendor file. // Change to relative to vendor file as needed. folder, _ := filepath.Split(vendorFilePath) relToFile := 0 relToGOPATH := 0 for _, pkg := range vf.Package { p := filepath.Join(folder, pathos.SlashToFilepath(pkg.Local)) _, err := os.Stat(p) if os.IsNotExist(err) { relToGOPATH++ continue } relToFile++ } if relToFile > relToGOPATH || len(vf.Package) == 0 { return vf, nil } gopathList := strings.Split(os.Getenv("GOPATH"), string(os.PathListSeparator)) gopath := "" for _, gp := range gopathList { if pathos.FileHasPrefix(folder, gp) { gopath = gp break } } if len(gopath) == 0 { return vf, nil } prefix := pathos.SlashToImportPath(pathos.FileTrimPrefix(folder, filepath.Join(gopath, "src"))) prefix = strings.TrimPrefix(prefix, "/") for _, pkg := range vf.Package { pkg.Local = strings.TrimPrefix(pkg.Local, prefix) } return vf, nil }
// addFileImports is called from loadPackage and resolveUnknown. func (ctx *Context) addFileImports(pathname, gopath string) error { dir, filenameExt := filepath.Split(pathname) importPath := pathos.FileTrimPrefix(dir, gopath) importPath = pathos.SlashToImportPath(importPath) importPath = strings.TrimPrefix(importPath, "/") importPath = strings.TrimSuffix(importPath, "/") if strings.HasSuffix(pathname, ".go") == false { return nil } f, err := parser.ParseFile(token.NewFileSet(), pathname, nil, parser.ImportsOnly|parser.ParseComments) if err != nil { return err } tags, err := ctx.getFileTags(pathname, f) if err != nil { return err } pkg, found := ctx.Package[importPath] if !found { status := StatusUnknown if f.Name.Name == "main" { status = StatusProgram } pkg = ctx.setPackage(dir, importPath, importPath, gopath, status) ctx.Package[importPath] = pkg } if pkg.Status != StatusLocal && pkg.Status != StatusProgram { for _, tag := range tags { for _, ignore := range ctx.ignoreTag { if tag == ignore { pkg.ignoreFile = append(pkg.ignoreFile, filenameExt) return nil } } } } pf := &File{ Package: pkg, Path: pathname, Imports: make([]string, len(f.Imports)), } pkg.Files = append(pkg.Files, pf) for i := range f.Imports { imp := f.Imports[i].Path.Value imp, err = strconv.Unquote(imp) if err != nil { return err } if strings.HasPrefix(imp, "./") { imp = path.Join(importPath, imp) } pf.Imports[i] = imp err = ctx.addSingleImport(pkg.Dir, imp) if err != nil { return err } } // Record any import comment for file. var ic *ast.Comment if f.Name != nil { pos := f.Name.Pos() big: // Find the next comment after the package name. for _, cblock := range f.Comments { for _, c := range cblock.List { if c.Pos() > pos { ic = c break big } } } } if ic != nil { // If it starts with the import text, assume it is the import comment and remove. if index := strings.Index(ic.Text, " import "); index > 0 && index < 5 { q := strings.TrimSpace(ic.Text[index+len(" import "):]) pf.ImportComment, err = strconv.Unquote(q) if err != nil { pf.ImportComment = q } } } return nil }
// addFileImports is called from loadPackage and resolveUnknown. func (ctx *Context) addFileImports(pathname, gopath string) error { dir, filenameExt := filepath.Split(pathname) importPath := pathos.FileTrimPrefix(dir, gopath) importPath = pathos.SlashToImportPath(importPath) importPath = strings.TrimPrefix(importPath, "/") importPath = strings.TrimSuffix(importPath, "/") if strings.HasSuffix(pathname, ".go") == false { return nil } f, err := parser.ParseFile(token.NewFileSet(), pathname, nil, parser.ImportsOnly|parser.ParseComments) if err != nil { return err } filename := filenameExt[:len(filenameExt)-3] filenameParts := strings.Split(filename, "_") tags := make([]string, 0) for i, part := range filenameParts { if i == 0 { continue } tags = append(tags, part) } const buildPrefix = "// +build " for _, cc := range f.Comments { for _, c := range cc.List { if strings.HasPrefix(c.Text, buildPrefix) { text := strings.TrimPrefix(c.Text, buildPrefix) ss := strings.Fields(text) for _, s := range ss { tags = append(tags, strings.Split(s, ",")...) } } } } pkg, found := ctx.Package[importPath] if !found { status := StatusUnknown if f.Name.Name == "main" { status = StatusProgram } pkg = ctx.setPackage(dir, importPath, importPath, gopath, status) ctx.Package[importPath] = pkg } for _, tag := range tags { for _, ignore := range ctx.ignoreTag { if tag == ignore { pkg.ignoreFile = append(pkg.ignoreFile, filenameExt) return nil } } } pf := &File{ Package: pkg, Path: pathname, Imports: make([]string, len(f.Imports)), } pkg.Files = append(pkg.Files, pf) for i := range f.Imports { imp := f.Imports[i].Path.Value imp, err = strconv.Unquote(imp) if err != nil { return err } if strings.HasPrefix(imp, "./") { imp = path.Join(importPath, imp) } pf.Imports[i] = imp err = ctx.addSingleImport(pkg.Dir, imp) if err != nil { return err } } return nil }
// AddImport adds the package to the context. The vendorFolder is where the // package should be added to relative to the project root. func (ctx *Context) ModifyImport(sourcePath string, mod Modify) error { var err error if !ctx.loaded || ctx.dirty { err = ctx.loadPackage() if err != nil { return err } } // Determine canonical and local import paths. sourcePath = pathos.SlashToImportPath(sourcePath) canonicalImportPath, err := ctx.findCanonicalPath(sourcePath) if err != nil { if mod != Remove { return err } if _, is := err.(ErrNotInGOPATH); !is { return err } } // If the import is already vendored, ensure we have the local path and not // the canonical path. localImportPath := sourcePath if vendPkg := ctx.VendorFilePackagePath(localImportPath); vendPkg != nil { localImportPath = path.Join(ctx.RootImportPath, ctx.RootToVendorFile, vendPkg.Path) } dprintf("AI: %s, L: %s, C: %s\n", sourcePath, localImportPath, canonicalImportPath) // Does the local import exist? // If so either update or just return. // If not find the disk path from the canonical path, copy locally and rewrite (if needed). pkg, foundPkg := ctx.Package[localImportPath] if !foundPkg { err = ctx.addSingleImport("", canonicalImportPath) if err != nil { return err } pkg, foundPkg = ctx.Package[canonicalImportPath] // Find by canonical path if stored by different local path. if !foundPkg { for _, p := range ctx.Package { if canonicalImportPath == p.Canonical { foundPkg = true pkg = p break } } } if !foundPkg { panic(fmt.Sprintf("Package %q should be listed internally but is not.", canonicalImportPath)) } } localExists, err := hasGoFileInFolder(filepath.Join(ctx.RootDir, ctx.VendorFolder, pathos.SlashToFilepath(canonicalImportPath))) if err != nil { return err } if mod == Add && localExists { return ErrPackageExists{path.Join(ctx.RootImportPath, ctx.VendorFolder, canonicalImportPath)} } switch mod { case Add: return ctx.modifyAdd(pkg) case AddUpdate: return ctx.modifyAdd(pkg) case Update: return ctx.modifyAdd(pkg) case Remove: return ctx.modifyRemove(pkg) default: panic("mod switch: case not handled") } }
// NewContext creates new context from a given root folder and vendor file path. // The vendorFolder is where vendor packages should be placed. func NewContext(root, vendorFilePathRel, vendorFolder string, rewriteImports bool) (*Context, error) { dprintf("CTX: %s\n", root) vendorFilePath := filepath.Join(root, vendorFilePathRel) vf, err := readVendorFile(vendorFilePath) if err != nil { if !os.IsNotExist(err) { return nil, err } vf = &vendorfile.File{} } // Get GOROOT. First check ENV, then run "go env" and find the GOROOT line. goroot := os.Getenv("GOROOT") if len(goroot) == 0 { // If GOROOT is not set, get from go cmd. cmd := exec.Command("go", "env") var goEnv []byte goEnv, err = cmd.CombinedOutput() if err != nil { return nil, err } const gorootLookFor = `GOROOT=` for _, line := range strings.Split(string(goEnv), "\n") { if strings.HasPrefix(line, gorootLookFor) == false { continue } goroot = strings.TrimPrefix(line, gorootLookFor) goroot, err = strconv.Unquote(goroot) if err != nil { return nil, err } break } } if goroot == "" { return nil, ErrMissingGOROOT } goroot = filepath.Join(goroot, "src") // Get the GOPATHs. Prepend the GOROOT to the list. all := os.Getenv("GOPATH") if len(all) == 0 { return nil, ErrMissingGOPATH } gopathList := filepath.SplitList(all) gopathGoroot := make([]string, 0, len(gopathList)+1) gopathGoroot = append(gopathGoroot, goroot) for _, gopath := range gopathList { gopathGoroot = append(gopathGoroot, filepath.Join(gopath, "src")+string(filepath.Separator)) } rootToVendorFile, _ := filepath.Split(vendorFilePathRel) vendorFileDir, _ := filepath.Split(vendorFilePath) vendorFolderRel, err := filepath.Rel(vendorFileDir, filepath.Join(root, vendorFolder)) if err != nil { return nil, err } vendorFileToFolder := pathos.SlashToImportPath(vendorFolderRel) ctx := &Context{ RootDir: root, GopathList: gopathGoroot, Goroot: goroot, VendorFile: vf, VendorFilePath: vendorFilePath, VendorFolder: vendorFolder, VendorFileToFolder: vendorFileToFolder, RootToVendorFile: pathos.SlashToImportPath(rootToVendorFile), VendorDiscoverFolder: "vendor", Package: make(map[string]*Package), RewriteRule: make(map[string]string, 3), rewriteImports: rewriteImports, } ctx.RootImportPath, ctx.RootGopath, err = ctx.findImportPath(root) if err != nil { return nil, err } ctx.IgnoreBuild(vf.Ignore) return ctx, nil }
// AddImport adds the package to the context. The vendorFolder is where the // package should be added to relative to the project root. func (ctx *Context) ModifyImport(sourcePath string, mod Modify) error { var err error if !ctx.loaded || ctx.dirty { err = ctx.loadPackage() if err != nil { return err } } tree := strings.HasSuffix(sourcePath, TreeSuffix) sourcePath = strings.TrimSuffix(sourcePath, TreeSuffix) // Determine canonical and local import paths. sourcePath = pathos.SlashToImportPath(sourcePath) canonicalImportPath, err := ctx.findCanonicalPath(sourcePath) if err != nil { if mod != Remove { return err } if _, is := err.(ErrNotInGOPATH); !is { return err } } // If the import is already vendored, ensure we have the local path and not // the canonical path. localImportPath := sourcePath if vendPkg := ctx.VendorFilePackagePath(localImportPath); vendPkg != nil { localImportPath = path.Join(ctx.RootImportPath, ctx.RootToVendorFile, vendPkg.Path) } dprintf("AI: %s, L: %s, C: %s\n", sourcePath, localImportPath, canonicalImportPath) // Does the local import exist? // If so either update or just return. // If not find the disk path from the canonical path, copy locally and rewrite (if needed). pkg, foundPkg := ctx.Package[localImportPath] if !foundPkg { err = ctx.addSingleImport("", canonicalImportPath) if err != nil { return err } pkg, foundPkg = ctx.Package[canonicalImportPath] // Find by canonical path if stored by different local path. if !foundPkg { for _, p := range ctx.Package { if canonicalImportPath == p.Canonical { foundPkg = true pkg = p break } } } if !foundPkg { panic(fmt.Sprintf("Package %q should be listed internally but is not.", canonicalImportPath)) } } // Do not support setting "tree" on Remove. if tree && mod != Remove { pkg.Tree = true } // A restriction where packages cannot live inside a tree package. if mod != Remove { if pkg.Tree { children := ctx.findPackageChild(pkg) if len(children) > 0 { return ErrTreeChildren{path: pkg.Canonical, children: children} } } treeParents := ctx.findPackageParentTree(pkg) if len(treeParents) > 0 { return ErrTreeParents{path: pkg.Canonical, parents: treeParents} } } // TODO (DT): figure out how to upgrade a non-tree package to a tree package with correct checks. localExists, err := hasGoFileInFolder(filepath.Join(ctx.RootDir, ctx.VendorFolder, pathos.SlashToFilepath(canonicalImportPath))) if err != nil { return err } if mod == Add && localExists { return ErrPackageExists{path.Join(ctx.RootImportPath, ctx.VendorFolder, canonicalImportPath)} } dprintf("stage 2: begin!\n") switch mod { case Add: return ctx.modifyAdd(pkg) case AddUpdate: return ctx.modifyAdd(pkg) case Update: return ctx.modifyAdd(pkg) case Remove: return ctx.modifyRemove(pkg) case Fetch: return ctx.modifyFetch(pkg) default: panic("mod switch: case not handled") } }
// addFileImports is called from loadPackage and resolveUnknown. func (ctx *Context) addFileImports(pathname, gopath string) error { dir, filenameExt := filepath.Split(pathname) importPath := pathos.FileTrimPrefix(dir, gopath) importPath = pathos.SlashToImportPath(importPath) importPath = strings.TrimPrefix(importPath, "/") importPath = strings.TrimSuffix(importPath, "/") if strings.HasSuffix(pathname, ".go") == false { return nil } f, err := parser.ParseFile(token.NewFileSet(), pathname, nil, parser.ImportsOnly|parser.ParseComments) if err != nil { return err } tags, err := ctx.getFileTags(pathname, f) if err != nil { return err } pkg, found := ctx.Package[importPath] if !found { status := StatusUnknown if f.Name.Name == "main" { status = StatusProgram } pkg = ctx.setPackage(dir, importPath, importPath, gopath, status) ctx.Package[importPath] = pkg } if pkg.Status != StatusLocal && pkg.Status != StatusProgram { for _, tag := range tags { for _, ignore := range ctx.ignoreTag { if tag == ignore { pkg.ignoreFile = append(pkg.ignoreFile, filenameExt) return nil } } } } pf := &File{ Package: pkg, Path: pathname, Imports: make([]string, len(f.Imports)), } pkg.Files = append(pkg.Files, pf) for i := range f.Imports { imp := f.Imports[i].Path.Value imp, err = strconv.Unquote(imp) if err != nil { return err } if strings.HasPrefix(imp, "./") { imp = path.Join(importPath, imp) } pf.Imports[i] = imp err = ctx.addSingleImport(pkg.Dir, imp) if err != nil { return err } } return nil }