Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatic TypeScript definition generation from JSG RTTI #113

Merged
merged 6 commits into from
Oct 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
.idea
.DS_Store

/rust-deps/target
/rust-deps/Cargo.toml

Expand Down
16 changes: 16 additions & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
load("@capnp-cpp//src/capnp:cc_capnp_library.bzl", "cc_capnp_library")
load("@hedron_compile_commands//:refresh_compile_commands.bzl", "refresh_compile_commands")
load("@aspect_rules_js//npm:defs.bzl", "npm_link_package", "npm_package")
load("@npm//:defs.bzl", "npm_link_all_packages")
load("@npm//:capnpc-ts/package_json.bzl", capnpc_ts_bin = "bin")

cc_capnp_library(
name = "icudata-embed",
Expand All @@ -13,3 +16,16 @@ cc_capnp_library(
refresh_compile_commands(
name = "refresh_compile_commands",
)

npm_link_all_packages(name = "node_modules")
mrbbot marked this conversation as resolved.
Show resolved Hide resolved

npm_link_package(
name = "node_modules/@workerd/jsg",
src = "//src/workerd/jsg:jsg_js",
package = "@workerd/jsg",
)

capnpc_ts_bin.capnpc_ts_binary(
name = "capnpc_ts",
visibility = ["//visibility:public"],
)
52 changes: 52 additions & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,58 @@ load("//rust-deps/cxxbridge_crates:crates.bzl", cxxbridge_repositories = "crate_

cxxbridge_repositories()

# ========================================================================================
# Node.js bootstrap
#
# workerd uses Node.js scripts for generating TypeScript types.

http_archive(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will definitely make embedding workerd hard.

Maybe extract this to a separate function setup_js_dependencies in bzl file so that it can be called by downstream deps if needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I'm missing something, I don't think you can call load() in functions (bazelbuild/bazel#1550)? So we wouldn't be able to call rules_js_dependencies, rules_ts_dependencies, npm_translate_lock or npm_repositories in our function? We have the same problem with Rust dependencies atm: the internal repository currently duplicates all the Rust WORKSPACE rules. 😕

name = "aspect_rules_js",
sha256 = "b9fde0f20de6324ad443500ae738bda00facbd73900a12b417ce794856e01407",
strip_prefix = "rules_js-1.5.0",
url = "https://github.com/aspect-build/rules_js/archive/refs/tags/v1.5.0.tar.gz",
)

http_archive(
name = "aspect_rules_ts",
sha256 = "743f0e988e4e3f1e25e52c79f9dc3da1ddd77507ae88787ae95b4e70c537872b",
strip_prefix = "rules_ts-1.0.0-rc4",
url = "https://github.com/aspect-build/rules_ts/archive/refs/tags/v1.0.0-rc4.tar.gz",
)

load("@aspect_rules_js//js:repositories.bzl", "rules_js_dependencies")

rules_js_dependencies()

load("@rules_nodejs//nodejs:repositories.bzl", "nodejs_register_toolchains")

nodejs_register_toolchains(
name = "nodejs",
node_version = "18.10.0",
)

load("@aspect_rules_ts//ts:repositories.bzl", TS_LATEST_VERSION = "LATEST_VERSION", "rules_ts_dependencies")

rules_ts_dependencies(ts_version = TS_LATEST_VERSION)

load("@aspect_rules_js//npm:npm_import.bzl", "npm_translate_lock")

npm_translate_lock(
name = "npm",
pnpm_lock = "//:pnpm-lock.yaml",
# Patches required for `capnp-ts` to type-check
patches = {
"[email protected]": ["//:patches/[email protected]"],
},
patch_args = {
"[email protected]": ["-p1"],
},
)

load("@npm//:repositories.bzl", "npm_repositories")

npm_repositories()

# ========================================================================================
# V8 and its dependencies
#
Expand Down
124 changes: 124 additions & 0 deletions build/js_capnp_library.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
"""
Bazel rule to compile .capnp files into JavaScript using capnp-ts.
Based on https://github.com/capnproto/capnproto/blob/3b2e368cecc4b1419b40c5970d74a7a342224fac/c++/src/capnp/cc_capnp_library.bzl.
"""

load("@aspect_rules_js//js:defs.bzl", "js_library")

capnp_provider = provider("Capnproto Provider", fields = {
"includes": "includes for this target (transitive)",
"inputs": "src + data for the target",
"src_prefix": "src_prefix of the target",
})

def _workspace_path(label, path):
if label.workspace_root == "":
return path
return label.workspace_root + "/" + path

def _capnp_gen_impl(ctx):
label = ctx.label
src_prefix = _workspace_path(label, ctx.attr.src_prefix)
includes = []

inputs = ctx.files.srcs + ctx.files.data
for dep_target in ctx.attr.deps:
includes += dep_target[capnp_provider].includes
inputs += dep_target[capnp_provider].inputs

if src_prefix != "":
includes.append(src_prefix)

system_include = ctx.files._capnp_system[0].dirname.removesuffix("/capnp")

out_dir = ctx.var["GENDIR"]
if src_prefix != "":
out_dir = out_dir + "/" + src_prefix

js_out = "-o%s:%s" % (ctx.executable._capnpc_ts.path, out_dir)
args = ctx.actions.args()
args.add_all(["compile", "--verbose", js_out])
args.add_all(["-I" + inc for inc in includes])
args.add_all(["-I", system_include])
if src_prefix != "":
args.add_all(["--src-prefix", src_prefix])

args.add_all([s for s in ctx.files.srcs])

ctx.actions.run(
inputs = inputs + ctx.files._capnpc_ts + ctx.files._capnpc_capnp + ctx.files._capnp_system,
tools = [ctx.executable._capnpc_ts], # Include required js_binary runfiles
outputs = ctx.outputs.outs,
executable = ctx.executable._capnpc,
arguments = [args],
mnemonic = "GenCapnp",
)

return [
capnp_provider(
includes = includes,
inputs = inputs,
src_prefix = src_prefix,
),
]

_capnp_gen = rule(
attrs = {
"srcs": attr.label_list(allow_files = True),
"deps": attr.label_list(providers = [capnp_provider]),
"data": attr.label_list(allow_files = True),
"outs": attr.output_list(),
"src_prefix": attr.string(),
"_capnpc": attr.label(executable = True, allow_single_file = True, cfg = "exec", default = "@capnp-cpp//src/capnp:capnp_tool"),
"_capnpc_ts": attr.label(executable = True, allow_single_file = True, cfg = "exec", default = "//:capnpc_ts"),
"_capnpc_capnp": attr.label(executable = True, allow_single_file = True, cfg = "exec", default = "@capnp-cpp//src/capnp:capnpc-capnp"),
"_capnp_system": attr.label(default = "@capnp-cpp//src/capnp:capnp_system_library"),
},
output_to_genfiles = True,
implementation = _capnp_gen_impl,
)

def js_capnp_library(
name,
srcs = [],
data = [],
deps = [],
src_prefix = "",
visibility = None,
target_compatible_with = None,
**kwargs):
"""Bazel rule to create a JavaScript capnproto library from capnp source files

Args:
name: library name
srcs: list of files to compile
data: additional files to provide to the compiler - data files and includes that need not to
be compiled
deps: other js_capnp_library rules to depend on
src_prefix: src_prefix for capnp compiler to the source root
visibility: rule visibility
target_compatible_with: target compatibility
**kwargs: rest of the arguments to js_library rule
"""

js_files = [s + ".js" for s in srcs]
d_ts_files = [s + ".d.ts" for s in srcs]

_capnp_gen(
name = name + "_gen",
srcs = srcs,
deps = [s + "_gen" for s in deps],
data = data,
outs = js_files + d_ts_files,
src_prefix = src_prefix,
visibility = visibility,
target_compatible_with = target_compatible_with,
)
js_library(
name = name,
srcs = js_files + d_ts_files,
deps = deps + ["//:node_modules/capnp-ts"],
visibility = visibility,
target_compatible_with = target_compatible_with,
**kwargs
)
13 changes: 13 additions & 0 deletions build/typescript.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
def module_name(ts_name):
if ts_name.endswith(".ts"):
return ts_name.removesuffix(".ts")
if ts_name.endswith(".mts"):
return ts_name.removesuffix(".mts")
fail("Expected TypeScript source file, got " + ts_name)

def js_name(ts_name):
if ts_name.endswith(".ts"):
return ts_name.removesuffix(".ts") + ".js"
if ts_name.endswith(".mts"):
return ts_name.removesuffix(".mts") + ".mjs"
fail("Expected TypeScript source file, got " + ts_name)
19 changes: 19 additions & 0 deletions build/wd_ts_project.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
load("@aspect_rules_ts//ts:defs.bzl", "ts_project")

def wd_ts_project(name, srcs, deps, testonly = False):
"""Bazel rule for a workerd TypeScript project, setting common options"""

ts_project(
name = name,
srcs = srcs,
deps = deps,
tsconfig = "//types:tsconfig.json",
allow_js = True,
composite = True,
source_map = True,
testonly = testonly,
# Disable workers to avoid issue with multiple targets
# (https://github.com/aspect-build/rules_ts/issues/128)
# TODO: try re-enable these on next aspect_rules_ts update
supports_workers = False,
)
23 changes: 23 additions & 0 deletions build/wd_ts_test.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
load("@aspect_rules_js//js:defs.bzl", "js_test")
load("//:build/wd_ts_project.bzl", "wd_ts_project")
load("//:build/typescript.bzl", "js_name", "module_name")

def wd_ts_test(src, deps = [], **kwargs):
"""Bazel rule to compile and run a TypeScript test"""

name = module_name(src)

wd_ts_project(
name = name + "@compile",
srcs = [src],
deps = deps,
testonly = True,
)

js_test(
name = name,
entry_point = js_name(src),
data = deps + [name + "@compile"],
tags = ["no-arm64", "js-test"],
**kwargs
)
25 changes: 25 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "@cloudflare/workerd-root",
"private": true,
"scripts": {
"lint": "eslint types/src"
},
"dependencies": {
"capnp-ts": "^0.7.0",
"prettier": "^2.7.1",
"typescript": "~4.7.4"
},
"devDependencies": {
"@types/debug": "^4.1.7",
"@types/node": "^18.7.18",
"@types/prettier": "^2.7.1",
"@typescript-eslint/eslint-plugin": "^5.37.0",
"@typescript-eslint/parser": "^5.37.0",
"capnpc-ts": "^0.7.0",
"esbuild": "^0.15.7",
"eslint": "^8.22.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.2.1"
}
}
11 changes: 11 additions & 0 deletions patches/[email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--- a/src/serialization/pointers/struct.ts
+++ b/src/serialization/pointers/struct.ts
@@ -107,8 +107,6 @@ export class Struct extends Pointer {
static readonly setText = setText;
static readonly testWhich = testWhich;

- readonly _capnp!: _Struct;
-
/**
* Create a new pointer to a struct.
*
Loading