Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,7 @@ codechecker_extension = use_extension(
"module_register_default_codechecker_tools",
)
use_repo(codechecker_extension, "default_codechecker_tools")

register_toolchains(
"//src:codechecker_local_toolchain",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder what is the meaning of "locat"?
Is there a "global" alternative?

)
80 changes: 80 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,3 +427,83 @@ After that you can find all artifacts in `bazel-bin` directory:

# compile_commands.json for compile_commands_pass
cat bazel-bin/test/compile_commands_pass/compile_commands.json

Toolchains
----------

The previous rules locate the CodeChecker, Clang and clang-tidy executables through a
Bazel [toolchain](https://bazel.build/extending/toolchains). Modeling the tools
as a toolchain means the analysis rule (`codechecker_test`) never hardcode binary paths:
Bazel resolves the correct tools at build time, and you can override them
per-project or per-platform without touching the rules themselves.

### The default toolchain

For most users no setup is required. `rules_codechecker` ships with a module
extension that provisions a default set of tools and a pre-registered
toolchain.

> [!NOTE]
> The default tools resolve to the `CodeChecker`, `clang` and `clang-tidy`
> binaries available from PATH on your system (see the [Prerequisites](#prerequisites)).

To enable it, add the following to your `MODULE.bazel`:

```python
codechecker_extension = use_extension(
"@rules_codechecker//src:tools.bzl",
"module_register_default_codechecker_tools",
)
use_repo(codechecker_extension, "default_codechecker_tools")
register_toolchains("@rules_codechecker//src:codechecker_local_toolchain")
```

Or the following to your `WORKSPACE`:

```python
load(
"@rules_codechecker//src:tools.bzl",
"register_default_codechecker_tools",
)

register_default_codechecker_tools()
register_toolchains(
"//src:codechecker_local_toolchain",
)
```

### Providing your own tools

If you don't want the default system tools -- for example to pin a specific
CodeChecker version or point at a custom build —- you can define and register your own toolchain.

First, define an implementation in a BUILD file with `codechecker_toolchain()`:

```python
load(
"@rules_codechecker//src:codechecker_toolchain.bzl",
"codechecker_toolchain",
)

codechecker_toolchain(
name = "codechecker_custom",
clang_tidy_binary = "@my_tools//:clang-tidy",
clangsa_binary = "@my_tools//:clang",
codechecker_binary = "@my_tools//:CodeChecker",
Comment on lines +490 to +492

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The description and examples are missing for @my_tools//:CodeChecker etc

)

toolchain(
name = "codechecker_custom_toolchain",
toolchain = ":codechecker_custom",
toolchain_type = "@rules_codechecker//src:toolchain_type",
# Optionally constrain which execution platform this applies to:
# exec_compatible_with = ["@platforms//os:linux"],
)
```
Finally register it in your `MODULE.bazel` (or `WORKSPACE`):
```python
register_toolchains("//path/to:codechecker_custom_toolchain")
```

[!NOTE]
> Toolchains you register yourself take precedence over the default one if registered earlier.
5 changes: 4 additions & 1 deletion WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ load(

register_default_python_toolchain()

register_toolchains("@default_python_tools//:python_toolchain")
register_toolchains(
"@default_python_tools//:python_toolchain",
"//src:codechecker_local_toolchain",
)

register_default_codechecker_tools()

Expand Down
18 changes: 18 additions & 0 deletions src/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
# 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(":codechecker_toolchain.bzl", "codechecker_toolchain")

# Tool filter compile_commands.json file
# In bazel 6 we use our own python toolchain,
Expand Down Expand Up @@ -63,3 +64,20 @@ label_flag(
build_setting_default = ":clang_tidy_additional_deps_default",
visibility = ["//visibility:public"],
)

# named this by convention
# https://bazel.build/extending/toolchains#:~:text=%23%20By%20convention%2C%20toolchain%5Ftype%20targets%20are%20named%20%22toolchain%5Ftype%22%20and
toolchain_type(name = "toolchain_type")

codechecker_toolchain(
name = "codechecker_local",
clang_tidy_binary = "@default_codechecker_tools//:clang_tidy",
clangsa_binary = "@default_codechecker_tools//:clang",
codechecker_binary = "@default_codechecker_tools//:CodeChecker",
)

toolchain(
name = "codechecker_local_toolchain",
toolchain = "codechecker_local",
toolchain_type = ":toolchain_type",
)
42 changes: 27 additions & 15 deletions src/codechecker.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,6 @@
Rulesets for running codechecker in a single Bazel job.
"""

load(
"@default_codechecker_tools//:defs.bzl",
"CLANG_BIN_PATH",
"CLANG_TIDY_BIN_PATH",
"CODECHECKER_BIN_PATH",
)
load(
"codechecker_config.bzl",
"codechecker_config_internal",
Expand Down Expand Up @@ -99,6 +93,8 @@ def _codechecker_impl(ctx):

config_file, codechecker_env = get_config_file(ctx)

info = ctx.toolchains["//src:toolchain_type"].codecheckerinfo

codechecker_files = ctx.actions.declare_directory(ctx.label.name + "/codechecker-files")
ctx.actions.expand_template(
template = ctx.file._codechecker_script_template,
Expand All @@ -107,10 +103,10 @@ def _codechecker_impl(ctx):
substitutions = {
"{Mode}": "Run",
"{Verbosity}": "DEBUG",
"{clang_bin}": CLANG_BIN_PATH,
"{clang_tidy_bin}": CLANG_TIDY_BIN_PATH,
"{clang_bin}": info.clangsa_bin.path,
"{clang_tidy_bin}": info.clang_tidy_bin.path,
"{codechecker_analyze}": " ".join(ctx.attr.analyze),
"{codechecker_bin}": CODECHECKER_BIN_PATH,
"{codechecker_bin}": info.codechecker_bin.path,
"{codechecker_config}": config_file.path,
"{codechecker_env}": codechecker_env,
"{codechecker_files}": codechecker_files.path,
Expand All @@ -123,6 +119,9 @@ def _codechecker_impl(ctx):
ctx.actions.run(
inputs = depset(
[
info.codechecker_bin,
info.clangsa_bin,
info.clang_tidy_bin,
ctx.outputs.codechecker_script,
ctx.outputs.codechecker_commands,
ctx.outputs.codechecker_skipfile,
Expand Down Expand Up @@ -209,7 +208,10 @@ codechecker = rule(
"codechecker_skipfile": "%{name}/codechecker_skipfile.cfg",
"compile_commands": "%{name}/compile_commands.json",
},
toolchains = [python_toolchain_type()],
toolchains = [
python_toolchain_type(),
"//src:toolchain_type",
],
)

def _codechecker_test_impl(ctx):
Expand All @@ -229,6 +231,8 @@ def _codechecker_test_impl(ctx):
if not codechecker_files:
fail("Execution results required for codechecker test are not available")

info = ctx.toolchains["//src:toolchain_type"].codecheckerinfo

# Create test script from template
ctx.actions.expand_template(
template = ctx.file._codechecker_script_template,
Expand All @@ -238,15 +242,20 @@ def _codechecker_test_impl(ctx):
"{Mode}": "Test",
"{Severities}": " ".join(ctx.attr.severities),
"{Verbosity}": "INFO",
"{clang_bin}": CLANG_BIN_PATH,
"{clang_tidy_bin}": CLANG_TIDY_BIN_PATH,
"{codechecker_bin}": CODECHECKER_BIN_PATH,
"{clang_bin}": info.clangsa_bin.short_path,
"{clang_tidy_bin}": info.clang_tidy_bin.short_path,
"{codechecker_bin}": info.codechecker_bin.short_path,
"{codechecker_files}": codechecker_files.short_path,
},
)

# Return test script and all required files
run_files = default_runfiles + [ctx.outputs.codechecker_test_script]
run_files = default_runfiles + [
ctx.outputs.codechecker_test_script,
info.codechecker_bin,
info.clang_tidy_bin,
info.clangsa_bin,
]
return [
DefaultInfo(
files = depset(all_files),
Expand Down Expand Up @@ -306,7 +315,10 @@ _codechecker_test = rule(
"codechecker_test_script": "%{name}/codechecker_test_script.py",
"compile_commands": "%{name}/compile_commands.json",
},
toolchains = [python_toolchain_type()],
toolchains = [
python_toolchain_type(),
"//src:toolchain_type",
],
test = True,
)

Expand Down
2 changes: 1 addition & 1 deletion src/codechecker_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

EXECUTION_MODE = "{Mode}"
VERBOSITY = "{Verbosity}"
CODECHECKER_PATH = "{codechecker_bin}"
CODECHECKER_PATH = os.path.realpath("{codechecker_bin}")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So... we call realpath() only for CodeChecker?

CLANG_PATH = "{clang_bin}"
CLANG_TIDY_PATH = "{clang_tidy_bin}"
CODECHECKER_SKIPFILE = "{codechecker_skipfile}"
Expand Down
46 changes: 46 additions & 0 deletions src/codechecker_toolchain.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we discussed, probably we do not need prefix "codechecker" in the file name.

This file provides the toolchain rule for CodeChecker
"""

CodeCheckerInfo = provider(
doc = "This provider provides the executable path for CodeChecker and its related tools",
fields = {
"clang_tidy_bin": "clang-tidy executable",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need suffix "_bin"? Does it have any meaning?

"clangsa_bin": "Clang executable",
"codechecker_bin": "CodeChecker executable",
},
)

def _codechecker_toolchain_impl(ctx):
toolchain_info = platform_common.ToolchainInfo(
codecheckerinfo = CodeCheckerInfo(
codechecker_bin = ctx.executable.codechecker_binary,
clang_tidy_bin = ctx.executable.clang_tidy_binary,
clangsa_bin = ctx.executable.clangsa_binary,
),
)
return [toolchain_info]

codechecker_toolchain = rule(
implementation = _codechecker_toolchain_impl,
attrs = {
"clang_tidy_binary": attr.label(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same question: "_binary" - do we need?

doc = "clang-tidy executable",
allow_single_file = True,
executable = True,
cfg = "exec",
),
"clangsa_binary": attr.label(
doc = "clang executable",
allow_single_file = True,
executable = True,
cfg = "exec",
),
"codechecker_binary": attr.label(
doc = "CodeChecker executable",
allow_single_file = True,
executable = True,
cfg = "exec",
),
},
)
14 changes: 14 additions & 0 deletions src/per_file.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,16 @@ def _run_code_checker(
content = "\n".join(ctx.attr.skip),
)

info = ctx.toolchains["//src:toolchain_type"].codecheckerinfo

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we discussed that to get rid of "src" you may try alias. (I havent tried myself though :) )


if "--ctu" in options:
inputs = [
compile_commands_json,
config_file,
config,
info.codechecker_bin,
info.clangsa_bin,
info.clang_tidy_bin,
] + sources_and_headers
else:
# NOTE: we collect only headers, so CTU may not work!
Expand All @@ -73,24 +78,32 @@ def _run_code_checker(
config_file,
src,
config,
info.codechecker_bin,
info.clangsa_bin,
info.clang_tidy_bin,
], transitive = [headers])

outputs = [clang_tidy_plist, clangsa_plist, codechecker_log]

analyzer_output_paths = "clangsa," + clangsa_plist.path + \
";clang-tidy," + clang_tidy_plist.path

analyzer_executables = "clangsa:" + info.clangsa_bin.path + \
";clang-tidy:" + info.clang_tidy_bin.path

# Action to run CodeChecker for a file
ctx.actions.run(
inputs = inputs,
outputs = outputs,
executable = ctx.outputs.per_file_script,
arguments = [
info.codechecker_bin.path,
data_dir,
src.path,
codechecker_log.path,
config.path,
analyzer_output_paths,
analyzer_executables,
],
mnemonic = "CodeChecker",
use_default_shell_env = True,
Expand Down Expand Up @@ -258,4 +271,5 @@ per_file_test = rule(
"test_script": "%{name}/test_script.sh",
},
test = True,
toolchains = ["//src:toolchain_type"],
)
Loading
Loading