blob: d75a7c2d0fe8d5646804aac290ba99b8f2ff0c4d [file] [log] [blame]
# Copyright 2025 The Pigweed Authors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
_COMMON_KCONFIG_TREE_ATTRS = {
"zephyr_root": attr.label(
mandatory = True,
allow_single_file = True,
),
"board_name": attr.string(
mandatory = True,
),
"board_qualifiers": attr.string(),
}
_COMMON_CONF_FILE_ATTRS = {
"conf_file": attr.label(
mandatory = True,
allow_single_file = True,
),
"extra_conf_files": attr.label_list(),
}
def _construct_environment(repo_ctx):
srctree = str(repo_ctx.path(repo_ctx.attr.zephyr_root).dirname)
# Provide paths to kconfiglib and kconfigfunctions.py.
pythonpath = repo_ctx.getenv("PYTHONPATH")
pythonpath = pythonpath + ":" + srctree + "/scripts/kconfig"
# Directories to hold outputs of kconfig_gen.
kconfig_binary_dirname = "Kconfig"
kconfig_board_dirname = "Kconfig/boards"
# Zephyr 4.3 added an "env file" in the Kconfig tree. This is for post-build
# metadata generation but is unconditionally sourced. We create an empty env
# file here since we don't care about post-build metadata.
kconfig_env_filename = "kconfig_module_dirs.env"
repo_ctx.file(kconfig_env_filename)
environment = {
"srctree": srctree,
"ZEPHYR_BASE": srctree,
"PYTHONPATH": pythonpath,
"KCONFIG_BINARY_DIR": str(repo_ctx.path(kconfig_binary_dirname)),
"KCONFIG_BOARD_DIR": str(repo_ctx.path(kconfig_board_dirname)),
"KCONFIG_ENV_FILE": str(repo_ctx.path(kconfig_env_filename)),
"BOARD": repo_ctx.attr.board_name,
"BOARD_QUALIFIERS": repo_ctx.attr.board_qualifiers,
"ARCH_DIR": srctree + "/arch",
"HWM_SCHEME": "v2",
}
return environment
def _run_kconfig_gen(repo_ctx, environment):
# Generate Kconfig snippets pointing to soc and arch directories.
kconfig_gen_script_path = repo_ctx.path(Label("//scripts/build:kconfig_gen.py"))
repo_ctx.watch(kconfig_gen_script_path)
srctree = environment["srctree"]
result = repo_ctx.execute(
[
repo_ctx.which("python3"),
kconfig_gen_script_path,
"--arch-root",
srctree,
"--soc-root",
srctree,
],
environment=environment,
)
if result.return_code != 0:
fail("Failed to run kconfig_gen (%d):\n%s" % (result.return_code, result.stderr))
def _write_configs_dot_c(repo_ctx):
# Generate configs.c
# Only applies to project-specific configs repository.
configs_c_filename = "generated/configs.c"
repo_ctx.file(configs_c_filename)
generate_configs_dot_c_script_path = repo_ctx.path(
Label("//scripts/build:generate_configs_dot_c.py"))
repo_ctx.watch(generate_configs_dot_c_script_path)
args = [
repo_ctx.which("python3"),
generate_configs_dot_c_script_path,
"--autoconf",
repo_ctx.path("generated/zephyr/autoconf.h"),
"-o",
repo_ctx.path(configs_c_filename),
]
result = repo_ctx.execute(args)
if result.return_code != 0:
fail("Failed to generate configs.c from autoconf.h (%d):\n%s" % (result.return_code, result.stderr))
def _write_build_file(repo_ctx):
# Declare the autoconf cc library
# Only applies to project-specific configs repository.
repo_ctx.file(
"BUILD.bazel",
content=
"""package(default_visibility = ["//visibility:public"])
cc_library(
name = "autoconf",
hdrs = ["generated/zephyr/autoconf.h"],
includes = ["generated"],
)
filegroup(
name = "configs_file",
srcs = ["generated/configs.c"],
)
""",)
def _gen_kconfiglib_impl(repo_ctx):
environment = _construct_environment(repo_ctx)
_run_kconfig_gen(repo_ctx, environment)
# Parse the Kconfig tree.
build_file_name = "BUILD.bazel"
build_file_path = repo_ctx.path(build_file_name)
repo_ctx.file(build_file_name)
bazel_config_script_path = repo_ctx.path(Label("//scripts/build:bazel_config.py"))
repo_ctx.watch(bazel_config_script_path)
srctree = environment["srctree"]
args = [
repo_ctx.which("python3"),
bazel_config_script_path,
"--kconfig",
srctree + "/Kconfig",
"gen_kconfig",
"-o",
build_file_path,
]
result = repo_ctx.execute(
args,
environment=environment,
)
if result.return_code != 0:
fail("Failed to generate kconfig BUILD file (%d):\n%s" % (result.return_code, result.stderr))
gen_kconfiglib = repository_rule(
implementation = _gen_kconfiglib_impl,
attrs = _COMMON_KCONFIG_TREE_ATTRS,
)
def _gen_projectlib_impl(repo_ctx):
environment = _construct_environment(repo_ctx)
_run_kconfig_gen(repo_ctx, environment)
# Parse the Kconfig tree.
bazel_config_script_path = repo_ctx.path(Label("//scripts/build:bazel_config.py"))
repo_ctx.watch(bazel_config_script_path)
bzl_file_name = "kconfig_flag_values.bzl"
bzl_file_path = repo_ctx.path(bzl_file_name)
repo_ctx.file(bzl_file_name)
srctree = environment["srctree"]
args = [
repo_ctx.which("python3"),
bazel_config_script_path,
"--kconfig",
srctree + "/Kconfig",
"gen_project",
"--project",
str(repo_ctx.path(repo_ctx.attr.conf_file)),
"-o",
bzl_file_path,
"--extra_configs_in",
]
for config in repo_ctx.attr.extra_conf_files:
args.append(repo_ctx.path(config))
repo_ctx.file("generated/zephyr/autoconf.h")
result = repo_ctx.execute(
args,
environment=environment,
)
if result.return_code != 0:
fail("Failed to generate kconfig BUILD file (%d):\n%s" % (result.return_code, result.stderr))
_write_configs_dot_c(repo_ctx)
_write_build_file(repo_ctx)
gen_projectlib = repository_rule(
implementation = _gen_projectlib_impl,
attrs = _COMMON_KCONFIG_TREE_ATTRS | _COMMON_CONF_FILE_ATTRS,
)
def _kconfig_impl(module_ctx):
if not module_ctx.modules[0].tags.tree:
fail(
msg = "kconfig requires inputs via the `tree` tag",
)
if len(module_ctx.modules[0].tags.tree) > 1:
fail(
msg = "kconfig only supports a single Kconfig tree",
)
zephyr_root = module_ctx.modules[0].tags.tree[0].zephyr_root
board_name = module_ctx.modules[0].tags.tree[0].board_name
board_qualifiers = module_ctx.modules[0].tags.tree[0].board_qualifiers
# Generate a repository to hold all the flags parsed from Kconfig symbols.
# These flags don't have values yet.
gen_kconfiglib(
name = "kconfig",
zephyr_root = zephyr_root,
board_name = board_name,
board_qualifiers = board_qualifiers,
)
# Generate one project configs repository for each project.
for project in module_ctx.modules[0].tags.project:
# Each project must have one prj.conf file and may have zero or more
# extra conf files.
conf_file = project.conf_file
extra_conf_files = project.extra_conf_files
# Tell the user the namespace of the generated flags.
print("Generating Kconfig-Bazel flags in repository: " + project.name)
gen_projectlib(
name = project.name,
zephyr_root = zephyr_root,
board_name = board_name,
board_qualifiers = board_qualifiers,
conf_file = conf_file,
extra_conf_files = extra_conf_files,
)
_kconfig_tree = tag_class(
attrs = _COMMON_KCONFIG_TREE_ATTRS,
)
_kconfig_project = tag_class(
attrs = {
"name": attr.string(
mandatory = True,
),
} | _COMMON_CONF_FILE_ATTRS,
)
kconfig = module_extension(
doc = """kconfig parses a Kconfig tree and generates a repository
containing Bazel config settings, each corresponding to a Kconfig symbol.
These config settings can be used with select() to conditionally compile
sources within Zephyr. Also generates a repository for each configured
project containing the config setting values parsed from prj.conf.""",
implementation = _kconfig_impl,
tag_classes = {
"tree": _kconfig_tree,
"project": _kconfig_project,
},
)