Skip to main content

ADR-0004: Workspace and Component Protocol

Accepted

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:

  1. 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.
  2. 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:

TargetDescription
buildConfigure and compile
installInstall to PREFIX
cleanRemove build artifacts
distcleanRemove all generated files

Optional targets:

TargetDescription
uninstallRemove installed files from PREFIX
testRun tests

The workspace passes two variables on every make invocation:

VariableDescription
PREFIXAbsolute path where the component must install its files
SYSROOTAbsolute 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:

  • build followed by install with the same PREFIX must produce a working installation.
  • clean must remove build artifacts so that a subsequent build reconfigures and recompiles from scratch.
  • distclean must 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 build resolves 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.