Skip to content

Commit

Permalink
feat(typescript): create a better ts_project worker (#2416)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrmeku committed Jan 30, 2021
1 parent 0466084 commit 99bfe5f
Show file tree
Hide file tree
Showing 9 changed files with 284 additions and 146 deletions.
2 changes: 1 addition & 1 deletion packages/typescript/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ pkg_npm(
":npm_version_check",
"//packages/typescript/internal:BUILD",
"//packages/typescript/internal:ts_project_options_validator.js",
"//packages/typescript/internal/worker",
"//packages/typescript/internal/worker:filegroup",
] + select({
# FIXME: fix stardoc on Windows; //packages/typescript:index.md generation fails with:
# ERROR: D:/b/62unjjin/external/npm_bazel_typescript/BUILD.bazel:36:1: Couldn't build file
Expand Down
45 changes: 14 additions & 31 deletions packages/typescript/internal/ts_project.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,7 @@ _DEFAULT_TSC = (
"//typescript/bin:tsc"
)

_DEFAULT_TSC_BIN = (
# BEGIN-INTERNAL
"@npm" +
# END-INTERNAL
"//:node_modules/typescript/bin/tsc"
)

_DEFAULT_TYPESCRIPT_MODULE = (
_DEFAULT_TYPESCRIPT_PACKAGE = (
# BEGIN-INTERNAL
"@npm" +
# END-INTERNAL
Expand Down Expand Up @@ -49,7 +42,7 @@ _ATTRS = {
# that compiler might allow more sources than tsc does.
"srcs": attr.label_list(allow_files = True, mandatory = True),
"supports_workers": attr.bool(default = False),
"tsc": attr.label(default = Label(_DEFAULT_TSC), executable = True, cfg = "target"),
"tsc": attr.label(default = Label(_DEFAULT_TSC), executable = True, cfg = "host"),
"tsconfig": attr.label(mandatory = True, allow_single_file = [".json"]),
}

Expand Down Expand Up @@ -209,6 +202,7 @@ def _ts_project_impl(ctx):
inputs = inputs,
arguments = [arguments],
outputs = outputs,
mnemonic = "TsProject",
executable = "tsc",
execution_requirements = execution_requirements,
progress_message = "%s %s [tsc -p %s]" % (
Expand Down Expand Up @@ -337,8 +331,8 @@ def ts_project_macro(
emit_declaration_only = False,
ts_build_info_file = None,
tsc = None,
worker_tsc_bin = _DEFAULT_TSC_BIN,
worker_typescript_module = _DEFAULT_TYPESCRIPT_MODULE,
typescript_package = _DEFAULT_TYPESCRIPT_PACKAGE,
typescript_require_path = "typescript",
validate = True,
supports_workers = False,
declaration_dir = None,
Expand Down Expand Up @@ -506,14 +500,13 @@ def ts_project_macro(
For example, `tsc = "@my_deps//typescript/bin:tsc"`
Or you can pass a custom compiler binary instead.
worker_tsc_bin: Label of the TypeScript compiler binary to run when running in worker mode.
typescript_package: Label of the package containing all data deps of tsc.
For example, `tsc = "@my_deps//node_modules/typescript/bin/tsc"`
Or you can pass a custom compiler binary instead.
For example, `typescript_package = "@my_deps//typescript"`
worker_typescript_module: Label of the package containing all data deps of worker_tsc_bin.
typescript_require_path: Module name which resolves to typescript_package when required
For example, `tsc = "@my_deps//typescript"`
For example, `typescript_require_path = "typescript"`
validate: boolean; whether to check that the tsconfig settings match the attributes.
Expand Down Expand Up @@ -642,25 +635,18 @@ def ts_project_macro(
# but that's our own code, so we don't.
"@npm//protobufjs",
# END-INTERNAL
Label("//packages/typescript/internal/worker:worker"),
Label(worker_tsc_bin),
Label(worker_typescript_module),
Label(typescript_package),
Label("//packages/typescript/internal/worker:filegroup"),
tsconfig,
],
entry_point = Label("//packages/typescript/internal/worker:worker_adapter"),
templated_args = [
"$(execpath {})".format(Label(worker_tsc_bin)),
"--project",
"$(execpath {})".format(tsconfig),
# FIXME: should take out_dir into account
"--outDir",
"$(RULEDIR)",
# FIXME: what about other settings like declaration_dir, root_dir, etc
"--typescript_require_path",
typescript_require_path,
],
)

tsc = ":" + tsc_worker

typings_out_dir = declaration_dir if declaration_dir else out_dir
tsbuildinfo_path = ts_build_info_file if ts_build_info_file else name + ".tsbuildinfo"
js_outs = []
Expand Down Expand Up @@ -701,9 +687,6 @@ Check the srcs attribute to see that some .ts files are present (or .js files wi
buildinfo_out = tsbuildinfo_path if composite or incremental else None,
tsc = tsc,
link_workspace_root = link_workspace_root,
supports_workers = select({
"@bazel_tools//src/conditions:host_windows": False,
"//conditions:default": supports_workers,
}),
supports_workers = supports_workers,
**kwargs
)
27 changes: 15 additions & 12 deletions packages/typescript/internal/worker/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
# BEGIN-INTERNAL

load("//internal/common:copy_to_bin.bzl", "copy_to_bin")
load("//packages/typescript:checked_in_ts_project.bzl", "checked_in_ts_project")
load("//third_party/github.com/bazelbuild/bazel-skylib:rules/copy_file.bzl", "copy_file")

# To update index.js run:
# bazel run //packages/typescript/internal/worker:worker_adapter_check_compiled.update

checked_in_ts_project(
name = "worker_adapter",
src = "worker_adapter.ts",
checked_in_js = "index.js",
visibility = ["//visibility:public"],
deps = ["@npm//@types/node"],
)

# Copy the proto file to a matching third_party/... nested directory
# so the runtime require() statements still work
_worker_proto_dir = "third_party/github.com/bazelbuild/bazel/src/main/protobuf"
Expand All @@ -22,14 +32,6 @@ copy_file(
visibility = ["//visibility:public"],
)

copy_to_bin(
name = "worker_adapter",
srcs = [
"worker_adapter.js",
],
visibility = ["//visibility:public"],
)

filegroup(
name = "package_contents",
srcs = [
Expand All @@ -41,12 +43,13 @@ filegroup(
# END-INTERNAL

exports_files([
"worker_adapter.js",
"index.js",
])

filegroup(
name = "worker",
name = "filegroup",
srcs = [
"index.js",
"third_party/github.com/bazelbuild/bazel/src/main/protobuf/worker_protocol.proto",
"worker.js",
"worker_adapter.js",
Expand Down
102 changes: 102 additions & 0 deletions packages/typescript/internal/worker/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/* THIS FILE GENERATED FROM .ts; see BUILD.bazel */ /* clang-format off */"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs = require("fs");
const ts = require("typescript");
const MNEMONIC = 'TsProject';
const worker = require('./worker');
let createWatchCompilerHost;
const formatHost = {
getCanonicalFileName: (path) => path,
getCurrentDirectory: ts.sys.getCurrentDirectory,
getNewLine: () => ts.sys.newLine,
};
const reportDiagnostic = (diagnostic) => {
worker.log(ts.formatDiagnostic(diagnostic, formatHost));
};
const reportWatchStatusChanged = (diagnostic) => {
worker.debug(ts.formatDiagnostic(diagnostic, formatHost));
};
function createWatchProgram(options, tsconfigPath, setTimeout) {
const host = createWatchCompilerHost(tsconfigPath, options, Object.assign(Object.assign({}, ts.sys), { setTimeout }), ts.createEmitAndSemanticDiagnosticsBuilderProgram, reportDiagnostic, reportWatchStatusChanged);
return ts.createWatchProgram(host);
}
let workerRequestTimestamp;
let cachedWatchedProgram;
let consolidateChangesCallback;
let cachedWatchProgramArgs;
function getWatchProgram(args) {
const newWatchArgs = args.join(' ');
if (cachedWatchedProgram && cachedWatchProgramArgs && cachedWatchProgramArgs !== newWatchArgs) {
cachedWatchedProgram.close();
cachedWatchedProgram = undefined;
cachedWatchProgramArgs = undefined;
}
if (!cachedWatchedProgram) {
const parsedArgs = ts.parseCommandLine(args);
const tsconfigPath = args[args.indexOf('--project') + 1];
cachedWatchProgramArgs = newWatchArgs;
cachedWatchedProgram = createWatchProgram(parsedArgs.options, tsconfigPath, (callback) => {
consolidateChangesCallback = callback;
});
}
return cachedWatchedProgram;
}
function emitOnce(args) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const watchProgram = getWatchProgram(args);
if (consolidateChangesCallback) {
consolidateChangesCallback();
}
workerRequestTimestamp = Date.now();
const result = yield ((_a = watchProgram) === null || _a === void 0 ? void 0 : _a.getProgram().emit(undefined, undefined, {
isCancellationRequested: function (timestamp) {
return timestamp !== workerRequestTimestamp;
}.bind(null, workerRequestTimestamp),
throwIfCancellationRequested: function (timestamp) {
if (timestamp !== workerRequestTimestamp) {
throw new ts.OperationCanceledException();
}
}.bind(null, workerRequestTimestamp),
}));
return Boolean(result && result.diagnostics.length === 0);
});
}
function main() {
const typescriptRequirePath = process.argv[process.argv.indexOf('--typescript_require_path') + 1];
try {
const customTypescriptModule = require(typescriptRequirePath);
createWatchCompilerHost = customTypescriptModule.createWatchCompilerHost;
}
catch (e) {
worker.log(`typescript_require_path '${typescriptRequirePath}' could not be resolved`);
throw e;
}
if (process.argv.includes('--persistent_worker')) {
worker.log(`Running ${MNEMONIC} as a Bazel worker`);
worker.runWorkerLoop(emitOnce);
}
else {
worker.log(`Running ${MNEMONIC} as a standalone process`);
worker.log(`Started a new process to perform this action. Your build might be misconfigured, try
--strategy=${MNEMONIC}=worker`);
let argsFilePath = process.argv.pop();
if (argsFilePath.startsWith('@')) {
argsFilePath = argsFilePath.slice(1);
}
const args = fs.readFileSync(argsFilePath).toString().split('\n');
emitOnce(args).finally(() => { var _a; return (_a = cachedWatchedProgram) === null || _a === void 0 ? void 0 : _a.close(); });
}
}
if (require.main === module) {
main();
}
99 changes: 0 additions & 99 deletions packages/typescript/internal/worker/worker_adapter.js

This file was deleted.

Loading

0 comments on commit 99bfe5f

Please sign in to comment.