win32: Auto-determine DLL path from LDFLAGS.

Copying DLLs raises the problem of finding where the DLLs are to copy.
With autotools, DLLs are usually installed to ${prefix}/bin alongside the
libraries installed to ${prefix}/lib. So we can use the -L arguments in
LDFLAGS passed to the linker to figure out a likely set of directories to
search in. Hook this into the build, too.

This fixes #764 - we now reliably build Windows .zip packages automatically
bundled with any DLLs which may be required.
This commit is contained in:
Simon Howard 2017-01-22 21:38:54 +00:00
parent 2c686fa912
commit 7dc588ee07
4 changed files with 85 additions and 22 deletions

View file

@ -100,6 +100,7 @@ case "$host" in
;;
esac
AC_CHECK_TOOL(OBJDUMP, objdump, )
AC_CHECK_TOOL(STRIP, strip, )
AM_CONDITIONAL(HAVE_WINDRES, test "$WINDRES" != "")

View file

@ -6,7 +6,9 @@
# Tools needed:
CC = @CC@
OBJDUMP = @OBJDUMP@
STRIP = @STRIP@
LDFLAGS = @LDFLAGS@
# Package name and version number:

View file

@ -38,11 +38,12 @@ hook-strife: staging-strife
staging-%:
mkdir $@
cp $(TOPLEVEL)/src/$(PROGRAM_PREFIX)$*.exe \
$(DLL_FILES) \
$@/
cp $(TOPLEVEL)/src/$(PROGRAM_PREFIX)setup.exe \
$@/$(PROGRAM_PREFIX)$*-setup.exe
./cp-with-libs --ldflags="$(LDFLAGS)" \
$(TOPLEVEL)/src/$(PROGRAM_PREFIX)$*.exe $@
./cp-with-libs --ldflags="$(LDFLAGS)" \
$(TOPLEVEL)/src/$(PROGRAM_PREFIX)setup.exe \
$@/$(PROGRAM_PREFIX)$*-setup.exe
$(STRIP) $@/*.exe
for f in $(DOC_FILES); do \

View file

@ -1,11 +1,28 @@
#!/usr/bin/env python
#
# Copy a Windows executable file, along with any dependencies that it also
# needs.
# Copyright(C) 2016 Simon Howard
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
#
# This script copies a Windows .exe file from a source to a destination
# (like cp); however, it also uses the binutils objdump command to
# recursively determine all its DLL dependencies and copy those too.
#
import argparse
import os
import re
import shlex
import shutil
import sys
import subprocess
@ -14,23 +31,26 @@ DLL_NAME_RE = re.compile('\s+DLL Name: (.*\.dll)')
# DLLs that are bundled with Windows, and we don't need to copy.
# Thanks to Martin Preisler; this is from mingw-bundledlls.
WIN32_DLLS = [
WIN32_DLLS = {
"advapi32.dll", "kernel32.dll", "msvcrt.dll", "ole32.dll",
"user32.dll", "ws2_32.dll", "comdlg32.dll", "gdi32.dll", "imm32.dll",
"oleaut32.dll", "shell32.dll", "winmm.dll", "winspool.drv",
"wldap32.dll", "ntdll.dll", "d3d9.dll", "mpr.dll", "crypt32.dll",
"dnsapi.dll", "shlwapi.dll", "version.dll", "iphlpapi.dll",
"msimg32.dll", "setupapi.dll",
]
}
parser = argparse.ArgumentParser(description='Copy EXE with DLLs.')
parser.add_argument('--objdump', type=str, default='objdump',
help='name of objdump binary to invoke')
parser.add_argument('--path', type=str, nargs='*',
help='list of paths to check for binaries')
parser.add_argument('source', type=str, nargs=1,
parser.add_argument('--dll_path', type=str, nargs='*', default=(),
help='list of paths to check for DLLs')
parser.add_argument('--ldflags', type=str, default="",
help='linker flags, which can be used to automatically '
'determine the DLL path')
parser.add_argument('source', type=str,
help='path to binary to copy')
parser.add_argument('destination', type=str, nargs=1,
parser.add_argument('destination', type=str,
help='destination to copy binary')
def file_dependencies(filename, objdump):
@ -83,34 +103,73 @@ def all_dependencies(filename, objdump, dll_paths):
dll_paths: List of directories to search for DLL files.
Returns:
Tuple containing:
List containing the file and all its DLL dependencies.
Set containing paths to all DLL dependencies of the file.
Set of filenames of missing DLLs.
"""
result, missing = [], set()
result, missing = set(), set()
to_process = {filename}
while len(to_process) > 0:
filename = to_process.pop()
result.append(filename)
for dll in file_dependencies(filename, objdump):
try:
dll = find_dll(dll, dll_paths)
if dll not in result:
result |= {dll}
to_process |= {dll}
except IOError as e:
missing |= {dll}
return result, missing
def get_dll_path():
"""Examine command line arguments and determine the DLL search path.
If the --path argument is provided, paths from this are added.
Furthermore, if --ldflags is provided, this is interpreted as a list of
linker flags and the -L paths are used to find associated paths that are
likely to contain DLLs, with the assumption that autotools usually
installs DLLs to ${prefix}/bin when installing Unix-style libraries into
${prefix}/lib.
Returns:
List of filesystem paths to check for DLLs.
"""
result = set(args.dll_path)
if args.ldflags != '':
for arg in shlex.split(args.ldflags):
if arg.startswith("-L"):
prefix, libdir = os.path.split(arg[2:])
if libdir != "lib":
continue
bindir = os.path.join(prefix, "bin")
if os.path.exists(bindir):
result |= {bindir}
return list(result)
args = parser.parse_args()
all_files, missing = all_dependencies(args.source[0], args.objdump, args.path)
# Copy all files to the destination.
for filename in all_files:
shutil.copy(filename, args.destination[0])
dll_path = get_dll_path()
dll_files, missing = all_dependencies(args.source, args.objdump, dll_path)
# Exit with failure if DLLs are missing.
if missing:
sys.stderr.write("Missing DLLs not found in path %s:\n" % args.path)
sys.stderr.write("Missing DLLs not found in path %s:\n" % (dll_path,))
for filename in missing:
sys.stderr.write("\t%s\n" % filename)
sys.exit(1)
# Destination may be a full path (rename) or may be a directory to copy into:
# cp foo.exe bar/baz.exe
# cp foo.exe bar/
if os.path.isdir(args.destination):
dest_dir = args.destination
else:
dest_dir = os.path.dirname(args.destination)
# Copy .exe and DLLs.
shutil.copy(args.source, args.destination)
for filename in dll_files:
shutil.copy(filename, dest_dir)