Hur man förvandlar en webbapp till en skrivbordsapp med Chromium och PyInstaller

Att paketera och distribuera din app låter i princip enkelt. Det är bara mjukvara. Men i praktiken är det ganska utmanande.

Jag har arbetat med en Python-modul som heter Sofi som genererar användargränssnitt. Den kan ge en skrivbordskänsla samtidigt som den använder standard ensidig webbteknik. För flexibilitetens skull designade jag den för att fungera genom två distributionsmetoder:i webbläsaren och körbar.

Körs i webbläsaren fungerar den ungefär som en vanlig webbsida. Du kan ladda den genom att öppna en fil eller starta den från ditt skal. Jag byggde också en körbar fil som körs som en paketerad app, oberoende och utan externa krav.

Med tiden, när jag hackade kod i Atom – min favoritredaktör nu för tiden – kom jag ihåg att Atom faktiskt är en webbläsare. Den använder Node.js som backend och Electron-ramverket för sitt användargränssnitt. Detta inspirerade mig att börja titta på Electrons interna delar i hopp om att hitta exempel och bästa praxis på hur de löste skrivbordspaketering.

Det tog inte lång tid för mig att upptäcka att allt är byggt på gratis och öppen källkod:webbläsaren Chromium och Chromium Embedded Framework. Detta innehöll lättintegrerade exempelanpassningar som kunde uppfylla mina krav.

Med allt detta i handen började jag jobba.

The Chromium Embedded Framework

Chromium är baskoden som matar Googles Chrome-webbläsare. Den samlar alla element som återger ett gränssnitt, bearbetar användarinmatning och skriptar dess funktioner.

Chromium Embedded Framework (CEF) är en grupp C-funktioner som kan styra den webbläsaren. Den tillhandahåller också skript som hjälper till att förenkla processen att bygga och kompilera den.

Visual Studio Code, Slack, Mattermost, Curse, Postman och Kitematic är alla exempel på stationära appar som använder Electron. Dessa system kvalificerar alla som webbplatser som utnyttjar webbläsaren under med CEF.

Om du tror att Python kan binda med C och dra nytta av dessa funktioner också, så har du rätt. Se inte längre än till pycef-projektet för att anropa CEF-omslagsfunktionerna direkt. Det kommer dock med Chromium binär som ett extra beroende. Så om du är orolig för att hantera komplicerade supportmeddelanden, tänk efter innan du hoppar.

I min speciella situation hanterar Sofi-projektet all interaktion genom en webbsocket, vilket ger ett konsekvent gränssnitt över olika typer av plattformar (webb, desktop, mobil, etc.). Det betyder att jag inte behöver styra eller köra webbläsaren manuellt. Jag vill bara interagera med DOM som webbläsaren visar genom standardwebbteknik.

Mitt mål är att anpassa UI-elementen som får en webbläsare att se ut som en webbläsare. Jag måste ta bort menyerna, verktygsfälten och statusfälten. När jag gör det kommer jag att få det att se ut som om vi är i helskärmsläge – men i ett programfönster.

Med tanke på mina enkla krav kände jag att pycef - eller andra bindningar på lägre nivå - var för mycket. Istället utnyttjade jag ett förbyggt prov från CEF-projektet:cefsimple . Den här webbläsaren döljer alla visuella element jag vill ha, så om jag använder dess CLI för att öppna en webbsida har användaren ingen aning om att de faktiskt finns i en webbläsare. Det ser ut som ett vanligt fönster från vilket program som helst.

Bygga cefsimple var inte alltför komplicerat när jag gick igenom dokumentationen. Men det tar oerhört mycket tid om du också bygger Chromium tillsammans med det. För att undvika detta tillhandahåller själva projektet förbyggda binärfiler som du kan anpassa och kompilera till cefsimple. Jag tyckte att det var bäst att dra nytta av dessa.

Stegen är som följer:

  1. Ta en snabb titt igenom hur man bygger med CEF från binärer.
  2. Ta tag i en av de binära distributionerna från repot. Se till att läsa verktygstipsen innan du väljer ett, eftersom inte alla paket innehåller samma filer. Jag letade specifikt efter en med cefsimple .
  3. Titta igenom CMakeLists.txt fil och se till att du installerar nödvändiga byggverktyg. Detta är plattformsspecifikt.
  4. Utför bygget. Detta förklaras i samma fil som föregående steg och är också plattformsspecifikt, men det tenderar att följa processen att:skapa och cd till byggkatalogen, kör cmake för dina kompileringsverktyg och arkitektur medan du pekar på den överordnade katalogen. Eftersom jag använde OSX Ninja-verktygen på en 64-bitars plattform såg kommandot ut som cmake -G "Ninja" -DPROJECT_ARCH="x86_64" ..
  5. Byggkatalogen kommer nu att innehålla utdatafilerna. Strukturen kan vara lite förvirrande, men den beskrivs i huvudtexten README . Som referens resulterade det föregående steget i ett AAB-paket under build/tests/cefsimple/Release/cefsimple.app .
  6. Glöm inte att du måste göra detta för att skapa de binärfiler du behöver för varje plattform och OS-arkitektur som du stöder.

Nu när du har en körbar fil, kör den från kommandoraden med --url ställ in på den webbsida du vill öppna. Detta innebär att det är enkelt att integrera det i ett Python-skript genom subprocess modul.

Även om det inte krävs, om du är intresserad av att kompilera Chromium själv, ta en titt på CEF-dokumentationen. Det kommer att peka dig i rätt riktning. Men varnas, det tar mycket tid att ladda ner, bygga och kompilera. Bra gammaldags bearbetningshästkrafter kommer definitivt att hjälpa till att få snabbare resultat.

Förpackning

Nu när vi kan leverera en skrivbordsupplevelse måste vi överväga hur vi ska distribuera den till våra användare. Traditionell Python-paketdistribution åstadkoms genom Python Package Index (PyPI). Det kräver dock att våra användare installerar Python-tolken och någon form av paketeringsverktyg som easy_install eller pip .

Även om detta inte är särskilt svårt, bör du överväga det bredare utbudet av användare. Att hantera en installationsprocess med separata manuella steg blir ganska komplicerat. Speciellt med icke-tekniska publik - av vilka några inte vet att Python är något annat än en stor orm. Medan andra kanske åtminstone känner till lufthastigheten för en europeisk olastad svala.

Om de kan språket har de flesta redan sin egen version installerad. Det är här paketberoende, olika operativsystem, webbläsare som du aldrig har hört talas om (eller trodde var döda vid det här laget) tillsammans med användarnas varierande kunskaper i att sätta upp virtuella miljöer. Detta tenderar att översättas till en stor mängd tid som ägnas åt att stödja felaktig programvara.

För att undvika en så stor röra finns det verktyg som kan bädda in alla dina beroenden i OS-specifika körbara filer. Efter noggrant övervägande, den jag valde för mina ansträngningar är PyInstaller. Det verkar ge mest flexibilitet i plattformar och format som stöds.

Ett kort utdrag från deras GitHub-förråd sammanfattar saker fint:

PyInstaller läser ett Python-skript skrivet av dig. Den analyserar din kod för att upptäcka alla andra moduler och bibliotek som ditt skript behöver för att kunna köras. Sedan samlar den in kopior av alla dessa filer - inklusive den aktiva Python-tolken! — och placerar dem med ditt skript i en enda mapp, eller valfritt i en enda körbar fil.

Verktyget höll sitt löfte. Jag hänvisade den till Python-filen för min exempelapplikation och den samlar den i en katalog tillräckligt enkelt med:pyinstaller sample.py . När jag istället vill ha en körbar fil lägger du bara till --onefile parameter.

Det blir lite knepigare när du behöver lägga till icke-Python-data till ditt paket. Detta är fallet med html- och js-filerna som ligger till grund för Sofi, och cefsimple webbläsare som presenterar applikationsgränssnittet från tidigare. Verktyget PyInstaller tillhandahåller --add-data för att göra just det, tillåta en mappning till sökvägen i ditt paket där datafilen (eller katalogen) kommer att finnas. Det tog mig dock ett tag att ta reda på hur man korrekt kommer åt dessa kataloger från min kod. Lyckligtvis pekade dokumentationen mig i rätt riktning.

Som det visar sig kan du inte lita på __file__ när du kör en PyInstaller-paketerad applikation och liknande mekanismer för att bestämma vägar. Istället lagrar PyInstaller bootloader den absoluta sökvägen till paketet i sys._MEIPASS och lägger till en frozen attribut för att låta dig veta att du kör i ett paket. Om sys.frozen är True ladda sedan dina filer baserat på sys._MEIPASS , annars använd normala sökvägsfunktioner för att avgöra var saker är.

Jag lyckades skapa både en OSX-buntad app och en körbar Linux-binär av samma Python-skript. Jag har verifierat att jag kan göra detsamma med en körbar Windows-fil, men jag har inte hunnit sätta ihop en Windows-version av cefsimple webbläsare för att testa paketets sökväg ännu.

Slutprodukten

För ett exempel på det webbläsarbaserade användargränssnittet paketerat med systemet som beskrivs här, ta en titt på min presentation på PyCaribbean 2017.

Demon som är relevant för CEF och förpackningar är av ett bildgalleri och den visas runt 18:15.

För ytterligare läsning om hur jag gjorde Sofi, ta en titt på A Python Ate My GUI-serien.

Om du gillade artikeln och vill läsa mer om Python och mjukvarupraxis, besök tryexceptpass.org. Håll dig informerad om deras senaste innehåll genom att prenumerera på e-postlistan.