godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
86.64k stars 19.32k forks source link

Manually linking static libraries with MSVC appends Godot's LIBSUFFIX #23687

Open asparagii opened 5 years ago

asparagii commented 5 years ago

fmod.zip

Godot version: 3.1

OS/device including version: Windows

Issue description: It's very likely to be my fault, but I can't include the FMOD library when linking a custom module. It's strange how the module itself compiles without problems, but when it comes to the "Linking program" step the compiler freezes for a bit and then outputs fatal error LNK2019: unresolved external symbol "public: static enum FMOD_RESULT __cdecl FMOD::Studio::System::create(class FMOD::Studio::System * *,unsigned int)" in function "public: __cdecl FMod::FMod(void)".

Minimal reproduction project: I placed the "FMOD Studio API Windows" under "thirdparty". The module itself basically contains just a call to FMOD's initialization function. I attached the module as a zip file. For those who don't want to download the zip, here's the SCsub:

Import('env')

fmodapifolder = "#thirdparty/FMOD Studio API Windows/api"

module_env = env.Clone()
module_env.add_source_files(env.modules_sources, "*.cpp")
module_env.Append(CPPPATH=fmodapifolder + "/studio/inc")
module_env.Append(LIBPATH=fmodapifolder + "/studio/lib")
module_env.Append(CPPPATH=fmodapifolder + "/lowlevel/inc")
module_env.Append(LIBPATH=fmodapifolder + "/lowlevel/lib")

Here's fmod.cpp:

#include "fmod.h"

FMod::FMod() {
    // Initialize fmod
    FMOD::Studio::System* system = NULL;
    FMOD::Studio::System::create(&system);
}

void FMod::_bind_methods(){
    // none
}

Here's fmod.h:

#ifndef FMODNODE
#define FMODNODE

#include "scene/main/node.h"
#include "fmod.h"
#include "fmod.hpp"
#include "fmod_common.h"
#include "fmod_studio.hpp"
#include "fmod_studio.h"
#include "fmod_studio_common.h"

class FMod : public Node {
    GDCLASS(FMod, Node);

protected:
    static void _bind_methods();

public:
    FMod();
};

#endif

Also, I don't know if this can help, but without any call to FMOD functions (only with the includes), scons compiles without any issue.

akien-mga commented 5 years ago

That's not a Godot issue, you just need to actually link the library by appending it to LIBS for your module_env (like module_env.Append(LIBS=['FMOD']) or similar). See http://docs.godotengine.org/en/latest/development/cpp/binding_to_external_libraries.html

If the docs are not clear enough for your use case, you can open an issue on https://github.com/godotengine/godot-docs to discuss it further and have the tutorial improved.

rmazzier commented 5 years ago

I have this issue too. I have the same setup as @MicheleLambertucci and my SCsub is:

Import('env')

fmodapifolder = "#thirdparty/FMOD Studio API Windows/api"

module_env = env.Clone()
module_env.add_source_files(env.modules_sources, "*.cpp")
module_env.Append(CPPPATH=fmodapifolder + "/studio/inc")
module_env.Append(LIBPATH=fmodapifolder + "/studio/lib")
module_env.Append(CPPPATH=fmodapifolder + "/lowlevel/inc")
module_env.Append(LIBPATH=fmodapifolder + "/lowlevel/lib")
module_env.Append(LIBS=['fmod','fmodL','fmodstudio','fmodstudioL','fmod_vc','fmodL_vc','fmodstudio_vc','fmodstudioL_vc'])

Still I get Linker errors. Note that the FMOD API is a shared library (so .lib and .dll files), and the both the docs and the engine use open source static libraries. So maybe this is not the correct way for binding an external Shared library? The docs don't say anything about it and the engine source code doesn't provide any example on how to do this.

starry-abyss commented 5 years ago

Not an expert, but from what I remember .lib files distributed with shared libraries (or .a on linux) are exactly static libraries. They should do the loading of .dll for you when included in the executable.

akien-mga commented 5 years ago

@rmazzier You're close - the issue is that the libs have to be passed to the linker for the Godot binary, and thus to the main env. This should work:

fmodapifolder = "#thirdparty/FMOD Studio API Windows/api"

module_env = env.Clone()
module_env.add_source_files(env.modules_sources, "*.cpp")
module_env.Append(CPPPATH=fmodapifolder + "/studio/inc")
module_env.Append(CPPPATH=fmodapifolder + "/lowlevel/inc")
env.Append(LIBPATH=fmodapifolder + "/studio/lib")
env.Append(LIBPATH=fmodapifolder + "/lowlevel/lib")
env.Append(LIBS=['fmod','fmodL','fmodstudio','fmodstudioL','fmod_vc','fmodL_vc','fmodstudio_vc','fmodstudioL_vc'])

Here's a test module I used (on Linux) to confirm that: sndfile.zip

module_env = env.Clone()
module_env.Append(CPPPATH=["#modules/sndfile/sndfile/include"])
env.Append(LIBPATH=["#modules/sndfile/sndfile/lib"])
env.Append(LIBS=["sndfile"])
# Special case here as sndfile depends on FLAC and vorbis
env.Append(LIBS=["FLAC", "vorbis", "vorbisenc"])
rmazzier commented 5 years ago

Thank you for your reply. Even if I had already tried the way you suggested, I gave it another chance. My current SCsub is:

Import('env')

fmodapifolder = "#thirdparty/fmod/"

module_env = env.Clone()
module_env.add_source_files(env.modules_sources, "*.cpp")

module_env.Append(CPPPATH=[fmodapifolder + "/studio/inc/"])
module_env.Append(CPPPATH=[fmodapifolder + "/lowlevel/inc/"])

env.Append(LIBPATH=[fmodapifolder + "/studio/lib/ "])
env.Append(LIBPATH=[fmodapifolder + "/lowlevel/lib/"])

env.Append(LIBS=['fmodstudio_ARM','fmodstudio_X64','fmodstudioL_ARM','fmodstudioL_X64'])
env.Append(LIBS=['fmod_ARM','fmod_X64','fmodL_ARM','fmodL_X64'])

But I still get linker errors. Specifically:

D:\Riccardo\Godot Build\godot-master>scons -j6 platform=windows
scons: Reading SConscript files ...
Configuring for Windows: target=debug, bits=default
Found MSVC compiler: amd64
Compiled program architecture will be a 64 bit executable (forcing bits=64).
YASM is necessary for WebM SIMD optimizations.
WebM SIMD optimizations are disabled. Check if your CPU architecture, CPU bits or platform are supported!
Checking for C header file mntent.h... (cached) no
scons: done reading SConscript files.
scons: Building targets ...
[ 97%] Linking Program        ==> bin\godot.windows.tools.64.exe
sibi[ 98%] le aprire il file di input 'fmodstudio_ARM.windows.tools.64.lib'
[100%] progress_finish(["progress_finish"], [])
[100%] scons: *** [bin\godot.windows.tools.64.exe] Error 1181
scons: building terminated because of errors.

Since I am not understanding whether I am doing something wrong or not , would you mind trying to compile it and see if you get the same error?

fmod_module_test.zip

Here is the zip with the libs and my current code. Thanks.

akien-mga commented 5 years ago

[ 98%] le aprire il file di input 'fmodstudio_ARM.windows.tools.64.lib'

What Godot commit are you building against? And what SCons version? This extra lib suffix should be fixed in the master branch already with #20380.

rmazzier commented 5 years ago

Master branch, latest commit.

Using the latest SCons version:

D:\Riccardo\Godot Build\godot-master>scons --version
SCons by Steven Knight et al.:
        script: v3.0.1.74b2c53bc42290e911b334a6b44f187da698a668, 2017/11/14 13:16:53, by bdbaddog on hpmicrodog
        engine: v3.0.1.74b2c53bc42290e911b334a6b44f187da698a668, 2017/11/14 13:16:53, by bdbaddog on hpmicrodog
        engine path: ['C:\\Python27\\scons-3.0.1\\SCons']
Copyright (c) 2001 - 2017 The SCons Foundation
akien-mga commented 5 years ago

Alright, it works fine on Linux but apparently #20045 is still valid on MSVC. Any idea @garyo?

We're now properly saving the original LIBSUFFIX and SHLIBSUFFIX in LIBSUFFIXES prior to modifying them, but MSVC's linker still seems to try LIBSUFFIX instead of iterating LIBSUFFIXES as hinted by the man page? https://github.com/godotengine/godot/blob/master/SConstruct#L425-L429

rxlecky commented 5 years ago

I found the cause of the issue. It is caused by different format in which windows and *nix linkers take their arguments.

On *nix systems, linkers take their arguments stripped from their prefixes and suffixes. This means that linker passed -lmylib argument actually looks for either libmylib.a or libmylib.so by default. $LIBPREFIXES and $LIBSUFFIXES are the variables that control which prefixes and suffixes the linker checks for.

MSVC linker, on the other hand, expects the arguments to be full filenames. So what SCons passes to the linker here is list of files that is constructed from list of libraries in$LIBS variable with $LIBLINKPREFIX and $LIBLINKSUFFIX prepended and appended respectively. And since $LIBLINKSUFFIX by default points to $LIBSUFFIX, we get error messages such as LINK : fatal error LNK1181: cannot open input file 'fmodL_vc.windows.opt.tools.32.lib'

There is a way, however, to override how the arguments that get passed to the linker are formatted. I'd suggest emulating the *nix linker behaviour on windows so that $LIBPREFIXES and $LIBSUFFIXES would work the same way on all platforms.

neikeq commented 5 years ago

I overcame this when linking mono by passing the LINKFLAGS directly.

rxlecky commented 5 years ago

@neikeq Looking at your code right now. Yeah, that's also a way to go around it. Maybe it would be even better solution than the one I implemented - just wrap this piece of code in custom env functions.

akien-mga commented 5 years ago

Reopening as it's not properly fixed yet - see #23910.

heraldofgargos commented 5 years ago

I'm having the same issue here. Trying to link FMOD using MSVC 2017.

[100%] LINK : fatal error LNK1181: cannot open input file 'fmod_vc.windows.tools.64.lib' scons: *** [bin\godot.windows.tools.64.exe] Error 1181 scons: building terminated because of errors.

Glad to see the issue mentioned here. Can somebody please suggest a temporary solution to this until it's properly fixed?

rxlecky commented 5 years ago

A temporary solution would be - as dumb as it sounds - renaming your libraries to match the Godot extensions, i.e. turn your fmod_vc.lib to fmod_vc.windows.tools.64.lib. If you are building on multiple configurations, you'd need to do have a copy of a library with each configuration's specific extension.

The other way would be taking a look at how neikeq did it with Mono. https://github.com/godotengine/godot/blob/d8c40bccbbbf74001cbd085da5f71180e054e17f/modules/mono/config.py#L113-L119

However, I am not sure how confident you are with SCons/Python. Let me know if you need some more help.

heraldofgargos commented 5 years ago

This is the first time I'm being exposed to the SCons build system and I'm still getting used to it. Renaming the files worked however and it compiled without any errors 🎉

marstaik commented 4 years ago

I can confirm this is still an issue, its looking for the godot lib extensions for my libraries.

Using LINKFLAGS worked, but its not a very graceful solution.

Looooong commented 3 years ago

I found an alternative workaround to using $LINKFLAGS:

if env.msvc:
    env.Append(LIBS=[File('path/to/fmod_vc.lib')])
else:
    env.Append(LIBS=['fmod_vc'])

According to $LIBS document, appending File(...) to $LIBS will ignore $LIBLINKPREFIX and $LIBLINKSUFFIX. Therefore, the path provided to File(...) construct must be relative or absolute path with the correct prefix and suffix.

Calinou commented 3 years ago

@Looooong Is there something we need to fix on our end? If so, please open a pull request :slightly_smiling_face:

Looooong commented 3 years ago

@Calinou Well, this is just a temporary workaround this issue when linking external static libraries with MSVC. The real issue might be on the SCons end as discussed here.

dmoody256 commented 2 years ago

A single point solution for windows, would be to overwrite the _LIBFLAGS environment variable in the SConstruct, which when using msvc, is just a concatenation function. The new function would check each library in the link line and if its one of Godots, it would add the custom suffix, otherwise use the standard windows .lib. Marking godot's libraries can be done in a number of ways, but since there is a common add_library function in methods.py, this could add a node.attribute to the library node, which indicates its a Godot library.

But looking at this issue from a higher lever, I don't think you should be messing with the LIBSUFFIX variable in the first place. This variable is intended to set the system standard libsuffix, not for a single set of user libraries. Really what you should do is just add the suffix in your custom add_library call, or override the Library builder to do it for you.

ashtonmeuser commented 1 year ago

I like the File() wrapping solution @Looooong mentioned. However, the issue is made more complicated when compiling a module and using Windows SDK libs for several reasons.

  1. Changes to LIBSUFFIX must be applied to the engine's build environment. Because LIBS must be edited on the primary env, not the cloned env, this will affect all other modules.
  2. Using MSVC and including Windows SDK libs e.g. bcrypt, userenv, etc. which can't easily be wrapped in File(). Including these libs will result in the linker looking for userenv.windows.template_release.x86_64.lib (or whatever your Godot build config determines). See https://github.com/ashtonmeuser/godot-wasm/issues/27.

@Calinou this seems like a bit of a blocker for module developers. How can I include userenv in a Godot module built with MSVC?