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 :