Skip to content

Commit 96d37b6

Browse files
twheysalexeagle
authored andcommitted
feat(typescript): added support for using non-file targets in srcs of ts_project
1 parent 7e627f7 commit 96d37b6

File tree

8 files changed

+141
-66
lines changed

8 files changed

+141
-66
lines changed

packages/typescript/index.bzl

Lines changed: 14 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ def ts_project(
402402
write_tsconfig(
403403
name = "_gen_tsconfig_%s" % name,
404404
config = tsconfig,
405-
files = [s for s in srcs if _lib.is_ts_src(s, allow_js) or _lib.is_json_src(s, resolve_json_module)],
405+
files = srcs,
406406
extends = Label("%s//%s:%s" % (native.repository_name(), native.package_name(), name)).relative(extends) if extends else None,
407407
out = "tsconfig_%s.json" % name,
408408
)
@@ -467,28 +467,11 @@ def ts_project(
467467
tsc = ":" + tsc_worker
468468
typings_out_dir = declaration_dir if declaration_dir else out_dir
469469
tsbuildinfo_path = ts_build_info_file if ts_build_info_file else name + ".tsbuildinfo"
470-
js_outs = []
471-
map_outs = []
472-
typings_outs = []
473-
typing_maps_outs = []
474-
475-
if not emit_declaration_only:
476-
exts = {
477-
"*": ".js",
478-
".jsx": ".jsx",
479-
".tsx": ".jsx",
480-
} if preserve_jsx else {"*": ".js"}
481-
js_outs.extend(_lib.out_paths(srcs, out_dir, root_dir, allow_js, exts))
482-
if source_map and not emit_declaration_only:
483-
exts = {
484-
"*": ".js.map",
485-
".tsx": ".jsx.map",
486-
} if preserve_jsx else {"*": ".js.map"}
487-
map_outs.extend(_lib.out_paths(srcs, out_dir, root_dir, False, exts))
488-
if declaration or composite:
489-
typings_outs.extend(_lib.out_paths(srcs, typings_out_dir, root_dir, allow_js, {"*": ".d.ts"}))
490-
if declaration_map:
491-
typing_maps_outs.extend(_lib.out_paths(srcs, typings_out_dir, root_dir, allow_js, {"*": ".d.ts.map"}))
470+
471+
js_outs = _lib.calculate_js_outs(srcs, out_dir, root_dir, allow_js, preserve_jsx, emit_declaration_only)
472+
map_outs = _lib.calculate_map_outs(srcs, out_dir, root_dir, source_map, preserve_jsx, emit_declaration_only)
473+
typings_outs = _lib.calculate_typings_outs(srcs, typings_out_dir, root_dir, declaration, composite, allow_js)
474+
typing_maps_outs = _lib.calculate_typing_maps_outs(srcs, typings_out_dir, root_dir, declaration_map, allow_js)
492475

493476
tsc_js_outs = []
494477
tsc_map_outs = []
@@ -557,36 +540,29 @@ def ts_project(
557540
**common_kwargs
558541
)
559542

560-
if not len(tsc_js_outs) and not len(typings_outs):
561-
label = "//{}:{}".format(native.package_name(), name)
562-
if transpiler:
563-
no_outs_msg = "ts_project target %s with custom transpiler needs `declaration = True`." % label
564-
else:
565-
no_outs_msg = """ts_project target %s is configured to produce no outputs.
566-
567-
This might be because
568-
- you configured it with `noEmit`
569-
- the `srcs` are empty
570-
""" % label
571-
fail(no_outs_msg + """
572-
This is an error because Bazel does not run actions unless their outputs are needed for the requested targets to build.
573-
""")
574-
575543
_ts_project(
576544
name = tsc_target_name,
577545
srcs = srcs,
578546
args = args,
579547
deps = tsc_deps,
580548
tsconfig = tsconfig,
549+
allow_js = allow_js,
581550
extends = extends,
551+
incremental = incremental,
552+
preserve_jsx = preserve_jsx,
553+
composite = composite,
554+
declaration = declaration,
582555
declaration_dir = declaration_dir,
556+
source_map = source_map,
557+
declaration_map = declaration_map,
583558
out_dir = out_dir,
584559
root_dir = root_dir,
585560
js_outs = tsc_js_outs,
586561
map_outs = tsc_map_outs,
587562
typings_outs = typings_outs,
588563
typing_maps_outs = typing_maps_outs,
589564
buildinfo_out = tsbuildinfo_path if composite or incremental else None,
565+
emit_declaration_only = emit_declaration_only,
590566
tsc = tsc,
591567
link_workspace_root = link_workspace_root,
592568
supports_workers = supports_workers,

packages/typescript/internal/ts_project.bzl

Lines changed: 90 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ _DEFAULT_TSC = (
1313
"//typescript/bin:tsc"
1414
)
1515

16-
_ATTRS = {
16+
_ATTRS = dict(_validate_lib.attrs, **{
1717
"args": attr.string_list(),
1818
"data": attr.label_list(default = [], allow_files = True),
1919
"declaration_dir": attr.string(),
@@ -25,7 +25,6 @@ _ATTRS = {
2525
],
2626
aspects = [module_mappings_aspect],
2727
),
28-
"extends": attr.label(allow_files = [".json"]),
2928
"link_workspace_root": attr.bool(),
3029
"out_dir": attr.string(),
3130
"root_dir": attr.string(),
@@ -36,9 +35,9 @@ _ATTRS = {
3635
"srcs": attr.label_list(allow_files = True, mandatory = True),
3736
"supports_workers": attr.bool(default = False),
3837
"tsc": attr.label(default = Label(_DEFAULT_TSC), executable = True, cfg = "exec"),
39-
"transpile": attr.bool(doc = "whether tsc should be used to produce .js outputs"),
38+
"transpile": attr.bool(doc = "whether tsc should be used to produce .js outputs", default = True),
4039
"tsconfig": attr.label(mandatory = True, allow_single_file = [".json"]),
41-
}
40+
})
4241

4342
# tsc knows how to produce the following kinds of output files.
4443
# NB: the macro `ts_project_macro` will set these outputs based on user
@@ -57,6 +56,19 @@ def _join(*elements):
5756
return "/".join(segments)
5857
return "."
5958

59+
def _relative_to_package(path, ctx):
60+
for prefix in (ctx.bin_dir.path, ctx.label.package):
61+
prefix += "/"
62+
if path.startswith(prefix):
63+
path = path[len(prefix):]
64+
return path
65+
66+
def _declare_outputs(ctx, paths):
67+
return [
68+
ctx.actions.declare_file(path)
69+
for path in paths
70+
]
71+
6072
def _calculate_root_dir(ctx):
6173
some_generated_path = None
6274
some_source_path = None
@@ -91,6 +103,19 @@ def _calculate_root_dir(ctx):
91103
)
92104

93105
def _ts_project_impl(ctx):
106+
srcs = [_relative_to_package(src.path, ctx) for src in ctx.files.srcs]
107+
108+
# Recalculate outputs inside the rule implementation.
109+
# The outs are first calculated in the macro in order to try to predetermine outputs so they can be declared as
110+
# outputs on the rule. This provides the benefit of being able to reference an output file with a label.
111+
# However, it is not possible to evaluate files in outputs of other rules such as filegroup, therefore the outs are
112+
# recalculated here.
113+
typings_out_dir = ctx.attr.declaration_dir or ctx.attr.out_dir
114+
js_outs = _declare_outputs(ctx, [] if not ctx.attr.transpile else _calculate_js_outs(srcs, ctx.attr.out_dir, ctx.attr.root_dir, ctx.attr.allow_js, ctx.attr.preserve_jsx, ctx.attr.emit_declaration_only))
115+
map_outs = _declare_outputs(ctx, [] if not ctx.attr.transpile else _calculate_map_outs(srcs, ctx.attr.out_dir, ctx.attr.root_dir, ctx.attr.source_map, ctx.attr.preserve_jsx, ctx.attr.emit_declaration_only))
116+
typings_outs = _declare_outputs(ctx, _calculate_typings_outs(srcs, typings_out_dir, ctx.attr.root_dir, ctx.attr.declaration, ctx.attr.composite, ctx.attr.allow_js))
117+
typing_maps_outs = _declare_outputs(ctx, _calculate_typing_maps_outs(srcs, typings_out_dir, ctx.attr.root_dir, ctx.attr.declaration_map, ctx.attr.allow_js))
118+
94119
arguments = ctx.actions.args()
95120
execution_requirements = {}
96121
progress_prefix = "Compiling TypeScript project"
@@ -114,7 +139,7 @@ def _ts_project_impl(ctx):
114139
"--rootDir",
115140
_calculate_root_dir(ctx),
116141
])
117-
if len(ctx.outputs.typings_outs) > 0:
142+
if len(typings_outs) > 0:
118143
declaration_dir = ctx.attr.declaration_dir if ctx.attr.declaration_dir else ctx.attr.out_dir
119144
arguments.add_all([
120145
"--declarationDir",
@@ -162,7 +187,7 @@ def _ts_project_impl(ctx):
162187
# However tsc will copy .json srcs to the output tree so we want to declare these outputs to include along with .js Default outs
163188
# NB: We don't have emit_declaration_only setting here, so use presence of any JS outputs as an equivalent.
164189
# tsc will only produce .json if it also produces .js
165-
if len(ctx.outputs.js_outs):
190+
if len(js_outs):
166191
pkg_len = len(ctx.label.package) + 1 if len(ctx.label.package) else 0
167192
json_outs = [
168193
ctx.actions.declare_file(_join(ctx.attr.out_dir, src.short_path[pkg_len:]))
@@ -172,15 +197,30 @@ def _ts_project_impl(ctx):
172197
else:
173198
json_outs = []
174199

175-
outputs = json_outs + ctx.outputs.js_outs + ctx.outputs.map_outs + ctx.outputs.typings_outs + ctx.outputs.typing_maps_outs
200+
outputs = json_outs + js_outs + map_outs + typings_outs + typing_maps_outs
176201
if ctx.outputs.buildinfo_out:
177202
arguments.add_all([
178203
"--tsBuildInfoFile",
179204
ctx.outputs.buildinfo_out.path,
180205
])
181206
outputs.append(ctx.outputs.buildinfo_out)
182-
runtime_outputs = json_outs + ctx.outputs.js_outs + ctx.outputs.map_outs
183-
typings_outputs = ctx.outputs.typings_outs + ctx.outputs.typing_maps_outs + [s for s in ctx.files.srcs if s.path.endswith(".d.ts")]
207+
runtime_outputs = json_outs + js_outs + map_outs
208+
typings_outputs = typings_outs + typing_maps_outs + [s for s in ctx.files.srcs if s.path.endswith(".d.ts")]
209+
210+
if not js_outs and not typings_outputs and not ctx.attr.deps:
211+
label = "//{}:{}".format(ctx.label.package, ctx.label.name)
212+
if ctx.attr.transpile:
213+
no_outs_msg = """ts_project target %s is configured to produce no outputs.
214+
215+
This might be because
216+
- you configured it with `noEmit`
217+
- the `srcs` are empty
218+
""" % label
219+
else:
220+
no_outs_msg = "ts_project target %s with custom transpiler needs `declaration = True`." % label
221+
fail(no_outs_msg + """
222+
This is an error because Bazel does not run actions unless their outputs are needed for the requested targets to build.
223+
""")
184224

185225
if ctx.attr.transpile:
186226
default_outputs_depset = depset(runtime_outputs) if len(runtime_outputs) else depset(typings_outputs)
@@ -274,21 +314,59 @@ def _replace_ext(f, ext_map):
274314
return new_ext
275315
return None
276316

277-
def _out_paths(srcs, outdir, rootdir, allow_js, ext_map):
278-
rootdir_replace_pattern = rootdir + "/" if rootdir else ""
317+
def _out_paths(srcs, out_dir, root_dir, allow_js, ext_map):
318+
rootdir_replace_pattern = root_dir + "/" if root_dir else ""
279319
outs = []
280320
for f in srcs:
281321
if _is_ts_src(f, allow_js):
282-
out = _join(outdir, f[:f.rindex(".")].replace(rootdir_replace_pattern, "") + _replace_ext(f, ext_map))
322+
out = _join(out_dir, f[:f.rindex(".")].replace(rootdir_replace_pattern, "") + _replace_ext(f, ext_map))
283323

284324
# Don't declare outputs that collide with inputs
285325
# for example, a.js -> a.js
286326
if out != f:
287327
outs.append(out)
288328
return outs
289329

330+
def _calculate_js_outs(srcs, out_dir, root_dir, allow_js, preserve_jsx, emit_declaration_only):
331+
if emit_declaration_only:
332+
return []
333+
334+
exts = {
335+
"*": ".js",
336+
".jsx": ".jsx",
337+
".tsx": ".jsx",
338+
} if preserve_jsx else {"*": ".js"}
339+
return _out_paths(srcs, out_dir, root_dir, allow_js, exts)
340+
341+
def _calculate_map_outs(srcs, out_dir, root_dir, source_map, preserve_jsx, emit_declaration_only):
342+
if not source_map or emit_declaration_only:
343+
return []
344+
345+
exts = {
346+
"*": ".js.map",
347+
".tsx": ".jsx.map",
348+
} if preserve_jsx else {"*": ".js.map"}
349+
return _out_paths(srcs, out_dir, root_dir, False, exts)
350+
351+
def _calculate_typings_outs(srcs, typings_out_dir, root_dir, declaration, composite, allow_js, include_srcs = True):
352+
if not (declaration or composite):
353+
return []
354+
355+
return _out_paths(srcs, typings_out_dir, root_dir, allow_js, {"*": ".d.ts"})
356+
357+
def _calculate_typing_maps_outs(srcs, typings_out_dir, root_dir, declaration_map, allow_js):
358+
if not declaration_map:
359+
return []
360+
361+
exts = {"*": ".d.ts.map"}
362+
return _out_paths(srcs, typings_out_dir, root_dir, allow_js, exts)
363+
290364
lib = struct(
291365
is_ts_src = _is_ts_src,
292366
is_json_src = _is_json_src,
293367
out_paths = _out_paths,
368+
calculate_js_outs = _calculate_js_outs,
369+
calculate_map_outs = _calculate_map_outs,
370+
calculate_typings_outs = _calculate_typings_outs,
371+
calculate_typing_maps_outs = _calculate_typing_maps_outs,
294372
)

packages/typescript/internal/validate_options.bzl

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,26 +52,31 @@ def _validate_options_impl(ctx):
5252
ValidOptionsInfo(marker = marker),
5353
]
5454

55+
# These attrs are shared between the validate and the ts_project rules
56+
SHARED_ATTRS = {
57+
"allow_js": attr.bool(),
58+
"composite": attr.bool(),
59+
"declaration": attr.bool(),
60+
"declaration_map": attr.bool(),
61+
"emit_declaration_only": attr.bool(),
62+
"extends": attr.label(allow_files = [".json"]),
63+
"incremental": attr.bool(),
64+
"preserve_jsx": attr.bool(),
65+
"resolve_json_module": attr.bool(),
66+
"source_map": attr.bool(),
67+
}
68+
5569
validate_options = rule(
5670
implementation = _validate_options_impl,
57-
attrs = {
58-
"allow_js": attr.bool(),
59-
"composite": attr.bool(),
60-
"declaration": attr.bool(),
61-
"declaration_map": attr.bool(),
62-
"emit_declaration_only": attr.bool(),
63-
"extends": attr.label(allow_files = [".json"]),
64-
"incremental": attr.bool(),
65-
"preserve_jsx": attr.bool(),
66-
"resolve_json_module": attr.bool(),
67-
"source_map": attr.bool(),
71+
attrs = dict(SHARED_ATTRS, **{
6872
"target": attr.string(),
6973
"ts_build_info_file": attr.string(),
7074
"tsconfig": attr.label(mandatory = True, allow_single_file = [".json"]),
7175
"validator": attr.label(default = Label("//packages/typescript/bin:ts_project_options_validator"), executable = True, cfg = "exec"),
72-
},
76+
}),
7377
)
7478

7579
lib = struct(
7680
tsconfig_inputs = _tsconfig_inputs,
81+
attrs = SHARED_ATTRS,
7782
)

packages/typescript/test/ts_project/c/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
],
66
// NB: we don't need the compilerOptions.outDir Windows workaround here
77
// because there is no project that references this one.
8-
}
8+
}

packages/typescript/test/ts_project/empty_intermediate/BUILD.bazel

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
# Use the ts_project rule directly, not the wrapper macro. We don't want checking for empty outs.
2-
load("//packages/typescript/internal:ts_project.bzl", "ts_project")
1+
load("//packages/typescript:index.bzl", "ts_project")
32

43
ts_project(
54
name = "tsconfig-a",
@@ -18,7 +17,6 @@ ts_project(
1817
ts_project(
1918
name = "tsconfig-c",
2019
srcs = ["c.ts"],
21-
js_outs = ["c.js"],
2220
tsconfig = ":tsconfig-c.json",
2321
deps = ["tsconfig-b"],
2422
)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
load("//packages/typescript:index.bzl", "ts_project")
2+
3+
filegroup(
4+
name = "srcs",
5+
srcs = glob(["*.ts"]),
6+
)
7+
8+
ts_project(
9+
srcs = [":srcs"],
10+
composite = True,
11+
extends = "//packages/typescript/test/ts_project:tsconfig-base.json",
12+
tsconfig = "tsconfig.json",
13+
visibility = ["//packages/typescript/test:__subpackages__"],
14+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log("Hello Filegroup!");
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": "../tsconfig-base.json",
3+
}

0 commit comments

Comments
 (0)