| /** |
| * 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 sysinit |
| |
| import ( |
| "bytes" |
| "fmt" |
| "github.com/spf13/cast" |
| "io" |
| "mynewt.apache.org/newt/util" |
| "sort" |
| "strings" |
| |
| "mynewt.apache.org/newt/newt/newtutil" |
| "mynewt.apache.org/newt/newt/pkg" |
| "mynewt.apache.org/newt/newt/stage" |
| "mynewt.apache.org/newt/newt/syscfg" |
| ) |
| |
| type SysinitCfg struct { |
| // Sorted in call order (stage-num,function-name). |
| StageFuncs []stage.StageFunc |
| |
| // Strings describing errors encountered while parsing the sysinit config. |
| InvalidSettings []string |
| |
| // Contains sets of entries with conflicting function names. |
| // [function-name] => <slice-of-stages-with-function-name> |
| Conflicts map[string][]stage.StageFunc |
| } |
| |
| func (scfg *SysinitCfg) readOnePkg(lpkg *pkg.LocalPackage, cfg *syscfg.Cfg) { |
| settings := cfg.AllSettingsForLpkg(lpkg) |
| initMap := lpkg.InitFuncs(settings) |
| for name, stageDef := range initMap { |
| var sf stage.StageFunc |
| var err error |
| |
| switch stageDef.(type) { |
| default: |
| stageStr := cast.ToString(stageDef) |
| |
| if stage.ValIsDep(stageStr) { |
| stageDeps := strings.Split(stageStr, ",") |
| sf, err = stage.NewStageFuncMultiDeps(name, stageDeps, lpkg, cfg) |
| } else { |
| sf, err = stage.NewStageFunc(name, stageStr, lpkg, cfg) |
| } |
| case []interface{}: |
| var stageDeps []string |
| |
| for _, stageDepIf := range stageDef.([]interface{}) { |
| stageDeps = append(stageDeps, cast.ToString(stageDepIf)) |
| } |
| |
| sf, err = stage.NewStageFuncMultiDeps(name, stageDeps, lpkg, cfg) |
| } |
| |
| if err != nil { |
| scfg.InvalidSettings = append(scfg.InvalidSettings, err.Error()) |
| } else { |
| scfg.StageFuncs = append(scfg.StageFuncs, sf) |
| } |
| } |
| } |
| |
| // Searches the sysinit configuration for entries with identical function |
| // names. The sysinit configuration object is populated with the results. |
| func (scfg *SysinitCfg) detectConflicts() { |
| m := map[string][]stage.StageFunc{} |
| |
| for _, sf := range scfg.StageFuncs { |
| m[sf.Name] = append(m[sf.Name], sf) |
| } |
| |
| for name, sfs := range m { |
| if len(sfs) > 1 { |
| scfg.Conflicts[name] = sfs |
| } |
| } |
| } |
| |
| func stageFuncResolve(sfRef *stage.StageFunc, stack *[]stage.StageFunc) error { |
| if sfRef.Resolved { |
| return nil |
| } |
| if sfRef.Resolving { |
| return util.FmtNewtError("Circular dependency detected while resolving \"%s (%s)\".", |
| sfRef.Name, sfRef.Pkg.FullName()) |
| } |
| |
| sfRef.Resolving = true |
| for _, sfDepRef := range sfRef.Deps { |
| err := stageFuncResolve(sfDepRef, stack) |
| if err != nil { |
| return err |
| } |
| } |
| sfRef.Resolving = false |
| |
| sfRef.Resolved = true |
| *stack = append([]stage.StageFunc{*sfRef}, *stack...) |
| |
| return nil |
| } |
| |
| func ResolveStageFuncsOrder(sfs []stage.StageFunc) ([]stage.StageFunc, error) { |
| var nodes []*stage.StageFunc |
| var nodesQ []*stage.StageFunc |
| nodesByStage := make(map[int][]*stage.StageFunc) |
| nodesByName := make(map[string]*stage.StageFunc) |
| |
| for idx, _ := range sfs { |
| sfRef := &sfs[idx] |
| nodesByName[sfRef.Name] = sfRef |
| if len(sfRef.Stage.Befores) == 0 && len(sfRef.Stage.Afters) == 0 { |
| stage, _ := sfRef.Stage.IntVal() |
| nodesByStage[stage] = append(nodesByStage[stage], sfRef) |
| } else { |
| nodesQ = append(nodesQ, sfRef) |
| } |
| } |
| |
| var stages []int |
| for stage := range nodesByStage { |
| stages = append(stages, stage) |
| } |
| sort.Ints(stages) |
| |
| // Lexicographically sort nodes in each stage, then build the nodes stack. |
| // This helps ensure that sysinit order is reproducable and deterministic |
| for stageIndex, _ := range stages { |
| lsfs := nodesByStage[stages[stageIndex]] |
| sort.Slice(lsfs, func(i int, j int) bool { |
| a := lsfs[i] |
| b := lsfs[j] |
| |
| if strings.Compare(a.Name, b.Name) == -1 { |
| return false |
| } |
| return true |
| }) |
| nodes = append(nodes, nodesByStage[stages[stageIndex]]...) |
| } |
| |
| sort.Slice(nodesQ, func(i int, j int) bool { |
| a := nodesQ[i] |
| b := nodesQ[j] |
| |
| if strings.Compare(a.Name, b.Name) == -1 { |
| return false |
| } |
| return true |
| }) |
| |
| // Put nodes without stages first, so they are resolved and put to |
| // stack first - we do not want them to precede all nodes with stages. |
| // While technically correct, it's better not to start sysinit with |
| // nodes that do not have any stage since this will likely happen |
| // before os init packages. |
| nodes = append(nodesQ, nodes...) |
| |
| // Add implicit dependencies for nodes with stages. We need to add |
| // direct dependencies between each node of stage X to each node of |
| // stage Y to make sure they can be resolved properly and reordered |
| // if needed due to other dependencies. |
| sfsPrev := nodesByStage[stages[0]] |
| stages = stages[1:] |
| for _, stage := range stages { |
| sfsCurr := nodesByStage[stage] |
| |
| for _, sfsP := range sfsPrev { |
| for _, sfsC := range sfsCurr { |
| sfsP.Deps = append(sfsP.Deps, sfsC) |
| // Keep separate list of implicit dependencies |
| // This is only used for Graphviz output |
| sfsP.DepsI = append(sfsP.DepsI, sfsC) |
| } |
| } |
| |
| sfsPrev = sfsCurr |
| } |
| |
| // Now add other dependencies, i.e. $after and $before |
| for _, sf := range nodesQ { |
| for _, depStr := range sf.Stage.Afters { |
| depSf := nodesByName[depStr] |
| if depSf == nil { |
| return []stage.StageFunc{}, |
| util.FmtNewtError("Unknown depdendency (\"%s\") for \"%s (%s)\".", |
| depStr, sf.Name, sf.Pkg.FullName()) |
| } |
| depSf.Deps = append(depSf.Deps, sf) |
| } |
| for _, depStr := range sf.Stage.Befores { |
| depSf := nodesByName[depStr] |
| if depSf == nil { |
| return []stage.StageFunc{}, |
| util.FmtNewtError("Unknown depdendency (\"%s\") for \"%s (%s)\".", |
| depStr, sf.Name, sf.Pkg.FullName()) |
| } |
| sf.Deps = append(sf.Deps, depSf) |
| } |
| } |
| |
| // Now we can resolve order of functions by sorting dependency graph |
| // topologically. This will also detect circular dependencies. |
| sfs = []stage.StageFunc{} |
| for _, sfRef := range nodes { |
| err := stageFuncResolve(sfRef, &sfs) |
| if err != nil { |
| return []stage.StageFunc{}, err |
| } |
| } |
| |
| return sfs, nil |
| } |
| |
| func Read(lpkgs []*pkg.LocalPackage, cfg *syscfg.Cfg) SysinitCfg { |
| scfg := SysinitCfg{ |
| Conflicts: map[string][]stage.StageFunc{}, |
| } |
| |
| for _, lpkg := range lpkgs { |
| scfg.readOnePkg(lpkg, cfg) |
| } |
| |
| scfg.detectConflicts() |
| |
| // Don't try to resolve order if there are name conflicts since that |
| // process depends on unique names for each function and could return |
| // other confusing errors. |
| if len(scfg.Conflicts) > 0 { |
| return scfg |
| } |
| |
| var err error |
| scfg.StageFuncs, err = ResolveStageFuncsOrder(scfg.StageFuncs) |
| if err != nil { |
| scfg.InvalidSettings = append(scfg.InvalidSettings, err.Error()) |
| } |
| |
| return scfg |
| } |
| |
| func (scfg *SysinitCfg) filter(lpkgs []*pkg.LocalPackage) []stage.StageFunc { |
| m := make(map[*pkg.LocalPackage]struct{}, len(lpkgs)) |
| |
| for _, lpkg := range lpkgs { |
| m[lpkg] = struct{}{} |
| } |
| |
| filtered := []stage.StageFunc{} |
| for _, sf := range scfg.StageFuncs { |
| if _, ok := m[sf.Pkg]; ok { |
| filtered = append(filtered, sf) |
| } |
| } |
| |
| return filtered |
| } |
| |
| // If any errors were encountered while parsing sysinit definitions, this |
| // function returns a string indicating the errors. If no errors were |
| // encountered, "" is returned. |
| func (scfg *SysinitCfg) ErrorText() string { |
| str := "" |
| |
| if len(scfg.InvalidSettings) > 0 { |
| str += "Invalid sysinit definitions detected:" |
| for _, e := range scfg.InvalidSettings { |
| str += "\n " + e |
| } |
| } |
| |
| if len(scfg.Conflicts) > 0 { |
| str += "Sysinit function name conflicts detected:\n" |
| for name, sfs := range scfg.Conflicts { |
| for _, sf := range sfs { |
| str += fmt.Sprintf(" Function=%s Package=%s\n", |
| name, sf.Pkg.FullName()) |
| } |
| } |
| |
| str += "\nResolve the problem by assigning unique function names " + |
| "to each entry." |
| } |
| |
| return str |
| } |
| |
| func (scfg *SysinitCfg) write(lpkgs []*pkg.LocalPackage, isLoader bool, |
| w io.Writer) error { |
| |
| var sfs []stage.StageFunc |
| if lpkgs == nil { |
| sfs = scfg.StageFuncs |
| } else { |
| sfs = scfg.filter(lpkgs) |
| } |
| |
| fmt.Fprintf(w, newtutil.GeneratedPreamble()) |
| |
| if isLoader { |
| fmt.Fprintf(w, "#if SPLIT_LOADER\n\n") |
| } else { |
| fmt.Fprintf(w, "#if !SPLIT_LOADER\n\n") |
| } |
| |
| stage.WritePrototypes(sfs, w) |
| |
| var fnName string |
| if isLoader { |
| fnName = "sysinit_loader" |
| } else { |
| fnName = "sysinit_app" |
| } |
| |
| fmt.Fprintf(w, "\n") |
| fmt.Fprintf(w, "void\n%s(void)\n{\n", fnName) |
| |
| stage.WriteCalls(sfs, "", w) |
| |
| fmt.Fprintf(w, "}\n\n") |
| fmt.Fprintf(w, "#endif\n") |
| |
| return nil |
| } |
| |
| func (scfg *SysinitCfg) EnsureWritten(lpkgs []*pkg.LocalPackage, srcDir string, |
| targetName string, isLoader bool) error { |
| |
| buf := bytes.Buffer{} |
| if err := scfg.write(lpkgs, isLoader, &buf); err != nil { |
| return err |
| } |
| |
| var path string |
| if isLoader { |
| path = fmt.Sprintf("%s/%s-sysinit-loader.c", srcDir, targetName) |
| } else { |
| path = fmt.Sprintf("%s/%s-sysinit-app.c", srcDir, targetName) |
| } |
| |
| return stage.EnsureWritten(path, buf.Bytes()) |
| } |