Простая автоматизация версионности и сборки C/C++ проекта на Ruby

от автора

Предположим, что, как и в моем случае, вы впервые столкнулись с необходимостью
минимизации телодвижения на пути от 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.

К установке требуются:

  • NSIS
  • Ruby (использовались пути установки по-умолчанию)

Для 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *