Unofficial community continuation of vector-of-bool/cmrc — see What Is This Fork? below.
CMakeRC is a resource compiler provided in a single CMake script that can easily be included in another project.
This repository is an unofficial, community-maintained continuation of vector-of-bool/cmrc, the original Standalone CMake-Based C++ Resource Compiler by vector-of-bool.
The original project has been unmaintained for over three years. Since then:
- 17 issues have accumulated without a response or fix
- 10 pull requests (including bug fixes and CMake compatibility updates) remain open and unreviewed
- The bundled code is starting to fall behind current CMake releases, and upcoming CMake deprecations will likely begin emitting warnings for the older requirement levels used in the project
This fork was created so that those fixes and improvements have somewhere to land — and so that the project keeps working with modern CMake for the people who use it.
This is a community continuation, not an official successor. The original author is not involved, and this fork carries no endorsement from them. The goal is simply to keep a useful, actively used library maintained.
All credit for the original design and implementation goes to vector-of-bool. The MIT license in this repository retains the original copyright notice; additional copyright lines cover only the modifications made in this fork.
For the purpose of this project, a resource compiler is a tool that will compile arbitrary data into a program. The program can then read this data from without needing to store that data on disk external to the program.
Examples use cases:
- Storing a web page tree for serving over HTTP to clients. Compiling the web page into the executable means that the program is all that is required to run the HTTP server, without keeping the site files on disk separately.
- Storing embedded scripts and/or shaders that support the program, rather than writing them in the code as string literals.
- Storing images and graphics for GUIs.
These things are all about aiding in the ease of portability and distribution of the program, as it is no longer required to ship a plethora of support files with a binary to your users.
CMakeRC is distributed as a CMake module, CMakeRC.cmake, along with the C++
source files it generates at build time: include/cmrc/cmrc.hpp (the runtime
header) and cmake/cmrc_lib.cpp.in (the per-library loader template). Keeping
these sources as real, readable files means they can be code-reviewed and
edited directly in the repository.
CMakeRC.cmake pulls in the other sources by relative path, so the whole
repository is the distribution unit — no external libraries or headers are
required.
This project was initially written as a "literate programming" experiment. The process for the pre-2.0 version can be read about here.
2.0.0+ is slightly different from what was written in the post, but a lot of it still applies.
FetchContent_Declare(
cmrc
GIT_REPOSITORY https://github.com/KeyWorksRW/CMakeRC2.git
GIT_TAG main
GIT_SHALLOW TRUE
DOWNLOAD_NO_PROGRESS TRUE
)
FetchContent_MakeAvailable(cmrc)
# cmrc's top-level CMakeLists already includes the module, but include by full
# path to guarantee cmrc_add_resource_library() is defined regardless of
# module-path resolution.
include("${cmrc_SOURCE_DIR}/CMakeRC.cmake")Alternatively, you can vendor the repository into your own tree (or copy just
CMakeRC.cmake, include/, and cmake/) and include it the same way. The
module resolves its helper sources relative to its own location, so the layout
must be preserved.
-
Once installed, simply import the
CMakeRC.cmakescript withinclude()to import the module. See Installing for how to make it available in your project. -
Once included, create a new resource library using
cmrc_add_resource_library, like this:cmrc_add_resource_library(foo-resources ...)
Where
...is simply a list of files that you wish to compile into the resource library.
You can use the ALIAS argument to immediately generate an alias target for
the resource library (recommended):
cmrc_add_resource_library(foo-resources ALIAS foo::rc ...)Note: If the name of the library target is not a valid C++ namespace
identifier, you will need to provide the NAMESPACE argument. Otherwise, the
name of the library will be used as the resource library's namespace.
cmrc_add_resource_library(foo-resources ALIAS foo::rc NAMESPACE foo ...)-
To use the resource library, link the resource library target into a binary using
target_link_libraries():add_executable(my-program main.cpp) target_link_libraries(my-program PRIVATE foo::rc)
Note: Linking into a shared library.
If you link the generated static resource library into a
SHAREDlibrary instead of an executable, you will hit a linker error such as:/usr/bin/ld: thelibx-resources.a(lib.cpp.o): relocation R_X86_64_PC32 against symbol ... can not be used when making a shared object; recompile with -fPICThis happens because a static library is normally compiled without position-independent code, but a shared library requires it. Fix it by setting the
POSITION_INDEPENDENT_CODEproperty on the generated resource library target:cmrc_add_resource_library(foo-resources ALIAS foo::rc NAMESPACE foo ...) set_property(TARGET foo-resources PROPERTY POSITION_INDEPENDENT_CODE ON) add_library(my-library SHARED mylib.cpp) target_link_libraries(my-library PRIVATE foo::rc)
The property must be set on the real target (
foo-resources), not on its alias (foo::rc), since aliases are not permitted inset_property(). Alternatively, you can setCMAKE_POSITION_INDEPENDENT_CODE ONglobally before defining your targets, which compiles everything with-fPIC. -
Inside of the source files, any time you wish to use the library, include the
cmrc/cmrc.hppheader, which will automatically become available to any target that links to a generated resource library target, asmy-programdoes above:#include <cmrc/cmrc.hpp> int main() { // ... }
-
At global scope within the
.cppfile, place theCMRC_DECLARE(<my-lib-ns>)macro using the namespace that was designated withcmrc_add_resource_library(or the library name if no namespace was specified):#include <cmrc/cmrc.hpp> CMRC_DECLARE(foo); int main() { // ... }
-
Obtain a handle to the embedded resource filesystem by calling the
get_filesystem()function in the generated namespace. It will be generated atcmrc::<my-lib-ns>::get_filesystem().int main() { auto fs = cmrc::foo::get_filesystem(); }
(This function was declared by the
CMRC_DECLARE()macro from the previous step.)You're now ready to work with the files in your resource library! See the section on
cmrc::embedded_filesystem.
All resource libraries have their own cmrc::embedded_filesystem that can be
accessed with the get_filesystem() function declared by CMRC_DECLARE().
This class is trivially copyable and destructible, and acts as a handle to the statically allocated resource library data.
open(const std::string& path) -> cmrc::file- Opens and returns a non-directoryfileobject atpath, or throwsstd::system_error()on error.is_file(const std::string& path) -> bool- Returnstrueif the givenpathnames a regular file,falseotherwise.is_directory(const std::string& path) -> bool- Returnstrueif the givenpathnames a directory.falseotherwise.exists(const std::string& path) -> boolreturnstrueif the given path names an existing file or directory,falseotherwise.iterate_directory(const std::string& path) -> cmrc::directory_iteratorreturns a directory iterator for iterating the contents of a directory. Throws if the givenpathdoes not identify a directory.
typename iteratorandtypename const_iterator- Justconst char*.begin()/cbegin() -> iterator- Return an iterator to the beginning of the resource.end()/cend() -> iterator- Return an iterator past the end of the resource.file()- Default constructor, refers to no resource.
typename value_type-cmrc::directory_entryiterator_category-std::input_iterator_tagdirectory_iterator()- Default construct.begin() -> directory_iterator- Returns*this.end() -> directory_iterator- Returns a past-the-end iterator corresponding to this iterator.operator*() -> value_type- Returns thedirectory_entryfor which the iterator corresponds.operator==,operator!=, andoperator++- Implement iterator semantics.
filename() -> std::string- The filename of the entry.is_file() -> bool-trueif the entry is a file.is_directory() -> bool-trueif the entry is a directory.
After calling cmrc_add_resource_library, you can add additional resources to
the library using cmrc_add_resources with the name of the library and the
paths to any additional resources that you wish to compile in. This way you can
lazily add resources to the library as your configure script runs.
Both cmrc_add_resource_library and cmrc_add_resources take two additional
keyword parameters:
-
WHENCEtells CMakeRC how to rewrite the filepaths to the resource files. The default value forWHENCEis theCMAKE_CURRENT_SOURCE_DIR, which is the source directory wherecmrc_add_resourcesorcmrc_add_resource_libraryis called. For example, if you saycmrc_add_resources(foo images/flower.jpg), the resource will be accessible viacmrc::open("images/flower.jpg"), but if you saycmrc_add_resources(foo WHENCE images images/flower.jpg), then the resource will be accessible only usingcmrc::open("flower.jpg"), because theimagesdirectory is used as the root where the resource will be compiled from.Because of the file transformation limitations,
WHENCEis required when adding resources which exist outside of the source directory, since CMakeRC will not be able to automatically rewrite the file paths. -
PREFIXtells CMakeRC to prepend a directory-style path to the resource filepath in the resulting binary. For example,cmrc_add_resources(foo PREFIX resources images/flower.jpg)will make the resource accessible usingcmrc::open("resources/images/flower.jpg"). This is useful to prevent resource libraries from having conflicting filenames. The defaultPREFIXis to have no prefix.
The two options can be used together to rewrite the paths to your heart's content:
cmrc_add_resource_library(
flower-images
NAMESPACE flower
WHENCE images
PREFIX flowers
images/rose.jpg
images/tulip.jpg
images/daisy.jpg
images/sunflower.jpg
)int foo() {
auto fs = cmrc::flower::get_filesystem();
auto rose = fs.open("flowers/rose.jpg");
}When CMakeRC generates the source file that holds a resource, it picks the best representation the compiling compiler supports:
-
#embed(C23 / C++26) — If the compiler supports#embed(__has_embedis defined) and reports the resource as embeddable, the generated TU uses it directly:#if defined(__has_embed) && __has_embed(".../icon.png") #embed ".../icon.png" #endif
#embedis the ideal path: zero source expansion, zero runtime cost, and the raw bytes live in.rodata. No configuration is needed — CMakeRC detects it automatically. GCC 15+, Clang 17+, and recent MSVC/EDG-based toolsets support it. -
\xNNcharacter literals (fallback, default) — On pre-#embedcompilers (older GCC/Clang, most MSVC), each byte becomes a'\xNN'character literal in a bigconst char[]array. This has zero runtime cost (no decoding) and works on every C++11 compiler, but the generated source is roughly 6× the resource size (18 MB of source for a 2.5 MB image), which significantly slows down parsing. -
Base64 fallback (
CMRC_BASE64=ON) — Set this CMake option to store fallback resources as base64 strings split into small literals, decoded once at static-initialization time:# in your project's CMakeLists.txt, before cmrc_add_resource_library: set(CMRC_BASE64 ON)
The generated source is roughly 1.33× the resource size (plus small chunk overhead), so parsing is ~5× faster than the
\xNNform. The decoder lives incmrc/cmrc.hpp, guarded byCMRC_CMRC_HPP_BASE64so it is not even compiled into translation units that don't use it.#embedstill takes precedence whenever the compiler supports it.
-
Small resources (a few KB) — Either fallback works fine; the generated TU is tiny either way. The
\xNNliterals have the edge (no runtime decode, no startup cost), so the default is a sensible choice. -
Large resources (hundreds of KB to MBs) — The
\xNNform balloons the generated source (~6×) and makes the resource TU slow to compile. Base64 (~1.33×) dramatically speeds up compilation of that TU. The costs are a one-time decode at startup and a heap allocation (the decoded bytes are copied out of the base64 string). Note that the executable is typically slightly larger with base64 — it stores the encoded string and the decoded heap buffer (and the decoder), so the win is compile time and generated-source size, not binary size. If you have truly massive resources,#embedon a modern compiler is strictly better than both.In the test suite,
tests/flower.jpg(2.5 MB) is used to compare the two fallbacks: the\xNNintermediate is ~36 MB while the base64 intermediate is ~7 MB, and both produce byte-for-byte identical embedded content (tests/flower_b64verifies this automatically, along with the base64 startup time).
CMRC_DISABLE_EMBED (default OFF) is provided for diagnostics and testing:
setting it forces the fallback path even on #embed-capable compilers, so you
can validate the literal/base64 generators without a pre-#embed toolchain.
Normal builds should leave it OFF.