diff --git a/cmake/usFunctionAddResources.cmake b/cmake/usFunctionAddResources.cmake index b2c7b46784..12717feada 100644 --- a/cmake/usFunctionAddResources.cmake +++ b/cmake/usFunctionAddResources.cmake @@ -1,136 +1,149 @@ #! \ingroup MicroServicesCMake #! \brief Add resources to a library or executable. #! #! This CMake function uses an external command line program to generate a ZIP archive #! containing data from external resources such as text files or images or other ZIP #! archives. The created archive file is appended as a binary blob to the target file. #! #! Each module can call this function to add resources and make them available at #! runtime through the Module class. Multiple calls to this function append the #! input files to the target file. #! #! \note To set-up correct file dependencies from your module target to your resource #! files, you have to add a file named \e $_resources.cpp -#! to the source list of the target. This ensures that changed resource files -#! will automatically be re-added to the module. +#! to the source list of the target (or the value provided to the SOURCE_OUTPUT +#! parameter). This ensures that changed resource files will automatically be +#! re-added to the module. #! #! In the case of linking static modules which contain resources to the target module, #! adding the static module target name to the ZIP_ARCHIVES list will merge its #! resources into the target module. #! #! Example usage: #! \code{.cmake} #! set(module_srcs ) #! usFunctionAddResources(TARGET mylib #! MODULE_NAME org_me_mylib #! FILES config.properties logo.png #! ) #! \endcode #! #! \param TARGET (required) The target to which the resource files are added. #! \param MODULE_NAME (required/optional) The module name of the target, as specified in #! the \c US_MODULE_NAME pre-processor definition of that target. This parameter #! is optional if a target property with the name US_MODULE_NAME exists, containing #! the required module name. +#! \param SOURCE_OUTPUT (optional) The name for a generated source which can be included in +#! the source list of the TARGET to set-up resource dependencies. If empty, the value +#! defautls to ${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_resources.cpp. If a relative path +#! is given, it will be appended to the current binary directory. Setting this parameter +#! to a non-default value for repeated calls of this macro for the same target is +#! recommended. #! \param COMPRESSION_LEVEL (optional) The zip compression level (0-9). Defaults to the default zip #! level. Level 0 disables compression. #! \param WORKING_DIRECTORY (optional) The root path for all resource files listed after the #! FILES argument. If no or a relative path is given, it is considered relativ to the #! current CMake source directory. #! \param FILES (optional) A list of resource files (paths to external files in the file system) #! relative to the current working directory. #! \param ZIP_ARCHIVES (optional) A list of zip archives (relative to the current working directory #! or absolute file paths) whose contents is merged into the target module. If a list entry #! is a valid target name and that target is a static library, its absolute file path is #! used instead. #! function(usFunctionAddResources) - cmake_parse_arguments(US_RESOURCE "" "TARGET;MODULE_NAME;WORKING_DIRECTORY;COMPRESSION_LEVEL" "FILES;ZIP_ARCHIVES" ${ARGN}) + cmake_parse_arguments(US_RESOURCE "" "TARGET;MODULE_NAME;SOURCE_OUTPUT;WORKING_DIRECTORY;COMPRESSION_LEVEL" "FILES;ZIP_ARCHIVES" ${ARGN}) if(NOT US_RESOURCE_TARGET) message(SEND_ERROR "TARGET argument not specified.") endif() if(NOT US_RESOURCE_MODULE_NAME) get_target_property(US_RESOURCE_MODULE_NAME ${US_RESOURCE_TARGET} US_MODULE_NAME) if(NOT US_RESOURCE_MODULE_NAME) message(SEND_ERROR "Either the MODULE_NAME argument or the US_MODULE_NAME target property is required.") endif() endif() + if(NOT US_RESOURCE_SOURCE_OUTPUT) + set(US_RESOURCE_SOURCE_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${US_RESOURCE_TARGET}_resources.cpp") + elseif(NOT IS_ABSOLUTE ${US_RESOURCE_SOURCE_OUTPUT}) + set(US_RESOURCE_SOURCE_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${US_RESOURCE_SOURCE_OUTPUT}") + endif() + if(NOT US_RESOURCE_FILES AND NOT US_RESOURCE_ZIP_ARCHIVES) message(WARNING "No resources specified. Skipping resource processing.") return() endif() if(NOT US_RESOURCE_WORKING_DIRECTORY) set(US_RESOURCE_WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) endif() if(NOT IS_ABSOLUTE ${US_RESOURCE_WORKING_DIRECTORY}) set(US_RESOURCE_WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${US_RESOURCE_WORKING_DIRECTORY}") endif() if(US_RESOURCE_COMPRESSION_LEVEL) set(cmd_line_args -${US_RESOURCE_COMPRESSION_LEVEL}) endif() set(resource_compiler ${US_RCC_EXECUTABLE}) if(TARGET ${US_RCC_EXECUTABLE_NAME}) set(resource_compiler ${US_RCC_EXECUTABLE_NAME}) elseif(NOT resource_compiler) message(FATAL_ERROR "The CppMicroServices resource compiler was not found. Check the US_RCC_EXECUTABLE CMake variable.") endif() set(_cmd_deps ) foreach(_file ${US_RESOURCE_FILES}) if(IS_ABSOLUTE ${_file}) list(APPEND _cmd_deps ${_file}) else() list(APPEND _cmd_deps ${US_RESOURCE_WORKING_DIRECTORY}/${_file}) endif() endforeach() set(_zip_args ) if(US_RESOURCE_ZIP_ARCHIVES) foreach(_zip_archive ${US_RESOURCE_ZIP_ARCHIVES}) if(TARGET ${_zip_archive}) get_target_property(_is_static_lib ${_zip_archive} TYPE) if(_is_static_lib STREQUAL "STATIC_LIBRARY") list(APPEND _cmd_deps ${_zip_archive}) list(APPEND _zip_args $) endif() else() if(IS_ABSOLUTE ${_zip_archive}) list(APPEND _cmd_deps ${_zip_archive}) else() list(APPEND _cmd_deps ${US_RESOURCE_WORKING_DIRECTORY}/${_zip_archive}) endif() list(APPEND _zip_args ${_zip_archive}) endif() endforeach() endif() # This command depends on the given resource files and creates a source # file which must be added to the source list of the related target. # This way, the following command is executed if the resources change # and it just touches the created source file to force a (actually unnecessary) # re-linking and hence the execution of POST_BUILD commands. add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${US_RESOURCE_TARGET}_resources.cpp - COMMAND ${CMAKE_COMMAND} -E copy ${US_CMAKE_RESOURCE_DEPENDENCIES_CPP} ${CMAKE_CURRENT_BINARY_DIR}/${US_RESOURCE_TARGET}_resources.cpp + OUTPUT ${US_RESOURCE_SOURCE_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E copy ${US_CMAKE_RESOURCE_DEPENDENCIES_CPP} ${US_RESOURCE_SOURCE_OUTPUT} DEPENDS ${_cmd_deps} ${resource_compiler} COMMENT "Checking resource dependencies for ${US_RESOURCE_TARGET}" VERBATIM ) add_custom_command( TARGET ${US_RESOURCE_TARGET} POST_BUILD COMMAND ${resource_compiler} ${cmd_line_args} $ ${US_RESOURCE_MODULE_NAME} -a ${US_RESOURCE_FILES} -m ${_zip_args} WORKING_DIRECTORY ${US_RESOURCE_WORKING_DIRECTORY} COMMENT "Adding resources to ${US_RESOURCE_TARGET}" VERBATIM ) endfunction() diff --git a/core/test/modules/libRWithResources/CMakeLists.txt b/core/test/modules/libRWithResources/CMakeLists.txt index 9d25945705..23f5a6b972 100644 --- a/core/test/modules/libRWithResources/CMakeLists.txt +++ b/core/test/modules/libRWithResources/CMakeLists.txt @@ -1,22 +1,23 @@ set(resource_files icons/compressable.bmp icons/cppmicroservices.png icons/readme.txt foo.txt special_chars.dummy.txt test.xml ) -configure_file(resources/foo.txt ${CMAKE_CURRENT_BINARY_DIR}/foo2.txt @COPYONLY) +configure_file(resources/foo.txt ${CMAKE_CURRENT_BINARY_DIR}/resources/foo2.txt @COPYONLY) -#usFunctionCreateTestModuleWithResources(TestModuleR -# SOURCES usTestModuleR.cpp -# RESOURCES ${resource_files}) +usFunctionCreateTestModuleWithResources(TestModuleR + SOURCES ${CMAKE_CURRENT_BINARY_DIR}/TestModuleR_binary_resources.cpp + RESOURCES ${resource_files} +) -usFunctionCreateTestModule(TestModuleR) usFunctionAddResources( TARGET TestModuleR - WORKING_DIRECTORY resources - FILES ${resource_files} - ) + SOURCE_OUTPUT TestModuleR_binary_resources.cpp + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/resources + FILES foo2.txt +) diff --git a/core/test/usModuleResourceTest.cpp b/core/test/usModuleResourceTest.cpp index d6a7a94c67..507a1e5348 100644 --- a/core/test/usModuleResourceTest.cpp +++ b/core/test/usModuleResourceTest.cpp @@ -1,452 +1,455 @@ /*============================================================================= Library: CppMicroServices Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. =============================================================================*/ #include #include #include #include #include #include #include #include #include "usTestingMacros.h" #include #include US_USE_NAMESPACE namespace { // Please confirm that a character count differing from the following targets is not due to // a misconfiguration of your versioning software (Correct line endings for your system) // See issue #18 ( https://github.com/saschazelzer/CppMicroServices/issues/18 ) void checkResourceInfo(const ModuleResource& res, const std::string& path, const std::string& baseName, const std::string& completeBaseName, const std::string& suffix, const std::string& completeSuffix, int size, bool children = false) { US_TEST_CONDITION_REQUIRED(res.IsValid(), "Valid resource") US_TEST_CONDITION(res.GetBaseName() == baseName, "GetBaseName()") US_TEST_CONDITION(res.GetChildren().empty() == !children, "No children") US_TEST_CONDITION(res.GetCompleteBaseName() == completeBaseName, "GetCompleteBaseName()") US_TEST_CONDITION(res.GetName() == completeBaseName + "." + suffix, "GetName()") US_TEST_CONDITION(res.GetResourcePath() == path + completeBaseName + "." + suffix, "GetResourcePath()") US_TEST_CONDITION(res.GetPath() == path, "GetPath()") US_TEST_CONDITION(res.GetSize() == size, "Data size") US_TEST_CONDITION(res.GetSuffix() == suffix, "Suffix") US_TEST_CONDITION(res.GetCompleteSuffix() == completeSuffix, "Complete suffix") } void testTextResource(Module* module) { ModuleResource res = module->GetResource("foo.txt"); #ifdef US_PLATFORM_WINDOWS checkResourceInfo(res, "/", "foo", "foo", "txt", "txt", 16, false); const std::streampos ssize(13); const std::string fileData = "foo and\nbar\n\n"; #else checkResourceInfo(res, "/", "foo", "foo", "txt", "txt", 13, false); const std::streampos ssize(12); const std::string fileData = "foo and\nbar\n"; #endif ModuleResourceStream rs(res); rs.seekg(0, std::ios::end); US_TEST_CONDITION(rs.tellg() == ssize, "Stream content length"); rs.seekg(0, std::ios::beg); std::string content; content.reserve(res.GetSize()); char buffer[1024]; while (rs.read(buffer, sizeof(buffer))) { content.append(buffer, sizeof(buffer)); } content.append(buffer, static_cast(rs.gcount())); US_TEST_CONDITION(rs.eof(), "EOF check"); US_TEST_CONDITION(content == fileData, "Resource content"); rs.clear(); rs.seekg(0); US_TEST_CONDITION_REQUIRED(rs.tellg() == std::streampos(0), "Move to start") US_TEST_CONDITION_REQUIRED(rs.good(), "Start re-reading"); std::vector lines; std::string line; while (std::getline(rs, line)) { lines.push_back(line); } US_TEST_CONDITION_REQUIRED(lines.size() > 1, "Number of lines") US_TEST_CONDITION(lines[0] == "foo and", "Check first line") US_TEST_CONDITION(lines[1] == "bar", "Check second line") } void testTextResourceAsBinary(Module* module) { ModuleResource res = module->GetResource("foo.txt"); #ifdef US_PLATFORM_WINDOWS checkResourceInfo(res, "/", "foo", "foo", "txt", "txt", 16, false); const std::streampos ssize(16); const std::string fileData = "foo and\r\nbar\r\n\r\n"; #else checkResourceInfo(res, "/", "foo", "foo", "txt", "txt", 13, false); const std::streampos ssize(13); const std::string fileData = "foo and\nbar\n\n"; #endif ModuleResourceStream rs(res, std::ios_base::binary); rs.seekg(0, std::ios::end); US_TEST_CONDITION(rs.tellg() == ssize, "Stream content length"); rs.seekg(0, std::ios::beg); std::string content; content.reserve(res.GetSize()); char buffer[1024]; while (rs.read(buffer, sizeof(buffer))) { content.append(buffer, sizeof(buffer)); } content.append(buffer, static_cast(rs.gcount())); US_TEST_CONDITION(rs.eof(), "EOF check"); US_TEST_CONDITION(content == fileData, "Resource content"); } void testInvalidResource(Module* module) { ModuleResource res = module->GetResource("invalid"); US_TEST_CONDITION_REQUIRED(res.IsValid() == false, "Check invalid resource") US_TEST_CONDITION(res.GetName().empty(), "Check empty name") US_TEST_CONDITION(res.GetPath().empty(), "Check empty path") US_TEST_CONDITION(res.GetResourcePath().empty(), "Check empty resource path") US_TEST_CONDITION(res.GetBaseName().empty(), "Check empty base name") US_TEST_CONDITION(res.GetCompleteBaseName().empty(), "Check empty complete base name") US_TEST_CONDITION(res.GetSuffix().empty(), "Check empty suffix") US_TEST_CONDITION(res.GetChildren().empty(), "Check empty children") US_TEST_CONDITION(res.GetSize() == 0, "Check zero size") ModuleResourceStream rs(res); US_TEST_CONDITION(rs.good() == true, "Check invalid resource stream") rs.ignore(); US_TEST_CONDITION(rs.good() == false, "Check invalid resource stream") US_TEST_CONDITION(rs.eof() == true, "Check invalid resource stream") } void testSpecialCharacters(Module* module) { ModuleResource res = module->GetResource("special_chars.dummy.txt"); #ifdef US_PLATFORM_WINDOWS checkResourceInfo(res, "/", "special_chars", "special_chars.dummy", "txt", "dummy.txt", 56, false); const std::streampos ssize(54); const std::string fileData = "German Füße (feet)\nFrench garçon de café (waiter)\n"; #else checkResourceInfo(res, "/", "special_chars", "special_chars.dummy", "txt", "dummy.txt", 54, false); const std::streampos ssize(53); const std::string fileData = "German Füße (feet)\nFrench garçon de café (waiter)"; #endif ModuleResourceStream rs(res); rs.seekg(0, std::ios_base::end); US_TEST_CONDITION(rs.tellg() == ssize, "Stream content length"); rs.seekg(0, std::ios_base::beg); std::string content; content.reserve(res.GetSize()); char buffer[1024]; while (rs.read(buffer, sizeof(buffer))) { content.append(buffer, sizeof(buffer)); } content.append(buffer, static_cast(rs.gcount())); US_TEST_CONDITION(rs.eof(), "EOF check"); US_TEST_CONDITION(content == fileData, "Resource content"); } void testBinaryResource(Module* module) { ModuleResource res = module->GetResource("/icons/cppmicroservices.png"); checkResourceInfo(res, "/icons/", "cppmicroservices", "cppmicroservices", "png", "png", 2424, false); ModuleResourceStream rs(res, std::ios_base::binary); rs.seekg(0, std::ios_base::end); std::streampos resLength = rs.tellg(); rs.seekg(0); std::ifstream png(US_CORE_SOURCE_DIR "/test/modules/libRWithResources/resources/icons/cppmicroservices.png", std::ifstream::in | std::ifstream::binary); US_TEST_CONDITION_REQUIRED(png.is_open(), "Open reference file") png.seekg(0, std::ios_base::end); std::streampos pngLength = png.tellg(); png.seekg(0); US_TEST_CONDITION(res.GetSize() == resLength, "Check resource size") US_TEST_CONDITION_REQUIRED(resLength == pngLength, "Compare sizes") char c1 = 0; char c2 = 0; bool isEqual = true; int count = 0; while (png.get(c1) && rs.get(c2)) { ++count; if (c1 != c2) { isEqual = false; break; } } US_TEST_CONDITION_REQUIRED(count == pngLength, "Check if everything was read"); US_TEST_CONDITION_REQUIRED(isEqual, "Equal binary contents"); US_TEST_CONDITION(png.eof(), "EOF check"); } void testCompressedResource(Module* module) { ModuleResource res = module->GetResource("/icons/compressable.bmp"); checkResourceInfo(res, "/icons/", "compressable", "compressable", "bmp", "bmp", 300122, false); ModuleResourceStream rs(res, std::ios_base::binary); rs.seekg(0, std::ios_base::end); std::streampos resLength = rs.tellg(); rs.seekg(0); std::ifstream bmp(US_CORE_SOURCE_DIR "/test/modules/libRWithResources/resources/icons/compressable.bmp", std::ifstream::in | std::ifstream::binary); US_TEST_CONDITION_REQUIRED(bmp.is_open(), "Open reference file") bmp.seekg(0, std::ios_base::end); std::streampos bmpLength = bmp.tellg(); bmp.seekg(0); US_TEST_CONDITION(300122 == resLength, "Check resource size") US_TEST_CONDITION_REQUIRED(resLength == bmpLength, "Compare sizes") char c1 = 0; char c2 = 0; bool isEqual = true; int count = 0; while (bmp.get(c1) && rs.get(c2)) { ++count; if (c1 != c2) { isEqual = false; break; } } US_TEST_CONDITION_REQUIRED(count == bmpLength, "Check if everything was read"); US_TEST_CONDITION_REQUIRED(isEqual, "Equal binary contents"); US_TEST_CONDITION(bmp.eof(), "EOF check"); } struct ResourceComparator { bool operator()(const ModuleResource& mr1, const ModuleResource& mr2) const { return mr1 < mr2; } }; void testResourceTree(Module* module) { ModuleResource res = module->GetResource(""); US_TEST_CONDITION(res.GetResourcePath() == "/", "Check root file path") US_TEST_CONDITION(res.IsDir() == true, "Check type") std::vector children = res.GetChildren(); std::sort(children.begin(), children.end()); - US_TEST_CONDITION_REQUIRED(children.size() == 4, "Check child count") + US_TEST_CONDITION_REQUIRED(children.size() == 5, "Check child count") US_TEST_CONDITION(children[0] == "foo.txt", "Check child name") - US_TEST_CONDITION(children[1] == "icons/", "Check child name") - US_TEST_CONDITION(children[2] == "special_chars.dummy.txt", "Check child name") - US_TEST_CONDITION(children[3] == "test.xml", "Check child name") + US_TEST_CONDITION(children[1] == "foo2.txt", "Check child name") + US_TEST_CONDITION(children[2] == "icons/", "Check child name") + US_TEST_CONDITION(children[3] == "special_chars.dummy.txt", "Check child name") + US_TEST_CONDITION(children[4] == "test.xml", "Check child name") US_TEST_CONDITION(module->FindResources("!$noexist=?", std::string(), "true").empty(), "Check not existant path"); ModuleResource readme = module->GetResource("/icons/readme.txt"); US_TEST_CONDITION(readme.IsFile() && readme.GetChildren().empty(), "Check file resource") ModuleResource icons = module->GetResource("icons/"); US_TEST_CONDITION(icons.IsDir() && !icons.IsFile() && !icons.GetChildren().empty(), "Check directory resource") children = icons.GetChildren(); US_TEST_CONDITION_REQUIRED(children.size() == 3, "Check icons child count") std::sort(children.begin(), children.end()); US_TEST_CONDITION(children[0] == "compressable.bmp", "Check child name") US_TEST_CONDITION(children[1] == "cppmicroservices.png", "Check child name") US_TEST_CONDITION(children[2] == "readme.txt", "Check child name") ResourceComparator resourceComparator; // find all .txt files std::vector nodes = module->FindResources("", "*.txt", false); std::sort(nodes.begin(), nodes.end(), resourceComparator); - US_TEST_CONDITION_REQUIRED(nodes.size() == 2, "Found child count") + US_TEST_CONDITION_REQUIRED(nodes.size() == 3, "Found child count") US_TEST_CONDITION(nodes[0].GetResourcePath() == "/foo.txt", "Check child name") - US_TEST_CONDITION(nodes[1].GetResourcePath() == "/special_chars.dummy.txt", "Check child name") + US_TEST_CONDITION(nodes[1].GetResourcePath() == "/foo2.txt", "Check child name") + US_TEST_CONDITION(nodes[2].GetResourcePath() == "/special_chars.dummy.txt", "Check child name") nodes = module->FindResources("", "*.txt", true); std::sort(nodes.begin(), nodes.end(), resourceComparator); - US_TEST_CONDITION_REQUIRED(nodes.size() == 3, "Found child count") + US_TEST_CONDITION_REQUIRED(nodes.size() == 4, "Found child count") US_TEST_CONDITION(nodes[0].GetResourcePath() == "/foo.txt", "Check child name") - US_TEST_CONDITION(nodes[1].GetResourcePath() == "/icons/readme.txt", "Check child name") - US_TEST_CONDITION(nodes[2].GetResourcePath() == "/special_chars.dummy.txt", "Check child name") + US_TEST_CONDITION(nodes[1].GetResourcePath() == "/foo2.txt", "Check child name") + US_TEST_CONDITION(nodes[2].GetResourcePath() == "/icons/readme.txt", "Check child name") + US_TEST_CONDITION(nodes[3].GetResourcePath() == "/special_chars.dummy.txt", "Check child name") // find all resources nodes = module->FindResources("", "", true); - US_TEST_CONDITION(nodes.size() == 7, "Total resource number") + US_TEST_CONDITION(nodes.size() == 8, "Total resource number") nodes = module->FindResources("", "**", true); - US_TEST_CONDITION(nodes.size() == 7, "Total resource number") + US_TEST_CONDITION(nodes.size() == 8, "Total resource number") // test pattern matching nodes.clear(); nodes = module->FindResources("/icons", "*micro*.png", false); US_TEST_CONDITION(nodes.size() == 1 && nodes[0].GetResourcePath() == "/icons/cppmicroservices.png", "Check file pattern matches") nodes.clear(); nodes = module->FindResources("", "*.txt", true); - US_TEST_CONDITION(nodes.size() == 3, "Check recursive pattern matches") + US_TEST_CONDITION(nodes.size() == 4, "Check recursive pattern matches") } void testResourceOperators(Module* module) { ModuleResource invalid = module->GetResource("invalid"); ModuleResource foo = module->GetResource("foo.txt"); US_TEST_CONDITION_REQUIRED(foo.IsValid() && foo, "Check valid resource") ModuleResource foo2(foo); US_TEST_CONDITION(foo == foo, "Check equality operator") US_TEST_CONDITION(foo == foo2, "Check copy constructor and equality operator") US_TEST_CONDITION(foo != invalid, "Check inequality with invalid resource") ModuleResource xml = module->GetResource("/test.xml"); US_TEST_CONDITION_REQUIRED(xml.IsValid() && xml, "Check valid resource") US_TEST_CONDITION(foo != xml, "Check inequality") US_TEST_CONDITION(foo < xml, "Check operator<") // check operator< by using a set std::set resources; resources.insert(foo); resources.insert(foo); resources.insert(xml); US_TEST_CONDITION(resources.size() == 2, "Check operator< with set") // check hash function specialization US_UNORDERED_SET_TYPE resources2; resources2.insert(foo); resources2.insert(foo); resources2.insert(xml); US_TEST_CONDITION(resources2.size() == 2, "Check operator< with unordered set") // check operator<< std::ostringstream oss; oss << foo; US_TEST_CONDITION(oss.str() == foo.GetResourcePath(), "Check operator<<") } void testResourceFromExecutable(Module* module) { ModuleResource resource = module->GetResource("usTestResource.txt"); US_TEST_CONDITION_REQUIRED(resource.IsValid(), "Check valid executable resource") std::string line; ModuleResourceStream rs(resource); std::getline(rs, line); US_TEST_CONDITION(line == "meant to be compiled into the test driver", "Check executable resource content") } } // end unnamed namespace int usModuleResourceTest(int /*argc*/, char* /*argv*/[]) { US_TEST_BEGIN("ModuleResourceTest"); ModuleContext* mc = GetModuleContext(); assert(mc); #ifdef US_BUILD_SHARED_LIBS #ifdef US_PLATFORM_WINDOWS const std::string LIB_PATH = US_RUNTIME_OUTPUT_DIRECTORY; #else const std::string LIB_PATH = US_LIBRARY_OUTPUT_DIRECTORY; #endif SharedLibrary libR(LIB_PATH, "TestModuleR"); try { libR.Load(); } catch (const std::exception& e) { US_TEST_FAILED_MSG(<< "Load module exception: " << e.what()) } #endif Module* moduleR = ModuleRegistry::GetModule("TestModuleR"); US_TEST_CONDITION_REQUIRED(moduleR != NULL, "Test for existing module TestModuleR") US_TEST_CONDITION(moduleR->GetName() == "TestModuleR", "Test module name") testInvalidResource(moduleR); testResourceFromExecutable(mc->GetModule()); testResourceTree(moduleR); testResourceOperators(moduleR); testTextResource(moduleR); testTextResourceAsBinary(moduleR); testSpecialCharacters(moduleR); testBinaryResource(moduleR); testCompressedResource(moduleR); ModuleResource foo = moduleR->GetResource("foo.txt"); US_TEST_CONDITION(foo.IsValid() == true, "Valid resource") #ifdef US_BUILD_SHARED_LIBS libR.Unload(); US_TEST_CONDITION(foo.IsValid() == true, "Still valid resource") #endif US_TEST_END() } diff --git a/tools/usResourceCompiler.c b/tools/usResourceCompiler.c index 83487fc933..60268099df 100644 --- a/tools/usResourceCompiler.c +++ b/tools/usResourceCompiler.c @@ -1,783 +1,806 @@ /*============================================================================= Library: CppMicroServices Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. =============================================================================*/ #include "miniz.h" #include #include #include #include #define US_STR_(x) #x #define US_STR(x) US_STR_(x) static int cleanup_archives(mz_zip_archive* writeArchive, mz_zip_archive* readArchive) { if (writeArchive && writeArchive->m_zip_mode != MZ_ZIP_MODE_INVALID) { if (writeArchive->m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED) { if (!mz_zip_writer_finalize_archive(writeArchive)) { return -1; } } if (writeArchive->m_zip_mode != MZ_ZIP_MODE_INVALID) { if (!mz_zip_writer_end(writeArchive)) { return -1; } } } if (readArchive && readArchive->m_zip_mode != MZ_ZIP_MODE_INVALID) { if (!mz_zip_reader_end(readArchive)) { return -1; } } return 0; } static void exit_printf(mz_zip_archive* writeArchive, mz_zip_archive* readArchive, const char* format, ...) { va_list args; cleanup_archives(writeArchive, readArchive); fprintf(stderr, "error: "); va_start(args, format); vfprintf(stderr, format, args); va_end(args); exit(EXIT_FAILURE); } static void exit_perror(mz_zip_archive* writeArchive, mz_zip_archive* readArchive, const char* desc) { cleanup_archives(writeArchive, readArchive); fprintf(stderr, "error: "); perror(desc); exit(EXIT_FAILURE); } // --------------------------------------------------------------------------------- // -------------------------- PLATFORM SPECIFIC CODE ------------------------- // --------------------------------------------------------------------------------- #if defined(_WIN32) || defined(_WIN64) #define WIN32_LEAN_AND_MEAN #define VC_EXTRALEAN #include #include #include #include #include #include static char* get_error_str() { // Retrieve the system error message for the last-error code LPVOID lpMsgBuf; DWORD dw = GetLastError(); FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL ); return lpMsgBuf; } static void free_error_str(char* buf) { LocalFree(buf); } static FILE* us_create_tmp_file() { TCHAR tmpPath[MAX_PATH]; TCHAR tmpFileName[MAX_PATH]; HANDLE hTmpFile = INVALID_HANDLE_VALUE; FILE* tmpFile = NULL; int tmpfd = 0; UINT retVal = 0; retVal = GetTempPath(MAX_PATH, tmpPath); if (retVal > MAX_PATH || retVal == 0) { exit_printf(NULL, NULL, get_error_str()); } retVal = GetTempFileName(tmpPath, TEXT("us_"), 0, tmpFileName); if (retVal == 0) { exit_printf(NULL, NULL, get_error_str()); } if (_sopen_s(&tmpfd, tmpFileName, _O_CREAT | _O_TEMPORARY | _O_RDWR | _O_BINARY, _SH_DENYRW, _S_IREAD | _S_IWRITE)) { exit_printf(NULL, NULL, get_error_str()); } tmpFile = _fdopen(tmpfd, "r+b"); if (tmpFile == NULL) { exit_printf(NULL, NULL, get_error_str()); } return tmpFile; } static void free_tmp_path(char* buf) { free(buf); } static char* us_strcpy(char* dest, size_t dest_size, const char* src) { if (strcpy_s(dest, dest_size, src)) { char* err_str = get_error_str(); exit_printf(NULL, NULL, err_str); } return dest; } static char* us_strncpy(char* dest, size_t dest_size, const char* src, size_t count) { if (strncpy_s(dest, dest_size, src, count)) { char* err_str = get_error_str(); exit_printf(NULL, NULL, err_str); } return dest; } static FILE* us_fopen(const char* filename, const char* mode) { FILE* file = NULL; fopen_s(&file, filename, mode); return file; } #define US_CWD(b, s) _getcwd(b, s) #define US_CLOSE _close #define US_READ _read #define US_FOPEN us_fopen #define US_FTRUNCATE _chsize_s #define US_FILENO _fileno #define US_STRCASECMP _stricmp #define US_STRCPY us_strcpy #define US_STRNCPY us_strncpy #define US_SSCANF sscanf_s #else #include static char* get_error_str() { return strerror(errno); } static void free_error_str(char* buf) { } static FILE* us_create_tmp_file() { FILE* file = tmpfile(); if (file == NULL) { exit_printf(NULL, NULL, get_error_str()); } return file; } static void free_tmp_path(char* buf) { } static char* us_strcpy(char* dest, size_t dest_size, const char* src) { return strcpy(dest, src); } static char* us_strncpy(char* dest, size_t dest_size, const char* src, size_t count) { return strncpy(dest, src, count); } #define US_CWD(b, s) getcwd(b, s) #define US_CLOSE close #define US_READ read #define US_FOPEN fopen #define US_FTRUNCATE ftruncate #define US_FILENO fileno #define US_STRCASECMP strcasecmp #define US_STRCPY us_strcpy #define US_STRNCPY us_strncpy #define US_SSCANF sscanf #endif // --------------------------------------------------------------------------------- // ----------------------------- DEBUGGING STUFF ----------------------------- // --------------------------------------------------------------------------------- //#define DEBUG_TRACE #ifdef DEBUG_TRACE #define dbg_print printf #else static int void_printf(const char* format, ...) { return 0; } #define dbg_print void_printf #endif // --------------------------------------------------------------------------------- // ----------------------------- HELPER FUNCTIONS ---------------------------- // --------------------------------------------------------------------------------- void* malloc_or_abort(size_t size) { void* p; if (size == 0) size = 1; p = malloc(size); if (!p) { // try to print an error message; this might very well fail fprintf(stderr, "Could not allocate enough memory (%ld bytes)\n", size); abort(); } return p; } static int cmpzipindex(const void *i1, const void *i2) { mz_uint index1 = *(const mz_uint*)i1; mz_uint index2 = *(const mz_uint*)i2; return index1 == index2 ? 0 : (index1 < index2 ? -1 : 1); } static int cmpstringp(const void *p1, const void *p2) { return US_STRCASECMP(* (char * const *) p1, * (char * const *) p2); } typedef struct us_archived_names_tag { char** names; mz_uint size; mz_uint capacity; mz_uint orderedSize; } us_archived_names; static void us_archived_names_free(us_archived_names * archivedNames) { mz_uint i; for (i = 0; i < archivedNames->size; ++i) { free(archivedNames->names[i]); } free(archivedNames->names); } enum { US_OK = 0, US_ARCHIVED_NAMES_ERROR_DUPLICATE = 1, US_MZ_ERROR_ADD_FILE = 2, US_ERROR_INVALID = 3 }; // messages can take two arguments: // 1. The archive entry name // 2. The path of the zip archive const char* us_error_msg[] = { "ok\n", "Duplicate entry '%s' (in %s)\n", "Could not add resource %s\n" }; static int us_archived_names_append(us_archived_names* archivedNames, const char* archiveName) { if (archivedNames->names != NULL && bsearch(&archiveName, archivedNames->names, archivedNames->orderedSize, sizeof(char*), cmpstringp) != NULL) { return US_ARCHIVED_NAMES_ERROR_DUPLICATE; } if (archivedNames->size >= archivedNames->capacity) { size_t newCapacity = archivedNames->size > archivedNames->capacity + 100 ? archivedNames->size + 1 : archivedNames->capacity + 100; archivedNames->names = realloc(archivedNames->names, newCapacity * sizeof(char*)); if (archivedNames->names == NULL) { fprintf(stderr, "Could not realloc enough memory (%ld bytes)\n", newCapacity); abort(); } memset(archivedNames->names + archivedNames->capacity, 0, sizeof(char*) * (newCapacity - archivedNames->capacity)); archivedNames->capacity = (mz_uint)newCapacity; } if (archivedNames->names[archivedNames->size] == NULL) { archivedNames->names[archivedNames->size] = malloc_or_abort(MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE * sizeof(char)); } US_STRCPY(archivedNames->names[archivedNames->size], MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE, archiveName); ++archivedNames->size; return US_OK; } static void us_archived_names_sort(us_archived_names* archivedNames) { qsort(archivedNames->names, archivedNames->size, sizeof(char*), cmpstringp); archivedNames->orderedSize = archivedNames->size; } static int us_zip_writer_add_dir_entries(mz_zip_archive* pZip, const char* pArchive_name, us_archived_names* archived_dirs) { size_t end; size_t length = strlen(pArchive_name); char dirName[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE]; if (sizeof dirName < length - 1) { // This should be impossible fprintf(stderr, "Archive file name '%s' too long (%ld > %ld)", pArchive_name, length-1, sizeof dirName); exit(EXIT_FAILURE); } // split the archive name into directory tokens for (end = 0; end < length; ++end) { if (pArchive_name[end] == '/') { US_STRNCPY(dirName, sizeof dirName, pArchive_name, end + 1); if (end < length-1) { dirName[end+1] = '\0'; } if (us_archived_names_append(archived_dirs, dirName) == US_OK) { dbg_print("-- found new dir entry %s\n", dirName); // The directory entry does not yet exist, so add it if (!mz_zip_writer_add_mem(pZip, dirName, NULL, 0, MZ_NO_COMPRESSION)) { dbg_print("-- zip add_mem error\n"); return US_MZ_ERROR_ADD_FILE; } us_archived_names_sort(archived_dirs); } } } return US_OK; } static int us_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, us_archived_names* archived_names, us_archived_names* archived_dirs) { int retCode = us_archived_names_append(archived_names, pArchive_name); if (US_OK != retCode) return retCode; if (!mz_zip_writer_add_file(pZip, pArchive_name, pSrc_filename, pComment, comment_size, level_and_flags)) { return US_MZ_ERROR_ADD_FILE; } return us_zip_writer_add_dir_entries(pZip, pArchive_name, archived_dirs); } static int us_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint file_index, us_archived_names* archived_names, us_archived_names* archived_dirs, char* archiveName, mz_uint archiveNameSize) { int retCode = 0; mz_uint numBytes = mz_zip_reader_get_filename(pSource_zip, file_index, archiveName, archiveNameSize); if (numBytes > 1 && archiveName[numBytes-2] != '/') { retCode = us_archived_names_append(archived_names, archiveName); if (US_OK != retCode) return retCode; if (!mz_zip_writer_add_from_zip_reader(pZip, pSource_zip, file_index)) { return US_MZ_ERROR_ADD_FILE; } } return us_zip_writer_add_dir_entries(pZip, archiveName, archived_dirs); } // --------------------------------------------------------------------------------- // ----------------------------- MAIN ENTRY POINT ---------------------------- // --------------------------------------------------------------------------------- int main(int argc, char** argv) { int compressionLevel = 6; int argIndex = 0; int bPrintHelp = 0; int errCode = US_OK; int mergeFlag = 0; const char* moduleFile = NULL; const char* moduleName = NULL; size_t moduleNameLength = 0; char currDir[1024]; FILE* moduleFileStream = NULL; mz_zip_archive writeArchive; mz_zip_archive readArchive; us_archived_names archivedNames; us_archived_names archivedDirs; FILE* tmp_archive_file = NULL; char archiveName[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE]; int numZipArgs = 0; int* zipArgIndices = NULL; mz_zip_archive currFileArchive; mz_uint oldIndex = 0; mz_uint numOldEntries = 0; int zipArgIndex = 0; char readBuffer[1024]; mz_uint numRead = 0; // --------------------------------------------------------------------------------- // COMMAND LINE VALIDATION // --------------------------------------------------------------------------------- if (argc < 4) { bPrintHelp = 1; } else if (argv[1][0] == '-') { if (strlen(argv[1]) == 2) { if (US_SSCANF(argv[1], "-%1d", &compressionLevel) != 1) { bPrintHelp = 1; } else { argIndex = 1; if (argc < 5 || compressionLevel < 0 || compressionLevel > 9) bPrintHelp = 1; } } else { bPrintHelp = 1; } } if (bPrintHelp) { printf("A resource compiler for C++ Micro Services modules\n\n"); printf("Usage: usResourceCompiler [-#] modulefile modulename [[-a] file...] [-m archive...]\n\n"); printf("Add files to modulefile as zip entries and merge archives.\n\n"); printf(" -# (-0, -1, -2, -3, -4, -5, -6, -7, -8, -9)\n"); printf(" The Zip compression level. The default compression level is -6.\n"); printf(" modulefile The absolute path of the module (shared library).\n"); printf(" modulename The module name as specified in the MODULE_NAME compile definition.\n"); printf(" file Path to a resource file, relative to the current working directory.\n"); printf(" archive Path to a zip archive for merging into modulefile.\n"); exit(EXIT_SUCCESS); } if (NULL == US_CWD(currDir, sizeof(currDir))) { perror(US_STR(US_CWD)); exit(EXIT_FAILURE); } moduleFile = argv[++argIndex]; moduleName = argv[++argIndex]; moduleNameLength = strlen(moduleName); // --------------------------------------------------------------------------------- // OPEN MODULE FILE (shared library) // --------------------------------------------------------------------------------- memset(&writeArchive, 0, sizeof(writeArchive)); memset(&readArchive, 0, sizeof(readArchive)); - // Try to open the module file for appending a new zip archive later - dbg_print("Try open module file '%s' as r+b... ", moduleFile); - if (NULL == (moduleFileStream = US_FOPEN(moduleFile, "r+b"))) - { - dbg_print("failure\n"); - exit_perror(&writeArchive, &readArchive, "fopen"); - } - else - { - dbg_print("success\n"); - } - dbg_print("Check for valid module zip archive... "); memset(&archivedNames, 0, sizeof archivedNames); memset(&archivedDirs, 0, sizeof archivedDirs); // Check if moduleFile is a valid zip archive if (!mz_zip_reader_init_file(&readArchive, moduleFile, 0)) { dbg_print("no\n"); } else { dbg_print("yes\n"); } // --------------------------------------------------------------------------------- // ZIP ARCHIVE WRITING (temporary archive) // --------------------------------------------------------------------------------- // Create a new zip archive which will be appended to the moduleFile later dbg_print("Creating temporary zip archive\n"); tmp_archive_file = us_create_tmp_file(); if (!mz_zip_writer_init_stream(&writeArchive, tmp_archive_file, 0)) { exit_printf(&writeArchive, &readArchive, "Internal error, could not init new zip archive\n"); } dbg_print("Initialized temporary zip archive\n"); // Add current files to the zip archive zipArgIndices = malloc_or_abort(argc * sizeof *zipArgIndices); while(++argIndex < argc) { const char* fileName = argv[argIndex]; const size_t fileNameLength = strlen(fileName); // determine the argument type if (strcmp(argv[argIndex], "-a") == 0) { mergeFlag = 0; continue; } else if (strcmp(argv[argIndex], "-m") == 0) { mergeFlag = 1; continue; } if (mergeFlag) { // check if the current file is a valid zip archive memset(&currFileArchive, 0, sizeof(currFileArchive)); if (mz_zip_reader_init_file(&currFileArchive, fileName, 0)) { dbg_print("Input is a valid zip archive: %s\n", fileName); zipArgIndices[numZipArgs++] = argIndex; mz_zip_reader_end(&currFileArchive); } // silently ignore files which are not zip archives continue; } if (fileNameLength + 1 + moduleNameLength > MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1) { exit_printf(&writeArchive, &readArchive, "Resource filename too long: %s\n", moduleName); } US_STRCPY(archiveName, sizeof archiveName, moduleName); archiveName[moduleNameLength] = '/'; US_STRCPY(archiveName + moduleNameLength + 1, (sizeof archiveName) - (moduleNameLength + 1), fileName); // add the current file to the new archive if ((errCode = us_zip_writer_add_file(&writeArchive, archiveName, fileName, NULL, 0, compressionLevel, &archivedNames, &archivedDirs))) { dbg_print("Adding %s failed\n", archiveName); exit_printf(&writeArchive, &readArchive, us_error_msg[errCode], archiveName, fileName); } if (mz_zip_reader_locate_file(&readArchive, archiveName, NULL, 0) > -1) { // we found a duplicate printf("updating: %s\n", archiveName); } else { printf(" adding: %s\n", archiveName); } } us_archived_names_sort(&archivedNames); dbg_print("Added cmd line files to tmp archive\n"); // --------------------------------------------------------------------------------- // MERGE MODULE ZIP ARCHIVE (into temporary archive) // --------------------------------------------------------------------------------- // Add all entries from the old archive oldIndex = 0; numOldEntries = mz_zip_reader_get_num_files(&readArchive); for (; oldIndex < numOldEntries; ++oldIndex) { errCode = us_zip_writer_add_from_zip_reader(&writeArchive, &readArchive, oldIndex, &archivedNames, &archivedDirs, archiveName, (mz_uint) sizeof archiveName); if (errCode == US_ARCHIVED_NAMES_ERROR_DUPLICATE) { // Duplicates in the module archive are not an error, // they were already treated as "updates" continue; } if (errCode) { exit_printf(&writeArchive, &readArchive, us_error_msg[errCode], archiveName, moduleFile); } } us_archived_names_sort(&archivedNames); dbg_print("Merged original entries into tmp archive\n"); // Now merge in the zip archives given on the command line for (zipArgIndex = 0; zipArgIndex < numZipArgs; ++zipArgIndex) { mz_zip_archive currZipArchive; const char* currArchiveFileName = NULL; mz_uint currZipIndex = 0; mz_uint numZipIndices = 0; memset(&currZipArchive, 0, sizeof(mz_zip_archive)); currArchiveFileName = argv[zipArgIndices[zipArgIndex]]; if (!mz_zip_reader_init_file(&currZipArchive, currArchiveFileName, 0)) { exit_printf(&writeArchive, &readArchive, "Could not initialize zip archive %s\n", currArchiveFileName); } numZipIndices = mz_zip_reader_get_num_files(&currZipArchive); for (currZipIndex = 0; currZipIndex < numZipIndices; ++currZipIndex) { printf(" merging: %s (from %s)\n", archiveName, currArchiveFileName); errCode = us_zip_writer_add_from_zip_reader(&writeArchive, &currZipArchive, currZipIndex, &archivedNames, &archivedDirs, archiveName, sizeof archiveName); if (errCode == US_ARCHIVED_NAMES_ERROR_DUPLICATE) { printf(" warning: Merge failed: "); printf(us_error_msg[errCode], archiveName, currArchiveFileName); } else if (errCode != US_OK) { mz_zip_reader_end(&currZipArchive); exit_printf(&writeArchive, &readArchive, us_error_msg[errCode], archiveName, currArchiveFileName); } } mz_zip_reader_end(&currZipArchive); us_archived_names_sort(&archivedNames); } // We are finished, finalize the temporary archive if (!mz_zip_writer_finalize_archive(&writeArchive)) { exit_printf(&writeArchive, &readArchive, "Could not finalize zip archive\n"); } dbg_print("Finalized tmp zip archive\n"); + // Close the module file + mz_zip_reader_end(&readArchive); + // --------------------------------------------------------------------------------- // APPEND NEW ARCHIVE TO MODULE // --------------------------------------------------------------------------------- if (archivedNames.size > 0) { + // Open the module file for appending the temporary zip archive + dbg_print("Opening module file '%s' as r+b... ", moduleFile); + if (NULL == (moduleFileStream = US_FOPEN(moduleFile, "r+b"))) + { + dbg_print("failure\n"); + exit_perror(&writeArchive, &readArchive, "fopen"); + } + else + { + dbg_print("success\n"); + } + // Append the temporary zip archive to the module if (numOldEntries > 0) { // we need to truncate the module file first if (US_FTRUNCATE(US_FILENO(moduleFileStream), readArchive.m_archive_file_ofs)) { exit_perror(&writeArchive, &readArchive, "ftruncate"); } + +#ifdef DEBUG_TRACE + { + long currPos = ftell(moduleFileStream); + fseek(moduleFileStream, 0, SEEK_END); + dbg_print("Truncated module file size: %ld bytes\n", ftell(moduleFileStream)); + fseek(moduleFileStream, currPos, SEEK_SET); + } +#endif + } if (fseek(tmp_archive_file, 0, SEEK_SET) == -1) { exit_perror(&writeArchive, &readArchive, "fseek"); } clearerr(tmp_archive_file); if (fseek(moduleFileStream, 0, SEEK_END) == -1) { exit_perror(&writeArchive, &readArchive, "fseek"); } dbg_print("Appending temporary zip archive to module\n"); do { numRead = US_READ(US_FILENO(tmp_archive_file), readBuffer, sizeof(readBuffer)); if (numRead == -1) { exit_perror(&writeArchive, &readArchive, "read"); } fwrite(readBuffer, numRead, 1, moduleFileStream); if (ferror(moduleFileStream)) { exit_printf(&writeArchive, &readArchive, "Appending resources failed\n"); } } while (numRead != 0); + +#ifdef DEBUG_TRACE + { + long currPos = ftell(moduleFileStream); + fseek(moduleFileStream, 0, SEEK_END); + dbg_print("New module file size: %ld bytes\n", ftell(moduleFileStream)); + fseek(moduleFileStream, currPos, SEEK_SET); + } +#endif + } // --------------------------------------------------------------------------------- // CLEANUP // --------------------------------------------------------------------------------- free(zipArgIndices); us_archived_names_free(&archivedNames); us_archived_names_free(&archivedDirs); if (cleanup_archives(&writeArchive, &readArchive) == -1) { fprintf(stderr, "Internal error finalizing zip archive"); return EXIT_FAILURE; } fclose(moduleFileStream); dbg_print("Successfully added resources\n"); return EXIT_SUCCESS; }