Nous allons voir comment définir les propriétés de l'exécutable généré via PyInstaller sous Windows.

Fichier de propriétés

Il y a quelques informations dans la documentation de PyInstaller à propos du fichier de configuration des propriétés de l'exécutable final : section Capturing Windows Version Data.
Mais ce n'est pas très clair. Pour faire simple, il est possible de spécifier un fichier de configuration (version resource file en angais) à PyInstaller pour personnaliser les informations de l'excutable comme le n° de version et une description.

Le fichier en question ressemble à ça (fichier properties.rc) :

VSVersionInfo(
  ffi=FixedFileInfo(
    filevers=(3, 1, 0, 0),
    prodvers=(3, 1, 0, 0),
    mask=0x3f,
    flags=0x0,
    OS=0x40004,
    fileType=0x1,
    subtype=0x0,
    date=(0, 0)
    ),
  kids=[
    StringFileInfo(
      [
      StringTable(
        u'040904B0',
        [StringStruct(u'CompanyName', u'Nuxeo'),
        StringStruct(u'FileDescription', u'Desktop synchronization client for Nuxeo'),
        StringStruct(u'FileVersion', u'3.1.0'),
        StringStruct(u'InternalName', u'ndrive'),
        StringStruct(u'LegalCopyright', u'\xa9 Nuxeo. All rights reserved.'),
        StringStruct(u'OriginalFilename', u'ndrive.exe'),
        StringStruct(u'ProductName', u'Nuxeo Drive'),
        StringStruct(u'ProductVersion', u'3.1.0')])
      ]),
    VarFileInfo([VarStruct(u'Translation', [0, 0])])
  ]
)

Il s'agit ni plus ni moins d'un morceau de code Python, d'où les u'' pour la prise en charge de l'unicode.

  • filevers=(3, 1, 0, 0) est le n° de version du fichier généré, habituellement identique à la version du programme. Il s'agit d'un tuple devant contenir 4 nombres. Si, comme dans notre exemple, vous ne vous servez que de 3 nombre, voire 2, mettez des zéros pour combler.
  • prodvers=(3, 1, 0, 0) est le n° de version du programme, idem ci-dessus.

Le reste de la structure ffi ne doit pas être modifié. Pour ce qui est des StringStruct qui se trouvent dans la structure kids, c'est assez parlant et vous pouvez tout modifier.


Utilisation dans PyInstaller

2 possibilités s'offrent à nous pour utiliser un tel fichier. Via un argument passé en ligne de commande :

pyinstaller --version-file=properties.rc ...

Ou directement dans le fichier .spec du projet :

exe = EXE(..., version='properties.rc')

Fichier de propriétés dynamique

La partie intéressante : comment intégrer un fichier de propriétés dynamique ? Par exemple, générer un installeur contenant des informations à jour sans intervention manuelle, via un job Jenkins ou Travis-CI.

L'idée est simple : un fichier modèle contenant des balises spécifiques d'un côté, et de l'autre nous injecterons les bonnes données avant la création de l'installeur.

Le fichier properties_tpl.rc, presque identique à celui plus haut :

VSVersionInfo(
  ffi=FixedFileInfo(
    filevers={version_tuple},
    prodvers={version_tuple},
    mask=0x3f,
    flags=0x0,
    OS=0x40004,
    fileType=0x1,
    subtype=0x0,
    date=(0, 0)
    ),
  kids=[
    StringFileInfo(
      [
      StringTable(
        u'040904B0',
        [StringStruct(u'CompanyName', u'Nuxeo'),
        StringStruct(u'FileDescription', u'Desktop synchronization client for Nuxeo'),
        StringStruct(u'FileVersion', u'{version_str}'),
        StringStruct(u'InternalName', u'ndrive'),
        StringStruct(u'LegalCopyright', u'\xa9 Nuxeo. All rights reserved.'),
        StringStruct(u'OriginalFilename', u'ndrive.exe'),
        StringStruct(u'ProductName', u'Nuxeo Drive'),
        StringStruct(u'ProductVersion', u'{version_str}')])
      ]),
    VarFileInfo([VarStruct(u'Translation', [0, 0])])
  ]
)

Les données seront injectées à la place de {version_tuple} et {version_str}.

Du côté du fichier .spec, ajoutons le strict nécessaire :

import io


def get_version(init_file):
    """
    Rapatrier la version du module à packager.
    En gros, on fait un 'grep "__version__" $init_file' 
    """

    with io.open(init_file, encoding='utf-8') as handler:
        for line in handler.readlines():
            if line.startswith('__version__'):
                return re.findall(r"'(.+)'", line)[0]


def generate_rc(template='properties_tpl.rc', output='properties.rc'):
    """
    Création du fichier de propriétés basé sur le modèle donné.
    Pour simplifier son usage, la fonction retourne le nom du fichier crée.
    """

    version_str = get_version('nxdrive\__init__.py')
    version_tuple = tuple(map(int, version.split('.') + [0]))

    tpl = io.open(template, encoding='utf-8')
    out = io.open(output, 'w', encoding='utf-8')
    with tpl, out:
        out.write(tpl.read().format(
            version_str=version,
            version_tuple=version_tuple))

    return output


exe = EXE(..., version=generate_rc())

Un exemple complet se trouve dans le dépôt de Nuxeo Drive.

Il ne reste pus qu'à lancer PyInstaller, patienter un peu et se féliciter ☺


Sources :