Generación de un archivo instalador en Windows para el visor de PyQGIS

El visor de PyQGIS tiene varias dependencias, como por ejemplo: Python, Qt y QGIS. Es ideal que los usuarios finales puedan utilizarlo sin tener que lidiar con ellas. A través de un script que emplea las utilidades py2exe e InnoSetup, es posible generar un instalador que contiene las dependencias y facilita la distribución de la aplicación en Windows.

Objetivo

El objetivo a cumplir es la generación de un archivo instalador para el visor de PyQGIS que contenga todas las dependencias que necesita la aplicación, liberando al usuario de cualquier procedimiento complejo en la instalación. Esto permitirá que los usuarios puedan acercarse a las herramientas libres de manera intuitiva.

Herramientas a utilizar

Para generar el archivo instalador del visor de PyQGIS se utilizarán las herramientas py2exe e InnoSetup.

py2exe es una utilidad que se encarga de generar archivos ejecutables de Windows a partir de código en Python. De esta forma, con base en archivos .py se obtienen ejecutables .exe de Windows. Dentro de sus principales características están las siguientes:

  • Genera ejecutables que pueden funcionar sin requerir una instalación de Python. py2exe genera un directorio en el que se incluye el ejecutable y sus dependencias, entre ellas, una librería de enlace dinámico (dll) de Python.
  • Cada aplicación puede utilizar una versión particular de Python. Como cada aplicación está vinculada a una dll que corresponde a una versión de Python (por ejemplo, python26.dll), pueden tenerse diferentes aplicaciones en el sistema operativo y cada una de ellas puede funcionar con una versión específica de Python.
  • Es ampliamente configurable a través de parámetros que indican por ejemplo, los paquetes y módulos a incluir, las librerías dinámicas (dll) a ignorar, la compresión y optimización del resultado, entre otros.

py2exe genera todos los archivos necesarios para ejecutar la aplicación, sin embargo, para su distribución es conveniente empaquetarlos en un archivo instalador, esa es la función de InnoSetup.

InnoSetup es una herramienta que permite obtener por medio de scripts, archivos instaladores personalizados para Windows. Sus principales funcionalidades son:

  • Compresión de archivos de la aplicación en un archivo ejecutable de instalación.
  • Generación de accesos directos en el escritorio, en la barra de acceso rápido y en el menú inicio.
  • Generación de archivo de desinstalación.

Instalación

Para instalar py2exe se descarga y ejecuta el archivo correspondiente a la versión de Python instalada en nuestro equipo. La descarga se realiza desde el siguiente enlace: https://pypi.org/project/py2exe/

Para instalar InnoSetup se descarga y ejecuta uno de los instaladores de la serie Stable release desde aquí: http://www.jrsoftware.org/isdl.php

Script de QGISLite

El proyecto QGISLite de Aaron Racicot utiliza un script que vincula las utilidades py2exe e InnoSetup para producir un archivo instalador a partir de código en Python, todo en un solo paso. El script está compuesto por varias partes:

  • Parámetros de configuración para py2exe.
  • Una clase para generar el ejecutable con py2exe y el script de InnoSetup.
  • Un método para compilar el script de InnoSetup para obtener finalemente el instalador (en este método se ejecuta el programa InnoSetup que debe estar preinstalado).

Para el instalador del visor PyQGIS se ha modificado el script de QGISLite obteniendo un código personalizado que puede descargarse desde este enlace. El script es el siguiente:

1.   # -*- encoding=utf-8 -*-
2.
3. from distutils.core import setup
4. import py2exe
5. import os
6.
7. # Build tree of files given a dir (for appending to py2exe data_files)
8. # Taken from http://osdir.com/ml/python.py2exe/2006-02/msg00085.html
9. def tree(src):
10. list = [(root, map(lambda f: os.path.join(root, f), files)) for (root, dirs, files) in os.walk(os.path.normpath(src))]
11. new_list = []
12. for (root, files) in list:
13. if len(files) > 0 and root.count('.svn') == 0:
14. new_list.append((root, files))
15. return new_list
16.
17. ################################################################
18. class InnoScript:
19. def __init__(self,
20. name,
21. lib_dir,
22. dist_dir,
23. windows_exe_files = [],
24. lib_files = [],
25. version = "1.0"):
26. self.lib_dir = lib_dir
27. self.dist_dir = dist_dir
28. if not self.dist_dir[-1] in "\\/":
29. self.dist_dir += "\\"
30. self.name = name
31. self.version = version
32. self.windows_exe_files = [self.chop(p) for p in windows_exe_files]
33. self.lib_files = [self.chop(p) for p in lib_files]
34.
35. def chop(self, pathname):
36. assert pathname.startswith(self.dist_dir)
37. return pathname[len(self.dist_dir):]
38.
39. def create(self, pathname="dist\\VisorGeografico.iss"):
40. self.pathname = pathname
41. ofi = self.file = open(pathname, "w")
42. print >> ofi, "; WARNING: This script has been created by py2exe. Changes to this script"
43. print >> ofi, "; will be overwritten the next time py2exe is run!"
44. print >> ofi, r"[Setup]"
45. print >> ofi, r"AppName=%s %s" % (self.name, self.version)
46. print >> ofi, r"AppVerName=%s %s" % (self.name, self.version)
47. print >> ofi, r"DefaultDirName={pf}\\%s" % self.name
48. print >> ofi, r"DefaultGroupName=%s" % self.name
49. print >> ofi, r"VersionInfoVersion=%s" % self.version
50. print >> ofi, r"VersionInfoCompany=GeoTux"
51. print >> ofi, r"VersionInfoDescription=Visor Geográfico"
52. print >> ofi, r"VersionInfoCopyright=GeoTux"
53. print >> ofi, r"AppCopyright=Germán Carrillo - 2009"
54. print >> ofi, r"AppSupportURL=http://geotux.tuxfamily.org"
55. print >> ofi, r"OutputBaseFilename=Visor_Geografico_GeoTux_Installer"
56. print >> ofi, r"LicenseFile=d:\\trabajos\\dllo\\qgis_python\\visor_instalador\\licencia.txt"
57. print >> ofi, r"WizardImageBackColor=clBlack"
58. print >> ofi
59. print >> ofi, r"[Languages]"
60. print >> ofi, r'Name: "spanish"; MessagesFile: "compiler:Languages\\Spanish.isl"'
61. print >> ofi
62.
63. print >> ofi, r"[Tasks]"
64. print >> ofi, r'Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"'
65. print >> ofi, r'Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"'
66. print >> ofi
67.
68. print >> ofi, r"[Files]"
69. for path in self.windows_exe_files + self.lib_files:
70. print >> ofi, r'Source: "%s"; DestDir: "{app}\\%s"; Flags: ignoreversion' % (path, os.path.dirname(path))
71. print >> ofi, r'Source: lib\\gdal16.dll; DestDir: {app}\\lib; Flags: ignoreversion'
72. print >> ofi, r'Source: lib\\QtSvg4.dll; DestDir: {app}\\lib; Flags: ignoreversion'
73. print >> ofi, r'Source: lib\\proj.dll; DestDir: {app}\\lib; Flags: ignoreversion'
74.
75. print >> ofi, r"[Icons]"
76. print >> ofi, r'Name: "{group}\\{cm:ProgramOnTheWeb,%s}"; Filename: "http://geotux.tuxfamily.org"' % \\
77. self.name
78.
79. for path in self.windows_exe_files:
80. print >> ofi, r'Name: "{group}\\%s"; Filename: "{app}\\%s"; WorkingDir: {app}' % \\
81. (self.name, path)
82.
83. print >> ofi, 'Name: "{group}\\Uninstall %s"; Filename: "{uninstallexe}"' % self.name
84. print >> ofi, 'Name: "{commondesktop}\\%s"; Filename: "{app}\\%s"; Tasks: desktopicon; WorkingDir: "{app}"' % \\
85. (self.name, path)
86. print >> ofi, 'Name: "{userappdata}\\Microsoft\\Internet Explorer\\Quick Launch\\%s"; Filename: "{app}\\%s"; Tasks: quicklaunchicon; WorkingDir: "{app}"' % \\
87. (self.name, path)
88. def compile(self):
89. try:
90. import ctypes2
91. except ImportError:
92. try:
93. import win32api
94. except ImportError:
95. import os
96. os.startfile(self.pathname)
97. else:
98. print "Ok, using win32api."
99. win32api.ShellExecute(0, "compile",
100. self.pathname,
101. None,
102. None,
103. 0)
104. else:
105. print "Cool, you have ctypes installed."
106. res = ctypes.windll.shell32.ShellExecuteA(0, "compile",
107. self.pathname,
108. None,
109. None,
110. 0)
111. if res < 32:
112. raise RuntimeError, "ShellExecute failed, error %d" % res
113.
114.
115. ################################################################
116.
117. from py2exe.build_exe import py2exe
118.
119. class build_installer(py2exe):
120. # This class first builds the exe file(s), then creates a Windows installer.
121. # You need InnoSetup for it.
122. def run(self):
123. # First, let py2exe do it's work.
124. py2exe.run(self)
125.
126. lib_dir = self.lib_dir
127. dist_dir = self.dist_dir
128.
129. # create the Installer, using the files py2exe has created.
130. script = InnoScript("Visor Geografico GeoTux",
131. lib_dir,
132. dist_dir,
133. self.windows_exe_files,
134. self.lib_files)
135. print "*** creating the inno setup script***"
136. script.create()
137. print "*** compiling the inno setup script***"
138. script.compile()
139. # Note: By default the final setup.exe will be in an Output subdirectory.
140.
141. ######################## py2exe setup options ########################################
142.
143. zipfile = r"lib\\shardlib"
144.
145. options = {
146. "py2exe": {
147. "compressed": 1,
148. "optimize": 2,
149. "includes": ['sip'],
150. "excludes": ['backend_gtkagg', 'backend_wxagg', 'tcl', 'tcl.tcl8.4', 'Tkinter' ],
151. "dll_excludes": ['libgdk_pixbuf-2.0-0.dll', 'libgobject-2.0-0.dll', 'libgdk-win32-2.0-0.dll', 'phonon4.dll', 'MSVCR80.dll', 'QtScriptTools4.dll', 'tcl84.dll', 'tk84.dll' ],
152. "packages": ["qgis", "PyQt4"],
153. "dist_dir": "dist",
154. }
155. }
156.
157.
158. data_files = tree('plugins') + tree('resources') + [ ( ".", [ "msvcp71.dll", "licencia.txt" ] ) ]
159.
160. setup(
161. options = options,
162. # The lib directory contains everything except the executables and the python dll.
163. zipfile = zipfile,
164. windows=[{"script": "VisorGeografico.py"}],
165. # use out build_installer class as extended py2exe build command
166. cmdclass = {"py2exe": build_installer},
167. data_files = data_files
168. )

Instalador para el visor de PyQGIS

Para generar el archivo instalador del visor de PyQGIS seguiremos los pasos que se describen a continuación:

1. Descargar insumos

El insumo principal para el ejercicio es el proyecto del visor de PyQGIS que se ha realizado en blogs anteriores (Construcción del visor de PyQGIS y Cargando capas de PostGIS al visor de PyQGIS). El proyecto puede descargarse desde este enlace. Una vez descargado lo descomprimimos en el directorio de trabajo, el script de QGISLite modificado debe estar dentro de la carpeta del proyecto descargado.

2. Copiar carpeta de plugins y de resources

Para que la aplicación de PyQGIS pueda funcionar sin tener instalado QGIS en el equipo del usuario final, es necesario tomar de este programa dos carpetas: plugins y resources. La carpeta plugins contiene librerías dll útiles para cargar datos espaciales de formatos convencionales como OGR y PostgreSQL (es decir, los data providers). La carpeta resources contiene información correspondiente a los sistemas de referencia empleados en QGIS.

Copiamos las dos carpetas en el directorio de trabajo, cuya estructura debe lucir así:

Directorio de trabajo

3. Definir qgis_prefix

Ahora necesitamos que la aplicación de PyQGIS busque las librerías y datos de las carpetas plugins y resources en una ubicación relativa. La variable qgis_prefix del archivo VisorGeografico.py almacena la ruta en la que se buscan dichos datos (ver línea 24 del archivo). Como el archivo y las dos carpetas están en el mismo directorio, la variable qgis_prefix debe ser definida como “.”. Así: qgis_prefix = ‘.’

4. Personalizar el script py2exe_innosetup.py

Si es necesario, se pueden ajustar algunos parámetros en el script py2exe_innosetup.py.

Específicamente se debe revisar el método create de la clase InnoScript, que define los parámetros del script que compilará InnoSetup.

5. Ejecutar el script modificado de QGISLite

El script modificado debe ser ejecutado desde la terminal de comandos, de la siguiente forma: python py2exe_innosetup.py py2exe

Al ejecutarlo se generan dos carpetas: build y dist. La carpeta build es una carpeta temporal y puede ser borrada sin problemas cuando termina la ejecución del script.

La carpeta dist contiene los ejecutables generados y sus dependencias, es decir, en la carpeta dist están los archivos a distribuir. Dentro de la carpeta dist se encuentra una carpeta Output y allí está ubicado el archivo instalador, que luce así:

Instalador

Conclusiones

Es posible distribuir el visor de PyQGIS a través de un archivo instalador para Windows que contiene todas las dependencias requeridas (Qt, QGIS y Python) y que no necesita de una instalación de Python en el equipo del usuario final.

Referencias

Pdf Puedes descargar este artículo en pdf desde aquí (91 KB).