From 0e202c391a0c3571aaa5fd64d14dfb7a95e449a1 Mon Sep 17 00:00:00 2001 From: Randalphwa <38287198+Randalphwa@users.noreply.github.com> Date: Sun, 6 Sep 2026 20:52:02 -0700 Subject: [PATCH 1/2] Add #embed generation support and string_view-based path APIs CMakeRC.cmake now emits a C23 #embed directive in generated resource files when the compiler supports it (guarded by __has_embed), falling back to the hex-literal array otherwise; empty files keep the existing zero-byte array behavior. cmrc.hpp gains optional std::string_view support: file::view(), a string_view-based path_param, and heterogeneous (std::less<>) map lookups under C++17, avoiding string copies when opening/embedded path lookups while remaining compatible with older standards. --- CMakeRC.cmake | 40 +++++++++++--- include/cmrc/cmrc.hpp | 121 ++++++++++++++++++++++++++++++------------ 2 files changed, 121 insertions(+), 40 deletions(-) diff --git a/CMakeRC.cmake b/CMakeRC.cmake index 15c9bc0..5604277 100644 --- a/CMakeRC.cmake +++ b/CMakeRC.cmake @@ -20,13 +20,39 @@ if(_CMRC_GENERATE_MODE) if(NOT cleanup_re STREQUAL "$") string(REGEX REPLACE "${cleanup_re}" "${cleanup_sub}" chars "${chars}") endif() - string(CONFIGURE [[ - namespace { const char file_array[] = { @chars@ 0 }; } - namespace cmrc { namespace @NAMESPACE@ { namespace res_chars { - extern const char* const @SYMBOL@_begin = file_array; - extern const char* const @SYMBOL@_end = file_array + @n_bytes@; - }}} - ]] code) + # #embed takes a header-name token: normalize the resource path to forward + # slashes and keep it quoted. The generated file lives in the build tree + # while the resource lives in the source tree, so the absolute path is + # baked into the directive; the #embed branch is only active when the + # compiling compiler defines __has_embed AND reports the file as + # embeddable, otherwise the hex-literal fallback below is emitted + # unchanged (pre-#embed compilers, MSVC, etc. all take the fallback). + file(TO_CMAKE_PATH "${INPUT_FILE}" INPUT_FILE) + if(n_bytes EQUAL 0) + # A #embed of an empty file (without if_empty()) is ill-formed in some + # compilers; keep the pre-existing zero-byte-array behaviour. + string(CONFIGURE [[ + namespace { const char file_array[] = { 0 }; } + namespace cmrc { namespace @NAMESPACE@ { namespace res_chars { + extern const char* const @SYMBOL@_begin = file_array; + extern const char* const @SYMBOL@_end = file_array + 0; + }}} + ]] code) + else() + string(CONFIGURE [[ + namespace { const char file_array[] = { + #if defined(__has_embed) && __has_embed("@INPUT_FILE@") + #embed "@INPUT_FILE@" + #else + @chars@ 0 + #endif + }; } + namespace cmrc { namespace @NAMESPACE@ { namespace res_chars { + extern const char* const @SYMBOL@_begin = file_array; + extern const char* const @SYMBOL@_end = file_array + @n_bytes@; + }}} + ]] code) + endif() file(WRITE "${OUTPUT_FILE}" "${code}") # Exit from the script. Nothing else needs to be processed return() diff --git a/include/cmrc/cmrc.hpp b/include/cmrc/cmrc.hpp index 97f6b64..3834282 100644 --- a/include/cmrc/cmrc.hpp +++ b/include/cmrc/cmrc.hpp @@ -1,10 +1,10 @@ -// CR: [09-06-2026] - #pragma once #ifndef CMRC_CMRC_HPP_INCLUDED #define CMRC_CMRC_HPP_INCLUDED +// CR: [09-06-2026] + #include #include #include @@ -17,6 +17,22 @@ #include #include +#if defined(_MSVC_LANG) +#define CMRC_CPLUSPLUS _MSVC_LANG +#else +#define CMRC_CPLUSPLUS __cplusplus +#endif + +#if CMRC_CPLUSPLUS >= 201703L && defined(__has_include) +#if __has_include() +#include +#define CMRC_HAS_STRING_VIEW 1 +#endif +#endif +#ifndef CMRC_HAS_STRING_VIEW +#define CMRC_HAS_STRING_VIEW 0 +#endif + #if !(defined(__EXCEPTIONS) || defined(__cpp_exceptions) || \ defined(_CPPUNWIND) || defined(CMRC_NO_EXCEPTIONS)) #define CMRC_NO_EXCEPTIONS 1 @@ -60,6 +76,12 @@ class file { return static_cast(std::distance(begin(), end())); } +#if CMRC_HAS_STRING_VIEW + std::string_view view() const noexcept { + return std::string_view(_begin, size()); + } +#endif + file() = default; file(iterator beg, iterator end) noexcept : _begin(beg), _end(end) {} }; @@ -71,6 +93,12 @@ namespace detail { class directory; class file_data; +#if CMRC_HAS_STRING_VIEW +using path_param = std::string_view; +#else +using path_param = const std::string &; +#endif + class file_or_directory { union _data_t { class file_data *file_data; @@ -112,10 +140,19 @@ struct created_subdirectory { class directory { std::list _files; std::list _dirs; +#if CMRC_CPLUSPLUS >= 201402L + std::map> _index; +#else std::map _index; +#endif +#if CMRC_CPLUSPLUS >= 201402L + using base_iterator = + std::map>::const_iterator; +#else using base_iterator = std::map::const_iterator; +#endif public: directory() = default; @@ -131,8 +168,13 @@ class directory { file_or_directory *add_file(std::string name, const char *begin, const char *end) & { +#if CMRC_CPLUSPLUS >= 201402L + std::map>::iterator existing = + _index.find(name); +#else std::map::iterator existing = _index.find(name); +#endif assert(existing == _index.end()); if (existing != _index.end()) { return &existing->second; @@ -197,29 +239,32 @@ class directory { iterator end() const noexcept { return iterator(_index.end(), _index.end()); } }; -inline std::string normalize_path(std::string path) { +inline std::string normalize_path(detail::path_param path) { + // Work on a copy: a string_view parameter cannot be modified in place, and + // the const-reference fallback must not mutate the caller's string either. + std::string p(path); // Translate backslashes to forward slashes. - for (std::string::size_type idx = path.find('\\'); idx != std::string::npos; - idx = path.find('\\', idx + 1)) { - path[idx] = '/'; + for (std::string::size_type idx = p.find('\\'); idx != std::string::npos; + idx = p.find('\\', idx + 1)) { + p[idx] = '/'; } - while (path.find("/") == 0) { - path.erase(path.begin()); + while (p.find("/") == 0) { + p.erase(p.begin()); } - while (!path.empty() && (path.rfind("/") == path.size() - 1)) { - path.pop_back(); + while (!p.empty() && (p.rfind("/") == p.size() - 1)) { + p.pop_back(); } - auto off = path.npos; - while ((off = path.find("//")) != path.npos) { - path.erase(path.begin() + static_cast(off)); + auto off = p.npos; + while ((off = p.find("//")) != p.npos) { + p.erase(p.begin() + static_cast(off)); } // Collapse "." and ".." path components. std::vector segments; std::string::size_type pos = 0; - while (pos <= path.size()) { - std::string::size_type sep = path.find('/', pos); - std::string segment = path.substr( - pos, sep == std::string::npos ? std::string::npos : sep - pos); + while (pos <= p.size()) { + std::string::size_type sep = p.find('/', pos); + std::string segment = + p.substr(pos, sep == std::string::npos ? std::string::npos : sep - pos); if (segment.empty() || segment == ".") { // Skip empty (duplicate slash) and "." components. } else if (segment == "..") { @@ -246,8 +291,13 @@ inline std::string normalize_path(std::string path) { return result; } +#if CMRC_CPLUSPLUS >= 201402L +using index_type = + std::map>; +#else using index_type = std::map; +#endif } // namespace detail @@ -281,9 +331,9 @@ using directory_iterator = detail::directory::iterator; class embedded_filesystem { // Never-null: const cmrc::detail::index_type *_index; - const detail::file_or_directory *_get(std::string path) const { - path = detail::normalize_path(path); - auto found = _index->find(path); + const detail::file_or_directory *_get(detail::path_param path) const { + const std::string normalized = detail::normalize_path(path); + auto found = _index->find(normalized); if (found == _index->end()) { return nullptr; } else { @@ -295,59 +345,64 @@ class embedded_filesystem { explicit embedded_filesystem(const detail::index_type &index) : _index(&index) {} - file open(const std::string &path) const { + file open(detail::path_param path) const { auto entry_ptr = _get(path); if (!entry_ptr) { #ifdef CMRC_NO_EXCEPTIONS - fprintf(stderr, "Error no such file or directory: %s\n", path.c_str()); + fprintf(stderr, "Error no such file or directory: %s\n", + std::string(path).c_str()); abort(); #else throw std::system_error( - make_error_code(std::errc::no_such_file_or_directory), path); + make_error_code(std::errc::no_such_file_or_directory), + std::string(path)); #endif } if (!entry_ptr->is_file()) { #ifdef CMRC_NO_EXCEPTIONS - fprintf(stderr, "Error is a directory: %s\n", path.c_str()); + fprintf(stderr, "Error is a directory: %s\n", std::string(path).c_str()); abort(); #else - throw std::system_error(make_error_code(std::errc::is_a_directory), path); + throw std::system_error(make_error_code(std::errc::is_a_directory), + std::string(path)); #endif } auto &dat = entry_ptr->as_file(); return file{dat.begin_ptr, dat.end_ptr}; } - bool is_file(const std::string &path) const noexcept { + bool is_file(detail::path_param path) const noexcept { auto entry_ptr = _get(path); return entry_ptr && entry_ptr->is_file(); } - bool is_directory(const std::string &path) const noexcept { + bool is_directory(detail::path_param path) const noexcept { auto entry_ptr = _get(path); return entry_ptr && entry_ptr->is_directory(); } - bool exists(const std::string &path) const noexcept { return !!_get(path); } + bool exists(detail::path_param path) const noexcept { return !!_get(path); } - directory_iterator iterate_directory(const std::string &path) const { + directory_iterator iterate_directory(detail::path_param path) const { auto entry_ptr = _get(path); if (!entry_ptr) { #ifdef CMRC_NO_EXCEPTIONS - fprintf(stderr, "Error no such file or directory: %s\n", path.c_str()); + fprintf(stderr, "Error no such file or directory: %s\n", + std::string(path).c_str()); abort(); #else throw std::system_error( - make_error_code(std::errc::no_such_file_or_directory), path); + make_error_code(std::errc::no_such_file_or_directory), + std::string(path)); #endif } if (!entry_ptr->is_directory()) { #ifdef CMRC_NO_EXCEPTIONS - fprintf(stderr, "Error not a directory: %s\n", path.c_str()); + fprintf(stderr, "Error not a directory: %s\n", std::string(path).c_str()); abort(); #else throw std::system_error(make_error_code(std::errc::not_a_directory), - path); + std::string(path)); #endif } return entry_ptr->as_directory().begin(); From 04a8e423f70688c959c3778378d12eb598594904 Mon Sep 17 00:00:00 2001 From: Randalphwa <38287198+Randalphwa@users.noreply.github.com> Date: Sun, 6 Sep 2026 21:06:43 -0700 Subject: [PATCH 2/2] Fix build failure when compiler doesn't have __has_embed --- CMakeRC.cmake | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CMakeRC.cmake b/CMakeRC.cmake index 5604277..9380d75 100644 --- a/CMakeRC.cmake +++ b/CMakeRC.cmake @@ -41,8 +41,12 @@ if(_CMRC_GENERATE_MODE) else() string(CONFIGURE [[ namespace { const char file_array[] = { - #if defined(__has_embed) && __has_embed("@INPUT_FILE@") + #if defined(__has_embed) + # if __has_embed("@INPUT_FILE@") #embed "@INPUT_FILE@" + # else + @chars@ 0 + # endif #else @chars@ 0 #endif