blob: 6927b382a45289dfc432c1a5787422dcfaa9724a [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 pkg
import (
"bytes"
"crypto/sha1"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
log "github.com/Sirupsen/logrus"
"mynewt.apache.org/newt/newt/interfaces"
"mynewt.apache.org/newt/newt/newtutil"
"mynewt.apache.org/newt/newt/repo"
"mynewt.apache.org/newt/newt/ycfg"
"mynewt.apache.org/newt/util"
"mynewt.apache.org/newt/yaml"
)
var PackageHashIgnoreDirs = map[string]bool{
"obj": true,
"bin": true,
".": true,
}
var LocalPackageSpecialNames = map[string]bool{
"src": true,
"include": true,
"bin": true,
}
type LocalPackage struct {
repo *repo.Repo
name string
basePath string
packageType interfaces.PackageType
linkedName string
// General information about the package
desc *PackageDesc
// Package init function name and stage. These are used to generate the
// sysinit C file.
init map[string]int
// Extra package-specific settings that don't come from syscfg. For
// example, SELFTEST gets set when the newt test command is used.
injectedSettings map[string]string
// Settings read from pkg.yml.
PkgY ycfg.YCfg
// Settings read from syscfg.yml.
SyscfgY ycfg.YCfg
// Names of all source yml files; used to determine if rebuild required.
cfgFilenames []string
}
func NewLocalPackage(r *repo.Repo, pkgDir string) *LocalPackage {
pkg := &LocalPackage{
desc: &PackageDesc{},
PkgY: ycfg.YCfg{},
SyscfgY: ycfg.YCfg{},
repo: r,
basePath: filepath.ToSlash(filepath.Clean(pkgDir)),
init: map[string]int{},
injectedSettings: map[string]string{},
}
return pkg
}
func (pkg *LocalPackage) Name() string {
return pkg.name
}
func (pkg *LocalPackage) NameWithRepo() string {
r := pkg.Repo()
return newtutil.BuildPackageString(r.Name(), pkg.Name())
}
func (pkg *LocalPackage) FullName() string {
r := pkg.Repo()
if r.IsLocal() {
return pkg.Name()
} else {
return pkg.NameWithRepo()
}
}
func (pkg *LocalPackage) LinkedName() string {
return pkg.linkedName
}
func (pkg *LocalPackage) BasePath() string {
return pkg.basePath
}
func (pkg *LocalPackage) RelativePath() string {
proj := interfaces.GetProject()
return strings.TrimPrefix(pkg.BasePath(), proj.Path())
}
func (pkg *LocalPackage) Type() interfaces.PackageType {
return pkg.packageType
}
func (pkg *LocalPackage) Repo() interfaces.RepoInterface {
return pkg.repo
}
func (pkg *LocalPackage) Desc() *PackageDesc {
return pkg.desc
}
func (pkg *LocalPackage) SetName(name string) {
pkg.name = name
}
func (pkg *LocalPackage) SetBasePath(basePath string) {
pkg.basePath = filepath.ToSlash(filepath.Clean(basePath))
}
func (pkg *LocalPackage) SetType(packageType interfaces.PackageType) {
pkg.packageType = packageType
}
func (pkg *LocalPackage) SetDesc(desc *PackageDesc) {
pkg.desc = desc
}
func (pkg *LocalPackage) SetRepo(r *repo.Repo) {
pkg.repo = r
}
func (pkg *LocalPackage) Hash() (string, error) {
hash := sha1.New()
err := filepath.Walk(pkg.basePath,
func(path string, info os.FileInfo, err error) error {
name := info.Name()
if PackageHashIgnoreDirs[name] {
return filepath.SkipDir
}
if info.IsDir() {
// SHA the directory name into the hash
hash.Write([]byte(name))
} else {
// SHA the file name & contents into the hash
contents, err := ioutil.ReadFile(path)
if err != nil {
return err
}
hash.Write(contents)
}
return nil
})
if err != nil && err != filepath.SkipDir {
return "", util.NewNewtError(err.Error())
}
hashStr := fmt.Sprintf("%x", hash.Sum(nil))
return hashStr, nil
}
func (pkg *LocalPackage) CfgFilenames() []string {
return pkg.cfgFilenames
}
func (pkg *LocalPackage) AddCfgFilename(cfgFilename string) {
pkg.cfgFilenames = append(pkg.cfgFilenames, cfgFilename)
}
func (pkg *LocalPackage) readDesc(yc ycfg.YCfg) (*PackageDesc, error) {
pdesc := &PackageDesc{}
pdesc.Author = yc.GetValString("pkg.author", nil)
pdesc.Homepage = yc.GetValString("pkg.homepage", nil)
pdesc.Description = yc.GetValString("pkg.description", nil)
pdesc.Keywords = yc.GetValStringSlice("pkg.keywords", nil)
return pdesc, nil
}
func (pkg *LocalPackage) sequenceString(key string) string {
var buffer bytes.Buffer
if pkg.PkgY != nil {
for _, f := range pkg.PkgY.GetValStringSlice(key, nil) {
buffer.WriteString(" - " + yaml.EscapeString(f) + "\n")
}
}
if buffer.Len() == 0 {
return ""
} else {
return key + ":\n" + buffer.String()
}
}
func (lpkg *LocalPackage) SaveSyscfg() error {
dirpath := lpkg.BasePath()
if err := os.MkdirAll(dirpath, 0755); err != nil {
return util.NewNewtError(err.Error())
}
filepath := dirpath + "/" + SYSCFG_YAML_FILENAME
file, err := os.Create(filepath)
if err != nil {
return util.NewNewtError(err.Error())
}
defer file.Close()
s := newtutil.YCfgToYaml(lpkg.SyscfgY)
file.WriteString(s)
return nil
}
// Saves the package's pkg.yml file.
// NOTE: This does not save every field in the package. Only the fields
// necessary for creating a new target get saved.
func (pkg *LocalPackage) Save() error {
dirpath := pkg.BasePath()
if err := os.MkdirAll(dirpath, 0755); err != nil {
return util.NewNewtError(err.Error())
}
filepath := dirpath + "/" + PACKAGE_FILE_NAME
file, err := os.Create(filepath)
if err != nil {
return util.NewNewtError(err.Error())
}
defer file.Close()
// XXX: Just iterate ycfg object's settings rather than calling out
// cached settings individually.
file.WriteString("pkg.name: " + yaml.EscapeString(pkg.Name()) + "\n")
file.WriteString("pkg.type: " +
yaml.EscapeString(PackageTypeNames[pkg.Type()]) + "\n")
file.WriteString("pkg.description: " +
yaml.EscapeString(pkg.Desc().Description) + "\n")
file.WriteString("pkg.author: " +
yaml.EscapeString(pkg.Desc().Author) + "\n")
file.WriteString("pkg.homepage: " +
yaml.EscapeString(pkg.Desc().Homepage) + "\n")
file.WriteString("\n")
file.WriteString(pkg.sequenceString("pkg.aflags"))
file.WriteString(pkg.sequenceString("pkg.cflags"))
file.WriteString(pkg.sequenceString("pkg.cxxflags"))
file.WriteString(pkg.sequenceString("pkg.lflags"))
return nil
}
func matchNamePath(name, path string) bool {
// assure that name and path use the same path separator...
names := strings.Split(name, "/")
name = strings.Join(names, "/")
if strings.HasSuffix(path, name) {
return true
}
return false
}
// Load reads everything that isn't identity specific into the package
func (pkg *LocalPackage) Load() error {
// Load configuration
log.Debugf("Loading configuration for package %s", pkg.basePath)
var err error
pkg.PkgY, err = newtutil.ReadConfig(pkg.basePath,
strings.TrimSuffix(PACKAGE_FILE_NAME, ".yml"))
if err != nil {
return err
}
pkg.AddCfgFilename(pkg.basePath + "/" + PACKAGE_FILE_NAME)
// Set package name from the package
pkg.name = pkg.PkgY.GetValString("pkg.name", nil)
if pkg.name == "" {
return util.FmtNewtError(
"Package \"%s\" missing \"pkg.name\" field in its `pkg.yml` file",
pkg.basePath)
}
if !matchNamePath(pkg.name, pkg.basePath) {
return util.FmtNewtError(
"Package \"%s\" has incorrect \"pkg.name\" field in its "+
"`pkg.yml` file (pkg.name=%s)", pkg.basePath, pkg.name)
}
typeString := pkg.PkgY.GetValString("pkg.type", nil)
pkg.packageType = PACKAGE_TYPE_LIB
if len(typeString) > 0 {
found := false
for t, n := range PackageTypeNames {
if typeString == n {
pkg.packageType = t
found = true
break
}
}
if !found {
return util.FmtNewtError(
"Package \"%s\" has incorrect \"pkg.type\" field in its "+
"`pkg.yml` file (pkg.type=%s)", pkg.basePath, typeString)
}
}
if pkg.packageType == PACKAGE_TYPE_TRANSIENT {
n := pkg.PkgY.GetValString("pkg.link", nil)
if len(n) == 0 {
return util.FmtNewtError(
"Transient package \"%s\" does not specify target "+
"package in its `pkg.yml` file (pkg.name=%s)",
pkg.basePath, pkg.name)
}
pkg.linkedName = n
// We don't really want anything else for this package
return nil
}
init := pkg.PkgY.GetValStringMapString("pkg.init", nil)
for name, stageStr := range init {
stage, err := strconv.ParseInt(stageStr, 10, 64)
if err != nil {
return util.NewNewtError(fmt.Sprintf("Parsing pkg %s config: %s",
pkg.FullName(), err.Error()))
}
pkg.init[name] = int(stage)
}
initFnName := pkg.PkgY.GetValString("pkg.init_function", nil)
initStage := pkg.PkgY.GetValInt("pkg.init_stage", nil)
if initFnName != "" {
pkg.init[initFnName] = initStage
}
// Read the package description from the file
pkg.desc, err = pkg.readDesc(pkg.PkgY)
if err != nil {
return err
}
// Load syscfg settings.
if util.NodeExist(pkg.basePath + "/" + SYSCFG_YAML_FILENAME) {
pkg.SyscfgY, err = newtutil.ReadConfig(pkg.basePath,
strings.TrimSuffix(SYSCFG_YAML_FILENAME, ".yml"))
if err != nil {
return err
}
pkg.AddCfgFilename(pkg.basePath + "/" + SYSCFG_YAML_FILENAME)
}
return nil
}
func (pkg *LocalPackage) Init() map[string]int {
return pkg.init
}
func (pkg *LocalPackage) InjectedSettings() map[string]string {
return pkg.injectedSettings
}
func (pkg *LocalPackage) Clone(newRepo *repo.Repo,
newName string) *LocalPackage {
// XXX: Validate name.
// Copy the package.
newPkg := *pkg
newPkg.repo = newRepo
newPkg.name = newName
newPkg.basePath = newRepo.Path() + "/" + newPkg.name
// Insert the clone into the global package map.
proj := interfaces.GetProject()
pMap := proj.PackageList()
(*pMap[newRepo.Name()])[newPkg.name] = &newPkg
return &newPkg
}
func LoadLocalPackage(repo *repo.Repo, pkgDir string) (*LocalPackage, error) {
pkg := NewLocalPackage(repo, pkgDir)
err := pkg.Load()
if err != nil {
err = util.FmtNewtError("%s; ignoring package.", err.Error())
return nil, err
}
return pkg, err
}
func LocalPackageSpecialName(dirName string) bool {
_, ok := LocalPackageSpecialNames[dirName]
return ok
}
func ReadLocalPackageRecursive(repo *repo.Repo,
pkgList map[string]interfaces.PackageInterface, basePath string,
pkgName string, searchedMap map[string]struct{}) ([]string, error) {
var warnings []string
dirList, err := repo.FilteredSearchList(pkgName, searchedMap)
if err != nil {
return append(warnings, err.Error()), nil
}
for _, name := range dirList {
if LocalPackageSpecialName(name) || strings.HasPrefix(name, ".") {
continue
}
subWarnings, err := ReadLocalPackageRecursive(repo, pkgList,
basePath, filepath.Join(pkgName, name), searchedMap)
warnings = append(warnings, subWarnings...)
if err != nil {
return warnings, err
}
}
if util.NodeNotExist(filepath.Join(basePath, pkgName, PACKAGE_FILE_NAME)) {
return warnings, nil
}
pkg, err := LoadLocalPackage(repo, filepath.Join(basePath, pkgName))
if err != nil {
warnings = append(warnings, err.Error())
return warnings, nil
}
if oldPkg, ok := pkgList[pkg.Name()]; ok {
oldlPkg := oldPkg.(*LocalPackage)
warnings = append(warnings,
fmt.Sprintf("Multiple packages with same pkg.name=%s "+
"in repo %s; path1=%s path2=%s", oldlPkg.Name(), repo.Name(),
oldlPkg.BasePath(), pkg.BasePath()))
return warnings, nil
}
pkgList[pkg.Name()] = pkg
return warnings, nil
}
func ReadLocalPackages(repo *repo.Repo, basePath string) (
*map[string]interfaces.PackageInterface, []string, error) {
pkgMap := &map[string]interfaces.PackageInterface{}
// Keep track of which directories we have traversed. Prevent infinite
// loops caused by symlink cycles by not inspecting the same directory
// twice.
searchedMap := map[string]struct{}{}
warnings, err := ReadLocalPackageRecursive(repo, *pkgMap,
basePath, "", searchedMap)
return pkgMap, warnings, err
}