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

Хочу поделиться некоторыми идеями по поводу организации хранилищ в системе контроля версий. Для определенности: мы используем Меркуриал, но это не столь важно.

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

Есть бортовые устройства, одинаковые для разных аппаратов, а есть и специфические. Устройства могут программироваться разными разработчиками, а некоторые из устройств программируют контрагенты. Марки процессоров (контроллеров) бортовых устройств различаются. В разных устройствах могут использоваться одинаковые библиотеки: драйверов, математики и пр.

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

Сейчас представляется удивительным, что такая работа проводилась без системы контроля версий. Возможно это удавалось потому, что проектов было не так много, а разработчиков всего двое. Но дальше так продолжаться дело уже не может.

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


Итак, о самой концепции контроля. Для нас принципиально использование распределенной системы контроля версий (см. выше частые и длительные командировки). По ряду причин выбор пал на Меркуриал, хотя сошел бы, наверное, и Гит.

Хранилище используется не одно, а целый их набор (дерево, структура). Хранилища взаимосвязаны между собой и распределены по папкам в определенном порядке. Дальше такой набор хранилищ буду называть деревом хранилищ.

Одно дерево хранилищ размещено на сервере. Его клоны – на локальных ПК разработчиков. Разработчик клонирует локальное дерево или отдельные его ветки, как хочет.

Шаблон дерева хранилищ следующий:

Repositories
|
+–.CrossplatformLib1
|
+–.CrossplatformLib2
|
+–...
|
+–Platform1
| |
| +–.Platform1Lib1
| |
| +–.Platform1Lib2
| |
| +–...
|
+–Platform2
| |
| +–.Platform2Lib1
| |
| +–.Platform2Lib2
| |
| +–...
|
+–...
|
+–Devices
| |
| +–.Device1
| |
| +–.Device2
| |
| +–...
|
+–Vehicles
  |
  +–Vehicle1
  |
  +–Vehicle2
  |
  +–...

Точкой в начале имени помечены хранилища, например, .Device1. Имена без точек – обычные папки, например, Devices.

В корне дерева (в корне папки Repositories) располагаются хранилища .CrossplatformLibX, содержащие кроссплатформенные библиотеки, например, математику, алгоритмы и пр.

На том же уровне находятся папки PlatformX, с хранилищами .PlatformXLibY, содержащими библиотеки, специфические для платформы PlatformX. Например, сетевые драйверы под ОС Windows.

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

Последней папкой верхнего уровня является Vehicles. В ней содержатся проекты для разных аппаратов VehicleX.

В свою очередь проект конкретного аппарата VehicleX может представлять собой хранилище или набор хранилищ. Сейчас это не столь важно, а позже будет рассмотрено подробнее.

Рассмотренная структура демонстрирует расположение хранилищ друг относительно друга. Теперь рассмотрим как устроены сами хранилища.

Существенным образом используется механизм подхранилищ (как я понимаю, во всех современных системах контроля версий есть такие возможности).

Рассмотрим для примера некоторое устройство Devices.DeviceX. Допустим, устройство Х использует кроссплатформенную библиотеку Y и специализированную для платформы Z библиотеку N. Тогда на уровне папки Devices.DeviceX создаются клоны хранилищ .CrossplatformLibY и PlatformZ.PlatformZLibN:

Repositories
|
+–Devices
  |
  +–.DeviceX
    |
    +–.CrossplatformLibY
    |
    +–.PlatformZLibN
    |
    +–DeviceXSpecificFolder1
    |
    +–...

Наряду с подхранилищами .CrossplatformLibY и .PlatformZLibN в папке располагаются специфические для устройства Х файлы исходного кода в папках DeviceXSpecificFolderM и пр.

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

Теперь вернемся к проекту аппарата VehicleX, включающего в себя множество сетевых устройств .DeviceY. В простейшем случае все бортовые устройства являются ведомыми абонентами в сети, а одно из них – ведущим. Назовем его Master. Ведущий абонент представляет собой центр интеграции системы – он знает форматы всех сообщений, рассылает запросы, получает ответы и пр. Соответствующее хранилище .VehicleX может иметь следующую структуру:

Repositories
|
+–Vehicles
  |
  +–.VehicleX
    |
    +–.DeviceY
    |
    +–.DeviceZ
    |
    +–DeviceM
    |
    +–...
    |
    +–Master

Сетевые устройства могут быть как универсальными (используемыми на разных аппаратах), так и специфическими. В первом случае соответствующее подхранилище (пусть это будет .DeviceY) является клоном хранилища Devices.DeviceY. Во втором случае, проект устройства может контролироваться как отдельное подхранилище (.DeviceZ), а может быть организовано, как отдельная подпапка хранилища .VehicleX (на примере – это .VehicleXDeviceM). Вариант с подхранилищем удобнее тем, что его проще вычленить из проекта (путем клонирования).

Возникает вопрос. А нужно ли в хранилище .VehicleX хранить все исходники универсальных устройств .DeviceY? По сути, для разработки ведущего абонента Master достаточно знать только интерфейсы универсальных устройств, в простейшем случае – форматы их сообщений. Эти форматы, как правило, стабильны и редко изменяются. В то же время реализация устройств подвергается изменениям регулярно, по крайней мере, на ранних стадиях разработки она меняется существенно чаще, чем интерфейс.

Зачем устанавливать между проектами .VehicleX и .DeviceY сильные связи? Зачем интегратору системы (разработчику Master) разбираться и синхронизировать эти проекты при изменении каких-то внутренних деталей реализации сетевых устройств?

Указанная проблема преодолевается, если синхронизировать не проекты устройств целиком, а только их интерфейсы. Один из вариантов отслеживания интерфейсов может быть таким. Выделить файлы интерфейса устройства .DeviceY в отдельное подхранилище .Interface:

Repositories
|
+–Devices
  |
  +–.DeviceY
    |
    +–.Interface
    | |
    + +- device_y.h
    |
    +–Source
    |
    +–...

Подхранилище .Interface нередко может состоять из единственного заголовочного файла device_y.h, ну и пусть!

Теперь в хранилище аппарата .VehicleX можно отслеживать не весь проект .DeviceY, а только его интерфейс .DeviceY.Interface, который менее громоздок и более стабилен. Его клон можно поместить в подхранилище .VehicleXDevices.DeviceY:

Repositories
|
+–Vehicles
  |
  +–.VehicleX
    |
    +–Devices
    | |
    + +-.DeviceY
    | |
    + +-...
    |
    +–...
    |
    +–Master

В папку .VehicleXDevices можно помещать интерфейсы и остальных сетевых устройств. Чтобы не прописывать в настройках компилятора Master пути к каждому подхранилищу интерфейса сетевого устройства .VehicleXDevices.DeviceY, в папку .VehicleXDevices можно помещать файлы device_y.h с переадресацией #include ”..DeviceYdevice_y.h”. Тогда для компилятора Master достаточно будет прописать только путь ..Devices.

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

Вот и все в общих чертах. Но здесь не все вопросы представлены. Например, мы пока не решили как контролировать версии исполняемых файлов (бинарников). Уж очень не хочется ставить их под контроль системы версий, чтобы хранилища не разбухали. А учитывая, что каждый проект состоит из множества исполняемых файлов – предвижу «кошмар dll».

Автор: YuT

Источник

Оставить комментарий