├── src ├── utf8_wrappers.h ├── utf8_wmain.c └── utf8_wrappers.c ├── prepare-sources.cmake ├── README.md └── CMakeLists.txt /src/utf8_wrappers.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Wrappers to provide Unicode (UTF-8) support on Windows. 3 | * 4 | * Copyright (c) 2018 Peter Wu 5 | * SPDX-License-Identifier: (GPL-2.0-or-later OR MIT) 6 | */ 7 | 8 | #ifdef _WIN32 9 | 10 | #if defined(loadlib_c) || defined(lauxlib_c) || defined(liolib_c) || defined(luac_c) 11 | #include /* for loadlib_c */ 12 | FILE *fopen_utf8(const char *pathname, const char *mode); 13 | #define fopen fopen_utf8 14 | #endif 15 | 16 | #ifdef lauxlib_c 17 | FILE *freopen_utf8(const char *pathname, const char *mode, FILE *stream); 18 | #define freopen freopen_utf8 19 | #endif 20 | 21 | #ifdef liolib_c 22 | FILE *popen_utf8(const char *command, const char *mode); 23 | #define _popen popen_utf8 24 | #endif 25 | 26 | #ifdef loslib_c 27 | int remove_utf8(const char *pathname); 28 | int rename_utf8(const char *oldpath, const char *newpath); 29 | int system_utf8(const char *command); 30 | #define remove remove_utf8 31 | #define rename rename_utf8 32 | #define system system_utf8 33 | #endif 34 | 35 | #ifdef loadlib_c 36 | DWORD GetModuleFileNameA_utf8(HMODULE hModule, LPSTR lpFilename, DWORD nSize); 37 | HMODULE LoadLibraryExA_utf8(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags); 38 | #define GetModuleFileNameA GetModuleFileNameA_utf8 39 | #define LoadLibraryExA LoadLibraryExA_utf8 40 | #endif 41 | #endif 42 | 43 | #if defined(lua_c) || defined(luac_c) 44 | int main_utf8(int argc, char *argv[]); 45 | #define main main_utf8 46 | #endif 47 | -------------------------------------------------------------------------------- /src/utf8_wmain.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Wrapper to provide UTF-8 support for lua and luac on Windows. 3 | * 4 | * Copyright (c) 2018 Peter Wu 5 | * SPDX-License-Identifier: (GPL-2.0-or-later OR MIT) 6 | */ 7 | 8 | #ifdef _WIN32 9 | #include 10 | #include 11 | #include 12 | 13 | extern int main_utf8(int argc, char *argv[]); 14 | 15 | int wmain(int argc, wchar_t *argv[]) { 16 | char **argv_utf8 = (char **)calloc(argc + 1, sizeof(char *)); 17 | if (!argv_utf8) { 18 | fprintf(stderr, "cannot allocate memory for arguments\n"); 19 | return EXIT_FAILURE; 20 | } 21 | for (int i = 0; i < argc; i++) { 22 | int size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, argv[i], -1, NULL, 0, NULL, NULL); 23 | if (size == 0) { 24 | fprintf(stderr, "Invalid character in argument %d\n", i); 25 | return EXIT_FAILURE; 26 | } 27 | 28 | argv_utf8[i] = (char *)malloc(size); 29 | if (!argv_utf8[i]) { 30 | fprintf(stderr, "cannot allocate memory for argument %d\n", i); 31 | return EXIT_FAILURE; 32 | } 33 | if (!WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, argv[i], -1, argv_utf8[i], size, NULL, NULL)) { 34 | fprintf(stderr, "Invalid character in argument %d\n", i); 35 | return EXIT_FAILURE; 36 | } 37 | } 38 | int ret = main_utf8(argc, argv_utf8); 39 | for (int i = 0; i < argc; i++) { 40 | free(argv_utf8[i]); 41 | } 42 | free(argv_utf8); 43 | return ret; 44 | } 45 | #endif 46 | -------------------------------------------------------------------------------- /prepare-sources.cmake: -------------------------------------------------------------------------------- 1 | # Downloads, verifies and unpacks a patched source tree into ${OUTPUT_DIR}. 2 | # (A subdirectory will be created for the sources.) 3 | 4 | cmake_minimum_required(VERSION 3.5) 5 | set(PROJECT_VERSION 5.2.4) 6 | 7 | set(URL https://www.lua.org/ftp/lua-${PROJECT_VERSION}.tar.gz) 8 | set(SHA256 b9e2e4aad6789b3b63a056d442f7b39f0ecfca3ae0f1fc0ae4e9614401b69f4b) 9 | 10 | # Directory to store the source tarball and create the source directory. 11 | if(NOT OUTPUT_DIR) 12 | set(OUTPUT_DIR .) 13 | endif() 14 | get_filename_component(OUTPUT_DIR "${OUTPUT_DIR}" ABSOLUTE) 15 | 16 | # Retrieve tarball if missing 17 | get_filename_component(TARBALL ${OUTPUT_DIR}/lua-${PROJECT_VERSION}.tar.gz ABSOLUTE) 18 | if(NOT EXISTS ${TARBALL}) 19 | message(STATUS "Downloading ${URL}") 20 | file(DOWNLOAD ${URL} ${TARBALL} EXPECTED_HASH SHA256=${SHA256}) 21 | else() 22 | file(SHA256 ${TARBALL} _hash) 23 | if(NOT _hash STREQUAL SHA256) 24 | message(FATAL_ERROR "Hash mismatch for ${TARBALL} 25 | Expected hash: ${SHA256} 26 | Actual hash: ${_hash}") 27 | endif() 28 | endif() 29 | 30 | # Unpack sources. 31 | set(OUTPUT_NAME lua-${PROJECT_VERSION}) 32 | set(LUA_SRCDIR ${OUTPUT_DIR}/${OUTPUT_NAME}) 33 | if(NOT EXISTS ${LUA_SRCDIR}) 34 | file(MAKE_DIRECTORY ${LUA_SRCDIR}) 35 | execute_process(COMMAND "${CMAKE_COMMAND}" -E tar xzf "${TARBALL}" 36 | WORKING_DIRECTORY "${OUTPUT_DIR}" 37 | RESULT_VARIABLE unpack_result) 38 | if(NOT unpack_result EQUAL 0) 39 | message(FATAL_ERROR "Failed to unpack ${TARBALL}") 40 | endif() 41 | endif() 42 | 43 | # Check for LUA_LIB as well as specific header names in case external projects 44 | # set the LUA_LIB macro. 45 | set(code [[ 46 | #if defined(lua_c) || defined(luac_c) || (defined(LUA_LIB) && \ 47 | (defined(lauxlib_c) || defined(liolib_c) || \ 48 | defined(loadlib_c) || defined(loslib_c))) 49 | #include "utf8_wrappers.h" 50 | #endif 51 | ]]) 52 | 53 | # Patch the header to include our UTF-8 wrappers for Windows. 54 | file(READ "${LUA_SRCDIR}/src/luaconf.h" luaconf_h) 55 | if(NOT luaconf_h MATCHES ".*utf8_wrappers.h.*") 56 | string(REPLACE "\\" "\\\\" code_escaped "${code}") 57 | string(REGEX REPLACE "(Local configuration\\.[^\n]+\n(\\*[^\n]+\n)+)" 58 | "\\1${code_escaped}" patched_luaconf_h "${luaconf_h}") 59 | if(luaconf_h STREQUAL patched_luaconf_h) 60 | message(FATAL_ERROR "Failed to patch luaconf.h") 61 | endif() 62 | file(WRITE "${LUA_SRCDIR}/src/luaconf.h" "${patched_luaconf_h}") 63 | message(STATUS "Patched luaconf.h") 64 | endif() 65 | file(COPY src/utf8_wrappers.h src/utf8_wrappers.c src/utf8_wmain.c 66 | DESTINATION "${LUA_SRCDIR}/src") 67 | file(COPY CMakeLists.txt build-all.ps1 prepare-sources.cmake README.md 68 | DESTINATION "${LUA_SRCDIR}") 69 | 70 | message(STATUS "Source tree is ready at ${LUA_SRCDIR}") 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lua Unicode for Windows 2 | Patched Lua library to add UTF-8 support on Windows. 3 | 4 | [Lua](https://www.lua.org/) is portable due to limiting itself to ANSI C APIs. 5 | Lua does not care about character encoding, its strings are essentially binary 6 | buffers. It is up to applications to interpret it. 7 | 8 | Systems like Linux and macOS typically use UTF-8 which has the advantage of 9 | being compatible with 7-bit ASCII. This encoding is also used for filesystem 10 | paths and shell commands. 11 | 12 | Windows on the other hand interprets these paths and commands according to a 13 | system-dependent code page (which is typically not UTF-8). For (embedded) 14 | applications that provide UTF-8 encoded strings, this means that some form of 15 | conversion is necessary, like 16 | [winapi.short_path](https://stevedonovan.github.io/winapi/api.html#short_path). 17 | This however complicates Lua code which must now detect Windows and convert 18 | paths before using `dofile`, `io.open` and so on. Additionally it is still not 19 | able to support filenames outside the current code page. 20 | 21 | We take a different approach and patch the Lua core to transparently accept 22 | UTF-8 and call appropriate Windows-specific Unicode routines. That way, 23 | applications can always assume UTF-8 support for the following: 24 | 25 | - loadfile, dofile and friends. 26 | - io.open 27 | - io.popen, os.execute (note: a cmd.exe window could still pop up in GUI 28 | applications, fixing that is probably out of scope for this project.) 29 | - os.remove 30 | - os.rename 31 | - package.loadlib (as well as the search path for Lua libraries). 32 | 33 | It deliberately does not add newer API interfaces (which would be better suited 34 | for an external library) nor change data types. The resulting binaries are ABI 35 | compatible with those distributed by the LuaBinaries project. 36 | 37 | Prebuilt binaries and source zips are available in the releases section of 38 | https://github.com/Lekensteyn/lua-unicode (issues can also be reported here). 39 | 40 | ## Changes 41 | This project modifies the Lua core in a minimal way and is ABI compatible with 42 | the standard Lua library. 43 | 44 | The source tree is created using `cmake -P prepare-sources.cmake` which: 45 | 46 | 1. Downloads the source tarball and verifies its integrity. 47 | 2. Unpacks the source tree. 48 | 3. Add `src/utf8_wrappers.h` and `src/utf8_wrappers.c`. 49 | 4. Add `src/utf8_wmain.c` (affects executables only, not the DLL). 50 | 5. Patch `src/luaconf.h` to add the following in the *Local configuration* part: 51 | 52 | #if defined(lua_c) || defined(luac_c) || (defined(LUA_LIB) && \ 53 | (defined(lauxlib_c) || defined(liolib_c) || \ 54 | defined(loadlib_c) || defined(loslib_c))) 55 | #include "utf8_wrappers.h" 56 | #endif 57 | 58 | 6. Add extra build scripts (CMakeLists.txt) and documentation. 59 | 60 | Run `build-all.ps1` in the resulting source tree to create zip archives for both 61 | 32-bit and 64-bit binaries consisting of: 62 | 63 | - Lua DLL (linked with VCRUNTIME140.dll or whatever compiler you use). 64 | - Header files (in the include directory). 65 | - Lua executables (lua and luac). 66 | - PDB for the Lua DLL (for debugging). 67 | - Build scripts, additional source code and documentation (for reproducibility). 68 | 69 | These zip archives will have a subdirectory containing all files (unlike the 70 | LuaBinaries project which puts all files in the top-level directory). 71 | 72 | ## License 73 | Lua is Copyright © 1994–2018 Lua.org, PUC-Rio. See the sources for details on 74 | the MIT license. 75 | 76 | The extra changes from this project (additional sources, build scripts and 77 | documentation files) are 78 | 79 | Copyright (c) 2018 Peter Wu 80 | SPDX-License-Identifier: (GPL-2.0-or-later OR MIT) 81 | -------------------------------------------------------------------------------- /src/utf8_wrappers.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Wrappers to provide Unicode (UTF-8) support on Windows. 3 | * 4 | * Copyright (c) 2018 Peter Wu 5 | * SPDX-License-Identifier: (GPL-2.0-or-later OR MIT) 6 | */ 7 | 8 | #ifdef _WIN32 9 | #include /* for MultiByteToWideChar */ 10 | #include /* for _wrename */ 11 | #include 12 | #include 13 | #include 14 | 15 | // Set a high limit in case long paths are enabled. 16 | #define MAX_PATH_SIZE 4096 17 | #define MAX_MODE_SIZE 128 18 | // cmd.exe argument length is reportedly limited to 8192. 19 | #define MAX_CMD_SIZE 8192 20 | 21 | FILE *fopen_utf8(const char *pathname, const char *mode) { 22 | wchar_t pathname_w[MAX_PATH_SIZE]; 23 | wchar_t mode_w[MAX_MODE_SIZE]; 24 | if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, pathname, -1, pathname_w, MAX_PATH_SIZE) || 25 | !MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, mode, -1, mode_w, MAX_MODE_SIZE)) { 26 | errno = EINVAL; 27 | return NULL; 28 | } 29 | return _wfopen(pathname_w, mode_w); 30 | } 31 | 32 | FILE *freopen_utf8(const char *pathname, const char *mode, FILE *stream) { 33 | wchar_t pathname_w[MAX_PATH_SIZE]; 34 | wchar_t mode_w[MAX_MODE_SIZE]; 35 | if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, pathname, -1, pathname_w, MAX_PATH_SIZE) || 36 | !MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, mode, -1, mode_w, MAX_MODE_SIZE)) { 37 | // Close stream as documented for the error case. 38 | fclose(stream); 39 | errno = EINVAL; 40 | return NULL; 41 | } 42 | return _wfreopen(pathname_w, mode_w, stream); 43 | } 44 | 45 | int remove_utf8(const char *pathname) { 46 | wchar_t pathname_w[MAX_PATH_SIZE]; 47 | if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, pathname, -1, pathname_w, MAX_PATH_SIZE)) { 48 | errno = EINVAL; 49 | return -1; 50 | } 51 | return _wremove(pathname_w); 52 | } 53 | 54 | int rename_utf8(const char *oldpath, const char *newpath) { 55 | wchar_t oldpath_w[MAX_PATH_SIZE]; 56 | wchar_t newpath_w[MAX_PATH_SIZE]; 57 | if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, oldpath, -1, oldpath_w, MAX_PATH_SIZE) || 58 | !MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, newpath, -1, newpath_w, MAX_PATH_SIZE)) { 59 | errno = EINVAL; 60 | return -1; 61 | } 62 | return _wrename(oldpath_w, newpath_w); 63 | } 64 | 65 | FILE *popen_utf8(const char *command, const char *mode) { 66 | wchar_t command_w[MAX_CMD_SIZE]; 67 | wchar_t mode_w[MAX_MODE_SIZE]; 68 | if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, command, -1, command_w, MAX_CMD_SIZE) || 69 | !MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, mode, -1, mode_w, MAX_MODE_SIZE)) { 70 | errno = EINVAL; 71 | return NULL; 72 | } 73 | return _wpopen(command_w, mode_w); 74 | } 75 | 76 | int system_utf8(const char *command) { 77 | wchar_t command_w[MAX_CMD_SIZE]; 78 | if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, command, -1, command_w, MAX_CMD_SIZE)) { 79 | errno = EINVAL; 80 | return -1; 81 | } 82 | return _wsystem(command_w); 83 | } 84 | 85 | DWORD GetModuleFileNameA_utf8(HMODULE hModule, LPSTR lpFilename, DWORD nSize) { 86 | wchar_t filename_w[MAX_PATH + 1]; 87 | if (!GetModuleFileNameW(hModule, filename_w, MAX_PATH + 1)) { 88 | return 0; 89 | } 90 | return WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, filename_w, -1, lpFilename, nSize, NULL, NULL); 91 | } 92 | 93 | HMODULE LoadLibraryExA_utf8(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) { 94 | wchar_t pathname_w[MAX_PATH_SIZE]; 95 | if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, lpLibFileName, -1, pathname_w, MAX_PATH_SIZE)) { 96 | SetLastError(ERROR_INVALID_NAME); 97 | return NULL; 98 | } 99 | return LoadLibraryExW(pathname_w, hFile, dwFlags); 100 | } 101 | #endif 102 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(Lua VERSION 5.2.4 LANGUAGES C) 3 | 4 | if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/luaconf.h") 5 | message(FATAL_ERROR "Run \"cmake -P prepare-sources.cmake\" and change dirs.") 6 | endif() 7 | 8 | # Lua headers and source files. 9 | set(LUA_TO_INC lua.h luaconf.h lualib.h lauxlib.h lua.hpp) 10 | #file(GLOB LUA_SOURCES src/l*.c) 11 | #list(REMOVE_ITEM LUA_SOURCES lua.c luac.c) 12 | # Copied CORE_O and LIB_O from src/Makefile, with .o -> .c 13 | set(LUA_CORE_SOURCES lapi.c lcode.c lctype.c ldebug.c ldo.c ldump.c lfunc.c 14 | lgc.c llex.c lmem.c lobject.c lopcodes.c lparser.c lstate.c lstring.c ltable.c 15 | ltm.c lundump.c lvm.c lzio.c) 16 | set(LUA_LIB_SOURCES lauxlib.c lbaselib.c lbitlib.c lcorolib.c ldblib.c liolib.c 17 | lmathlib.c loslib.c lstrlib.c ltablib.c loadlib.c linit.c) 18 | set(LUA_SOURCES) 19 | foreach(_src IN LISTS LUA_CORE_SOURCES LUA_LIB_SOURCES) 20 | list(APPEND LUA_SOURCES "src/${_src}") 21 | endforeach() 22 | set(LUA_HEADERS) 23 | foreach(_hdr IN LISTS LUA_TO_INC) 24 | list(APPEND LUA_HEADERS "src/${_hdr}") 25 | endforeach() 26 | # Extra sources. 27 | list(APPEND LUA_SOURCES src/utf8_wrappers.c) 28 | 29 | add_definitions(-DLUA_COMPAT_ALL) 30 | if(WIN32) 31 | add_definitions(-DLUA_BUILD_AS_DLL 32 | -DWIN32_LEAN_AND_MEAN -D_CRT_SECURE_NO_DEPRECATE) 33 | elseif(APPLE) 34 | add_definitions(-DLUA_USE_MACOSX) 35 | elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") 36 | add_definitions(-DLUA_USE_LINUX) 37 | elseif(UNIX) 38 | add_definitions(-DLUA_USE_POSIX) 39 | else() 40 | message(WARNING "Unknown target, the library might lack features.") 41 | endif() 42 | if(MSVC) 43 | # /Zo Enhance Optimized Debugging (since MSVC 2013). 44 | add_compile_options(/Zo) 45 | endif() 46 | if(WIN32) 47 | set(OUTPUT_NAME_SUFFIX ${PROJECT_VERSION_MAJOR}${PROJECT_VERSION_MINOR}) 48 | else() 49 | set(OUTPUT_NAME_SUFFIX ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}) 50 | endif() 51 | 52 | 53 | # Build objects used by the shared library and executables. 54 | add_library(lua_objects OBJECT ${LUA_SOURCES}) 55 | set_property(TARGET lua_objects PROPERTY POSITION_INDEPENDENT_CODE ON) 56 | 57 | # Build shared library. 58 | add_library(lua_shared SHARED $) 59 | set_target_properties(lua_shared PROPERTIES 60 | OUTPUT_NAME lua${OUTPUT_NAME_SUFFIX}) 61 | if(UNIX) 62 | target_link_libraries(lua_shared m ${CMAKE_DL_LIBS}) 63 | endif() 64 | 65 | # Build executables. 66 | add_executable(lua_binary src/lua.c src/utf8_wmain.c) 67 | set_target_properties(lua_binary PROPERTIES 68 | PDB_NAME lua${OUTPUT_NAME_SUFFIX}.exe 69 | OUTPUT_NAME lua${OUTPUT_NAME_SUFFIX}) 70 | if(APPLE OR CMAKE_SYSTEM_NAME STREQUAL "Linux") 71 | target_link_libraries(lua_binary readline) 72 | endif() 73 | # Link dynamically to ensure a shared VM when loading C libraries. 74 | target_link_libraries(lua_binary lua_shared) 75 | 76 | add_executable(luac_binary src/luac.c src/utf8_wmain.c 77 | $) 78 | set_target_properties(luac_binary PROPERTIES 79 | OUTPUT_NAME luac${OUTPUT_NAME_SUFFIX}) 80 | if(UNIX) 81 | target_link_libraries(luac_binary m ${CMAKE_DL_LIBS}) 82 | endif() 83 | 84 | if(WIN32 AND MSVC) 85 | # LUA_BUILD_AS_DLL results in LNK4217 warnings while linking the executables 86 | # since these statically link with objects that export symbols which are 87 | # locally used by the executables. Ignore to avoid recompiling. 88 | set_target_properties(luac_binary PROPERTIES 89 | LINK_FLAGS "/ignore:4217") 90 | endif() 91 | 92 | 93 | install(FILES ${LUA_HEADERS} DESTINATION include) 94 | if(WIN32) 95 | install(TARGETS lua_shared lua_binary luac_binary DESTINATION .) 96 | install(FILES $ 97 | CONFIGURATIONS Debug RelWithDebInfo DESTINATION .) 98 | 99 | # Add build files for documentation purposes. 100 | install(FILES CMakeLists.txt build-all.ps1 prepare-sources.cmake README.md 101 | DESTINATION .) 102 | install(FILES src/utf8_wrappers.c src/utf8_wrappers.h src/utf8_wmain.c 103 | DESTINATION src) 104 | else() 105 | install(TARGETS lua_shared LIBRARY DESTINATION lib) 106 | install(TARGETS lua_binary luac_binary RUNTIME DESTINATION bin) 107 | endif() 108 | 109 | if(WIN32) 110 | if(CMAKE_GENERATOR_PLATFORM STREQUAL "Win32") 111 | set(name lua-${PROJECT_VERSION}-unicode-win32) 112 | elseif(CMAKE_GENERATOR_PLATFORM STREQUAL "x64") 113 | set(name lua-${PROJECT_VERSION}-unicode-win64) 114 | else() 115 | message(FATAL_ERROR "Unexpected generator platform") 116 | endif() 117 | if(MSVC14) 118 | # VS 2015 / VS 2017 (using VCRUNTIME140.dll). 119 | set(name "${name}-vc14") 120 | elseif(MSVC_TOOLSET_VERSION) # Requires CMake 3.12 121 | math(EXPR msvc_major "${MSVC_TOOLSET_VERSION} / 10") 122 | set(name "${name}-vc${msvc_major}") 123 | endif() 124 | set(CMAKE_INSTALL_PREFIX ${name}) 125 | add_custom_command(OUTPUT ${name}.zip 126 | COMMAND "${CMAKE_COMMAND}" -E remove_directory "${name}" 127 | COMMAND "${CMAKE_COMMAND}" --build . --target install --config $ 128 | COMMAND "${CMAKE_COMMAND}" -E tar cf "${name}.zip" --format=zip "${name}" 129 | VERBATIM) 130 | add_custom_target(create-zip DEPENDS ${name}.zip) 131 | message(STATUS "Ready to build ${name}") 132 | endif() 133 | --------------------------------------------------------------------------------