Skip to content

Commit 4437ab9

Browse files
Init
0 parents  commit 4437ab9

7 files changed

+301
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
build/
2+
.vscode/.cmaketools.json

CMakeLists.txt

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
cmake_minimum_required(VERSION 3.6)
2+
project(CMRCExample)
3+
4+
include(CMakeToolsHelpers)
5+
include(CMakeRC.cmake)
6+
7+
cmrc_add_resource_library(hello hello.txt)
8+
add_executable(main main.cpp)
9+
target_link_libraries(main PRIVATE hello)
10+
11+
include(CTest)
12+
enable_testing()
13+
add_test(main main)
14+
set_property(TEST main PROPERTY PASS_REGULAR_EXPRESSION "Hello, world!")
15+
16+
add_executable(flower flower.cpp)
17+
target_link_libraries(flower PRIVATE hello)
18+
add_test(flower flower ${CMAKE_CURRENT_SOURCE_DIR}/flower.jpg)
19+
cmrc_add_resources(hello flower.jpg)

CMakeRC.cmake

+234
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
if(_CMRC_GENERATE_MODE)
2+
# Read in the digits
3+
file(READ "${INPUT_FILE}" bytes HEX)
4+
# Format each pair into a character literal
5+
string(REGEX REPLACE "(..)" "'\\\\x\\1', " chars "${bytes}")
6+
file(WRITE "${OUTPUT_FILE}" "
7+
namespace { const char file_array[] = { ${chars} }; }
8+
namespace cmrc { namespace ${LIBRARY} { namespace res_chars {
9+
extern const char* const ${SYMBOL}_begin = file_array;
10+
extern const char* const ${SYMBOL}_end = file_array + sizeof(file_array);
11+
}}}
12+
")
13+
return()
14+
endif()
15+
16+
if(COMMAND cmrc_add_resource_library)
17+
# CMakeRC has already been included! Don't do anything
18+
return()
19+
endif()
20+
21+
set(this_script "${CMAKE_CURRENT_LIST_FILE}")
22+
23+
set(CMRC_INCLUDE_DIR "${CMAKE_BINARY_DIR}/_cmrc/include" CACHE INTERNAL "Directory for CMakeRC include files")
24+
# Let's generate the primary include file
25+
file(MAKE_DIRECTORY "${CMRC_INCLUDE_DIR}/cmrc")
26+
file(WRITE "${CMRC_INCLUDE_DIR}/cmrc/cmrc.hpp" [==[
27+
#ifndef CMRC_CMRC_HPP_INCLUDED
28+
#define CMRC_CMRC_HPP_INCLUDED
29+
30+
#include <string>
31+
#include <map>
32+
#include <mutex>
33+
34+
#define CMRC_INIT(libname) \
35+
do { \
36+
extern void cmrc_init_resources_##libname(); \
37+
cmrc_init_resources_##libname(); \
38+
} while (0)
39+
40+
namespace cmrc {
41+
42+
class resource {
43+
const char* _begin = nullptr;
44+
const char* _end = nullptr;
45+
public:
46+
const char* begin() const { return _begin; }
47+
const char* end() const { return _end; }
48+
49+
resource() = default;
50+
resource(const char* beg, const char* end) : _begin(beg), _end(end) {}
51+
};
52+
53+
using resource_table = std::map<std::string, resource>;
54+
55+
namespace detail {
56+
57+
inline resource_table& table_instance() {
58+
static resource_table table;
59+
return table;
60+
}
61+
62+
inline std::mutex& table_instance_mutex() {
63+
static std::mutex mut;
64+
return mut;
65+
}
66+
67+
// We restrict access to the resource table through a mutex so that multiple
68+
// threads can access it safely.
69+
template <typename Func>
70+
inline auto with_table(Func fn) -> decltype(fn(std::declval<resource_table&>())) {
71+
std::lock_guard<std::mutex> lk{ table_instance_mutex() };
72+
return fn(table_instance());
73+
}
74+
75+
}
76+
77+
inline resource open(const char* fname) {
78+
return detail::with_table([fname](const resource_table& table) {
79+
auto iter = table.find(fname);
80+
if (iter == table.end()) {
81+
return resource {};
82+
}
83+
return iter->second;
84+
});
85+
}
86+
87+
inline resource open(const std::string& fname) {
88+
return open(fname.data());
89+
}
90+
91+
}
92+
93+
#endif // CMRC_CMRC_HPP_INCLUDED
94+
]==])
95+
96+
add_library(cmrc-base INTERFACE)
97+
target_include_directories(cmrc-base INTERFACE "${CMRC_INCLUDE_DIR}")
98+
target_compile_features(cmrc-base INTERFACE cxx_nullptr)
99+
set_property(TARGET cmrc-base PROPERTY INTERFACE_CXX_EXTENSIONS OFF)
100+
add_library(cmrc::base ALIAS cmrc-base)
101+
102+
function(cmrc_add_resource_library name)
103+
# Generate the identifier for the resource library's namespace
104+
string(MAKE_C_IDENTIFIER "${name}" libident)
105+
# Generate a library with the compiled in character arrays.
106+
set(cpp_content [=[
107+
#include <cmrc/cmrc.hpp>
108+
#include <map>
109+
110+
namespace cmrc { namespace %{libident} {
111+
112+
namespace res_chars {
113+
// These are the files which are available in this resource library
114+
$<JOIN:$<TARGET_PROPERTY:%{libname},CMRC_EXTERN_DECLS>,
115+
>
116+
}
117+
118+
inline void load_resources() {
119+
// This initializes the list of resources and pointers to their data
120+
static std::once_flag flag;
121+
std::call_once(flag, [] {
122+
cmrc::detail::with_table([](resource_table& table) {
123+
$<JOIN:$<TARGET_PROPERTY:%{libname},CMRC_TABLE_POPULATE>,
124+
>
125+
});
126+
});
127+
}
128+
129+
namespace {
130+
extern struct resource_initializer {
131+
resource_initializer() {
132+
load_resources();
133+
}
134+
} dummy;
135+
}
136+
137+
}}
138+
139+
// The resource library initialization function. Intended to be called
140+
// before anyone intends to use any of the resource defined by this
141+
// resource library
142+
extern void cmrc_init_resources_%{libident}() {
143+
cmrc::%{libident}::load_resources();
144+
}
145+
]=])
146+
get_filename_component(libdir "${CMAKE_CURRENT_BINARY_DIR}/${name}" ABSOLUTE)
147+
get_filename_component(libcpp "${libdir}/lib.cpp" ABSOLUTE)
148+
string(REPLACE "%{libname}" "${name}" cpp_content "${cpp_content}")
149+
string(REPLACE "%{libident}" "${libident}" cpp_content "${cpp_content}")
150+
string(REPLACE "\n " "\n" cpp_content "${cpp_content}")
151+
file(GENERATE OUTPUT "${libcpp}" CONTENT "${cpp_content}")
152+
# Generate the actual static library. Each source file is just a single file
153+
# with a character array compiled in containing the contents of the
154+
# corresponding resource file.
155+
add_library(${name} STATIC ${libcpp})
156+
target_link_libraries(${name} PUBLIC cmrc::base)
157+
set_property(TARGET ${name} PROPERTY CMRC_IS_RESOURCE_LIBRARY TRUE)
158+
cmrc_add_resources(${name} ${ARGN})
159+
endfunction()
160+
161+
function(cmrc_add_resources name)
162+
get_target_property(is_reslib ${name} CMRC_IS_RESOURCE_LIBRARY)
163+
if(NOT TARGET ${name} OR NOT is_reslib)
164+
message(SEND_ERROR "cmrc_add_resources called on target '${name}' which is not an existing resource library")
165+
return()
166+
endif()
167+
168+
set(options)
169+
set(args WHENCE PREFIX)
170+
set(list_args)
171+
cmake_parse_arguments(PARSE_ARGV 1 ARG "${options}" "${args}" "${list_args}")
172+
173+
if(NOT ARG_WHENCE)
174+
set(ARG_WHENCE ${CMAKE_CURRENT_SOURCE_DIR})
175+
endif()
176+
177+
# Generate the identifier for the resource library's namespace
178+
string(MAKE_C_IDENTIFIER "${name}" libident)
179+
180+
foreach(input IN LISTS ARG_UNPARSED_ARGUMENTS)
181+
get_filename_component(abs_input "${input}" ABSOLUTE)
182+
# Generate a filename based on the input filename that we can put in
183+
# the intermediate directory.
184+
file(RELATIVE_PATH relpath "${ARG_WHENCE}" "${abs_input}")
185+
if(relpath MATCHES "^\\.\\.")
186+
# For now we just error on files that exist outside of the soure dir.
187+
message(SEND_ERROR "Cannot add file '${input}': File must be in a subdirectory of ${ARG_WHENCE}")
188+
continue()
189+
endif()
190+
get_filename_component(abspath "${libdir}/intermediate/${relpath}.cpp" ABSOLUTE)
191+
# Generate a symbol name relpath the file's character array
192+
_cm_encode_fpath(sym "${relpath}")
193+
# Generate the rule for the intermediate source file
194+
_cmrc_generate_intermediate_cpp(${libident} ${sym} "${abspath}" "${abs_input}")
195+
target_sources(${name} PRIVATE ${abspath})
196+
set_property(TARGET ${name} APPEND PROPERTY CMRC_EXTERN_DECLS
197+
"// Pointers to ${input}"
198+
"extern const char* const ${sym}_begin\;"
199+
"extern const char* const ${sym}_end\;"
200+
)
201+
if(ARG_PREFIX AND NOT ARG_PREFIX MATCHES "/$")
202+
set(ARG_PREFIX "${ARG_PREFIX}/")
203+
endif()
204+
set_property(TARGET ${name} APPEND PROPERTY CMRC_TABLE_POPULATE
205+
"// Table entry for ${input}"
206+
"table.emplace(\"${ARG_PREFIX}${relpath}\", resource{res_chars::${sym}_begin, res_chars::${sym}_end})\;"
207+
)
208+
endforeach()
209+
endfunction()
210+
211+
function(_cmrc_generate_intermediate_cpp libname symbol outfile infile)
212+
add_custom_command(
213+
# This is the file we will generate
214+
OUTPUT "${outfile}"
215+
# These are the primary files that affect the output
216+
DEPENDS "${infile}" "${this_script}"
217+
COMMAND
218+
"${CMAKE_COMMAND}"
219+
-D_CMRC_GENERATE_MODE=TRUE
220+
-DLIBRARY=${libname}
221+
-DSYMBOL=${symbol}
222+
"-DINPUT_FILE=${infile}"
223+
"-DOUTPUT_FILE=${outfile}"
224+
-P "${this_script}"
225+
COMMENT "Generating intermediate file for ${infile}"
226+
)
227+
endfunction()
228+
229+
function(_cm_encode_fpath var fpath)
230+
string(MAKE_C_IDENTIFIER "${fpath}" ident)
231+
string(MD5 hash "${fpath}")
232+
string(SUBSTRING "${hash}" 0 4 hash)
233+
set(${var} f_${hash}_${ident} PARENT_SCOPE)
234+
endfunction()

flower.cpp

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#include <cmrc/cmrc.hpp>
2+
3+
#include <algorithm>
4+
#include <fstream>
5+
#include <iterator>
6+
#include <iostream>
7+
8+
9+
int main(int argc, char** argv) {
10+
if (argc != 2) {
11+
std::cerr << "Invalid arguments passed to flower\n";
12+
return 2;
13+
}
14+
std::cout << "Reading flower from " << argv[1] << '\n';
15+
std::ifstream flower_fs{ argv[1], std::ios_base::binary };
16+
if (!flower_fs) {
17+
std::cerr << "Invalid filename passed to flower: " << argv[1] << '\n';
18+
return 2;
19+
}
20+
21+
using iter = std::istreambuf_iterator<char>;
22+
const auto fs_size = std::distance(iter(flower_fs), iter());
23+
flower_fs.seekg(0);
24+
25+
CMRC_INIT(hello);
26+
auto flower_rc = cmrc::open("flower.jpg");
27+
const auto rc_size = std::distance(flower_rc.begin(), flower_rc.end());
28+
if (rc_size != fs_size) {
29+
std::cerr << "Flower file sizes do not match: FS == " << fs_size << ", RC == " << rc_size << "\n";
30+
return 1;
31+
}
32+
if (!std::equal(flower_rc.begin(), flower_rc.end(), iter(flower_fs))) {
33+
std::cerr << "Flower file contents do not match\n";
34+
return 1;
35+
}
36+
}

flower.jpg

2.47 MB
Loading

hello.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Hello, world!

main.cpp

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#include <cmrc/cmrc.hpp>
2+
3+
#include <iostream>
4+
5+
int main() {
6+
CMRC_INIT(hello);
7+
auto data = cmrc::open("hello.txt");
8+
std::cout << std::string(data.begin(), data.end()) << '\n';
9+
}

0 commit comments

Comments
 (0)