Add pkg/win32 support to create self-supporting Windows zips (#49)

This commit is contained in:
Mike Swanson 2020-01-21 23:43:21 -08:00 committed by Fabian Greffrath
parent ffef698040
commit 822f09ea79
7 changed files with 233 additions and 0 deletions

View File

@ -84,5 +84,6 @@ examples/Makefile
Source/Makefile
Source/resource.rc
toolsrc/Makefile
pkg/config.make
])
AC_OUTPUT

2
pkg/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
Makefile
config.make

8
pkg/Makefile.am Normal file
View File

@ -0,0 +1,8 @@
WIN32_FILES= \
win32/GNUmakefile \
win32/cp-with-libs \
win32/README
EXTRA_DIST=$(WIN32_FILES)

21
pkg/config.make.in Normal file
View File

@ -0,0 +1,21 @@
# Shared file included by the makefiles used to build packages.
# This contains various information needed by the makefiles,
# and is autogenerated by configure to include various
# necessary details.
# Tools needed:
CC = @CC@
OBJDUMP = @OBJDUMP@
STRIP = @STRIP@
LDFLAGS = @LDFLAGS@
# Package name and version number:
PACKAGE = @PACKAGE@
PACKAGE_VERSION = @PACKAGE_VERSION@
# Documentation files to distribute with packages.
DOC_FILES = README.md \
COPYING

2
pkg/win32/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
staging/
*.zip

24
pkg/win32/GNUmakefile Normal file
View File

@ -0,0 +1,24 @@
include ../config.make
TOPLEVEL=../..
ZIP=$(PACKAGE)$(PACKAGE_VERSION)-win32.zip
all: $(ZIP)
$(ZIP): staging
zip -X -j -r $@ $<
staging:
mkdir $@
LC_ALL=C ./cp-with-libs --ldflags="$(LDFLAGS)" "$(TOPLEVEL)/Source/woof.exe"
$(STRIP) $@/*.exe $@/*.dll
unix2dos --add-bom --newfile "$(TOPLEVEL)/README.md" $@/README.txt
unix2dos --add-bom --newfile "$(TOPLEVEL)/COPYING" $@/COPYING.txt
clean:
$(RM) -r -- $(ZIP)
$(RM) -r -- staging-*

175
pkg/win32/cp-with-libs Executable file
View File

@ -0,0 +1,175 @@
#!/usr/bin/env python
#
# 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
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 = {
"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", "dsound.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('--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,
help='destination to copy binary')
def file_dependencies(filename, objdump):
"""Get the direct DLL dependencies of the given file.
The DLLs depended on by the given file are determined by invoking
the provided objdump binary. DLLs which are part of the standard
Win32 API are excluded from the result.
Args:
filename: Path to a file to query.
objdump: Name of objdump binary to invoke.
Returns:
List of filenames (not fully qualified paths).
"""
cmd = subprocess.Popen([objdump, '-p', filename],
stdout=subprocess.PIPE)
try:
result = []
for line in cmd.stdout:
m = DLL_NAME_RE.match(line.decode())
if m:
dll = m.group(1)
if dll.lower() not in WIN32_DLLS:
result.append(dll)
return result
finally:
cmd.wait()
assert cmd.returncode == 0, (
'%s invocation for %s exited with %d' % (
objdump, filename, cmd.returncode))
def find_dll(filename, dll_paths):
"""Search the given list of paths for a DLL with the given name."""
for path in dll_paths:
full_path = os.path.join(path, filename)
if os.path.exists(full_path):
return full_path
raise IOError('DLL file %s not found in path: %s' % (
filename, dll_paths))
def all_dependencies(filename, objdump, dll_paths):
"""Recursively find all dependencies of the given executable file.
Args:
filename: Executable file to examine.
objdump: Command to invoke to find dependencies.
dll_paths: List of directories to search for DLL files.
Returns:
Tuple containing:
Set containing paths to all DLL dependencies of the file.
Set of filenames of missing DLLs.
"""
result, missing = set(), set()
to_process = {filename}
while len(to_process) > 0:
filename = to_process.pop()
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()
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" % (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)