Blog (107)
Komentarze (2.3k)
Recenzje (0)
@webnullcz.1| Jak to jest być deweloperem aplikacji wieloplatformowej - Deweloper vs Windows

cz.1| Jak to jest być deweloperem aplikacji wieloplatformowej - Deweloper vs Windows

Witam.

Długo nie pisałem - bo ostatni cały tydzień spędziłem na głowieniu się dlaczego to, dlaczego tamto i sto innych rzeczy nie działa pod systemem Windows. Wybaczcie za wstęp, ale straciłem na prawdę sporo czasu na przenoszeniu swojej jednej małej aplikacji napisanej w Pythonie na system Windows.

Spis treści: 1. Proces przenoszenia kodu Pythona 2. Ogólny proces paczkowania przez wirtualną maszynę, Tworzenie "exe" z plików Pythona 3. Pakowanie projektu w instalator NSIS 4. Podsumowanie i krótki komentarz

No dobrze, no to zacznę według kolejności.

1. Proces przenoszenia kodu Pythona

Jako, że Python jako interpreter działa natywnie pod systemami opartymi o jądro Linux jak i pod systemami z rodziny Windows (NT) tak więc można wnioskować - "a co tam takiego zależnego od platformy jest" a jednak troszkę jest i to troszkę to za dużo.

Zdaję sobie sprawę, że w C, C++ czy innym kompilowanym języku na pewno jest jeszcze więcej problemów przy przenoszeniu aplikacji ale ja opisuję w tym wypadku Pythona i nie zajmuję się C, C++ czy innym językiem kompilowanym.

1) Ładowanie wtyczek, plików językowych

W zasadzie nic trudnego, wystarczy dodać do istniejących odwołań do systemu plików po prostu prefix zależny od systemu operacyjnego który będzie wyglądać mniej więcej tak:


if os.name == "nt":
self.prefix = "c:/Program Files/Aplikacja"
else
self.prefix = ""

Wtedy odwołania bedą wyglądać mniej więcej tak:


self.window.set_icon_from_file(self.prefix+"/usr/share/aplikacja/icons/window.png")

Jednak sprawa się bardzo komplikuje jeżeli nie wiemy gdzie aplikacja będzie się znajdować.

Tak więc musimy iść na sam koniec procesu przenoszenia aplikacji na platformę Windows i zbudować instalator, tak zbudować instalator do nie działającej jeszcze aplikacji ponieważ jej działanie jest zależne od instalatora!

Instalator musi utworzyć wpis w rejestrze systemowym z lokalizacją plików aplikacji tak aby nasza aplikacja wiedziała gdzie się znajduje!

Dla instalatora NSIS będzie to taka linijka:

  WriteRegStr HKCU "SOFTWARE\NazwaAplikacji" 'Directory' '$INSTDIR'

Gdzie:  Directory - nazwa klucza $INSTDIR - katalog w którym jest zainstalowana aplikacja

Tak więc po zainstalowaniu tworzony jest klucz w rejestrze który wskazuje na katalog w którym użytkownik zainstalował aplikację. Z poziomu aplikacji możemy zatem odczytać ten klucz, aby to zrobić wystarczy mniej więcej coś takiego:


if os.name == "nt":
    import _winreg

    try:
        key = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, 'Software\\MojaAplikacja\\', 0, _winreg.KEY_READ)
        (value, valuetype) = _winreg.QueryValueEx(key, 'Directory')

        self.prefix = str(value)

    except WindowsError:
        print "Cannot find registry key HKEY_CURRENT_USER\Software\MojaAplikacja\Directory, exiting."
        sys.exit(2)

Brzmi skomplikowanie? Teraz jak to działa w systemach Uniksowych

W systemach Uniksowych znajdują się katalogi /usr/bin, /usr/share, /usr/lib, /etc, $HOME, /tmp i wiele innych, służą one do tego aby segregować instalowaną treść.


/usr/bin - pliki wykonywalne (binarne lub skryptowe)
/usr/lib - biblioteki, wtyczki i wszystko co się ładuje dynamicznie
/usr/share - obrazki, ikony, dokumentacje, tłumaczenia itp.
/etc - pliki konfiguracyjne które są obejmowane ochroną nadpisania podczas aktualizacji systemu, istnieją specjalne narzędzia aby je aktualizować bez żadnych strat
$HOME - katalog domowy użytkownika, można w nim zapisać spersonalizowaną konfigurację programu dla danego użytkownika
/tmp - katalog tymczasowy, przykładowo program archiwizujący dane może tam tworzyć archiwum a następnie je przenieść do katalogu wybranego przez użytkownika

Budowanie pakietu dla poszczególnych systemów Uniksowych jest bardzo proste, i sprowadza się do edycji jednego pliku oraz wykonania odpowiedniego polecenia które zrobi wszystko za nas.

Dzięki temu, że menadżer pakietów zapamiętuje jakie akcje wykonuje podczas instalacji pakietu jest później w stanie odwrócić zmiany - czyli możliwe jest usunięcie pakietu bez tworzenia deinstalatora.

Przykładowe tworzenie paczki w Arch Linux:

1> Tworzymy katalog z nazwą aplikacji, a w nim plik PKGBUILD z zawartością przykładowo:

pkgname=nazwaaplikacji-git
pkgver=0.6 # wersja
pkgrel=1 # numer kompilacji paczki dla aktualnej wersji
pkgdesc="Opis naszej aplikacji"
arch=('i686' 'x86_64') # dostępne platformy
url="http://dobreprogramy.pl" # adres url
license=('GPL') # licencja
depends=('git' 'alang-py' 'python' 'pygtk') # zależności, czyli to co zostanie zainstalowane automatycznie przez menadżer pakietów za nas
makedepends=('git') # zależności do samego zbudowania paczki
provides=('nazwaaplikacji-git') # paczka zastępuje inną paczkę?
conflicts=('nazwaaplikacji') # paczka konfliktuje z inną paczką?

# adres url do pobrania z GIT, można także pobrać z archiwum, SVN czy innego źródła, ja wybrałem GIT
_gitroot="git://github.com/webnull/nazwaaplikacji.git"
_gitname="nazwaaplikacji"

build() {
  cd "$srcdir"

  if [ -d $_gitname ] ; then
    cd $_gitname && git pull origin
    msg "Zaaktualizowano lokalne pliki do najnowszej wersji"
  else
    git clone $_gitroot $_gitname
  fi

  # czyszczenie katalogu budowania
  rm -rf "$srcdir/$_gitname-build"
  git clone "$srcdir/$_gitname" "$srcdir/$_gitname-build"
  ./configure -parametr1 -parametr2
  make

  # kopiowanie plików wyjściowych do katalogu z danymi paczki
  cp "$srcdir/mojaaplikacja/usr" "$srcdir/../pkg/usr" -R
}

2> Następnie wywołujemy makepkg i za powiedzmy 10 sekund mamy gotową paczkę do zainstalowania którą po zainstalowaniu można jeszcze odinstalować choć nie tworzyliśmy dla niej odinstalatora, proste prawda?

2) Odczyt i zapis plików

Przez kilka godzin dochodziłem do tego dlaczego Windows pozwala odczytać tylko ok. 1/100 zawartości pliku zamiast całości.

Problemem okazało się najwyraźniej kodowanie bo otwierałem pliki w których były polskie ogonki.

Jak widać Linux poradził sobie z ogonkami zakodowanymi w windows-1250 bez problemów, a sam Windows miał problemy ze swoim genialnym kodowaniem.

Rozwiązanie było proste, ale ciężko mi było na nie wpaść przez kilka godzin - a mianowicie należy otwierać pliki w trybie binarnym czyli np. "rb" a nie w zwykłym trybie "r". Linux radzi sobie z ogonkami w obydwóch trybach bez żadnych problemów dlatego błędu nie potrafiłem wykryć.

Zatem jak ktoś mi nie wierzy to zapraszam do testów, proszę spróbować pliki z napisami filmowymi które w 90% są kodowane w windows-1250.


#!/usr/bin/python2
# kompatybilny z Linux i Windows
fileHandler = open("tekst.txt", "rb")
contents = fileHandler.read()
fileHandler.close()

print str(len(contents))

#!/usr/bin/python2
# kompatybilny z Linux
fileHandler = open("tekst.txt", "r")
contents = fileHandler.read()
fileHandler.close()

print str(len(contents))

3) Wywołania systemowe w razie braku bibliotek

W Pythonie problemem było rozpakowanie pliku skompresowanego przy użyciu 7zip, dlatego postanowiłem użyć zewnętrznego programu /usr/bin/7z (Linux) oraz 7za.exe (Windows).

Pod Linuksem sprawa jest o tyle banalna, że w zależnościach paczki dodajemy wymagany pakiet p7zip i menadżer pakietów sam to zainstaluje - no ba, pakiet znajduje się w każdym systemie Linuksowym więc problemu nie ma.

W Windows trzeba spakować plik 7za.exe do katalogu z aplikacją, a co jeśli wyjdzie aktualizacja do tego pliku? - A co jeśli licencja nie pozwala? Pełno problemów i ograniczeń, pod Windows świat staje się taki skomplikowany...

Dziwny problem z os.system i subprocess

Gdy próbowałem wywołać pod os.system pewne polecenie pod Windows to zwracało mi komunikat w stylu "C:\program" nie ma takiego pliku lub katalogu jednak w subprocess to samo wyrażenie nie powodowało błędu.


# Podobny przykład działa pod Linuksem, ale nie pod Windowsem
os.system("\"c:\\katalog z spacja\\7za.exe\" --parametry --otworz archiwum.7z > zapis-do-pliku.txt")

# Podobny przykład działa pdo Windowsem, nie testowany pod Linuksem
subprocess.call("\"c:\\katalog z spacja\\7za.exe\" --parametry --otworz archiwum.7z > zapis-do-pliku.txt", shell=True, bufsize=1)

Skoro adres do pliku w którym po drodze znajduje się spacja był wzięty w cudzysłów to system nie powinien mieć problemów z odnalezieniem pliku - a jednak miał.

Ciąg dalszy nastąpi według spisu treści...

Wybrane dla Ciebie
Komentarze (19)