Come trasformare un'app Web in un'app desktop, utilizzando Chromium e PyInstaller

Il packaging e la distribuzione della tua app sembrano semplici in linea di principio. È solo software. Ma in pratica è piuttosto impegnativo.

Ho lavorato a un modulo Python chiamato Sofi che genera interfacce utente. Può fornire un aspetto desktop durante l'utilizzo di tecnologie web standard a pagina singola. Per flessibilità, l'ho progettato per funzionare attraverso due metodi di distribuzione: nel browser ed eseguibile.

In esecuzione nel browser, funziona in modo molto simile a una normale pagina web. Puoi caricarlo aprendo un file o avviandolo dalla tua shell. Ho anche creato un eseguibile che funziona come app in pacchetto, indipendente e senza requisiti esterni.

Nel tempo, mentre hackeravo il codice in Atom, il mio editor preferito in questi giorni, mi sono ricordato che Atom è in realtà un browser. Utilizza Node.js come back-end e il framework Electron per la sua interfaccia utente. Questo mi ha spinto a iniziare a esaminare le parti interne di Electron, sperando di trovare esempi e best practice su come hanno risolto il packaging desktop.

Non mi ci è voluto molto per scoprire che è tutto basato su tecnologie gratuite e open source: il browser Chromium e Chromium Embedded Framework. Questo presentava personalizzazioni di esempio facili da integrare che erano in grado di soddisfare le mie esigenze.

Con tutto questo in mano, devo lavorare.

Chromium Embedded Framework

Chromium è il codice di base che alimenta il browser Chrome di Google. Riunisce tutti gli elementi che rendono un'interfaccia, elaborano l'input dell'utente e ne scrivono le funzioni.

Chromium Embedded Framework (CEF) è un gruppo di funzioni C che possono controllare quel browser. Fornisce inoltre script che aiutano a semplificare il processo di creazione e compilazione.

Visual Studio Code, Slack, Mattermost, Curse, Postman e Kitematic sono tutti esempi di app desktop che utilizzano Electron. Questi sistemi si qualificano tutti come siti Web che sfruttano il browser sottostante con CEF.

Se stai pensando che Python possa collegarsi con C e sfruttare anche queste funzionalità, allora hai ragione. Non guardare oltre il progetto pycef per chiamare direttamente le funzioni wrapper CEF. Tuttavia, viene fornito con il binario Chromium come dipendenza aggiuntiva. Quindi, se sei preoccupato per la gestione di dichiarazioni di supporto complicate, pensa prima di saltare.

Nella mia situazione particolare, il progetto Sofi gestisce tutte le interazioni tramite un websocket, fornendo un'interfaccia coerente su diversi tipi di piattaforme (web, desktop, mobile, ecc.). Ciò significa che non ho bisogno di comandare manualmente o guidare il browser. Desidero solo interagire con il DOM visualizzato dal browser tramite tecnologie web standard.

Il mio obiettivo è personalizzare gli elementi dell'interfaccia utente che fanno sembrare un browser un browser. Devo rimuovere i menu, le barre degli strumenti e le barre di stato. In tal modo, farò sembrare che siamo in modalità a schermo intero, ma all'interno di una finestra dell'applicazione.

Date le mie semplici esigenze, sentivo che pycef - o qualsiasi altro attacco di livello inferiore - era troppo. Invece ho approfittato di un campione precostruito dal progetto CEF: cefsimple . Questo browser nasconde tutti gli elementi visivi che desidero, quindi se utilizzo la sua CLI per aprire una pagina Web, l'utente non ha idea che si trovino effettivamente all'interno di un browser. Sembra una finestra normale da qualsiasi applicazione.

La creazione di cefsimple non è stata troppo complicata una volta esaminata la documentazione. Ma ci vuole un'enorme quantità di tempo se costruisci anche Chromium insieme ad esso. Per evitare ciò, il progetto stesso fornisce binari predefiniti che è possibile personalizzare e compilare in cefsimple. Ho trovato meglio approfittarne.

I passi sono come segue:

  1. Dai una rapida occhiata a come compilare con CEF da binari.
  2. Prendi una delle distribuzioni binarie dal repo. Assicurati di leggere i suggerimenti prima di selezionarne uno, poiché non tutti i pacchetti contengono gli stessi file. Stavo cercando specificamente uno con cefsimple.
  3. Esamina il CMakeLists.txtfile e assicurati di installare gli strumenti di compilazione necessari. Questo è specifico della piattaforma.
  4. Esegui la build. Questo è spiegato nello stesso file del passaggio precedente ed è anche specifico della piattaforma, ma tende a seguire il processo di: make e cd nella directory build, eseguire cmake per gli strumenti di compilazione e l'architettura puntando alla directory padre. Dato che ho utilizzato gli strumenti OSX Ninja su una piattaforma a 64 bit, il comando sembravacmake -G "Ninja" -DPROJECT_ARCH="x86_64" ..
  5. La directory build conterrà ora i file di output. La struttura può creare un po 'di confusione, ma è descritta nella parte principale README. Come riferimento, il passaggio precedente ha portato a un app bundle in build/tests/cefsimple/Release/cefsimple.app.
  6. Non dimenticare che dovrai farlo per creare i file binari necessari per ogni piattaforma e architettura del sistema operativo che supporti.

Ora che hai un eseguibile, eseguilo dalla riga di comando con --urlimpostato sulla pagina web che desideri aprire. Ciò significa che incorporarlo in uno script Python è facilmente realizzabile tramite il subprocessmodulo.

Sebbene non sia richiesto, se sei interessato a compilare Chromium stesso, dai un'occhiata alla documentazione CEF. Ti indicherà la giusta direzione. Ma attenzione, ci vuole molto tempo per scaricare, costruire e compilare. La buona potenza di elaborazione vecchio stile aiuterà sicuramente a ottenere risultati più rapidi.

Confezione

Ora che possiamo offrire un'esperienza desktop, dobbiamo considerare come distribuirla ai nostri utenti. La distribuzione tradizionale dei pacchetti Python viene eseguita tramite il Python Package Index (PyPI). Tuttavia, richiede ai nostri utenti di installare l'interprete Python e qualche forma di strumento di pacchettizzazione come easy_installo pip.

Anche se questo non è particolarmente difficile, dovresti considerare la più ampia gamma di utenti. La gestione di un processo di installazione con passaggi manuali separati diventa piuttosto complicata. Soprattutto con un pubblico non tecnico, alcuni dei quali non sanno che Python è qualcosa di diverso da un grande serpente. Mentre altri potrebbero almeno conoscere la velocità della velocità dell'aria di una rondine a vuoto europea.

Se conoscono la lingua, la maggior parte ha già la propria versione installata. È qui che entrano in gioco le dipendenze dei pacchetti, diversi sistemi operativi, browser di cui non hai mai sentito parlare (o che pensavi fossero ormai morti), insieme alle diverse abilità degli utenti nella configurazione di ambienti virtuali. Ciò tende a tradursi in una grande quantità di tempo speso a supportare software non corrispondenti.

Per evitare un tale disordine, esistono strumenti che possono incorporare tutte le tue dipendenze in file eseguibili specifici del sistema operativo. Dopo un'attenta considerazione, quello che ho scelto per i miei sforzi è PyInstaller. Sembra fornire la massima flessibilità nelle piattaforme e nei formati supportati.

Un breve estratto dal loro repository GitHub riassume bene le cose:

PyInstaller legge uno script Python scritto da te. Analizza il tuo codice per scoprire ogni altro modulo e libreria di cui il tuo script ha bisogno per essere eseguito. Quindi raccoglie copie di tutti quei file, incluso l'interprete Python attivo! - e li inserisce con lo script in una singola cartella o, facoltativamente, in un singolo file eseguibile.

Lo strumento ha mantenuto la sua promessa. Ho fatto notare al file di Python per la mia applicazione di esempio e fasci in una directory abbastanza facilmente con: pyinstaller sample.py. Quando invece voglio un eseguibile, aggiungi semplicemente il --onefileparametro.

Diventa un po 'più complicato quando devi aggiungere dati non Python al tuo bundle. Questo è il caso dei file html e js che costituiscono la base di Sofi e del browser cefsimple che presenta l'interfaccia dell'applicazione precedente. L'utilità PyInstaller fornisce --add-dataproprio questo, consentendo una mappatura al percorso all'interno del bundle in cui risiederà il file di dati (o la directory). Tuttavia, mi ci è voluto un po 'per capire come accedere correttamente a quelle directory dal mio codice. Fortunatamente la documentazione mi ha indirizzato nella giusta direzione.

A quanto pare, quando si esegue un'applicazione in bundle PyInstaller, non è possibile fare affidamento su __file__meccanismi simili per determinare i percorsi. Invece, il bootloader PyInstaller memorizza il percorso assoluto del bundle sys._MEIPASSe aggiunge un frozenattributo per farti sapere che stai eseguendo all'interno di un bundle. Se sys.frozenè Truequindi carica i tuoi file in base a sys._MEIPASS, altrimenti usa le normali funzioni di percorso per determinare dove sono le cose.

Sono stato in grado di creare con successo sia un'app in bundle OSX che un binario Linux eseguibile dello stesso script Python. Ho verificato di poter fare lo stesso con un eseguibile Windows, ma non ho ancora avuto il tempo di mettere insieme una versione Windows del browser cefsimple per testare il percorso del bundle.

Il prodotto finale

Per un esempio dell'interfaccia utente basata su browser confezionata con il sistema qui descritto, dai un'occhiata alla mia presentazione a PyCaribbean 2017.

La demo relativa a CEF e packaging è di una galleria di immagini e appare intorno alle 18:15.

Per ulteriori letture su come ho creato Sofi, dai un'occhiata alla serie A Python Ate My GUI.

Se l'articolo ti è piaciuto e vuoi saperne di più su Python e sulle pratiche software, visita tryexceptpass.org. Tieniti informato con i loro ultimi contenuti iscrivendoti alla mailing list.