From: Rafael Kitover Date: Thu, 27 Aug 2020 19:23:26 +0000 (+0000) Subject: Support building on MSVC (#632) X-Git-Tag: v4.0~161 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=96e06e2bb7441fd839e1467cb8204c9149d9bbff;p=thirdparty%2Fccache.git Support building on MSVC (#632) With these changes, the project builds with Visual Studio 2019, unit tests pass and it works correctly with mingw gcc. NOTE: The very latest version of Visual Studio 2019 is required, because there was just a necessary fix for template arguments. Tested building and running unit tests on Windows+MSVC, Windows+MinGW, Linux and macOS. - Enable `ZSTD_FROM_INTERNET` by default for MSVC when not using vcpkg or conan. - Add include tests for some standard UNIX headers not available on MSVC. - Add necessary MSVC compiler flags. - In `Args::from_gcc_atfile()` iterate over the string via `c_str()` instead of `cbegin()`, the MSVC string character iterator does not include the ending null byte. - Misc. minor cmake fix-ups. - Add some headers that are not implicitly included from other headers like ``, ``, `` and `` in some places, gcc does this but MSVC does not. - Add `std::filesystem` version of `Util::traverse()` when dirent.h is not available, which is preferred for performance reasons. - Add implementations of the following functions that are not available in MSVC in Win32Util.cpp: `gettimeofday()`, `localtime_r()`, `asprintf()`. - Add Windows implementation of `getopt_long()` from https://www.codeproject.com/Articles/157001/Full-getopt-Port-for-Unicode-and-Multibyte-Microso to third_party/win32. - Add some compatibility typedefs, constants and macros to the `_WIN32` section of system.hpp, as well as the prototypes for the functions added to Win32Util.cpp. - Fix up unit tests expecting '/' separated paths to expect paths delimited by `DIR_DELIM_CH`. - Invoke test/run with bash from cmake, necessary on msys2+mingw64, many fail, there is more work to do here. - Set the warning level to `/W4` and silence all the uninteresting warning types. Compiles with no warnings now. - Switch to using standard C++ attributes `[[nodiscard]]` and `[[maybe_unused]]` and define macros for gcc for their equivalents. - `#define DOCTEST_CONFIG_USE_STD_HEADERS` for MSVC only, because it requires explicitly including ``. - Add vim files to .gitignore. Signed-off-by: Rafael Kitover --- diff --git a/.gitignore b/.gitignore index 4ccbc98cb..bb7158fcc 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,7 @@ testdir/ # Visual Studio Code /.vscode/ + +# Vim +.*.sw? +.*.un~ diff --git a/CMakeLists.txt b/CMakeLists.txt index edd06ae14..8b1ee1b5e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,7 +38,14 @@ endif() # # Third party # -option(ZSTD_FROM_INTERNET "Download and use libzstd from the Internet" OFF) +set(ZSTD_FROM_INTERNET_DEFAULT OFF) + +# Default to downloading deps for Visual Studio, unless using a package manager. +if(MSVC AND NOT CMAKE_TOOLCHAIN_FILE MATCHES "vcpkg|conan") + set(ZSTD_FROM_INTERNET_DEFAULT ON) +endif() + +option(ZSTD_FROM_INTERNET "Download and use libzstd from the Internet" ${ZSTD_FROM_INTERNET_DEFAULT}) find_package(zstd 1.1.2 REQUIRED) # diff --git a/LGPL-3.0.txt b/LGPL-3.0.txt new file mode 100644 index 000000000..0a041280b --- /dev/null +++ b/LGPL-3.0.txt @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/LICENSE.adoc b/LICENSE.adoc index 68b01afb1..5a6e3af6e 100644 --- a/LICENSE.adoc +++ b/LICENSE.adoc @@ -158,6 +158,17 @@ https://www.postgresql.org[PostgreSQL] and has the following license text: ------------------------------------------------------------------------------- +src/third_party/win32/getopt.[hc] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This implementation of `getopt_long()` for Win32 was taken from +https://www.codeproject.com/Articles/157001/Full-getopt-Port-for-Unicode-and-Multibyte-Microso +and is licensed under the LGPL. + +The full license text can be found in LGPL-3.0.txt and at +https://www.gnu.org/licenses/lgpl-3.0.html. + + src/third_party/nonstd/optional.hpp ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/cmake/GenerateConfigurationFile.cmake b/cmake/GenerateConfigurationFile.cmake index d16964876..7e9020ee4 100644 --- a/cmake/GenerateConfigurationFile.cmake +++ b/cmake/GenerateConfigurationFile.cmake @@ -7,8 +7,15 @@ set(include_files sys/mman.h sys/time.h sys/wait.h + sys/file.h syslog.h - termios.h) + termios.h + dirent.h + strings.h + unistd.h + utime.h + sys/utime.h + varargs.h) foreach(include_file IN ITEMS ${include_files}) string(TOUPPER ${include_file} include_var) string(REGEX REPLACE "[/.]" "_" include_var ${include_var}) @@ -65,5 +72,9 @@ endif() # alias set(MTR_ENABLED "${ENABLE_TRACING}") +# Check sizeof(int). +include(CheckTypeSize) +check_type_size(int SIZEOF_INT) + configure_file(${CMAKE_SOURCE_DIR}/cmake/config.h.in ${CMAKE_BINARY_DIR}/config.h @ONLY) diff --git a/cmake/StandardSettings.cmake b/cmake/StandardSettings.cmake index 9ca91a2e1..a30f82072 100644 --- a/cmake/StandardSettings.cmake +++ b/cmake/StandardSettings.cmake @@ -47,4 +47,6 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "^GNU|Clang$") standard_settings INTERFACE -fsanitize=${LIST_OF_SANITIZERS}) endif() +elseif(MSVC) + target_compile_options(standard_settings INTERFACE /std:c++latest /Zc:preprocessor /Zc:__cplusplus /D_CRT_SECURE_NO_WARNINGS) endif() diff --git a/cmake/StandardWarnings.cmake b/cmake/StandardWarnings.cmake index f5f954c82..f879da091 100644 --- a/cmake/StandardWarnings.cmake +++ b/cmake/StandardWarnings.cmake @@ -137,4 +137,24 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") add_target_compile_flag_if_supported( standard_warnings "-Wno-unused-variable") endif() +elseif(MSVC) + # Remove any warning level flags added by cmake. + string(REGEX REPLACE "/W[0-4]" "" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") + string(REGEX REPLACE "/W[0-4]" "" CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS}") + string(REGEX REPLACE "/W[0-4]" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + + target_compile_options( + standard_warnings + INTERFACE + /W4 + # Ignore bad macro in winbase.h triggered by /Zc:preprocessor + /wd5105 + # Conversion warnings. + /wd4244 + /wd4267 + # Assignment in conditional. + /wd4706 + # Non-underscore-prefixed POSIX functions. + /wd4996 + ) endif() diff --git a/cmake/config.h.in b/cmake/config.h.in index 34935e05f..30393c84f 100644 --- a/cmake/config.h.in +++ b/cmake/config.h.in @@ -52,6 +52,8 @@ # pragma clang diagnostic pop #endif +#define SIZEOF_INT @SIZEOF_INT@ + #cmakedefine MTR_ENABLED /* Define to 1 if you have the `asctime_r' function. */ @@ -126,9 +128,30 @@ /* Define to 1 if you have that is POSIX.1 compatible. */ #cmakedefine HAVE_SYS_WAIT_H +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_FILE_H + /* Define to 1 if you have the header file. */ #cmakedefine HAVE_TERMIOS_H +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_DIRENT_H + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STRINGS_H + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_UNISTD_H + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_UTIME_H + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_UTIME_H + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_VARARGS_H + /* Define to 1 if you have the `unsetenv' function. */ #cmakedefine HAVE_UNSETENV diff --git a/misc/format-files b/misc/format-files index 3a79f7f6d..d54978433 100755 --- a/misc/format-files +++ b/misc/format-files @@ -24,7 +24,7 @@ for arg in "$@"; do done if [ -n "$all" ]; then - exec "$0" $check $(git ls-files '*.[ch]' '*.[ch]pp' ':!:src/third_party') + exec sh "$0" $check $(git ls-files '*.[ch]' '*.[ch]pp' ':!:src/third_party') fi clang_format=${CLANG_FORMAT:-clang-format} diff --git a/src/Args.cpp b/src/Args.cpp index 9eff36be4..034233fbf 100644 --- a/src/Args.cpp +++ b/src/Args.cpp @@ -53,7 +53,7 @@ Args::from_gcc_atfile(const std::string& filename) } Args args; - auto pos = argtext.cbegin(); + auto pos = argtext.c_str(); std::string argbuf; argbuf.resize(argtext.length() + 1); auto argpos = argbuf.begin(); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 27c3f1fe3..250d2e19d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -57,7 +57,7 @@ if(WIN32) if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") target_link_libraries( ccache_lib PRIVATE -static gcc stdc++ winpthread -dynamic) - else() + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") target_link_libraries(ccache_lib PRIVATE -static c++ -dynamic) endif() endif() diff --git a/src/Lockfile.cpp b/src/Lockfile.cpp index ec6cfa83f..5186a802b 100644 --- a/src/Lockfile.cpp +++ b/src/Lockfile.cpp @@ -27,6 +27,8 @@ #include "third_party/fmt/core.h" +#include + using Logging::log; namespace { diff --git a/src/ProgressBar.cpp b/src/ProgressBar.cpp index 1e01e0aef..cb0811b24 100644 --- a/src/ProgressBar.cpp +++ b/src/ProgressBar.cpp @@ -28,6 +28,8 @@ # include #endif +#include + namespace { const size_t k_max_width = 120; diff --git a/src/Result.cpp b/src/Result.cpp index 3091e97b4..99e20c5cf 100644 --- a/src/Result.cpp +++ b/src/Result.cpp @@ -31,6 +31,8 @@ #include "exceptions.hpp" #include "stats.hpp" +#include + // Result data format // ================== // diff --git a/src/Util.cpp b/src/Util.cpp index 15cb20cff..6b78b743e 100644 --- a/src/Util.cpp +++ b/src/Util.cpp @@ -28,6 +28,10 @@ #include #include +#ifndef HAVE_DIRENT_H +# include +#endif + #ifdef HAVE_PWD_H # include #endif @@ -477,7 +481,7 @@ fallocate(int fd, long new_size) int err = 0; try { write_fd(fd, buf, bytes_to_write); - } catch (Error& e) { + } catch ([[maybe_unused]] Error& e) { err = errno; } lseek(fd, saved_pos, SEEK_SET); @@ -782,7 +786,7 @@ is_nfs_fd(int fd, bool* is_nfs) } #else int -is_nfs_fd([[gnu::unused]] int fd, [[gnu::unused]] bool* is_nfs) +is_nfs_fd([[maybe_unused]] int fd, [[maybe_unused]] bool* is_nfs) { return -1; } @@ -959,7 +963,7 @@ int parse_int(const std::string& value) { size_t end; - long result; + long result = 0; bool failed = false; try { result = std::stoi(value, &end, 10); @@ -1017,7 +1021,7 @@ uint32_t parse_uint32(const std::string& value) { size_t end; - long long result; + long long result = 0; bool failed = false; try { result = std::stoll(value, &end, 10); @@ -1250,7 +1254,7 @@ setenv(const std::string& name, const std::string& value) #else char* string; asprintf(&string, "%s=%s", name.c_str(), value.c_str()); - putenv(string); // Leak to environment. + putenv(string); // Leak to environment. #endif } @@ -1306,6 +1310,8 @@ to_lowercase(string_view string) return result; } +#ifdef HAVE_DIRENT_H + void traverse(const std::string& path, const TraverseVisitor& visitor) { @@ -1320,11 +1326,11 @@ traverse(const std::string& path, const TraverseVisitor& visitor) std::string entry_path = path + "/" + entry->d_name; bool is_dir; -#ifdef _DIRENT_HAVE_D_TYPE +# ifdef _DIRENT_HAVE_D_TYPE if (entry->d_type != DT_UNKNOWN) { is_dir = entry->d_type == DT_DIR; } else -#endif +# endif { auto stat = Stat::lstat(entry_path); if (!stat) { @@ -1352,6 +1358,31 @@ traverse(const std::string& path, const TraverseVisitor& visitor) } } +#else // If not available, use the C++17 std::filesystem implementation. + +void +traverse(const std::string& path, const TraverseVisitor& visitor) +{ + if (std::filesystem::is_directory(path)) { + for (auto&& p : std::filesystem::directory_iterator(path)) { + std::string entry = p.path().string(); + + if (p.is_directory()) { + traverse(entry, visitor); + } else { + visitor(entry, false); + } + } + visitor(path, true); + } else if (std::filesystem::exists(path)) { + visitor(path, false); + } else { + throw Error("failed to open directory {}: {}", path, strerror(errno)); + } +} + +#endif + bool unlink_safe(const std::string& path, UnlinkLog unlink_log) { @@ -1465,6 +1496,10 @@ write_file(const std::string& path, const std::string& data, std::ios_base::openmode open_mode) { + if (path.empty()) { + throw Error("No such file or directory"); + } + open_mode |= std::ios::out; std::ofstream file(path, open_mode); if (!file) { diff --git a/src/Util.hpp b/src/Util.hpp index 712f06ad9..f83365683 100644 --- a/src/Util.hpp +++ b/src/Util.hpp @@ -26,6 +26,7 @@ #include "third_party/nonstd/string_view.hpp" #include +#include #include #include #include @@ -125,8 +126,7 @@ ends_with(nonstd::string_view string, nonstd::string_view suffix) // Expand all instances of $VAR or ${VAR}, where VAR is an environment variable, // in `str`. Throws `Error` if one of the environment variables. -[[gnu::warn_unused_result]] std::string -expand_environment_variables(const std::string& str); +[[nodiscard]] std::string expand_environment_variables(const std::string& str); // Extends file size to at least new_size by calling posix_fallocate() if // supported, otherwise by writing zeros last to the file. @@ -406,16 +406,13 @@ starts_with(nonstd::string_view string, nonstd::string_view prefix) } // Returns a copy of string with the specified ANSI CSI sequences removed. -[[gnu::warn_unused_result]] std::string -strip_ansi_csi_seqs(nonstd::string_view string); +[[nodiscard]] std::string strip_ansi_csi_seqs(nonstd::string_view string); // Strip whitespace from left and right side of a string. -[[gnu::warn_unused_result]] std::string -strip_whitespace(const std::string& string); +[[nodiscard]] std::string strip_whitespace(const std::string& string); // Convert a string to lowercase. -[[gnu::warn_unused_result]] std::string -to_lowercase(nonstd::string_view string); +[[nodiscard]] std::string to_lowercase(nonstd::string_view string); // Traverse `path` recursively (postorder, i.e. files are visited before their // parent directory). diff --git a/src/Win32Util.cpp b/src/Win32Util.cpp index ce22684a6..17c3b6330 100644 --- a/src/Win32Util.cpp +++ b/src/Win32Util.cpp @@ -20,6 +20,9 @@ #include "Util.hpp" +#include +#include + namespace Win32Util { std::string @@ -91,3 +94,75 @@ argv_to_string(const char* const* argv, const std::string& prefix) } } // namespace Win32Util + +// From: https://stackoverflow.com/a/58162122/262458 +#ifdef _MSC_VER +int +gettimeofday(struct timeval* tp, [[maybe_unused]] struct timezone* tzp) +{ + namespace sc = std::chrono; + sc::system_clock::duration d = sc::system_clock::now().time_since_epoch(); + sc::seconds s = sc::duration_cast(d); + tp->tv_sec = static_cast(s.count()); + tp->tv_usec = + static_cast(sc::duration_cast(d - s).count()); + + return 0; +} +#endif + +void +usleep(int64_t usec) +{ + std::this_thread::sleep_for(std::chrono::microseconds(usec)); +} + +struct tm* +localtime_r(time_t* _clock, struct tm* _result) +{ + struct tm* p = localtime(_clock); + + if (p) + *(_result) = *p; + + return p; +} + +// From: https://stackoverflow.com/a/40160038/262458 +#ifdef _MSC_VER +int +vasprintf(char** strp, const char* fmt, va_list ap) +{ + // _vscprintf tells you how big the buffer needs to be + int len = _vscprintf(fmt, ap); + if (len == -1) { + return -1; + } + size_t size = (size_t)len + 1; + char* str = static_cast(malloc(size)); + if (!str) { + return -1; + } + // vsprintf_s is the "secure" version of vsprintf + int r = vsprintf_s(str, len + 1, fmt, ap); + if (r == -1) { + free(str); + return -1; + } + *strp = str; + return r; +} +#endif + +// Also from: https://stackoverflow.com/a/40160038/262458 +#ifdef _MSC_VER +int +asprintf(char** strp, const char* fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + int r = vasprintf(strp, fmt, ap); + va_end(ap); + return r; +} +#endif diff --git a/src/ZstdCompressor.cpp b/src/ZstdCompressor.cpp index 4e054942a..c50df840d 100644 --- a/src/ZstdCompressor.cpp +++ b/src/ZstdCompressor.cpp @@ -21,6 +21,8 @@ #include "Logging.hpp" #include "exceptions.hpp" +#include + using Logging::log; ZstdCompressor::ZstdCompressor(FILE* stream, int8_t compression_level) diff --git a/src/ccache.cpp b/src/ccache.cpp index c9e8ec82e..5526bc7b8 100644 --- a/src/ccache.cpp +++ b/src/ccache.cpp @@ -56,6 +56,8 @@ #ifdef HAVE_GETOPT_LONG # include +#elif defined(_WIN32) +# include "third_party/win32/getopt.h" #else # include "third_party/getopt_long.h" #endif diff --git a/src/system.hpp b/src/system.hpp index c96ea26e3..79d07effa 100644 --- a/src/system.hpp +++ b/src/system.hpp @@ -25,7 +25,10 @@ #include "config.h" -#include +#ifdef HAVE_SYS_FILE_H +# include +#endif + #ifdef HAVE_SYS_MMAN_H # include #endif @@ -43,15 +46,35 @@ #include #include #include +#include #include #include #include #include -#include + +#ifdef HAVE_DIRENT_H +# include +#endif + #include -#include -#include -#include + +#ifdef HAVE_STRINGS_H +# include +#endif + +#ifdef HAVE_UNISTD_H +# include +#endif + +#ifdef HAVE_UTIME_H +# include +#elif defined(HAVE_SYS_UTIME_H) +# include +#endif + +#ifdef HAVE_VARARGS_H +# include +#endif // AIX/PASE does not properly define usleep within its headers. However, the // function is available in libc.a. This extern define ensures that it is @@ -60,8 +83,6 @@ extern int usleep(useconds_t); #endif -extern char** environ; - #define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0])) // Buffer size for I/O operations. Should be a multiple of 4 KiB. @@ -76,17 +97,76 @@ const size_t READ_BUFFER_SIZE = 65536; // _WIN32_WINNT is set in the generated header config.h # error _WIN32_WINNT is undefined # endif + +# ifdef _MSC_VER +typedef int mode_t; +typedef int pid_t; +# endif + +# ifndef __MINGW32__ +typedef int64_t ssize_t; +# endif + +// Defined in Win32Util.cpp +void usleep(int64_t usec); +struct tm* localtime_r(time_t* _clock, struct tm* _result); + +# ifdef _MSC_VER +int gettimeofday(struct timeval* tp, struct timezone* tzp); +int asprintf(char** strp, const char* fmt, ...); +# endif + +// From: +// http://mesos.apache.org/api/latest/c++/3rdparty_2stout_2include_2stout_2windows_8hpp_source.html +# ifdef _MSC_VER +const mode_t S_IRUSR = mode_t(_S_IREAD); +const mode_t S_IWUSR = mode_t(_S_IWRITE); +# endif + +// From https://stackoverflow.com/a/62371749/262458 +# define _CRT_INTERNAL_NONSTDC_NAMES 1 +# include +# if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG) +# define S_ISREG(m) (((m)&S_IFMT) == S_IFREG) +# endif +# if !defined(S_ISDIR) && defined(S_IFMT) && defined(S_IFDIR) +# define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR) +# endif + +# include +# include +# include +# define NOMINMAX 1 # include -# define mkdir(a, b) mkdir(a) +# define mkdir(a, b) _mkdir(a) # define link(src, dst) (CreateHardLink(dst, src, nullptr) ? 0 : -1) # define execv(a, b) win32execute(a, b, 0, -1, -1) +# define strncasecmp _strnicmp +# define strcasecmp _stricmp + +# ifdef _MSC_VER +# define PATH_MAX MAX_PATH +# endif + +# ifdef _MSC_VER +# define DLLIMPORT __declspec(dllimport) +# else +# define DLLIMPORT +# endif + +# define STDIN_FILENO 0 +# define STDOUT_FILENO 1 +# define STDERR_FILENO 2 # define DIR_DELIM_CH '\\' # define PATH_DELIM ";" #else +# define DLLIMPORT # define DIR_DELIM_CH '/' # define PATH_DELIM ":" #endif +DLLIMPORT extern char** environ; + // Work with silly DOS binary open. #ifndef O_BINARY # define O_BINARY 0 @@ -102,3 +182,9 @@ const size_t READ_BUFFER_SIZE = 65536; #else # define IS_TRIVIALLY_COPYABLE(T) std::is_trivially_copyable::value #endif + +// GCC version of a couple of standard C++ attributes +#ifdef __GNUC__ +# define nodiscard gnu::warn_unused_result +# define maybe_unused gnu::unused +#endif diff --git a/src/third_party/CMakeLists.txt b/src/third_party/CMakeLists.txt index ed52d6e0f..24efe6e00 100644 --- a/src/third_party/CMakeLists.txt +++ b/src/third_party/CMakeLists.txt @@ -1,4 +1,11 @@ -add_library(third_party_lib STATIC format.cpp getopt_long.c xxhash.c) +if(NOT MSVC) + add_library(third_party_lib STATIC format.cpp getopt_long.c xxhash.c) +else() + add_library(third_party_lib STATIC format.cpp win32/getopt.c xxhash.c) + + target_compile_definitions(third_party_lib PUBLIC -DSTATIC_GETOPT) +endif() + if(ENABLE_TRACING) target_sources(third_party_lib PRIVATE minitrace.c) endif() @@ -13,17 +20,27 @@ target_link_libraries(third_party_lib INTERFACE blake3) # These warnings are enabled by default even without e.g. -Wall, but we don't # want them in third_party. -target_compile_options( - third_party_lib - PRIVATE - $<$:-Wno-implicit-function-declaration - -Wno-int-conversion>) +if(CMAKE_CXX_COMPILER_ID MATCHES "^GNU|Clang$") + target_compile_options( + third_party_lib + PRIVATE + $<$:-Wno-implicit-function-declaration + -Wno-int-conversion>) +endif() + if(CMAKE_C_COMPILER_ID STREQUAL "GNU") target_compile_options( third_party_lib PRIVATE $<$:-Wno-attributes>) endif() +# Silence warning from winbase.h due to /Zc:preprocessor. +if(MSVC) + target_compile_options( + third_party_lib + PRIVATE /wd5105) +endif() + # The headers are included from the rest of the project, so turn off warnings as # required. if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") diff --git a/src/third_party/win32/getopt.c b/src/third_party/win32/getopt.c new file mode 100644 index 000000000..6f98bbe70 --- /dev/null +++ b/src/third_party/win32/getopt.c @@ -0,0 +1,975 @@ +/* Getopt for Microsoft C +This code is a modification of the Free Software Foundation, Inc. +Getopt library for parsing command line argument the purpose was +to provide a Microsoft Visual C friendly derivative. This code +provides functionality for both Unicode and Multibyte builds. + +Date: 02/03/2011 - Ludvik Jerabek - Initial Release +Version: 1.0 +Comment: Supports getopt, getopt_long, and getopt_long_only +and POSIXLY_CORRECT environment flag +License: LGPL + +Revisions: + +02/03/2011 - Ludvik Jerabek - Initial Release +02/20/2011 - Ludvik Jerabek - Fixed compiler warnings at Level 4 +07/05/2011 - Ludvik Jerabek - Added no_argument, required_argument, optional_argument defs +08/03/2011 - Ludvik Jerabek - Fixed non-argument runtime bug which caused runtime exception +08/09/2011 - Ludvik Jerabek - Added code to export functions for DLL and LIB +02/15/2012 - Ludvik Jerabek - Fixed _GETOPT_THROW definition missing in implementation file +08/01/2012 - Ludvik Jerabek - Created separate functions for char and wchar_t characters so single dll can do both unicode and ansi +10/15/2012 - Ludvik Jerabek - Modified to match latest GNU features +06/19/2015 - Ludvik Jerabek - Fixed maximum option limitation caused by option_a (255) and option_w (65535) structure val variable + +**DISCLAIMER** +THIS MATERIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +EITHER EXPRESS OR IMPLIED, INCLUDING, BUT Not LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE, OR NON-INFRINGEMENT. SOME JURISDICTIONS DO NOT ALLOW THE +EXCLUSION OF IMPLIED WARRANTIES, SO THE ABOVE EXCLUSION MAY NOT +APPLY TO YOU. IN NO EVENT WILL I BE LIABLE TO ANY PARTY FOR ANY +DIRECT, INDIRECT, SPECIAL OR OTHER CONSEQUENTIAL DAMAGES FOR ANY +USE OF THIS MATERIAL INCLUDING, WITHOUT LIMITATION, ANY LOST +PROFITS, BUSINESS INTERRUPTION, LOSS OF PROGRAMS OR OTHER DATA ON +YOUR INFORMATION HANDLING SYSTEM OR OTHERWISE, EVEN If WE ARE +EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +*/ +#ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS +#endif +#include +#include +#include +#include "getopt.h" + +#ifdef __cplusplus + #define _GETOPT_THROW throw() +#else + #define _GETOPT_THROW +#endif + +int optind = 1; +int opterr = 1; +int optopt = '?'; +enum ENUM_ORDERING { REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER }; + +// +// +// Ansi structures and functions follow +// +// + +static struct _getopt_data_a +{ + int optind; + int opterr; + int optopt; + char *optarg; + int __initialized; + char *__nextchar; + enum ENUM_ORDERING __ordering; + int __posixly_correct; + int __first_nonopt; + int __last_nonopt; +} getopt_data_a; +char *optarg_a; + +static void exchange_a(char **argv, struct _getopt_data_a *d) +{ + int bottom = d->__first_nonopt; + int middle = d->__last_nonopt; + int top = d->optind; + char *tem; + while (top > middle && middle > bottom) + { + if (top - middle > middle - bottom) + { + int len = middle - bottom; + register int i; + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[top - (middle - bottom) + i]; + argv[top - (middle - bottom) + i] = tem; + } + top -= len; + } + else + { + int len = top - middle; + register int i; + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[middle + i]; + argv[middle + i] = tem; + } + bottom += len; + } + } + d->__first_nonopt += (d->optind - d->__last_nonopt); + d->__last_nonopt = d->optind; +} +static const char *_getopt_initialize_a (const char *optstring, struct _getopt_data_a *d, int posixly_correct) +{ + d->__first_nonopt = d->__last_nonopt = d->optind; + d->__nextchar = NULL; + d->__posixly_correct = posixly_correct | !!getenv("POSIXLY_CORRECT"); + if (optstring[0] == '-') + { + d->__ordering = RETURN_IN_ORDER; + ++optstring; + } + else if (optstring[0] == '+') + { + d->__ordering = REQUIRE_ORDER; + ++optstring; + } + else if (d->__posixly_correct) + d->__ordering = REQUIRE_ORDER; + else + d->__ordering = PERMUTE; + return optstring; +} +int _getopt_internal_r_a (int argc, char *const *argv, const char *optstring, const struct option_a *longopts, int *longind, int long_only, struct _getopt_data_a *d, int posixly_correct) +{ + int print_errors = d->opterr; + if (argc < 1) + return -1; + d->optarg = NULL; + if (d->optind == 0 || !d->__initialized) + { + if (d->optind == 0) + d->optind = 1; + optstring = _getopt_initialize_a (optstring, d, posixly_correct); + d->__initialized = 1; + } + else if (optstring[0] == '-' || optstring[0] == '+') + optstring++; + if (optstring[0] == ':') + print_errors = 0; + if (d->__nextchar == NULL || *d->__nextchar == '\0') + { + if (d->__last_nonopt > d->optind) + d->__last_nonopt = d->optind; + if (d->__first_nonopt > d->optind) + d->__first_nonopt = d->optind; + if (d->__ordering == PERMUTE) + { + if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind) + exchange_a ((char **) argv, d); + else if (d->__last_nonopt != d->optind) + d->__first_nonopt = d->optind; + while (d->optind < argc && (argv[d->optind][0] != '-' || argv[d->optind][1] == '\0')) + d->optind++; + d->__last_nonopt = d->optind; + } + if (d->optind != argc && !strcmp(argv[d->optind], "--")) + { + d->optind++; + if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind) + exchange_a((char **) argv, d); + else if (d->__first_nonopt == d->__last_nonopt) + d->__first_nonopt = d->optind; + d->__last_nonopt = argc; + d->optind = argc; + } + if (d->optind == argc) + { + if (d->__first_nonopt != d->__last_nonopt) + d->optind = d->__first_nonopt; + return -1; + } + if ((argv[d->optind][0] != '-' || argv[d->optind][1] == '\0')) + { + if (d->__ordering == REQUIRE_ORDER) + return -1; + d->optarg = argv[d->optind++]; + return 1; + } + d->__nextchar = (argv[d->optind] + 1 + (longopts != NULL && argv[d->optind][1] == '-')); + } + if (longopts != NULL && (argv[d->optind][1] == '-' || (long_only && (argv[d->optind][2] || !strchr(optstring, argv[d->optind][1]))))) + { + char *nameend; + unsigned int namelen; + const struct option_a *p; + const struct option_a *pfound = NULL; + struct option_list + { + const struct option_a *p; + struct option_list *next; + } *ambig_list = NULL; + int exact = 0; + int indfound = -1; + int option_index; + for (nameend = d->__nextchar; *nameend && *nameend != '='; nameend++); + namelen = (unsigned int)(nameend - d->__nextchar); + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp(p->name, d->__nextchar, namelen)) + { + if (namelen == (unsigned int)strlen(p->name)) + { + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + pfound = p; + indfound = option_index; + } + else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val) + { + struct option_list *newp = (struct option_list*)alloca(sizeof(*newp)); + newp->p = p; + newp->next = ambig_list; + ambig_list = newp; + } + } + if (ambig_list != NULL && !exact) + { + if (print_errors) + { + struct option_list first; + first.p = pfound; + first.next = ambig_list; + ambig_list = &first; + fprintf (stderr, "%s: option '%s' is ambiguous; possibilities:", argv[0], argv[d->optind]); + do + { + fprintf (stderr, " '--%s'", ambig_list->p->name); + ambig_list = ambig_list->next; + } + while (ambig_list != NULL); + fputc ('\n', stderr); + } + d->__nextchar += strlen(d->__nextchar); + d->optind++; + d->optopt = 0; + return '?'; + } + if (pfound != NULL) + { + option_index = indfound; + d->optind++; + if (*nameend) + { + if (pfound->has_arg) + d->optarg = nameend + 1; + else + { + if (print_errors) + { + if (argv[d->optind - 1][1] == '-') + { + fprintf(stderr, "%s: option '--%s' doesn't allow an argument\n",argv[0], pfound->name); + } + else + { + fprintf(stderr, "%s: option '%c%s' doesn't allow an argument\n",argv[0], argv[d->optind - 1][0],pfound->name); + } + } + d->__nextchar += strlen(d->__nextchar); + d->optopt = pfound->val; + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (d->optind < argc) + d->optarg = argv[d->optind++]; + else + { + if (print_errors) + { + fprintf(stderr,"%s: option '--%s' requires an argument\n",argv[0], pfound->name); + } + d->__nextchar += strlen(d->__nextchar); + d->optopt = pfound->val; + return optstring[0] == ':' ? ':' : '?'; + } + } + d->__nextchar += strlen(d->__nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + if (!long_only || argv[d->optind][1] == '-' || strchr(optstring, *d->__nextchar) == NULL) + { + if (print_errors) + { + if (argv[d->optind][1] == '-') + { + fprintf(stderr, "%s: unrecognized option '--%s'\n",argv[0], d->__nextchar); + } + else + { + fprintf(stderr, "%s: unrecognized option '%c%s'\n",argv[0], argv[d->optind][0], d->__nextchar); + } + } + d->__nextchar = (char *)""; + d->optind++; + d->optopt = 0; + return '?'; + } + } + { + char c = *d->__nextchar++; + char *temp = (char*)strchr(optstring, c); + if (*d->__nextchar == '\0') + ++d->optind; + if (temp == NULL || c == ':' || c == ';') + { + if (print_errors) + { + fprintf(stderr, "%s: invalid option -- '%c'\n", argv[0], c); + } + d->optopt = c; + return '?'; + } + if (temp[0] == 'W' && temp[1] == ';') + { + char *nameend; + const struct option_a *p; + const struct option_a *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = 0; + int option_index; + if (longopts == NULL) + goto no_longs; + if (*d->__nextchar != '\0') + { + d->optarg = d->__nextchar; + d->optind++; + } + else if (d->optind == argc) + { + if (print_errors) + { + fprintf(stderr,"%s: option requires an argument -- '%c'\n",argv[0], c); + } + d->optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + return c; + } + else + d->optarg = argv[d->optind++]; + for (d->__nextchar = nameend = d->optarg; *nameend && *nameend != '='; nameend++); + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp(p->name, d->__nextchar, nameend - d->__nextchar)) + { + if ((unsigned int) (nameend - d->__nextchar) == strlen(p->name)) + { + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + pfound = p; + indfound = option_index; + } + else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val) + ambig = 1; + } + if (ambig && !exact) + { + if (print_errors) + { + fprintf(stderr, "%s: option '-W %s' is ambiguous\n",argv[0], d->optarg); + } + d->__nextchar += strlen(d->__nextchar); + d->optind++; + return '?'; + } + if (pfound != NULL) + { + option_index = indfound; + if (*nameend) + { + if (pfound->has_arg) + d->optarg = nameend + 1; + else + { + if (print_errors) + { + fprintf(stderr, "%s: option '-W %s' doesn't allow an argument\n",argv[0], pfound->name); + } + d->__nextchar += strlen(d->__nextchar); + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (d->optind < argc) + d->optarg = argv[d->optind++]; + else + { + if (print_errors) + { + fprintf(stderr, "%s: option '-W %s' requires an argument\n",argv[0], pfound->name); + } + d->__nextchar += strlen(d->__nextchar); + return optstring[0] == ':' ? ':' : '?'; + } + } + else + d->optarg = NULL; + d->__nextchar += strlen(d->__nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } +no_longs: + d->__nextchar = NULL; + return 'W'; + } + if (temp[1] == ':') + { + if (temp[2] == ':') + { + if (*d->__nextchar != '\0') + { + d->optarg = d->__nextchar; + d->optind++; + } + else + d->optarg = NULL; + d->__nextchar = NULL; + } + else + { + if (*d->__nextchar != '\0') + { + d->optarg = d->__nextchar; + d->optind++; + } + else if (d->optind == argc) + { + if (print_errors) + { + fprintf(stderr,"%s: option requires an argument -- '%c'\n",argv[0], c); + } + d->optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + } + else + d->optarg = argv[d->optind++]; + d->__nextchar = NULL; + } + } + return c; + } +} +int _getopt_internal_a (int argc, char *const *argv, const char *optstring, const struct option_a *longopts, int *longind, int long_only, int posixly_correct) +{ + int result; + getopt_data_a.optind = optind; + getopt_data_a.opterr = opterr; + result = _getopt_internal_r_a (argc, argv, optstring, longopts,longind, long_only, &getopt_data_a,posixly_correct); + optind = getopt_data_a.optind; + optarg_a = getopt_data_a.optarg; + optopt = getopt_data_a.optopt; + return result; +} +int getopt_a (int argc, char *const *argv, const char *optstring) _GETOPT_THROW +{ + return _getopt_internal_a (argc, argv, optstring, (const struct option_a *) 0, (int *) 0, 0, 0); +} +int getopt_long_a (int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW +{ + return _getopt_internal_a (argc, argv, options, long_options, opt_index, 0, 0); +} +int getopt_long_only_a (int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW +{ + return _getopt_internal_a (argc, argv, options, long_options, opt_index, 1, 0); +} +int _getopt_long_r_a (int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index, struct _getopt_data_a *d) +{ + return _getopt_internal_r_a (argc, argv, options, long_options, opt_index,0, d, 0); +} +int _getopt_long_only_r_a (int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index, struct _getopt_data_a *d) +{ + return _getopt_internal_r_a (argc, argv, options, long_options, opt_index, 1, d, 0); +} + +// +// +// Unicode Structures and Functions +// +// + +static struct _getopt_data_w +{ + int optind; + int opterr; + int optopt; + wchar_t *optarg; + int __initialized; + wchar_t *__nextchar; + enum ENUM_ORDERING __ordering; + int __posixly_correct; + int __first_nonopt; + int __last_nonopt; +} getopt_data_w; +wchar_t *optarg_w; + +static void exchange_w(wchar_t **argv, struct _getopt_data_w *d) +{ + int bottom = d->__first_nonopt; + int middle = d->__last_nonopt; + int top = d->optind; + wchar_t *tem; + while (top > middle && middle > bottom) + { + if (top - middle > middle - bottom) + { + int len = middle - bottom; + register int i; + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[top - (middle - bottom) + i]; + argv[top - (middle - bottom) + i] = tem; + } + top -= len; + } + else + { + int len = top - middle; + register int i; + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[middle + i]; + argv[middle + i] = tem; + } + bottom += len; + } + } + d->__first_nonopt += (d->optind - d->__last_nonopt); + d->__last_nonopt = d->optind; +} +static const wchar_t *_getopt_initialize_w (const wchar_t *optstring, struct _getopt_data_w *d, int posixly_correct) +{ + d->__first_nonopt = d->__last_nonopt = d->optind; + d->__nextchar = NULL; + d->__posixly_correct = posixly_correct | !!_wgetenv(L"POSIXLY_CORRECT"); + if (optstring[0] == L'-') + { + d->__ordering = RETURN_IN_ORDER; + ++optstring; + } + else if (optstring[0] == L'+') + { + d->__ordering = REQUIRE_ORDER; + ++optstring; + } + else if (d->__posixly_correct) + d->__ordering = REQUIRE_ORDER; + else + d->__ordering = PERMUTE; + return optstring; +} +int _getopt_internal_r_w (int argc, wchar_t *const *argv, const wchar_t *optstring, const struct option_w *longopts, int *longind, int long_only, struct _getopt_data_w *d, int posixly_correct) +{ + int print_errors = d->opterr; + if (argc < 1) + return -1; + d->optarg = NULL; + if (d->optind == 0 || !d->__initialized) + { + if (d->optind == 0) + d->optind = 1; + optstring = _getopt_initialize_w (optstring, d, posixly_correct); + d->__initialized = 1; + } + else if (optstring[0] == L'-' || optstring[0] == L'+') + optstring++; + if (optstring[0] == L':') + print_errors = 0; + if (d->__nextchar == NULL || *d->__nextchar == L'\0') + { + if (d->__last_nonopt > d->optind) + d->__last_nonopt = d->optind; + if (d->__first_nonopt > d->optind) + d->__first_nonopt = d->optind; + if (d->__ordering == PERMUTE) + { + if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind) + exchange_w((wchar_t **) argv, d); + else if (d->__last_nonopt != d->optind) + d->__first_nonopt = d->optind; + while (d->optind < argc && (argv[d->optind][0] != L'-' || argv[d->optind][1] == L'\0')) + d->optind++; + d->__last_nonopt = d->optind; + } + if (d->optind != argc && !wcscmp(argv[d->optind], L"--")) + { + d->optind++; + if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind) + exchange_w((wchar_t **) argv, d); + else if (d->__first_nonopt == d->__last_nonopt) + d->__first_nonopt = d->optind; + d->__last_nonopt = argc; + d->optind = argc; + } + if (d->optind == argc) + { + if (d->__first_nonopt != d->__last_nonopt) + d->optind = d->__first_nonopt; + return -1; + } + if ((argv[d->optind][0] != L'-' || argv[d->optind][1] == L'\0')) + { + if (d->__ordering == REQUIRE_ORDER) + return -1; + d->optarg = argv[d->optind++]; + return 1; + } + d->__nextchar = (argv[d->optind] + 1 + (longopts != NULL && argv[d->optind][1] == L'-')); + } + if (longopts != NULL && (argv[d->optind][1] == L'-' || (long_only && (argv[d->optind][2] || !wcschr(optstring, argv[d->optind][1]))))) + { + wchar_t *nameend; + unsigned int namelen; + const struct option_w *p; + const struct option_w *pfound = NULL; + struct option_list + { + const struct option_w *p; + struct option_list *next; + } *ambig_list = NULL; + int exact = 0; + int indfound = -1; + int option_index; + for (nameend = d->__nextchar; *nameend && *nameend != L'='; nameend++); + namelen = (unsigned int)(nameend - d->__nextchar); + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!wcsncmp(p->name, d->__nextchar, namelen)) + { + if (namelen == (unsigned int)wcslen(p->name)) + { + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + pfound = p; + indfound = option_index; + } + else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val) + { + struct option_list *newp = (struct option_list*)alloca(sizeof(*newp)); + newp->p = p; + newp->next = ambig_list; + ambig_list = newp; + } + } + if (ambig_list != NULL && !exact) + { + if (print_errors) + { + struct option_list first; + first.p = pfound; + first.next = ambig_list; + ambig_list = &first; + fwprintf(stderr, L"%s: option '%s' is ambiguous; possibilities:", argv[0], argv[d->optind]); + do + { + fwprintf (stderr, L" '--%s'", ambig_list->p->name); + ambig_list = ambig_list->next; + } + while (ambig_list != NULL); + fputwc (L'\n', stderr); + } + d->__nextchar += wcslen(d->__nextchar); + d->optind++; + d->optopt = 0; + return L'?'; + } + if (pfound != NULL) + { + option_index = indfound; + d->optind++; + if (*nameend) + { + if (pfound->has_arg) + d->optarg = nameend + 1; + else + { + if (print_errors) + { + if (argv[d->optind - 1][1] == L'-') + { + fwprintf(stderr, L"%s: option '--%s' doesn't allow an argument\n",argv[0], pfound->name); + } + else + { + fwprintf(stderr, L"%s: option '%c%s' doesn't allow an argument\n",argv[0], argv[d->optind - 1][0],pfound->name); + } + } + d->__nextchar += wcslen(d->__nextchar); + d->optopt = pfound->val; + return L'?'; + } + } + else if (pfound->has_arg == 1) + { + if (d->optind < argc) + d->optarg = argv[d->optind++]; + else + { + if (print_errors) + { + fwprintf(stderr,L"%s: option '--%s' requires an argument\n",argv[0], pfound->name); + } + d->__nextchar += wcslen(d->__nextchar); + d->optopt = pfound->val; + return optstring[0] == L':' ? L':' : L'?'; + } + } + d->__nextchar += wcslen(d->__nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + if (!long_only || argv[d->optind][1] == L'-' || wcschr(optstring, *d->__nextchar) == NULL) + { + if (print_errors) + { + if (argv[d->optind][1] == L'-') + { + fwprintf(stderr, L"%s: unrecognized option '--%s'\n",argv[0], d->__nextchar); + } + else + { + fwprintf(stderr, L"%s: unrecognized option '%c%s'\n",argv[0], argv[d->optind][0], d->__nextchar); + } + } + d->__nextchar = (wchar_t *)L""; + d->optind++; + d->optopt = 0; + return L'?'; + } + } + { + wchar_t c = *d->__nextchar++; + wchar_t *temp = (wchar_t*)wcschr(optstring, c); + if (*d->__nextchar == L'\0') + ++d->optind; + if (temp == NULL || c == L':' || c == L';') + { + if (print_errors) + { + fwprintf(stderr, L"%s: invalid option -- '%c'\n", argv[0], c); + } + d->optopt = c; + return L'?'; + } + if (temp[0] == L'W' && temp[1] == L';') + { + wchar_t *nameend; + const struct option_w *p; + const struct option_w *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = 0; + int option_index; + if (longopts == NULL) + goto no_longs; + if (*d->__nextchar != L'\0') + { + d->optarg = d->__nextchar; + d->optind++; + } + else if (d->optind == argc) + { + if (print_errors) + { + fwprintf(stderr,L"%s: option requires an argument -- '%c'\n",argv[0], c); + } + d->optopt = c; + if (optstring[0] == L':') + c = L':'; + else + c = L'?'; + return c; + } + else + d->optarg = argv[d->optind++]; + for (d->__nextchar = nameend = d->optarg; *nameend && *nameend != L'='; nameend++); + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!wcsncmp(p->name, d->__nextchar, nameend - d->__nextchar)) + { + if ((unsigned int) (nameend - d->__nextchar) == wcslen(p->name)) + { + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + pfound = p; + indfound = option_index; + } + else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val) + ambig = 1; + } + if (ambig && !exact) + { + if (print_errors) + { + fwprintf(stderr, L"%s: option '-W %s' is ambiguous\n",argv[0], d->optarg); + } + d->__nextchar += wcslen(d->__nextchar); + d->optind++; + return L'?'; + } + if (pfound != NULL) + { + option_index = indfound; + if (*nameend) + { + if (pfound->has_arg) + d->optarg = nameend + 1; + else + { + if (print_errors) + { + fwprintf(stderr, L"%s: option '-W %s' doesn't allow an argument\n",argv[0], pfound->name); + } + d->__nextchar += wcslen(d->__nextchar); + return L'?'; + } + } + else if (pfound->has_arg == 1) + { + if (d->optind < argc) + d->optarg = argv[d->optind++]; + else + { + if (print_errors) + { + fwprintf(stderr, L"%s: option '-W %s' requires an argument\n",argv[0], pfound->name); + } + d->__nextchar += wcslen(d->__nextchar); + return optstring[0] == L':' ? L':' : L'?'; + } + } + else + d->optarg = NULL; + d->__nextchar += wcslen(d->__nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } +no_longs: + d->__nextchar = NULL; + return L'W'; + } + if (temp[1] == L':') + { + if (temp[2] == L':') + { + if (*d->__nextchar != L'\0') + { + d->optarg = d->__nextchar; + d->optind++; + } + else + d->optarg = NULL; + d->__nextchar = NULL; + } + else + { + if (*d->__nextchar != L'\0') + { + d->optarg = d->__nextchar; + d->optind++; + } + else if (d->optind == argc) + { + if (print_errors) + { + fwprintf(stderr,L"%s: option requires an argument -- '%c'\n",argv[0], c); + } + d->optopt = c; + if (optstring[0] == L':') + c = L':'; + else + c = L'?'; + } + else + d->optarg = argv[d->optind++]; + d->__nextchar = NULL; + } + } + return c; + } +} +int _getopt_internal_w (int argc, wchar_t *const *argv, const wchar_t *optstring, const struct option_w *longopts, int *longind, int long_only, int posixly_correct) +{ + int result; + getopt_data_w.optind = optind; + getopt_data_w.opterr = opterr; + result = _getopt_internal_r_w (argc, argv, optstring, longopts,longind, long_only, &getopt_data_w,posixly_correct); + optind = getopt_data_w.optind; + optarg_w = getopt_data_w.optarg; + optopt = getopt_data_w.optopt; + return result; +} +int getopt_w (int argc, wchar_t *const *argv, const wchar_t *optstring) _GETOPT_THROW +{ + return _getopt_internal_w (argc, argv, optstring, (const struct option_w *) 0, (int *) 0, 0, 0); +} +int getopt_long_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW +{ + return _getopt_internal_w (argc, argv, options, long_options, opt_index, 0, 0); +} +int getopt_long_only_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW +{ + return _getopt_internal_w (argc, argv, options, long_options, opt_index, 1, 0); +} +int _getopt_long_r_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d) +{ + return _getopt_internal_r_w (argc, argv, options, long_options, opt_index,0, d, 0); +} +int _getopt_long_only_r_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d) +{ + return _getopt_internal_r_w (argc, argv, options, long_options, opt_index, 1, d, 0); +} diff --git a/src/third_party/win32/getopt.h b/src/third_party/win32/getopt.h new file mode 100644 index 000000000..ca74ba63d --- /dev/null +++ b/src/third_party/win32/getopt.h @@ -0,0 +1,136 @@ +/* Getopt for Microsoft C +This code is a modification of the Free Software Foundation, Inc. +Getopt library for parsing command line argument the purpose was +to provide a Microsoft Visual C friendly derivative. This code +provides functionality for both Unicode and Multibyte builds. + +Date: 02/03/2011 - Ludvik Jerabek - Initial Release +Version: 1.0 +Comment: Supports getopt, getopt_long, and getopt_long_only +and POSIXLY_CORRECT environment flag +License: LGPL + +Revisions: + +02/03/2011 - Ludvik Jerabek - Initial Release +02/20/2011 - Ludvik Jerabek - Fixed compiler warnings at Level 4 +07/05/2011 - Ludvik Jerabek - Added no_argument, required_argument, optional_argument defs +08/03/2011 - Ludvik Jerabek - Fixed non-argument runtime bug which caused runtime exception +08/09/2011 - Ludvik Jerabek - Added code to export functions for DLL and LIB +02/15/2012 - Ludvik Jerabek - Fixed _GETOPT_THROW definition missing in implementation file +08/01/2012 - Ludvik Jerabek - Created separate functions for char and wchar_t characters so single dll can do both unicode and ansi +10/15/2012 - Ludvik Jerabek - Modified to match latest GNU features +06/19/2015 - Ludvik Jerabek - Fixed maximum option limitation caused by option_a (255) and option_w (65535) structure val variable + +**DISCLAIMER** +THIS MATERIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +EITHER EXPRESS OR IMPLIED, INCLUDING, BUT Not LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE, OR NON-INFRINGEMENT. SOME JURISDICTIONS DO NOT ALLOW THE +EXCLUSION OF IMPLIED WARRANTIES, SO THE ABOVE EXCLUSION MAY NOT +APPLY TO YOU. IN NO EVENT WILL I BE LIABLE TO ANY PARTY FOR ANY +DIRECT, INDIRECT, SPECIAL OR OTHER CONSEQUENTIAL DAMAGES FOR ANY +USE OF THIS MATERIAL INCLUDING, WITHOUT LIMITATION, ANY LOST +PROFITS, BUSINESS INTERRUPTION, LOSS OF PROGRAMS OR OTHER DATA ON +YOUR INFORMATION HANDLING SYSTEM OR OTHERWISE, EVEN If WE ARE +EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +*/ +#ifndef __GETOPT_H_ + #define __GETOPT_H_ + + #ifdef _GETOPT_API + #undef _GETOPT_API + #endif + + #if defined(EXPORTS_GETOPT) && defined(STATIC_GETOPT) + #error "The preprocessor definitions of EXPORTS_GETOPT and STATIC_GETOPT can only be used individually" + #elif defined(STATIC_GETOPT) +// #pragma message("Warning static builds of getopt violate the Lesser GNU Public License") + #define _GETOPT_API + #elif defined(EXPORTS_GETOPT) + #pragma message("Exporting getopt library") + #define _GETOPT_API __declspec(dllexport) + #else + #pragma message("Importing getopt library") + #define _GETOPT_API __declspec(dllimport) + #endif + + // Change behavior for C\C++ + #ifdef __cplusplus + #define _BEGIN_EXTERN_C extern "C" { + #define _END_EXTERN_C } + #define _GETOPT_THROW throw() + #else + #define _BEGIN_EXTERN_C + #define _END_EXTERN_C + #define _GETOPT_THROW + #endif + + // Standard GNU options + #define null_argument 0 /*Argument Null*/ + #define no_argument 0 /*Argument Switch Only*/ + #define required_argument 1 /*Argument Required*/ + #define optional_argument 2 /*Argument Optional*/ + + // Shorter Options + #define ARG_NULL 0 /*Argument Null*/ + #define ARG_NONE 0 /*Argument Switch Only*/ + #define ARG_REQ 1 /*Argument Required*/ + #define ARG_OPT 2 /*Argument Optional*/ + + #include + #include + +_BEGIN_EXTERN_C + + extern _GETOPT_API int optind; + extern _GETOPT_API int opterr; + extern _GETOPT_API int optopt; + + // Ansi + struct option_a + { + const char* name; + int has_arg; + int *flag; + int val; + }; + extern _GETOPT_API char *optarg_a; + extern _GETOPT_API int getopt_a(int argc, char *const *argv, const char *optstring) _GETOPT_THROW; + extern _GETOPT_API int getopt_long_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW; + extern _GETOPT_API int getopt_long_only_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW; + + // Unicode + struct option_w + { + const wchar_t* name; + int has_arg; + int *flag; + int val; + }; + extern _GETOPT_API wchar_t *optarg_w; + extern _GETOPT_API int getopt_w(int argc, wchar_t *const *argv, const wchar_t *optstring) _GETOPT_THROW; + extern _GETOPT_API int getopt_long_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW; + extern _GETOPT_API int getopt_long_only_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW; + +_END_EXTERN_C + + #undef _BEGIN_EXTERN_C + #undef _END_EXTERN_C + #undef _GETOPT_THROW + #undef _GETOPT_API + + #ifdef _UNICODE + #define getopt getopt_w + #define getopt_long getopt_long_w + #define getopt_long_only getopt_long_only_w + #define option option_w + #define optarg optarg_w + #else + #define getopt getopt_a + #define getopt_long getopt_long_a + #define getopt_long_only getopt_long_only_a + #define option option_a + #define optarg optarg_a + #endif +#endif // __GETOPT_H_ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 04e5f137f..8b6c015ee 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,7 +1,7 @@ function(addtest name) add_test( NAME "test.${name}" - COMMAND ${CMAKE_SOURCE_DIR}/test/run ${name} + COMMAND bash ${CMAKE_SOURCE_DIR}/test/run ${name} WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) set_tests_properties( diff --git a/unittest/TestUtil.hpp b/unittest/TestUtil.hpp index 2db64ec03..0f06d3ee3 100644 --- a/unittest/TestUtil.hpp +++ b/unittest/TestUtil.hpp @@ -22,6 +22,10 @@ #include +#ifdef _MSC_VER +# define DOCTEST_CONFIG_USE_STD_HEADERS +#endif + namespace TestUtil { // This class is intended to be instantiated in all test cases that create local diff --git a/unittest/test_Stat.cpp b/unittest/test_Stat.cpp index 998cde17e..c61a62c0b 100644 --- a/unittest/test_Stat.cpp +++ b/unittest/test_Stat.cpp @@ -22,7 +22,9 @@ #include "third_party/doctest.h" -#include +#ifdef HAVE_UNISTD_H +# include +#endif using TestUtil::TestContext; diff --git a/unittest/test_Util.cpp b/unittest/test_Util.cpp index d89dd8f77..9425fc5a7 100644 --- a/unittest/test_Util.cpp +++ b/unittest/test_Util.cpp @@ -23,6 +23,8 @@ #include "third_party/doctest.h" +#include + using TestUtil::TestContext; TEST_SUITE_BEGIN("Util"); @@ -297,6 +299,16 @@ TEST_CASE("Util::get_extension") CHECK(Util::get_extension("/foo/bar/f.abc.txt") == ".txt"); } +static inline std::string +os_path(std::string path) +{ +#if !defined(HAVE_DIRENT_H) && DIR_DELIM_CH != '/' + std::replace(path.begin(), path.end(), '/', DIR_DELIM_CH); +#endif + + return path; +} + TEST_CASE("Util::get_level_1_files") { TestContext test_context; @@ -339,13 +351,13 @@ TEST_CASE("Util::get_level_1_files") return f1->path() < f2->path(); }); - CHECK(files[0]->path() == "0/1/file_b"); + CHECK(files[0]->path() == os_path("0/1/file_b")); CHECK(files[0]->lstat().size() == 1); - CHECK(files[1]->path() == "0/1/file_c"); + CHECK(files[1]->path() == os_path("0/1/file_c")); CHECK(files[1]->lstat().size() == 2); - CHECK(files[2]->path() == "0/f/c/file_d"); + CHECK(files[2]->path() == os_path("0/f/c/file_d")); CHECK(files[2]->lstat().size() == 3); - CHECK(files[3]->path() == "0/file_a"); + CHECK(files[3]->path() == os_path("0/file_a")); CHECK(files[3]->lstat().size() == 0); } } @@ -565,20 +577,19 @@ TEST_CASE("Util::parse_int") CHECK_THROWS_WITH(Util::parse_int("0 "), "invalid integer: \"0 \""); // check boundary values - if (sizeof(int) == 2) { - CHECK(Util::parse_int("-32768") == -32768); - CHECK(Util::parse_int("32767") == 32767); - CHECK_THROWS_WITH(Util::parse_int("-32768"), "invalid integer: \"-32768\""); - CHECK_THROWS_WITH(Util::parse_int("32768"), "invalid integer: \"32768\""); - } - if (sizeof(int) == 4) { - CHECK(Util::parse_int("-2147483648") == -2147483648); - CHECK(Util::parse_int("2147483647") == 2147483647); - CHECK_THROWS_WITH(Util::parse_int("-2147483649"), - "invalid integer: \"-2147483649\""); - CHECK_THROWS_WITH(Util::parse_int("2147483648"), - "invalid integer: \"2147483648\""); - } +#if SIZEOF_INT == 2 + CHECK(Util::parse_int("-32768") == -32768); + CHECK(Util::parse_int("32767") == 32767); + CHECK_THROWS_WITH(Util::parse_int("-32768"), "invalid integer: \"-32768\""); + CHECK_THROWS_WITH(Util::parse_int("32768"), "invalid integer: \"32768\""); +#elif SIZEOF_INT == 4 + CHECK(Util::parse_int("-2147483648") == -2147483648LL); + CHECK(Util::parse_int("2147483647") == 2147483647); + CHECK_THROWS_WITH(Util::parse_int("-2147483649"), + "invalid integer: \"-2147483649\""); + CHECK_THROWS_WITH(Util::parse_int("2147483648"), + "invalid integer: \"2147483648\""); +#endif } TEST_CASE("Util::parse_size") @@ -664,6 +675,9 @@ TEST_CASE("Util::read_file and Util::write_file") CHECK_THROWS_WITH(Util::write_file("", "does/not/exist"), "No such file or directory"); + + CHECK_THROWS_WITH(Util::write_file("does/not/exist", "does/not/exist"), + "No such file or directory"); } TEST_CASE("Util::remove_extension") @@ -870,8 +884,8 @@ TEST_CASE("Util::traverse") { CHECK_NOTHROW(Util::traverse("dir-with-files", visitor)); REQUIRE(visited.size() == 3); - std::string f1 = "[f] dir-with-files/f1"; - std::string f2 = "[f] dir-with-files/f2"; + std::string f1 = os_path("[f] dir-with-files/f1"); + std::string f2 = os_path("[f] dir-with-files/f2"); CHECK(((visited[0] == f1 && visited[1] == f2) || (visited[0] == f2 && visited[1] == f1))); CHECK(visited[2] == "[d] dir-with-files"); @@ -881,8 +895,8 @@ TEST_CASE("Util::traverse") { CHECK_NOTHROW(Util::traverse("dir-with-subdir-and-file", visitor)); REQUIRE(visited.size() == 3); - CHECK(visited[0] == "[f] dir-with-subdir-and-file/subdir/f"); - CHECK(visited[1] == "[d] dir-with-subdir-and-file/subdir"); + CHECK(visited[0] == os_path("[f] dir-with-subdir-and-file/subdir/f")); + CHECK(visited[1] == os_path("[d] dir-with-subdir-and-file/subdir")); CHECK(visited[2] == "[d] dir-with-subdir-and-file"); } } diff --git a/unittest/test_argprocessing.cpp b/unittest/test_argprocessing.cpp index 1348a4c07..93f152b27 100644 --- a/unittest/test_argprocessing.cpp +++ b/unittest/test_argprocessing.cpp @@ -26,6 +26,8 @@ #include "third_party/doctest.h" +#include + using TestUtil::TestContext; namespace {