Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
323 changes: 9 additions & 314 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,312 +9,7 @@ option(B_EMBED_SILENCE_DEVMODE_WARNING "Disable the battery::embed devmode warni

# Remember the binary dir for later
set(EMBED_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/embed CACHE INTERNAL "binary directory of the battery::embed library" FORCE)

# The template for how to generate the .cpp file
set(EMBED_SOURCE_FILE_TEMPLATE [==[
// File generated using battery::embed (https://github.com/batterycenter/embed)
// Embedded file: '${FILENAME}' as '${IDENTIFIER}'
// Filesize: ${FILESIZE} bytes
// DO NOT EDIT THIS FILE!!!

#include "battery/embed.hpp"

namespace b {

EmbedInternal::EmbeddedFile EmbedInternal::${IDENTIFIER} = {
std::string_view(
${GENERATED_BYTE_ARRAY},
${FILESIZE}
)
, "${FILENAME}"
#ifndef B_PRODUCTION_MODE
, "${FULL_PATH}"
#endif // !B_PRODUCTION_MODE
};

} // namespace b
]==])
file(WRITE ${EMBED_BINARY_DIR}/embed_source_file_template.cpp "${EMBED_SOURCE_FILE_TEMPLATE}")

# The single b::embed master source file which is compiled into the app
set(EMBED_MASTER_SOURCE_FILE [==[
// File generated using battery::embed (https://github.com/batterycenter/embed)
// DO NOT EDIT THIS FILE!!!

#include "battery/embed.hpp"

#if !defined(B_PRODUCTION_MODE) && !defined(B_OS_WEB)
#include <codecvt>
#include <thread>
#include <filesystem>
#include <fstream>
#include <mutex>
#include <unordered_map>
#include <optional>
#endif // !B_PRODUCTION_MODE and !B_OS_WEB

namespace b {

#if !defined(B_PRODUCTION_MODE) && !defined(B_OS_WEB)

// All this crazyness is necessary because we need to use std::string_view because of constexpr,
// and the string_view only points to a char array, so we need a string that outlives
// the string_view it is assigned to.

struct FileData {
std::string filename;
std::string newContent;
std::string filepath;
std::filesystem::file_time_type lastWriteTime;
std::function<void(const b::EmbedInternal::EmbeddedFile&)> callback;
};

static std::mutex embeddedFilesMapMutex;
static std::unordered_map<std::string,FileData> embeddedFiles;

#ifdef _WIN32
#ifdef __clang__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif

static std::wstring embed_widen(const std::string& str) {
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
return converter.from_bytes(str);
}

#ifdef __clang__
#pragma GCC diagnostic pop
#endif

#endif // _WIN32

static std::optional<std::string> embed_read_file(const std::string_view& filename) {
#ifdef _WIN32
std::ifstream file(embed_widen(std::string(filename)).c_str(), std::ios::binary);
#else // _WIN32
std::ifstream file(std::string(filename), std::ios::binary);
#endif // _WIN32
if (file.fail()) {
return {};
}
return std::string((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
}

// Apple-Clang does not support std::jthread yet, hence we make our own version of jthread here:
class AutoThread {
public:
template<typename Tfunc>
AutoThread(Tfunc&& function)
: m_thread([&]() { function(m_shouldStop); })
{}

~AutoThread() {
m_shouldStop = true;
m_thread.join();
}

AutoThread(const AutoThread& other) = delete;
AutoThread(AutoThread&& other) = delete;
AutoThread& operator=(const AutoThread& other) = delete;
AutoThread& operator=(AutoThread&& other) = delete;

private:
std::thread m_thread;
std::atomic<bool> m_shouldStop = false;
};

// This is a global file watcher thread that is created at program start and automatically destroyed at program end
// All embedded files are hot-reloaded by this thread.
AutoThread embeddedFileWatcherThread = AutoThread([](std::atomic<bool>& shouldStop) {
while (!shouldStop) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::lock_guard<std::mutex> lock(embeddedFilesMapMutex);
for (auto& [key, filedata] : embeddedFiles) {
std::filesystem::path filepath(key);
std::error_code ec;
auto newWriteTime = std::filesystem::last_write_time(filepath, ec);
if (!ec) {
if (newWriteTime != filedata.lastWriteTime) {
filedata.lastWriteTime = newWriteTime;
auto fileresult = embed_read_file(key);
if (!fileresult) {
continue;
}
filedata.newContent = *fileresult;
EmbedInternal::EmbeddedFile newFile(filedata.newContent, filedata.filename, filedata.filepath);
filedata.callback(newFile);
}
}
}
}
});

#endif // !B_PRODUCTION_MODE and !B_OS_WEB

void EmbedInternal::EmbeddedFile::get(const std::function<void(const b::EmbedInternal::EmbeddedFile&)>& callback) {
#if defined(B_PRODUCTION_MODE) || defined(B_OS_WEB)
callback(*this);
#else // B_PRODUCTION_MODE or B_OS_WEB
auto fileresult = embed_read_file(m_fullFilepath);
if (fileresult) {
m_data = *fileresult;
}
callback(*this);

FileData fileData;
fileData.filename = m_filename;
fileData.newContent = m_data;
fileData.filepath = m_fullFilepath;
fileData.callback = callback;
fileData.lastWriteTime = std::filesystem::last_write_time(m_fullFilepath);

std::lock_guard<std::mutex> lock(embeddedFilesMapMutex);
embeddedFiles[std::string(m_fullFilepath)] = fileData;
#endif // B_PRODUCTION_MODE or B_OS_WEB
}

} // namespace b
]==])
file(WRITE ${EMBED_BINARY_DIR}/embed_impl.cpp "${EMBED_MASTER_SOURCE_FILE}")

# The common battery::embed header and source files
set(EMBED_HEADER_FILE [=[
// File generated by battery::embed
// 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;

constexpr EmbeddedFile(
const std::string_view& data,
const std::string_view& filename
#ifndef B_PRODUCTION_MODE
, const std::string_view& fullFilepath
#endif // !B_PRODUCTION_MODE
)
: m_data(data)
, m_filename(filename)
#ifndef B_PRODUCTION_MODE
, m_fullFilepath(fullFilepath)
#endif // !B_PRODUCTION_MODE
{}

[[nodiscard]] std::string str() const {
return m_data.data();
}

[[nodiscard]] const char* data() const {
return m_data.data();
}

[[nodiscard]] std::vector<uint8_t> vec() const {
return { m_data.begin(), m_data.end() };
}

[[nodiscard]] size_t length() const {
return m_data.size();
}

[[nodiscard]] size_t size() const {
return m_data.size();
}

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;
#ifndef B_PRODUCTION_MODE
std::string_view m_fullFilepath;
#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<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);
}

template<embed_string_literal identifier>
constexpr EmbedInternal::EmbeddedFile embed() {
${EMBEDDED_FILES_RETURNS}{
static_assert(identifier._false(), "[b::embed<>] No such file or directory");
}
}

} // namespace b

inline std::ostream& operator<<(std::ostream& stream, const b::EmbedInternal::EmbeddedFile& file) {
stream << file.str();
return stream;
}

#endif // BATTERY_EMBED_HPP
]=])
file(WRITE ${EMBED_BINARY_DIR}/embed_header_file_template.hpp "${EMBED_HEADER_FILE}")

# 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}\"")
configure_file(${INFILE} ${OUTFILE})
]=])
file(WRITE ${EMBED_BINARY_DIR}/generate.cmake ${EMBED_GENERATE_SCRIPT})
set(EMBED_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "source directory of the battery::embed library" FORCE)

set(EMBED_IDENTIFIERS "" CACHE INTERNAL "list of all identifiers used by battery::embed")
set(EMBED_FILENAMES "" CACHE INTERNAL "list of all filenames used by battery::embed")
Expand Down Expand Up @@ -353,10 +48,10 @@ function(_embed_generate_hpp TARGET)
set(EMBEDDED_FILES_RETURNS "${EMBEDDED_FILES_RETURNS}if constexpr (identifier == \"${FILENAME}\") { return EmbedInternal::${IDENTIFIER}; }\n else ")
endif()
endforeach()
file(READ ${EMBED_BINARY_DIR}/embed_header_file_template.hpp EMBED_HEADER_FILE)
string(CONFIGURE "${EMBED_HEADER_FILE}" EMBED_HEADER_FILE_GENERATED)
set(EMBED_HPP "${EMBED_BINARY_DIR}/autogen/${TARGET}/include/battery/embed.hpp")
file(WRITE "${EMBED_HPP}" "${EMBED_HEADER_FILE_GENERATED}")
configure_file(
"${EMBED_SOURCE_DIR}/script/embed_header_file_template.hpp"
"${EMBED_HPP}")
endfunction(_embed_generate_hpp)

# Internal function for validating an identifier
Expand Down Expand Up @@ -395,16 +90,16 @@ function(b_embed TARGET FILENAME)

# This action generates both files and is called on-demand whenever the resource file changes
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")
set(EMBED_CPP_TEMPLATE "${EMBED_SOURCE_DIR}/script/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"
-P "${EMBED_SOURCE_DIR}/script/generate.cmake"
DEPENDS "${FULL_PATH}" "${EMBED_HPP}" "${EMBED_SOURCE_DIR}/script/generate.cmake" "${EMBED_SOURCE_DIR}/script/embed_source_file_template.cpp"
OUTPUT ${CPP_FILE}
VERBATIM
)
Expand All @@ -414,7 +109,7 @@ function(b_embed TARGET FILENAME)
$<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_SOURCE_DIR}/script/embed_impl.cpp ${EMBED_HPP})
target_compile_definitions(${TARGET} PRIVATE _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING)

if (NOT B_PRODUCTION_MODE)
Expand All @@ -427,7 +122,7 @@ function(b_embed TARGET FILENAME)
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_SOURCE_DIR} PREFIX "embed/autogen" FILES ${EMBED_SOURCE_DIR}/script/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()
Expand Down
Loading