From 38be3d57dde848ac950a0c4a321a834be1a1b500 Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Fri, 6 Jan 2023 14:00:51 +0100 Subject: [PATCH] Vendor fetch. --- .github/workflows/main.yml | 16 ++++- CMakeLists.txt | 18 +++++- src/build/build_options.c | 12 ++++ src/build/build_options.h | 2 + src/build/builder.c | 1 + src/compiler/compiler.c | 28 +++++++++ src/compiler/compiler.h | 1 + src/main.c | 3 + src/utils/common.h | 6 ++ src/utils/http.c | 123 +++++++++++++++++++++++++++++++++++++ src/utils/lib.h | 3 + src/version.h | 2 +- 12 files changed, 210 insertions(+), 5 deletions(-) create mode 100644 src/utils/http.c diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b8105860d..b9e92f75a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -47,6 +47,10 @@ jobs: cd resources/testproject ..\..\build\${{ matrix.build_type }}\c3c.exe --debug-log build hello_world_win32_lib + - name: Vendor-fetch + run: | + build\${{ matrix.build_type }}\c3c.exe vendor-fetch raylib + - name: run compiler tests run: | cd test @@ -103,6 +107,10 @@ jobs: cd resources/testproject ../../build/c3c run --debug-log + - name: Vendor-fetch + run: | + ./build/c3c vendor-fetch raylib + - name: Build testproject lib run: | cd resources/testproject @@ -175,7 +183,7 @@ jobs: - uses: actions/checkout@v3 - name: Install common deps run: | - sudo apt-get install zlib1g zlib1g-dev python3 ninja-build + sudo apt-get install zlib1g zlib1g-dev python3 ninja-build curl - name: Install Clang ${{matrix.llvm_version}} run: | @@ -264,7 +272,7 @@ jobs: - name: Download LLVM run: | brew update && brew install --overwrite python && brew install python-tk - brew install llvm@${{ matrix.llvm_version }} botan ninja + brew install llvm@${{ matrix.llvm_version }} botan ninja curl echo "/usr/local/opt/llvm@${{ matrix.llvm_version }}/bin" >> $GITHUB_PATH TMP_PATH=$(xcrun --show-sdk-path)/user/include echo "CPATH=$TMP_PATH" >> $GITHUB_ENV @@ -274,6 +282,10 @@ jobs: cmake -B build -G Ninja -DC3_LLVM_VERSION=${{matrix.llvm_version}} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} cmake --build build + - name: Vendor-fetch + run: | + ./build/c3c vendor-fetch raylib + - name: Compile and run some examples run: | cd resources diff --git a/CMakeLists.txt b/CMakeLists.txt index a6622137f..73f4f29af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,7 +42,9 @@ if(C3_USE_MIMALLOC) ) FetchContent_MakeAvailable(mimalloc) endif() - +if (NOT WIN32) + find_package(CURL) +endif() if (NOT C3_LLVM_VERSION STREQUAL "auto") if (${C3_LLVM_VERSION} VERSION_LESS 13 OR ${C3_LLVM_VERSION} VERSION_GREATER 16) message(FATAL_ERROR "LLVM ${C3_LLVM_VERSION} is not supported!") @@ -263,7 +265,7 @@ add_executable(c3c src/compiler/codegen_asm.c src/compiler/asm_target.c src/compiler/llvm_codegen_builtins.c - src/compiler/expr.c src/utils/time.c) + src/compiler/expr.c src/utils/time.c src/utils/http.c) target_include_directories(c3c PRIVATE @@ -291,6 +293,18 @@ if(C3_USE_MIMALLOC) target_link_libraries(c3c mimalloc-static) endif() +if (WIN32) + target_link_libraries(c3c Winhttp.lib) +endif() + +if (CURL_FOUND) + target_link_libraries(c3c ${CURL_LIBRARIES}) + target_include_directories(c3c PRIVATE ${CURL_INCLUDES}) + target_compile_definitions(c3c PUBLIC CURL_FOUND=1) +else() + target_compile_definitions(c3c PUBLIC CURL_FOUND=0) +endif() + if(MSVC) message("Adding MSVC options") target_compile_options(c3c PRIVATE /wd4068 /wd4090 /WX /Wv:18) diff --git a/src/build/build_options.c b/src/build/build_options.c index 30ff0dbfb..65cf94fd2 100644 --- a/src/build/build_options.c +++ b/src/build/build_options.c @@ -76,6 +76,7 @@ static void usage(void) OUTPUT(" static-lib [ ...] Compile files without a project into a static library."); OUTPUT(" dynamic-lib [ ...] Compile files without a project into a dynamic library."); OUTPUT(" headers [ ...] Analyse files and generate C headers for public methods."); + OUTPUT(" vendor-fetch ... Fetches one or more libraries from the vendor collection."); OUTPUT(""); OUTPUT("Options:"); OUTPUT(" --tinybackend - Use the TinyBackend for compilation."); @@ -274,6 +275,17 @@ static void parse_command(BuildOptions *options) options->command = COMMAND_STATIC_LIB; return; } + if (arg_match("vendor-fetch")) + { + options->command = COMMAND_VENDOR_FETCH; + if (at_end() || next_is_opt()) error_exit("error: vendor-fetch needs at least one library."); + while (!at_end() && !next_is_opt()) + { + const char *lib = next_arg(); + vec_add(options->libraries_to_fetch, lib); + } + return; + } if (arg_match("dynamic-lib")) { options->command = COMMAND_DYNAMIC_LIB; diff --git a/src/build/build_options.h b/src/build/build_options.h index 682c19877..2441c7e4f 100644 --- a/src/build/build_options.h +++ b/src/build/build_options.h @@ -33,6 +33,7 @@ typedef enum COMMAND_RUN, COMMAND_CLEAN_RUN, COMMAND_CLEAN, + COMMAND_VENDOR_FETCH, COMMAND_DIST, COMMAND_DOCS, COMMAND_BENCH, @@ -248,6 +249,7 @@ typedef struct BuildOptions_ const char *sdk_version; } macos; int build_threads; + const char** libraries_to_fetch; const char** files; const char* output_name; const char* project_name; diff --git a/src/build/builder.c b/src/build/builder.c index 8a7721b26..5b44717f6 100644 --- a/src/build/builder.c +++ b/src/build/builder.c @@ -84,6 +84,7 @@ bool command_is_projectless(CompilerCommand command) case COMMAND_BENCH: case COMMAND_PRINT_SYNTAX: case COMMAND_TEST: + case COMMAND_VENDOR_FETCH: return false; } UNREACHABLE diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index 28d0c73e7..bc0263649 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -574,6 +574,34 @@ static void setup_bool_define(const char *id, bool value) error_exit("Redefined ident %s", id); } } +#if FETCH_AVAILABLE +void vendor_fetch(BuildOptions *options) +{ + unsigned count = 0; + FOREACH_BEGIN(const char *lib, options->libraries_to_fetch) + const char *resource = str_printf("/c3lang/vendor/releases/download/latest/%s.c3l", lib); + printf("Fetching library '%s'...", lib); + fflush(stdout); + const char *error = download_file("https://github.com", resource, str_printf("%s.c3l", lib)); + if (!error) + { + puts("ok."); + count++; + } + else + { + printf("FAILED: '%s'\n", error); + } + FOREACH_END(); + if (count == 0) error_exit("Error: Failed to download any libraries."); + if (count < vec_size(options->libraries_to_fetch)) error_exit("Error: Only some libraries were downloaded."); +} +#else +void vendor_fetch(BuildOptions *options) +{ + error_exit("Error: vendor-fetch only available when compiled with cURL."); +} +#endif void print_syntax(BuildOptions *options) { diff --git a/src/compiler/compiler.h b/src/compiler/compiler.h index 258de68f6..6bad80128 100644 --- a/src/compiler/compiler.h +++ b/src/compiler/compiler.h @@ -16,6 +16,7 @@ void init_default_build_target(BuildTarget *target, BuildOptions *options); void symtab_init(uint32_t max_size); void symtab_destroy(); void print_syntax(BuildOptions *options); +void vendor_fetch(BuildOptions *options); extern double compiler_init_time; extern double compiler_parsing_time; diff --git a/src/main.c b/src/main.c index bbd37d0bb..83c09fe3b 100644 --- a/src/main.c +++ b/src/main.c @@ -69,6 +69,9 @@ int main_real(int argc, const char *argv[]) case COMMAND_CLEAN: compile_clean(&build_options); break; + case COMMAND_VENDOR_FETCH: + vendor_fetch(&build_options); + break; case COMMAND_CLEAN_RUN: case COMMAND_BUILD: case COMMAND_RUN: diff --git a/src/utils/common.h b/src/utils/common.h index 9f23ecda1..8e6728e79 100644 --- a/src/utils/common.h +++ b/src/utils/common.h @@ -38,6 +38,12 @@ #endif #endif +#if CURL_FOUND || PLATFORM_WINDOWS +#define FETCH_AVAILABLE 1 +#else +#define FETCH_AVAILABLE 0 +#endif + #define IS_GCC 0 #define IS_CLANG 0 #ifdef __GNUC__ diff --git a/src/utils/http.c b/src/utils/http.c new file mode 100644 index 000000000..12195a2ab --- /dev/null +++ b/src/utils/http.c @@ -0,0 +1,123 @@ +// Copyright (c) 2023 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. + +#include "lib.h" + +#if PLATFORM_WINDOWS + +#include +#include + +static inline wchar_t *char_to_wchar(const char *str) +{ + size_t size = strlen(str) + 1; + wchar_t *wc = malloc(sizeof(wchar_t) * size); + mbstowcs(wc, str, size); + return wc; +} + +const char *download_file(const char *url, const char *resource, const char *file_path) +{ + + LPSTR pszOutBuffer; + bool results = false; + HINTERNET hSession = NULL, + hConnect = NULL, + hRequest = NULL; + + + bool is_https = memcmp("https://", url, 8) == 0; + url = url + (is_https ? 8 : 7); + + // Use WinHttpOpen to obtain a session handle. + HINTERNET session = WinHttpOpen(L"C3C/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); + + if (!session) error_exit("Failed to create http session."); + // Specify an HTTP server. + + wchar_t *wurl = char_to_wchar(url); + HINTERNET connect = WinHttpConnect(session, wurl, is_https ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT, + 0); + if (!connect) error_exit("Failed to connect to '%s'", url); + free(wurl); + + // Create an HTTP request handle. + wchar_t *wresource = char_to_wchar(resource); + HINTERNET request = WinHttpOpenRequest(connect, L"GET", wresource, NULL, + WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, is_https ? WINHTTP_FLAG_SECURE : 0); + + if (!connect) error_exit("Failed to connect to '%s'.", url); + free(wresource); + + FILE* file = fopen(file_path, "w+b"); + if (!file) return str_printf("Failed to open file '%s' for output", file_path); + + + // Send a request. + bool result = WinHttpSendRequest(request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, + WINHTTP_NO_REQUEST_DATA, 0, 0, 0); + bool success = false; + if (!result || !WinHttpReceiveResponse(request, NULL)) goto END; + + char buffer[8192]; + while (1) + { + DWORD dw_size = 0; + if (!WinHttpReadData(request, (LPVOID)buffer, sizeof(buffer), &dw_size)) goto END; + fwrite(buffer, (size_t)dw_size, (size_t)1, file); + if (!WinHttpQueryDataAvailable(request, &dw_size)) goto END; + if (!dw_size) break; + } + success = true; +END: + fclose(file); + WinHttpCloseHandle(request); + WinHttpCloseHandle(connect); + WinHttpCloseHandle(session); + if (!success) + { + remove(file_path); + return str_printf("Failed to retrieve '%s%s'.", url, resource); + } + return NULL; +} + +#elif CURL_FOUND +#include + +static size_t write_data(void *ptr, size_t size, size_t nmemb, void *stream) +{ + return fwrite(ptr, size, nmemb, (FILE *)stream); +} + +const char *download_file(const char *url, const char *resource, const char *file_path) +{ + CURL *curl_handle = curl_easy_init(); + if (!curl_handle) error_exit("Could not initialize cURL subsystem."); + FILE* file = fopen(file_path, "w+b"); + if (!file) return str_printf("Failed to open file '%s' for output", file_path); + const char *total_url = str_printf("%s%s", url, resource); + curl_easy_setopt(curl_handle, CURLOPT_URL, total_url); + curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 0L); + // Enable this later + curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1L); + curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, 1L); + curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_data); + curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, file); + CURLcode result = curl_easy_perform(curl_handle); + if (curl_easy_perform(curl_handle) != CURLE_OK) + { + + fclose(file); + remove(file_path); + return curl_easy_strerror(result); + } + fclose(file); + return NULL; +} + +#endif \ No newline at end of file diff --git a/src/utils/lib.h b/src/utils/lib.h index 2e81fc4b3..861d31af8 100644 --- a/src/utils/lib.h +++ b/src/utils/lib.h @@ -11,6 +11,9 @@ #include "direct.h" #endif +#if FETCH_AVAILABLE +const char *download_file(const char *url, const char *resource, const char *file_path); +#endif typedef struct StringSlice_ { diff --git a/src/version.h b/src/version.h index 45fe15796..c37d200fb 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define COMPILER_VERSION "0.4.4" \ No newline at end of file +#define COMPILER_VERSION "0.4.5" \ No newline at end of file