You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by cc...@apache.org on 2018/11/01 18:40:03 UTC
[mynewt-newt] 02/09: logcfg: Allow logs to be defined in
`syscfg.yml`
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 <cc...@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 != "" {