File: frigcal-products/unzipped/build/build-app-exe/build3.0/build.py
#/usr/bin/env python3 """ =========================================================================== [Adapted from PPUS's pc-build/build.py -- but here for Tkinter, not Kivy] For Frigcal: there is no Kivy (only tkinter), PIL is unused (now), there are supplemental console and GUI executables. PPUS Kivy notes here were retained because that may be a later context for Frigcal. [Adapted from Mergeall's Windows-exe build.py] Use PyInstaller to make a Windows|Linux|macOS single-file executable or app from the app's main.py; copy extra data items to its folder; and zip the result for distribution. Unlike Mergeall's version, this script is coded to work portably on Windows, Linux, and macOS. To use - on all three PC platforms: - Ensure that app runs as source code; if needed: pip3 install kivy - Edit paths in code here if needed (now mostly moot) - Install PyInstaller in a console with: pip3 install pyinstaller - Download and unzip ziptools in ~/Downloads from learning-python.com - Run this script in its own folder (pc-build/) with: python3 build.py Build tours (verify all copies along the way): On macOS: run pc-build/build.py in the app folder itself to make zip. The dev folder is ~/Desktop.DEV-BD/apps/PC-Phone-USB-Sync (not $C). UPDATE: now builds universal2 on macbook with py 3.10, not dev mbp; builds are run in ~/Desktop: copy app folder there, rename to P-P-U-S, run its pc-build/build.py, copy zip to SSD. On Windows: copy app folder from SSD to Desktop\temp, rename it as PC-Phone-USB-Sync, run its pc-build/build.py, copy zip back to SSD. UPDATE: build as user 'lutz', not 'me' ('me' is for installs/shots). On Linux: copy app folder from SSD to ~/ppus (optional?), rename it as PC-Phone-USB-Sync, run its pc-build/build.py, copy zip back to SSD. Made no-op virtual env to trim space (once), purged/reinstalled kivy; See ./HOW_TO_BULD_UBUNTU22_OCT23.txt for setup saga; latest build cmds: ~$ export PYTHONPATH=/home/me/ppus/lib/python3.8/site-packages ~/ppus/PC-Phone-USB-Sync/pc-build$ ~/ppus/bin/python3.8 build.py Copy 3 collected zips from SSD to macOS pc-builds/, copy all 3 zips to _website/+downloads, run _website/_publish.sh to upload site with exes. On Windows only, also edit and run _winver-maker.py once (see its docs), and copy the .spec file made on first run from temp-build-ppus/ to file pc-build/_Windows--PC-Phone USB Sync.spec, and edit to add 3 required lines for Kivy dependencies. The .spec is auto used on all later runs instead of command-line args. Linux doesn't use the .spec; kivy-deps doesn't exist for Linux. macOS sprouted a .spec later (see ahead). Unlike py2app, there is no setup.py file for PyInstaller. There can be a .spec files made on first build (or pyi-makespec), and passed to pyinstaller instead of main.py; this is required here for Windows only (not Linux or macOS). Mergeall instead uses py2app on macOS, but this can expose its source code, and requires different and extra handling. Update: macOS now uses a .spec create on first run too, just to set its app version number via spec-file-only BUNDLE arg; copy from tempfolder to pc-build/_macOS--PC-Phone USB Sync.spec and edit there. Required because modding the app version number by post-build manifest edits triggers an harsh warning about damage/tampering with no option to open anyhow (even though the app works after killing its quarantine attrs), and PyInstaller has no other support for version# on macOS (see weird Windows-only _winver). Why freeze? Frozen executables/apps: - Set program icons automatically - Match the platform's paradigms well - Require no Python or any other installs - Are immune to more in Python and other libs and tools - Don't require users to know how to launch scripts OTOH, they can still break on radical platform/hardware changes (see Linux's libc and Apple's M chips), and don't benefit from tool upgrades. APP DATA ACCESS =============== PyInstaller unzips --onefile exes and their added data to a temporary folder, and builds an app folder on macOS. These convolute the executable's access to data items at runtime. See main.py's "if hasattr(sys, 'frozen'" for policies on exe data access implemented here; all data docs were moved there. BUILD NOTES =========== Need icon for both the froze app/exe, and window borders at runtime. PyInstaller and Kivy automate some of this. The former now builds a .ico or .icns from a .png (like iconify.py!), thuogh this is not used on some platforms (see ahead_). PyInstaller sets cwd to anything; main.py cd's to dir(sys.executable), and copies data files to folder holding exe for . = exe's dir. There are no included secondary frozen scripts in this build, and hence no relative-path launch issue for cd'ing to exe in the .py. On Windows and Linux, this makes 64-bit executables only (32-bit has largely faded). Currently built on Windows 11 and Ubuntu only. On macOS, this may make an Intel-only app, which requires rosetta2 to be installed on used for runs (right-click or Get Info on the app). A universal binary would help, but this depends on PyInstaller, Kivy, all libs, and the build machine, and building on a later macOS version means earlier versions are not supported. Currently built on Catalina. macOS bluntly dropped 32-bit support altogether a few years ago... UPDATE: now builds a universal2 binary with py3.10 on old mac book. Mergeall's build.py has a note on the perils of symlinks in app builds. There are no symlinks in this app, but the required symlinks=True in shutil.copytree() is in place here in any event. UPDATE: there are symlinks in the macOS --onedir build, but copies, ziptools, and most unzip tools all handle them properly. RECENT CHANGES ============== [feb22] Mod paths for new exe build on Windows 11, for Mergeall 3.3. [feb22] Only provide a 64-bit exe on Windows (not 32-bit); see README. [mar22] Need symlinks=True for extras shutil.copytree(); see above. [oct22] Mergeall's GUI 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). Python-PC USC Sync always uses online resurces only. [apr23] Adapted for PC-Phone USB Sync, and made portable to all PCs. EDITORIAL ========= Kivy, buildozer, and PyInstaller are the products of an amazing amount of effort, and Kivy may be the best game in town today for Python-coded apps (GUIs) that must run on Android. But buildozer was a buggy slog, and didn't work at all for macOS as advertised. PyInstaller was just as buggy and sloggy. And Kivy required dozens of coding workarounds across Android and PCs (e.g., orange touch dots and DPI issues in Windows still unfixed). Moreover, all the docs seem thin at the worst places. Kivy's guides to building macOS/Windows/Linux exes with PyInstaller omit crucial details (e.g., packaging .kv files), and its resources are often misleading or just plain wrong (e.g., a .spec is optional on macOS; Linux exes aren't covered and Windows docs don't apply; and file paths and POSIX do work on Android with All files access permission). Kivy's docs may be dated, but it's rude to leave docs online that will send users down pointless and frustrating rabbit holes. Especially docs which end with terse statements that imply that tasks are somehow easy. Some of this may reflect project competition (see also the full absence of tkinter support in Kivy's buildozer), but it's subpar. Fix, please! =========================================================================== """ import os, sys, shutil join, sep = os.path.join, os.path.sep # run this script in its own dir (and mind the top-level name!) assert os.getcwd().endswith(join(*'frigcal3.0/build/build-app-exe/build3.0'.split('/'))) # Python 3.X+ only assert int(sys.version[0]) >= 3 # this script never used on Android RunningOnMacOS = sys.platform.startswith('darwin') # intel and apple m (rosetta|not) RunningOnWindows = sys.platform.startswith('win') # Windows py, may be run by Cygwin RunningOnLinux = sys.platform.startswith('linux') # native, Windows WSL, Android appname = 'Frigcal' # spaces complicate windows zip cmd exename = appname # not: appname.replace(' ', '_') homedir = os.path.expanduser('~') platform = 'Windows' if RunningOnWindows else 'macOS' if RunningOnMacOS else 'Linux' force = len(sys.argv) > 1 # remake icon iff any arg (maybe: this is now cruft) startdir = os.getcwd() # this build script's dir: build3.0 (run this here!) python = sys.executable # py running this script (may have embedded spaces!) def FWP(path): """ allow long paths on Windows: pyedit auto-save files see mergeall and ziptools for tons of details probably not required in this app, but... """ return path if not RunningOnWindows else '\\\\?\\' + os.path.abspath(path) # [1.2.0] drop macOS AppleDouble files from exFAT removable-drive copies; # else, they wind up in _MEI* temp unzips and elsewhere, and can puzzle; # this will run just once per drive copy, but not other good place for it; dropped = False for fname in os.listdir('.'): if fname.startswith('._'): dropped = True print('Dropping macOS cruft:', fname) os.remove(fname) if dropped: input('-Drops pause-') #---------------------------------------------------------------------------- # Make exe's icon if one doesn't already exist #---------------------------------------------------------------------------- # handle this once manually, iff needed # NEW: allow pyinstaller to build a .ico or .icns from .png using pillow; # this seems to work reasonably on all platforms, and obviates iconify.py # UPDATE: no it doesn't - pyinstaller's auto icons botched rounded corners; # go with custom .ico/.icns icons built by iconify.py before this script runs; # UPDATE: except the custom .ico fails on Windows (why?) - use rounded .png; # the icon is still also set in the .py, else the kivi icon is used (why?); # UPDATE: Linux taskbar icons need a .desktop file, provided with the zip. """ print('ICONS') iconship = join('..', '..', 'icons') iconmake = join('..', '..', 'build-icons') iconnames = ['mergeall'] # 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 source tree to temp folder to avoid accidental code loss #---------------------------------------------------------------------------- print('TEMP COPY') devfolder = 'frigcal3.0' # dev, not dist; mind the dashes # cp can't include itself tempfolder = join(homedir, 'Desktop', 'temp-build-frigcal') if os.path.exists(tempfolder): shutil.rmtree(FWP(tempfolder)) # rerun script if fails on macos... os.mkdir(tempfolder) # NEW: omit moots in the temp copy, so they're not added to built exes; # else top-level android .buildozer is 4.86G, macos .DS_Store kills build, # pc-build kills the build too (below), and _website adds 113M to the exes; # (but need mergeall/ at runtime, and usbsync-pc/ at builtime and runtime); # # why does pyinstaller add these, and yet provide no good way to omit them? # _website has just 1 .py script which is not imported anywhere in the app; # --exclude-modules and excludes in .spec don't help; filtering may work in # the .spec file (and it's easy with a temp copy here); but why not an arg? skips = shutil.ignore_patterns( '_private', '_private_', 'build', '_old-screenshots', 'dependencies', 'UserGuide.html', 'docetc', '.buildozer', '_dev-misc', 'bin', '.DS_Store', 'pc-build', '_website') # copy all to temp shutil.copytree(FWP(join('..', '..', '..', '..', devfolder)), # relative to build3.0 FWP(tempfolder), # copies contents, not dir dirs_exist_ok=True, # else need new nested subdir symlinks=True, # copy symlinks verbatim ignore=skips) # skip android and build dirs, etc # Windows only: post run1, copy .spec from tempfolder to pc-build/, add kivy deps lines # macOS only: post run1, copy .spec from tempfolder to pc-build/, mod version# lines # copy pc-build/ files up to main.py's level: else pc-build/ makes exe >2G, build fails! specfile = '_%s--Frigcal.spec' % platform # one per platform hasspec = os.path.exists(specfile) # none on run1, or linux (so far) if hasspec: # from cwd = dev's pcbuild/ shutil.copy2(specfile, join(tempfolder, 'Frigcal.spec')) # drop platform if RunningOnWindows: shutil.copy2('_winver.txt', tempfolder) # windows-only oddment os.chdir(tempfolder) # goto temp build dir for next steps (no FWP(): breaks!) #-------------------------------------------------------------------------- # Build one-file main exe in temp dir's dist/ with pyinstaller # # pyinstall ignores icons on linux, though app may add to app bar; # no need to exclude mergeall/ - it's run by exec() sans imports; # # nested mergeall/ scripts are run as source code via Python's exec() # in threads an all PCs, not as separate frozen exes; # # Android app runs nested mergeall/ scripts via exec() too in a thread # or service process, but is moot here; #-------------------------------------------------------------------------- # BUILD TOOL # pyinstaller may not be on PATH if RunningOnWindows: #pyscripts = 'C:\\Python\\Scripts\\' pydir = os.path.dirname(sys.executable) # else py3.8's older pyinstaller [3.0] pyscripts = pydir + '\\Scripts\\' # where py installed, not the default [feb22] elif RunningOnLinux: pyscripts = '~/ppus/bin/' # temp try: virtualenv to cut exe size (40M vs 200M!) elif RunningOnMacOS: pyscripts = '' # assume pyinstaller on system path, no venv needed # BUILD TARGETS guifreeze = [ # freeze this into app/executable with --windowed, and --onefile | --onedir # script name on Windows+macOS first runs, and Linux always (simple pyinstaller use) # else manually-edited .spec file on Windows+macOS for runs 2+ (kivy-deps, version#) 'Frigcal.spec' if hasspec else 'frigcal-main.py', ] scriptfreeze = [ # freeze these into command-line exes in main folder with --console # [3.0] drop searchcals: now in GUI, and can't find configs module # [3.0] configs module issue fixed for all tools: include searchcals. 'pickcolor.py', # GUI, but hardly worth a whle app on macOS 'makenewcalendar.py', # freeze these into exes in main folder 'searchcals.py', # also ship source for their docs (only) 'unicodemod.py', # freezes require no Py install; source does ] extradatas = [ # ship these with executable, access in .py by cwd uniformly # tbd: use a 'tools' folder to reduce some folder clutter? # nit: this exposes the .kv, but it's useless without the .py # file still needed if build() manually, unless its code in .py 'frigcal_configs.py', # ship these in install folder='.' 'frigcal_configs_base.py', 'README.txt', 'README-3.0.txt', # [3.0] supplemental doc 'icons', # UPDATE SPEC FILES IS THIS LIST IS CHANGED 'Calendars', 'icalendar', 'pytz', 'terms-of-use.txt', # legalese: caution, t-o-u, privacy ] # the .kv must be in cwd for macOS --onedir only """ if RunningOnLinux: extradatas = extradatas[:-1] # drop .kv file on Linux: must --add-data to _MEI* if RunningOnWindows: extradatas = extradatas[:-1] # drop .kv file on Windows: somehow in _MEI* auto """ extradatas = extradatas + scriptfreeze # moot here: no command-line script exes # BUILD COMMANDS # ==> *NOTE* builds2+ on Windows and macOS use .spec files here, not these commands # ==> specfiles generated in temp build tree on first run: copy to '.' and rname+mod # ==> *MUST* make changes in both places - this scheme seems bizarre and error-prone! # ==> *HAVE* been burned by this multiple times, requiring restarts of build tours... if RunningOnWindows: """ ---------------------------------------------------------- Allow for embedded quotes in command line on Windows (not just '%s\\pyinstaller' or '"%s\\pyinstaller"'). Windows requires a .spec file to add kivy-deps bits; really, and only on Windows; exes won't work without this, despite the thin Kivy/PyInstaller docs. Windows adds an odd version file, and all data items are at the top-level of the install/unzip folder, and found via cwd reset in main.py - including the created run-counter and configs-save files. The .kv file loaded by Kivy used to be in added to the install folder too, but Windows exes still run if it's not there... oddly. Unlike Linux, the .kv doesn't have to be an --add-data. The Windows build still has a few "critical" errors that popup unreadable message boxes and might be fixed with extra dep installs, but are harmless in this app (but see Linux build: fixing undefs shot size up badly). [3.0] Spec file not used on Windows for Frigcal 3.0: it does not need Kivy deps/adds, and code need not be hidded. [3.0] PyInstaller splash screen need not be 512x512 (this is for buildozer on Android) and can have transparency, but must be PNG else converted to it - hence animated GIFs don't work. For Frigcal, there are two N-second delays: unzip of the exe, and load of calendar files. The prior manual animation in 2.0 only runs for the second (the only delay in py2app 2.0), but splash screen spans both. Tool exes open in 1-3 secs: no need for splash on these. ---------------------------------------------------------- """ # first build: create .spec file from args extraargs1 = ( ' --splash icons/splash2.png' # shown during load, win+lin only, not rounded ' --version-file _winver.txt' # windows-only: via _winver-maker.py; convoluted! ' --hidden-import ctypes') # windows-only: kivy dpi scaling bug workaround buildcmd1 = ( # BAIL ON SPEC FILE FOR FRIGCAL 3.0 - no kivy deps or code hiding here #f'cmd /C ""{pyscripts}pyi-makespec"' f'cmd /C ""{pyscripts}pyinstaller"' ' --onefile' # unzip to _MEIxxxxxx on start, extras in unzip dir ' --windowed' # no stdout/err console f' --name "{exename}"' # this instead of from main.py ##' --hidden-import PIL' # for animated GIF, ref'd in .kv but not .py ' --exclude-module frigcal_configs' ' --exclude-module frigcal_configs_base' ' --icon icons/frigcal.ico' # pyinstall/kivy auto, not iconify.py f' {extraargs1}' f' "%(target)s""') # target not set till for loops ahead # builds 2+: use MANUALLY copied and edited spec file: required for kivy-deps buildcmd2 = ( f'cmd /C ""{pyscripts}pyinstaller" --clean' ' "%(target)s""') buildcmd = buildcmd1 if not hasspec else buildcmd2 elif RunningOnLinux: """ ---------------------------------------------------------------- Run in ~/ppus virtualenv, but this was probably pointless once all undefs installed, and results are now the same either way. Linux does NOT require a .spec file for kivy-deps like Windows. In fact, the kivy-deps module doesn't exist for Linux at all, but kivy's docs don't say so, and omit Linux exes completely! https://kivy.org/doc/stable/gettingstarted/installation.html On Linux ONLY, Kivy does NOT look for .kv file in CWD, but only in _MEI* unzip temp folder: use --add-data for it here. All other extras (including run-count and config-save files) work fine in cwd after resetting it to the install folder. The only way to catch this was by running the exe from a cmd line with -d to get extra debug info in ~/.kivy/logs (not build/warn*), else this fatal error was NEVER reported... UPDATE: Windows appears to locate+find .kv in the unzipped exe auto, though macOS requires it in '.' in --onedir mode. Also required treesize.py to find and exclude large modules;. Running in a virtualenv helped with size some, but not much: once undef modules were installed, size wentfrom 40M back ~200M. Caveat: this makes a plain Linux exe which omits system libs and may not work broadly. Alt: use compileall.py and PyZipFile to ship bytecode in a zipfile, with a simple .py to add the zip to sys.path and import main.py to run. This requires users to install Python and Kivy on their own machine, but seems much more likely to span hosts. OTOH, bytecode=>code happens. The Linux exe IS now verified to work elsewhere, via online vm, and WSL2 on Windows, though only on Ubuntu 22 distros to date. [3.0] Wholly moot on Linux - no exe built, use source-code pkg. ---------------------------------------------------------------- """ extraargs = ( ' --splash icons/Frigcal1024.png' # shown during load, not supported on macos #' --add-data pcphoneusbsync.kv:.' # see above: a kivy linux req+convolution! ' --exclude-module opencv' # else still ~140M after installing undefs ' --exclude-module numpy' # back to 40M with module exludes ' --exclude-module enchant' # maybe easier with a .spec, but extra cruft ' --exclude-module cv2') # and this makes virtualenv pointless... buildcmd = ( f'{pyscripts}pyinstaller' ' --onefile' # unzip to _MEIxxxxxx on start, extras in unzip dir ' --windowed' # no stdout/err console f' --name "{exename}"' # this instead of from main.py ##' --hidden-import PIL' # for animated GIF, ref'd in .kv but not .py ' --exclude-module frigcal_configs' ' --exclude-module frigcal_configs_base' ' --icon icons/Frigcal1024.png' # icon not used on Linux: ignored, use linux.desktop f' {extraargs}' ' "%(target)s"') # target not set till for loops ahead elif RunningOnMacOS: """ -------------------------------------------------------------------- Buildozer was a fail and spec file not required, despite kivy docs! Also no splash screen, unzip lag, dpi issue, or logging errors. Update: macOS now requires a .spec file like Windows, because there is no other way to set app version number (manifest edits trigger a big scary warning about tampering, with no option to run anyhow). Unlike Windows+Linux, macOS uses --onedir, because already builds an app-bundle folder, and --onefile takes ~12 seconds to load with no suport for a splash screen, versus 1~2 seconds for --onedir. This exposes the .kv file's code, but it's not worth much sans .py. On macOS, all data items are added to app folder's Contents/MacOS via --add-data, and located via a cwd reset in the .py. This includes the .kv file: --onedir exe won't run without the .kv in Contents/MacOS. On macOS only, run-counter and config-save files are made in ~/Library, not install (app own-folder access is gray, though mergeall/mergeall_configs.py edits are proved persistent). Currently builds an X86_64 Intel 64-bit exe only (run a 'file' cmd on the MacOS/ exe). To build a universal2 app exe that supports M chips natively too, use [target_arch='universal2'] in the spec file, and use [--target-architecture universal2] here. This failed on Py 3.8's struct, with the following msg; Py 3.9+ may work, but libs must support M chips too, and the project has no way to verify this today: "" PyInstaller.utils.osx.IncompatibleBinaryArchError: /Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/ lib-dynload/_struct.cpython-38-darwin.so is not a fat binary! ERROR: build failed: 256 "" UPDATE: now _does_ build universal2 (intel+arm/M) binary on catalina mac book (not dev mac book pro: py 3.8). To make this work, install ziptools, python 3.10 (or later), pyinstaller, kivy, and pillow; add to PATH /Users/mini/Library/Python/3.10/bin, set $Z in ~/.bash_profile; and fetch+combine pillow intel and arm binaries into a 'fat' universal2 lib with steps here (pillow does not ship a combo universal2, weirdly): https://pillow.readthedocs.io/en/stable/installation.html#basic-installation ==> PIL (Pillow) is no longer used by Frigcal, after MonthWidows cut Summary: % cd pc-build % python3 -m pip download --only-binary=:all: --platform macosx_10_15_x86_64 Pillow % python3 -m pip download --only-binary=:all: --platform macosx_11_0_arm64 Pillow % python3 -m pip install delocate >>> from delocate.fuse import fuse_wheels >>> fuse_wheels('Pillow-9.5.0-cp310-cp310-macosx_10_10_x86_64.whl', 'Pillow-9.5.0-cp310-cp310-macosx_11_0_arm64.whl', 'Pillow-9.5.0-cp310-cp310-macosx_10_15_universal2.whl') % pip3 install --force-reinstall Pillow-9.5.0-cp310-cp310-macosx_10_15_universal2.whl % python3 build.py And set target_architecture here + .spec. Builds a universal2 with both x86_64 and arm64 exes in Content/MacOS (per % file xxx), which is 34m (vs 21) and works on intel mb+mbp. macOS app IS now verified to work on arm too, via online vm setup in AWS. [3.0] There is a wicked-long delay when first running tools exes, but PyInstaller doesn't support splash screens on macOS. These could be --onedir folders+apps, but that seems overkill; punt. -------------------------------------------------------------------- """ # files need '.' else in subdir extraargs1 = ' '.join( ' --add-data %s:%s' % (data, (data if os.path.isdir(data) else '.')) for data in extradatas) buildcmd1 = ( f'{pyscripts}pyi-makespec' # not recognized: --clean ##' --onefile' # (unzip to _MEIxxxxxx on start, always mod ma/configs.py) ' --onedir' # extras in app/Content/MacOS folder, no unzip on start ' --windowed' # no console, make macos app-bundle folder, like --onedir? f' --name "{exename}"' # this instead of from main.py ##' --hidden-import PIL' # for animated GIF, ref'd in .kv but not .py ' --exclude-module frigcal_configs' ' --exclude-module frigcal_configs_base' ' --icon icons/frigcal.icns' # iconify.py, not pyinstaller auto ' --target-architecture universal2' # mb, py 3.10+, combine pillow wheels f' {extraargs1}' ' %(target)s') # target not set till for loops ahead # builds 2+: use MANUALLY copied and edited spec file: required for kivy-deps buildcmd2 = ( f'{pyscripts}pyinstaller --clean' ' "%(target)s"') buildcmd = buildcmd1 if not hasspec else buildcmd2 # BUILD RUNS # make main app/exe with cmds above (or specfiles) for target in guifreeze[:1]: print('\nBUILDING:', target) exitstat = os.system(buildcmd % vars()) if exitstat: print('ERROR: build failed:', exitstat) sys.exit(exitstat) # don't continue here # make extra GUI standalones (if any) """MOOT for target in guifreeze[1:]: print('\nBUILDING:', target) exitstat = os.system( f'%s{pyscripts}pyinstaller%s' ' --onefile' ' --windowed' f' --icon icons{sep}{'Frigcal1024.png' if RunningOnWindows else 'frigcal.icns'}' ' --exclude-module frigcal_configs' ' --exclude-module frigcal_configs_base' f' {target}%s' % (('cmd /S /C ""', '"', '"') if RunningOnWindows else ('', '', '')) ) if exitstat: print('ERROR: build failed:', exitstat) sys.exit(exitstat) # don't continue here MOOT""" # make extra cmdline exes for target in scriptfreeze: print('\nBUILDING:', target) exitstat = os.system( f'%s{pyscripts}pyinstaller%s' ' --onefile' ' --console' f' --icon icons{sep}Frigcal1024.png' ' --exclude-module frigcal_configs' # NEEDED for Configs.icspath setting ' --exclude-module frigcal_configs_base' # in both makenewcal and searchcals f' {target}%s' % (('cmd /S /C ""', '"', '"') if RunningOnWindows else ('', '', '')) ) if exitstat: print('ERROR: build failed:', exitstat) sys.exit(exitstat) # don't continue here # no good way to do this... is it worth it??? if RunningOnMacOS: shutil.move(join('dist', target.split('.')[0]), join('dist', 'Frigcal.app', 'Contents', 'Resources')) ##sys.exit() # to stop and inspect #-------------------------------------------------------------------------- # Use app exe (not main.py script) name in zipped result #-------------------------------------------------------------------------- # now done via pyinstaller --name """ exeext = '.exe' if RunningOnWindows else '' shutil.move(join('dist', 'main%s' % exeext), join('dist', '%s%s' % (exename, exeext))) """ #-------------------------------------------------------------------------- # Copy extras to exe's folder: the .py arranges to see these # # note: --add-data gets unzipped in a temp dir the user won't see, # though that's okay for items that will not change from run to run. # # for macos: since builds an app folder anyhow, use --onedir and # make data items --add-data to store alongside executable in app # folder, and cd to sys.executable dir on startup for cwd access. # # for windows and linux: store data items alongside single-file # --onefile executable in a manual zipfile, and cd to sys.executable # (install) dir on startup for cwd access (like Mergeall). # # See main.py's "if hasattr(sys, 'frozen'" for more details. #-------------------------------------------------------------------------- if RunningOnWindows or RunningOnLinux: # auto on macos with --add-data for name in extradatas: 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 #-------------------------------------------------------------------------- # Cleanup private bits - nothing here (except for calendards in Frigcal!) #-------------------------------------------------------------------------- # currently in tempfolder (copy) # drop personal calendar items: make new default on start, unless dir set in configs if RunningOnMacOS: prunee = join('dist', 'Frigcal.app', 'Contents', 'Resources', 'Calendars') # really else: prunee = join('dist', 'Calendars') for item in os.listdir(prunee): if item not in ('README.txt'): itempath = join(prunee, item) print('Removing', itempath) if os.path.isdir(itempath): shutil.rmtree(itempath) else: os.remove(itempath) #---------------------------------------------------------------------------- # Finale: move temp's dist/ to app's pc-build/ and zip exe/app folder # the exe or app is embedded in the resulting zipped download folder #---------------------------------------------------------------------------- # zip targets: folder and zip thedir = appname + ('.app' if RunningOnMacOS else '') # nested .exe on win thezip = appname + '--' + platform + '.zip' # zip command: use portable ziptools if RunningOnMacOS: zipper = join(os.environ['Z'], 'zip-create.py') # dev version elif RunningOnWindows or RunningOnLinux: zipper = join(homedir, 'Downloads', 'ziptools', 'zip-create.py') # assume here! # allow spaces in app name, but not py (win) zipit = '%s %s "%s" "%s" -skipcruft' % (python, zipper, thezip, thedir) # move dist product folder to build script's pc-build/ folder os.chdir(startdir) if os.path.exists(thezip): shutil.move(thezip, 'prev-' + thezip) # save previous version? if os.path.exists(thedir): shutil.rmtree(FWP(thedir)) # nuke unzipped version # move build to unzip name in pc-build/, folder+app in dist on macos if not RunningOnMacOS: shutil.move(join(tempfolder, 'dist'), thedir) else: shutil.move(join(tempfolder, 'dist', appname) + '.app', thedir) # forelorn hack 1 - linux exe doesn't have exe permission (why?) # because it's a user error: use -permissions if extract with ziptools # (py3 $Z/zip-extract.py PC-Phone\ USB\ Sync--Linux.zip . -permissions) """ if RunningOnLinux: os.chmod(join(thedir, appname), 0o775) """ # forelorn hack 2 - mod manifest on macos for version# # version3# is supported by pyinstaller, but require .spec file # # Problem: this mod triggers a harsh message when the app is # unzipped and run - '"<appname>" is damaged and can't be opened. # You should move it to the trash', with only "Move" and "Cancel" # options. App still runs if remove quarantine with xattrs # (xattr -r -d com.apple.quarantine PC-Phone\ USB\ Sync.app) # but is scary enough to put off most users. Sans this edit, the # message is much less severe, and offers a simple "Open" option. # All on Catalina; Gatekeeper may grow more douchey later... # # this prompted using a .spec file on macOS too per rewrites above """ if RunningOnMacOS: plist = open(thedir + '/Contents/Info.plist', 'r').read() plist = plist.replace('<string>0.0.0</string>', '<string>1.0.0</string>') open(thedir + '/Contents/Info.plist', 'w').write(plist) """ # zip the build/app folder os.system(zipit) # make download zipfile shutil.rmtree(FWP(thedir)) # no need to save raw dist here ##shutil.rmtree(FWP(tempfolder)) # rm entire temp build tree now print('Done: see %s in build3.0/' % thezip) if RunningOnWindows and sys.stdin.isatty(): input('Press enter to close') # stay up if clicked (Windows) # +unzip exe folder, and move to ~/somewhere to make it permanent