[Concept,02/19] buildman: Add option to build then show summary

Message ID 20260130035849.3580212-3-simon.glass@canonical.com
State New
Headers
Series Enhanced command-line editing with undo/redo support |

Commit Message

Simon Glass Jan. 30, 2026, 3:58 a.m. UTC
  The -s option shows a summary of existing build results, but requires
that a build has already been done. Add a -z/--build-summary option
which performs the build first, then shows the summary afterwards.

The summary is shown regardless of whether the build succeeds, and the
return code reflects the build result.

Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: Simon Glass <simon.glass@canonical.com>
---

 tools/buildman/buildman.rst |   5 +-
 tools/buildman/cmdline.py   |   2 +
 tools/buildman/control.py   |   4 ++
 tools/buildman/test.py      | 110 ++++++++++++++++++++++++++++++++++++
 4 files changed, 120 insertions(+), 1 deletion(-)
  

Patch

diff --git a/tools/buildman/buildman.rst b/tools/buildman/buildman.rst
index 603b019540b..831431670d3 100644
--- a/tools/buildman/buildman.rst
+++ b/tools/buildman/buildman.rst
@@ -111,7 +111,10 @@  Otherwise buildman will perform random actions. Use -n to check what the
 random actions might be.
 
 Buildman effectively has two modes: without -s it builds, with -s it
-summarises the results of previous (or active) builds.
+summarises the results of previous (or active) builds. You can combine
+both with -z (--build-summary), which builds first and then shows the
+summary. This is useful when you want to do a build and immediately see
+a summary of the results without running buildman twice.
 
 If you just want to build the current source tree, leave off the -b flag.
 This will display results and errors as they happen. You can still look at
diff --git a/tools/buildman/cmdline.py b/tools/buildman/cmdline.py
index fae1910307b..f4a1a3d018c 100644
--- a/tools/buildman/cmdline.py
+++ b/tools/buildman/cmdline.py
@@ -185,6 +185,8 @@  def add_after_m(parser):
     parser.add_argument('-Y', '--filter-migration-warnings', action='store_true',
           default=False,
           help='Filter out migration warnings from output')
+    parser.add_argument('-z', '--build-summary', action='store_true',
+          default=False, help='Build first, then show a summary of the results')
 
 
 def parse_args():
diff --git a/tools/buildman/control.py b/tools/buildman/control.py
index 728389d9a8b..7fc0d20574a 100644
--- a/tools/buildman/control.py
+++ b/tools/buildman/control.py
@@ -573,6 +573,10 @@  def run_builder(builder, commits, board_selected, display_options, args):
         fail, warned, excs = builder.build_boards(
             commits, board_selected, args.keep_outputs, args.verbose,
             args.fragments)
+        if args.build_summary:
+            builder.commits = commits
+            builder.result_handler.show_summary(
+                commits, board_selected, args.step)
         if excs:
             return 102
         if fail:
diff --git a/tools/buildman/test.py b/tools/buildman/test.py
index 0f4a5b9e543..37930ad9720 100644
--- a/tools/buildman/test.py
+++ b/tools/buildman/test.py
@@ -1222,6 +1222,116 @@  class TestBuildMisc(TestBuildBase):
                                                     'other_board'))
 
 
+class TestBuildSummary(TestBuildBase):
+    """Tests for build summary functionality"""
+
+    def test_build_summary(self):
+        """Test --build-summary option builds then shows summary"""
+        opts = DisplayOptions(
+            show_errors=False, show_sizes=False, show_detail=False,
+            show_bloat=False, show_config=False, show_environment=False,
+            show_unknown=False, ide=False, list_error_boards=False)
+        build = builder.Builder(self.toolchains, self.base_dir, None, 1,
+                                2, self._col, ResultHandler(self._col, opts),
+                                checkout=False)
+        build._result_handler.set_builder(build)
+
+        # Track calls to build_boards and show_summary
+        build_boards_called = []
+        show_summary_called = []
+
+        def mock_build_boards(*args, **kwargs):
+            build_boards_called.append(True)
+            return False, False, False
+
+        def mock_show_summary(*args, **kwargs):
+            show_summary_called.append(True)
+
+        build.build_boards = mock_build_boards
+        build._result_handler.show_summary = mock_show_summary
+
+        # Create args with build_summary=True
+        class Args:
+            summary = False
+            build_summary = True
+            step = 1
+            keep_outputs = False
+            verbose = False
+            fragments = ''
+            ignore_warnings = False
+            ide = False
+            filter_dtb_warnings = False
+            filter_migration_warnings = False
+            git = '.'
+            threads = 1
+            jobs = 1
+
+        args = Args()
+        board_selected = self.brds.get_selected_dict()
+
+        # Mock gnu_make detection
+        with patch.object(command, 'output', return_value='make'):
+            control.run_builder(build, self.commits, board_selected,
+                                opts, args)
+
+        # Verify both build_boards and show_summary were called
+        self.assertEqual(1, len(build_boards_called),
+                         'build_boards should be called once')
+        self.assertEqual(1, len(show_summary_called),
+                         'show_summary should be called once')
+
+    def test_build_summary_with_failures(self):
+        """Test --build-summary shows summary even when build fails"""
+        opts = DisplayOptions(
+            show_errors=False, show_sizes=False, show_detail=False,
+            show_bloat=False, show_config=False, show_environment=False,
+            show_unknown=False, ide=False, list_error_boards=False)
+        build = builder.Builder(self.toolchains, self.base_dir, None, 1,
+                                2, self._col, ResultHandler(self._col, opts),
+                                checkout=False)
+        build._result_handler.set_builder(build)
+
+        show_summary_called = []
+
+        def mock_build_boards(*args, **kwargs):
+            # Simulate build failure
+            return True, False, False
+
+        def mock_show_summary(*args, **kwargs):
+            show_summary_called.append(True)
+
+        build.build_boards = mock_build_boards
+        build._result_handler.show_summary = mock_show_summary
+
+        class Args:
+            summary = False
+            build_summary = True
+            step = 1
+            keep_outputs = False
+            verbose = False
+            fragments = ''
+            ignore_warnings = False
+            ide = False
+            filter_dtb_warnings = False
+            filter_migration_warnings = False
+            git = '.'
+            threads = 1
+            jobs = 1
+
+        args = Args()
+        board_selected = self.brds.get_selected_dict()
+
+        with patch.object(command, 'output', return_value='make'):
+            ret = control.run_builder(build, self.commits, board_selected,
+                                      opts, args)
+
+        # Summary should still be shown even with failures
+        self.assertEqual(1, len(show_summary_called),
+                         'show_summary should be called even on failure')
+        # Return code should indicate failure
+        self.assertEqual(100, ret)
+
+
 class TestBuilderFuncs(TestBuildBase):
     """Tests for individual Builder methods"""