diff --git a/BUILD b/BUILD index 9992a061..07743858 100644 --- a/BUILD +++ b/BUILD @@ -46,3 +46,8 @@ exports_files( ["MODULE.bazel"], visibility = ["//test/buildifier:__pkg__"], ) + +exports_files( + ["requirements_lock.txt"], + visibility = ["//visibility:public"], +) diff --git a/MODULE.bazel b/MODULE.bazel index 7cf38295..848e072d 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -44,6 +44,22 @@ codechecker_extension = use_extension( ) use_repo(codechecker_extension, "default_codechecker_tools") +# pip-based CodeChecker: hermetic installation via rules_python +python = use_extension("@rules_python//python/extensions:python.bzl", "python") +python.toolchain( + is_default = True, + python_version = "3.12", +) + +pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip") +pip.parse( + hub_name = "pip", + python_version = "3.12", + requirements_lock = "//:requirements_lock.txt", +) +use_repo(pip, "pip") + register_toolchains( + "//src:codechecker_pip_toolchain", "//:default_toolchain", ) diff --git a/requirements_lock.txt b/requirements_lock.txt new file mode 100644 index 00000000..f7614729 --- /dev/null +++ b/requirements_lock.txt @@ -0,0 +1,47 @@ +alembic==1.19.1 +argcomplete==3.7.2 +authlib==1.7.2 +certifi==2026.7.22 +cffi==2.1.1 +charset-normalizer==3.5.0 +codechecker==6.28.0 +contourpy==1.3.3 +cryptography==50.0.0 +cycler==0.12.1 +dill==0.4.1 +fonttools==4.63.0 +gitdb==4.0.12 +gitpython==3.1.59 +greenlet==3.5.5 +idna==3.18 +jinja2==3.1.6 +joserfc==1.7.4 +jsonpath-ng==1.8.0 +kiwisolver==1.5.0 +lxml==6.1.1 +mako==1.4.1 +markupsafe==3.0.3 +matplotlib==3.11.1 +multiprocess==0.70.19 +numpy==2.5.2 +packaging==26.3 +pillow==12.3.0 +portalocker==3.2.0 +psutil==7.2.2 +pycparser==3.0 +pyparsing==3.3.2 +python-dateutil==2.9.0.post0 +python-docx==1.2.0 +pyyaml==6.0.3 +requests==2.34.2 +sarif-tools==3.0.5 +semver==3.0.4 +six==1.17.0 +smmap==5.0.3 +sqlalchemy==2.0.52 +thrift==0.24.0 +types-psutil==7.2.2.20260518 +types-pyyaml==6.0.12.20260724 +typing-extensions==4.16.0 +urllib3==2.7.0 +setuptools==80.9.0 diff --git a/src/BUILD b/src/BUILD index a43a5b3f..f7d54fe5 100644 --- a/src/BUILD +++ b/src/BUILD @@ -11,7 +11,15 @@ # 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( + "@rules_python//python:py_binary.bzl", + "py_binary", +) +load( + "@rules_python//python/entry_points:py_console_script_binary.bzl", + "py_console_script_binary", +) +load(":codechecker_toolchain.bzl", "codechecker_toolchain") # Tool filter compile_commands.json file py_binary( @@ -65,3 +73,25 @@ label_flag( build_setting_default = ":clang_tidy_additional_deps_default", visibility = ["//visibility:public"], ) + +# Hermetic pip-based CodeChecker toolchain (uses rules_python pip package) +py_console_script_binary( + name = "CodeChecker", + data = ["@pip//codechecker:data"], + pkg = "@pip//codechecker", + script = "CodeChecker", + visibility = ["//visibility:public"], +) + +codechecker_toolchain( + name = "codechecker_pip", + clang_tidy = "@default_codechecker_tools//:clang_tidy", + clangsa = "@default_codechecker_tools//:clang", + codechecker = ":CodeChecker", +) + +toolchain( + name = "codechecker_pip_toolchain", + toolchain = ":codechecker_pip", + toolchain_type = "//:toolchain_type", +) diff --git a/src/codechecker.bzl b/src/codechecker.bzl index 5c3e8c90..23340a78 100644 --- a/src/codechecker.bzl +++ b/src/codechecker.bzl @@ -97,6 +97,18 @@ def _codechecker_impl(ctx): else: info = ctx.toolchains["//:toolchain_type"].codecheckerinfo + # Derive CC_BIN_DIR so pip-installed CodeChecker can find its data dir. + # CodeChecker resolves its config as dirname(CC_BIN_DIR) which must + # contain config/, ld_logger/, etc. The pip wheel places these under + # data/share/codechecker/. We scan the toolchain runfiles for that path. + for f in info.runfiles.to_list(): + idx = f.path.find("data/share/codechecker/") + if idx >= 0: + cc_bin_dir = f.path[:idx] + "data/share/codechecker/bin" + cc_bin_env = "CC_BIN_DIR=" + cc_bin_dir + codechecker_env = (codechecker_env + "; " + cc_bin_env) if codechecker_env else cc_bin_env + break + codechecker_files = ctx.actions.declare_directory(ctx.label.name + "/codechecker-files") codechecker_script = ctx.actions.declare_file(ctx.label.name + "/codechecker_script") @@ -128,6 +140,7 @@ def _codechecker_impl(ctx): ), tools = [ info.runfiles, + info.codechecker_files_to_run, ctx.attr._codechecker_script[DefaultInfo].files_to_run, ], outputs = [ diff --git a/src/codechecker_toolchain.bzl b/src/codechecker_toolchain.bzl index f8da745b..70ef3c12 100644 --- a/src/codechecker_toolchain.bzl +++ b/src/codechecker_toolchain.bzl @@ -8,6 +8,9 @@ CodeCheckerInfo = provider( "clang_tidy": "clang-tidy executable", "clangsa": "Clang executable", "codechecker": "CodeChecker executable", + "codechecker_files_to_run": "FilesToRunProvider for the CodeChecker executable. " + + "Pass to `tools` in ctx.actions.run so that Bazel " + + "creates the .runfiles tree in the sandbox.", "runfiles": "Depset of files needed to run the tools: the three executables " + "plus their transitive data_runfiles. Pass to `tools` in " + "ctx.actions.run and include in test runfiles.", @@ -33,6 +36,7 @@ def _codechecker_toolchain_impl(ctx): toolchain_info = platform_common.ToolchainInfo( codecheckerinfo = CodeCheckerInfo( codechecker = ctx.executable.codechecker, + codechecker_files_to_run = ctx.attr.codechecker[DefaultInfo].files_to_run, clang_tidy = ctx.executable.clang_tidy, clangsa = ctx.executable.clangsa, runfiles = runfiles, diff --git a/src/per_file.bzl b/src/per_file.bzl index 57dd2efe..3b03a394 100644 --- a/src/per_file.bzl +++ b/src/per_file.bzl @@ -94,6 +94,14 @@ def _run_code_checker( analyzer_executables = "clangsa:" + info.clangsa.path + \ ";clang-tidy:" + info.clang_tidy.path + # Derive CC_BIN_DIR so pip-installed CodeChecker can find its data dir. + action_env = {} + for f in info.runfiles.to_list(): + idx = f.path.find("data/share/codechecker/") + if idx >= 0: + action_env["CC_BIN_DIR"] = f.path[:idx] + "data/share/codechecker/bin" + break + # Action to run CodeChecker for a file # env_vars are unused for now, since # use_default_shell_env and env are incompatible @@ -104,8 +112,10 @@ def _run_code_checker( executable = per_file_script, tools = [ info.runfiles, + info.codechecker_files_to_run, ctx.attr._per_file_script[DefaultInfo].files_to_run, ], + env = action_env, arguments = [ "--codechecker", info.codechecker.path, @@ -230,17 +240,28 @@ def _per_file_impl(ctx): sources_and_headers, ) all_files += outputs + + # Derive CC_BIN_DIR for the test script so CodeChecker parse can find config + cc_bin_dir_export = "" + for f in info.runfiles.to_list(): + idx = f.short_path.find("data/share/codechecker/") + if idx >= 0: + # In test runfiles, short_path for external repos starts with ../ + cc_bin_dir_export = 'export CC_BIN_DIR="$TEST_SRCDIR/_main/' + f.short_path[:idx] + 'data/share/codechecker/bin"' + break + ctx.actions.write( output = ctx.outputs.test_script, is_executable = True, content = """ + {cc_bin_dir_export} DATA_DIR=$(dirname {dirname}) # ls -la $DATA_DIR/data # find $DATA_DIR/data -name *.plist -exec sed -i -e "s|.*execroot/codechecker_bazel/||g" {{}} \\; # cat $DATA_DIR/data/test-src-lib.cc_clangsa.plist echo "Running: CodeChecker parse $DATA_DIR/data" $(realpath {codechecker}) parse $DATA_DIR/data - """.format(dirname = ctx.outputs.test_script.short_path, codechecker = info.codechecker.short_path), + """.format(dirname = ctx.outputs.test_script.short_path, codechecker = info.codechecker.short_path, cc_bin_dir_export = cc_bin_dir_export), ) files = depset( direct = all_files, diff --git a/test/foss/foss_test_runner.py b/test/foss/foss_test_runner.py index 3dd93c28..350bb881 100644 --- a/test/foss/foss_test_runner.py +++ b/test/foss/foss_test_runner.py @@ -37,6 +37,12 @@ path = "{rules_path}", ) bazel_dep(name = "rules_codechecker") +bazel_dep(name = "rules_python", version = "0.40.0") +python = use_extension("@rules_python//python/extensions:python.bzl", "python") +python.toolchain( + is_default = True, + python_version = "3.12", +) """ BUILD_TEMPLATE = """ diff --git a/test/unit/caching/caching_check.py b/test/unit/caching/caching_check.py index d083d40c..3d094326 100644 --- a/test/unit/caching/caching_check.py +++ b/test/unit/caching/caching_check.py @@ -39,11 +39,18 @@ module(name = "caching_test_workspace") bazel_dep(name = "rules_cc", version = "0.2.3") +bazel_dep(name = "rules_python", version = "0.40.0") bazel_dep(name = "rules_codechecker") local_path_override( module_name = "rules_codechecker", path = "{rules_codechecker_path}", ) + +python = use_extension("@rules_python//python/extensions:python.bzl", "python") +python.toolchain( + is_default = True, + python_version = "3.12", +) """ BUILD_BAZEL = """\ diff --git a/test/unit/external_repository/MODULE.bazel b/test/unit/external_repository/MODULE.bazel index 80b5c822..893c8199 100644 --- a/test/unit/external_repository/MODULE.bazel +++ b/test/unit/external_repository/MODULE.bazel @@ -31,3 +31,10 @@ local_path_override( bazel_dep(name = "external_lib") bazel_dep(name = "rules_cc", version = "0.2.17") +bazel_dep(name = "rules_python", version = "0.40.0") + +python = use_extension("@rules_python//python/extensions:python.bzl", "python") +python.toolchain( + is_default = True, + python_version = "3.12", +)