diff --git a/MODULE.bazel b/MODULE.bazel index e6b4b5dc..a02540df 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -16,18 +16,16 @@ limitations under the License. module(name = "rules_codechecker") -python_extension = use_extension( - "//src:tools.bzl", - "module_register_default_python_toolchain", -) - +bazel_dep(name = "rules_python", version = "0.32.0") +bazel_dep(name = "rules_cc", version = "0.2.3") + +#python = use_extension("@rules_python//python/extensions:python.bzl", "python") +#python.toolchain( +# python_version = "3.10", +# is_default = True, +#) codechecker_extension = use_extension( "//src:tools.bzl", "module_register_default_codechecker", ) - -use_repo(python_extension, "default_python_tools") - use_repo(codechecker_extension, "default_codechecker_tools") - -register_toolchains("@default_python_tools//:python_toolchain") diff --git a/src/codechecker.bzl b/src/codechecker.bzl index 6030b201..c4d859ea 100644 --- a/src/codechecker.bzl +++ b/src/codechecker.bzl @@ -27,6 +27,7 @@ load( ) load( "common.bzl", + "python_interpreter_tool", "python_path", "python_toolchain_type", "version_specific_attributes", @@ -97,14 +98,17 @@ def _codechecker_impl(ctx): config_file, codechecker_env = get_config_file(ctx) codechecker_files = ctx.actions.declare_directory(ctx.label.name + "/codechecker-files") + # For hermeticism the executable should be a python interpreter + # provided by Bazel. This interpreter cannot always be added to the script + # as a shebang. Because of this this script is explicitly marked as + # non executable. ctx.actions.expand_template( template = ctx.file._codechecker_script_template, output = ctx.outputs.codechecker_script, - is_executable = True, + is_executable = False, substitutions = { "{Mode}": "Run", "{Verbosity}": "DEBUG", - "{PythonPath}": python_path(ctx), # "/usr/bin/env python3", "{codechecker_bin}": CODECHECKER_BIN_PATH, "{compile_commands}": ctx.outputs.codechecker_commands.path, "{codechecker_skipfile}": ctx.outputs.codechecker_skipfile.path, @@ -129,13 +133,12 @@ def _codechecker_impl(ctx): codechecker_files, ctx.outputs.codechecker_log, ], - executable = ctx.outputs.codechecker_script, - arguments = [], - # executable = python_path(ctx), - # arguments = [ctx.outputs.codechecker_script.path], + executable = python_path(ctx), + tools = python_interpreter_tool(ctx), + arguments = [ctx.outputs.codechecker_script.path], mnemonic = "CodeChecker", progress_message = "CodeChecker %s" % str(ctx.label), - # use_default_shell_env = True, + use_default_shell_env = True, ) # List all files required at build and run (test) time @@ -226,27 +229,57 @@ def _codechecker_test_impl(ctx): fail("Execution results required for codechecker test are not available") # Create test script from template + # For hermeticism the executable should be a python interpreter + # provided by Bazel. This interpreter cannot always be added to the script + # as a shebang. Because of this this script is explicitly marked as + # non executable. ctx.actions.expand_template( template = ctx.file._codechecker_script_template, output = ctx.outputs.codechecker_test_script, - is_executable = True, + is_executable = False, substitutions = { "{Mode}": "Test", "{Verbosity}": "INFO", - "{PythonPath}": python_path(ctx), # "/usr/bin/env python3", "{codechecker_bin}": CODECHECKER_BIN_PATH, "{codechecker_files}": codechecker_files.short_path, "{Severities}": " ".join(ctx.attr.severities), }, ) + # If we use our custom toolchain, the path will be an absolute path, + # executable by bazel + python_interpreter_path = python_path(ctx) + + # If we can find an interpreter tool provided by bazel, + # use that as an executable + if python_interpreter_tool(ctx) != []: + python_interpreter_path = python_interpreter_tool(ctx)[0].short_path + + # Since we cannot give parameters to the runfiles + # we wrap the executable (a python binary) in a script. + # In the script we give the CodeChecker script as an argument. + ctx.actions.write( + output = ctx.outputs.test_script_wrapper, + is_executable = True, + content = """ + {python_bin} {test_script} + """.format( + python_bin = python_interpreter_path, + test_script = ctx.outputs.codechecker_test_script.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, + ctx.outputs.test_script_wrapper, + ] + python_interpreter_tool(ctx) + return [ DefaultInfo( files = depset(all_files), runfiles = ctx.runfiles(files = run_files), - executable = ctx.outputs.codechecker_test_script, + executable = ctx.outputs.test_script_wrapper, ), ] @@ -300,6 +333,7 @@ _codechecker_test = rule( "codechecker_script": "%{name}/codechecker_script.py", "codechecker_log": "%{name}/codechecker.log", "codechecker_test_script": "%{name}/codechecker_test_script.py", + "test_script_wrapper": "%{name}/test_script_wrapper.sh", }, toolchains = [python_toolchain_type()], test = True, diff --git a/src/codechecker_script.py b/src/codechecker_script.py index dd87a74a..d7769bea 100644 --- a/src/codechecker_script.py +++ b/src/codechecker_script.py @@ -1,5 +1,3 @@ -#!{PythonPath} - # Copyright 2023 Ericsson AB # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/common.bzl b/src/common.bzl index 198b800e..cbca0d67 100644 --- a/src/common.bzl +++ b/src/common.bzl @@ -53,6 +53,11 @@ def python_toolchain_type(): def python_path(ctx): """ Returns version specific Python path + + Args: + ctx: The context variable. + Returns: + A string containing the path to a python binary """ py_toolchain = ctx.toolchains[python_toolchain_type()] if hasattr(py_toolchain, "py3_runtime_info"): @@ -60,13 +65,36 @@ def python_path(ctx): python_path = py_runtime_info.interpreter elif hasattr(py_toolchain, "py3_runtime"): py_runtime = py_toolchain.py3_runtime - python_path = py_runtime.interpreter_path + if ( + hasattr(py_runtime, "interpreter") and + hasattr(py_runtime.interpreter, "path") + ): + python_path = py_runtime.interpreter.path + elif hasattr(py_runtime, "interpreter_path"): + python_path = py_runtime.interpreter_path + else: + fail("Python interpreter path could not be resolved.") else: fail("The resolved Python toolchain does not provide a Python3 runtime.") if not python_path: fail("The resolved Python toolchain does not provide a Python3 interpreter.") return python_path +def python_interpreter_tool(ctx): + """ + Returns version specific Python interpreter object as only element in a list + that is appropriate for the Bazel version that is running + This list should be added to tools in ctx.action.run using it + """ + py_toolchain = ctx.toolchains[python_toolchain_type()] + python_interpreter = None + if hasattr(py_toolchain, "py3_runtime"): + py_runtime = py_toolchain.py3_runtime + python_interpreter = py_runtime.interpreter + if python_interpreter == None: + return [] + return [python_interpreter] + def warning(ctx, msg): """ Prints message if the debug tag is enabled. diff --git a/src/per_file.bzl b/src/per_file.bzl index 8a9be0fa..6c1fe809 100644 --- a/src/per_file.bzl +++ b/src/per_file.bzl @@ -19,8 +19,14 @@ for each translation unit. load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES") load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain") +load("@default_codechecker_tools//:defs.bzl", "BAZEL_VERSION") load("codechecker_config.bzl", "get_config_file") -load("common.bzl", "SOURCE_ATTR") +load( + "common.bzl", + "python_interpreter_tool", + "python_path", + "python_toolchain_type", +) load( "compile_commands.bzl", "SourceFilesInfo", @@ -54,11 +60,23 @@ def _run_code_checker( codechecker_log = ctx.actions.declare_file(codechecker_log_file_name) if "--ctu" in options: - inputs = [compile_commands_json, config_file] + sources_and_headers + inputs = [ + ctx.outputs.per_file_script, + compile_commands_json, + config_file, + ] + sources_and_headers else: # NOTE: we collect only headers, so CTU may not work! headers = depset(transitive = target[SourceFilesInfo].headers.to_list()) - inputs = depset([compile_commands_json, config_file, src], transitive = [headers]) + inputs = depset( + [ + ctx.outputs.per_file_script, + compile_commands_json, + config_file, + src, + ], + transitive = [headers], + ) outputs = [clang_tidy_plist, clangsa_plist, codechecker_log] @@ -69,8 +87,10 @@ def _run_code_checker( ctx.actions.run( inputs = inputs, outputs = outputs, - executable = ctx.outputs.per_file_script, + executable = python_path(ctx), + tools = python_interpreter_tool(ctx), arguments = [ + ctx.outputs.per_file_script.path, data_dir, src.path, codechecker_log.path, @@ -124,9 +144,8 @@ def _create_wrapper_script(ctx, options, compile_commands_json, config_file): ctx.actions.expand_template( template = ctx.file._per_file_script_template, output = ctx.outputs.per_file_script, - is_executable = True, + is_executable = False, substitutions = { - "{PythonPath}": ctx.attr._python_runtime[PyRuntimeInfo].interpreter_path, "{compile_commands_json}": compile_commands_json.path, "{codechecker_args}": options_str, "{config_file}": config_file.path, @@ -225,14 +244,12 @@ per_file_test = rule( default = ":per_file_script.py", allow_single_file = True, ), - "_python_runtime": attr.label( - default = "@default_python_tools//:py3_runtime", - ), }, outputs = { "compile_commands": "%{name}/compile_commands.json", "test_script": "%{name}/test_script.sh", "per_file_script": "%{name}/per_file_script.py", }, + toolchains = [python_toolchain_type()], test = True, ) diff --git a/src/per_file_script.py b/src/per_file_script.py index 1cfad4b9..378d0925 100644 --- a/src/per_file_script.py +++ b/src/per_file_script.py @@ -1,5 +1,3 @@ -#!{PythonPath} - # Copyright 2023 Ericsson AB # # Licensed under the Apache License, Version 2.0 (the "License");