cross-compilingbazelbazel-python

How do I provide a py_binary's runfiles to a C toolchain in Bazel


I am trying to write a compiler, well, a Python wrapper for Clang that does some platform specific stuff in Bazel. I have created a MRE where I can reproduce my problem here: https://github.com/allsey87/custom_cc_toolchain

The workspace is supposed to be built using the following command:

bazel build --platforms=//:custom_platform //:test

Based on the following output from Bazel, I think my py_binary has been created and is found, however, there is a failing assertion from the Bazel's Python wrapper code as follows:

$ bazel clean --expunge
INFO: Starting clean (this may take a while). Consider using --async if the clean takes more than several minutes.
$ bazel build --verbose_failures --sandbox_debug --platforms=//:custom_platform //:test 
Starting local Bazel server and connecting to it...
WARNING: --enable_bzlmod is set, but no MODULE.bazel file was found at the workspace root. Bazel will create an empty MODULE.bazel file. Please consider migrating your external dependencies from WORKSPACE to MODULE.bazel. For more details, please refer to https://github.com/bazelbuild/bazel/issues/18958.
INFO: Analyzed target //:test (87 packages loaded, 754 targets configured).
ERROR: /workspaces/bazel_test/BUILD:23:10: Compiling empty.c failed: (Exit 1): process-wrapper failed: error executing CppCompile command 
  (cd /home/developer/.cache/bazel/_bazel_developer/e4895a8da4e216f782d166038e1bbca2/sandbox/processwrapper-sandbox/1/execroot/_main && \
  exec env - \
    PATH=/vscode/vscode-server/bin/linux-x64/5437499feb04f7a586f677b155b039bc2b3669eb/bin/remote-cli:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
    PWD=/proc/self/cwd \
    TMPDIR=/tmp \
  /home/developer/.cache/bazel/_bazel_developer/install/937e6e0d806e40997135c14c1d61532a/process-wrapper '--timeout=0' '--kill_delay=15' '--stats=/home/developer/.cache/bazel/_bazel_developer/e4895a8da4e216f782d166038e1bbca2/sandbox/processwrapper-sandbox/1/stats.out' bazel-out/k8-opt-exec-ST-d57f47055a04/bin/clang_wrapper -MD -MF bazel-out/k8-fastbuild/bin/_objs/test/empty.d '-frandom-seed=bazel-out/k8-fastbuild/bin/_objs/test/empty.o' -iquote . -iquote bazel-out/k8-fastbuild/bin -iquote external/bazel_tools -iquote bazel-out/k8-fastbuild/bin/external/bazel_tools -c empty.c -o bazel-out/k8-fastbuild/bin/_objs/test/empty.o)
Traceback (most recent call last):
  File "/home/developer/.cache/bazel/_bazel_developer/e4895a8da4e216f782d166038e1bbca2/sandbox/processwrapper-sandbox/1/execroot/_main/bazel-out/k8-opt-exec-ST-d57f47055a04/bin/clang_wrapper", line 559, in <module>
    Main()
  File "/home/developer/.cache/bazel/_bazel_developer/e4895a8da4e216f782d166038e1bbca2/sandbox/processwrapper-sandbox/1/execroot/_main/bazel-out/k8-opt-exec-ST-d57f47055a04/bin/clang_wrapper", line 457, in Main
    module_space = FindModuleSpace(main_rel_path)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/developer/.cache/bazel/_bazel_developer/e4895a8da4e216f782d166038e1bbca2/sandbox/processwrapper-sandbox/1/execroot/_main/bazel-out/k8-opt-exec-ST-d57f47055a04/bin/clang_wrapper", line 172, in FindModuleSpace
    raise AssertionError('Cannot find .runfiles directory for %s' % sys.argv[0])
AssertionError: Cannot find .runfiles directory for bazel-out/k8-opt-exec-ST-d57f47055a04/bin/clang_wrapper
Target //:test failed to build
INFO: Elapsed time: 5.067s, Critical Path: 0.05s
INFO: 7 processes: 7 internal.
ERROR: Build did NOT complete successfully

How can I provide this .runfiles directory to the toolchain?


Solution

  • Looking at https://github.com/bazelbuild/bazel/blob/674e67bdc3c08b5ac72d5f1ce456bb52766a0f84/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainConfigInfo.java#L56 I'm not sure that the cc toolchain is set up to accept targets that have runfiles. It looks like it expects self contained / pre-compiled binaries, because I don't see anything that tracks runfiles in the ActionConfig or Tool classes.

    So the most expedient thing to do is make your py_binary a self contained executable. There is an output group in py_binary called ptyhon_zip_file that's supposed to make a self-executable python zip file, but it looks like there's an issue where #!/usr/bin/env python3 is not added to the zip file: https://github.com/bazelbuild/bazel/issues/17629 One workaround is to add the hashbang in a genrule:

    py_binary(
      name = "clang_wrapper",
      srcs = ["clang_wrapper.py"],
    )
    filegroup(
      name = "clang_wrapper_zip",
      srcs = [":clang_wrapper"],
      output_group = "python_zip_file",
    )
    genrule(
      name = "clang_wrapper_zip_py_executable",
      srcs = [":clang_wrapper_zip"],
      outs = ["clang_wrapper_zip_py_executable.zip"],
      cmd = "echo '#!/usr/bin/env python3' | cat - $< >$@",
      executable = True,
    )
    
    custom_cc_toolchain_config_rule(
      name = "custom_toolchain_config",
      compiler = ":clang_wrapper_zip_py_executable"
    )
    
    filegroup(name = "empty")
    
    cc_toolchain(
        name = "custom_cc_toolchain",
        all_files = ":empty",
        ar_files = ":empty",
        as_files = ":empty",
        compiler_files = ":clang_wrapper_zip_py_executable",
        dwp_files = ":empty",
        linker_files = ":empty",
        objcopy_files = ":empty",
        strip_files = ":empty",
        toolchain_config = "custom_toolchain_config",
        toolchain_identifier = "custom_toolchain_identifier",
    )
    

    Note that this probably only works on Linux and Mac -- on Windows a different codepath in py_binary is taken to make an exe and the genrule will probably make this all fail on Windows.