diff --git a/.gitignore b/.gitignore index b5d29abf7..146cccecc 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,8 @@ dkms.conf /resources/y.tab.c /resources/y.tab.h /bin/ + +#visual studio files +.vs/ +.vscode/ +out/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index f1bbb5c2f..732319580 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,18 +53,21 @@ llvm_map_components_to_libnames(llvm_libs ${LLVM_LINK_COMPONENTS}) file(COPY ${CMAKE_SOURCE_DIR}/resources/lib DESTINATION ${CMAKE_BINARY_DIR}) -find_library(LLD_COFF NAMES lldCOFF.a liblldCOFF.a PATHS ${LLVM_LIBRARY_DIRS}) -find_library(LLD_COMMON NAMES lldCommon.a liblldCommon.a PATHS ${LLVM_LIBRARY_DIRS}) -find_library(LLD_CORE NAMES lldCore.a liblldCore.a PATHS ${LLVM_LIBRARY_DIRS}) -find_library(LLD_WASM NAMES lldWasm.a liblldWasm.a PATHS ${LLVM_LIBRARY_DIRS}) -find_library(LLD_MINGW NAMES lldMinGW.a liblldMinGW.a PATHS ${LLVM_LIBRARY_DIRS}) -find_library(LLD_ELF NAMES lldELF.a liblldELF.a PATHS ${LLVM_LIBRARY_DIRS}) -find_library(LLD_DRIVER NAMES lldDriver.a liblldDriver.a PATHS ${LLVM_LIBRARY_DIRS}) -find_library(LLD_READER_WRITER NAMES lldReaderWriter.a liblldReaderWriter.a PATHS ${LLVM_LIBRARY_DIRS}) -find_library(LLD_MACHO NAMES lldMachO.a liblldMachO.a PATHS ${LLVM_LIBRARY_DIRS}) -find_library(LLD_YAML NAMES lldYAML.a liblldYAML.a PATHS ${LLVM_LIBRARY_DIRS}) +# These don't seem to be reliable on windows. +if(UNIX) + message(STATUS "using find_library") + find_library(LLD_COFF NAMES lldCOFF.a liblldCOFF.a PATHS ${LLVM_LIBRARY_DIRS}) + find_library(LLD_COMMON NAMES lldCommon.a liblldCommon.a PATHS ${LLVM_LIBRARY_DIRS}) + find_library(LLD_CORE NAMES lldCore.a liblldCore.a PATHS ${LLVM_LIBRARY_DIRS}) + find_library(LLD_WASM NAMES lldWasm.a liblldWasm.a PATHS ${LLVM_LIBRARY_DIRS}) + find_library(LLD_MINGW NAMES lldMinGW.a liblldMinGW.a PATHS ${LLVM_LIBRARY_DIRS}) + find_library(LLD_ELF NAMES lldELF.a liblldELF.a PATHS ${LLVM_LIBRARY_DIRS}) + find_library(LLD_DRIVER NAMES lldDriver.a liblldDriver.a PATHS ${LLVM_LIBRARY_DIRS}) + find_library(LLD_READER_WRITER NAMES lldReaderWriter.a liblldReaderWriter.a PATHS ${LLVM_LIBRARY_DIRS}) + find_library(LLD_MACHO NAMES lldMachO.a liblldMachO.a PATHS ${LLVM_LIBRARY_DIRS}) + find_library(LLD_YAML NAMES lldYAML.a liblldYAML.a PATHS ${LLVM_LIBRARY_DIRS}) -set(lld_libs + set(lld_libs ${LLD_COFF} ${LLD_COMMON} ${LLD_WASM} @@ -76,6 +79,8 @@ set(lld_libs ${LLD_YAML} ${LLD_CORE} ) + message(STATUS "linking to llvm libs ${llvm_libs} ${lld_libs}") +endif() add_library(c3c_wrappers STATIC wrapper/src/wrapper.cpp) add_executable(c3c @@ -107,6 +112,7 @@ add_executable(c3c src/compiler/module.c src/compiler/llvm_codegen.c src/utils/stringutils.c + src/utils/find_msvc.c src/compiler/dwarf.h src/compiler/copying.c src/compiler/llvm_codegen_stmt.c @@ -145,9 +151,11 @@ add_executable(c3c src/compiler/llvm_codegen_c_abi_riscv.c src/compiler/llvm_codegen_c_abi_wasm.c) -target_compile_options(c3c PRIVATE -Wall -Werror -Wno-unknown-pragmas -Wno-unused-result - -Wno-unused-function -Wno-unused-variable -Wno-unused-parameter) - +if(NOT CMAKE_C_COMPILER_ID STREQUAL "MSVC") + message(STATUS "using gcc/clang warning switches") + target_compile_options(c3c PRIVATE -Wall -Werror -Wno-unknown-pragmas -Wno-unused-result + -Wno-unused-function -Wno-unused-variable -Wno-unused-parameter) +endif() target_include_directories(c3c PRIVATE "${CMAKE_SOURCE_DIR}/src/") @@ -158,9 +166,18 @@ target_include_directories(c3c_wrappers PRIVATE message(STATUS "Found LLD ${lld_libs}") -target_link_libraries(c3c_wrappers ${llvm_libs} ${lld_libs}) -#target_link_libraries(c3c m ${llvm_libs} c3c_wrappers lldCommon lldCore lldCOFF lldWASM lldMinGW lldELF lldDriver lldReaderWriter lldMachO lldYAML) -target_link_libraries(c3c m ${llvm_libs} c3c_wrappers ${lld_libs}) +if(UNIX) + message(STATUS "adding unix link params") + target_link_libraries(c3c_wrappers ${llvm_libs} ${lld_libs}) + target_link_libraries(c3c m ${llvm_libs} c3c_wrappers ${lld_libs}) +else() + # todo: maybe get this from llvm-config somehow? it should be in LLVM_DIR\..\..\..\bin I think. + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -LIBPATH:C:\\llvm\\llvm\\build\\Release\\lib") # needed for lldCommon.lib + #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -LIBPATH:${LLVM_LIBPATH}") # This doesn't seem to work for some reason + message(STATUS "${LLVM_LIBPATH}") + target_link_libraries(c3c debug ${llvm_libs} c3c_wrappers lldCommon lldCore lldCOFF lldWASM lldMinGW lldELF lldDriver lldReaderWriter lldMachO lldYAML) + target_link_libraries(c3c optimized ${llvm_libs} c3c_wrappers lldCommon lldCore lldCOFF lldWASM lldMinGW lldELF lldDriver lldReaderWriter lldMachO lldYAML) +endif() if (WIN32) if (CMAKE_C_COMPILER_ID STREQUAL "Clang" OR CMAKE_C_COMPILE_ID STREQUAL "GNU") diff --git a/CMakeSettings.json b/CMakeSettings.json new file mode 100644 index 000000000..54caad2a8 --- /dev/null +++ b/CMakeSettings.json @@ -0,0 +1,15 @@ +{ + "configurations": [ + { + "name": "build", + "generator": "Ninja", + "configurationType": "Release", + "inheritEnvironments": [ "msvc_x64_x64" ], + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "-DLLVM_DIR=C:\\llvm\\llvm\\build\\lib\\cmake\\llvm -DCMAKE_CXX_FLAGS:STRING=\"${CMAKE_CXX_FLAGS} /J\"", + "buildCommandArgs": "-v", + "ctestCommandArgs": "" + } + ] +} \ No newline at end of file diff --git a/src/build/build_options.c b/src/build/build_options.c index 891e82df3..eaed8c57f 100644 --- a/src/build/build_options.c +++ b/src/build/build_options.c @@ -6,7 +6,9 @@ #include #include #include +#ifndef _MSC_VER #include +#endif #include #include #include diff --git a/src/build/builder.c b/src/build/builder.c index 8bbe585c9..2c876296a 100644 --- a/src/build/builder.c +++ b/src/build/builder.c @@ -1,8 +1,11 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. // Use of this source code is governed by a LGPLv3.0 // a copy of which can be found in the LICENSE file. - +#ifndef _MSC_VER #include +#else +#include "utils/dirent.h" +#endif #include "build_internal.h" #include "build_options.h" @@ -93,6 +96,16 @@ static void update_build_target_from_options(BuildTarget *target, BuildOptions * { target->arch_os_target = options->arch_os_target_override; } +#ifndef _MSC_VER +#define _MSC_VER 0 +#endif + else if (_MSC_VER) + { + // The current handling of ARCH_OS_TARGET_DEFAULT works on unix, but not on windows. + // to deal with this, simply default to x64-windows (unless using mingw). + // ARCH_OS_TARGET_DEFAULT could be handled in a more cross-platform manner later on. + target->arch_os_target = X64_WINDOWS; + } if (options->pie != PIE_DEFAULT) target->pie = options->pie; if (options->pic != PIC_DEFAULT) target->pic = options->pic; diff --git a/src/build/project_creation.c b/src/build/project_creation.c index 6cf66ebbb..9cb24a062 100644 --- a/src/build/project_creation.c +++ b/src/build/project_creation.c @@ -6,7 +6,9 @@ #include #include #include +#ifndef _MSC_VER #include +#endif #include #include "project_creation.h" #include "build_options.h" diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index 8a4ce1668..1a978d0cd 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -4,7 +4,9 @@ #include "compiler_internal.h" #include "parser_internal.h" +#ifndef _MSC_VER #include +#endif #define MAX_OUTPUT_FILES 1000000 #define MAX_MODULES 100000 @@ -483,7 +485,7 @@ static void target_expand_source_names(BuildTarget *target) void compile_target(BuildOptions *options) { - init_default_build_target(&active_target, options, "a.out"); + init_default_build_target(&active_target, options, DEFAULT_EXE); compile(); } diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index 574a888c4..64e56c557 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -30,6 +30,13 @@ typedef int32_t ScopeId; #define DEFAULT_EXE "a.out" #endif +#if PLATFORM_WINDOWS +#define DEFAULT_OBJ_FILE_EXT ".obj" +#else +#define DEFAULT_OBJ_FILE_EXT ".o" +#endif + + typedef uint32_t SourceLoc; typedef struct { @@ -554,9 +561,9 @@ typedef struct Decl_ SourceSpan span; const char *external_name; Ast *docs; - DeclKind decl_kind : 6; - Visibility visibility : 2; - ResolveStatus resolve_status : 2; + DeclKind decl_kind : 7; + Visibility visibility : 3; + ResolveStatus resolve_status : 3; bool is_packed : 1; bool is_opaque : 1; bool needs_additional_pad : 1; diff --git a/src/compiler/enums.h b/src/compiler/enums.h index c44ef9f66..236ec1e48 100644 --- a/src/compiler/enums.h +++ b/src/compiler/enums.h @@ -295,7 +295,7 @@ typedef enum TYPE_INFO_POINTER, } TypeInfoKind; -typedef enum +typedef enum { TOKEN_INVALID_TOKEN = 0, diff --git a/src/compiler/linker.c b/src/compiler/linker.c index 4def40fc3..e24e5fb4c 100644 --- a/src/compiler/linker.c +++ b/src/compiler/linker.c @@ -2,6 +2,10 @@ #include // for LLVM_VERSION_STRING +#ifdef PLATFORM_WINDOWS +#include "utils/find_msvc.h" +#endif + extern bool llvm_link_elf(const char **args, int arg_count, const char** error_string); extern bool llvm_link_macho(const char **args, int arg_count, const char** error_string); extern bool llvm_link_coff(const char **args, int arg_count, const char** error_string); @@ -64,8 +68,19 @@ static void prepare_msys2_linker_flags(const char ***args, const char **files_to static bool link_exe(const char *output_file, const char **files_to_link, unsigned file_count) { const char **args = NULL; - vec_add(args, "-o"); - vec_add(args, output_file); +#ifdef _MSC_VER + if (platform_target.os == OS_TYPE_WIN32) + { + vec_add(args, join_strings((const char* []) {"/out:", output_file}, 2)); + } + else + { +#endif + vec_add(args, "-o"); + vec_add(args, output_file); +#ifdef _MSC_VER + } +#endif VECEACH(active_target.link_args, i) { vec_add(args, active_target.link_args[i]); @@ -78,16 +93,43 @@ static bool link_exe(const char *output_file, const char **files_to_link, unsign // TODO: properly detect if llvm-lld is available // TODO: check if running inside MSYS2, it could be done via getting MSYSTEM environment variable // https://stackoverflow.com/questions/65527286/how-to-check-if-my-program-is-running-on-mingwor-msys-shell-or-on-cmd - if (!platform_target.x64.is_mingw64) return false; - if (NULL == getenv("MSYSTEM")) return false; - if (!strcmp(getenv("MSYSTEM"), "CLANG64") || !strcmp(getenv("MSYSTEM"), "MINGW64")) + if (NULL == getenv("MSYSTEM")) { - prepare_msys2_linker_flags(&args, files_to_link, file_count); + // "native" windows + + + // find paths to library directories. + // ex: + // C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC\\Tools\\MSVC\\14.28.29910\\lib\\x64 + // C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC\\Tools\\MSVC\\14.28.29910\\atlmfc\\lib\\x64 + // C:\\Program Files (x86)\\Windows Kits\\10\\Lib\\10.0.19041.0\\ucrt\\x64 + // C:\\Program Files (x86)\\Windows Kits\\10\\Lib\\10.0.19041.0\\um\\x64 +#ifdef _MSC_VER + PathPair msvc_paths = get_latest_available_vs_path(); + PathPair windows_kit_paths = find_winkit_path(); + vec_add(args, join_strings((const char* []) { "-libpath:C:", msvc_paths.first }, 2)); + vec_add(args, join_strings((const char* []) { "-libpath:C:", msvc_paths.second }, 2)); + vec_add(args, join_strings((const char* []) { "-libpath:C:", windows_kit_paths.first }, 2)); + vec_add(args, join_strings((const char* []) { "-libpath:C:", windows_kit_paths.second }, 2)); + + vec_add(args, "-defaultlib:libcmt"); + vec_add(args, "-nologo"); + add_files(&args, files_to_link, file_count); +#else + error_exit("ERROR - c3c must be compiled with MSVC to target x64-windows\n"); +#endif } else { - return false; - } + if (!strcmp(getenv("MSYSTEM"), "CLANG64") || !strcmp(getenv("MSYSTEM"), "MINGW64")) + { + prepare_msys2_linker_flags(&args, files_to_link, file_count); + } + else + { + return false; + } + } break; case OS_TYPE_MACOSX: add_files(&args, files_to_link, file_count); diff --git a/src/compiler/llvm_codegen_module.c b/src/compiler/llvm_codegen_module.c index c7dce5497..2a7c77d38 100644 --- a/src/compiler/llvm_codegen_module.c +++ b/src/compiler/llvm_codegen_module.c @@ -22,8 +22,7 @@ void gencontext_begin_module(GenContext *c) } const char *result = scratch_buffer_to_string(); c->ir_filename = strformat("%s.ll", result); - // TODO filename should depend on platform. - c->object_filename = strformat("%s.o", result); + c->object_filename = strformat("%s%s", result, DEFAULT_OBJ_FILE_EXT); c->module = LLVMModuleCreateWithNameInContext(c->code_module->name->module, c->context); c->machine = llvm_target_machine_create(); diff --git a/src/compiler/parse_expr.c b/src/compiler/parse_expr.c index ad00fcfc9..acd5ae3be 100644 --- a/src/compiler/parse_expr.c +++ b/src/compiler/parse_expr.c @@ -226,12 +226,14 @@ Expr *parse_cond(Context *context) return decl_expr; } -inline Expr* parse_expr(Context *context) +// These used to be explicitly inlined, but that seems to lead to confusing MSVC linker errors. +// They are probably still inlined by the compiler, though I haven't checked. +Expr* parse_expr(Context *context) { return parse_precedence(context, PREC_ASSIGNMENT); } -inline Expr* parse_constant_expr(Context *context) +Expr* parse_constant_expr(Context *context) { return parse_precedence(context, PREC_TERNARY); } diff --git a/src/compiler/sema_decls.c b/src/compiler/sema_decls.c index d06ab83ac..f76c51330 100644 --- a/src/compiler/sema_decls.c +++ b/src/compiler/sema_decls.c @@ -989,6 +989,7 @@ static inline bool sema_analyse_func(Context *context, Decl *decl) { DEBUG_LOG("----Analysing function %s", decl->name); Type *func_type = sema_analyse_function_signature(context, &decl->func_decl.function_signature, true); + decl->type = func_type; if (!func_type) return decl_poison(decl); if (decl->func_decl.type_parent) diff --git a/src/compiler/sema_name_resolution.c b/src/compiler/sema_name_resolution.c index 50a2f714d..e7ff73607 100644 --- a/src/compiler/sema_name_resolution.c +++ b/src/compiler/sema_name_resolution.c @@ -4,7 +4,10 @@ #include "compiler_internal.h" - +#if defined(_MSC_VER) +// This isn't standard apparently, so MSVC doesn't have it built in... +typedef long long int ssize_t; +#endif static inline bool matches_subpath(Path *path_to_check, Path *path_to_find) { diff --git a/src/compiler/source_file.c b/src/compiler/source_file.c index 0a0d79f17..57613b0c1 100644 --- a/src/compiler/source_file.c +++ b/src/compiler/source_file.c @@ -3,7 +3,13 @@ // a copy of which can be found in the LICENSE file. #include +#ifdef _MSC_VER + +#define PATH_MAX 260 + +#else #include +#endif #include "compiler_internal.h" #include "../build/build_options.h" @@ -23,7 +29,11 @@ File *source_file_load(const char *filename, bool *already_loaded) if (already_loaded) *already_loaded = false; if (!source_files.files) source_files.files = VECNEW(File *, LEXER_FILES_START_CAPACITY); - char *full_path = malloc_arena(PATH_MAX + 1); +#ifdef _MSC_VER + char* full_path = malloc_arena(MAX_PATH + 1); +#else + char* full_path = malloc_arena(PATH_MAX + 1); +#endif if (!realpath(filename, full_path)) { error_exit("Failed to resolve %s", filename); diff --git a/src/main.c b/src/main.c index 8fe885b3e..f41d255ee 100644 --- a/src/main.c +++ b/src/main.c @@ -45,7 +45,6 @@ int main(int argc, const char *argv[]) UNREACHABLE } - print_arena_status(); free_arena(); return 0; diff --git a/src/utils/dirent.h b/src/utils/dirent.h new file mode 100644 index 000000000..49b1afd9d --- /dev/null +++ b/src/utils/dirent.h @@ -0,0 +1,1030 @@ +/* + * Dirent interface for Microsoft Visual Studio + * + * Copyright (C) 1998-2019 Toni Ronkko + * This file is part of dirent. Dirent may be freely distributed + * under the MIT license. For all details and documentation, see + * https://github.com/tronkko/dirent + */ +#ifndef DIRENT_H +#define DIRENT_H + + /* Hide warnings about unreferenced local functions */ +#if defined(__clang__) +# pragma clang diagnostic ignored "-Wunused-function" +#elif defined(_MSC_VER) +# pragma warning(disable:4505) +#elif defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wunused-function" +#endif + +/* + * Include windows.h without Windows Sockets 1.1 to prevent conflicts with + * Windows Sockets 2.0. + */ +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + /* Indicates that d_type field is available in dirent structure */ +#define _DIRENT_HAVE_D_TYPE + +/* Indicates that d_namlen field is available in dirent structure */ +#define _DIRENT_HAVE_D_NAMLEN + +/* Entries missing from MSVC 6.0 */ +#if !defined(FILE_ATTRIBUTE_DEVICE) +# define FILE_ATTRIBUTE_DEVICE 0x40 +#endif + +/* File type and permission flags for stat(), general mask */ +#if !defined(S_IFMT) +# define S_IFMT _S_IFMT +#endif + +/* Directory bit */ +#if !defined(S_IFDIR) +# define S_IFDIR _S_IFDIR +#endif + +/* Character device bit */ +#if !defined(S_IFCHR) +# define S_IFCHR _S_IFCHR +#endif + +/* Pipe bit */ +#if !defined(S_IFFIFO) +# define S_IFFIFO _S_IFFIFO +#endif + +/* Regular file bit */ +#if !defined(S_IFREG) +# define S_IFREG _S_IFREG +#endif + +/* Read permission */ +#if !defined(S_IREAD) +# define S_IREAD _S_IREAD +#endif + +/* Write permission */ +#if !defined(S_IWRITE) +# define S_IWRITE _S_IWRITE +#endif + +/* Execute permission */ +#if !defined(S_IEXEC) +# define S_IEXEC _S_IEXEC +#endif + +/* Pipe */ +#if !defined(S_IFIFO) +# define S_IFIFO _S_IFIFO +#endif + +/* Block device */ +#if !defined(S_IFBLK) +# define S_IFBLK 0 +#endif + +/* Link */ +#if !defined(S_IFLNK) +# define S_IFLNK 0 +#endif + +/* Socket */ +#if !defined(S_IFSOCK) +# define S_IFSOCK 0 +#endif + +/* Read user permission */ +#if !defined(S_IRUSR) +# define S_IRUSR S_IREAD +#endif + +/* Write user permission */ +#if !defined(S_IWUSR) +# define S_IWUSR S_IWRITE +#endif + +/* Execute user permission */ +#if !defined(S_IXUSR) +# define S_IXUSR 0 +#endif + +/* Read group permission */ +#if !defined(S_IRGRP) +# define S_IRGRP 0 +#endif + +/* Write group permission */ +#if !defined(S_IWGRP) +# define S_IWGRP 0 +#endif + +/* Execute group permission */ +#if !defined(S_IXGRP) +# define S_IXGRP 0 +#endif + +/* Read others permission */ +#if !defined(S_IROTH) +# define S_IROTH 0 +#endif + +/* Write others permission */ +#if !defined(S_IWOTH) +# define S_IWOTH 0 +#endif + +/* Execute others permission */ +#if !defined(S_IXOTH) +# define S_IXOTH 0 +#endif + +/* Maximum length of file name */ +#if !defined(PATH_MAX) +# define PATH_MAX MAX_PATH +#endif +#if !defined(FILENAME_MAX) +# define FILENAME_MAX MAX_PATH +#endif +#if !defined(NAME_MAX) +# define NAME_MAX FILENAME_MAX +#endif + +/* File type flags for d_type */ +#define DT_UNKNOWN 0 +#define DT_REG S_IFREG +#define DT_DIR S_IFDIR +#define DT_FIFO S_IFIFO +#define DT_SOCK S_IFSOCK +#define DT_CHR S_IFCHR +#define DT_BLK S_IFBLK +#define DT_LNK S_IFLNK + +/* Macros for converting between st_mode and d_type */ +#define IFTODT(mode) ((mode) & S_IFMT) +#define DTTOIF(type) (type) + +/* + * File type macros. Note that block devices, sockets and links cannot be + * distinguished on Windows and the macros S_ISBLK, S_ISSOCK and S_ISLNK are + * only defined for compatibility. These macros should always return false + * on Windows. + */ +#if !defined(S_ISFIFO) +# define S_ISFIFO(mode) (((mode) & S_IFMT) == S_IFIFO) +#endif +#if !defined(S_ISDIR) +# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#endif +#if !defined(S_ISREG) +# define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) +#endif +#if !defined(S_ISLNK) +# define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK) +#endif +#if !defined(S_ISSOCK) +# define S_ISSOCK(mode) (((mode) & S_IFMT) == S_IFSOCK) +#endif +#if !defined(S_ISCHR) +# define S_ISCHR(mode) (((mode) & S_IFMT) == S_IFCHR) +#endif +#if !defined(S_ISBLK) +# define S_ISBLK(mode) (((mode) & S_IFMT) == S_IFBLK) +#endif + + /* Return the exact length of the file name without zero terminator */ +#define _D_EXACT_NAMLEN(p) ((p)->d_namlen) + +/* Return the maximum size of a file name */ +#define _D_ALLOC_NAMLEN(p) ((PATH_MAX)+1) + + +#ifdef __cplusplus +extern "C" { +#endif + + + /* Wide-character version */ + struct _wdirent { + /* Always zero */ + long d_ino; + + /* File position within stream */ + long d_off; + + /* Structure size */ + unsigned short d_reclen; + + /* Length of name without \0 */ + size_t d_namlen; + + /* File type */ + int d_type; + + /* File name */ + wchar_t d_name[PATH_MAX + 1]; + }; + typedef struct _wdirent _wdirent; + + struct _WDIR { + /* Current directory entry */ + struct _wdirent ent; + + /* Private file data */ + WIN32_FIND_DATAW data; + + /* True if data is valid */ + int cached; + + /* Win32 search handle */ + HANDLE handle; + + /* Initial directory name */ + wchar_t* patt; + }; + typedef struct _WDIR _WDIR; + + /* Multi-byte character version */ + struct dirent { + /* Always zero */ + long d_ino; + + /* File position within stream */ + long d_off; + + /* Structure size */ + unsigned short d_reclen; + + /* Length of name without \0 */ + size_t d_namlen; + + /* File type */ + int d_type; + + /* File name */ + char d_name[PATH_MAX + 1]; + }; + typedef struct dirent dirent; + + struct DIR { + struct dirent ent; + struct _WDIR* wdirp; + }; + typedef struct DIR DIR; + + + /* Dirent functions */ + static DIR* opendir(const char* dirname); + static _WDIR* _wopendir(const wchar_t* dirname); + + static struct dirent* readdir(DIR* dirp); + static struct _wdirent* _wreaddir(_WDIR* dirp); + + static int readdir_r( + DIR* dirp, struct dirent* entry, struct dirent** result); + static int _wreaddir_r( + _WDIR* dirp, struct _wdirent* entry, struct _wdirent** result); + + static int closedir(DIR* dirp); + static int _wclosedir(_WDIR* dirp); + + static void rewinddir(DIR* dirp); + static void _wrewinddir(_WDIR* dirp); + + static int scandir(const char* dirname, struct dirent*** namelist, + int (*filter)(const struct dirent*), + int (*compare)(const struct dirent**, const struct dirent**)); + + static int alphasort(const struct dirent** a, const struct dirent** b); + + static int versionsort(const struct dirent** a, const struct dirent** b); + + static int strverscmp(const char* a, const char* b); + + /* For compatibility with Symbian */ +#define wdirent _wdirent +#define WDIR _WDIR +#define wopendir _wopendir +#define wreaddir _wreaddir +#define wclosedir _wclosedir +#define wrewinddir _wrewinddir + +/* Compatibility with older Microsoft compilers and non-Microsoft compilers */ +#if !defined(_MSC_VER) || _MSC_VER < 1400 +# define wcstombs_s dirent_wcstombs_s +# define mbstowcs_s dirent_mbstowcs_s +#endif + +/* Optimize dirent_set_errno() away on modern Microsoft compilers */ +#if defined(_MSC_VER) && _MSC_VER >= 1400 +# define dirent_set_errno _set_errno +#endif + + +/* Internal utility functions */ + static WIN32_FIND_DATAW* dirent_first(_WDIR* dirp); + static WIN32_FIND_DATAW* dirent_next(_WDIR* dirp); + +#if !defined(_MSC_VER) || _MSC_VER < 1400 + static int dirent_mbstowcs_s( + size_t* pReturnValue, wchar_t* wcstr, size_t sizeInWords, + const char* mbstr, size_t count); +#endif + +#if !defined(_MSC_VER) || _MSC_VER < 1400 + static int dirent_wcstombs_s( + size_t* pReturnValue, char* mbstr, size_t sizeInBytes, + const wchar_t* wcstr, size_t count); +#endif + +#if !defined(_MSC_VER) || _MSC_VER < 1400 + static void dirent_set_errno(int error); +#endif + + + /* + * Open directory stream DIRNAME for read and return a pointer to the + * internal working area that is used to retrieve individual directory + * entries. + */ + static _WDIR* _wopendir(const wchar_t* dirname) + { + wchar_t* p; + + /* Must have directory name */ + if (dirname == NULL || dirname[0] == '\0') { + dirent_set_errno(ENOENT); + return NULL; + } + + /* Allocate new _WDIR structure */ + _WDIR* dirp = (_WDIR*)malloc(sizeof(struct _WDIR)); + if (!dirp) + return NULL; + + /* Reset _WDIR structure */ + dirp->handle = INVALID_HANDLE_VALUE; + dirp->patt = NULL; + dirp->cached = 0; + + /* + * Compute the length of full path plus zero terminator + * + * Note that on WinRT there's no way to convert relative paths + * into absolute paths, so just assume it is an absolute path. + */ +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + /* Desktop */ + DWORD n = GetFullPathNameW(dirname, 0, NULL, NULL); +#else + /* WinRT */ + size_t n = wcslen(dirname); +#endif + + /* Allocate room for absolute directory name and search pattern */ + dirp->patt = (wchar_t*)malloc(sizeof(wchar_t) * n + 16); + if (dirp->patt == NULL) + goto exit_closedir; + + /* + * Convert relative directory name to an absolute one. This + * allows rewinddir() to function correctly even when current + * working directory is changed between opendir() and rewinddir(). + * + * Note that on WinRT there's no way to convert relative paths + * into absolute paths, so just assume it is an absolute path. + */ +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + /* Desktop */ + n = GetFullPathNameW(dirname, n, dirp->patt, NULL); + if (n <= 0) + goto exit_closedir; +#else + /* WinRT */ + wcsncpy_s(dirp->patt, n + 1, dirname, n); +#endif + + /* Append search pattern \* to the directory name */ + p = dirp->patt + n; + switch (p[-1]) { + case '\\': + case '/': + case ':': + /* Directory ends in path separator, e.g. c:\temp\ */ + /*NOP*/; + break; + + default: + /* Directory name doesn't end in path separator */ + *p++ = '\\'; + } + *p++ = '*'; + *p = '\0'; + + /* Open directory stream and retrieve the first entry */ + if (!dirent_first(dirp)) + goto exit_closedir; + + /* Success */ + return dirp; + + /* Failure */ + exit_closedir: + _wclosedir(dirp); + return NULL; + } + + /* + * Read next directory entry. + * + * Returns pointer to static directory entry which may be overwritten by + * subsequent calls to _wreaddir(). + */ + static struct _wdirent* _wreaddir(_WDIR* dirp) + { + /* + * Read directory entry to buffer. We can safely ignore the return + * value as entry will be set to NULL in case of error. + */ + struct _wdirent* entry; + (void)_wreaddir_r(dirp, &dirp->ent, &entry); + + /* Return pointer to statically allocated directory entry */ + return entry; + } + + /* + * Read next directory entry. + * + * Returns zero on success. If end of directory stream is reached, then sets + * result to NULL and returns zero. + */ + static int _wreaddir_r( + _WDIR* dirp, struct _wdirent* entry, struct _wdirent** result) + { + /* Read next directory entry */ + WIN32_FIND_DATAW* datap = dirent_next(dirp); + if (!datap) { + /* Return NULL to indicate end of directory */ + *result = NULL; + return /*OK*/0; + } + + /* + * Copy file name as wide-character string. If the file name is too + * long to fit in to the destination buffer, then truncate file name + * to PATH_MAX characters and zero-terminate the buffer. + */ + size_t n = 0; + while (n < PATH_MAX && datap->cFileName[n] != 0) { + entry->d_name[n] = datap->cFileName[n]; + n++; + } + entry->d_name[n] = 0; + + /* Length of file name excluding zero terminator */ + entry->d_namlen = n; + + /* File type */ + DWORD attr = datap->dwFileAttributes; + if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) + entry->d_type = DT_CHR; + else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) + entry->d_type = DT_DIR; + else + entry->d_type = DT_REG; + + /* Reset dummy fields */ + entry->d_ino = 0; + entry->d_off = 0; + entry->d_reclen = sizeof(struct _wdirent); + + /* Set result address */ + *result = entry; + return /*OK*/0; + } + + /* + * Close directory stream opened by opendir() function. This invalidates the + * DIR structure as well as any directory entry read previously by + * _wreaddir(). + */ + static int _wclosedir(_WDIR* dirp) + { + if (!dirp) { + dirent_set_errno(EBADF); + return /*failure*/-1; + } + + /* Release search handle */ + if (dirp->handle != INVALID_HANDLE_VALUE) + FindClose(dirp->handle); + + /* Release search pattern */ + free(dirp->patt); + + /* Release directory structure */ + free(dirp); + return /*success*/0; + } + + /* + * Rewind directory stream such that _wreaddir() returns the very first + * file name again. + */ + static void _wrewinddir(_WDIR* dirp) + { + if (!dirp) + return; + + /* Release existing search handle */ + if (dirp->handle != INVALID_HANDLE_VALUE) + FindClose(dirp->handle); + + /* Open new search handle */ + dirent_first(dirp); + } + + /* Get first directory entry */ + static WIN32_FIND_DATAW* dirent_first(_WDIR* dirp) + { + if (!dirp) + return NULL; + + /* Open directory and retrieve the first entry */ + dirp->handle = FindFirstFileExW( + dirp->patt, FindExInfoStandard, &dirp->data, + FindExSearchNameMatch, NULL, 0); + if (dirp->handle == INVALID_HANDLE_VALUE) + goto error; + + /* A directory entry is now waiting in memory */ + dirp->cached = 1; + return &dirp->data; + + error: + /* Failed to open directory: no directory entry in memory */ + dirp->cached = 0; + + /* Set error code */ + DWORD errorcode = GetLastError(); + switch (errorcode) { + case ERROR_ACCESS_DENIED: + /* No read access to directory */ + dirent_set_errno(EACCES); + break; + + case ERROR_DIRECTORY: + /* Directory name is invalid */ + dirent_set_errno(ENOTDIR); + break; + + case ERROR_PATH_NOT_FOUND: + default: + /* Cannot find the file */ + dirent_set_errno(ENOENT); + } + return NULL; + } + + /* Get next directory entry */ + static WIN32_FIND_DATAW* dirent_next(_WDIR* dirp) + { + /* Is the next directory entry already in cache? */ + if (dirp->cached) { + /* Yes, a valid directory entry found in memory */ + dirp->cached = 0; + return &dirp->data; + } + + /* No directory entry in cache */ + if (dirp->handle == INVALID_HANDLE_VALUE) + return NULL; + + /* Read the next directory entry from stream */ + if (FindNextFileW(dirp->handle, &dirp->data) == FALSE) + goto exit_close; + + /* Success */ + return &dirp->data; + + /* Failure */ + exit_close: + FindClose(dirp->handle); + dirp->handle = INVALID_HANDLE_VALUE; + return NULL; + } + + /* Open directory stream using plain old C-string */ + static DIR* opendir(const char* dirname) + { + /* Must have directory name */ + if (dirname == NULL || dirname[0] == '\0') { + dirent_set_errno(ENOENT); + return NULL; + } + + /* Allocate memory for DIR structure */ + struct DIR* dirp = (DIR*)malloc(sizeof(struct DIR)); + if (!dirp) + return NULL; + + /* Convert directory name to wide-character string */ + wchar_t wname[PATH_MAX + 1]; + size_t n; + int error = mbstowcs_s(&n, wname, PATH_MAX + 1, dirname, PATH_MAX + 1); + if (error) + goto exit_failure; + + /* Open directory stream using wide-character name */ + dirp->wdirp = _wopendir(wname); + if (!dirp->wdirp) + goto exit_failure; + + /* Success */ + return dirp; + + /* Failure */ + exit_failure: + free(dirp); + return NULL; + } + + /* Read next directory entry */ + static struct dirent* readdir(DIR* dirp) + { + /* + * Read directory entry to buffer. We can safely ignore the return + * value as entry will be set to NULL in case of error. + */ + struct dirent* entry; + (void)readdir_r(dirp, &dirp->ent, &entry); + + /* Return pointer to statically allocated directory entry */ + return entry; + } + + /* + * Read next directory entry into called-allocated buffer. + * + * Returns zero on success. If the end of directory stream is reached, then + * sets result to NULL and returns zero. + */ + static int readdir_r( + DIR* dirp, struct dirent* entry, struct dirent** result) + { + /* Read next directory entry */ + WIN32_FIND_DATAW* datap = dirent_next(dirp->wdirp); + if (!datap) { + /* No more directory entries */ + *result = NULL; + return /*OK*/0; + } + + /* Attempt to convert file name to multi-byte string */ + size_t n; + int error = wcstombs_s( + &n, entry->d_name, PATH_MAX + 1, + datap->cFileName, PATH_MAX + 1); + + /* + * If the file name cannot be represented by a multi-byte string, then + * attempt to use old 8+3 file name. This allows the program to + * access files although file names may seem unfamiliar to the user. + * + * Be ware that the code below cannot come up with a short file name + * unless the file system provides one. At least VirtualBox shared + * folders fail to do this. + */ + if (error && datap->cAlternateFileName[0] != '\0') { + error = wcstombs_s( + &n, entry->d_name, PATH_MAX + 1, + datap->cAlternateFileName, PATH_MAX + 1); + } + + if (!error) { + /* Length of file name excluding zero terminator */ + entry->d_namlen = n - 1; + + /* File attributes */ + DWORD attr = datap->dwFileAttributes; + if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) + entry->d_type = DT_CHR; + else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) + entry->d_type = DT_DIR; + else + entry->d_type = DT_REG; + + /* Reset dummy fields */ + entry->d_ino = 0; + entry->d_off = 0; + entry->d_reclen = sizeof(struct dirent); + } + else { + /* + * Cannot convert file name to multi-byte string so construct + * an erroneous directory entry and return that. Note that + * we cannot return NULL as that would stop the processing + * of directory entries completely. + */ + entry->d_name[0] = '?'; + entry->d_name[1] = '\0'; + entry->d_namlen = 1; + entry->d_type = DT_UNKNOWN; + entry->d_ino = 0; + entry->d_off = -1; + entry->d_reclen = 0; + } + + /* Return pointer to directory entry */ + *result = entry; + return /*OK*/0; + } + + /* Close directory stream */ + static int closedir(DIR* dirp) + { + int ok; + + if (!dirp) + goto exit_failure; + + /* Close wide-character directory stream */ + ok = _wclosedir(dirp->wdirp); + dirp->wdirp = NULL; + + /* Release multi-byte character version */ + free(dirp); + return ok; + + exit_failure: + /* Invalid directory stream */ + dirent_set_errno(EBADF); + return /*failure*/-1; + } + + /* Rewind directory stream to beginning */ + static void rewinddir(DIR* dirp) + { + if (!dirp) + return; + + /* Rewind wide-character string directory stream */ + _wrewinddir(dirp->wdirp); + } + + /* Scan directory for entries */ + static int scandir( + const char* dirname, struct dirent*** namelist, + int (*filter)(const struct dirent*), + int (*compare)(const struct dirent**, const struct dirent**)) + { + int result; + + /* Open directory stream */ + DIR* dir = opendir(dirname); + if (!dir) { + /* Cannot open directory */ + return /*Error*/ -1; + } + + /* Read directory entries to memory */ + struct dirent* tmp = NULL; + struct dirent** files = NULL; + size_t size = 0; + size_t allocated = 0; + while (1) { + /* Allocate room for a temporary directory entry */ + if (!tmp) { + tmp = (struct dirent*)malloc(sizeof(struct dirent)); + if (!tmp) + goto exit_failure; + } + + /* Read directory entry to temporary area */ + struct dirent* entry; + if (readdir_r(dir, tmp, &entry) != /*OK*/0) + goto exit_failure; + + /* Stop if we already read the last directory entry */ + if (entry == NULL) + goto exit_success; + + /* Determine whether to include the entry in results */ + if (filter && !filter(tmp)) + continue; + + /* Enlarge pointer table to make room for another pointer */ + if (size >= allocated) { + /* Compute number of entries in the new table */ + size_t num_entries = size * 2 + 16; + + /* Allocate new pointer table or enlarge existing */ + void* p = realloc(files, sizeof(void*) * num_entries); + if (!p) + goto exit_failure; + + /* Got the memory */ + files = (dirent**)p; + allocated = num_entries; + } + + /* Store the temporary entry to ptr table */ + files[size++] = tmp; + tmp = NULL; + } + + exit_failure: + /* Release allocated file entries */ + for (size_t i = 0; i < size; i++) { + free(files[i]); + } + + /* Release the pointer table */ + free(files); + files = NULL; + + /* Exit with error code */ + result = /*error*/ -1; + goto exit_status; + + exit_success: + /* Sort directory entries */ + qsort(files, size, sizeof(void*), + (int (*) (const void*, const void*)) compare); + + /* Pass pointer table to caller */ + if (namelist) + *namelist = files; + + /* Return the number of directory entries read */ + result = (int)size; + + exit_status: + /* Release temporary directory entry, if we had one */ + free(tmp); + + /* Close directory stream */ + closedir(dir); + return result; + } + + /* Alphabetical sorting */ + static int alphasort(const struct dirent** a, const struct dirent** b) + { + return strcoll((*a)->d_name, (*b)->d_name); + } + + /* Sort versions */ + static int versionsort(const struct dirent** a, const struct dirent** b) + { + return strverscmp((*a)->d_name, (*b)->d_name); + } + + /* Compare strings */ + static int strverscmp(const char* a, const char* b) + { + size_t i = 0; + size_t j; + + /* Find first difference */ + while (a[i] == b[i]) { + if (a[i] == '\0') { + /* No difference */ + return 0; + } + ++i; + } + + /* Count backwards and find the leftmost digit */ + j = i; + while (j > 0 && isdigit(a[j - 1])) { + --j; + } + + /* Determine mode of comparison */ + if (a[j] == '0' || b[j] == '0') { + /* Find the next non-zero digit */ + while (a[j] == '0' && a[j] == b[j]) { + j++; + } + + /* String with more digits is smaller, e.g 002 < 01 */ + if (isdigit(a[j])) { + if (!isdigit(b[j])) { + return -1; + } + } + else if (isdigit(b[j])) { + return 1; + } + } + else if (isdigit(a[j]) && isdigit(b[j])) { + /* Numeric comparison */ + size_t k1 = j; + size_t k2 = j; + + /* Compute number of digits in each string */ + while (isdigit(a[k1])) { + k1++; + } + while (isdigit(b[k2])) { + k2++; + } + + /* Number with more digits is bigger, e.g 999 < 1000 */ + if (k1 < k2) + return -1; + else if (k1 > k2) + return 1; + } + + /* Alphabetical comparison */ + return (int)((unsigned char)a[i]) - ((unsigned char)b[i]); + } + + /* Convert multi-byte string to wide character string */ +#if !defined(_MSC_VER) || _MSC_VER < 1400 + static int dirent_mbstowcs_s( + size_t* pReturnValue, wchar_t* wcstr, + size_t sizeInWords, const char* mbstr, size_t count) + { + /* Older Visual Studio or non-Microsoft compiler */ + size_t n = mbstowcs(wcstr, mbstr, sizeInWords); + if (wcstr && n >= count) + return /*error*/ 1; + + /* Zero-terminate output buffer */ + if (wcstr && sizeInWords) { + if (n >= sizeInWords) + n = sizeInWords - 1; + wcstr[n] = 0; + } + + /* Length of multi-byte string with zero terminator */ + if (pReturnValue) { + *pReturnValue = n + 1; + } + + /* Success */ + return 0; + } +#endif + + /* Convert wide-character string to multi-byte string */ +#if !defined(_MSC_VER) || _MSC_VER < 1400 + static int dirent_wcstombs_s( + size_t* pReturnValue, char* mbstr, + size_t sizeInBytes, const wchar_t* wcstr, size_t count) + { + /* Older Visual Studio or non-Microsoft compiler */ + size_t n = wcstombs(mbstr, wcstr, sizeInBytes); + if (mbstr && n >= count) + return /*error*/1; + + /* Zero-terminate output buffer */ + if (mbstr && sizeInBytes) { + if (n >= sizeInBytes) { + n = sizeInBytes - 1; + } + mbstr[n] = '\0'; + } + + /* Length of resulting multi-bytes string WITH zero-terminator */ + if (pReturnValue) { + *pReturnValue = n + 1; + } + + /* Success */ + return 0; + } +#endif + + /* Set errno variable */ +#if !defined(_MSC_VER) || _MSC_VER < 1400 + static void dirent_set_errno(int error) + { + /* Non-Microsoft compiler or older Microsoft compiler */ + errno = error; + } +#endif + +#ifdef __cplusplus +} +#endif +#endif /*DIRENT_H*/ \ No newline at end of file diff --git a/src/utils/file_utils.c b/src/utils/file_utils.c index ae2bb707f..556fcdfb1 100644 --- a/src/utils/file_utils.c +++ b/src/utils/file_utils.c @@ -6,18 +6,65 @@ #include "common.h" #include "errors.h" #include "lib.h" + +#if PLATFORM_WINDOWS + +#include + +#endif + +#ifndef _MSC_VER #include +#include #include #include -#include +#else +#include "utils/dirent.h" +#define PATH_MAX 260 + +// dirname and basename on windows +#include "win_dirname_basename.h" + +// copied from https://github.com/kindkaktus/libconfig/commit/d6222551c5c01c326abc99627e151d549e0f0958 +#ifndef S_ISDIR +#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#endif +// copied from https://stackoverflow.com/questions/11238918/s-isreg-macro-undefined +#if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG) +#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +#endif + +#endif + #include #include "whereami.h" #if PLATFORM_WINDOWS -#include + + +/** + * remove C: drive prefix (C:, D:, etc.) from a path. THIS WON'T WORK WITH D:, ETC. + * If that is an issue, I think dirent will have to be replaced or the dirent + * port in use will have to be replaced. + */ +char* strip_drive_prefix(char* path) { + if ((*path == 'c' || *path == 'C') && path[1] == ':') { + return path + 2; // remove first two characters + } + else if (path[1] == ':' && (path[2] == '/' || path[2] == '\\')) { // I don't *think* a relative path can start with '[char]:/' ? right? + // nothing can be done about this currently + error_exit("Illegal path %s - absolute path must start with /, \\, c:, or C: (file a github issue if this is a problem)"); + } + else { + // path is ok + return path; + } +} + #endif + const char* expand_path(const char* path) { if (path[0] == '~' && path[1] == '/') @@ -147,7 +194,11 @@ void file_find_top_dir() void file_add_wildcard_files(const char ***files, const char *path, bool recursive) { - DIR *dir = opendir(path); +#ifdef _MSC_VER + DIR *dir = opendir(strip_drive_prefix(path)); +#else + DIR* dir = opendir(path); +#endif bool path_ends_with_slash = path[strlen(path) - 1] == '/'; if (!dir) { diff --git a/src/utils/find_msvc.c b/src/utils/find_msvc.c new file mode 100644 index 000000000..b0fa1d9de --- /dev/null +++ b/src/utils/find_msvc.c @@ -0,0 +1,79 @@ +#include "utils/common.h" +#ifdef _MSC_VER + +#include "utils/find_msvc.h" + +#include + +#include "utils/dirent.h" + +#define MSVC_BASE_PATH "/Program Files (x86)/Microsoft Visual Studio/" +#define WINKIT_BASE_PATH "/Program Files (x86)/Windows Kits/" + + +int is_numeric(const struct dirent* ent) { + return atoi(ent->d_name); // don't use this function if you expect a file named "0" ? +} + +char* get_highest_ver(char* directory, int (*filter)(const struct dirent*)) { + struct dirent** files; + int num_files = scandir(directory, &files, filter, versionsort); + if (num_files < 0) { + error_exit("ERROR - Failed to autodetect MSVC libpaths\n"); + } + char* path_ret = (char*)malloc(260); + strcpy_s(path_ret, 260, files[num_files - 1]->d_name); + for (int i = 0; i < num_files; i++) free(files[i]); + free(files); + return path_ret; +} + +/** + * @returns PathPair containing paths to .../MSVC/[version]/lib/x64 and .../MSVC/[v]/atlmfc/lib/x64 + */ +PathPair get_latest_available_vs_path() { + char ver_name[16] = ""; + + char* highest_ver = get_highest_ver(MSVC_BASE_PATH, is_numeric); + strncpy_s(ver_name, 16, highest_ver, 4); + free(highest_ver); + + char newpath[260]; + snprintf(newpath, 260, "%s%s/BuildTools/VC/Tools/MSVC/", MSVC_BASE_PATH, ver_name); + + highest_ver = get_highest_ver(newpath, NULL); + strcat_s(newpath, 260, highest_ver); + free(highest_ver); + + PathPair ret = { 0 }; + snprintf(ret.first, 260, "%s/lib/x64", newpath); + snprintf(ret.second, 260, "%s/atlmfc/lib/x64", newpath); + + return ret; +} + +/** + * @returns PathPair containing paths to /Program Files (x86)/Windows Kits/[version]/Lib/[version]/ucrt/x64 and .../[version]/um/x64 + */ +PathPair find_winkit_path() { + // windows version + char win_ver_major[16] = ""; + char* highest_ver = get_highest_ver(WINKIT_BASE_PATH, is_numeric); + strcpy_s(win_ver_major, 16, highest_ver); + free(highest_ver); + + // windows kit version? or something + char newpath[260] = ""; + sprintf_s(newpath, 260, "%s%s/Lib/", WINKIT_BASE_PATH, win_ver_major); + highest_ver = get_highest_ver(newpath, NULL); + strcat_s(newpath, 260, highest_ver); + free(highest_ver); + + PathPair ret = { 0 }; + snprintf(ret.first, 260, "%s/ucrt/x64", newpath); + snprintf(ret.second, 260, "%s/um/x64", newpath); + + return ret; +} + +#endif //defined(_MSC_VER) \ No newline at end of file diff --git a/src/utils/find_msvc.h b/src/utils/find_msvc.h new file mode 100644 index 000000000..31d536cbd --- /dev/null +++ b/src/utils/find_msvc.h @@ -0,0 +1,9 @@ +#pragma once + +typedef struct { + char first[260]; + char second[260]; +} PathPair; + +PathPair get_latest_available_vs_path(); +PathPair find_winkit_path(); \ No newline at end of file diff --git a/src/utils/stringutils.c b/src/utils/stringutils.c index e888e74b9..6bef7dbe6 100644 --- a/src/utils/stringutils.c +++ b/src/utils/stringutils.c @@ -104,6 +104,21 @@ int vasnprintf(char **strp, const char *fmt, va_list args) return res; } +int vasprintf(char** ret, const char* fmt, va_list args) { + int length = _vsnprintf(NULL, 0, fmt, args); + if (length < 0) { // check if _vsnprintf failed + return -1; + } + *ret = malloc(length + 1); + if (!*ret) { // check if malloc failed + return -1; + } + // Write String + _vsnprintf(*ret, length + 1, fmt, args); + (*ret)[length] = '\0'; // make sure there is a null terminator + return length; +} + char *strndup(const char *s, size_t n) { n = strnlen(s, n); diff --git a/src/utils/vmem.c b/src/utils/vmem.c index 1c413d58a..dfaa85a88 100644 --- a/src/utils/vmem.c +++ b/src/utils/vmem.c @@ -2,13 +2,14 @@ // Use of this source code is governed by a LGPLv3.0 // a copy of which can be found in the LICENSE file. + #include "vmem.h" #if PLATFORM_POSIX #include #endif -#if PLATFORM_WINDOWS +#if defined(WIN32) #include #define COMMIT_PAGE_SIZE 0x10000 #endif diff --git a/src/utils/whereami.c b/src/utils/whereami.c index 104bbae77..69c1be105 100644 --- a/src/utils/whereami.c +++ b/src/utils/whereami.c @@ -4,6 +4,10 @@ // Code based off Gregory Pakosz's whereami. +#if defined(WIN32) +#include +#endif + #include "whereami.h" #include @@ -32,7 +36,6 @@ #pragma warning(push, 3) #endif -#include #include #if defined(_MSC_VER) diff --git a/src/utils/win_dirname_basename.h b/src/utils/win_dirname_basename.h new file mode 100644 index 000000000..dee5bbf39 --- /dev/null +++ b/src/utils/win_dirname_basename.h @@ -0,0 +1,64 @@ +#include +#include +#include + +// TODO I made these super quickly so they probably have some bugs from cases I didn't think of. +// A better implementation of these functions may be necessary. + +char* basename(char* path) +{ + size_t len = strlen(path); + size_t lastSlash = 0; + for (size_t i = len - 2; i > 0; i--) + { + if (path[i] == '\\' || path[i] == '/') + { + if (i == len) + { + continue; + } + lastSlash = i; + break; + } + if (i == 0) + { + // according to `man 3 basename` if there is no /, the original path should be returned + char* newStr = (char*)malloc(len); + strcpy(newStr, path); + return newStr; + } + } + size_t newSize = len - lastSlash - 1; + char* newStr = (char*)malloc(newSize); + strncpy(newStr, path + lastSlash + 1, newSize); + return newStr; +} + +char* dirname(char* path) +{ + size_t len = strlen(path); + size_t lastSlash = 0; + for (size_t i = len - 2; i > 0; i--) + { + if (path[i] == '\\' || path[i] == '/') + { + if (i == len) + { + continue; + } + lastSlash = i; + break; + } + if (i == 0) + { + // according to `man 3 basename` if there is no /, "." should be returned + char* newStr = (char*)malloc(2); + strcpy(newStr, "."); + return newStr; + } + } + // size_t newSize = len - lastSlash - 1; + char* newStr = (char*)malloc(lastSlash); + strncpy(newStr, path, lastSlash); + return newStr; +} \ No newline at end of file diff --git a/windows_build.md b/windows_build.md new file mode 100644 index 000000000..de2f0b213 --- /dev/null +++ b/windows_build.md @@ -0,0 +1,29 @@ +# Building and using C3C on windows + +Windows support in C3C is currently experimental and unstable. It has many limitations and likely has bugs. With that aside, here's how you can build and use it: + +## Compiling LLVM + +First, follow the steps outlined here to set up and compile LLVM: + +https://llvm.org/docs/GettingStartedVS.html + +When running cmake, use the following options to enable all of the libraries needed by C3C: + +``` +cmake .. -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TARGETS_TO_BUILD="ARM;AArch64;RISCV;WebAssembly;X86" -Thost=x64 +``` + +## Compiling C3C + +Clone the C3C github repository and open its directory in visual studio. VS should detect the CMake configuration, allowing you to simply use Build -> Build All to compile C3C. A `c3c.exe` will be placed in `out/build/build`. + +## Using C3C on Windows + +At this point, you should be able to use C3C normally. For example: + +``` +/c3c/out/build/build/c3c.exe compile ./hello_world.c3c +``` + +Note that on windows, linker arguments passed through c3c using `-z` will need to be of the format expected by `lld-link`, which uses MSVC `link.exe`-format arguments rather than the GCC/Clang format.