Taming the Beast: Refactoring Buildman for Maintainability

Buildman is the Swiss Army knife of U-Boot development. It handles the heavy lifting of building hundreds of boards in parallel, fetching the correct toolchains, and—perhaps most importantly—analysing the impact of your patches across git history. Whether you are checking for code bloat or verifying that a refactor doesn’t break a board you don’t own, buildman is the tool that makes it possible.
However, if you have ever peered into the internals of buildman, you might have noticed that the Builder class had become… well, enormous. Over the years, it accumulated responsibilities ranging from managing threads and file I/O to formatting console output and tracking build statistics.
A recent series of 18 patches to address this technical debt. The goal: split the monolithic Builder class into smaller, more focused components.
The Problem: A Class That Did Too Much
The Builder class in tools/buildman/builder.py had grown to over 2200 lines of code. It was handling two distinct responsibilities:
- Orchestrating the Build: Managing threads, checking out commits, running
make, and saving artifacts. - Reporting Results: Processing build outcomes, calculating size deltas, tracking error lines, and formatting the summary for the user.
These two concerns were tightly coupled, making the code hard to read, test, and maintain.
The Solution: Introduce ResultHandler
This refactor extracts the result-processing logic (~900 lines) into a new class called ResultHandler. The separation is clean:
Builderfocuses on the act of building (I/O, git operations, process management).ResultHandlerfocuses on the presentation of the build (summaries, error reporting, bloat analysis).
How We Got There
Refactoring a core tool like buildman is risky—regressions here can break workflows for developers everywhere. We took a stepwise approach:
- Extract Data Structures: We moved configuration handling (
Config) and build outcomes (Outcome,BoardStatus,ErrLine) into their own modules (cfgutil.py,outcome.py). This broke the circular dependencies that often plague monolithic classes. - Formalise Options: Instead of passing a dozen boolean flags (like
show_errors,show_bloat) around individually, we grouped them into aDisplayOptionsnamed tuple. - Create the Handler: We introduced
ResultHandlerand progressively moved display methods into it—starting with simple size summaries and moving up to complex error delta calculations. - Clean Up: Finally, we enforced Python naming conventions by adding underscore prefixes to private members (
self.colbecameself._col), clarifying the public API.
The Result
The Builder class is now down to ~1200 lines—a much more manageable size. The display logic lives in tools/buildman/resulthandler.py, where it can be evolved independently.
While this doesn’t change the user-facing behaviour of buildman today, it makes the codebase significantly healthier and ready for future improvements. Sometimes the most important feature is code you can actually understand!