Cómo convertir una aplicación web en una aplicación de escritorio, usando Chromium y PyInstaller

Empaquetar y distribuir su aplicación suena simple en principio. Es solo software. Pero en la práctica, es bastante desafiante.

He estado trabajando en un módulo de Python llamado Sofi que genera interfaces de usuario. Puede ofrecer una sensación de escritorio mientras utiliza tecnologías web estándar de una sola página. Para mayor flexibilidad, lo diseñé para que funcione a través de dos métodos de distribución: en el navegador y ejecutable.

Al ejecutarse en el navegador, funciona de manera muy similar a una página web normal. Puede cargarlo abriendo un archivo o ejecutarlo desde su shell. También construí un ejecutable que se ejecuta como una aplicación empaquetada, independiente y sin requisitos externos.

Con el tiempo, mientras pirateaba el código en Atom, mi editor de elección en estos días, recordé que Atom es en realidad un navegador. Utiliza Node.js como back-end y el marco Electron para su interfaz de usuario. Esto me inspiró a comenzar a hurgar en los aspectos internos de Electron, con la esperanza de encontrar ejemplos y mejores prácticas sobre cómo resolvieron los empaques de escritorio.

No me tomó mucho tiempo descubrir que todo está construido sobre tecnologías gratuitas y de código abierto: el navegador Chromium y el marco integrado Chromium. Este presentaba ejemplos de personalizaciones fáciles de integrar que eran capaces de cumplir con mis requisitos.

Con todo esto en la mano, me puse manos a la obra.

El marco integrado de Chromium

Chromium es el código base que alimenta el navegador Chrome de Google. Reúne todos los elementos que representan una interfaz, procesan la entrada del usuario y escriben sus funciones.

Chromium Embedded Framework (CEF) es un grupo de funciones C que pueden controlar ese navegador. También proporciona scripts que ayudan a simplificar el proceso de creación y compilación.

Visual Studio Code, Slack, Mattermost, Curse, Postman y Kitematic son ejemplos de aplicaciones de escritorio que usan Electron. Todos estos sistemas califican como sitios web que explotan el navegador subyacente con CEF.

Si está pensando que Python puede vincularse con C y aprovechar estas funciones también, entonces tiene razón. No busque más allá del proyecto pycef para llamar directamente a las funciones contenedoras CEF. Sin embargo, viene con el binario de Chromium como una dependencia adicional. Entonces, si le preocupa administrar declaraciones de apoyo complicadas, piense antes de saltar.

En mi situación particular, el proyecto Sofi gestiona todas las interacciones a través de un websocket, proporcionando una interfaz coherente en diferentes tipos de plataformas (web, escritorio, móvil, etc.). Esto significa que no necesito ordenar o manejar manualmente el navegador. Solo deseo interactuar con el DOM que muestra el navegador a través de tecnologías web estándar.

Mi objetivo es personalizar los elementos de la interfaz de usuario que hacen que un navegador parezca un navegador. Necesito eliminar los menús, las barras de herramientas y las barras de estado. Al hacerlo, haré que parezca que estamos en modo de pantalla completa, pero dentro de una ventana de aplicación.

Dados mis requisitos simples, sentí que pycef, o cualquier otro enlace de nivel inferior, era demasiado. En su lugar, aproveché una muestra prediseñada del proyecto CEF: cefsimple . Este navegador oculta todos los elementos visuales que quiero, por lo que si uso su CLI para abrir una página web, el usuario no tiene idea de que en realidad están dentro de un navegador. Parece una ventana normal desde cualquier aplicación.

Construir cefsimple no fue demasiado complicado una vez que revisé la documentación. Pero lleva una enorme cantidad de tiempo si también construyes Chromium junto con él. Para evitar esto, el proyecto en sí proporciona binarios prediseñados que puede personalizar y compilar en cefsimple. Encontré que era mejor aprovecharlos.

Los pasos son los siguientes:

  1. Eche un vistazo rápido a cómo construir con CEF a partir de binarios.
  2. Coge una de las distribuciones binarias del repositorio. Asegúrese de leer la información sobre herramientas antes de seleccionar una, ya que no todos los paquetes contienen los mismos archivos. Estaba buscando específicamente uno con cefsimple.
  3. Revise el CMakeLists.txtarchivo y asegúrese de instalar las herramientas de compilación necesarias. Esto es específico de la plataforma.
  4. Realice la construcción. Esto se explica en el mismo archivo que el paso anterior y también es específico de la plataforma, pero tiende a seguir el proceso de: make y cd en el directorio de compilación, ejecute cmake para sus herramientas de compilación y arquitectura mientras apunta al directorio principal. Como utilicé las herramientas de OSX Ninja en una plataforma de 64 bits, el comando parecíacmake -G "Ninja" -DPROJECT_ARCH="x86_64" ..
  5. El directorio de compilación ahora contendrá los archivos de salida. La estructura puede ser un poco confusa, pero se describe en la principal README. Como referencia, el paso anterior resultó en un paquete de aplicaciones debajo build/tests/cefsimple/Release/cefsimple.app.
  6. No olvide que tendrá que hacer esto para crear los binarios que necesita para cada plataforma y arquitectura de sistema operativo que admita.

Ahora que tiene un ejecutable, ejecútelo desde la línea de comandos con --urlconfigurado en la página web que desea abrir. Esto significa que incorporarlo a un script de Python se realiza fácilmente a través del subprocessmódulo.

Si bien no es obligatorio, si está interesado en compilar Chromium, consulte la documentación de CEF. Le indicará la dirección correcta. Pero tenga cuidado, se necesita mucho tiempo para descargar, construir y compilar. La buena potencia de procesamiento a la antigua definitivamente ayudará a obtener resultados más rápidos.

embalaje

Ahora que podemos ofrecer una experiencia de escritorio, tenemos que considerar cómo distribuirla a nuestros usuarios. La distribución tradicional de paquetes de Python se logra mediante el índice de paquetes de Python (PyPI). Sin embargo, requiere que nuestros usuarios instalen el intérprete de Python y alguna forma de herramienta de empaquetado como easy_installo pip.

Si bien esto no es particularmente difícil, debe considerar la gama más amplia de usuarios. Administrar un proceso de instalación con pasos manuales separados se vuelve bastante complicado. Especialmente con audiencias no técnicas, algunas de las cuales no saben que Python es otra cosa que una gran serpiente. Mientras que otros pueden al menos conocer la velocidad del aire de una golondrina europea sin carga.

Si conocen el idioma, la mayoría ya tiene su propia versión instalada. Aquí es donde entran en juego las dependencias de paquetes, diferentes sistemas operativos, navegadores de los que nunca ha oído hablar (o que pensaba que estaban muertos), junto con las diferentes habilidades de los usuarios para configurar entornos virtuales. Esto tiende a traducirse en una gran cantidad de tiempo dedicado a admitir software incompatible.

Para evitar un lío tan grande, existen herramientas que pueden integrar todas sus dependencias en archivos ejecutables específicos del sistema operativo. Después de una cuidadosa consideración, el que elegí para mis esfuerzos es PyInstaller. Parece proporcionar la mayor flexibilidad en plataformas y formatos compatibles.

Un breve extracto de su repositorio de GitHub resume las cosas muy bien:

PyInstaller lee un script de Python escrito por usted. Analiza su código para descubrir todos los demás módulos y bibliotecas que su script necesita para ejecutarse. Luego, recopila copias de todos esos archivos, ¡incluido el intérprete activo de Python! - y los coloca con su script en una sola carpeta u opcionalmente en un solo archivo ejecutable.

La herramienta cumplió su promesa. Me indicó que en el fichero de Python para mi aplicación de ejemplo y manojos en un directorio con bastante facilidad con: pyinstaller sample.py. Cuando quiera un ejecutable en su lugar, simplemente agregue el --onefileparámetro.

Se vuelve un poco más complicado cuando necesita agregar datos que no son de Python a su paquete. Este es el caso de los archivos html y js que forman la base de Sofi, y el navegador cefsimple que presenta la interfaz de la aplicación anterior. La utilidad PyInstaller proporciona --add-dataprecisamente eso, permitiendo una asignación a la ruta dentro de su paquete donde residirá el archivo de datos (o directorio). Sin embargo, me tomó un tiempo averiguar cómo acceder correctamente a esos directorios desde mi código. Afortunadamente, la documentación me indicó la dirección correcta.

Resulta que cuando se ejecuta una aplicación empaquetada de PyInstaller, no se puede confiar en __file__mecanismos similares para determinar las rutas. En cambio, el gestor de arranque PyInstaller almacena la ruta absoluta al paquete sys._MEIPASSy agrega un frozenatributo para hacerle saber que está ejecutando dentro de un paquete. Si sys.frozenes Trueasí, cargue sus archivos según sys._MEIPASS, de lo contrario, use las funciones de ruta normales para determinar dónde están las cosas.

Pude crear con éxito tanto una aplicación en paquete OSX como un binario ejecutable de Linux del mismo script de Python. Verifiqué que puedo hacer lo mismo con un ejecutable de Windows, pero aún no he tenido tiempo de armar una versión de Windows del navegador cefsimple para probar la ruta del paquete.

El producto final

Para ver un ejemplo de la interfaz de usuario basada en navegador empaquetada con el sistema descrito aquí, eche un vistazo a mi presentación en PyCaribbean 2017.

La demostración relevante para CEF y empaque es de una galería de imágenes y aparece alrededor de las 18:15.

Para leer más sobre cómo hice Sofi, eche un vistazo a la serie A Python Ate My GUI.

Si le gustó el artículo y desea leer más sobre Python y las prácticas de software, visite tryexceptpass.org. Manténgase informado con su contenido más reciente suscribiéndose a la lista de correo.