├── .gitignore ├── CMakeLists.txt ├── CMakeRC.cmake ├── LICENSE.txt ├── README.md └── tests ├── CMakeLists.txt ├── enoent.cpp ├── flower.cpp ├── flower.jpg ├── hello.txt ├── iterate.cpp ├── prefix.cpp ├── simple.cpp ├── subdir_a └── subdir_b │ ├── file_a.txt │ └── file_b.txt ├── whence.cpp └── whence_prefix.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .vscode/.cmaketools.json 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.6) 2 | project(CMakeRC VERSION 2.0.0) 3 | 4 | list(INSERT CMAKE_MODULE_PATH 0 "${PROJECT_SOURCE_DIR}/cmake") 5 | 6 | # Include the main module 7 | include(CMakeRC.cmake) 8 | 9 | option(BUILD_TESTS "Build tests" ON) 10 | if(BUILD_TESTS AND (PROJECT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)) 11 | enable_testing() 12 | if(NOT MSVC) 13 | add_compile_options(-Wall -Wextra) 14 | endif() 15 | add_subdirectory(tests) 16 | endif() 17 | -------------------------------------------------------------------------------- /CMakeRC.cmake: -------------------------------------------------------------------------------- 1 | # This block is executed when generating an intermediate resource file, not when 2 | # running in CMake configure mode 3 | if(_CMRC_GENERATE_MODE) 4 | # Read in the digits 5 | file(READ "${INPUT_FILE}" bytes HEX) 6 | # Format each pair into a character literal. Heuristics seem to favor doing 7 | # the conversion in groups of five for fastest conversion 8 | string(REGEX REPLACE "(..)(..)(..)(..)(..)" "'\\\\x\\1','\\\\x\\2','\\\\x\\3','\\\\x\\4','\\\\x\\5'," chars "${bytes}") 9 | # Since we did this in groups, we have some leftovers to clean up 10 | string(LENGTH "${bytes}" n_bytes2) 11 | math(EXPR n_bytes "${n_bytes2} / 2") 12 | math(EXPR remainder "${n_bytes} % 5") # <-- '5' is the grouping count from above 13 | set(cleanup_re "$") 14 | set(cleanup_sub ) 15 | while(remainder) 16 | set(cleanup_re "(..)${cleanup_re}") 17 | set(cleanup_sub "'\\\\x\\${remainder}',${cleanup_sub}") 18 | math(EXPR remainder "${remainder} - 1") 19 | endwhile() 20 | if(NOT cleanup_re STREQUAL "$") 21 | string(REGEX REPLACE "${cleanup_re}" "${cleanup_sub}" chars "${chars}") 22 | endif() 23 | string(CONFIGURE [[ 24 | namespace { const char file_array[] = { @chars@ 0 }; } 25 | namespace cmrc { namespace @NAMESPACE@ { namespace res_chars { 26 | extern const char* const @SYMBOL@_begin = file_array; 27 | extern const char* const @SYMBOL@_end = file_array + @n_bytes@; 28 | }}} 29 | ]] code) 30 | file(WRITE "${OUTPUT_FILE}" "${code}") 31 | # Exit from the script. Nothing else needs to be processed 32 | return() 33 | endif() 34 | 35 | set(_version 2.0.0) 36 | 37 | cmake_minimum_required(VERSION 3.12) 38 | include(CMakeParseArguments) 39 | 40 | if(COMMAND cmrc_add_resource_library) 41 | if(NOT DEFINED _CMRC_VERSION OR NOT (_version STREQUAL _CMRC_VERSION)) 42 | message(WARNING "More than one CMakeRC version has been included in this project.") 43 | endif() 44 | # CMakeRC has already been included! Don't do anything 45 | return() 46 | endif() 47 | 48 | set(_CMRC_VERSION "${_version}" CACHE INTERNAL "CMakeRC version. Used for checking for conflicts") 49 | 50 | set(_CMRC_SCRIPT "${CMAKE_CURRENT_LIST_FILE}" CACHE INTERNAL "Path to CMakeRC script") 51 | 52 | function(_cmrc_normalize_path var) 53 | set(path "${${var}}") 54 | file(TO_CMAKE_PATH "${path}" path) 55 | while(path MATCHES "//") 56 | string(REPLACE "//" "/" path "${path}") 57 | endwhile() 58 | string(REGEX REPLACE "/+$" "" path "${path}") 59 | set("${var}" "${path}" PARENT_SCOPE) 60 | endfunction() 61 | 62 | get_filename_component(_inc_dir "${CMAKE_BINARY_DIR}/_cmrc/include" ABSOLUTE) 63 | set(CMRC_INCLUDE_DIR "${_inc_dir}" CACHE INTERNAL "Directory for CMakeRC include files") 64 | # Let's generate the primary include file 65 | file(MAKE_DIRECTORY "${CMRC_INCLUDE_DIR}/cmrc") 66 | set(hpp_content [==[ 67 | #ifndef CMRC_CMRC_HPP_INCLUDED 68 | #define CMRC_CMRC_HPP_INCLUDED 69 | 70 | #include 71 | #include 72 | #include 73 | #include 74 | #include 75 | #include 76 | #include 77 | #include 78 | #include 79 | 80 | #if !(defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) || defined(CMRC_NO_EXCEPTIONS)) 81 | #define CMRC_NO_EXCEPTIONS 1 82 | #endif 83 | 84 | namespace cmrc { namespace detail { struct dummy; } } 85 | 86 | #define CMRC_DECLARE(libid) \ 87 | namespace cmrc { namespace detail { \ 88 | struct dummy; \ 89 | static_assert(std::is_same::value, "CMRC_DECLARE() must only appear at the global namespace"); \ 90 | } } \ 91 | namespace cmrc { namespace libid { \ 92 | cmrc::embedded_filesystem get_filesystem(); \ 93 | } } static_assert(true, "") 94 | 95 | namespace cmrc { 96 | 97 | class file { 98 | const char* _begin = nullptr; 99 | const char* _end = nullptr; 100 | 101 | public: 102 | using iterator = const char*; 103 | using const_iterator = iterator; 104 | iterator begin() const noexcept { return _begin; } 105 | iterator cbegin() const noexcept { return _begin; } 106 | iterator end() const noexcept { return _end; } 107 | iterator cend() const noexcept { return _end; } 108 | std::size_t size() const { return static_cast(std::distance(begin(), end())); } 109 | 110 | file() = default; 111 | file(iterator beg, iterator end) noexcept : _begin(beg), _end(end) {} 112 | }; 113 | 114 | class directory_entry; 115 | 116 | namespace detail { 117 | 118 | class directory; 119 | class file_data; 120 | 121 | class file_or_directory { 122 | union _data_t { 123 | class file_data* file_data; 124 | class directory* directory; 125 | } _data; 126 | bool _is_file = true; 127 | 128 | public: 129 | explicit file_or_directory(file_data& f) { 130 | _data.file_data = &f; 131 | } 132 | explicit file_or_directory(directory& d) { 133 | _data.directory = &d; 134 | _is_file = false; 135 | } 136 | bool is_file() const noexcept { 137 | return _is_file; 138 | } 139 | bool is_directory() const noexcept { 140 | return !is_file(); 141 | } 142 | const directory& as_directory() const noexcept { 143 | assert(!is_file()); 144 | return *_data.directory; 145 | } 146 | const file_data& as_file() const noexcept { 147 | assert(is_file()); 148 | return *_data.file_data; 149 | } 150 | }; 151 | 152 | class file_data { 153 | public: 154 | const char* begin_ptr; 155 | const char* end_ptr; 156 | file_data(const file_data&) = delete; 157 | file_data(const char* b, const char* e) : begin_ptr(b), end_ptr(e) {} 158 | }; 159 | 160 | inline std::pair split_path(const std::string& path) { 161 | auto first_sep = path.find("/"); 162 | if (first_sep == path.npos) { 163 | return std::make_pair(path, ""); 164 | } else { 165 | return std::make_pair(path.substr(0, first_sep), path.substr(first_sep + 1)); 166 | } 167 | } 168 | 169 | struct created_subdirectory { 170 | class directory& directory; 171 | class file_or_directory& index_entry; 172 | }; 173 | 174 | class directory { 175 | std::list _files; 176 | std::list _dirs; 177 | std::map _index; 178 | 179 | using base_iterator = std::map::const_iterator; 180 | 181 | public: 182 | 183 | directory() = default; 184 | directory(const directory&) = delete; 185 | 186 | created_subdirectory add_subdir(std::string name) & { 187 | _dirs.emplace_back(); 188 | auto& back = _dirs.back(); 189 | auto& fod = _index.emplace(name, file_or_directory{back}).first->second; 190 | return created_subdirectory{back, fod}; 191 | } 192 | 193 | file_or_directory* add_file(std::string name, const char* begin, const char* end) & { 194 | assert(_index.find(name) == _index.end()); 195 | _files.emplace_back(begin, end); 196 | return &_index.emplace(name, file_or_directory{_files.back()}).first->second; 197 | } 198 | 199 | const file_or_directory* get(const std::string& path) const { 200 | auto pair = split_path(path); 201 | auto child = _index.find(pair.first); 202 | if (child == _index.end()) { 203 | return nullptr; 204 | } 205 | auto& entry = child->second; 206 | if (pair.second.empty()) { 207 | // We're at the end of the path 208 | return &entry; 209 | } 210 | 211 | if (entry.is_file()) { 212 | // We can't traverse into a file. Stop. 213 | return nullptr; 214 | } 215 | // Keep going down 216 | return entry.as_directory().get(pair.second); 217 | } 218 | 219 | class iterator { 220 | base_iterator _base_iter; 221 | base_iterator _end_iter; 222 | public: 223 | using value_type = directory_entry; 224 | using difference_type = std::ptrdiff_t; 225 | using pointer = const value_type*; 226 | using reference = const value_type&; 227 | using iterator_category = std::input_iterator_tag; 228 | 229 | iterator() = default; 230 | explicit iterator(base_iterator iter, base_iterator end) : _base_iter(iter), _end_iter(end) {} 231 | 232 | iterator begin() const noexcept { 233 | return *this; 234 | } 235 | 236 | iterator end() const noexcept { 237 | return iterator(_end_iter, _end_iter); 238 | } 239 | 240 | inline value_type operator*() const noexcept; 241 | 242 | bool operator==(const iterator& rhs) const noexcept { 243 | return _base_iter == rhs._base_iter; 244 | } 245 | 246 | bool operator!=(const iterator& rhs) const noexcept { 247 | return !(*this == rhs); 248 | } 249 | 250 | iterator& operator++() noexcept { 251 | ++_base_iter; 252 | return *this; 253 | } 254 | 255 | iterator operator++(int) noexcept { 256 | auto cp = *this; 257 | ++_base_iter; 258 | return cp; 259 | } 260 | }; 261 | 262 | using const_iterator = iterator; 263 | 264 | iterator begin() const noexcept { 265 | return iterator(_index.begin(), _index.end()); 266 | } 267 | 268 | iterator end() const noexcept { 269 | return iterator(); 270 | } 271 | }; 272 | 273 | inline std::string normalize_path(std::string path) { 274 | while (path.find("/") == 0) { 275 | path.erase(path.begin()); 276 | } 277 | while (!path.empty() && (path.rfind("/") == path.size() - 1)) { 278 | path.pop_back(); 279 | } 280 | auto off = path.npos; 281 | while ((off = path.find("//")) != path.npos) { 282 | path.erase(path.begin() + static_cast(off)); 283 | } 284 | return path; 285 | } 286 | 287 | using index_type = std::map; 288 | 289 | } // detail 290 | 291 | class directory_entry { 292 | std::string _fname; 293 | const detail::file_or_directory* _item; 294 | 295 | public: 296 | directory_entry() = delete; 297 | explicit directory_entry(std::string filename, const detail::file_or_directory& item) 298 | : _fname(filename) 299 | , _item(&item) 300 | {} 301 | 302 | const std::string& filename() const & { 303 | return _fname; 304 | } 305 | std::string filename() const && { 306 | return std::move(_fname); 307 | } 308 | 309 | bool is_file() const { 310 | return _item->is_file(); 311 | } 312 | 313 | bool is_directory() const { 314 | return _item->is_directory(); 315 | } 316 | }; 317 | 318 | directory_entry detail::directory::iterator::operator*() const noexcept { 319 | assert(begin() != end()); 320 | return directory_entry(_base_iter->first, _base_iter->second); 321 | } 322 | 323 | using directory_iterator = detail::directory::iterator; 324 | 325 | class embedded_filesystem { 326 | // Never-null: 327 | const cmrc::detail::index_type* _index; 328 | const detail::file_or_directory* _get(std::string path) const { 329 | path = detail::normalize_path(path); 330 | auto found = _index->find(path); 331 | if (found == _index->end()) { 332 | return nullptr; 333 | } else { 334 | return found->second; 335 | } 336 | } 337 | 338 | public: 339 | explicit embedded_filesystem(const detail::index_type& index) 340 | : _index(&index) 341 | {} 342 | 343 | file open(const std::string& path) const { 344 | auto entry_ptr = _get(path); 345 | if (!entry_ptr || !entry_ptr->is_file()) { 346 | #ifdef CMRC_NO_EXCEPTIONS 347 | fprintf(stderr, "Error no such file or directory: %s\n", path.c_str()); 348 | abort(); 349 | #else 350 | throw std::system_error(make_error_code(std::errc::no_such_file_or_directory), path); 351 | #endif 352 | } 353 | auto& dat = entry_ptr->as_file(); 354 | return file{dat.begin_ptr, dat.end_ptr}; 355 | } 356 | 357 | bool is_file(const std::string& path) const noexcept { 358 | auto entry_ptr = _get(path); 359 | return entry_ptr && entry_ptr->is_file(); 360 | } 361 | 362 | bool is_directory(const std::string& path) const noexcept { 363 | auto entry_ptr = _get(path); 364 | return entry_ptr && entry_ptr->is_directory(); 365 | } 366 | 367 | bool exists(const std::string& path) const noexcept { 368 | return !!_get(path); 369 | } 370 | 371 | directory_iterator iterate_directory(const std::string& path) const { 372 | auto entry_ptr = _get(path); 373 | if (!entry_ptr) { 374 | #ifdef CMRC_NO_EXCEPTIONS 375 | fprintf(stderr, "Error no such file or directory: %s\n", path.c_str()); 376 | abort(); 377 | #else 378 | throw std::system_error(make_error_code(std::errc::no_such_file_or_directory), path); 379 | #endif 380 | } 381 | if (!entry_ptr->is_directory()) { 382 | #ifdef CMRC_NO_EXCEPTIONS 383 | fprintf(stderr, "Error not a directory: %s\n", path.c_str()); 384 | abort(); 385 | #else 386 | throw std::system_error(make_error_code(std::errc::not_a_directory), path); 387 | #endif 388 | } 389 | return entry_ptr->as_directory().begin(); 390 | } 391 | }; 392 | 393 | } 394 | 395 | #endif // CMRC_CMRC_HPP_INCLUDED 396 | ]==]) 397 | 398 | set(cmrc_hpp "${CMRC_INCLUDE_DIR}/cmrc/cmrc.hpp" CACHE INTERNAL "") 399 | set(_generate 1) 400 | if(EXISTS "${cmrc_hpp}") 401 | file(READ "${cmrc_hpp}" _current) 402 | if(_current STREQUAL hpp_content) 403 | set(_generate 0) 404 | endif() 405 | endif() 406 | file(GENERATE OUTPUT "${cmrc_hpp}" CONTENT "${hpp_content}" CONDITION ${_generate}) 407 | 408 | add_library(cmrc-base INTERFACE) 409 | target_include_directories(cmrc-base INTERFACE $) 410 | # Signal a basic C++11 feature to require C++11. 411 | target_compile_features(cmrc-base INTERFACE cxx_nullptr) 412 | set_property(TARGET cmrc-base PROPERTY INTERFACE_CXX_EXTENSIONS OFF) 413 | add_library(cmrc::base ALIAS cmrc-base) 414 | 415 | function(cmrc_add_resource_library name) 416 | set(args ALIAS NAMESPACE TYPE) 417 | cmake_parse_arguments(ARG "" "${args}" "" "${ARGN}") 418 | # Generate the identifier for the resource library's namespace 419 | set(ns_re "[a-zA-Z_][a-zA-Z0-9_]*") 420 | if(NOT DEFINED ARG_NAMESPACE) 421 | # Check that the library name is also a valid namespace 422 | if(NOT name MATCHES "${ns_re}") 423 | message(SEND_ERROR "Library name is not a valid namespace. Specify the NAMESPACE argument") 424 | endif() 425 | set(ARG_NAMESPACE "${name}") 426 | else() 427 | if(NOT ARG_NAMESPACE MATCHES "${ns_re}") 428 | message(SEND_ERROR "NAMESPACE for ${name} is not a valid C++ namespace identifier (${ARG_NAMESPACE})") 429 | endif() 430 | endif() 431 | set(libname "${name}") 432 | # Check that type is either "STATIC" or "OBJECT", or default to "STATIC" if 433 | # not set 434 | if(NOT DEFINED ARG_TYPE) 435 | set(ARG_TYPE STATIC) 436 | elseif(NOT "${ARG_TYPE}" MATCHES "^(STATIC|OBJECT)$") 437 | message(SEND_ERROR "${ARG_TYPE} is not a valid TYPE (STATIC and OBJECT are acceptable)") 438 | set(ARG_TYPE STATIC) 439 | endif() 440 | # Generate a library with the compiled in character arrays. 441 | string(CONFIGURE [=[ 442 | #include 443 | #include 444 | #include 445 | 446 | namespace cmrc { 447 | namespace @ARG_NAMESPACE@ { 448 | 449 | namespace res_chars { 450 | // These are the files which are available in this resource library 451 | $, 452 | > 453 | } 454 | 455 | namespace { 456 | 457 | const cmrc::detail::index_type& 458 | get_root_index() { 459 | static cmrc::detail::directory root_directory_; 460 | static cmrc::detail::file_or_directory root_directory_fod{root_directory_}; 461 | static cmrc::detail::index_type root_index; 462 | root_index.emplace("", &root_directory_fod); 463 | struct dir_inl { 464 | class cmrc::detail::directory& directory; 465 | }; 466 | dir_inl root_directory_dir{root_directory_}; 467 | (void)root_directory_dir; 468 | $, 469 | > 470 | $, 471 | > 472 | return root_index; 473 | } 474 | 475 | } 476 | 477 | cmrc::embedded_filesystem get_filesystem() { 478 | static auto& index = get_root_index(); 479 | return cmrc::embedded_filesystem{index}; 480 | } 481 | 482 | } // @ARG_NAMESPACE@ 483 | } // cmrc 484 | ]=] cpp_content @ONLY) 485 | get_filename_component(libdir "${CMAKE_CURRENT_BINARY_DIR}/__cmrc_${name}" ABSOLUTE) 486 | get_filename_component(lib_tmp_cpp "${libdir}/lib_.cpp" ABSOLUTE) 487 | string(REPLACE "\n " "\n" cpp_content "${cpp_content}") 488 | file(GENERATE OUTPUT "${lib_tmp_cpp}" CONTENT "${cpp_content}") 489 | get_filename_component(libcpp "${libdir}/lib.cpp" ABSOLUTE) 490 | add_custom_command(OUTPUT "${libcpp}" 491 | DEPENDS "${lib_tmp_cpp}" "${cmrc_hpp}" 492 | COMMAND ${CMAKE_COMMAND} -E copy_if_different "${lib_tmp_cpp}" "${libcpp}" 493 | COMMENT "Generating ${name} resource loader" 494 | ) 495 | # Generate the actual static library. Each source file is just a single file 496 | # with a character array compiled in containing the contents of the 497 | # corresponding resource file. 498 | add_library(${name} ${ARG_TYPE} ${libcpp}) 499 | set_property(TARGET ${name} PROPERTY CMRC_LIBDIR "${libdir}") 500 | set_property(TARGET ${name} PROPERTY CMRC_NAMESPACE "${ARG_NAMESPACE}") 501 | target_link_libraries(${name} PUBLIC cmrc::base) 502 | set_property(TARGET ${name} PROPERTY CMRC_IS_RESOURCE_LIBRARY TRUE) 503 | if(ARG_ALIAS) 504 | add_library("${ARG_ALIAS}" ALIAS ${name}) 505 | endif() 506 | cmrc_add_resources(${name} ${ARG_UNPARSED_ARGUMENTS}) 507 | endfunction() 508 | 509 | function(_cmrc_register_dirs name dirpath) 510 | if(dirpath STREQUAL "") 511 | return() 512 | endif() 513 | # Skip this dir if we have already registered it 514 | get_target_property(registered "${name}" _CMRC_REGISTERED_DIRS) 515 | if(dirpath IN_LIST registered) 516 | return() 517 | endif() 518 | # Register the parent directory first 519 | get_filename_component(parent "${dirpath}" DIRECTORY) 520 | if(NOT parent STREQUAL "") 521 | _cmrc_register_dirs("${name}" "${parent}") 522 | endif() 523 | # Now generate the registration 524 | set_property(TARGET "${name}" APPEND PROPERTY _CMRC_REGISTERED_DIRS "${dirpath}") 525 | _cm_encode_fpath(sym "${dirpath}") 526 | if(parent STREQUAL "") 527 | set(parent_sym root_directory) 528 | else() 529 | _cm_encode_fpath(parent_sym "${parent}") 530 | endif() 531 | get_filename_component(leaf "${dirpath}" NAME) 532 | set_property( 533 | TARGET "${name}" 534 | APPEND PROPERTY CMRC_MAKE_DIRS 535 | "static auto ${sym}_dir = ${parent_sym}_dir.directory.add_subdir(\"${leaf}\")\;" 536 | "root_index.emplace(\"${dirpath}\", &${sym}_dir.index_entry)\;" 537 | ) 538 | endfunction() 539 | 540 | function(cmrc_add_resources name) 541 | get_target_property(is_reslib ${name} CMRC_IS_RESOURCE_LIBRARY) 542 | if(NOT TARGET ${name} OR NOT is_reslib) 543 | message(SEND_ERROR "cmrc_add_resources called on target '${name}' which is not an existing resource library") 544 | return() 545 | endif() 546 | 547 | set(options) 548 | set(args WHENCE PREFIX) 549 | set(list_args) 550 | cmake_parse_arguments(ARG "${options}" "${args}" "${list_args}" "${ARGN}") 551 | 552 | if(NOT ARG_WHENCE) 553 | set(ARG_WHENCE ${CMAKE_CURRENT_SOURCE_DIR}) 554 | endif() 555 | _cmrc_normalize_path(ARG_WHENCE) 556 | get_filename_component(ARG_WHENCE "${ARG_WHENCE}" ABSOLUTE) 557 | 558 | # Generate the identifier for the resource library's namespace 559 | get_target_property(lib_ns "${name}" CMRC_NAMESPACE) 560 | 561 | get_target_property(libdir ${name} CMRC_LIBDIR) 562 | get_target_property(target_dir ${name} SOURCE_DIR) 563 | file(RELATIVE_PATH reldir "${target_dir}" "${CMAKE_CURRENT_SOURCE_DIR}") 564 | if(reldir MATCHES "^\\.\\.") 565 | message(SEND_ERROR "Cannot call cmrc_add_resources in a parent directory from the resource library target") 566 | return() 567 | endif() 568 | 569 | foreach(input IN LISTS ARG_UNPARSED_ARGUMENTS) 570 | _cmrc_normalize_path(input) 571 | get_filename_component(abs_in "${input}" ABSOLUTE) 572 | # Generate a filename based on the input filename that we can put in 573 | # the intermediate directory. 574 | file(RELATIVE_PATH relpath "${ARG_WHENCE}" "${abs_in}") 575 | if(relpath MATCHES "^\\.\\.") 576 | # For now we just error on files that exist outside of the soure dir. 577 | message(SEND_ERROR "Cannot add file '${input}': File must be in a subdirectory of ${ARG_WHENCE}") 578 | continue() 579 | endif() 580 | if(DEFINED ARG_PREFIX) 581 | _cmrc_normalize_path(ARG_PREFIX) 582 | endif() 583 | if(ARG_PREFIX AND NOT ARG_PREFIX MATCHES "/$") 584 | set(ARG_PREFIX "${ARG_PREFIX}/") 585 | endif() 586 | get_filename_component(dirpath "${ARG_PREFIX}${relpath}" DIRECTORY) 587 | _cmrc_register_dirs("${name}" "${dirpath}") 588 | get_filename_component(abs_out "${libdir}/intermediate/${ARG_PREFIX}${relpath}.cpp" ABSOLUTE) 589 | # Generate a symbol name relpath the file's character array 590 | _cm_encode_fpath(sym "${relpath}") 591 | # Get the symbol name for the parent directory 592 | if(dirpath STREQUAL "") 593 | set(parent_sym root_directory) 594 | else() 595 | _cm_encode_fpath(parent_sym "${dirpath}") 596 | endif() 597 | # Generate the rule for the intermediate source file 598 | _cmrc_generate_intermediate_cpp(${lib_ns} ${sym} "${abs_out}" "${abs_in}") 599 | target_sources(${name} PRIVATE "${abs_out}") 600 | set_property(TARGET ${name} APPEND PROPERTY CMRC_EXTERN_DECLS 601 | "// Pointers to ${input}" 602 | "extern const char* const ${sym}_begin\;" 603 | "extern const char* const ${sym}_end\;" 604 | ) 605 | get_filename_component(leaf "${relpath}" NAME) 606 | set_property( 607 | TARGET ${name} 608 | APPEND PROPERTY CMRC_MAKE_FILES 609 | "root_index.emplace(" 610 | " \"${ARG_PREFIX}${relpath}\"," 611 | " ${parent_sym}_dir.directory.add_file(" 612 | " \"${leaf}\"," 613 | " res_chars::${sym}_begin," 614 | " res_chars::${sym}_end" 615 | " )" 616 | ")\;" 617 | ) 618 | endforeach() 619 | endfunction() 620 | 621 | function(_cmrc_generate_intermediate_cpp lib_ns symbol outfile infile) 622 | add_custom_command( 623 | # This is the file we will generate 624 | OUTPUT "${outfile}" 625 | # These are the primary files that affect the output 626 | DEPENDS "${infile}" "${_CMRC_SCRIPT}" 627 | COMMAND 628 | "${CMAKE_COMMAND}" 629 | -D_CMRC_GENERATE_MODE=TRUE 630 | -DNAMESPACE=${lib_ns} 631 | -DSYMBOL=${symbol} 632 | "-DINPUT_FILE=${infile}" 633 | "-DOUTPUT_FILE=${outfile}" 634 | -P "${_CMRC_SCRIPT}" 635 | COMMENT "Generating intermediate file for ${infile}" 636 | ) 637 | endfunction() 638 | 639 | function(_cm_encode_fpath var fpath) 640 | string(MAKE_C_IDENTIFIER "${fpath}" ident) 641 | string(MD5 hash "${fpath}") 642 | string(SUBSTRING "${hash}" 0 4 hash) 643 | set(${var} f_${hash}_${ident} PARENT_SCOPE) 644 | endfunction() 645 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 vector-of-bool 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CMakeRC - A Standalone CMake-Based C++ Resource Compiler 2 | 3 | CMakeRC is a resource compiler provided in a single CMake script that can easily 4 | be included in another project. 5 | 6 | ## What is a "Resource Compiler"? 7 | 8 | For the purpose of this project, a _resource compiler_ is a tool that will 9 | compile arbitrary data into a program. The program can then read this data from 10 | without needing to store that data on disk external to the program. 11 | 12 | Examples use cases: 13 | 14 | - Storing a web page tree for serving over HTTP to clients. Compiling the web 15 | page into the executable means that the program is all that is required to run 16 | the HTTP server, without keeping the site files on disk separately. 17 | - Storing embedded scripts and/or shaders that support the program, rather than 18 | writing them in the code as string literals. 19 | - Storing images and graphics for GUIs. 20 | 21 | These things are all about aiding in the ease of portability and distribution of 22 | the program, as it is no longer required to ship a plethora of support files 23 | with a binary to your users. 24 | 25 | ## What is Special About CMakeRC? 26 | 27 | CMakeRC is implemented as a single CMake module, `CMakeRC.cmake`. No additional 28 | libraries or headers are required. 29 | 30 | This project was initially written as a "literate programming" experiment. [The process for the pre-2.0 version can be read about here](https://vector-of-bool.github.io/2017/01/21/cmrc.html). 31 | 32 | 2.0.0+ is slightly different from what was written in the post, but a lot of it 33 | still applies. 34 | 35 | ## Installing 36 | 37 | Installing CMakeRC is designed to be as simple as possible. The only thing 38 | required is the `CMakeRC.cmake` script. You can copy it into your project 39 | directory (recommended) or install it as a package and get all the features you 40 | need. 41 | 42 | For [vcpkg](https://github.com/microsoft/vcpkg) users there is a `cmakerc` [port](https://github.com/microsoft/vcpkg/tree/master/ports/cmakerc) that can be installed via `vcpkg install cmakerc` or by adding it to `dependencies` section of your `vcpkg.json` file. 43 | 44 | ## Usage 45 | 46 | 1. Once installed, simply import the `CMakeRC.cmake` script. If you placed the 47 | module in your project directory (recommended), simply use `include(CMakeRC)` 48 | to import the module. If you installed it as a package, use `find_package(CMakeRC)`. 49 | 50 | 2. Once included, create a new resource library using `cmrc_add_resource_library`, 51 | like this: 52 | 53 | ```cmake 54 | cmrc_add_resource_library(foo-resources ...) 55 | ``` 56 | 57 | Where `...` is simply a list of files that you wish to compile into the 58 | resource library. 59 | 60 | You can use the `ALIAS` argument to immediately generate an alias target for 61 | the resource library (recommended): 62 | 63 | ```cmake 64 | cmrc_add_resource_library(foo-resources ALIAS foo::rc ...) 65 | ``` 66 | 67 | **Note:** If the name of the library target is not a valid C++ `namespace` 68 | identifier, you will need to provide the `NAMESPACE` argument. Otherwise, the 69 | name of the library will be used as the resource library's namespace. 70 | 71 | ```cmake 72 | cmrc_add_resource_library(foo-resources ALIAS foo::rc NAMESPACE foo ...) 73 | ``` 74 | 75 | 3. To use the resource library, link the resource library target into a binary 76 | using `target_link_libraries()`: 77 | 78 | ```cmake 79 | add_executable(my-program main.cpp) 80 | target_link_libraries(my-program PRIVATE foo::rc) 81 | ``` 82 | 83 | 4. Inside of the source files, any time you wish to use the library, include the 84 | `cmrc/cmrc.hpp` header, which will automatically become available to any 85 | target that links to a generated resource library target, as `my-program` 86 | does above: 87 | 88 | ```c++ 89 | #include 90 | 91 | int main() { 92 | // ... 93 | } 94 | ``` 95 | 96 | 5. At global scope within the `.cpp` file, place the `CMRC_DECLARE()` macro 97 | using the namespace that was designated with `cmrc_add_resource_library` (or 98 | the library name if no namespace was specified): 99 | 100 | ```c++ 101 | #include 102 | 103 | CMRC_DECLARE(foo); 104 | 105 | int main() { 106 | // ... 107 | } 108 | ``` 109 | 110 | 6. Obtain a handle to the embedded resource filesystem by calling the 111 | `get_filesystem()` function in the generated namespace. It will be 112 | generated at `cmrc::::get_filesystem()`. 113 | 114 | ```c++ 115 | int main() { 116 | auto fs = cmrc::foo::get_filesystem(); 117 | } 118 | ``` 119 | 120 | (This function was declared by the `CMRC_DECLARE()` macro from the previous 121 | step.) 122 | 123 | You're now ready to work with the files in your resource library! 124 | See the section on `cmrc::embedded_filesystem`. 125 | 126 | ## The `cmrc::embedded_filesystem` API 127 | 128 | All resource libraries have their own `cmrc::embedded_filesystem` that can be 129 | accessed with the `get_filesystem()` function declared by `CMRC_DECLARE()`. 130 | 131 | This class is trivially copyable and destructible, and acts as a handle to the 132 | statically allocated resource library data. 133 | 134 | ### Methods on `cmrc::embedded_filesystem` 135 | 136 | - `open(const std::string& path) -> cmrc::file` - Opens and returns a 137 | non-directory `file` object at `path`, or throws `std::system_error()` on 138 | error. 139 | - `is_file(const std::string& path) -> bool` - Returns `true` if the given 140 | `path` names a regular file, `false` otherwise. 141 | - `is_directory(const std::string& path) -> bool` - Returns `true` if the given 142 | `path` names a directory. `false` otherwise. 143 | - `exists(const std::string& path) -> bool` returns `true` if the given path 144 | names an existing file or directory, `false` otherwise. 145 | - `iterate_directory(const std::string& path) -> cmrc::directory_iterator` 146 | returns a directory iterator for iterating the contents of a directory. Throws 147 | if the given `path` does not identify a directory. 148 | 149 | ## Members of `cmrc::file` 150 | 151 | - `typename iterator` and `typename const_iterator` - Just `const char*`. 152 | - `begin()/cbegin() -> iterator` - Return an iterator to the beginning of the 153 | resource. 154 | - `end()/cend() -> iterator` - Return an iterator past the end of the resource. 155 | - `file()` - Default constructor, refers to no resource. 156 | 157 | ## Members of `cmrc::directory_iterator` 158 | 159 | - `typename value_type` - `cmrc::directory_entry` 160 | - `iterator_category` - `std::input_iterator_tag` 161 | - `directory_iterator()` - Default construct. 162 | - `begin() -> directory_iterator` - Returns `*this`. 163 | - `end() -> directory_iterator` - Returns a past-the-end iterator corresponding 164 | to this iterator. 165 | - `operator*() -> value_type` - Returns the `directory_entry` for which the 166 | iterator corresponds. 167 | - `operator==`, `operator!=`, and `operator++` - Implement iterator semantics. 168 | 169 | ## Members of `cmrc::directory_entry` 170 | 171 | - `filename() -> std::string` - The filename of the entry. 172 | - `is_file() -> bool` - `true` if the entry is a file. 173 | - `is_directory() -> bool` - `true` if the entry is a directory. 174 | 175 | ## Additional Options 176 | 177 | After calling `cmrc_add_resource_library`, you can add additional resources to 178 | the library using `cmrc_add_resources` with the name of the library and the 179 | paths to any additional resources that you wish to compile in. This way you can 180 | lazily add resources to the library as your configure script runs. 181 | 182 | Both `cmrc_add_resource_library` and `cmrc_add_resources` take two additional 183 | keyword parameters: 184 | 185 | - `WHENCE` tells CMakeRC how to rewrite the filepaths to the resource files. 186 | The default value for `WHENCE` is the `CMAKE_CURRENT_SOURCE_DIR`, which is 187 | the source directory where `cmrc_add_resources` or `cmrc_add_resource_library` 188 | is called. For example, if you say `cmrc_add_resources(foo images/flower.jpg)`, 189 | the resource will be accessible via `cmrc::open("images/flower.jpg")`, but 190 | if you say `cmrc_add_resources(foo WHENCE images images/flower.jpg)`, then 191 | the resource will be accessible only using `cmrc::open("flower.jpg")`, because 192 | the `images` directory is used as the root where the resource will be compiled 193 | from. 194 | 195 | Because of the file transformation limitations, `WHENCE` is _required_ when 196 | adding resources which exist outside of the source directory, since CMakeRC 197 | will not be able to automatically rewrite the file paths. 198 | 199 | - `PREFIX` tells CMakeRC to prepend a directory-style path to the resource 200 | filepath in the resulting binary. For example, 201 | `cmrc_add_resources(foo PREFIX resources images/flower.jpg)` will make the 202 | resource accessible using `cmrc::open("resources/images/flower.jpg")`. This is 203 | useful to prevent resource libraries from having conflicting filenames. The 204 | default `PREFIX` is to have no prefix. 205 | 206 | The two options can be used together to rewrite the paths to your heart's 207 | content: 208 | 209 | ```cmake 210 | cmrc_add_resource_library( 211 | flower-images 212 | NAMESPACE flower 213 | WHENCE images 214 | PREFIX flowers 215 | images/rose.jpg 216 | images/tulip.jpg 217 | images/daisy.jpg 218 | images/sunflower.jpg 219 | ) 220 | ``` 221 | 222 | ```c++ 223 | int foo() { 224 | auto fs = cmrc::flower::get_filesystem(); 225 | auto rose = fs.open("flowers/rose.jpg"); 226 | } 227 | ``` 228 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | function(cmrc_add_test) 2 | set(options WILL_FAIL) 3 | set(args NAME PASS_REGEX WHENCE PREFIX) 4 | set(list_args RESOURCES TEST_ARGV) 5 | cmake_parse_arguments(PARSE_ARGV 0 ARG "${options}" "${args}" "${list_args}") 6 | 7 | if(DEFINED ARG_WHENCE) 8 | set(whence_arg WHENCE "${ARG_WHENCE}") 9 | endif() 10 | if(DEFINED ARG_PREFIX) 11 | set(prefix_arg PREFIX "${ARG_PREFIX}") 12 | endif() 13 | 14 | add_executable("${ARG_NAME}" "${ARG_NAME}.cpp") 15 | cmrc_add_resource_library(rc_${ARG_NAME} NAMESPACE "${ARG_NAME}" ${whence_arg} ${prefix_arg} ${ARG_RESOURCES}) 16 | target_link_libraries("${ARG_NAME}" PRIVATE rc_${ARG_NAME}) 17 | add_test("${ARG_NAME}" "${ARG_NAME}" ${ARG_TEST_ARGV}) 18 | if(DEFINED ARG_PASS_REGEX) 19 | set_property( 20 | TEST "${ARG_NAME}" 21 | PROPERTY PASS_REGULAR_EXPRESSION "${ARG_PASS_REGEX}" 22 | ) 23 | endif() 24 | if(ARG_UNPARSED_ARGUMENTS) 25 | message(WARNING "Invalid test arguments: ${ARG_UNPARSED_ARGUMENTS}") 26 | endif() 27 | set_property(TEST "${ARG_NAME}" PROPERTY WILL_FAIL "${ARG_WILL_FAIL}") 28 | endfunction() 29 | 30 | cmrc_add_test( 31 | NAME simple 32 | PASS_REGEX "^Hello, world!" 33 | RESOURCES 34 | hello.txt 35 | # Resources not used by the test, but just make sure we get no compilation 36 | # errors with the more complex paths: 37 | subdir_a/subdir_b/file_a.txt 38 | subdir_a/subdir_b/file_b.txt 39 | ) 40 | 41 | cmrc_add_test( 42 | NAME flower 43 | RESOURCES flower.jpg 44 | TEST_ARGV "${CMAKE_CURRENT_SOURCE_DIR}/flower.jpg" 45 | ) 46 | 47 | cmrc_add_test( 48 | NAME prefix 49 | PASS_REGEX "^Hello, world!" 50 | RESOURCES hello.txt 51 | PREFIX some-prefix 52 | ) 53 | 54 | cmrc_add_test( 55 | NAME whence 56 | PASS_REGEX "^I am a file!" 57 | RESOURCES subdir_a/subdir_b/file_a.txt 58 | WHENCE subdir_a 59 | ) 60 | 61 | cmrc_add_test( 62 | NAME whence_prefix 63 | PASS_REGEX "^I am a file!" 64 | RESOURCES subdir_a/subdir_b/file_b.txt 65 | WHENCE subdir_a 66 | PREFIX imaginary-prefix/ 67 | ) 68 | 69 | cmrc_add_test( 70 | NAME iterate 71 | PASS_REGEX "^subdir_a\nfile_a.txt\nfile_b.txt\n$" 72 | RESOURCES 73 | subdir_a/subdir_b/file_a.txt 74 | subdir_a/subdir_b/file_b.txt 75 | ) 76 | 77 | cmrc_add_test( 78 | NAME enoent 79 | WILL_FAIL 80 | ) -------------------------------------------------------------------------------- /tests/enoent.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | CMRC_DECLARE(enoent); 6 | 7 | int main() { 8 | auto fs = cmrc::enoent::get_filesystem(); 9 | try { 10 | auto data = fs.open("hello.txt"); 11 | } catch (std::system_error e) { 12 | if (e.code() == std::errc::no_such_file_or_directory) { 13 | return 1; 14 | } 15 | } 16 | return 0; 17 | } 18 | -------------------------------------------------------------------------------- /tests/flower.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | CMRC_DECLARE(flower); 9 | 10 | int main(int argc, char** argv) { 11 | if (argc != 2) { 12 | std::cerr << "Invalid arguments passed to flower\n"; 13 | return 2; 14 | } 15 | std::cout << "Reading flower from " << argv[1] << '\n'; 16 | std::ifstream flower_fs{argv[1], std::ios_base::binary}; 17 | if (!flower_fs) { 18 | std::cerr << "Invalid filename passed to flower: " << argv[1] << '\n'; 19 | return 2; 20 | } 21 | 22 | using iter = std::istreambuf_iterator; 23 | const auto fs_size = std::distance(iter(flower_fs), iter()); 24 | flower_fs.seekg(0); 25 | 26 | auto fs = cmrc::flower::get_filesystem(); 27 | auto flower_rc = fs.open("flower.jpg"); 28 | const auto rc_size = std::distance(flower_rc.begin(), flower_rc.end()); 29 | if (rc_size != fs_size) { 30 | std::cerr << "Flower file sizes do not match: FS == " << fs_size << ", RC == " << rc_size 31 | << "\n"; 32 | return 1; 33 | } 34 | if (!std::equal(flower_rc.begin(), flower_rc.end(), iter(flower_fs))) { 35 | std::cerr << "Flower file contents do not match\n"; 36 | return 1; 37 | } 38 | } -------------------------------------------------------------------------------- /tests/flower.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector-of-bool/cmrc/952ffddba731fc110bd50409e8d2b8a06abbd237/tests/flower.jpg -------------------------------------------------------------------------------- /tests/hello.txt: -------------------------------------------------------------------------------- 1 | Hello, world! -------------------------------------------------------------------------------- /tests/iterate.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | CMRC_DECLARE(iterate); 6 | 7 | int main() { 8 | auto fs = cmrc::iterate::get_filesystem(); 9 | for (auto&& entry : fs.iterate_directory("")) { 10 | std::cout << entry.filename() << '\n'; 11 | } 12 | for (auto&& entry : fs.iterate_directory("subdir_a/subdir_b")) { 13 | std::cout << entry.filename() << '\n'; 14 | } 15 | } -------------------------------------------------------------------------------- /tests/prefix.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | CMRC_DECLARE(prefix); 6 | 7 | int main() { 8 | auto fs = cmrc::prefix::get_filesystem(); 9 | auto data = fs.open("some-prefix/hello.txt"); 10 | std::cout << std::string(data.begin(), data.end()) << '\n'; 11 | } 12 | -------------------------------------------------------------------------------- /tests/simple.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | CMRC_DECLARE(simple); 6 | 7 | int main() { 8 | auto fs = cmrc::simple::get_filesystem(); 9 | auto data = fs.open("hello.txt"); 10 | std::cout << std::string(data.begin(), data.end()) << '\n'; 11 | } 12 | -------------------------------------------------------------------------------- /tests/subdir_a/subdir_b/file_a.txt: -------------------------------------------------------------------------------- 1 | I am a file! -------------------------------------------------------------------------------- /tests/subdir_a/subdir_b/file_b.txt: -------------------------------------------------------------------------------- 1 | I am a file! -------------------------------------------------------------------------------- /tests/whence.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | CMRC_DECLARE(whence); 6 | 7 | int main() { 8 | auto fs = cmrc::whence::get_filesystem(); 9 | auto data = fs.open("subdir_b/file_a.txt"); 10 | std::cout << std::string(data.begin(), data.end()) << '\n'; 11 | } 12 | -------------------------------------------------------------------------------- /tests/whence_prefix.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | CMRC_DECLARE(whence_prefix); 6 | 7 | int main() { 8 | auto fs = cmrc::whence_prefix::get_filesystem(); 9 | auto data = fs.open("imaginary-prefix/subdir_b/file_b.txt"); 10 | std::cout << std::string(data.begin(), data.end()) << '\n'; 11 | } 12 | --------------------------------------------------------------------------------