premake5
Premake is a simple build system using the lua programming language. I prefer this over cmake for a few reasons.
- Project configurations are easier to follow
- Tool specific languages tend to be underdocumented
- cmake can get pissy about whitespace
- Having a general language built in can be very powerful
Do take into consideration that premake is not nearly as widely adopted as cmake so more often then not you'll have to configure the scripts yourself. For this reason I purposfully choose libraries that are easy to build.
General Directory Structure
The directory structure I use is to make the project as easy to navigate as possible. The directory structure is shown below. Git and other toolchains may add more to this but generally aren't too important to worry about. We just add them to the .gitignore
.
project_root
├─ bin // Binaries, libraries, and debug symbols
├─ bin_int // Compilation and linkage intermediates
├─ app // Project
│ ├─ build.lua // Project build script
│ ├─ source // Project source code
│ └─ vendor // Single header libraries
├─ premake // Premake scripts for third party libraries
├─ vendor // Third party libraries, typically added via git submodules
├─ working // Working directory (assets)
└─ premake5.lua // Main build script
Configuring the Main Build Script
The main build script mostly contains default values for all dependencies and projects. The generalized script is shown below with comments.
premake5.lua
workspace "workspace_solution_name"
-- What architectures to support
-- I only bother supporting 64-bit
architecture "x86_64"
-- List of configurations
-- `debug` and `release` are typical, `dist` is an extra level above release for removing as much debugging as possible
configurations { "debug", "release", "dist" }
-- This will be the default startup project
startproject "app"
-- Use all CPU cores for compilation
flags "MultiProcessorCompile"
-- Use latest language versions
language "C++"
cppdialect "C++latest"
cdialect "C17"
-- Link the static runtime, makes applications a bit larger
-- but prevent end users from ever missing a required dll
staticruntime "On"
-- Basic optimization technique
stringpooling "On"
-- Edit and continue may interfere with certain debugging tools
editandcontinue "Off"
-- By default most projects will be static libraries
-- This can and will be overwritten occasionally
kind "StaticLib"
-- Specify build paths
-- Eg: bin/windows_debug/app.exe
-- Eg: bin/linux_release/app
targetdir "%{wks.location}/bin/%{cfg.system}_%{cfg.buildcfg}"
objdir "%{wks.location}/bin_int/%{cfg.system}_%{cfg.buildcfg}"
-- Setup configuration for debug builds
filter "configurations:debug"
runtime "Debug"
optimize "Debug"
symbols "On"
defines "_DEBUG"
defines "APP_DEBUG" -- A friendly macro for your own code
-- Setup configuration for release builds
-- Similar in concept but using release settings
filter "configurations:release"
runtime "Release"
optimize "Speed"
symbols "On"
defines "NDEBUG"
defines "APP_RELEASE"
-- Setup configuration for dist builds
filter "configurations:dist"
runtime "Release"
optimize "Speed"
symbols "Off"
defines "APP_DIST"
-- Additional optimization for dist
flags { "LinkTimeOptimization", "NoBufferSecurityCheck" }
-- Windows specific flags
-- Windows can be a bit weird in terms of compliance
filter "system:windows"
systemversion "latest"
-- The MIN and MAX macros are annoying
defines { "NOMINMAX", "APP_WINDOWS" }
-- `/EHsc` controls exception stack unwinding, Uses standard unwinding and assumes `extern "C"` functions are `nothrow`
-- `/Zc:preprocessor` enables the preprocessor to be conforming
-- `/Zc:preprocessor`makes makes the `__cplusplus` get defined correctly, this is usually default off, we arent in the early 2000s anymore
-- `/experimental:c11atomics` msvc still considers c atomics as experimental, every other major toolchain this is not the case
buildoptions { "/EHsc", "/Zc:preprocessor", "/Zc:__cplusplus", "/experimental:c11atomics" }
-- For linux all we need is our define
filter "system:linux"
defines "APP_LINUX"
-- For dist builds on windows, we want to use the winmain entrypoint, we'll just define a macro for this and define it further in code
filter { "configurations:dist", "system:windows" }
defines "APP_ENTRY_WINMAIN"
-- Clear our filters
filter {}
-- Include all premake files within the premake directory
-- We put them into the dependencies group
-- Groups dont affect code generation whatsoever
group "dependencies"
for _, matchedfile in ipairs(os.matchfiles("premake/*.lua")) do
include(matchedfile)
end
group ""
-- Include our apps build file
include "app/build.lua"
These just consider our defaults, we have to configure each project but with our defaults this is much easier to do.
Configuring the Main Project Script
app/build.lua
project "app" -- Matches startproject in main script
debugdir "../working" -- Set the working directory to the one in our directory structure
kind "ConsoleApp" -- Override the type to be a console app
-- Include all files of the following types
files {
"%{prj.location}/**.cpp",
"%{prj.location}/**.cc",
"%{prj.location}/**.c",
"%{prj.location}/**.hpp",
"%{prj.location}/**.h",
"%{prj.location}/**.inl",
}
-- Add these directies to our include paths
-- Third party includes go here too
includedirs {
"%{prj.location}",
"%{prj.location}/source",
"%{prj.location}/vendor",
}
-- Link to our third party libraries (none rn)
links {}
-- On windows include the .rc files
-- Link to the opengl32 dll
filter "system:windows"
files "%{prj.location}/*.rc"
links "opengl32"
-- On windows dist we want winmain
filter { "configurations:dist", "system:windows" }
kind "WindowedApp"
-- On linux we need to link against these libraries
filter "system:linux"
links { "pthread", "dl", "m" }
Generating Project Files
To generate project files from our scripts we simple execute premake5.
premake5 --help
will show you the available targets, gmake2
and vs2022
will be your most common targets.
Adding Third Party Libraries
The setup is generally pretty simple. We prefer to build from source.
- add the library as a submodule inside
vendor/library_name
- If the library needs compilation
-
- If the library includes a premake script, add it the
premake5.lua
file
- If the library includes a premake script, add it the
-
- Otherwise create your own inside
premake/
, you must properly define the symbols, include directories and files.
- Otherwise create your own inside
-
- Add the library to the
links
block insideapp/build.lua
- Add the library to the
- Add relavent include path include
app/build.lua
. Commonly will look like"%{wks.location}/vendor/library/include"