cxpm docs

Quickstart

This goes from nothing to an installed package of a library of your own: create it, build it, test it, pack it, install it.

The whole walkthrough, recorded against a freshly built cxpm by misc/record-quickstart-cast.sh:

Install cxpm

cxpm needs a C++23 compiler (GCC 14 or newer), pkg-config and git.

git clone --recurse-submodules https://github.com/arthurafarias/cxpm
cd cxpm
./cxpm-build.sh --install --prefix /usr/local

cxpm-build.sh compiles a first cxpm with the bare compiler; that one builds and packs cxpm from its own manifests and installs the packages. Add --root DIR to install under another root, or --package-manager pacman (or rpm, deb) to install it as a package of your distribution.

Building with CMake

The repository also carries a CMake build of the same projects, for working on cxpm itself with an IDE, a debugger or ctest. It needs CMake 3.23 or newer:

cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
ctest --test-dir build --output-on-failure
build/bin/cxpm --help                       # runs from the build tree
cmake --install build --prefix /usr/local   # the same layout `cxpm install` makes

cmake --install copies files and registers nothing with a package manager; a cxpm that should be removable as a package is installed with cxpm-build.sh --install.

bubblewrap (bwrap) is optional and recommended: with it, manifests and build steps run confined .

Create a project

cxpm project create greeter --type static-library --tests --example
cd greeter
greeter/package.cpp                   the library
greeter/include/greeter/greeter.hpp
greeter/src/greeter.cpp
greeter/tests/package.cpp             a project that tests it
greeter/tests/src/main.cpp
greeter/example/package.cpp           a project that uses it
greeter/example/src/main.cpp

package.cpp is the manifest:

#include <CXPM/Project.hpp>
#include <CXPM/Target.hpp>

using namespace CXPM;

auto target = Target()
                  .name_set("greeter")
                  .version_set("0.1.0")
                  .type_set("static-library")
                  .sources_append({"src/*.cpp"})
                  .include_directories_append({"include"})
                  .create();

auto project = Project().target_set(target).members_append("example").tests_append("tests").create();

A project has one target. The tests and the example are projects too: each has its own package.cpp whose target depends on greeter by name.

--type is executable (the default), shared-library, static-library or header-library.

Build and test

cxpm build
cxpm test
Building greeter (static-library) with g++
Installing greeter into .../greeter/.cxpm/sysroot
Building greeter-example (executable) with g++
Building greeter-tests (executable) with g++
Running greeter-tests
greeter-tests passed
1 of 1 tests projects passed

Everything cxpm writes goes under .cxpm/: objects and the built target in .cxpm/build/<name>/, and the library installed where the example and the tests find it, in .cxpm/sysroot/. Nothing is written to your system. A second cxpm build compiles nothing; changing one source recompiles that object and links again.

cxpm test exits with 0 only if every tests project builds and its executable exits with 0.

Add a dependency

A dependency is a name and, optionally, a version constraint:

.dependencies_append({"zlib >= 1.2"})

cxpm dependency add "zlib >= 1.2" writes that line for you, and fetches the dependency when the package index publishes it.

The name is a project of the same workspace, a dependency the manifest fetches, or a package installed in the sysroot, found through pkg-config. One that is fetched says where from:

#include <CXPM/Dependency.hpp>

.dependencies_append(Dependency()
                         .name_set("greeter")
                         .repository_set("https://example.org/greeter.git")
                         .revision_set("v0.1.0"))

cxpm fetch clones it into .cxpm/sources/ and writes the exact commit to .cxpm/lock.json; cxpm build then builds it from its own manifest and installs it into .cxpm/sysroot. Commit the lock file if you want everyone to build the same commit.

Pack and install

cxpm pack
cxpm install --prefix /usr/local
Packed .../greeter/.cxpm/packages/greeter-0.1.0.cxpm.tar
Packed .../greeter/.cxpm/packages/greeter-example-0.1.0.cxpm.tar
Installing greeter into /usr/local with archive
Installing greeter-example into /usr/local with archive

Every member of the workspace is packed and installed, so the example goes along as bin/greeter-example; drop members_append("example") from the manifest to ship the library alone.

Installed:

/usr/local/lib/libgreeter.a
/usr/local/include/greeter/greeter/greeter.hpp
/usr/local/lib/pkgconfig/greeter.pc
/usr/local/lib/cxpm/packages/greeter.json      what was installed, for uninstall

Another project now depends on it with .dependencies_append({"greeter"}), and anything that speaks pkg-config finds it with pkg-config --cflags --libs greeter.

To get a package of your distribution instead of an archive:

cxpm pack --package-manager pacman     # or rpm, deb
sudo cxpm install --package-manager pacman --prefix /usr

cxpm uninstall removes what cxpm install installed, through the same package manager.

Where to go next

  • The cxpm command describes every stage, the manifest API, toolchains, building for another sysroot and confinement.
  • cxpm help <command> lists the options of a command.