diff --git a/bazel/emscripten_toolchain/wasm_binary.py b/bazel/emscripten_toolchain/wasm_binary.py index 8c67c7c..d7d6142 100644 --- a/bazel/emscripten_toolchain/wasm_binary.py +++ b/bazel/emscripten_toolchain/wasm_binary.py @@ -3,16 +3,9 @@ This script will take a tar archive containing the output of the emscripten toolchain. This file contains any output files produced by a wasm_cc_binary or a cc_binary built with --config=wasm. The files are extracted into the given -output path. +output paths. -The name of archive is expected to be of the format `foo` or `foo.XXX` and -the contents are expected to be foo.js and foo.wasm. - -Several optional files may also be in the archive, including but not limited to -foo.js.mem, pthread-main.js, and foo.wasm.map. - -If the file is not a tar archive, the passed file will simply be copied to its -destination. +The contents of the archive are expected to match the given outputs extnames. This script and its accompanying Bazel rule should allow you to extract a WebAssembly binary into a larger web application. @@ -29,39 +22,34 @@ def ensure(f): pass -def check(f): - if not os.path.exists(f): - raise Exception('Expected file in archive: %s' % f) - - def main(): parser = argparse.ArgumentParser() parser.add_argument('--archive', help='The archive to extract from.') - parser.add_argument('--output_path', help='The path to extract into.') + parser.add_argument('--outputs', help='Comma separated list of files that should be extracted from the archive. Only the extname has to match a file in the archive.') + parser.add_argument('--allow_empty_outputs', help='If an output listed in --outputs does not exist, create it anyways.', action='store_true') args = parser.parse_args() args.archive = os.path.normpath(args.archive) + args.outputs = args.outputs.split(",") - basename = os.path.basename(args.archive) - stem = basename.split('.')[0] - - # Extract all files from the tarball. tar = tarfile.open(args.archive) - tar.extractall(args.output_path) - # At least one of these two files should exist at this point. - ensure(os.path.join(args.output_path, stem + '.js')) - ensure(os.path.join(args.output_path, stem + '.wasm')) + for member in tar.getmembers(): + extname = '.' + member.name.split('.', 1)[1] + for idx, output in enumerate(args.outputs): + if output.endswith(extname): + member_file = tar.extractfile(member) + with open(output, "wb") as output_file: + output_file.write(member_file.read()) + args.outputs.pop(idx) + break - # And can optionally contain these extra files. - ensure(os.path.join(args.output_path, stem + '.wasm.map')) - ensure(os.path.join(args.output_path, stem + '.worker.js')) - ensure(os.path.join(args.output_path, stem + '.js.mem')) - ensure(os.path.join(args.output_path, stem + '.data')) - ensure(os.path.join(args.output_path, stem + '.fetch.js')) - ensure(os.path.join(args.output_path, stem + '.js.symbols')) - ensure(os.path.join(args.output_path, stem + '.wasm.debug.wasm')) - ensure(os.path.join(args.output_path, stem + '.html')) + for output in args.outputs: + extname = '.' + output.split('.', 1)[1] + if args.allow_empty_outputs: + ensure(output) + else: + print("[ERROR] Archive does not contain file with extname: %s" % extname) if __name__ == '__main__': diff --git a/bazel/emscripten_toolchain/wasm_cc_binary.bzl b/bazel/emscripten_toolchain/wasm_cc_binary.bzl index dbdb238..9cf6328 100644 --- a/bazel/emscripten_toolchain/wasm_cc_binary.bzl +++ b/bazel/emscripten_toolchain/wasm_cc_binary.bzl @@ -54,11 +54,81 @@ _wasm_transition = transition( ], ) -def _wasm_binary_impl(ctx): - args = ctx.actions.args() - args.add("--output_path", ctx.outputs.loader.dirname) - args.add_all("--archive", ctx.files.cc_target) +_ALLOW_OUTPUT_EXTNAMES = [ + ".js", + ".wasm", + ".wasm.map", + ".worker.js", + ".js.mem", + ".data", + ".fetch.js", + ".js.symbols", + ".wasm.debug.wasm", + ".html", +] +_WASM_BINARY_COMMON_ATTRS = { + "backend": attr.string( + default = "_default", + values = ["_default", "emscripten", "llvm"], + ), + "cc_target": attr.label( + cfg = _wasm_transition, + mandatory = True, + ), + "exit_runtime": attr.bool( + default = False, + ), + "threads": attr.string( + default = "_default", + values = ["_default", "emscripten", "off"], + ), + "simd": attr.bool( + default = False, + ), + "_allowlist_function_transition": attr.label( + default = "@bazel_tools//tools/allowlists/function_transition_allowlist", + ), + "_wasm_binary_extractor": attr.label( + executable = True, + allow_files = True, + cfg = "exec", + default = Label("@emsdk//emscripten_toolchain:wasm_binary"), + ), +} + +def _wasm_cc_binary_impl(ctx): + args = ctx.actions.args() + cc_target = ctx.attr.cc_target[0] + + for output in ctx.outputs.outputs: + valid_extname = False + for allowed_extname in _ALLOW_OUTPUT_EXTNAMES: + if output.path.endswith(allowed_extname): + valid_extname = True + break + if not valid_extname: + fail("Invalid output '{}'. Allowed extnames: {}".format(output.basename, ", ".join(_ALLOW_OUTPUT_EXTNAMES))) + + args.add_all("--archive", ctx.files.cc_target) + args.add_joined("--outputs", ctx.outputs.outputs, join_with = ",") + + ctx.actions.run( + inputs = ctx.files.cc_target, + outputs = ctx.outputs.outputs, + arguments = [args], + executable = ctx.executable._wasm_binary_extractor, + ) + + return DefaultInfo( + files = depset(ctx.outputs.outputs), + # This is needed since rules like web_test usually have a data + # dependency on this target. + data_runfiles = ctx.runfiles(transitive_files = depset(ctx.outputs.outputs)), + ) + +def _wasm_cc_binary_legacy_impl(ctx): + cc_target = ctx.attr.cc_target[0] outputs = [ ctx.outputs.loader, ctx.outputs.wasm, @@ -72,6 +142,11 @@ def _wasm_binary_impl(ctx): ctx.outputs.html, ] + args = ctx.actions.args() + args.add("--allow_empty_outputs") + args.add_all("--archive", ctx.files.cc_target) + args.add_joined("--outputs", outputs, join_with = ",") + ctx.actions.run( inputs = ctx.files.cc_target, outputs = outputs, @@ -87,7 +162,19 @@ def _wasm_binary_impl(ctx): data_runfiles = ctx.runfiles(transitive_files = depset(outputs)), ) -def _wasm_binary_outputs(name, cc_target): +_wasm_cc_binary = rule( + name = "wasm_cc_binary", + implementation = _wasm_cc_binary_impl, + attrs = dict( + _WASM_BINARY_COMMON_ATTRS, + outputs = attr.output_list( + allow_empty = False, + mandatory = True, + ), + ), +) + +def _wasm_binary_legacy_outputs(name, cc_target): basename = cc_target.name basename = basename.split(".")[0] outputs = { @@ -105,6 +192,13 @@ def _wasm_binary_outputs(name, cc_target): return outputs +_wasm_cc_binary_legacy = rule( + name = "wasm_cc_binary", + implementation = _wasm_cc_binary_legacy_impl, + attrs = _WASM_BINARY_COMMON_ATTRS, + outputs = _wasm_binary_legacy_outputs, +) + # Wraps a C++ Blaze target, extracting the appropriate files. # # This rule will transition to the emscripten toolchain in order @@ -113,36 +207,10 @@ def _wasm_binary_outputs(name, cc_target): # Args: # name: The name of the rule. # cc_target: The cc_binary or cc_library to extract files from. -wasm_cc_binary = rule( - implementation = _wasm_binary_impl, - attrs = { - "backend": attr.string( - default = "_default", - values = ["_default", "emscripten", "llvm"], - ), - "cc_target": attr.label( - cfg = _wasm_transition, - mandatory = True, - ), - "exit_runtime": attr.bool( - default = False, - ), - "threads": attr.string( - default = "_default", - values = ["_default", "emscripten", "off"], - ), - "simd": attr.bool( - default = False, - ), - "_allowlist_function_transition": attr.label( - default = "@bazel_tools//tools/allowlists/function_transition_allowlist", - ), - "_wasm_binary_extractor": attr.label( - executable = True, - allow_files = True, - cfg = "exec", - default = Label("@emsdk//emscripten_toolchain:wasm_binary"), - ), - }, - outputs = _wasm_binary_outputs, -) +def wasm_cc_binary(outputs = None, **kwargs): + # for backwards compatibility if no outputs are set the deprecated + # implementation is used. + if not outputs: + _wasm_cc_binary_legacy(**kwargs) + else: + _wasm_cc_binary(outputs = outputs, **kwargs) diff --git a/bazel/test_external/BUILD b/bazel/test_external/BUILD index 4e69238..e3a8e9e 100644 --- a/bazel/test_external/BUILD +++ b/bazel/test_external/BUILD @@ -8,6 +8,10 @@ cc_binary( wasm_cc_binary( name = "hello-world-wasm", cc_target = ":hello-world", + outputs = [ + "hello-world.js", + "hello-world.wasm", + ], ) BASE_LINKOPTS = [