From 50878f33bd45455ea9f83d9ca2f6dbfbd4a2e77a Mon Sep 17 00:00:00 2001 From: Nico Zanferrari Date: Fri, 22 Mar 2019 15:33:14 +0100 Subject: [PATCH 1/9] cleanup --- extras/build_web2py/setup_app.py | 160 ------------------------------- 1 file changed, 160 deletions(-) delete mode 100755 extras/build_web2py/setup_app.py diff --git a/extras/build_web2py/setup_app.py b/extras/build_web2py/setup_app.py deleted file mode 100755 index 92985418..00000000 --- a/extras/build_web2py/setup_app.py +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -This is a setup.py script generated by py2applet - -Usage: - python setup.py py2app -""" - -copy_apps = False -copy_scripts = True -copy_site_packages = True -remove_build_files = True -make_zip = True -zip_filename = "web2py_osx" - -from setuptools import setup -from gluon.import_all import base_modules, contributed_modules -from gluon.fileutils import readlines_file -import os -import fnmatch -import shutil -import sys -import re -import zipfile - -#read web2py version from VERSION file -web2py_version_line = readlines_file('VERSION')[0] -#use regular expression to get just the version number -v_re = re.compile('[0-9]+\.[0-9]+\.[0-9]+') -web2py_version = v_re.search(web2py_version_line).group(0) - -class reglob: - def __init__(self, directory, pattern="*"): - self.stack = [directory] - self.pattern = pattern - self.files = [] - self.index = 0 - - def __getitem__(self, index): - while 1: - try: - file = self.files[self.index] - self.index = self.index + 1 - except IndexError: - self.index = 0 - self.directory = self.stack.pop() - self.files = os.listdir(self.directory) - else: - fullname = os.path.join(self.directory, file) - if os.path.isdir(fullname) and not os.path.islink(fullname): - self.stack.append(fullname) - if not (file.startswith('.') or file.startswith('#') or file.endswith('~')) \ - and fnmatch.fnmatch(file, self.pattern): - return fullname - -setup(app=['web2py.py'], - version=web2py_version, - description="web2py web framework", - author="Massimo DiPierro", - license="LGPL v3", - data_files=[ - 'NEWINSTALL', - 'ABOUT', - 'LICENSE', - 'VERSION', - 'splashlogo.gif', - 'logging.example.conf', - 'options_std.py', - ], - options={'py2app': { - 'argv_emulation': True, - 'includes': base_modules, - }}, - setup_requires=['py2app']) - - -def copy_folders(source, destination): - """Copy files & folders from source to destination (within dist/)""" - print 'copying %s -> %s' % (source, destination) - base = 'dist/web2py.app/Contents/Resources/' - if os.path.exists(os.path.join(base, destination)): - shutil.rmtree(os.path.join(base, destination)) - shutil.copytree(os.path.join(source), os.path.join(base, destination)) - -#Should we include applications? -copy_folders('gluon','gluon') - -if copy_apps: - copy_folders('applications', 'applications') - print "Your application(s) have been added" -else: - #only copy web2py's default applications - copy_folders('applications/admin', 'applications/admin') - copy_folders('applications/welcome', 'applications/welcome') - copy_folders('applications/examples', 'applications/examples') - print "Only web2py's admin, examples & welcome applications have been added" - - -#should we copy project's site-packages into dist/site-packages -if copy_site_packages: - #copy site-packages - copy_folders('site-packages', 'site-packages') -else: - #no worries, web2py will create the (empty) folder first run - print "Skipping site-packages" - pass - -#should we copy project's scripts into dist/scripts -if copy_scripts: - #copy scripts - copy_folders('scripts', 'scripts') -else: - #no worries, web2py will create the (empty) folder first run - print "Skipping scripts" - pass - - -#borrowed from http://bytes.com/topic/python/answers/851018-how-zip-directory-python-using-zipfile -def recursive_zip(zipf, directory, folder=""): - for item in os.listdir(directory): - if os.path.isfile(os.path.join(directory, item)): - zipf.write(os.path.join(directory, item), folder + os.sep + item) - elif os.path.isdir(os.path.join(directory, item)): - recursive_zip( - zipf, os.path.join(directory, item), folder + os.sep + item) - -#should we create a zip file of the build? - -if make_zip: - #to keep consistent with how official web2py windows zip file is setup, - #create a web2py folder & copy dist's files into it - shutil.copytree('dist', 'zip_temp/web2py') - #create zip file - #use filename specified via command line - zipf = zipfile.ZipFile( - zip_filename + ".zip", "w", compression=zipfile.ZIP_DEFLATED) - path = 'zip_temp' # just temp so the web2py directory is included in our zip file - recursive_zip( - zipf, path) # leave the first folder as None, as path is root. - zipf.close() - shutil.rmtree('zip_temp') - print "Your Windows binary version of web2py can be found in " + \ - zip_filename + ".zip" - print "You may extract the archive anywhere and then run web2py/web2py.exe" - -#should py2exe build files be removed? -if remove_build_files: - shutil.rmtree('build') - shutil.rmtree('deposit') - shutil.rmtree('dist') - print "py2exe build files removed" - -#final info -if not make_zip and not remove_build_files: - print "Your Windows binary & associated files can also be found in /dist" - -print "Finished!" -print "Enjoy web2py " + web2py_version_line From f9db6a8306c92dca452f8be502dc5f5e5b64dd27 Mon Sep 17 00:00:00 2001 From: Nico Zanferrari Date: Fri, 22 Mar 2019 15:33:29 +0100 Subject: [PATCH 2/9] cleanup --- extras/build_web2py/setup_exe.conf | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 extras/build_web2py/setup_exe.conf diff --git a/extras/build_web2py/setup_exe.conf b/extras/build_web2py/setup_exe.conf deleted file mode 100644 index 9996db06..00000000 --- a/extras/build_web2py/setup_exe.conf +++ /dev/null @@ -1,27 +0,0 @@ -[Setup] -#py2exe often includes DLLS from windows which aren't licensed for -#open source distribution. Should they be removed? -remove_microsoft_dlls: Yes - -#copy all web2py apps currently installed? -#If no, only the default admin, welcome & example apps will be included -copy_apps: No - -#include the web2py\site-packages directory? -copy_site_packages: Yes - -#include the web2py\scripts directory? -copy_scripts: Yes - -#create a zip file of the build for easy distribution? -make_zip: Yes - -#what should the zip file be named? (leave off the .zip extension) -zip_filename = web2py_win - -#should the build, deposit & dist directories used by py2exe be removed? -#if you created a zip file you likely don't need these directories anymore -remove_build_files = Yes - -#should the build include the gevented webserver (needs gevent) -include_gevent = Yes \ No newline at end of file From 7e30be377d3744cb95f4d3dfe2e7d6c688dcc556 Mon Sep 17 00:00:00 2001 From: Nico Zanferrari Date: Fri, 22 Mar 2019 15:33:44 +0100 Subject: [PATCH 3/9] cleanup --- extras/build_web2py/setup_exe.py | 232 ------------------------------- 1 file changed, 232 deletions(-) delete mode 100755 extras/build_web2py/setup_exe.py diff --git a/extras/build_web2py/setup_exe.py b/extras/build_web2py/setup_exe.py deleted file mode 100755 index ae0d00ee..00000000 --- a/extras/build_web2py/setup_exe.py +++ /dev/null @@ -1,232 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -#Adapted from http://bazaar.launchpad.net/~flavour/sahana-eden/trunk/view/head:/static/scripts/tools/standalone_exe.py - -USAGE = """ -Usage: - Copy this and setup_exe.conf to web2py root folder - To build with py2exe: - Install py2exe: http://sourceforge.net/projects/py2exe/files/ - run python setup_exe.py py2exe - To build with bbfreeze: - Install bbfreeze: https://pypi.python.org/pypi/bbfreeze/ - run python setup_exe.py bbfreeze -""" - -from distutils.core import setup -from gluon.import_all import base_modules, contributed_modules -from gluon.fileutils import readlines_file -from glob import glob -import fnmatch -import os -import shutil -import sys -import re -import zipfile - -if len(sys.argv) != 2 or not os.path.isfile('web2py.py'): - print USAGE - sys.exit(1) -BUILD_MODE = sys.argv[1] -if not BUILD_MODE in ('py2exe', 'bbfreeze'): - print USAGE - sys.exit(1) - -def unzip(source_filename, dest_dir): - with zipfile.ZipFile(source_filename) as zf: - zf.extractall(dest_dir) - -#borrowed from http://bytes.com/topic/python/answers/851018-how-zip-directory-python-using-zipfile -def recursive_zip(zipf, directory, folder=""): - for item in os.listdir(directory): - if os.path.isfile(os.path.join(directory, item)): - zipf.write(os.path.join(directory, item), folder + os.sep + item) - elif os.path.isdir(os.path.join(directory, item)): - recursive_zip( - zipf, os.path.join(directory, item), folder + os.sep + item) - - -#read web2py version from VERSION file -web2py_version_line = readlines_file('VERSION')[0] -#use regular expression to get just the version number -v_re = re.compile('[0-9]+\.[0-9]+\.[0-9]+') -web2py_version = v_re.search(web2py_version_line).group(0) - -#pull in preferences from config file -import ConfigParser -Config = ConfigParser.ConfigParser() -Config.read('setup_exe.conf') -remove_msft_dlls = Config.getboolean("Setup", "remove_microsoft_dlls") -copy_apps = Config.getboolean("Setup", "copy_apps") -copy_site_packages = Config.getboolean("Setup", "copy_site_packages") -copy_scripts = Config.getboolean("Setup", "copy_scripts") -make_zip = Config.getboolean("Setup", "make_zip") -zip_filename = Config.get("Setup", "zip_filename") -remove_build_files = Config.getboolean("Setup", "remove_build_files") -include_gevent = Config.getboolean("Setup", "include_gevent") - -# Python base version -python_version = sys.version_info[:3] - - - -if BUILD_MODE == 'py2exe': - import py2exe - - setup( - console=[{'script':'web2py.py', - 'icon_resources': [(0, 'extras/icons/web2py.ico')] - }], - windows=[{'script':'web2py.py', - 'icon_resources': [(1, 'extras/icons/web2py.ico')], - 'dest_base':'web2py_no_console' # MUST NOT be just 'web2py' otherwise it overrides the standard web2py.exe - }], - name="web2py", - version=web2py_version, - description="web2py web framework", - author="Massimo DiPierro", - license="LGPL v3", - data_files=[ - 'ABOUT', - 'LICENSE', - 'VERSION' - ], - options={'py2exe': { - 'packages': contributed_modules, - 'includes': base_modules, - }}, - ) - #py2exe packages lots of duplicates in the library.zip, let's save some space - library_temp_dir = os.path.join('dist', 'library_temp') - library_zip_archive = os.path.join('dist', 'library.zip') - os.makedirs(library_temp_dir) - unzip(library_zip_archive, library_temp_dir) - os.unlink(library_zip_archive) - zipl = zipfile.ZipFile(library_zip_archive, "w", compression=zipfile.ZIP_DEFLATED) - recursive_zip(zipl, library_temp_dir) - zipl.close() - shutil.rmtree(library_temp_dir) - print "web2py binary successfully built" - -elif BUILD_MODE == 'bbfreeze': - modules = base_modules + contributed_modules - from bbfreeze import Freezer - f = Freezer(distdir="dist", includes=(modules)) - f.addScript("web2py.py") - #to make executable without GUI we need this trick - shutil.copy("web2py.py", "web2py_no_console.py") - f.addScript("web2py_no_console.py", gui_only=True) - if include_gevent: - #fetch the gevented webserver script and copy to root - gevented_webserver = os.path.join("handlers", "web2py_on_gevent.py") - shutil.copy(gevented_webserver, "web2py_on_gevent.py") - f.addScript("web2py_on_gevent.py") - f.setIcon('extras/icons/web2py.ico') - f() # starts the freezing process - os.unlink("web2py_no_console.py") - if include_gevent: - os.unlink("web2py_on_gevent.py") - #add data_files - for req in ['ABOUT', 'LICENSE', 'VERSION']: - shutil.copy(req, os.path.join('dist', req)) - print "web2py binary successfully built" - -try: - os.unlink('storage.sqlite') -except: - pass - -#This need to happen after bbfreeze is run because Freezer() deletes distdir before starting! -if python_version > (2,5): - # Python26 compatibility: http://www.py2exe.org/index.cgi/Tutorial#Step52 - try: - shutil.copytree('C:\Bin\Microsoft.VC90.CRT', 'dist/Microsoft.VC90.CRT/') - except: - print "You MUST copy Microsoft.VC90.CRT folder into the archive" - -def copy_folders(source, destination): - """Copy files & folders from source to destination (within dist/)""" - if os.path.exists(os.path.join('dist', destination)): - shutil.rmtree(os.path.join('dist', destination)) - shutil.copytree(os.path.join(source), os.path.join('dist', destination)) - -#should we remove Windows OS dlls user is unlikely to be able to distribute -if remove_msft_dlls: - print "Deleted Microsoft files not licensed for open source distribution" - print "You are still responsible for making sure you have the rights to distribute any other included files!" - #delete the API-MS-Win-Core DLLs - for f in glob('dist/API-MS-Win-*.dll'): - os.unlink(f) - #then delete some other files belonging to Microsoft - other_ms_files = ['KERNELBASE.dll', 'MPR.dll', 'MSWSOCK.dll', - 'POWRPROF.dll'] - for f in other_ms_files: - try: - os.unlink(os.path.join('dist', f)) - except: - print "unable to delete dist/" + f - -#Should we include applications? -if copy_apps: - copy_folders('applications', 'applications') - print "Your application(s) have been added" -else: - #only copy web2py's default applications - copy_folders('applications/admin', 'applications/admin') - copy_folders('applications/welcome', 'applications/welcome') - copy_folders('applications/examples', 'applications/examples') - print "Only web2py's admin, examples & welcome applications have been added" - -copy_folders('extras', 'extras') -copy_folders('examples', 'examples') -copy_folders('handlers', 'handlers') - - -#should we copy project's site-packages into dist/site-packages -if copy_site_packages: - #copy site-packages - copy_folders('site-packages', 'site-packages') -else: - #no worries, web2py will create the (empty) folder first run - print "Skipping site-packages" - -#should we copy project's scripts into dist/scripts -if copy_scripts: - #copy scripts - copy_folders('scripts', 'scripts') -else: - #no worries, web2py will create the (empty) folder first run - print "Skipping scripts" - -#should we create a zip file of the build? -if make_zip: - #create a web2py folder & copy dist's files into it - shutil.copytree('dist', 'zip_temp/web2py') - #create zip file - zipf = zipfile.ZipFile(zip_filename + ".zip", - "w", compression=zipfile.ZIP_DEFLATED) - # just temp so the web2py directory is included in our zip file - path = 'zip_temp' - # leave the first folder as None, as path is root. - recursive_zip(zipf, path) - zipf.close() - shutil.rmtree('zip_temp') - print "Your Windows binary version of web2py can be found in " + \ - zip_filename + ".zip" - print "You may extract the archive anywhere and then run web2py/web2py.exe" - -#should py2exe build files be removed? -if remove_build_files: - if BUILD_MODE == 'py2exe': - shutil.rmtree('build') - shutil.rmtree('deposit') - shutil.rmtree('dist') - print "build files removed" - -#final info -if not make_zip and not remove_build_files: - print "Your Windows binary & associated files can also be found in /dist" - -print "Finished!" -print "Enjoy web2py " + web2py_version_line From 7a0e113a5f6910100fce8ccf80ca0dae99daf110 Mon Sep 17 00:00:00 2001 From: Nico Zanferrari Date: Fri, 22 Mar 2019 15:34:50 +0100 Subject: [PATCH 4/9] new PyInstaller script --- extras/build_web2py/build_web2py.py | 145 ++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 extras/build_web2py/build_web2py.py diff --git a/extras/build_web2py/build_web2py.py b/extras/build_web2py/build_web2py.py new file mode 100644 index 00000000..1734ab2c --- /dev/null +++ b/extras/build_web2py/build_web2py.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# up to 2019, we have used py2applet, py2exe and bbfreeze for building web2py binaries +# The original scripts can be found on GitHub for web2py up to version 2.18.4 +# See also Niphlod's work on http://www.web2pyslices.com/slice/show/1726/build-windows-binaries +# Then we switched to Pyinstaller in order to fully support Python 3 + +from distutils.core import setup +from gluon.import_all import base_modules, contributed_modules +from gluon.fileutils import readlines_file +from glob import glob +import os +import shutil +import sys +import re +import zipfile +import subprocess +import platform + + +USAGE = """ +build_web2py - make web2py Windows and MacOS binaries with pyinstaller + +Usage: + Install pyinstaller program, copy this file to web2py root folder and run: + + python build_py3.py + +""" + +if len(sys.argv) != 1 or not os.path.isfile('web2py.py'): + print(USAGE) + sys.exit(1) +os_version = platform.system() +if os_version not in ('Windows', 'Darwin'): + print('Unsupported system: %s' % os_version) + sys.exit(1) + + +def unzip(source_filename, dest_dir): + with zipfile.ZipFile(source_filename) as zf: + zf.extractall(dest_dir) + +# borrowed from http://bytes.com/topic/python/answers/851018-how-zip-directory-python-using-zipfile + + +def recursive_zip(zipf, directory, folder=""): + for item in os.listdir(directory): + if os.path.isfile(os.path.join(directory, item)): + zipf.write(os.path.join(directory, item), folder + os.sep + item) + elif os.path.isdir(os.path.join(directory, item)): + recursive_zip( + zipf, os.path.join(directory, item), folder + os.sep + item) + + +# read web2py version from VERSION file +web2py_version_line = readlines_file('VERSION')[0] +# use regular expression to get just the version number +v_re = re.compile('[0-9]+\.[0-9]+\.[0-9]+') +web2py_version = v_re.search(web2py_version_line).group(0) + +# Python base version +python_version = sys.version_info[:3] + + +if os_version == 'Windows': + print("\nBuilding binary web2py for Windows\n") + # to make executable without GUI we need this trick + shutil.copy("web2py.py", "web2py_no_console.py") + + subprocess.run('pyinstaller --clean --icon=extras/icons/web2py.ico --hidden-import=gluon.packages.dal.pydal \ + --hidden-import=gluon.packages.yatl.yatl --hidden-import=site-packages web2py.py') + subprocess.run('pyinstaller -w --clean --icon=extras/icons/web2py.ico --hidden-import=gluon.packages.dal.pydal \ + --hidden-import=gluon.packages.yatl.yatl --hidden-import=site-packages web2py_no_console.py') + + # cleanup + move binary files to dist folder + os.unlink('web2py_no_console.py') + os.unlink('web2py_no_console.spec') + source = 'dist/web2py/' + for files in os.listdir(source): + shutil.move(os.path.join(source, files), 'dist') + source2 = 'dist/web2py_no_console/' + files = 'web2py_no_console.exe' + shutil.move(os.path.join(source2, files), 'dist') + shutil.rmtree(source) + shutil.rmtree(source2) + os.unlink('dist/web2py.exe.manifest') + + zip_filename = 'web2py_win' + bin_folder = 'dist' + + +elif os_version == 'Darwin': + print("\nBuilding binary web2py for MacOS\n") + + import subprocess + subprocess.call("pyinstaller --clean --windowed --icon=extras/icons/web2py.icns --hidden-import=gluon.packages.dal.pydal --hidden-import=gluon.packages.yatl.yatl \ + --hidden-import=site-packages --add-binary='/System/Library/Frameworks/Tk.framework/Tk':'tk' \ + --add-binary='/System/Library/Frameworks/Tcl.framework/Tcl':'tcl' web2py.py", shell=True) + + # cleanup + move binary files to dist folder + shutil.rmtree(os.path.join('dist', 'web2py')) + shutil.rmtree('build') + + zip_filename = 'web2py_osx' + bin_folder = (os.path.join('dist', 'web2py.app/Contents/MacOS')) + +print("\nWeb2py binary successfully built!\n") + + +# add data_files +for req in ['CHANGELOG', 'LICENSE', 'VERSION']: + shutil.copy(req, os.path.join(bin_folder, req)) +# cleanup unuseful binary cache +for dirpath, dirnames, files in os.walk('.'): + if dirpath.endswith('__pycache__'): + print('Deleting cached binary directory : %s' % dirpath) + shutil.rmtree(dirpath) + +print("\nPreparing package ...") +# misc +for folders in ['gluon', 'extras', 'site-packages', 'scripts', 'applications', 'examples', 'handlers']: + shutil.copytree(folders, os.path.join(bin_folder, folders)) +os.mkdir(os.path.join(bin_folder, 'logs')) +os.unlink('web2py.spec') + + +# create a web2py folder & copy dist's files into it +shutil.copytree('dist', 'zip_temp/web2py') +# create zip file +zipf = zipfile.ZipFile(zip_filename + ".zip", + "w", compression=zipfile.ZIP_DEFLATED) +# just temp so the web2py directory is included in our zip file +path = 'zip_temp' +# leave the first folder as None, as path is root. +recursive_zip(zipf, path) +zipf.close() +shutil.rmtree('zip_temp') +shutil.rmtree('dist') + +print("Your binary version of web2py can be found in " + \ + zip_filename + ".zip") +print("You may extract the archive anywhere and then run web2py without worrying about dependency") +print("\nEnjoy binary web2py " + web2py_version_line + "\n with embedded Python " + sys.version + "\n") From ca7b676591c1203d135a5f5af39a6920b23b4725 Mon Sep 17 00:00:00 2001 From: Nico Zanferrari Date: Fri, 22 Mar 2019 15:37:14 +0100 Subject: [PATCH 5/9] Update README --- extras/build_web2py/README | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extras/build_web2py/README b/extras/build_web2py/README index 0fc35b14..d060dbfd 100644 --- a/extras/build_web2py/README +++ b/extras/build_web2py/README @@ -1,2 +1,4 @@ +# build-web2py + The files in this folder must be run from the main web2py folder. -They are for building windows and osx binary distribution and not meant for the end user. \ No newline at end of file +They are for building windows and osx binary distribution using PyInstaller and not meant for the end user. From c03c962778d77ad80377b2e2cf326593cdc644fc Mon Sep 17 00:00:00 2001 From: Nico Zanferrari Date: Fri, 22 Mar 2019 15:37:44 +0100 Subject: [PATCH 6/9] Rename README to README.md --- extras/build_web2py/{README => README.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename extras/build_web2py/{README => README.md} (100%) diff --git a/extras/build_web2py/README b/extras/build_web2py/README.md similarity index 100% rename from extras/build_web2py/README rename to extras/build_web2py/README.md From 7b9d2c87c2b676b2713498b107eb03508dcc9e58 Mon Sep 17 00:00:00 2001 From: Nico Zanferrari Date: Wed, 24 Apr 2019 23:28:48 +0200 Subject: [PATCH 7/9] PY2 compatibility --- extras/build_web2py/build_web2py.py | 90 ++++++++++++++++++----------- 1 file changed, 55 insertions(+), 35 deletions(-) diff --git a/extras/build_web2py/build_web2py.py b/extras/build_web2py/build_web2py.py index 1734ab2c..0259fc7f 100644 --- a/extras/build_web2py/build_web2py.py +++ b/extras/build_web2py/build_web2py.py @@ -23,11 +23,21 @@ USAGE = """ build_web2py - make web2py Windows and MacOS binaries with pyinstaller Usage: - Install pyinstaller program, copy this file to web2py root folder and run: + Install the pyinstaller program, copy this file (plus web2py.ngm.spec and web2py.ngm_no_console.spec) + to web2py root folder and run: python build_py3.py - + + (tested with python 3.7 only) """ +BUILD_DEBUG = False +""" +If BUILD_DEBUG is set to False, no gluon modules will be embedded inside the binary web2py.exe. + Thus, you can easily update the build version by changing the gluon folder inside the resulting ZIP file. +In case of problem , set BUILD_DEBUG to True. Then all the gluon modules will be analyzed and embedded, too. + You can later analyze the .exe with 'pyi-archive_viewer web2py.exe' and then 'o PYZ-00.pyz' + in order to check for missing system modules to be manually inserted in the SPEC file + """ if len(sys.argv) != 1 or not os.path.isfile('web2py.py'): print(USAGE) @@ -66,64 +76,73 @@ python_version = sys.version_info[:3] if os_version == 'Windows': print("\nBuilding binary web2py for Windows\n") - # to make executable without GUI we need this trick - shutil.copy("web2py.py", "web2py_no_console.py") + if BUILD_DEBUG: # debug only + subprocess.call('pyinstaller --clean --icon=extras/icons/web2py.ico \ + --hidden-import=site-packages --hidden-import=gluon.packages.dal.pydal \ + --hidden-import=gluon.packages.yatl.yatl web2py.py') + zip_filename = 'web2py_win_debug' + else: # normal run + subprocess.call('pyinstaller --clean web2py.win.spec') + subprocess.call('pyinstaller --clean web2py.win_no_console.spec') + source_no_console = 'dist/web2py_no_console/' + files = 'web2py_no_console.exe' + shutil.move(os.path.join(source_no_console, files), 'dist') + shutil.rmtree(source_no_console) + shutil.rmtree('build') + zip_filename = 'web2py_win' - subprocess.run('pyinstaller --clean --icon=extras/icons/web2py.ico --hidden-import=gluon.packages.dal.pydal \ - --hidden-import=gluon.packages.yatl.yatl --hidden-import=site-packages web2py.py') - subprocess.run('pyinstaller -w --clean --icon=extras/icons/web2py.ico --hidden-import=gluon.packages.dal.pydal \ - --hidden-import=gluon.packages.yatl.yatl --hidden-import=site-packages web2py_no_console.py') - - # cleanup + move binary files to dist folder - os.unlink('web2py_no_console.py') - os.unlink('web2py_no_console.spec') source = 'dist/web2py/' for files in os.listdir(source): shutil.move(os.path.join(source, files), 'dist') - source2 = 'dist/web2py_no_console/' - files = 'web2py_no_console.exe' - shutil.move(os.path.join(source2, files), 'dist') shutil.rmtree(source) - shutil.rmtree(source2) os.unlink('dist/web2py.exe.manifest') - zip_filename = 'web2py_win' - bin_folder = 'dist' + + + bin_folders = ['dist',] elif os_version == 'Darwin': print("\nBuilding binary web2py for MacOS\n") - import subprocess - subprocess.call("pyinstaller --clean --windowed --icon=extras/icons/web2py.icns --hidden-import=gluon.packages.dal.pydal --hidden-import=gluon.packages.yatl.yatl \ - --hidden-import=site-packages --add-binary='/System/Library/Frameworks/Tk.framework/Tk':'tk' \ - --add-binary='/System/Library/Frameworks/Tcl.framework/Tcl':'tcl' web2py.py", shell=True) + if BUILD_DEBUG: #debug only + subprocess.call("pyinstaller --clean --icon=extras/icons/web2py.icns --hidden-import=gluon.packages.dal.pydal --hidden-import=gluon.packages.yatl.yatl \ + --hidden-import=site-packages --windowed web2py.py", shell=True) + zip_filename = 'web2py_osx_debug' + else: # normal run + subprocess.call("pyinstaller --clean web2py.mac.spec", shell=True) + # cleanup + move binary files to dist folder + #shutil.rmtree(os.path.join('dist', 'web2py')) + shutil.rmtree('build') + zip_filename = 'web2py_osx' - # cleanup + move binary files to dist folder - shutil.rmtree(os.path.join('dist', 'web2py')) - shutil.rmtree('build') - - zip_filename = 'web2py_osx' - bin_folder = (os.path.join('dist', 'web2py.app/Contents/MacOS')) - -print("\nWeb2py binary successfully built!\n") + shutil.move((os.path.join('dist', 'web2py')),(os.path.join('dist', 'web2py_cmd'))) + bin_folders = [(os.path.join('dist', 'web2py.app/Contents/MacOS')), (os.path.join('dist', 'web2py_cmd'))] + print("\nWeb2py binary successfully built!\n") # add data_files for req in ['CHANGELOG', 'LICENSE', 'VERSION']: - shutil.copy(req, os.path.join(bin_folder, req)) + for bin_folder in bin_folders: + shutil.copy(req, os.path.join(bin_folder, req)) # cleanup unuseful binary cache for dirpath, dirnames, files in os.walk('.'): if dirpath.endswith('__pycache__'): print('Deleting cached binary directory : %s' % dirpath) shutil.rmtree(dirpath) - +for dirpath, dirnames, files in os.walk('.'): + for file in files: + if file.endswith('.pyc'): + print('Deleting cached binary file : %s' % file) + os.unlink(os.path.join(dirpath, file)) + print("\nPreparing package ...") # misc for folders in ['gluon', 'extras', 'site-packages', 'scripts', 'applications', 'examples', 'handlers']: - shutil.copytree(folders, os.path.join(bin_folder, folders)) -os.mkdir(os.path.join(bin_folder, 'logs')) -os.unlink('web2py.spec') + for bin_folder in bin_folders: + shutil.copytree(folders, os.path.join(bin_folder, folders)) + if not os.path.exists(os.path.join(bin_folder, 'logs')): + os.mkdir(os.path.join(bin_folder, 'logs')) # create a web2py folder & copy dist's files into it @@ -139,6 +158,7 @@ zipf.close() shutil.rmtree('zip_temp') shutil.rmtree('dist') + print("Your binary version of web2py can be found in " + \ zip_filename + ".zip") print("You may extract the archive anywhere and then run web2py without worrying about dependency") From b79b5951f83eab37031db6274074a16d5ed3d678 Mon Sep 17 00:00:00 2001 From: Nico Zanferrari Date: Wed, 24 Apr 2019 23:35:44 +0200 Subject: [PATCH 8/9] Update docs --- extras/build_web2py/build_web2py.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/extras/build_web2py/build_web2py.py b/extras/build_web2py/build_web2py.py index 0259fc7f..59aeb76d 100644 --- a/extras/build_web2py/build_web2py.py +++ b/extras/build_web2py/build_web2py.py @@ -21,14 +21,13 @@ import platform USAGE = """ build_web2py - make web2py Windows and MacOS binaries with pyinstaller - Usage: - Install the pyinstaller program, copy this file (plus web2py.ngm.spec and web2py.ngm_no_console.spec) + Install the pyinstaller program, copy this file (plus web2py.*.spec files) to web2py root folder and run: python build_py3.py - (tested with python 3.7 only) + (tested with python 3.7.3 and 2.7.16 with PyInstaller 3.4) """ BUILD_DEBUG = False """ From 9769314f01d2c2edcc3e65233e1ed382c0be3b75 Mon Sep 17 00:00:00 2001 From: Nico Zanferrari Date: Wed, 24 Apr 2019 23:38:06 +0200 Subject: [PATCH 9/9] PY2 compatibility --- extras/build_web2py/web2py.mac.spec | 52 +++++++++++++++++++ extras/build_web2py/web2py.win.spec | 44 ++++++++++++++++ .../build_web2py/web2py.win_no_console.spec | 44 ++++++++++++++++ 3 files changed, 140 insertions(+) create mode 100644 extras/build_web2py/web2py.mac.spec create mode 100644 extras/build_web2py/web2py.win.spec create mode 100644 extras/build_web2py/web2py.win_no_console.spec diff --git a/extras/build_web2py/web2py.mac.spec b/extras/build_web2py/web2py.mac.spec new file mode 100644 index 00000000..1a1894b0 --- /dev/null +++ b/extras/build_web2py/web2py.mac.spec @@ -0,0 +1,52 @@ +# -*- mode: python -*- + +block_cipher = None + + +a = Analysis(['web2py.py'], + pathex=['.'], + binaries=[('/System/Library/Frameworks/Tk.framework/Tk', 'tk'), ('/System/Library/Frameworks/Tcl.framework/Tcl', 'tcl')], + datas=[], + hiddenimports=['site-packages', 'cgi', 'cgitb', 'code', 'concurrent', 'concurrent.futures', + 'concurrent.futures._base', 'concurrent.futures.process', 'concurrent.futures.thread', 'configparser', 'cProfile', 'csv', 'ctypes.wintypes', + 'email.mime', 'email.mime.base', 'email.mime.multipart', 'email.mime.nonmultipart', 'email.mime.text', 'html.parser', 'http.cookies', + 'ipaddress', 'imaplib', 'imp', 'json', 'json.decoder', 'json.encoder', 'json.scanner', 'logging.config', 'logging.handlers', 'profile', 'pstats', + 'psycopg2', 'psycopg2._ipaddress', 'psycopg2._json', 'psycopg2._range', 'psycopg2.extensions', 'psycopg2.extras', 'psycopg2.sql', + 'psycopg2.tz', 'pyodbc', 'python-ldap', 'rlcompleter', 'sched', 'site', 'smtplib', 'sqlite3', 'sqlite3.dbapi2', 'sqlite3.dump', 'timeit', 'tkinter', + 'tkinter.commondialog', 'tkinter.constants', 'tkinter.messagebox', 'uuid', 'win32evtlogutil', 'wsgiref', + 'wsgiref.handlers', 'wsgiref.headers', 'wsgiref.simple_server', 'wsgiref.util', 'xml.dom', 'xml.dom.NodeFilter', 'xml.dom.domreg', + 'xml.dom.expatbuilder', 'xml.dom.minicompat', 'xml.dom.minidom', 'xml.dom.pulldom', 'xml.dom.xmlbuilder', 'xmlrpc.server'], + hookspath=[], + runtime_hooks=[], + excludes=['gluon'], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False) +pyz = PYZ(a.pure, a.zipped_data, + cipher=block_cipher) +exe = EXE(pyz, + a.scripts, + [], + exclude_binaries=True, + name='web2py', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=False, + icon='extras/icons/web2py.icns') +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=True, + name='web2py') +app = BUNDLE(coll, + name='web2py.app', + icon='extras/icons/web2py.icns', + bundle_identifier=None, + info_plist={ + 'NSPrincipleClass': 'NSApplication', + 'NSAppleScriptEnabled': False}) diff --git a/extras/build_web2py/web2py.win.spec b/extras/build_web2py/web2py.win.spec new file mode 100644 index 00000000..6dce1ebf --- /dev/null +++ b/extras/build_web2py/web2py.win.spec @@ -0,0 +1,44 @@ +# -*- mode: python -*- + +block_cipher = None + + +a = Analysis(['web2py.py'], + pathex=['.'], + binaries=[], + datas=[], + hiddenimports=['site-packages', 'cgi', 'cgitb', 'code', 'concurrent', 'concurrent.futures', + 'concurrent.futures._base', 'concurrent.futures.process', 'concurrent.futures.thread', 'configparser', 'csv', 'ctypes.wintypes', + 'email.mime', 'email.mime.base', 'email.mime.multipart', 'email.mime.nonmultipart', 'email.mime.text', 'html.parser', 'http.cookies', + 'ipaddress', 'imp', 'json', 'json.decoder', 'json.encoder', 'json.scanner', 'logging.config', 'logging.handlers', 'profile', 'pstats', + 'psycopg2', 'psycopg2._ipaddress', 'psycopg2._json', 'psycopg2._range', 'psycopg2.extensions', 'psycopg2.extras', 'psycopg2.sql', + 'psycopg2.tz', 'pyodbc', 'python-ldap', 'rlcompleter', 'sched', 'site', 'smtplib', 'sqlite3', 'sqlite3.dbapi2', 'sqlite3.dump', 'timeit', 'tkinter', + 'tkinter.commondialog', 'tkinter.constants', 'tkinter.messagebox', 'uuid', 'win32con', 'win32evtlogutil', 'winerror', 'wsgiref', + 'wsgiref.handlers', 'wsgiref.headers', 'wsgiref.simple_server', 'wsgiref.util', 'xml.dom', 'xml.dom.NodeFilter', 'xml.dom.domreg', + 'xml.dom.expatbuilder', 'xml.dom.minicompat', 'xml.dom.minidom', 'xml.dom.pulldom', 'xml.dom.xmlbuilder', 'xmlrpc.server'], + hookspath=[], + runtime_hooks=[], + excludes=['gluon'], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False) +pyz = PYZ(a.pure, a.zipped_data, + cipher=block_cipher) +exe = EXE(pyz, + a.scripts, + [], + exclude_binaries=True, + name='web2py', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=True , icon='extras\\icons\\web2py.ico') +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=True, + name='web2py') diff --git a/extras/build_web2py/web2py.win_no_console.spec b/extras/build_web2py/web2py.win_no_console.spec new file mode 100644 index 00000000..75932924 --- /dev/null +++ b/extras/build_web2py/web2py.win_no_console.spec @@ -0,0 +1,44 @@ +# -*- mode: python -*- + +block_cipher = None + + +a = Analysis(['web2py.py'], + pathex=['.'], + binaries=[], + datas=[], + hiddenimports=['site-packages', 'cgi', 'cgitb', 'code', 'concurrent', 'concurrent.futures', + 'concurrent.futures._base', 'concurrent.futures.process', 'concurrent.futures.thread', 'configparser', 'csv', 'ctypes.wintypes', + 'email.mime', 'email.mime.base', 'email.mime.multipart', 'email.mime.nonmultipart', 'email.mime.text', 'html.parser', 'http.cookies', + 'ipaddress', 'imp', 'json', 'json.decoder', 'json.encoder', 'json.scanner', 'logging.config', 'logging.handlers', 'profile', 'pstats', + 'psycopg2', 'psycopg2._ipaddress', 'psycopg2._json', 'psycopg2._range', 'psycopg2.extensions', 'psycopg2.extras', 'psycopg2.sql', + 'psycopg2.tz', 'pyodbc', 'python-ldap', 'rlcompleter', 'sched', 'site', 'smtplib', 'sqlite3', 'sqlite3.dbapi2', 'sqlite3.dump', 'timeit', 'tkinter', + 'tkinter.commondialog', 'tkinter.constants', 'tkinter.messagebox', 'uuid', 'win32con', 'win32evtlogutil', 'winerror', 'wsgiref', + 'wsgiref.handlers', 'wsgiref.headers', 'wsgiref.simple_server', 'wsgiref.util', 'xml.dom', 'xml.dom.NodeFilter', 'xml.dom.domreg', + 'xml.dom.expatbuilder', 'xml.dom.minicompat', 'xml.dom.minidom', 'xml.dom.pulldom', 'xml.dom.xmlbuilder', 'xmlrpc.server'], + hookspath=[], + runtime_hooks=[], + excludes=['gluon'], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False) +pyz = PYZ(a.pure, a.zipped_data, + cipher=block_cipher) +exe = EXE(pyz, + a.scripts, + [], + exclude_binaries=True, + name='web2py_no_console', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=False , icon='extras\\icons\\web2py.ico') +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=True, + name='web2py_no_console')