#!/usr/bin/env python ####################################### ####### CONFIGURATION OPTIONS ######### ####################################### # main binary Name NAME="skylive-ng" # list of main python scripts (use python list!) # WARNING: only the first one will be taken for OSX!!! BASESCRIPTS=["skylive-ng.py", "fitsopener.py", "proxyconf.py"] # version VERSION="0.2.0" # package description DESCRIPTION="Skylive Client" # Produce an optimized python bytecode (0,1,2) OPTIMIZE=0 # Append all modules to the binary created (bool) APPEND_TO_EXE=False # Compression (bool) COMPRESSION=True # Compile as service for windows # Use False for no services, a list ['Name', 'Name'] without extension WINSERVICES=False # Use windows console option instead of windows. Valid only if WINSERVICES is # False and WX_GUI_PROGRAM is True WINCONSOLE=False # Use False for no daemons, a list ['Name', 'Name'] without extension # This is cause if you need Windows Services, you can't specify # it in basescripts, so, it need to be separated also for Unixes UNIXDAEMONS=False # build also MSI installer under windows platforms WIN_BUILD_MSI=False # Have this program a pycard/wxpython gui? WX_GUI_PROGRAM=True ICON="ICONA.ico" # Can be False! OSX_ICON="ICONA.icns" # Run before to create the dmg, path relative to root of the tree OSX_PRE_EXECUTE=['scripts/osx_createwrapper.sh'] # Run before makeself on linux LINUX_PRE_EXECUTE=['scripts/linux_copygstreamer.sh'] # Files to be added to the root of the package (python list) ADD_FILES = ['LICENSE.txt','README.txt', ICON, 'skyliveng.desktop', 'skylive-science.directory', 'skylive-ng.exe.manifest', 'VERSION.txt'] AUTHOR="Skylive Staff" EMAIL="skylive@skylive.it" URL="http://www.skylive.it" LICENSE_TYPE="GPLv3" # Modules that we need to exclude from our executable EXCLUDES=['Tkinter'] ####################################### # Don't edit below this line # ####################################### # # TODO: # # Verificare la presenza dei requisiti # per la creazione dell'installer ed in generale # migliorare l'error management, magari anche con # un log file # # TODO: # # Aggiungere la gestione di installer multilingua # # TODO: # # Gestire sotto Linux anche eventuale creazione di device # per i chroot? # # # XXX TODO: # # Verificare come si comporta sotto altri sistemi, # come ad esempio BSD e/o Solaris # # XXX TODO: # # Gestire anche la creazione di pacchetti python classici? # # TODO: # # Creazione diretta anche di rpm e deb? # # TODO: # # gestione .app per OSX # # TODO: # # gestione 32/64bit e diverse architetture # # TODO: # # Cython support # ######################################## # FUTURE IMPROVEMENTS ######################################## # # - Use msilib to generate the MSI installer # # - Avoid the usage of ISS, implement an EXE generator # based on the bdist_wininst distutils command? # # - under Linux, reimplement makeself in pure python # # - under Linux, reimplement ldd ans strip in pure python # # - reimplement whole things as a python module # # - use a config file to set variables and/or package # description # ######################################## ######################################## # basic imports # ######################################## import platform import os from distutils.core import Command from distutils.command import clean as distutils_clean import sys, subprocess ######################################## # Setting variables and various things # ######################################## # This way we can run just ./setup.py to build # the installer if len(sys.argv) == 1: sys.argv.append('create') # Setting usefull paths curdir = os.path.abspath(os.path.dirname(__file__)) srcdir = os.path.join(curdir, 'src') distdir = os.path.join(curdir, 'dist') scriptdir = os.path.join(curdir, 'scripts') sys.path.append(srcdir) # Change current dir to the root of the sources os.chdir(curdir) print 'Working in dir %s' % curdir # Settings some lists of needed python packages to be included in our # package includes = ['encodings.ascii', 'encodings.utf_8', 'encodings.idna', 'encodings.iso8859_16'] includes += ['encodings.string_escape','Crypto.Hash.SHA', 'numpy', 'appcomponents.infohtmlwindow'] includes += ['appcomponents.ulist', 'appcomponents.image', 'encodings.latin_1', 'twisted.web.resource'] includes += ['appcomponents.container', 'pkg_resources'] languages = ['it', 'en'] for lang in languages: includes.append('skylive.lang.'+lang) # Put wx and pycard things in includes if WX_GUI_PROGRAM: pycardcomponents = [ 'button', 'staticbox', 'statictext', 'textarea', 'textfield', 'passwordfield', 'list', 'notebook', 'combobox', 'spinner', 'checkbox', 'bitmapcanvas', 'slider', 'gauge' ] pycardincludes = [] for component in pycardcomponents: pycardincludes.append('Pycard.components.'+component) includes += pycardincludes wxincludes = [] wxcomponents = [ '_core' ] for component in wxcomponents: wxincludes.append('wx.'+component) includes += wxincludes # assure that our source dir is included in the search path of included modules sys.path.append(srcdir) ######################################## # Utility functions # ######################################## def unix_shellcmd(cmd): """ Launch a subprocess in the unix shell and return stdout of the command (blocking!) """ ret = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, close_fds=True).communicate()[0] return ret.split("\n") def win_shellcmd(cmd): """ Launch a subprocess in win32 (blocking!) """ ret = subprocess.Popen(cmd) ret.wait() return def cxf_guipackagedata(base): """ Collect GUI resource files and gui Images to be copied in the package in the right format for the include_files option in cx_freeze Input: str(base) - base (absolute) path of the package Output: a list of tuples with (src, target) files where src is the absolute path of the source file, target is the relative path inside the package """ ret = [] imgpath = os.path.join(base, 'gui', 'img') guipath = os.path.join(base, 'gui') for root, dirs, files in os.walk(imgpath): if not '.svn' in root: for file in files: src = os.path.join(root, file) target = os.path.join(root.replace(base, '')[1:], file) ret.append((src, target)) for root, dirs, files in os.walk(guipath): if not '.svn' in root: for file in files: if file[-8:] == '.rsrc.py': src = os.path.join(root, file) target = os.path.join(root.replace(base, '')[1:], file) ret.append((src, target)) return ret def add_dataFiles(base, compiler='cx_freeze'): ret = [] if compiler == 'cx_freeze': for file in ADD_FILES: src = os.path.join(base, file) target = file ret.append((src, target)) elif compiler == 'py2exe' or compiler == 'py2app': srcs = [] for file in ADD_FILES: srcs.append(os.path.join(base, file)) ret.append(('.', srcs)) return ret def p2exe_guipackagedata(base): """ Collect GUI resource files and gui images to be copied in the package in the right format for the data_files option in py2exe """ files = cxf_guipackagedata(base) tdict = {} for src, target in files: if os.path.dirname(target) in tdict.keys(): tdict[os.path.dirname(target)].append(src) else: tdict[os.path.dirname(target)] = [src] ret = [] for target in tdict.keys(): ret.append((target, tdict[target])) return ret ######################################## # distutils commands # ######################################## class makeDmg(Command): """ Distutils command that take a .app application and make a DMG compressed image with the app and optionally a README, INSTALL and LICENSE files. """ user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): import shutil print 'Test if we have license, readme or install files' for name in os.listdir(srcdir): if os.path.isfile(os.path.join(srcdir, name)) \ and (name[:6].upper() == 'README' or name[:7].upper() == 'LICENSE' or name[:7].upper() == 'INSTALL'): dst = os.path.join(distdir, name) if not os.path.exists(dst): print 'Copying ', name shutil.copy(os.path.join(srcdir, name), os.path.join(distdir, name)) dmg = os.path.join(curdir, 'builds', 'osx', NAME+"-"+VERSION+"_OSX_universal.dmg") if os.path.exists(dmg): print 'Removing old dmg image' os.remove(dmg) print 'Creating new dmg image' volname = NAME+"-"+VERSION if len(OSX_PRE_EXECUTE) > 0: for ex in OSX_PRE_EXECUTE: unix_shellcmd(curdir+'/'+ex) cmd = "hdiutil create -srcfolder %s -format UDZO -imagekey zlib-level=9 -volname %s %s" \ % (distdir, volname, dmg) print unix_shellcmd(cmd)[0] print 'Installer ready in '+os.path.join(curdir, 'builds', 'osx')+' directory' class makeself(Command): """ Distutils command that take an open package directory created by cx_freeze and create a .run self extracting Linux/Posix executable installer with all libraryes and dependencies inside the package using standard tools like strip, ldd, makeself """ # TODO: Manage differencies between 32bit and 64bit systems user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): import shutil libdir=os.path.join(distdir, 'lib') os.mkdir(libdir) if LINUX_PRE_EXECUTE and len(LINUX_PRE_EXECUTE) > 0: print 'execute pre-installer script' for lcommand in LINUX_PRE_EXECUTE: print 'Executing '+lcommand+'...' unix_shellcmd(lcommand+" "+distdir) print 'OK' print 'Finding all needed dynamic libraries and stripping binaryes' bins = unix_shellcmd('find '+distdir+' -type f -name "*.so"') bins.append(os.path.join(distdir, NAME)) libs = [] for bin in bins: if bin: ldd = unix_shellcmd("ldd "+bin) if os.path.basename(bin) != NAME: # If we strip our binary we lose zipped content! unix_shellcmd("strip "+bin) for lib in ldd: if len(lib.split()) > 3: libs.append(lib.split()[2]) elif len(lib.split()) == 3: libs.append(os.path.join('/lib', lib.split()[0])) elif len(lib.split()) == 2: libs.append(lib.split()[0]) libs.sort() libs = list(set(libs)) print 'copying needed libraries' for lib in libs: try: shutil.copy(lib, libdir) unix_shellcmd("strip "+os.path.join(libdir, os.path.basename(lib))) except: pass print 'creating installer' shutil.copy(os.path.join(scriptdir, 'linux_install.sh'), distdir) os.chmod(os.path.join(distdir, 'linux_install.sh'), 0755) runname = NAME+"-"+VERSION+"_"+platform.system()+"_i386-x86_64_install.run" cmd = "makeself "+distdir+" "+runname cmd += ' "'+NAME+' for '+platform.system()+' '+VERSION+'" ./linux_install.sh' unix_shellcmd(cmd) shutil.move(os.path.join(curdir, runname), os.path.join(curdir, 'builds', 'linux32')) print 'remove dist directory' shutil.rmtree(distdir) print 'Installer ready in '+os.path.join(curdir, 'builds', 'linux32')+' directory' class makeWininstall(Command): """ Distutils command that get an open package created by py2exe and make an exe and an optional msi windows installer using InnoSetup """ # TODO: Manage differencies between 32bit and 64bit systems user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): print 'Creating .exe installer' destpath = os.path.join(curdir, 'builds', 'win32') fname = NAME+"-"+VERSION+"_Win32_install" inopath = os.path.join(curdir, 'scripts', 'ino.iss') win_shellcmd("ISCC /O%s /F%s %s" % (destpath, fname, inopath)) print '.exe installer ready in %s' % destpath if WIN_BUILD_MSI: licensefilertf = False print 'a .msi installer is requested. Try to build it' wixpath = os.getenv('WIX') if not wixpath: print 'WIX environment variable not set. Try to find the WIX path from the PATH environment variable' for path in os.getenv('PATH').split(';'): if os.path.exists(os.path.join(path, 'candle.exe')): wixpath = path break if not wixpath: print 'I can\'t find an usable WIX path. please set the WIX environment variable and re-execute '+sys.argv[0] else: print 'Checking for license file...' if os.path.exists(os.path.join(curdir, 'dist', 'license.rtf')): licensefilertf = True else: print 'RTF license file not found. Trying to generate it from the txt one' try: licensefile = LICENSE_FILE except: print 'LICENSE_FILE variable not set. Trying to autodetect one' for file in ['LICENSE', 'LICENSE.txt', 'license.txt']: if os.path.exists(os.path.join(curdir, 'dist', file)): licensefile = file break if not licensefile: print 'I can\'t find a suitable license file txt. Please supply one!' else: try: import PyRTF tfile = open(os.path.join(curdir, 'dist', licensefile), 'r') tfilecont = tfile.read() tfile.close() doc = PyRTF.Document() ss = doc.StyleSheet section = PyRTF.Section() doc.Sections.append(section) section.append(tfilecont) dr = PyRTF.Renderer() fout = open(os.path.join(curdir, 'dist', 'license.rtf'), 'wb') dr.Write(doc, fout) fout.close() licensefilertf = True print 'RTF License file successfuly generated' except: print 'PyRTF not installed. I can\'t generate the license file!' if licensefilertf: fname = NAME+"-"+VERSION+"_Win32_install.msi" wxspath = os.path.join(curdir, 'scripts', 'wix.wxs') candlepath = os.path.join(wixpath, 'candle.exe') print 'Compiling wxs with candle.exe' win_shellcmd("%s %s" % (candlepath, wxspath)) print 'Linking with light.exe' win_shellcmd("light.exe -out %s %s %s\wixui.wixlib -loc %s\WixUI_en-us.wxl" % ( os.path.join(destpath, fname), os.path.join(curdir, 'wix.wixobj'), wixpath, wixpath)) os.remove(os.path.join(curdir, 'wix.wixobj')) print '.msi installer ready in %s' % destpath class create(Command): """ Distutils command detecting the platform and run the right command sequence to produce installers. """ user_options = [] def get_sub_commands(self): plat = platform.system() print 'Platform detected: ', plat if plat == 'Linux': return ['build', 'makeself'] elif plat == 'Darwin': return ['py2app', 'makeDmg'] elif plat == 'Windows' or plat == 'Microsoft': return ['py2exe', 'makeWininstall'] return False # XXX Fare anche osx def initialize_options(self): pass def finalize_options(self): pass def run(self): print 'Creating right installer for this platform' commands = self.get_sub_commands() if commands: self.run_command('clean') for command in commands: self.run_command(command) else: print 'i don\'t know what to do, sorry!' class clean(distutils_clean.clean): """ Distutils command that override the default clean command performing more clean actions on the root path of the sources """ # XXX Deve rimuovere anche eventuali .c e .so/pyd da cython paths = [ 'MANIFEST', 'build', 'dist', 'wix.wixobj' ] def run(self): distutils_clean.clean.run(self) for path in self.paths: p = os.path.join(curdir, path) if os.path.exists(p): if os.path.isdir(p): for root, dirs, files in os.walk(p, topdown=False): for name in files: os.remove(os.path.join(root, name)) for name in dirs: os.rmdir(os.path.join(root, name)) os.rmdir(root) else: os.remove(p) ######################################## # Setting different dicts for # # distutils dependently of the # # platform # ######################################## # Options for all systems cmdclass = { 'clean': clean, 'create': create } # Options for Windows systems (with py2exe) if platform.system() == 'Windows' or platform.system() == 'Microsoft': from distutils.core import setup import py2exe if APPEND_TO_EXE: ZIPFILE=None includes+=['lxml._elementpath','lxml.ElementInclude'] setup_options = { 'py2exe': { 'includes':includes, 'excludes': EXCLUDES, 'optimize': OPTIMIZE, 'compressed': COMPRESSION } } extra_options = { 'data_files': p2exe_guipackagedata(srcdir)+add_dataFiles(srcdir, 'py2exe') } if APPEND_TO_EXE: extra_options['zipfile'] = None #extra_options['bundle_files'] = 1 setup_options['py2exe']['bundle_files'] = 1 service = [] console = [] # XXX gestire meglio le icone for bscript in BASESCRIPTS: console.append({ 'script': os.path.join(srcdir, bscript), 'icon_resource': [(1, os.path.join(srcdir, ICON))] }) if WINSERVICES: for bscript in WINSERVICES: service.append({ 'modules': 'src.'+bscript.replace('.py', ''), 'cmdline_style': 'pywin32' }) if len(service) > 0: extra_options['service'] = service if len(console) > 0: if WX_GUI_PROGRAM: if WINCONSOLE: extra_options['console'] = console else: extra_options['windows'] = console else: extra_options['console'] = console # Override build_exe command to get some DLL included origIsSytemDLL = py2exe.build_exe.isSystemDLL def isSystemDLL(path): if os.path.basename(path).lower() in ('gdiplus.dll', 'msvcp71.dll', 'msvcp90.dll', 'gdi32.dll') or \ os.path.basename(path).lower()[:5] in ('msvcp', 'msvcr') or \ os.path.basename(path).lower().endswith('pyd'): print str(path)+" INCLUDED" return 0 return origIsSytemDLL(path) py2exe.build_exe.isSystemDLL = isSystemDLL # Add specific command for windows installer cmdclass['makeWininstall'] = makeWininstall # Options for Mac OSX systems (with py2app) elif platform.system() == 'Darwin': from distutils.core import setup import py2app #cmdclass['makedmg'] = makedmg executables = [] for bscript in BASESCRIPTS: executables.append(os.path.join(srcdir, bscript)) if UNIXDAEMONS: for bscript in UNIXDAEMONS: executables.append(os.path.join(srcdir, bscript)) # py2app require the same format of py2exe py2app_guipackagedata = p2exe_guipackagedata # Multiple targets not (yet) supported by py2app # get only the first one! extra_options = { 'app': [executables[0]], 'setup_requires': ["py2app"], 'data_files': p2exe_guipackagedata(srcdir)+add_dataFiles(srcdir, 'py2app'), } setup_options = { 'py2app': { 'includes':includes, 'excludes': EXCLUDES, 'optimize': OPTIMIZE, 'plist': {}, 'compressed': COMPRESSION, 'strip': True, 'dist_dir': distdir, 'site_packages': True, 'semi_standalone': False } } if OSX_ICON: setup_options['py2app']['iconfile'] = os.path.join(srcdir, OSX_ICON) # Add specific command for creating a DMG Image cmdclass['makeDmg'] = makeDmg # Options for Linux (and maybe other posix?) systems (with cx_freeze) else: from cx_Freeze import setup, Executable cmdclass['makeself'] = makeself executables = [] for bscript in BASESCRIPTS: executables.append(Executable(os.path.join(srcdir, bscript))) if UNIXDAEMONS: for bscript in UNIXDAEMONS: executables.append(Executable(os.path.join(srcdir, bscript))) extra_options = { 'executables': executables, } CREATE_SHARED_LIB=True if APPEND_TO_EXE: CREATE_SHARED_LIB=False setup_options = { 'build': { 'build_exe': distdir }, 'build_exe': { 'optimize': OPTIMIZE, 'includes': includes, 'excludes': EXCLUDES, 'compressed': COMPRESSION, 'create_shared_zip': CREATE_SHARED_LIB, 'append_script_to_exe': APPEND_TO_EXE, 'icon': os.path.join(srcdir, ICON), 'include_files': cxf_guipackagedata(srcdir)+add_dataFiles(srcdir), } } ######################################## # Cython setup() declarations # ######################################## #if sys.argv[1] == 'create': # # XXX Qui inserire un controllo che verifica l'esistenza # di file .pyx e nel caso usa cython per compilare # #try: # from distutils.core import setup, Extension # from Cython.Distutils import build_ext #except: # print 'Cython compilation required, but Cython not installed!' # sys.exit(0) ######################################## # Distutils setup() declaration # ######################################## # TODO: Far configurare anche i classifiers setup( name = NAME, version = VERSION, description = DESCRIPTION, author = AUTHOR, author_email = EMAIL, url = URL, options = setup_options, classifiers = [ 'Developement Status :: 0.2.0 - Unstable', 'Environment :: Desktop GUI', 'Intended Audience :: Developers', 'Intended Audience :: End Users/Desktop', 'License :: '+LICENSE_TYPE, 'Operating System :: MacOS :: MacOS X', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX :: GNU/Linux', 'Programming Language :: Python', 'Topic :: Communications', ], cmdclass = cmdclass, **extra_options )