Skip to content

Commit

Permalink
app: config: Add support for appending to the config string
Browse files Browse the repository at this point in the history
In some cases, and specifically in the manifest.group-filter and
manifest.project-filter options, it is sometimes useful to be able to
append to a value instead of replacing it completely.

For example, assuming one wants to add to an existing group filter,
without this patch the user needs to do:

(assuming the group filter is currently +unstable,-optional, and the
user wants to add +extras).
> west config manifest.group-filter
> west config manifest.group-filter +unstable,-optional,+extras

With this patch instead:

> west config -a manifest.group-filter ,+extras

Signed-off-by: Carles Cufi <[email protected]>
  • Loading branch information
carlescufi committed Jan 7, 2025
1 parent 79aa8c8 commit 33d4230
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 2 deletions.
28 changes: 26 additions & 2 deletions src/west/app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@
To set a value for <name>, type:
west config <name> <value>
To append to a value for <name>, type:
west config -a <name> <value>
A value must exist in the selected configuration file in order to be able
to append to it. The existing value can be empty.
Examples:
west config -a build.cmake-args -- " -DEXTRA_CFLAGS='-Wextra -g0' -DFOO=BAR"
west config -a manifest.group-filter ,+optional
To list all options and their values:
west config -l
Expand All @@ -64,7 +72,7 @@

CONFIG_EPILOG = '''\
If the configuration file to use is not set, reads use all three in
precedence order, and writes use the local file.'''
precedence order, and writes (including appends) use the local file.'''

ALL = ConfigFile.ALL
SYSTEM = ConfigFile.SYSTEM
Expand Down Expand Up @@ -92,13 +100,14 @@ def do_add_parser(self, parser_adder):
"action to perform (give at most one)"
).add_mutually_exclusive_group()


group.add_argument('-l', '--list', action='store_true',
help='list all options and their values')
group.add_argument('-d', '--delete', action='store_true',
help='delete an option in one config file')
group.add_argument('-D', '--delete-all', action='store_true',
help="delete an option everywhere it's set")
group.add_argument('-a', '--append', action='store_true',
help='append to an existing value')

group = parser.add_argument_group(
"configuration file to use (give at most one)"
Expand Down Expand Up @@ -129,13 +138,18 @@ def do_run(self, args, user_args):
elif not args.name:
self.parser.error('missing argument name '
'(to list all options and values, use -l)')
elif args.append:
if args.value is None:
self.parser.error('-a requires both name and value')

if args.list:
self.list(args)
elif delete:
self.delete(args)
elif args.value is None:
self.read(args)
elif args.append:
self.append(args)
else:
self.write(args)

Expand Down Expand Up @@ -180,6 +194,16 @@ def read(self, args):
self.dbg(f'{args.name} is unset')
raise CommandError(returncode=1)

def append(self, args):
self.check_config(args.name)
where = args.configfile or LOCAL
value = self.config.get(args.name, configfile=where)
if value is None:
self.die(f'option {args.name} not found in the {where.name.lower()} '
'configuration file')
args.value = value + args.value
self.write(args)

def write(self, args):
self.check_config(args.name)
what = args.configfile or LOCAL
Expand Down
39 changes: 39 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,45 @@ def test_local_creation_with_topdir():
assert 'pytest' not in cfg(f=GLOBAL)
assert cfg(f=LOCAL, topdir=str(topdir))['pytest']['key'] == 'val'

def test_append():
update_testcfg('pytest', 'key', 'system', configfile=SYSTEM)
update_testcfg('pytest', 'key', 'global', configfile=GLOBAL)
update_testcfg('pytest', 'key', 'local', configfile=LOCAL)
# Appending with no configfile specified should modify the local one
cmd('config -a pytest.key ,bar')

# Only the local one will be modified
assert cfg(f=SYSTEM)['pytest']['key'] == 'system'
assert cfg(f=GLOBAL)['pytest']['key'] == 'global'
assert cfg(f=LOCAL)['pytest']['key'] == 'local,bar'

# Test a more complex one, and at a particular configfile level
update_testcfg('build', 'cmake-args', '-DCONF_FILE=foo.conf', configfile=GLOBAL)
assert cfg(f=GLOBAL)['build']['cmake-args'] == '-DCONF_FILE=foo.conf'

# Use a list instead of a string to avoid one level of nested quoting
cmd(['config', '--global', '-a', 'build.cmake-args', '--',
' -DEXTRA_CFLAGS=\'-Wextra -g0\' -DFOO=BAR'])

assert cfg(f=GLOBAL)['build']['cmake-args'] == \
'-DCONF_FILE=foo.conf -DEXTRA_CFLAGS=\'-Wextra -g0\' -DFOO=BAR'

def test_append_novalue():
with pytest.raises(subprocess.CalledProcessError) as exc_info:
cmd('config -a pytest.foo', stderr=subprocess.STDOUT)
# Get the output into a variable to simplify pytest error messages
err_msg = exc_info.value.output.decode("utf-8")
assert '-a requires both name and value' in err_msg

def test_append_notfound():
update_testcfg('pytest', 'key', 'val', configfile=LOCAL)
with pytest.raises(subprocess.CalledProcessError) as exc_info:
cmd('config -a pytest.foo bar', stderr=subprocess.STDOUT)
# Get the output into a variable to simplify pytest error messages
err_msg = exc_info.value.output.decode("utf-8")
assert 'option pytest.foo not found in the local configuration file' in err_msg


def test_delete_basic():
# Basic deletion test: write local, verify global and system deletions
# don't work, then delete local does work.
Expand Down

0 comments on commit 33d4230

Please sign in to comment.