rules_tcl
Bazel rules for building, testing, and managing Tcl applications and libraries.
Overview
rules_tcl provides a comprehensive set of Bazel rules for working with the Tcl scripting language. It supports:
- Building executables with
tcl_binary - Creating reusable libraries with
tcl_library - Writing and running tests with
tcl_test - Code quality checks with linting and formatting aspects
- Dependency management through Tcl's package system
The rules handle Tcl's package system, runfiles, and provide seamless integration with Bazel's build system.
Quick Start
Setup
rules_tcl does not ship a pre-registered toolchain — you declare the interpreter
and libraries you want and register a tcl_toolchain locally. Add the following to
your MODULE.bazel:
bazel_dep(name = "rules_tcl", version = "{version}")
bazel_dep(name = "tcl_lang", version = "{tcl_lang_version}")
bazel_dep(name = "bazel_skylib", version = "{bazel_skylib_version}")
tcllib_ext = use_extension("@rules_tcl//tcl:extensions.bzl", "tcllib")
use_repo(tcllib_ext, "tcllib")
register_toolchains(
"//toolchain",
)
Then in //toolchain/BUILD.bazel:
load("@rules_tcl//tcl:tcl_toolchain.bzl", "tcl_toolchain", "tclcore_filegroup")
tclcore_filegroup(
name = "tclcore",
srcs = ["@tcl_lang//:tcl_core"],
)
tcl_toolchain(
name = "tcl_toolchain",
tclsh = "@tcl_lang//:tclsh",
tclcore = ":tclcore",
tcllib = "@tcllib",
wrapper_template = "@rules_tcl//tcl/private:binary_wrapper.tpl",
wrapper_entrypoint = "@rules_tcl//tcl/private:entrypoint.tcl",
)
toolchain(
name = "toolchain",
toolchain = ":tcl_toolchain",
toolchain_type = "@rules_tcl//tcl:toolchain_type",
)
See the examples/ directory
for a working setup. Users who need a custom interpreter (e.g. a vendor tool's
tclsh) or a custom wrapper stub swap the corresponding attribute — see the
tcl_toolchain rule doc for the full contract.
Basic Example
Create a simple Tcl executable:
load("@rules_tcl//tcl:tcl_binary.bzl", "tcl_binary")
tcl_binary(
name = "hello",
srcs = ["hello.tcl"],
)
Library Example
Create a reusable Tcl library:
load("@rules_tcl//tcl:tcl_library.bzl", "tcl_library")
tcl_library(
name = "greetings",
srcs = [
"greet.tcl",
"pkgIndex.tcl", # Include to make the library available via `package require`
],
)
A pkgIndex.tcl is only needed when consumers will load the library with
package require. Libraries without one are still available to their
dependents through runfiles and can be loaded directly with source.
Tcl Bazel rules
Rules
tcl_binary
load("@rules_tcl//tcl:defs.bzl", "tcl_binary")
tcl_binary(name, deps, srcs, data, env, main)
A tcl_binary is an executable Tcl program consisting of a collection of
.tcl or .do source files (possibly belonging to other tcl_library rules), a *.runfiles
directory tree containing all the code and data needed by the program at run-time,
and a stub script that starts up the program with the correct initial environment
and data.
load("@rules_tcl//tcl:defs.bzl", "tcl_binary")
tcl_binary(
name = "foo",
srcs = ["foo.tcl"],
deps = [
":bar", # a tcl_library
],
)
ATTRIBUTES
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| deps | Other Tcl packages to link to the current target. | List of labels | optional | [] |
| srcs | The list of source (.tcl or .do) files that are processed to create the target. | List of labels | required | |
| data | Files needed by this rule at runtime. May list file or rule targets. Generally allows any target. | List of labels | optional | [] |
| env | Dictionary of strings; values are subject to $(location) and "Make variable" substitution. | Dictionary: String -> String | optional | {} |
| main | The name of the source file that is the main entry point of the application. This file must also be listed in srcs. If left unspecified, name is used instead. If name does not match any filename in srcs, main must be specified. | Label | optional | None |
tcl_library
load("@rules_tcl//tcl:defs.bzl", "tcl_library")
tcl_library(name, deps, srcs, data)
A Tcl library that can be depended upon by other Tcl targets.
A tcl_library groups a set of .tcl or .do source files that can be consumed by other Tcl
targets. If a pkgIndex.tcl file is included in srcs, its directory is added to the consumer's
auto_path so the package can be loaded with Tcl's package require command. Libraries without a
pkgIndex.tcl are still made available through runfiles and can be loaded directly with source
(typically via the runfiles library's rlocation).
Example with package require:
load("@rules_tcl//tcl:defs.bzl", "tcl_library")
tcl_library(
name = "mylib",
srcs = [
"mylib.tcl",
"pkgIndex.tcl",
],
deps = [
":otherlib", # Another tcl_library
],
visibility = ["//visibility:public"],
)
The library can then be used as a dependency in other targets:
tcl_binary(
name = "app",
srcs = ["app.tcl"],
deps = [":mylib"],
)
ATTRIBUTES
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| deps | Other Tcl packages to link to the current target. | List of labels | optional | [] |
| srcs | The list of source (.tcl or .do) files that are processed to create the target. | List of labels | required | |
| data | Files needed by this rule at runtime. May list file or rule targets. Generally allows any target. | List of labels | optional | [] |
tcl_toolchain
load("@rules_tcl//tcl:defs.bzl", "tcl_toolchain")
tcl_toolchain(name, tclcore, tcllib, tclsh, wrapper_entrypoint, wrapper_template)
A toolchain rule that defines the Tcl interpreter, libraries, and executable wrapper used
to build tcl_binary, tcl_library, and tcl_test targets.
Registering a toolchain
rules_tcl does not register a toolchain for you. Add the following to your MODULE.bazel
to use the default (a stock tclsh and tcllib, with the built-in shell/batch wrapper):
register_toolchains("@rules_tcl//tcl/toolchain")
If you need a custom toolchain (e.g. a different Tcl version, or a wrapper that dispatches
into a different host interpreter), define your own. tclcore and tcllib are matched by
provider — pass a target that returns TclCoreInfo / TclLibInfo, typically produced by
the sibling tclcore_filegroup and tcllib_filegroup rules:
load("@rules_tcl//tcl:tcl_toolchain.bzl", "tcl_toolchain", "tclcore_filegroup")
tclcore_filegroup(
name = "my_tclcore",
srcs = ["@my_tcl//:tclcore_files"],
)
tcl_toolchain(
name = "my_tcl_toolchain",
tclsh = "@my_tcl//:tclsh",
tclcore = ":my_tclcore", # optional; must provide TclCoreInfo
tcllib = "@tcllib", # optional; must provide TclLibInfo
wrapper_template = "//path/to:my_wrapper_bundle",
wrapper_entrypoint = "//path/to:my_entrypoint.tcl", # optional
)
tclcore and tcllib may be omitted when the interpreter ships them itself.
wrapper_entrypoint is optional when the wrapper does not need a bootstrap script.
The template's own DefaultInfo.default_runfiles are merged into every produced binary,
so a template that needs a helper library (e.g. @rules_shell//shell/runfiles for sh,
@rules_batch//batch/runfiles for batch) should be declared via a filegroup that lists
the helper under data:
filegroup(
name = "my_wrapper_bundle",
srcs = ["my_wrapper.sh.tpl"],
data = ["@rules_shell//shell/runfiles"],
)
The same is true for wrapper_entrypoint — a filegroup around the entrypoint can carry
extra runtime data it needs at execution.
The extension appended to the produced wrapper is derived from the template's filename by
splitting the basename on the first . and dropping any trailing parts that are known
template suffixes (.tpl, .template, .tmpl, .in); whatever remains is the extension.
E.g. my_wrapper.sh.tpl yields .sh, my_wrapper.bat.template yields .bat,
my_wrapper.vhook.tcl.tpl yields .vhook.tcl. Since the split happens at the FIRST dot,
templates whose basename contains a dot in the name portion (e.g. com.foo.wrapper.sh.tpl
intending .sh) should be renamed so the first . marks the extension boundary.
ToolchainInfo contract
The rule returns a platform_common.ToolchainInfo with the following fields. Non-underscore
fields are the public contract that consumers (including third-party rules that operate
over Tcl targets) may read. Underscore-prefixed fields are internal to the built-in
tcl_binary / tcl_test implementation — third-party rules that want a materially
different wrapper should carry their own template rather than reach into them.
| Field | Type | Access | Notes |
|---|---|---|---|
tclsh | File | public | Executable of the interpreter that will run the produced binaries. |
includes | depset[str] | public | Runfiles-relative include paths implicitly on auto_path. |
init_tcl | File | None | public | init.tcl from tclcore, or None. |
tcllib_pkg_index | File | None | public | tcllib's top-level pkgIndex.tcl, or None. |
all_files | depset[File] | public | Runtime files merged into every produced target. |
make_variable_info | platform_common.TemplateVariableInfo | public | Exposes $(TCLSH) for env on downstream rules. |
_wrapper_* | (various) | internal | Wrapper template, entrypoint, and derived extension. |
Wrapper template contract
The built-in tcl_binary / tcl_test expand wrapper_template once per target into a
file named <target><wrapper_extension> and set it as the target's executable. The
template supports these substitutions; values whose backing toolchain field is unset
expand to the empty string, so a template can simply omit lines it does not need:
| Substitution | Value | Empty when |
|---|---|---|
{interpreter} | Runfiles path (rlocation) of tclsh. | never |
{entrypoint} | Runfiles path of wrapper_entrypoint. | wrapper_entrypoint unset |
{config} | Runfiles path of a per-target JSON config (see below). | never |
{main} | Runfiles path of the target's entry .tcl (resolved from main / srcs). | never |
{init_tcl} | Runfiles path of tclcore's init.tcl. | tclcore unset |
{tcllib_pkg_index} | Runfiles path of tcllib's top-level pkgIndex.tcl. | tcllib unset |
{auto_path} | Tcl-list literal (brace-quoted) of every include path visible to the target — the target's own workspace, each dep's TclInfo.includes, and the toolchain's includes. Suitable to lappend onto auto_path. | never |
The {config} file is JSON with this shape:
{
"includes": ["workspace_name", "workspace_name/path/to/lib", "..."],
"runfiles": ["workspace_name/path/to/file", "..."]
}
includes is the same set of runfiles-relative paths surfaced through {auto_path};
runfiles is every file merged into the target's runfiles. The default entrypoint.tcl
consumes this to set TCLLIBPATH and to materialize a RUNFILES_DIR when only a manifest
is available; alternate wrappers can consume, ignore, or extend it as they see fit.
ATTRIBUTES
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| tclcore | A target providing TclCoreInfo (typically a tclcore_filegroup). | Label | optional | None |
| tcllib | A target providing TclLibInfo (typically a tcllib_filegroup). | Label | optional | None |
| tclsh | The path to a tclsh binary. Runtime dependency of every produced tcl_binary / tcl_test. | Label | required | |
| wrapper_entrypoint | Optional .tcl file added to the binary's runfiles and referenced via the {entrypoint} template substitution. The target's default_runfiles are also merged in. Leave unset when the wrapper doesn't need a bootstrap. | Label | optional | None |
| wrapper_template | Template expanded per tcl_binary / tcl_test. The target's default_runfiles are merged into every produced binary, so a filegroup wrapping the template can list helper libraries under data (e.g. @bazel_tools//tools/bash/runfiles). See the wrapper template contract in the rule docs for the supported substitutions. | Label | required |
Nagelfar Bazel rules
Rules
Providers
Aspects
nagelfar_syntaxdb
load("@rules_tcl//tcl/nagelfar:defs.bzl", "nagelfar_syntaxdb")
nagelfar_syntaxdb(name, srcs)
Wraps a set of Nagelfar syntax database (syntaxdb) files so they can be
attached to a Tcl target via data and picked up by tcl_nagelfar_aspect
and tcl_nagelfar_test when linting that target or any dependent.
Usage:
load("@rules_tcl//tcl:tcl_library.bzl", "tcl_library")
load("@rules_tcl//tcl/nagelfar:nagelfar_syntaxdb.bzl", "nagelfar_syntaxdb")
nagelfar_syntaxdb(
name = "mylib_syntaxdb",
srcs = ["mylib.syntaxdb.tcl"],
)
tcl_library(
name = "mylib",
srcs = ["mylib.tcl"],
data = [":mylib_syntaxdb"],
)
ATTRIBUTES
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| srcs | Nagelfar syntax database files. | List of labels | required |
nagelfar_toolchain
load("@rules_tcl//tcl/nagelfar:defs.bzl", "nagelfar_toolchain")
nagelfar_toolchain(name, extra_args, nagelfar, syntaxbuild, syntaxdb)
A toolchain rule for configuring the Nagelfar Tcl syntax checker.
The nagelfar_toolchain rule specifies the Nagelfar script, syntax database files,
and any additional Nagelfar arguments used by tcl_nagelfar_aspect and
tcl_nagelfar_test for static analysis of Tcl code.
The toolchain also generates a syntax database for the ambient tcl_toolchain's
tcllib by running syntaxbuild.tcl under an exec-cfg tcl_binary. That generated
database is automatically appended to syntaxdb and reaches every lint action.
The @nagelfar hub repository (populated by the nagelfar module extension)
exposes the shipped script and syntax databases as labels users combine with
their own files:
load("@rules_tcl//tcl/nagelfar:nagelfar_toolchain.bzl", "nagelfar_toolchain")
nagelfar_toolchain(
name = "nagelfar_toolchain",
nagelfar = "@nagelfar//:nagelfar",
syntaxbuild = "@nagelfar//:syntaxbuild",
syntaxdb = [
"@nagelfar//:syntaxdb",
"//path/to:my_project_syntaxdb.tcl",
],
extra_args = ["-len", "100"],
)
toolchain(
name = "toolchain",
toolchain = ":nagelfar_toolchain",
toolchain_type = "@rules_tcl//tcl/nagelfar:toolchain_type",
)
Register the toolchain in MODULE.bazel:
nagelfar = use_extension("@rules_tcl//tcl/nagelfar:extensions.bzl", "nagelfar")
use_repo(nagelfar, "nagelfar")
register_toolchains("//path/to:toolchain")
ATTRIBUTES
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| extra_args | Additional command-line arguments forwarded to nagelfar on every invocation. | List of strings | optional | [] |
| nagelfar | The nagelfar.tcl script. | Label | required | |
| syntaxbuild | The syntaxbuild.tcl script shipped with Nagelfar, used to snapshot the ambient tcllib into a syntax database at toolchain-build time. | Label | required | |
| syntaxdb | Nagelfar syntax database files. The tcllib syntaxdb this toolchain generates for its ambient tcl_toolchain is appended automatically. | List of labels | optional | [] |
tcl_nagelfar_test
load("@rules_tcl//tcl/nagelfar:defs.bzl", "tcl_nagelfar_test")
tcl_nagelfar_test(name, target)
A test rule for performing Nagelfar static analysis on a Tcl target.
Usage:
load("@rules_tcl//tcl/nagelfar:tcl_nagelfar_test.bzl", "tcl_nagelfar_test")
tcl_nagelfar_test(
name = "mylib_nagelfar",
target = ":mylib",
)
ATTRIBUTES
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| target | The Tcl target to perform Nagelfar analysis on. | Label | required |
NagelfarSyntaxdbInfo
load("@rules_tcl//tcl/nagelfar:defs.bzl", "NagelfarSyntaxdbInfo")
NagelfarSyntaxdbInfo(files)
A set of Nagelfar syntax database files contributed by a target.
FIELDS
tcl_nagelfar_aspect
load("@rules_tcl//tcl/nagelfar:defs.bzl", "tcl_nagelfar_aspect")
tcl_nagelfar_aspect()
An aspect for performing Nagelfar static analysis on Tcl targets.
The tcl_nagelfar_aspect applies Nagelfar
checks to all Tcl targets in the dependency graph. It also traverses deps
and data (via a required collector aspect) to gather any nagelfar_syntaxdb
targets attached to the graph, and forwards their files to Nagelfar alongside
the toolchain-shipped databases.
Usage:
bazel build //my:target \
--aspects=@rules_tcl//tcl/nagelfar:tcl_nagelfar_aspect.bzl%tcl_nagelfar_aspect \
--output_groups=+tcl_nagelfar_checks
Or configure it in your .bazelrc:
build:nagelfar --aspects=@rules_tcl//tcl/nagelfar:tcl_nagelfar_aspect.bzl%tcl_nagelfar_aspect
build:nagelfar --output_groups=+tcl_nagelfar_checks
Ignoring targets:
To skip Nagelfar for specific targets, add one of these tags:
no_tcl_nagelfarno_nagelfarno_lintnolint
ASPECT ATTRIBUTES
| Name | Type |
|---|---|
| deps | String |
| data | String |
ATTRIBUTES
Tclint Bazel rules
Rules
Aspects
tcl_tclint_fmt_test
load("@rules_tcl//tcl/tclint:defs.bzl", "tcl_tclint_fmt_test")
tcl_tclint_fmt_test(name, target)
A test rule for performing formatting checks on a Tcl target.
Usage:
load("@rules_tcl//tcl/tclint:tcl_tclint_fmt_test.bzl", "tcl_tclint_fmt_test")
tcl_tclint_fmt_test(
name = "mylib_format",
target = ":mylib",
)
ATTRIBUTES
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| target | The Tcl target to perform formatting checks on. | Label | required |
tcl_tclint_test
load("@rules_tcl//tcl/tclint:defs.bzl", "tcl_tclint_test")
tcl_tclint_test(name, target)
A test rule for performing tclint linting checks on a Tcl target.
Usage:
load("@rules_tcl//tcl/tclint:tcl_tclint_test.bzl", "tcl_tclint_test")
tcl_tclint_test(
name = "mylib_tclint",
target = ":mylib",
)
ATTRIBUTES
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| target | The Tcl target to perform linting on. | Label | required |
tclint_toolchain
load("@rules_tcl//tcl/tclint:defs.bzl", "tclint_toolchain")
tclint_toolchain(name, tclint)
A toolchain rule for configuring tclint.
The tclint_toolchain rule specifies the tclint Python library used by
tcl_tclint_aspect, tcl_format_aspect, and related test rules.
Typically, you don't need to define this directly. Instead, use the bzlmod extension:
tclint = use_extension("@rules_tcl//tcl/tclint:extensions.bzl", "tclint")
tclint.toolchain()
use_repo(tclint, "tclint_toolchains")
register_toolchains("@tclint_toolchains//:all")
ATTRIBUTES
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| tclint | The tclint python library. | Label | required |
tcl_tclint_aspect
load("@rules_tcl//tcl/tclint:defs.bzl", "tcl_tclint_aspect")
tcl_tclint_aspect()
An aspect for performing tclint linting checks on Tcl targets.
Uses tclint to check for code quality issues.
Usage:
bazel build //my:target \
--aspects=@rules_tcl//tcl/tclint:tcl_tclint_aspect.bzl%tcl_tclint_aspect \
--output_groups=+tcl_tclint_checks
Ignoring targets:
To skip tclint for specific targets, add one of these tags:
no_tcl_lintno_tclintno_lintnolint
ASPECT ATTRIBUTES
ATTRIBUTES
tcl_tclint_fmt_aspect
load("@rules_tcl//tcl/tclint:defs.bzl", "tcl_tclint_fmt_aspect")
tcl_tclint_fmt_aspect()
An aspect for performing formatting checks on Tcl targets.
Uses tclint to verify formatting.
Usage:
bazel build //my:target \
--aspects=@rules_tcl//tcl/tclint:tcl_tclint_fmt_aspect.bzl%tcl_tclint_fmt_aspect \
--output_groups=+tcl_tclint_fmt_checks
Ignoring targets:
To skip format checking for specific targets, add one of these tags:
no_tcl_formatno_tclformatno_tclfmtnoformatnofmt
ASPECT ATTRIBUTES
ATTRIBUTES