Skip to content
File

Blob: build/wd_js_bundle.bzl

11.0 KB
1load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
2load("@bazel_skylib//rules:copy_file.bzl", "copy_file")
3load("@capnp-cpp//src/capnp:cc_capnp_library.bzl", "cc_capnp_library")
4 
5CAPNP_TEMPLATE = """@{schema_id};
6 
7# generated by @workerd//build/wd_js_bundle.bzl
8 
9using Modules = import "/workerd/jsg/modules.capnp";
10 
11const {const_name} :Modules.Bundle = (
12 modules = [
13{modules}
14]);
15"""
16 
17def _to_name(file_name):
18 return file_name.removesuffix(".js")
19 
20def _to_d_ts(file_name):
21 return file_name.removesuffix(".js") + ".d.ts"
22 
23def _relative_path(file_path, dir_path):
24 if not file_path.startswith(dir_path):
25 fail("file_path need to start with dir_path: " + file_path + " vs " + dir_path)
26 return file_path.removeprefix(dir_path)
27 
28def _gen_compile_cache_impl(ctx):
29 file_list = ctx.actions.declare_file("in")
30 
31 srcs = []
32 for src in ctx.attr.srcs:
33 srcs.extend(src.files.to_list())
34 
35 outs = [ctx.actions.declare_file(src.basename + "_cache") for src in srcs]
36 
37 content = []
38 for i in range(0, len(srcs)):
39 content.append("{} {}".format(srcs[i].path, outs[i].path))
40 
41 ctx.actions.write(
42 output = file_list,
43 content = "\n".join(content) + "\n",
44 )
45 
46 args = ctx.actions.args()
47 args.add(file_list)
48 
49 run_under = ctx.attr._run_under[BuildSettingInfo].value
50 
51 # use run_shell together with cfg = target instead of ctx.actions.run
52 # to prevent double-compilation of v8.
53 ctx.actions.run_shell(
54 outputs = outs,
55 inputs = [file_list] + srcs,
56 command = run_under + " " + ctx.executable._tool.path + " $@",
57 arguments = [args],
58 use_default_shell_env = True,
59 tools = [ctx.executable._tool],
60 )
61 
62 return [
63 DefaultInfo(files = depset(direct = outs)),
64 ]
65 
66_gen_compile_cache = rule(
67 implementation = _gen_compile_cache_impl,
68 attrs = {
69 "srcs": attr.label_list(mandatory = True, allow_files = True),
70 "_tool": attr.label(
71 executable = True,
72 allow_single_file = True,
73 cfg = "target",
74 default = "//src/rust/gen-compile-cache",
75 ),
76 "_run_under": attr.label(default = "//build/config:target_run_under"),
77 },
78)
79 
80def _get_compile_cache(compile_cache, m):
81 if not compile_cache:
82 return None
83 files = m.files.to_list()
84 
85 if len(files) != 1:
86 fail("only single file expected")
87 
88 return compile_cache.get(files[0].path)
89 
90MODULE_TEMPLATE = """ (name = "{name}", {src_type} = embed "{path}", type = {type}, {extras})"""
91 
92def _gen_api_bundle_capnpn_impl(ctx):
93 output_dir = ctx.outputs.out.dirname + "/"
94 
95 def _render_module(name, label, src_type, type, cache = None):
96 ts_declaration_extra = (
97 "tsDeclaration = embed \"" + _relative_path(
98 ctx.expand_location("$(location {})".format(ctx.attr.declarations[name]), ctx.attr.data),
99 output_dir,
100 ) + "\", "
101 ) if name in ctx.attr.declarations else ""
102 cache_extra = (
103 "compileCache = embed \"{}\", ".format(_relative_path(cache, output_dir))
104 ) if cache else ""
105 extras = ts_declaration_extra + cache_extra
106 
107 return MODULE_TEMPLATE.format(
108 name = name,
109 # capnp doesn't allow ".." dir escape, make paths relative.
110 # this won't work for embedding paths outside of rule directory subtree.
111 src_type = src_type,
112 path = _relative_path(
113 ctx.expand_location("$(location {})".format(label), ctx.attr.data),
114 output_dir,
115 ),
116 type = type,
117 extras = extras,
118 )
119 
120 compile_cache = {}
121 if ctx.attr.compile_cache:
122 locations = ctx.expand_location("$(locations {})".format(ctx.attr.compile_cache.label)).split(" ")
123 for loc in locations:
124 compile_cache[loc.removesuffix("_cache")] = loc
125 
126 modules = [
127 _render_module(ctx.attr.builtin_modules[m], m.label, "src", "builtin", _get_compile_cache(compile_cache, m))
128 for m in ctx.attr.builtin_modules
129 ]
130 modules += [
131 _render_module(ctx.attr.internal_modules[m], m.label, "src", "internal", _get_compile_cache(compile_cache, m))
132 for m in ctx.attr.internal_modules
133 ]
134 modules += [
135 _render_module(ctx.attr.internal_wasm_modules[m], m.label, "wasm", "internal")
136 for m in ctx.attr.internal_wasm_modules
137 ]
138 modules += [
139 _render_module(ctx.attr.internal_data_modules[m], m.label, "data", "internal")
140 for m in ctx.attr.internal_data_modules
141 ]
142 modules += [
143 _render_module(ctx.attr.internal_json_modules[m], m.label, "json", "internal")
144 for m in ctx.attr.internal_json_modules
145 ]
146 
147 content = CAPNP_TEMPLATE.format(
148 schema_id = ctx.attr.schema_id,
149 modules = ",\n".join(modules),
150 const_name = ctx.attr.const_name,
151 )
152 ctx.actions.write(ctx.outputs.out, content)
153 
154gen_api_bundle_capnpn = rule(
155 implementation = _gen_api_bundle_capnpn_impl,
156 attrs = {
157 "schema_id": attr.string(mandatory = True),
158 "out": attr.output(mandatory = True),
159 "builtin_modules": attr.label_keyed_string_dict(allow_files = True),
160 "internal_modules": attr.label_keyed_string_dict(allow_files = True),
161 "internal_wasm_modules": attr.label_keyed_string_dict(allow_files = True),
162 "internal_data_modules": attr.label_keyed_string_dict(allow_files = True),
163 "internal_json_modules": attr.label_keyed_string_dict(allow_files = True),
164 "declarations": attr.string_dict(),
165 "data": attr.label_list(allow_files = True),
166 "const_name": attr.string(mandatory = True),
167 "deps": attr.label_list(),
168 "compile_cache": attr.label(),
169 },
170)
171 
172def _copy_modules(modules, declarations, out_dir = ""):
173 """Copy files from the modules map to the current package.
174 
175 Returns new module map using file copies.
176 This is necessary since capnp compiler doesn't allow embeds outside of current subidrectory.
177 """
178 result = dict()
179 declarations_result = dict()
180 for m in modules:
181 new_filename = out_dir + modules[m].replace(":", "_").replace("/", "_")
182 copy_file(name = new_filename + "@copy", src = m, out = new_filename)
183 
184 m_d_ts = _to_d_ts(m)
185 if m_d_ts in declarations:
186 new_d_ts_filename = new_filename + ".d.ts"
187 copy_file(name = new_d_ts_filename + "@copy", src = m_d_ts, out = new_d_ts_filename)
188 declarations_result[modules[m]] = str(native.package_relative_label(new_d_ts_filename))
189 
190 result[new_filename] = modules[m]
191 return result, declarations_result
192 
193def wd_js_bundle(
194 name,
195 import_name,
196 schema_id,
197 builtin_modules,
198 internal_modules = [],
199 internal_wasm_modules = [],
200 internal_data_modules = [],
201 internal_json_modules = [],
202 declarations = [],
203 deps = [],
204 gen_compile_cache = False,
205 out_dir = ""):
206 """Generate cc capnp library with js api bundle.
207 
208 NOTE: Due to capnpc embed limitation all modules must be in the same or sub directory of the
209 actual rule usage.
210 
211 Args:
212 name: cc_capnp_library rule name
213 import_name: The js import specifier. builtin modules are accessible from
214 user code under `<import_name>:<module_name>`, internal modules
215 are accessible from builtin modules under
216 `<import_name>-internal:<module_name>`
217 The capnproto bundle object generated will be called
218 `import_name` + "Bundle"
219 schema_id: capnpn schema id
220 builtin_modules: list of js source files for builtin modules
221 internal_modules: list of js source files for internal modules
222 internal_wasm_modules: list of wasm source files
223 internal_data_modules: list of data source files
224 internal_json_modules: list of json source files
225 declarations: d.ts label set
226 deps: dependency list
227 gen_compile_cache: generate compilation cache of every file and include into the bundle
228 """
229 builtin_modules_dict = {
230 m: "{}:{}".format(import_name, _to_name(m).removeprefix(out_dir))
231 for m in builtin_modules
232 }
233 internal_modules_dict = {
234 m: "{}-internal:{}".format(import_name, _to_name(m.removeprefix(out_dir).removeprefix("internal/")))
235 for m in internal_modules
236 }
237 internal_wasm_modules_dict = {
238 m: "{}-internal:{}".format(import_name, m.removeprefix(out_dir).removeprefix("internal/"))
239 for m in internal_wasm_modules
240 }
241 internal_data_modules_dict = {
242 m: "{}-internal:{}".format(import_name, m.removeprefix(out_dir).removeprefix("internal/"))
243 for m in internal_data_modules
244 }
245 internal_json_modules_dict = {
246 m: "{}-internal:{}".format(import_name, m.removeprefix(out_dir).removeprefix("internal/"))
247 for m in internal_json_modules
248 }
249 
250 builtin_modules_dict, builtin_declarations = _copy_modules(
251 builtin_modules_dict,
252 declarations,
253 out_dir,
254 )
255 internal_modules_dict, internal_declarations = _copy_modules(
256 internal_modules_dict,
257 declarations,
258 out_dir,
259 )
260 internal_wasm_modules_dict, _ = _copy_modules(
261 internal_wasm_modules_dict,
262 declarations,
263 out_dir,
264 )
265 internal_data_modules_dict, _ = _copy_modules(
266 internal_data_modules_dict,
267 declarations,
268 out_dir,
269 )
270 internal_json_modules_dict, _ = _copy_modules(
271 internal_json_modules_dict,
272 declarations,
273 out_dir,
274 )
275 
276 data = (
277 list(builtin_modules_dict) +
278 list(internal_modules_dict) +
279 list(internal_wasm_modules_dict) +
280 list(internal_data_modules_dict) +
281 list(internal_json_modules_dict) +
282 list(builtin_declarations.values()) +
283 list(internal_declarations.values())
284 )
285 
286 compile_cache = None
287 if gen_compile_cache:
288 _gen_compile_cache(
289 name = name + "@compile_cache",
290 srcs = builtin_modules_dict.keys() + internal_modules_dict.keys(),
291 )
292 compile_cache = name + "@compile_cache"
293 deps = deps + [compile_cache]
294 data = data + [compile_cache]
295 
296 gen_api_bundle_capnpn(
297 name = name + "@gen",
298 out = name + ".capnp",
299 schema_id = schema_id,
300 const_name = import_name + "Bundle",
301 builtin_modules = builtin_modules_dict,
302 internal_modules = internal_modules_dict,
303 internal_wasm_modules = internal_wasm_modules_dict,
304 internal_data_modules = internal_data_modules_dict,
305 internal_json_modules = internal_json_modules_dict,
306 declarations = builtin_declarations | internal_declarations,
307 data = data,
308 deps = deps,
309 compile_cache = compile_cache,
310 )
311 
312 cc_capnp_library(
313 name = name,
314 srcs = [name + ".capnp"],
315 strip_include_prefix = "",
316 visibility = ["//visibility:public"],
317 data = data,
318 deps = ["@workerd//src/workerd/jsg:modules_capnp"],
319 include_prefix = import_name,
320 )