blob: 807dea0fd89d9d7b4d09e4c29291d34e32b4b8ba [file] [log] [blame]
# Copyright 2017 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"Protocol Buffers"
def _run_pbjs(actions, executable, output_name, proto_files, suffix = ".js", wrap = "amd", amd_name = ""):
js_file = actions.declare_file(output_name + suffix)
# Create an intermediate file so that we can do some manipulation of the
# generated .js output that makes it compatible with our named AMD loading.
js_tmpl_file = actions.declare_file(output_name + suffix + ".tmpl")
# Reference of arguments:
# https://github.com/dcodeIO/ProtoBuf.js/#pbjs-for-javascript
args = actions.args()
args.add_all(["--target", "static-module"])
args.add_all(["--wrap", wrap])
args.add("--strict-long") # Force usage of Long type with int64 fields
args.add_all(["--out", js_file.path + ".tmpl"])
args.add_all(proto_files)
actions.run(
executable = executable._pbjs,
inputs = proto_files,
outputs = [js_tmpl_file],
arguments = [args],
)
actions.expand_template(
template = js_tmpl_file,
output = js_file,
substitutions = {
# convert anonymous AMD module
# define(["protobufjs/minimal"], function($protobuf) {
# to named
# define("wksp/path/to/module", ["protobufjs/minimal"], ...
"define([": "define('%s/%s', [" % (amd_name, output_name),
},
)
return js_file
def _run_pbts(actions, executable, js_file):
ts_file = actions.declare_file(js_file.basename[:-len(".closure.js")] + ".d.ts")
# Reference of arguments:
# https://github.com/dcodeIO/ProtoBuf.js/#pbts-for-typescript
args = actions.args()
args.add_all(["--out", ts_file.path])
args.add(js_file.path)
actions.run(
executable = executable._pbts,
progress_message = "Generating typings from %s" % js_file.short_path,
inputs = [js_file],
outputs = [ts_file],
arguments = [args],
)
return ts_file
def _ts_proto_library(ctx):
sources = depset()
for dep in ctx.attr.deps:
if not hasattr(dep, "proto"):
fail("ts_proto_library dep %s must be a proto_library rule" % dep.label)
# TODO(alexeagle): go/new-proto-library suggests
# > should not parse .proto files. Instead, they should use the descriptor
# > set output from proto_library
# but protobuf.js doesn't seem to accept that bin format
sources = depset(transitive = [sources, dep.proto.transitive_sources])
output_name = ctx.attr.output_name or ctx.label.name
js_es5 = _run_pbjs(
ctx.actions,
ctx.executable,
output_name,
sources,
amd_name = "/".join([p for p in [
ctx.workspace_name,
ctx.label.package,
] if p]),
)
js_es6 = _run_pbjs(
ctx.actions,
ctx.executable,
output_name,
sources,
suffix = ".closure.js",
wrap = "es6",
)
dts = _run_pbts(ctx.actions, ctx.executable, js_es6)
# Return a structure that is compatible with the deps[] of a ts_library.
return struct(
files = depset([dts]),
typescript = struct(
declarations = depset([dts]),
transitive_declarations = depset([dts]),
type_blacklisted_declarations = depset(),
es5_sources = depset([js_es5]),
es6_sources = depset([js_es6]),
transitive_es5_sources = depset(),
transitive_es6_sources = depset([js_es6]),
),
)
ts_proto_library = rule(
implementation = _ts_proto_library,
attrs = {
"output_name": attr.string(
doc = """Name of the resulting module, which you will import from.
If not specified, the name will match the target's name.""",
),
"deps": attr.label_list(doc = "proto_library targets"),
"_pbjs": attr.label(
default = Label("//internal/protobufjs:pbjs"),
executable = True,
cfg = "host",
),
"_pbts": attr.label(
default = Label("//internal/protobufjs:pbts"),
executable = True,
cfg = "host",
),
},
)
"""
Wraps https://github.com/dcodeIO/protobuf.js for use in Bazel.
`ts_proto_library` has identical outputs to `ts_library`, so it can be used anywhere
a `ts_library` can appear, such as in the `deps[]` of another `ts_library`.
Example:
```
load("@npm_bazel_typescript//:defs.bzl", "ts_library", "ts_proto_library")
proto_library(
name = "car_proto",
srcs = ["car.proto"],
)
ts_proto_library(
name = "car",
deps = [":car_proto"],
)
ts_library(
name = "test_lib",
testonly = True,
srcs = ["car.spec.ts"],
deps = [":car"],
)
```
Note in this example we named the `ts_proto_library` rule `car` so that the
result will be `car.d.ts`. This means our TypeScript code can just
`import {symbols} from './car'`. Use the `output_name` attribute if you want to
name the rule differently from the output file.
The JavaScript produced by protobuf.js has a runtime dependency on a support library.
Under devmode (e.g. `ts_devserver`, `ts_web_test_suite`) you'll need to include these scripts
in the `bootstrap` phase (before Require.js loads). You can use the label
`@npm_bazel_typescript//:protobufjs_bootstrap_scripts` to reference these scripts
in the `bootstrap` attribute of `ts_web_test_suite` or `ts_devserver`.
To complete the example above, you could write a `ts_web_test_suite`:
```
load("@npm_bazel_karma//:defs.bzl", "ts_web_test_suite")
ts_web_test_suite(
name = "test",
deps = ["test_lib"],
bootstrap = ["@npm_bazel_typescript//:protobufjs_bootstrap_scripts"],
browsers = [
"@io_bazel_rules_webtesting//browsers:chromium-local",
"@io_bazel_rules_webtesting//browsers:firefox-local",
],
)
```
"""