File: mergeall-products/unzipped/build/build-app-exe/linux/build.py
#!python3 """ =========================================================================== Main file: make a Linux 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. To use, edit paths (see [feb22]) and install PyInstaller in a console with: pip3 install pyinstaller 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. Linux translation of a Python recoding of an original DOS version; now based on a PyInstaller version (with icon code fron 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. *NOTE*: Linux builds make only a 64-bit executable currently. ---- UPDATE [3.3]: as of March 2022, the shutil.copytree() for extras here requires symlinks=True to copy instead of follow, because symlinks are now correctly created (instead of stubbed) in the Windows code copy that is used as the build source on Linux too. This call's default follow for False didn't cause a build abort due to permissions as on Windows, but it triggered a different error when trying to follow links with a Unicode variant name that was apparently not normalized by the OS. To sidestep this, the symlinks=True copies symlinks verbatim, and thus defers to other for later follows (e.g., unzipping the resulting package with ziptools recreates links correctly). See also the Windows build.py. ---- Major recent changes: [feb22] mod paths for new executable build on Linux, for Mergeall 3.3. [mar22] add symlinks=True to shutil.copytree() for extras (see above). [oct22] the GUI's Help button now opens the online user guide, not the local copy--whose links may reference files absent in frozen packages. The local copy may now not be very useful sans more includes (tbd). =========================================================================== """ 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! # this is curently moot on Linux bitsize = 64 # or 32 mainhost = (bitsize == 64) #---------------------------------------------------------------------------- # make exe's icon if one doesn't already exist #---------------------------------------------------------------------------- pass # not on Linux #---------------------------------------------------------------------------- # 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); #---------------------------------------------------------------------------- # automated setup - run in this file's dir print('TEMP COPY') temp = '/home/me/Desktop/tempsrc' # cp can't include self! [feb22] if os.path.exists(temp): # same dir on 64/32 m/c shutil.rmtree(temp) os.mkdir(temp) # move all to temp shutil.copytree('../../../../mergeall', temp+'/mergeall', symlinks=True) #shutil.copy('setup.py', temp+r'\TextEditor') # not for pyinstaller os.chdir(temp+'/mergeall') # 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; # pyinstall ignores icons on linux, though mergeall adds to app bar; # # 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... # # Unlike the Mac app, there is no bundled Python executable with the # sum of all the frozen script's depedencies, so all mergeall spawners # must run its frozen executable, not source code. This includes both # the GUI and console lunchers, as well as the frozen rollback.py. #-------------------------------------------------------------------------- # pyinstaller may not be on PATH pyscripts = '' # with trailing / if used guifreeze = [ 'launch-mergeall-GUI.pyw', ] scriptfreeze = [ 'mergeall.py', # freeze these into exes in main folder 'diffall.py', # also ship source for their docs (only) 'cpall.py', # freezes require no Py install; source does 'rollback.py', 'fix-fat-dst-modtimes.py', 'nuke-cruft-files.py', 'launch-mergeall-Console.py', 'deltas.py', # new in 3.2 [feb22] 'fix-nonportable-filenames.py' # new in 3.2 [feb22] ] for target in guifreeze: print('\nBUILDING:', target) exitstat = os.system( '%spyinstaller' ' --onefile' ' --windowed' ' --icon icons/mergeall.ico' # icon not used on Linux ' --exclude-module mergeall_configs' ' %s' % (pyscripts, target)) if exitstat: print('ERROR: build failed:', exitstat) sys.exit(exitstat) # don't continue here for target in scriptfreeze: print('\nBUILDING:', target) exitstat = os.system( '%spyinstaller' ' --onefile' ' --console' ' --icon icons/mergeall.ico' ' --exclude-module mergeall_configs' ' %s' % (pyscripts, target)) if exitstat: print('ERROR: build failed:', exitstat) sys.exit(exitstat) # don't continue here #-------------------------------------------------------------------------- # use exe (not script) name [not here] #-------------------------------------------------------------------------- # shutil.move(r'dist\textEditor.exe', r'dist\PyEdit.exe') #-------------------------------------------------------------------------- # 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... #-------------------------------------------------------------------------- extras = [ # tbd: use a 'tools' folder? 'mergeall_configs.py', # ship these in install folder='.' 'README.txt', 'icons', 'docetc', 'UserGuide.html', 'test' # not scandir_defunct.py: now stubbed out ] extras = extras + scriptfreeze for name in extras: if os.path.isfile(name): shutil.copy2(name, 'dist') # [3.1] +file times (with data, mode bits) else: shutil.copytree(name, join('dist', name), symlinks=True) # [3.1] ok: files use copy2() # [3.3] symlinks=True # nested system cleanup shutil.rmtree(join('dist', 'test', 'ziptools', '__private__')) #---------------------------------------------------------------------------- # 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 = 'Mergeall' thezip = thedir + '.zip' code = '/home/name/Desktop/Code/mergeall/test/ziptools' code = '/home/me/Desktop/MY-STUFF/Code/mergeall/test/ziptools' # [feb22] code = '/home/me/Downloads/Mergeall-source/test/ziptools' # [feb22] 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+'/mergeall/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