Skip to content
Closed
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
1 change: 0 additions & 1 deletion .ci/micromamba/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ channels:
dependencies:
- python=3.11
- pytest
- pylint
- pycodestyle
- buildifier
- clang=21
Expand Down
1 change: 0 additions & 1 deletion .ci/micromamba/env.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ channels:
dependencies:
- python=3.11
- pytest
- pylint
- pycodestyle
- buildifier
- clang=21
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ jobs:

- name: Run lint tests
run: |
pylint .
bazel test //test/buildifier --test_output=all
bazel test //... --test_tag_filters=pylint --test_output=all
shell: bash -el {0}

# Splitting test runs to: bazel unit tests, bazel foss tests, python unittests
Expand Down Expand Up @@ -155,8 +155,8 @@ jobs:

- name: Run lint tests
run: |
pylint .
runuser -u test -- bazel test //test/buildifier --test_output=all
runuser -u test -- bazel test //... --test_tag_filters=pylint --test_output=all
shell: bash -el {0}

# Splitting test runs to: bazel unit tests, bazel foss tests, python unittests
Expand Down
11 changes: 0 additions & 11 deletions .pylintrc

This file was deleted.

22 changes: 22 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,28 @@ codechecker_extension = use_extension(
)
use_repo(codechecker_extension, "default_codechecker_tools")

python = use_extension(
"@rules_python//python/extensions:python.bzl",
"python",
dev_dependency = True,
)
python.toolchain(
is_default = True,
python_version = "3.12",
)

pip = use_extension(
"@rules_python//python/extensions:pip.bzl",
"pip",
dev_dependency = True,
)
pip.parse(
hub_name = "pylint_deps",
python_version = "3.12",
requirements_lock = "//test/pylint:requirements.txt",
)
use_repo(pip, "pylint_deps")

register_toolchains(
"//:default_toolchain",
)
20 changes: 17 additions & 3 deletions docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,23 @@ Or let one script do the whole round:
On how to run or add a new test, see
[test/README.md](https://github.com/Ericsson/rules_codechecker/blob/main/test/README.md).

Note that `test/unit/external_repository` is in `.bazelignore`, it consumes
these rules as an external module and therefore runs through `pytest` only.
It is the test that catches breakage visible to our users but not to us.
### Linting Python with Bazel

Python targets are linted through a per-target Bazel macro.
Add a `pylint(...)` target next to a top-level
`py_binary`/`py_library`/`py_test`:

```starlark
load("//test/pylint:pylint_test.bzl", "pylint")

pylint(
name = "pylint",
targets = [":my_py_binary"],
)
```

It lints the targets and their dependencies (except pip packages)
These targets run as part of `bazel test //...` (tagged `pylint`).


## Submitting a patch
Expand Down
10 changes: 10 additions & 0 deletions src/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
load("@rules_python//python:py_binary.bzl", "py_binary")
load("//test/pylint:pylint_test.bzl", "pylint")

# Tool filter compile_commands.json file
py_binary(
Expand All @@ -32,6 +33,15 @@ py_binary(
visibility = ["//visibility:public"],
)

pylint(
name = "pylint",
targets = [
":codechecker_script",
":compile_commands_filter",
":per_file_script",
],
)

# The following are flags and default values for clang_tidy_aspect
filegroup(
name = "clang_tidy_config_default",
Expand Down
Empty file removed src/__init__.py
Empty file.
14 changes: 14 additions & 0 deletions test/foss/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,22 @@
# To add a new project: add a foss_test() call below.
# -----------------------------------------------------------------------

load("@rules_python//python:py_library.bzl", "py_library")
load("//test/pylint:pylint_test.bzl", "pylint")
load(":foss_test.bzl", "foss_test")

# Library wrapping the foss_test runner so it can be linted.
py_library(
name = "foss_test_runner_lib",
srcs = ["foss_test_runner.py"],
tags = ["manual"],
)

pylint(
name = "pylint",
targets = [":foss_test_runner_lib"],
)

foss_test(
name = "zlib",
tests = [
Expand Down
8 changes: 8 additions & 0 deletions test/pylint/.pylintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[MASTER]

[FORMAT]
max-line-length=80

[MESSAGES CONTROL]
disable=
fixme,
25 changes: 24 additions & 1 deletion test/foss/__init__.py → test/pylint/BUILD
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2023 Ericsson AB
# 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.
Expand All @@ -11,3 +11,26 @@
# 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("@rules_python//python:py_binary.bzl", "py_binary")
load("//test/pylint:pylint_test.bzl", "pylint")

exports_files([
".pylintrc",
"pylint_runner.py",
"requirements.txt",
])

py_binary(
name = "pylint_runner",
srcs = ["pylint_runner.py"],
main = "pylint_runner.py",
visibility = ["//visibility:public"],
deps = ["@pylint_deps//pylint"],
)

# Lint the pylint runner itself.
pylint(
name = "pylint",
targets = [":pylint_runner"],
)
79 changes: 79 additions & 0 deletions test/pylint/pylint_runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# 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.

"""Runner script for the per-target pylint Bazel test.

Reads a manifest of .py files (produced by the pylint rule) and runs
pylint on exactly those files. Imports are resolved from this runner's
own sys.path, which rules_python populates from the linted target's
runfiles; no repository-root chdir is needed.

Pylint is imported as a Python dependency provided by Bazel,
not invoked from PATH.
"""

import argparse
import os
import sys

from pylint import lint


def read_manifest(manifest):
"""Read the list of files to lint from the manifest."""
files = []
with open(manifest, encoding="utf-8") as handle:
for line in handle:
entry = line.strip()
if entry:
files.append(entry)
return files


def main():
"""Parse arguments and run pylint on the manifest sources."""
parser = argparse.ArgumentParser(
description="Run pylint on Python source files."
)
parser.add_argument(
"--manifest",
required=True,
help="Path to a manifest file listing the .py files to lint.",
)
parser.add_argument(
"--rcfile",
default=None,
help="Path to a .pylintrc configuration file.",
)
args = parser.parse_args()

rcfile_path = os.path.realpath(args.rcfile) if args.rcfile else None
sources = [os.path.realpath(s) for s in read_manifest(args.manifest)]

if not sources:
print("pylint_runner: no .py files found")
sys.exit(1)

pylint_args = []
if rcfile_path:
pylint_args.append(f"--rcfile={rcfile_path}")
pylint_args.extend(sources)

print(f"Running: pylint {' '.join(pylint_args)}")
result = lint.Run(pylint_args, exit=False)
sys.exit(result.linter.msg_status)


if __name__ == "__main__":
main()
Loading
Loading