@@ -2197,3 +2197,135 @@ class DevtoolUpgradeTests(DevtoolBase):
#Step 4.5
runCmd("grep %s %s" % (modconfopt, codeconfigfile))
+
+ def test_devtool_modify_kernel_overrides(self):
+ """
+ [YOCTO #14723]
+
+ Test that patches in SRC_URI overrides round-trip correctly through devtool modify, especially when
+ appends/prepends are present.
+ """
+ import typing
+
+ # Perform some initial setup
+ kernel_provider = self.td['PREFERRED_PROVIDER_virtual/kernel']
+ self.track_for_cleanup(self.workspacedir)
+ self.add_command_to_tearDown('bitbake -c clean %s' % kernel_provider)
+ self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
+
+ # Collect some information for later
+ machine = get_bb_var("MACHINE")
+ recipefile = get_bb_var('FILE', kernel_provider)
+ recipedir = os.path.dirname(recipefile)
+ res = re.search('recipes-.*', recipedir)
+ self.assertTrue(res, 'Unable to find recipe subdirectory')
+ recipesubdir = res[0]
+
+ new_file_contents = "A new file"
+
+ bitbake('%s -c clean' % kernel_provider)
+
+ # We are going to call 'devtool modify' multiple times in this test, so we use a context manager for the temp
+ # dir rather than call 'track_for_cleanup', since we want the tempdirs destroyed between sub-tests. Also it's
+ # easier and clearer this way.
+ with tempfile.TemporaryDirectory(prefix='devtoolqa') as tempdir:
+ runCmd('devtool modify %s -x %s' % (kernel_provider, tempdir))
+ self._check_src_repo(tempdir)
+
+ tags = runCmd('git tag --points-at HEAD', cwd=tempdir).output.strip().splitlines()
+ self.assertSetEqual(set(tags), {"devtool-base", "devtool-patched"})
+
+ # Construct a patch to add a file
+ runCmd(f'echo "{new_file_contents}" > devtool-new-file', cwd=tempdir)
+ runCmd('git add devtool-new-file', cwd=tempdir)
+ # Need to add the Upstream-Status otherwise the patch will be rejected next time we 'modify'.
+ runCmd('git commit -m "Add a new file\n\nUpstream-Status: Inappropriate [OE self-test specific]"', cwd=tempdir)
+ self.add_command_to_tearDown('rm -rf %s' % os.path.join(self.testlayer_path, recipesubdir))
+ runCmd('devtool finish %s meta-selftest' % kernel_provider)
+
+ # Check patch was created
+ patchfile = os.path.join(self.testlayer_path, recipesubdir, kernel_provider, "0001-Add-a-new-file.patch")
+ self.assertExists(patchfile, "Patch file doesn't exist")
+
+ # Check bbappend was created
+ appendfn = os.path.join(self.testlayer_path, recipesubdir, '%s_%%.bbappend' % kernel_provider)
+ self.assertExists(appendfn, "bbappend doesn't exist")
+
+ def _assert_new_file_presence(exists: bool):
+ file_path = os.path.join(tempdir, "devtool-new-file")
+ if exists:
+ with open(file_path, "r") as f:
+ contents = f.read()
+ self.assertEqual(contents, f"{new_file_contents}\n")
+ else:
+ self.assertNotExists(file_path)
+
+ # Check that the patch round-trips
+ with tempfile.TemporaryDirectory(prefix='devtoolqa') as tempdir:
+ result = runCmd(f'devtool modify {kernel_provider} -x {tempdir}')
+ self._check_src_repo(tempdir)
+ _assert_new_file_presence(True)
+ runCmd(f'devtool reset {kernel_provider}')
+
+ def _modify_append_file(fn: typing.Callable[[str], str]):
+ with open(appendfn, "r+") as f:
+ contents = f.read()
+ contents = fn(contents)
+ f.seek(0)
+ f.write(contents)
+ f.truncate()
+
+ # Modify bbappend to conditionally apply the patch
+ _modify_append_file(lambda contents: contents.replace('SRC_URI += "', f'SRC_URI:append:{machine} = " '))
+
+ # Check that the patch still applies and that all branches/tags are created as expected
+ with tempfile.TemporaryDirectory(prefix='devtoolqa') as tempdir:
+ result = runCmd(f'devtool modify {kernel_provider} -x {tempdir}')
+ self._check_src_repo(tempdir)
+ _assert_new_file_presence(True)
+ runCmd(f'devtool reset {kernel_provider}')
+
+ # Change the bbappend such that the patch is applied for a non-active OVERRIDE
+ _modify_append_file(lambda contents: contents.replace(f'SRC_URI:append:{machine} = " ', f'SRC_URI:append:{machine}-fake = " '))
+
+ with tempfile.TemporaryDirectory(prefix='devtoolqa') as tempdir:
+ result = runCmd(f'devtool modify {kernel_provider} -x {tempdir}')
+ self._check_src_repo(tempdir)
+
+ # File shouldn't exist on this branch...
+ _assert_new_file_presence(False)
+ current_branch = runCmd('git symbolic-ref --short HEAD', cwd=tempdir).output.strip()
+ self.assertEqual(current_branch, "devtool")
+
+ tags = runCmd('git tag --points-at HEAD', cwd=tempdir).output.strip().splitlines()
+ self.assertSetEqual(set(tags), {"devtool-base", "devtool-patched"})
+
+ # It should exist on the 'devtool-override-${MACHINE}-fake' branch
+ runCmd(f'git checkout devtool-override-{machine}-fake', cwd=tempdir)
+ _assert_new_file_presence(True)
+ runCmd(f'devtool reset {kernel_provider}')
+
+ # Finally, change the bbappend such that the patch is applied to two different OVERRIDES.
+ # Would get two branches created, devtool-override-${MACHINE}-fake and devtool-override-${MACHINE}-fake2, with
+ # the patch applied. This tests that patch series generation is working for multiple overrides branches.
+ #
+ # The 'devtool' branch will not have the patch applied because there are no active OVERRIDES.
+ _modify_append_file(lambda contents: contents.replace(f'SRC_URI:append:{machine}-fake = " ', f'SRC_URI:append:{machine}-fake2 = " file://0001-Add-a-new-file.patch"\nSRC_URI:append:{machine}-fake = " '))
+ with tempfile.TemporaryDirectory(prefix='devtoolqa') as tempdir:
+ result = runCmd(f'devtool modify {kernel_provider} -x {tempdir}')
+ self._check_src_repo(tempdir)
+
+ # File shouldn't exist on this branch...
+ _assert_new_file_presence(False)
+ current_branch = runCmd('git symbolic-ref --short HEAD', cwd=tempdir).output.strip()
+ self.assertEqual(current_branch, "devtool")
+
+ tags = runCmd('git tag --points-at HEAD', cwd=tempdir).output.strip().splitlines()
+ self.assertSetEqual(set(tags), {"devtool-base", "devtool-patched"})
+
+ # It should exist on the 'devtool-override-${MACHINE}-fake' branch
+ for branch in [f"devtool-override-{machine}-fake", f"devtool-override-{machine}-fake2"]:
+ runCmd(f'git checkout {branch}', cwd=tempdir)
+ _assert_new_file_presence(True)
+
+ runCmd(f'devtool reset {kernel_provider}')