slint icon indicating copy to clipboard operation
slint copied to clipboard

Add support for static linking with CMake

Open std-microblock opened this issue 1 year ago • 15 comments

I saw this line in the cpp example:

# On Windows, copy the Slint DLL next to the application binary so that it's found.

Isn't it possible to link to the library statically so that I can make it to a single binary for the convenience of distribution?

std-microblock avatar Jul 26 '24 14:07 std-microblock

At the moment, static linking is not supported for C++ builds. I think that this would be a desirable feature of the build system to support in the future. It's a little tricky because cargo can't tell us the names/paths of all the dependencies when creating a static library - transitive dependencies. But we could work around this by trying to solve this by hand on the CMake side, i.e. manually keeping track.

tronical avatar Jul 26 '24 14:07 tronical

I'm actually a bit confused about why it's not so easy to implement. I'm assuming that you are using cabi to do the ffi, thus, making it into a .lib file shouldn't be hard

std-microblock avatar Jul 26 '24 14:07 std-microblock

wdym by dependencies?

std-microblock avatar Jul 26 '24 14:07 std-microblock

Yes, creating the .a file is easy, cargo does that for us. But there are system libraries that libslint_cpp.so links against, say libfreetype.so.2 when using Skia. When creating libslint_cpp.so, cargo encodes the need for libfreetype.so.2 into libslint_cpp.so and an application linking against libslint_cpp.so is good and doesn't need to know about libfreetype.so.2.

However, static libraries that cargo creates are just archives (.a), they don't provide a way to encode system library dependencies. So there needs to be another way of conveying to the build system of the application that includes libslint_cpp.a to also link against libfreetype.so.2 at application link time.

CMake itself can solve that when it creates the static library, it calls ar to create the archive and encodes the dependencies needed in the generated cmake targets. Libtool archives are another (older way) of solving this. Cargo however doesn't provide a mechanism of finding out what the external library dependencies are of any dependent crates when creating a staticlib.

tronical avatar Jul 26 '24 15:07 tronical

got that. In the CI, we can compile the dll first. Then, read IAT of the dll (or whatever thing like that in executables other than PE formats) to get the system dlls it used.

std-microblock avatar Jul 26 '24 15:07 std-microblock

it's weird, though.

std-microblock avatar Jul 26 '24 15:07 std-microblock

Interesting idea to compile twice. CMake even has a function for it : https://cmake.org/cmake/help/latest/command/file.html#get-runtime-dependencies

tronical avatar Jul 26 '24 15:07 tronical

wow, I haven't heard that until now then, with this and LIBRARIES, we should achieve that easily. I'll try it now

std-microblock avatar Jul 26 '24 16:07 std-microblock

No, this is not for build time, it's for only install stage

std-microblock avatar Jul 26 '24 17:07 std-microblock

image damn, I eventually got it working!

std-microblock avatar Jul 26 '24 18:07 std-microblock

Also found that here's a --print=native-static-libs arg in rustc, this can provide essential information for us.

std-microblock avatar Jul 26 '24 18:07 std-microblock

CMakeLists.txt for that:

cmake_minimum_required(VERSION 3.28)
project(test_slint_static_link)

set(CMAKE_CXX_STANDARD 23)
set(BUILD_SHARED_LIBS OFF)
add_subdirectory(../slint slint)

add_executable(test_slint_static_link main.cpp)
set(CMAKE_VERBOSE_MAKEFILE ON)
set_property(TARGET test_slint_static_link PROPERTY
        MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
set_property(TARGET test_slint_static_link PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
slint_target_sources(test_slint_static_link ui.slint)
target_link_directories(test_slint_static_link PUBLIC ./slint)
target_link_libraries(test_slint_static_link PUBLIC Ws2_32 wsock32 opengl32 imm32 dwmapi UxTheme Uiautomationcore slint_cpp2 Slint::Slint)

The slint_cpp.lib is renamed to slint_cpp2.lib (to avoid being shadowed by the slint_cpp target) and placed in ./slint

Also a small patch is applied to build.ninja:

Remove all \\(\S+):msvcrt

idk why but as I linked to Slint::Slint, the part is added to the link target as a "library" by something evil, causing the error ninja: error: '/defaultlib:msvcrt', needed by 'test_slint_static_link.exe', missing and no known rule to make it to appear. I have no choice but to remove it manually.

std-microblock avatar Jul 26 '24 18:07 std-microblock

image btw, even with LTO enabled and relwithdebuginfo profile, the binary size of a simple helloworld goes to ~10MiB. Can this be reduced somehow?

std-microblock avatar Jul 26 '24 18:07 std-microblock

As per https://github.com/slint-ui/slint/discussions/3376#discussioncomment-6860995 at least with Rust and static linking I see sizes around 2.6-3-6MiB for a hello world. I'm not sure why you see a ~10MiB size.

tronical avatar Jul 29 '24 09:07 tronical

Also found that here's a --print=native-static-libs arg in rustc, this can provide essential information for us.

I don't know of a way to invoke this and get all transitive library dependencies for a crate :(

tronical avatar Jul 29 '24 09:07 tronical

I support it. Static linking is very necessary.

I tried to poke around with it myself. Everything started with "BUILD_SHARED_LIBS=false", but there is a problem. The linker really does remove some symbols, marking them as "unused". So I added "/OPT:NOREF" and everything worked, but the binary size leaves much to be desired.

receiver1 avatar Aug 07 '25 17:08 receiver1