Skip to content

Projects today

This page explains how "projects" work in Incan.

The current model

Incan source files (.incn) are the source of truth.

When you run:

incan build path/to/main.incn

Incan checks the source and generates caller-owned Rust, then Oven verifies the build receipt, selects and leases a compatible Loaf, and executes its stored direct-rustc plan. For the documented Alpha envelope, the normal command neither launches Cargo nor reads a Cargo target directory. A compatibility miss stops with actionable guidance rather than changing backend.

Cargo currently remains at one explicit maintainer boundary: the internal release publisher can prepare a supported Loaf envelope. That publication step is not part of an ordinary build, run, or test command. See Oven Alpha for the rationale, architecture, and current limitations.

Contributor source-build fallback

If you are working from a compiler checkout instead of a toolchain install, you can run the repository-built binary directly:

  • from the repository root:
./target/release/incan ...
  • or via an absolute path (from anywhere):
/absolute/path/to/incan run path/to/file.incn

Where outputs go

On build, Incan prints the generated project directory and the compiled binary path. In practice, outputs live under:

  • Generated Rust project: target/incan/<name>/
  • Built binary: target/incan/<name>/oven/release/<name>

See: CLI reference.

What gets regenerated

The generated Rust project under target/incan/ is tool-managed output. Treat it as generated:

  • It is safe to delete when no Incan command is using it: rm -rf target/incan/
  • Manual edits inside target/incan/<name>/ may be overwritten on the next build

If an oven.lock exists, its relevant dependency and source evidence contributes to the Oven compatibility identity. A normal build validates and selects an authorized Loaf from that evidence; it does not materialize a Cargo project for execution.

Dependencies

Rust crate dependencies are resolved through a three-tier system:

  1. loaf.toml (highest priority): Project manifest declares explicit dependencies with versions, features, and sources.
  2. Inline annotations: import rust::foo @ "1.0" specifies versions directly in source files.
  3. Known-good defaults: For common crates, the compiler provides tested version/feature defaults.

If none of these apply, the compiler emits an error.

See: Rust Interop and Managing dependencies.

Project configuration

Projects can optionally have a loaf.toml manifest at the project root:

my_project/
├── src/
│   └── main.incn
├── tests/
│   └── test_main.incn
├── loaf.toml         # Project manifest (optional)
└── oven.lock         # Lock file (auto-generated, commit to VCS)

Create one with incan init. See: Project configuration reference.

Lock files

oven.lock stores resolved dependency versions and normalized build inputs for reproducible builds. It is generated by incan lock (or automatically on first build) and should be committed to version control. Oven binds relevant lock evidence into Loaf selection.

Use --locked or --frozen in CI to enforce that the lock is present and up to date. Within the Alpha envelope, an unavailable compatible Loaf is an explicit miss; normal commands do not ask Cargo to resolve or build one.

See: Managing dependencies.

Retained command preparation

On Unix, ordinary executable builds automatically start or connect to a compatible background build owner. The owner retains the existing project model, canonical oven.lock, dependency admission and selected native plans between requests. Commands keep their own output and diagnostics; a failed request does not prevent later requests from using the owner. Other platforms execute the same driver operation within the command process.

Before reusing a completed output, the owner checks the observed inputs for changes. Unchanged directory metadata allows reuse of its previous child list; changed file candidates are read and hashed before deciding whether build inputs changed. Changed manifests, locks, compiler selections or selected output files invalidate the corresponding retained state. An unchanged lock does not hide a source edit, and an invalid replacement never exposes the previous valid record.

On Unix, test commands automatically connect to the same compatible project owner as builds. It retains canonical project and dependency-session preparation while reading current test and conftest sources for each request. Build and test share native dependency admission under the same reconciled lock, compiler and provider identity; changed selected native inputs discard that admission before it can be reused. Each execution runs fresh test processes and fixtures; passing results are never cached. The invoking CLI formats completed console, JSON Lines and JUnit reports from the runner’s typed results. Stable result IDs retain their collected file identity even if a test changes or removes its source file. Progress streams as typed events and the requesting CLI renders session banners, fixture details and phase timings. JSON Lines mode suppresses these human-readable notices, including with --verbose. Diagnostics and uncaptured test output retain their original streams, including output from parallel workers. A report-file write failure fails the command even when all tests passed. Incan --list uses parser-only preparation without preparing test artifacts. Rust listing prepares or reuses each libtest executable to discover cases without running test bodies. Other platforms invoke the same runner within the command process.

Lock preparation

On Unix, the two lock commands submit to the same project owner when standalone Oven is beside its compatible Incan compiler and both commands use the same directory and environment. The owner retains successful lock preparation in its original dependency session. A workspace publication retains the original sessions and test-discovery observations of every contributing member together; changing a sibling still invalidates the shared lock preparation. Within one owner, changing the invoking member or transient package-feature flags does not recollect an otherwise identical workspace lock. Source reconciliation and observations from the canonical test-discovery walk determine whether that preparation is reusable. The observations cover test sources outside src/, potential inline tests and nested-project boundaries.

The Unix service identity currently includes the command working directory and inherited environment, so commands launched from different member directories can start separate owners. This is separate from sharing one canonical workspace lock.

Changed inputs use the existing lock collector. External-entrypoint and unsupported source-layout requests collect afresh. This preserves their existing behavior without treating incomplete observations as proof that nothing changed. A malformed lock is regenerated by the explicit lock command; it does not authorize reuse of the earlier preparation.

When a publication has identical regular-file bytes, the writer synchronizes the file and parent directory without replacing the file. Preserving that physical identity avoids invalidating an otherwise unchanged project's directory observations. Changed bytes use the atomic publication path. Non-Unix commands perform the same lock operation in process.

Vocab companion preparation

A library's [vocab].crate points to a companion Rust crate. The compiler resolves that path, validates Cargo.toml and src/lib.rs, and reads its checked registration through compiler-owned vocabulary support in the selected direct-rustc plan. The companion's library_vocab() registration supplies the vocab payload packaged in .incnlib.

When the companion registers an incan_vocab::DesugarerRegistration, the library build also packages the matching Wasm artifact from the compiler-owned direct-rustc output. Intermediate serialized metadata belongs to the tooling pipeline rather than the author's interface. The manifest requirements are in the [vocab] reference.

Rust caller preparation

An explicit incan oven bake --project <rust-project> scans Rust units for sibling-library paths such as policy::caller::incan::<name>. It validates those names against checked public exports and emits only the selected caller projection. The Incan library and each Rust binary or declared Rust library then compile through receipted direct rustc plans.

The unit's declared [rust-dependencies] closure is composed with its sibling Incan caller closure for that bake. Library receipts and projections have a separate namespace from binary targets so that equal target names cannot overwrite each other's records. Unchanged bakes can reuse the recorded output or restore its missing mutable projection from the Store. This explicit caller route remains distinct from ordinary transitive Rust library discovery and project-level build/run preparation. Supported types, defaults, paths and refusals are in the [rust] reference.

Compiler-internals preparation

A driver that declares rustc_private, rustc-dev and rustc_driver needs a narrow toolchain permission. For its first build, Oven strips ambient RUSTC_BOOTSTRAP and then sets it to that unit's normalized crate name for the driver invocation only. The invocation has a matching explicit --crate-name and -Zallow-features=rustc_private; dependency compilation receives no such grant.

The receipt identity records the capability, crate name, feature allowlist, bootstrap-derived Cheat session permission, pinned loader policy, complete compiler identity, canonical sysroot, and driver library path and digest. Those inputs bind reuse to the admitted compiler and driver rather than an ambient environment override. Fact-record environments cannot declare RUSTC_BOOTSTRAP. The exact capability and toolchain requirements are in the [[rust.bin]] reference.

Publisher preparation

Native-link and generator facts describe work performed by the publisher before a consumer receives an asset. A link bake resolves executable.path and every owner argument below the caller-held immutable executable-owner root, reproduces the declared closure identity, verifies the executable digest, clears ambient environment variables, and invokes the compiler once per declared object. Once every invocation succeeds and the product root contains exactly those objects, Oven writes the target's indexed static archive in process, without an external archiver, and binds the logical arguments and output digests into a receipt. The resulting asset contains the archive and publisher-receipt.json.

A generator invocation similarly resolves the executable only below its owner, verifies its digest immediately before execution, clears the ambient environment and supplies declared arguments and environment entries in order. A deny-by-default Seatbelt profile provides confinement on macOS. GNU/Linux publishers use bubblewrap with a private filesystem, process and network namespaces, explicit read-only files and one writable product root. The executable’s optional runtime_files records its exact loader dependencies in the same owner identity; Linux derives loader search directories from those files and exposes no ambient library tree. Missing platform confinement or undeclared runtime dependencies refuse the operation. Consumers use the verified receipt-bound asset bytes; they do not rerun the producer or discover tools on the host.

The exact owner-inventory hash format, path and environment restrictions, product contracts and refusal conditions are in the rust.facts.link and rust.facts.tool reference.

GNU loader separators and expansions cannot represent runtime parent paths containing :, ; or $ literally. Publisher preparation refuses those paths rather than interpreting one physical directory as multiple search paths or expanding it as a loader token.

  • Project lifecycle commands are available as incan new, incan init, incan version, and incan env.
  • Tooling diagnostics are available under incan tools doctor.