bazelbazel-rulesbazel-cppispc

How to convert a macro that invokes native.genrule and native.cc_library to a rule?


I have a macro that looks like this:

def ispc_cc_library(name, out, ispc_main_source_file, srcs, defines = [], **kwargs):
    generated_header_filename = out

    ispc_defines_list = ""
    if "defines" in kwargs:
        ispc_defines_list = "-D" + " -D".join(kwargs["defines"])
    elif len(defines) > 0:
        ispc_defines_list = "-D" + " -D".join(defines)

    native.genrule(
        name = "%s_ispc_gen" % name,
        srcs = srcs,
        outs = [name + ".o", generated_header_filename],
        cmd = select({
            "@platforms//os:linux": "$(location @ispc_linux_x86_64//:ispc) %s --target=avx2 --target-os=linux --arch=x86-64 --addressing=64 --pic $(locations %s) --header-outfile=$(location %s) -o $(location %s.o)" % (ispc_defines_list, ispc_main_source_file, generated_header_filename, name),
            "@rules_ispc//:osx_arm64": "$(location @ispc_osx_arm64//:ispc) %s --target=neon --target-os=macos --arch=aarch64 --addressing=64 --pic $(locations %s) --header-outfile=$(location %s) -o $(location %s.o)" % (ispc_defines_list, ispc_main_source_file, generated_header_filename, name),
            "@rules_ispc//:osx_x86_64": "$(location @ispc_osx_x86_64//:ispc) %s --target=sse2 --target-os=macos --arch=x86-64 --addressing=64 --pic $(locations %s) --header-outfile=$(location %s) -o $(location %s.o)" % (ispc_defines_list, ispc_main_source_file, generated_header_filename, name),
            "@platforms//os:windows": "$(location @ispc_windows_x86_64//:ispc) %s --target=avx2 --target-os=windows --arch=x86-64 --addressing=64 $(locations %s) --header-outfile=$(location %s) -o $(location %s.o)" % (ispc_defines_list, ispc_main_source_file, generated_header_filename, name),
        }),
        tools = select({
            "@platforms//os:linux": ["@ispc_linux_x86_64//:ispc"],
            "@rules_ispc//:osx_arm64": ["@ispc_osx_arm64//:ispc"],
            "@rules_ispc//:osx_x86_64": ["@ispc_osx_x86_64//:ispc"],
            "@platforms//os:windows": ["@ispc_windows_x86_64//:ispc"],
        }),
    )
    native.cc_library(
        name = name,
        srcs = [name + ".o"],
        hdrs = [name + ".h"],
        defines = defines,
        **kwargs
    )

I want to convert it to a rule. My attempt so far:

def _ispc_cc_library_impl(ctx):
    info = ctx.toolchains["@rules_ispc//tools:toolchain_type"].ispc_info
    default_target_os = info.default_target_os

    generated_header_filename = ctx.attr.generated_header_filename

    ispc_defines_list = ""
    if len(ctx.attr.defines) > 0:
        ispc_defines_list = "-D" + " -D".join(ctx.attr.defines)

    srcs = ctx.files.srcs
    inputs = depset(srcs)  # see https://bazel.build/extending/rules

    output_file1 = ctx.actions.declare_file(generated_header_filename)
    output_file2 = ctx.actions.declare_file(ctx.attr.name + ".o")

    args = ctx.actions.args()
    args.add("-o", generated_header_filename)
    args.add(ispc_defines_list)
    args.add("--target=neon")
    args.add("--target-os=%s" % default_target_os)
    args.add("--arch=aarch64")
    args.add("--addressing=64")
    args.add("--pic")
    args.add("square.ispc")#ctx.attr.ispc_main_source_file)
    args.add("--header-outfile=%s" % generated_header_filename)
    args.add("-o")
    args.add(ctx.attr.name + ".o")

    ctx.actions.run(
        inputs = inputs,
        outputs = [output_file1, output_file2],
        arguments = [args],
        executable = info.ispc_path,
    )

    return [
        DefaultInfo(files = depset([output_file1, output_file2])),
    ]

ispc_library2 = rule(
    implementation = _ispc_cc_library_impl,
    doc = """Compiles a ISPC program and makes it available as a C++ library

This rule uses a precompiled version of ISPC v1.19.0 for compilation.""",
    attrs = {
        "generated_header_filename": attr.string(
            doc = """
            Name of the generated header file.
            """,
        ),
        "ispc_main_source_file": attr.label(
            allow_single_file = [".ispc"],
            doc = """
            File to compile.
            """,
        ),
        "srcs": attr.label_list(
            allow_files = [".ispc", ".isph"],
            doc = """
            The list of ISPC source files that are compiled to create the library.
            Only `.ispc` and `.isph` files are permitted.
            """,
        ),
        "defines": attr.string_list(
            doc = """
            List of defines handed over to the ISPC compiler.
            """,
        ),
        #"out": attr.output_list(),
    },
    #outputs = {
    #    "out": "{generated_header_filename}.o"
    #},
    toolchains = ["@rules_ispc//tools:toolchain_type"],
)

def ispc_cc_library2(name, generated_header_filename, ispc_main_source_file, srcs, defines = [], **kwargs):
    ispc_library2(
        name = "%s_ispc_gen" % name,
        generated_header_filename = generated_header_filename,
        ispc_main_source_file = ispc_main_source_file,
        srcs = srcs,
        defines = defines,
        **kwargs
    )
    native.cc_library(
        name = name,
        srcs = [name + ".o"],
        hdrs = [name + ".h"],
        defines = defines,
        #deps = ["%s_ispc_gen" % name],
        **kwargs
    )

The repository with the source code is here (see ispc.bzl). When I try to replace in my tests ispc_cc_library with ispc_cc_library2 I get some errors:

ERROR: /Users/vertexwahn/rules_ispc/tests/defines/BUILD.bazel:12:17: Linking defines/libsquare.a failed: missing input file '//defines:square.o'
ERROR: /Users/vertexwahn/rules_ispc/tests/defines/BUILD.bazel:12:17: Middleman _middlemen/_S_Sdefines_Csquare-cc_library-compile failed: missing input file '//defines:square.h'
ERROR: /Users/vertexwahn/rules_ispc/tests/defines/BUILD.bazel:12:17: Linking defines/libsquare.a failed: 1 input file(s) do not exist
ERROR: /Users/vertexwahn/rules_ispc/tests/defines/BUILD.bazel:12:17: Middleman _middlemen/_S_Sdefines_Csquare-cc_library-compile failed: 1 input file(s) do not exist
ERROR: /Users/vertexwahn/rules_ispc/tests/defines/BUILD.bazel:12:17: //defines:square: missing input file '//defines:square.o'
ERROR: /Users/vertexwahn/rules_ispc/tests/defines/BUILD.bazel:12:17 Linking defines/libsquare.a failed: 1 input file(s) do not exist

The idea of ispc_library2 is to generate a header file and object file. Those two files should be added to a cc_library. I still don't understand how I can handover those artefacts - any advices welcome!

Steps to reproduce:

git clone https://github.com/Vertexwahn/rules_ispc.git
cd rules_ispc/tests/defines
bazel build //defines:main # should fail

Solution

  • Properly declare the object and header output of ispc_library2, so that the cc_library in the macro can see and depend on them:

    diff --git a/ispc.bzl b/ispc.bzl
    index e548836..499c091 100644
    --- a/ispc.bzl
    +++ b/ispc.bzl
    @@ -14,11 +14,10 @@ def _ispc_cc_library_impl(ctx):
         srcs = ctx.files.srcs
         inputs = depset(srcs)  # see https://bazel.build/extending/rules
     
    -    output_file1 = ctx.actions.declare_file(generated_header_filename)
    -    output_file2 = ctx.actions.declare_file(ctx.attr.name + ".o")
    +    object = ctx.actions.declare_file(ctx.attr.name + ".o")
     
         args = ctx.actions.args()
    -    args.add("-o", generated_header_filename)
    +    args.add("-o", object)
         args.add(ispc_defines_list)
         args.add("--target=neon")
         args.add("--target-os=%s" % default_target_os)
    @@ -26,21 +25,17 @@ def _ispc_cc_library_impl(ctx):
         args.add("--addressing=64")
         args.add("--pic")
         args.add("square.ispc")#ctx.attr.ispc_main_source_file)
    -    args.add("--header-outfile=%s" % generated_header_filename)
    -    args.add("-o")
    -    args.add(ctx.attr.name + ".o")
    +    args.add("--header-outfile", ctx.outputs.generated_header_filename)
     
         ctx.actions.run(
             inputs = inputs,
    -        outputs = [output_file1, output_file2],
    +        outputs = [object, ctx.outputs.generated_header_filename],
             arguments = [args],
             executable = info.ispc_path,
         )
     
         return [
    -        #DefaultInfo()
    -        #DefaultInfo(files = depset([output_file1, output_file2])),
    -        OutputGroupInfo(out = depset([output_file1, output_file2]))
    +        DefaultInfo(files = depset(direct=[object])),
         ]
     
     ispc_library2 = rule(
    @@ -49,7 +44,7 @@ ispc_library2 = rule(
     
     This rule uses a precompiled version of ISPC v1.19.0 for compilation.""",
         attrs = {
    -        "generated_header_filename": attr.string(
    +        "generated_header_filename": attr.output(
                 doc = """
                 Name of the generated header file.
                 """,
    @@ -72,11 +67,7 @@ This rule uses a precompiled version of ISPC v1.19.0 for compilation.""",
                 List of defines handed over to the ISPC compiler.
                 """,
             ),
    -        #"out": attr.output_list(),
         },
    -    #outputs = {
    -    #    "out": "{generated_header_filename}.o"
    -    #},
         toolchains = ["@rules_ispc//tools:toolchain_type"],
     )
     
    @@ -91,10 +82,9 @@ def ispc_cc_library2(name, generated_header_filename, ispc_main_source_file, src
         )
         native.cc_library(
             name = name,
    -        srcs = [name + ".o"],
    +        srcs = [":%s_ispc_gen" % name],
             hdrs = [name + ".h"],
             defines = defines,
    -        #deps = ["%s_ispc_gen" % name],
             **kwargs
         )