Custom `platformio.ini` options as `list[str]`, not `str`?

I want to use Custom options in platformio.ini — PlatformIO latest documentation

In my config I define an option like this:

custom_foo =
    bar
    baz

In my pio_extra_script.py I retrieve the value(s) like this:

values = env.GetProjectOption('custom_foo')

The result is a multiline str → not list[str], with values of the config, split by a new line character.

So I do it manually:

values = [
    value
    for value
    in env.GetProjectOption('custom_foo', default='').split('\n')
    if value != ''
]

BUT, interestingly, if I take a built–in config value, like GetProjectOption('build_src_flags'), it is already a list[str], and I don’t need to convert it manually.

So, the question is: can I somehow mark my custom_foo config option to automatically be a list[str], just like the build_src_flags is?

The use case.

You can use ProjectConfig internal API:

Import("env")

values = env.GetProjectConfig().parse_multi_values(env.GetProjectOption('custom_foo'))
1 Like

Cool, it is working, thanks.

The question remains though:

Or, in other words: why is GetProjectOption(build_src_flags) a list[str] automatically, while my custom options are not? I guess it is processed somewhere on the way?

I’m guessing I should somehow configure my option for option_metaplatformio-core/platformio/project/config.py at develop · platformio/platformio-core · GitHub

You can try to mock ProjectOption in PRE script and declare a custom project option. In this case, it can start with anything that you want (no need to use the prefix custom.

Please note that I didn’t test this. Please share here your feedback.

1 Like

It works… How bad is it? :smiley:


import click
from debug import var_dump
from platformio.project.config import ProjectOptions
from platformio.project.options import ConfigEnvOption
from typing import Callable

Import('env')

def list_available_packages() -> list[str]:
    return [
        package.metadata.name
        for package
        in env.PioPlatform().get_installed_packages()
    ]

def add_custom_config_option(
    name: str,
    description: str,
    type: Callable = str,
    multiple: bool = False,
    default=None,
    validate: Callable = None,
) -> None:
    option = ConfigEnvOption(
        group='custom',
        name=name,
        description=description,
        type=type,
        multiple=multiple,
        default=default,
        validate=validate,
    )
    ProjectOptions['%s.%s' % (option.scope, option.name)] = option

add_custom_config_option(
    name='custom_system_packages',
    description=(
        'Adds `-isystem` flag for the specified packages. '
        'As the result, warnings for those packages will be silenced.'
    ),
    multiple=True,
    type=click.Choice(list_available_packages()),
)

var_dump(env.GetProjectOption('custom_system_packages'))
  • it actually works within a post:extra_script.py.
  • I’m not sure if Callable is the proper typehint in type: Callable = str and validate: Callable = None in add_custom_config_option()
  • group='custom' in for ConfigEnvOption is a guess
  • description for ConfigEnvOption is useless? i’ve tried grep for it, but haven’t found any usage [?]
  • BTW, I believe multiple should be handled before validate and validate should be called process or processor → as it does not do any validation, but it is being used to change dir paths
1 Like

actually, as for the naming, my understanding is that it should be the other way around xd

right now type validates the value, while validate actually casts xd

And if the multiple key has no more meaning than to call a special callback that converts the value, then you can get rid of it for just reusing the current validate key:


def convert_to_multiple(self, value):
    return self.parse_multi_values(value or [])

[…]

            ConfigEnvOption(
                group="platform",
                name="framework",
                description="A list of project dependent frameworks",
                validate=convert_to_multiple,
                buildenvvar="PIOFRAMEWORK",
            )

validate=convert_to_multiple is instead of multiple=True. And now you can see how validate is actually more like convert / cast / process :wink:

1 Like

Thanks for sharing the example of how to use it! :pray:

It could be (more?) useful: ProjectOptionValueError: displays the config option description by MacDada · Pull Request #4674 · platformio/platformio-core · GitHub