Skip to content

Commit

Permalink
fix: do not include tsconfig inputs in ts_project outputs
Browse files Browse the repository at this point in the history
  • Loading branch information
jbedard committed Jan 9, 2025
1 parent 8b02b7a commit ad6f86c
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 43 deletions.
61 changes: 37 additions & 24 deletions ts/private/ts_project.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,34 @@ def _gather_transitive_typecheck_from_output_group_infos(typecheck_outs, targets
]
return depset(typecheck_outs, transitive = files_depsets)

def _gather_tsconfig_deps(ctx):
tsconfig = copy_file_to_bin_action(ctx, ctx.file.tsconfig)

# Gather TsConfig info from both the direct (tsconfig) and indirect (extends) attribute
tsconfig_inputs = [tsconfig] + copy_files_to_bin_actions(ctx, ctx.files.extends)

deps = []

# Direct TsConfigInfo deps
if TsConfigInfo in ctx.attr.tsconfig:
deps.append(ctx.attr.tsconfig[TsConfigInfo].deps)
if ctx.attr.extends and TsConfigInfo in ctx.attr.extends:
deps.append(ctx.attr.extends[TsConfigInfo].deps)

# When TypeScript builds a composite project, our compilation will want to read the tsconfig.json of
# composite projects we reference.
# Otherwise we'd get an error like
# examples/project_references/lib_b/tsconfig.json(5,9): error TS6053:
# File 'execroot/aspect_rules_ts/bazel-out/k8-fastbuild/bin/examples/project_references/lib_a/tsconfig.json' not found.
if ctx.attr.composite:
deps.extend([
dep[TsConfigInfo].deps
for dep in ctx.attr.deps
if TsConfigInfo in dep
])

return tsconfig, tsconfig_inputs, depset(tsconfig_inputs, transitive = deps)

def _ts_project_impl(ctx):
"""Creates the action which spawns `tsc`.
Expand All @@ -52,15 +80,7 @@ def _ts_project_impl(ctx):
"""
options = ctx.attr._options[OptionsInfo]
srcs_inputs = copy_files_to_bin_actions(ctx, ctx.files.srcs)
tsconfig = copy_file_to_bin_action(ctx, ctx.file.tsconfig)

# Gather TsConfig info from both the direct (tsconfig) and indirect (extends) attribute
tsconfig_inputs = copy_files_to_bin_actions(ctx, _validate_lib.tsconfig_inputs(ctx).to_list())
tsconfig_transitive_deps = [
dep[TsConfigInfo].deps
for dep in ctx.attr.deps
if TsConfigInfo in dep
]
tsconfig, tsconfig_inputs, tsconfig_transitive_deps = _gather_tsconfig_deps(ctx)

srcs = [_lib.relative_to_package(src.path, ctx) for src in srcs_inputs]

Expand All @@ -87,7 +107,7 @@ def _ts_project_impl(ctx):

validation_outs = []
if ctx.attr.validate:
validation_outs.extend(_validate_lib.validation_action(ctx, tsconfig_inputs))
validation_outs.extend(_validate_lib.validation_action(ctx, tsconfig, tsconfig_transitive_deps))
_lib.validate_tsconfig_dirs(ctx.attr.root_dir, ctx.attr.out_dir, typings_out_dir)

typecheck_outs = []
Expand Down Expand Up @@ -158,14 +178,7 @@ See https://github.com/aspect-build/rules_ts/issues/361 for more details.

inputs = srcs_inputs + tsconfig_inputs

transitive_inputs = []
if ctx.attr.composite:
# When TypeScript builds a composite project, our compilation will want to read the tsconfig.json of
# composite projects we reference.
# Otherwise we'd get an error like
# examples/project_references/lib_b/tsconfig.json(5,9): error TS6053:
# File 'execroot/aspect_rules_ts/bazel-out/k8-fastbuild/bin/examples/project_references/lib_a/tsconfig.json' not found.
transitive_inputs.extend(tsconfig_transitive_deps)
transitive_inputs = [tsconfig_transitive_deps]

assets_outs = []
for a in ctx.files.assets:
Expand Down Expand Up @@ -213,11 +226,11 @@ See https://github.com/aspect-build/rules_ts/issues/361 for more details.
# Special case where there are no source outputs so we add output_types to the default outputs.
default_outputs = output_sources if len(output_sources) else output_types

srcs_tsconfig_deps = ctx.attr.srcs + [ctx.attr.tsconfig] + ctx.attr.deps
srcs_deps = ctx.attr.srcs + ctx.attr.deps

inputs = copy_files_to_bin_actions(ctx, inputs)

transitive_inputs.append(_gather_types_from_js_infos(srcs_tsconfig_deps))
transitive_inputs.append(_gather_types_from_js_infos(srcs_deps + [ctx.attr.tsconfig]))
transitive_inputs_depset = depset(
inputs,
transitive = transitive_inputs,
Expand Down Expand Up @@ -316,9 +329,9 @@ See https://github.com/aspect-build/rules_ts/issues/361 for more details.
},
)

transitive_sources = js_lib_helpers.gather_transitive_sources(output_sources, srcs_tsconfig_deps)
transitive_sources = js_lib_helpers.gather_transitive_sources(output_sources, srcs_deps)

transitive_types = js_lib_helpers.gather_transitive_types(output_types, srcs_tsconfig_deps)
transitive_types = js_lib_helpers.gather_transitive_types(output_types, srcs_deps)

transitive_typecheck = _gather_transitive_typecheck_from_output_group_infos(typecheck_outs, ctx.attr.deps)

Expand All @@ -340,7 +353,7 @@ See https://github.com/aspect-build/rules_ts/issues/361 for more details.
ctx = ctx,
sources = output_sources_depset,
data = ctx.attr.data,
deps = srcs_tsconfig_deps,
deps = srcs_deps,
data_files = ctx.files.data,
copy_data_files_to_bin = True, # NOTE: configurable (default true) in js_library()
no_copy_to_bin = [], # NOTE: configurable (default []) in js_library()
Expand All @@ -365,7 +378,7 @@ See https://github.com/aspect-build/rules_ts/issues/361 for more details.
npm_sources = npm_sources,
npm_package_store_infos = npm_package_store_infos,
),
TsConfigInfo(deps = depset(tsconfig_inputs, transitive = tsconfig_transitive_deps)),
TsConfigInfo(deps = tsconfig_transitive_deps),
OutputGroupInfo(
types = output_types_depset,
typecheck = depset(typecheck_outs),
Expand Down
25 changes: 6 additions & 19 deletions ts/private/ts_validate_options.bzl
Original file line number Diff line number Diff line change
@@ -1,27 +1,15 @@
"Helper rule to check that ts_project attributes match tsconfig.json properties"

load("@aspect_bazel_lib//lib:copy_to_bin.bzl", "copy_file_to_bin_action", "copy_files_to_bin_actions")
load("@aspect_bazel_lib//lib:paths.bzl", "to_output_relative_path")
load(":ts_config.bzl", "TsConfigInfo")

def _tsconfig_inputs(ctx):
"""Returns all transitively referenced tsconfig files from "tsconfig" and "extends" attributes."""
inputs = []
if TsConfigInfo in ctx.attr.tsconfig:
inputs.append(ctx.attr.tsconfig[TsConfigInfo].deps)
else:
inputs.append(depset([ctx.file.tsconfig]))
if hasattr(ctx.attr, "extends") and ctx.attr.extends:
if TsConfigInfo in ctx.attr.extends:
inputs.append(ctx.attr.extends[TsConfigInfo].deps)
else:
inputs.append(ctx.attr.extends.files)
return depset(transitive = inputs)
def _validate_action(ctx, tsconfig, tsconfig_deps):
"""Create an action to validate the ts_project attributes against the tsconfig.json properties.
Assumes all tsconfig file deps are already copied to the bin directory.
"""

def _validate_action(ctx, tsconfig_inputs):
# Bazel validation actions must still produce an output file.
marker = ctx.actions.declare_file("%s_params.validation" % ctx.label.name)
tsconfig = copy_file_to_bin_action(ctx, ctx.file.tsconfig)

arguments = ctx.actions.args()
config = struct(
Expand Down Expand Up @@ -49,7 +37,7 @@ def _validate_action(ctx, tsconfig_inputs):

ctx.actions.run(
executable = ctx.executable.validator,
inputs = copy_files_to_bin_actions(ctx, tsconfig_inputs),
inputs = tsconfig_deps,
outputs = [marker],
arguments = [arguments],
mnemonic = "TsValidateOptions",
Expand All @@ -61,6 +49,5 @@ def _validate_action(ctx, tsconfig_inputs):
return [marker]

lib = struct(
tsconfig_inputs = _tsconfig_inputs,
validation_action = _validate_action,
)
3 changes: 3 additions & 0 deletions ts/test/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ load("//ts:defs.bzl", "ts_project")
load(":flags_test.bzl", "ts_project_flags_test_suite")
load(":mock_transpiler.bzl", "mock")
load(":transpiler_tests.bzl", "transpiler_test_suite")
load(":ts_config_test.bzl", "ts_config_test_suite")
load(":ts_project_test.bzl", "ts_project_test_suite")
load(":ts_proto_library_test.bzl", "ts_proto_library_test_suite")

transpiler_test_suite()

ts_project_test_suite(name = "ts_project_test")

ts_config_test_suite(name = "ts_config_test")

ts_project_flags_test_suite(name = "ts_project_flags_test")

ts_proto_library_test_suite(name = "ts_proto_library_test")
Expand Down
153 changes: 153 additions & 0 deletions ts/test/ts_config_test.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
"UnitTests for ts_project and ts_config/tsconfig/extends"

load("@aspect_rules_js//js:providers.bzl", "JsInfo")
load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")
load("@bazel_skylib//rules:write_file.bzl", "write_file")
load("//ts:defs.bzl", "ts_config", "ts_project")

def ts_config_test_suite(name):
"""Test suite including all tests and data
Args:
name: Target name of the test_suite target.
"""

# A simple source file to compile
write_file(
name = "src_ts",
out = "src_ts.ts",
content = ["console.log(1)"],
tags = ["manual"],
)

# A simple tsconfig file
write_file(
name = "src_tsconfig",
out = "src_tsconfig.json",
content = ["""{"compilerOptions": {"declaration": true, "outDir": "outdir"}}"""],
tags = ["manual"],
)
write_file(
name = "src_tsconfig2",
out = "src_tsconfig2.json",
content = ["""{"compilerOptions": {"declaration": true, "outDir": "outdir2"}}"""],
tags = ["manual"],
)

# An extending tsconfig file
write_file(
name = "src_tsconfig_extending",
out = "src_tsconfig_extending.json",
content = ["""{"extends": "./src_tsconfig.json", "compilerOptions": {"outDir": "extending-outdir"}}"""],
tags = ["manual"],
)

ts_config(
name = "tsconfig",
src = "src_tsconfig.json",
)
ts_config(
name = "tsconfig_extending",
src = "src_tsconfig_extending.json",
deps = [":tsconfig"],
)

# Referencing a ts_config target
ts_project(
name = "use_tsconfig_target",
srcs = [":src_ts"],
declaration = True,
out_dir = "outdir",
tsconfig = ":tsconfig",
)
_ts_project_outputs_only_srcs_types_test(
name = "outputs_tsconfig_target_test",
target_under_test = "use_tsconfig_target",
)

# a dict() extending a tsconfig target
ts_project(
name = "use_tsconfig_dict",
srcs = [":src_ts"],
tsconfig = {"compilerOptions": {"declaration": True, "outDir": "dict-outdir"}},
extends = ":tsconfig",
)
_ts_project_outputs_only_srcs_types_test(
name = "outputs_tsconfig_dict_test",
target_under_test = "use_tsconfig_dict",
)

# Referencing a config file
ts_project(
name = "use_tsconfig_file",
srcs = [":src_ts"],
declaration = True,
out_dir = "outdir2",
tsconfig = "src_tsconfig2.json",
)
_ts_project_outputs_only_srcs_types_test(
name = "outputs_tsconfig_file_test",
target_under_test = "use_tsconfig_file",
)

# a ts_config target with transitive deps
ts_project(
name = "use_extending_tsconfig_target",
srcs = [":src_ts"],
declaration = True,
out_dir = "extending-outdir",
tsconfig = ":tsconfig_extending",
)
_ts_project_outputs_only_srcs_types_test(
name = "outputs_use_extending_tsconfig_target_test",
target_under_test = "use_extending_tsconfig_target",
)

# a tsconfig dict() extending a ts_config()
ts_project(
name = "use_dict_extending_tsconfig_target",
srcs = [":src_ts"],
tsconfig = {"compilerOptions": {"declaration": True, "outDir": "dict-extending-outdir"}},
extends = ":tsconfig_extending",
)
_ts_project_outputs_only_srcs_types_test(
name = "outputs_use_dict_extending_tsconfig_target_test",
target_under_test = "use_dict_extending_tsconfig_target",
)

native.test_suite(
name = name,
tests = [
":outputs_tsconfig_file_test",
":outputs_tsconfig_dict_test",
":outputs_tsconfig_target_test",
":outputs_use_extending_tsconfig_target_test",
":outputs_use_dict_extending_tsconfig_target_test",
],
)

def _ts_project_outputs_only_srcs_types_test_impl(ctx):
env = analysistest.begin(ctx)
target_under_test = analysistest.target_under_test(env)

jsinfo = target_under_test[JsInfo]

output_types = sorted([f.path for f in jsinfo.types.to_list()])
asserts.equals(env, 1, len(output_types))
asserts.true(env, output_types[0].find("/src_ts.d.ts") != -1)

output_transitive_types = sorted([f.path for f in jsinfo.transitive_types.to_list()])
asserts.equals(env, 1, len(output_transitive_types))
asserts.true(env, output_transitive_types[0].find("/src_ts.d.ts") != -1)

output_sources = sorted([f.path for f in jsinfo.sources.to_list()])
asserts.equals(env, 1, len(output_sources))
asserts.true(env, output_sources[0].find("/src_ts.js") != -1)

output_transitive_sources = sorted([f.path for f in jsinfo.transitive_sources.to_list()])
asserts.equals(env, 1, len(output_transitive_sources))
asserts.true(env, output_transitive_sources[0].find("/src_ts.js") != -1)

return analysistest.end(env)

_ts_project_outputs_only_srcs_types_test = analysistest.make(_ts_project_outputs_only_srcs_types_test_impl)
1 change: 1 addition & 0 deletions ts/test/ts_project_test.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def _dir_test_impl(ctx):

# assert the inputs to the tsc action are what we expect
action_inputs = target_under_test[OutputGroupInfo]._action_inputs.to_list()
action_inputs = sorted(action_inputs)
asserts.equals(env, 2, len(action_inputs))
asserts.true(env, action_inputs[0].path.find("/dir.ts") != -1)
asserts.true(env, action_inputs[1].path.find("/tsconfig_dir.json") != -1)
Expand Down

0 comments on commit ad6f86c

Please sign in to comment.