ref: 7dc588ee07686eab554c3bdfd6f98953c4affa7f
parent: 2c686fa912cce3da0c1053f1250d011a7b1a384b
author: Simon Howard <fraggle@soulsphere.org>
date: Sun Jan 22 16:38:54 EST 2017
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.
--- a/configure.ac
+++ b/configure.ac
@@ -100,6 +100,7 @@
;;
esac
+AC_CHECK_TOOL(OBJDUMP, objdump, )
AC_CHECK_TOOL(STRIP, strip, )
AM_CONDITIONAL(HAVE_WINDRES, test "$WINDRES" != "")
--- 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:
--- a/pkg/win32/GNUmakefile
+++ b/pkg/win32/GNUmakefile
@@ -38,11 +38,12 @@
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 \
--- 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,7 +31,7 @@
# 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",
@@ -21,16 +38,19 @@
"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,18 +103,18 @@
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}
@@ -101,16 +121,55 @@
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.
-# Copy all files to the destination.
-for filename in all_files:
- shutil.copy(filename, args.destination[0])
+ 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()
+
+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)