Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/javascript/selenium-webdriver/private/generate_bidi.bzl
11813 views
"""Bazel rules for generating WebDriver BiDi TypeScript modules from CDDL specification."""

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

# Language bindings that may consume the shared bidi-ast.json / bidi-model.json artifacts.
_ARTIFACT_VISIBILITY = [
    "//java:__subpackages__",
    "//py:__subpackages__",
    "//rb:__subpackages__",
]

# Output TypeScript file names produced by generate_bidi.mjs, one per domain.
_DOMAIN_TS_FILES = [
    "bluetooth.ts",
    "browser.ts",
    "browsing_context.ts",
    "common.ts",
    "emulation.ts",
    "input.ts",
    "log.ts",
    "network.ts",
    "permissions.ts",
    "script.ts",
    "session.ts",
    "speculation.ts",
    "storage.ts",
    "user_agent_client_hints.ts",
    "webextension.ts",
]

def _merge_cddl_impl(ctx):
    """Merges one or more CDDL files into a single output file."""
    out = ctx.outputs.out
    args = ctx.actions.args()
    args.add(out)
    args.add_all(ctx.files.srcs)
    ctx.actions.run(
        inputs = ctx.files.srcs,
        outputs = [out],
        executable = ctx.executable.tool,
        arguments = [args],
        mnemonic = "MergeCddl",
        progress_message = "Merging CDDL files into %s" % out.short_path,
    )
    return [DefaultInfo(files = depset([out]))]

_merge_cddl = rule(
    implementation = _merge_cddl_impl,
    attrs = {
        "srcs": attr.label_list(allow_files = True, mandatory = True),
        "out": attr.output(mandatory = True),
        "tool": attr.label(
            executable = True,
            cfg = "exec",
            mandatory = True,
        ),
    },
    doc = "Merges CDDL specification files into a single file using an external merge tool.",
)

def _compile_bidi_ts_impl(ctx):
    ts_files = ctx.files.srcs
    output_subdir = ctx.attr.output_subdir
    tsc = ctx.executable.tsc

    js_outputs = [
        ctx.actions.declare_file(output_subdir + "/" + f.basename.replace(".ts", ".js"))
        for f in ts_files
    ]
    dts_outputs = [
        ctx.actions.declare_file(output_subdir + "/" + f.basename.replace(".ts", ".d.ts"))
        for f in ts_files
    ]
    all_outputs = js_outputs + dts_outputs

    args = ctx.actions.args()
    args.add("--target", "ES2020")
    args.add("--module", "NodeNext")
    args.add("--moduleResolution", "NodeNext")
    args.add("--declaration")
    args.add("--outDir", js_outputs[0].dirname)
    for f in ts_files:
        args.add(f.path)

    ctx.actions.run(
        inputs = ts_files,
        outputs = all_outputs,
        executable = tsc,
        arguments = [args],
        env = {
            "BAZEL_BINDIR": ctx.bin_dir.path,
            # Prevent the js_binary wrapper from cd-ing to BAZEL_BINDIR.
            # Without this, all file paths passed to tsc (which start with
            # bazel-out/..., i.e. relative to the execroot) would be resolved
            # relative to BAZEL_BINDIR and end up double-prefixed.
            "JS_BINARY__NO_CD_BINDIR": "1",
        },
        mnemonic = "TscCompileBiDi",
        progress_message = "Compiling WebDriver BiDi TypeScript to JavaScript",
    )

    return [DefaultInfo(files = depset(all_outputs))]

_compile_bidi_ts = rule(
    implementation = _compile_bidi_ts_impl,
    attrs = {
        "output_subdir": attr.string(mandatory = True),
        "srcs": attr.label_list(allow_files = True, mandatory = True),
        "tsc": attr.label(
            executable = True,
            cfg = "exec",
            default = "@npm_typescript//:tsc",
        ),
    },
    doc = "Compiles generated BiDi TypeScript files to JavaScript + declaration files",
)

def generate_bidi_library(
        name,
        cddl_file,
        extra_cddl_files = [],
        enhancements_manifest = None,
        generator = None,
        merge_tool = "//py/private:merge_cddl",
        spec_version = "1.0",
        output_path = "bidi/generated"):
    """Macro that merges CDDL, generates BiDi TypeScript modules, and compiles them to JS.

    Args:
        name: Base name for the targets.
        cddl_file: Primary CDDL spec label (webdriver-bidi-all.cddl).
        extra_cddl_files: Additional CDDL files merged before generation.
        enhancements_manifest: JSON manifest for per-domain customisations.
        generator: The generate_bidi.mjs js_binary label. Defaults to :generate_bidi_script.
        merge_tool: Python binary that concatenates CDDL files (output first, then inputs).
        spec_version: Spec version string passed to the generator.
        output_path: Output path for generated files within the package (default: bidi/generated).
    """
    if generator == None:
        generator = ":generate_bidi_script"

    pkg = native.package_name()
    ts_src_path = output_path + "_src"

    # Step 1: merge CDDL files into one.
    # merge_cddl signature: <output> <input1> [<input2> ...]
    # Uses ctx.actions.run so arguments are passed as an argv list rather than
    # a shell command string, avoiding quoting/escaping issues with special chars.
    merged_name = name + "_merged_cddl"
    _merge_cddl(
        name = merged_name,
        srcs = [cddl_file] + extra_cddl_files,
        out = name + "_merged.cddl",
        tool = merge_tool,
    )

    # Step 2: parse the merged CDDL once into the reusable AST artifact.
    # Exposed to the other bindings so they can consume it directly.
    ast_target = name + "_ast"
    ast_out = name + "_ast.json"
    js_run_binary(
        name = ast_target,
        srcs = [":" + merged_name],
        outs = [ast_out],
        args = [
            "--cddl",
            "$(location :" + merged_name + ")",
            "--dump-ast",
            pkg + "/" + ast_out,
        ],
        tool = generator,
        visibility = _ARTIFACT_VISIBILITY,
    )

    # Step 3: extract the binding-neutral command/event model from the AST.
    # Exposed to the other bindings so they can consume it directly.
    json_target = name + "_json"
    model_out = name + "_model.json"
    js_run_binary(
        name = json_target,
        srcs = [":" + ast_target],
        outs = [model_out],
        args = [
            "--ast",
            "$(location :" + ast_target + ")",
            "--dump-model",
            pkg + "/" + model_out,
        ],
        tool = generator,
        visibility = _ARTIFACT_VISIBILITY,
    )

    # Step 4: generate one .ts module per BiDi domain from the AST + model.
    ts_outs = [ts_src_path + "/" + f for f in _DOMAIN_TS_FILES]
    gen_srcs = [":" + ast_target, ":" + json_target]
    gen_args = [
        "--ast",
        "$(location :" + ast_target + ")",
        "--model",
        "$(location :" + json_target + ")",
        "--output-dir",
        pkg + "/" + ts_src_path,
        "--spec-version",
        spec_version,
    ]
    if enhancements_manifest:
        gen_srcs.append(enhancements_manifest)
        gen_args += ["--enhancements", "$(location " + enhancements_manifest + ")"]

    ts_target = name + "_ts"
    js_run_binary(
        name = ts_target,
        srcs = gen_srcs,
        outs = ts_outs,
        args = gen_args,
        tool = generator,
    )

    # Step 5: compile .ts → .js + .d.ts via tsc (custom rule for ctx.bin_dir.path).
    _compile_bidi_ts(
        name = name,
        srcs = [":" + ts_target],
        output_subdir = output_path,
    )