blob: 6984ce6b5dbb0ca986e0ddab26aa24ad16758398 [file] [log] [blame]
/*
Copyright 2015 Runtime Inc.
Licensed 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 cli
import (
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
)
type EggMgr struct {
// Repository associated with the Eggs
Repo *Repo
Target *Target
// List of packages for Repo
Eggs map[string]*Egg
}
// Allocate a new package manager structure, and initialize it.
func NewEggMgr(r *Repo, t *Target) (*EggMgr, error) {
em := &EggMgr{
Repo: r,
Target: t,
}
err := em.Init()
return em, err
}
func (em *EggMgr) CheckEggDeps(egg *Egg,
deps map[string]*DependencyRequirement, reqcap map[string]*DependencyRequirement,
caps map[string]*DependencyRequirement) error {
// if no dependencies, then everything is ok!
if egg.Deps == nil || len(egg.Deps) == 0 {
return nil
}
for _, depReq := range egg.Deps {
// don't process this package if we've already processed it
if _, ok := deps[depReq.String()]; ok {
continue
}
log.Printf("[DEBUG] Checking dependency %s for package %s", depReq, egg.Name)
egg, ok := em.Eggs[depReq.Name]
if !ok {
return NewNewtError(fmt.Sprintf("No package dependency %s found for %s",
depReq.Name, egg.Name))
}
if ok := depReq.SatisfiesDependency(egg); !ok {
return NewNewtError(fmt.Sprintf("Egg %s doesn't satisfy dependency %s",
egg.Name, depReq))
}
// We've checked this dependency requirement, all is gute!
deps[depReq.String()] = depReq
}
// Now go through and recurse through the sub-package dependencies
for _, depReq := range egg.Deps {
if _, ok := deps[depReq.String()]; ok {
continue
}
if err := em.CheckEggDeps(em.Eggs[depReq.Name], deps, reqcap, caps); err != nil {
return err
}
}
return nil
}
func (em *EggMgr) VerifyCaps(reqcaps map[string]*DependencyRequirement,
caps map[string]*DependencyRequirement) error {
for name, rcap := range reqcaps {
capability, ok := caps[name]
if !ok {
return NewNewtError(fmt.Sprintf("Required capability %s not found", name))
}
if err := rcap.SatisfiesCapability(capability); err != nil {
return err
}
}
return nil
}
func (em *EggMgr) CheckDeps() error {
// Go through all the packages and check that their dependencies are satisfied
for _, egg := range em.Eggs {
deps := map[string]*DependencyRequirement{}
reqcap := map[string]*DependencyRequirement{}
caps := map[string]*DependencyRequirement{}
if err := em.CheckEggDeps(egg, deps, reqcap, caps); err != nil {
return err
}
}
return nil
}
// Load an individual package specified by eggName into the package list for
// this repository
func (em *EggMgr) loadEgg(eggDir string) error {
log.Println("[INFO] Loading Egg " + eggDir + "...")
if em.Eggs == nil {
em.Eggs = make(map[string]*Egg)
}
egg, err := NewEgg(em.Repo, em.Target, eggDir)
if err != nil {
return nil
}
em.Eggs[egg.FullName] = egg
return nil
}
func (em *EggMgr) String() string {
str := ""
for eggName, _ := range em.Eggs {
str += eggName + " "
}
return str
}
// Recursively load a package. Given the baseDir of the packages (e.g. egg/ or
// hw/bsp), and the base package name.
func (em *EggMgr) loadEggDir(baseDir string, eggPrefix string, eggName string) error {
log.Printf("[DEBUG] Loading packages in %s, starting with package %s", baseDir,
eggName)
// first recurse and load subpackages
list, err := ioutil.ReadDir(baseDir + "/" + eggName)
if err != nil {
return NewNewtError(err.Error())
}
for _, ent := range list {
if !ent.IsDir() {
continue
}
name := ent.Name()
if name == "src" || name == "include" || strings.HasPrefix(name, ".") ||
name == "bin" {
continue
} else {
if err := em.loadEggDir(baseDir, eggPrefix, eggName+"/"+name); err != nil {
return err
}
}
}
if NodeNotExist(baseDir + "/" + eggName + "/egg.yml") {
return nil
}
return em.loadEgg(baseDir + "/" + eggName)
}
// Load all the packages in the repository into the package structure
func (em *EggMgr) loadEggs() error {
r := em.Repo
// Multiple package directories to be searched
searchDirs := []string{"libs/", "hw/bsp/", "hw/mcu/", "hw/mcu/stm", "hw/drivers/", "hw/"}
for _, eggDir := range searchDirs {
eggBaseDir := r.BasePath + "/" + eggDir
if NodeNotExist(eggBaseDir) {
continue
}
eggList, err := ioutil.ReadDir(eggBaseDir)
if err != nil {
return NewNewtError(err.Error())
}
for _, subEggDir := range eggList {
name := subEggDir.Name()
if filepath.HasPrefix(name, ".") || filepath.HasPrefix(name, "..") {
continue
}
if !subEggDir.IsDir() {
continue
}
if err = em.loadEggDir(eggBaseDir, eggDir, name); err != nil {
return err
}
}
}
return nil
}
// Initialize the package manager
func (em *EggMgr) Init() error {
err := em.loadEggs()
return err
}
// Resolve the package specified by eggName into a package structure.
func (em *EggMgr) ResolveEggName(eggName string) (*Egg, error) {
egg, ok := em.Eggs[eggName]
if !ok {
return nil, NewNewtError(fmt.Sprintf("Invalid package %s specified (eggs = %s)",
eggName, em))
}
return egg, nil
}
func (em *EggMgr) ResolveEggDir(eggDir string) (*Egg, error) {
eggDir = filepath.Clean(eggDir)
for name, egg := range em.Eggs {
if filepath.Clean(egg.BasePath) == eggDir {
return em.Eggs[name], nil
}
}
return nil, NewNewtError(fmt.Sprintf("Cannot resolve package dir %s in package "+
"manager", eggDir))
}
func (em *EggMgr) VerifyEgg(eggDir string) (*Egg, error) {
err := em.loadEgg(eggDir)
if err != nil {
return nil, err
}
egg, err := em.ResolveEggDir(eggDir)
if err != nil {
return nil, err
}
if err := em.checkIncludes(egg); err != nil {
return nil, err
}
return egg, nil
}
// Clean the build for the package specified by eggName. if cleanAll is
// specified, all architectures are cleaned.
func (em *EggMgr) BuildClean(t *Target, eggName string, cleanAll bool) error {
egg, err := em.ResolveEggName(eggName)
if err != nil {
return err
}
tName := t.Name + "/"
if cleanAll {
tName = ""
}
if egg.Clean {
return nil
}
egg.Clean = true
for _, dep := range egg.Deps {
if err := em.BuildClean(t, dep.Name, cleanAll); err != nil {
return err
}
}
c, err := NewCompiler(t.GetCompiler(), t.Cdef, t.Name, []string{})
if err != nil {
return err
}
if NodeExist(egg.BasePath + "/src/") {
if err := c.RecursiveClean(egg.BasePath+"/src/", tName); err != nil {
return err
}
if err := os.RemoveAll(egg.BasePath + "/bin/" + tName); err != nil {
return NewNewtError(err.Error())
}
}
egg.Clean = true
return nil
}
func (em *EggMgr) GetEggLib(t *Target, egg *Egg) string {
libDir := egg.BasePath + "/bin/" + t.Name + "/" +
"lib" + filepath.Base(egg.Name) + ".a"
return libDir
}
// @param incls Extra include paths that get specified during
// build; not modified by this function.
// @param libs List of libraries that have been built so far;
// This function appends entries to this list.
func (em *EggMgr) buildDeps(egg *Egg, t *Target, incls *[]string,
libs *[]string) error {
log.Printf("[DEBUG] Building package dependencies for %s, target %s",
egg.Name, t.Name)
var err error
if egg.Includes, err = egg.GetIncludes(t); err != nil {
return err
}
if incls == nil {
incls = &[]string{}
}
if libs == nil {
libs = &[]string{}
}
for _, dep := range egg.Deps {
if dep.Name == "" {
break
}
log.Printf("[DEBUG] Loading package dependency: %s", dep.Name)
// Get package structure
degg, err := em.ResolveEggName(dep.Name)
if err != nil {
return err
}
// Build the package
if err = em.Build(t, dep.Name, *incls, libs); err != nil {
return err
}
// After build, get dependency package includes. Build function
// generates all the package includes
egg.Includes = append(egg.Includes, degg.Includes...)
if lib := em.GetEggLib(t, degg); NodeExist(lib) {
*libs = append(*libs, lib)
}
}
// Add on dependency includes to package includes
log.Printf("[DEBUG] Egg dependencies for %s built, incls = %s",
egg.Name, egg.Includes)
return nil
}
// Build the package specified by eggName
//
// @param incls Extra include paths that get specified during
// build. Note: passed by value.
// @param libs List of libraries that have been built so far;
// This function appends entries to this list.
func (em *EggMgr) Build(t *Target, eggName string, incls []string,
libs *[]string) error {
// Look up package structure
egg, err := em.ResolveEggName(eggName)
if err != nil {
return err
}
log.Println("[INFO] Building package " + eggName + " for arch " + t.Arch)
// already built the package, no need to rebuild. This is to handle
// recursive calls to Build()
if egg.Built {
return nil
}
egg.Built = true
if err := em.buildDeps(egg, t, &incls, libs); err != nil {
return err
}
// NOTE: this assignment must happen after the call to buildDeps(), as
// buildDeps() fills in the package includes.
incls = append(incls, EggIncludeDirs(egg, t)...)
log.Printf("[DEBUG] Egg includes for %s are %s", eggName, incls)
srcDir := egg.BasePath + "/src/"
if NodeNotExist(srcDir) {
// nothing to compile, return true!
return nil
}
// Build the package designated by eggName
// Initialize a compiler
c, err := NewCompiler(t.GetCompiler(), t.Cdef, t.Name, incls)
if err != nil {
return err
}
// setup Cflags, Lflags and Aflags
c.Cflags = CreateCflags(em, c, t, egg.Cflags)
c.Lflags += " " + egg.Lflags + " " + t.Lflags
c.Aflags += " " + egg.Aflags + " " + t.Aflags
log.Printf("[DEBUG] compiling src packages in base package directories: %s",
srcDir)
// For now, ignore test code. Tests get built later if the test identity
// is in effect.
ignDirs := []string{"test"}
if err = BuildDir(srcDir, c, t, ignDirs); err != nil {
return err
}
// Now build the test code if requested.
if t.HasIdentity("test") {
testSrcDir := srcDir + "/test"
if err = BuildDir(testSrcDir, c, t, ignDirs); err != nil {
return err
}
}
// Archive everything into a static library, which can be linked with a
// main program
if err := os.Chdir(egg.BasePath + "/"); err != nil {
return NewNewtError(err.Error())
}
binDir := egg.BasePath + "/bin/" + t.Name + "/"
if NodeNotExist(binDir) {
if err := os.MkdirAll(binDir, 0755); err != nil {
return NewNewtError(err.Error())
}
}
if err = c.CompileArchive(em.GetEggLib(t, egg), ""); err != nil {
return err
}
return nil
}
// Check the include directories for the package, to make sure there are no conflicts in
// include paths for source code
func (em *EggMgr) checkIncludes(egg *Egg) error {
incls, err := filepath.Glob(egg.BasePath + "/include/*")
if err != nil {
return NewNewtError(err.Error())
}
// Append all the architecture specific directories
archDir := egg.BasePath + "/include/" + egg.Name + "/arch/"
dirs, err := ioutil.ReadDir(archDir)
if err != nil {
return NewNewtError(err.Error())
}
for _, dir := range dirs {
if !dir.IsDir() {
return NewNewtError(fmt.Sprintf("Only directories are allowed in "+
"architecture dir: %s", archDir+dir.Name()))
}
incls2, err := filepath.Glob(archDir + dir.Name() + "/*")
if err != nil {
return NewNewtError(err.Error())
}
incls = append(incls, incls2...)
}
for _, incl := range incls {
finfo, err := os.Stat(incl)
if err != nil {
return NewNewtError(err.Error())
}
bad := false
if !finfo.IsDir() {
bad = true
}
if filepath.Base(incl) != egg.Name {
if egg.IsBsp && filepath.Base(incl) != "bsp" {
bad = true
}
}
if bad {
return NewNewtError(fmt.Sprintf("File %s should not exist in include "+
"directory, only file allowed in include directory is a directory with "+
"the package name %s",
incl, egg.Name))
}
}
return nil
}
// Clean the tests in the tests parameter, for the package identified by
// eggName. If cleanAll is set to true, all architectures will be removed.
func (em *EggMgr) TestClean(t *Target, eggName string,
cleanAll bool) error {
egg, err := em.ResolveEggName(eggName)
if err != nil {
return err
}
tName := t.Name + "/"
if cleanAll {
tName = ""
}
if err := os.RemoveAll(egg.BasePath + "/src/test/bin/" + tName); err != nil {
return NewNewtError(err.Error())
}
if err := os.RemoveAll(egg.BasePath + "/src/test/obj/" + tName); err != nil {
return NewNewtError(err.Error())
}
return nil
}
// Compile tests specified by the tests parameter. The tests are linked
// to the package specified by the egg parameter
func (em *EggMgr) linkTests(t *Target, egg *Egg,
incls []string, libs *[]string) error {
c, err := NewCompiler(t.GetCompiler(), t.Cdef, t.Name, incls)
if err != nil {
return err
}
// Configure Lflags. Since we are only linking, Cflags and Aflags are
// unnecessary.
c.Lflags += " " + egg.Lflags + " " + t.Lflags
testBinDir := egg.BasePath + "/src/test/bin/" + t.Name + "/"
if NodeNotExist(testBinDir) {
if err := os.MkdirAll(testBinDir, 0755); err != nil {
return NewNewtError(err.Error())
}
}
if err := c.CompileBinary(testBinDir+egg.TestBinName(), map[string]bool{},
strings.Join(*libs, " ")); err != nil {
return err
}
return nil
}
// Run all the tests in the tests parameter. egg is the package to check for
// the tests. exitOnFailure specifies whether to exit immediately when a
// test fails, or continue executing all tests.
func (em *EggMgr) runTests(t *Target, egg *Egg, exitOnFailure bool) error {
if err := os.Chdir(egg.BasePath + "/src/test/bin/" + t.Name +
"/"); err != nil {
return err
}
o, err := ShellCommand("./" + egg.TestBinName())
if err != nil {
log.Printf("[ERROR] Test %s failed, output: %s", egg.TestBinName(),
string(o))
if exitOnFailure {
return NewNewtError("Unit tests failed to complete successfully.")
}
} else {
log.Printf("[INFO] Test %s ok!", egg.TestBinName())
}
return nil
}
// Check to ensure tests exist. Go through the array of tests specified by
// the tests parameter. egg is the package to check for these tests.
func (em *EggMgr) testsExist(egg *Egg) error {
dirName := egg.BasePath + "/src/test/"
if NodeNotExist(dirName) {
return NewNewtError("No test exists for package " + egg.Name)
}
return nil
}
// Test the package identified by eggName, by executing the tests specified.
// exitOnFailure signifies whether to stop the test program when one of them
// fails.
func (em *EggMgr) Test(t *Target, eggName string, exitOnFailure bool) error {
log.Printf("[INFO] Testing package %s for arch %s", eggName, t.Arch)
egg, err := em.ResolveEggName(eggName)
if err != nil {
return err
}
// Make sure the test directories exist
if err := em.testsExist(egg); err != nil {
return err
}
incls := []string{}
libs := []string{}
// The 'test' identity is implicitly exported during a package test.
t.Identities = append(t.Identities, "test")
// If there is a BSP:
// 1. Calculate the include paths that it and its dependencies export.
// This set of include paths is accessible during all subsequent
// builds.
// 2. Build the BSP package.
if t.Bsp != "" {
incls, err = BspIncludePaths(em, t)
if err != nil {
return err
}
_, err = buildBsp(t, em, &incls, &libs)
if err != nil {
return err
}
}
// Build the package under test. This must be compiled with the PKG_TEST
// symbol defined so that the appropriate main function gets built.
egg.Cflags += " -DPKG_TEST"
if err := em.Build(t, eggName, incls, &libs); err != nil {
return err
}
lib := em.GetEggLib(t, egg)
if !NodeExist(lib) {
return NewNewtError("Egg " + eggName + " did not produce binary")
}
libs = append(libs, lib)
// Compile the package's test code.
if err := em.linkTests(t, egg, incls, &libs); err != nil {
return err
}
// Run the tests.
if err := em.runTests(t, egg, exitOnFailure); err != nil {
return err
}
return nil
}