bazel

Bazel "zip" executable fails to create output


I am following a bazel tutorial that ends up with a "zip" command. When I execute it - I end up with an error.

I am running on a mac laptop. I have tried multiple variations, so I am sharing just the latest one.

Here is the rule definition

def _archive(ctx):
    out_file = ctx.actions.declare_file(ctx.attr.out)

    args = ctx.actions.args()


    args.add(ctx.attr.out)
    args.add_all(ctx.files.files)



    ctx.actions.run(
        executable = "zip",
        arguments=[args],
        inputs=ctx.files.files,
        outputs=[out_file],
        use_default_shell_env = True,
    )
    return [DefaultInfo(files=depset([out_file]))]

archive = rule(
    implementation = _archive,
    attrs = {
        "files": attr.label_list(allow_files=True),
        "out": attr.string(mandatory=True),
    },
)

Here is the usage

load(":archive.bzl", "archive")

filegroup(
    name = "text_files",
    srcs = [
        "file1.txt",
        "file2.txt",
    ],
)


archive(
    name = "archive",
    files = [
        ":text_files",
    ],
    out = "all.zip",
)

Here is the command I am using

bazel build archive

And here is the error I am getting

$  bazel build archive                                                                             
INFO: Analyzed target //:archive (0 packages loaded, 4 targets configured).
INFO: From Action all.zip:
  adding: file1.txt (stored 0%)
  adding: file2.txt (stored 0%)
ERROR: <redacted>/bazel-playground-2/zip_custom_rule/BUILD:12:8: output 'all.zip' was not created
ERROR: <redacted>/bazel-playground-2/zip_custom_rule/BUILD:12:8: Action all.zip failed: not all outputs were created or valid
Target //:archive failed to build
Use --verbose_failures to see the command lines of failed build steps.
INFO: Elapsed time: 0.151s, Critical Path: 0.03s
INFO: 2 processes: 1 internal, 1 darwin-sandbox.
ERROR: Build did NOT complete successfully

Debugging and log prints all show everything is at it should be.

When I implement a macro using genrule it works fine

def archive(name, files, out):
    native.genrule(
        name = name,
        srcs = files,
        outs = [out],
        cmd = "zip -j $@ $(SRCS)",
    )

How can I change my rule implementation to make it work?


Solution

  • The problem is that the out attribute should be an output (file) rather than a string:

    def _archive(ctx):
    
        args = ctx.actions.args()
        args.add(ctx.outputs.out)
        args.add_all(ctx.files.files)
    
        ctx.actions.run(
            executable = "zip",
            arguments = [args],
            inputs = ctx.files.files,
            outputs = [ctx.outputs.out],
            use_default_shell_env = True,
        )
        return [DefaultInfo(files = depset([ctx.outputs.out]))]
    
    archive = rule(
        implementation = _archive,
        attrs = {
            "files": attr.label_list(allow_files=True),
            "out": attr.output(mandatory=True),
        },
    )
    

    You can see the command line that bazel uses with --subcommands:

    out as a string:

    zip all.zip file1.txt file2.txt

    vs

    out as an output (file):

    zip bazel-out/k8-fastbuild/bin/all.zip file1.txt file2.txt

    Also note that zip embeds timestamps in the archive, making the output non-deterministic. Also, relying on the system zip is not hermetic. Consider using rules_pkg at https://github.com/bazelbuild/rules_pkg which has the pkg_zip rule.