Compiler / Package Manifests (silk.toml)

Package Manifests (silk.toml)

This document specifies Silk’s package manifest format and how the silk compiler consumes it.

Manifests are a build/package concept (they are not part of the core language syntax). The language-level package / import / export semantics remain defined in packages imports exports.

For the broader package authoring/publication/consumption model, including third-party distribution channels, binary-only packages, and the rationale for the current manifest shape, see package distribution. This document describes the current implemented manifest format and current CLI behavior.

Manifest Discovery#

A package root directory MAY contain a manifest file with one of these names:

  • silk.toml

Rules:

  • When a manifest directory is provided (CLI via --package <dir>), the compiler looks for silk.toml in that directory.
  • When a manifest path is explicitly provided (CLI via --package <path>), the compiler reads exactly that file (and it MUST be named silk.toml).

Manifests are encoded as TOML v1.0.

Diagnostics#

When a manifest is malformed (invalid TOML syntax or an invalid manifest shape), the compiler reports a diagnostic with file, line, and column information and a caret snippet pointing at the offending token when possible.

Build Modules (build.slk)#

A package root directory MAY also contain a build module:

  • build.slk

When a build module is enabled (via the CLI or [build].build_module = true), the compiler compiles and runs the module and parses the manifest it emits in this format (used in place of reading silk.toml for the root package).

See build scripts.

Package Metadata ([package])#

Minimal manifest shape:

[package]
name = "my_app"
version = "0.1.0"

package.name (required)#

The package name used for package imports (for example import ui; or import ui from ui;) and as the default package name for modules that omit an explicit package ...; declaration.

name MUST be a valid Silk package path:

  • one or more identifiers separated by ::
  • each identifier matches [A-Za-z_][A-Za-z0-9_]*

Examples:

  • ui
  • my_app
  • my_app::core

package.version (optional)#

Package version string (recommended: Semantic Versioning such as MAJOR.MINOR.PATCH).

When building from a package manifest, the compiler surfaces this value to runtime code via std::runtime::build::version() (otherwise it defaults to "0.0.0").

When another package uses a dependency version = "..." constraint, the compiler interprets this field as a SemVer string. Reusable packages SHOULD therefore use SemVer-compatible versions.

Additional channel-agnostic metadata fields are supported under [package]:

  • description
  • license
  • homepage
  • repository
  • documentation
  • readme
  • authors
  • keywords

These fields are surfaced by silk package inspect and preserved in installed package manifests.

package.readme and package.documentation may be either:

  • ordinary package metadata strings (for example a hosted documentation URL), or
  • local package-root-relative file or directory paths.

Local metadata doc paths must stay inside the package root. Absolute paths and relative paths that escape the package root are rejected.

When a local path is used, silk man treats it as part of the package’s discoverable documentation surface when that package root is selected via --package, nearest-manifest discovery, or package-search-path resolution.

When silk build install packages a local package.readme or local package.documentation landing page, the installed manifest rewrites that field to a packaged copy under share/silk/docs/readme/... or share/silk/docs/documentation/... inside the installed package root so the overview/docs aliases remain self-contained after install.

When package.documentation points at a static [[target]] kind = "man" source, installed manifests rewrite that field to the installed share/man/man<section>/... path so silk man docs / silk man documentation continue to work after silk build install.

Package Documentation Discovery for silk man#

When silk man resolves a package root from silk.toml, it may discover package-authored documentation from that root in addition to source doc comments. Source-doc queries from silk man and silk doc --man only consider the root package’s own modules, not dependency docs in the same manifest graph:

  • local package.readme paths provide the package overview page,
  • local package.documentation paths provide a package documentation landing page,
  • and package man sources are discovered recursively under <root>/docs/man/, <root>/man/, <root>/share/man/, and installed sectioned roots such as <root>/share/man/man1/.

Current conventions:

  • Markdown man sources SHOULD be named <name>.1.md, <name>.3.md, or <name>.7.md.
  • Markdown pages under docs/man/ or man/ that omit an explicit section suffix default to section 7, which makes package-authored overview/concept topics easy to ship without man-specific filenames.
  • When package.documentation points at a directory, silk man looks for a <package>.md.
  • silk man --list and silk man --search include these package-local pages whenever a package root is already in scope.
  • Remote package.readme / package.documentation URLs remain valid metadata; silk man prints them as references rather than fetching them.

package.definitions (optional)#

Optional list of definition files (header-style prototype modules) for this package:

[package]
name = "my_lib"
definitions = ["defs/api.slk"]

Rules:

  • Each entry MUST be a path to a .slk (or .silk) file, relative to the manifest directory.
  • Definition files SHOULD consist of:
  • exported type declarations, and
  • declaration-only exported function prototypes (export fn name(...) -> T;) that describe the public API surface.
  • when a package expects downstream Formal Silk consumers to verify imported calls or import reusable theories without the implementation sources, the relevant exported theories and exported function/method contracts SHOULD be mirrored in these definition files too. See packages imports exports (“Prototype exports”).
  • The compiler does not treat definition files specially during ordinary builds; this field exists so tooling can locate an explicit “API surface” without scanning arbitrary source files.
  • silk build install uses this list when installing libraries into PREFIX/lib/silk/<package>/... so that the installed package remains importable (for example import my_lib;) via the system package search root.
  • Executable-only and manpage-only packages do not need [package].definitions; this field matters only when an installed package must expose a Silk import surface for library-style targets.

Distribution Payload ([dist])#

Packages may declare the distributable package payload separately from the module-set source files:

[dist]
include = ["defs/**/*.slk", "lib/**/*.a", "README.md"]
exclude = ["**/.DS_Store"]

Rules:

  • Patterns are evaluated against forward-slash (/) relative paths rooted at the manifest directory.
  • Supported glob syntax matches [sources]:
  • * matches any characters within a single path segment.
  • ** matches zero or more path segments.
  • When [dist] is omitted, package integrity hashing falls back to the source-oriented [sources] file set.
  • When [dist] is present, package integrity hashing covers:
  • the manifest bytes, and
  • every file selected by [dist].

This section is used by:

  • computePackageSha256String(...) / dependency sha256 verification,
  • silk package lint,
  • installed package manifests emitted by silk build install,
  • and package-graph loading / package inspection when installed packages expose Formal Silk bundles under share/silk/formal/....

Installed manifests also include compiler-emitted Formal Silk bundle files under share/silk/formal/<artifact-relative-path>/... when the built artifact carries exported Formal Silk surface.

Source Layout ([sources]: include / exclude)#

Packages may specify which .slk files belong to the package with glob patterns:

[sources]
include = ["src/**/*.slk"]
exclude = ["src/experimental/**"]

Rules:

  • Patterns are evaluated against forward-slash (/) relative paths rooted at the manifest directory.
  • Supported glob syntax:
  • * matches any characters within a single path segment.
  • ** matches zero or more path segments.
  • If include is omitted, the default is to include all **/*.slk under the manifest directory.
  • exclude patterns always remove files, even if they match an include.
  • When building a target, the target’s entry file MUST be included after applying include/exclude (or the build fails).

Dependencies ([dependencies])#

Dependencies are a table mapping dependency import names to dependency specs:

[dependencies]
ui = { path = "../libs/silk-ui", version = "^1.4.0", sha256 = "sha256:0123456789abcdef...", features = ["tui"] }
my.dep.b = { version = "^0.1.0" }

Fields:

  • The dependency key (ui above) is a local dependency import root used by quoted POSIX module specifiers. An exact root import such as import ui from "ui"; resolves to the dependency's default source module (src/lib.slk) or, for a binary-only dependency, to its single definition file. Subpath imports such as import form from "ui/form"; resolve under the dependency's source-module directory. The key MUST be a Silk identifier or dot-separated identifier path and MUST NOT contain :: or /.
  • Dotted dependency keys provide package-style namespacing for dependency roots: my.dep.b maps to quoted dependency paths rooted at "my/dep/b" and to a search-path candidate directory named my/dep/b.
  • The dependency key does not need to match the dependency's own manifest package.name. Quoted imports use the local dependency key; unquoted package-path imports such as import ui from vendor::ui; use the dependency manifest's package.name.
  • path (optional): local filesystem path to the dependency package root, resolved relative to the importing manifest directory when not absolute. When path is omitted, the dependency is resolved from the package search path (see “Dependency discovery via SILK_PACKAGE_PATH” below).
  • version (optional): SemVer requirement string checked against the dependency’s package.version. Supported forms:
  • exact version: 1.2.3
  • caret range: ^1.4.0
  • tilde range: ~1.4.0
  • comma-separated comparator conjunctions such as >=1.2.0, <2.0.0
  • sha256 (optional): integrity hash string.
  • features (optional): enabled build features for this dependency package.
  • It may be:
  • an array of strings of the form NAME or NAME=VALUE, or
  • an inline table mapping NAME = <bool|int|string>.
  • NAME = true is equivalent to NAME.
  • otherwise it is equivalent to NAME=VALUE.
  • Feature names start with a letter or _ and may contain letters, digits, _, and -.
  • These features populate the enabled feature set queried by attr(feature="...") within the dependency package’s modules (see attributes).
  • When building a package graph (via --package), dependency feature specs are merged from every manifest in the graph.
  • If multiple manifests assign different values to the same feature name for a single package, the build fails unless overridden by a CLI --feature <package>/<spec> entry.

Dependency resolution + verification:

  • When version is present, the dependency manifest’s package.version must exist, parse as SemVer, and satisfy the requirement.

  • When sha256 is present, the compiler verifies the dependency payload hash.

  • The sha256 value must be of the form sha256:<64 hex digits> (case-insensitive).

  • The compiler verifies it by hashing the dependency package’s contents using a deterministic scheme:

  • When [dist] is omitted:

  • the hash input starts with the ASCII prefix silk-package-sha256-v1\0,

  • then the exact bytes of the dependency’s silk.toml, followed by \0,

  • then, in sorted order by relative path:

  • the file’s relative path bytes, then \0,

  • the file’s bytes, then \0,

  • and only files included by that dependency manifest’s [sources] include/exclude rules are hashed.

  • When [dist] is present:

  • the hash input starts with the ASCII prefix silk-package-sha256-v2\0,

  • then the exact bytes of the dependency’s silk.toml, followed by \0,

  • then the sorted [dist] payload files using the same relative-path\0bytes\0 scheme.

Limitations:

  • Only local-path dependencies are supported (no remote fetch).
  • Dependency features are currently selected only via the importer’s manifest ([dependencies].<dep>.features) and the CLI. A dependency’s own [build].features are applied only when that dependency is built as the root package.

Dependency discovery via contextual package roots and SILK_PACKAGE_PATH#

When a dependency entry omits path, the compiler resolves it by searching a PATH-like list of package roots.

Rules:

  • During package-graph loading, search roots are contextual to the importing package:
  • relative entries from SILK_PACKAGE_PATH are resolved from the importing package root and then from each parent package root up to the graph root,
  • when SILK_PACKAGE_PATH is not set, packages/ is searched using that same importer-to-graph-root walk. This means a dependency manifest can declare its own pathless dependencies and find packages in packages/ next to the dependency, or in packages/ directories between that dependency and the root package being built.
  • Absolute entries in SILK_PACKAGE_PATH are searched as written. Relative entries also retain the historical current-working-directory fallback after the contextual importer roots.
  • When SILK_PACKAGE_PATH is not set, the compiler also uses these install/user roots after contextual packages/ roots and the historical ./packages fallback:
  • ../share/silk/packages relative to the silk executable (installed layout),
  • $HOME/.local/share/silk/packages when it exists (user-local installs).
  • Finally, the compiler appends a system library root at PREFIX/lib/silk (default PREFIX=/usr/local) as the last search path entry when it exists.
  • For a dependency key named my_api, each root directory contributes the candidate package root <root>/my_api.
  • For a dotted dependency key named my.dep.b, dots are path separators and each root directory contributes the candidate package root:
  • <root>/my/dep/b
  • and the manifest is <candidate>/silk.toml.
  • The compiler searches roots in order and uses the first candidate that exists.
  • The discovered manifest's package.name may be namespaced and may differ from the dependency key. The dependency is still subject to the version and sha256 verification rules above.
  • Explicit dependency path entries remain resolved relative to the importing manifest directory, so nested dependency manifests may use path = "../peer" or omit path and rely on contextual package roots.

Worked Dependency Mapping Example#

This layout demonstrates the four dependency shapes that are expected to work together:

workspace/
  app/
    silk.toml
    src/main.slk
  libs/
    logger/
      silk.toml
      src/lib.slk
    local-math/
      silk.toml
      src/lib.slk
  packages/
    my/dep/a/
      silk.toml
      src/lib.slk
    my/dep/b/
      silk.toml
      defs/api.slk
      lib/linux-x86_64/libdep_b.a
  libs/logger/packages/
    log_backend/
      silk.toml
      src/lib.slk

The root package declares explicit path dependencies and package-search dependencies in the same [dependencies] table:

# workspace/app/silk.toml
[package]
name = "app"
version = "0.1.0"

[sources]
include = ["src/**/*.slk"]

[dependencies]
logger = { path = "../libs/logger", version = "^0.1.0" }
local.math = { path = "../libs/local-math", version = "0.3.0" }
my.dep.a = { version = "^1.2.0" }
my.dep.b = { version = "^2.0.0" }

[[target]]
name = "app"
kind = "executable"
entry = "src/main.slk"

The meaning of each dependency key is:

  • logger is a path-based dependency rooted at ../libs/logger. Quoted dependency module imports use the key ("logger" for src/lib.slk, or a subpath such as "logger/api"), while unquoted package imports use the dependency package's own package.name.
  • local.math is also path-based. Its quoted dependency root is "local/math" because dots in dependency keys map to slashes in quoted module paths.
  • my.dep.a has no path, so the package search path is used. With SILK_PACKAGE_PATH=../packages, the package root is ../packages/my/dep/a.
  • my.dep.b also has no path, so the package root is ../packages/my/dep/b. This example is binary-only: it ships definitions and a compatible native archive instead of implementation sources.
  • If logger declares log_backend = { version = "^0.1.0" }, that nested pathless dependency is resolved from workspace/libs/logger/packages/ before the search walks upward toward workspace/app and then falls back to the configured global/install roots.

The path dependency may expose a package namespace that differs from the local dependency key:

# workspace/libs/logger/silk.toml
[package]
name = "oro::logger"
version = "0.1.2"

[sources]
include = ["src/**/*.slk"]
// workspace/libs/logger/src/lib.slk
package oro::logger;

export fn info () -> int {
  return 1;
}

The dotted path dependency maps quoted "local/math" imports to its source root:

# workspace/libs/local-math/silk.toml
[package]
name = "local::math"
version = "0.3.0"

[sources]
include = ["src/**/*.slk"]
// workspace/libs/local-math/src/lib.slk
package local::math;

export fn add (a: int, b: int) -> int {
  return a + b;
}

The package-search dependency is stored under the package root path. Its manifest path is discovered from the dependency key:

# workspace/packages/my/dep/a/silk.toml
[package]
name = "my::dep::a"
version = "1.2.3"

[sources]
include = ["src/**/*.slk"]
// workspace/packages/my/dep/a/src/lib.slk
package my::dep::a;

export fn transform (value: int) -> int {
  return value + 10;
}

The binary-only dependency ships a definition file plus a native archive:

# workspace/packages/my/dep/b/silk.toml
[package]
name = "my::dep::b"
version = "2.0.1"
definitions = ["defs/api.slk"]

[sources]
include = ["defs/**/*.slk"]

[dist]
include = ["defs/**/*.slk", "lib/linux-x86_64/*.a"]

[[artifact]]
name = "dep_b_static"
kind = "static"
path = "lib/linux-x86_64/libdep_b.a"
target = "linux-x86_64"
definitions = ["defs/api.slk"]
// workspace/packages/my/dep/b/defs/api.slk
package my::dep::b;

export fn some_function () -> int;

The consuming source can mix quoted dependency-rooted imports and unquoted package-namespace imports:

// workspace/app/src/main.slk
import log_file from "logger";
import log_pkg from oro::logger;
import { add } from "local/math";
import my::dep::a::transform;
import my::dep::b::some_function;
import { some_function as some_function_from_defs } from "my/dep/b";

fn main () -> int {
  return log_file::info()
    + log_pkg::info()
    + add(20, 1)
    + transform(10)
    + some_function()
    + some_function_from_defs();
}

Important resolution rules shown by this example:

  • Quoted strings use POSIX-style dependency paths and MUST NOT contain ::.
  • An exact quoted dependency-key import such as "logger" or "local/math" resolves to that dependency's root module (src/lib.slk) even when the dependency manifest declares a different package.name.
  • Unquoted package paths use :: and resolve by the package graph's package.name values.
  • Dotted dependency keys map to slash prefixes for quoted imports: local.math -> "local/math", my.dep.b -> "my/dep/b".
  • The longest dependency key wins. If both my and my.dep.b are declared, "my/dep/b" resolves through my.dep.b, while "my/other" can still resolve through my when that key exists.
  • A pathless dependency is resolved from the package search path only; it is not fetched remotely.
  • version and sha256 checks apply to the resolved dependency manifest, not to the local dependency key.
  • A binary-only dependency root can satisfy unquoted package-symbol imports through its definition file and auto-link a compatible native artifact. When the package has one definition file and no default source module, an exact quoted dependency-root import such as "my/dep/b" can bind named imports from that definition file.
  • Compiler and LSP module graphs resolve that exact quoted dependency-root import to the same definition-file identity. This applies to named imports and default/namespace imports, including dotted dependency keys matched through their slash-separated spelling.

A checked-in runnable companion for these rules lives at examples/projects/cove/. It is a static HTTP file server that uses an explicit local path dependency for its document-root abstraction and a pathless access_log dependency resolved from the root package's default packages/ directory. The docroot dependency owns a target-gated [[native]] C helper for POSIX realpath(3) containment checks on linux-x86_64 and macos-aarch64; the root package owns a target-gated native socket helper that detects idle browser preconnects and bounds request reads on the same hosted POSIX example targets.

Distributed Artifacts ([[artifact]])#

Packages may declare shipped native artifacts explicitly:

[[artifact]]
name = "my_lib_static"
kind = "static"
path = "lib/linux-x86_64/libmy_lib.a"
target = "linux-x86_64"
definitions = ["defs/api.slk"]
c_header = "include/my_lib.h"

Fields:

  • name (required): artifact identifier unique within the manifest.
  • kind (required): one of executable, object, static, shared, or man.
  • path (required): relative path inside the package root.
  • target (optional): target triple for the artifact payload.
  • libc / libc_min (optional): structured compatibility metadata for native libraries. Supported libc values are glibc/gnu and musl; this is used when selecting artifacts for Linux targets such as linux-x86_64 and linux-x86_64-musl.
  • definitions (optional): definition files associated with this artifact.
  • c_header (optional): C header shipped with this artifact.

Additional rules for kind = "man":

  • path should typically live under share/man/man1/, share/man/man3/, or share/man/man7/.
  • target, libc, libc_min, definitions, and c_header are invalid for manpage artifacts.

Current uses:

  • silk package inspect prints declared artifacts and any installed Formal Silk bundles attached to those artifacts,
  • silk package lint validates that artifact files exist and are covered by [dist],
  • silk build and silk test --package auto-consume one compatible artifact for imported binary-only/interface-only dependencies (currently on linux/x86_64 glibc or musl, preferring object, then static, then shared payloads),
  • and silk build install emits installed [[artifact]] records for built package targets.

Example:

[dependencies]
my_api = { sha256 = "sha256:0123456789abcdef..." }

Package Native Requirements ([[native]])#

Packages may declare native code and link requirements that should be applied when that package participates in a build:

[[native]]
target = "linux-x86_64"
inputs = ["native/portable.c", "lib/linux-x86_64/libhelper.a"]
cflags = ["-Inative/include"]
ldflags = ["-lm"]
runpath = ["$ORIGIN/../lib/linux-x86_64"]

Fields:

  • target (optional): target triple that gates this native requirement. When omitted, the requirement applies to every target that can consume the listed native inputs. When present, the requirement applies only when the active compiler target exactly matches the triple.
  • inputs (optional): native input paths using the same file-kind rules as [[target]].inputs (.c, .h, supported .m, .o, .a, .so, and versioned .so.*; @builtin/<name>.a is also accepted).
  • cflags (optional): native compiler arguments used for .c / .h / supported .m inputs from this requirement. Relative -I and -isystem paths are resolved relative to the owning package root.
  • ldflags (optional): linker arguments using the same supported forms as [[target]].ldflags. Relative -L paths are resolved relative to the owning package root.
  • needed (optional): dynamic-library sonames to add to the consuming link.
  • runpath (optional): runtime search-path entries to add to the consuming executable or shared-library link.

Rules:

  • At least one of inputs, cflags, ldflags, needed, or runpath must be present.
  • Matching root-package [[native]] entries are merged into silk build --package and silk test --package for the selected code target.
  • Matching dependency-package [[native]] entries are merged when the imported dependency package is present in the loaded module set. This is the preferred manifest surface for source or hybrid dependencies whose Silk implementation calls package-owned native helpers through ext.
  • [[artifact]] remains the distribution surface for prebuilt package outputs. Binary-only/interface-only dependencies should continue to expose native payloads as [[artifact]] entries paired with definition files.
  • [[target]].inputs remains available for native files that belong only to a specific artifact recipe. Prefer [[native]] for package-level native code that dependencies should carry with them.
  • silk package lint validates declared native input files and [dist] coverage for non-@builtin/... inputs.

Build Targets ([[target]])#

A package may declare one or more build targets. Each target produces one artifact (an executable, an object, a static library, a shared library, a wasm module, or a manpage).

Example:

[[target]]
name = "my_app"
kind = "executable"
entry = "src/main.slk"
output = "build/my_app"

[[target]]
name = "my_lib"
kind = "static"
entry = "src/lib.slk"
output = "build/libmy_lib.a"
c_header = "build/my_lib.h"

[[target]]
kind = "man"
source = "man/my_app.1"

Fields:

  • name (required for executable|object|static|shared; optional for man): unique target name within the package.
  • When omitted for kind = "man", the compiler synthesizes a stable target name during manifest loading:
  • static man sources default to <page>.<section> derived from source (for example man/my_app.1 -> my_app.1, docs/man/my-app.7.md -> my-app.7),
  • source-derived man targets default to the trimmed query string.
  • These synthesized names are the names used by build.default_target, silk build --package-target <name>, and installed manifest metadata.
  • kind (required): one of executable, object, static, shared, or man.
  • entry (required for executable|object|static|shared): path to the entry module, relative to the manifest directory.
  • source (required for static man targets): path to a checked-in manpage source file, relative to the manifest directory. Supported static forms are:
  • roff pages named name.1, name.3, or name.7,
  • Markdown man sources named name.1.md, name.3.md, or name.7.md.
  • query (required for source-derived man targets): documentation query rendered through the same source-doc pipeline as silk doc --man (for example a @cli page name or an @misc topic).
  • Source-derived package man targets query only the root package’s own source modules; dependency sources in the same manifest graph are not part of the query corpus. Exactly one of source or query must be set for kind = "man".
  • inputs (optional): additional non-.slk build inputs for this target:
  • for package-level native code that should travel with a source/hybrid dependency, prefer [[native]]; inputs is for files specific to this one build target,
  • entries are paths (relative to the manifest directory when not absolute),
  • entries may also use a toolchain-relative built-in archive reference:
  • @builtin/<name>.a — resolves to the built-in static archive under the active Silk prefix (for example @builtin/libmbedtls.a),
  • this form is supported only for .a inputs and only on supported native hosts (linux/x86_64, macos/aarch64) in the current toolchain,
  • explicit @builtin/... entries remain supported when a package wants to pin a specific built-in archive input, but common built-in dependency families are also auto-linked for executable/shared targets:
  • libsodium, mbedTLS, and libssh2 are auto-linked by the builtin security provider on supported native hosts (linux/x86_64, macos/aarch64) when the Silk module set imports std::crypto / std::tls / std::ssh / std::ssh2, or when native .c / .h / .m / .o / .a inputs reference their symbol families,
  • libsqlite3 is auto-linked on linux/x86_64 when the Silk module set imports std::sqlite, or when native .c / .h / .m / .o / .a inputs reference sqlite3_* symbols,
  • each entry MUST end with one of:
  • .c — compiled via the native compiler for the active target and linked as an object,
  • .m — compiled as Objective-C for supported Apple host-backed Mach-O targets and linked as an object,
  • .h — treated as a C build input:
  • if a sibling .c file exists next to the header, Silk compiles that .c file and links the resulting object,
  • otherwise, if a sibling .m file exists next to the header, Silk compiles that Objective-C file and links the resulting object,
  • otherwise Silk falls back to compiling the header itself as a C translation unit (passed as -x c) and links the resulting object,
  • .o — linked as an object (and included in static archives),
  • .a — linked as a static archive,
  • .so / *.so.<ver> — treated as a dynamic dependency (equivalent to adding a needed entry for the library’s basename),
  • .slk entries are rejected (use [sources] instead),
  • note: non-.slk inputs are supported for linux/x86_64 native targets and for macos-aarch64 plus iOS device/simulator executable/object/static/shared outputs on Apple Silicon macOS hosts (same limitation as silk build CLI inputs).
  • note: Objective-C .m inputs are supported only for macos-aarch64, ios-aarch64, ios-simulator-aarch64, and ios-simulator-x86_64 on Apple Silicon macOS hosts; supported executable/shared outputs that include .m inputs link the Objective-C runtime automatically.
  • cflags (optional): additional native compiler arguments used when compiling any .c/.h/.m inputs for this target (from inputs and/or CLI native inputs when building a single target).
  • entries are single cc arguments (no shell splitting),
  • include paths passed via -I<rel> / -I, <rel> and -isystem<rel> / -isystem, <rel> are resolved relative to the manifest directory.
  • On linux/x86_64, when compiling .c/.h inputs, silk also adds the active toolchain’s built-in include directory to the native compiler’s include search path, so C sources can include headers like #include <mbedtls/net_sockets.h> without hardcoding a repo-relative -I.../vendor/include path.
  • ldflags (optional): additional backend linker arguments for this target. Platform-linker backends can pass supported arguments through directly. Internal backends translate the forms they can represent into existing manifest/CLI linkage state and reject unsupported payloads. Supported forms:
  • -L<rel> / -L, <rel> → adds a library search path; relative paths are resolved relative to the manifest directory,
  • -Wl,-rpath,<path> / -Wl,-rpath=<path> → adds a runpath entry,
  • -Wl,-soname,<name> / -Wl,-soname=<name> → sets soname,
  • -Wl,--dynamic-linker,<path> / -Wl,-dynamic-linker,<path> / -Wl,--dynamic-linker=<path> / -Wl,-dynamic-linker=<path> → sets elf_interp,
  • -lfoo / -l, foo → links with a library name:
  • on linux/x86_64, -L paths are searched first; a found .so adds a needed entry by basename, and a found .a is linked as a static archive,
  • if no matching -L library is found, silk maps to needed = ["libfoo.so"],
  • when the selected dynamic loader looks like glibc (ld-linux), silk maps common system libraries to their versioned runtime sonames (for example -lm → needed = ["libm.so.6"], -lpthread → needed = ["libpthread.so.0"]),
  • when the selected dynamic loader looks like musl (ld-musl), silk maps musl's libc component libraries (-lc, -lm, -lpthread, -ldl, -lrt, -lutil, -lresolv, -lcrypt, -lxnet) to needed = ["libc.so"],
  • note: some distros ship libfoo.so only in *-dev packages, so prefer -l:libfoo.so.<ver> or an explicit needed = ["libfoo.so.<ver>"] when targeting versioned shared libraries),
  • -l:libfoo.so.1 → adds needed = ["libfoo.so.1"].
  • output (optional): output path relative to the manifest directory. If omitted, the compiler chooses a default under build/ based on name and kind:
  • executable: build/<name> (or build/<name>.wasm for wasm targets, or build/<name>.exe for Windows targets),
  • object: build/<name>.o,
  • static: build/lib<name>.a,
  • shared: build/lib<name>.so (current hosted baseline is linux/x86_64).
  • man: build/share/man/man<section>/<page>.<section> where <page> and <section> come from the static source filename or the rendered query result.
  • arch / target (optional): default codegen target for this artifact.
  • arch is one of x86_64, wasm32, wasm32-wasi (same as silk build --arch).
  • target is a target triple string accepted by silk build --target (for example linux-x86_64, wasm32-wasi).
  • arch and target MUST NOT both be set for the same target.
  • c_header (optional): emit a C header when building this target (only valid for kind = object|static|shared).
  • iOS app bundle fields (optional; only valid for kind = executable):
  • ios_app_bundle = true tells silk build --package to materialize an iOS .app directory next to the executable output for ios-aarch64, ios-simulator-aarch64, or ios-simulator-x86_64 targets.
  • ios_info_plist = "Info.plist" copies a package-root-relative Info.plist into the bundle. If omitted, ios_bundle_identifier is required and Silk writes a small generated plist.
  • ios_bundle_identifier = "com.example.app" and ios_bundle_name = "ExampleApp" provide metadata for the generated plist. ios_bundle_name defaults to the executable basename.
  • ios_codesign = "ad-hoc" signs the app bundle with an ad-hoc identity on macOS after the executable and Info.plist are copied. This is the default when ios_app_bundle = true; use ios_codesign = "none" to leave the bundle unsigned.
  • The executable output path remains the output field. The app bundle path is <output>.app, and the executable is copied into that bundle under its basename.
  • Dynamic linkage fields (optional; passed through to the backend):
  • needed = ["libc.so.6", "..."] (repeatable DT_NEEDED entries),
  • runpath = ["$ORIGIN", "..."] (joined with : for DT_RUNPATH),
  • soname = "libfoo.so" (for shared libraries).
  • elf_interp = "/lib64/ld-linux-x86-64.so.2" (for linux/x86_64 executable outputs; emitted as PT_INTERP; also influences glibc/musl defaults for ldflags -l... mapping).
  • This field is rejected for non-linux/x86_64 targets.
  • Note: needed entries starting with libsilk_rt are rejected; bundled runtime helpers are linked statically by silk build when referenced.

Additional rules for kind = "man":

  • name may be omitted; when omitted, the compiler derives the internal target name from source or query using the rules above.
  • entry, inputs, cflags, ldflags, arch, target, c_header, iOS app-bundle fields, needed, runpath, soname, and elf_interp are invalid.
  • Static Markdown sources are rendered to roff at build time.
  • Static roff sources are copied into the target output and normalized to the filename-derived page name and section.
  • silk build install installs built man targets into the package root under share/man/man<section>/... and mirrors them to <prefix>/share/man/man<section>/....
  • silk build install also packages local package.readme / package.documentation landing pages under share/silk/docs/... inside the package root and rewrites the installed manifest to those packaged paths, unless package.documentation is rewritten to an installed man target under share/man/...; in that case no redundant share/silk/docs/documentation/... copy is installed.
  • Ad hoc metadata tables such as [docs] remain inert; only [[target]] entries participate in silk build / silk build install.

Example:

[[target]]
name = "app"
kind = "executable"
entry = "src/main.slk"
inputs = ["src/logger.c", "vendor/libextra.a", "build/helpers.o", "lib/libfoo.so"]
cflags = ["-Isrc/include"]
runpath = ["$ORIGIN"]

Build Defaults ([build])#

[build] records package-wide defaults used by single-target contexts and build-module execution.

[build]
default_target = "my_app"
security_provider = "auto"     # optional: auto, platform, or builtin
build_module = true            # optional opt-in (default: false)
build_module_path = "build.slk" # optional; default "build.slk"
features = ["tui", "MY_FEATURE=123", "enable_this_feature=true"] # optional
# or:
# features = { tui = true, MY_FEATURE = 123, enable_this_feature = true }

Rules:

  • If build.default_target is set, it MUST name an existing [[target]].
  • build.security_provider (optional) selects the package default for security-sensitive stdlib primitives and auto-linking. Accepted values are:
  • auto — Apple targets use platform-backed APIs first and fall back to built-in archives for std APIs that do not yet have an Apple platform mapping; other targets use the built-in provider,
  • platform — Apple targets use Apple Security-backed std::crypto core/random helpers and Apple framework linkage, and reject fallback-only std/native security APIs,
  • builtin — use the toolchain-built libsodium, mbedTLS, and libssh2 static archives. CLI --security-provider wins over this field, and SILK_SECURITY_PROVIDER wins when the CLI flag is absent.
  • silk build --package builds every manifest [[target]] by default when --package-target is omitted.
  • build.default_target is still used by contexts that need one code-bearing target:
  • package-graph entry ordering prefers that target’s entry,
  • silk test --package uses that target’s inputs, cflags, ldflags, needed, and runpath, plus matching package [[native]] entries, as the manifest native/link metadata for the test harness.
  • For silk test --package:
  • build.default_target must name a code target (one with entry = "..."); pointing it at kind = "man" is an error,
  • when build.default_target is unset, the first declared code target is used,
  • raw manifest native source inputs (.c, .h, and supported .m) are compiled to temporary objects and linked with the generated test harness, using that target’s cflags,
  • built-in provider native-input auto-linking for libsodium, mbedTLS, and libssh2, plus built-in SQLite auto-linking, follows the same supported-target rules as package builds,
  • when no code targets exist, tests still run from the package source set but no manifest link metadata is applied.
  • build.build_module (optional; default false) enables build module execution for manifest-driven package commands:
  • when true, the build module runs for:
  • silk build --package,
  • silk check --package,
  • silk test --package,
  • silk build install,
  • and silk build uninstall, without requiring CLI build-module opt-in.
  • build.build_module_path (optional) specifies the default build module path used when a build module is executed and the CLI does not provide --build-module-path.
  • If the path is relative, it is resolved relative to <package_root>.
  • If omitted, the default is <package_root>/build.slk.
  • Note: setting build_module_path does not enable build module execution by itself; use build_module = true or the CLI.
  • When a build module is executed:
  • the manifest it emits replaces the root manifest for the remainder of the build (see build scripts),
  • the emitted manifest’s [build].build_module / [build].build_module_path values are ignored for the current invocation to prevent recursive build module execution,
  • silk check --package and silk test --package currently invoke the build module with the action string "build" for compatibility with existing build modules,
  • CLI overrides:
  • --build-module-path <path> wins (and implies build module execution),
  • otherwise --build-module wins.
  • build.features (optional) enables build features for this package when it is selected as the root package for silk build / silk check / silk test.
  • It may be:
  • an array of strings of the form NAME or NAME=VALUE, or
  • an inline table mapping NAME = <bool|int|string>.
  • NAME = true is equivalent to NAME.
  • otherwise it is equivalent to NAME=VALUE.
  • Feature names start with a letter or _ and may contain letters, digits, _, and -.
  • These features populate the enabled feature set queried by attr(feature="...") (see attributes).
  • CLI --feature / -F entries override manifest features of the same name.
  • The effective feature selection participates in package target cache keys. Changing a selected feature invalidates artifacts whose checked or emitted declarations may differ.
  • Target cache keys also include the complete resolved local file-import closure, including modules reached from a declared source but not listed separately in [sources].

Interaction With package Declarations#

  • If a module contains an explicit package name; declaration, that name is authoritative.
  • If a module omits package, the compiler assigns it to the manifest package.name (for files under that package root).

This defaulting behavior exists to support small projects that do not want to repeat package ...; in every file.

Reserved Fields#

The manifest reserves additional fields for future build integration:

  • provenance / integrity metadata (repo, richer dependency sources),
  • richer native build configuration (additional include path kinds, defines, link search paths, platform selection),
  • embedded targets / budgets.

Source repository · Edit this page · View Markdown