Skip to content

Commit 71f5fb0

Browse files
authored
Explicit outputs for wasm_cc_binary (#1047)
* Explicit outputs for wasm_cc_binary * Backwards compatibility * data_runfiles restore * restore test_bazel.sh * Using wrong path on accident * two separate rules for legacy support * Added name attribute to wasm_cc_binary rule
1 parent b81dd81 commit 71f5fb0

File tree

3 files changed

+129
-69
lines changed

3 files changed

+129
-69
lines changed

bazel/emscripten_toolchain/wasm_binary.py

Lines changed: 21 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,9 @@
33
This script will take a tar archive containing the output of the emscripten
44
toolchain. This file contains any output files produced by a wasm_cc_binary or a
55
cc_binary built with --config=wasm. The files are extracted into the given
6-
output path.
6+
output paths.
77
8-
The name of archive is expected to be of the format `foo` or `foo.XXX` and
9-
the contents are expected to be foo.js and foo.wasm.
10-
11-
Several optional files may also be in the archive, including but not limited to
12-
foo.js.mem, pthread-main.js, and foo.wasm.map.
13-
14-
If the file is not a tar archive, the passed file will simply be copied to its
15-
destination.
8+
The contents of the archive are expected to match the given outputs extnames.
169
1710
This script and its accompanying Bazel rule should allow you to extract a
1811
WebAssembly binary into a larger web application.
@@ -29,39 +22,34 @@ def ensure(f):
2922
pass
3023

3124

32-
def check(f):
33-
if not os.path.exists(f):
34-
raise Exception('Expected file in archive: %s' % f)
35-
36-
3725
def main():
3826
parser = argparse.ArgumentParser()
3927
parser.add_argument('--archive', help='The archive to extract from.')
40-
parser.add_argument('--output_path', help='The path to extract into.')
28+
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.')
29+
parser.add_argument('--allow_empty_outputs', help='If an output listed in --outputs does not exist, create it anyways.', action='store_true')
4130
args = parser.parse_args()
4231

4332
args.archive = os.path.normpath(args.archive)
33+
args.outputs = args.outputs.split(",")
4434

45-
basename = os.path.basename(args.archive)
46-
stem = basename.split('.')[0]
47-
48-
# Extract all files from the tarball.
4935
tar = tarfile.open(args.archive)
50-
tar.extractall(args.output_path)
51-
52-
# At least one of these two files should exist at this point.
53-
ensure(os.path.join(args.output_path, stem + '.js'))
54-
ensure(os.path.join(args.output_path, stem + '.wasm'))
5536

56-
# And can optionally contain these extra files.
57-
ensure(os.path.join(args.output_path, stem + '.wasm.map'))
58-
ensure(os.path.join(args.output_path, stem + '.worker.js'))
59-
ensure(os.path.join(args.output_path, stem + '.js.mem'))
60-
ensure(os.path.join(args.output_path, stem + '.data'))
61-
ensure(os.path.join(args.output_path, stem + '.fetch.js'))
62-
ensure(os.path.join(args.output_path, stem + '.js.symbols'))
63-
ensure(os.path.join(args.output_path, stem + '.wasm.debug.wasm'))
64-
ensure(os.path.join(args.output_path, stem + '.html'))
37+
for member in tar.getmembers():
38+
extname = '.' + member.name.split('.', 1)[1]
39+
for idx, output in enumerate(args.outputs):
40+
if output.endswith(extname):
41+
member_file = tar.extractfile(member)
42+
with open(output, "wb") as output_file:
43+
output_file.write(member_file.read())
44+
args.outputs.pop(idx)
45+
break
46+
47+
for output in args.outputs:
48+
extname = '.' + output.split('.', 1)[1]
49+
if args.allow_empty_outputs:
50+
ensure(output)
51+
else:
52+
print("[ERROR] Archive does not contain file with extname: %s" % extname)
6553

6654

6755
if __name__ == '__main__':

bazel/emscripten_toolchain/wasm_cc_binary.bzl

Lines changed: 104 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,81 @@ _wasm_transition = transition(
5454
],
5555
)
5656

57-
def _wasm_binary_impl(ctx):
57+
_ALLOW_OUTPUT_EXTNAMES = [
58+
".js",
59+
".wasm",
60+
".wasm.map",
61+
".worker.js",
62+
".js.mem",
63+
".data",
64+
".fetch.js",
65+
".js.symbols",
66+
".wasm.debug.wasm",
67+
".html",
68+
]
69+
70+
_WASM_BINARY_COMMON_ATTRS = {
71+
"backend": attr.string(
72+
default = "_default",
73+
values = ["_default", "emscripten", "llvm"],
74+
),
75+
"cc_target": attr.label(
76+
cfg = _wasm_transition,
77+
mandatory = True,
78+
),
79+
"exit_runtime": attr.bool(
80+
default = False,
81+
),
82+
"threads": attr.string(
83+
default = "_default",
84+
values = ["_default", "emscripten", "off"],
85+
),
86+
"simd": attr.bool(
87+
default = False,
88+
),
89+
"_allowlist_function_transition": attr.label(
90+
default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
91+
),
92+
"_wasm_binary_extractor": attr.label(
93+
executable = True,
94+
allow_files = True,
95+
cfg = "exec",
96+
default = Label("@emsdk//emscripten_toolchain:wasm_binary"),
97+
),
98+
}
99+
100+
def _wasm_cc_binary_impl(ctx):
58101
args = ctx.actions.args()
59-
args.add("--output_path", ctx.outputs.loader.dirname)
102+
cc_target = ctx.attr.cc_target[0]
103+
104+
for output in ctx.outputs.outputs:
105+
valid_extname = False
106+
for allowed_extname in _ALLOW_OUTPUT_EXTNAMES:
107+
if output.path.endswith(allowed_extname):
108+
valid_extname = True
109+
break
110+
if not valid_extname:
111+
fail("Invalid output '{}'. Allowed extnames: {}".format(output.basename, ", ".join(_ALLOW_OUTPUT_EXTNAMES)))
112+
60113
args.add_all("--archive", ctx.files.cc_target)
114+
args.add_joined("--outputs", ctx.outputs.outputs, join_with = ",")
61115

116+
ctx.actions.run(
117+
inputs = ctx.files.cc_target,
118+
outputs = ctx.outputs.outputs,
119+
arguments = [args],
120+
executable = ctx.executable._wasm_binary_extractor,
121+
)
122+
123+
return DefaultInfo(
124+
files = depset(ctx.outputs.outputs),
125+
# This is needed since rules like web_test usually have a data
126+
# dependency on this target.
127+
data_runfiles = ctx.runfiles(transitive_files = depset(ctx.outputs.outputs)),
128+
)
129+
130+
def _wasm_cc_binary_legacy_impl(ctx):
131+
cc_target = ctx.attr.cc_target[0]
62132
outputs = [
63133
ctx.outputs.loader,
64134
ctx.outputs.wasm,
@@ -72,6 +142,11 @@ def _wasm_binary_impl(ctx):
72142
ctx.outputs.html,
73143
]
74144

145+
args = ctx.actions.args()
146+
args.add("--allow_empty_outputs")
147+
args.add_all("--archive", ctx.files.cc_target)
148+
args.add_joined("--outputs", outputs, join_with = ",")
149+
75150
ctx.actions.run(
76151
inputs = ctx.files.cc_target,
77152
outputs = outputs,
@@ -87,7 +162,19 @@ def _wasm_binary_impl(ctx):
87162
data_runfiles = ctx.runfiles(transitive_files = depset(outputs)),
88163
)
89164

90-
def _wasm_binary_outputs(name, cc_target):
165+
_wasm_cc_binary = rule(
166+
name = "wasm_cc_binary",
167+
implementation = _wasm_cc_binary_impl,
168+
attrs = dict(
169+
_WASM_BINARY_COMMON_ATTRS,
170+
outputs = attr.output_list(
171+
allow_empty = False,
172+
mandatory = True,
173+
),
174+
),
175+
)
176+
177+
def _wasm_binary_legacy_outputs(name, cc_target):
91178
basename = cc_target.name
92179
basename = basename.split(".")[0]
93180
outputs = {
@@ -105,6 +192,13 @@ def _wasm_binary_outputs(name, cc_target):
105192

106193
return outputs
107194

195+
_wasm_cc_binary_legacy = rule(
196+
name = "wasm_cc_binary",
197+
implementation = _wasm_cc_binary_legacy_impl,
198+
attrs = _WASM_BINARY_COMMON_ATTRS,
199+
outputs = _wasm_binary_legacy_outputs,
200+
)
201+
108202
# Wraps a C++ Blaze target, extracting the appropriate files.
109203
#
110204
# This rule will transition to the emscripten toolchain in order
@@ -113,36 +207,10 @@ def _wasm_binary_outputs(name, cc_target):
113207
# Args:
114208
# name: The name of the rule.
115209
# cc_target: The cc_binary or cc_library to extract files from.
116-
wasm_cc_binary = rule(
117-
implementation = _wasm_binary_impl,
118-
attrs = {
119-
"backend": attr.string(
120-
default = "_default",
121-
values = ["_default", "emscripten", "llvm"],
122-
),
123-
"cc_target": attr.label(
124-
cfg = _wasm_transition,
125-
mandatory = True,
126-
),
127-
"exit_runtime": attr.bool(
128-
default = False,
129-
),
130-
"threads": attr.string(
131-
default = "_default",
132-
values = ["_default", "emscripten", "off"],
133-
),
134-
"simd": attr.bool(
135-
default = False,
136-
),
137-
"_allowlist_function_transition": attr.label(
138-
default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
139-
),
140-
"_wasm_binary_extractor": attr.label(
141-
executable = True,
142-
allow_files = True,
143-
cfg = "exec",
144-
default = Label("@emsdk//emscripten_toolchain:wasm_binary"),
145-
),
146-
},
147-
outputs = _wasm_binary_outputs,
148-
)
210+
def wasm_cc_binary(outputs = None, **kwargs):
211+
# for backwards compatibility if no outputs are set the deprecated
212+
# implementation is used.
213+
if not outputs:
214+
_wasm_cc_binary_legacy(**kwargs)
215+
else:
216+
_wasm_cc_binary(outputs = outputs, **kwargs)

bazel/test_external/BUILD

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ cc_binary(
88
wasm_cc_binary(
99
name = "hello-world-wasm",
1010
cc_target = ":hello-world",
11+
outputs = [
12+
"hello-world.js",
13+
"hello-world.wasm",
14+
],
1115
)
1216

1317
BASE_LINKOPTS = [

0 commit comments

Comments
 (0)