Build and consume an Incan library¶
This tutorial creates a reusable Incan library, exports a deliberately small public API, and consumes it from a second project through a path dependency. Both projects live in one workspace so they share portable lock state.
- ProduceCreate the library project
- ExportChoose the public surface
- BuildEmit the library artifact
- ConsumeImport through
pub:: - LockMake resolution repeatable
The complete producer and consumer live at examples/advanced/library_package.
Step 1: Create the producer¶
Create this workspace layout:
incan.toml
producer/
├── incan.toml
└── src/
├── lib.incn
└── pricing.incn
The root incan.toml defines both projects as workspace members:
[workspace]
members = ["producer", "consumer"]
default-members = ["pricing_app"]
producer/incan.toml identifies the package:
[project]
name = "pricing_core"
version = "0.1.0"
Put reusable implementation in src/pricing.incn:
pub model LineItem: # (1)
"""One priced item whose quantity participates in a subtotal."""
pub name: str
pub quantity: int
pub unit_price: float
pub def subtotal(item: LineItem) -> float: # (2)
"""Return `quantity * unit_price` for one line item."""
return float(item.quantity) * item.unit_price
- The type and the fields used by consumers are public at the producer module boundary.
- The function accepts the domain type rather than exposing storage or backend details.
Step 2: Define the exported API¶
src/lib.incn is the library root:
pub from pricing import LineItem, subtotal # (1)
pub fromis the library's deliberate export list; declarations omitted here stay internal even if another producer module can see them.
The export is explicit. A declaration being pub inside pricing.incn makes the model visible to the library root, while its pub fields can be read across module boundaries. The pub from ... line decides which declarations downstream packages receive.
Step 3: Build the library¶
From producer/:
incan lock
incan oven bake --project . --format json
incan build --lib
The explicit bake seals the producer's project extension and checked library sidecars. The build emits a .incnlib artifact under target/lib/ together with generated library output. Consumers use the checked public manifest rather than importing the producer's private source paths.
Step 4: Create the consumer¶
Create a sibling project:
consumer/
├── incan.toml
└── src/
└── main.incn
Declare the local dependency in consumer/incan.toml:
[project]
name = "pricing_app"
version = "0.1.0"
[project.scripts]
main = "src/main.incn"
[dependencies]
pricing = { path = "../producer" }
The dependency key becomes the public import namespace:
from pub::pricing import LineItem, subtotal # (1)
def main() -> None:
item = LineItem(name="keyboard", quantity=2, unit_price=79.5)
println(f"{item.name}: {subtotal(item)}")
pub::pricingselects the dependency's checked public namespace rather than a path into its source tree.
pub::pricing is a package boundary. It is not a path into producer/src/.
Step 5: Lock and run¶
From the workspace root:
incan lock
incan oven bake --project . --format json
incan run --member pricing_app --locked
The workspace bake discovers the producer and consumer, publishes the required project extensions and completed outputs, and names their exact full-standard-library base. The normal run then reuses those checked artifacts without invoking Cargo. Commit the generated root incan.lock; member-local lockfiles are not workspace authorities. When the producer changes its public API, bake and build it again rather than copying source files between projects.
You selected a public API, built its checked library artifact, locked the workspace, and ran a consumer through the public package boundary.