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.
This commit is contained in:
Manuel Barrio Linares
2026-02-09 12:56:31 -03:00
committed by Christoffer Lerno
parent c5d7a03859
commit de8a733c77
8 changed files with 131 additions and 60 deletions

View File

@@ -129,9 +129,6 @@ if(C3_USE_MIMALLOC)
) )
FetchContent_MakeAvailable(mimalloc) FetchContent_MakeAvailable(mimalloc)
endif() endif()
if (NOT WIN32)
find_package(CURL)
endif()
find_package(Git QUIET) find_package(Git QUIET)
if(C3_USE_TB AND GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git") 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") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--stack,8388608")
endif () endif ()
if (CURL_FOUND) if (NOT WIN32)
target_link_libraries(c3c ${CURL_LIBRARIES}) # For dlopen support
target_include_directories(c3c PRIVATE ${CURL_INCLUDE_DIRS}) if (CMAKE_DL_LIBS)
target_compile_definitions(c3c PUBLIC CURL_FOUND=1) target_link_libraries(c3c ${CMAKE_DL_LIBS})
else() endif()
target_compile_definitions(c3c PUBLIC CURL_FOUND=0)
endif() endif()

View File

@@ -259,9 +259,7 @@ static void project_usage()
PRINTF("Project Subcommands:"); PRINTF("Project Subcommands:");
print_cmd("view", "view the current projects structure."); print_cmd("view", "view the current projects structure.");
print_cmd("add-target <name> <target_type> [sources...]", "add a new target to the project."); print_cmd("add-target <name> <target_type> [sources...]", "add a new target to the project.");
#if FETCH_AVAILABLE
print_cmd("fetch", "fetch missing project libraries."); print_cmd("fetch", "fetch missing project libraries.");
#endif
} }
static void project_view_usage() static void project_view_usage()

View File

@@ -268,9 +268,14 @@ static void view_target(BuildParseContext context, JSONObject *target, bool verb
#if FETCH_AVAILABLE
void fetch_project(BuildOptions* options) 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)) if (!file_exists(PROJECT_JSON5) && !file_exists(PROJECT_JSON))
{ {
error_exit("Failed: no project file found."); 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) { void add_libraries_to_project_file(const char** libs, const char* target_name) {

View File

@@ -966,7 +966,7 @@ bool use_ansi(void)
#endif #endif
} }
#if FETCH_AVAILABLE
const char * vendor_fetch_single(const char* lib, const char* path) const char * vendor_fetch_single(const char* lib, const char* path)
{ {
const char *resource = str_printf("/c3lang/vendor/releases/download/latest/%s.c3l", lib); 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) 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(); bool ansi = use_ansi();
if (str_eq(options->path, DEFAULT_PATH)) 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"); 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) void print_syntax(BuildOptions *options)
{ {

View File

@@ -54,11 +54,6 @@
#endif #endif
#endif #endif
#if CURL_FOUND || PLATFORM_WINDOWS
#define FETCH_AVAILABLE 1
#else
#define FETCH_AVAILABLE 0
#endif
#define IS_GCC 0 #define IS_GCC 0
#define IS_CLANG 0 #define IS_CLANG 0

View File

@@ -136,7 +136,7 @@ static void closedir(DIR *dir)
} }
#endif #endif
#if FETCH_AVAILABLE
static int version_compare(const char *v1, const char *v2) 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) 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; verbose_level = options->verbosity_level;
const char *tmp_dir_base = dir_make_temp_dir(); const char *tmp_dir_base = dir_make_temp_dir();
if (!tmp_dir_base) error_exit("Failed to create temp directory"); 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); 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

View File

@@ -105,8 +105,84 @@ END:
return NULL; return NULL;
} }
#elif CURL_FOUND bool download_available(void)
#include <curl/curl.h> {
return true;
}
#elif PLATFORM_POSIX
#include <dlfcn.h>
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) 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) const char *download_file(const char *url, const char *resource, const char *file_path)
{ {
CURL *curl_handle = curl_easy_init(); if (!load_curl())
if (!curl_handle) error_exit("Could not initialize cURL subsystem."); {
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"); FILE *file = fopen(file_path, "w+b");
if (!file) 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); return str_printf("Failed to open file '%s' for output", file_path);
} }
const char *total_url = str_printf("%s%s", url, resource); const char *total_url = str_printf("%s%s", url, resource);
curl_easy_setopt(curl_handle, CURLOPT_URL, total_url); ptr_curl_easy_setopt(curl_handle, CURLOPT_URL, total_url);
curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1L); ptr_curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 0L); ptr_curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 0L);
// Enable this later ptr_curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1L);
curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1L); ptr_curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, 1L);
curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, 1L); ptr_curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_data);
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_data); ptr_curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, file);
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, file);
CURLcode result = curl_easy_perform(curl_handle); CURLcode result = ptr_curl_easy_perform(curl_handle);
if (result != CURLE_OK) if (result != CURLE_OK)
{ {
fclose(file); fclose(file);
remove(file_path); remove(file_path);
const char *err_msg = str_dup(curl_easy_strerror(result)); const char *err_msg = str_dup(ptr_curl_easy_strerror(result));
curl_easy_cleanup(curl_handle); ptr_curl_easy_cleanup(curl_handle);
return err_msg; return err_msg;
} }
fclose(file); fclose(file);
curl_easy_cleanup(curl_handle); ptr_curl_easy_cleanup(curl_handle);
return NULL; return NULL;
} }

View File

@@ -12,9 +12,8 @@
#include "intrin.h" #include "intrin.h"
#endif #endif
#if FETCH_AVAILABLE
const char *download_file(const char *url, const char *resource, const char *file_path); 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])) #define ELEMENTLEN(x) (sizeof(x) / sizeof(x[0]))
extern const char *compiler_exe_name; extern const char *compiler_exe_name;