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
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
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
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
a = os.path.dirname(os.path.realpath(__file__))
il faut modifier le code ainsi
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
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)
"""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
#------------------------------------------------------------------------------
# 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
"""
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()
# 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 <Name> and
# uninstalling the service is done with the option --uninstall <Name>. The
# value for <Name> 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)