diff --git a/bridge.go b/bridge.go index 1a90d8bb..e1af2431 100644 --- a/bridge.go +++ b/bridge.go @@ -25,10 +25,12 @@ type sourceBridge interface { deduceRemoteRepo(path string) (*remoteRepo, error) } -func newBridge(sm SourceManager, downgrade bool) sourceBridge { +func newBridge(name ProjectName, root string, sm SourceManager, downgrade bool) sourceBridge { return &bridge{ sm: sm, sortdown: downgrade, + name: name, + root: root, vlists: make(map[ProjectName][]Version), } } @@ -54,6 +56,14 @@ type bridge struct { // true for downgrades. sortdown bool + // The name of the root project we're operating on. Used to redirect some + // calls that would ordinarily go to the SourceManager to a root-specific + // logical path, instead. + name ProjectName + + // The path to the base directory of the root project. + root string + // Map of project root name to their available version list. This cache is // layered on top of the proper SourceManager's cache; the only difference // is that this keeps the versions sorted in the direction required by the @@ -358,6 +368,18 @@ func (b *bridge) computeRootReach(path string) ([]string, error) { return listExternalDeps(path, path, true) } +// listPackages lists all the packages contained within the given project at a +// particular version. +// +// Special handling is done for the root project. +func (b *bridge) listPackages(id ProjectIdentifier, v Version) (map[string]string, error) { + if id.LocalName != b.name { + return b.sm.ListPackages(b.key(id), v) + } + + return listPackages(b.root, string(b.name), true) +} + // verifyRoot ensures that the provided path to the project root is in good // working condition. This check is made only once, at the beginning of a solve // run. diff --git a/pkg_analysis.go b/pkg_analysis.go index 88196541..6c3664d6 100644 --- a/pkg_analysis.go +++ b/pkg_analysis.go @@ -258,6 +258,65 @@ func listExternalDeps(basedir, projname string, main bool) ([]string, error) { return ex, nil } +// listPackages lists all packages, optionally including main packages, +// contained at or below the provided path. +// +// Directories without any valid Go files are excluded. Directories with +// multiple packages are excluded. (TODO - maybe accommodate that?) +// +// A map of import path to package name is returned. +func listPackages(basedir, prefix string, main bool) (map[string]string, error) { + ctx := build.Default + ctx.UseAllFiles = true // optimistic, but we do it for the first try + exm := make(map[string]string) + + err := filepath.Walk(basedir, func(path string, fi os.FileInfo, err error) error { + if err != nil && err != filepath.SkipDir { + return err + } + if !fi.IsDir() { + return nil + } + + // Skip a few types of dirs + if !localSrcDir(fi) { + return filepath.SkipDir + } + + // Scan for dependencies, and anything that's not part of the local + // package gets added to the scan list. + p, err := ctx.ImportDir(path, 0) + var imps []string + if err != nil { + switch err.(type) { + case *build.NoGoError: + return nil + case *build.MultiplePackageError: + // Multiple package names declared in the dir, which causes + // ImportDir() to choke; use our custom iterative scanner. + imps, err = IterativeScan(path) + if err != nil { + return err + } + default: + return err + } + } else { + if prefix == "" { + exm[path] = path + } else { + exm[path] = prefix + os.PathSeparator + path + } + } + }) + + if err != nil { + return nil, err + } + + return exm, nil +} + func localSrcDir(fi os.FileInfo) bool { // Ignore _foo and .foo if strings.HasPrefix(fi.Name(), "_") || strings.HasPrefix(fi.Name(), ".") { diff --git a/project_manager.go b/project_manager.go index 7266682a..d1467c05 100644 --- a/project_manager.go +++ b/project_manager.go @@ -22,6 +22,7 @@ type ProjectManager interface { ExportVersionTo(Version, string) error ExternalReach(Version) (map[string][]string, error) ListExternal(Version) ([]string, error) + ListPackages(Version) (map[string]string, error) } type ProjectAnalyzer interface { @@ -207,6 +208,40 @@ func (pm *projectManager) ListExternal(v Version) ([]string, error) { return ex, err } +func (pm *projectManager) ListPackages(v Version) (map[string]string, error) { + var err error + if err = pm.ensureCacheExistence(); err != nil { + return nil, err + } + + pm.crepo.mut.Lock() + // Check out the desired version for analysis + if pv, ok := v.(PairedVersion); ok { + // Always prefer a rev, if it's available + err = pm.crepo.r.UpdateVersion(pv.Underlying().String()) + } else { + // If we don't have a rev, ensure the repo is up to date, otherwise we + // could have a desync issue + if !pm.crepo.synced { + err = pm.crepo.r.Update() + if err != nil { + return nil, fmt.Errorf("Could not fetch latest updates into repository") + } + pm.crepo.synced = true + } + err = pm.crepo.r.UpdateVersion(v.String()) + } + + // Nothing within the SourceManager is responsible for computing deps of a + // root package; it's assumed we're always operating on libraries. + // Consequently, we never want to include main packages, so we hardcode + // false for the third param. + ex, err := listPackages(filepath.Join(pm.ctx.GOPATH, "src", string(pm.n)), string(pm.n), true) + pm.crepo.mut.Unlock() + + return ex, err +} + func (pm *projectManager) ensureCacheExistence() error { // Technically, methods could could attempt to return straight from the // metadata cache even if the repo cache doesn't exist on disk. But that diff --git a/solver.go b/solver.go index ee56504d..53ab6795 100644 --- a/solver.go +++ b/solver.go @@ -165,7 +165,7 @@ func prepareSolver(opts SolveOpts, sm SourceManager) (*solver, error) { s := &solver{ o: opts, - b: newBridge(sm, opts.Downgrade), + b: newBridge(o.N, o.Root, sm, opts.Downgrade), tl: opts.TraceLogger, } diff --git a/source_manager.go b/source_manager.go index 46ad02f7..2b46a81e 100644 --- a/source_manager.go +++ b/source_manager.go @@ -17,6 +17,7 @@ type SourceManager interface { VendorCodeExists(ProjectName) (bool, error) ExternalReach(ProjectName, Version) (map[string][]string, error) ListExternal(ProjectName, Version) ([]string, error) + ListPackages(ProjectName, Version) (map[string]string, error) ExportProject(ProjectName, Version, string) error Release() // Flush() @@ -119,6 +120,15 @@ func (sm *sourceManager) ListExternal(n ProjectName, v Version) ([]string, error return pmc.pm.ListExternal(v) } +func (sm *sourceManager) ListPackages(n ProjectName, v Version) ([]string, error) { + pmc, err := sm.getProjectManager(n) + if err != nil { + return nil, err + } + + return pmc.pm.ListPackages(v) +} + func (sm *sourceManager) ListVersions(n ProjectName) ([]Version, error) { pmc, err := sm.getProjectManager(n) if err != nil {