Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: How to instruct cmake to generate a static library? #175

Closed
degski opened this issue Apr 29, 2019 · 39 comments
Closed

Question: How to instruct cmake to generate a static library? #175

degski opened this issue Apr 29, 2019 · 39 comments
Assignees

Comments

@degski
Copy link

degski commented Apr 29, 2019

How to instruct cmake to generate a static library. Do I need to define any MACRO.

I'm using VC17 + clang-cl-9. The generation goes fine, I just want/need a static library.

Thanks in advance.

@degski degski changed the title Question: How to instruct cmake to generate a static library Question: How to instruct cmake to generate a static library? Apr 29, 2019
@bluescarni
Copy link
Owner

@degski at the moment the build system hard codes the generation of a shared library. Would you like me to add an option for a static library build?

@degski
Copy link
Author

degski commented Apr 29, 2019

Yes, please. Like I said, all goes well, except it's not what I would like to have. Just to be clear, that also implies static run-time linking (/MT, /MTd).

@bluescarni
Copy link
Owner

Is the compilation with /MT enough or are there any other special considerations on MSVC to be taken into account when building static libs? I'll have some reading to do, as my experience with MSVC is a bit limited, but I'll implement it.

@degski
Copy link
Author

degski commented Apr 29, 2019

No, not that I'm aware of, it's just /MT for Release and /MTd for Debug.

There is one thing, that is possibly something you did not consider. The __declspec(dllexport) and __declspec(dllimport) should not be added in a static build (nothing needs to be added), so normally people define macros for those 2 directives in a dynamic build and when it is a static build they just define those macros to be empty. Often, like in SFML, one needs to define f.e. SFML_STATIC (both in the build and the use of the lib), this is how SFML does it.

There is one other point, though, that is the debug-info [where maybe I'm awkward]. The std way of doing things is to add the /Zi flag, while what I usually do is give the /Z7 flag. The result is the same, except with /Zi you get the famous .pdb file, with Z7, that's included (wrapped up) in the library, i.e. you end up with one file (including debug-info). The /Z7 flag is considered 'old fashioned' (and for decennia about the be removed), beats me why.

@bluescarni
Copy link
Owner

Ok, thanks for the info!

Do you happen to know if I can set these flags via some CMake mechanism? Or perhaps CMake already does the "right" thing when I tell it to build a static vs dynamic library, and release vs debug?

I'd much rather set this sort of thing via, e.g., a target property rather than mucking around with the CXXFLAGS.

@degski
Copy link
Author

degski commented Apr 29, 2019

Yeah, probably. I know not much about CMake. You could have a look at https://github.com/SFML/SFML/blob/master/CMakeLists.txt . Like I said, they deal with this using basically 1 flag, one macro. The CMake file is rather big, but it's mostly because SFML has masses of dependencies, I guess the static/dynamic bit is rather small.

@degski
Copy link
Author

degski commented Apr 29, 2019

I could not find where MPPP_PUBLIC was defined, but I guess that's the one. Don't do anything with the /Z7 and/Zi flags, most people want the default, /Zi.

@bluescarni
Copy link
Owner

MPPP_PUBLIC is the macro that sets the dllexport/dllimport attributes on windows and the visibility attribute on Unix. It is defined here:

https://github.com/bluescarni/mppp/blob/master/include/mp%2B%2B/detail/visibility.hpp

Speaking of it, are these attributes still necessary when building a static library?

As a first try, I'll just add an option to build a static library and observe what kind of flags CMake sets in the CI runs on appveyor. Crossing fingers, that might be good enough. Thanks for all the info!

@bluescarni
Copy link
Owner

@degski sorry just saw your edit above about dllexport...

@bluescarni
Copy link
Owner

Ok so it looks like I could just add another definition to config.hpp to be set at build time indicating whether mp++ was built as static or dynamic library, and then define MPPP_PUBLIC to expand to nothing in static builds.

@degski
Copy link
Author

degski commented Apr 29, 2019

Yes.

@bluescarni bluescarni self-assigned this Apr 29, 2019
@bluescarni
Copy link
Owner

I have some initial code up here:

https://github.com/bluescarni/mppp/compare/pr/static_lib

Works locally on my linux installation, I'll check tonight what it does on the appveyor CI.

@7ofNine
Copy link
Contributor

7ofNine commented Apr 29, 2019

I just compiled it with MSVC 2019 successfully, All tests ran in Release and Debug (for some tests considerably slower)

@bluescarni
Copy link
Owner

@7ofNine Thanks for testing! PR is up at #176. I enabled the static library option for the clang-cl builds. If the builds are green I'll merge tomorrow.

@degski
Copy link
Author

degski commented Apr 30, 2019

I've tried the pr/static_lib branch. It appears that it links against the dynamic crt's, i.e. the flags used are /MD and /MDd. The mp++.lib built with clang-cl and then consumed by cl or clang-cl does build, link and work.

SFML has this code snippet to achieve the MD -> MT:

if(SFML_COMPILER_MSVC AND SFML_USE_STATIC_STD_LIBS)
        foreach(flag
                CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
                CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
            if(${flag} MATCHES "/MD")
                string(REGEX REPLACE "/MD" "/MT" ${flag} "${${flag}}")
            endif()
        endforeach()
    endif()
endif()

@bluescarni
Copy link
Owner

@degski it is not clear to me what the relation is between building a static version of the library and linking to the static runtime. Does one imply the other or are they independent? Is it allowed, e.g., to build a static library and link it to the dynamic runtime?

@bluescarni
Copy link
Owner

Ok so I dug a bit and found this:

https://gitlab.kitware.com/cmake/cmake/issues/19108
https://cmake.org/cmake/help/git-master/prop_tgt/MSVC_RUNTIME_LIBRARY.html

That is, the latest CMake git head exposes a target property for selecting the MSVC runtime to build against. So clearly this is the way to go for the future, but we still need a working solution for older CMake versions.

This CMake FAQ entry discusses the topic a bit:

https://gitlab.kitware.com/cmake/community/wikis/FAQ#how-can-i-build-my-msvc-application-with-a-static-runtime

So I'd be inclined to implement the "Dynamic Replace" solution, even with its shortcomings.

@bluescarni
Copy link
Owner

To be clear, this means adding another CMake option MPPP_WITH_STATIC_MSVC_RUNTIME which would be available only when compiling with MSVC, and, as I understand it, it would be independent from the MPPP_BUILD_STATIC option.

@degski
Copy link
Author

degski commented Apr 30, 2019

Does one imply the other or are they independent?

No not really, but you cannot mix them [if you link in more than 1 lib], and in general it is considered good practice to link to the static crt when compiling static libs. Like f.e. vcpkg [and one would assume that 'they know what's best'] does this by default.

So I'd be inclined to implement the "Dynamic Replace" solution, even with its shortcomings.

Yeah, I guess it's not perfect, but it works for SFML.

To be clear, this means adding another CMake option MPPP_WITH_STATIC_MSVC_RUNTIME which would be available only when compiling with MSVC, and, as I understand it, it would be independent from the MPPP_BUILD_STATIC option.

MSVC and clang-cl. I would do it the other way around, by default link to the dynamic crt for dll's and to the static crt for lib's. And then have an opt-in macro, something like MPPP_STATIC_WITH_DYNAMIC_MSVC_RUNTIME, to build a static lib with dynamic crt linking.

Additionally, I would name the macro MPPP_STATIC, because this macro will need to be specified both during the build of mp++ and the application that is going to consume mp++.

@bluescarni
Copy link
Owner

bluescarni commented Apr 30, 2019

No not really, but you cannot mix them [if you link in more than 1 lib], and in general it is considered good practice to link to the static crt when compiling static libs. Like f.e. vcpkg [and one would assume that 'they know what's best'] does this by default.

MSVC and clang-cl. I would do it the other way around, by default link to the dynamic crt for dll's and to the static crt for lib's. And then have an opt-in macro, something like MPPP_STATIC_WITH_DYNAMIC_MSVC_RUNTIME, to build a static lib with dynamic crt linking.

If there is no specific reason for mixing static library with dynamic runtime (or vice versa), I'd rather go with the best practice and leave a single option (BUILD_STATIC) which enables both the static library and linking to the static runtime.

Would that work for you?

@degski
Copy link
Author

degski commented Apr 30, 2019

It has to include MPPP as BUILD_STATIC is too generic, i.e. `MPPP_STATIC'.

If there is no specific reason for mixing static library with dynamic runtime (or vice versa) ...

You would do that to work around a problem. Like f.e. Intel Threading Building Blocks cannot [due to shared state issues in the lib] be compiled statically, so to work around this MPPP_STATIC_WITH_DYNAMIC_MSVC_RUNTIME is an option [but not without caveats]. Basically dll's on windows is a mess [unlike nix], nothing gets recompiled and updated if one thing [or the kernel] changes, having said that, lib's have their issues too. jemalloc is another one, you start to see the common tune, they both provide some memory resource management.

PS: I updated the other post.

@bluescarni
Copy link
Owner

It has to include MPPP as BUILD_STATIC is too generic, i.e. `MPPP_STATIC'.

Sure sure, I was writing too fast :)

You would do that to work around a problem. Like f.e. Intel Threading Building Blocks cannot [due to shared state issues in the lib] be compiled statically, so to work around this MPPP_STATIC_WITH_DYNAMIC_MSVC_RUNTIME is an option [but not without caveats]. Basically dll's on windows is a mess [unlike nix], nothing gets recompiled and updated if one thing [or the kernel] changes, having said that, lib's have their issues too.

Just to make sure I understood, MPPP_STATIC_WITH_DYNAMIC_MSVC_RUNTIME needs only to be an option at build time and needs not to be exposed as a definition in config.hpp. config.hpp only needs to know whether libmp++ was compiled as a static or dynamic library, and not whether libmp++ links to the static or dynamic crt. Is that correct?

@degski
Copy link
Author

degski commented Apr 30, 2019

Yes, that one would be a build macro. MPPP_STATIC deals with the import/export stuff for dll's (which are in your headers), so you need that one at/on consumption as well.

@7ofNine
Copy link
Contributor

7ofNine commented Apr 30, 2019

Hello Francesco,
just to clarify: The creation of a static library is not related to the way the run-time is linked, they are independent. This seems to be a standard point of confusion and the MS documentation is not very supportive here either. A static library can be linked either with the static or the dynamic run-time library (/MT) and the compiler defines _MT. If one creates a dynamic library one can do the same; use either a static run-time or a dynamic run-time but the typical way is using /MD, which defines _MT and _DLL. _MT stands for multi-threading and is historic, the single thread variant has disappeared a while ago.
What type of library one creates is independent of the run-time library one is using. The creation of a dynamic library (DLL) is controlled by a link option with the "surprising" name /DLL. The compiler option(!) for creating a DLL is actually /LD which passes /DLL to the linker. The compiler basically uses the /MT or /MD options to embed the run-time library name into the *.obj files, which then further down the road causes the infamous warnings about incompatible run-time libraries. If the /DLL linker option is defined the linker will either create a DllMain function for you or you have to create one, depending on your intentions. Many don't define a DllMain. One uses it mostly to register the DLL and/or an interface if one creates a com object so you can access the library via a GUID, but this is typically outside the realm of a numerical application.
The really confusing thing is if one specifies /LD (i.e. dynamic library) it implies /MT (!!) unless one overwrites it with /MD. I think that this is where one part of the confusion comes from

@bluescarni
Copy link
Owner

bluescarni commented Apr 30, 2019

Hello Francesco,
just to clarify

Thanks a lot for the explanation! It sure does sound like an intricate system.

Do you also agree that it's generally a sensible thing to do to link to the static runtime when building a static library?

@bluescarni
Copy link
Owner

@degski how is it looking now?

I took the chance to make some changes to the build system when using clang-cl, it would be good if you could confirm everything is working properly.

@degski
Copy link
Author

degski commented May 1, 2019

I took the chance to make some changes to the build system when using clang-cl, it would be good if you could confirm everything is working properly.

I know you didn't ask me the question, but I'll add to what I already said [just to repeat that, yes, you can do both]. The way it's done is not very relevant, it's just /MT vs /MD. The effects are different though. Windows depends on CRT's. And every major update of VSXXXX and/or Windows OS comes with a new(er) additional run-time. This is distinctly different from Linux, which just gets a new kernel, the distribution maintainer re-compiles everything with that new kernel and the distro's package manager installs the lot, all dependencies that have changed due to a new kernel. On Windows that implies that if you distribute software, which is dynamically linked to the crt of the developer machine, now is directly dependent on having that particular crt installed on the target [the user] machine, side-by-side with various other crt's [this is called SxS in Windows lingo]. So, one needs to coerce the user to install that run-time [if not present], or provide it at install time [by some installer] and install it behind the user's back. This is the mechanism that makes that software written in the 90's can actually still run on Windows without re-compilation [so in a way it's a feature]. In the case of static linking, none of this is necessary, because all the functionality is included in the binary. The converse is therefor that static libraries combined with dynamic crt linkage makes simply little sense. If you're going to provide run-times, you can just as well make your own libs dll's as well, which will reduce coupling, decrease compile times etc etc. Mixed linking is possible, but one has to tell the compiler to although parts of the libraries are statically linked to call the dynamic crt instead [that's a linker flag], or the other way around.

I re-submit that vcpkg does it the way I proposed and is what people expect. Both types of crt linking have their pros and cons, there is no best way. The opt-in flag MPPP_BUILD_STATIC_LIBRARY_WITH_DYNAMIC_MSVC_RUNTIME allows for @7ofNine 's use-case, so no problem for him either.

@degski how is it looking now?

I'm gonna have a look at that now and report back.

@degski
Copy link
Author

degski commented May 1, 2019

@degski how is it looking now?

I have successfully built and linked mp++.lib [with clang-cl], the crt's are set correctly [both with VC and clang-cl].

PS: Previously I have been using MPIR to give me GMP. Unfortunately this project can be declared dead [on Windows], Brian Gladman is the only maintainer on Windows, and Brian is on the wrong side of 75 [his own words]. Bill and Brian have parted ways and have decided to each do their own thing, see.

So, I have been looking for another solution, and I found one [it involves static linking :-)]. I have built a working statically linked version of gmp-6.2.1 with mingwx64/gcc-8.3.0, using msys2.

It's as simple as:

./configure --build=broadwell-pc-mingw64 --disable-shared ABI=64
make
make install

and thereafter [with vc/link and clang-cl/lld] link to libgcc.a, libgmp.a. Because these are static c-libs, this linking works. Dynamic linking would not work (at least not so easily). There are some additional little issues, I have solved as well. Maybe you or I should put this somewhere on github?

@degski
Copy link
Author

degski commented May 1, 2019

I've now also checked the MPPP_BUILD_STATIC_LIBRARY_WITH_DYNAMIC_MSVC_RUNTIME flag, and that all works as expected as well.

@bluescarni
Copy link
Owner

@degski Thanks for checking! I am ok with doing what vcpkg does for the MSVC runtime, so, if the current solution is ok for you, I am ok with merging the PR.

Regarding the MPIR/GMP discussion, in the past I have also been building GMP on windows with MinGW/MSYS and it always worked fine (as long as static linking was being used, as you point out). MinGW is great, but it has a critical issue for my use cases which is a bug in the implementation of thread_local which greatly cripples usage in parallel code. The bug is known since a few years at least, and reported multiple times, but it seem like nobody cares and the MinGW development seems to have stalled a bit lately.

I never understood the point of MPIR as a GMP fork. I understand the need of working around GMP's developers indifference/hostility to MSVC, but IMO a thin extra layer in the build system would have been enough (which, as I understand, is what Brian Gladman was/is doing). There was no need for a full fledged fork, and I never used MPIR myself. My only interaction with MPIR so far has been ensuring that mp++ works fine in conjunction with it.

@degski
Copy link
Author

degski commented May 1, 2019

... if the current solution is ok for you, I am ok with merging the PR.

I'm ok with it.

I never understood the point of MPIR as a GMP fork. I understand the need of working around GMP's developers indifference/hostility to MSVC, but IMO a thin extra layer in the build system would have been enough (which, as I understand, is what Brian Gladman was/is doing).

Yeah, that's how it started, then other hobbyists got on board and basically took over the project. Thinking that you can do better than GMP seems rather stupid IMO, why even try?

MinGW is great, but it has a critical issue for my use cases which is a bug in the implementation of thread_local which greatly cripples usage in parallel code.

I don't know exactly what you are referring to [as you are calling it a bug] but there is this repo, which adds almost all the missing bits at least. I just add an include line [to the files supplied by that repo] to the end of the relevant std-headers and this has worked for me so far.

@degski
Copy link
Author

degski commented May 1, 2019

... but it seem like nobody cares and the MinGW development seems to have stalled a bit lately.

I don't know, who does what etc, but the distro I linked to is pretty solid, it's also multilib (and has ada, obj-c and fortran as well, the latter handy for OpenBlas), i.e. it builds both 32- and 64-bit apps. The latest build, with gcc-8.3.0 is pretty recent [like weeks old], who knows.

These are the build specs:

$ gcc -v
Using built-in specs.
COLLECT_GCC=Y:\mingw64\bin\gcc.exe
COLLECT_LTO_WRAPPER=y:/mingw64/bin/../libexec/gcc/x86_64-w64-mingw32/8.3.0/lto-wrapper.exe
Target: x86_64-w64-mingw32
Configured with: ../gcc-8.3.0/configure --build=x86_64-w64-mingw32 --enable-targets=all --enable-multilib --enable-64bit --prefix=/mingw64 --with-sysroot=/mingw64 --disable-shared --enable-static --disable-nls --enable-version-specific-runtime-libs --disable-win32-registry --without-dwarf2 --enable-sjlj-exceptions --enable-fully-dynamic-string --enable-languages=c,ada,lto,c++,objc,obj-c++,fortran --enable-libgomp --enable-lto --enable-libssp -enable-gnattools --disable-bootstrap --with-gcc --with-gnu-as --with-gnu-ld --with-stabs --enable-interwork --with-mpfr-include=/d/msys64/home/alpha/gcc-build/../gcc-8.3.0/mpfr/src --with-mpfr-lib=/d/msys64/home/alpha/gcc-build/mpfr/src/.libs --enable-objc-gc --with-target-bdw-gc=/mingw64
Thread model: win32
gcc version 8.3.0 (GCC)

@bluescarni
Copy link
Owner

I don't know exactly what you are referring to [as you are calling it a bug] but there is this repo, which adds almost all the missing bits at least. I just add an include line [to the files supplied by that repo] to the end of the relevant std-headers and this has worked for me so far.

Referring to these:

https://sourceforge.net/p/mingw-w64/bugs/445/
msys2/MINGW-packages#2519

You can find other reports if you search for them, which, I believe, are various manifestations of the same underlying problem (i.e., that the destructor of thread_local objects is called at the wrong time, leading to memory corruption). I experienced this bug first hand with mp++, which led to disabling certain multi-threaded optimisations in the code when compiled with MinGW.

Note that this is an issue mostly with C++ classes which do non-trivial operations during destruction. If you limit yourself to C-like data types, you should be fine because there's really no destructor being called.

@bluescarni
Copy link
Owner

... but it seem like nobody cares and the MinGW development seems to have stalled a bit lately.

I don't know, who does what etc, but the distro I linked to is pretty solid, it's also multilib (and has ada, obj-c and fortran as well, the latter handy for OpenBlas), i.e. it builds both 32- and 64-bit apps. The latest build, with gcc-8.3.0 is pretty recent [like weeks old], who knows.

These are the build specs:

$ gcc -v
Using built-in specs.
COLLECT_GCC=Y:\mingw64\bin\gcc.exe
COLLECT_LTO_WRAPPER=y:/mingw64/bin/../libexec/gcc/x86_64-w64-mingw32/8.3.0/lto-wrapper.exe
Target: x86_64-w64-mingw32
Configured with: ../gcc-8.3.0/configure --build=x86_64-w64-mingw32 --enable-targets=all --enable-multilib --enable-64bit --prefix=/mingw64 --with-sysroot=/mingw64 --disable-shared --enable-static --disable-nls --enable-version-specific-runtime-libs --disable-win32-registry --without-dwarf2 --enable-sjlj-exceptions --enable-fully-dynamic-string --enable-languages=c,ada,lto,c++,objc,obj-c++,fortran --enable-libgomp --enable-lto --enable-libssp -enable-gnattools --disable-bootstrap --with-gcc --with-gnu-as --with-gnu-ld --with-stabs --enable-interwork --with-mpfr-include=/d/msys64/home/alpha/gcc-build/../gcc-8.3.0/mpfr/src --with-mpfr-lib=/d/msys64/home/alpha/gcc-build/mpfr/src/.libs --enable-objc-gc --with-target-bdw-gc=/mingw64
Thread model: win32
gcc version 8.3.0 (GCC)

Thanks for the pointer!

I was using the mingw-builds builds,

https://mingw-w64.org/doku.php/download/mingw-builds

which, last time I checked, were stuck to an older version of GCC (8.1 I believe). I'll give a try to the builds you are using.

@degski
Copy link
Author

degski commented May 1, 2019

There are definitely some issues related to mpfr in conjunction with the points you mentioned.

@bluescarni
Copy link
Owner

@degski want to open a new report about the issues you encountered?

@degski
Copy link
Author

degski commented May 1, 2019

Yes I will, but first have to be able to at least reproduce it on my end, for the moment I'm not sure what I'm looking at. I might revert back to Brian's setup, or try to mix Brian's mpfr with the MinGW GMP.

@bluescarni
Copy link
Owner

Addressed in #176, will close this report for the time being.

@degski
Copy link
Author

degski commented May 2, 2019

... will close this report for the time being.

Yes, the changes are good.

Re. TLS, there's a new ticket: https://sourceforge.net/p/mingw-w64/bugs/799/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants