From 4756a77b47cc4a0c86315d8949468b347268c8eb Mon Sep 17 00:00:00 2001 From: Dominik Riebeling Date: Mon, 30 Aug 2010 17:51:53 +0000 Subject: Support resolving of DLLs when running on Windows. Resolve the DLLs required by the built executable and try to add the required DLL files that are not recognized as system libraries to the resulting zip / NSIS installer. This means that it's now possible to easily build both Theme Editor and Rockbox Utility as dynamically linked binary without the risk of missing required DLLs in the package. The major advantage of this is that it's not necessary anymore to have a statically built Qt installation for building releases. The drawback is that the created binaries will rely on additional DLL files, so it's no longer a single-run binary. Binary release of Rockbox Utility should still be statically build. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@27945 a1c6a512-1295-4272-9138-f99709370657 --- utils/common/deploy.py | 110 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 97 insertions(+), 13 deletions(-) diff --git a/utils/common/deploy.py b/utils/common/deploy.py index 662a104e34..9912bfc2ca 100755 --- a/utils/common/deploy.py +++ b/utils/common/deploy.py @@ -82,6 +82,25 @@ useupx = False # OS X: files to copy into the bundle. Workaround for out-of-tree builds. bundlecopy = { } +# DLL files to ignore when searching for required DLL files. +systemdlls = ['advapi32.dll', + 'comdlg32.dll', + 'gdi32.dll', + 'imm32.dll', + 'kernel32.dll', + 'msvcrt.dll', + 'msvcrt.dll', + 'netapi32.dll', + 'ole32.dll', + 'oleaut32.dll', + 'setupapi.dll', + 'shell32.dll', + 'user32.dll', + 'winmm.dll', + 'winspool.drv', + 'ws2_32.dll'] + + # == Functions == def usage(myself): print "Usage: %s [options]" % myself @@ -238,19 +257,22 @@ def upxfile(wd="."): return 0 -def runnsis(versionstring, nsis, srcfolder): +def runnsis(versionstring, nsis, script, srcfolder): # run script through nsis to create installer. print "Running NSIS ..." + # Assume the generated installer gets placed in the same folder the nsi # script lives in. This seems to be a valid assumption unless the nsi # script specifies a path. NSIS expects files relative to source folder so - # copy the relevant binaries. - for f in programfiles: - b = srcfolder + "/" + os.path.dirname(nsisscript) + "/" + os.path.dirname(f) - if not os.path.exists(b): - os.mkdir(b) - shutil.copy(srcfolder + "/" + f, b) - output = subprocess.Popen([nsis, srcfolder + "/" + nsisscript], stdout=subprocess.PIPE) + # copy progexe. Additional files are injected into the nsis script. + + # FIXME: instead of copying binaries around copy the NSI file and inject + # the correct paths. + b = srcfolder + "/" + os.path.dirname(script) + "/" + os.path.dirname(progexe) + if not os.path.exists(b): + os.mkdir(b) + shutil.copy(srcfolder + "/" + progexe, b) + output = subprocess.Popen([nsis, srcfolder + "/" + script], stdout=subprocess.PIPE) output.communicate() if not output.returncode == 0: print "NSIS failed!" @@ -258,16 +280,70 @@ def runnsis(versionstring, nsis, srcfolder): setupfile = program + "-" + versionstring + "-setup.exe" # find output filename in nsis script file nsissetup = "" - for line in open(srcfolder + "/" + nsisscript): + for line in open(srcfolder + "/" + script): if re.match(r'^[^;]*OutFile\s+', line) != None: nsissetup = re.sub(r'^[^;]*OutFile\s+"(.+)"', r'\1', line).rstrip() if nsissetup == "": print "Could not retrieve output file name!" return -1 - shutil.copy(srcfolder + "/" + os.path.dirname(nsisscript) + "/" + nsissetup, setupfile) + shutil.copy(srcfolder + "/" + os.path.dirname(script) + "/" + nsissetup, setupfile) return 0 +def nsisfileinject(nsis, outscript, filelist): + '''Inject files in filelist into NSIS script file after the File line + containing the main binary. This assumes that the main binary is present + in the NSIS script and that all additiona files (dlls etc) to get placed + into $INSTDIR.''' + output = open(outscript, "w") + for line in open(nsis, "r"): + output.write(line) + # inject files after the progexe binary. Match the basename only to avoid path mismatches. + if re.match(r'^\s*File\s*.*' + os.path.basename(progexe), line, re.IGNORECASE): + for f in filelist: + injection = " File /oname=$INSTDIR\\" + os.path.basename(f) + " " + os.path.normcase(f) + "\n" + output.write(injection) + output.write(" ; end of injected files\n") + output.close() + + +def finddlls(program, extrapaths = []): + '''Check program for required DLLs. Find all required DLLs except ignored + ones and return a list of DLL filenames (including path).''' + # ask objdump about dependencies. + output = subprocess.Popen(["objdump", "-x", program], stdout=subprocess.PIPE) + cmdout = output.communicate() + + # create list of used DLLs. Store as lower case as W32 is case-insensitive. + dlls = [] + for line in cmdout[0].split('\n'): + if re.match(r'\s*DLL Name', line) != None: + dll = re.sub(r'^\s*DLL Name:\s+([a-zA-Z_\-0-9\.]+).*$', r'\1', line) + dlls.append(dll.lower()) + + # find DLLs in extrapaths and PATH environment variable. + dllpaths = [] + for file in dlls: + if file in systemdlls: + print file + ": System DLL" + continue + dllpath = "" + for path in extrapaths: + if os.path.exists(path + "/" + file): + dllpath = re.sub(r"\\", r"/", path + "/" + file) + print file + ": found at " + dllpath + dllpaths.append(dllpath) + break + if dllpath == "": + try: + dllpath = re.sub(r"\\", r"/", which.which(file)) + print file + ": found at " + dllpath + dllpaths.append(dllpath) + except: + print file + ": NOT FOUND." + return dllpaths + + def zipball(versionstring, buildfolder): '''package created binary''' print "Creating binary zipball." @@ -278,7 +354,10 @@ def zipball(versionstring, buildfolder): os.mkdir(outfolder) # move program files to output folder for f in programfiles: - shutil.copy(buildfolder + "/" + f, outfolder) + if re.match(r'^(/|[a-zA-Z]:)', f) != None: + shutil.copy(f, outfolder) + else: + shutil.copy(buildfolder + "/" + f, outfolder) # create zipball from output folder zf = zipfile.ZipFile(archivename, mode='w', compression=zipfile.ZIP_DEFLATED) for root, dirs, files in os.walk(outfolder): @@ -503,15 +582,20 @@ def deploy(): if not upxfile(sourcefolder) == 0: tempclean(workfolder, cleanup and not keeptemp) sys.exit(1) + dllfiles = finddlls(sourcefolder + "/" + progexe, [os.path.dirname(qm)]) + if dllfiles.count > 0: + programfiles.extend(dllfiles) archive = zipball(ver, sourcefolder) + # only when running native right now. + if nsisscript != "" and makensis != "": + nsisfileinject(sourcefolder + "/" + nsisscript, sourcefolder + "/" + nsisscript + ".tmp", dllfiles) + runnsis(ver, makensis, nsisscript + ".tmp", sourcefolder) elif sys.platform == "darwin": archive = macdeploy(ver, sourcefolder) else: if os.uname()[4].endswith("64"): ver += "-64bit" archive = tarball(ver, sourcefolder) - if nsisscript != "" and makensis != "": - runnsis(ver, makensis, sourcefolder) # remove temporary files tempclean(workfolder, cleanup) -- cgit v1.2.3