A simplistic C++ build tool based on Ninja with first-class support for C++20 modules.
Go to file
Lexi / Zoe 6f6e52f97f
Add more documentation to README.md
2025-10-18 00:56:39 +02:00
examples Add web target to SDL example (without SDL3 files) 2025-10-18 00:56:19 +02:00
src Initial code commit (shuriken 0.1.0) 2025-10-17 23:47:12 +02:00
.gitignore Add text to README and .gitignore 2025-10-17 23:35:11 +02:00
LICENSE Initial commit 2025-10-17 20:19:39 +02:00
README.md Add more documentation to README.md 2025-10-18 00:56:39 +02:00

README.md

shuriken

A simplistic C++ build tool based on Ninja with first-class support for C++20 modules.

It's written in Python with minimal dependencies (so far only PyYAML) and as a single file, to allow for easy integration into a project and quick customizability if needed.

Design goals

First and foremost, the goal of this project is to have a simple C++ build script for my own personal projects. It is not designed as a general purpose build system, but specifically tailored to my own use cases.

That being said, feel free to use it in your own projects if it works for you, and tell me your feedback.

The main design goals are as follows:

  • Keep it simple by relying on conventions rather than supporting every possible use case.
  • Support C++20 modules for structuring code without header files, but without supporting every possible use case of modules.
  • Allow building for multiple targets (e.g. Linux, Windows, WebAssembly).
  • Allow overriding config values with a local override file instead of modifying the build config.
  • Autodetect source files instead of listing them all manually.

Requirements / conventions

Projects need to follow some rules to allow building them with shuriken.

  • All source code must be in a source directory (defaults to src, configurable with source_dir).
  • All build artifacts will be created in a build directory (defaults to build, configurable with build_dir) with subdirectories for each target (e.g. build/linux/).
  • C++ source files must use the extension .cppm if they define a module, .cpp otherwise.
  • Modules must be defined in source files using the same path and name as the module, replacing . with /. (For example, the module foo.bar.baz must be defined in src/foo/bar/baz.cppm.)
  • Modules can only be defined in one file. Splitting module interface and implementation is not supported.
  • Clang should be used as the compiler for the default target. While the compiler itself is configurable, the build dependency detection for modules uses clang-scan-deps. Other targets can use other compilers, since dependency detection is always done using the default target.

Installation

While you could theoretically install the shuriken script in your system, it is recommended to instead add it to your project.

It's just a single Python file, and adding it to your project has several advantages. For example, if someone wants to build your project, they don't need to install yet another obscure build tool on their system. You also avoid issues because of different shuriken versions and you can modify the build script if needed.

However, you do need to install the dependencies used by shuriken on your system if you don't have them installed already. These are the Python library PyYAML, the Ninja build system (minimum version 1.10) and of course the Clang compiler.

On Debian: apt install pyyaml ninja-build clang

On Arch Linux: pacman -S python-yaml ninja clang

Finally, copy the file src/shuriken.py from this repository to your project and make sure it's executable (chmod 755).

You can put the file anywhere you want, one example would be to use tools/shuriken.py. You can then define a shell alias like alias shuriken=tools/shuriken.py so that you can easily run commands like shuriken build. Alternatively, copy the file to shuriken in the root directory of your project and run it like ./shuriken build.

Configuration

To run shuriken, a build configuration is required. A shuriken config is a YAML file that defines the build targets as well as build parameters like which compiler and linker flags to use.

By default, shuriken looks for a file shuriken.yaml in the project root directory, although you can specify a different config using the -c parameter.

A minimal shuriken config looks like this:

default_target: linux

targets:
  linux:
    output_file: exampleapp

This will build all C++ source files from src using clang++ and output an executable file in build/linux/exampleapp.

A complete example using all available config settings looks as follows. Unless specified otherwise, the shown values are the defaults.

# Directory containing the source files
source_dir: src

# Directory where build artifacts and the output files are stored
build_dir: build

# Default values that are used for every target unless overridden in "targets".
# The specification is the same as for the targets below. The "_extra" keys
# not intended to be set here, but can be used in "targets" to extend rather
# than override settings. For example, you can define compile flags using
# "cpp_flags" here and add target-specific flags in "cpp_flags_extra" without
# overriding the defaults.
defaults:
  # Which compiler to use for C++ files
  cpp_compiler: clang++

  # Which C++ standard to use (value for "-std=" compile flag)
  cpp_standard: c++20

  # Compiler flags for compiling C++ files (default: empty)
  # (Note that "-std=..." will be set automatically based on "cpp_standard"
  # and should not be set here!)
  cpp_flags: -Wall -Wextra -Werror -pedantic
  #cpp_flags_extra:
  
  # Linker flags for linking the output file (default: empty)
  # (These flags are used at the start of the linker command, see also
  # "linker_args" below.)
  linker_flags:
  #linker_flags_extra:
  
  # Additional linker arguments that are *appended* to the linker command,
  # i.e. following the list of object files built by the compiler.
  # This should be used to specify libraries to link against, since the order
  # of arguments in linker commands is relevant. (default: empty)
  linker_args: -lSDL3 -lSDL3_image
  #linker_args_extra:
  
  # File name of the output file, i.e. the executable generated by the linker.
  # This is relative to "{build_dir}/{target}/" and can contain subdirectories.
  # Usually this is target-specific and should therefore be set in "targets".
  # (default: empty)
  output_file:
  
  # The command for running the built project when using "shuriken run".
  # This command will be run in a shell and can therefore contain shell syntax.
  # It supports two parameters: "{out}" will be replaced with the (relative)
  # path of the output file, i.e. "{build_dir}/{target}/{output_file}".
  # "{args}" will be replaced by any arguments given to "shuriken run".
  # Defaults to running the output file.
  run_command: ./{out} {args}

# Which target (defined below in "targets") to build when calling shuriken
# without specifying a target. This must be defined.
# The default target is also used for scanning the project for build
# dependencies (since "clang-scan-deps" might not work with other compilers,
# e.g. when building for WebAssembly).
default_target: linux

# Definitions for build targets. The keys are the names of the target, the
# values are mappings following the same specification as "defaults" above).
# (Note that the target names don't have any special meaning, e.g. "linux"
# and "web" are just arbitrary strings. They are used in file paths however,
# so you should stick to alphanumeric characters, "-" and "_" to avoid issues).
targets:
  # Example for a target that uses the values from "defaults" as defined above
  # and only sets the output filename (required unless defined in "defaults").
  linux:
    output_file: exampleapp

  # Example for a target using emscripten to compile the app to WebAssembly.
  # Every setting not specified here will be taken from "defaults" above.
  web:
    # Override which compiler to use.
    cpp_compiler: em++
    
    # Use the default flags from "defaults.cpp_flags", but add an additional
    # flag for setting a custom include path.
    cpp_flags_extra: -Ivendor
    
    # Override linker arguments. Instead of using "-lSDL", we need to link
    # against static libraries for a web build. Also specifies other arguments
    # specific to emscripten.
    # (We use a YAML multiline string here for better readability. It has the
    # same meaning as a regular string. See https://yaml-multiline.info/).
    linker_args: >-
      lib/web/libSDL3.a
      lib/web/libSDL3_image.a 
      --use-preload-plugins
      --preload-file assets/      
    
    # Set a different output filename. Emscripten will generate an HTML file
    # with this name, as well as some additional files in the same directory.
    # We use a subdirectory here to separate the files from other build files.
    output_file: output/index.html
    
    # Override the command that runs when using "shuriken run". We cannot
    # execute an HTML file, so we start an ad-hoc web server using Python
    # instead which will serve the generated files.
    run_command: python -m http.server -d build/web/output

Command usage

To get an overview of all commands and options, use shuriken --help or shuriken COMMAND --help.

The most common commands are as follows. If no target is specified, shuriken will use the default target as defined in the config.

  • shuriken [-t TARGET] build: Builds the application for the given target. This is the default command, so just shuriken works as well.
  • shuriken [-t TARGET] run: Builds the application and runs it (see run_command in the config).
  • shuriken [-t TARGET] clean: Removes all generated files for the given target from the build directory, including the output file.
  • shuriken clean --all: Removes all generated files of all targets from the build directory.