Outils pour utilisateurs

Outils du site


cx_freeze_pyqt4_windows

Diffusion de programmes PyQt4 autonomes sous Windows grâce à cx_freeze

[modernisation de la page le 9/8/2014]

Problématique

Faire un joli programme qui marche est une (bonne) chose, le diffuser en est une autre.

Il faut en effet que le PC du nouvel utilisateur dispose déjà des programmes nécessaires de Python, de PyQt4 (, etc…), avec les bonnes versions, sans lesquels votre programme ne fonctionnera pas (et vous porterez le chapeau…).

Alors, il est important de pouvoir diffuser votre programme, accompagné de toutes les bibliothèques qui lui permettront de fonctionner correctement, même si l'utilisateur n'a rien installé du tout! C'est l'objet du présent tuto.

On va utiliser le logiciel cx_freeze pour cela (http://cx-freeze.sourceforge.net/). Il a l'avantage d'être multiplateforme (Windows-Linux). Il faudra bien sûr l'utiliser sous Windows pour avoir la version Windows et sous Linux pour avoir la version Linux, car les bibliothèques ne sont pas les mêmes.

On va étudier ici l'utilisation de cx_freeze sur Windows (XP, Vista, 7 ou 8).

Utilisation de cx_freeze sur Windows

Pour qu'un programme PyQt4 fonctionne sous Windows, il faut peu de choses:

Pour Windows, ces 2 logiciels existent sous forme de binaires, et sont donc très faciles à installer.

Et c'est tout!

Installation de cx_freeze sur Windows

cx_freeze est aussi présenté sous forme de binaire facile à installer.

Téléchargement ici: http://cx-freeze.sourceforge.net/ (choisir de préférence une verson >=4.2.1).

Voilà, vous avez le cx_freeze le plus récent!

Configuration du cx_freeze pour PyQt4

Il existe plusieurs façons d'utiliser cx_freeze, et j'ai choisi l'utilisation de setup.py (même méthode que pour py2exe sous Windows). L'avantage est que, si on débrouille bien, le même setup.py pourra être utilisé sous Windows et sous Linux.

J'utilise un “modèle” de setup.py que je fais évoluer au gré de mes programmes, et, il faut bien le dire, au gré de mes (nombreux et longs) tâtonnements.

Voilà les particularités des options de setup.py pour des programmes complexes PyQt4:

  • Si votre programme comporte des modules à intégrer, placés dans des sous-répertoires, il faut citer ces sous-répertoires dans l'option “path”.
  • les fichiers et répertoires à copier en plus (ex: les fichiers d'aide) sont mentionnés avec l'option “include_files”. Chaque copie demandée est un tuple composé du chemin source (absolu ou relatif) et du chemin destination (toujours relatif). Par exemple pour recopier un répertoire comportant les fichiers d'aide: “include_files”: [(“aide”, “aide”)].
  • c'est le cas pour les plugins de PyQt4 (y compris les drivers sql de QtSql)
  • c'est aussi le cas pour les fichiers de traduction, pour avoir “oui” ou “non” au lieu de “yes” ou “no” dans les messagebox.

Et c'est tout: avec ça, vous récupérez un répertoire avec tout ce qu'il faut pour une exécution “standalone” sur un Windows dans lequel il n'y a ni Python, ni PyQt4!

Voilà un exemple.

On a un projet dans un répertoire. Ce répertoire contiendra:

  • programme1.pyw
  • programme2.pyw
  • setup.py
  • cx_freeze.bat

Les 2 programmes programme1.pyw sont identiques, sauf le nom de leur fichier et le titre de leur fenêtre. Cela permettra de tester la construction de 2 fenêtres différentes avec un seul setup.

Voilà le code du 1er programme programme1.pyw:


#!/usr/bin/python
# -*- coding: utf-8 -*-
# Python 3
 
import sys
from PyQt4 import QtCore, QtGui
 
#############################################################################
class Fenetre(QtGui.QWidget):
 
    def __init__(self, parent=None):
        super(Fenetre, self).__init__(parent)
 
        self.resize(400, 300)
        self.setWindowTitle("Programme 1")
 
        self.bouton = QtGui.QPushButton(u"bouton", self)
        self.bouton.clicked.connect(self.clictest)
 
        posit = QtGui.QGridLayout()
        posit.addWidget(self.bouton, 0, 0)
        self.setLayout(posit)
 
    def clictest(self):
        QtGui.QMessageBox.information(self,
            u"Pour information",
            u"Le bouton a été cliqué")
 
    def closeEvent(self, event):
        reponse = QtGui.QMessageBox.question(self, 
            "Fermeture de la fenêtre",
            "Etes-vous sûr(e) de vouloir quitter?", 
            QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
 
        if reponse == QtGui.QMessageBox.Yes:
            event.accept()
        else:
            event.ignore()
 
#############################################################################
if __name__ == "__main__":
 
    app = QtGui.QApplication(sys.argv)
 
    # mettre la même icone pour toutes les fenêtres de l'application
    #app.setWindowIcon(QtGui.QIcon('icone.png'))
 
    # mettre le style des fenêtres    
    style = 'Plastique'
    app.setStyle(QtGui.QStyleFactory.create(style))
    app.setPalette(QtGui.QApplication.style().standardPalette())
 
    # pour assurer la traduction automatique du conversationnel à la locale
    locale = QtCore.QLocale.system().name()
    translator = QtCore.QTranslator ()
    if getattr(sys, 'frozen', False):
        # exécution de la version exécutable avec cx_freeze (ou équivalent): 
        #=>les fichiers de traduction doivent se trouver dans "translations"
        reptrad = "translations"
    else:
        # exécution par l'interpréteur normal
        reptrad = QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.TranslationsPath)
    translator.load("qt_" + locale, reptrad)
    app.installTranslator(translator)
 
    #
    fen = Fenetre()
    fen.setAttribute(QtCore.Qt.WA_DeleteOnClose)
    fen.show()
    sys.exit(app.exec_())

le fichier cx_freeze.bat ne servira qu“à lancer le traitement par cx_freeze. On prendra l'explorateur Windows, et on naviguera jusqu'au répertoire du projet, et on double-cliquera sur ce nom. Une console s'affichera avec toutes les lignes crées par cx_freeze: on vérifiera l'absence d'erreur, et sinon, on saura modifier le setup pour corriger. Voilà le contenu du cx_freeze.bat:


python setup.py build
pause

Le setup.py contient les instructions destinées au traitement par cx_freeze.

Pour que le setup.py puisse, sans modification, servir sous Windows et sous Linux, il faudra utiliser à plusieurs endroits les tests habituels de plateforme (if sys.platform == “win32” ou if sys.platform == “linux2”) puisque certaines adresses de bibliothèques seront différentes. Peut-être que des adaptations mineures permettraient l'utilisation sur Mac OSX et plus (Solaris, …) mais je n'ai pas essayé.


#!/usr/bin/python
# -*- coding: utf-8 -*-
# Python 3
 
"""
Pas d'accent dans le setup.py, ni dans la description, ni dans les commentaires
 
Icone sous Windows: il faut:
=> un xxx.ico pour integration dans le exe, avec "icon=xxx.ico"
=> un xxx.png pour integration avec PyQt4 + demander la copie avec includefiles.  
"""
 
import sys, os
from cx_Freeze import setup, Executable
 
#############################################################################
# preparation des options 
 
# chemins de recherche des modules
path = sys.path
 
# options d'inclusion/exclusion des modules
includes = []
excludes = []
packages = []
 
# copier les fichiers et/ou repertoires et leur contenu:
includefiles = [] # recopier l'icone.png de la fenetre ici
 
if sys.platform == "linux2":
    includefiles += [(r"/usr/lib/qt4/plugins","plugins")]
    includefiles += [(r"/usr/share/qt4/translations","translations")]
elif sys.platform == "win32":
    includefiles += [(r"E:\Python34\Lib\site-packages\PyQt4\plugins","plugins")]
    includefiles += [(r"E:\Python34\Lib\site-packages\PyQt4\translations","translations")]
else:
    pass
 
binpathincludes = []
if sys.platform == "linux2":
    # Linux: pour que les bibliotheques de /usr/lib soient copiees aussi
    binpathincludes += ["/usr/lib"]
 
# construction du dictionnaire des options
options = {"path": path,
           "includes": includes,
           "excludes": excludes,
           "packages": packages,
           "include_files": includefiles,
           "bin_path_includes": binpathincludes,
           "create_shared_zip": False,
           "include_in_shared_zip": False,
           "compressed": False
           }
 
# pour inclure sous Windows les dll system necessaires
if sys.platform == "win32":
    options["include_msvcr"] = True
 
#############################################################################
# preparation des cibles
base = None
if sys.platform == "win32":
    # plateforme Windows
    base = "Win32GUI" # pour les programmes graphiques
    #base = "Console" # pour les programmes en console
 
icone = None
if sys.platform == "win32":
    icone = None # mettre ici l'icone.ico pour integration dans l'exe
 
cible_1 = Executable(
    script = "programme1.pyw",
    base = base,
    compress = False,
    copyDependentFiles = True,
    appendScriptToExe = True,
    appendScriptToLibrary = False,
    icon = icone
    )
 
cible_2 = Executable(
    script = "programme2.pyw",
    base = base,
    compress = False,
    copyDependentFiles = True,
    appendScriptToExe = True,
    appendScriptToLibrary = False,
    icon = icone
    )
#############################################################################
# creation du setup
setup(
    name = "Programme",
    version = "1.00",
    description = "essai",
    author = "Tyrtamos",
    options = {"build_exe": options},
    executables = [cible_1, cible_2]
    )

Une fois le setup.py écrit et mis dans la racine du répertoire du programme à traiter, on double-clique sur cx_freeze.bat. Une console s'affiche, le traitement se déroule et les programmes ”.exe“ se construisent dans le sous-répertoire “build\exe.win32-3.4”. Nous auront ici programme1.exe et programme2.exe, accompagnés de toutes les bibliothèques nécessaires.

Dans tous les cas, il faudra lire attentivement la longue liste du traitement dans la console pour vérifier qu'il n'y a pas d'erreur. Sinon, il faudra modifier le setup.py jusqu'à ce que les erreurs aient disparu.

Il ne reste plus qu'à essayer de lancer les programmes ”.exe“. Ce serait bien de le faire sur un autre PC qui n'a pas Python. Enfin, dans un premier temps, il faut lancer les programmes ”.exe“ à partir de la console pour avoir tous les éventuels messages d'erreur.

Si c'est ok, on peut alors le lancer directement avec une icône (à créer) du bureau.

On peut aussi aller plus loin en encapsulant les fichiers obtenus dans un programme d'installation comme “innosetup” (http://www.jrsoftware.org/isinfo.php). Vous obtiendrez alors un programme diffusé par un seul fichier qui s'installera et se désinstallera comme n'importe quel logiciel Windows: les utilisateurs se sauront même pas que c'est du Python…


Amusez-vous bien!

cx_freeze_pyqt4_windows.txt · Dernière modification: 2014/08/09 08:01 par tyrtamos

Outils de la page