you all know the problem, that you only want to define public interfaces as visible in shared libraries (with __declspec(dllexport/import) or equivalent gcc properties).
But then, you may only access these symbols from the user. You can not unit test the internal functions/classes. There are multiple ways to work around this but none of it seems elegant, some seem plainly wrong:
Recompile library sources (directly or via property SOURCES) into executable
Recompile shared lib also as static lib and use this instead
Create intermediate OBJECT lib and use it in shared lib and executable
Create shared lib twice, once with WINDOWS_EXPORT_ALL_SYMBOLS (or gcc equivalent) and use this one in test.
Somehow bake the tests into the original shared library
make internal interfaces public (but do not ship headers)
I really do not care about additional build time, what I care about is the usability. If its hard to test a shared library, users might not do it.
Is there any CMake feature that makes this easier? What is your preferred way?
CMake doesn’t have any way to do this. Mainly because C++ does not provide any way to do this either for that matter. If build time is not an issue at all, compiling the library sources twice is sufficient. If it isn’t, OBJECT libraries can reduce the time in exchange for more complicated export macro wrangling (since it is not SHARED, you’ll need to manage export symbols more closely since CMake won’t provide a default _EXPORT macro by default).
I am shamelessly bumping this topic as this is the only comprehensive list of options for this problem I found out there.
This is by far the cleanest solution to the problem as long as you have CMake 3.12 available to you.
If you need to use an older version of CMake, then you could use a static library for the internals and use this in both the shared library and the test executable.
You could still use an object library, but you cannot link it to other targets prior to 3.12. You would need to extract the list of resulting objects and pass it on using the $<TARGET_OBJECTS:target>. Other properties such as dependencies. include directories and compilation options needs to be complied too.
The Book “Professional CMake” contains a section related to this issue.
This problem is not limited to Windows/DLL. This also exists in gcc and Linux, probably other compilers as well.
I want to emphasize my statement: what I care about is the usability Many developers here (coming from IDEs like Visual Studio or programming huge monoliths) are overwhelmed by CMake and its “object” based programming. They never used a linker in commandline and do not understand what target_link_library(a PRIVATE/PUBLIC/INTERFACE b) could mean.
That said, the OBJECT solution is to hard to understand, especially when you need to care about -fpic and -fpie.
We currently use these two solutions:
make internal interfaces public (but do not ship headers)
We have a CMake function target_add_api(a) which creates the header with the defines. It also sets the define to switch export/import depending on a LIBRARY_TYPE.
We have seperate macros to mark this: e.g. A_API_EXPORT_FOR_TESTING void privateFunction();.
Developers that know more about CMake create INTERFACE libraries with INTERFACE_SOURCES and use those in tests and libraries.
To properly address this problem, I think, there should be a special mode for target_link_libraries() that implicitly handles this in one of the originally posted ways.
There’s no single way CMake could possibly handle this for every project out there. Symbols are either exported or not; there is no intermediate state available for CMake to even make this work (this isn’t up to the compiler, but the platform binary format of which none have such a thing). I think OBJECT libraries are the best you have available right now.
I faced this as well, without clear response within Professional CMake 17th edition. I would have loved a section about testing internals of a shared library.
So my current flow is to create a SHARED library as usual, let’s call it lib.
Then create an INTERFACE library called lib-impl.
Use target_sources to add the SHARED library objects to the created INTERFACElib-impl using $<TARGET_OBJECTS:lib>.
And use target_link_libraries(lib-impl INTERFACE $<TARGET_PROPERTY:lib,LINK_LIBRARIES>)
Includes directories properties can be imported as well from lib to lib-impl with the same generator expression $<TARGET_PROPERTY:lib,INCLUDE_DIRECTORIES> (with the addition of the non public header paths).
Within the unit-tests, use target_link_libraries(unit-test PRIVATE lib-impl)
If lib is static, (user set BUILD_SHARED_LIBSOFF), then nothing change.
Objects are built once, and no need to set POSITION_INDEPENDENT property on an object library.
All this can easily be moved into a dedicated function.