cxpm docs

Architecture

The lifecycle

Every command is a stage, and each stage runs the ones before it when they are stale. All of them are methods of Controllers::ProjectManager.

argv
  │
  ▼
Views::ApplicationView            a cx::console::cli router; owns usage and exit codes
  │
  ▼
Controllers::ProjectManager
  │
  ├─ fetch       Fetcher clones each dependency that names a repository, or that the
  │              package index publishes (IndexClient), into .cxpm/sources/ and records
  │              the commit in .cxpm/lock.json
  │
  ├─ configure   ManifestLoader loads the manifest; ProjectGraph orders the workspace;
  │              ToolchainManager picks a toolchain; PkgConfigManager resolves every
  │              dependency. The result is written to .cxpm/configuration.json
  │
  ├─ build       ToolchainDriver turns the configuration into compile, link and archive
  │              commands and runs the stale ones on a thread pool
  │
  ├─ test        builds each tests project and runs its executable
  │
  ├─ pack        Staging lays the target out under .cxpm/staging/ as it will be installed;
  │              a PackageManager turns that tree into a package in .cxpm/packages/
  │
  └─ install     the same PackageManager installs the package under <root><prefix>
     uninstall   and removes what its record lists

Every external program in that picture — the compiler, pkg-config, git, rpmbuild, the sandbox helper — is started by Controllers::ProcessRunner, as an argument vector with no shell.

Everything a project produces is under .cxpm/

.cxpm/
  descriptors/          package.cpp compiled into a plugin, reused until the source changes
  sources/<name>/       fetched repositories
  lock.json             the commit (and index version) each fetched dependency resolved to
  configuration.json    the configured toolchain, dependencies and options
  build/<target>/       objects, the built target, compile_commands.json
  staging/              the target laid out as it will be installed
  packages/             the package made from the staging tree
  sysroot/              (workspace root) the members other members depend on, installed

Deleting .cxpm/ returns a project to a clean checkout.

Workspaces

A project has exactly one target. What other build systems express as several targets in one project is several projects here: a root manifest lists members and tests, and ProjectGraph sorts them by their declared dependencies and rejects a cycle.

When a member depends on another member, the dependency is packed and installed into the workspace’s own .cxpm/sysroot, and the dependent finds it there through pkg-config, exactly as it would find a package installed on the system. Nothing is found by reaching into a sibling’s source directory. cxpm builds itself this way: cxpm-interface and the cx libraries are installed into the sysroot, and cxpm compiles against them.

Two layers in one library

cxpm-interface is header-only and has two audiences:

  • The manifest API: Target, Project, Toolchain, Dependency, their descriptors, CompilerOptions, Status and the exception types. A package.cpp or toolchain.cpp includes only these.
  • The tool internals: everything under CXPM::Controllers, CXPM::Manifest, CXPM::Views and CXPM::Modules::Generators, built on the cx libraries under third-party/.

Components

Component Responsibility
Views::ApplicationView The command router: commands, options, usage text, exit codes
ProjectManager The lifecycle: decides what is stale and runs each stage
ManifestLoader Loads package.cpp (through a plugin) or package.json into one ProjectDescriptor
Workspace, ProjectGraph Finds the workspace root, its members and tests, and orders them
Fetcher, VersionConstraint Clones dependencies, checks versions against constraints, keeps the lock file
PackageIndex, IndexServer The package index: its store and HTTP API, and cxpm serve binding it to a TCP port or a unix socket with cx-networking
IndexClient, Publisher The client side: what fetch, search, login and publish say to an index, and the stored tokens
Configuration Reads and writes .cxpm/configuration.json and decides whether it is still valid
ToolchainManager Finds toolchain descriptors (toolchain.cpp or toolchain.json) and selects one
ToolchainDriver, SourcePatterns Expands source globs, builds the command lines, runs the stale ones in parallel
PkgConfigManager Resolves dependencies through pkg-config; writes a library’s .pc file
Staging Lays a built target out as it will be installed, for each target type
PackageManager and PackageManagers The interface pack, install and uninstall go through, and the registry of backends
ArchivePackageManager, PacmanPackageManager, RpmPackageManager, DebPackageManager The backends: a plain tar with an install record, and the three native formats
TarArchive, Sha256, NativePackageSupport Reproducible archives and checksums the backends share
DescriptorPlugin Compiles a package.cpp/toolchain.cpp and a generated loader into a shared object
DescriptorSandbox Runs cxpm-descriptor-sandbox to read a descriptor out of that shared object
ProcessRunner The one place a process is started; applies confinement; replaceable in tests
Installation Where cxpm finds its own pieces: headers, toolchain directories, host compiler, helper
JsonManifest.hpp (CXPM::Manifest) Descriptor to JSON and back: package.json, toolchain.json, the configuration, the sandbox’s output
Modules::Generators::ManifestGenerator Backs cxpm generate and cxpm project create

Two manifest formats, one representation

package.cpp is a real C++ translation unit. DescriptorPlugin compiles it, with a generated loader, into a shared object under .cxpm/descriptors/; the manifest language is as expressive as C++, with no parser or schema of its own to keep in step. The compiled manifest is reused until package.cpp changes.

package.json is the declarative alternative, parsed straight into the same ProjectDescriptor with no compiler involved. Everything after “the manifest is loaded” is identical for both. package.cpp wins when both exist.

Both carry a schema number. A descriptor from a newer cxpm than the one reading it is refused rather than misread.

Toolchains are data, not code in cxpm

A toolchain is a ToolchainDescriptor: the executables, how each neutral option (standard-c++23, optimize-speed, …) is spelled, how files are named, and optionally the sysroot it compiles against and the package manager of that system. It comes from a toolchain.cpp plugin or a toolchain.json. The ones under modules/toolchains/ are projects of type data that install their toolchain.cpp into share/cxpm/toolchains/<name>/.

Running code cxpm did not write

Two things cxpm runs are not its own: a compiled manifest, and the build of a project.

The manifest. cxpm never calls dlopen. DescriptorSandbox starts cxpm-descriptor-sandbox, a disposable child with CPU, memory and time limits, which loads the plugin and prints its descriptor as JSON, the same JSON package.json is parsed from.

The build. ProcessRunner wraps the sandbox helper and every compile and link in a bubblewrap namespace: no network, the project directory writable, and only the toolchain, the system directories, the sysroot in use and the workspace sysroot readable. A manifest that reads your home directory or opens a socket fails. CXPM_CONFINEMENT chooses between auto (confine when bubblewrap works), required and off.

What this does not cover is stated in tooling.md : cxpm test runs the project’s own tests unconfined, and /etc is readable.

Where the design is recorded

The requirements this implements, and the findings in the original implementation that led to it, are on the project board, planning.pb at the root of the repository, under the “SRS” headings. roadmap.md says which milestone did what.