File: pyedit-products/unzipped/build/build-app-exe/macosx/build.py

#!/usr/bin/env python3
"""
=============================================================================
Make a Mac OS X App bundle (folder), using py2app.
Python recoding of an original shell-script version.
Main file: run me in this folder to build the app.

An app allows text files to be associated to auto-open in PyEdit on clicks;
makes the program immune from Python changes; requires no Python installs;
and better supports file drag-and-drops, program icons, and more (but also
does a woefully-good job of hiding user-config source files and auto-saves!).

Didn't get cx_freeze or pyinstaller to work, but stopped short...
-pyinstaller had issues with Active Tcl/Tk, cx_freeze had xcode issues (?)
-pyinstaller may be retried if use homebrew python/tk for menu/dock issues; 

For the build (only), moves PP4E pkg root (less TextEditor) to become nested
in TextEditor, not above it.  Both are moved to a temp build folder for
simplicity here (and to avoid source-code tragedies).

Mac app also required... tailoring apple/window menus, __main__ workaround
for opendoc events, sys.path=., a mac-format .icns icon (the latter became 
an iconify.py extension), and more; see texEditor.py's "[3.0]" changes.

Besides the executable, the app needs:
-textConfig.py: must be source code, and user-visible and editable;
-icon for the exe/app (window borders/app bar for Windows/Linux);
-UserGuide.html and docetc items it uses at runtime;
-docetc image for the Help dialog
-subprocProxy.py for the Run Code's Capture-mode launcher
 
py2app sets the cwd to the bundle's Contents/Resource folder in all cases:
add '.' to sys.path in textEditor.py (now via importing fixfrozenpaths.py), 
and use files copied to .=Resources, to which __file__ will refer.  Note: 
subprocproxy.py need not be frozen here, because py2app bundles include 
a Python executable for running source, and all modules the proxy needs.

Windows were not opening on first text-file click (in dock): needed
__main__ Apple opendoc event workaround and disable py2app's broken 
argv emulation in setup.py (as of the version used, at least).

For the Run Code subprocprocy, must force all Python stdlibs to be included 
in the app's bundled Python, by uing a generated setup.py which lists them
all.  See include-full-stdlib.py for details.  This scheme is imitated by 
Windows/Linux builds via PyInstaller hook files.

NOTE: it's assumed that the frozen app, plus the Python and Tk included 
in the app bundle are univeral binaries, supporting both 64- and 32-bit
machines.  This appears to be so, though the app's portability to older 
OS X versions remains to be shown (10.11 El Capitan is build host).

NOTE: tools scripts here are shipped in the Resources/tools folder in 
source-code forms, and hence require a separately-installed Python to 
run (or the app budle's Python, but this is obcure).  These are too few 
and minor to warrant extra separate feezes in PyEdit.

NOTE: py2app's --use-pythonpath flag doesn't incorporate PYTHONPATH
settings if the app is run from Finder (by a click).  This is an Apple
quirk, not a py2app oer PyEdit bug.  Run from a command line if needed.

NOTE: the version of Pyhon you use to run this script is crucial--it's
the version bundled with the app.
=============================================================================
"""

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

# 'python3' fails in both IDLE and PyEdit RunCode (env's PATH not inherited?)
python = sys.executable

#----------------------------------------------------------------------------
# force all stdlib mods to be baked-in to the app's python, for Run Code;
# else user code has minimal library access (though this is still not
# sufficient for local package installs);  see include-full-stdlib.py;
#----------------------------------------------------------------------------

exitstat = os.system(python + ' include-full-stdlib.py')   # makes setup.py
assert exitstat == 0

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

iconship = join('..', '..', '..', 'icons')
iconmake = join('..', '..', 'build-icons')
iconname = 'pyedit'
iconfile = iconname + '.icns'

# step into icon build dir and make
if force or not os.path.exists(iconship + sep + iconfile):
    os.chdir(iconmake)
   #os.system('iconutil -c icns %s.iconset' % iconname)                    # Apple util
   #os.system('./resize-on-mac.sh Pyedit1024new images-pyedit')            # just once
    os.system('%s iconify.py -mac images-pyedit %s' % (python, iconname))  # iconify 2.0
    os.chdir(startdir)
    shutil.move(join(iconmake, iconfile), join(iconship, iconfile))     # mv to ship

#----------------------------------------------------------------------------
# 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 = '/Users/blue/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')            # add setup script to root
os.chdir(temp+'/TextEditor')                           # goto temp build dir
 
#----------------------------------------------------------------------------
# cp so use PyEdit name for both the app-bundle folder and auto-menu title;
# this name must also be used in this project's setup.py file here;
# we can't rename these after the build, and "textEditor.py" is ingrained;
#----------------------------------------------------------------------------

shutil.copy('textEditor.py', 'PyEdit.py')

#----------------------------------------------------------------------------
# build app-bundle folder in ./dist, using ./setup.py, copying N extras
# to the main Resources folder along with the generated program itself;
# note: subprocproxy.py could be built via --extra-scripts, but this seems
# no better than running it as source via sys.executable=python in bundle
# (all modules imported by it will be available in the frozen app bundle);
#
# of course, this assumes that PyEdit.app/Contents/MacOS/python suffices:
# must either '--includes' *all* of std lib, or ask user to install Python;
# see include-full-stdlib.py here for one (arguably hackish) way to do it,
# and ../../textConfig.py for interpreter and path settings that override
# defaults: including stdlib still does not pickup locally-installed libs;
#----------------------------------------------------------------------------

extras = ['textConfig.py', 
          'README.txt', 
          'icons',
          'tools',
          'docetc',
          'UserGuide.html', 
          'subprocproxy.py']     # proxy is run as source here, not frozen

exitstat = os.system(
    '%s setup.py py2app' 
    '   --excludes textConfig' 
    '   --resources %s'
    '   --iconfile icons/pyedit.icns' % (python, ','.join(extras)))

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

#----------------------------------------------------------------------------
# cleanup: move and zip the app folder for easy xfer and backup (it has
# _very_ many files: nearly 3K files+folders for the current PyEdit App);
# [update - teardown actions are now automated (but still no data to copy)]
# don't copy extras to Contents/Resources folder here: automatic via the 
# py2app args above;  fixfrozenpaths.py arranges to see these as needed;
# DON'T -skipcruft in the zip command: py2app makes a Resources/site.pyc!
#----------------------------------------------------------------------------

# zip command: use portable ziptools (vs: 'zip -r %s %s' % (thezip, thedir))
thedir = 'PyEdit.app'
thezip = thedir + '.zip'
code   = '/MY-STUFF/Code/ziptools/link'
zipit  = '%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+'/TextEditor/dist', '.')
shutil.rmtree(temp)                            # rm temp build tree 

# zip the nested app 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.chdir('dist')   
os.system(zipit)                               # run zip in dist: has app dir
os.chdir('..')   
shutil.rmtree('dist')                          # rm dist: _very_ many files

print('Done: see ./%s' % thezip)

# +unzip app and copy it to /Applications to make it official



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