Skip to content

Commit 0eae9fc

Browse files
committed
Initial commit
0 parents  commit 0eae9fc

File tree

14 files changed

+377
-0
lines changed

14 files changed

+377
-0
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.cache
2+
.vscode
3+
build

README.md

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# CMake Resource Library
2+
3+
Embed a whole directory tree into a C++ binary. Usage in CMakeLists.txt:
4+
5+
```cmake
6+
# Add the resource library
7+
include("PATH_TO_CMAKE_RESOURCE_LIBRARY/resource_library.cmake")
8+
add_resource_library(assets "${ASSETS_BASE_DIR}")
9+
10+
# Use it wherever it's needed
11+
add_executable(example "main.cpp")
12+
target_link_libraries(example PRIVATE assets)
13+
```
14+
15+
This creates a cmake library which contains all the files contained in the provided directory which then can be used from C++ code.
16+
17+
```c++
18+
#include <assets.h>
19+
20+
int main() {
21+
// Runtime access (file might not exist)
22+
std::optional<crl::FileEntry&> ok_icon = assets::get("icons/ok.png");
23+
std::span<const std::byte> content = ok_icon.content;
24+
25+
// Static access (compiler checks the file is available)
26+
crl::FileEntry& ok_icon = assets::icons::ok_png;
27+
crl::FileEntry& ok_icon = assets::get_static("icons/ok.png");
28+
}
29+
```
30+
31+
TODO: Can the static variant be done with string literals and constexpr?
32+

example/CMakeLists.txt

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
cmake_minimum_required(VERSION 3.16)
2+
3+
project(srm-example ASM CXX)
4+
5+
set(CMAKE_CXX_STANDARD 20)
6+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
7+
8+
set(CRL_ROOT "${CMAKE_SOURCE_DIR}/../lib")
9+
add_subdirectory("${CRL_ROOT}/crl" crl)
10+
include("${CRL_ROOT}/crl/cmake/resource_library.cmake")
11+
add_resource_library(assets "${CMAKE_SOURCE_DIR}/assets/")
12+
13+
add_executable(example "main.cpp")
14+
target_link_libraries(example PRIVATE assets)

example/assets/favicon.png

67 Bytes
Loading

example/assets/icons/cross.png

67 Bytes
Loading
107 Bytes
Loading

example/assets/index.html

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<html>
2+
3+
</html>

example/assets/logs/source/t.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"asd"''/asd\

example/main.cpp

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#include <assets.h>
2+
#include <crl.h>
3+
#include <iostream>
4+
5+
int main() {
6+
std::optional<std::span<const std::byte>> f =
7+
assets::get_file("logs/source/t.txt");
8+
if (f.has_value()) {
9+
std::cout << "Found file: "
10+
<< std::string_view{reinterpret_cast<const char *>(
11+
f.value().data()),
12+
f.value().size()}
13+
<< std::endl;
14+
} else {
15+
std::cout << "File not found" << std::endl;
16+
}
17+
}

lib/crl/CMakeLists.txt

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
add_library(crl
2+
"src/crl.cpp"
3+
)
4+
5+
target_include_directories(crl PUBLIC include)

lib/crl/cmake/generate.cmake

+216
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
cmake_minimum_required(VERSION 3.14)
2+
3+
function(write_header target_name target_path)
4+
file(WRITE "${target_path}"
5+
"// Generated by crl
6+
#pragma once
7+
8+
#include <crl.h>
9+
10+
#include <string_view>
11+
12+
namespace ${target_name} {
13+
14+
const crl::DirectoryEntry& root();
15+
16+
std::optional<std::span<const std::byte>> get_file(std::string_view path);
17+
constexpr std::span<const std::byte> get_file_static(std::string_view path);
18+
19+
} // namespace ${target_name}
20+
"
21+
)
22+
endfunction()
23+
24+
function(write_cpp_intro target_path)
25+
file(WRITE "${target_path}"
26+
"// Generated by crl
27+
#include <array>
28+
#include <cstddef>
29+
#include <crl.h>
30+
31+
using crl::DirectoryEntry;
32+
using crl::FileEntry;
33+
34+
template<typename... Ts>
35+
constexpr std::array<std::byte, sizeof...(Ts)> make_bytes(Ts&&... args) noexcept {
36+
return{std::byte(std::forward<Ts>(args))...};
37+
}
38+
39+
")
40+
endfunction()
41+
42+
function(write_cpp_outro target_name target_path)
43+
file(APPEND "${target_path}"
44+
"namespace ${target_name} {
45+
46+
const crl::DirectoryEntry& root() {
47+
return directory_root;
48+
}
49+
50+
std::optional<std::span<const std::byte>> get_file(std::string_view path) {
51+
return crl::get_file(root(), path);
52+
}
53+
54+
constexpr std::span<const std::byte> get_file_static(std::string_view path) {
55+
if (path == \"logs/source/t.txt\") {
56+
return file_0_data;
57+
}
58+
return file_0_data;
59+
// static_assert(false);
60+
}
61+
62+
} // namespace ${target_name}
63+
")
64+
endfunction()
65+
66+
function(write_data_array input_path variable_name target_path)
67+
file(READ "${input_path}" file_content HEX)
68+
string(LENGTH "${file_content}" data_length_hex)
69+
math(EXPR data_length_bytes "${data_length_hex} / 2")
70+
file(APPEND "${target_path}" "constexpr std::array<std::byte, ${data_length_bytes}> ${variable_name} = make_bytes(")
71+
72+
if(data_length_bytes GREATER 0)
73+
if(data_length_bytes GREATER 1)
74+
math(EXPR data_length_bytes_minus_two "${data_length_bytes} - 2")
75+
foreach(i RANGE 0 ${data_length_bytes_minus_two})
76+
math(EXPR index "2 * ${i}")
77+
string(SUBSTRING "${file_content}" ${index} 2 BYTE_HEX)
78+
file(APPEND "${target_path}" "0x${BYTE_HEX},")
79+
endforeach()
80+
endif()
81+
math(EXPR index "2 * ${data_length_bytes} - 2")
82+
string(SUBSTRING "${file_content}" ${index} 2 BYTE_HEX)
83+
file(APPEND "${target_path}" "0x${BYTE_HEX}")
84+
endif()
85+
file(APPEND "${target_path}" ");\n")
86+
endfunction()
87+
88+
function(write_file_entry file_index file_path target_path)
89+
cmake_path(GET file_path FILENAME file_name)
90+
file(APPEND
91+
"${target_path}"
92+
"constexpr FileEntry file_${file_index} = { \"${file_name}\", file_${file_index}_data };\n\n"
93+
)
94+
endfunction()
95+
96+
function(write_file_entries base_dir resource_files target_path)
97+
set(index 0)
98+
foreach(resource_file IN LISTS resource_files)
99+
set(absolute_resource_file "${base_dir}/${resource_file}")
100+
message(STATUS "Embed ${resource_file} into ${target_path}")
101+
write_data_array("${absolute_resource_file}" "file_${index}_data" "${target_path}")
102+
write_file_entry("${index}" "${resource_file}" "${target_path}")
103+
math(EXPR index "${index} + 1")
104+
endforeach()
105+
endfunction()
106+
107+
108+
function (add_directory_to_directory_list candidate directory_list out)
109+
list(FIND directory_list "${candidate}" pos)
110+
if("${candidate}" IN_LIST directory_list)
111+
else()
112+
list(APPEND directory_list "${candidate}")
113+
set(${out} "${directory_list}" PARENT_SCOPE)
114+
endif()
115+
endfunction()
116+
117+
function (expand_directories_for_single_file relative_file_path directory_list out)
118+
get_filename_component(dir_path "${relative_file_path}" DIRECTORY)
119+
if("${dir_path}" STREQUAL "${relative_file_path}")
120+
message("Skip file ${dir_path}")
121+
return()
122+
endif()
123+
string(REPLACE "/" ";" dir_part_list "${dir_path}")
124+
foreach(dir_part IN LISTS dir_part_list)
125+
set(dir "${dir}/${dir_part}")
126+
add_directory_to_directory_list("${dir}" "${directory_list}" x)
127+
set(directory_list "${x}")
128+
endforeach()
129+
set(${out} "${directory_list}" PARENT_SCOPE)
130+
endfunction()
131+
132+
function (expand_directories relative_file_list out)
133+
foreach(relative_file IN LISTS relative_file_list)
134+
expand_directories_for_single_file("${relative_file}" "${directory_list}" x)
135+
set(directory_list "${x}")
136+
endforeach()
137+
set(${out} "${directory_list}" PARENT_SCOPE)
138+
endfunction()
139+
140+
function(find_children dir expanded_dirs out)
141+
foreach(candidate IN LISTS "${expanded_dirs}")
142+
cmake_path(GET candidate PARENT_PATH x)
143+
if ("${x}" STREQUAL "${dir}")
144+
list(APPEND result ${candidate})
145+
endif()
146+
endforeach()
147+
set(${out} ${result} PARENT_SCOPE)
148+
endfunction()
149+
150+
function(write_directory_subdirectories dir dir_index subdirectories resource_directories target_file)
151+
list(LENGTH subdirectories subdirs_length)
152+
file(APPEND "${target_file}" "constexpr std::array<const DirectoryEntry*, ${subdirs_length}> directory_${dir_index}_subdirectories = {")
153+
foreach(subdir IN LISTS subdirectories)
154+
list(FIND resource_directories "${subdir}" index)
155+
if (${index} EQUAL -1)
156+
message(FATAL_ERROR "Internal error: directory '${subdir}' is not part of the resource directories: '${resource_directories}'")
157+
endif()
158+
file(APPEND "${target_file}" "&directory_${index},")
159+
endforeach()
160+
file(APPEND "${target_file}" "};\n")
161+
endfunction()
162+
163+
function(write_directory_subfiles dir dir_index subfiles resource_files target_file)
164+
list(LENGTH subfiles subfiles_length)
165+
file(APPEND "${target_file}" "constexpr std::array<const FileEntry*, ${subfiles_length}> directory_${dir_index}_subfiles = {")
166+
foreach(subfile IN LISTS subfiles)
167+
list(FIND resource_files "${subfile}" index)
168+
if (${index} EQUAL -1)
169+
message(FATAL_ERROR "Internal error: file '${subfile}' is not part of the resource files: '${resource_files}'")
170+
endif()
171+
file(APPEND "${target_file}" "&file_${index},")
172+
endforeach()
173+
file(APPEND "${target_file}" "};\n")
174+
endfunction()
175+
176+
function(write_directory_entries resource_files resource_directories target_file)
177+
list(REVERSE resource_directories)
178+
foreach(dir IN LISTS resource_directories)
179+
list(FIND resource_directories "${dir}" dir_index)
180+
if (${dir_index} EQUAL -1)
181+
message(FATAL_ERROR "Internal error: directory '${dir}' is not part of the resource directories: '${resource_directories}'")
182+
endif()
183+
find_children("${dir}" resource_directories subdirs)
184+
STRING(REGEX REPLACE "^/" "" dir_without_leading_slash "${dir}")
185+
find_children("${dir_without_leading_slash}" resource_files subfiles)
186+
write_directory_subdirectories("${dir}" "${dir_index}" "${subdirs}" "${resource_directories}" "${target_file}")
187+
write_directory_subfiles("${dir}" "${dir_index}" "${subfiles}" "${resource_files}" "${target_file}")
188+
cmake_path(GET dir FILENAME dir_name)
189+
file(APPEND "${target_file}" "constexpr DirectoryEntry directory_${dir_index} = { \"${dir_name}\", directory_${dir_index}_subdirectories, directory_${dir_index}_subfiles };\n\n")
190+
endforeach()
191+
find_children("/" resource_directories subdirs)
192+
find_children("" resource_files subfiles)
193+
write_directory_subdirectories("${dir}" "root" "${subdirs}" "${resource_directories}" "${target_file}")
194+
write_directory_subfiles("${dir}" "root" "${subfiles}" "${resource_files}" "${target_file}")
195+
file(APPEND "${target_file}" "constexpr DirectoryEntry directory_root = { \"\", directory_root_subdirectories, directory_root_subfiles };\n\n")
196+
endfunction()
197+
198+
function(main)
199+
if(NOT IS_DIRECTORY "${SOURCE_DIR}")
200+
message(FATAL_ERROR "The source directory '${SOURCE_DIR}' provided for the resource library does not exist.")
201+
endif()
202+
file(GLOB_RECURSE resource_files RELATIVE "${SOURCE_DIR}" "${SOURCE_DIR}/*")
203+
expand_directories("${resource_files}" resource_directories)
204+
205+
set(output_cpp_file "src/${TARGET_NAME}.cpp")
206+
set(output_h_file "include/${TARGET_NAME}.h")
207+
208+
write_cpp_intro("${output_cpp_file}")
209+
write_file_entries("${SOURCE_DIR}" "${resource_files}" "${output_cpp_file}")
210+
write_directory_entries("${resource_files}" "${resource_directories}" "${output_cpp_file}")
211+
write_cpp_outro("${TARGET_NAME}" "${output_cpp_file}")
212+
213+
write_header("${TARGET_NAME}" "${output_h_file}")
214+
endfunction()
215+
216+
main()

lib/crl/cmake/resource_library.cmake

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
cmake_minimum_required(VERSION 3.14)
2+
3+
set(SRM_CMAKE_DIR ${CMAKE_CURRENT_LIST_DIR})
4+
5+
function(add_resource_library TARGET_NAME SOURCE_DIR)
6+
get_filename_component(SOURCE_DIR "${SOURCE_DIR}" ABSOLUTE)
7+
8+
# Collect all files to rebuild when something changes
9+
file(GLOB_RECURSE RESOURCE_FILES CONFIGURE_DEPENDS "${SOURCE_DIR}/*")
10+
11+
set(output_cpp_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}/src/${TARGET_NAME}.cpp")
12+
set(output_h_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}/include/${TARGET_NAME}.h")
13+
add_custom_command(
14+
OUTPUT "${output_cpp_file}" "${output_h_file}"
15+
COMMAND ${CMAKE_COMMAND} -D TARGET_NAME=${TARGET_NAME}
16+
-D SOURCE_DIR=${SOURCE_DIR}
17+
-P "${SRM_CMAKE_DIR}/generate.cmake"
18+
DEPENDS ${RESOURCE_FILES} "${SRM_CMAKE_DIR}/generate.cmake"
19+
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}"
20+
COMMENT "Creating resource library for ${TARGET_NAME} from ${SOURCE_DIR}"
21+
VERBATIM
22+
)
23+
add_custom_target(${TARGET_NAME}_gen_target DEPENDS "${output_cpp_file}")
24+
25+
add_library(${TARGET_NAME} STATIC "${output_cpp_file}")
26+
add_dependencies(${TARGET_NAME} ${TARGET_NAME}_gen_target)
27+
target_include_directories(${TARGET_NAME} PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}/include")
28+
target_link_libraries(${TARGET_NAME} PUBLIC crl)
29+
endfunction()

lib/crl/include/crl.h

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#pragma once
2+
3+
#include <optional>
4+
#include <span>
5+
#include <string_view>
6+
7+
namespace crl {
8+
9+
struct FileEntry {
10+
std::string_view name;
11+
std::span<const std::byte> data;
12+
};
13+
14+
struct DirectoryEntry {
15+
std::string_view name;
16+
std::span<const DirectoryEntry *const> subdirectories;
17+
std::span<const FileEntry *const> files;
18+
};
19+
20+
std::optional<std::span<const std::byte>>
21+
get_file(const crl::DirectoryEntry &directory, std::string_view path);
22+
23+
} // namespace crl

lib/crl/src/crl.cpp

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#include <crl.h>
2+
3+
namespace crl {
4+
5+
std::optional<std::span<const std::byte>>
6+
get_file(const crl::DirectoryEntry &directory, std::string_view path) {
7+
const crl::DirectoryEntry *d = &directory;
8+
while (true) {
9+
auto pos = path.find('/');
10+
if (pos == std::string_view::npos) {
11+
for (const auto &f : d->files) {
12+
if (f->name == path) {
13+
return f->data;
14+
}
15+
}
16+
return {};
17+
}
18+
std::string_view sub_d_name = path.substr(0, pos);
19+
bool found_match = false;
20+
for (const auto &sub_d : d->subdirectories) {
21+
if (sub_d->name == sub_d_name) {
22+
path = path.substr(pos + 1);
23+
d = sub_d;
24+
found_match = true;
25+
break;
26+
}
27+
}
28+
if (!found_match) {
29+
return {};
30+
}
31+
}
32+
}
33+
34+
} // namespace crl

0 commit comments

Comments
 (0)