Номер ревизии SVN в коде 1С 7.7

в 13:28, , рубрики: 1с 7.7, svn, Программирование, разработка, метки: ,

Добрый день.

Хочу поделиться способом подстановки в код 1С 7.7 номера ревизии и других значений из рабочей копии SVN.
Реализовать нижеописанное навеяло статьей, автору выражаю благодарность за вдохновение.

Предыстория

Торговая сеть средних размеров (примерно 150 магазинов), небольшой коллектив разработчиков, пишем на 1С 7.7, конфигурации в магазинах самописные.
Используется SVN сервер Collabnet Subversion, клиент TurtoiseSVN, компилятор/декомпилятор метаданных 1С gComp. Есть 1Сная база(мы зовем ее «копирка») из которой мы «раскидываем» обновления и обработки по магазинами. Магазины обновляются автоматом с помощью ConfStarter'а.
Есть несколько самописных Java парсеров для подстановки функций (автологирования) и форматирования кода и пара bat'ников для упрощения работы с ними и с GComp:

  • Compile.bat — запускает компиляцию MD из исходников, на выходе 1cv7.compile.md. Его содержимое включает всего одну строчку:
    	"%GCOMP_PATH%gcomp" -c -D .REPO -F 1cv7.compile.md>.compile.log	

    где REPO — каталог с исходниками в каталоге БД

  • Build.bat — копирует исходники в отдельную папку BUILD и запускает Java-парсеры для автологирования и форматирования кода, потом GComp для компиляции и получаем на выходе 1cv7.build.md, такие MDшники идут в магазины.

Каталог, где они живут, включен в PATH, поэтому запускается все это добро прямо из каталога БД 1С, что вполне удобно.

Причина

Редко, но все же случались казусы — то не ту сборку (ревизию) закинут, то не с той ветки, то магазин не обновился, и т.п.
Необходимо было реализовать контроль за состоянием магазинов, а именно хотелось иметь достоверную информацию о том, какая именно сборка в каком магазине установлена.

Идея

По заданному шаблону получаем данные о рабочей копии с помощью TurtoiseSVN, на выходе получаем другой файл-шаблон со структурой «МаскаДляПоиска» = «ЗначениеДляЗамены» для программки-парсера.
Парсер перебирает файл-шаблон и ищет совпадения в файле с кодом. Маской для поиска является название функции. Если таковая находится — заменяется следующая после нее строчка (которая возвращает значение функции) на «ЗначениеДляЗамены» из шаблона. После этого файл передается дальше на компиляцию. Далее из магазина выгружается файл с данными и передается в офис, где полученная информация обрабатывается и консолидируется.

Реализация

Для получения данных о рабочей копии используется программа "SubWCRev.exe" из TurtoiseSVN. Она принимает три параметра:

  • Путь к рабочей копии SVN (это папка REPO в каталоге БД)
  • Путь к файлу-шаблону (файл shablon.txt в папке со скриптами)
  • Путь, куда выгружать готовый файл (я назвал его ReplacerSettings.txt, выгружается в каталог БД)

Файл shablon.txt имеет вышеупомянутую структуру «МаскаДляПоиска» = «ЗначениеДляЗамены» и следующие записи:

###
getSVNRevision=	Return "$WCREV$";
getSVNURL=	Return "$WCURL$";
getSVNBuildTime=	Return "$WCNOW$";

На выходе получаем такой же файл, только вместо ключевых слов, указанных между знаками $ подставляются соответствующие значения:

  • $WCREV$ — Номер ревизии рабочей копии
  • $WCURL$ — адрес ветки
  • $WCNOW$ — Текущее время. Указывается в качестве времени сборки MD файла.

Подробнее о подстановке ключевых слов тут

Программка-парсер была написана на AutoIt. Я назвал ее Replacer. Она принимает два параметра:

  • путь к файлу с настройками (это ReplacerSettings.txt)
  • путь до файла, в котором будут подменяться значения функций (это ГлобальныйМодуль.1s)

Первая строчка в файле с настройками должна иметь значение ### для «защиты от дурака» — чтобы не перепутать порядок файлов. Именно поэтому так сделано в файле shablon.txt

Содержимое Replacer'а:

#include "file.au3"
;$CmdLine[0] - это количество переданных параметров. 
if $CmdLine[0] <2 Then
    MsgBox(0,"error!","Replacer have not all params!")
	Exit
 EndIf
 
;Добавляем глобальные переменные 
Global $SettingsFilePath = $CmdLine[1]
Global $WorkFilePath = $CmdLine[2]

;Проверяем на существование файл настроек
if FileExists ( $SettingsFilePath) = 0 Then
 MsgBox(0, "Error", "Settings File is not exist!.")
 exit
EndIf

;Проверяем на существование файл с кодом
if FileExists ( $WorkFilePath ) = 0 Then
 MsgBox(0, "Error", "Global Module File is not exist!.")
exit
EndIf

;Открываем файл настроек
  Local $Settingsfile = openFile($SettingsfilePath, 0)
 Local $T = FileRead($SettingsFilePath,3)
 if $T <> "###" Then
	MsgBox(0,"Error in the settings file!","Settings file not have in first line a secure code ###")
   Exit
 EndIf
	
	;Запускаем бесконечный цикл
   While 1
   ;Считываем строку файла настроек
   Local $line = FileReadLine($Settingsfile)
    ;Если неудачно, то выходим
	If @error = -1 Then ExitLoop
   ;Ищем разделитель
	Local $SeparatorPos = StringInStr($line, "=")
	if $SeparatorPos = 0 Then
	   ;Если не нашли - продолжаем цикл
	   ContinueLoop
	EndIf
   ;с помощью функции получаем из строки значение для поиска
   Local $find = getFindString($line)
   ;И для замены
   Local $replace = getReplaceString($line)
	;С помощью функции подменяем значение 
      ___ReplaceStringInFile($WorkFilePath,$find,$replace)
   WEnd
  FileClose($Settingsfile)
;выходим из программы. Далее указанные функции выполняются только при вызове
exit 

;В файле "file.au3" я нашел подходящую функцию, мне осталось только немного модифицировать ее.
;Она читает файл, создает массив строк, в массиве происходит поиск и замена, потом обратно запись из массива в файл.
Func ___ReplaceStringInFile($szFileName, $szSearchString, $szReplaceString, $fCaseness = 0, $fOccurance = 1)
	Local $iRetVal = 0
	Local $nCount, $sEndsWith
	; Check if file is readonly ..
	If StringInStr(FileGetAttrib($szFileName), "R") Then Return SetError(6, 0, -1)
	;===============================================================================
	;== Read the file into an array
	;===============================================================================
	Local $hFile = FileOpen($szFileName, $FO_READ)
	If $hFile = -1 Then Return SetError(1, 0, -1)
	Local $s_TotFile = FileRead($hFile, FileGetSize($szFileName))
	If StringRight($s_TotFile, 2) = @CRLF Then
		$sEndsWith = @CRLF
	ElseIf StringRight($s_TotFile, 1) = @CR Then
		$sEndsWith = @CR
	ElseIf StringRight($s_TotFile, 1) = @LF Then
		$sEndsWith = @LF
	Else
		$sEndsWith = ""
	EndIf
	Local $aFileLines = StringSplit(StringStripCR($s_TotFile), @LF)
	FileClose($hFile)
	;===============================================================================
	;== Open the output file in write mode
	;===============================================================================
	Local $iEncoding = FileGetEncoding($szFileName)
	Local $hWriteHandle = FileOpen($szFileName, $iEncoding + $FO_OVERWRITE)
	If $hWriteHandle = -1 Then Return SetError(2, 0, -1)
	;===============================================================================
	;== Loop through the array and search for $szSearchString
	;===============================================================================
	local $needReplace = 0
	For $nCount = 1 To $aFileLines[0]
	  ;тут я добавил проверку на необходимость замены строки
	  if $needReplace = 1 Then
		;подменяем содержимое строки и выходим из цикла
		$aFileLines[$nCount] = $szReplaceString
		 ExitLoop
	  EndIf
	  ;Если нашли значение в строке
		If StringInStr($aFileLines[$nCount], $szSearchString, $fCaseness) Then
			;Тут я закомментировал оригинальную строку
			;$aFileLines[$nCount] = StringReplace($aFileLines[$nCount], $szSearchString, $szReplaceString, 1 - $fOccurance, $fCaseness)
			;"Включаю" переменную needReplace и при следующей итерации цикла произойдет замена. Как раз то что нужно.
			$needReplace = 1 	
			$iRetVal = $iRetVal + 1

			;======================================================================
			;== If we want just the first string replaced, copy the rest of the lines
			;== and stop
			;======================================================================
			If $fOccurance = 0 Then
				$iRetVal = 1
				ExitLoop
			EndIf
		 EndIf
	Next
	;===============================================================================
	;== Write the lines back to original file.
	;===============================================================================
	For $nCount = 1 To $aFileLines[0] - 1
		If FileWriteLine($hWriteHandle, $aFileLines[$nCount]) = 0 Then
			FileClose($hWriteHandle)
			Return SetError(3, 0, -1)
		EndIf
	Next
	; Write the last record and ensure it ends with the same as the input file
	If $aFileLines[$nCount] <> "" Then FileWrite($hWriteHandle, $aFileLines[$nCount] & $sEndsWith)
	FileClose($hWriteHandle)

	Return $iRetVal
EndFunc

;открываем файл
Func openFile($FilePath,$mode)
Local $file = FileOpen($FilePath, $mode)
If $file = -1 Then
    MsgBox(0, "Error", "Unable to open file." $FilePath)
   exit
 Else
	 Return $file
EndIf   
EndFunc

;ищем разделитель, возвращаем все, что до него
Func getFindString($inString)
	Return StringLeft($inString, StringInStr($inString, "=")-1)
 EndFunc

 ;А тут все, что после него
Func getReplaceString($inString)
	Return StringRight($inString, StringLen($inString)- StringInStr($inString, "="))
EndFunc

Для автоматизации всех этих действий я создал bat'ник "getRevision.bat", параметром в него передается папка, где находится глобальный модуль (это BUILD или REPO).

Содержимое getRevision.bat:

REM На всякий пожарный проверяется существование папки с исходниками
if not exist .REPO (
    echo FAIL: REPO folder isn't exist! 
	pause
    exit /b 1
	)
REM и файла-шаблона.
if not exist "%~dp0shablon.txt" (
    echo FAIL: Shablon isn't exist! 
	pause
    exit /b 2
	)
	
REM Запускаем SubWCRev.exe, получаем шаблон для парсера
"c:Program FilesTortoiseSVNbinSubWCRev.exe" .REPO "%~dp0shablon.txt" .ReplacerSettings.txt -f
REM Запускаем парсер, подменяем значение функций в глобальном модуле
"%~dp0Replacer.exe" ".ReplacerSettings.txt" "%1ГлобальныйМодуль.1s"
REM выпиливаем шаблон для парсера
del ".ReplacerSettings.txt"

Вызов getRevision.bat был добавлен в Build.bat, а так как Build.bat предварительно копирует исходники в отдельную папку BUILD и она же указывается параметром для getRevision.bat подмена значений никак не отражается в папке REPO с исходниками и соответственно изменения не попадают в SVN, что для меня очень удобно.
По просьбе коллег был создан отдельный bat'ник — СompileSVN.bat
Как можно понять по называнию, он делает подмену значений в оригинальной папке с исходниками и запускает компиляцию. Его содержимое:

@echo Установка значений SVN в глобальный модуль...
@echo off
call getrevision.bat .REPO
@echo Компиляция файлов из папки REPO в 1cv7.compile.md...
"%GCOMP_PATH%gcomp" -c -D .REPO -F 1cv7.compile.md>.compile.log

Compile.bat остался без изменений

Чтобы парсеру было что искать, вставляем в глобальный модуль функции «пустышки»

//-------------------------------------------------
Функция getSVNRevision() Экспорт
	Возврат 0;
КонецФункции
//-------------------------------------------------
Функция getSVNURL() Экспорт
	Возврат 0;
КонецФункции
//-------------------------------------------------
Функция getSVNBuildTime() Экспорт
	Возврат 0;
КонецФункции
//-------------------------------------------------
Результат

Если запустить CompileSVN.bat и посмотреть изменения в коммите, можно увидеть следующее:
(некоторые данные изменены):

//-------------------------------------------------
Функция getSVNRevision() Экспорт
	Return "5135";
КонецФункции
//-------------------------------------------------
Функция getSVNURL() Экспорт
	Return "https://сервер:порт/svn/trunk";
КонецФункции
//-------------------------------------------------
Функция getSVNBuildTime() Экспорт
	Return "2013/01/22 10:35:15";
КонецФункции
//-------------------------------------------------

Так же в глобальный модуль была добавлена процедура для выгрузки данных из магазина

глВыгрузитьMD_INFO

Процедура глВыгрузитьMD_INFO() Экспорт
	ВремяПоследнейЗаписи = "";
	Попытка
		ФС.АтрибутыФайла( КаталогИБ() + "1CV7.MD", , , , , ВремяПоследнейЗаписи,  );
	Исключение
	КонецПопытки;
	Текст = СоздатьОбъект( "Текст" );
	Текст.ДобавитьСтроку( "###MD Info" );
	Текст.ДобавитьСтроку( "SVN_Revision: " + getSVNRevision() );
	Текст.ДобавитьСтроку( "SVN_URL: " + getSVNURL() );
	Текст.ДобавитьСтроку( 
		"SVN_BuildTime: " +
			СтрЗаменить( getSVNBuildTime(), "/", "." )
		 );
	Текст.ДобавитьСтроку( "MD_UpdateTime: " + ВремяПоследнейЗаписи );
	Текст.ДобавитьСтроку( "### Константы: " );
	Текст.ДобавитьСтроку( "Магазин: " + СокрЛП( Константа.Магазин ) );
	Текст.ДобавитьСтроку( "КодМагазина: " + СокрЛП( Константа.КодМагазина ) );
	Попытка
		Текст.Записать( 
			глПолучитьПапкуОбмена_Исходящие() +
				"" +
				глПравильноеИмяФайлаДляОбмена( "MDInfo", "" )
			 );
	Исключение
	КонецПопытки;
КонецПроцедуры

Выгружается такой вот файлик (некоторые данные изменены):

### MD Info
SVN_Revision: 5137
SVN_URL: https://сервер:порт/svn/branches/имя_ветки
SVN_BuildTime: 2013.01.21 13:45:44
MD_UpdateTime: 2013.01.22 05:02:52
### Константы: 
Магазин: Имя_магазина 
КодМагазина: 24 

Файл записывается в папку для обмена, потом передается на FTP сервер. Оттуда наша база «Копирка» будет собирать эти файлы и полученные данные записывать в базу.

Для удобства коллективного обновления скриптов был использован инсталятор inno setup, подробно описывать его нет смысла — там все просто и есть хороший help.

При небольших манипуляциях вышеописанное вполне легко можно приспособить под любой другой язык программирования.

Благодарю за внимание.

Автор: RootOfLife

Источник

Поделиться

* - обязательные к заполнению поля