File: pygadgets-products/unzipped/build/build-app-exe/macosx/build.py
#!/usr/bin/env python3
"""
=============================================================================
Make a Mac OS X App bundle (folder), using py2app.
Based on the more-complicated build.py of PyEdit.
Main file: run me in this folder to build the app.
An app allows text files to be associated (though irrelevant here)
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!).
Mac app also required... a mac-format .icns icon (the latter became an
iconify.py extension), fixfrozenpaths.py's context configurations,
menus and iconify logic, etc.
Besides the executable, the app needs:
-pygadgets_configs.py: must be source code, and user-visible and editable;
-icon for the exe/app (window borders/app bar for Windows/Linux);
-README.txt documentation file
py2app sets the cwd to the bundle's Contents/Resource folder in all cases:
add '.' to sys.path, and use files copied to .=Resources, to which __file__
will refer. Associations are irrelevant and unused here.
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).
=============================================================================
"""
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 = '/usr/local/bin/python3' # sys.executable (else proxy in PyEdit?)
def announce(message):
print(message + '-'*40)
#----------------------------------------------------------------------------
# make app's icon if one doesn't already exist
#----------------------------------------------------------------------------
announce('ICONS')
iconship = join('..', '..', '..', 'icons')
iconmake = join('..', '..', 'build-icons')
iconname = 'pygadgets'
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('sips -s format gif PyGadgets1024.png --out PyGadgets.gif') # for Linux
os.system('./resize-on-mac.sh PyGadgets1024 images-pygadgets') # just once
os.system('%s iconify.py -mac images-pygadgets %s' % (python, iconname)) # iconify 2.0
os.system('%s iconify.py -win images-pygadgets %s' % (python, iconname)) # iconify 2.0
os.chdir(startdir)
shutil.move(join(iconmake, iconfile), join(iconship, iconfile)) # mv to ship
shutil.move(join(iconmake, iconname + '.ico'), join(iconship, iconname +'.ico'))
shutil.move(join(iconmake, iconname + '.gif'), join(iconship, iconname + '.gif'))
#----------------------------------------------------------------------------
# first, copy source tree to temp folder to avoid accidental code loss;
# [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;]
#----------------------------------------------------------------------------
announce('TEMP COPY')
# 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, add setup.py
shutil.copytree('../../../../pygadgets', temp+'/pygadgets', symlinks=True)
shutil.copy('setup.py', temp+'/pygadgets') # add setup script to root
os.chdir(temp+'/pygadgets') # goto temp build dir
#----------------------------------------------------------------------------
# cp so use pygadgets 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 the launcher is ingrained;
#----------------------------------------------------------------------------
# shutil.copy('textEditor.py', 'PyEdit.py')
# rename after launcher app built ahead
#----------------------------------------------------------------------------
# build app-bundle folder in ./dist, using ./setup.py, copying N extras
# to the main Resources folder along with the generated program itself;
# this uses --resources to copy non-code extras, and -extras-scripts to
# also freeze releated scripts shipped with pygadgets, so that they can
# be run from the app's Content/MacOS folder without a ".py" extension
# and without requiring a separate Python install. Source is incuded
# for its docs only -- won't run as source.
#----------------------------------------------------------------------------
announce('BUILDING')
extras = [ # tbd: use a 'tools' folder?
'PyGadgets_configs.py', # ship these in Content/Resources='.'
'README.txt',
'icons',
'screenshots',
'_PyCalc',
'_PyClock', # or freeze and run with 'open x'?
'_PyPhoto', # downside: their source trees have extras
'_PyToe',
'Gui',
'pickcolor.py',
'__sloc__.py',
'_etc'
]
alsofreeze = [
'_PyCalc/Calculator/calculator.py', # include external dependencies for these too
'_PyClock/Clock/clock.py', # these are run as source in their extras dirs,
'_PyPhoto/PIL/pyphoto.py', # using the python incuded in the bundle
'_PyToe/TicTacToe/tictactoe.py', # listing here include their libs in bindle
'_PyPhoto/PIL/delete-pyphoto2.0-thumbs-folders.py'
]
exitstat = os.system(
'%s setup.py py2app'
' --includes math,random,statistics' # imported via exec("from *")
' --excludes PyGadgets_configs' # user-facing, in Resources
' --iconfile icons/pygadgets.icns'
' --extra-scripts %s'
' --resources %s'
' --packages PIL' # for PyPhoto and PyClock
% (python, ','.join(alsofreeze), ','.join(extras + alsofreeze))
)
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!
#----------------------------------------------------------------------------
announce('CLEANUP')
# zip command: use portable ziptools (vs: 'zip -r %s %s' % (thezip, thedir))
thedir = 'PyGadgets.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+'/pygadgets/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')
# shutil.move('pygadgets_bar.app', thedir)
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