Skip to content

Tutorial

This tutorial walks you through the process of building a simple C++ project using Pajama. The project introduces several key Pajama features.

Download and Install Pajama

Note

The version number shown here will change as new versions of Pajama are released. If you are reading this to reinstall Pajama, first refresh this page.

curl -LO https://joshcameron.github.io/pajama-docs/pajama-0.1.20250702012117-py3-none-any.whl
python3 -m pip install pajama-0.1.20250702012117-py3-none-any.whl

The Pajama Python package contains a command line tool called pajama which is used to build projects. You can choose to install Pajama in your system Python as shown above, or in a virtual environment. If you choose to install Pajama in a virtual environment, you will need to activate the virtual environment before running the pajama command.

Clone the tutorial code

An example project is available on GitHub. Clone it to your local machine and follow along with the documentation below to learn how to use Pajama.

git clone git@github.com:joshcameron/pajama-tutorial.git
cd pajama-tutorial

Understand the project

In pajama-tutorial, you will find a simple C++ project containing the following files and directories:

pajama-tutorial directory tree
pajama-tutorial
├── .pajama
├── .pajama-project
│   └── config.toml
└── src
    ├── .pajama
    ├── a.cpp
    ├── a.h
    ├── b.cpp
    ├── b.h
    ├── c.cpp
    └── main.cpp

A Pajama project / The .pajama-project directory

The .pajama-project directory identifies the parent directory as the "project directory" -- the root directory of a Pajama project.

When you use the pajama command at the prompt, the first thing it does is attempt to determine what Pajama project it is working with. It looks in the current directory and upwards through the parent directory hierarchy for a .pajama-project subdirectory.

The .pajama-project directory initially contains a config.toml file, which specifies configuration options for the project. As you work with Pajama, other files may be created in this directory.

.pajama files

Your project directory and the src directory below it both contain .pajama files. These files describe how to build the project. Each file describes how to build the contents of its directory.

.pajama files are Python files. If you already know Python, you are well on your way to writing Pajama code.

Let's take a look at the .pajama file in the src directory. It looks like this:

src/.pajama
from pajama import cpp, libtool, settings

cpp.select_compiler('clang.cpp.compiler')

current_src_dir = settings.get('build.current_src_dir')
settings.set('clang.cpp.flags', f'-I{current_src_dir}')
current_build_dir = settings.get('build.current_build_dir')
settings.set('clang.cpp.flags', f'-L{current_build_dir}')

a_object = cpp.compile_to_object('a.cpp')
b_object = cpp.compile_to_object('b.cpp', 'b.o')

# Set to true if you want to link with a static library instead of a dynamic library.
want_static_lib = True

static_lib = None
dynamic_lib = None

if want_static_lib:

    static_lib = libtool.create_library([a_object, b_object], 'ab', dynamic=False)

else:

    dynamic_lib = libtool.create_library([a_object, b_object], 'ab')

# Compile main.cpp and link with the library.
executable = cpp.compile_to_executable(
    'main.cpp',
    static_lib=static_lib,
    dynamic_lib=dynamic_lib)

Just like a standard Python file, we begin by importing modules we need in the code below.

The cpp module provides functions for selecting a compiler and compiling C++ code. In this file, we use cpp.select_compiler() to select the clang.cpp.compiler tool. Subsequent calls to the cpp.compile_to_object() and cpp.compile_to_executable() functions use the clang compiler to compile the code.

The cpp module is an example of what is called an abstract tool. When a function from the cpp module is invoked in a .pajama file, the action taken by the function will be performed using the currently selected concrete tool. There are many possible C++ compilers, of course, and in this example we select clang.cpp.compiler to perform our C++ compiles. Using the functions provided by a generic tool like cpp allows us to write code which is agnostic of the actual compiler being used. This makes it easy to change the compiler used for all or part of the project.

The settings module provides functions for setting and getting build settings. Settings are used implicitly by the tools they are associated with. For example, the clang.cpp.flags setting is used by clang.cpp.compiler to set the flags used when compiling C++ code. Settings can also be used explicitly, as shown in this code where we use the build.current_src_dir and build.current_build_dir settings to set the include and library paths for the compiler.

The libtool module provides functions for creating static and dynamic libraries. In this file, we use libtool.create_library() to create a static or dynamic library from the object files. The decision to create a static or dynamic library is controlled by an ordinary Python if statement and an ordinary Python variable.

The return values of settings.get(), cpp.compile_to_object(), libtool.create_library() and cpp.compile_to_executable() are all build artifacts. Artifacts represent entities like files, directories, and strings used in the build process. Some artifacts are created by actions of the build process, while others -- such as source files -- exist prior to invoking the build.

Initial build / full build / clean build

To build pajama-tutorial for the first time:

cd pajama-tutorial
pajama build --clean

The pajama build --clean command deletes all existing output artifacts (if any) and performs a full build -- all actions necessary to build all output artifacts described by the build's .pajama files.

If ever you want to ensure every output artifact of your project gets rebuilt, you can do so with pajama build --clean.

Incremental build

During your day to day work, you will typically perform an incremental build. This means using Pajama to build only the output artifacts which need to be built, based on the current state of the project. For example, if you modify main.cpp, you can run:

pajama build

Pajama will then only rebuild the output artifacts which depend on main.cpp, such as a.out, the executable created from main.cpp. It will not rebuild the static or dynamic libraries, since neither a.cpp nor b.cpp have changed.

Pajama infers the dependencies of the build based on how build artifacts are created from other artifacts. For example, main.cpp is an input artifact to a build action which creates output artifact a.out. Pajama has also cached information about the actions performed in the previous build. It knows if main.cpp has not been modified, and the settings relevant to the action which creates a.out have not changed, then a.out does not need to be created again. In an incremental build, Pajama does only what needs to be done.

Note

When building a project for the first time, a pajama build command is equivalent to a pajama build --clean command, because none of the output artifacts exist yet.

Features

  • Library to share code among multiple applications
  • .cpp to .o
  • .o to .so (dynamic library)
  • .o to .a (static library)
  • executable
  • full build, then incremental: note that incremental is fast
  • add .cpp to the build: note that other .cpps don't need to be rebuilt.

Current limitations

Pajama is currently in a very early alpha state.

  • It is only tested on macOS
  • The only supported toolset is clang
  • Esoteric clang flags might not work. Let me know if something you need is not supported.
  • Parallelism is not yet supported.

The focus is on demonstrating the key differentiating feature of Pajama: it allows you to work with the build system in a more intuitive way, because it's so similar to how you would manually build code from the command line.