File: pygadgets-products/unzipped/build/build-app-exe/windows/build.py
#!python3 """ =========================================================================== Main file: make a Windows single-file executable with PyInstaller, and manually copy some data items to its folder. This sets program icons automatically, and avoids Python install and morph. There is no setup.py file for PyIstaller. Didn't get cx_freeze or py2exe to work, but stopped short... Need config files to be source code, and user-visible/editable. Need icon for both the exe, and window borders in Tk runtime. PyInstaller sets cwd to anything; uses dir of sys.argv[0], and copies files to folder holding exe for .=exe's dir. Need exes for included scripts: there may be no Python install. Python recoding of an original DOS batch-file version; now based on a Linux PyInstaller version (with icon code from a Mac OS X version): Python portability makes this nearly the same everywhere. Could also neutralize slash diffs with join() and sep, but other parts vary too. UPDATE: on Windows, make both 64- and 32-bit executables. The 32-bit build machine varies from the 64-bit main machine; specialize paths, and quote to allow for embedded spaces in Python dirs on 32-bit host. The 32-bit Windows 8 box PyInstaller required runnning as admin, and upgrading pip and setuptools to fix a Windows path-quoting bug... *NOTE*: change "bitsize" in this script from 64 to 32 as needed. =========================================================================== """ import os, sys, shutil join, sep = os.path.join, os.path.sep force = len(sys.argv) > 1 # remake icon if any arg? startdir = os.getcwd() # this build script's dir python = sys.executable # may have embedded spaces! bitsize = 64 # or 32 mainhost = (bitsize == 64) def FWP(path): # allow long paths on Windows: pyedit auto-save files # see mergeall and ziptools for tons of details return '\\\\?\\' + os.path.abspath(path) def announce(message): print(message + '-'*40) #---------------------------------------------------------------------------- # make exe's icon if one doesn't already exist #---------------------------------------------------------------------------- announce('ICONS') # this is a no-op: all pygadgets icons are built on Mac OS first iconship = join('..', '..', '..', 'icons') iconmake = join('..', '..', 'build-icons') iconnames = ['pygadgets'] # step into icon build dir and make for iconname in iconnames: iconfile = iconname + '.ico' if force or not os.path.exists(iconship + sep + iconfile): os.chdir(iconmake) # requires a py with Pillow installed, pre sized/arranged images # os.system('%s iconify.py -win images-%s %s' % (python, iconname, iconname)) os.system('py -3.3 iconify.py -win images-%s %s' % (iconname, iconname)) os.chdir(startdir) shutil.move(join(iconmake, iconfile), join(iconship, iconfile)) #---------------------------------------------------------------------------- # first: copy source tree to temp folder to avoid accidental code loss; # setup and teardown steps are now automated (run this script in its dir); #---------------------------------------------------------------------------- announce('TEMP COPY') # automated setup - run in this file's dir temp = r'C:\Users\mark\Desktop\tempsrc' # cp can't include self! if os.path.exists(temp): # same dir on 64/32 m/c shutil.rmtree(FWP(temp)) os.mkdir(temp) # move all to temp shutil.copytree(FWP(r'..\..\..\..\pygadgets'), FWP(temp+r'\pygadgets'), symlinks=True) #shutil.copy('setup.py', temp+r'\pygadgets') # not for pyinstaller os.chdir(temp+r'\pygadgets') # goto temp build dir #-------------------------------------------------------------------------- # build one-file main exe in .\dist, plus extras for standalone scripts # can't assume a local Python install to run the included scripts; # # UPDATE: PyInstaller's "multipackage" feature (1 bundle, N scripts) # is currently broken, so make (large) standalone exes for all; blah. # This seems a deal-breaker, but py2exe is languishing too, and... #-------------------------------------------------------------------------- # pyinstaller may not be on PATH if bitsize == 64: pyscripts = r'C:\Users\mark\AppData\Local\Programs\Python\Python35\Scripts' else: pyscripts = r'c:\Program Files\Python 3.5\Scripts' guifreeze = [ 'PyGadgets.py', '_PyCalc/Calculator/calculator.py', # step into subfolders to build '_PyClock/Clock/clock.py', # and copy exe up to main dist '_PyPhoto/PIL/pyphoto.py', '_PyToe/TicTacToe/tictactoe.py' ] scriptfreeze = [ # freeze these into exes in main folder 'pickcolor.py', # also ship source for their docs (only) '_PyPhoto/PIL/delete-pyphoto2.0-thumbs-folders.py' ] # allow for embedded quotes (not just '%s\\pyinstaller' or '"%s\\pyinstaller"') for target in guifreeze: announce('\nBUILDING: ' + target) dirname, filename = os.path.split(target) if dirname: rootdir = os.getcwd() os.chdir(dirname) target = filename icon = '..\\..\\icons\\pygadgets.ico' else: icon = 'icons\\pygadgets.ico' exitstat = os.system( 'cmd /C ""%s\\pyinstaller"' ' --onefile' ' --windowed' # use --console to debug ' --icon %s' ' --exclude-module PyGadgets_configs' ' --hidden-import math' # 3 math mods for PyCalc only ' --hidden-import random' # ...imported by mod name str ' --hidden-import statistics' ' --hidden-import tictactoe_lists' # imported by class name str ' --hidden-import PIL' # imported in an if stmt ' %s"' % (pyscripts, icon, target)) # ...and install PIL for 3.5! if dirname: os.chdir(rootdir) shutil.copyfile(join(dirname, 'dist', filename.replace('.py', '.exe')), join('dist', filename.replace('.py', '.exe')) ) if exitstat: print('ERROR: build failed:', exitstat) sys.exit(exitstat) # don't continue here for target in scriptfreeze: announce('\nBUILDING: ' + target) exitstat = os.system( 'cmd /S /C ""%s\\pyinstaller"' ' --onefile' ' --console' ' --icon icons\\pygadgets.ico' ' --exclude-module PyGadgets_configs' ' %s"' % (pyscripts, target)) if exitstat: print('ERROR: build failed:', exitstat) sys.exit(exitstat) # don't continue here #-------------------------------------------------------------------------- # copy extras to exe's folder: source code arranges to see these; # not --add-data: it gets unzipped in a Temp dir the user won't see... #-------------------------------------------------------------------------- announce('EXTRAS') extras = [ # tbd: use a 'tools' folder? 'PyGadgets_configs.py', # ship these in install folder='.' 'README.txt', 'icons', 'screenshots', join('_PyPhoto', 'PIL', 'images-large'), # spawned exes' nested data items join('_PyPhoto', 'PIL', 'images-mixed'), join('_PyPhoto', 'PIL', 'images-errors'), join('_PyPhoto', 'PIL', 'images-saved'), join('_PyPhoto', 'PIL', 'noimage.png'), join('_PyClock', 'Clock', 'images'), join('Gui', 'gifs'), '__sloc__.py', '_etc' ] extras = extras + scriptfreeze for name in extras: if os.path.isfile(name): shutil.copy(name, 'dist') else: shutil.copytree(name, join('dist', name)) #---------------------------------------------------------------------------- # cleanup: move and zip the exe folder for easy xfer (just a few files here); # [update - teardown actions are now automated (but still no data to copy)] #---------------------------------------------------------------------------- announce('CLEANUP') # zip command: use portable ziptools thedir = 'PyGadgets' + ('-64bit' if bitsize == 64 else '-32bit') thezip = thedir + '.zip' if bitsize == 64: code = r'G:\MY-STUFF\Code\mergeall\test\ziptools' else: code = r'D:\MY-STUFF\Code\mergeall\test\ziptools' # allow spaces zipit = r'"%s" %s\zip-create.py %s %s -skipcruft' % (python, code, thezip, thedir) # move dist product folder here os.chdir(startdir) if os.path.exists('dist'): shutil.rmtree('dist') # nuke bogus retained temp? shutil.move(FWP(temp+r'\pygadgets\dist'), '.') shutil.rmtree(temp) # rm temp build tree # zip the exe=dist folder - unzip to test and use here or elsewhere if os.path.exists(thezip): shutil.move(thezip, 'prev-'+thezip) # save previous version? if os.path.exists(thedir): shutil.rmtree(FWP(thedir)) # nuke unzipped version os.rename('dist', thedir) # rename for unzip name os.system(zipit) shutil.rmtree(FWP(thedir)) # no need to save raw dist print('Done: see .\%s' % thezip) if sys.stdin.isatty(): input('Press enter to close') # stay up if clicked (Win) # +unzip exe folder, and move to Desktop (?) to make it permanent