| /* |
| 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 ( |
| "crypto/sha1" |
| "fmt" |
| "io/ioutil" |
| "log" |
| "os" |
| "path/filepath" |
| "regexp" |
| "strconv" |
| "strings" |
| ) |
| |
| type VersMatch struct { |
| CompareType string |
| Vers *Version |
| } |
| |
| type Version struct { |
| Major int64 |
| Minor int64 |
| Revision int64 |
| } |
| |
| type DependencyRequirement struct { |
| Name string |
| Stability string |
| VersMatches []*VersMatch |
| } |
| |
| type Egg struct { |
| // Base directory of the egg |
| BasePath string |
| // Name of the egg |
| Name string |
| // Full Name of the egg include prefix dir |
| FullName string |
| // Nest this egg belongs to |
| Nest *Nest |
| // Egg version |
| Version *Version |
| // Type of egg |
| LinkerScript string |
| |
| // Has the configuration been loaded for this egg |
| CfgLoaded bool |
| |
| // Egg sources |
| Sources []string |
| // Egg include directories |
| Includes []string |
| |
| // Egg compiler flags |
| Cflags string |
| |
| // Egg linker flags |
| Lflags string |
| |
| // Egg assembler flags |
| Aflags string |
| |
| // Whether or not this egg is a BSP |
| IsBsp bool |
| |
| // Capabilities that this egg exports |
| Capabilities []*DependencyRequirement |
| |
| // Capabilities that this egg requires |
| ReqCapabilities []*DependencyRequirement |
| |
| // Whether or not we've already compiled this egg |
| Built bool |
| |
| // Whether or not we've already cleaned this egg |
| Clean bool |
| |
| // Eggs that this egg depends on |
| Deps []*DependencyRequirement |
| } |
| |
| type EggShell struct { |
| FullName string |
| Version *Version |
| /* Clutch this eggshell belongs to */ |
| Clutch *Clutch |
| Hash string |
| Deps []*DependencyRequirement |
| Caps []*DependencyRequirement |
| ReqCaps []*DependencyRequirement |
| } |
| |
| func NewEggShell(clutch *Clutch) (*EggShell, error) { |
| eShell := &EggShell{ |
| Clutch: clutch, |
| } |
| |
| return eShell, nil |
| } |
| |
| func (es *EggShell) serializeDepReq(name string, |
| drList []*DependencyRequirement, indent string) string { |
| drStr := "" |
| if len(drList) > 0 { |
| drStr += fmt.Sprintf("%s%s:\n", indent, name) |
| for _, dr := range drList { |
| drStr += fmt.Sprintf("%s - %s\n", indent, dr) |
| } |
| } |
| |
| return drStr |
| } |
| |
| func (es *EggShell) Serialize(indent string) string { |
| esStr := fmt.Sprintf("%s%s:\n", indent, es.FullName) |
| indent += " " |
| if es.Version == nil { |
| es.Version = &Version{0, 0, 0} |
| } |
| esStr += fmt.Sprintf("%svers: %s\n", indent, es.Version) |
| esStr += fmt.Sprintf("%shash: %s\n", indent, es.Hash) |
| esStr += es.serializeDepReq("deps", es.Deps, indent) |
| esStr += es.serializeDepReq("caps", es.Caps, indent) |
| esStr += es.serializeDepReq("req_caps", es.ReqCaps, indent) |
| |
| return esStr |
| } |
| |
| func (v *Version) compareVersions(vers1 *Version, vers2 *Version) int64 { |
| log.Printf("[DEBUG] Comparing %s to %s (%d %d %d)", vers1, vers2, |
| vers1.Major-vers2.Major, vers1.Minor-vers2.Minor, |
| vers1.Revision-vers2.Revision) |
| |
| if r := vers1.Major - vers2.Major; r != 0 { |
| return r |
| } |
| |
| if r := vers1.Minor - vers2.Minor; r != 0 { |
| return r |
| } |
| |
| if r := vers1.Revision - vers2.Revision; r != 0 { |
| return r |
| } |
| |
| return 0 |
| } |
| |
| func (v *Version) SatisfiesVersion(versMatches []*VersMatch) bool { |
| if versMatches == nil { |
| return true |
| } |
| |
| for _, match := range versMatches { |
| r := v.compareVersions(match.Vers, v) |
| switch match.CompareType { |
| case "<": |
| if r <= 0 { |
| return false |
| } |
| case "<=": |
| if r < 0 { |
| return false |
| } |
| case ">": |
| if r >= 0 { |
| return false |
| } |
| case ">=": |
| if r > 0 { |
| return false |
| } |
| case "==": |
| if r != 0 { |
| return false |
| } |
| } |
| } |
| |
| return true |
| } |
| |
| func (vers *Version) String() string { |
| return fmt.Sprintf("%d.%d.%d", vers.Major, vers.Minor, vers.Revision) |
| } |
| |
| func NewVersParseString(versStr string) (*Version, error) { |
| var err error |
| |
| parts := strings.Split(versStr, ".") |
| if len(parts) > 3 { |
| return nil, NewNewtError(fmt.Sprintf("Invalid version string: %s", versStr)) |
| } |
| |
| if strings.Trim(parts[0], " ") == "" || strings.Trim(parts[0], " ") == "none" { |
| return nil, nil |
| } |
| |
| v := &Version{} |
| |
| // convert first string to an int |
| if v.Major, err = strconv.ParseInt(parts[0], 0, 64); err != nil { |
| return nil, NewNewtError(err.Error()) |
| } |
| if len(parts) >= 2 { |
| if v.Minor, err = strconv.ParseInt(parts[1], 0, 64); err != nil { |
| return nil, NewNewtError(err.Error()) |
| } |
| } |
| if len(parts) == 3 { |
| if v.Revision, err = strconv.ParseInt(parts[2], 0, 64); err != nil { |
| return nil, NewNewtError(err.Error()) |
| } |
| } |
| |
| return v, nil |
| } |
| |
| // |
| // Set the version comparison constraints on a dependency requirement. |
| // The version string contains a list of version constraints in the following format: |
| // - <comparison><version> |
| // Where <comparison> can be any one of the following comparison |
| // operators: <=, <, >, >=, == |
| // And <version> is specified in the form: X.Y.Z where X, Y and Z are all |
| // int64 types in decimal form |
| func (dr *DependencyRequirement) SetVersStr(versStr string) error { |
| var err error |
| |
| re, err := regexp.Compile(`(<=|>=|==|>|<)([\d\.]+)`) |
| if err != nil { |
| return err |
| } |
| |
| matches := re.FindAllStringSubmatch(versStr, -1) |
| if matches != nil { |
| dr.VersMatches = make([]*VersMatch, 0, len(matches)) |
| for _, match := range matches { |
| vm := &VersMatch{} |
| vm.CompareType = match[1] |
| if vm.Vers, err = NewVersParseString(match[2]); err != nil { |
| return err |
| } |
| |
| if vm.Vers != nil { |
| dr.VersMatches = append(dr.VersMatches, vm) |
| } |
| } |
| } else { |
| dr.VersMatches = make([]*VersMatch, 0) |
| vm := &VersMatch{} |
| vm.CompareType = "==" |
| if vm.Vers, err = NewVersParseString(versStr); err != nil { |
| return err |
| } |
| if vm.Vers != nil { |
| dr.VersMatches = append(dr.VersMatches, vm) |
| } |
| } |
| |
| if len(dr.VersMatches) == 0 { |
| dr.VersMatches = nil |
| } |
| |
| return nil |
| } |
| |
| // Convert the array of version matches into a string for display |
| func (dr *DependencyRequirement) VersMatchesString() string { |
| if dr.VersMatches != nil { |
| str := "" |
| for _, match := range dr.VersMatches { |
| str += fmt.Sprintf("%s%s", match.CompareType, match.Vers) |
| } |
| return str |
| } else { |
| return "none" |
| } |
| } |
| |
| // Convert the dependency requirement to a string for display |
| func (dr *DependencyRequirement) String() string { |
| return fmt.Sprintf("%s@%s#%s", dr.Name, dr.VersMatchesString(), dr.Stability) |
| } |
| |
| func (dr *DependencyRequirement) SatisfiesCapability( |
| capability *DependencyRequirement) error { |
| if dr.Name != capability.Name { |
| return NewNewtError(fmt.Sprintf("Required capability name %s doesn't match "+ |
| "specified capability name %s", dr.Name, capability.Name)) |
| } |
| |
| for _, versMatch := range dr.VersMatches { |
| if !versMatch.Vers.SatisfiesVersion(capability.VersMatches) { |
| return NewNewtError(fmt.Sprintf("Capability %s doesn't satisfy version "+ |
| "requirement %s", capability, versMatch.Vers)) |
| } |
| } |
| |
| return nil |
| } |
| |
| // Check whether the passed in egg satisfies the current dependency requirement |
| func (dr *DependencyRequirement) SatisfiesDependency(egg *Egg) bool { |
| if egg.FullName != dr.Name { |
| return false |
| } |
| |
| if egg.Version.SatisfiesVersion(dr.VersMatches) { |
| return true |
| } |
| |
| return false |
| } |
| |
| // Convert the dependency requirement to branch name to look for |
| func (dr *DependencyRequirement) BranchName() string { |
| if dr.Stability != "stable" { |
| // XXX should compare to latest |
| return dr.Stability |
| } |
| for _, versMatch := range dr.VersMatches { |
| if versMatch.CompareType == "==" || versMatch.CompareType == "<=" { |
| if versMatch.Vers.Minor == 0 && versMatch.Vers.Revision == 0 { |
| return fmt.Sprintf("%d", versMatch.Vers.Major) |
| } else if versMatch.Vers.Revision == 0 { |
| return fmt.Sprintf("%d.%d", versMatch.Vers.Major, |
| versMatch.Vers.Minor) |
| } else { |
| return fmt.Sprintf("%d.%d.%d", versMatch.Vers.Major, |
| versMatch.Vers.Minor, versMatch.Vers.Revision) |
| } |
| } |
| // XXX What to do with other version comparisons? |
| } |
| return "master" |
| } |
| |
| // Create a New DependencyRequirement structure from the contents of the depReq |
| // string that has been passed in as an argument. |
| func NewDependencyRequirementParseString(depReq string) (*DependencyRequirement, |
| error) { |
| // Allocate dependency requirement |
| dr := &DependencyRequirement{} |
| // Split string into multiple parts, @# |
| // first, get dependency name |
| parts := strings.Split(depReq, "@") |
| if len(parts) == 1 { |
| parts = strings.Split(depReq, "#") |
| dr.Name = parts[0] |
| if len(parts) > 1 { |
| dr.Stability = parts[1] |
| } else { |
| dr.Stability = "stable" |
| } |
| } else if len(parts) == 2 { |
| dr.Name = parts[0] |
| verParts := strings.Split(parts[1], "#") |
| |
| if err := dr.SetVersStr(verParts[0]); err != nil { |
| return nil, err |
| } |
| if len(verParts) == 2 { |
| dr.Stability = verParts[1] |
| } else { |
| dr.Stability = "stable" |
| } |
| } |
| |
| return dr, nil |
| } |
| |
| // Get a map of egg capabilities. The returned map contains the name of the |
| // capability, and its version as the key, and a pointer to the |
| // Capability structure associated with that name. |
| func (egg *Egg) GetCapabilities() ([]*DependencyRequirement, error) { |
| return egg.Capabilities, nil |
| } |
| |
| // Return the egg dependencies for this egg. |
| func (egg *Egg) GetDependencies() ([]*DependencyRequirement, error) { |
| return egg.Deps, nil |
| } |
| |
| func (eggShell *EggShell) GetCapabilities() ([]*DependencyRequirement, error) { |
| return eggShell.Caps, nil |
| } |
| |
| // Return the egg dependencies for this eggShell. |
| func (eggShell *EggShell) GetDependencies() ([]*DependencyRequirement, error) { |
| return eggShell.Deps, nil |
| } |
| |
| // Load a egg's configuration information from the egg config |
| // file. |
| func (egg *Egg) GetIncludes(t *Target) ([]string, error) { |
| // Return the include directories for just this egg |
| incls := []string{ |
| egg.BasePath + "/include/", |
| egg.BasePath + "/include/" + egg.Name + "/arch/" + t.Arch + "/", |
| } |
| |
| return incls, nil |
| } |
| |
| // Load capabilities from a string containing a list of capabilities. |
| // The capability format is expected to be one of: |
| // name@version |
| // name |
| // @param capList An array of capability strings |
| // @return On success error is nil, and a list of capabilities is returned, |
| // on failure error is non-nil |
| func (egg *Egg) loadCaps(capList []string) ([]*DependencyRequirement, error) { |
| if len(capList) == 0 { |
| return nil, nil |
| } |
| |
| // Allocate an array of capabilities |
| caps := make([]*DependencyRequirement, 0) |
| |
| StatusMessage(VERBOSITY_VERBOSE, "Loading capabilities %s", |
| strings.Join(capList, " ")) |
| for _, capItem := range capList { |
| dr, err := NewDependencyRequirementParseString(capItem) |
| if err != nil { |
| return nil, err |
| } |
| |
| caps = append(caps, dr) |
| log.Printf("[DEBUG] Appending new capability egg: %s, cap:%s", |
| egg.Name, dr) |
| } |
| |
| return caps, nil |
| } |
| |
| // Generate a hash of the contents of an egg. This function recursively |
| // processes the contents of a directory, ignoring hidden files and the |
| // bin and obj directories. It returns a hash of all the files, their |
| // contents. |
| func (egg *Egg) GetHash() (string, error) { |
| hash := sha1.New() |
| |
| err := filepath.Walk(egg.BasePath, |
| func(path string, info os.FileInfo, err error) error { |
| name := info.Name() |
| if name == "bin" || name == "obj" || name[0] == '.' { |
| 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 "", NewNewtError(err.Error()) |
| } |
| |
| hashStr := fmt.Sprintf("%x", hash.Sum(nil)) |
| |
| return hashStr, nil |
| } |
| |
| // Load a egg's configuration. This allocates & initializes a fair number of |
| // the main data structures within the egg. |
| func (egg *Egg) LoadConfig(t *Target, force bool) error { |
| if egg.CfgLoaded && !force { |
| return nil |
| } |
| |
| log.Printf("[DEBUG] Loading configuration for egg %s", egg.BasePath) |
| |
| v, err := ReadConfig(egg.BasePath, "egg") |
| if err != nil { |
| return err |
| } |
| |
| egg.FullName = v.GetString("egg.name") |
| egg.Name = filepath.Base(egg.FullName) |
| |
| egg.Version, err = NewVersParseString(v.GetString("egg.vers")) |
| if err != nil { |
| return err |
| } |
| |
| egg.LinkerScript = GetStringIdentities(v, t, "egg.linkerscript") |
| |
| egg.Cflags += GetStringIdentities(v, t, "egg.cflags") |
| egg.Lflags += GetStringIdentities(v, t, "egg.lflags") |
| egg.Aflags += GetStringIdentities(v, t, "egg.aflags") |
| |
| // Append all the identities that this egg exposes to sub-eggs |
| if t != nil { |
| idents := GetStringSliceIdentities(v, t, "egg.identities") |
| t.Identities = append(t.Identities, idents...) |
| } |
| |
| // Load egg dependencies |
| depList := GetStringSliceIdentities(v, t, "egg.deps") |
| if len(depList) > 0 { |
| egg.Deps = make([]*DependencyRequirement, 0, len(depList)) |
| for _, depStr := range depList { |
| log.Printf("[DEBUG] Loading depedency %s from egg %s", depStr, |
| egg.FullName) |
| dr, err := NewDependencyRequirementParseString(depStr) |
| if err != nil { |
| return err |
| } |
| |
| egg.Deps = append(egg.Deps, dr) |
| } |
| } |
| |
| // Load the list of capabilities that this egg exposes |
| egg.Capabilities, err = egg.loadCaps(GetStringSliceIdentities(v, t, |
| "egg.caps")) |
| if err != nil { |
| return err |
| } |
| |
| // Load the list of capabilities that this egg requires |
| egg.ReqCapabilities, err = egg.loadCaps(GetStringSliceIdentities(v, t, |
| "egg.req_caps")) |
| if err != nil { |
| return err |
| } |
| |
| egg.CfgLoaded = true |
| |
| return nil |
| } |
| |
| // Initialize a egg: loads the egg configuration, and sets up egg data |
| // structures. Should only be called from NewEgg |
| func (egg *Egg) Init() error { |
| return nil |
| } |
| |
| // Allocate and initialize a new egg, and return a fully initialized Egg |
| // structure. |
| // @param nest The Nest this egg is located in |
| // @param basePath The path to this egg, within the specified nest |
| // @return On success, error is nil, and a Egg is returned. on failure, |
| // error is not nil. |
| func NewEgg(nest *Nest, basePath string) (*Egg, error) { |
| egg := &Egg{ |
| BasePath: basePath, |
| Nest: nest, |
| } |
| |
| if err := egg.Init(); err != nil { |
| return nil, err |
| } |
| |
| return egg, nil |
| } |
| |
| func (egg *Egg) TestBinName() string { |
| return "test_" + egg.Name |
| } |
| |
| /* |
| * Download egg from a clutch and stick it to nest. |
| */ |
| func (eggShell *EggShell) Install(eggMgr *Clutch, branch string) error { |
| downloaded, err := eggMgr.InstallEgg(eggShell.FullName, branch, nil) |
| for _, remoteNest := range downloaded { |
| remoteNest.Remove() |
| } |
| return err |
| } |
| |
| func (egg *Egg) Remove() error { |
| return os.RemoveAll(egg.BasePath) |
| } |