shithub: choc

Download patch

ref: 7dc588ee07686eab554c3bdfd6f98953c4affa7f
parent: 2c686fa912cce3da0c1053f1250d011a7b1a384b
author: Simon Howard <[email protected]>
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)