le module cx_freeze ******************* Ce module est téléchargeable sur http://sourceforge.net/projects/cx-freeze/ Les principaux avantages de ce module est: - support de python 3 - création possible d'un installateur - création possible d'un service (uniquement avec python2 ... dommage) le principe est assez simple: - on génére un fichier setup.py qui contient les informations de compilation - on génère l'exe Création d'un exécutable ou d'un installateur --------------------------------------------- :: python setup.py build - ou bien l'installateur :: python setup.py bdist_msi un exemple simple de fichier setup.py .. code-block:: python import sys from cx_Freeze import setup, Executable setup( name = "foo", version = "0.1", description = "My application!", executables = [Executable("foo.py")]) il faut parfois rajouter des modules externes et gérer les guis .. code-block:: python import sys from cx_Freeze import setup, Executable # Dependencies are automatically detected, but it might need fine tuning. build_exe_options = {"packages": ["os"], "excludes": ["tkinter"]} # GUI applications require a different base on Windows (the default is for a # console application). base = None if sys.platform == "win32": base = "Win32GUI" setup( name = "guifoo", version = "0.1", description = "My GUI application!", options = {"build_exe": build_exe_options}, executables = [Executable("guifoo.py", base=base)]) Concernant les dépendances, il faut noter que les dépendances aux format *egg* ne sont pas bien pris en charge (ex colorconsole, ...). On obtient alors une liste de module en erreur j'ai construit un script qui permet de décompresser à la voler ces *egg* afin de les intégrer correctement .. code-block:: python import sys import shutil import os import os.path import zipfile from cx_Freeze import setup, Executable def manage_eggs(eggs, eggs_unzip_path): if len(eggs) > 0: if os.path.isdir(eggs_unzip_path): shutil.rmtree(eggs_unzip_path) os.mkdir(eggs_unzip_path) list_eggs = [] for k in sys.path: if os.path.isfile(k) and os.path.splitext(k)[1] == '.egg': list_eggs.append(k) if len(list_eggs) == 0: raise RuntimeError("not found module %s" % (','.join(eggs))) for i in eggs: egg = [item for item in list_eggs if item.find(i) > 0 ] if len(egg) == 0: raise RuntimeError("not found module %s" % (','.join(eggs))) with zipfile.ZipFile(egg[0], "r") as z: z.extractall(eggs_unzip_path) sys.path.insert(1,eggs_unzip_path) eggs=['colorconsole'] eggs_unzip_path = 'libeggs' manage_eggs(eggs, os.path.join(os.path.split(__file__)[0],eggs_unzip_path)) setup( name='Glances', version='1.7.2', description="A cross-platform curses-based monitoring tool", long_description=open('README.rst').read(), author='Nicolas Hennion', author_email='nicolas@nicolargo.com', url='https://github.com/nicolargo/glances', license="LGPL", keywords="cli curses monitoring system", executables = [Executable("glances/glances.py")] ) if len(eggs) > 0: shutil.rmtree(eggs_unzip_path) Il faut regarder du côté de la documentation pour trouver plein d'argument très utile http://cx-freeze.readthedocs.org/en/latest/distutils.html#distutils Il est aussi possible d'avori des problèmes de compilation si le code utilise par exemple .. code-block:: python a = os.path.dirname(os.path.realpath(__file__)) il faut modifier le code ainsi .. code-block:: python def we_are_frozen(): # All of the modules are built-in to the interpreter, e.g., by py2exe return hasattr(sys, "frozen") def module_path(): encoding = sys.getfilesystemencoding() if we_are_frozen(): return os.path.dirname(os.path.realpath(unicode(sys.executable, encoding))) return os.path.dirname(os.path.realpath(unicode(__file__, encoding))) a = module_path() Il est aussi possible de créer par le msi des raccourcit dans le dossier Programmes de windows Voiçi un exemple concret .. code-block:: python import sys import shutil import os import os.path import zipfile from cx_Freeze import * def manage_eggs(eggs, eggs_unzip_path): if len(eggs) > 0: if os.path.isdir(eggs_unzip_path): shutil.rmtree(eggs_unzip_path) os.mkdir(eggs_unzip_path) list_eggs = [] for k in sys.path: if os.path.isfile(k) and os.path.splitext(k)[1] == '.egg': list_eggs.append(k) if len(list_eggs) == 0: raise RuntimeError("not found module %s" % (','.join(eggs))) for i in eggs: egg = [item for item in list_eggs if item.find(i) > 0 ] if len(egg) == 0: raise RuntimeError("not found module %s" % (','.join(eggs))) with zipfile.ZipFile(egg[0], "r") as z: z.extractall(eggs_unzip_path) sys.path.insert(1,eggs_unzip_path) eggs=['colorconsole'] eggs_unzip_path = 'libeggs' manage_eggs(eggs, os.path.join(os.path.split(__file__)[0],eggs_unzip_path)) directory_table = [ ("ProgramMenuFolder", # Directory_ "TARGETDIR", # Directory_Parent ".:Programs", # DefaultDir ), ("GlancesMenuFolder", # Directory_ "ProgramMenuFolder", # Directory_Parent "Glances", # DefaultDir ) ] shortcut_table = [ ("GlancesExe", # Shortcut "GlancesMenuFolder", # Directory_ "glances", # Name "TARGETDIR", # Component_ "[TARGETDIR]glances.exe", # Target None, # Arguments None, # Description None, # Hotkey None, # Icon None, # IconIndex None, # ShowCmd 'TARGETDIR' # WkDir ), ("GlancesExeServer", # Shortcut "GlancesMenuFolder", # Directory_ "glances server", # Name "TARGETDIR", # Component_ "[TARGETDIR]glances.exe", # Target "-s", # Arguments None, # Description None, # Hotkey None, # Icon None, # IconIndex None, # ShowCmd 'TARGETDIR' # WkDir ) ] msi_data = {"Directory": directory_table, "Shortcut": shortcut_table} bdist_msi_options = {'data': msi_data} setup( name='Glances', version='1.7.2', description="A cross-platform curses-based monitoring tool", long_description=open('README.rst').read(), author='Nicolas Hennion', author_email='nicolas@nicolargo.com', url='https://github.com/nicolargo/glances', license="LGPL", keywords="cli curses monitoring system", options = { "bdist_msi": bdist_msi_options, 'build_exe': {'icon':'glances.ico'} }, executables = [Executable("glances/glances.py", icon = "glances.ico",)] ) if len(eggs) > 0: shutil.rmtree(eggs_unzip_path) .. note:: il existe comme dossier spécifique windows pour les raccourcits: DesktopFolder, StartupFolder,StartMenuFolder,ProgramMenuFolder, SystemFolder Pour voir ce qu'il est possible d'ajouter dans un MSI vous pouvez utiliser MSI_Analyser et le msi d'apache par exemple Création d'un service --------------------- Il faut pour cela avoir sur sa machine le module Cx_Logging (http://cx-logging.sourceforge.net/) il faut aussi avoir le fichier cx_Thread (modifié par mes soins) .. code-block:: python """Defines methods for managing threads, events and queues.""" #import cx_Exceptions import cx_Logging import sys import thread import time class Thread(object): """Base class for threads which is a little more lightweight than the threading module exports and allows for finer control.""" def __init__(self, function, *args, **keywordArgs): self.function = function self.args = args self.keywordArgs = keywordArgs self.started = False self.stopped = False self.errorObj = None self.event = None self.name = None def OnThreadEnd(self): """Called when the thread is ended. Override in child classes.""" cx_Logging.Info("thread %r ending", self.name) def OnThreadStart(self): """Called when the thread is started. Override in child classes.""" cx_Logging.Info("thread %r starting", self.name) def Start(self, loggingState = None): """Start the thread.""" self.started = True self.stopped = False self.errorObj = None thread.start_new_thread(self._Run, (loggingState,)) def _Run(self, loggingState): """Execute the function associated with the thread.""" try: if loggingState is not None: cx_Logging.SetLoggingState(loggingState) self.OnThreadStart() try: self.function(*self.args, **self.keywordArgs) except: #self.errorObj = cx_Exceptions.GetExceptionInfo(*sys.exc_info()) #cx_Logging.LogException(self.errorObj) cx_Logging.Error("Thread %r terminating", self.name) finally: self.stopped = True self.OnThreadEnd() if self.event: self.event.Set() class Event(object): """Event class which permits synchronization between threads.""" def __init__(self): self.lock = thread.allocate_lock() self.isSet = False self.waiters = [] def Clear(self): """Clear the flag.""" self.lock.acquire() self.isSet = False self.lock.release() def Set(self): """Set the flag and notify all waiters of the event.""" self.lock.acquire() self.isSet = True if self.waiters: for waiter in self.waiters: waiter.release() self.waiters = [] self.isSet = False self.lock.release() def Wait(self): """Wait for the flag to be set and immediately reset it.""" self.lock.acquire() if self.isSet: self.isSet = False self.lock.release() else: waiterLock = thread.allocate_lock() waiterLock.acquire() self.waiters.append(waiterLock) self.lock.release() waiterLock.acquire() class Queue(object): """Light weight implementation of stacks and queues.""" def __init__(self): self.lock = thread.allocate_lock() self.queueEvent = Event() self.items = [] def QueueItem(self, item): """Add an item to end of the list of items (for queues).""" self.lock.acquire() self.items.append(item) self.lock.release() self.queueEvent.Set() def PopItem(self, returnNoneIfEmpty=False): """Get the next item from the beginning of the list of items, optionally returning None if nothing is found.""" self.lock.acquire() while not self.items: self.lock.release() if returnNoneIfEmpty: return None self.queueEvent.Wait() self.lock.acquire() item = self.items.pop(0) self.lock.release() return item def PushItem(self, item): """Add an item to the beginning of the list of items (for stacks).""" self.lock.acquire() self.items.insert(0, item) self.lock.release() self.queueEvent.Set() class ResourcePool(object): """Implements a pool of resources.""" def __init__(self, maxResources, newResourceFunc): self.lock = thread.allocate_lock() self.poolEvent = Event() self.freeResources = [] self.busyResources = [] self.maxResources = maxResources self.newResourceFunc = newResourceFunc def Destroy(self): """Destroy the resource pool, this blocks until all resources are returned to the pool for destruction.""" self.lock.acquire() self.freeResources = [] self.maxResources = 0 self.lock.release() while self.busyResources: self.poolEvent.Wait() def Get(self): """Gets a resource form the pool, creating new resources as necessary. The calling thread will block until a resource is available, if necessary.""" resource = None self.lock.acquire() while resource is None: try: if self.freeResources: resource = self.freeResources.pop() elif len(self.busyResources) < self.maxResources: resource = self.newResourceFunc() elif not self.maxResources: raise "No resources not available." else: self.lock.release() self.poolEvent.Wait() self.lock.acquire() except: if self.lock.locked(): self.lock.release() raise self.busyResources.append(resource) self.lock.release() return resource def Put(self, resource, addToFreeList = True): """Put a resource back into the pool.""" self.lock.acquire() try: index = self.busyResources.index(resource) del self.busyResources[index] if self.maxResources and addToFreeList: self.freeResources.append(resource) finally: self.lock.release() self.poolEvent.Set() class Timer(Thread): """Operates a timer.""" def __init__(self, timeInSeconds): Thread.__init__(self, time.sleep, timeInSeconds) il faut par la suite 3 fichiers: - Config.py - ServiceHandler.py (notre service) - setup.py config.py .. code-block:: python #------------------------------------------------------------------------------ # Config.py # This file defines information about the service. The first four # attributes are expected to be defined and if they are not an exception will # be thrown when attempting to create the service: # # NAME # the name to call the service with one %s place holder that will be used # to identify the service further. # # DISPLAY_NAME # the value to use as the display name for the service with one %s place # holder that will be used to identify the service further. # # MODULE_NAME # the name of the module implementing the service. # # CLASS_NAME # the name of the class within the module implementing the service. This # class should accept no parameters in the constructor. It should have a # method called "Initialize" which will accept the configuration file # name. It should also have a method called "Run" which will be called # with no parameters when the service is started. It should also have a # method called "Stop" which will be called with no parameters when the # service is stopped using the service control GUI. # # DESCRIPTION # the description of the service (optional) # # AUTO_START # whether the service should be started automatically (optional) # # SESSION_CHANGES # whether the service should monitor session changes (optional). If # True, session changes will call the method "SessionChanged" with the # parameters sessionId and eventTypeId. #------------------------------------------------------------------------------ NAME = "cx_FreezeSampleService%s" DISPLAY_NAME = "cx_Freeze Sample Service - %s" MODULE_NAME = "ServiceHandler" CLASS_NAME = "Handler" DESCRIPTION = "Sample service description" AUTO_START = False SESSION_CHANGES = False ServiceHandler.py .. code-block:: python """ Implements a simple service using cx_Freeze. This sample makes use of cx_PyGenLib (http://cx-pygenlib.sourceforge.net) and cx_Logging (http://cx-logging.sourceforge.net). See below for more information on what methods must be implemented and how they are called. """ import cx_Logging import cx_Threads import sys class Handler(object): # no parameters are permitted; all configuration should be placed in the # configuration file and handled in the Initialize() method def __init__(self): cx_Logging.Info("creating handler instance") self.stopEvent = cx_Threads.Event() # called when the service is starting def Initialize(self, configFileName): cx_Logging.Info("initializing: config file name is %r", configFileName) # called when the service is starting immediately after Initialize() # use this to perform the work of the service; don't forget to set or check # for the stop event or the service GUI will not respond to requests to # stop the service def Run(self): cx_Logging.Info("running service....") self.stopEvent.Wait() # called when the service is being stopped by the service manager GUI def Stop(self): cx_Logging.Info("stopping service...") self.stopEvent.Set() .. code-block:: python # A simple setup script for creating a Windows service. See the comments in the # Config.py and ServiceHandler.py files for more information on how to set this # up. # # Installing the service is done with the option --install and # uninstalling the service is done with the option --uninstall . The # value for is intended to differentiate between different invocations # of the same service code -- for example for accessing different databases or # using different configuration files. from cx_Freeze import setup, Executable buildOptions = dict(includes = ["ServiceHandler"]) executable = Executable("Config.py", base = "Win32Service", targetName = "cx_FreezeSampleService.exe") setup( name = "cx_FreezeSampleService", version = "0.1", description = "Sample cx_Freeze Windows serice", executables = [executable], options = dict(build_exe = buildOptions)) pour génerer l'executable :: python setup.py build par la suite pour installer le service (en mode Administrateur) :: cx_FreezeSampleService.exe --install tata pour le désinstaller :: cx_FreezeSampleService.exe --uninstall tata .. warning:: si lors du lancement du msi vous avez un message comme quoi le msi n'est pas "reading" il faut ajouter au repertoire du msi tout les droits pour tout le monde (il s'agit d'un problème de création de dossier temporaire qui bloque l'installation)