-
Notifications
You must be signed in to change notification settings - Fork 133
Support building as a shared library on Windows #147
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
Support building as a shared library on Windows #147
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks promising. I've been meaning to do something similar, to reduce the size of the dynamic symbol table on Linux. What you're doing is an important part of that.
@@ -0,0 +1,14 @@ | |||
#ifndef CUKE_CUKEDLL_HPP_ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd appreciate it if you could use CMake's GenerateExportHeader module, or at least make this header match the output that would be generated by that module much closer. To produce this output to get an example, add this to src/CMakeLists.txt
and look for the generated files in $BUILDDIR/src/
:
include(GenerateExportHeader)
set_target_properties(cucumber-cpp cucumber-cpp-nomain
PROPERTIES
DEFINE_SYMBOL cucumber_cpp_EXPORTS
)
generate_export_header(cucumber-cpp)
That should pave the way for symbol visibility on non-Windows OSes too, without having to redo the Windows part later on.
Additionally: to reflect that, please rename this header to not mention DLLs, but the fact that you're declaring which symbols are to be exported. E.g. CukeExport.h
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I can fix CMakeLists.txt
to use GenerateExportHeader
. It seems to me though that it would make CukeDll.hpp
(or CukeExport.h
, or however it should be named) to be obsolete. If it's ok with you, I'll just remove this file and add the necessary include_directories
to CMake so that it finds the generated cucumber-cpp_export.h
file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replacing completely with the generated done is okay with me. Please make that target_include_directories
so that transitive build requirements work properly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@muggenhor ah, I forgot about that, added a fixup just now for that.
#ifndef CUKE_CUKEDLL_HPP_ | ||
#define CUKE_CUKEDLL_HPP_ | ||
|
||
#ifndef CUKE_API_ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't do this check, it should be considered a bug (and thus reported by the compiler) if it's already defined.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, good point. However I will instead remove this file (see above comment).
#define CUKE_CUKEDLL_HPP_ | ||
|
||
#ifndef CUKE_API_ | ||
#if CUKE_LINKED_AS_SHARED_LIBRARY |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just like GenerateExportHeader, please:
- wrap both the export and import case in
#ifndef CUCUMBER_CPP_STATIC_DEFINE
to detect when we're building or using a static library. - distinguish the export from the import case by the presence of the macros
cucumber_cpp_EXPORTS
andcucumber_cpp_nomain_EXPORTS
(CMake defines those only while building objects for the mentioned target, see the documentation for DEFINE_SYMBOL for more details)
@@ -1,5 +1,6 @@ | |||
#include "step/StepManager.hpp" | |||
#include "hook/HookRegistrar.hpp" | |||
#include "ContextManager.hpp" | |||
#include "CukeDll.hpp" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please follow standard "include-what-you-use" rules and don't include what you don't use (in the same file).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, removed.
#elif CUKE_CREATE_SHARED_LIBRARY | ||
#define CUKE_API_ __declspec(dllexport) | ||
#else | ||
#define CUKE_API_ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI: GenerateExportHeader would call this CUCUMBER_CPP_EXPORT
(and for Windows #define
that to be dllimport when importing).
@@ -13,7 +13,7 @@ namespace internal { | |||
* Currently it is a wrapper around CukeCommands. It will have its own | |||
* implementation when feature #31 is complete. | |||
*/ | |||
class CukeEngineImpl : public CukeEngine { | |||
class CUKE_API_ CukeEngineImpl : public CukeEngine { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You forgot to include the header. It probably builds because of an indirect include, but please don't depend on that (include-what-you-use: directly in the file where it's used).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
b8e685f
to
cf5ef19
Compare
@muggenhor thanks for the feedback! I force pushed this branch with some fixes and also cleaned up the commit message to make it more platform-generic. |
src/CMakeLists.txt
Outdated
) | ||
generate_export_header(cucumber-cpp) | ||
|
||
include_directories(${CMAKE_CURRENT_BINARY_DIR}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should no longer be necessary, target_include_directories
replaces it.
810c656
to
b9b2131
Compare
src/CMakeLists.txt
Outdated
set_target_properties(cucumber-cpp cucumber-cpp-nomain PROPERTIES | ||
DEFINE_SYMBOL cucumber_cpp_EXPORTS | ||
) | ||
generate_export_header(cucumber-cpp) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about using the EXPORT_FILE_NAME
parameter of GENERATE_EXPORT_HEADER
(if I read correctly) to match the current file naming conventions for include files? #include "cucumber-cpp_export.h"
does not read well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, how does CukeExport.hpp
sound (again)? 🙂 fixup pushed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Except for the alteration to EXPORT_FILE_NAME
I suggested, this looks good to go.
I would like to know however whether exporting just parts of a class (as I describe in another comment in this review) works on MSVC. Because that would make it possible to further reduce the exported set of symbols, and thus narrow the exposure of private implementation details further.
src/CMakeLists.txt
Outdated
) | ||
|
||
generate_export_header(cucumber-cpp | ||
EXPORT_FILE_NAME "CukeExport.hpp" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In order to place this in the same, relative, include directory structure, I think we should use EXPORT_FILE_NAME cucumber-cpp/internal/CukeExport.h
(this will affect the required include directives which will need to change to #include <cucumber-cpp/internal/CukeExport.h>
). This would make library installs place the headers all in a single location.
@@ -16,7 +18,7 @@ namespace internal { | |||
|
|||
typedef std::vector<shared_ptr<void> > contexts_type; | |||
|
|||
class ContextManager { | |||
class CUCUMBER_CPP_EXPORT ContextManager { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's possible to export only the member function purgeContexts
and add a non-template overload for addContext
and export that too.
But this can also be done after this PR.
@@ -59,7 +61,7 @@ class PendingStepException : public InvokeException { | |||
* It uses standard types (as much as possible) to be easier to call. | |||
* Returns standard types if possible. | |||
*/ | |||
class CukeEngine { | |||
class CUCUMBER_CPP_EXPORT CukeEngine { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe all of the members of this class are used internally only, with the exception of the constructor and destructor. So if you explicitly declare those and add (empty) implementations to the .cpp file then exporting just those should be enough.
I know on GNU/Linux that would work. Could you verify that that works on MSVC too? I.e. only exporting some members of a class without exporting the entire class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAIK MSVC should support exporting individual methods and not the entire class. I can test this with dumpbin /EXPORTS
(tho I'm not on my Windows machine now, so I'll comment again once I've verified that).
Oh, before I forget. Could you please add (or modify) an AppVeyor build that will fail to build at least one of the tests or examples if one of the symbols you currently export doesn't get exported? I'd appreciate it if you could demonstrate that failure with an intermediate commit. This to ensure we don't accidentally break this in the future. |
@muggenhor Sorry for the delay on finishing up this PR, I had some other work that I had to prioritize in the last few weeks. Anyways, sure, I can adapt the AppVeyor build configuration. I'll be creating another PR so that I can make sure that it fails/succeeds as expected. Please ignore that PR; I'll ping you back in this one once everything is ready to be reviewed again. |
244a082
to
3de2faa
Compare
3de2faa
to
051807f
Compare
051807f
to
d7b95d0
Compare
@muggenhor Thanks for dusting off and reviving this PR. Do you need any help from me to get it ready to merge? Namely, should I rebase it and fix the merge conflicts? |
I have a rebase ready as e9d074a already. If CI succeeds for d13440a (a commit for CI only that I don't intend to merge) then I intend to push these to this PR. I would appreciate it if you could confirm that it still works as intended for you at that point. That'll probably be about 2 hours from now. |
@muggenhor I don't have time today to test these changes, but I'm sure they'll be fine. 😄 I did a quick look at the code and I'm sure that it'll work fine for us. We don't use CMake internally and most of the source changes are pretty trivial. Go ahead and merge! |
dfb4a0e
to
2d0b693
Compare
I intend to merge this tomorrow. It now works on all CI, just minor stuff turned out to be the more difficult things. |
On Windows, it is necessary to add some __declspec() calls to expose some symbols with different linkage types. This commit makes it possible for users to easily build cucumber-cpp either as a static or dynamic library by setting CUKE_ENABLE_SHARED_LIBS to ON or OFF at build time.
This makes it possible to install this header at some future point.
Instead of the custom CUKE_ENABLE_SHARED_LIB option prefer the builtin BUILD_SHARED_LIBS option which controls the default library format for add_library.
Making sure GTest/GMock is built as position independent code is necessary when it's linked statically and we try to link it into Cucumber-CPP when that's built as a shared library. Furthermore Boost::unit_test_framework is a direct dependency of cucumber-cpp: we need to link to it when we use it.
Instead of only exporting the GTestDriver, make sure they're all exported from SHARED libraries.
Otherwise how can we execute anything?
This prevents unmarked symbols, which we consider to be internals, from being available on the dynamic library's dynamic symbol table (EXPORT table on Windows). Furthermore we split out a static library. This allows us to expose the library internals to unit tests only, through this static library. This works because symbol hiding only affects dynamic linking, not static linking.
Because this will catch errors with APIs not being properly marked for export. AFAIK this shouldn't fail to catch any errors that a static build would, so I'm not retaining the static build.
4e3bedb
to
9fe13ae
Compare
Great, thanks again for your work on this PR @muggenhor! |
This exports all externally used functions and variables to make them accessible from a DLL build. Additionally we now hide all non-marked symbols by default to achieve a similar effect with shared library builds on other platforms.
This PR has been merged into master with 183c79d. Thanks for your change! |
On Windows, it is necessary to add some _declspec() calls to expose
some symbols with different linkage types. This commit adds the
CUKE_API macro, which makes it possible for users to easily build
cucumber-cpp on Windows either as a static or dynamic library by
defining either CUKE_LINKED_AS_SHARED_LIBRARY or
CUKE_CREATE_SHARED_LIBRARY at build time.