func (proj *Project) ResolvePath(
	basePath string, name string) (string, error) {
	repoName, subPath, err := newtutil.ParsePackageString(name)
	if err != nil {
		return "", util.FmtNewtError("invalid path: %s (%s)", name,
			err.Error())
	}
	if repoName == "" {
		return basePath + "/" + subPath, nil
	} else {
		repo := proj.repos[repoName]
		if repo == nil {
			return "", util.FmtNewtError("Unknown repository: %s", repoName)
		}
		return repo.Path() + "/" + subPath, nil
	}
}
Resolves a path with an optional repo prefix (e.g., "@apache-mynewt-core").
package project
import (
	"fmt"
	"os"
	"path"
	"path/filepath"
	"strings"
	log "github.com/sirupsen/logrus"
	"mynewt.apache.org/newt/newt/compat"
	"mynewt.apache.org/newt/newt/config"
	"mynewt.apache.org/newt/newt/deprepo"
	"mynewt.apache.org/newt/newt/downloader"
	"mynewt.apache.org/newt/newt/install"
	"mynewt.apache.org/newt/newt/interfaces"
	"mynewt.apache.org/newt/newt/newtutil"
	"mynewt.apache.org/newt/newt/pkg"
	"mynewt.apache.org/newt/newt/repo"
	"mynewt.apache.org/newt/newt/ycfg"
	"mynewt.apache.org/newt/util"
)
var globalProject *Project = nil
const PROJECT_FILE_NAME = "project.yml"
var ignoreSearchDirs []string = []string{
	"bin",
	"repos",
}
type Project struct {
	name string
	BasePath string
	packages interfaces.PackageList
	repos deprepo.RepoMap
	localRepo *repo.Repo
	rootRepoReqs deprepo.RequirementMap
	warnings []string
	unknownRepoVers map[string]struct{}
	yc ycfg.YCfg
}
func initProject(dir string, download bool) error {
	var err error
	globalProject, err = LoadProject(dir, download)
	if err != nil {
		return err
	}
	if err := globalProject.loadPackageList(); err != nil {
		return err
	}
	return nil
}
func initialize(download bool) error {
	if globalProject == nil {
		wd, err := os.Getwd()
		wd = filepath.ToSlash(wd)
		if err != nil {
			return util.NewNewtError(err.Error())
		}
		if err := initProject(wd, download); err != nil {
			return err
		}
	}
	return nil
}
func TryGetProject() (*Project, error) {
	if err := initialize(false); err != nil {
		return nil, err
	}
	return globalProject, nil
}
func TryGetOrDownloadProject() (*Project, error) {
	if err := initialize(true); err != nil {
		return nil, err
	}
	return globalProject, nil
}
func GetProject() *Project {
	if _, err := TryGetProject(); err != nil {
		panic(err.Error())
	}
	return globalProject
}
func ResetProject() {
	globalProject = nil
}
func ResetDeps(newList interfaces.PackageList) interfaces.PackageList {
	return nil
	if globalProject == nil {
		return nil
	}
	oldList := globalProject.packages
	globalProject.packages = newList
	if newList == nil {
		globalProject.loadPackageList()
	}
	return oldList
}
func NewProject(dir string, download bool) (*Project, error) {
	proj := &Project{}
	if err := proj.Init(dir, download); err != nil {
		return nil, err
	}
	return proj, nil
}
func (proj *Project) Path() string {
	return proj.BasePath
}
func (proj *Project) Name() string {
	return proj.name
}
func (proj *Project) Repos() map[string]*repo.Repo {
	return proj.repos
}
func (proj *Project) FindRepo(rname string) *repo.Repo {
	if rname == repo.REPO_NAME_LOCAL {
		return proj.LocalRepo()
	} else {
		r, _ := proj.repos[rname]
		return r
	}
}
func (proj *Project) FindRepoPath(rname string) string {
	r := proj.FindRepo(rname)
	if r == nil {
		return ""
	}
	return r.Path()
}
func (proj *Project) GetRepoVersion(
	rname string) (*newtutil.RepoVersion, error) {
	r := proj.repos[rname]
	if r == nil {
		return nil, nil
	}
	ver, err := r.InstalledVersion()
	if err != nil {
		return nil, err
	}
	if ver == nil {
		commit, err := r.CurrentHash()
		if err != nil {
			return nil, err
		}
		if proj.unknownRepoVers == nil {
			proj.unknownRepoVers = map[string]struct{}{}
		}
		if _, ok := proj.unknownRepoVers[rname]; !ok {
			proj.unknownRepoVers[rname] = struct{}{}
		}
		ver = &newtutil.RepoVersion{
			Commit: commit,
		}
	}
	return ver, nil
}
func (proj *Project) RepoIsInstalled(rname string) bool {
	ver, err := proj.GetRepoVersion(rname)
	return err == nil && ver != nil
}
func (proj *Project) RepoIsRoot(rname string) bool {
	_, ok := proj.rootRepoReqs[rname]
	return ok == true
}
func (proj *Project) LocalRepo() *repo.Repo {
	return proj.localRepo
}
func (proj *Project) Warnings() []string {
	return proj.warnings
}
func (proj *Project) SelectRepos(pred func(r *repo.Repo) bool) []*repo.Repo {
	all := proj.repos.Sorted()
	var filtered []*repo.Repo
	for _, r := range all {
		if pred(r) {
			filtered = append(filtered, r)
		}
	}
	return filtered
}
func (proj *Project) UpgradeIf(
	force bool, ask bool, predicate func(r *repo.Repo) bool) error {
	if err := proj.downloadRepositoryYmlFiles(); err != nil {
		return err
	}
	specifiedRepoList := proj.SelectRepos(predicate)
	inst, err := install.NewInstaller(proj.repos, proj.rootRepoReqs)
	if err != nil {
		return err
	}
	return inst.Upgrade(specifiedRepoList, force, ask)
}
func (proj *Project) InfoIf(predicate func(r *repo.Repo) bool,
	remote bool) error {
	if remote {
		if err := proj.downloadRepositoryYmlFiles(); err != nil {
			return err
		}
	}
	repoList := proj.SelectRepos(predicate)
	inst, _ := install.NewInstaller(proj.repos, proj.rootRepoReqs)
	if err := inst.Info(repoList, remote); err != nil {
		return err
	}
	return nil
}
func (proj *Project) loadRepo(name string, fields map[string]string) (
	*repo.Repo, error) {
	if fields["type"] == "" {
		return nil,
			util.FmtNewtError("Missing type for repository %s", name)
	}
	dl, err := downloader.LoadDownloader(name, fields)
	if err != nil {
		return nil, err
	}
	r, err := repo.NewRepo(name, dl)
	if err != nil {
		return nil, err
	}
	for _, ignDir := range ignoreSearchDirs {
		r.AddIgnoreDir(ignDir)
	}
	if err := r.Read(); err != nil {
		return r, err
	}
	log.Debugf("Loaded repository %s (type: %s, user: %s, repo: %s)", name,
		fields["type"], fields["user"], fields["repo"])
	return r, nil
}
func (proj *Project) checkNewtVer() error {
	compatSms, err := proj.yc.GetValStringMapString(
		"project.newt_compatibility", nil)
	util.OneTimeWarningError(err)
	if len(compatSms) == 0 {
		return nil
	}
	tbl, err := compat.ParseNcTable(compatSms)
	if err != nil {
		return util.FmtNewtError("Error reading project.yml: %s", err.Error())
	}
	code, msg := tbl.CheckNewtVer(newtutil.NewtVersion)
	msg = fmt.Sprintf("This version of newt (%s) is incompatible with "+
		"your project; %s", newtutil.NewtVersion.String(), msg)
	switch code {
	case compat.NEWT_COMPAT_GOOD:
		return nil
	case compat.NEWT_COMPAT_WARN:
		util.OneTimeWarning("%s", msg)
		return nil
	case compat.NEWT_COMPAT_ERROR:
		return util.NewNewtError(msg)
	default:
		return nil
	}
}
func (proj *Project) loadRepoDeps(download bool) error {
	seen := map[string]struct{}{}
	loadDeps := func(r *repo.Repo) ([]*repo.Repo, error) {
		var newRepos []*repo.Repo
		depMap := r.CommitDepMap()
		for _, depSlice := range depMap {
			for _, dep := range depSlice {
				if _, ok := seen[dep.Name]; !ok {
					seen[r.Name()] = struct{}{}
					depRepo := proj.repos[dep.Name]
					if depRepo == nil {
						var err error
						depRepo, err = proj.loadRepo(dep.Name, dep.Fields)
						if err != nil {
							if !util.IsNotExist(err) {
								return nil, err
							}
						}
						if err := proj.addRepo(depRepo, download); err != nil {
							return nil, err
						}
					}
					newRepos = append(newRepos, depRepo)
					if download {
						if _, err := depRepo.UpdateDesc(); err != nil {
							return nil, err
						}
					}
				}
			}
		}
		return newRepos, nil
	}
	curRepos := proj.repos.Sorted()
	for len(curRepos) > 0 {
		var nextRepos []*repo.Repo
		for _, r := range curRepos {
			depRepos, err := loadDeps(r)
			if err != nil {
				return err
			}
			nextRepos = append(nextRepos, depRepos...)
		}
		curRepos = nextRepos
	}
	return nil
}
func (proj *Project) downloadRepositoryYmlFiles() error {
	for _, r := range proj.repos.Sorted() {
		if !r.IsLocal() {
			if _, err := r.UpdateDesc(); err != nil {
				return err
			}
		}
	}
	if err := proj.loadRepoDeps(true); err != nil {
		return err
	}
	return nil
}
func (proj *Project) verifyNewtCompat() error {
	var errors []string
	for name, r := range proj.repos {
		if r.Downloader() == nil {
			continue
		}
		if !proj.RepoIsInstalled(name) {
			continue
		}
		ver, err := proj.GetRepoVersion(name)
		if err != nil {
			return err
		}
		if ver != nil {
			code, msg := r.CheckNewtCompatibility(*ver, newtutil.NewtVersion)
			switch code {
			case compat.NEWT_COMPAT_GOOD:
			case compat.NEWT_COMPAT_WARN:
				util.OneTimeWarning("%s", msg)
			case compat.NEWT_COMPAT_ERROR:
				errors = append(errors, msg)
			}
		}
	}
	if errors != nil {
		return util.NewNewtError(strings.Join(errors, "\n"))
	}
	return nil
}
func (proj *Project) addRepo(r *repo.Repo, download bool) error {
	if download {
		if err := r.EnsureExists(); err != nil {
			return err
		}
	} else {
		if !r.CheckExists() {
			return util.NewNewtError(
				fmt.Sprintf(
					"Repo \"%s\" is not installed, please run `newt upgrade`!",
					r.Name()))
		}
	}
	proj.repos[r.Name()] = r
	return nil
}
func (proj *Project) loadConfig(download bool) error {
	yc, err := config.ReadFile(proj.BasePath + "/" + PROJECT_FILE_NAME)
	if err != nil {
		return util.NewNewtError(err.Error())
	}
	proj.yc = yc
	proj.name, err = yc.GetValString("project.name", nil)
	util.OneTimeWarningError(err)
	r, err := repo.NewLocalRepo(proj.name)
	if err != nil {
		return err
	}
	proj.repos[proj.name] = r
	proj.localRepo = r
	for _, ignDir := range ignoreSearchDirs {
		r.AddIgnoreDir(ignDir)
	}
	for k, _ := range yc.AllSettings() {
		repoName := strings.TrimPrefix(k, "repository.")
		if repoName != k {
			fields, err := yc.GetValStringMapString(k, nil)
			util.OneTimeWarningError(err)
			r, err := proj.loadRepo(repoName, fields)
			if err != nil {
				if !util.IsNotExist(err) {
					return err
				}
			}
			verReq, err := newtutil.ParseRepoVersion(fields["vers"])
			if err != nil {
				return util.FmtNewtError(
					"Repo \"%s\" contains invalid version requirement: "+
						"%s (%s)",
					repoName, fields["vers"], err.Error())
			}
			if err := proj.addRepo(r, download); err != nil {
				return err
			}
			proj.rootRepoReqs[repoName] = verReq
		}
	}
	if err := proj.loadRepoDeps(download); err != nil {
		return err
	}
	if err := proj.verifyNewtCompat(); err != nil {
		return err
	}
	ignoreDirs, err := yc.GetValStringSlice("project.ignore_dirs", nil)
	util.OneTimeWarningError(err)
	for _, ignDir := range ignoreDirs {
		repoName, dirName, err := newtutil.ParsePackageString(ignDir)
		if err != nil {
			return err
		}
		if repoName == "" {
			r = proj.LocalRepo()
		} else {
			r = proj.FindRepo(repoName)
		}
		if r == nil {
			return util.NewNewtError(
				fmt.Sprintf("ignore_dirs: unknown repo %s", repoName))
		}
		r.AddIgnoreDir(dirName)
	}
	if err := proj.checkNewtVer(); err != nil {
		return err
	}
	return nil
}
func (proj *Project) Init(dir string, download bool) error {
	proj.BasePath = filepath.ToSlash(filepath.Clean(dir))
	interfaces.SetProject(proj)
	proj.repos = map[string]*repo.Repo{}
	proj.rootRepoReqs = map[string]newtutil.RepoVersion{}
	if err := proj.loadConfig(download); err != nil {
		return err
	}
	return nil
}
func (proj *Project) ResolveDependency(dep interfaces.DependencyInterface) interfaces.PackageInterface {
	type NamePath struct {
		name string
		path string
	}
	for _, pkgList := range proj.packages {
		for _, pkg := range *pkgList {
			if dep.SatisfiesDependency(pkg) {
				return pkg
			}
		}
	}
	return nil
}
func (proj *Project) ResolvePackage(
	dfltRepo interfaces.RepoInterface, name string) (*pkg.LocalPackage, error) {
	name = strings.TrimSuffix(name, "/")
	repoName, pkgName, err := newtutil.ParsePackageString(name)
	if err != nil {
		return nil, util.FmtNewtError("invalid package name: %s (%s)", name,
			err.Error())
	}
	var repo interfaces.RepoInterface
	if repoName == "" {
		repo = dfltRepo
	} else if proj.repos[repoName] != nil {
		repo = proj.repos[repoName]
	} else {
		return nil, util.FmtNewtError("invalid package name: %s (unkwn repo %s)",
			name, repoName)
	}
	dep, err := pkg.NewDependency(repo, pkgName)
	if err != nil {
		return nil, util.FmtNewtError("invalid package name: %s (%s)", name,
			err.Error())
	}
	if dep == nil {
		return nil, util.NewNewtError("invalid package name: " + name)
	}
	pack := proj.ResolveDependency(dep)
	if pack == nil {
		return nil, util.NewNewtError("unknown package: " + name)
	}
	return pack.(*pkg.LocalPackage), nil
}