diff options
| author | Alexander Kanavin <alex@linutronix.de> | 2024-12-11 14:46:30 +0100 |
|---|---|---|
| committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2024-12-13 11:11:18 +0000 |
| commit | 22f046d67c02823f2df2b26cb3c463f567eae3fb (patch) | |
| tree | 2bc7fc6e26cea6a251628fa8c0c19bccb92f551e | |
| parent | aadff6930b293da8d5ce312c62b9d63335cde424 (diff) | |
| download | poky-22f046d67c02823f2df2b26cb3c463f567eae3fb.tar.gz | |
bitbake-config-build: add a plugin for config fragments
This allows fine-tuning local configurations with pre-frabricated
configuration snippets in a structured, controlled way. It's also
an important building block for bitbake-setup.
The tool requires that each fragment contains a one-line summary, and one or more
lines of description, as BB_CONF_FRAGMENT_SUMMARY style metadata.
There are three (and a half) operations (list/enable/disable/disable all), and here's the 'list' output:
alex@Zen2:/srv/storage/alex/yocto/build-64$ bitbake-config-build list-fragments
NOTE: Starting bitbake server...
Available fragments in selftest layer located in /srv/work/alex/poky/meta-selftest:
Enabled fragments:
selftest/test-fragment This is a configuration fragment intended for testing in oe-selftest context
Unused fragments:
selftest/more-fragments-here/test-another-fragment This is a second configuration fragment intended for testing in oe-selftest context
(From OE-Core rev: fdb611e13bd7aa00360d3a68e4818ef5f05c8944)
Signed-off-by: Alexander Kanavin <alex@linutronix.de>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
4 files changed, 196 insertions, 0 deletions
diff --git a/meta-selftest/conf/fragments/more-fragments-here/test-another-fragment.conf b/meta-selftest/conf/fragments/more-fragments-here/test-another-fragment.conf new file mode 100644 index 0000000000..3bf0459047 --- /dev/null +++ b/meta-selftest/conf/fragments/more-fragments-here/test-another-fragment.conf | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | BB_CONF_FRAGMENT_SUMMARY = "This is a second configuration fragment intended for testing in oe-selftest context" | ||
| 2 | BB_CONF_FRAGMENT_DESCRIPTION = "It defines another variable that can be checked inside the test." | ||
| 3 | SELFTEST_FRAGMENT_ANOTHER_VARIABLE = "someothervalue" | ||
diff --git a/meta-selftest/conf/fragments/test-fragment.conf b/meta-selftest/conf/fragments/test-fragment.conf new file mode 100644 index 0000000000..4c1d240945 --- /dev/null +++ b/meta-selftest/conf/fragments/test-fragment.conf | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | BB_CONF_FRAGMENT_SUMMARY = "This is a configuration fragment intended for testing in oe-selftest context" | ||
| 2 | BB_CONF_FRAGMENT_DESCRIPTION = "It defines a variable that can be checked inside the test." | ||
| 3 | SELFTEST_FRAGMENT_VARIABLE = "somevalue" | ||
diff --git a/meta/lib/bbconfigbuild/configfragments.py b/meta/lib/bbconfigbuild/configfragments.py new file mode 100644 index 0000000000..30cc5ece07 --- /dev/null +++ b/meta/lib/bbconfigbuild/configfragments.py | |||
| @@ -0,0 +1,159 @@ | |||
| 1 | # | ||
| 2 | # Copyright OpenEmbedded Contributors | ||
| 3 | # | ||
| 4 | # SPDX-License-Identifier: GPL-2.0-only | ||
| 5 | # | ||
| 6 | |||
| 7 | import logging | ||
| 8 | import os | ||
| 9 | import sys | ||
| 10 | import os.path | ||
| 11 | |||
| 12 | import bb.utils | ||
| 13 | |||
| 14 | from bblayers.common import LayerPlugin | ||
| 15 | |||
| 16 | logger = logging.getLogger('bitbake-config-layers') | ||
| 17 | |||
| 18 | sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) | ||
| 19 | |||
| 20 | def plugin_init(plugins): | ||
| 21 | return ConfigFragmentsPlugin() | ||
| 22 | |||
| 23 | class ConfigFragmentsPlugin(LayerPlugin): | ||
| 24 | def get_fragment_info(self, path, name): | ||
| 25 | d = bb.data.init() | ||
| 26 | bb.parse.handle(path, d, True) | ||
| 27 | summary = d.getVar('BB_CONF_FRAGMENT_SUMMARY') | ||
| 28 | description = d.getVar('BB_CONF_FRAGMENT_DESCRIPTION') | ||
| 29 | if not summary: | ||
| 30 | raise Exception('Please add a one-line summary as BB_CONF_FRAGMENT_SUMMARY = \"...\" variable at the beginning of {}'.format(path)) | ||
| 31 | |||
| 32 | if not description: | ||
| 33 | raise Exception('Please add a description as BB_CONF_FRAGMENT_DESCRIPTION = \"...\" variable at the beginning of {}'.format(path)) | ||
| 34 | |||
| 35 | return summary, description | ||
| 36 | |||
| 37 | def discover_fragments(self): | ||
| 38 | fragments_path_prefix = self.tinfoil.config_data.getVar('OE_FRAGMENTS_PREFIX') | ||
| 39 | allfragments = {} | ||
| 40 | for layername in self.bbfile_collections: | ||
| 41 | layerdir = self.bbfile_collections[layername] | ||
| 42 | fragments = [] | ||
| 43 | for topdir, dirs, files in os.walk(os.path.join(layerdir, fragments_path_prefix)): | ||
| 44 | fragmentdir = os.path.relpath(topdir, os.path.join(layerdir, fragments_path_prefix)) | ||
| 45 | for fragmentfile in sorted(files): | ||
| 46 | fragmentname = os.path.normpath("/".join((layername, fragmentdir, fragmentfile.split('.')[0]))) | ||
| 47 | fragmentpath = os.path.join(topdir, fragmentfile) | ||
| 48 | fragmentsummary, fragmentdesc = self.get_fragment_info(fragmentpath, fragmentname) | ||
| 49 | fragments.append({'path':fragmentpath, 'name':fragmentname, 'summary':fragmentsummary, 'description':fragmentdesc}) | ||
| 50 | if fragments: | ||
| 51 | allfragments[layername] = {'layerdir':layerdir,'fragments':fragments} | ||
| 52 | return allfragments | ||
| 53 | |||
| 54 | def do_list_fragments(self, args): | ||
| 55 | """ List available configuration fragments """ | ||
| 56 | def print_fragment(f, verbose, is_enabled): | ||
| 57 | if not verbose: | ||
| 58 | print('{}\t{}'.format(f['name'], f['summary'])) | ||
| 59 | else: | ||
| 60 | print('Name: {}\nPath: {}\nEnabled: {}\nSummary: {}\nDescription:\n{}\n'.format(f['name'], f['path'], 'yes' if is_enabled else 'no', f['summary'],''.join(f['description']))) | ||
| 61 | |||
| 62 | all_enabled_fragments = (self.tinfoil.config_data.getVar('OE_FRAGMENTS') or "").split() | ||
| 63 | |||
| 64 | for layername, layerdata in self.discover_fragments().items(): | ||
| 65 | layerdir = layerdata['layerdir'] | ||
| 66 | fragments = layerdata['fragments'] | ||
| 67 | enabled_fragments = [f for f in fragments if f['name'] in all_enabled_fragments] | ||
| 68 | disabled_fragments = [f for f in fragments if f['name'] not in all_enabled_fragments] | ||
| 69 | |||
| 70 | print('Available fragments in {} layer located in {}:\n'.format(layername, layerdir)) | ||
| 71 | if enabled_fragments: | ||
| 72 | print('Enabled fragments:') | ||
| 73 | for f in enabled_fragments: | ||
| 74 | print_fragment(f, args.verbose, is_enabled=True) | ||
| 75 | print('') | ||
| 76 | if disabled_fragments: | ||
| 77 | print('Unused fragments:') | ||
| 78 | for f in disabled_fragments: | ||
| 79 | print_fragment(f, args.verbose, is_enabled=False) | ||
| 80 | print('') | ||
| 81 | |||
| 82 | def fragment_exists(self, fragmentname): | ||
| 83 | for layername, layerdata in self.discover_fragments().items(): | ||
| 84 | for f in layerdata['fragments']: | ||
| 85 | if f['name'] == fragmentname: | ||
| 86 | return True | ||
| 87 | return False | ||
| 88 | |||
| 89 | def create_conf(self, confpath): | ||
| 90 | if not os.path.exists(confpath): | ||
| 91 | with open(confpath, 'w') as f: | ||
| 92 | f.write('') | ||
| 93 | with open(confpath, 'r') as f: | ||
| 94 | lines = f.read() | ||
| 95 | if "OE_FRAGMENTS += " not in lines: | ||
| 96 | lines += "\nOE_FRAGMENTS += \"\"\n" | ||
| 97 | with open(confpath, 'w') as f: | ||
| 98 | f.write(lines) | ||
| 99 | |||
| 100 | def do_enable_fragment(self, args): | ||
| 101 | """ Enable a fragment in the local build configuration """ | ||
| 102 | def enable_helper(varname, origvalue, op, newlines): | ||
| 103 | enabled_fragments = origvalue.split() | ||
| 104 | if args.fragmentname in enabled_fragments: | ||
| 105 | print("Fragment {} already included in {}".format(args.fragmentname, args.confpath)) | ||
| 106 | else: | ||
| 107 | enabled_fragments.append(args.fragmentname) | ||
| 108 | return " ".join(enabled_fragments), None, 0, True | ||
| 109 | |||
| 110 | if not self.fragment_exists(args.fragmentname): | ||
| 111 | raise Exception("Fragment {} does not exist; use 'list-fragments' to see the full list.".format(args.fragmentname)) | ||
| 112 | |||
| 113 | self.create_conf(args.confpath) | ||
| 114 | modified = bb.utils.edit_metadata_file(args.confpath, ["OE_FRAGMENTS"], enable_helper) | ||
| 115 | if modified: | ||
| 116 | print("Fragment {} added to {}.".format(args.fragmentname, args.confpath)) | ||
| 117 | |||
| 118 | def do_disable_fragment(self, args): | ||
| 119 | """ Disable a fragment in the local build configuration """ | ||
| 120 | def disable_helper(varname, origvalue, op, newlines): | ||
| 121 | enabled_fragments = origvalue.split() | ||
| 122 | if args.fragmentname in enabled_fragments: | ||
| 123 | enabled_fragments.remove(args.fragmentname) | ||
| 124 | else: | ||
| 125 | print("Fragment {} not currently enabled in {}".format(args.fragmentname, args.confpath)) | ||
| 126 | return " ".join(enabled_fragments), None, 0, True | ||
| 127 | |||
| 128 | self.create_conf(args.confpath) | ||
| 129 | modified = bb.utils.edit_metadata_file(args.confpath, ["OE_FRAGMENTS"], disable_helper) | ||
| 130 | if modified: | ||
| 131 | print("Fragment {} removed from {}.".format(args.fragmentname, args.confpath)) | ||
| 132 | |||
| 133 | def do_disable_all_fragments(self, args): | ||
| 134 | """ Disable all fragments in the local build configuration """ | ||
| 135 | def disable_all_helper(varname, origvalue, op, newlines): | ||
| 136 | return "", None, 0, True | ||
| 137 | |||
| 138 | self.create_conf(args.confpath) | ||
| 139 | modified = bb.utils.edit_metadata_file(args.confpath, ["OE_FRAGMENTS"], disable_all_helper) | ||
| 140 | if modified: | ||
| 141 | print("All fragments removed from {}.".format(args.confpath)) | ||
| 142 | |||
| 143 | def register_commands(self, sp): | ||
| 144 | default_confpath = os.path.join(os.environ["BBPATH"], "conf/auto.conf") | ||
| 145 | |||
| 146 | parser_list_fragments = self.add_command(sp, 'list-fragments', self.do_list_fragments, parserecipes=False) | ||
| 147 | parser_list_fragments.add_argument("--confpath", default=default_confpath, help='Configuration file which contains a list of enabled fragments (default is {}).'.format(default_confpath)) | ||
| 148 | parser_list_fragments.add_argument('--verbose', '-v', action='store_true', help='Print extended descriptions of the fragments') | ||
| 149 | |||
| 150 | parser_enable_fragment = self.add_command(sp, 'enable-fragment', self.do_enable_fragment, parserecipes=False) | ||
| 151 | parser_enable_fragment.add_argument("--confpath", default=default_confpath, help='Configuration file which contains a list of enabled fragments (default is {}).'.format(default_confpath)) | ||
| 152 | parser_enable_fragment.add_argument('fragmentname', help='The name of the fragment (use list-fragments to see them)') | ||
| 153 | |||
| 154 | parser_disable_fragment = self.add_command(sp, 'disable-fragment', self.do_disable_fragment, parserecipes=False) | ||
| 155 | parser_disable_fragment.add_argument("--confpath", default=default_confpath, help='Configuration file which contains a list of enabled fragments (default is {}).'.format(default_confpath)) | ||
| 156 | parser_disable_fragment.add_argument('fragmentname', help='The name of the fragment') | ||
| 157 | |||
| 158 | parser_disable_all = self.add_command(sp, 'disable-all-fragments', self.do_disable_all_fragments, parserecipes=False) | ||
| 159 | parser_disable_all.add_argument("--confpath", default=default_confpath, help='Configuration file which contains a list of enabled fragments (default is {}).'.format(default_confpath)) | ||
diff --git a/meta/lib/oeqa/selftest/cases/bblayers.py b/meta/lib/oeqa/selftest/cases/bblayers.py index 695d17377d..68b0377720 100644 --- a/meta/lib/oeqa/selftest/cases/bblayers.py +++ b/meta/lib/oeqa/selftest/cases/bblayers.py | |||
| @@ -240,3 +240,34 @@ class BitbakeLayers(OESelftestTestCase): | |||
| 240 | self.assertEqual(first_desc_2, '', "Describe not cleared: '{}'".format(first_desc_2)) | 240 | self.assertEqual(first_desc_2, '', "Describe not cleared: '{}'".format(first_desc_2)) |
| 241 | self.assertEqual(second_rev_2, second_rev_1, "Revision should not be updated: '{}'".format(second_rev_2)) | 241 | self.assertEqual(second_rev_2, second_rev_1, "Revision should not be updated: '{}'".format(second_rev_2)) |
| 242 | self.assertEqual(second_desc_2, second_desc_1, "Describe should not be updated: '{}'".format(second_desc_2)) | 242 | self.assertEqual(second_desc_2, second_desc_1, "Describe should not be updated: '{}'".format(second_desc_2)) |
| 243 | |||
| 244 | class BitbakeConfigBuild(OESelftestTestCase): | ||
| 245 | def test_enable_disable_fragments(self): | ||
| 246 | self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_VARIABLE'), None) | ||
| 247 | self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_ANOTHER_VARIABLE'), None) | ||
| 248 | |||
| 249 | runCmd('bitbake-config-build enable-fragment selftest/test-fragment') | ||
| 250 | self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_VARIABLE'), 'somevalue') | ||
| 251 | self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_ANOTHER_VARIABLE'), None) | ||
| 252 | |||
| 253 | runCmd('bitbake-config-build enable-fragment selftest/more-fragments-here/test-another-fragment') | ||
| 254 | self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_VARIABLE'), 'somevalue') | ||
| 255 | self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_ANOTHER_VARIABLE'), 'someothervalue') | ||
| 256 | |||
| 257 | fragment_metadata_command = "bitbake-getvar -f {} --value {}" | ||
| 258 | result = runCmd(fragment_metadata_command.format("selftest/test-fragment", "BB_CONF_FRAGMENT_SUMMARY")) | ||
| 259 | self.assertIn("This is a configuration fragment intended for testing in oe-selftest context", result.output) | ||
| 260 | result = runCmd(fragment_metadata_command.format("selftest/test-fragment", "BB_CONF_FRAGMENT_DESCRIPTION")) | ||
| 261 | self.assertIn("It defines a variable that can be checked inside the test.", result.output) | ||
| 262 | result = runCmd(fragment_metadata_command.format("selftest/more-fragments-here/test-another-fragment", "BB_CONF_FRAGMENT_SUMMARY")) | ||
| 263 | self.assertIn("This is a second configuration fragment intended for testing in oe-selftest context", result.output) | ||
| 264 | result = runCmd(fragment_metadata_command.format("selftest/more-fragments-here/test-another-fragment", "BB_CONF_FRAGMENT_DESCRIPTION")) | ||
| 265 | self.assertIn("It defines another variable that can be checked inside the test.", result.output) | ||
| 266 | |||
| 267 | runCmd('bitbake-config-build disable-fragment selftest/test-fragment') | ||
| 268 | self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_VARIABLE'), None) | ||
| 269 | self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_ANOTHER_VARIABLE'), 'someothervalue') | ||
| 270 | |||
| 271 | runCmd('bitbake-config-build disable-fragment selftest/more-fragments-here/test-another-fragment') | ||
| 272 | self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_VARIABLE'), None) | ||
| 273 | self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_ANOTHER_VARIABLE'), None) | ||
