Understanding ___isOSVersionAtLeast on macOS is not defined
Include
I came across with the linker problem. My application was not able to link with the
___isOSVersionAtLeast symbol is undefined error.
It was the problem linking curl
with a
Kotlin/Native
app. I did a tiny project to reproduce the linker error.
As a side effect, here is a Minimalistic C library and Kotlin/Native
example. And finally, the main rant and investigation post. The good part
is I got a suggestion to try.
Use clang not ld
The suggestion I got (and I say thank you for that) was to use clang
command, not the ld
one:
It did work. It was able to link. Next, we may try -v
switch to see how it works inside,
with arguments split per lines and long paths simplified:
The right path (and I did it wrong in the previous post) is
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/9.1.0/lib/darwin/libclang_rt.osx.a
Still, the best way to use that commandline is to have no such commandline in the build.
The Linker Error Solution
The answer here is to use clang
command instead of ld
command. Note, that
command line options are different, and clang
calls
(specify -v
key to see that) ld
with additional arguments.
Linking with Kotlin/Native
At that point, we have a workable inc.sh
script to compile and link the static library with the executable
in the repository on GitHub. Time to fix in.sh
that links
the static C library with a Kotlin/Native
executable. You may want to have a look at the introductory post
here.
A Dumb Approach
The very first (and dump) fix is to include the libclang_rt.osx.a
path (see above) into the konanc
call with
the -linkerOpts <path>/libclang_rt.osx.a
. It make the code compile and run.
Right Approach
Let’s debug how Kotlin/Native executes the linker first. You pass the --verbose linker
to see the verbose output
from the linking phase (use konanc --list_phases
to learn all phases). The output on my machine is as follows,
with arguments split per lines and long paths simplified:
Let’s compare the commands. The linker executable is selected right from both sides. The sensible difference
is we miss libclang_rt.osx.a
from the Kotlin/Native command. There are two ways to go:
- link the
libclang_rt.osx.a
withlib.a
before the final linking - infer path to
libclang_rt.osx.a
and include it into the linker command
Checking clang --help
and I found the -print-libgcc-file-name
argument. It does provide hints,
but it is not enough just now.
The Outcome
Frankly, I do not like linking with libclang_rt
explicitly. The library is a part
of toolchain internals. It will make builds too fragile or not incorrect.
The hack will not worth it in the long run.
The right way, as I see it, is to fix the original library build to make
sure it is pre-linked with all necessary internals.
That matches with the encapsulation principle. It simplifies the rest.
The backup plan is to call clang
with -v
key, parse the command output
to get the path to the libclang_rt
, and use it as an explicit library.
Building Curl
A pre-history. I first saw the missing ___isOSVersionAtLeast
symbol error
while
building and static linking
the libcurl. My goal is to have a
self-contained static library out of it.
I used the wrong artifact and build it incorrectly. I see the right static library
is somewhere under curl
build directory.
I did that wrong. Do that right. Respect the privacy and build internals.
The right way is
- to set install prefix path in
./configure
- to call
make install
- use artifacts for the prefix path
- deliver the
libclang_rt
as dependency (or merge it in?)
The library under the prefix do contain reference to our
favorite ___isOSVersionAtLeast
symbol. The make install
package
looks reusable and reduces hard-coded build hacks, still.