#!/usr/bin/env python3 """ =========================================================================== Main file: make a Linux single-file executable with PyInstaller, and manually copy some data items to its folder. There is no setup.py file for PyIstaller. Python recoding of an original shell-script version. Associating files with executables on Linux can be tedious (e.g., editing /usr/share mime-list and .desktop files). See the web for resources: https://duckduckgo.com/?q=associate+program+with+file+ubuntu See also the ../windows/build.py PyInstaller build for Windows; because it's so similar to this, most comments have been stripped here for space. ODDMENT: to get the PyInstaller build to work on Linux, had to make a dummy, empty /usr/include/Python3.5m/pyconfig.h file. It may have also worked to install "python_dev" instead (unknown). The pyconfig.h file is apparently required for some stdlib module, most likely distutils, and perhaps for its PyInstalller hook file. The consequences of stubbing it out remain unknown (but the frozen PyEdit runs fine). =========================================================================== """ import os, sys, shutil join, sep = os.path.join, os.path.sep force = len(sys.argv) > 1 # remake icon if any arg (no-op here) startdir = os.getcwd() # this build script's dir python = sys.executable # this is curently moot on Linux # bitsize = 64 # or 32 # mainhost = (bitsize == 64) mainhost = True #---------------------------------------------------------------------------- # force all stdlib mods to be baked-in to proxy exe's python, for Run Code #---------------------------------------------------------------------------- if mainhost: # make hook file exitstat = os.system('"%s" include-full-stdlib.py' % python) assert exitstat == 0 #---------------------------------------------------------------------------- # make exe's icon if doesn't exist (NOT for Linux - uses image for app bar) #---------------------------------------------------------------------------- pass #---------------------------------------------------------------------------- # first: copy PP4E, move its TextEditor to root, and nest PP4E inside it; # [update - setup and teardown steps are now automated (run this script in # its dir), and Info.plist edits are now automatic by setup.py options;] #---------------------------------------------------------------------------- # automated setup - run in this file's dir temp = '/home/name/Desktop/tempsrc' # cp can't include self! if os.path.exists(temp): shutil.rmtree(temp) os.mkdir(temp) # move all to temp, nest PP4E in TextEditor shutil.copytree('../../../../../../PP4E', temp+'/PP4E', symlinks=True) shutil.move(temp+'/PP4E/Gui/TextEditor', temp) # move nested up to temp root shutil.move(temp+'/PP4E', temp+'/TextEditor') # move PP4E down to nested #shutil.copy('setup.py', temp+'/TextEditor') # not for pyinstaller os.chdir(temp+'/TextEditor') # goto temp build dir #-------------------------------------------------------------------------- # build one-file exe in ./dist (now actually _two_ exes, per below) # ABOUT THE EXTRA EXECUTABLE and Issues Summary: see ../windows/build.py # NOTE: the icon option is ignored on Linux (but PyEdit sets in app bar). #-------------------------------------------------------------------------- # may not be on PATH pyscripts = '' # with trailing / if used exitstat = os.system( '%spyinstaller' ' --onefile' ' --windowed' ' --icon icons/pyedit-window-main.gif' # icon ignored in Linux ' --exclude-module textConfig' ' textEditor.py' % pyscripts) # can't list subprocproxy.py too if exitstat: print('ERROR: build failed:', exitstat) sys.exit(exitstat) # don't continue here exitstat = os.system( '%spyinstaller' ' --onefile' ' --noconsole' ' --icon icons/pyedit-subprocproxy.gif' ' --exclude-module textConfig' ' --additional-hooks-dir build/build-app-exe/linux' ' subprocproxy.py' % pyscripts) if exitstat: print('ERROR: build failed:', exitstat) sys.exit(exitstat) # don't continue here #-------------------------------------------------------------------------- # use exe (not script) name #-------------------------------------------------------------------------- shutil.move('dist/textEditor', 'dist/PyEdit') #-------------------------------------------------------------------------- # copy extras to exe's folder: textEditor.py arranges to see these; # not --add-data: it gets unzipped in a Temp dir the user won't see... #-------------------------------------------------------------------------- extras = ['textConfig.py', 'README.txt', 'icons', 'tools', 'docetc', 'UserGuide.html', 'subprocproxy.py'] # proxy: ship frozen AND source versions 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)] #---------------------------------------------------------------------------- # zip command: use portable ziptools thedir = 'PyEdit' # + ('-64bit' if bitsize == 64 else '-32bit') thezip = thedir + '.zip' code = '/media/name/End_of_the_drive/MARKS-STUFF/Code/mergeall/test/ziptools' zipit = '%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(temp+'/TextEditor/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(thedir) # nuke unzipped version os.rename('dist', thedir) # rename for unzip name os.system(zipit) shutil.rmtree(thedir) # no need to save raw dist print('Done: see ./%s' % thezip) # +unzip exe folder, and move to $HOME or desktop (?) to make it permanent