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 != "" {