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:
parent
2c686fa912
commit
7dc588ee07
4 changed files with 85 additions and 22 deletions
|
|
@ -100,6 +100,7 @@ case "$host" in
|
|||
;;
|
||||
esac
|
||||
|
||||
AC_CHECK_TOOL(OBJDUMP, objdump, )
|
||||
AC_CHECK_TOOL(STRIP, strip, )
|
||||
|
||||
AM_CONDITIONAL(HAVE_WINDRES, test "$WINDRES" != "")
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@
|
|||
# Tools needed:
|
||||
|
||||
CC = @CC@
|
||||
OBJDUMP = @OBJDUMP@
|
||||
STRIP = @STRIP@
|
||||
LDFLAGS = @LDFLAGS@
|
||||
|
||||
# Package name and version number:
|
||||
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue