Jeśli tworzenie aplikacji desktopowych nie jest Ci obce, to z pewnością wiesz, że w toku rozwoju tego typu oprogramowania wcześniej lub później przychodzi moment, w którym trzeba zastanowić się nad sposobem jego dystrybucji. Wiele zależy tu od użytych technologii oraz środowiska, na które kierujemy swoje programy. Na przykład jeśli korzystamy z Javy, to jedną z oczywistych możliwości będzie tworzenie i udostępnianie jarów, zaś pisząc apkę przeznaczoną dla systemu Ubuntu, zapewne dobrze będzie wygenerować .deba z naszym dziełem. A co w przypadku Windowsa? Większość programów, których używamy, jest przez nas instalowanych. Instalowanych – to własnie słowo klucz na dziś. A zatem potrzebujemy instalatora – pojedynczego pliku, który możemy udostępnić innym, ci zaś, przy pomocy kilku kliknięć, mogą wypakować nasze oprogramowanie w żądane miejsce.
Spis treści
Co wybrać?
Stajemy więc przed pytaniem: jak stworzyć taki instalator? Jakich narzędzi, bibliotek, technologii należałoby użyć? Okazuje się, że dostępnych rozwiązań jest całkiem sporo – niektóre z nich są płatne (np. popularny InstallShield), inne darmowe. Jedne wymagają przyswojenia pewnej dawki wiedzy zanim efektywnie będziemy mogli ich używać, inne z kolei charakteryzują się nieco większą prostotą (połączoną niekiedy z mniejszą liczbą funkcjonalności). Jeśli rozglądamy się za bezpłatnymi narzędziami, warto przyjrzeć się zwłaszcza dwóm poniższym:
- WiX Toolset – rozbudowane oprogramowanie, pozwalające na generowanie plików MSI (Windows Installera). Swego czasu był stosowany nawet w Microsofcie, m.in. do stworzenia instalatora dla Microsoft Office 2007 [2]. Niestety w parze z mnogością funkcji idzie pewien poziom skomplikowania – tutaj możecie zobaczyć przykład realnego wykorzystania WiXa i przyjrzeć się jak wyglądają używane w takim przypadku pliki XML-owe.
- NSIS (Nullsoft Scriptable Install System) – narzędzie umożliwiające tworzenie instalatorów (w postaci plików .exe) przy pomocy prostego języka skryptowego. Innym dziełem tego samego producenta jest np. Winamp, więc działanie wygenerowanego przez NSIS instalatora zapewne część z Was widziała w akcji przy instalacji tego właśnie programu.
W tym artykule zajmiemy się drugim z wymienionych tu rozwiązań. Do jego zalet należą:
- lekkość – narzut (ang. overhead) wynosi zaledwie 34 KB, tzn. że o tyle zwiększy się rozmiar naszego programu jeśli użyjemy instalatora NSIS
- prostota i łatwość użytkowania dzięki użyciu bazowaniu na skryptach
- przenośny kompilator – instalator możemy przygotowywać nawet na systemach uniksowych (i wcale nie potrzebujemy do tego Wine)
- wsparcie dla instalacji opierającej się o pobieranie plików z internetu
Pełną listę oferowanych funkcjonalności można znaleźć na oficjalnej stronie NSIS.
Przykładowy instalator
Zobaczmy zatem jak wygląda w NSIS jakieś instalatorowe „Hello World”. Na oficjalnej stronie (pod tym linkiem) można znaleźć kilka przykładów bardzo prostych instalatorów. Wśród nich znajduje się np. taki (usunąłem z niego komentarze):
1 2 3 4 5 6 7 |
Outfile "simple installer.exe" InstallDir $DESKTOP Section SetOutPath $INSTDIR File test.txt SectionEnd |
Aby z powyższego skryptu wygenerować plik .exe musimy pobrać NSIS i uruchomić narzędzie MakeNSIS, klikając link „Compile NSIS scripts” w głównym oknie NSIS. Następnie ładujemy skrypt (zapisany uprzednio z rozszerzeniem .nsi
) i kompilujemy go. Z racji, że w kodzie odwołujemy się do pliku test.txt
, powinniśmy umieścić go z tym sasmym folderze, w którym zapisaliśmy skrypt.
Sam kod jest chyba dość oczywisty, ponieważ ogranicza się do zdefiniowania następujących rzeczy:
- wygenerowany plik instalatora ma mieć nazwę „simple installer.exe”
- katalogiem instalacyjnym będzie pulpit
- kopiujemy plik
test.txt
do katalogu instalacyjnego
Po uruchomieniu instalatora, naszym oczom ukaże się takie oto okno:

Jak widać instalacja natychmiastowo się zakończyła, a użytkownik nie został nawet o nic zapytany. Plik test.txt
został przekopiowany na pulpit, a jedyna interakcja, na jaką pozwala instalator to zamknięcie okna. Wizualnie też nie wygląda to zbyt nowocześnie.
Na szczęście NSIS zawiera coś, co jest doskonałą odpowiedzią na potrzeby osób, które niewielkim wysiłkiem chcą stworzyć nieco ładniej wyglądający instalator, posiadający typowe dla tego rodzaju programów możliwości, tj.:
- przesdtawienie licencji do zaakceptowania
- wybór przez użytkownika folderu docelowego
- wybór komponentów aplikacji, które mają zostać zainstalowane
- możliwość dodania skrótu na pulpicie oraz w menu start
- dodanie dezinstalatora
Nowoczesny interfejs
Odpowiedzią tą jest komponent o nazwie NSIS Modern User Interface, dzięki któremu szybko można wygenerować spełniający te kryteria instalator. Poniżej prezentuję skrypt stworzonego przeze mnie przykładowego instalator, w którym wykorzystałem właśnie Modern UI:
- Kod skryptu
- Repozytorium na GitHubie, zawierające resztę niezbędnych plików (pliki do instalacji, licencję, ikonkę)
Częściowo bazowałem na jednym z przykładów z oficjalnej strony NSIS – z niego pochodzą znajdujące się w kodzie komentarze, których pozwoliłem sobie nie usuwać. Główna logika instalatora zdefiniowana została w miejscu, gdzie znajdują się dyrektywy wstawiające makra poszczególnych kroków:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
!insertmacro MUI_PAGE_WELCOME !insertmacro MUI_PAGE_LICENSE "licence.txt" Page Custom GetEnvPageCreate GetEnvPageLeave ;Custom page !insertmacro MUI_PAGE_COMPONENTS !insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_PAGE_FINISH !insertmacro MUI_UNPAGE_WELCOME !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES !insertmacro MUI_UNPAGE_FINISH |
Nazwy makr są dosyć opisowe, więc już na pierwszy rzut oka widać jak będą wyglądały kolejne kroki naszego instalatora:
- Strona z informacjami powitalnymi
- Licencja do zaakceptowania (jej treść wczytujemy z osobnego pliku tekstowego)
- Nasza customowa strona
- Wybór komponentów do instalacji
- Wskazanie przez użytkownika folderu docelowego
- Dodawanie wpisu do Menu Start
- Właściwa instalacja plików
- Strona z informacjami końcowymi
Kolejne wstawiane makra dotyczą natomiast dezinstalatora, który zostanie dołączony do naszego programu.
Poszczególne strony dostosowujemy w różny sposób, zależnie od ich typu. Przy licencji wskazujemy plik zawierający jej treść, zaś jeśli chodzi o informacje powitalne, po prostu definiujemy odpowiednią stałą:
1 |
!define MUI_WELCOMEPAGE_TEXT "Installer Example$\r$\nAuthor: Maciej Michalec$\r$\n$\r$\n--$\r$\nMore info on: https://PolyDev.pl" |
Z kolei komponenty, mające znajdować się na liście elementów do wyboru określamy, korzystając z sekcji, np.:
1 2 3 4 5 6 7 8 |
Section /o "Additional Tools" SecAdditionalTools SectionEnd LangString DESC_SecAdditionalTools ${LANG_ENGLISH} "Set of additional tools" !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN !insertmacro MUI_DESCRIPTION_TEXT ${SecAdditionalTools} $(DESC_SecAdditionalTools) !insertmacro MUI_FUNCTION_DESCRIPTION_END |
Powyższa sekcja będzie wyświetlana jako „Additional Tools”, z napisem „Set of additional tools” pojawiającym się z prawej strony okna instalatora po najechaniu myszką, i domyślnie będzie niezaznaczona (opcja /o
). Fragment ${LANG_ENGLISH}
to element związany z funkcjonalnością NSIS, pozwalającą na wybór przez użytkownika języka instalacji (w naszym przykładzie akurat jej nie używamy). Sama sekcja jest pusta, co oznacza, że zaznaczenie tego komponentu nie spowoduje żadnych dodatkowych działań.
Sporo działań mamy natomiast w drugiej sekcji:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
Section "PolyDev App Core" SecCore SetOutPath "$INSTDIR" ;Install myApp directory File /nonfatal /a /r "myApp\" ;Store installation folder WriteRegStr HKCU "Software\polydev" "" $INSTDIR ;Create uninstaller WriteUninstaller "$INSTDIR\Uninstall.exe" !insertmacro MUI_STARTMENU_WRITE_BEGIN Application ;Create shortcuts in start menu CreateDirectory "$SMPROGRAMS\$StartMenuFolder" CreateShortcut "$SMPROGRAMS\$StartMenuFolder\Uninstall.lnk" "$INSTDIR\Uninstall.exe" CreateShortcut "$SMPROGRAMS\$StartMenuFolder\PolyDevApp.lnk" "$INSTDIR\run_app.py" !insertmacro MUI_STARTMENU_WRITE_END SectionEnd |
Wreszcie używamy katalogu instalacyjnego wybranego przez użytkownika ($INSTDIR
), aby przekopiować tam (rekurencyjnie, bo z opcją /r
) katalog myApp
. Oprócz tego dodajemy wpis w rejestrze systemowym, tworzymy dezinstalator oraz dodajemy skróty do Menu Start: najpierw tworzymy w nim katalog (jego nazwa jest zapisana w zmiennej $StartMenuFolder
i może zostać zmodyfikowana przez użytkownika), a następnie dodajemy do niego skróty do dezinstalatora oraz pliku uruchomieniowego aplikacji.
Dodatkowe kroki
A co, jeśli chcemy od naszego instalatora coś więcej niż tylko parę generycznych kroków? Może np. potrzebujemy dodatkowych formularzy, aby uzyskać od użytkownika niezbędne informacje? W takiej sytuacji pewna doza kodowania staje się nieunikniona, ale bynajmniej nie ma tu nic skomplikowanego. W załączonym przykładzie stworzyłem dodatkowy krok, umożliwiający wybór jednej z trzech opcji (czyli tzw. radio buttons).
Aby obsłużyć taki przypadek wymagane jest napisanie dwóch funkcji, których nazwy przekazujemy do obiektu strony: Page Custom GetEnvPageCreate GetEnvPageLeave. Pierwsza z metod odpowiada za utworzenie wszystkich elementów graficznych, z kolei druga za obsługę opuszczenia kroku (kliknięcia Next).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
Function GetEnvPageCreate !insertmacro MUI_HEADER_TEXT "Programming Skills" "Provide information about your programming skills" nsDialogs::Create 1018 ${NSD_CreateLabel} 0 0 100% 15u "Select level of your programming skills" pop $0 ${NSD_CreateRadioButton} 0 20u 100% 10u "Beginner" pop $1 ${NSD_CreateRadioButton} 0 30u 100% 10u "Intermediate" pop $2 ${NSD_CreateRadioButton} 0 40u 100% 10u "Expert" pop $3 ${If} $EnvType == Beginner ${NSD_Check} $1 ${ElseIf} $EnvType == Intermediate ${NSD_Check} $2 ${ElseIf} $EnvType == Expert ${NSD_Check} $3 ${Else} ${NSD_Check} $1 ${EndIf} nsDialogs::Show FunctionEnd Function GetEnvPageLeave ${NSD_GetState} $1 $R0 ${NSD_GetState} $2 $R1 ${If} $R0 = ${BST_CHECKED} StrCpy $EnvType Beginner ${ElseIf} $R1 = ${BST_CHECKED} StrCpy $EnvType Intermediate ${Else} StrCpy $EnvType Expert ${EndIf} FunctionEnd |
Sama ifologia raczej nie wymaga omówienia (ot, po prostu używamy charakterystycznej dla tego języka składni instrukcji warunkowej), za to warto wyjaśnić jej przeznaczenie w funkcji GetEnvPageCreate
, ponieważ może nie być to oczywiste na pierwszy rzut oka. Otóż sprawdzenia w liniach 15-23 odpowiadają za to, aby jeśli przejdziemy do kolejnego kroku instalacji, to po cofnięciu się nasz wcześniejszy wybór był zapamiętany. Dokonany przez użytkownika wybór zapisujemy sobie w zmiennej $EnvType
, której później możemy użyć np. przy zapisie konfiguracji do pliku. Jeśli taki sposób tworzenia własnych kroków i tak brzmi dla Ciebie strasznie, to dobrą informacją będzie fakt, że istnieją programy wspomagające ich tworzenie. Jednym z nich jest HM NIS EDIT, w którym generowanie customowych formularzy sprowadza się do drag&drop.
Poniżej zamieszczam jeszcze parę screenshotów, pokazujących końcowy wygląd stworzonego instalatora. Jeśli taki wygląd nie wydaje Ci się satysfakcjonujący, to pozostaje skorzystać z… NSIS Ultra-Modern User Interface! 😀
Kompilacja pod Linuksem
Jedną ze znaczących dla mnie zalet NSIS jest możliwość kompilacji jego skryptów i generowania pliku instalatora na systemach uniksowych. Osobiście przetestowałem sposób, który dla NSIS w wersji 2.x opisany został na blogu Alejandro Celaya i śmiało mogę potwierdzić, że działa on również dla wydania 3.x, a stworzone w ten sposób pliki .exe poprawnie wykonują swoją pracę pod Windowsem. Ta funkcjonalność NSIS otwiera całe spektrum zastosowań w projektach, w których część usług i komponentów jest uruchamiana na Linuksie. Instalatory mogą być w prosty sposób generowane w środowiskach ciągłej integracji (np. na Jenkinsie) lub po stronie innego działającego na Linuksie serwera.
Podsumowanie
Na tyle na ile poznałem NSIS, mogę opisać je jako rozwiązanie lekkie i proste, a przy tym nie na tyle niszowe, aby znalezienie odpowiedzi na większość pojawiających się przy developmencie problemów stanowiło trudność. Na samym StackOverflow zadano już ponad 2 tysiące pytań na temat NSIS, a i sama dokumentacja jest całkiem dobra. Warto też wspomnieć o projekcie Pawła Porwisza, który wykonał dużą pracę dla polskojęzycznych użytkowników, przygotowując polską wersję dokumentacji, dostępną na jego stronie internetowej.