| /** |
| * 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" |
| "fmt" |
| "os" |
| "path/filepath" |
| "strings" |
| |
| "mynewt.apache.org/newt/newt/config" |
| "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 |
| |
| // 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{}, |
| repo: r, |
| basePath: filepath.ToSlash(filepath.Clean(pkgDir)), |
| injectedSettings: map[string]string{}, |
| } |
| |
| pkg.PkgY = ycfg.NewYCfg(pkg.PkgYamlPath()) |
| pkg.SyscfgY = ycfg.NewYCfg(pkg.SyscfgYamlPath()) |
| 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) PkgYamlPath() string { |
| return fmt.Sprintf("%s/%s", pkg.BasePath(), PACKAGE_FILE_NAME) |
| } |
| |
| func (pkg *LocalPackage) SyscfgYamlPath() string { |
| return fmt.Sprintf("%s/%s", pkg.BasePath(), SYSCFG_YAML_FILENAME) |
| } |
| |
| 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) 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{} |
| |
| var err error |
| |
| pdesc.Author, err = yc.GetValString("pkg.author", nil) |
| util.OneTimeWarningError(err) |
| |
| pdesc.Homepage, err = yc.GetValString("pkg.homepage", nil) |
| util.OneTimeWarningError(err) |
| |
| pdesc.Description, err = yc.GetValString("pkg.description", nil) |
| util.OneTimeWarningError(err) |
| |
| pdesc.Keywords, err = yc.GetValStringSlice("pkg.keywords", nil) |
| util.OneTimeWarningError(err) |
| |
| return pdesc, nil |
| } |
| |
| func (pkg *LocalPackage) sequenceString(key string) string { |
| var buffer bytes.Buffer |
| |
| vals, err := pkg.PkgY.GetValStringSlice(key, nil) |
| util.OneTimeWarningError(err) |
| for _, f := range vals { |
| 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()) |
| } |
| |
| file, err := os.Create(lpkg.SyscfgYamlPath()) |
| if err != nil { |
| return util.NewNewtError(err.Error()) |
| } |
| defer file.Close() |
| |
| s := lpkg.SyscfgY.YAML() |
| 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()) |
| } |
| |
| file, err := os.Create(pkg.PkgYamlPath()) |
| 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 { |
| var err error |
| |
| pkg.PkgY, err = config.ReadFile(pkg.PkgYamlPath()) |
| if err != nil { |
| return err |
| } |
| pkg.AddCfgFilename(pkg.PkgYamlPath()) |
| |
| // Set package name from the package |
| pkg.name, err = pkg.PkgY.GetValString("pkg.name", nil) |
| util.OneTimeWarningError(err) |
| 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, err := pkg.PkgY.GetValString("pkg.type", nil) |
| util.OneTimeWarningError(err) |
| 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, err := pkg.PkgY.GetValString("pkg.link", nil) |
| util.OneTimeWarningError(err) |
| 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 |
| } |
| |
| // Read the package description from the file |
| pkg.desc, err = pkg.readDesc(pkg.PkgY) |
| if err != nil { |
| return err |
| } |
| |
| // Load syscfg settings. |
| pkg.SyscfgY, err = config.ReadFile(pkg.SyscfgYamlPath()) |
| if err != nil && !util.IsNotExist(err) { |
| return err |
| } |
| |
| pkg.AddCfgFilename(pkg.SyscfgYamlPath()) |
| |
| return nil |
| } |
| |
| func (pkg *LocalPackage) InitFuncs( |
| settings map[string]string) map[string]string { |
| |
| vals, err := pkg.PkgY.GetValStringMapString("pkg.init", settings) |
| util.OneTimeWarningError(err) |
| return vals |
| } |
| |
| // DownFuncs retrieves the package's shutdown functions. The returned map has: |
| // key=C-function-name, value=numeric-stage. |
| func (pkg *LocalPackage) DownFuncs( |
| settings map[string]string) map[string]string { |
| |
| vals, err := pkg.PkgY.GetValStringMapString("pkg.down", settings) |
| util.OneTimeWarningError(err) |
| return vals |
| } |
| |
| func (pkg *LocalPackage) PreBuildCmds( |
| settings map[string]string) map[string]string { |
| |
| vals, err := pkg.PkgY.GetValStringMapString("pkg.pre_build_cmds", settings) |
| util.OneTimeWarningError(err) |
| return vals |
| } |
| |
| func (pkg *LocalPackage) PreLinkCmds( |
| settings map[string]string) map[string]string { |
| |
| vals, err := pkg.PkgY.GetValStringMapString("pkg.pre_link_cmds", settings) |
| util.OneTimeWarningError(err) |
| return vals |
| } |
| |
| func (pkg *LocalPackage) PostLinkCmds( |
| settings map[string]string) map[string]string { |
| |
| vals, err := pkg.PkgY.GetValStringMapString("pkg.post_link_cmds", settings) |
| util.OneTimeWarningError(err) |
| return vals |
| } |
| |
| 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 %s.", |
| err.Error(), pkgDir) |
| 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 |
| } |