This is an automated email from the ASF dual-hosted git repository. ccollins pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/mynewt-newt.git
commit d1384bc57d0ab759519f629fdb42c60f33d2f8ef Author: Christopher Collins <ccoll...@apache.org> AuthorDate: Mon Oct 15 18:49:36 2018 -0700 logcfg: Allow logs to be defined in `syscfg.yml` A package can define logs in its `syscfg.yml` file under the heading of `syscfg.logs`. During the build process, newt generates a C header file called `logcfg/logcfg.h` containing macros for using each generated log. Each entry in the `syscfg.logs` map has the following structure: <log-name>: module: <module> level: <level> guest: <true/false> (optional) For example: syscfg.logs: MY_LOG: module: MYNEWT_VAL(MY_LOG_MODULE) level: MYNEWT_VAL(MY_LOG_LEVEL) It is recommended, though not required, that the module and level fields refer to syscfg settings, as above. This allows the target to reconfigure a package's log without modifying the package itself. The above log definition generates the following code in `logcfg/logcfg.h` (assuming `MY_LOG_MODULE is set to LOG_LEVEL_ERROR (3)): #define MY_LOG_DEBUG(logcfg_lvl_, ...) IGNORE(__VA_ARGS__) #define MY_LOG_INFO(logcfg_lvl_, ...) IGNORE(__VA_ARGS__) #define MY_LOG_WARN(logcfg_lvl_, ...) IGNORE(__VA_ARGS__) #define MY_LOG_ERROR(logcfg_lvl_, ...) MODLOG_ ## logcfg_lvl_(MYNEWT_VAL(MY_LOG_MODULE), __VA_ARGS__) #define MY_LOG_CRITICAL(logcfg_lvl_, ...) MODLOG_ ## logcfg_lvl_(MYNEWT_VAL(MY_LOG_MODULE), __VA_ARGS__) If two or more logs have module values that resolve to the same number, newt aborts the build with an error: Error: Log module conflicts detected: Module=100 Log=MY_LOG Package=sys/coredump Module=100 Log=YOUR_LOG Package=sys/coredump Resolve the problem by assigning unique module IDs to each log, or by setting the "guest" flag of all but one. The "guest" flag, when set, allows a log to use the same module as another without generating an error. --- newt/builder/targetbuild.go | 9 ++ newt/logcfg/logcfg.go | 363 ++++++++++++++++++++++++++++++++++++++++++++ newt/resolve/resolve.go | 10 +- 3 files changed, 381 insertions(+), 1 deletion(-) diff --git a/newt/builder/targetbuild.go b/newt/builder/targetbuild.go index e4ccc21..519632c 100644 --- a/newt/builder/targetbuild.go +++ b/newt/builder/targetbuild.go @@ -101,6 +101,9 @@ func NewTargetTester(target *target.Target, injectedSettings: map[string]string{}, } + // Indicate that this version of newt supports the generated logcfg header. + t.InjectSetting("NEWT_FEATURE_LOGCFG", "1") + return t, nil } @@ -219,6 +222,12 @@ func (t *TargetBuilder) validateAndWriteCfg() error { return err } + if err := t.res.LCfg.EnsureWritten( + GeneratedIncludeDir(t.target.Name())); err != nil { + + return err + } + return nil } diff --git a/newt/logcfg/logcfg.go b/newt/logcfg/logcfg.go new file mode 100644 index 0000000..1cd5b27 --- /dev/null +++ b/newt/logcfg/logcfg.go @@ -0,0 +1,363 @@ +/** + * 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 logcfg + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strings" + + log "github.com/Sirupsen/logrus" + "github.com/spf13/cast" + + "mynewt.apache.org/newt/newt/newtutil" + "mynewt.apache.org/newt/newt/pkg" + "mynewt.apache.org/newt/newt/syscfg" + "mynewt.apache.org/newt/util" +) + +const HEADER_PATH = "logcfg/logcfg.h" + +type LogSetting struct { + // The exact text specified as the YAML map key. + Text string + + // If this setting refers to a syscfg setting via the `MYNEWT_VAL(...)` + // notation, this contains the name of the setting. Otherwise, "". + RefName string + + // The setting value, after setting references are resolved. + Value string +} + +type Log struct { + // Log name; equal to the name of the YAML map that defines the log. + Name string + + // The package that defines the log. + Source *pkg.LocalPackage + + // The log's numeric module ID. + Module LogSetting + + // The level assigned to this log. + Level LogSetting +} + +// Map of: [log-name] => log +type LogMap map[string]Log + +// The log configuration of the target. +type LCfg struct { + // [log-name] => log + Logs LogMap + + // Strings describing errors encountered while parsing the log config. + InvalidSettings []string + + // Contains sets of logs with conflicting module IDs. + // [module-ID] => <slice-of-logs-with-module-id> + ModuleConflicts map[int][]Log +} + +// Maps numeric log levels to their string representations. Used when +// generating the C log macros. +var logLevelNames = []string{ + 0: "DEBUG", + 1: "INFO", + 2: "WARN", + 3: "ERROR", + 4: "CRITICAL", +} + +func LogLevelString(level int) string { + if level < 0 || level >= len(logLevelNames) { + return "???" + } + + return logLevelNames[level] +} + +func NewLCfg() LCfg { + return LCfg{ + Logs: map[string]Log{}, + ModuleConflicts: map[int][]Log{}, + } +} + +// IntVal Extracts a log setting's integer value. +func (ls *LogSetting) IntVal() (int, error) { + iv, err := util.AtoiNoOct(ls.Value) + if err != nil { + return 0, util.ChildNewtError(err) + } + + return iv, nil +} + +// Constructs a log setting from a YAML string. +func resolveLogVal(s string, cfg *syscfg.Cfg) (LogSetting, error) { + refName, val, err := cfg.ExpandRef(s) + if err != nil { + return LogSetting{}, + util.FmtNewtError("value \"%s\" references undefined setting", s) + } + + return LogSetting{ + Text: s, + RefName: refName, + Value: val, + }, nil +} + +// Parses a single log definition from a YAML map. The `logMapItf` parameter +// should be a map with the following elements: +// "module": <module-string> +// "level": <level-string> +func parseOneLog(name string, lpkg *pkg.LocalPackage, logMapItf interface{}, + cfg *syscfg.Cfg) (Log, error) { + + cl := Log{ + Name: name, + Source: lpkg, + } + + logMap := cast.ToStringMapString(logMapItf) + if logMap == nil { + return cl, util.FmtNewtError( + "\"%s\" missing required field \"module\"", name) + } + + modStr := logMap["module"] + if modStr == "" { + return cl, util.FmtNewtError( + "\"%s\" missing required field \"module\"", name) + } + mod, err := resolveLogVal(modStr, cfg) + if err != nil { + return cl, util.FmtNewtError( + "\"%s\" contains invalid \"module\": %s", + name, err.Error()) + } + if _, err := mod.IntVal(); err != nil { + return cl, util.FmtNewtError( + "\"%s\" contains invalid \"module\": %s", name, err.Error()) + } + + levelStr := logMap["level"] + if levelStr == "" { + return cl, util.FmtNewtError( + "\"%s\" missing required field \"level\"", name) + } + level, err := resolveLogVal(levelStr, cfg) + if err != nil { + return cl, util.FmtNewtError( + "\"%s\" contains invalid \"level\": %s", + name, err.Error()) + } + if _, err := level.IntVal(); err != nil { + return cl, util.FmtNewtError( + "\"%s\" contains invalid \"level\": %s", name, err.Error()) + } + + cl.Module = mod + cl.Level = level + + return cl, nil +} + +// Reads all the logs defined by the specified package. The log definitions +// are read from the `syscfg.logs` map in the package's `syscfg.yml` file. +func (lcfg *LCfg) readOnePkg(lpkg *pkg.LocalPackage, cfg *syscfg.Cfg) { + lsettings := cfg.AllSettingsForLpkg(lpkg) + logMaps := lpkg.SyscfgY.GetValStringMap("syscfg.logs", lsettings) + for name, logMapItf := range logMaps { + cl, err := parseOneLog(name, lpkg, logMapItf, cfg) + if err != nil { + lcfg.InvalidSettings = + append(lcfg.InvalidSettings, strings.TrimSpace(err.Error())) + } else { + lcfg.Logs[cl.Name] = cl + } + } +} + +// Searches the log configuration for logs with identical module IDs. The log +// configuration object is populated with the results. +func (lcfg *LCfg) detectModuleConflicts() { + m := map[int][]Log{} + + for _, l := range lcfg.Logs { + intMod, _ := l.Module.IntVal() + m[intMod] = append(m[intMod], l) + } + + for mod, logs := range m { + if len(logs) > 1 { + for _, l := range logs { + lcfg.ModuleConflicts[mod] = + append(lcfg.ModuleConflicts[mod], l) + } + } + } +} + +// Reads all log definitions for each of the specified packages. The +// returned LCfg object is populated with the result of this operation. +func Read(lpkgs []*pkg.LocalPackage, cfg *syscfg.Cfg) LCfg { + lcfg := NewLCfg() + + for _, lpkg := range lpkgs { + lcfg.readOnePkg(lpkg, cfg) + } + + lcfg.detectModuleConflicts() + + return lcfg +} + +// If any errors were encountered while parsing log definitions, this function +// returns a string indicating the errors. If no errors were encountered, "" +// is returned. +func (lcfg *LCfg) ErrorText() string { + str := "" + + if len(lcfg.InvalidSettings) > 0 { + str += "Invalid log definitions detected:" + for _, e := range lcfg.InvalidSettings { + str += "\n " + e + } + } + + if len(lcfg.ModuleConflicts) > 0 { + str += "Log module conflicts detected:\n" + for mod, logs := range lcfg.ModuleConflicts { + for _, l := range logs { + str += fmt.Sprintf(" Module=%d Log=%s Package=%s\n", + mod, l.Name, l.Source.FullName()) + } + } + + str += + "\nResolve the problem by assigning unique module IDs to each log." + } + + return str +} + +// Retrieves a sorted slice of logs from the receiving log configuration. +func (lcfg *LCfg) sortedLogs() []Log { + names := make([]string, 0, len(lcfg.Logs)) + + for n, _ := range lcfg.Logs { + names = append(names, n) + } + sort.Strings(names) + + logs := make([]Log, 0, len(names)) + for _, n := range names { + logs = append(logs, lcfg.Logs[n]) + } + + return logs +} + +// Writes a no-op stub log C macro definition. +func writeLogStub(logName string, levelStr string, w io.Writer) { + fmt.Fprintf(w, "#define %s_%s(...) IGNORE(__VA_ARGS__)\n", + logName, levelStr) +} + +// Writes a log C macro definition. +func writeLogMacro(logName string, module int, levelStr string, w io.Writer) { + fmt.Fprintf(w, + "#define %s_%s(...) MODLOG_%s(%d, __VA_ARGS__)\n", + logName, levelStr, levelStr, module) +} + +// Write log C macro definitions for each log in the log configuration. +func (lcfg *LCfg) writeLogMacros(w io.Writer) { + logs := lcfg.sortedLogs() + for _, l := range logs { + fmt.Fprintf(w, "\n") + + levelInt, _ := util.AtoiNoOct(l.Level.Value) + for i, levelStr := range logLevelNames { + if i < levelInt { + writeLogStub(l.Name, levelStr, w) + } else { + modInt, _ := l.Module.IntVal() + writeLogMacro(l.Name, modInt, levelStr, w) + } + } + } +} + +// Writes a logcfg header file to the specified writer. +func (lcfg *LCfg) write(w io.Writer) { + fmt.Fprintf(w, newtutil.GeneratedPreamble()) + + fmt.Fprintf(w, "#ifndef H_MYNEWT_LOGCFG_\n") + fmt.Fprintf(w, "#define H_MYNEWT_LOGCFG_\n\n") + + if len(lcfg.Logs) > 0 { + fmt.Fprintf(w, "#include \"modlog/modlog.h\"\n") + fmt.Fprintf(w, "#include \"log_common/log_common.h\"\n") + + lcfg.writeLogMacros(w) + fmt.Fprintf(w, "\n") + } + + fmt.Fprintf(w, "#endif\n") +} + +// Ensures an up-to-date logcfg header is written for the target. +func (lcfg *LCfg) EnsureWritten(includeDir string) error { + buf := bytes.Buffer{} + lcfg.write(&buf) + + path := includeDir + "/" + HEADER_PATH + + writeReqd, err := util.FileContentsChanged(path, buf.Bytes()) + if err != nil { + return err + } + if !writeReqd { + log.Debugf("logcfg unchanged; not writing header file (%s).", path) + return nil + } + + log.Debugf("logcfg changed; writing header file (%s).", path) + + if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { + return util.NewNewtError(err.Error()) + } + + if err := ioutil.WriteFile(path, buf.Bytes(), 0644); err != nil { + return util.NewNewtError(err.Error()) + } + + return nil +} diff --git a/newt/resolve/resolve.go b/newt/resolve/resolve.go index dcdb0a3..b5fa87a 100644 --- a/newt/resolve/resolve.go +++ b/newt/resolve/resolve.go @@ -27,11 +27,12 @@ import ( log "github.com/Sirupsen/logrus" "mynewt.apache.org/newt/newt/flash" + "mynewt.apache.org/newt/newt/logcfg" "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" + "mynewt.apache.org/newt/util" ) // Represents a supplied API. @@ -58,6 +59,7 @@ type Resolver struct { injectedSettings map[string]string flashMap flash.FlashMap cfg syscfg.Cfg + lcfg logcfg.LCfg // [api-name][api-supplier] apiConflicts map[string]map[*ResolvePackage]struct{} @@ -104,6 +106,7 @@ type ApiConflict struct { // The result of resolving a target's configuration, APIs, and dependencies. type Resolution struct { Cfg syscfg.Cfg + LCfg logcfg.LCfg ApiMap map[string]*ResolvePackage UnsatisfiedApis map[string][]*ResolvePackage ApiConflicts []ApiConflict @@ -540,6 +543,9 @@ func (r *Resolver) resolveDepsAndCfg() error { return err } + lpkgs := RpkgSliceToLpkgSlice(r.rpkgSlice()) + r.lcfg = logcfg.Read(lpkgs, &r.cfg) + // Log the final syscfg. r.cfg.Log() @@ -632,6 +638,7 @@ func ResolveFull( res := newResolution() res.Cfg = r.cfg + res.LCfg = r.lcfg // Determine which package satisfies each API and which APIs are // unsatisfied. @@ -748,6 +755,7 @@ func (res *Resolution) ErrorText() string { } str += res.Cfg.ErrorText() + str += res.LCfg.ErrorText() str = strings.TrimSpace(str) if str != "" {