Targets
Terminology: This specification uses RFC 2119 keywords (MUST, SHOULD, MAY, etc.) to indicate requirement levels.
This document describes the target system in Structyl.
Overview
A target is a buildable unit in a Structyl project. Targets are unified—both language implementations and auxiliary tools use the same configuration model.
Empty targets configuration: When targets is absent or empty ({}), Structyl uses auto-discovery mode to scan for toolchain marker files. See project-structure.md for details.
Target Name Constraints
Target names (the keys in the targets object) MUST follow these rules:
- Pattern:
^[a-z][a-z0-9-]*$(lowercase letters, digits, hyphens only) - Length: 1-64 characters (enforced both at runtime and by JSON Schema for IDE validation)
Target Name Flexibility
Target names allow trailing hyphens and consecutive hyphens (e.g., my-target-, my--target), unlike project names which have stricter rules for package registry compatibility. Target names are internal identifiers and don't flow to external systems.
Invalid target names cause exit code 2. CLI output: structyl: targets.{name}: target name must match pattern ^[a-z][a-z0-9-]*$ (lowercase letters, digits, hyphens)
Reserved names: The target name all is reserved and MUST NOT be used. This prevents ambiguity with commands that operate on all targets (e.g., structyl build all vs. structyl build for a target named "all"). Using a reserved name causes exit code 2. CLI output: structyl: targets.all: "all" is a reserved name
Target Types
| Type | Description | Included In |
|---|---|---|
language | Programming language implementation | build, test, demo |
auxiliary | Supporting tools (docs, images, etc.) | build only |
Language Targets
Language targets represent implementations of your library in different programming languages.
Characteristics:
- Included in
structyl testandstructyl demo - Expected to have reference test integration
Auxiliary Targets
Auxiliary targets are supporting tools that aren't language implementations.
Examples:
- Image generation (
img) - PDF documentation (
pdf) - Website (
web) - Code generation (
gen)
Characteristics:
- Only included in
structyl build - May have dependencies on other targets
- No test/demo expectations
- When
structyl testorstructyl demoruns on all targets, auxiliary targets are skipped (see commands.md for skip behavior)
Target Configuration
Minimal Configuration
With toolchain auto-detection:
{
"targets": {
"rs": {
"type": "language",
"title": "Rust"
}
}
}Structyl detects Cargo.toml in rs/ and uses the cargo toolchain.
Explicit Toolchain
{
"targets": {
"rs": {
"type": "language",
"title": "Rust",
"toolchain": "cargo"
}
}
}With Command Overrides
{
"targets": {
"cs": {
"type": "language",
"title": "C#",
"toolchain": "dotnet",
"commands": {
"test": "dotnet run --project Pragmastat.Tests",
"demo": "dotnet run --project Pragmastat.Demo"
}
}
}
}Full Configuration
{
"targets": {
"cs": {
"type": "language",
"title": "C#",
"toolchain": "dotnet",
"directory": "cs",
"cwd": "cs",
"vars": {
"test_project": "Pragmastat.Tests"
},
"env": {
"DOTNET_CLI_TELEMETRY_OPTOUT": "1"
},
"commands": {
"test": "dotnet run --project ${test_project}",
"demo": "dotnet run --project Pragmastat.Demo"
}
},
"pdf": {
"type": "auxiliary",
"title": "PDF Manual",
"directory": "pdf",
"depends_on": ["img"],
"commands": {
"build": "latexmk -pdf manual.tex",
"clean": "latexmk -C"
}
}
}
}Configuration Fields
| Field | Type | Default | Description |
|---|---|---|---|
type | string | Required | "language" or "auxiliary" |
title | string | Required | Display name (1-64 characters, non-empty) |
toolchain | string | Auto-detect | Toolchain preset (see toolchains.md) |
toolchain_version | string | None | Override mise tool version for this target |
Type Requirement:
- In Explicit mode (targets defined in
.structyl/config.json):typeMUST be specified for all targets - In Auto-Discovery mode:
typeis inferred from the slug—known language slugs becomelanguage, others becomeauxiliary
The Default Language Slugs table defines which slugs map to which types during auto-discovery.
Title Validation:
Invalid title values cause exit code 2 with one of:
targets.{name}.title: required(empty string)targets.{name}.title: must be 64 characters or less(exceeds maximum)
| Field | Type | Default | Description |
|---|---|---|---|
directory | string | Target key | Directory path relative to root |
cwd | string | directory | Working directory for commands |
commands | object | From toolchain | Command definitions/overrides |
vars | object | {} | Custom variables for command interpolation |
env | object | {} | Environment variables |
depends_on | array | [] | Targets that must build first |
demo_path | string | None | Path to demo source (for doc generation) |
Demo Path Configuration
The demo_path field specifies a custom path to the demo source file used by the $DEMO$ placeholder during documentation generation. When not specified, Structyl looks for a demo file at the conventional location <target_directory>/demo.<extension>, where the extension is determined by the target's toolchain (e.g., .py for Python toolchains, .go for Go, .rs for Cargo).
Example — custom demo path:
{
"targets": {
"py": {
"type": "language",
"title": "Python",
"toolchain": "uv",
"demo_path": "py/examples/quickstart.py"
}
}
}With this configuration, the $DEMO$ placeholder in README templates resolves to the content of py/examples/quickstart.py instead of the default py/demo.py.
Missing demo file: If no demo file exists at the default location and demo_path is not configured, the $DEMO$ placeholder in README templates is replaced with an empty string. This is not an error condition.
Demo file markers: If the demo file contains structyl:demo:begin and structyl:demo:end markers, only the content between these markers is extracted:
# Full example with setup code
import mylib
# structyl:demo:begin
result = mylib.calculate([1, 2, 3, 4, 5])
print(f"Result: {result}")
# structyl:demo:end
# Cleanup code...Toolchains
Toolchains provide default command implementations. See toolchains.md for the full reference.
Available Toolchains
| Toolchain | Ecosystem | Auto-detect File |
|---|---|---|
cargo | Rust | Cargo.toml |
dotnet | .NET (C#/F#) | *.csproj, *.fsproj |
go | Go | go.mod |
npm | Node.js | package.json |
pnpm | Node.js | pnpm-lock.yaml |
yarn | Node.js | yarn.lock |
bun | Bun | bun.lockb |
python | Python | pyproject.toml, setup.py |
uv | Python (uv) | uv.lock |
poetry | Python (Poetry) | poetry.lock |
gradle | JVM | build.gradle, build.gradle.kts |
maven | JVM | pom.xml |
make | Generic | Makefile |
cmake | C/C++ | CMakeLists.txt |
swift | Swift | Package.swift |
Complete Toolchain Reference
This table shows commonly used toolchains. For the complete list of 27 built-in toolchains with command mappings and auto-detection markers, see toolchains.md.
Toolchain Auto-Detection
When toolchain is not specified, Structyl checks for marker files in the target directory:
rs/
├── Cargo.toml ← detected as "cargo"
└── src/Auto-detection is best-effort. Explicit toolchain declaration is RECOMMENDED.
Commands
Commands are defined via the commands field or inherited from the toolchain. See commands.md for full details.
Command Inheritance
- If
toolchainspecified → inherit all toolchain commands commandsobject → override specific commands- Missing command → error at runtime
Command Forms
{
"commands": {
// String: shell command
"build": "cargo build",
// Variant: colon naming convention
"build:release": "cargo build --release",
// Array: sequential execution
"check": ["lint", "format-check"],
// Null: explicitly disabled
"bench": null
}
}Per-command working directory and environment overrides are not supported. Use target-level cwd and env fields instead.
See commands.md for the variant naming convention.
Dependencies
Targets can declare dependencies on other targets:
{
"targets": {
"img": { "type": "auxiliary", "title": "Images" },
"pdf": {
"type": "auxiliary",
"title": "PDF",
"depends_on": ["img"]
},
"web": {
"type": "auxiliary",
"title": "Website",
"depends_on": ["img", "pdf"]
}
}
}Execution Order
When running structyl build:
- Build targets with no dependencies first
- Build targets whose dependencies are satisfied
- Language targets can build in parallel (no implicit dependencies)
- Auxiliary targets build in dependency order
For the example above:
1. img (no dependencies)
2. pdf (depends on img) + language targets (parallel)
3. web (depends on img, pdf)Parallel Execution
Targets are scheduled for execution in dependency order. When STRUCTYL_PARALLEL=1, targets execute sequentially with strict dependency guarantees. When STRUCTYL_PARALLEL > 1, targets at the same dependency depth MAY execute concurrently (see Known Limitation below).
Execution model:
- A target becomes eligible when all targets in its
depends_onlist have been scheduled - Multiple eligible targets execute in parallel (up to
STRUCTYL_PARALLELworkers) - Language targets without explicit dependencies are immediately eligible
- Note: Parallel mode does not guarantee dependency completion before dependent execution
Example:
{
"targets": {
"gen": { "type": "auxiliary" },
"cs": { "type": "language", "depends_on": ["gen"] },
"py": { "type": "language", "depends_on": ["gen"] },
"rs": { "type": "language" }
}
}Execution order:
genandrsstart immediately (no dependencies)- When
gencompletes,csandpybecome eligible and start in parallel
Known Limitation: Parallel Execution and Dependencies
When STRUCTYL_PARALLEL > 1, Structyl DOES NOT guarantee that targets in depends_on complete before the dependent target starts execution. Topological ordering ensures dependencies are scheduled first, but the semaphore-based worker pool MAY execute dependent targets before their dependencies finish.
Formal Statement: Implementations requiring strict dependency ordering MUST use STRUCTYL_PARALLEL=1 or external orchestration.
Workarounds:
- Use sequential execution (
STRUCTYL_PARALLEL=1) - Ensure dependency relationships are idempotent (safe to re-run)
- Use external orchestration (mise, make) for strict dependency-aware parallelism
This is a known limitation tracked for future improvement.
STRUCTYL_PARALLEL Value | Behavior |
|---|---|
| Unset or empty | Default to number of CPU cores |
1 | Serial execution (one target at a time) |
2 to 256 | Parallel execution with N workers |
0, negative, >256, or non-integer | Falls back to CPU core count (with warning) |
Output Handling:
- Each target's stdout/stderr is buffered independently
- Output is printed atomically when the target completes
- Output order follows completion order, not start order
Failure Behavior:
- Fail-fast: First failure cancels all pending targets; running targets continue to completion
Note: There is no continue-on-error mode. Structyl delegates to mise for task execution, and mise stops on first failure.
Dependency Validation
At project load time, Structyl validates all target dependencies:
| Validation | Error Message |
|---|---|
| Reference to undefined target | target "{name}": depends on undefined target "{dep}" |
| Self-reference | target "{name}": cannot depend on itself |
| Circular dependency | circular dependency detected: {cycle} |
All dependency validation errors exit with code 2.
Circular Dependencies
Circular dependencies are detected and reported as configuration errors:
structyl: error: circular dependency detected: a -> b -> c -> aTarget Directory Validation
At project load time, Structyl validates that each target's directory exists:
| Condition | Error Message | Exit Code |
|---|---|---|
| Directory does not exist | target "{name}": directory not found: {path} | 2 |
| Directory is not a directory | target "{name}": path is not a directory: {path} | 2 |
Note:
{path}in directory validation errors is the resolved path (relative to project root), not the raw configured value. Ifdirectoryis not specified,{path}equals the target key.
Default Language Slugs
Structyl recognizes these slugs as language targets during auto-discovery:
| Slug | Language | Code Fence | Default Toolchain |
|---|---|---|---|
cs | C# | csharp | dotnet |
go | Go | go | go |
kt | Kotlin | kotlin | gradle |
py | Python | python | python |
r | R | r | — |
rs | Rust | rust | cargo |
ts | TypeScript | typescript | npm |
js | JavaScript | javascript | npm |
java | Java | java | gradle |
cpp | C++ | cpp | cmake |
c | C | c | cmake |
rb | Ruby | ruby | — |
swift | Swift | swift | swift |
scala | Scala | scala | gradle |
Custom slugs default to auxiliary type unless explicitly configured.
Target Operations
Single Target
structyl <command> <target> [args]
# Examples
structyl build cs
structyl test py
structyl build:release rsAll Targets
structyl <command> [args]
# Examples
structyl build # Build all targets
structyl test # Test all language targets
structyl clean # Clean all targetsFiltered Operations
# Build specific targets
structyl build cs py rs
# Build only language targets (explicit)
structyl build --type=language
# Build only auxiliary targets
structyl build --type=auxiliaryMeta Commands vs Target Commands
| Command | Scope | Notes |
|---|---|---|
structyl build | All targets | Respects dependencies |
structyl test | Language targets only | Parallel execution |
structyl demo | Language targets only | Parallel execution |
structyl clean | All targets | No dependency order |
structyl ci | All targets | Full pipeline |
Target Listing
structyl targets
Languages:
cs C# (dotnet)
go Go (go)
py Python (uv)
rs Rust (cargo)
ts TypeScript (pnpm)
Auxiliary:
img Image Generation
pdf PDF Manual (depends: img)
web Website (depends: img, pdf)Output Format Stability
The structyl targets output is designed for human readability and is explicitly unstable. Do not parse this output programmatically. See stability.md#unstable-may-change for the list of unstable interfaces.
Adding Custom Targets
- Create directory
- Either:
- Let Structyl auto-discover toolchain
- Add to
targetsin.structyl/config.jsonwith explicit configuration
Example—adding an image generation target:
{
"targets": {
"img": {
"type": "auxiliary",
"title": "Image Generation",
"commands": {
"build": "python scripts/generate_images.py",
"clean": "rm -rf output/images"
}
}
}
}structyl build img
structyl clean img