ADR-0004: Workspace and Component Protocol
Context
Ideal is composed of multiple independent components — the foundational library, the toolkit, the desktop shell, the plugin runtime, the documentation site, and others yet to come. Each component lives in its own repository with its own development pace, but they must be built and integrated together to produce a working system.
This raises two questions:
- How do developers work on multiple components at once? Cross-component changes (e.g. a new API in the foundational library consumed by the toolkit) require building and testing several repositories in the right order against a consistent set of dependencies.
- How do components interoperate at build time? A component that depends on another needs to find its headers, libraries, and pkg-config files without assuming a system-wide installation.
Decision
Development is organized around a workspace: a top-level repository that checks out component repositories side by side and orchestrates their build, test, and installation.
The workspace provides:
- A component manifest that declares which components exist, their repository URLs, target branches or pinned revisions, and their inter-component dependencies.
- A staging directory (
SYSROOT) where components are installed during development. Components discover each other's build artifacts through this directory, using standard mechanisms (pkg-config, include paths, library paths). - Orchestration targets (
build,test,install,clean,distclean,sync) that operate across all components, resolving build order from declared dependencies.
Each component must conform to a component protocol: a root Makefile that exposes a standard set of
targets. The internal build system is up to the component — the workspace only interacts through this Makefile
interface.
Component Protocol
Required targets:
| Target | Description |
|---|---|
build | Configure and compile |
install | Install to PREFIX |
clean | Remove build artifacts |
distclean | Remove all generated files |
Optional targets:
| Target | Description |
|---|---|
uninstall | Remove installed files from PREFIX |
test | Run tests |
The workspace passes two variables on every make invocation:
| Variable | Description |
|---|---|
PREFIX | Absolute path where the component must install its files |
SYSROOT | Absolute path where dependencies have been staged |
Components must install following a standard filesystem layout under PREFIX (bin/, lib/, include/,
share/, lib/pkgconfig/), and must use SYSROOT to locate headers and libraries from other components.
The protocol imposes the following behavioral contract:
buildfollowed byinstallwith the samePREFIXmust produce a working installation.cleanmust remove build artifacts so that a subsequentbuildreconfigures and recompiles from scratch.distcleanmust leave the source directory as if freshly cloned.- All targets must work when invoked from any directory via
make -C.
Alternatives Considered
Monorepo. Placing all components in a single repository simplifies cross-component changes (one commit, one CI run) and eliminates the need for a separate workspace orchestrator. However, it couples release cycles, inflates clone sizes, and complicates permissions — a contributor fixing a shell bug must clone the entire toolkit and runtime. Independent repositories with a thin workspace overlay provide the coordination benefits of a monorepo without the coupling.
No workspace — system-installed dependencies only. Each component could be built independently, finding its dependencies via system package managers. This works for releases but is impractical during development: it requires packaging and installing every intermediate change, and breaks down when working on unreleased API changes across components.
Nix / Guix development shells. Functional package managers can provide reproducible development environments with pinned dependencies. However, they impose a significant tooling and conceptual overhead on contributors, and are not universally available across target distributions. The workspace approach achieves per-component isolation with standard Make and shell tools that every contributor already has.
Consequences
- Developers can work on any subset of components by checking them out into the workspace. A single
make buildresolves dependencies and produces an integrated staging tree. - The component protocol is intentionally minimal — it does not prescribe the internal build system, testing framework, or directory structure. This keeps components autonomous while ensuring they compose reliably.
- Adding a new component is a one-line addition to the manifest. Removing one is equally simple.
- The workspace is a development tool, not a release mechanism. Distribution packaging and CI will build components individually against their declared versioned dependencies.
- Contributors must understand the workspace conventions in addition to the component they are working on. Developer documentation must cover workspace setup as part of the onboarding guide.