1# CMakeRC is a third-party utility. 2# https://github.com/vector-of-bool/cmrc 3# 4# MIT License 5# 6# Copyright (c) 2017 vector-of-bool <vectorofbool@gmail.com> 7# 8# Permission is hereby granted, free of charge, to any person obtaining a copy 9# of this software and associated documentation files (the "Software"), to deal 10# in the Software without restriction, including without limitation the rights 11# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12# copies of the Software, and to permit persons to whom the Software is 13# furnished to do so, subject to the following conditions: 14# 15# The above copyright notice and this permission notice shall be included in all 16# copies or substantial portions of the Software. 17# 18# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24# SOFTWARE. 25 26 27# This block is executed when generating an intermediate resource file, not when 28# running in CMake configure mode 29if(_CMRC_GENERATE_MODE) 30 # Read in the digits 31 file(READ "${INPUT_FILE}" bytes HEX) 32 # Format each pair into a character literal. Heuristics seem to favor doing 33 # the conversion in groups of five for fastest conversion 34 string(REGEX REPLACE "(..)(..)(..)(..)(..)" "'\\\\x\\1','\\\\x\\2','\\\\x\\3','\\\\x\\4','\\\\x\\5'," chars "${bytes}") 35 # Since we did this in groups, we have some leftovers to clean up 36 string(LENGTH "${bytes}" n_bytes2) 37 math(EXPR n_bytes "${n_bytes2} / 2") 38 math(EXPR remainder "${n_bytes} % 5") # <-- '5' is the grouping count from above 39 set(cleanup_re "$") 40 set(cleanup_sub ) 41 while(remainder) 42 set(cleanup_re "(..)${cleanup_re}") 43 set(cleanup_sub "'\\\\x\\${remainder}',${cleanup_sub}") 44 math(EXPR remainder "${remainder} - 1") 45 endwhile() 46 if(NOT cleanup_re STREQUAL "$") 47 string(REGEX REPLACE "${cleanup_re}" "${cleanup_sub}" chars "${chars}") 48 endif() 49 string(CONFIGURE [[ 50 namespace { const char file_array[] = { @chars@ 0 }; } 51 namespace cmrc { namespace @NAMESPACE@ { namespace res_chars { 52 extern const char* const @SYMBOL@_begin = file_array; 53 extern const char* const @SYMBOL@_end = file_array + @n_bytes@; 54 }}} 55 ]] code) 56 file(WRITE "${OUTPUT_FILE}" "${code}") 57 # Exit from the script. Nothing else needs to be processed 58 return() 59endif() 60 61set(_version 2.0.0) 62 63cmake_minimum_required(VERSION 3.3) 64include(CMakeParseArguments) 65 66if(COMMAND cmrc_add_resource_library) 67 if(NOT DEFINED _CMRC_VERSION OR NOT (_version STREQUAL _CMRC_VERSION)) 68 message(WARNING "More than one CMakeRC version has been included in this project.") 69 endif() 70 # CMakeRC has already been included! Don't do anything 71 return() 72endif() 73 74set(_CMRC_VERSION "${_version}" CACHE INTERNAL "CMakeRC version. Used for checking for conflicts") 75 76set(_CMRC_SCRIPT "${CMAKE_CURRENT_LIST_FILE}" CACHE INTERNAL "Path to CMakeRC script") 77 78function(_cmrc_normalize_path var) 79 set(path "${${var}}") 80 file(TO_CMAKE_PATH "${path}" path) 81 while(path MATCHES "//") 82 string(REPLACE "//" "/" path "${path}") 83 endwhile() 84 string(REGEX REPLACE "/+$" "" path "${path}") 85 set("${var}" "${path}" PARENT_SCOPE) 86endfunction() 87 88get_filename_component(_inc_dir "${CMAKE_BINARY_DIR}/_cmrc/include" ABSOLUTE) 89set(CMRC_INCLUDE_DIR "${_inc_dir}" CACHE INTERNAL "Directory for CMakeRC include files") 90# Let's generate the primary include file 91file(MAKE_DIRECTORY "${CMRC_INCLUDE_DIR}/cmrc") 92set(hpp_content [==[ 93#ifndef CMRC_CMRC_HPP_INCLUDED 94#define CMRC_CMRC_HPP_INCLUDED 95 96#include <cassert> 97#include <functional> 98#include <iterator> 99#include <list> 100#include <map> 101#include <mutex> 102#include <string> 103#include <system_error> 104#include <type_traits> 105 106namespace cmrc { namespace detail { struct dummy; } } 107 108#define CMRC_DECLARE(libid) \ 109 namespace cmrc { namespace detail { \ 110 struct dummy; \ 111 static_assert(std::is_same<dummy, ::cmrc::detail::dummy>::value, "CMRC_DECLARE() must only appear at the global namespace"); \ 112 } } \ 113 namespace cmrc { namespace libid { \ 114 cmrc::embedded_filesystem get_filesystem(); \ 115 } } static_assert(true, "") 116 117namespace cmrc { 118 119class file { 120 const char* _begin = nullptr; 121 const char* _end = nullptr; 122 123public: 124 using iterator = const char*; 125 using const_iterator = iterator; 126 iterator begin() const noexcept { return _begin; } 127 iterator cbegin() const noexcept { return _begin; } 128 iterator end() const noexcept { return _end; } 129 iterator cend() const noexcept { return _end; } 130 std::size_t size() const { return std::distance(begin(), end()); } 131 132 file() = default; 133 file(iterator beg, iterator end) noexcept : _begin(beg), _end(end) {} 134}; 135 136class directory_entry; 137 138namespace detail { 139 140class directory; 141class file_data; 142 143class file_or_directory { 144 union _data_t { 145 class file_data* file_data; 146 class directory* directory; 147 } _data; 148 bool _is_file = true; 149 150public: 151 explicit file_or_directory(file_data& f) { 152 _data.file_data = &f; 153 } 154 explicit file_or_directory(directory& d) { 155 _data.directory = &d; 156 _is_file = false; 157 } 158 bool is_file() const noexcept { 159 return _is_file; 160 } 161 bool is_directory() const noexcept { 162 return !is_file(); 163 } 164 const directory& as_directory() const noexcept { 165 assert(!is_file()); 166 return *_data.directory; 167 } 168 const file_data& as_file() const noexcept { 169 assert(is_file()); 170 return *_data.file_data; 171 } 172}; 173 174class file_data { 175public: 176 const char* begin_ptr; 177 const char* end_ptr; 178 file_data(const file_data&) = delete; 179 file_data(const char* b, const char* e) : begin_ptr(b), end_ptr(e) {} 180}; 181 182inline std::pair<std::string, std::string> split_path(const std::string& path) { 183 auto first_sep = path.find("/"); 184 if (first_sep == path.npos) { 185 return std::make_pair(path, ""); 186 } else { 187 return std::make_pair(path.substr(0, first_sep), path.substr(first_sep + 1)); 188 } 189} 190 191struct created_subdirectory { 192 class directory& directory; 193 class file_or_directory& index_entry; 194}; 195 196class directory { 197 std::list<file_data> _files; 198 std::list<directory> _dirs; 199 std::map<std::string, file_or_directory> _index; 200 201 using base_iterator = std::map<std::string, file_or_directory>::const_iterator; 202 203public: 204 205 directory() = default; 206 directory(const directory&) = delete; 207 208 created_subdirectory add_subdir(std::string name) & { 209 _dirs.emplace_back(); 210 auto& back = _dirs.back(); 211 auto& fod = _index.emplace(name, file_or_directory{back}).first->second; 212 return created_subdirectory{back, fod}; 213 } 214 215 file_or_directory* add_file(std::string name, const char* begin, const char* end) & { 216 assert(_index.find(name) == _index.end()); 217 _files.emplace_back(begin, end); 218 return &_index.emplace(name, file_or_directory{_files.back()}).first->second; 219 } 220 221 const file_or_directory* get(const std::string& path) const { 222 auto pair = split_path(path); 223 auto child = _index.find(pair.first); 224 if (child == _index.end()) { 225 return nullptr; 226 } 227 auto& entry = child->second; 228 if (pair.second.empty()) { 229 // We're at the end of the path 230 return &entry; 231 } 232 233 if (entry.is_file()) { 234 // We can't traverse into a file. Stop. 235 return nullptr; 236 } 237 // Keep going down 238 return entry.as_directory().get(pair.second); 239 } 240 241 class iterator { 242 base_iterator _base_iter; 243 base_iterator _end_iter; 244 public: 245 using value_type = directory_entry; 246 using difference_type = std::ptrdiff_t; 247 using pointer = const value_type*; 248 using reference = const value_type&; 249 using iterator_category = std::input_iterator_tag; 250 251 iterator() = default; 252 explicit iterator(base_iterator iter, base_iterator end) : _base_iter(iter), _end_iter(end) {} 253 254 iterator begin() const noexcept { 255 return *this; 256 } 257 258 iterator end() const noexcept { 259 return iterator(_end_iter, _end_iter); 260 } 261 262 inline value_type operator*() const noexcept; 263 264 bool operator==(const iterator& rhs) const noexcept { 265 return _base_iter == rhs._base_iter; 266 } 267 268 bool operator!=(const iterator& rhs) const noexcept { 269 return !(*this == rhs); 270 } 271 272 iterator operator++() noexcept { 273 auto cp = *this; 274 ++_base_iter; 275 return cp; 276 } 277 278 iterator& operator++(int) noexcept { 279 ++_base_iter; 280 return *this; 281 } 282 }; 283 284 using const_iterator = iterator; 285 286 iterator begin() const noexcept { 287 return iterator(_index.begin(), _index.end()); 288 } 289 290 iterator end() const noexcept { 291 return iterator(); 292 } 293}; 294 295inline std::string normalize_path(std::string path) { 296 while (path.find("/") == 0) { 297 path.erase(path.begin()); 298 } 299 while (!path.empty() && (path.rfind("/") == path.size() - 1)) { 300 path.pop_back(); 301 } 302 auto off = path.npos; 303 while ((off = path.find("//")) != path.npos) { 304 path.erase(path.begin() + off); 305 } 306 return path; 307} 308 309using index_type = std::map<std::string, const cmrc::detail::file_or_directory*>; 310 311} // detail 312 313class directory_entry { 314 std::string _fname; 315 const detail::file_or_directory* _item; 316 317public: 318 directory_entry() = delete; 319 explicit directory_entry(std::string filename, const detail::file_or_directory& item) 320 : _fname(filename) 321 , _item(&item) 322 {} 323 324 const std::string& filename() const & { 325 return _fname; 326 } 327 std::string filename() const && { 328 return std::move(_fname); 329 } 330 331 bool is_file() const { 332 return _item->is_file(); 333 } 334 335 bool is_directory() const { 336 return _item->is_directory(); 337 } 338}; 339 340directory_entry detail::directory::iterator::operator*() const noexcept { 341 assert(begin() != end()); 342 return directory_entry(_base_iter->first, _base_iter->second); 343} 344 345using directory_iterator = detail::directory::iterator; 346 347class embedded_filesystem { 348 // Never-null: 349 const cmrc::detail::index_type* _index; 350 const detail::file_or_directory* _get(std::string path) const { 351 path = detail::normalize_path(path); 352 auto found = _index->find(path); 353 if (found == _index->end()) { 354 return nullptr; 355 } else { 356 return found->second; 357 } 358 } 359 360public: 361 explicit embedded_filesystem(const detail::index_type& index) 362 : _index(&index) 363 {} 364 365 file open(const std::string& path) const { 366 auto entry_ptr = _get(path); 367 if (!entry_ptr || !entry_ptr->is_file()) { 368 throw std::system_error(make_error_code(std::errc::no_such_file_or_directory), path); 369 } 370 auto& dat = entry_ptr->as_file(); 371 return file{dat.begin_ptr, dat.end_ptr}; 372 } 373 374 bool is_file(const std::string& path) const noexcept { 375 auto entry_ptr = _get(path); 376 return entry_ptr && entry_ptr->is_file(); 377 } 378 379 bool is_directory(const std::string& path) const noexcept { 380 auto entry_ptr = _get(path); 381 return entry_ptr && entry_ptr->is_directory(); 382 } 383 384 bool exists(const std::string& path) const noexcept { 385 return !!_get(path); 386 } 387 388 directory_iterator iterate_directory(const std::string& path) const { 389 auto entry_ptr = _get(path); 390 if (!entry_ptr) { 391 throw std::system_error(make_error_code(std::errc::no_such_file_or_directory), path); 392 } 393 if (!entry_ptr->is_directory()) { 394 throw std::system_error(make_error_code(std::errc::not_a_directory), path); 395 } 396 return entry_ptr->as_directory().begin(); 397 } 398}; 399 400} 401 402#endif // CMRC_CMRC_HPP_INCLUDED 403]==]) 404 405set(cmrc_hpp "${CMRC_INCLUDE_DIR}/cmrc/cmrc.hpp" CACHE INTERNAL "") 406set(_generate 1) 407if(EXISTS "${cmrc_hpp}") 408 file(READ "${cmrc_hpp}" _current) 409 if(_current STREQUAL hpp_content) 410 set(_generate 0) 411 endif() 412endif() 413file(GENERATE OUTPUT "${cmrc_hpp}" CONTENT "${hpp_content}" CONDITION ${_generate}) 414 415add_library(cmrc-base INTERFACE) 416target_include_directories(cmrc-base INTERFACE "${CMRC_INCLUDE_DIR}") 417# Signal a basic C++11 feature to require C++11. 418target_compile_features(cmrc-base INTERFACE cxx_nullptr) 419set_property(TARGET cmrc-base PROPERTY INTERFACE_CXX_EXTENSIONS OFF) 420add_library(cmrc::base ALIAS cmrc-base) 421 422function(cmrc_add_resource_library name) 423 set(args ALIAS NAMESPACE) 424 cmake_parse_arguments(ARG "" "${args}" "" "${ARGN}") 425 # Generate the identifier for the resource library's namespace 426 set(ns_re "[a-zA-Z_][a-zA-Z0-9_]*") 427 if(NOT DEFINED ARG_NAMESPACE) 428 # Check that the library name is also a valid namespace 429 if(NOT name MATCHES "${ns_re}") 430 message(SEND_ERROR "Library name is not a valid namespace. Specify the NAMESPACE argument") 431 endif() 432 set(ARG_NAMESPACE "${name}") 433 else() 434 if(NOT ARG_NAMESPACE MATCHES "${ns_re}") 435 message(SEND_ERROR "NAMESPACE for ${name} is not a valid C++ namespace identifier (${ARG_NAMESPACE})") 436 endif() 437 endif() 438 set(libname "${name}") 439 # Generate a library with the compiled in character arrays. 440 string(CONFIGURE [=[ 441 #include <cmrc/cmrc.hpp> 442 #include <map> 443 #include <utility> 444 445 namespace cmrc { 446 namespace @ARG_NAMESPACE@ { 447 448 namespace res_chars { 449 // These are the files which are available in this resource library 450 $<JOIN:$<TARGET_PROPERTY:@libname@,CMRC_EXTERN_DECLS>, 451 > 452 } 453 454 namespace { 455 456 const cmrc::detail::index_type& 457 get_root_index() { 458 static cmrc::detail::directory root_directory_; 459 static cmrc::detail::file_or_directory root_directory_fod{root_directory_}; 460 static cmrc::detail::index_type root_index; 461 root_index.emplace("", &root_directory_fod); 462 struct dir_inl { 463 class cmrc::detail::directory& directory; 464 }; 465 dir_inl root_directory_dir{root_directory_}; 466 (void)root_directory_dir; 467 $<JOIN:$<TARGET_PROPERTY:@libname@,CMRC_MAKE_DIRS>, 468 > 469 $<JOIN:$<TARGET_PROPERTY:@libname@,CMRC_MAKE_FILES>, 470 > 471 return root_index; 472 } 473 474 } 475 476 cmrc::embedded_filesystem get_filesystem() { 477 static auto& index = get_root_index(); 478 return cmrc::embedded_filesystem{index}; 479 } 480 481 } // @ARG_NAMESPACE@ 482 } // cmrc 483 ]=] cpp_content @ONLY) 484 get_filename_component(libdir "${CMAKE_CURRENT_BINARY_DIR}/__cmrc_${name}" ABSOLUTE) 485 get_filename_component(lib_tmp_cpp "${libdir}/lib_.cpp" ABSOLUTE) 486 string(REPLACE "\n " "\n" cpp_content "${cpp_content}") 487 file(GENERATE OUTPUT "${lib_tmp_cpp}" CONTENT "${cpp_content}") 488 get_filename_component(libcpp "${libdir}/lib.cpp" ABSOLUTE) 489 add_custom_command(OUTPUT "${libcpp}" 490 DEPENDS "${lib_tmp_cpp}" "${cmrc_hpp}" 491 COMMAND ${CMAKE_COMMAND} -E copy_if_different "${lib_tmp_cpp}" "${libcpp}" 492 COMMENT "Generating ${name} resource loader" 493 ) 494 # Generate the actual static library. Each source file is just a single file 495 # with a character array compiled in containing the contents of the 496 # corresponding resource file. 497 add_library(${name} STATIC ${libcpp}) 498 set_property(TARGET ${name} PROPERTY CMRC_LIBDIR "${libdir}") 499 set_property(TARGET ${name} PROPERTY CMRC_NAMESPACE "${ARG_NAMESPACE}") 500 target_link_libraries(${name} PUBLIC cmrc::base) 501 set_property(TARGET ${name} PROPERTY CMRC_IS_RESOURCE_LIBRARY TRUE) 502 if(ARG_ALIAS) 503 add_library("${ARG_ALIAS}" ALIAS ${name}) 504 endif() 505 cmrc_add_resources(${name} ${ARG_UNPARSED_ARGUMENTS}) 506endfunction() 507 508function(_cmrc_register_dirs name dirpath) 509 if(dirpath STREQUAL "") 510 return() 511 endif() 512 # Skip this dir if we have already registered it 513 get_target_property(registered "${name}" _CMRC_REGISTERED_DIRS) 514 if(dirpath IN_LIST registered) 515 return() 516 endif() 517 # Register the parent directory first 518 get_filename_component(parent "${dirpath}" DIRECTORY) 519 if(NOT parent STREQUAL "") 520 _cmrc_register_dirs("${name}" "${parent}") 521 endif() 522 # Now generate the registration 523 set_property(TARGET "${name}" APPEND PROPERTY _CMRC_REGISTERED_DIRS "${dirpath}") 524 _cm_encode_fpath(sym "${dirpath}") 525 if(parent STREQUAL "") 526 set(parent_sym root_directory) 527 else() 528 _cm_encode_fpath(parent_sym "${parent}") 529 endif() 530 get_filename_component(leaf "${dirpath}" NAME) 531 set_property( 532 TARGET "${name}" 533 APPEND PROPERTY CMRC_MAKE_DIRS 534 "static auto ${sym}_dir = ${parent_sym}_dir.directory.add_subdir(\"${leaf}\")\;" 535 "root_index.emplace(\"${dirpath}\", &${sym}_dir.index_entry)\;" 536 ) 537endfunction() 538 539function(cmrc_add_resources name) 540 get_target_property(is_reslib ${name} CMRC_IS_RESOURCE_LIBRARY) 541 if(NOT TARGET ${name} OR NOT is_reslib) 542 message(SEND_ERROR "cmrc_add_resources called on target '${name}' which is not an existing resource library") 543 return() 544 endif() 545 546 set(options) 547 set(args WHENCE PREFIX) 548 set(list_args) 549 cmake_parse_arguments(ARG "${options}" "${args}" "${list_args}" "${ARGN}") 550 551 if(NOT ARG_WHENCE) 552 set(ARG_WHENCE ${CMAKE_CURRENT_SOURCE_DIR}) 553 endif() 554 _cmrc_normalize_path(ARG_WHENCE) 555 get_filename_component(ARG_WHENCE "${ARG_WHENCE}" ABSOLUTE) 556 557 # Generate the identifier for the resource library's namespace 558 get_target_property(lib_ns "${name}" CMRC_NAMESPACE) 559 560 get_target_property(libdir ${name} CMRC_LIBDIR) 561 get_target_property(target_dir ${name} SOURCE_DIR) 562 file(RELATIVE_PATH reldir "${target_dir}" "${CMAKE_CURRENT_SOURCE_DIR}") 563 if(reldir MATCHES "^\\.\\.") 564 message(SEND_ERROR "Cannot call cmrc_add_resources in a parent directory from the resource library target") 565 return() 566 endif() 567 568 foreach(input IN LISTS ARG_UNPARSED_ARGUMENTS) 569 _cmrc_normalize_path(input) 570 get_filename_component(abs_in "${input}" ABSOLUTE) 571 # Generate a filename based on the input filename that we can put in 572 # the intermediate directory. 573 file(RELATIVE_PATH relpath "${ARG_WHENCE}" "${abs_in}") 574 if(relpath MATCHES "^\\.\\.") 575 # For now we just error on files that exist outside of the soure dir. 576 message(SEND_ERROR "Cannot add file '${input}': File must be in a subdirectory of ${ARG_WHENCE}") 577 continue() 578 endif() 579 if(DEFINED ARG_PREFIX) 580 _cmrc_normalize_path(ARG_PREFIX) 581 endif() 582 if(ARG_PREFIX AND NOT ARG_PREFIX MATCHES "/$") 583 set(ARG_PREFIX "${ARG_PREFIX}/") 584 endif() 585 get_filename_component(dirpath "${ARG_PREFIX}${relpath}" DIRECTORY) 586 _cmrc_register_dirs("${name}" "${dirpath}") 587 get_filename_component(abs_out "${libdir}/intermediate/${relpath}.cpp" ABSOLUTE) 588 # Generate a symbol name relpath the file's character array 589 _cm_encode_fpath(sym "${relpath}") 590 # Get the symbol name for the parent directory 591 if(dirpath STREQUAL "") 592 set(parent_sym root_directory) 593 else() 594 _cm_encode_fpath(parent_sym "${dirpath}") 595 endif() 596 # Generate the rule for the intermediate source file 597 _cmrc_generate_intermediate_cpp(${lib_ns} ${sym} "${abs_out}" "${abs_in}") 598 target_sources(${name} PRIVATE "${abs_out}") 599 set_property(TARGET ${name} APPEND PROPERTY CMRC_EXTERN_DECLS 600 "// Pointers to ${input}" 601 "extern const char* const ${sym}_begin\;" 602 "extern const char* const ${sym}_end\;" 603 ) 604 get_filename_component(leaf "${relpath}" NAME) 605 set_property( 606 TARGET ${name} 607 APPEND PROPERTY CMRC_MAKE_FILES 608 "root_index.emplace(" 609 " \"${ARG_PREFIX}${relpath}\"," 610 " ${parent_sym}_dir.directory.add_file(" 611 " \"${leaf}\"," 612 " res_chars::${sym}_begin," 613 " res_chars::${sym}_end" 614 " )" 615 ")\;" 616 ) 617 endforeach() 618endfunction() 619 620function(_cmrc_generate_intermediate_cpp lib_ns symbol outfile infile) 621 add_custom_command( 622 # This is the file we will generate 623 OUTPUT "${outfile}" 624 # These are the primary files that affect the output 625 DEPENDS "${infile}" "${_CMRC_SCRIPT}" 626 COMMAND 627 "${CMAKE_COMMAND}" 628 -D_CMRC_GENERATE_MODE=TRUE 629 -DNAMESPACE=${lib_ns} 630 -DSYMBOL=${symbol} 631 "-DINPUT_FILE=${infile}" 632 "-DOUTPUT_FILE=${outfile}" 633 -P "${_CMRC_SCRIPT}" 634 COMMENT "Generating intermediate file for ${infile}" 635 ) 636endfunction() 637 638function(_cm_encode_fpath var fpath) 639 string(MAKE_C_IDENTIFIER "${fpath}" ident) 640 string(MD5 hash "${fpath}") 641 string(SUBSTRING "${hash}" 0 4 hash) 642 set(${var} f_${hash}_${ident} PARENT_SCOPE) 643endfunction() 644