Outcome: Assemble a locked two-module package, run a selected test along with the package entry, and execute its Python artifact without the .tpz project or Topaz compiler.
Prerequisite: Complete Failures and Resources, install topaz, and ensure Python 3.11 or newer is available.
Start with the ordinary scaffold
In the previous five lessons, every exercise ran out of a single source file without a project manifest. In this final guide, you build a complete multi-module package with locked dependencies and targeted build artifacts. That requires a project manifest configuration file named topaz.toml.
topaz.toml serves as the project manifest configuration. It records package identity, specifies language versions, defines entry points, configures build targets, and declares dependencies. Single-file scripts do not need manifest files because they do not declare dependencies or build targets. Multi-module projects require topaz.toml so the compiler can resolve file trees and target settings.
From the directory intended for your project, run:
topaz init --root study-plan
cd study-planRunning topaz init --root study-plan creates a new project directory named study-plan. Inside that directory, init generates two initial files, topaz.toml and src/main.tpz. init refuses to overwrite existing projects. It creates topaz.toml and src/main.tpz, but does not create a lockfile or test file. Navigating into study-plan with cd study-plan positions your terminal inside the project root where subsequent Topaz commands expect to run.
You must initialize the scaffold directory first and navigate into it before adding source files or running build tools. Replace the entry source code and add the module and selected test so the tree structure becomes:
study-plan/
├── topaz.toml
├── src/
│ ├── main.tpz
│ └── plan.tpz
└── tests/
└── plan.tpzCreating src/plan.tpz and tests/plan.tpz establishes a multi-file layout. The manifest topaz.toml sits at the project root to govern all files in src/ and tests/.
The scaffold manifest configuration is:
[package]
name = "study-plan"
version = "0.1.0"
language = "5.19"
entry = "src/main.tpz"
[build]
target = "native"
deterministic = true
[dependencies]
std = "5.19"This manifest establishes project settings. [package] sets the package name to "study-plan", the version to "0.1.0", the language version to "5.19", and designates src/main.tpz as the main application entry point. [build] selects the compilation target, while [dependencies] declares standard library requirements.
Create the two modules
Rather than keeping all code inside a single file, this project divides code across two separate modules, src/plan.tpz and src/main.tpz. Separating logic into two modules isolates reusable domain models and operations from application entry logic. This division keeps domain code decoupled from program initialization and command-line handling.
import and export control visibility across module boundaries. export explicitly exposes selected records or functions to other modules. Symbols without export remain private internal implementation details. import pulls exposed symbols into another module scope so they can be referenced directly.
src/plan.tpz defines the domain model and reusable operations:
export record StudyTask {
title: string,
minutes: int,
done: bool = false,
}
function parseMinutes(text: string) -> Result<int, string> {
match toInt(text) {
case Some(value) if value > 0 => Ok(value)
case _ => Err("minutes must be a positive integer")
}
}
export function makeTask(
title: string,
minuteText: string,
done: bool = false,
) -> Result<StudyTask, string> {
let minutes = parseMinutes(minuteText)?
Ok(StudyTask { title: title, minutes: minutes, done: done })
}
export function summarize(tasks: Array<StudyTask>) -> string {
let mut remaining = 0
for task in tasks {
if !task.done {
remaining = remaining + task.minutes
}
}
"tasks={tasks.length}, remaining={remaining} minutes"
}Only symbols marked with export are visible to other modules. parseMinutes remains an internal implementation detail.
src/main.tpz imports those exported symbols and defines the package entry point:
import src.plan { StudyTask, makeTask, summarize }
export function main(args: Array<string>, stdin: string) -> Result<int, string> {
let tasks: Array<StudyTask> = [
makeTask("Run first program", "10", true)?,
makeTask("Build application", "25", false)?,
]
print(summarize(tasks))
Ok(0)
}The explicit main function receives command-line arguments and standard input, returning an exit code or an error message. While this application requires neither input stream, maintaining this signature keeps the application interface clear. src/main.tpz imports StudyTask, makeTask, and summarize from src.plan to create sample tasks and output the summary string.
With production modules established, create a dedicated test file in tests/plan.tpz. Adding tests/plan.tpz:
import src.plan { StudyTask, makeTask, summarize }
match makeTask("Read syntax map", "15") {
case Ok(task) => assert(task.minutes == 15, "valid minutes")
case Err(message) => assert(false, message)
}
match makeTask("Read syntax map", "later") {
case Ok(_) => assert(false, "invalid minutes were accepted")
case Err(message) => assert(
message == "minutes must be a positive integer",
"invalid minutes",
)
}
let tasks: Array<StudyTask> = [
StudyTask { title: "Run first program", minutes: 10, done: true },
StudyTask { title: "Build application", minutes: 25 },
]
assert(
summarize(tasks) == "tasks=2, remaining=25 minutes",
"summary",
)The isolated TestHost environment provides the standard assert(...) function for testing. This test file validates correct input, handles invalid input, and verifies the final summary without altering production code.
Lock, format, check, test, and run
Topaz requires a resolved lockfile before running verification checks or executing code. You create the lockfile first so that all subsequent commands evaluate source code against explicit locked manifest state.
Create and inspect the dependency lockfile:
topaz lock --root .Running topaz lock --root . reads topaz.toml in the current directory, resolves package settings, and generates a new file named topaz.lock.
The generated topaz.lock file contains:
[[package]]
name = "study-plan"
version = "0.1.0"
source = "root"
manifest_hash = "sha256:780ff549a9fb9b09d27c62df8ce48e70c1fc7b33d6d429e6bb826685f42e7615"This file records package metadata and the calculated manifest hash.
Now run the quality verification commands in sequence. You format code first to check formatting compliance, check types second to validate resolution against topaz.lock, and execute tests third to verify logic.
Run the local quality verification loop in order:
topaz fmt --check --root .
topaz check --root . --locked
topaz test tests/plan.tpz --root . --lockedfmt --check flags formatting differences without modifying files. check validates the resolved unit. The explicit path targets a single test entry point. Running these three commands verifies formatting compliance, resolves source dependencies against topaz.lock, and executes the assertions in tests/plan.tpz.
The command produces the following test output:
tests/plan.tpz: test-okThis output confirms that all test assertions in tests/plan.tpz passed successfully.
Once formatting, unit resolution, and tests have passed, execute the package entry point.
Run the package entry point:
topaz run --root . --lockedRunning topaz run --root . --locked executes the entry point defined in topaz.toml (src/main.tpz) using topaz.lock.
The command outputs:
tasks=2, remaining=25 minutesThis output displays the evaluated task summary printed by main.
Build and run without the Topaz project
With local verification and package execution complete, compile the project into a target artifact. Building comes after testing and running to ensure that only verified source code is packaged into distribution files.
The scaffold defaults to native compilation targets, but passing an explicit flag selects an alternative target. Build the Python distribution for this course:
topaz build --target python --root . --locked --out-dir ../study-plan-productRunning topaz build --target python --root . --locked --out-dir ../study-plan-product creates the directory ../study-plan-product and generates Python distribution files inside it.
Python is selected intentionally for this step. It requires Python 3.11 or newer at runtime without requiring a Rust installation for new users. The managed artifact directory contains the following files:
study-plan-product/
├── GENERATED-OUTPUT-NOTICE.txt
├── LICENSE
├── NOTICE
├── program.py
├── topaz-artifact.json
└── topaz_py_rt.pyThis file tree lists all generated build files in study-plan-product/. program.py contains the compiled program logic, topaz_py_rt.py provides runtime support modules, topaz-artifact.json stores artifact metadata, and legal notices complete the distribution directory.
To verify that the artifact runs independently without project source code or compiler tools, move the Topaz project directory away before executing program.py.
Move the Topaz project directory away, then execute the application from the artifact directory:
cd ..
mv study-plan source-unavailable
cd study-plan-product
python3 program.pyExecuting cd .. steps out of the project directory. Running mv study-plan source-unavailable renames the source project directory so its files are unavailable. Entering study-plan-product with cd study-plan-product puts the shell into the artifact directory. Executing python3 program.py runs the compiled Python program directly using Python 3.
In PowerShell, replace the move command with Rename-Item study-plan source-unavailable and run python program.py. The execution output matches the package output:
tasks=2, remaining=25 minutesThis output matches the earlier result of topaz run, proving that the output artifact executes identically without source .tpz files.
The artifact directory includes generated Python source code and runtime support modules. In this context, "without source" means the directory contains no .tpz files and operates independently of the Topaz project, compiler, registry, or build workspace.
Keep the boundaries straight
- A file refers to a single
.tpzsource file. - A module represents the namespace exposed by a file through
export. - A unit is the set of Topaz source files resolved from an entry point and its imports.
- A package defines product identity and locked dependencies inside
topaz.tomlandtopaz.lock. - A target specifies the output compilation format, such as Python.
- An artifact is the managed build result prepared for distribution and execution.
Decision: source loop or product boundary?
Use check, test, and run during active development to obtain rapid feedback at the source level. Use build when you require the final deliverable, and verify that deliverable outside the project directory. Executing code exclusively within the source tree validates program logic, but it does not confirm deployment boundaries.
Try this
Modify the minute text for the second task in src/main.tpz from "25" to "30". Which expectations must change before the full execution loop passes?
Answer
Executing the package entry point and the Python artifact will output tasks=2, remaining=30 minutes. The selected test defines its own 25-minute task fixture, so its existing summary remains valid and passes. This separation demonstrates that the test owns its test fixture rather than depending on main.
Ready to continue when
You can explain all six boundaries defined above, regenerate and enforce the lockfile, run fmt --check, check, the selected test, and run, and execute the Python artifact after the .tpz project source is moved.
Use Syntax at a Glance as your reference map, then deepen your understanding with Modules & Visibility, Application Loop, and Artifacts & Deployment.