豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content

Commit b066f7b

Browse files
committed
feat(bazel): move devserver from angular/components to dev-infra
Exposes the devserver from angular components within dev-infra. This allows us to remove duplication within the devtools folder in framework, and also helps us with using this devserver as alternative to the Go-based one (which does not work with M1 and Windows). Code remains mostly untouched, except: * Renamed the devserver to "http-server". * Removed some Material-specific DX logic. Added `environment_variables` attribute instead. * Replaced minimist with Yargs (as we have a dep for that already and it's easier).
1 parent 19bdffc commit b066f7b

File tree

13 files changed

+1106
-17
lines changed

13 files changed

+1106
-17
lines changed

bazel/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ filegroup(
99
"//bazel/browsers:files",
1010
"//bazel/constraints:files",
1111
"//bazel/esbuild:files",
12+
"//bazel/http-server:files",
1213
"//bazel/integration:files",
1314
"//bazel/remote-execution:files",
1415
"//bazel/spec-bundling:files",

bazel/http-server/BUILD.bazel

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary")
2+
load("@npm//@bazel/typescript:index.bzl", "ts_library")
3+
4+
package(default_visibility = ["//visibility:public"])
5+
6+
exports_files(["launcher_template.sh"])
7+
8+
# Make source files available for distribution via pkg_npm
9+
filegroup(
10+
name = "files",
11+
srcs = glob(["*"]),
12+
)
13+
14+
ts_library(
15+
name = "server_lib",
16+
srcs = [
17+
"ibazel.ts",
18+
"main.ts",
19+
"server.ts",
20+
],
21+
# A tsconfig needs to be specified as otherwise `ts_library` will look for the config
22+
# in `//:package.json` and this breaks when the BUILD file is copied to `@npm//`.
23+
tsconfig = "//:tsconfig.json",
24+
deps = [
25+
"@npm//@types/browser-sync",
26+
"@npm//@types/node",
27+
"@npm//@types/send",
28+
"@npm//@types/yargs",
29+
"@npm//browser-sync",
30+
"@npm//send",
31+
"@npm//yargs",
32+
],
33+
)
34+
35+
nodejs_binary(
36+
name = "server_bin",
37+
data = [
38+
":server_lib",
39+
],
40+
entry_point = ":main.ts",
41+
templated_args = ["--bazel_patch_module_resolver"],
42+
)

bazel/http-server/ibazel.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {createInterface} from 'readline';
10+
import {HttpServer} from './server';
11+
12+
// ibazel will write this string after a successful build.
13+
const ibazelNotifySuccessMessage = 'IBAZEL_BUILD_COMPLETED SUCCESS';
14+
15+
/**
16+
* Sets up ibazel support for the specified server. ibazel communicates with
17+
* an executable over the "stdin" interface. Whenever a specific message is sent
18+
* over "stdin", the server can be reloaded.
19+
*/
20+
export function setupBazelWatcherSupport(server: HttpServer) {
21+
// If iBazel is not configured for this process, we do not setup the watcher.
22+
if (process.env['IBAZEL_NOTIFY_CHANGES'] !== 'y') {
23+
return;
24+
}
25+
26+
// ibazel communicates via the stdin interface.
27+
const rl = createInterface({input: process.stdin, terminal: false});
28+
29+
rl.on('line', (chunk: string) => {
30+
if (chunk === ibazelNotifySuccessMessage) {
31+
server.reload();
32+
}
33+
});
34+
35+
rl.on('close', () => {
36+
// Give ibazel some time to kill this process, otherwise we exit the process manually.
37+
// TODO(devversion): re-check if this is still needed?
38+
setTimeout(() => {
39+
console.error('Bazel watcher did not stop the HTTP server after 5 seconds. Exiting...');
40+
process.exit(1);
41+
}, 5000);
42+
});
43+
}

bazel/http-server/index.bzl

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
load("@build_bazel_rules_nodejs//:providers.bzl", "JSNamedModuleInfo")
2+
3+
def _get_workspace_name(ctx):
4+
if ctx.label.workspace_root:
5+
# We need the workspace_name for the target being visited.
6+
# Starlark doesn't have this - instead they have a workspace_root
7+
# which looks like "external/repo_name" - so grab the second path segment.
8+
return ctx.label.workspace_root.split("/")[1]
9+
else:
10+
return ctx.workspace_name
11+
12+
def _http_server_rule_impl(ctx):
13+
"""Implementation of the http server rule."""
14+
15+
workspace_name = _get_workspace_name(ctx)
16+
package_manifest_path = "%s/%s" % (workspace_name, ctx.label.package)
17+
18+
# List of files which are required for the server to run. This includes the
19+
# bazel runfile helpers (to resolve runfiles in bash) and the server binary
20+
# with its transitive runfiles (in order to be able to run the server).
21+
required_tools = ctx.files._bash_runfile_helpers + \
22+
ctx.files._server_bin + \
23+
ctx.attr._server_bin[DefaultInfo].files.to_list() + \
24+
ctx.attr._server_bin[DefaultInfo].data_runfiles.files.to_list()
25+
26+
# Walk through all dependencies specified in the "deps" attribute. These labels need to be
27+
# unwrapped in case there are built using TypeScript-specific rules.
28+
transitive_depsets = []
29+
for dep in ctx.attr.deps:
30+
if JSNamedModuleInfo in dep:
31+
transitive_depsets.append(dep[JSNamedModuleInfo].sources)
32+
elif DefaultInfo in dep:
33+
transitive_depsets.append(dep[DefaultInfo].files)
34+
35+
root_paths = ["", package_manifest_path] + ctx.attr.additional_root_paths
36+
37+
# We can't use "ctx.actions.args()" because there is no way to convert the args object
38+
# into a string representing the command line arguments. It looks like bazel has some
39+
# internal logic to compute the string representation of "ctx.actions.args()".
40+
args = ""
41+
42+
for root in root_paths:
43+
args += "--root-paths '%s' " % root
44+
45+
if ctx.attr.history_api_fallback:
46+
args += "--history-api-fallback=true "
47+
48+
if ctx.attr.enable_dev_ui:
49+
args += "--enable-dev-ui=true "
50+
51+
for variable_name in ctx.attr.environment_variables:
52+
args += "--environment-variables '%s' " % variable_name
53+
54+
ctx.actions.expand_template(
55+
template = ctx.file._launcher_template,
56+
output = ctx.outputs.launcher,
57+
substitutions = {
58+
"TEMPLATED_args": args,
59+
},
60+
is_executable = True,
61+
)
62+
63+
runfiles = ctx.runfiles(
64+
files = ctx.files.srcs + required_tools,
65+
transitive_files = depset(transitive = transitive_depsets),
66+
collect_data = True,
67+
collect_default = True,
68+
)
69+
70+
return [
71+
DefaultInfo(
72+
files = depset([ctx.outputs.launcher]),
73+
runfiles = runfiles,
74+
),
75+
]
76+
77+
http_server_rule = rule(
78+
implementation = _http_server_rule_impl,
79+
outputs = {
80+
"launcher": "%{name}.sh",
81+
},
82+
attrs = {
83+
"additional_root_paths": attr.string_list(
84+
doc = """
85+
Additional paths to serve files from. The paths should be passed
86+
as manifest paths (e.g. "my_workspace/src").
87+
""",
88+
),
89+
"deps": attr.label_list(
90+
allow_files = True,
91+
doc = """
92+
Dependencies that need to be available for resolution. This attribute can be
93+
used for TypeScript targets which provide multiple flavors of output.
94+
""",
95+
),
96+
"enable_dev_ui": attr.bool(
97+
default = False,
98+
doc = """
99+
Whether an additional UI for development should be enabled.
100+
The development UI can be helpful for throttling network and more. This
101+
is a feature from the underlying browsersync implementation.
102+
""",
103+
),
104+
"history_api_fallback": attr.bool(
105+
default = True,
106+
doc = """
107+
Whether the server should fallback to "/index.html" for non-file requests.
108+
This is helpful for single page applications using the HTML history API.
109+
""",
110+
),
111+
"environment_variables": attr.string_list(
112+
default = [],
113+
doc = """
114+
List of environment variables that will be made available in the `index.html`
115+
file. Variables can be accessed through `window.<NAME>`.
116+
117+
This is useful as an example when developers want to have an API key available
118+
as part of their development workflow, but not hard-coding it into the sources.
119+
""",
120+
),
121+
"srcs": attr.label_list(
122+
allow_files = True,
123+
doc = """
124+
Sources that should be available to the server for resolution. This attribute can
125+
be used for explicit files. This attribute only uses the files exposed by the
126+
`DefaultInfo` provider (i.e. TypeScript targets should be added to "deps").
127+
""",
128+
),
129+
"_bash_runfile_helpers": attr.label(default = Label("@bazel_tools//tools/bash/runfiles")),
130+
"_server_bin": attr.label(
131+
default = Label("//bazel/http-server:server_bin"),
132+
),
133+
"_launcher_template": attr.label(
134+
allow_single_file = True,
135+
default = Label("//bazel/http-server:launcher_template.sh"),
136+
),
137+
},
138+
)
139+
140+
def http_server(name, testonly = False, port = 4200, tags = [], **kwargs):
141+
"""Creates a HTTP server that can depend on individual bazel targets. The server uses
142+
bazel runfile resolution in order to work with Bazel package paths. e.g. developers can
143+
request files through their manifest path: "my_workspace/src/dev-app/my-genfile"."""
144+
145+
http_server_rule(
146+
name = "%s_launcher" % name,
147+
visibility = ["//visibility:private"],
148+
tags = tags,
149+
testonly = testonly,
150+
**kwargs
151+
)
152+
153+
native.sh_binary(
154+
name = name,
155+
# The "ibazel_notify_changes" tag tells ibazel to not relaunch the executable on file
156+
# changes. Rather it will communicate with the server implementation through "stdin".
157+
tags = tags + ["ibazel_notify_changes"],
158+
srcs = ["%s_launcher.sh" % name],
159+
data = [":%s_launcher" % name],
160+
args = ["--port=%s" % port],
161+
testonly = testonly,
162+
)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/usr/bin/env bash
2+
3+
# --- begin runfiles.bash initialization v2 ---
4+
# Copy-pasted from the Bazel Bash runfiles library v2. We need to copy the runfile
5+
# helper code as we want to resolve Bazel targets through the runfiles (in order to
6+
# make the server work on windows where runfiles are not symlinked). The runfile
7+
# helpers expose a bash function called "rlocation" that can be used to resolve targets.
8+
set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash
9+
source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \
10+
source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \
11+
source "$0.runfiles/$f" 2>/dev/null || \
12+
source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
13+
source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
14+
{ echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e
15+
# --- end runfiles.bash initialization v2 ---
16+
17+
# If we do not run the server as part of a test, we always enforce runfile
18+
# resolution when invoking the server NodeJS binary. This is necessary as
19+
# runfile trees are disabled as part of this repository. The server NodeJS
20+
# binary would not find a relative runfile tree directory and error out.
21+
if [[ -z "${TEST_SRCDIR:-""}" ]]; then
22+
export RUNFILES_MANIFEST_ONLY="1"
23+
fi
24+
25+
# Resolve the path of the server binary. Note: usually we either need to
26+
# resolve the "nodejs_binary" executable with different file extensions on
27+
# windows, but since we already run this launcher as part of a "sh_binary", we
28+
# can safely execute another shell script from the current shell.
29+
serverBin=$(rlocation "dev-infra/bazel/http-server/server_bin.sh")
30+
31+
# Start the server with the given arguments. The arguments will be
32+
# substituted based on the rule attributes.
33+
${serverBin} TEMPLATED_args "$@"

bazel/http-server/main.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import * as yargs from 'yargs';
10+
11+
import {HttpServer} from './server';
12+
import {setupBazelWatcherSupport} from './ibazel';
13+
14+
const {rootPaths, historyApiFallback, enableDevUi, environmentVariables, port} = yargs(
15+
process.argv.slice(2),
16+
)
17+
.strict()
18+
.option('port', {type: 'number', default: 4200})
19+
.option('historyApiFallback', {type: 'boolean', default: false})
20+
.option('rootPaths', {type: 'array', default: ['']})
21+
.option('environmentVariables', {type: 'array', default: []})
22+
.option('enableDevUi', {type: 'boolean', default: false})
23+
.parseSync();
24+
25+
// In non-test executions, we will never allow for the browser-sync dev UI.
26+
const enableUi = process.env.TEST_TARGET === undefined && enableDevUi;
27+
const server = new HttpServer(port, rootPaths, enableUi, historyApiFallback, environmentVariables);
28+
29+
// Setup ibazel support.
30+
setupBazelWatcherSupport(server);
31+
32+
// Start the server. The server will always bind to the loopback and
33+
// the public interface of the current host.
34+
server.start();

0 commit comments

Comments
 (0)