commit 7dc588ee07686eab554c3bdfd6f98953c4affa7f
Author: Simon Howard <fraggle@soulsphere.org>
AuthorDate: Sun Jan 22 21:38:54 2017 +0000
Commit: Simon Howard <fraggle@soulsphere.org>
CommitDate: Sun Jan 22 21:38:54 2017 +0000
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.
---
configure.ac | 1 +
pkg/config.make.in | 2 ++
pkg/win32/GNUmakefile | 11 +++---
pkg/win32/cp-with-libs | 93 +++++++++++++++++++++++++++++++++++++++++---------
4 files changed, 85 insertions(+), 22 deletions(-)
diff --git a/configure.ac b/configure.ac
index 554eea63..567a39d2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -100,6 +100,7 @@ case "$host" in
;;
esac
+AC_CHECK_TOOL(OBJDUMP, objdump, )
AC_CHECK_TOOL(STRIP, strip, )
AM_CONDITIONAL(HAVE_WINDRES, test "$WINDRES" != "")
diff --git a/pkg/config.make.in b/pkg/config.make.in
index 023b3e9c..c604006b 100644
--- a/pkg/config.make.in
+++ b/pkg/config.make.in
@@ -6,7 +6,9 @@
# Tools needed:
CC = @CC@
+OBJDUMP = @OBJDUMP@
STRIP = @STRIP@
+LDFLAGS = @LDFLAGS@
# Package name and version number:
diff --git a/pkg/win32/GNUmakefile b/pkg/win32/GNUmakefile
index 69f6bd1d..7e9fcb83 100644
--- a/pkg/win32/GNUmakefile
+++ b/pkg/win32/GNUmakefile
@@ -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 \
diff --git a/pkg/win32/cp-with-libs b/pkg/win32/cp-with-libs
index 95024831..4aff201e 100755
--- a/pkg/win32/cp-with-libs
+++ b/pkg/win32/cp-with-libs
@@ -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
-args = parser.parse_args()
+def get_dll_path():
+ """Examine command line arguments and determine the DLL search path.
-all_files, missing = all_dependencies(args.source[0], args.objdump, args.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()
-# 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)