## Problem Windows builds break with a current local toolchain (Scoop LLVM 22.1.8, CMake 4.4.0, VS 2026), in two independent ways: 1. The build stops at curl's deliberate guard: `#error "no non-blocking method was found/used/set"` in `third-party/curl/lib/nonblock.c`. 2. From the second configure onward, `cmake --build` re-runs CMake in an endless loop (observed 42 consecutive reconfigure cycles in a single build). Likely the same mechanism behind the "endlessly building" VS 2026 note in `docs/setup/dev/vs.md`. ## Root cause 1. `third-party/curl/CMake/CurlTests.c` passes `int *` to `ioctlsocket()`, whose third parameter is `u_long *`. Clang 22 promotes `-Wincompatible-pointer-types` to a hard error in C, so the `HAVE_IOCTLSOCKET_FIONBIO` try_compile silently fails and `curl_config.h` never defines it. Upstream CI does not see this because the windows-2022 runner image ships an older LLVM. GCC 14 promotes the same warning to a hard error, which is very likely the `CurlTests.c.obj` failure reported from MSYS2 in open-goal/jak-project#3551. Upstream curl hit the identical problem with GCC 14 and fixed the probe in curl 8.8.0 (curl/curl#13578). 2. The root CMakeLists copies the build tree's `compile_commands.json` into `<src>/build/` for clangd using `configure_file()`, which registers its input as a configure dependency. CMake rewrites `compile_commands.json` late in every generation, after `CTestTestfile.cmake` and `cmake_install.cmake` (outputs of the same Ninja regen rule), so once the dependency is registered the rule is deterministically dirty and every `ninja` invocation re-runs CMake. A pristine first configure is safe (the file does not exist yet, so the `if(EXISTS ...)` guard skips the copy), which is why the loop looks machine- or IDE-specific. ## Fix 1. Per review, re-vendor `third-party/curl` at the `curl-8_21_0` tag (previously `curl-8_3_0`), which carries the upstream probe fix plus two years of upstream development; `vendor.yaml` updated to match. Adjustments the version jump forced: - curl 8.15 removed the native macOS Secure Transport backend (`CURL_USE_SECTRANSP`), so macOS now builds curl against OpenSSL like Linux. The two macOS workflows install Homebrew `openssl@3` and export `OPENSSL_ROOT_DIR` (keg-only), and the macOS setup docs gained the same two lines. - `CURL_BROTLI` / `CURL_ZSTD` switched to AUTO-detection in curl 8.10; pinned OFF to keep the previous no-compression behavior and avoid silently linking whatever the CI images happen to have. - curl's new top-level `BUILD_EXAMPLES` cache option (default ON) leaked into discord-rpc's identically named option and broke configure at a nonexistent `examples/send-presence` directory; pinned OFF ahead of the third-party subdirectories. The diff is dominated by the mechanical tag-tree swap under `third-party/curl` (linguist-vendored, collapsed in review). The hand-written changes are `CMakeLists.txt`, the two macOS workflows, `docs/setup/system/macos.md`, and `vendor.yaml`. 2. Swap `configure_file()` for `file(COPY ...)`: the same clangd copy with no configure dependency registered. (`file(COPY_FILE ... ONLY_IF_DIFFERENT)` would be cleaner still but requires CMake 3.21, above the declared `cmake_minimum_required(VERSION 3.10)`.) ## Test plan - [x] Fresh `cmake --preset Release-windows-clang` (LLVM 22, no cache seeding) completes and logs `Enabled SSL backends: Schannel`; the FIONBIO probe passes without the previous `#error` - [x] Full Windows Release build from scratch in the branch worktree (all 1422 targets) - [x] goalc-test suite: 1509 passed, 0 failed - [x] Second consecutive configure with `compile_commands.json` present: the regen rule in `build.ninja` has no `compile_commands.json` input; `<src>/build/compile_commands.json` is still refreshed for clangd - [x] Repeated `ninja` invocations after a full build no longer re-run CMake - [x] macOS Intel and ARM CI green (first exercise of the OpenSSL backend switch) --- I work off a self-hosted forge, so this GitHub account is quiet; the configure logs and ninja dirty-node traces from the investigation are available if anyone wants the raw data. (AI-assisted)
9.3 KiB
Vendored
Generated
curl C code style
Source code that has a common style is easier to read than code that uses different styles in different places. It helps making the code feel like one single code base. Easy-to-read is an important property of code and helps making it easier to review when new things are added and it helps debugging code when developers are trying to figure out why things go wrong. A unified style is more important than individual contributors having their own personal tastes satisfied.
Our C code has a few style rules. Most of them are verified and upheld by the
scripts/checksrc.pl script. Invoked with make checksrc or even by default
by the build system when built after ./configure --enable-debug has been
used.
It is normally not a problem for anyone to follow the guidelines, copy the style already used in the source code and there are no particularly unusual rules in our set of rules.
We also work hard on writing code that are warning-free on all the major platforms and in general on as many platforms as possible. Code that causes warnings is not accepted as-is.
Readability
A primary characteristic for code is readability. The intent and meaning of the code should be visible to the reader. Being clear and unambiguous beats being clever and saving two lines of code. Write simple code. You and others who come back to this code over the coming decades want to be able to quickly understand it when debugging.
Naming
Try using a non-confusing naming scheme for your new functions and variable names. It does not necessarily have to mean that you should use the same as in other places of the code, only that the names should be logical, understandable and be named according to what they are used for. File-local functions should be made static. We like lower case names.
See the INTERNALS document on how we name non-exported library-global symbols.
Indenting
We use only spaces for indentation, never TABs. We use two spaces for each new open brace.
if(something_is_true) {
while(second_statement == fine) {
moo();
}
}
Comments
Since we write C89 code, // comments are not allowed. They were not introduced in the C standard until C99. We use only /* comments */.
/* this is a comment */
Long lines
Source code in curl may never be wider than 79 columns and there are two reasons for maintaining this even in the modern era of large and high resolution screens:
-
Narrower columns are easier to read than wide ones. There is a reason newspapers have used columns for decades or centuries.
-
Narrower columns allow developers to easier show multiple pieces of code next to each other in different windows. It allows two or three source code windows next to each other on the same screen - as well as multiple terminal and debugging windows.
Braces
In if/while/do/for expressions, we write the open brace on the same line as the keyword and we then set the closing brace on the same indentation level as the initial keyword. Like this:
if(age < 40) {
/* clearly a youngster */
}
You may omit the braces if they would contain only a one-line statement:
if(!x)
continue;
For functions the opening brace should be on a separate line:
int main(int argc, char **argv)
{
return 1;
}
'else' on the following line
When adding an else clause to a conditional expression using braces, we add it on a new line after the closing brace. Like this:
if(age < 40) {
/* clearly a youngster */
}
else {
/* probably grumpy */
}
No space before parentheses
When writing expressions using if/while/do/for, there shall be no space between the keyword and the open parenthesis. Like this:
while(1) {
/* loop forever */
}
Use boolean conditions
Rather than test a conditional value such as a bool against TRUE or FALSE, a pointer against NULL or != NULL and an int against zero or not zero in if/while conditions we prefer:
result = do_something();
if(!result) {
/* something went wrong */
return result;
}
No assignments in conditions
To increase readability and reduce complexity of conditionals, we avoid assigning variables within if/while conditions. We frown upon this style:
if((ptr = malloc(100)) == NULL)
return NULL;
and instead we encourage the above version to be spelled out more clearly:
ptr = malloc(100);
if(!ptr)
return NULL;
New block on a new line
We never write multiple statements on the same source line, even for short if() conditions.
if(a)
return TRUE;
else if(b)
return FALSE;
and NEVER:
if(a) return TRUE;
else if(b) return FALSE;
Space around operators
Please use spaces on both sides of operators in C expressions. Postfix (), [], ->, ., ++, -- and Unary +, -, !, ~, & operators excluded they should have no space.
Examples:
bla = func();
who = name[0];
age += 1;
true = !false;
size += -2 + 3 * (a + b);
ptr->member = a++;
struct.field = b--;
ptr = &address;
contents = *pointer;
complement = ~bits;
empty = (!*string) ? TRUE : FALSE;
No parentheses for return values
We use the 'return' statement without extra parentheses around the value:
int works(void)
{
return TRUE;
}
Parentheses for sizeof arguments
When using the sizeof operator in code, we prefer it to be written with parentheses around its argument:
int size = sizeof(int);
Column alignment
Some statements cannot be completed on a single line because the line would be too long, the statement too hard to read, or due to other style guidelines above. In such a case the statement spans multiple lines.
If a continuation line is part of an expression or sub-expression then you should align on the appropriate column so that it is easy to tell what part of the statement it is. Operators should not start continuation lines. In other cases follow the 2-space indent guideline. Here are some examples from libcurl:
if(Curl_pipeline_wanted(handle->multi, CURLPIPE_HTTP1) &&
(handle->set.httpversion != CURL_HTTP_VERSION_1_0) &&
(handle->set.httpreq == HTTPREQ_GET ||
handle->set.httpreq == HTTPREQ_HEAD))
/* did not ask for HTTP/1.0 and a GET or HEAD */
return TRUE;
If no parenthesis, use the default indent:
data->set.http_disable_hostname_check_before_authentication =
va_arg(param, long) ? TRUE : FALSE;
Function invoke with an open parenthesis:
if(option) {
result = parse_login_details(option, strlen(option),
(userp ? &user : NULL),
(passwdp ? &passwd : NULL),
NULL);
}
Align with the "current open" parenthesis:
DEBUGF(infof(data, "Curl_pp_readresp_ %d bytes of trailing "
"server response left\n",
(int)clipamount));
Platform dependent code
Use #ifdef HAVE_FEATURE to do conditional code. We avoid checking for
particular operating systems or hardware in the #ifdef lines. The HAVE_FEATURE
shall be generated by the configure script for Unix-like systems and they are
hard-coded in the config-[system].h files for the others.
We also encourage use of macros/functions that possibly are empty or defined to constants when libcurl is built without that feature, to make the code seamless. Like this example where the magic() function works differently depending on a build-time conditional:
#ifdef HAVE_MAGIC
void magic(int a)
{
return a + 2;
}
#else
#define magic(x) 1
#endif
int content = magic(3);
No typedefed structs
Use structs by all means, but do not typedef them. Use the struct name way
of identifying them:
struct something {
void *valid;
size_t way_to_write;
};
struct something instance;
Not okay:
typedef struct {
void *wrong;
size_t way_to_write;
} something;
something instance;
Banned functions
To avoid footguns and unintended consequences we forbid the use of a number of
C functions. The checksrc script finds and yells about them if used. This
makes us write better code.
This is the full list of functions generally banned.
_access
_fstati64
_lseeki64
_mbscat
_mbsncat
_open
_tcscat
_tcsdup
_tcsncat
_tcsncpy
_waccess
_wcscat
_wcsdup
_wcsncat
_wfopen
_wfreopen
_wopen
accept
accept4
access
aprintf
atoi
atol
calloc
close
CreateFile
CreateFileA
CreateFileW
fclose
fdopen
fopen
fprintf
free
freeaddrinfo
freopen
fstat
getaddrinfo
gets
gmtime
llseek
LoadLibrary
LoadLibraryA
LoadLibraryEx
LoadLibraryExA
LoadLibraryExW
LoadLibraryW
localtime
lseek
malloc
mbstowcs
MoveFileEx
MoveFileExA
MoveFileExW
msnprintf
mvsnprintf
open
printf
realloc
recv
rename
send
snprintf
socket
socketpair
sprintf
sscanf
stat
strcat
strcpy
strdup
strerror
strncat
strncpy
strtok
strtok_r
strtol
strtoul
vaprintf
vfprintf
vprintf
vsnprintf
vsprintf
wcscpy
wcsdup
wcsncpy
wcstombs
WSASocket
WSASocketA
WSASocketW