blob: e6e3f065bc1fc25494af4b017192607c22486507 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 project
import (
"bufio"
"fmt"
"log"
"os"
"path"
"strings"
"mynewt.apache.org/newt/newt/downloader"
"mynewt.apache.org/newt/newt/interfaces"
"mynewt.apache.org/newt/newt/pkg"
"mynewt.apache.org/newt/newt/repo"
"mynewt.apache.org/newt/util"
"mynewt.apache.org/newt/viper"
)
var globalProject *Project = nil
const PROJECT_FILE_NAME = "project.yml"
var PackageSearchDirs []string = []string{
"apps/",
"compiler/",
"fs/",
"libs/",
"net/",
"hw/bsp/",
"hw/mcu/",
"hw/mcu/stm",
"hw/drivers/",
"hw/",
"project/",
"targets/",
"sys/",
}
type Project struct {
// Name of this project
name string
// Base path of the project
BasePath string
packages interfaces.PackageList
projState *ProjectState
// Repositories configured on this project
repos map[string]*repo.Repo
localRepo *repo.Repo
// Package search directories for this project
packageSearchDirs []string
v *viper.Viper
}
func InitProject(dir string) error {
var err error
globalProject, err = LoadProject(dir)
if err != nil {
return err
}
globalProject.LoadPackageList()
return nil
}
func GetProject() *Project {
if globalProject == nil {
wd, err := os.Getwd()
if err != nil {
panic(err.Error())
}
err = InitProject(wd)
if err != nil {
panic(err.Error())
}
}
return globalProject
}
func NewProject(dir string) (*Project, error) {
proj := &Project{}
if err := proj.Init(dir); 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 {
r, _ := proj.repos[rname]
return r
}
func (proj *Project) LocalRepo() *repo.Repo {
return proj.localRepo
}
func (proj *Project) PackageSearchDirs() []string {
return proj.packageSearchDirs
}
func (proj *Project) upgradeCheck(r *repo.Repo, vers *repo.Version,
force bool) (bool, error) {
rdesc, err := r.GetRepoDesc()
if err != nil {
return false, err
}
branch, newVers, _ := rdesc.Match(r)
if newVers == nil {
util.StatusMessage(util.VERBOSITY_DEFAULT,
"No matching version to upgrade to "+
"found for %s. Please check your project requirements, and use the "+
"project show-repo command to see available repository versions.",
r.Name())
return false, util.NewNewtError(fmt.Sprintf("Cannot find a "+
"version of repository %s that matches project requirements.",
r.Name()))
}
// If the change between the old repository and the new repository would cause
// and upgrade. Then prompt for an upgrade response, unless the force option
// is present.
if vers.CompareVersions(newVers, vers) != 0 ||
vers.Stability() != newVers.Stability() {
if !force {
str := ""
if newVers.Stability() != repo.VERSION_STABILITY_NONE {
str += "(" + branch + ")"
}
fmt.Printf("Would you like to upgrade repository %s from %s to %s %s? [Yn] ",
r.Name(), vers.String(), newVers.String(), str)
line, more, err := bufio.NewReader(os.Stdin).ReadLine()
if more || err != nil {
return false, util.NewNewtError(fmt.Sprintf(
"Couldn't read upgrade response: %s\n", err.Error()))
}
// Anything but no means yes.
answer := strings.ToUpper(strings.Trim(string(line), " "))
if answer == "N" || answer == "NO" {
fmt.Printf("Users says don't upgrade, skipping upgrade of %s\n",
r.Name())
return true, nil
}
}
} else {
util.StatusMessage(util.VERBOSITY_DEFAULT,
"Repository %s doesn't need to be upgraded, latest "+
"version installed.\n", r.Name())
return true, nil
}
return false, nil
}
func (proj *Project) checkVersionRequirements(r *repo.Repo, upgrade bool, force bool) (bool, error) {
rdesc, err := r.GetRepoDesc()
if err != nil {
return false, err
}
rname := r.Name()
vers := proj.projState.GetInstalledVersion(rname)
if vers != nil {
_, newVers, ok := rdesc.Match(r)
if !ok {
// No matching version to install!
util.StatusMessage(util.VERBOSITY_DEFAULT,
"No matching install version found for repository %s\n", rname)
return false, util.NewNewtError("No matching install version found for " + rname)
}
if vers.CompareVersions(newVers, vers) != 0 ||
vers.Stability() != newVers.Stability() {
ok = false
}
//ok := rdesc.SatisfiesVersion(vers, r.VersionRequirements())
//fmt.Println(ok)
if !ok && !upgrade {
util.StatusMessage(util.VERBOSITY_DEFAULT, "WARNING: Installed "+
"version %s of repository %s does not match desired "+
"version %s in project file. You can fix this by either upgrading"+
" your repository, or modifying the project.yml file.\n",
vers, rname, r.VersionRequirementsString())
return true, err
} else {
if !upgrade {
util.StatusMessage(util.VERBOSITY_DEFAULT,
fmt.Sprintf("Installed repository %s has version %s, "+
"which meets project requirements. Moving on to next repository.\n",
r.Name(), vers))
return true, nil
} else {
skip, err := proj.upgradeCheck(r, vers, force)
return skip, err
}
}
} else {
// Fallthrough and perform the installation.
// Check to make sure that this repository contains a version
// that can satisfy.
_, _, ok := rdesc.Match(r)
if !ok {
fmt.Printf("WARNING: No matching repository version found for repository "+
"%s specified in project. To find available versions for a repository"+
" issue the project show-repo command.\n", r.Name())
return true, err
}
}
return false, nil
}
func (proj *Project) checkRepos(repos []*repo.Repo) (bool, error) {
return false, nil
}
func (proj *Project) Install(upgrade bool, force bool) error {
repoList := proj.Repos()
for rname, r := range repoList {
// Ignore the local repo on install
if rname == repo.REPO_NAME_LOCAL {
continue
}
// First thing we do is update repository description. This
// will get us available branches and versions in the repository.
repos, err := r.UpdateDesc()
if err != nil {
return err
}
// Check to see if these
change, err := proj.checkRepos(repos)
if err != nil {
return err
}
if change {
// Reprocess the list of repositories, with the new repolist
return proj.Install(upgrade, force)
}
// Check the version requirements on this repository, and see
// whether or not we need to install/upgrade it.
skip, err := proj.checkVersionRequirements(r, upgrade, force)
if err != nil {
return err
}
if skip {
continue
}
// Do the hard work of actually copying and installing the repository.
rvers, err := r.Install(upgrade || force)
if err != nil {
return err
}
// Update the project state with the new repository version information.
proj.projState.Replace(rname, rvers)
}
// Save the project state, including any updates or changes to the project
// information that either install or upgrade caused.
if err := proj.projState.Save(); err != nil {
return err
}
return nil
}
func (proj *Project) Upgrade(force bool) error {
return proj.Install(true, force)
}
func (proj *Project) loadRepo(rname string, v *viper.Viper) error {
varName := fmt.Sprintf("repository.%s", rname)
repoVars := v.GetStringMapString(varName)
if repoVars["type"] != "github" {
return util.NewNewtError("Only github repositories are currently supported.")
}
rversreq := repoVars["vers"]
dl := downloader.NewGithubDownloader()
dl.User = repoVars["user"]
dl.Repo = repoVars["repo"]
r, err := repo.NewRepo(rname, rversreq, dl)
if err != nil {
return err
}
log.Printf("[VERBOSE] Loaded repository %s (type: %s, user: %s, repo: %s)", rname,
repoVars["type"], repoVars["user"], repoVars["repo"])
proj.repos[r.Name()] = r
return nil
}
func (proj *Project) loadConfig() error {
v, err := util.ReadConfig(proj.BasePath,
strings.TrimSuffix(PROJECT_FILE_NAME, ".yml"))
if err != nil {
return util.NewNewtError(err.Error())
}
// Store configuration object for access to future values,
// this avoids keeping every string around as a project variable when
// we need to process it later.
proj.v = v
proj.projState, err = LoadProjectState()
if err != nil {
return err
}
proj.name = v.GetString("project.name")
// Local repository always included in initialization
r, err := repo.NewLocalRepo()
if err != nil {
return err
}
proj.repos[r.Name()] = r
proj.localRepo = r
rstrs := v.GetStringSlice("project.repositories")
for _, repoName := range rstrs {
if err := proj.loadRepo(repoName, v); err != nil {
return err
}
}
pkgDirs := v.GetStringSlice("project.pkg_dirs")
if len(pkgDirs) > 0 {
proj.packageSearchDirs = append(proj.packageSearchDirs, pkgDirs...)
}
return nil
}
func (proj *Project) Init(dir string) error {
proj.BasePath = dir
// Only one project per system, when created, set it as the global project
interfaces.SetProject(proj)
proj.repos = map[string]*repo.Repo{}
proj.packageSearchDirs = PackageSearchDirs
// Load Project configuration
if err := proj.loadConfig(); err != nil {
return err
}
return nil
}
func (proj *Project) ResolveDependency(dep interfaces.DependencyInterface) interfaces.PackageInterface {
for _, pkgList := range proj.packages {
for _, pkg := range *pkgList {
if dep.SatisfiesDependency(pkg) {
return pkg
}
}
}
return nil
}
func findProjectDir(dir string) (string, error) {
for {
projFile := path.Clean(dir) + "/" + PROJECT_FILE_NAME
log.Printf("[DEBUG] Searching for project file %s", projFile)
if util.NodeExist(projFile) {
log.Printf("[INFO] Project file found at %s", projFile)
break
}
// Move back one directory and continue searching
dir = path.Clean(dir + "../../")
if dir == "/" {
return "", util.NewNewtError("No project file found!")
}
}
return dir, nil
}
func (proj *Project) LoadPackageList() error {
proj.packages = interfaces.PackageList{}
// Go through a list of repositories, starting with local, and search for
// packages / store them in the project package list.
repos := proj.Repos()
for name, repo := range repos {
log.Printf("[VERBOSE] Loading packages in repository %s", repo.Path())
list, err := pkg.ReadLocalPackages(repo, repo.Path(), proj.PackageSearchDirs())
if err != nil {
return err
}
proj.packages[name] = list
}
return nil
}
func (proj *Project) PackageList() interfaces.PackageList {
return proj.packages
}
func (proj *Project) PackagesOfType(pkgType interfaces.PackageType) []interfaces.PackageInterface {
matches := []interfaces.PackageInterface{}
packs := proj.PackageList()
for _, packHash := range packs {
for _, pack := range *packHash {
if pack.Type() == pkgType {
matches = append(matches, pack)
}
}
}
return matches
}
func LoadProject(dir string) (*Project, error) {
projDir, err := findProjectDir(dir)
if err != nil {
return nil, err
}
proj, err := NewProject(projDir)
return proj, err
}