[PATCH v3 2/3] buildman: Allow skipping the dtc build

Simon Glass
2024-08-15T19:58:30Z

For most boards, the device-tree compiler is built in-tree, ignoring the
system version. Add a special option to skip this build. This can be
useful when the system dtc is up-to-date, as it speeds up the build.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

(no changes since v1)

tools/buildman/builder.py | 27 ++++++++++++++++++++++++++-
tools/buildman/builderthread.py | 4 ++--
tools/buildman/buildman.rst | 3 +++
tools/buildman/cmdline.py | 2 ++
tools/buildman/control.py | 3 ++-
tools/buildman/test.py | 31 +++++++++++++++++++++++++++++++
6 files changed, 66 insertions(+), 4 deletions(-)

diff --git a/tools/buildman/builder.py b/tools/buildman/builder.py
index c4384f53e8d..4090d328b30 100644
--- a/tools/buildman/builder.py
+++ b/tools/buildman/builder.py
@@ -22,6 +22,7 @@ from buildman import toolchain
from patman import gitutil
from u_boot_pylib import command
from u_boot_pylib import terminal
+from u_boot_pylib import tools
from u_boot_pylib.terminal import tprint

# This indicates an new int or hex Kconfig property with no default
@@ -263,7 +264,8 @@ class Builder:
adjust_cfg=None, allow_missing=False, no_lto=False,
reproducible_builds=False, force_build=False,
force_build_failures=False, force_reconfig=False,
- in_tree=False, force_config_on_failure=False, make_func=None):
+ in_tree=False, force_config_on_failure=False, make_func=None,
+ dtc_skip=False):
"""Create a new Builder object

Args:
@@ -312,6 +314,7 @@ class Builder:
force_config_on_failure (bool): Reconfigure the build before
retrying a failed build
make_func (function): Function to call to run 'make'
+ dtc_skip (bool): True to skip building dtc and use the system one
"""
self.toolchains = toolchains
self.base_dir = base_dir
@@ -354,6 +357,12 @@ class Builder:
self.in_tree = in_tree
self.force_config_on_failure = force_config_on_failure
self.fallback_mrproper = fallback_mrproper
+ if dtc_skip:
+ self.dtc = shutil.which('dtc')
+ if not self.dtc:
+ raise ValueError('Cannot find dtc')
+ else:
+ self.dtc = None

if not self.squash_config_y:
self.config_filenames += EXTRA_CONFIG_FILENAMES
@@ -407,6 +416,22 @@ class Builder:
def signal_handler(self, signal, frame):
sys.exit(1)

+ def make_environment(self, toolchain):
+ """Create the environment to use for building
+
+ Args:
+ toolchain (Toolchain): Toolchain to use for building
+
+ Returns:
+ dict:
+ key (str): Variable name
+ value (str): Variable value
+ """
+ env = toolchain.MakeEnvironment(self.full_path)
+ if self.dtc:
+ env[b'DTC'] = tools.to_bytes(self.dtc)
+ return env
+
def set_display_options(self, show_errors=False, show_sizes=False,
show_detail=False, show_bloat=False,
list_error_boards=False, show_config=False,
diff --git a/tools/buildman/builderthread.py b/tools/buildman/builderthread.py
index bbe2f6f0d24..54f3c02c844 100644
--- a/tools/buildman/builderthread.py
+++ b/tools/buildman/builderthread.py
@@ -406,7 +406,7 @@ class BuilderThread(threading.Thread):
the next incremental build
"""
# Set up the environment and command line
- env = self.toolchain.MakeEnvironment(self.builder.full_path)
+ env = self.builder.make_environment(self.toolchain)
mkdir(out_dir)

args, cwd, src_dir = self._build_args(brd, out_dir, out_rel_dir,
@@ -574,7 +574,7 @@ class BuilderThread(threading.Thread):
outf.write(f'{result.return_code}')

# Write out the image and function size information and an objdump
- env = result.toolchain.MakeEnvironment(self.builder.full_path)
+ env = self.builder.make_environment(self.toolchain)
with open(os.path.join(build_dir, 'out-env'), 'wb') as outf:
for var in sorted(env.keys()):
outf.write(b'%s="%s"' % (var, env[var]))
diff --git a/tools/buildman/buildman.rst b/tools/buildman/buildman.rst
index b8ff3bf1ab2..e873611e596 100644
--- a/tools/buildman/buildman.rst
+++ b/tools/buildman/buildman.rst
@@ -1030,6 +1030,9 @@ of the source tree, thus allowing rapid tested evolution of the code::

./tools/buildman/buildman -Pr tegra

+Note also the `--dtc-skip` option which uses the system device-tree compiler to
+avoid needing to build it for each board. This can save 10-20% of build time.
+An alternative is to set DTC=/path/to/dtc when running buildman.

Checking configuration
----------------------
diff --git a/tools/buildman/cmdline.py b/tools/buildman/cmdline.py
index 544a391a464..7573e5bdfe8 100644
--- a/tools/buildman/cmdline.py
+++ b/tools/buildman/cmdline.py
@@ -46,6 +46,8 @@ def add_upto_m(parser):
help='Show detailed size delta for each board in the -S summary')
parser.add_argument('-D', '--debug', action='store_true',
help='Enabling debugging (provides a full traceback on error)')
+ parser.add_argument('--dtc-skip', action='store_true', default=False,
+ help='Skip building of dtc and use the system version')
parser.add_argument('-e', '--show_errors', action='store_true',
default=False, help='Show errors and warnings')
parser.add_argument('-E', '--warnings-as-errors', action='store_true',
diff --git a/tools/buildman/control.py b/tools/buildman/control.py
index d3d027f02ab..55d4d770c5c 100644
--- a/tools/buildman/control.py
+++ b/tools/buildman/control.py
@@ -809,7 +809,8 @@ def do_buildman(args, toolchains=None, make_func=None, brds=None,
force_build = args.force_build,
force_build_failures = args.force_build_failures,
force_reconfig = args.force_reconfig, in_tree = args.in_tree,
- force_config_on_failure=not args.quick, make_func=make_func)
+ force_config_on_failure=not args.quick, make_func=make_func,
+ dtc_skip=args.dtc_skip)

TEST_BUILDER = builder

diff --git a/tools/buildman/test.py b/tools/buildman/test.py
index 6191af95445..91f7a3a475a 100644
--- a/tools/buildman/test.py
+++ b/tools/buildman/test.py
@@ -952,6 +952,37 @@ class TestBuild(unittest.TestCase):
self.assertEqual(
{b'CROSS_COMPILE': b'fred aarch64-linux-', b'LC_ALL': b'C'}, diff)

+ def test_skip_dtc(self):
+ """Test skipping building the dtc tool"""
+ old_path = os.getenv('PATH')
+ try:
+ os.environ['PATH'] = self.base_dir
+
+ # Check a missing tool
+ with self.assertRaises(ValueError) as exc:
+ builder.Builder(self.toolchains, self.base_dir, None, 0, 2,
+ dtc_skip=True)
+ self.assertIn('Cannot find dtc', str(exc.exception))
+
+ # Create a fake tool to use
+ dtc = os.path.join(self.base_dir, 'dtc')
+ tools.write_file(dtc, b'xx')
+ os.chmod(dtc, 0o777)
+
+ build = builder.Builder(self.toolchains, self.base_dir, None, 0, 2,
+ dtc_skip=True)
+ toolchain = self.toolchains.Select('arm')
+ env = build.make_environment(toolchain)
+ self.assertIn(b'DTC', env)
+
+ # Try the normal case, i.e. not skipping the dtc build
+ build = builder.Builder(self.toolchains, self.base_dir, None, 0, 2)
+ toolchain = self.toolchains.Select('arm')
+ env = build.make_environment(toolchain)
+ self.assertNotIn(b'DTC', env)
+ finally:
+ os.environ['PATH'] = old_path
+

if __name__ == "__main__":
unittest.main()
--
2.34.1