0. Вступление
Доброго времени суток, Хабр!
Началось всё с того, что у меня возникла необходимость сделать GUI-установщик для Django под Windows. Не увидев на хабре какой-либо статьи, описывающей создание установщиков, я подумал, «А почему бы и нет»? Из сообщества Django по этому вопросу мне ответили приблизительно так, как это сделал хабраюзер ffriend. Спасибо ему за конструктивность и открытость. Подробности под катом.
Вот такое письмо пришло мне от сообщества Django в ответ на моё фи. Собственно говоря, именно из-за этого фи я и оказался в минусе по репутации с рейтингом.
Thanks for the suggestion.
The contact form you’ve used is for the DSF — we’re the fund raising
arm and legal arm of the Django project.
If you’ve got ideas for how Django can be improved, you should direct
those ideas to the django-developers mailing list, or open a ticket on
Django’s ticket tracker.
Better still, try your hand at implementing the features you want to
see, and provide those changes as a patch against the Django codebase.
If you can provide a better user experience for Windows or Cyrillic
users, we may integrate those changes into the core product.
Yours,
Russ Magee %-)
Сразу скажу, как не надо делать. Не надо пытаться пользоваться ограниченной редакцией InstallShield, прилагаемой бонусом к MSVS Pro 2013.
Быть может, в отдельных простых случаях он подходит более чем, но в моём был желателен как можно больший контроль над процессом, и когда я открыл InstallShield и стал разбирать его по косточкам — последний меня разочаровал своей ограниченностью.
К счастью, на этом свете существует NSIS – NullSoft Install System, а именно – скриптовый генератор установщиков с возможностями, радующими глаз:
- Поддержка ZLIB, BZIP2 и LZMA (файлы могут быть сжаты вместе или по отдельности).
- Генерация программ удаления.
- Настраиваемый пользовательский интерфейс: диалоги, шрифты, фоны, иконки, текст, галочки, изображения и т.д.
- Классический и современный интерфейсы мастеров.
- Поддержка нескольких языков в одной установке. Более 60 переводов доступны, но вы также можете создавать свои собственные. Поддержка unicode позволяет использовать ещё больше языков.
- Постраничная логика организации: вы можете добавить стандартные страницы мастера или создать собственные.
- Дерево для выбора компонентов установки.
- Возможность определить конфигурации: обычно минимальная, типовая и полная установки, а также возможно создать пользовательскую конфигурацию.
- Самопроверка CRC32.
- Малые расходы на сжатие, а именно — 34 Кб с параметрами по умолчанию.
- Возможность отображения лицензионного соглашения RTF или TXT.
- Возможность выявлять каталог назначения из реестра.
- Много плагинов для создания пользовательских диалогов, интернет-соединений, HTTP-скачивания, модификация файлов, вызовы WinAPI и т.д.
- Монтажники может быть как 2 Гб.
- Дополнительный тихий режим для автоматизированных установок.
- Препроцессор с поддержкой определенных символов, макросов, условной компиляции, стандартных предопределений.
- Кодирование с элементами PHP и include ( включают в себя пользовательские переменные, стек, реальное управление потоком.
Установщики имеют свои собственные виртуальные машины, которые позволяют следующее
- Извлечение файлов с настраиваемыми параметрами перезаписи.
- Копирование файлов и каталогов, переименование, удаление, поиск.
- Плагин для вызова DLL.
- Регистрация DLL, ActiveX, снятие с регистрации.
- Оболочка исполнения с возможностью ожидания выполнения операций.
- Создание ярлыка.
- Работать с реестром.
- Чтение и запись INI.
- Чтение и запись общего текстового файла.
- Гибкие манипуляции с целыми и строковыми значениями.
- Открытие окна на основе имени класса или его названия.
- Манипуляции с пользовательским интерфейсом: шрифт, установка текста.
- Окно отправки сообщений.
- Взаимодействие пользователей с окнами сообщений или страницами.
- Ветвление, сравнение и прочее в этом духе.
- Возможность контроля над ситуацией при возникновении ошибок.
- Перезагрузка, в том числе удаление или переименование при перезагрузке.
- Команды поведение установщика. Например, «показать», «скрыть», «ожидать».
- Пользовательские функции в скрипте.
- Функции обратного вызова для действий пользователя.
Этот список взят из относительно полной документации на sourceforge, тогда как в штатном пакете присутствуют лишь общие сведения о генераторе и его языке, о чём и сказано в соответствующем файле справки.
К моему удивлению, на sf не оказалось ссылки на архив с этой самой полной документацией. Эту оплошность исправил самостоятельно.
Вместе с тем существует архив с набором «простых руководств» по реализации конкретных фич, который до этого приходилось качать через сторонний файловый хостинг сторонней же программой. Исправлен и этот недочёт.
Существует великое множество – простите за каламбур – генераторов скрипта генератора на основе GUI (из опробованных наилучшим оказался HM NIS Edit), но вместе с тем существует только лишь одна методика продуктивного обучения языкам…
1. Итак…
Простейший код, не требующий особенных пояснений, для первого знакомства с общими принципами:
- # define installer name
- OutFile "installer.exe"
- # set desktop as install directory
- InstallDir $DESKTOP
- # default section start
- Section
- # define output path
- SetOutPath $INSTDIR
- # specify file to go in output path
- File test.txt
- # define uninstaller name
- WriteUninstaller $INSTDIR\uninstaller.exe
- #——-
- # default section end
- SectionEnd
- # create a section to define what the uninstaller does.
- # the section will always be named "Uninstall"
- Section "Uninstall"
- # Always delete uninstaller first
- Delete $INSTDIR\uninstaller.exe
- # now delete installed file
- Delete $INSTDIR\test.txt
- SectionEnd
Да, я ленивый, использовал копипасту отсюда, адаптируя оную под свои нужды.
Кстати, у НЛО не нашлось подсветки nsi, поэтому был использован highlight.hohli.com, возможно, эта информация окажется полезной.
Я не разбираюсь в тонкостях питона, но судя по файлу setup.py в Django-1.6.2 при установке работа идёт только и только с путями в той или иной системе.
import os import sys from distutils.core import setup from distutils.sysconfig import get_python_lib # Warn if we are installing over top of an existing installation. This can # cause issues where files that were deleted from a more recent Django are # still present in site-packages. See #18115. overlay_warning = False if "install" in sys.argv: lib_paths = [get_python_lib()] if lib_paths[0].startswith("/usr/lib/"): # We have to try also with an explicit prefix of /usr/local in order to # catch Debian's custom user site-packages directory. lib_paths.append(get_python_lib(prefix="/usr/local")) for lib_path in lib_paths: existing_path = os.path.abspath(os.path.join(lib_path, "django")) if os.path.exists(existing_path): # We note the need for the warning here, but present it after the # command is run, so it's more likely to be seen. overlay_warning = True break def fullsplit(path, result=None): """ Split a pathname into components (the opposite of os.path.join) in a platform-neutral way. """ if result is None: result = [] head, tail = os.path.split(path) if head == '': return [tail] + result if head == path: return result return fullsplit(head, [tail] + result) EXCLUDE_FROM_PACKAGES = ['django.conf.project_template', 'django.conf.app_template', 'django.bin'] def is_package(package_name): for pkg in EXCLUDE_FROM_PACKAGES: if package_name.startswith(pkg): return False return True # Compile the list of packages available, because distutils doesn't have # an easy way to do this. packages, package_data = [], {} root_dir = os.path.dirname(__file__) if root_dir != '': os.chdir(root_dir) django_dir = 'django' for dirpath, dirnames, filenames in os.walk(django_dir): # Ignore PEP 3147 cache dirs and those whose names start with '.' dirnames[:] = [d for d in dirnames if not d.startswith('.') and d != '__pycache__'] parts = fullsplit(dirpath) package_name = '.'.join(parts) if '__init__.py' in filenames and is_package(package_name): packages.append(package_name) elif filenames: relative_path = [] while '.'.join(parts) not in packages: relative_path.append(parts.pop()) relative_path.reverse() path = os.path.join(*relative_path) package_files = package_data.setdefault('.'.join(parts), []) package_files.extend([os.path.join(path, f) for f in filenames]) # Dynamically calculate the version based on django.VERSION. version = __import__('django').get_version() setup( name='Django', version=version, url='http://www.djangoproject.com/', author='Django Software Foundation', author_email='foundation@djangoproject.com', description=('A high-level Python Web framework that encourages ' 'rapid development and clean, pragmatic design.'), license='BSD', packages=packages, package_data=package_data, scripts=['django/bin/django-admin.py'], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Framework :: Django', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 'Topic :: Internet :: WWW/HTTP :: WSGI', 'Topic :: Software Development :: Libraries :: Application Frameworks', 'Topic :: Software Development :: Libraries :: Python Modules', ], ) if overlay_warning: sys.stderr.write(""" ======== WARNING! ======== You have just installed Django over top of an existing installation, without removing it first. Because of this, your install may now include extraneous files from a previous version that have since been removed from Django. This is known to cause a variety of problems. You should manually remove the %(existing_path)s directory and re-install Django. """ % {"existing_path": existing_path})
Я не разбираюсь в питоне, но в общем и целом видно, что здесь происходит работа с путями в зависимости от оси, проверка, не идёт ли установка поверх существующей Django и в setup(), по-видимому, содержится информация для пакета python в целом.
К тому же, файл INSTALL утверждает:
AS AN ALTERNATIVE, you can just copy the entire «django» directory to Python’s
site-packages directory, which is located wherever your Python installation
lives. Some places you might check are:/usr/lib/python2.7/site-packages (Unix, Python 2.7)
/usr/lib/python2.6/site-packages (Unix, Python 2.6)
C:\\PYTHON\site-packages (Windows)
Однако именно из-за того, что я не знаю питон, предпочёл более кошерное применение setup.py, так как доподлинно мне не известно, чем отзовётся отсутствие setup(). Вот, что получилось у меня после адаптации bigtest.nsi. В комментариях особо отмечены места, которые не оказываются интуитивно понятными, с которыми пришлось повозиться, пока всё пошло, как нужно.
- !include "Include\LogicLib.nsh"
- OutFile "installer.exe"
- Name "Django 1.6.2"
- Caption "Django 1.6.2"
- Icon "${NSISDIR}\Contrib\Graphics\Icons\nsis1-install.ico"
- CRCCheck on
- SilentInstall normal
- BGGradient 000000 0000FF FFFFFF
- InstallColors FF8080 000030
- XPStyle on
- InstallDir $EXEDIR /* Обеспечиввает установку по умолчанию туда, куда был сохранён установщик */
- SetFont "Tahoma" 10 /* Нравится мне этот шрифт и всё тут */
- SetOverWrite on
- #-NonSectionInstructionsEnd-
- Section "BaseInstructions"
- SetDatablockOptimize on
- /* Без установки выходного пути для файлов установщик ругался
- на невозможность записи уже во время выполнения,
- хотя кажется, что необходимо достаточно InstallDir */
- SetOutPath "$INSTDIR\*"
- SetOverWrite on
- File /r "C:\ins\Django-1.6.2\*" /* Та папка, в которой у меня лежал Django.
- Именно так можно включить в установщик нужные файлы */
- SectionEnd
- RequestExecutionLevel highest /* От греха подальше… */
- CheckBitmap "${NSISDIR}\Contrib\Graphics\Checks\classic-cross.bmp"
- LicenseText "Django license"
- LicenseData "license.rtf" /* Отделался… лень =) */
- /* "Сколько языков ты знаешь, столько раз ты человек" =)
- NSIDIR, как можно понять, папка установки генератора.
- Весь язык сам по себе замечателен интуитивной понятностью */
- LoadLanguageFile "${NSISDIR}\Contrib\Language files\English.nlf"
- LoadLanguageFile "${NSISDIR}\Contrib\Language files\Dutch.nlf"
- LoadLanguageFile "${NSISDIR}\Contrib\Language files\French.nlf"
- LoadLanguageFile "${NSISDIR}\Contrib\Language files\German.nlf"
- LoadLanguageFile "${NSISDIR}\Contrib\Language files\Korean.nlf"
- LoadLanguageFile "Russian.nlf" /* Слегка подправленный файл перевода на родной */
- LoadLanguageFile "${NSISDIR}\Contrib\Language files\Spanish.nlf"
- LoadLanguageFile "${NSISDIR}\Contrib\Language files\Swedish.nlf"
- LoadLanguageFile "${NSISDIR}\Contrib\Language files\TradChinese.nlf"
- LoadLanguageFile "${NSISDIR}\Contrib\Language files\SimpChinese.nlf"
- LoadLanguageFile "${NSISDIR}\Contrib\Language files\Slovak.nlf"
- ; Set name using the normal interface (Name command)
- LangString Name ${LANG_ENGLISH} "English"
- LangString Name ${LANG_DUTCH} "Dutch"
- LangString Name ${LANG_FRENCH} "French"
- LangString Name ${LANG_GERMAN} "German"
- LangString Name ${LANG_KOREAN} "Korean"
- LangString Name ${LANG_RUSSIAN} "Russian"
- LangString Name ${LANG_SPANISH} "Spanish"
- LangString Name ${LANG_SWEDISH} "Swedish"
- LangString Name ${LANG_TRADCHINESE} "Traditional Chinese"
- LangString Name ${LANG_SIMPCHINESE} "Simplified Chinese"
- LangString Name ${LANG_SLOVAK} "Slovak"
- ; Directly change the inner lang strings (Same as ComponentText)
- LangString ^ComponentsText ${LANG_ENGLISH} "English component page"
- LangString ^ComponentsText ${LANG_DUTCH} "Dutch component page"
- LangString ^ComponentsText ${LANG_FRENCH} "French component page"
- LangString ^ComponentsText ${LANG_GERMAN} "German component page"
- LangString ^ComponentsText ${LANG_KOREAN} "Korean component page"
- LangString ^ComponentsText ${LANG_RUSSIAN} "Russian component page"
- LangString ^ComponentsText ${LANG_SPANISH} "Spanish component page"
- LangString ^ComponentsText ${LANG_SWEDISH} "Swedish component page"
- LangString ^ComponentsText ${LANG_TRADCHINESE} "Traditional Chinese component page"
- LangString ^ComponentsText ${LANG_SIMPCHINESE} "Simplified Chinese component page"
- LangString ^ComponentsText ${LANG_SLOVAK} "Slovak component page"
- ; A LangString for the section name
- LangString Sec1Name ${LANG_ENGLISH} "English section #1"
- LangString Sec1Name ${LANG_DUTCH} "Dutch section #1"
- LangString Sec1Name ${LANG_FRENCH} "French section #1"
- LangString Sec1Name ${LANG_GERMAN} "German section #1"
- LangString Sec1Name ${LANG_KOREAN} "Korean section #1"
- LangString Sec1Name ${LANG_RUSSIAN} "Russian section #1"
- LangString Sec1Name ${LANG_SPANISH} "Spanish section #1"
- LangString Sec1Name ${LANG_SWEDISH} "Swedish section #1"
- LangString Sec1Name ${LANG_TRADCHINESE} "Trandional Chinese section #1"
- LangString Sec1Name ${LANG_SIMPCHINESE} "Simplified Chinese section #1"
- LangString Sec1Name ${LANG_SLOVAK} "Slovak section #1"
- ;———————————
- Function .onInit
- ;Language selection dialog
- Push ""
- Push ${LANG_ENGLISH}
- Push English
- Push ${LANG_DUTCH}
- Push Dutch
- Push ${LANG_FRENCH}
- Push French
- Push ${LANG_GERMAN}
- Push German
- Push ${LANG_KOREAN}
- Push Korean
- Push ${LANG_RUSSIAN}
- Push Russian
- Push ${LANG_SPANISH}
- Push Spanish
- Push ${LANG_SWEDISH}
- Push Swedish
- Push ${LANG_TRADCHINESE}
- Push "Traditional Chinese"
- Push ${LANG_SIMPCHINESE}
- Push "Simplified Chinese"
- Push ${LANG_SLOVAK}
- Push Slovak
- Push A ; A means auto count languages
- ; for the auto count to work the first empty push (Push "") must remain
- LangDLL::LangDialog "Installer Language" "Please select the language of the installer"
- Pop $LANGUAGE
- StrCmp $LANGUAGE "cancel" 0 +2
- Abort
- FunctionEnd
- ;———————————
- ;———————————
- Page license
- Page directory
- Page instfiles
- AutoCloseWindow false
- ShowInstDetails show
- Function "FinalStage"
- MessageBox MB_OK "Close window and see on your screen… Thanks!"
- FunctionEnd
- /* Обратите внимаание на обёртку. Казалось бы, что должно
- сработать без секции, просто вызов и на этом закончили, но нет… 🙁 */
- Section "Final"
- Call "FinalStage"
- SectionEnd
- Function .onGUIEnd /* Есть и другие колбэки */
- ClearErrors
- /* При чистой установке можно было обойтись
- копированием django в site-packages, однако использование setup.py
- мне показалось более кошерным, учитывая мета-информацию. Я питон не знаю
- и значит не знаю, где и как аукнется отсутствие вызова setup(). */
- FileOpen $0 $INSTDIR\installer.bat w
- IfErrors done
- FileWrite $0 "cd $INSTDIR $\npython setup.py install" /* Абракадабра с регистрами мне не всегда нравится */
- /*Здесь нужно отметить, что добавление в батник чего-то вроде del /Q $INSTDIR\installer.bat к хорошему не приводит.
- Буду благодарен, если мне подскажут, почему так происходит, ибо это не нагуглишь и интуитивно представляется,
- что если уж команда запустилась, то наличие или отсутствие файла с командой роли не играет, ан нет же…*/
- FileClose $0
- done:
- Exec "$INSTDIR\installer.bat"
- FunctionEnd
- /* Можно было бы сделать страницу с выбором папки питона, на случай, если питон не прописан в PATH —
- именно так было бы идеально, но я не маньяк-перфекционист и сделаю это, только если вдруг установщик
- кому-то понравится и мне будет указано на недочёт. */
А ниже — скрин одного из результатов при обкатывании генератора.
Осталось только вторично достучаться до сообщества Django и сделать мелочи типа указания версии, имени файла и т.п…
Надеюсь, что этот пост оказался кому-то полезным и достаточно информативным.
ссылка на оригинал статьи http://habrahabr.ru/post/219285/
Добавить комментарий