blob: 91ff58ca453f061a2a69cb097d1a55be2557b9de [file]
# 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.
load("//bazel/private:repo_rule_python.bzl", "COMMON_PY_REPO_RULE_ATTRS", "get_python")
_COMMON_GLOBAL_ATTRS = {
"oot_dts_roots": attr.label_list(),
"modules": attr.string_list(
doc = """Zephyr modules to enable for Kconfig purposes. A module's
Kconfig symbols are all set to false unless the module name is passed in here.
Listing a module here tells this module extension that this module is present.
The equivalent in cmake zephyr is west will enable all modules in west.yml.""",
),
}
_COMMON_KCONFIG_TREE_ATTRS = {
"zephyr_root": attr.label(
mandatory = True,
allow_single_file = True,
),
"workspace_root": attr.label(
mandatory = True,
allow_single_file = True,
doc = """A label to a file in the workspace root, e.g. @//:MODULE.bazel, this sets
the root for paths to local Zephyr modules""",
),
"boards": attr.string_list(
allow_empty = False,
doc = "All Zephyr boards used in the project including qualifiers",
),
"oot_board_dir": attr.label(),
} | COMMON_PY_REPO_RULE_ATTRS
_COMMON_CONF_FILE_ATTRS = {
"dir": attr.label(
mandatory = True,
allow_single_file = True,
),
"extra_conf_files": attr.label_list(),
}
def _gen_kconfiglib_impl(repo_ctx):
srctree = str(repo_ctx.path(repo_ctx.attr.zephyr_root).dirname)
python = get_python(
repo_ctx,
extra_import_paths = [
# Provide paths to kconfiglib and kconfigfunctions.py.
srctree + "/scripts/kconfig",
],
)
# Parse the Kconfig tree.
script_path = repo_ctx.path(Label("//scripts/build:kconfig_gen_repos.py"))
repo_ctx.watch_tree(script_path.dirname)
workspace_root_path = repo_ctx.path(repo_ctx.attr.workspace_root).dirname
for module in repo_ctx.attr.modules:
if module.startswith("//"):
repo_ctx.watch_tree(workspace_root_path.get_child(module[2:]))
args = [
script_path,
"--zephyr_root",
srctree,
"--workspace_root",
str(repo_ctx.path(repo_ctx.attr.workspace_root).dirname),
]
for module in repo_ctx.attr.modules:
args.append("--module")
args.append(module)
for board in repo_ctx.attr.boards:
args.append("--board")
args.append(board)
if repo_ctx.attr.oot_board_dir:
oot_board_dir_path = repo_ctx.path(repo_ctx.attr.oot_board_dir).dirname
repo_ctx.watch_tree(oot_board_dir_path)
args += [
"--oot_board_dir",
str(oot_board_dir_path),
]
for dts_root in repo_ctx.attr.oot_dts_roots:
oot_dts_root_path = repo_ctx.path(dts_root).dirname
repo_ctx.watch_tree(oot_dts_root_path)
args.append("--oot_dts_root")
args.append(str(oot_dts_root_path))
args += [
"tree",
"--output_dir",
repo_ctx.path("."),
]
result = python.execute(
args,
)
if result.return_code != 0:
fail("Failed to generate kconfig BUILD file (%d):\n%s\n%s" % (result.return_code, result.stdout, result.stderr))
gen_kconfiglib = repository_rule(
implementation = _gen_kconfiglib_impl,
attrs = _COMMON_GLOBAL_ATTRS | _COMMON_KCONFIG_TREE_ATTRS,
)
def _gen_projectlib_impl(repo_ctx):
srctree = str(repo_ctx.path(repo_ctx.attr.zephyr_root).dirname)
python = get_python(
repo_ctx,
extra_import_paths = [
# Provide paths to kconfiglib and kconfigfunctions.py.
srctree + "/scripts/kconfig",
],
)
# Invalidate generated stuff if conf file changed.
repo_ctx.watch(repo_ctx.attr.conf_file)
repo_ctx.watch(repo_ctx.attr.kconfig_tree)
for config in repo_ctx.attr.extra_conf_files:
repo_ctx.watch(config)
# Invalidate generated stuff if app directory changes.
repo_ctx.watch_tree(repo_ctx.path(repo_ctx.attr.dir).dirname)
# Invalidate generated stuff if app Kconfig or boards directory changes.
# We avoid watching the root directory to prevent circular dependencies
# with targets defined in the same package.
repo_ctx.watch(repo_ctx.attr.dir.relative(":Kconfig"))
repo_ctx.watch(repo_ctx.attr.dir.relative(":boards"))
# Parse the Kconfig tree.
script_path = repo_ctx.path(Label("//scripts/build:kconfig_gen_repos.py"))
repo_ctx.watch_tree(script_path.dirname)
workspace_root_path = repo_ctx.path(repo_ctx.attr.workspace_root).dirname
for module in repo_ctx.attr.modules:
if module.startswith("//"):
repo_ctx.watch_tree(workspace_root_path.get_child(module[2:]))
args = [
script_path,
"--zephyr_root",
srctree,
"--workspace_root",
str(repo_ctx.path(repo_ctx.attr.workspace_root).dirname),
"--board",
repo_ctx.attr.board,
]
for module in repo_ctx.attr.modules:
args.append("--module")
args.append(module)
if repo_ctx.attr.oot_board_dir:
oot_board_dir_path = repo_ctx.path(repo_ctx.attr.oot_board_dir).dirname
repo_ctx.watch_tree(oot_board_dir_path)
args += [
"--oot_board_dir",
str(oot_board_dir_path),
]
for dts_root in repo_ctx.attr.oot_dts_roots:
oot_dts_root_path = repo_ctx.path(dts_root).dirname
repo_ctx.watch_tree(oot_dts_root_path)
args.append("--oot_dts_root")
args.append(str(oot_dts_root_path))
args += [
"project",
"--conf_file",
repo_ctx.attr.conf_file,
"--output_dir",
repo_ctx.path("."),
]
if repo_ctx.attr.project_name:
args += ["--project_name", repo_ctx.attr.project_name]
if repo_ctx.attr.extra_conf_files:
args.append("--extra_conf_files")
for config in repo_ctx.attr.extra_conf_files:
args.append(str(repo_ctx.path(config)))
board_conf_file = repo_ctx.attr.dir.relative(":boards/{}.conf".format(repo_ctx.attr.board.replace("/", "_")))
board_conf_file_path = repo_ctx.path(board_conf_file)
if board_conf_file_path.exists:
args.append("--board_conf_file")
args.append(board_conf_file_path)
result = python.execute(
args,
)
if result.return_code != 0:
fail("Failed to generate project config files (%d):\n%s\n%s" % (result.return_code, result.stdout, result.stderr))
gen_projectlib = repository_rule(
implementation = _gen_projectlib_impl,
attrs = {
"board": attr.string(
mandatory = True,
doc = "Full Zephyr board name including qualifiers, e.g. board/core",
),
"conf_file": attr.label(
mandatory = True,
allow_single_file = True,
),
"project_name": attr.string(),
"kconfig_tree": attr.label(
doc = "Reference to the @kconfig repository generated by gen_kconfiglib.",
),
} | _COMMON_GLOBAL_ATTRS | _COMMON_KCONFIG_TREE_ATTRS | _COMMON_CONF_FILE_ATTRS,
)
def _kconfig_impl(module_ctx):
root_module = module_ctx.modules[0]
for mod in module_ctx.modules:
if mod.is_root:
root_module = mod
break
if not root_module.tags.tree:
fail(
msg = "kconfig requires inputs via the `tree` tag",
)
if len(root_module.tags.tree) > 1:
fail(
msg = "kconfig only supports a single Kconfig tree",
)
zephyr_root = root_module.tags.tree[0].zephyr_root
boards = root_module.tags.tree[0].boards
oot_board_dir = root_module.tags.tree[0].oot_board_dir
workspace_root = root_module.tags.tree[0].workspace_root
oot_dts_roots = []
zephyr_modules = []
if root_module.tags.global_setup:
oot_dts_roots = root_module.tags.global_setup[0].oot_dts_roots
zephyr_modules = root_module.tags.global_setup[0].modules
# 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,
workspace_root = workspace_root,
boards = boards,
oot_board_dir = oot_board_dir,
oot_dts_roots = oot_dts_roots,
modules = zephyr_modules,
)
# Generate one project configs repository for each project board combination.
for project in root_module.tags.project:
# The conf_file for a project can be manually specified. If it is not, the prj.conf
# in the project directory is used.
conf_file = project.conf_file
if conf_file == None:
conf_file = project.dir.relative(":prj.conf")
for board in project.boards:
# Tell the user the namespace of the generated flags.
board_name = board.replace("/", "_")
repo_name = "{}_{}".format(project.name, board_name)
print("Generating Kconfig-Bazel flags in repository: " + repo_name)
extra_conf_files = list(project.extra_conf_files)
if board == "native_sim":
extra_conf_files.append(Label("//bazel_overlay/boards/native/native_sim:native_sim.conf"))
gen_projectlib(
name = repo_name,
project_name = project.name,
dir = project.dir,
zephyr_root = zephyr_root,
workspace_root = workspace_root,
# May be different from the board in gen_kconfiglib().
board = board,
oot_board_dir = oot_board_dir,
oot_dts_roots = oot_dts_roots,
conf_file = conf_file,
extra_conf_files = extra_conf_files,
modules = zephyr_modules,
kconfig_tree = "@kconfig//:BUILD.bazel",
)
_kconfig_global_setup = tag_class(
attrs = _COMMON_GLOBAL_ATTRS,
)
_kconfig_tree = tag_class(
attrs = _COMMON_KCONFIG_TREE_ATTRS,
)
_kconfig_project = tag_class(
attrs = {
"name": attr.string(
mandatory = True,
),
"boards": attr.string_list(
mandatory = True,
doc = "Full Zephyr board names including qualifiers, e.g. board/soc/core",
),
"conf_file": attr.label(
allow_single_file = True,
doc = "Optional field to override using the default prj.conf config file.",
),
} | _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 = {
"global_setup": _kconfig_global_setup,
"tree": _kconfig_tree,
"project": _kconfig_project,
},
)