diff --git a/CMakeLists.txt b/CMakeLists.txt index c057b31..1989e3b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,12 +20,12 @@ set(EMBED_SOURCE_FILE_TEMPLATE [==[ #include "battery/embed.hpp" namespace b { - + const unsigned char ${IDENTIFIER}_data[] = { + ${GENERATED_BYTE_ARRAY} + }; EmbedInternal::EmbeddedFile EmbedInternal::${IDENTIFIER} = { - std::string_view( - ${GENERATED_BYTE_ARRAY}, + ${IDENTIFIER}_data, ${FILESIZE} - ) , "${FILENAME}" #ifndef B_PRODUCTION_MODE , "${FULL_PATH}" @@ -51,6 +51,7 @@ set(EMBED_MASTER_SOURCE_FILE [==[ #include #include #include +#include #endif // !B_PRODUCTION_MODE and !B_OS_WEB namespace b { @@ -142,7 +143,7 @@ namespace b { continue; } filedata.newContent = *fileresult; - EmbedInternal::EmbeddedFile newFile(filedata.newContent, filedata.filename, filedata.filepath); + EmbedInternal::EmbeddedFile newFile(filedata.newContent.c_str(), filedata.newContent.size(), filedata.filename.c_str(), filedata.filepath.c_str()); filedata.callback(newFile); } } @@ -158,7 +159,9 @@ namespace b { #else // B_PRODUCTION_MODE or B_OS_WEB auto fileresult = embed_read_file(m_fullFilepath); if (fileresult) { - m_data = *fileresult; + m_storage = std::move(*fileresult); + m_data = reinterpret_cast(m_storage.c_str()); + m_size = m_storage.size(); } callback(*this); @@ -184,110 +187,79 @@ set(EMBED_HEADER_FILE [=[ // DO NOT EDIT THIS FILE!!! #ifndef BATTERY_EMBED_HPP #define BATTERY_EMBED_HPP - #include #include -#include #include #include #include #include #include -#ifndef __cpp_constexpr_dynamic_alloc -# error "battery::embed requires C++20 (Your compiler does not provide __cpp_constexpr_dynamic_alloc, which is needed for battery::embed)" -#endif - namespace b { - struct EmbedInternal { - class EmbeddedFile { public: - constexpr EmbeddedFile() = default; + EmbeddedFile() = default; - constexpr EmbeddedFile( - const std::string_view& data, - const std::string_view& filename + EmbeddedFile( + const unsigned char* data, size_t size, + const char* filename #ifndef B_PRODUCTION_MODE - , const std::string_view& fullFilepath -#endif // !B_PRODUCTION_MODE + , const char* fullFilepath +#endif ) : m_data(data) + , m_size(size) , m_filename(filename) #ifndef B_PRODUCTION_MODE , m_fullFilepath(fullFilepath) -#endif // !B_PRODUCTION_MODE +#endif {} [[nodiscard]] std::string str() const { - return m_data.data(); - } - - [[nodiscard]] const char* data() const { - return m_data.data(); + return std::string(reinterpret_cast(m_data), m_size); } - + [[nodiscard]] const unsigned char* data() const { return m_data; } [[nodiscard]] std::vector vec() const { - return { m_data.begin(), m_data.end() }; - } - - [[nodiscard]] size_t length() const { - return m_data.size(); + return { m_data, m_data + m_size }; } + [[nodiscard]] size_t size() const { return m_size; } + [[nodiscard]] size_t length() const { return m_size; } - [[nodiscard]] size_t size() const { - return m_data.size(); - } - - operator std::string() const { - return str(); - } - - operator std::vector() const { - return vec(); - } + operator std::string() const { return str(); } + operator std::vector() const { return vec(); } void get(const std::function& callback); private: - std::string_view m_data; - std::string_view m_filename; + const unsigned char* m_data = nullptr; + size_t m_size = 0; + const char* m_filename = nullptr; #ifndef B_PRODUCTION_MODE - std::string_view m_fullFilepath; + std::string m_storage; + const char* m_fullFilepath = nullptr; #endif - }; // class EmbeddedFile + }; ${EMBEDDED_FILES_DECLARATIONS} - }; // struct embedded_files - - template - struct embed_string_literal { - constexpr embed_string_literal(const char (&str)[N]) { - std::copy_n(str, N, value); - } - constexpr bool operator!=(const embed_string_literal& other) const { - return std::equal(value, value + N, other.value); - } - [[nodiscard]] std::string str() const { - return std::string(value, N); - } - [[nodiscard]] constexpr bool _false() const { - return false; - } - char value[N]; }; + + template + EmbedInternal::EmbeddedFile embed(const S& identifier) { + ${EMBEDDED_FILES_RETURNS} + throw std::runtime_error("[b::embed<>] No such file or directory"); + } - template - constexpr bool operator==(const embed_string_literal& left, const char (&right)[M]) { - return std::equal(left.value, left.value + N, right); + inline EmbedInternal::EmbeddedFile embed(const char* identifier) { + ${EMBEDDED_FILES_RET_CHARP} + throw std::runtime_error("[b::embed<>] No such file or directory"); } - template - constexpr EmbedInternal::EmbeddedFile embed() { - ${EMBEDDED_FILES_RETURNS}{ - static_assert(identifier._false(), "[b::embed<>] No such file or directory"); - } + // Функция для получения списка всех встроенных файлов + [[nodiscard]] inline std::vector embed_list() { + return { + ${EMBEDDED_FILES_LIST} + }; } } // namespace b @@ -303,17 +275,28 @@ file(WRITE ${EMBED_BINARY_DIR}/embed_header_file_template.hpp "${EMBED_HEADER_FI # The cmake script to embed the files. This is called later on-demand, in a separate process set(EMBED_GENERATE_SCRIPT [=[ -file(READ "${FULL_PATH}" GENERATED_BYTE_ARRAY HEX) -string(LENGTH "${GENERATED_BYTE_ARRAY}" FILESIZE) -math(EXPR FILESIZE "${FILESIZE} / 2") - -string(REPEAT "[0-9a-f]" 32 PATTERN) -set(GENERATED_BYTE_ARRAY "\"${GENERATED_BYTE_ARRAY}") -string(REGEX REPLACE "${PATTERN}" "\\0\"\n \"" GENERATED_BYTE_ARRAY ${GENERATED_BYTE_ARRAY}) -string(REGEX REPLACE "([0-9a-f][0-9a-f])" "\\\\x\\1" GENERATED_BYTE_ARRAY ${GENERATED_BYTE_ARRAY}) -set(GENERATED_BYTE_ARRAY "${GENERATED_BYTE_ARRAY}\"") +file(READ "${FULL_PATH}" HEX_DATA HEX) +string(LENGTH "${HEX_DATA}" HEX_LENGTH) +math(EXPR BYTE_COUNT "${HEX_LENGTH} / 2") + +# Преобразуем hex-строку: 48656c6c6f → 0x48, 0x65, 0x6c, 0x6c, 0x6f +set(GENERATED_BYTE_ARRAY "${HEX_DATA}") + +# Шаг 1: добавляем 0x перед каждой парой +string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1" GENERATED_BYTE_ARRAY "${GENERATED_BYTE_ARRAY}") + +# Шаг 2: добавляем запятую и пробел после каждого байта +string(REGEX REPLACE "(0x[0-9a-f][0-9a-f])" "\\1, " GENERATED_BYTE_ARRAY "${GENERATED_BYTE_ARRAY}") + +# Шаг 3: удаляем лишнюю запятую и пробел в конце +string(REGEX REPLACE ", $" "" GENERATED_BYTE_ARRAY "${GENERATED_BYTE_ARRAY}") + +# Устанавливаем размер +math(EXPR FILESIZE "${BYTE_COUNT}") + configure_file(${INFILE} ${OUTFILE}) ]=]) + file(WRITE ${EMBED_BINARY_DIR}/generate.cmake ${EMBED_GENERATE_SCRIPT}) set(EMBED_IDENTIFIERS "" CACHE INTERNAL "list of all identifiers used by battery::embed") @@ -333,31 +316,45 @@ endfunction(_embed_generate_all_hpps) function(_embed_generate_hpp TARGET) string(TOLOWER "${TARGET}" TARGET) string(REGEX REPLACE "[^a-zA-Z0-9_]" "_" TARGET "${TARGET}") - if (NOT EMBED_IDENTIFIERS) return() endif() + set(EMBEDDED_FILES_DECLARATIONS "") - list(LENGTH EMBED_IDENTIFIERS num_identifiers) - foreach (IDENTIFIER IN LISTS EMBED_IDENTIFIERS) - if (IDENTIFIER MATCHES "^${TARGET}_") - set(EMBEDDED_FILES_DECLARATIONS "${EMBEDDED_FILES_DECLARATIONS}static EmbeddedFile ${IDENTIFIER};\n ") - endif() - endforeach() set(EMBEDDED_FILES_RETURNS "") - math(EXPR num_identifiers "${num_identifiers} - 1") - foreach (INDEX RANGE ${num_identifiers}) + set(EMBEDDED_FILES_RET_CHARP "") + set(EMBEDDED_FILES_LIST "") + + # Iterate over indices + list(LENGTH EMBED_IDENTIFIERS num_identifiers) + math(EXPR last_index "${num_identifiers} - 1") + foreach(INDEX RANGE 0 ${last_index}) list(GET EMBED_IDENTIFIERS ${INDEX} IDENTIFIER) list(GET EMBED_FILENAMES ${INDEX} FILENAME) if (IDENTIFIER MATCHES "^${TARGET}_") - set(EMBEDDED_FILES_RETURNS "${EMBEDDED_FILES_RETURNS}if constexpr (identifier == \"${FILENAME}\") { return EmbedInternal::${IDENTIFIER}; }\n else ") + set(EMBEDDED_FILES_DECLARATIONS "${EMBEDDED_FILES_DECLARATIONS}static EmbeddedFile ${IDENTIFIER};\n") + + set(EMBEDDED_FILES_RETURNS "${EMBEDDED_FILES_RETURNS}if (identifier == \"${FILENAME}\") { return EmbedInternal::${IDENTIFIER}; }\nelse ") + + set(EMBEDDED_FILES_RET_CHARP "${EMBEDDED_FILES_RET_CHARP}if (identifier == std::string(\"${FILENAME}\")) { return EmbedInternal::${IDENTIFIER}; }\nelse ") + + set(EMBEDDED_FILES_LIST "${EMBEDDED_FILES_LIST} \"${FILENAME}\",\n") endif() endforeach() + + # Remove the last comma + string(REGEX REPLACE ",\n$" "" EMBEDDED_FILES_LIST "${EMBEDDED_FILES_LIST}") + + # Generate the header file file(READ ${EMBED_BINARY_DIR}/embed_header_file_template.hpp EMBED_HEADER_FILE) string(CONFIGURE "${EMBED_HEADER_FILE}" EMBED_HEADER_FILE_GENERATED) + + # Path to embed.hpp set(EMBED_HPP "${EMBED_BINARY_DIR}/autogen/${TARGET}/include/battery/embed.hpp") + get_filename_component(EMBED_HPP_DIR "${EMBED_HPP}" DIRECTORY) + file(MAKE_DIRECTORY "${EMBED_HPP_DIR}") file(WRITE "${EMBED_HPP}" "${EMBED_HEADER_FILE_GENERATED}") -endfunction(_embed_generate_hpp) +endfunction() # Internal function for validating an identifier function(embed_validate_identifier IDENTIFIER) # Validate the identifier against C variable naming rules @@ -370,51 +367,51 @@ endfunction() function(b_embed TARGET FILENAME) string(TOLOWER "${TARGET}" TARGET_ID) string(REGEX REPLACE "[^a-zA-Z0-9_]" "_" TARGET_ID "${TARGET_ID}") - if (IS_ABSOLUTE "${FILENAME}") message(FATAL_ERROR "embed: File name must be relative to the current source directory: '${FILENAME}'") endif() - # Make the identifier string(TOLOWER "${TARGET_ID}_${FILENAME}" IDENTIFIER) string(REGEX REPLACE "[^a-zA-Z0-9_]" "_" IDENTIFIER "${IDENTIFIER}") # Replace all invalid characters with underscores embed_validate_identifier("${IDENTIFIER}") # Validate the identifier against C variable naming rules - # Set up paths get_filename_component(FULL_PATH "${FILENAME}" ABSOLUTE) # Make the file path absolute set(CPP_FILE "${EMBED_BINARY_DIR}/autogen/${TARGET_ID}/src/${IDENTIFIER}.cpp") - # If identifier already in use list(FIND EMBED_IDENTIFIERS ${IDENTIFIER} EMBED_USED_IDENTIFIERS_INDEX) if (NOT EMBED_USED_IDENTIFIERS_INDEX EQUAL -1) message(FATAL_ERROR "embed: Identifier already in use: '${IDENTIFIER}'") endif() - set(EMBED_IDENTIFIERS ${EMBED_IDENTIFIERS} ${IDENTIFIER} CACHE INTERNAL "list of all identifiers used by the embed library") - set(EMBED_FILENAMES ${EMBED_FILENAMES} ${FILENAME} CACHE INTERNAL "list of all filenames used by the embed library") - set(EMBED_TARGETS ${EMBED_TARGETS} ${TARGET} CACHE INTERNAL "list of all targets used by the embed library") - - # This action generates both files and is called on-demand whenever the resource file changes + # === Add identifier and file name === + set(EMBED_IDENTIFIERS ${EMBED_IDENTIFIERS} ${IDENTIFIER} CACHE INTERNAL "list of all identifiers used by the embed library" FORCE) + set(EMBED_FILENAMES ${EMBED_FILENAMES} ${FILENAME} CACHE INTERNAL "list of all filenames used by the embed library" FORCE) + set(EMBED_TARGETS ${EMBED_TARGETS} ${TARGET} CACHE INTERNAL "list of all targets used by the embed library" FORCE) + + # === Generate embed.hpp lazy === + _embed_generate_hpp(${TARGET}) + + # === Generate .cpp file for embed === set(EMBED_HPP "${EMBED_BINARY_DIR}/autogen/${TARGET_ID}/include/battery/embed.hpp") set(EMBED_CPP_TEMPLATE "${EMBED_BINARY_DIR}/embed_source_file_template.cpp") add_custom_command( - COMMAND ${CMAKE_COMMAND} - -DINFILE=${EMBED_CPP_TEMPLATE} - -DOUTFILE=${CPP_FILE} - -DFULL_PATH=${FULL_PATH} - -DIDENTIFIER=${IDENTIFIER} - -DFILENAME=${FILENAME} - -P "${EMBED_BINARY_DIR}/generate.cmake" - DEPENDS "${FULL_PATH}" "${EMBED_HPP}" "${EMBED_BINARY_DIR}/generate.cmake" "${EMBED_BINARY_DIR}/embed_source_file_template.cpp" - OUTPUT ${CPP_FILE} - VERBATIM + COMMAND ${CMAKE_COMMAND} + -DINFILE=${EMBED_CPP_TEMPLATE} + -DOUTFILE=${CPP_FILE} + -DFULL_PATH=${FULL_PATH} + -DIDENTIFIER=${IDENTIFIER} + -DFILENAME=${FILENAME} + -P "${EMBED_BINARY_DIR}/generate.cmake" + DEPENDS "${FULL_PATH}" "${EMBED_HPP}" "${EMBED_BINARY_DIR}/generate.cmake" "${EMBED_BINARY_DIR}/embed_source_file_template.cpp" + OUTPUT ${CPP_FILE} + VERBATIM ) - # Add the generated files to the target + # === Adding files to the target (embed.hpp now already exists!) === target_include_directories(${TARGET} PUBLIC $ $ ) - target_sources(${TARGET} PRIVATE ${CPP_FILE} ${EMBED_BINARY_DIR}/embed_impl.cpp ${EMBED_HPP}) + target_sources(${TARGET} PRIVATE ${CPP_FILE} ${EMBED_BINARY_DIR}/embed_impl.cpp) target_compile_definitions(${TARGET} PRIVATE _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING) if (NOT B_PRODUCTION_MODE) @@ -425,13 +422,28 @@ function(b_embed TARGET FILENAME) endif () if (MSVC) + # target_sources(${TARGET} PRIVATE ${FULL_PATH}) + # source_group(TREE ${EMBED_BINARY_DIR}/autogen/${TARGET_ID}/src PREFIX "embed/autogen" FILES ${CPP_FILE}) + # source_group(TREE ${EMBED_BINARY_DIR} PREFIX "embed/autogen" FILES ${EMBED_BINARY_DIR}/embed_impl.cpp) + # source_group(TREE ${EMBED_BINARY_DIR}/autogen/${TARGET_ID}/include/battery PREFIX "embed/autogen" FILES ${EMBED_HPP}) + # source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "/embed" FILES ${FULL_PATH}) + target_sources(${TARGET} PRIVATE ${FULL_PATH}) + set_source_files_properties(${FULL_PATH} PROPERTIES HEADER_FILE_ONLY TRUE) + source_group(TREE ${EMBED_BINARY_DIR}/autogen/${TARGET_ID}/src PREFIX "embed/autogen" FILES ${CPP_FILE}) source_group(TREE ${EMBED_BINARY_DIR} PREFIX "embed/autogen" FILES ${EMBED_BINARY_DIR}/embed_impl.cpp) source_group(TREE ${EMBED_BINARY_DIR}/autogen/${TARGET_ID}/include/battery PREFIX "embed/autogen" FILES ${EMBED_HPP}) - source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "/embed" FILES ${FULL_PATH}) - endif() + set(_embed_src_root "${CMAKE_CURRENT_SOURCE_DIR}") + cmake_path(IS_PREFIX _embed_src_root "${FULL_PATH}" NORMALIZE _is_subpath) + if (_is_subpath) + source_group(TREE "${_embed_src_root}" PREFIX "embed" FILES ${FULL_PATH}) + else() + source_group("embed" FILES ${FULL_PATH}) + endif() + + endif() endfunction() function(b_embed_proxy_target MAIN_TARGET PROXY_TARGET) diff --git a/README.md b/README.md index 25875e2..b42015f 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Embedding files into executables made easy. Single CMake file, Modern C++, a min ## What is this? -This is a CMake-based C++20 library that allows you to very easily embed resource files +This is a CMake-based C++17 library that allows you to very easily embed resource files into your executable. It is meant to be used for files such as icons, images, shader code, configuration files, etc.. You don't have to worry about distributing them with your application as the files are part of the application once compiled. It might also be an advantage that an end @@ -65,9 +65,9 @@ Currently, every file would need to be retrieved and written to disk manually. ## Requirements - CMake >=3.21 - - A C++20 compiler + - A C++17 compiler -This project requires C++20. If you are stuck with an older version, you can use [Release v1.0.0](https://github.com/batterycenter/embed/releases/tag/v1.0.0). It was made for C++14. +This project requires C++17. If you are stuck with an older version, you can use [Release v1.0.0](https://github.com/batterycenter/embed/releases/tag/v1.0.0). It was made for C++14. ## How it works @@ -199,7 +199,9 @@ src/main.cpp #include "battery/embed.hpp" int main() { - std::cout << b::embed<"resources/message.txt">() << std::endl; + for (auto filename : b::embed_list()) { + std::cout << "Embedded file: " << filename << "[" << b::embed(filename).size() << "]" << std::endl; + } return 0; } ``` @@ -222,11 +224,11 @@ That's it, now run it and see what happens! The returned object is a class that provides a number of functions and conversion operators for convenience. ```cpp -std::string file = b::embed<"resources/message.txt">().str(); -const char* pointer = b::embed<"resources/message.txt">().data(); -size_t length = b::embed<"resources/message.txt">().length(); -size_t size = b::embed<"resources/message.txt">().size(); -std::vector vec = b::embed<"resources/message.txt">().vec(); +std::string file = b::embed("resources/message.txt").str(); +const char* pointer = b::embed("resources/message.txt").data(); +size_t length = b::embed("resources/message.txt").length(); +size_t size = b::embed("resources/message.txt").size(); +std::vector vec = b::embed("resources/message.txt").vec(); ``` And all conversions also exist as operators, allowing for this: @@ -235,8 +237,8 @@ And all conversions also exist as operators, allowing for this: void foo_str(const std::string& test); void foo_vec(const std::vector& test); -foo_str(b::embed<"resources/message.txt">()); -foo_vec(b::embed<"resources/message.txt">()); +foo_str(b::embed("resources/message.txt")); +foo_vec(b::embed("resources/message.txt")); ``` ### Hot-reloading files @@ -257,7 +259,7 @@ int main() { // This lambda is called from another thread every time the file changes on-disk, // and in production mode, it is called immediately from the same thread, once. - b::embed<"assets/source.txt">().get([&file] (const auto& content) { + b::embed("assets/source.txt").get([&file] (const auto& content) { file = content.str(); std::cout << "File changed: " << file << std::endl; }); @@ -285,7 +287,7 @@ void test() { } // Or pass it along - auto icon = b::embed<"resources/message.txt">(); + auto icon = b::embed("resources/message.txt); old_c_style_function(icon.data(), icon.size()); } ``` @@ -328,7 +330,7 @@ b_embed(MyTarget-resources resource2.txt) CMakeLists.txt: ```cmake add_executable(MyTarget) -target_compile_features(MyTarget PUBLIC cxx_std_20) +target_compile_features(MyTarget PUBLIC cxx_std_17) add_subdirectory(resources) ```