This is an automated email from the ASF dual-hosted git repository.

sjanc pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mynewt-newt.git

commit 6668f24c4316c681856f6d0bb3a28020fc3b94f4
Author: Szymon Janc <[email protected]>
AuthorDate: Mon Oct 19 17:53:26 2026 +0200

    newt/resolve: Fix hang on non-converging syscfg
    
    Dependency and syscfg resolution is repeated until the configuration
    stops changing. Contradicting conditional overrides never converge,
    e.g. a setting X overridden to 0 under the condition X, or a setting
    whose override removes the package defining the setting that the
    override depends on. newt then looped forever in "target config show",
    "target dep", "build" etc.
    
    Record the resolver state (settings and packages) after each iteration
    and fail with an error listing the oscillating settings and packages
    when a previously seen state repeats.
    
    Assisted-by: Claude:claude-opus-5-5
---
 newt/resolve/resolve.go                            | 105 +++++++++++++++++++++
 newt/syscfg_stability_test.go                      |  64 +++++++++++++
 .../testdata/syscfg_stability/apps/osc_dep/pkg.yml |  26 +++++
 .../syscfg_stability/apps/osc_dep/syscfg.yml       |  22 +++++
 .../syscfg_stability/apps/osc_self/pkg.yml         |  24 +++++
 .../syscfg_stability/apps/osc_self/syscfg.yml      |  22 +++++
 .../repos/apache-mynewt-core/sys/osc/pkg.yml       |  22 +++++
 .../repos/apache-mynewt-core/sys/osc/syscfg.yml    |  27 ++++++
 .../repos/apache-mynewt-core/sys/oscbar/pkg.yml    |  22 +++++
 .../repos/apache-mynewt-core/sys/oscbar/syscfg.yml |  24 +++++
 .../syscfg_stability/targets/osc_dep/pkg.yml       |  22 +++++
 .../syscfg_stability/targets/osc_dep/target.yml    |  23 +++++
 .../syscfg_stability/targets/osc_self/pkg.yml      |  22 +++++
 .../syscfg_stability/targets/osc_self/target.yml   |  23 +++++
 14 files changed, 448 insertions(+)

diff --git a/newt/resolve/resolve.go b/newt/resolve/resolve.go
index 4678ea6a..a12858b1 100644
--- a/newt/resolve/resolve.go
+++ b/newt/resolve/resolve.go
@@ -1023,6 +1023,98 @@ func (r *Resolver) resolveDeps() ([]*ResolvePackage, 
error) {
        return rpkgs, nil
 }
 
+// A snapshot of the resolver state used to detect configurations that never
+// converge.
+type resolveState struct {
+       // Setting name => value.
+       settings map[string]string
+
+       // Full names of the resolved packages.
+       pkgs map[string]struct{}
+
+       // Unique textual representation of the state.
+       key string
+}
+
+func (r *Resolver) curState() resolveState {
+       st := resolveState{
+               settings: make(map[string]string, len(r.cfg.Settings)),
+               pkgs:     make(map[string]struct{}, len(r.pkgMap)),
+       }
+
+       lines := make([]string, 0, len(r.cfg.Settings)+len(r.pkgMap))
+       for k, entry := range r.cfg.Settings {
+               st.settings[k] = entry.Value
+               lines = append(lines, fmt.Sprintf("s:%s=%s:%d",
+                       k, entry.Value, len(entry.History)))
+       }
+       for _, rpkg := range r.pkgMap {
+               name := rpkg.Lpkg.FullName()
+               st.pkgs[name] = struct{}{}
+               lines = append(lines, "p:"+name)
+       }
+       sort.Strings(lines)
+       st.key = strings.Join(lines, "\n")
+
+       return st
+}
+
+// Builds an error describing a resolution that oscillates between the
+// specified states without ever converging.
+func oscillationError(cycle []resolveState) error {
+       settingVals := map[string][]string{}
+       pkgSeen := map[string]int{}
+
+       for _, st := range cycle {
+               for k := range st.settings {
+                       settingVals[k] = nil
+               }
+               for name := range st.pkgs {
+                       pkgSeen[name]++
+               }
+       }
+
+       var settingLines []string
+       for k := range settingVals {
+               var vals []string
+               differ := false
+               for i, st := range cycle {
+                       v, ok := st.settings[k]
+                       if !ok {
+                               v = "(undefined)"
+                       }
+                       if i > 0 && v != vals[0] {
+                               differ = true
+                       }
+                       vals = append(vals, v)
+               }
+               if differ {
+                       settingLines = append(settingLines,
+                               fmt.Sprintf("    %s: %s", k, strings.Join(vals, 
" -> ")))
+               }
+       }
+       sort.Strings(settingLines)
+
+       var pkgLines []string
+       for name, count := range pkgSeen {
+               if count != len(cycle) {
+                       pkgLines = append(pkgLines, "    "+name)
+               }
+       }
+       sort.Strings(pkgLines)
+
+       str := "Syscfg does not converge; conditional settings or " +
+               "dependencies contradict each other"
+       if len(settingLines) > 0 {
+               str += "\nOscillating settings:\n" + strings.Join(settingLines, 
"\n")
+       }
+       if len(pkgLines) > 0 {
+               str += "\nOscillating packages:\n" + strings.Join(pkgLines, 
"\n")
+       }
+
+       return util.NewNewtError(str)
+}
+
 // Performs a set of resolution actions:
 // 1. Calculates the system configuration (syscfg).
 // 2. Determines which packages satisfy which API requirements.
@@ -1032,6 +1124,12 @@ func (r *Resolver) resolveDepsAndCfg() error {
                return err
        }
 
+       // States seen so far; used to detect a configuration that never
+       // converges (e.g., a setting whose override disables its own
+       // condition).
+       var states []resolveState
+       stateIdx := map[string]int{}
+
        for {
                cfgChanged, err := r.reloadCfg()
                if err != nil {
@@ -1054,6 +1152,13 @@ func (r *Resolver) resolveDepsAndCfg() error {
                if !cfgChanged {
                        break
                }
+
+               st := r.curState()
+               if i, ok := stateIdx[st.key]; ok {
+                       return oscillationError(states[i:])
+               }
+               stateIdx[st.key] = len(states)
+               states = append(states, st)
        }
 
        // Now that the final set of packages is known, determine which ones
diff --git a/newt/syscfg_stability_test.go b/newt/syscfg_stability_test.go
index 59f5e57b..672d207a 100644
--- a/newt/syscfg_stability_test.go
+++ b/newt/syscfg_stability_test.go
@@ -20,9 +20,11 @@
 package main
 
 import (
+       "bytes"
        "path/filepath"
        "strings"
        "testing"
+       "time"
 )
 
 var syscfgStabilityDir = filepath.Join("testdata", "syscfg_stability")
@@ -86,3 +88,65 @@ func TestSyscfgStabilityExtRepoTarget(t *testing.T) {
                }
        }
 }
+
+// syscfgStabilityRunTimeout runs newt and kills it if it does not finish
+// within the timeout. Returns combined output and whether newt timed out.
+func syscfgStabilityRunTimeout(t *testing.T, dir string,
+       timeout time.Duration, args ...string) (string, bool) {
+
+       t.Helper()
+
+       cmd := newtExecCmd(dir, nil, args...)
+       var out bytes.Buffer
+       cmd.Stdout = &out
+       cmd.Stderr = &out
+
+       if err := cmd.Start(); err != nil {
+               t.Fatal(err)
+       }
+
+       done := make(chan struct{})
+       go func() {
+               cmd.Wait()
+               close(done)
+       }()
+
+       select {
+       case <-done:
+               return out.String(), false
+       case <-time.After(timeout):
+               cmd.Process.Kill()
+               <-done
+               return out.String(), true
+       }
+}
+
+// Conditional overrides that contradict each other are reported as an
+// error instead of making newt loop forever.
+func TestSyscfgStabilityOscillation(t *testing.T) {
+       tests := []struct {
+               target string
+               msgs   []string
+       }{
+               {"osc_self", []string{"OSC_X: "}},
+               {"osc_dep", []string{"OSC_Y: ", 
"@apache-mynewt-core/sys/oscbar"}},
+       }
+
+       for _, tc := range tests {
+               t.Run(tc.target, func(t *testing.T) {
+                       out, timedOut := syscfgStabilityRunTimeout(t, 
syscfgStabilityDir,
+                               30*time.Second, "target", "config", "show", 
tc.target)
+                       if timedOut {
+                               t.Fatalf("newt target config show did not 
finish\n%s", out)
+                       }
+                       syscfgStabilityCheckNoPanic(t, out)
+
+                       msgs := append([]string{"Syscfg does not converge"}, 
tc.msgs...)
+                       for _, msg := range msgs {
+                               if !strings.Contains(out, msg) {
+                                       t.Errorf("missing \"%s\" in 
output\n%s", msg, out)
+                               }
+                       }
+               })
+       }
+}
diff --git a/newt/testdata/syscfg_stability/apps/osc_dep/pkg.yml 
b/newt/testdata/syscfg_stability/apps/osc_dep/pkg.yml
new file mode 100644
index 00000000..6762e7d3
--- /dev/null
+++ b/newt/testdata/syscfg_stability/apps/osc_dep/pkg.yml
@@ -0,0 +1,26 @@
+#
+# 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.
+#
+
+
+pkg.name: apps/osc_dep
+pkg.type: app
+pkg.deps:
+    - "@apache-mynewt-core/sys/osc"
+pkg.deps.'!OSC_Y':
+    - "@apache-mynewt-core/sys/oscbar"
diff --git a/newt/testdata/syscfg_stability/apps/osc_dep/syscfg.yml 
b/newt/testdata/syscfg_stability/apps/osc_dep/syscfg.yml
new file mode 100644
index 00000000..999e8913
--- /dev/null
+++ b/newt/testdata/syscfg_stability/apps/osc_dep/syscfg.yml
@@ -0,0 +1,22 @@
+#
+# 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.
+#
+
+
+syscfg.vals.OSC_BARDEF:
+    OSC_Y: 1
diff --git a/newt/testdata/syscfg_stability/apps/osc_self/pkg.yml 
b/newt/testdata/syscfg_stability/apps/osc_self/pkg.yml
new file mode 100644
index 00000000..62008371
--- /dev/null
+++ b/newt/testdata/syscfg_stability/apps/osc_self/pkg.yml
@@ -0,0 +1,24 @@
+#
+# 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.
+#
+
+
+pkg.name: apps/osc_self
+pkg.type: app
+pkg.deps:
+    - "@apache-mynewt-core/sys/osc"
diff --git a/newt/testdata/syscfg_stability/apps/osc_self/syscfg.yml 
b/newt/testdata/syscfg_stability/apps/osc_self/syscfg.yml
new file mode 100644
index 00000000..990b6307
--- /dev/null
+++ b/newt/testdata/syscfg_stability/apps/osc_self/syscfg.yml
@@ -0,0 +1,22 @@
+#
+# 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.
+#
+
+
+syscfg.vals.OSC_X:
+    OSC_X: 0
diff --git 
a/newt/testdata/syscfg_stability/repos/apache-mynewt-core/sys/osc/pkg.yml 
b/newt/testdata/syscfg_stability/repos/apache-mynewt-core/sys/osc/pkg.yml
new file mode 100644
index 00000000..d63851e4
--- /dev/null
+++ b/newt/testdata/syscfg_stability/repos/apache-mynewt-core/sys/osc/pkg.yml
@@ -0,0 +1,22 @@
+#
+# 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.
+#
+
+
+pkg.name: sys/osc
+pkg.type: lib
diff --git 
a/newt/testdata/syscfg_stability/repos/apache-mynewt-core/sys/osc/syscfg.yml 
b/newt/testdata/syscfg_stability/repos/apache-mynewt-core/sys/osc/syscfg.yml
new file mode 100644
index 00000000..a506be43
--- /dev/null
+++ b/newt/testdata/syscfg_stability/repos/apache-mynewt-core/sys/osc/syscfg.yml
@@ -0,0 +1,27 @@
+#
+# 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.
+#
+
+
+syscfg.defs:
+    OSC_X:
+        description: Setting disabling its own override condition.
+        value: 1
+    OSC_Y:
+        description: Setting controlling a dependency.
+        value: 0
diff --git 
a/newt/testdata/syscfg_stability/repos/apache-mynewt-core/sys/oscbar/pkg.yml 
b/newt/testdata/syscfg_stability/repos/apache-mynewt-core/sys/oscbar/pkg.yml
new file mode 100644
index 00000000..a97c2ae7
--- /dev/null
+++ b/newt/testdata/syscfg_stability/repos/apache-mynewt-core/sys/oscbar/pkg.yml
@@ -0,0 +1,22 @@
+#
+# 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.
+#
+
+
+pkg.name: sys/oscbar
+pkg.type: lib
diff --git 
a/newt/testdata/syscfg_stability/repos/apache-mynewt-core/sys/oscbar/syscfg.yml 
b/newt/testdata/syscfg_stability/repos/apache-mynewt-core/sys/oscbar/syscfg.yml
new file mode 100644
index 00000000..21dbf0c5
--- /dev/null
+++ 
b/newt/testdata/syscfg_stability/repos/apache-mynewt-core/sys/oscbar/syscfg.yml
@@ -0,0 +1,24 @@
+#
+# 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.
+#
+
+
+syscfg.defs:
+    OSC_BARDEF:
+        description: Setting defined only if sys/oscbar is included.
+        value: 1
diff --git a/newt/testdata/syscfg_stability/targets/osc_dep/pkg.yml 
b/newt/testdata/syscfg_stability/targets/osc_dep/pkg.yml
new file mode 100644
index 00000000..7860a09a
--- /dev/null
+++ b/newt/testdata/syscfg_stability/targets/osc_dep/pkg.yml
@@ -0,0 +1,22 @@
+#
+# 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.
+#
+
+
+pkg.name: targets/osc_dep
+pkg.type: target
diff --git a/newt/testdata/syscfg_stability/targets/osc_dep/target.yml 
b/newt/testdata/syscfg_stability/targets/osc_dep/target.yml
new file mode 100644
index 00000000..9bf20f18
--- /dev/null
+++ b/newt/testdata/syscfg_stability/targets/osc_dep/target.yml
@@ -0,0 +1,23 @@
+#
+# 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.
+#
+
+
+target.app: apps/osc_dep
+target.bsp: "@apache-mynewt-core/hw/bsp/sim"
+target.build_profile: default
diff --git a/newt/testdata/syscfg_stability/targets/osc_self/pkg.yml 
b/newt/testdata/syscfg_stability/targets/osc_self/pkg.yml
new file mode 100644
index 00000000..e4ecb281
--- /dev/null
+++ b/newt/testdata/syscfg_stability/targets/osc_self/pkg.yml
@@ -0,0 +1,22 @@
+#
+# 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.
+#
+
+
+pkg.name: targets/osc_self
+pkg.type: target
diff --git a/newt/testdata/syscfg_stability/targets/osc_self/target.yml 
b/newt/testdata/syscfg_stability/targets/osc_self/target.yml
new file mode 100644
index 00000000..59eedc73
--- /dev/null
+++ b/newt/testdata/syscfg_stability/targets/osc_self/target.yml
@@ -0,0 +1,23 @@
+#
+# 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.
+#
+
+
+target.app: apps/osc_self
+target.bsp: "@apache-mynewt-core/hw/bsp/sim"
+target.build_profile: default

Reply via email to