минимизации телодвижения на пути от SVN исходников к NSIS инсталлеру с попутной автоинкрементацией версии проекта. В ручном же режиме это выглядит примерно так:
- Прописываем новую версию в соответствующем исходнике проекта. Нужно ли это для пользователей или в качестве диагностической информации – несущественно.
- Собираем проектные файлы, необходимые для setup.
- Обновляем версию внутри nsi-файла, поскольку используем ее в окнах на этапе установки и в имени результирующего setup-файла.
Уязвимость подобной практики обнаруживается при авралах, когда один или несколько из этих пунктов оказываются пропущенными, в результате чего у пользователя на руках остаются несогласованные между собой компоненты.
Не претендуя на новизну, предлагаемые ниже скрипты автоматизируют сборку C/C++ Visual Studio проектов практически в один клик и будут полезны, прежде всего, при одиночной разработке.
Предполагается использование формата A.B.C.D, где A и B меняются редко и вручную, а следующие два параметра подлежат обновлению при каждой release-компиляции:
- С – номер SVN ревизии;
- D – номер сборки, который инкрементируется.
Первый скрипт обновляет версию файла через специальный хедер, который, подключаясь к файлу ресурсов .rc, изменяет атрибуты компилируемого файла проекта. Второй скрипт использует их для генерации NSIS инсталлера, в имени которого будет отражена вся информация о текущей версии, например, MyApp-1.0.837.1.exe. Тем самым гарантируется автоматизация отмеченных выше этапов сборки.
Реализация автоинкрементации
Для получения номера SVN ревизии воспользуемся базовой утилитой SubWCRev.exe, а для автоинкремента номера сборки не обойтись без Pre-Build Event, как это было сделано, например, здесь или тут.
Создадим файл VersionInfo.h:
#ifndef __VERSION_INFO_H #define __VERSION_INFO_H #define APP_NAME "MyApp" #define APP_VERSION 1,0,,0 #define APP_VERSION_S "" #define APP_DATE "" #endif
и подключим его в .rc файл (с указанием пути куда сохранили):
#include "../src/VersionInfo.h"
Изменяем версию итогового бинарника, которая понадобится позже для .nsi:
FILEVERSION APP_VERSION PRODUCTVERSION APP_VERSION
В задачу скрипта VersionBuild.rb после каждого выполнения
C:\Ruby193\bin\ruby VersionBuild.rb VersionInfo.h
входит изменение VersionInfo.h путем вставки специальных ключевых слов $WCREV$ и $WCNOW=$ из SubWCRev.exe:
#define APP_VERSION 1,0,$WCREV$,1 #define APP_VERSION_S "1.0.$WCREV$.1" #define APP_DATE "$WCNOW=%d.%m.%Y %H:%M$"
#define APP_VERSION 1,0,$WCREV$,2 #define APP_VERSION_S "1.0.$WCREV$.2" #define APP_DATE "$WCNOW=%d.%m.%Y %H:%M$"
...
Последующий вызов
SubWCRev.exe .. VersionInfo.h VersionInfo.h
подставит актуальные значения номера SVN ревизии и даты, например
#define APP_VERSION 1,0,29,2 #define APP_VERSION_S "1.0.29.2" #define APP_DATE "01.11.2012 20:09"
При реализации VersionBuild.rb использованы регулярные выражения так, чтобы сохранялось форматирование (пробелы/табуляции):
FNAME = ARGV[0] file = File::read(FNAME) ANY_IN_QUOTES = Regexp.new('"[^"]*"') def replaceVersion(file) #строка с макросом APP_VERSION matcher = Regexp.new('APP_VERSION\s*[^\r\n]*') line = file.match(matcher)[0] old_ver = /(\d+),(\d+),(.*),(\d+)/ line.match(old_ver) new_ver = [$1, $2, "$WCREV$", $4.to_i + 1] line.sub!(old_ver, new_ver.join(",")) file.sub!(matcher, line) #строка с макросом APP_VERSION_S matcher = Regexp.new('APP_VERSION_S\s*' + ANY_IN_QUOTES.source) line = file.match(matcher)[0] line.sub!(ANY_IN_QUOTES, '"' + new_ver.join(".") + '"') file.sub!(matcher, line) end def replaceDate(file) #строка с макросом APP_DATE matcher = Regexp.new('APP_DATE\s*' + ANY_IN_QUOTES.source) date = file.match(matcher)[0] date.sub!(ANY_IN_QUOTES, '"$WCNOW=%d.%m.%Y %H:%M$"') file.sub!(matcher, date) end replaceVersion(file) replaceDate(file) File::write(FNAME, file)
Чтобы избежать увеличения времени компиляции в большом проекте рекомендуется не включать VersionInfo.h в хедеры проекта, а завести дополнительные прокси-файлы:
#include "Version.h" #include "VersionInfo.h" const char *Version() { return APP_VERSION_S; } const char *BuildDate() { return APP_DATE; }
Реализация сборки setup
Ниже приведен стандартный NSIS файл для установки одного exe (находится в той же папке) с созданием ярлыков на рабочем столе и в Пуске.
!include "MUI.nsh" ;Раскоммментировать при необходимости ручной сборки ;!define APP_NAME "MyApp" ;!define MAJOR_VERSION "1" ;!define MINOR_VERSION "0" ;!define SVN_REVISION "29" ;!define BUILD_NUMBER "2" ;!define APP_VERSION "${MAJOR_VERSION}.${MINOR_VERSION}.${SVN_REVISION}.${BUILD_NUMBER}" ;!define SETUP_NAME "${APP_NAME}-${APP_VERSION}.exe" Name ${SETUP_NAME} OutFile ${SETUP_NAME} InstallDir $PROGRAMFILES\${APP_NAME} VIProductVersion ${APP_VERSION} VIAddVersionKey "ProductName" ${APP_NAME} VIAddVersionKey "Comments" "" VIAddVersionKey "CompanyName" "" VIAddVersionKey "LegalTrademarks" "" VIAddVersionKey "LegalCopyright" "© " VIAddVersionKey "FileDescription" ${APP_NAME} VIAddVersionKey "FileVersion" ${APP_VERSION} !define MUI_ABORTWARNING !insertmacro MUI_PAGE_WELCOME !insertmacro MUI_PAGE_COMPONENTS !insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_PAGE_FINISH !insertmacro MUI_UNPAGE_WELCOME !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES !insertmacro MUI_UNPAGE_FINISH !insertmacro MUI_LANGUAGE "Russian" Section "${APP_NAME} (required)" SetOutPath $INSTDIR File "${APP_NAME}.exe" WriteUninstaller "uninstall.exe" SectionEnd Section "Uninstall" Delete "$INSTDIR\*.*" Delete "$SMPROGRAMS\${APP_NAME}\*.*" Delete "$DESKTOP\${APP_NAME}.lnk" RMDir "$SMPROGRAMS\${APP_NAME}" RMDir "$INSTDIR" SectionEnd Section "Start Menu and Desktop Shortcuts" CreateDirectory "$SMPROGRAMS\${APP_NAME}" CreateShortCut "$SMPROGRAMS\${APP_NAME}\Uninstall.lnk" "$INSTDIR\uninstall.exe" "" "$INSTDIR\uninstall.exe" 0 CreateShortCut "$DESKTOP\${APP_NAME}.lnk" "$INSTDIR\${APP_NAME}.exe" SectionEnd
Причем вместо ручного задания констант !define мы используем передачу констант APP_NAME и APP_VERSION утилите makensis.exe. Этим занимается скрипт SetupBuild.rb, принимающий на вход имя скомпилированного бинарного файла:
C:\Ruby193\bin\ruby SetupBuild.rb MyApp.exe
К примеру, если после компиляции MyApp.exe имел APP_VERSION_S = «1.0.29.2», то скрипт запросит строковую версию из атрибутов файла и в случае успеха создаст файл установки MyApp-1.0.29.2.exe, иначе будет открыт лог-файл от NSIS в блокноте.
Исходный код SetupBuild.rb:
NSIS_MAKE = '"C:\Program Files (x86)\NSIS\makensis.exe"' require 'win32ole' def versionOf(fname) fso = WIN32OLE.new("Scripting.FileSystemObject") return fso.GetFileVersion(fname) end def alert(msg) fso = WIN32OLE.new("WScript.Shell") fso.Popup msg end SRC_FNAME = File::expand_path(ARGV[0]) APP_NAME = File::basename(SRC_FNAME, '.*') params = Hash.new params['APP_NAME'] = APP_NAME params['APP_VERSION'] = versionOf(SRC_FNAME) params['SETUP_NAME'] = "#{APP_NAME}-#{params['APP_VERSION']}.exe" #префикс /D сразу перед параметром задает соответствующую константу в NSIS (аналог !define) cmd = params.map{|key, val| "/D#{key}=#{val}"}.join(" ") LOG_FNAME = "#{APP_NAME}_#{$0}.log" r = system("#{NSIS_MAKE} #{cmd} #{APP_NAME}.nsi > #{LOG_FNAME}") if r File::delete(LOG_FNAME) else system('notepad', LOG_FNAME) end
Использование
Тестовый SVN-проект на Visual Studio 2008 лежит здесь: https://ruby-nsis.googlecode.com/svn/trunk.
К установке требуются:
Для release в Project->Properties->Build Events->Pre-Build Event->Command Line задано
C:\Ruby193\bin\ruby "..\src\VersionBuild.rb" "..\src\VersionInfo.h" SubWCRev.exe .. "..\src\VersionInfo.h" "..\src\VersionInfo.h"
При запуске setup\SetupBuild.bat c содержимым
C:\Ruby193\bin\ruby SetupBuild.rb MyApp.exe
будет сгенерирован setup-файл MyApp-1.0.x.y.exe, где x — номер SVN версии, y — порядковый номер компиляции.
Если поместить SetupBuild.bat в Post-Build Event, инсталлер будет собираться при каждой перекомпиляции.
ссылка на оригинал статьи http://habrahabr.ru/post/157197/
Добавить комментарий