The run_merge_config() function constructs paths for merge_config.sh
using out_dir and cfg_file which are relative to the original working
directory. However, the commands run with cwd=src_dir (the work
directory), so these paths resolve incorrectly.
For example, with src_dir='../exph/.bm-work/00' and
out_dir='../exph/.bm-work/00/build', the -O flag would pass the full
out_dir path. When make runs from src_dir, it interprets this as
'../exph/.bm-work/00/../exph/.bm-work/00/build', doubling the path.
Fix this by converting out_dir and cfg_file to paths relative to
src_dir using os.path.relpath(). This ensures the paths resolve
correctly when commands execute from the work directory.
Fixes: 635c5f5638a0 ("buildman: Use merge_config.sh for --adjust-cfg")
Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: Simon Glass <simon.glass@canonical.com>
---
tools/buildman/cfgutil.py | 21 ++++++++++-----
tools/buildman/test_cfgutil.py | 48 ++++++++++++++++++++++++++++++++++
2 files changed, 62 insertions(+), 7 deletions(-)
@@ -368,9 +368,18 @@ def run_merge_config(src_dir, out_dir, cfg_file, adjust_cfg, env):
# Create a minimal defconfig from the current .config
# This is necessary for 'imply' to work - the full .config has
# '# CONFIG_xxx is not set' lines that prevent imply from taking effect
- defconfig_path = os.path.join(out_dir or '.', 'defconfig')
- make_cmd = ['make', f'O={out_dir}' if out_dir else None,
- f'KCONFIG_CONFIG={cfg_file}', 'savedefconfig']
+ #
+ # Convert paths to be relative to src_dir since commands run with
+ # cwd=src_dir
+ if src_dir and out_dir:
+ rel_out_dir = os.path.relpath(out_dir, src_dir)
+ rel_cfg_file = os.path.relpath(cfg_file, src_dir)
+ else:
+ rel_out_dir = out_dir or '.'
+ rel_cfg_file = cfg_file
+ defconfig_path = os.path.join(rel_out_dir, 'defconfig')
+ make_cmd = ['make', f'O={rel_out_dir}' if rel_out_dir != '.' else None,
+ f'KCONFIG_CONFIG={rel_cfg_file}', 'savedefconfig']
make_cmd = [x for x in make_cmd if x] # Remove None elements
result = command.run_one(*make_cmd, cwd=src_dir, env=env, capture=True,
capture_stderr=True)
@@ -382,10 +391,8 @@ def run_merge_config(src_dir, out_dir, cfg_file, adjust_cfg, env):
try:
# Run merge_config.sh with the minimal defconfig as base
# -O sets output dir; defconfig is the base, fragment is merged
- merge_script = os.path.join(src_dir or '.', 'scripts', 'kconfig',
- 'merge_config.sh')
- out = out_dir or '.'
- cmd = [merge_script, '-O', out, defconfig_path, frag_path]
+ merge_script = os.path.join('scripts', 'kconfig', 'merge_config.sh')
+ cmd = [merge_script, '-O', rel_out_dir, defconfig_path, frag_path]
result = command.run_one(*cmd, cwd=src_dir, env=env, capture=True,
capture_stderr=True)
finally:
@@ -180,6 +180,54 @@ class TestAdjustCfg(unittest.TestCase):
result)
+class TestRunMergeConfig(unittest.TestCase):
+ """Tests for run_merge_config() function"""
+
+ def test_merge_script_path(self):
+ """Test that merge_config.sh path is relative to cwd, not absolute"""
+ from unittest import mock
+ from u_boot_pylib import command
+
+ # Track commands that were run
+ commands_run = []
+
+ def mock_run_one(*args, **kwargs):
+ commands_run.append((args, kwargs))
+ result = command.CommandResult()
+ result.return_code = 0
+ result.stdout = ''
+ result.stderr = ''
+ return result
+
+ with mock.patch.object(command, 'run_one', mock_run_one):
+ with mock.patch('os.path.exists', return_value=True):
+ with mock.patch('os.unlink'):
+ # Use a work directory path like buildman does
+ src_dir = '../branch/.bm-work/00'
+ cfgutil.run_merge_config(
+ src_dir, 'build', 'build/.config',
+ {'LOCALVERSION_AUTO': '~LOCALVERSION_AUTO'}, {})
+
+ # Find the merge_config.sh command
+ merge_cmd = None
+ for args, kwargs in commands_run:
+ if args and 'merge_config.sh' in args[0]:
+ merge_cmd = args
+ merge_cwd = kwargs.get('cwd')
+ break
+
+ self.assertIsNotNone(merge_cmd, 'merge_config.sh command not found')
+
+ # The script path should be relative, not include src_dir
+ script_path = merge_cmd[0]
+ self.assertEqual('scripts/kconfig/merge_config.sh', script_path,
+ f'Script path should be relative, got: {script_path}')
+
+ # The cwd should be src_dir
+ self.assertEqual(src_dir, merge_cwd,
+ f'cwd should be src_dir, got: {merge_cwd}')
+
+
class TestProcessConfig(unittest.TestCase):
"""Tests for process_config() function"""