Workspace
The workspace is the top-level repository that orchestrates all Ideal components. It checks out component
repositories side by side under components/, builds them in dependency order, and installs their artifacts into a
shared staging directory so they can discover each other at build time.
The design is described in ADR-0004 — Workspace and Component Protocol. This page is the reference for the workspace itself: its configuration, manifest format, available targets, and VCS integration.
Repository layout
ideal/
├── GNUmakefile # Workspace orchestration
├── manifest.mk # Component registry
├── local.mk # User-specific overrides (not tracked)
├── scripts/
│ └── component-vcs.sh # VCS operations for components
├── components/
│ ├── cmake-modules/ # Shared CMake build infrastructure
│ └── ... # Other components
├── docs/ # Documentation portal (Docusaurus)
├── stage/ # Staging directory (installed artifacts)
└── local/ # Local reference documents (not published)
Configuration
Defaults are defined in GNUmakefile. Override them in local.mk at the workspace root — this file is not tracked
by version control.
Workspace settings
| Variable | Default | Description |
|---|---|---|
VCS | git | Version control system (git/jj) |
BRANCH | main | Default branch for all components |
COMPONENTS_DIR | components | Component checkout directory |
STAGE_DIR | stage | Staging directory for build output |
PREFIX | <workspace>/stage | Install prefix |
DEFAULT_REMOTE | https://git.sr.ht/~cpradog | Base URL for repositories |
RELEASE_BRANCH_PREFIX | stable/ | Release branch prefix |
local.mk
When a component is cloned (make sync), the workspace generates a local.mk inside the component that includes
the workspace's local.mk:
-include /path/to/workspace/local.mk
BRANCH ?= main
This gives every component access to the workspace-wide configuration. Variables set in the workspace local.mk
propagate to all components automatically.
A typical workspace local.mk:
WORKSPACE_DIR := $(patsubst %/,%,$(dir $(lastword $(MAKEFILE_LIST))))
VCS := jj
DEFAULT_REMOTE := git@git.sr.ht:~cpradog
PREFIX := $(abspath $(WORKSPACE_DIR)/stage)
SYSROOT := $(abspath $(WORKSPACE_DIR)/stage)
PRESET := dev
WORKSPACE_DIR resolves to the directory containing local.mk, so the paths work both when included from
GNUmakefile and when included from a component's own local.mk.
Component manifest
Components are declared in manifest.mk. Each entry adds a name to the COMPONENTS list and optionally overrides
its default variables.
COMPONENTS += mylib
mylib_REMOTE ?= $(DEFAULT_REMOTE)/ideal-mylib
mylib_BRANCH ?= $(BRANCH)
mylib_DEPS ?= sx
Per-component variables
| Variable | Default | Description |
|---|---|---|
<name>_REMOTE | $(DEFAULT_REMOTE)/<name> | Repository URL |
<name>_BRANCH | $(BRANCH) | Branch override |
<name>_REV | (unset) | Pin to a commit or tag (takes priority over branch) |
<name>_PATH | $(COMPONENTS_DIR)/<name> | Local checkout path |
<name>_DEPS | (empty) | Space-separated list of component dependencies |
When _REV is set, it takes priority over _BRANCH. This is useful for pinning a component to a specific release
tag or commit.
Dependencies listed in _DEPS must refer to other components declared in COMPONENTS. The workspace validates
this at parse time and reports an error if a dependency is not found.
Intrinsic dependencies
Components listed in INTRINSIC_DEPS are automatically added as build and install dependencies for every component
in COMPONENTS. This is intended for shared build infrastructure that all components need but should not have to
declare individually.
INTRINSIC_DEPS += cmake-modules
In the example above, every component automatically depends on cmake-modules at build and install time — there is
no need to add it to each component's _DEPS.
Intrinsic dependencies themselves are excluded from this automatic injection to avoid circular references. If an
intrinsic dependency needs another intrinsic dependency, it must declare it explicitly in its own _DEPS.
A component listed in INTRINSIC_DEPS must also be listed in COMPONENTS — INTRINSIC_DEPS controls the
automatic injection behavior, while COMPONENTS is where the component is actually declared and configured.
Staging directory
The staging directory (stage/ by default) is the mechanism through which components discover each other during
development. When a component is built, it is immediately installed into the staging directory. Subsequent
components find headers, libraries, and pkg-config files there.
The workspace passes two variables on every make invocation:
| Variable | Description |
|---|---|
PREFIX | Where the component must install its files |
SYSROOT | Where dependencies have been staged (for lookup) |
During workspace builds, both point to the staging directory. During make install, PREFIX points to the final
installation path while SYSROOT still points to the staging directory.
Targets
Build
| Target | Description |
|---|---|
build | Build all components in dependency order |
build-<name> | Build a single component (and its dependencies) |
test | Run tests for all components |
test-<name> | Run tests for a single component |
build compiles each component and installs it to the staging directory so that dependent components can find it.
The order is determined by _DEPS and INTRINSIC_DEPS.
test first builds the component (if not already built), then runs its test suite. Components without a test
target are silently skipped.
Install
| Target | Description |
|---|---|
install | Install all components to PREFIX |
install-<name> | Install a single component |
uninstall | Uninstall all components from PREFIX |
uninstall-<name> | Uninstall a single component |
install rebuilds each component with the final PREFIX and installs it. This is distinct from the staging install
that happens during build.
Clean
| Target | Description |
|---|---|
clean | Clean build artifacts for all components |
clean-<name> | Clean a single component |
distclean | Clean all components and remove the staging directory |
distclean-<name> | Distclean a single component |
Documentation
| Target | Description |
|---|---|
docs | Build component docs and the documentation site |
docs-<name> | Build and deploy docs for a single component |
docs-serve | Build component docs and serve the site with live-reload |
The docs target first builds documentation for each component (running their docs and push-docs targets),
then builds the Docusaurus documentation site. Components without a docs target are silently skipped.
Version control
| Target | Description |
|---|---|
sync | Clone or fetch + switch all components |
sync-<name> | Sync a single component |
fetch | Fetch remote changes for all components |
fetch-<name> | Fetch a single component |
switch | Switch all working copies to their manifest revision |
switch-<name> | Switch a single component |
status | Show each component's status relative to the manifest |
status-<name> | Show status for a single component |
The workspace supports both Git and Jujutsu (jj). Set VCS in local.mk to
choose. The sync target clones a component if it does not exist locally, or fetches and switches if it does.
Release
| Target | Description |
|---|---|
check-release MAJOR=X | Validate that all components are ready for release |
release MAJOR=X | Create release branches and tag vX.0.0 |
Release creates a stable/X branch in each component and tags the initial release. The documentation site is
versioned as part of the release process.
See ADR-0007 — Versioning and Release Model for the full versioning policy.
Scaffolding
| Target | Description |
|---|---|
new NAME=<name> DESC="<description>" | Create a new component |
Scaffolding initializes a new component repository under components/, populates it with standard project files
from the cmake-modules template, adds it to manifest.mk, and configures its local.mk. Pass REPO_NAME= to
override the default repository name (ideal-<NAME>).
See the Project Scaffolding reference for details on generated files and presets.
Dependency resolution
The workspace resolves build order from declared dependencies. When you run make build, the dependency chain
determines the order:
- Components in
INTRINSIC_DEPSare built first (they have no automatic dependencies on each other). - Each component's explicit
_DEPSare built and staged before the component itself. - The combination of intrinsic and explicit dependencies forms a DAG that Make resolves automatically.
For example, given:
INTRINSIC_DEPS += cmake-modules
COMPONENTS += cmake-modules
COMPONENTS += sx
COMPONENTS += toolkit
toolkit_DEPS = sx
Running make build-toolkit triggers: build-cmake-modules (intrinsic) and build-sx (explicit dep, which itself
depends on build-cmake-modules), then build-toolkit.