Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
242 changes: 127 additions & 115 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand All @@ -51,6 +51,7 @@ set(EMBED_MASTER_SOURCE_FILE [==[
#include <mutex>
#include <unordered_map>
#include <optional>
#include <atomic>
#endif // !B_PRODUCTION_MODE and !B_OS_WEB

namespace b {
Expand Down Expand Up @@ -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);
}
}
Expand All @@ -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<const unsigned char*>(m_storage.c_str());
m_size = m_storage.size();
}
callback(*this);

Expand All @@ -184,110 +187,79 @@ set(EMBED_HEADER_FILE [=[
// DO NOT EDIT THIS FILE!!!
#ifndef BATTERY_EMBED_HPP
#define BATTERY_EMBED_HPP

#include <vector>
#include <string>
#include <string_view>
#include <stdexcept>
#include <sstream>
#include <functional>
#include <cstdint>
#include <algorithm>

#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<const char*>(m_data), m_size);
}

[[nodiscard]] const unsigned char* data() const { return m_data; }
[[nodiscard]] std::vector<uint8_t> 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<uint8_t>() const {
return vec();
}
operator std::string() const { return str(); }
operator std::vector<uint8_t>() const { return vec(); }

void get(const std::function<void(const b::EmbedInternal::EmbeddedFile&)>& 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<size_t N>
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<class S>
EmbedInternal::EmbeddedFile embed(const S& identifier) {
${EMBEDDED_FILES_RETURNS}
throw std::runtime_error("[b::embed<>] No such file or directory");
}

template<size_t N, size_t M>
constexpr bool operator==(const embed_string_literal<N>& 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<embed_string_literal identifier>
constexpr EmbedInternal::EmbeddedFile embed() {
${EMBEDDED_FILES_RETURNS}{
static_assert(identifier._false(), "[b::embed<>] No such file or directory");
}
// Функция для получения списка всех встроенных файлов
[[nodiscard]] inline std::vector<std::string> embed_list() {
return {
${EMBEDDED_FILES_LIST}
};
}

} // namespace b
Expand All @@ -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")
Expand All @@ -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
Expand All @@ -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
$<BUILD_INTERFACE:${EMBED_BINARY_DIR}/autogen/${TARGET_ID}/include>
$<INSTALL_INTERFACE:>
)
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)
Expand All @@ -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)
Expand Down
Loading