Показать сообщение отдельно
Старый 03.01.2003, 09:34   #1  
Андре is offline
Андре
Moderator
Сотрудники компании GMCS
 
2,375 / 464 (20) +++++++
Регистрация: 03.12.2001
Размышления на тему “Системы контроля версий в Аксапте”.
Добрый день.

Люблю праздники - неожиданная куча свободного времени позволяет обдумать и реализовать то, о чем давно думал, но руки не доходили. Собственно, эта статья и является результатом моих экспериментов. Здесь я попытаюсь подвести итог своим размышлениям. Следует учесть, что данные размышления, пока так и остаются теорией и не были проверены на практике. Поэтому относитесь к проекту не как к готовому решению, а как к одному из возможных направлений решения подобных задач.

Итак, к делу. Не раз меняя что-либо в стандартной функциональности Аксапты я доходил до точки, когда был вынужден сказать себе – “Стоп. Вернемся к тому, с чего начали” или “Интересно, как выглядел этот код, пока он я еще работал и я не начал его менять ?”. Посмотрим, что для этого может предложить стандартная функциональность Аксапты. Да в принципе и не много – ну могу я периодически выгружать необходимый проект во внешний файл(в конце концов файлов становится так много, что сам перестаешь в них разбираться) , могу откатиться назад к первоначальному состоянию, просто удалив свой слой для данного объекта (дешево и сердито). Эти способы мало того, что неудобны, так еще и не позволяют многих вещей, таких как:

• Откат отдельного метода класса. Почему я вынужден экспортировать/импортировать весь класс, когда мне нужен всего лишь один метод? И не говорите мне, что это противоречит духу ООП – да противоречит, но часто бывает нужным.
• Установление личности программиста, внесшего те или иные изменения в проект. Да есть такая вещь как комментарии, но либо мы храним в методе закомментированную историю всего метода и код вырастает до нереальных объемов, либо мы удаляем исправленный код и эту версию нам уже не вернуть.
• Наглядное сравнение двух версий проекта. Этот пункт в принципе реализуем – на usr слой грузим одну версию проекта, на usp – другую и сравниваем. Но уж больно муторно все это и нужна очень веская причина, чтобы начинать всю эту бодягу.

Итак, после некоторого времени программирования в Аксапте перед программистом встает проблема контроля версий проектов, классов методов и прочих объектов Аксапты.

Что здесь можно сделать? Я вижу здесь два решения:

1. Написание своей системы контроля версий в Аксапте.

Аксапта предоставляет средства, которые позволяют получить доступ к коду любого метода любого класса. Получаем код и в необходимый момент времени сохраняем этот код в специально созданную таблицу примерно такого вида:



Кроме того, в этой либо другой таблице можно хранить информацию о версии (а именно дату версии, автора версии и т.д.)

Однако прежде чем реализовывать данный вариант следует подумать о таких вещах, как:

• Мы храним каждую версию метода, а не различия между ними. Это приводит к увеличению объема нашей таблицы, что понятно не есть хорошо.
• Кроме того, неплохо бы подумать о том, как мы будем сравнивать версии методов и визуализировать эти отличия.

Эти моменты требуют разработки очень большой функциональности и прежде чем пойти по этому пути, стоит решить – а стоит ли это тех усилий, которые вы затратите. С другой стороны, вы получаете полный контроль над процессом. Так что решать вам.

2. Можно интегрировать Аксапту с одной из уже имеющихся систем контроля версий, например Microsoft SourceSafe.


По этому пути я и попробовал пойти.

Запускаем Ole View из состава Microsoft Visual Studio и в разделе Type Libraries находим строку Microsoft SourceSafe 6.0 Type Library (Ver 5.1). Отлично – библиотека есть, значит, уже есть шанс. Там же можно посмотреть, что сервером объектов в моем случае является d:\Languages\vs6\vss\win32\SSAPI.dll.

Остается рассмотреть только некоторые ключевые моменты:

Открываем базу данных в SourceSafe:

PHP код:
static void dem_ssf_tut1(Args _args)
{
   
str s;
   
COM com1;
   
COM com2;
   ;
   
com1 = new COM("SourceSafe");   // IVSSDatabase
   
com1.open('d:\\SS\\SRCSAFE.INI''Andrey''password'); 
Следует обратить внимание на то, что в качестве пути к базе следует указывать не каталог, а именно ini файл. Вторым и третьим параметром являются имя пользователя и его пароль в SourceSafe; естественно они должны быть заданы в SS.

Выбираем проект, который будет использоваться при работе с Аксаптой:

PHP код:
com1.CurrentProject('Axapta'); 
Проект должен быть предварительно создан.

И добавляем какой-либо файл в SourceSafe:

PHP код:
   com2 com1.VSSItem('$/Axapta');
   
com2.add('c:\\1.xml''Hello'); 
Второй параметр – это комментарий, который будет виден в SourceSafe.

Аналогичным образом интерфейс IVSSItem предоставляет возможность выбирать в SourceSafe необходимые файлы, делать Check In и Checkout.

Рассмотрим подробнее операцию сравнения двух версий одного метода. Немного покопавшись в реестре, я нашел интересный ActiveX, зарегистрированный в моей системе – DiffMergeCtrl ActiveX Control Module (DiffMergeCtl.ocx). К сожалению, у меня не было возможности узнать, кто его зарегистрировал, но судя по тому, что он находился в папке D:\Program Files\Common Files\Microsoft Shared\vs98 его установил либо сам SourceSafe,либо он зарегистрировался при установке какой-либо иной части Visual Studio. На всякий случай я выложу этот ocx, если у вас не зарегистрирован этот компонент – скачайте и зарегистрируйте его.

Все это дело я реализовал как Add-Ins:



поэтому при импорте проекта будьте осторожны – он перезапишет ваш SysContextMenu.

Вся функциональность реализована в виде двух классов – dem_SourceSafeEngine и dem_SourceSafeLauncher.

При выборе любого из пунктов меню запускается метод main класса dem_SourceSafeLauncher. При этом сам выбранный пункт меню передается в EnumParameter MenuItem’а. В методе извлекается вся необходимая информация и запускается метод Run() этого же класса:


PHP код:
       sysContextMenu   args.parmObject();
       
treeNode         sysContextMenu.first();
       
actionType       args.parmEnum();

       
launcher = new dem_SourceSafeLauncher();
       
launcher.Run(treeNodeactionType); 
В методе Run() мы определяем то, какой из пунктов меню был выбран и вызываем соответствующий метод все этого же класса:

PHP код:
void Run(TreeNode _nodeint _actionType)
{
       switch(
_actionType)
       {
            case 
0:         // Добавление в хранилище
                
this.AddItem(_node);
                break;
            case 
1:         // CheckOut
                
this.CheckOut(_node);
                break;
            case 
2:         // CheckIn
                
this.CheckIn(_node);
                break;
            case 
3:         // Show history
                
this.ShowHistory(_node);
                break;
        }   
// first switch

Если Вам необходимо добавить еще одну операцию – добавьте еще один элемент в dem_ssAction, создайте MenuItem с данным значением Enum параметра и добавьте в этот метод еще один case.

Рассмотрим один из методов, например AddItem:

PHP код:
void AddItem(TreeNode _node)
{
       switch(
_node.sysNodeType())
     {
        case 
502,503,504 :  // методы класса
            
ssEngine.AddItem(_nodethis.getComment());
            break;
        case 
329 :          // классы
            
ssEngine.AddClass(_nodethis.getComment());
            break;
     } 
// second switch

Здесь мы анализируем с каким из типов TreeNode мы работаем и в зависимости от этого вызываем тот или иной метод dem_SourceSafeEngine.
Если вы решите аналогичным образом работать с методами формы и таблиц, Вам нужно реализовать соответсвующие методы dem_SourceSafeEngine и добавить в этот метод соответсвующие cas’ы.

Напоследок, покажу пару методов из dem_SourceSafeEngine – addItem и addClass:

PHP код:
void AddItem(TreeNode _nodestr _comment)
{
   
TextBuffer   tb;
   
str          source;
   
str          element;

   
// Получаем имя элемента (какое оно будет в SourceSafe)
   
element this.GetElementNameFromTreeNode(_node);
   
// Проверяем, нет ли уже такого элемента
   
if (this.haveItem(element))
     
Info("Данный элемент присутствует в хранилище и добавить его нельзя");
   else
   {
     
source _node.getSource();
     
// Так как работа с SS ведется через файлы, сохраняем код в файл
     
tb = new TextBuffer();
     
tb.appendText(source);
     
tb.toFile(tmpPath+"\\"+element);
     
// Заносим в SS
     
this.AddItemFromFile(tmpPath+"\\"+element_comment);
   }


В первой строке метода мы генерируем имя, под которым элемент будет находиться в хранилище. Алгоритм генерации этого имени расположен в GetElementNameFromTreeNode. В данный момент имя элемента образуется из имени класса и имени метода, разделенных точкой. Если Вас не устраивает данный алгоритм – перепишите этот метод.

Теперь addClass:

PHP код:
void AddClass(TreeNode _nodestr _comment)
{
   
int          methodCnt;      
   
int          i;
   
TreeNode     methodNode;
   ;
   
methodCnt _node.childNodeCount();

   
methodNode _node.firstChild();
   
this.AddItem(methodNode_comment);

   while (
methodNode)
   {
      
methodNode methodNode.nextSibling();
      if (
methodNode)
            
this.AddItem(methodNode_comment);
   }

Для каждого метода класса вызывается метод addItem, рассмотренный выше. Если Вы хотите реализовать контроль версий методов форм и таблиц нужно реализовать метода addTable и addForm, в цикле которых аналогично будет вызываться addItem. По-моему все просто.

Перед началом работы необходимо произвести настройку всей системы. Для этого предназначена форма dem_SSSetup.



Описание настроек:

• Путь к базе – полный путь к файлу srcsafe.ini соответствующей базы SourceSaf’а. Путь должен быть задан в соответствии с “Аксаптовским стандартом” – двойной слеш вместо одинарного.

• Имя проекта в SourceSafe – тот проект в SourceSafe, в котором Вы собираетесь хранить методы Аксапты. Естественно проект должен быть создан перед началом работы.



• Имя временной папки – укажите папку, в которую будут временно выгружаться файлы при Checkout. Папка должна существовать. Кроме того, в Аксапте необходимо сделать эту папку Working Folder для проекта Axapta. (Становимся в дереве на проект “Аксапта”, говорим File -> Set Working Folder и выбираем эту папку). Обратите внимание, что путь к временной папке должен заканчиваться двойным слешем – это важно.

• В таблице задаются имена пользователей и пароли для доступа в SourceSafe. Пользователи выбираются из таблицы EmplTable, пароли непосредственно заводятся в таблицу. В базе SourceSafe должны существовать такие пользователи с заданными паролями. Для занесения пользователей в SourceSafe воспользуйтесь утилитой SourceSafeAdmin. При работе Аксапта определяет текущего пользователя (curUserId()) берет из этой таблицы соответствующий пароль и именно с этим именем и паролем лезет в SourceSafe.

В случае неправильной настройки одного из этих параметров в методе dem_SourceSafeEngine.new произойдет ошибка при попытке вызвать метод open COM-объекта.

Теперь о том, что осталось за кадром:

• Все это дело реализовано только для классов и методов классов. Система контроля версий не будет работать с методами таблиц и отчетов. Я пока не стал это реализовывать; думаю, если это кому-то понадобится - справитесь.

• В проекте опущены некоторые проверки, которые не существенны для того чтобы понять суть дела, но необходимы в финальной версии проекта.

• В тот момент, когда я делаю CheckOut, неплохо было бы блокировать в Аксапте соответствующий TreeNode в АОТ’е. По-моему, я когда-то видел такую возможность, но сейчас когда это понадобилось, я не нашел этой возможности. Если кто-нибудь знает как это сделать - сообщите пожалуйста мне об этом.

• Интерфейс IVSSVersions. Этот интерфейс предоставляет информацию о версиях. Как работать с ним в Axapta я так и не понял.

Вот как бы это выглядело в VB:


PHP код:
Dim strSrcSafeIni As String
Dim strUserName 
As String
Dim strPassword 
As String
Dim objDatabase 
As VSSDatabase
Dim cur 
As VSSItem
Dim proj 
As VSSItem
Dim i 
As Integer
Dim ver 
As VSSVersion
Dim it 
As VSSItem

strSrcSafeIni 
"d:\SS\SRCSAFE.INI"
strUserName "dem"
strPassword "dem"

Set objDatabase = New VSSDatabase
objDatabase
.Open strSrcSafeInistrUserNamestrPassword
objDatabase
.CurrentProject "MyProject"
Set proj objDatabase.VSSItem("$/MyProject")
Set cur proj.Items.Item("1.xml")

For 
Each ver In cur.Versions                       ' ***
   Text1.Text = Text1.Text + ver.Username        ' 
***
Next                                      ' ***

Set objDatabase = Nothing
End Sub 
Несколько сложнее в VC:

PHP код:
SourceSafeTypeLib::IVSSDatabasePtr    db;
                
HRESULT hr;
hr db.CreateInstance(__uuidof(SourceSafeTypeLib::VSSDatabase));
hr db->OpenL"d:\\SS\\SRCSAFE.INI"L"dem"L"dem" );

SourceSafeTypeLib::IVSSItemPtr    projcur;

proj db->GetVSSItem(L"$/MyProject"VARIANT_FALSE);
cur proj->GetItems(VARIANT_FALSE)->GetItem_variant_t(L"1.xml") );

CComPtr<IEnumVARIANTVersions;
IUnknownPtr    unk;
SourceSafeTypeLib::IVSSVersionsPtr vers;
vers cur->GetVersions(0);
unk vers->_NewEnum();
hr unk.QueryInterface(__uuidof(IEnumVARIANT), &Versions);
        
SourceSafeTypeLib::IVSSVersionPtr ver;
CComVariant    vVer;
ULONG Fetched;
for (
Versions->Next(1, &vVer, &Fetched); Fetched != 0Versions->Next(1, &vVer, &Fetched))
{
            
ver vVer.pdispVal;
            
ATLTRACE("%s\n",(char*)ver->GetUsername());
 } 
Имея интерфейс IVSSVersions в VB с помощью конструкции For Each …. я перебирал все версии конкретного Item’а.
В VC я смог вызвать _NewEnum().
Как реализовать данную функциональность в Аксапте мне не понятно. Опять же, если у кого то есть идеи по этому поводу – буду рад их услышать.

• Обработка ошибок при вызове COM – методов. В случае если вызов COM объекта/метода приводит к ошибке появляется похожее окошко:



Как можно подавить появление этого окошка ? Конструкция try {… не спасает, а лишь откладывает появление данного сообщения до момента уничтожения объекта, некорректно вызвавшего метод COM объекта.

• В Аксапте я нашел следующие классы xVcsServer и xVcsClient, обладающие весьма многообещающими методами. Судя по всему, эти классы нигде не используются и мне не совсем понятно их назначение – то ли у нас не закуплена соответствующая функциональность, то ли это запланировано на будущее, то ли это наоборот осталось от старой функциональности. Если кто-либо поделится со мной этой информацией – буду признателен.

Заключение:

1. Буду рад услышать любые мысли/замечания как по моей реализации, так и по самой проблеме статьи. Особенно буду благодарен тем, кто поможет решить вопросы описанные чуть выше.
2. Вы можете использовать этот проект по своему усмотрению, как только Вам будет угодно(то есть, без каких либо ограничений). Если идея кому-то понравится и дело дойдет до коммерческого распространения буду рад увидеть ссылку на автора(то есть меня) в исходном коде. Хотя и это не является обязательным.
Вложения
Тип файла: img6701-1 (4.7 Кб, 1857 просмотров)
Тип файла: img6701-2 (17.4 Кб, 1898 просмотров)
Тип файла: img6701-3 (34.1 Кб, 1777 просмотров)
Тип файла: img6701-4 (3.2 Кб, 1767 просмотров)
Тип файла: img6701-5 (14.3 Кб, 1880 просмотров)
За это сообщение автора поблагодарили: mazzy (17).