From de8a733c776baa3c1670b6b09499da11f46da202 Mon Sep 17 00:00:00 2001 From: Manuel Barrio Linares Date: Mon, 9 Feb 2026 12:56:31 -0300 Subject: [PATCH] Implement lazy-loaded libcurl - Uses `dlopen` to load libcurl at runtime for better portability. - Replaces the FETCH_AVAILABLE macro with download_available() check. - Provides descriptive error messages when libcurl is missing. --- CMakeLists.txt | 14 ++-- src/build/build_options.c | 4 +- src/build/project_manipulation.c | 14 ++-- src/compiler/compiler.c | 14 ++-- src/utils/common.h | 5 -- src/utils/fetch_msvc.c | 19 +++-- src/utils/http.c | 118 ++++++++++++++++++++++++++----- src/utils/lib.h | 3 +- 8 files changed, 131 insertions(+), 60 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4734905e3..93e2dbfec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -129,9 +129,6 @@ if(C3_USE_MIMALLOC) ) FetchContent_MakeAvailable(mimalloc) endif() -if (NOT WIN32) - find_package(CURL) -endif() find_package(Git QUIET) if(C3_USE_TB AND GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git") @@ -606,12 +603,11 @@ if(MINGW) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--stack,8388608") endif () -if (CURL_FOUND) - target_link_libraries(c3c ${CURL_LIBRARIES}) - target_include_directories(c3c PRIVATE ${CURL_INCLUDE_DIRS}) - target_compile_definitions(c3c PUBLIC CURL_FOUND=1) -else() - target_compile_definitions(c3c PUBLIC CURL_FOUND=0) +if (NOT WIN32) + # For dlopen support + if (CMAKE_DL_LIBS) + target_link_libraries(c3c ${CMAKE_DL_LIBS}) + endif() endif() diff --git a/src/build/build_options.c b/src/build/build_options.c index c1d2dd7f8..c2d367ae1 100644 --- a/src/build/build_options.c +++ b/src/build/build_options.c @@ -259,9 +259,7 @@ static void project_usage() PRINTF("Project Subcommands:"); print_cmd("view", "view the current projects structure."); print_cmd("add-target [sources...]", "add a new target to the project."); - #if FETCH_AVAILABLE - print_cmd("fetch", "fetch missing project libraries."); - #endif + print_cmd("fetch", "fetch missing project libraries."); } static void project_view_usage() diff --git a/src/build/project_manipulation.c b/src/build/project_manipulation.c index 708a93ae4..01d313ab8 100644 --- a/src/build/project_manipulation.c +++ b/src/build/project_manipulation.c @@ -268,9 +268,14 @@ static void view_target(BuildParseContext context, JSONObject *target, bool verb -#if FETCH_AVAILABLE + void fetch_project(BuildOptions* options) { + if (!download_available()) + { + error_exit("The 'project fetch' command requires libcurl to download dependencies.\n" + "Please ensure libcurl is installed on your system."); + } if (!file_exists(PROJECT_JSON5) && !file_exists(PROJECT_JSON)) { error_exit("Failed: no project file found."); @@ -343,12 +348,7 @@ void fetch_project(BuildOptions* options) } } } -#else -void fetch_project(BuildOptions* options) -{ - error_exit("Error: project fetch only available when compiled with cURL."); -} -#endif + void add_libraries_to_project_file(const char** libs, const char* target_name) { diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index 0842bfd06..91c8b1cd8 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -966,7 +966,7 @@ bool use_ansi(void) #endif } -#if FETCH_AVAILABLE + const char * vendor_fetch_single(const char* lib, const char* path) { const char *resource = str_printf("/c3lang/vendor/releases/download/latest/%s.c3l", lib); @@ -993,6 +993,11 @@ void update_progress_bar(const char* lib, int current_step, int total_steps) void vendor_fetch(BuildOptions *options) { + if (!download_available()) + { + error_exit("The 'vendor-fetch' command requires libcurl to download libraries.\n" + "Please ensure libcurl is installed on your system."); + } bool ansi = use_ansi(); if (str_eq(options->path, DEFAULT_PATH)) @@ -1061,12 +1066,7 @@ void vendor_fetch(BuildOptions *options) if (ansi) printf("\033[32mFetching complete.\033[0m\t\t\n"); } -#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/utils/common.h b/src/utils/common.h index ed7d65915..aa09cf113 100644 --- a/src/utils/common.h +++ b/src/utils/common.h @@ -54,11 +54,6 @@ #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 diff --git a/src/utils/fetch_msvc.c b/src/utils/fetch_msvc.c index 99ba367d0..dae98cdec 100644 --- a/src/utils/fetch_msvc.c +++ b/src/utils/fetch_msvc.c @@ -136,7 +136,7 @@ static void closedir(DIR *dir) } #endif -#if FETCH_AVAILABLE + static int version_compare(const char *v1, const char *v2) { @@ -509,6 +509,13 @@ static bool check_license(JSONObject *rj1_channel_items, bool accept_all) void fetch_msvc(BuildOptions *options) { + if (!download_available()) + { + error_exit("Failed to find Windows SDK.\n" + "Windows applications cannot be cross-compiled without it.\n" + "To download the SDK automatically, please ensure libcurl is installed.\n" + "Alternatively, provide the SDK path manually using --winsdk."); + } verbose_level = options->verbosity_level; const char *tmp_dir_base = dir_make_temp_dir(); if (!tmp_dir_base) error_exit("Failed to create temp directory"); @@ -749,13 +756,5 @@ void fetch_msvc(BuildOptions *options) if (verbose_level == 0) file_delete_dir(tmp_dir_base); } -#else -void fetch_msvc(BuildOptions *options) -{ - error_exit("Failed to find Windows SDK.\n" - "Windows applications cannot be cross-compiled without it.\n" - "This build of c3c lacks cURL support and cannot download the SDK automatically.\n" - "Please provide the SDK path manually using --winsdk."); -} -#endif + diff --git a/src/utils/http.c b/src/utils/http.c index 94a52196e..915ec4812 100644 --- a/src/utils/http.c +++ b/src/utils/http.c @@ -105,8 +105,84 @@ END: return NULL; } -#elif CURL_FOUND -#include +bool download_available(void) +{ + return true; +} + +#elif PLATFORM_POSIX + +#include + +typedef void CURL; +typedef int CURLcode; +typedef int CURLoption; + +#define CURLE_OK 0 +#define CURLOPT_URL 10002 +#define CURLOPT_FOLLOWLOCATION 52 +#define CURLOPT_VERBOSE 41 +#define CURLOPT_NOPROGRESS 43 +#define CURLOPT_FAILONERROR 45 +#define CURLOPT_WRITEFUNCTION 20011 +#define CURLOPT_WRITEDATA 10001 +#define CURLOPT_CAINFO 10065 + +static void *libcurl = NULL; +static CURL* (*ptr_curl_easy_init)(void); +static CURLcode (*ptr_curl_easy_setopt)(CURL *, CURLoption, ...); +static CURLcode (*ptr_curl_easy_perform)(CURL *); +static void (*ptr_curl_easy_cleanup)(CURL *); +static const char* (*ptr_curl_easy_strerror)(CURLcode); + +static bool load_curl(void) +{ + if (libcurl) return true; + const char *names[] = { +#ifdef __APPLE__ + "libcurl.4.dylib", + "libcurl.dylib", + "/usr/lib/libcurl.4.dylib", + "/usr/lib/libcurl.dylib", + "/opt/homebrew/lib/libcurl.dylib", + "/usr/local/lib/libcurl.dylib" +#else + "libcurl.so.4", + "libcurl.so", + "libcurl.so.3", + "libcurl-gnutls.so.4", + "libcurl-nss.so.4" +#endif + }; + + for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); i++) + { + libcurl = dlopen(names[i], RTLD_LAZY); + if (libcurl) break; + } + + if (!libcurl) return false; + + ptr_curl_easy_init = dlsym(libcurl, "curl_easy_init"); + ptr_curl_easy_setopt = dlsym(libcurl, "curl_easy_setopt"); + ptr_curl_easy_perform = dlsym(libcurl, "curl_easy_perform"); + ptr_curl_easy_cleanup = dlsym(libcurl, "curl_easy_cleanup"); + ptr_curl_easy_strerror = dlsym(libcurl, "curl_easy_strerror"); + + if (!ptr_curl_easy_init || !ptr_curl_easy_setopt || !ptr_curl_easy_perform || !ptr_curl_easy_cleanup || !ptr_curl_easy_strerror) + { + dlclose(libcurl); + libcurl = NULL; + return false; + } + + return true; +} + +bool download_available(void) +{ + return load_curl(); +} static size_t write_data(void *ptr, size_t size, size_t nmemb, void *stream) { @@ -115,35 +191,43 @@ static size_t write_data(void *ptr, size_t size, size_t nmemb, void *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."); + if (!load_curl()) + { + return "This build of c3c lacks cURL support and cannot download files automatically.\n" + "Please ensure libcurl is installed on your system."; + } + + CURL *curl_handle = ptr_curl_easy_init(); + if (!curl_handle) return "Could not initialize cURL subsystem."; + FILE *file = fopen(file_path, "w+b"); if (!file) { - curl_easy_cleanup(curl_handle); + ptr_curl_easy_cleanup(curl_handle); 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); + ptr_curl_easy_setopt(curl_handle, CURLOPT_URL, total_url); + ptr_curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1L); + ptr_curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 0L); + ptr_curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1L); + ptr_curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, 1L); + ptr_curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_data); + ptr_curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, file); + + CURLcode result = ptr_curl_easy_perform(curl_handle); if (result != CURLE_OK) { fclose(file); remove(file_path); - const char *err_msg = str_dup(curl_easy_strerror(result)); - curl_easy_cleanup(curl_handle); + const char *err_msg = str_dup(ptr_curl_easy_strerror(result)); + ptr_curl_easy_cleanup(curl_handle); return err_msg; } + fclose(file); - curl_easy_cleanup(curl_handle); + ptr_curl_easy_cleanup(curl_handle); return NULL; } diff --git a/src/utils/lib.h b/src/utils/lib.h index cc32b1d90..7ef2aaa86 100644 --- a/src/utils/lib.h +++ b/src/utils/lib.h @@ -12,9 +12,8 @@ #include "intrin.h" #endif -#if FETCH_AVAILABLE const char *download_file(const char *url, const char *resource, const char *file_path); -#endif +bool download_available(void); #define ELEMENTLEN(x) (sizeof(x) / sizeof(x[0])) extern const char *compiler_exe_name;