Skip to content

Commit 1111a63

Browse files
committed
Merge branch 'feat/support_kconfig' into 'main'
feat: support kconfig item as left value in if clause See merge request espressif/idf-component-manager!477
2 parents 8f3a697 + df28b00 commit 1111a63

File tree

10 files changed

+419
-170
lines changed

10 files changed

+419
-170
lines changed

docs/en/reference/manifest_file.rst

Lines changed: 135 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -390,35 +390,79 @@ Conditional Dependencies
390390

391391
The ``if`` field is a boolean expression that is evaluated to determine if the dependency should be included. An expression consists of three parts: left value, operator, and right value.
392392

393-
The left value could be
393+
Here's a detailed comparison table for the ``if`` field:
394394

395-
- keyword ``idf_version``: the version of ESP-IDF that is used to build the component
396-
- keyword ``target``: the current target selected for the project
397-
- a string
398-
- `environment variables`_
395+
.. list-table:: Supported Comparison Types
396+
:header-rows: 1
399397

400-
The right value could be
398+
- - Left Value Type
399+
- Operators
400+
- Right Value Type
401401

402-
- a string
403-
- a list of strings
402+
- - keyword ``idf_version``
403+
- N/A
404+
- string that represents :ref:`version ranges <version-range-specifications>`
404405

405-
The operator to compare with a string could be
406+
- - keyword ``target``
407+
- ``!=``, ``==``
408+
- string
406409

407-
- ``<=``
408-
- ``<``
409-
- ``>=``
410-
- ``>``
411-
- ``~=``
412-
- ``~``
413-
- ``=``
414-
- ``^``
415-
- ``!=``
416-
- ``==``
410+
- - keyword ``target``
411+
- ``in``, ``not in``
412+
- list of strings
417413

418-
The operator to compare with a list of strings could be
414+
- - arbitrary string
415+
- ``!=``, ``==``
416+
- string
419417

420-
- ``not in``
421-
- ``in``
418+
- - arbitrary string
419+
- ``in``, ``not in``
420+
- list of strings
421+
422+
- - `environment variables`_
423+
- N/A
424+
- string that represents :ref:`Version Ranges <version-range-specifications>`
425+
426+
- - `environment variables`_
427+
- ``!=``, ``==``
428+
- string
429+
430+
- - `environment variables`_
431+
- ``in``, ``not in``
432+
- list of strings
433+
434+
- - `kconfig options`_
435+
- ``==``, ``!=``
436+
- string
437+
438+
- - `kconfig options`_
439+
- ``in``, ``not in``
440+
- list of strings
441+
442+
- - `kconfig options`_
443+
- ``==``, ``!=``, ``<=``, ``<``, ``>=``, ``>``
444+
- decimal integer or hexadecimal integer (e.g. ``0x1234``)
445+
446+
- - `kconfig options`_
447+
- ``!=``, ``==``
448+
- boolean (``True``, ``False``)
449+
450+
.. versionadded:: 2.2.0
451+
452+
- Support `kconfig options`_ as left value (requires ESP-IDF v5.5+)
453+
- Support ``boolean``, ``integer`` and ``hexadecimal integer`` data types for `kconfig options`_
454+
455+
.. warning::
456+
457+
Since kconfig supports data types, you MUST use double quotes for strings while comparing with kconfig options. Otherwise, the component manager will treat the value as an integer, and raise an error if the value is not parsable as an integer.
458+
459+
Double quotes are not required for strings when not comparing with kconfig options, but we recommend using them for consistency.
460+
461+
.. warning::
462+
463+
If you specified `environment variables`_ as the left value of the if clause, and the environment variable is not set, an error will be raised.
464+
465+
If you specified `kconfig options`_ as the left value of the if clause, but the kconfig is included in your project, or components, an error will be raised.
422466

423467
To make a complex boolean expression, you can use nested parentheses with boolean operators ``&&`` and ``||``.
424468

@@ -429,22 +473,22 @@ To make a complex boolean expression, you can use nested parentheses with boolea
429473
version: "~1.0.0"
430474
rules:
431475
- if: "idf_version >=3.3,<5.0"
432-
- if: "target in [esp32, esp32c3]"
476+
- if: target in ["esp32", "esp32c3"]
433477
# the above two conditions equals to
434-
- if: idf_version >=3.3,<5.0 && target in [esp32, esp32c3]
478+
- if: idf_version >=3.3,<5.0 && target in ["esp32", "esp32c3"]
435479
436-
The left value of the if clause could be `environment variables`_. If the environment variable is not set, an error will be raised.
480+
.. hint::
437481

438-
One possible use-case is to test it in the CI/CD pipeline. For example:
482+
One possible use-case of `environment variables`_ is to test it in the CI/CD pipeline. For example:
439483

440-
.. code:: yaml
484+
.. code:: yaml
441485
442-
dependencies:
443-
optional_component:
444-
matches:
445-
- if: "$TESTING_COMPONENT in [foo, bar]"
486+
dependencies:
487+
optional_component:
488+
matches:
489+
- if: "$TESTING_COMPONENT in [foo, bar]"
446490
447-
The dependency will only be included when the environment variable ``TESTING_COMPONENT`` is set to ``foo`` or ``bar``.
491+
The dependency will only be included when the environment variable ``TESTING_COMPONENT`` is set to ``foo`` or ``bar``.
448492

449493
``version`` (if clause)
450494
-----------------------
@@ -472,6 +516,10 @@ Environment Variables
472516

473517
Environment variables are not allowed in manifests when uploading components to the ESP Component Registry.
474518

519+
.. warning::
520+
521+
Environment variables should only contain alphanumeric characters and underscores, and should not start with a number.
522+
475523
You can use environment variables for the attributes that support them. The component manager will replace the environment variables with their values. Use the following syntax:
476524

477525
- ``$VAR``
@@ -481,6 +529,61 @@ If you need to use a literal dollar sign (``$``), escape it with another dollar
481529

482530
.. _local-source:
483531

532+
Kconfig Options
533+
===============
534+
535+
You can use Kconfig options for the attributes that support them. All Kconfig options are prefixed with ``$kconfig.``, and don't have to include the ``CONFIG_`` prefix.
536+
537+
For example, to compare with the Kconfig option ``CONFIG_MY_OPTION``, use ``$kconfig.MY_OPTION``.
538+
539+
Besides, only Kconfig options that are defined in the ESP-IDF project and the direct dependency components are supported. For example
540+
541+
.. code:: yaml
542+
543+
dependencies:
544+
cmp:
545+
version: "*"
546+
matches:
547+
- if: "$kconfig.BOOTLOADER_LOG_LEVEL_WARN == True"
548+
549+
This works, since ``CONFIG_BOOTLOADER_LOG_LEVEL_WARN`` is defined in the ESP-IDF project.
550+
551+
.. code:: yaml
552+
553+
dependencies:
554+
example/cmp:
555+
version: "*"
556+
matches:
557+
- if: "$kconfig.MY_OPTION == True"
558+
559+
This will not work, since ``CONFIG_MY_OPTION`` is not defined in the ESP-IDF project.
560+
561+
.. code:: yaml
562+
563+
dependencies:
564+
espressif/mdns:
565+
version: "1.8.1"
566+
567+
example/cmp:
568+
version: "*"
569+
matches:
570+
- if: "$kconfig.MDNS_MAX_SERVICES == 10"
571+
572+
This works, since ``CONFIG_MDNS_MAX_SERVICES`` is defined in the ``espressif/mdns`` component, and ``espressif/mdns`` is a direct dependency of your project.
573+
574+
.. code:: yaml
575+
576+
dependencies:
577+
cmp_a:
578+
version: "*"
579+
580+
example/cmp:
581+
version: "*"
582+
matches:
583+
- if: "$kconfig.OPTION_FROM_CMP_B == True"
584+
585+
This will not work, even if ``CONFIG_OPTION_FROM_CMP_B`` is defined in the ``cmp_b`` component, and ``cmp_a`` depends on ``cmp_b``, since `cmp_b` is not a direct dependency of your project.
586+
484587
Local Directory Dependencies
485588
============================
486589

idf_component_manager/core.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from idf_component_tools.build_system_tools import build_name, is_component
2626
from idf_component_tools.config import root_managed_components_dir
2727
from idf_component_tools.constants import MANIFEST_FILENAME
28+
from idf_component_tools.debugger import KCONFIG_CONTEXT
2829
from idf_component_tools.errors import (
2930
FatalError,
3031
InternalError,
@@ -132,6 +133,7 @@ def __init__(
132133
lock_path: t.Optional[str] = None,
133134
manifest_path: t.Optional[str] = None,
134135
interface_version: int = 0,
136+
sdkconfig_json_file: t.Optional[str] = None,
135137
) -> None:
136138
# Working directory
137139
self.path = Path(path).resolve()
@@ -164,6 +166,9 @@ def __init__(
164166

165167
self.interface_version = interface_version
166168

169+
if sdkconfig_json_file:
170+
KCONFIG_CONTEXT.get().update_from_file(sdkconfig_json_file)
171+
167172
def _get_manifest_dir(self, component: str = 'main', path: t.Optional[str] = None) -> str:
168173
if component != 'main' and path is not None:
169174
raise FatalError(
@@ -665,6 +670,7 @@ def prepare_dep_dirs(
665670
managed_components_list_file,
666671
component_list_file,
667672
local_components_list_file=None,
673+
sdkconfig_json_file=None,
668674
):
669675
"""Process all manifests and download all dependencies"""
670676
# root core components
@@ -719,7 +725,10 @@ def prepare_dep_dirs(
719725

720726
project_requirements = ProjectRequirements(manifests)
721727
downloaded_components = download_project_dependencies(
722-
project_requirements, self.lock_path, self.managed_components_path
728+
project_requirements,
729+
self.lock_path,
730+
self.managed_components_path,
731+
sdkconfig_json_file,
723732
)
724733

725734
# Exclude requirements paths

idf_component_manager/prepare_components/prepare.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env python
22
#
3-
# SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD
3+
# SPDX-FileCopyrightText: 2019-2025 Espressif Systems (Shanghai) CO LTD
44
# SPDX-License-Identifier: Apache-2.0
55
#
66
# 'prepare.py' is a tool to be used by CMake build system to prepare components
@@ -12,6 +12,7 @@
1212

1313
from idf_component_manager.core import ComponentManager
1414
from idf_component_tools import error, setup_logging
15+
from idf_component_tools.debugger import KCONFIG_CONTEXT
1516
from idf_component_tools.errors import FatalError
1617

1718

@@ -20,6 +21,9 @@ def _component_list_file(build_dir):
2021

2122

2223
def prepare_dep_dirs(args):
24+
if args.sdkconfig_json_file:
25+
KCONFIG_CONTEXT.get().update_from_file(args.sdkconfig_json_file)
26+
2327
build_dir = args.build_dir or os.path.dirname(args.managed_components_list_file)
2428
ComponentManager(
2529
args.project_dir,
@@ -31,8 +35,15 @@ def prepare_dep_dirs(args):
3135
local_components_list_file=args.local_components_list_file,
3236
)
3337

38+
kconfig_ctx = KCONFIG_CONTEXT.get()
39+
if kconfig_ctx.missed_keys:
40+
exit(10)
41+
3442

3543
def inject_requirements(args):
44+
if args.sdkconfig_json_file:
45+
KCONFIG_CONTEXT.get().update_from_file(args.sdkconfig_json_file)
46+
3647
ComponentManager(
3748
args.project_dir,
3849
lock_path=args.lock_path,
@@ -58,17 +69,22 @@ def main():
5869
# *1* starting ESP-IDF 5.0
5970
# *2* starting ESP-IDF 5.1
6071
# *3* starting ESP-IDF 5.2
72+
# *4* starting ESP-IDF 5.5
6173

6274
parser.add_argument(
6375
'--interface_version',
6476
help='Version of ESP-IDF build system integration',
6577
default=0,
6678
type=int,
67-
choices=[0, 1, 2, 3],
79+
choices=[0, 1, 2, 3, 4],
6880
)
6981

7082
parser.add_argument('--lock_path', help='lock file path relative to the project path')
71-
83+
parser.add_argument(
84+
'--sdkconfig_json_file',
85+
required=False,
86+
help='Path to file with sdkconfig.json, used for parsing kconfig in if clauses',
87+
)
7288
subparsers = parser.add_subparsers(dest='step')
7389
subparsers.required = True
7490

@@ -109,7 +125,7 @@ def main():
109125

110126
for step in inject_step_data:
111127
inject_step = subparsers.add_parser(
112-
step['name'], help=f"Inject requirements to CMake{step.get('extra_help', '')}"
128+
step['name'], help=f'Inject requirements to CMake{step.get("extra_help", "")}'
113129
)
114130
inject_step.set_defaults(func=inject_requirements)
115131
inject_step.add_argument(

idf_component_tools/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,5 @@
2626
{} -m pip install --upgrade idf-component-manager
2727
""".format(sys.executable)
2828
MANIFEST_FILENAME = 'idf_component.yml'
29+
30+
KCONFIG_VAR_PREFIX = '$kconfig.'

idf_component_tools/debugger.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
1+
# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
22
# SPDX-License-Identifier: Apache-2.0
33
import contextvars
4+
import json
5+
import os.path
46
import typing as t
57
from collections import defaultdict
68

9+
if t.TYPE_CHECKING:
10+
from idf_component_tools.manifest import ComponentRequirement
11+
712

813
class DebugInfoCollector:
914
def __init__(self):
@@ -17,4 +22,21 @@ def declare_dep(self, dep_name: str, introduced_by: str):
1722
self.dep_introduced_by[dep_name].add(introduced_by)
1823

1924

25+
class SdkconfigContext:
26+
def __init__(self):
27+
self.sdkconfig: t.Dict[str, t.Any] = {}
28+
self.missed_keys: t.Dict[str, t.Set['ComponentRequirement']] = defaultdict(set)
29+
30+
def update_from_file(self, file_path: str):
31+
if not os.path.isfile(file_path):
32+
return
33+
34+
with open(file_path, encoding='utf8') as f:
35+
self.sdkconfig.update(json.load(f))
36+
37+
def set_missed_kconfig(self, key: str, req: 'ComponentRequirement'):
38+
self.missed_keys[key].add(req)
39+
40+
2041
DEBUG_INFO_COLLECTOR = contextvars.ContextVar('DebugInfoCollector', default=DebugInfoCollector())
42+
KCONFIG_CONTEXT = contextvars.ContextVar('SdkconfigContext', default=SdkconfigContext())

idf_component_tools/errors.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
1+
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
22
# SPDX-License-Identifier: Apache-2.0
33

44

@@ -122,3 +122,8 @@ class WarningAsExceptionError(FatalError):
122122

123123
class NoSuchProfile(FatalError):
124124
pass
125+
126+
127+
# not fatal error, since it shall be caught, and handle exit code
128+
class MissingKconfigError(Exception):
129+
pass

0 commit comments

Comments
 (0)