[WIP] Detect presence of flockfile#4737
Conversation
Just a dry-run at a possible implementation pending clarification on details. Fixes fmtlib#4646
|
@vitaut, If it helps, here is a (Click to expand `Dockerfile` source)# syntax=docker/dockerfile:1
FROM ubuntu:noble
# Lastest release of official "Arm Toolchain for Embedded"
ADD --unpack=true https://github.com/arm/arm-toolchain/releases/download/release-22.1.0-ATfE/ATfE-22.1.0-Linux-x86_64.tar.xz /usr/local
ENV TOOLCHAIN_DIR="/usr/local/ATfE-22.1.0-Linux-x86_64"
ENV TOOLCHAIN_BIN_DIR="${TOOLCHAIN_DIR}/bin"
ENV PATH="${TOOLCHAIN_BIN_DIR}:$PATH"
ENV SRC_DIR=/src
RUN mkdir -p "${SRC_DIR}"
ADD --chmod=0755 <<EOF "${SRC_DIR}/build-and-run.sh"
#!/bin/bash
set -euo pipefail
CFLAGS=(
--std=c23
--target=armv7-unknown-none-eabi -mfpu=none # generic defaults so the compiler can pick a set of runtime libraries
--config=\${TOOLCHAIN_BIN_DIR}/llvmlibc.cfg -Wl,-Tllvmlibc.ld # use llvm libc\'s runtime libraries and linker script
-static # no dynamic linking available because no dynamic linker present (nor fs to load shared libs from) on bare-metal
-nostartfiles # by default, the llvm linker looks for (the non-existent) crt0.o; we need to specify our own because the real file is named like libcrt0.a
-lcrt0-semihost -lsemihost # choose the libcrt0.a for semihosting, which provides support for stdin/out
)
set -x
cd "\${SRC_DIR}"
! test -f "test" || rm -f test
"${TOOLCHAIN_BIN_DIR}/clang" "\${CFLAGS[@]}" -o test test.c
exec ./test
EOF
ADD <<EOF "${SRC_DIR}/test.c"
#include <stdio.h>
#if __has_include(<newlib.h>)
# include <newlib.h> // include __NEWLIB__ macro, if it exists
#endif
#if __NEWLIB__
# warning "using newlib"
#endif
#if __LLVM_LIBC__
# warning "using llvm libc"
#endif
int main() {
flockfile(stdout);
funlockfile(stdout);
printf("Test complete!\n");
return 0;
}
EOF
WORKDIR "${SRC_DIR}"
CMD ["./build-and-run.sh"]To use, write the contents above to a file named docker build -t mvastola/fmt-test-flockfile-poc-llvm-libc . && docker run --rm mvastola/fmt-test-flockfile-poc-llvm-libcExpected output is: |
|
So, I think I was able to come up with the beginnings of a headers-only way to do this, if you feel strongly about approaching it that way. Let me know if you prefer this approach. While this wouldn't be my preference, I wanted to put it out there as an alternative approach if you're not comfortable with the one in this PR. (If that's not an issue, no need to bother with this as it's a more complex and slightly more fraught solution.) Basically this alternate approach turns There are some downsides/implications, that I'll list here for your consideration:
|
vitaut
left a comment
There was a problem hiding this comment.
Thanks for the PR. CMake is the recommended build systems but not the only one {fmt} users rely on. So I still suggest putting the check in the code and relying on __NEWLIB__ macro. If there is other similarly broken system, we can generalize the check similarly to what we do for other FMT_USE_* macros. Weak symbols is an interesting idea but will likely be more complex as you outlined in the implications of this approach.
|
(Updated with some added info.)
@vitaut, you mentioned this in the issue thread as a reason not to do the check in CMake, but I didn't understand this point then either; I think there might be a misunderstanding here, but I'm not sure on whose part it is: If we implemented autodetection in CMake (a la this PR as it is currently), users who don't use CMake simply wouldn't be able to take advantage of the macro value being determined for them, in the same way that they can't take advantage of any of the other defines/build/compile options that I guess I just don't understand why the fact that some users don't use CMake would counsel against using CMake to much more accurately guess how to set the macro for people who do use CMake, particularly when it's the recommended build system and using it already removes the need to specifically set many other compile options. If we wanted to use the
Is there any reason why we couldn't/shouldn’t compromise by using the CMake compile check for CMake users, and use
Could you elaborate on this? As far as I can tell, these are set (in If we implemented this headers-only, we would have to do it by detecting the presence of specific libc implementations associated with bare-metal/embedded devices. The ultimate result is that we'd be disabling This is especially problematic when you consider that LLVM's libc as I mentioned has the same problem with a declared but not necessarily defined I wonder if part of the disconnect could be that you're focusing on these cases as being on "broken systems".. While I certainly agree that declaring functions without implementing them is far less than ideal and (obviously) has the potential to cause annoying issues (this one being a case in point), whether they're broken/poorly designed is kind of beside the point: this flaw is common to any OS or device-independent libc implementations. Compliance with standards like POSIX requires that implementations declare the standard set of methods, while their definitions/availability always inherently depend on the OS and hardware capabilities.
Agreed, unfortunately. :-\ |
vitaut
left a comment
There was a problem hiding this comment.
The issue isn’t that some users don’t use CMake, it’s that putting the logic in CMake makes the behavior depend on the build system rather than the code. fmt is meant to be build-system agnostic, and we try to keep configuration in code so all users get consistent defaults.
Using something like __NEWLIB__, possibly combined with a more specific platform check is a reasonable fallback. Users on platforms where the heuristic is wrong can still override the macro explicitly, which keeps behavior predictable and uniform across build systems.
We already spent way too much time on this esoteric issue.
|
I know this has taken longer than it should, and I don't want to prolong this, nor butt heads. I think we have some fundamentally different philosophies/priorities and issue happens to hit at the heart of those differences. I know this is your project, but at the same time I still feel a sense of responsibility/ownership for the code I push and I've been struggling because I fear the fix you are proposing to this relatively esoteric issue will negatively impact the userbase beyond those whom this issue actually does/should affect. And I don't want to make a contribution I can't be proud of, or -- at the very least -- confident in. I guess the bottom line is that, despite trying, I can't convince myself using The value the macro needs to be set to has at best a tertiary relationship with any particular libc implementation. In any case, I regret the extent to which I facilitated the focus on newlib. I am happy to provide more info or further explanation if it will help, but I'm not trying to waste your time, so please tell me if you think discussing this further would be helpful. While I totally concur that consistent config defaults is a worthy goal for settings/preferences, the value this macro should be set to is inherently and inextricably dependent on the build and run environments, rather than anything that can be determined from within library's code at compile-time. As worthy of an ideal that consistent defaults are, I think it's fundamentally outweighed by the implicit/universal expectation by users that their build system (if they choose to use one) handles their software's build configuration for them and doesn't break it in subtle and unexpected ways between versions. The desire to keep configuration consistent shouldn't be taken to such an extreme that it effectively becomes the goal that use of the build system confers no benefit at all. When someone decides to not use the supported build system for any software, they take up the responsibility of looking at what the pre-packaged build system does and duplicating its functionality. They must expect to need to configure things manually that the build system does automatically. Someone rolling their own build process would also be expected to be on the lookout for changes to distributed build scripts between versions and ensure their own custom build process remains consistent. If a compile option can't be correctly guessed for some portion of the userbase, these users should be the preference to comprise that portion. As for where we go from here, I honestly don't know. I just can't get behind what you're proposing. If you see some sort of hole in my logic, I'm more than happy to hear you out. If there's something that I said that wasn't clear or accurate, I'd be more than happy to revisit it or look closer. Baring that or some sort of compromise though, I don't want to keep beating a dead horse if we've truly hit an impasse. There won't be any hard feelings from if you choose to make this change on your own, or if you want to just close this PR. In that case, I'm just sorry this didn't work out. |
@vitaut,
Wanted to continue the discussion from #4646 since that's a closed issue..
@vitaut thanks for your understanding on this. I truly appreciate what you're saying, and it makes sense, but from what I've seen (and from the logistics I anticipate would be involved in not doing so), separating the declarations and implementations of low-level standard library functions in such a way that the former can't depend on the latter is SOP for bare-metal targets. (If you have any resources that suggest otherwise, I'm very open.)
A few clarifications:
How strongly wedded are you to using the presence of newlib specifically as the deciding factor in setting
FMT_USE_FLOCKFILE?IMHO, the ideal solution would be to do something along the lines of what I've done in this PR, which directly tests if
flockfilecan be linked, especially if you're now okay making a small addition to the CMake script. This involves as little fuss as possible and is 100% accurate, baring a mismatch in compile options between the compilation of this library and the software it is included in.I'm realizing, however, that while your stated condition was that it be "easy to detect" and this implementation is certainly simple, it's possible you wanted me to just do this via a macro. I wanted to double check since you expressed reticence about doing this in CMake previously.
Unfortunately, if we restrict ourselves to macros and/or detecting newlib, the accuracy of testing if
flockfileworks/links drops significantly:It seems there is an official macro to test the presence of newlib (aptly named
__NEWLIB__), but as I mentioned previously, the use of newlib isn't dispositive offlockfilebeing unimplemented. Newlib is just onelibcimplementation popular with bare-metal targets (others includepicolibcandLLVM libc). Further, some bare-metal platforms do, in fact, support filesystems (and, thus,flockfile).If you're not convinced about using CMake to do the check, I'd be happy to address any concerns you have and figure out how to obviate them.
For sake of completeness, I've also included several macro-only solutions I've investigated here, with my assessment of each. As mentioned, they unfortunately all have (often major) downsides.
#if __STDC_HOSTED__ == 0.Downside: This depends on the user or platform toolchain passing
-ffreestandingto the compiler, which isn't always done.#if !defined(_WIN32) && !defined(__linux__) && !defined(__APPLE__) && !defined(__unix__) ...Downside: this is brittle and would requires an exhaustive list of OSes with filesystems. Additionally, there are quite a few RTOS OSes where filesystem support is an optional feature.
#if (defined(__ELF__) && !defined(__linux__) && !defined(__unix__)) || defined(__ARM_EABI__)Downside: While this would likely be more accurate than the previous two at identifying bare-metal targets, it still can't tell if its being used on a bare-metal target with a filesystem, and is likely to miss many bare-metal targets anyway.
#include <dirent.h>works. While newlib doesn't have any macros to indicate filesystem support, one thing it does have is a defaultsys/dirent.hcontaining#errordirective. If<dirent.h>is included andsys/dirent.his not masked by a file of the same name in the platform's headers containing a real implementation, a compile-time error occurs.Downside: this only works on newlib, plus there's no way to use this indicator without using CMake to assist, because the compiler will only know if it has an
#errordirective by#includeing it, at which point you've broken compilation.