I have always felt that work in C++ projects was too cumbersome. I wanted to have a simple way to create a project, and I wanted to have a simple way to build and test it.
C/C++ third-party libraries are a pain to build, historically. You have to download the source, configure, build, and install it. This is a pain, especially if you are not using a package manager.
xmake for the rescue
xmake is a lightweight cross-platform build utility based on Lua. It uses xmake.lua to maintain project builds. Compared with makefile/CMakeLists.txt, the configuration syntax is more concise and intuitive. It is very friendly to novices and can quickly get started in a short time. Let users focus more on actual project development.
xmake is awesome and extremely easy for simple and even more complex and robust projects, it works as a package manager (which was the selling point for me) and as a project generator + build backend.
The installation can be done through curl
, but there are other methods in their docs.
bash <(curl -fsSL https://xmake.io/shget.text)
Sample project
To create a project we run:
xmake create sample-project # Notice that sample-project is the name, you can choose any other
The xmake.lua file should look like this:
add_rules("mode.debug", "mode.release")
target("sample-project")
set_kind("binary")
add_files("src/*.cpp")
-- A bunch of comments
First, I usually set the language to use the standard c++20 with set_languages("c++20")
, then I add a list of libs that this project will have, for now, I will only include the {fmt} lib with local libs = { "fmt" }
, removing the comments it should look like this now:
set_languages("c++20")
add_rules("mode.debug", "mode.release")
local libs = { "fmt" }
target("sample-project")
set_kind("binary")
add_files("src/*.cpp")
The folder structure that I adopted is having the includes and source folder for the application-specific scope like a library implementation and the standalone folder as the binary API entry point for the library.
.
├── benchmark (optional in case we need benchmark)
├── include
├── source
├── standalone
│ └── main.cpp
├── test (optional in case we need tests)
└── xmake.lua
As we added the include and source folder for the library we need to adapt xmake.lua
set_languages("c++20")
add_rules("mode.debug", "mode.release")
local libs = { "fmt" }
add_includedirs("include")
add_requires(table.unpack(libs))
target("sample-project-lib")
set_kind("static")
add_files("source/**/*.cpp")
add_packages(table.unpack(libs))
target("sample-project")
set_kind("binary")
add_files("standalone/main.cpp")
add_packages(table.unpack(libs))
add_deps("sample-project-lib")
-- NOTE: Requires a test library to be installed
-- target("test")
-- set_kind("binary")
-- add_files("test/*.cpp")
-- add_packages(table.unpack(libs))
-- add_deps("sample-project-lib")
-- NOTE: Requires a benchmark library to be installed
-- target("benchmark")
-- set_kind("binary")
-- add_files("benchmark/*.cpp")
-- add_packages(table.unpack(libs))
-- add_deps("sample-project-lib")
Now that we added the packages to the binary standalone when we run xmake to compile it the next time it’ll be prompting us to download {fmt} through xrepo, the first time we download a version of a library it’ll keep it at ~/.xmake/packages
for future projects.
Now, with standalone/main.cpp like
#include <fmt/core.h>
auto main() -> int {
fmt::print("Hello, {}!\n", "world");
return 0;
}
We can run xmake && xmake run
to build and run the default target, if we want to specify another target we can run xmake run target_name
would produce the same effect as xmake run sample-project
.
Boilerplate Repo
If you are looking for a easy to begin with boilerplate I’ve created one at my GitHub.