Targets
A target is one buildable unit declared inside a Cabin
package. A package may declare any number of targets; each
target has a [target.<name>] table with at least a type and
usually a sources list. Header-only libraries are the main
exception.
This document covers the target kinds Cabin supports today, when each one is built, and where their outputs land.
Target kinds describe artifact role only. They do not encode
a source-language contract: a single target may freely mix C and
C++ sources. The compile driver is selected per source file based
on its extension, and the link driver is selected from the direct
and transitive source-language closure. If you want to keep a
target C-only, rely on review, convention, or external linting;
Cabin will not reject C++ sources just because the target is
named, say, executable.
Supported target kinds
| Type | Output | Built by cabin build (default) |
Run by cabin test |
|---|---|---|---|
library |
static archive (.a) |
yes | no |
header_only |
none | yes — graph/interface only | no |
executable |
linked executable | yes | no |
test |
linked executable | no — only when explicit | yes |
example |
linked executable | no — only when explicit | no |
header_only libraries declare include_dirs instead of
sources; declaring sources on a header_only target is
rejected at manifest-load time. The other kinds all carry a
sources list of .c and/or C++ source files.
C and C++ source languages
Cabin treats C and C++ as related but distinct source languages. Every source file is classified by its filename extension:
| Extension | Language |
|---|---|
.c |
C |
.cc, .cpp, .cxx, .c++, .C |
C++ |
The planner then:
- compiles
.csources with the C compiler driver (cc/clang/gcc) and-std=c11; - compiles C++ sources with the C++ compiler driver
(
c++/clang++/g++) and-std=c++17; - chooses the link driver by walking the target's own objects plus every transitively reachable library object: if any object came from a C++ source, link with the C++ driver; otherwise link with the C driver. Pure-C executables therefore stay off the C++ runtime; mixed targets inherit the C++ runtime as required.
Sources whose extension is not recognized produce an explicit
unrecognized extension build error so a misnamed file never
silently picks the wrong compiler.
[package] does not carry a language field — declaring one is
rejected as an unknown field. There is no language = "c" /
language = "cpp" switch on a target either. Per-target build
behavior is decided by per-source classification (.c → C,
.cc / .cpp / .cxx → C++) plus the target kind.
Manifest syntax
Every target is a [target.<name>] table. The type field
selects the kind; the rest of the fields apply to all kinds.
[target.demo]
type = "library"
sources = ["src/lib.cc"]
include_dirs = ["include"]
[target.demo_test]
type = "test"
sources = ["tests/lib_test.cc"]
deps = ["demo"]
[target.hello_example]
type = "example"
sources = ["examples/hello.cc"]
deps = ["demo"]
Common fields:
sources— source files relative to the package root.include_dirs— public include directories. Consumers of this target inherit them throughdeps.defines— preprocessor defines applied to this target's compile actions.deps— references to other targets:- same-package by bare name:
deps = ["lib"]; - cross-package by name (resolves to the dep package's default
libraryorheader_onlytarget):deps = ["fmt"]; - qualified
package:target:deps = ["fmt:fmt"].
Cross-package deps must reach the consumer through a [dependencies]
edge. [dev-dependencies] are not auto-linked into ordinary targets.
Default-build vs. explicit selection
cabin build enumerates every library, header_only, and
executable target in the selected packages. Header-only targets
participate in dependency/interface propagation but emit no
compile, archive, or link action. Dev-only kinds (test,
example) are excluded from the default enumeration; they reach
the build graph in two ways:
cabin testselects everytesttarget in the selected packages, builds the chosen test executables, and runs them;- any
testorexampletarget may appear in another target'starget.<X>.deps, in which case it is pulled into the build closure as a transitive dependency.
Cabin does not expose a single-target selector flag on
cabin build or cabin test. Narrow the build or test scope
by narrowing the package selection with --package /
--workspace / --exclude.
This keeps cabin build predictable: a package can ship tests
and examples without forcing every consumer's CI to build them.
Output paths
Cabin lays test / example executables out the same way as
ordinary executable targets:
<build-dir>/<profile>/packages/<pkg>/<target>
<build-dir> defaults to build/; <profile> is the resolved
profile name (dev by default, release for --release, or
any custom profile declared in [profile.<name>]).
Two targets with the same <target> name in the same package
would collide here; the planner rejects duplicate target names
within a package, so this is a static guarantee.
Dependency-kind policy summary
| Kind | cabin build |
cabin test |
|---|---|---|
[dependencies] |
included | included |
[dev-dependencies] |
declaration-only | included for selected packages |
`system = true deps` |
active normal declarations are probed with pkg-config; flags merge into build configuration |
same, plus selected packages' dev-kind system declarations |
cabin test activates the selected packages' [dev-dependencies]
as real graph edges. The activation never propagates: a transitive
dep's own dev-deps stay declaration-only. cabin build continues
to ignore all dev-deps so ordinary builds are unaffected.
Packaging behavior
cabin package includes every declared source file in the
deterministic source archive — including test and example
sources. Consumers of the published package keep the right to
rebuild them locally.
The published canonical metadata records package-level surfaces
such as dependencies, features, profiles, toolchain/build
settings, checksum, and source location. It does
not contain a target list; target declarations remain in the
archived cabin.toml and are visible to local tooling through
cabin metadata.
Limitations
The test surface is intentionally small:
- no test discovery inside binaries (no GoogleTest / Catch2 / doctest output parsing);
- no XML / JUnit output;
- no
cabin run --example, and no single-example selector oncabin build—exampletargets only reach the build graph as a transitive dep of another selected target; - no automatic
tests//examples/discovery; - no parallel test execution.