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.

"Lua.exe - System Error" screenshot

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