Search code examples
buildbazel

Bazel : handle intermediate files in ctx.action


I am trying to implement custom c_library() rule in Bazel which takes a bunch of .c files as input and produces .a static library. In my implementation, .c files generate .o objects and then archives into .a library. But it seems that .o files are not getting generated. Here is the custom c_library() rule (rules.bzl file):

def _change_ext_to_x( file_list, x = 'o' ):
    # inputs a file list (i.e. string list)
    # and changes their extensions to '.{x}'
    return [ "".join( onefile.split('.')[:-1] ) + '.' + x \
                    for onefile in file_list ]

def _str_to_File( ctx, file_list ):
    # A constructor of File() object
    # pass the context
    return [ ctx.new_file( onefile ) for onefile in file_list ]

def _c_library_impl( ctx ):
    # implementation of 'c_library' rule

    # the source files list ( strings )
    in_file_list = []
    for onefile in ctx.files.srcs:
        in_file_list.append( onefile.basename )

    out_file_list = _str_to_File( ctx, # the current context
                           _change_ext_to_x(in_file_list, x = 'o') )

    ctx.action(
        inputs = ctx.files.srcs,
        outputs = out_file_list,
        progress_message = "COMPILING FROM <.c> TO <.o>",
        use_default_shell_env = True,
        command = "gcc -c -O3 %s" % " ".join( in_file_list )
    )

    ctx.action(
        inputs = out_file_list,
        outputs = [ ctx.outputs._libfile ],
        progress_message = "ARCHIVING <.o>s to <.a>",
        use_default_shell_env = True,
        arguments = [ctx.outputs._libfile.basename],
        command = "ar cr $1 %s" % " ".join( [onefile.basename 
                                        for onefile in out_file_list] )
    )

    pass

c_library = rule(
    # define rule for 'c' library
    implementation = _c_library_impl,
    attrs = {
        "srcs" : attr.label_list( allow_files = True,
                                  mandatory = True,
                                  allow_empty = False ),
        "out" : attr.output( mandatory = False )
    },
    outputs = {
        "_libfile" : "lib%{name}.a"
    }
)

And here is my BUILD file:

load("//:rules.bzl", "c_library")

c_library(
    name = "foo",
    srcs = glob(include = ["*.c"], exclude = ["main.c"])
    # there are two files 'cmd.c' and 'utils.c' in the package
)

When I did bazel build //:foo, I got the following error:

INFO: Found 1 target...
ERROR: /home/spyder/Desktop/project/BUILD:3:1: output 'cmd.o' was not created.
ERROR: /home/spyder/Desktop/project/BUILD:3:1: output 'utils.o' was not created.
ERROR: /home/spyder/Desktop/project/BUILD:3:1: not all outputs were created or valid.
Target //:foo failed to build
Use --verbose_failures to see the command lines of failed build steps.
INFO: Elapsed time: 2.249s, Critical Path: 1.94s

How exactly to persist the intermediate files between two successive ctx.actions ?


Solution

  • You should create one action per .c file instead of one action for all of them, that will also increase parallelism, and then specified the output:

    def _c_library_impl( ctx ):
        # implementation of 'c_library' rule
        out_file_list = []
        for f in ctx.files.srcs:
          o = ctx.new_file(f.basename + ".o")
          out_file_list.append(o)
          ctx.action(
            inputs = [f],
            outputs = [o],
            progress_message = "COMPILING FROM <.c> TO <.o>",
            use_default_shell_env = True,
            command = "gcc -c -O3 %s %s" % (f.path, o.path)
          )
    
        ctx.action(
            inputs = out_file_list,
            outputs = [ ctx.outputs._libfile ],
            progress_message = "ARCHIVING <.o>s to <.a>",
            use_default_shell_env = True,
            command = "ar cr %s %s" % (ctx.outputs._libfile.path, 
                                       "\n".join([onefile.basename 
                                            for onefile in out_file_list] )
        )