1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
|
---
title: "Tala project files"
description: "A project is described by one file, a Kotlin script that Tazel evaluates."
source: "https://github.com/Progmasoft/tazel/blob/main/Documents/PROJECT_FILES.md"
---
<!-- SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com> -->
<!-- SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-2.0 -->
A project is described by one file, a Kotlin script that [Tazel](/docs/reference/commands) evaluates. Tazel reads it, finds the
sources it names and starts the compiler; the compiler itself never sees a project file.
## Discovery
A project file has one of five names. Tazel looks for them in this order and uses the first that exists; the others are
not read:
1. `BUILD.kts`
2. `BUILD.tzl`
3. `BUILD.tazel`
4. `BUILD`
5. `Tazel.build`
The names are case-sensitive on every host, and all five hold the same syntax. A command works on the project of the
current directory: the nearest directory, upward, that holds a project file.
The script is compiled and run inside Tazel. A machine needs a JRE 25 or newer and no Kotlin installation.
## Minimal project
```kotlin
project {
name = "Example"
}
sources {
executable {
entry = "Example.Program"
}
}
```
```powershell
tazel run
```
This compiles every `.tala` file below `Sources/` and writes `build/debug/Example.exe` (no extension outside Windows).
[`BUILD.kts`](https://github.com/Progmasoft/tazel/blob/main/BUILD.kts) shows the whole DSL in one file. What is not in that file is not part of the DSL.
## `project`
```kotlin
project {
name = "Example"
version = "0.1.0"
stability = Stability.STABLE // STABLE, RC, UNSTABLE, BETA, NIGHTLY, ALPHA or DEV
description = "A Tala project"
authors("Leitwolf", "Helmut")
defaultFeatures("TOML")
defaultRun = "Server" // the executable `tazel run` runs when none is named; default: the only one
}
```
`name` is always required, so every project file has a `project` block. Every other field is optional on its own. With
`sources.viget.push = true`, `version`, `stability`, `description` and at least one author are required too; this is checked
once the whole file has run, so the order of the blocks does not matter.
`name` is one identifier and `version` is an exact version: `A.B.C`, then `.D`, `.E` and `-METADATA` if needed.
## `compiler`
```kotlin
compiler {
standard = talaStandardOf(26)
backend = Backend.LLVM
buildMode = BuildMode.RELEASE
werror = true
warnings = Warnings.ALL
wshadow = true
xppOptimizationPasses = true
xmmOptimizationPasses = true
optLevel = OptLevel.O3
staticTalaRTLibrary = true
llvm {
compiler = LlvmCompiler.AOT
lto = LlvmLto.NONE
args("-time-passes")
}
}
```
| Setting | Values | Default | Compiler option |
| --- | --- | --- | --- |
| `standard` | `talaStandardOf(26)` | `talaStandardOf(26)` | `--standard` |
| `backend` | `Backend.LLVM` | `LLVM` | `--codegen-backend` |
| `buildMode` | `BuildMode.DEBUG`, `RELEASE` | `DEBUG` | chooses the output directory and the default `optLevel` |
| `werror` | `true`, `false` | `false` | `--warnings-as-errors` |
| `warnings` | `Warnings.ALL`, `MEDIUM`, `LOW`, `NONE` | `MEDIUM` | `--warnings` |
| `wshadow` | `true`, `false` | `false` | `--warnings=shadow` |
| `xppOptimizationPasses` | `true`, `false` | `true` | `--xpp-optimization-passes` |
| `xmmOptimizationPasses` | `true`, `false` | `true` | `--xmm-optimization-passes` |
| `optLevel` | `OptLevel.O0`, `O1`, `O2`, `O3`, `OG`, `OS`, `OZ` | `O0` for `DEBUG`, `O3` for `RELEASE` | `--opt-level` |
| `staticTalaRTLibrary` | `true`, `false`, `null` | `null` | `--static-talart-library` |
| `llvm.compiler` | `LlvmCompiler.AOT`, `ORC` | `AOT` | `--llvm-compiler` |
| `llvm.lto` | `LlvmLto.FAT`, `THIN`, `NONE` | `NONE` | `--llvm-lto` |
| `llvm.args(...)` | arguments for LLVM | none | `--llvm-args` |
Tazel writes every setting onto the compiler's command line, the ones at their default too, so a build follows from the
project file and not from the defaults of whichever compiler is installed. A setting the compiler cannot carry out yet is
refused by the compiler, by name; see the [compiler reference](/docs/reference/commands#not-implemented-yet).
`staticTalaRTLibrary` is the exception. `null`, or leaving it out, writes no option and leaves the choice to the compiler;
`true` writes `--static-talart-library=true`, which links TalaRT into the output, and `false` writes
`--static-talart-library=false`, which keeps it in a shared library beside the output.
No warning level turns `wshadow` on. `wshadow = true` with `Warnings.NONE` is a contradiction and is rejected.
## `buildCache`
```kotlin
buildCache {
local {
enabled = true
maxEntries = 8
}
remote {
url = "https://cache.example.com"
read = true
write = false
token = System.getenv("TAZEL_CACHE_TOKEN")
publicKey = "MCowBQYDK2VwAyEA..."
signingKey = System.getenv("TAZEL_CACHE_SIGNING_KEY")
}
}
```
| Setting | Values | Default | Meaning |
| --- | --- | --- | --- |
| `local.enabled` | `true`, `false` | `true` | keeps results in `.tazel/cache` |
| `local.maxEntries` | 1 or more | `8` | how many results of each target stay |
| `remote.url` | an `https` address; `http` for this machine only | required | where the shared results are |
| `remote.read` | `true`, `false` | `true` | asks the server for results |
| `remote.write` | `true`, `false` | `false` | sends results to the server |
| `remote.token` | text or `null` | `null` | sent as `Authorization: Bearer`; empty counts as none |
| `remote.publicKey` | an Ed25519 key or `null` | `null` | only results signed for this key are used |
| `remote.signingKey` | an Ed25519 key or `null` | `null` | signs what this machine writes; a secret |
The block is optional, and without it a project has the local cache alone. The local cache has priority: the server is
asked only for what is not there, and what it gives is kept locally unless `local.enabled` is `false`. See
[incremental builds](/docs/reference/commands#incremental-builds) for what makes a result the same.
Write tokens and the signing key as variables, never into the project file. A project file that reads a variable with
`System.getenv("NAME")` gets the value of the command, also under the daemon: when the value differs from the one the daemon
started with, the command runs without the daemon and stops it, and the next command starts one that sees the new value. The
name has to be written as text for that; a name computed at run time is not noticed.
A result is a program, so a result from a server is code that this machine will run. `tazel cache keygen` prints a pair of
Ed25519 keys. Give every project the `publicKey`, and the `signingKey` only to what builds trustworthy sources, such as CI on
the main branch. A reader with a `publicKey` then uses a result only with a signature for that key, for that target and that
key of the cache, so neither a token that leaked nor a server that was broken into can plant a program. A result without a
signature counts as missing, and a wrong signature is a warning; either way the compiler runs. A reader without a
`publicKey` trusts the server, and is warned when it uses a result.
The server answers two requests, each with the token if there is one. `GET <url>/<slug>/<key>` gives the result as a ZIP
archive, or `404` when it is not there, and `<key>.sig` its signature. `PUT` of the same paths stores them and answers `200`,
`201` or `204`, and `HEAD` says whether a result is there, for `tazel cache push`. Redirects are not followed. A server that fails or cannot be reached is a warning: the build goes on and the
server is not asked again during that command. [`tazel cache serve`](/docs/reference/commands#a-remote-cache-server) is such a server.
## `outdirs`, `targets` and `pml`
```kotlin
outdirs {
release = "build/release"
debug = "build/debug"
}
targets {
target("x86_64-pc-windows-msvc", "aarch64-apple-darwin")
}
pml {
enabled = true
}
```
Output directories are relative to the project file and stay inside the project. An artifact is named after its target:
`build/debug/<name>.exe` for a build, `build/debug/<name>.talameta` for a check.
`targets` lists the LLVM target triples the package is meant for; the default is the target of the host. `check`, `build`
and `run` compile for the host. `pml.enabled = false` is meant to close the Progmasoft Library (`Progmasoft.*`) to the
project; the setting is recorded, and the compiler has no option for it yet.
## `sources`
```kotlin
sources {
viget {
push = true
exclude("build/**/*")
}
executable {
name = "Example"
srcDir = "Sources"
exclude("Sources/Generated")
entry = "Example.Program"
}
library {
name = "ExampleLibrary"
viPkgType(ViPkgType.TALALIB, ViPkgType.STATICLIB)
srcDir = "Sources"
namespace = "Example.Library"
}
test("Unit") {
testDir = "Tests/Unit"
}
}
```
A project has any number of executables and libraries. `name` defaults to the name of the project, so a target of a project
without a name has to name itself; names are case-sensitive and unique within their kind. `srcDir` defaults to `Sources`.
`entry` names a class by its namespace: `Example.Program`. That class provides the parameterless
`public static void Main()` that starts the program. The entry is found by namespace and class, not by file: neither file
names nor directories have to mirror namespaces. A library has no entry; `namespace` names its root namespace.
`viPkgType` is `TALALIB` unless the library says otherwise; `STATICLIB` and `CDYLIB` are the other kinds.
The sources of a target are every file below its `srcDir` whose name ends in `.tala`, written exactly so. Symbolic links
are not followed. `exclude` takes patterns relative to the project file, written with `/`:
- `*` and `?` match within one path segment;
- a segment that is `**` matches any number of segments;
- a pattern that names a directory excludes everything below it.
Patterns are compared the way the host compares file names: without regard to case on Windows, exactly elsewhere.
`test("Name")` declares a named test suite; a name is one identifier and may be declared once. `testDir` defaults to
`Tests/<name>`. Test sources and test artifacts never enter a `.vipkg`. `sources.viget.exclude` removes further files from
what is packaged.
## `dependencies`
```kotlin
dependencies {
dependency("Publisher") {
name = "Name"
version = "1.2.3"
stability = Stability.STABLE
}
dependency("Publisher") {
name = "Toml"
version = "1.0.0"
feature = "TOML"
defaultFeatures = false
feature("Serde") { enabled = true }
}
localDependency { path = "Dependency.vipkg" }
testDependency("Publisher") {
name = "TestDep"
version = "0.1.0"
}
localTestDependency { path = "TestDependency.vipkg" }
}
```
A dependency is a package of ViGet, written as a publisher, a name and an exact version. `stability` defaults to
`STABLE`. The publisher is the case-sensitive name of a Progmasoft Account. ViGet is the only remote source: there is no
list of repositories and no way to name another one.
`feature = "TOML"` makes the dependency optional: it is brought in only when the feature `TOML` of this project is on.
`defaultFeatures` and `feature("Name") { enabled = true }` choose among the features of the dependency itself.
`localDependency` names a `.vipkg` inside the project directory. `testDependency` and `localTestDependency` serve the test
suites only. A package may be declared once per kind; two declarations of one package that differ are an error.
Tazel records the declarations and does not fetch them yet, so `check`, `build` and `run` refuse a project that declares a
dependency of the build. The namespaces of the standard library are not dependencies and are never declared.
## `execution`
```kotlin
execution {
jobs = 8
cpus = 6
memory = "16g"
memoryPerJob = "1g"
keepGoing = true
incremental = true
profile = "build/trace.json"
remote {
url = "https://build.example.com"
token = System.getenv("TAZEL_REMOTE_TOKEN")
}
}
```
The whole block is optional, and [the flags](/docs/reference/commands#spending-the-machine) of a command go before it. In a workspace, the block
of the root counts. `profile` is relative to the project file. `url` takes `https`, or `http` for this machine only.
## `workspace`
```kotlin
workspace {
members("./Server", "../Shared", "Client")
defaultMembers("./Server", "../Shared")
exclude("OldProject")
}
```
Each member is a directory with a project file of its own, written relative to this project file. A project that only
gathers members needs a `project.name` and no `sources`. `defaultMembers` are the members `tazel fmt` and `tazel lint` work on at the root;
`exclude` removes a directory from the members. See [workspaces](/docs/reference/commands#workspaces).
## `plugins`
Plugins extend the DSL. They are declared in a `plugins` block that Tazel reads before it compiles the rest of the file:
```kotlin
plugins {
plugin("Progmasoft") {
name = "CMake"
version = "0.1.0"
}
localPlugin {
path = "plugin.jar"
}
}
```
A plugin of ViGet is found in the cache of the project, `.tazel/plugins`; a plugin the cache lacks is fetched from
`https://viget.progmasoft.com/dslplugins/tazel/<Publisher>/<Name>/`. A local plugin is a JAR inside the
project directory. Either way the JAR describes itself in `META-INF/tazel-plugin.properties` (publisher, name, version,
API version, the imports it adds to the project file) and provides exactly one `ProjectPlugin` service. Tazel checks the
coordinate, the version, the API version and the SHA-256 digest before it loads the plugin; a `<plugin>.jar.sha256` file
beside the JAR pins the expected digest. A plugin has no stability.
A plugin is written against the package `com.progmasoft.api.tazel.dsl.plugin`. It can register DSL extensions, contribute
metadata and run a hook once the project is complete. A plugin is trusted build logic: it runs with the full access of
the JVM, and Tazel does not sandbox it. [Writing a plugin](/docs/extending/plugins) shows how one is built and loaded.
## Host values and helpers
| Name | Meaning |
| --- | --- |
| `OS` | the operating system Tazel runs on: `OperatingSystem.LINUX`, `MACOS`, `WINDOWS`, `FREEBSD`, `OPENBSD`, `NETBSD` or `UNKNOWN` |
| `ARCH` | its processor architecture: `Architecture.X86_64`, `AARCH64`, `ARMV7H`, `RISCV64` or `UNKNOWN` |
| `panic(message)` | stops the evaluation; the command fails with the message |
| `eprint(value)` | writes to the standard error stream |
```kotlin
if (OS == OperatingSystem.OPENBSD) panic("this project does not build on OpenBSD")
```
After its `project` and `compiler` blocks a project file can read back what it wrote: `project.name`,
`compiler.standard`.
## Lockfile
`LOCK.sqlite3`, beside the project file, is an SQLite database that records the dependencies the project declares and the
plugins that evaluated it. `tazel lock resolve` renews it; `check`, `build` and `run` renew it when it no longer matches
the project file. It records declarations, not a solved dependency graph.
`tazel lock create-dump` writes `LOCK.dump`: the same content as SQL text, deterministic and replayable by SQLite, for
review and for diffs. `--path` writes it to another file in a folder that already exists; see [CLI](/docs/reference/commands).
[The lockfile](/docs/reference/lockfile) describes its tables and when each command writes it.
## Tool configuration
`talafmt.kts`, `talalint.kts` and `tala-analyzer.kts` configure the formatter, the linter and the analyzer. Each tool uses
its defaults when its file is absent.
|