blob: 4cceb1adc83b6d3c38104d4876dfb5c361525eb6 [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.
"""The ts_devserver rule brings up our "getting started" devserver.
See the README.md.
"""
load("@build_bazel_rules_nodejs//internal:node.bzl",
"sources_aspect",
)
def _ts_devserver(ctx):
files = depset()
for d in ctx.attr.deps:
if hasattr(d, "node_sources"):
files += d.node_sources
elif hasattr(d, "files"):
files += d.files
if ctx.label.workspace_root:
# We need the workspace_name for the target being visited.
# Skylark doesn't have this - instead they have a workspace_root
# which looks like "external/repo_name" - so grab the second path segment.
# TODO(alexeagle): investigate a better way to get the workspace name
workspace_name = ctx.label.workspace_root.split("/")[1]
else:
workspace_name = ctx.workspace_name
# Create a manifest file with the sources in arbitrary order, and without
# bazel-bin prefixes ("root-relative paths").
# TODO(alexeagle): we should experiment with keeping the files toposorted, to
# see if we can get performance gains out of the module loader.
ctx.actions.write(ctx.outputs.manifest, "".join([
workspace_name + "/" + f.short_path + "\n" for f in files
]))
# Requirejs is always needed so its included as the first script
# in script_files before any user specified scripts for the devserver
# to concat in order.
script_files = depset()
script_files += ctx.files._requirejs_script
script_files += ctx.files.scripts
ctx.actions.write(ctx.outputs.scripts_manifest, "".join([
workspace_name + "/" + f.short_path + "\n" for f in script_files
]))
devserver_runfiles = [
ctx.executable._devserver,
ctx.outputs.manifest,
ctx.outputs.scripts_manifest,
ctx.file._requirejs_script]
devserver_runfiles += ctx.files.static_files
devserver_runfiles += ctx.files.scripts
serving_arg = ""
if ctx.attr.serving_path:
serving_arg = "-serving_path=%s" % ctx.attr.serving_path
# FIXME: more bash dependencies makes Windows support harder
ctx.actions.write(
output = ctx.outputs.executable,
is_executable = True,
content = """#!/bin/sh
RUNFILES="$PWD/.."
{main} {serving_arg} \
-base "$RUNFILES" \
-packages={workspace}/{package} \
-manifest={workspace}/{manifest} \
-scripts_manifest={workspace}/{scripts_manifest} \
-entry_module={entry_module} \
"$@"
""".format(
main = ctx.executable._devserver.short_path,
serving_arg = serving_arg,
workspace = workspace_name,
package = ctx.label.package,
manifest = ctx.outputs.manifest.short_path,
scripts_manifest = ctx.outputs.scripts_manifest.short_path,
entry_module = ctx.attr.entry_module))
return [DefaultInfo(
runfiles = ctx.runfiles(
files = devserver_runfiles,
# We don't expect executable targets to depend on the devserver, but if they do,
# they can see the JavaScript code.
transitive_files = depset(ctx.files.data) + files,
collect_data = True,
collect_default = True,
)
)]
ts_devserver = rule(
implementation = _ts_devserver,
attrs = {
"deps": attr.label_list(allow_files = True, aspects = [sources_aspect]),
"serving_path": attr.string(),
"data": attr.label_list(allow_files = True, cfg = "data"),
"static_files": attr.label_list(allow_files = True),
# User scripts for the devserver to concat before the source files
"scripts": attr.label_list(allow_files = True),
# The entry_module should be the AMD module name of the entry module such as "__main__/src/index"
# Devserver concats the following snippet after the bundle to load the application: require(["entry_module"]);
"entry_module": attr.string(),
"_requirejs_script": attr.label(allow_files = True, single_file = True, default = Label("@build_bazel_rules_typescript_devserver_deps//:node_modules/requirejs/require.js")),
"_devserver": attr.label(
default = Label("//internal/devserver/main"),
executable = True,
cfg = "host",
),
},
outputs = {
"manifest": "%{name}.MF",
"scripts_manifest": "scripts_%{name}.MF",
},
executable = True,
)
def ts_devserver_macro(tags = [], **kwargs):
ts_devserver(
# Users don't need to know that these tags are required to run under ibazel
tags = tags + [
# Tell ibazel not to restart the devserver when its deps change.
"ibazel_notify_changes",
# Tell ibazel to serve the live reload script, since we expect a browser will connect to
# this program.
"ibazel_live_reload",
],
**kwargs
)