- PVSM.RU - https://www.pvsm.ru -

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

Добрый день.

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

Предыстория

Торговая сеть средних размеров (примерно 150 магазинов), небольшой коллектив разработчиков, пишем на 1С 7.7, конфигурации в магазинах самописные.
Используется SVN сервер Collabnet Subversion [2], клиент TurtoiseSVN [3], компилятор/декомпилятор метаданных 1С gComp [4]. Есть 1Сная база(мы зовем ее «копирка») из которой мы «раскидываем» обновления и обработки по магазинами. Магазины обновляются автоматом с помощью ConfStarter [5]'а.
Есть несколько самописных 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 файла.

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

Программка-парсер была написана на AutoIt [7]. Я назвал ее 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 [8], подробно описывать его нет смысла — там все просто и есть хороший help.

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

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

Автор: RootOfLife

Источник [9]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/svn/25511

Ссылки в тексте:

[1] статьей: http://habrahabr.ru/post/157197/

[2] Collabnet Subversion: http://www.collab.net/downloads/subversion

[3] TurtoiseSVN: http://tortoisesvn.net/downloads.html

[4] gComp: http://1c.alterplast.ru/gcomp/

[5] ConfStarter: http://infostart.ru/public/14304/

[6] тут: http://tortoisesvn.net/docs/nightly/TortoiseSVN_ru/tsvn-subwcrev-keywords.html

[7] AutoIt: http://www.autoitscript.com/site/autoit/downloads/

[8] inno setup: http://www.jrsoftware.org/isdl.php

[9] Источник: http://habrahabr.ru/post/166769/