How to build a Lua binary with MSYS2 that does not depend on MinGW-w64’s DLLs
As an environment for building Windows binaries MSYS2 improves upon MinGW/MSYS in several important ways. When you switch, however, you will notice that its builds of Lua, unlike those produced with plain old mingw-base
, depend upon external DLLs by default.
When you build Lua 5.1.5 with MSYS2’s 32-bit version of MinGW-w64 using the command make mingw
you end up with a lua51.dll
that requires libgcc_s_dw2-1.dll
and libwinpthread-1.dll
:
$ wget https://www.lua.org/ftp/lua-5.1.5.tar.gz
$ tar xvf lua-5.1.5.tar.gz
$ cd lua-5.1.5/src/
$ make mingw
$ ldd lua51.dll
ntdll.dll => /c/WINDOWS/system32/ntdll.dll (0x7c900000)
kernel32.dll => /c/WINDOWS/system32/kernel32.dll (0x7c800000)
msvcrt.dll => /c/WINDOWS/system32/msvcrt.dll (0x77c10000)
libgcc_s_dw2-1.dll => /mingw32/bin/libgcc_s_dw2-1.dll (0x6eb40000)
libwinpthread-1.dll => /mingw32/bin/libwinpthread-1.dll (0x64b40000)
If you want fewer DLLs to manage, you have several options. For a quick fix you could build a Lua interpreter statically with make all
—but then you would not be able to use binary modules, which expect a lua51.dll
. A solution that keeps lua51.dll
is explained below.
The output of ldd
and the message box in the screenshot mentions libgcc_s_dw2-1.dll
, which means the library lua51.dll
depends on a dynamically linked copy of libgcc
. We should be able to use GCC’s flag -static-libgcc
to ensure libgcc
is linked statically. The makefile for Lua has a section for user-supplied settings with the variables MYCFLAGS
and MYLDFLAGS
for the compiler and the linker flags respectively. However, convenience build targets like mingw
actually ignore those. Let’s look at the target mingw
itself, then, with an eye for what to change there:
mingw:
$(MAKE) "LUA_A=lua51.dll" "LUA_T=lua.exe" \
"AR=$(CC) -shared -o" "RANLIB=strip --strip-unneeded" \
"MYCFLAGS=-DLUA_BUILD_AS_DLL" "MYLIBS=" "MYLDFLAGS=-s" lua.exe
$(MAKE) "LUAC_T=luac.exe" luac.exe
The first command runs make
recursively to build both lua51.dll
(LUA_A
) and lua.exe
(LUA_T
) with a predefined set of MY*
flags.
To keep the original target intact for reference, we’ll make a copy of it named mingw-w64
and modify that. If we put -static-libgcc
in MYLDFLAGS
or MYCFLAGS
in the copy, though, it will not accomplish what we are trying to do. Why? If you search the makefile for the target LUA_A
you will find the following:
$(LUA_A): $(CORE_O) $(LIB_O)
$(AR) $@ $(CORE_O) $(LIB_O) # DLL needs all object files
$(RANLIB) $@
It turns out that it is the $(AR)
command that creates lua51.dll
. Looking at the target mingw-w64
we can see that, despite its name, the variable AR
is set to $(CC) -shared -o
, i.e., the $(AR) $@ ...
command runs GCC. So let’s add -static-libgcc
where we set AR
and try to build again:
mingw-w64:
$(MAKE) "LUA_A=lua51.dll" "LUA_T=lua.exe" \
"AR=$(CC) -shared -static-libgcc -o" "RANLIB=strip --strip-unneeded" \
"MYCFLAGS=-DLUA_BUILD_AS_DLL" "MYLIBS=" "MYLDFLAGS=-s" lua.exe
$(MAKE) "LUAC_T=luac.exe" luac.exe
$ make clean
$ make mingw-w64
$ ldd lua51.dll
ntdll.dll => /c/WINDOWS/system32/ntdll.dll (0x7c900000)
kernel32.dll => /c/WINDOWS/system32/kernel32.dll (0x7c800000)
msvcrt.dll => /c/WINDOWS/system32/msvcrt.dll (0x77c10000)
There. We’ve built a lua51.dll
(and a lua.exe
) that does not expect an external libgcc
.
If you want to achieve the same effect without editing the makefile you can use an equivalent command:
$ make clean
$ make "LUA_A=lua51.dll" "LUA_T=lua.exe" "AR=cc -shared -static-libgcc -o" "RANLIB=strip --strip-unneeded" "MYCFLAGS=-DLUA_BUILD_AS_DLL" "MYLIBS=" "MYLDFLAGS=-s" lua.exe
$ make "LUAC_T=luac.exe" luac.exe