File: pyedit-products/unzipped/build/build-app-exe/windows/unused-defunct/build-pre-32-bit.py

#!python3
"""
===========================================================================
Main file: make a Windows single-file executable with PyInstaller,
and manually copy some data items to its folder.

This allows text files to be associated to open in PyEdit on clicks,
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...

Moves PP4E pkg root (less TextEditor) to be nested here, not above.
Windows exe didn't require any changes, other than sys.path=argv[0].

Need textConfig 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.

Python recoding of an original DOS batch-file version; now based on 
a Linux 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.
===========================================================================
"""

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

#----------------------------------------------------------------------------
# force all stdlib mods to be baked-in to proxy exe's python, for Run Code
#----------------------------------------------------------------------------

exitstat = os.system('%s include-full-stdlib.py' % python)   # make hook file
assert exitstat == 0

#----------------------------------------------------------------------------
# make exe's icon if one doesn't already exist
#----------------------------------------------------------------------------

iconship  = join('..', '..', '..', 'icons')
iconmake  = join('..', '..', 'build-icons')
iconnames = ['pyedit',
             'pyedit-window-main',
             'pyedit-window-popup',
             'pyedit-subprocproxy']

# 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 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 = r'C:\Users\mark\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(r'..\..\..\..\..\..\PP4E', temp+r'\PP4E', symlinks=True) 
shutil.move(temp+r'\PP4E\Gui\TextEditor', temp)        # move nested up to temp root
shutil.move(temp+r'\PP4E', temp+r'\TextEditor')        # move PP4E down to nested
#shutil.copy('setup.py', temp+r'\TextEditor')          # not for pyinstaller
os.chdir(temp+r'\TextEditor')                          # goto temp build dir

#--------------------------------------------------------------------------
# build one-file exe in .\dist (now actually _two_ exes, per below)
#
# ABOUT THE EXTRA EXECUTABLE:
#
# A PyInstaller one-file build is essentially a self-extracting bundle,
# which unpacks itself into a temp dir, named by sys._MEIPASS; the temp
# dir is deleted on (normal) program exit.  Hence the start-up delay,
# and need to copy long-lived data items to the exe's permanent folder.
#
# Unlike both source code and py2app Mac bundles, with frozen exes there
# is no Python executable, just a Python binary lib, and sys.executable is
# not Python (it's the exe itself).  Thus, to spawn the proxy Python script
# from a frozen Windows/Linux executable, the script must either be frozen
# too, or be somehow started by a new instance of the frozen PyEdit (e.g.,
# with a special cmdline arg and import or exec); chose former scheme here.
#
# This also makes it impossible to run arbitrary Python script processes
# from a frozen executable, unless they are run by exec() as in the Capture
# Run-Code mode's scheme - there may be no installed Python, the scripts
# can't be expected to be frozen too, and requiring an extra Python install
# would negate most of the frozen exe's benefits.
#
# This was addressed by forcing as many standard libs into the freeze as
# possible, and allowing users to configure a locally-installed Python and
# module import-path extensions in their textConfig.py file.  In Capture
# mode, the proxy is launched as source if a Python is configured, or as
# a frozen exe otherwise, and both versions are shipped in the zipfile.
#
# The frozen proxy also required custom code exception-text parsing (there
# is one less output line), and special arguments (shell=True) on Windows
# to suppress console (Command Prompt) popup in some frozen exe contexts.
# LESSON: frozen exes don't play well with subprocess or multiprocessing,
# seem painfully-reminiscent of C development and makefiles, and require
# code changes and extra tasks in nontrivial use-cases.  OTOH, they also
# are a valiant effort to promote scripts to first-class program status. 
#
# For the proxy only, and using PyInstaller only, force the inclusion of
# the full stdlib (at freeze time) by generating a "hook" file having all
# stdlib module names, sourced on import of "os" in subprocproxy.py at
# exe build time.  See the hook maker, include-full-stdlib.py, for more.
#
# Caveats: --one-file builds incur a speed hit at startup to unpack to
# a temp dir, and may leave substantially-large garbage in the temp dir.
# An alternative --one-dir build was explored (see unused-defunct/) but
# abandoned. because no good way was found to move the many included
# dependencies to a subfolder (DLLs seemed problematic).  Instead, PyEdit
# now attempts to reap temp dir trash when run in frozen mode on Windows.
#
# Issues summary:
# - py2exe and cx_freeze had issues early on
# - multiprocessing requires a patch, subprocessing may require args
# - must adjust cwd and import paths for runtime components use
# - must exclude and ship user-editable source-code config module
# - must include extras like help files and window icons
# - no standalone py is shipped, and Run Code cannot assume one is present
# - frozen proxy does not include full stlib: must force for Run Code 
# - --one-file unpacks to temp folder, leaving garbage to accumulate: prune
# - --one-file takes time to unpack itself to temp folder on startup: punt
# - only subprocproxy needs full stdlib (could share in --one-dir builds)
# - pyedit is windowed and subprocproxy is not: can't list in same build?
# - frozen exe streams don't respect unbuffered and IO encoding env vars
#--------------------------------------------------------------------------

# may not be on PATH
pyscripts = r'C:\Users\mark\AppData\Local\Programs\Python\Python35\Scripts'

exitstat = os.system(
    '%s\\pyinstaller'
    '   --onefile'
    '   --windowed'
    '   --icon icons\\pyedit.ico'
    '   --exclude-module textConfig'
    '   textEditor.py' % pyscripts)         # or list subprocproxy.py here too

if exitstat:
    print('ERROR: build failed:', exitstat)
    sys.exit(exitstat)   # don't continue here

exitstat = os.system(
    '%s\\pyinstaller'
    '   --onefile'
    '   --noconsole'
    '   --icon icons\\pyedit-subprocproxy.ico'
    '   --exclude-module textConfig'
    '   --additional-hooks-dir build-app-exe\windows'
    '   subprocproxy.py' % pyscripts)

if exitstat:
    print('ERROR: build failed:', exitstat)
    sys.exit(exitstat)   # don't continue here

#--------------------------------------------------------------------------
# use exe (not script) name
#--------------------------------------------------------------------------

shutil.move(r'dist\textEditor.exe', r'dist\PyEdit.exe')

#--------------------------------------------------------------------------
# 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'
thezip = thedir + '.zip'
code   = r'C:\MY-STUFF\Code\mergeall\test\ziptools'
zipit  = r'%s %s\zip-create.py %s %s' % (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+r'\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 .\PyEdit.zip')
if sys.stdin.isatty():
   input('Press enter to close')                   # stay up if clicked (Win)

# +unzip exe folder, and move to C:\Programs (?) to use



[Home page] Books Code Blog Python Author Train Find ©M.Lutz