In plain words
A project is a folder with a file called turf.toml in it. That file, the manifest, is the project’s name tag and shopping list: its name, its version, and which other packages it needs. A package is a named set of modules that can be built into a program, shared as a library, or both. turf reads the manifest, fetches the dependencies, pins the git ones in a lock file (turf.lock) so that everyone builds the same thing, and compiles everything in one go.
You do not need a project for a single file: turf hello.tr works with no manifest.
A TurfLang project is a directory with a turf.toml. It holds one
package: a named set of modules that can be built into a program, imported
by other packages as a library, or both. turf reads the manifest, finds the
package’s dependencies (other directories, or git repositories), pins the git
ones in a lock file, and compiles everything, every module of every package,
in one go.
You do not need a project to compile a program. turf hello.tr and
turf compile hello.tr work on a single file with no manifest; see
CLI & Diagnostics. A project is what you
want once a program has more than one file, or uses someone else’s code.
Two multi-package projects you can build and run are in
examples/: a library, geometry, and a program,
plotter, that depends on it.
1. A project
myapp/
turf.toml the manifest
turf.lock written by turf; commit it
src/main.tr present: the package is a program
src/myapp.tr present: the package is a library; this is its root module
src/**.tr the package's other (internal) modules
tests/**.tr the package's tests, run by `turf test`
build/ output: the executable and package-graph.json; not committed
-
A program has
src/main.tr: the package’s one entry point.turf buildproducesbuild/<name>from it, andturf runruns it and nothing else.- No other file of a package can be its program: the compiler refuses one
in a package build.
turf run <file.tr>runs a single file on its own, outside the package.
-
A library has
src/<name>.tr, its root module, named after the package. It is the only module another package canimport.turf buildalways checks it (as a module, so it needs nomain), so its errors show even when nothing imports it.- A library without
src/main.trbuilds no executable, andturf runthere says how to add one (below).
-
A package may be both, and
turf new --libmakes it so. Withsrc/main.trandsrc/both.tr,turf runruns the program, and another package can still depend on it and importboth. The dependency sees only the root module; itssrc/main.tris ignored. -
turf runwith nosrc/main.tr:Terminal$ turf run turf: package 'geometry' has no src/main.tr, so there is nothing to run. hint: `turf run` runs src/main.tr, the package's entry point. Create it, for example: // src/main.tr: this package's entry point. `turf run` builds and runs it. // The library itself is src/geometry.tr; other packages import that, never this. import io; import geometry; fn main(): int { io::printline("Hello, TurfLang! geometry::answer() is " + (geometry::answer() to string) + "."); return 0; } To run some other file on its own instead: turf run <file.tr>
-
A package with neither cannot be built:
turf: package 'empty' has neither src/main.tr (a program) nor src/empty.tr (a library) -
Internal modules are any other
.trfile undersrc/, in any subdirectory.import shouter;findssrc/util/text/shouter.tr; the directory path is not part of the name. Two files with the same name anywhere undersrc/are an ambiguity (section 4). -
turf.lockis created by the firstturf build,run,checkorfetch, even when there is nothing to pin.build/is disposable:turf cleandeletes it.tests/is whatturf testruns (see Tests below).
turf new and turf init
$ turf new hello Created program package 'hello' in hello/ hello/turf.toml hello/src/main.tr hello/README.md hello/.vscode/extensions.json hello/.gitignore $ cd hello $ turf run Compiled to build/hello.o Successfully compiled to build/hello Hello, TurfLang! This is hello.
Every new package runs straight away. The generated README.md lists the
everyday commands (turf run, build, check, add, remove, update,
clean, fmt, explain, help) and the project’s layout.
.vscode/extensions.json recommends the Turf extension, so VS Code (and its
forks) offers to install it when the project is opened.
turf new geometry --lib also creates the root module src/geometry.tr. Its
src/main.tr imports it, so the library can be tried with turf run:
$ turf new geometry --lib Created library package 'geometry' in geometry/ geometry/turf.toml geometry/src/main.tr geometry/src/geometry.tr geometry/README.md geometry/.vscode/extensions.json geometry/.gitignore
turf init does the same in the current directory. Its name defaults to the
directory’s; an existing src/main.tr, root module, README.md or
.vscode/extensions.json is never overwritten; and /build/ is added to an
existing .gitignore:
$ turf init Initialized package 'existing' in …/existing turf.toml src/main.tr (already there, left as it is) README.md .vscode/extensions.json .gitignore
Both refuse a name that could not be imported:
$ turf new list --lib turf: 'list' is the name of a standard-library module, so a library called that could never be imported (`import list;` would name the stdlib module) hint: pick another name $ turf new fn turf: 'fn' is a reserved word, so `import fn;` could never be written $ turf new my-app turf: 'my-app' is not a valid package name: it must match [A-Za-z_][A-Za-z0-9_]* because it is spelled in `import`
A program named like a stdlib module is fine (turf new json works), since
nothing can import a program.
Tests: turf test
turf test compiles and runs every .tr file under the project’s tests/
directory, at any depth, in parallel, and exits 0 only if all of them pass.
A project with no tests/ directory is an error, and so is one whose
tests/ holds no tests: a run of zero tests that reports success is how an
untested project goes unnoticed.
$ turf test turf: this project has no tests/ directory (looked for /home/me/app/tests) hint: create it and put test files in it, for example tests/basic.tr: import test; fn main(): int { test::assert(1 + 1 == 2, "arithmetic still works"); return 0; }
A test is an ordinary program with a main that returns 0, and the stdlib’s
test module (import test;) has the assertions. What decides pass or fail is
the file’s name:
| File | Passes when |
|---|---|
name.tr |
it compiles and exits with status 0 |
fail_name.tr |
it fails to compile (the compiler crashing does not count) |
fail_name.tr containing the comment // RUNTIME_ABORT_EXPECTED |
it compiles and then aborts (exits non-zero) |
name_module.tr |
never run: a companion for a test to import |
Comments can pin the reason, not only the outcome, so a test cannot keep passing because it broke in some other way:
| Comment | Holds when |
|---|---|
// EXPECT-DIAG: E011 |
the compiler’s output has the error code E011 |
// EXPECT-DIAG: some text |
… or this text (for a warning, which has no code) |
// EXPECT-NO-DIAG: some text |
the compiler’s output does not have this text |
// EXPECT-RUNTIME: some text |
the aborting program printed this text |
// EXPECT-EXIT: 134 |
the aborting program exited with this status |
Each test runs in its own directory (so it can open a data file beside it) with
no standard input, and is stopped after 5 seconds. A test imports what the
package itself can: its own modules under src/, its direct dependencies and
the standard library, plus the *_module.tr helpers in tests/. A test may
share its file’s name with the module it tests: in tests/route.tr,
import route; is src/route.tr. The
internal-module rules of section 4 apply to a test exactly as to src/: it
cannot import another package’s internal module (E091).
turf test <path>... runs only the tests it names: each path is a test file or
a directory of tests under tests/, written from the current directory
(turf test tests/route.tr from the project root). A path that does not exist,
is outside tests/ or holds no test is an error. With no path, every test runs.
turf test takes --offline and --locked (it resolves the project first,
like turf build), and hands the rest to the compiler’s runner:
| Flag | Meaning |
|---|---|
--jobs <n> |
tests running at once (default: every processor, or $TURF_TEST_JOBS) |
--timeout <seconds> |
how long one test program may run (default 5, or $TURF_TEST_TIMEOUT_SEC) |
--exclude <path> |
skip a file or directory, relative to tests/; repeatable |
-O0..-Os, -g, --link <lib> |
passed to every test’s compile |
-o and --package-graph are refused, as for turf build. The exit status is 0
when every test passed and 1 when one failed or nothing could run. A failing
test is listed with its exit status and what it printed:
$ turf test Running Turf Test Suites... [tests] bad.tr ... RUNTIME FAILED (Exit code 134) [tests] good.tr ... PASS === Test Summary === Total Tests: 2 Passed: 1 Failed: 1 Failed Tests Details: --- tests/bad.tr (Runtime Error exit 134): bad.tr:3: math ---
The same runner is turfc --test [<dir>] [--package-graph <file>] ..., for a
directory of tests that is not a project (it needs no manifest).
2. turf.toml
turf.toml is read with a real TOML parser. Here is what turf new writes:
[package]
name = "hello"
version = "0.1.0"
turf = "2.0"
[dependencies]
There are three tables and no others.
| Table | Key | Meaning |
|---|---|---|
[package] |
name |
Required. The package’s name, spelled in import. It must match [A-Za-z_][A-Za-z0-9_]* and not be a reserved word. A library may not be named like a standard-library module. |
version |
Required. A string, not interpreted in 2.0. Dependents pin a git tag or commit, not this. | |
turf |
Optional. The oldest compiler this package needs, "<major>.<minor>". The major must equal the compiler’s, and the minor must not be newer than it. |
|
[dependencies] |
<name> = { … } |
One entry per dependency (section 3). The key must be the dependency’s own package name. |
[unsafe] |
allow = [ "<name>", … ] |
The dependencies whose unsafe module; declarations are honored (section 8). |
An unknown key or table is an error, not ignored. A silently ignored setting is how a typo becomes a policy.
turf: …/turf.toml:4: unknown key `authors` in [package]: the keys are name, version and turf
turf: …/turf.toml:5: unknown key `build` at the top level: the tables are [package], [dependencies] and [unsafe]
turf: …/turf.toml:6: unknown key `deny` in [unsafe]: the only key is allow
The other mistakes have their own messages, each naming the file and, where it can be recovered, the line:
turf: …/turf.toml:1: [package] is missing the required key `name`
turf: …/turf.toml:1: [package] is missing the required key `version`
turf: …/turf.toml:3: `version` in [package] must be a string
turf: …/turf.toml:2: invalid `name` in [package]: 'fn' is a reserved word, so `import fn;` could never be written
turf: …/turf.toml:2: invalid `name` in [package]: 'my-app' is not a valid package name: it must match [A-Za-z_][A-Za-z0-9_]* because it is spelled in `import`
turf: …/turf.toml:4: invalid `turf` in [package]: '2' is not "<major>.<minor>"
hint: write the minimum compiler version as "<major>.<minor>", for example "2.0"
turf: …/turf.toml: missing [package] table
A turf requirement the compiler does not meet:
turf: package 'app' (…/turf.toml) requires turf "9.0", but the compiler is turfc 2.0.0 (…/build/turfc): a different major version
hint: use a turfc 9.x, or change `turf` in that manifest
turf: package 'app' (…/turf.toml) requires turf "2.99", but the compiler is turfc 2.0.0 (…/build/turfc), which is older
hint: upgrade turfc, or lower `turf` in that manifest
turf and turfc are versioned separately. turf version shows both:
$ turf version turf 1.0.0 (needs turfc interface 1.1+) turfc 2.0.0 (interface 1.7, …/bin/turfc) …
The turf key in the manifest is about the compiler’s version (turfc, the
number after turfc above). The remaining lines of turf version are
described in CLI & Diagnostics.
3. Dependencies
A dependency is an inline table with a path, or a git URL and one ref:
[dependencies]
geometry = { path = "../geometry" }
jsonx = { git = "https://github.com/someone/jsonx", tag = "v1.2.0" }
The key is the dependency’s package name, and turf checks it against the
dependency’s own turf.toml:
turf: dependency 'geo' (../../geometry) in …/turf.toml is the package 'geometry'; the key must be its package name
path is a directory, relative to the manifest that declares it. It is
live source: turf reads it as it is on disk, and turf.lock records the
path but no hash. The directory must hold a turf.toml for that package and a
root module src/<name>.tr:
turf: dependency 'tool' in …/turf.toml is not a library package: it has no src/tool.tr
git needs exactly one of tag, rev or branch:
tag = "v1.2.0"is what a release is. Once locked, the commit does not move unless you runturf update.rev = "<commit>"names a commit and never moves.branch = "main"follows a branch, but only when you runturf update;turf buildkeeps the commit inturf.lock.
turf: …/turf.toml:6: git dependency 'm' needs exactly one of `tag`, `rev` or `branch`, and has 2
hint: pin the dependency to a release with `tag`, or to a commit with `rev`
turf: …/turf.toml:6: dependency 'm' has both `path` and `git`: it is one or the other
turf: …/turf.toml:6: dependency 'm' needs `path`, or `git` with one of `tag`, `rev` or `branch`
A registry-style entry is refused, with a pointer to what works:
turf: …/turf.toml:6: dependency 'math' is a version string, not a table
hint: registry versions are not part of TurfLang 2.0 (…); use `{ path = "..." }` or `{ git = "...", tag = "..." }`
A dependency may not be named like a standard-library module, because
import list; would never reach it (section 4):
turf: dependency 'list' (…/libs/list/turf.toml) has the name of the standard-library module 'list', so `import list;` could never reach it
hint: rename the package
Direct dependencies only
A package imports its own modules, its direct dependencies and the
standard library, and nothing else. If plotter depends on geometry and
geometry depends on basepkg, plotter cannot import basepkg; until it
lists basepkg itself (section 4 shows the message). Dependencies of
dependencies are fetched and built, but they are not in plotter’s namespace.
One version per package name
A build has one package per name. If two packages need the same name at different places or commits (a diamond that disagrees), it is an error naming both requirers; there is no multi-version support. The first line says what differs: two versions (tag, rev or commit) of one git repository, two directories, two git repositories, or a git repository against a directory:
turf: package 'libA' is required at two different versions of git …/up/libA.git, and a build has one package per name:
package 'left' (…/dia/left/turf.toml) needs it at git …/up/libA.git (tag=v1, commit 11343e6dbefb)
package 'right' (…/dia/right/turf.toml) needs it at git …/up/libA.git (tag=v2, commit 483d19a6a2f2)
A diamond that agrees, meaning the same source and the same commit reached by
two routes (two spellings of one commit, tag = "v1" and its rev, agree), is
one package and builds. Two directories both holding a package called pc
is the same error, worded for directories:
turf: package 'pc' is required from two different directories, and a build has one package per name:
package 'pa' (…/pcs/pa/turf.toml) needs it at …/pcs/one/pc
package 'pb' (…/pcs/pb/turf.toml) needs it at …/pcs/two/pc
A dependency cycle is refused too:
turf: dependency cycle between packages: pa -> pb -> pa
turf add and turf remove
$ turf add geometry --path ../geometry Added dependency 'geometry' (../geometry) to …/turf.toml $ turf add jsonx --git https://github.com/someone/jsonx --tag v1.2.0 turf: fetching jsonx (https://github.com/someone/jsonx) Added dependency 'jsonx' (git https://github.com/someone/jsonx, tag=v1.2.0, commit 0858be2466c2) to …/turf.toml
--path is relative to where you type the command (it is written relative to
the manifest). add checks what it is about to write with the same code as
turf build, and writes nothing if the build would reject it:
turf: cannot add 'ghost' (nothing was written): dependency 'ghost' in …/turf.toml: cannot use path "../../ghost": no such file or directory
turf: dependency 'geometry' is already in [dependencies] of …/turf.toml
turf: a git dependency needs exactly one of --tag, --branch or --rev, and got 0
hint: pin a release with --tag, or a commit with --rev
add --git fetches the repository first, so it needs the network, and checks
its root manifest and module. It does not write turf.lock; the next
turf build (or turf fetch) does.
add and remove edit one line of turf.toml and touch nothing else.
Your comments, blank lines and key order stay, and an add followed by a
remove restores the file byte for byte:
# tool, my first program
[package]
name = "tool"
version = "0.1.0"
turf = "2.0"
# what I depend on
[dependencies]
geometry = { path = "../../geometry" }
remove deletes the line for that name in [dependencies] and nothing else.
A dependency written over several lines, or as a [dependencies.name] table,
is refused (edit it by hand), and a name that is not there is an error:
turf: dependency 'geometry' is not in [dependencies] of …/turf.toml
A name also listed in [unsafe] allow gets a note, because that line is left
for you to change and the next build rejects it until you do:
Removed dependency 'geometry' from …/plotter/turf.toml
turf: note: 'geometry' is still listed in [unsafe] allow in …/plotter/turf.toml; the next build will reject it until you remove it there
4. How imports resolve
In a project, import foo; inside package P names exactly one of:
- P’s own module
foo: a filesrc/**/foo.trofP. - A direct dependency named
foo: its root module. - The standard-library module
foo.
If more than one exists, that is an error listing every candidate: there is no
shadowing and “your own file wins” is not a rule. If none does, it is the usual
“module not found”. import foo::bar; finds foo the same way (a flat
import geometry::distance_squared; of a dependency’s pub function works).
The directory of the importing file, the current directory and lib/usr are
not searched in a project: a helper.tr next to turf.toml, or a module in
lib/usr, is not found by import helper;:
── error at line 1, col 1 [E027] Could not find module 'helper'. Hint: In a project, `import helper;` names one of three things: a module of this package (a file 'helper.tr' anywhere under hello's src/), a package listed under [dependencies] in its turf.toml, or a standard-library module. Check the spelling, create src/helper.tr, or add the dependency (`turf add helper --path <dir>`).
E090: two things answer to one name
── error at line 1, col 8 [E090] Module 'list' is ambiguous: 2 different files answer to that name. standard library …/lib/std/collections/list.tr module of package 'shapes' ./src/list.tr Hint: A module may not share its name with a standard-library module. Rename ./src/list.tr (for example to 'my_list.tr') and import it by the new name. 1 │ import list; │ ^~~~ Here!
The other collisions read the same way:
Module 'geometry' is ambiguous: 2 different files answer to that name.
module of package 'shapes' ./src/geometry.tr
dependency 'geometry' …/geometry/src/geometry.tr
Hint: A module may not share its name with a dependency. Rename ./src/geometry.tr (for example to 'my_geometry.tr') and import it by the new name.
Module 'shouter' is ambiguous: 2 different files answer to that name.
module of package 'deep' ./src/other/shouter.tr
module of package 'deep' ./src/util/text/shouter.tr
Hint: These are different files that both answer to 'shouter'. Rename one of them, or delete the one you did not mean.
The E090 is the real error. Each later use of the module (list::n()) also
reports an E145 (“Module ‘list’ is not imported in this file”); read the first
diagnostic, not the ones after it.
Two different packages can each have an internal module named util, each
declaring a Node, without any collision: a module’s identity is its package
plus its path, so they are two modules with two distinct types.
E091: the module exists but is out of reach
A transitive dependency, or another package’s internal module, exists in the build but is not yours to import. This is a different error from “not found” because the fix is different:
── error at line 2, col 8 [E091] Package 'basepkg' is not a dependency of 'plotter'. It is in this build because 'geometry' depends on it. Hint: A package can import only its own modules, its direct dependencies and the standard library. Add 'basepkg' to [dependencies] in plotter's turf.toml to import it here. 2 │ import basepkg; │ ^~~~~~~ Here!
── error at line 2, col 8 [E091] Module 'vecmath' is internal to package 'geometry'. …/geometry/src/vecmath.tr Hint: Only a package's root module can be imported from outside it. Import 'geometry' and use what its root module makes `pub`; if 'geometry' should offer this, its root module has to expose it. 2 │ import vecmath; │ ^~~~~~~ Here!
Internal modules are private to their package
A package’s API is its root module, and inside that module, what is marked
pub. Anything else in the package, vecmath.tr in examples/geometry, can
be imported only from the same package, whatever its own items say. Within a
module, visibility is the ordinary rule (everything is private unless pub),
which applies across packages too:
── error at line 4, col 22 [E088] Function 'hidden' is private to module 'geometry'. Note: 'hidden' is declared in module 'geometry' without `pub` (line 44, col 4) Hint: If 'hidden' is meant to be used from other modules, mark it `pub` in geometry.tr: `pub fn hidden(...)`. Otherwise, 'geometry' offers these public functions: distance_squared, manhattan, pick, size_of_type.
So a package has two levels: private to a module, and pub; and pub in an
internal module means “visible inside this package”. For the same reason the
root module may not put a type of an internal module in a pub signature
(E089): other packages could receive its values but never write its name.
Declare the types of a package’s API in its root module.
Single-file mode
Outside a project (turf hello.tr, turf compile, turf check file.tr), the
compiler has no packages. import foo; looks in the standard library, in
lib/usr, next to the importing file and in the current directory, and it
examines all of them. If more than one distinct file answers, it is the
same E090:
── error at line 1, col 8 [E090] Module 'list' is ambiguous: 2 different files answer to that name. standard library …/lib/std/collections/list.tr next to b.tr ./list.tr Hint: A module may not share its name with a standard-library module. Rename ./list.tr (for example to 'my_list.tr') and import it by the new name.
The same file found twice (the source directory is the current directory) is one candidate, not two.
A single-file compile inside a project cannot see the project’s dependencies.
When an import fails there and a turf.toml exists in the file’s directory or
above, the error says so:
$ turf compile src/main.tr ── error at line 1, col 1 [E027] Could not find module 'geometry'. Hint: Make sure the file 'geometry.tr' exists in the standard library or your current directory. This file is inside a TurfLang project (…/sf/turf.toml). Its dependencies are only visible to `turf build`, which compiles the project with its package graph.
The compiler only checks that a manifest file exists; it never reads it.
Use turf build, turf run or turf check (with no file) in a project.
5. turf.lock
turf.lock belongs to the root package (the one you build). A dependency’s
own turf.lock is ignored. It is generated, and you commit it.
# This file is generated by turf. Commit it; do not edit it.
version = 1
[[package]]
name = "jsonx"
source = "git+…/up/jsonx.git"
ref = "tag=v1.0.0"
commit = "dec803e73fd04df31be79d859828efe595c14c38"
hash = "sha256:c2a61e5dbd09d27985855ab6c1ffacbe45ab24789738592e07dbbec29c1e8d4a"
-
A git entry pins the requested ref, the resolved commit and a SHA-256 of the fetched tree (independent of file times and permissions). A path entry (
source = "path+../geometry") records where, and no hash, because a path dependency is live source. Packages are listed sorted by name. -
The hash is checked on every build. If a file in the cached tree has been edited, or the cache is corrupt, the build stops:
turf: the cached tree of 'jsonx' (…/cache/src/jsonx-1d0f2d73…) does not match turf.lock (expected sha256:da455b69…, found sha256:618cac84…): it was modified or corrupted hint: delete …/cache/src/jsonx-1d0f2d73… and build again to re-extract it; if turf.lock itself is wrong, 'turf update jsonx' -
turf buildnever moves a pin. Atagthat was re-pointed upstream, or abranchwith new commits, is not noticed: the build keeps using the locked commit. Onlyturf updatemoves it:Terminal$ turf update turf: fetching jsonx (…/up/jsonx.git) jsonx de7262e335ed -> 1d0f2d7317cf $ turf update turf: fetching jsonx (…/up/jsonx.git) jsonx unchanged
turf update jsonxupdates just that dependency. Arevresolves to the same commit, so updating it changes nothing (lib1 unchanged). Apathdependency, the root package and an unknown name are refused:turf: 'geometry' is a path dependency; it is always used as it is on disk turf: 'app' is the project's own package, not a dependency turf: 'nosuch' is not a package in this project's dependency graphWith no git dependency at all,
turf updateprintsno git dependencies to update. It needs the network, so--offlineis not accepted. -
If a dependency is added to
turf.toml, the nextturf buildlocks it.turf.lockis written before the compiler runs, so a program that then fails to compile still leaves the updated lock behind.
--locked and --offline
Both apply to build, run, check and fetch.
-
--locked:turf.lockmust already be exactly what this build needs, or it is an error and nothing is written. This is the CI flag.turf: turf.lock is out of date, and --locked forbids changing it (nothing was written): added 'extra' (git+…/up/extra.git branch=main) hint: run `turf build` or `turf fetch` without --locked, and commit the new turf.lock turf: --locked needs turf.lock, and there is none at …/app/turf.lock hint: run `turf build` or `turf fetch` once without --locked and commit turf.lock -
--offline: run nogitnetwork command. A dependency that is not both locked and cached is an error naming it:turf: dependency 'extra' (…/up/extra.git, branch=main) is not in turf.lock, so it needs the network; run without --offline turf: dependency 'jsonx' (…/up/jsonx.git, tag=v1.0.0) is locked (commit 1d0f2d7317cf) but neither its tree nor the commit is cached, so it needs the network; run without --offline
--locked is not --offline. With a manifest the lock does not cover,
--locked alone may still clone or fetch (it fetched extra above) before it
reports the difference. A CI check that must not use the network passes both:
turf build --locked --offline. The projects in examples/
build that way against their committed lock files.
6. The cache
A git dependency is fetched once into a cache and extracted from there:
<cache>/
git/<16 hex digits>/ one bare mirror per repository URL (all history and refs)
src/<name>-<full commit>/ the extracted tree a build reads
-
The cache is
$TURF_CACHE_DIRif set. Otherwise it is theturfdirectory of your platform’s user cache directory: on Linux$XDG_CACHE_HOME/turf, or~/.cache/turfif that is unset. -
It is disposable: deleting it only costs a re-fetch. Deleting one
src/<name>-<commit>/directory re-extracts it from the mirror, offline: after the tampered tree above is deleted,turf build --offlinerebuilds it. -
gitmust be onPATHfor every git dependency, cached and locked or not, so that a build does not work on one machine and fail on another because of what happens to be cached:turf: git dependencies need the 'git' program, which is not on PATH ('lib1' in …/app/turf.toml)On macOS,
gitis not there until you install it./usr/bin/gitexists on every Mac, but it is a placeholder for the Xcode Command Line Tools and does nothing without them. Compiling and running Turf programs needs neither Xcode nor a C toolchain; git dependencies need a realgit(xcode-select --install, orbrew install git).turftells the two apart:turf: git dependencies need the 'git' program, and /usr/bin/git is only macOS's placeholder for the Xcode Command Line Tools, which are not installed; install git with `xcode-select --install` or `brew install git` ('lib1' in …/app/turf.toml)turfruns the systemgitandturf.lockrecords the URL exactly as you wrote it in the manifest. -
The tree is
git archiveoutput, not a checkout. A path markedexport-ignorein.gitattributesis absent, and a submodule is an empty directory (submodules are not fetched). For a repository with both, the extracted tree is:TREE/.gitattributes TREE/.gitmodules TREE/src/lib.tr TREE/vendored (an empty directory: vendored/data.txt is not there)(
src/skip.tr, markedexport-ignore, is missing.)A package that marks a file it needs
export-ignore, or depends on a submodule, is fine as a checkout and broken as a dependency. -
Every build reads every byte of every git dependency’s tree to re-hash it, so the cost grows with the dependencies’ size.
7. Commands
Project commands work anywhere inside a project (they walk up to the nearest
turf.toml) and run the compiler from the project root, so src/main.tr in a
diagnostic means the same file wherever you typed the command. Outside a
project they say so:
$ turf build turf: not inside a TurfLang project (no turf.toml here or in any parent directory); use 'turf new <name>', or 'turf compile <file.tr>' for a single file
That is exit status 2, the status of a usage error; a build or compile failure is 1.
| Command | What it does |
|---|---|
turf new <name> [--lib] |
Create <name>/ with a manifest, a starter source file and a .gitignore. |
turf init [--lib] [name] |
The same in the current directory. |
turf build [--offline] [--locked] [flags] |
Resolve dependencies, write build/package-graph.json, compile. A program becomes build/<name>; a library is checked. |
turf run [--offline] [--locked] [flags] [-- args] |
turf build, then run build/<name>, passing the arguments after --, in the directory you typed the command in. The exit status is the program’s. A library without src/main.tr says there is nothing to run and how to add one (section 1; exit 1). |
turf check [--offline] [--locked] [flags] |
Every diagnostic, no executable. |
turf clean |
Delete build/. Nothing to delete is not an error. |
turf graph [--project <dir>] [--offline] [--locked] |
Print the package graph (the JSON turf build writes to build/package-graph.json) on stdout, including the root’s tests/ when the project has one. Writes nothing (no turf.lock, no build/) and needs no compiler. This is what an editor asks for, so that import there resolves as in turf build. |
turf fetch [--offline] [--locked] |
Do everything build does up to turf.lock, without compiling. Prints one line per git dependency: jsonx de7262e335ed (tag=v1.0.0). Needs no compiler. |
turf update [name…] |
Move git dependencies to what their refs point at now (section 5). |
turf add <name> --path <dir> / --git <url> (--tag|--branch|--rev) <ref> |
Add a dependency (section 3). |
turf remove <name> |
Remove one. |
turf publish |
Reserved. There is no registry in 2.0. |
turf test [path...] [--offline] [--locked] [--jobs n] [--timeout s] [--exclude path] |
Compile and run every test under tests/, or only the files and directories named; an error if there is no tests/ (section 1, Tests). |
turf fmt, turf explain, turf migrate, turf dump, turf compile |
Work on files, in or out of a project (CLI & Diagnostics). turf fmt --write with no path formats the project’s src/ and tests/. |
Compiler flags are forwarded. turf build -O2, turf build -g and
turf run -O0 reach the compiler unchanged. -o and --package-graph are
refused in a project, because turf owns both; --offline and --locked
are turf’s own and are not forwarded:
$ turf build -o x turf: a project build writes build/<name> itself, so -o is not accepted; use 'turf compile -o …' to name the executable $ turf build --package-graph x.json turf: turf writes the package graph itself (build/package-graph.json); --package-graph is not accepted here
Every turf build recompiles everything, because the compiler has no
incremental mode, and prints the compiler’s own Compiled to … lines. When a
program does not compile, the first failing compiler status ends the command
and is its exit status. A compiler diagnostic is never rewritten or
re-rendered by turf.
turf publish and a mistyped command:
$ turf publish turf: the package registry is not part of TurfLang 2.0; publish a library by pushing a git tag (…) $ turf biuld turf: unknown command 'biuld' did you mean 'build'? run `turf help` for the list of commands
8. unsafe module; in dependencies
A module that begins unsafe module; may use raw pointers (int*) outside an
unsafe { } block. In a project, a dependency’s unsafe module; is honored
only if the root manifest lists that dependency in [unsafe] allow:
$ turf build ../ffi/src/rawmod.tr (1 error) ── error at line 3, col 5 [E130] Unsafe pointer use! Raw pointer type 'int*' cannot be used outside an 'unsafe' block. Hint: Wrap this in 'unsafe { }', or use a safe reference ('&int') instead if you don't actually need a possibly-null raw pointer.
[dependencies]
ffi = { path = "../ffi" }
[unsafe]
allow = ["ffi"]
With that line the same build compiles.
-
The root package’s own modules are honored without listing. You wrote them, so there is nothing to consent to. (A program with its own
unsafe module;builds with no[unsafe]table.) -
Listing is not transitive. The root lists every package, direct or indirect, that needs it.
-
A dependency cannot allow itself. An
[unsafe]table in a dependency’s ownturf.tomlis ignored; the build above still fails with E130. -
--allow-unsafe-modulesdoes not apply in a project. It is a single-file flag, and passing it toturf buildstill fails with the same E130, because one global switch would grant every dependency at once, which is the consent[unsafe] allowmakes explicit. -
A name in
allowthat is not in the graph, or the root package’s own name, is an error:turf: …/turf.toml: [unsafe] allow names 'nope', which is not a package in this project's dependency graph hint: list only packages the build actually depends on, directly or transitively turf: …/turf.toml: [unsafe] allow lists the root package 'app': its own modules need no listing
The standard library is always trusted.
9. Distributing
- A library is a git tag. Push
v0.1.0; consumers write{ git = "…", tag = "v0.1.0" }. There is nothing to upload or register, and a consumer’sturf.lockpins the exact commit and tree. - A program is one executable: copy
build/<name>somewhere. The Turf runtime is linked into it statically, andlddon one lists onlylibm,libcand the loader. How the program was linked depends on your install:turf versionprints alinkerline. Withembedded-lldand abundled-runtime, a program that uses no C library beyond the C runtime is linked without any C toolchain on the machine; withsystem-cc,turfcruns the machine’scc(a build from source).
10. Not in 2.0
- A registry, semver ranges,
turf publish. Publish a library by pushing a git tag. - Workspaces (several packages in one manifest), build scripts, and
subdir(a package that lives in a subdirectory of a git repository: the repository’s root must be the package). - Multiple versions of one package in one build.
- Separate compilation and a binary package format: a build compiles every
module of every package from source. This is what lets a generic function of
geometrybe called with a type onlyplotterdeclares. - Renaming a dependency, per-package compiler flags, and
pub(package). std::-qualified imports to spell which candidate an ambiguous import means: today the E090 tells you to rename.- Cross-linking for another machine (CLI & Diagnostics)
and a filter for
turf testthat selects tests by name rather than by path.
The internals of module resolution are in
docs/architecture/modules_and_scope_resolution.md.