Skip to content
Draft
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
7 changes: 7 additions & 0 deletions defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,20 @@ load(
_platforms_transition = "platforms_transition",
)

# Store rule, for uploading results to a CodeChecker server
load(
"//src:store.bzl",
_store = "store",
)

codechecker_test = _codechecker_test
codechecker_suite = _codechecker_suite
codechecker_config = _codechecker_config
codechecker_toolchain = _codechecker_toolchain
compile_commands = _compile_commands
clang_tidy_test = _clang_tidy_test
clang_analyze_test = _clang_analyze_test
codechecker_store = _store

# Helper for the platform suffix codechecker_suite() adds to its test names
get_platform_alias = _get_platform_alias
Expand Down
9 changes: 5 additions & 4 deletions docs/codechecker.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,18 @@ bazel test ...
### Analysis results

You can find the analysis results in the `bazel-bin/` folder, on which you
can run [`CodeChecker store`](https://github.com/Ericsson/codechecker/blob/master/docs/web/user_guide.md#store)
or [`CodeChecker parse`](https://github.com/Ericsson/codechecker/blob/master/docs/analyzer/user_guide.md#parse).
can run [`CodeChecker parse`](https://github.com/Ericsson/codechecker/blob/master/docs/analyzer/user_guide.md#parse).
The precise output path to the directory can vary,
but you should look for `your_codechecker_rule_name/codechecker-files/data`.
In simpler cases, something like the following:

```bash
CodeChecker parse bazel-bin/your_codechecker_rule_name/codechecker-files/data
CodeChecker store bazel-bin/your_codechecker_rule_name/codechecker-files/data -n "Run name"
```

To upload results to a CodeChecker server, use the [`codechecker_store()`](store#store)
rule instead of calling `CodeChecker store` manually. See [Store](store) for
details.

<!-- For now, we consider codechecker() to be an internal rule.

### Build-only CodeChecker analysis: `codechecker()`
Expand Down
1 change: 1 addition & 0 deletions docs/public-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ load("@rules_codechecker//:defs.bzl", "codechecker_test")
| [`clang_tidy_test()`](clang#clang_tidy_test) | run clang-tidy without CodeChecker |
| [`clang_analyze_test()`](clang#clang_analyze_test) | run the Clang Static Analyzer without CodeChecker |
| [`compile_commands()`](compile-commands#compile_commands) | generate `compile_commands.json` |
| [`codechecker_store()`](store#store) | upload analysis results to a CodeChecker server |
| [`codechecker_toolchain()`](toolchains#providing-your-own-tools) | provide your own tools |


Expand Down
1 change: 1 addition & 0 deletions docs/sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@

- **Rules**
- [CodeChecker](codechecker)
- [Store](store)
- [Clang](clang)
- [Compilation Database](compile-commands)
77 changes: 77 additions & 0 deletions docs/store.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
Store
=====

## store()

The Bazel rule `store()` uploads CodeChecker analysis results to a remote
[CodeChecker server](https://codechecker.readthedocs.io/en/latest/web/user_guide/).
It collects the analysis output from one or more `codechecker_test()` targets
and runs
[`CodeChecker store`](https://github.com/Ericsson/codechecker/blob/master/docs/web/user_guide.md#store)
under the hood.

To use it, add the following to your BUILD file:

```python
load(
"@rules_codechecker//:defs.bzl",
"codechecker_test",
"codechecker_store",
)
```

Create a `codechecker_test()` target and a `codechecker_store()` target that references it:

```python
codechecker_test(
name = "codechecker",
targets = [
"your_target",
],
)

codechecker_store(
name = "store",
targets = [
":codechecker",
],
)
```

Then run it, passing the server URL and a run name:

```bash
bazel run //:store -- --url=http://localhost:8001/Default --name=my_run
```
Comment on lines +25 to +45

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.

Hi @furtib,
This is very interesting approach! Thanks for this prototype!
But I have many practical questions, for instance:

  • What if :codechecker test fails?
  • How to handle credentials?

Let's discuss!

@furtib furtib Sep 9, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hi!
Thanks for taking a look so fast!

  • The :codechecker test actually fails in this case!
  • Credentials: I have little experience in this part, so please help me out with how you might use it, but I have tested this solution by running CodeChecker cmd login before it, and it worked.
  • On another note: other command-line arguments.
    I have changed it so we capture everything after -- and pass it to CodeChecker. This should work for most arguments. One notable exception is the config file one; I think I should integrate this into the rule, otherwise users must use absolute paths for this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Do note that since bazel run is interactive, we could also prompt the user.


### Parameters

| Parameter | Description |
|-------------|-----------------------------------------------------------------------------|
| `name` | Name of the store target. |
| `targets` | List of `codechecker_test()` targets whose results should be stored. |
| `toolchain` | Optional `codechecker_toolchain()` target. When set, tools from this target are used instead of Bazel's toolchain resolution. |
| `tags` | Bazel tags. The `"store"` tag is added automatically. |

### Runtime arguments

The `--url` and `--name` arguments are required and passed after `--` on the
command line:

| Argument | Description |
|----------|--------------------------------------------------------------------|
| `--url` | URL of the CodeChecker server product, e.g. `http://localhost:8001/Default`. |
| `--name` | Name of the analysis run on the server. |


### How it works

1. `codechecker_test()` exposes its analysis output via an output group.
2. `codechecker_store()` collects those files from all its target dependencies.
3. The report data is symlinked to a writable temporary directory (Bazel outputs
are read-only and `CodeChecker store` needs write access).
4. `CodeChecker store` is executed against the specified server.

> [!NOTE]
> The `codechecker_store()` rule is an executable rule (`bazel run`), not a test rule.
> It is meant to be run manually when you want to publish results.
6 changes: 6 additions & 0 deletions src/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ py_binary(
visibility = ["//visibility:public"],
)

py_binary(
name = "store_script",
srcs = ["store_script.py"],
visibility = ["//visibility:public"],
)

# The following are flags and default values for clang_tidy_aspect
filegroup(
name = "clang_tidy_config_default",
Expand Down
3 changes: 3 additions & 0 deletions src/codechecker.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,9 @@ def _codechecker_test_impl(ctx):
runfiles = all_runfiles,
executable = launcher,
),
OutputGroupInfo(
codechecker_files = depset([codechecker_files]),
),
]

_codechecker_test = rule(
Expand Down
3 changes: 3 additions & 0 deletions src/per_file.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,9 @@ def _per_file_impl(ctx):
runfiles = ctx.runfiles(files = run_files),
executable = ctx.outputs.test_script,
),
OutputGroupInfo(
codechecker_files = files,
),
]

per_file_test = rule(
Expand Down
149 changes: 149 additions & 0 deletions src/store.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# Copyright 2026 Ericsson AB
#
# 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
#
# http://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.

"""
Rule for storing CodeChecker analysis results on a remote server.

Usage:
bazel run :my_store -- --url=http://localhost:8001/Default --name=my_run
"""

def _store_impl(ctx):
# Resolve CodeChecker from the toolchain
if ctx.attr.toolchain:
info = ctx.attr.toolchain[platform_common.ToolchainInfo].codecheckerinfo
else:
info = ctx.toolchains["//:toolchain_type"].codecheckerinfo

# Collect codechecker_files entries from targets.
#
# Each entry is passed to the store script via --files. Entries may
# be either:
# * a directory (monolithic codechecker_test): report data lives
# in a "data" subdirectory inside it, and
# * individual report files (per_file_test): plists/logs that
# already live directly under the target's "data" directory.
# The store script inspects each --files entry at runtime and
# handles directories and individual files accordingly.
codechecker_files_entries = []
for target in ctx.attr.targets:
if OutputGroupInfo in target:
files = target[OutputGroupInfo].codechecker_files.to_list()
codechecker_files_entries.extend(files)

if not codechecker_files_entries:
fail("No codechecker_files found in targets. " +
"Make sure targets are codechecker_test or " +
"per_file_test rules.")

store_script = ctx.attr._store_script[DefaultInfo].files_to_run

# Build --files arguments for each codechecker_files entry
files_args = " ".join([
"--files='{}'".format(d.short_path)
for d in codechecker_files_entries
])

# Generate the launcher shell script
launcher = ctx.actions.declare_file(
ctx.label.name + "_launcher.sh",
)
ctx.actions.write(
output = launcher,
content = """#!/bin/bash
exec {script} \
--codechecker_path '{codechecker}' \
{files_args} \
"$@"
""".format(
script = store_script.executable.short_path,
codechecker = info.codechecker.short_path,
files_args = files_args,
),
is_executable = True,
)

run_files = (
codechecker_files_entries +
[launcher] +
info.runfiles.to_list()
)
all_runfiles = ctx.runfiles(files = run_files)
all_runfiles = all_runfiles.merge(
ctx.attr._store_script[DefaultInfo].default_runfiles,
)

return [
DefaultInfo(
executable = launcher,
runfiles = all_runfiles,
),
]

_store = rule(
implementation = _store_impl,
attrs = {
"targets": attr.label_list(
doc = "List of codechecker_test targets " +
"whose results should be stored.",
),
"toolchain": attr.label(
default = None,
doc = "Optional toolchain() target. " +
"When set, tools from this target are used " +
"instead of Bazel's toolchain resolution.",
),
"_store_script": attr.label(
allow_files = True,
executable = True,
cfg = "target",
default = ":store_script",
),
},
executable = True,
toolchains = ["//:toolchain_type"],
)

def store(
name,
targets,
toolchain = None,
tags = [],
**kwargs):
"""
Macro to create a CodeChecker store target.

Run with:
bazel run :<name> -- --url=<server_url> --name=<run_name>

Args:
name: Name of the store target.
targets: List of codechecker_test targets whose
results should be stored.
toolchain: Optional toolchain() target.
tags: Bazel tags.
**kwargs: Other miscellaneous arguments.
"""
store_tags = [] + tags
if "store" not in tags:
store_tags.append("store")
_store(
name = name,
targets = targets,
toolchain = toolchain,
tags = store_tags,
# Needed to be able to depend on test targets.
testonly = True,
**kwargs
)
Loading
Loading