#!/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.1.5"

# 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.1.1 - 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
      )

