blob: dcdb0a37816772faacdf971c96a5f1cd7225f32b [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 resolve
import (
"fmt"
"sort"
"strings"
log "github.com/Sirupsen/logrus"
"mynewt.apache.org/newt/newt/flash"
"mynewt.apache.org/newt/newt/pkg"
"mynewt.apache.org/newt/newt/project"
"mynewt.apache.org/newt/newt/syscfg"
"mynewt.apache.org/newt/util"
"mynewt.apache.org/newt/newt/ycfg"
)
// Represents a supplied API.
type resolveApi struct {
// The package which supplies the API.
rpkg *ResolvePackage
// The expression which enabled this API.
expr string
}
// Represents a required API.
type resolveReqApi struct {
// Whether the API requirement has been satisfied by a hard dependency.
satisfied bool
// The expression which enabled this API requirement.
expr string
}
type Resolver struct {
apis map[string]resolveApi
pkgMap map[*pkg.LocalPackage]*ResolvePackage
injectedSettings map[string]string
flashMap flash.FlashMap
cfg syscfg.Cfg
// [api-name][api-supplier]
apiConflicts map[string]map[*ResolvePackage]struct{}
}
type ResolveDep struct {
// Package being depended on.
Rpkg *ResolvePackage
// Name of API that generated the dependency; "" if a hard dependency.
Api string
// Text of syscfg expression that generated this dependency; "" for none.
Expr string
}
type ResolvePackage struct {
Lpkg *pkg.LocalPackage
Deps map[*ResolvePackage]*ResolveDep
// Keeps track of API requirements and whether they are satisfied.
reqApiMap map[string]resolveReqApi
depsResolved bool
// Tracks this package's number of dependents. If this number reaches 0,
// this package can be deleted from the resolver.
refCount int
}
type ResolveSet struct {
// Parent resoluion. Contains this ResolveSet.
Res *Resolution
// All seed pacakges and their dependencies.
Rpkgs []*ResolvePackage
}
type ApiConflict struct {
Api string
Pkgs []*ResolvePackage
}
// The result of resolving a target's configuration, APIs, and dependencies.
type Resolution struct {
Cfg syscfg.Cfg
ApiMap map[string]*ResolvePackage
UnsatisfiedApis map[string][]*ResolvePackage
ApiConflicts []ApiConflict
LpkgRpkgMap map[*pkg.LocalPackage]*ResolvePackage
// Contains all dependencies; union of loader and app.
MasterSet *ResolveSet
LoaderSet *ResolveSet
AppSet *ResolveSet
}
func newResolver(
seedPkgs []*pkg.LocalPackage,
injectedSettings map[string]string,
flashMap flash.FlashMap) *Resolver {
r := &Resolver{
apis: map[string]resolveApi{},
pkgMap: map[*pkg.LocalPackage]*ResolvePackage{},
injectedSettings: injectedSettings,
flashMap: flashMap,
cfg: syscfg.NewCfg(),
apiConflicts: map[string]map[*ResolvePackage]struct{}{},
}
if injectedSettings == nil {
r.injectedSettings = map[string]string{}
}
for _, lpkg := range seedPkgs {
r.addPkg(lpkg)
}
return r
}
func newResolution() *Resolution {
r := &Resolution{
ApiMap: map[string]*ResolvePackage{},
UnsatisfiedApis: map[string][]*ResolvePackage{},
}
r.MasterSet = &ResolveSet{Res: r}
r.LoaderSet = &ResolveSet{Res: r}
r.AppSet = &ResolveSet{Res: r}
return r
}
func NewResolvePkg(lpkg *pkg.LocalPackage) *ResolvePackage {
return &ResolvePackage{
Lpkg: lpkg,
reqApiMap: map[string]resolveReqApi{},
Deps: map[*ResolvePackage]*ResolveDep{},
}
}
func (r *Resolver) resolveDep(dep *pkg.Dependency, depender string) (*pkg.LocalPackage, error) {
proj := project.GetProject()
if proj.ResolveDependency(dep) == nil {
return nil, util.FmtNewtError("Could not resolve package dependency: "+
"%s; depender: %s", dep.String(), depender)
}
lpkg := proj.ResolveDependency(dep).(*pkg.LocalPackage)
return lpkg, nil
}
// @return true if the package's dependency list was
// modified.
func (rpkg *ResolvePackage) AddDep(
apiPkg *ResolvePackage, api string, expr string) bool {
if dep := rpkg.Deps[apiPkg]; dep != nil {
if dep.Api != "" && api == "" {
dep.Api = api
} else {
return false
}
} else {
rpkg.Deps[apiPkg] = &ResolveDep{
Rpkg: apiPkg,
Api: api,
Expr: expr,
}
}
// If this dependency came from an API requirement, record that the API
// requirement is now satisfied.
if api != "" {
apiReq := rpkg.reqApiMap[api]
apiReq.expr = expr
apiReq.satisfied = true
rpkg.reqApiMap[api] = apiReq
}
apiPkg.refCount++
return true
}
func (r *Resolver) rpkgSlice() []*ResolvePackage {
rpkgs := make([]*ResolvePackage, len(r.pkgMap))
i := 0
for _, rpkg := range r.pkgMap {
rpkgs[i] = rpkg
i++
}
return rpkgs
}
func (r *Resolver) apiSlice() []string {
apis := make([]string, len(r.apis))
i := 0
for api, _ := range r.apis {
apis[i] = api
i++
}
return apis
}
// @return ResolvePackage The rpkg corresponding to the specified lpkg.
// This is a new package if a package was
// added; old if it was already present.
// bool true if this is a new package.
func (r *Resolver) addPkg(lpkg *pkg.LocalPackage) (*ResolvePackage, bool) {
if rpkg := r.pkgMap[lpkg]; rpkg != nil {
return rpkg, false
}
rpkg := NewResolvePkg(lpkg)
r.pkgMap[lpkg] = rpkg
return rpkg, true
}
func (r *Resolver) sortedRpkgs() []*ResolvePackage {
rpkgs := make([]*ResolvePackage, 0, len(r.pkgMap))
for _, rpkg := range r.pkgMap {
rpkgs = append(rpkgs, rpkg)
}
SortResolvePkgs(rpkgs)
return rpkgs
}
// Selects the final API suppliers among all packages implementing APIs. The
// result gets written to the resolver's `apis` map. If more than one package
// implements the same API, an API conflict error is recorded.
func (r *Resolver) selectApiSuppliers() {
apiMap := map[string][]resolveApi{}
for _, rpkg := range r.sortedRpkgs() {
settings := r.cfg.AllSettingsForLpkg(rpkg.Lpkg)
apiStrings := rpkg.Lpkg.PkgY.GetSlice("pkg.apis", settings)
for _, entry := range apiStrings {
apiStr, ok := entry.Value.(string)
if ok && apiStr != "" {
apiMap[apiStr] = append(apiMap[apiStr], resolveApi{
rpkg: rpkg,
expr: entry.Expr,
})
}
}
}
apiNames := make([]string, 0, len(apiMap))
for name, _ := range apiMap {
apiNames = append(apiNames, name)
}
sort.Strings(apiNames)
for _, name := range apiNames {
apis := apiMap[name]
for _, api := range apis {
old := r.apis[name]
if old.rpkg != nil {
if r.apiConflicts[name] == nil {
r.apiConflicts[name] = map[*ResolvePackage]struct{}{}
}
r.apiConflicts[name][api.rpkg] = struct{}{}
r.apiConflicts[name][old.rpkg] = struct{}{}
} else {
r.apis[name] = api
}
}
}
}
// Populates the specified package's set of API requirements.
func (r *Resolver) calcApiReqsFor(rpkg *ResolvePackage) {
settings := r.cfg.AllSettingsForLpkg(rpkg.Lpkg)
reqApiEntries := rpkg.Lpkg.PkgY.GetSlice("pkg.req_apis", settings)
for _, entry := range reqApiEntries {
apiStr, ok := entry.Value.(string)
if ok && apiStr != "" {
rpkg.reqApiMap[apiStr] = resolveReqApi{
satisfied: false,
expr: "",
}
}
}
}
// Populates all packages' API requirements sets.
func (r *Resolver) calcApiReqs() {
for _, rpkg := range r.pkgMap {
r.calcApiReqsFor(rpkg)
}
}
// @return bool True if this this function changed the resolver
// state; another full iteration is required
// in this case.
// error non-nil on failure.
func (r *Resolver) loadDepsForPkg(rpkg *ResolvePackage) (bool, error) {
settings := r.cfg.AllSettingsForLpkg(rpkg.Lpkg)
changed := false
var depEntries []ycfg.YCfgEntry
if rpkg.Lpkg.Type() == pkg.PACKAGE_TYPE_TRANSIENT {
depEntries = rpkg.Lpkg.PkgY.GetSlice("pkg.link", nil)
} else {
depEntries = rpkg.Lpkg.PkgY.GetSlice("pkg.deps", settings)
}
depender := rpkg.Lpkg.Name()
seen := make(map[*ResolvePackage]struct{}, len(rpkg.Deps))
for _, entry := range depEntries {
depStr, ok := entry.Value.(string)
if ok && depStr != "" {
newDep, err := pkg.NewDependency(rpkg.Lpkg.Repo(), depStr)
if err != nil {
return false, err
}
lpkg, err := r.resolveDep(newDep, depender)
if err != nil {
return false, err
}
depRpkg, _ := r.addPkg(lpkg)
if rpkg.AddDep(depRpkg, "", entry.Expr) {
changed = true
}
seen[depRpkg] = struct{}{}
}
}
// This iteration may have deleted some dependency relationships (e.g., if
// a new syscfg setting was discovered which causes this package's
// dependency list to be overwritten). Detect and delete these
// relationships.
for rdep, _ := range rpkg.Deps {
if _, ok := seen[rdep]; !ok {
delete(rpkg.Deps, rdep)
rdep.refCount--
changed = true
// If we just deleted the last reference to a package, remove the
// package entirely from the resolver and syscfg.
if rdep.refCount == 0 {
delete(r.pkgMap, rdep.Lpkg)
// Delete the package from syscfg.
r.cfg.DeletePkg(rdep.Lpkg)
}
}
}
return changed, nil
}
// Attempts to resolve all of a build package's dependencies, APIs, and
// required APIs. This function should be called repeatedly until the package
// is fully resolved.
//
// If a dependency is resolved by this function, the new dependency needs to be
// processed. The caller should attempt to resolve all packages again.
//
// @return bool true if >=1 dependencies were resolved.
// error non-nil on failure.
func (r *Resolver) resolvePkg(rpkg *ResolvePackage) (bool, error) {
var err error
newDeps := false
if !rpkg.depsResolved {
newDeps, err = r.loadDepsForPkg(rpkg)
if err != nil {
return false, err
}
rpkg.depsResolved = !newDeps
}
return newDeps, nil
}
// @return changed,err
func (r *Resolver) reloadCfg() (bool, error) {
lpkgs := RpkgSliceToLpkgSlice(r.rpkgSlice())
apis := r.apiSlice()
// Determine which settings have been detected so far. The feature map is
// required for reloading syscfg, as settings may unlock additional
// settings.
settings := r.cfg.SettingValues()
cfg, err := syscfg.Read(lpkgs, apis, r.injectedSettings, settings,
r.flashMap)
if err != nil {
return false, err
}
cfg.ResolveValueRefs()
// Determine if any settings have changed.
for k, v := range cfg.Settings {
oldval, ok := r.cfg.Settings[k]
if !ok || len(oldval.History) != len(v.History) {
r.cfg = cfg
return true, nil
}
}
return false, nil
}
func (r *Resolver) resolveDepsOnce() (bool, error) {
// Circularly resolve dependencies, APIs, and required APIs until no new
// ones exist.
newDeps := false
for {
reprocess := false
for _, rpkg := range r.pkgMap {
newDeps, err := r.resolvePkg(rpkg)
if err != nil {
return false, err
}
if newDeps {
// The new dependencies need to be processed. Iterate again
// after this iteration completes.
reprocess = true
}
}
if !reprocess {
break
}
}
return newDeps, nil
}
// Given a fully calculated syscfg and API map, resolves package dependencies
// by populating the resolver's package map. This function should only be
// called if the resolver's syscfg (`cfg`) member is assigned. This only
// happens for split images when the individual loader and app components are
// resolved separately, after the master syscfg and API map have been
// calculated.
func (r *Resolver) resolveDeps() ([]*ResolvePackage, error) {
if _, err := r.resolveDepsOnce(); err != nil {
return nil, err
}
// Now that the final set of packages is known, determine which ones
// satisfy each required API.
r.selectApiSuppliers()
// Determine which packages have API requirements.
r.calcApiReqs()
// Satisfy API requirements.
if err := r.resolveApiDeps(); err != nil {
return nil, err
}
rpkgs := r.rpkgSlice()
return rpkgs, nil
}
// Performs a set of resolution actions:
// 1. Calculates the system configuration (syscfg).
// 2. Determines which packages satisfy which API requirements.
// 3. Resolves package dependencies by populating the resolver's package map.
func (r *Resolver) resolveDepsAndCfg() error {
if _, err := r.resolveDepsOnce(); err != nil {
return err
}
for {
cfgChanged, err := r.reloadCfg()
if err != nil {
return err
}
if cfgChanged {
// A new supported feature was discovered. It is impossible
// to determine what new dependency and API requirements are
// generated as a result. All packages need to be
// reprocessed.
for _, rpkg := range r.pkgMap {
rpkg.depsResolved = false
}
}
newDeps, err := r.resolveDepsOnce()
if err != nil {
return err
}
if !newDeps && !cfgChanged {
break
}
}
// Now that the final set of packages is known, determine which ones
// satisfy each required API.
r.selectApiSuppliers()
// Determine which packages have API requirements.
r.calcApiReqs()
// Satisfy API requirements.
if err := r.resolveApiDeps(); err != nil {
return err
}
// Log the final syscfg.
r.cfg.Log()
return nil
}
func joinExprs(expr1 string, expr2 string) string {
if expr1 == "" {
return expr2
}
if expr2 == "" {
return expr1
}
return expr1 + "," + expr2
}
// Transforms each package's required APIs to hard dependencies. That is, this
// function determines which package supplies each required API, and adds the
// corresponding dependecy to each package which requires the API.
func (r *Resolver) resolveApiDeps() error {
for _, rpkg := range r.pkgMap {
for apiString, reqApi := range rpkg.reqApiMap {
// Determine which package satisfies this API requirement.
api, ok := r.apis[apiString]
// If there is a package that supports the requested API, add a
// hard dependency to the package. Otherwise, record an
// unsatisfied API requirement with an empty API struct.
if ok && api.rpkg != nil {
rpkg.AddDep(api.rpkg, apiString,
joinExprs(api.expr, reqApi.expr))
} else if !ok {
r.apis[apiString] = resolveApi{}
}
}
}
return nil
}
func (r *Resolver) apiResolution() (
map[string]*ResolvePackage,
map[string][]*ResolvePackage) {
apiMap := make(map[string]*ResolvePackage, len(r.apis))
anyUnsatisfied := false
for name, api := range r.apis {
if api.rpkg == nil {
anyUnsatisfied = true
} else {
apiMap[name] = api.rpkg
}
}
unsatisfied := map[string][]*ResolvePackage{}
if anyUnsatisfied {
for _, rpkg := range r.pkgMap {
for name, reqApi := range rpkg.reqApiMap {
if !reqApi.satisfied {
slice := unsatisfied[name]
slice = append(slice, rpkg)
unsatisfied[name] = slice
}
}
}
}
return apiMap, unsatisfied
}
func ResolveFull(
loaderSeeds []*pkg.LocalPackage,
appSeeds []*pkg.LocalPackage,
injectedSettings map[string]string,
flashMap flash.FlashMap) (*Resolution, error) {
// First, calculate syscfg and determine which package provides each
// required API. Syscfg and APIs are project-wide; that is, they are
// calculated across the aggregate of all app packages and loader packages
// (if any). The dependency graph for the entire set of packages gets
// calculated here as a byproduct.
allSeeds := append(loaderSeeds, appSeeds...)
r := newResolver(allSeeds, injectedSettings, flashMap)
if err := r.resolveDepsAndCfg(); err != nil {
return nil, err
}
res := newResolution()
res.Cfg = r.cfg
// Determine which package satisfies each API and which APIs are
// unsatisfied.
apiMap := map[string]*ResolvePackage{}
apiMap, res.UnsatisfiedApis = r.apiResolution()
for api, m := range r.apiConflicts {
c := ApiConflict{
Api: api,
}
for rpkg, _ := range m {
c.Pkgs = append(c.Pkgs, rpkg)
}
res.ApiConflicts = append(res.ApiConflicts, c)
}
res.LpkgRpkgMap = r.pkgMap
res.MasterSet.Rpkgs = r.rpkgSlice()
// We have now resolved all packages so go through them and emit warning
// when using link packages
for _, rpkg := range res.MasterSet.Rpkgs {
if rpkg.Lpkg.Type() != pkg.PACKAGE_TYPE_TRANSIENT {
continue
}
log.Warnf("Transient package %s used, update configuration "+
"to use linked package instead (%s)",
rpkg.Lpkg.FullName(), rpkg.Lpkg.LinkedName())
}
// If there is no loader, then the set of all packages is just the app
// packages. We already resolved the necessary dependency information when
// syscfg was calculated above.
if loaderSeeds == nil {
res.AppSet.Rpkgs = r.rpkgSlice()
res.LoaderSet = nil
res.Cfg.DetectErrors(flashMap)
return res, nil
}
// Otherwise, we need to resolve dependencies separately for:
// 1. The set of loader pacakges, and
// 2. The set of app packages.
//
// These need to be resolved separately so that it is possible later to
// determine which packages need to be shared between loader and app.
// It is OK if the app requires an API that is supplied by the loader.
// Ensure each set of packages has access to the API-providers.
for _, rpkg := range apiMap {
loaderSeeds = append(loaderSeeds, rpkg.Lpkg)
appSeeds = append(appSeeds, rpkg.Lpkg)
}
// Resolve loader dependencies.
r = newResolver(loaderSeeds, injectedSettings, flashMap)
r.cfg = res.Cfg
var err error
res.LoaderSet.Rpkgs, err = r.resolveDeps()
if err != nil {
return nil, err
}
// Resolve app dependencies. The app automtically gets all the packages
// from the loader except for the loader-app-package.
for _, rpkg := range res.LoaderSet.Rpkgs {
if rpkg.Lpkg.Type() != pkg.PACKAGE_TYPE_APP {
appSeeds = append(appSeeds, rpkg.Lpkg)
}
}
r = newResolver(appSeeds, injectedSettings, flashMap)
r.cfg = res.Cfg
res.AppSet.Rpkgs, err = r.resolveDeps()
if err != nil {
return nil, err
}
res.Cfg.DetectErrors(flashMap)
return res, nil
}
func (res *Resolution) ErrorText() string {
str := ""
if len(res.UnsatisfiedApis) > 0 {
apiNames := make([]string, 0, len(res.UnsatisfiedApis))
for api, _ := range res.UnsatisfiedApis {
apiNames = append(apiNames, api)
}
sort.Strings(apiNames)
str += "Unsatisfied APIs detected:\n"
for _, api := range apiNames {
str += fmt.Sprintf(" * %s, required by: ", api)
rpkgs := res.UnsatisfiedApis[api]
pkgNames := make([]string, len(rpkgs))
for i, rpkg := range rpkgs {
pkgNames[i] = rpkg.Lpkg.Name()
}
sort.Strings(pkgNames)
str += strings.Join(pkgNames, ", ")
str += "\n"
}
}
str += res.Cfg.ErrorText()
str = strings.TrimSpace(str)
if str != "" {
str += "\n"
}
return str
}
func (res *Resolution) WarningText() string {
text := ""
for _, c := range res.ApiConflicts {
text += fmt.Sprintf("Warning: API conflict: %s (", c.Api)
for i, rpkg := range c.Pkgs {
if i != 0 {
text += " <-> "
}
text += rpkg.Lpkg.Name()
}
text += ")\n"
}
return text + res.Cfg.WarningText()
}
func (res *Resolution) DeprecatedWarning() []string {
return res.Cfg.DeprecatedWarning()
}