Отслеживание вариантов исходного кода

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

Я вижу несколько вариантов сохранения вариантов прошивки, но ни один из них меня не слишком радует. Какие лучшие практики вы применяете в своей работе?

Альтернативы, которые я могу придумать:

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

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

  • основной репозиторий как подмодуль. Сделайте все файлы, содержащие основные функции, отдельным репозиторием. Все продукты содержат версию основного репозитория в качестве подмодуля. Минусы: я не вижу, чтобы в конечном итоге не было вариантов основного подмодуля. Затем у нас снова проблемы, и тогда мы снова будем использовать определения или что-то плохое. Основной репозиторий с ветками? Затем мы возвращаемся к предыдущему варианту: изменение, которое применяется ко всем ветвям, должно быть объединено, но слияние также включает в себя специфические для продукта вещи.

  • создайте репозиторий для каждого модуля. Например, репозиторий для драйвера дисплея, еще один для оборудования управления питанием, еще один для пользовательского интерфейса ввода, ... Плюсы: хорошая модульность. Сделайте новый продукт, просто подобрав нужные вам модули в качестве подмодулей! Все подмодули могут иметь ответвления, если, например, вариант использует аппаратное обеспечение по-другому. Минусы: много-много модулей, каждый из которых отслеживает пару файлов (включаемый файл и исходный файл). Хлопот. Кто-то делает важное обновление в каком-то модуле? Затем кто-то должен включить изменение в другие ветки этого модуля, если это уместно. Затем кто-то также должен обновить подмодуль в каждом репозитории продукта. Довольно много работы, и мы как бы теряем моментальную сторону git.

Как вы это делаете, и как это работает? Или как бы вы это сделали?

У меня есть ощущение, что я должен получить опыт в сборе вишни.


person Gauthier    schedule 21.09.2009    source источник
comment
С помощью одной ветки для решения по продукту вы всегда можете выполнять разработку в отдельной ветке (ветвях), а затем объединять эту ветку с вариантами (для каждого продукта) ветки.   -  person Jakub Narębski    schedule 21.09.2009


Ответы (6)


Я бы постарался использовать #define как можно чаще. С правильным кодом вы можете свести к минимуму влияние на читабельность и повторения.

Но в то же время подход #define можно смело сочетать с разделением и ветвлением, применение которых зависит от характера кодовой базы.

person Michael Krelin - hacker    schedule 21.09.2009
comment
Определения - худшее из возможных решений. Они делают исходный код, который вы видите в редакторе, отличным от того, который видит компилятор. - person xpmatteo; 19.12.2014
comment
А if делают исходный код, который вы видите в редакторе, отличным от того, который выполняется. Все это называется программированием. - person Michael Krelin - hacker; 19.12.2014
comment
Весь смысл языков высокого уровня в том, чтобы упростить программирование для людей. Необходимость интерпретировать в уме IF во время компиляции, а также IF во время выполнения значительно усложняет и подвержена ошибкам понимание того, что делает программа. - person xpmatteo; 23.12.2014
comment
Да, сложность обычно приходит со сложностью. И вы с такой же вероятностью сделаете свой код нечитаемым из-за ifs во время компиляции, как из-за ifs во время выполнения. Да, надо думать, прежде чем что-либо использовать. - person Michael Krelin - hacker; 23.12.2014

Вы должны стремиться, насколько это возможно, хранить пользовательский код каждого варианта в отдельном наборе файлов. Затем ваша система сборки (Makefile или что-то еще) выбирает, какие источники использовать в зависимости от того, какой вариант вы собираете.

Преимущество этого в том, что при работе с конкретным вариантом вы видите весь его код вместе, без кода других вариантов, который мог бы запутать вещи. Читабельность также намного лучше, чем засорение исходного кода #ifdef, #elif, #endif и т. д.

Ветви работают лучше всего, когда вы знаете, что в будущем вы захотите объединить весь код из ветки в главную ветку (или другие ветки). Это не работает только для объединения некоторых изменений из ветки в ветку (хотя это, безусловно, можно сделать). Таким образом, сохранение отдельных веток для каждого варианта, вероятно, не даст хороших результатов.

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

person Dan Moulding    schedule 21.09.2009
comment
+1 за совет о ветвлении, только если вы планируете снова слиться (хотя есть исключения). Также за риск использования трюков VCS для поддержки кода. См. мой комментарий к PhiLho о DRY с такими пользовательскими файлами кода. - person Gauthier; 21.09.2009
comment
Когда я работаю над определенным вариантом, мне часто хочется увидеть код других вариантов. Это хорошее применение для свертывания кода. - person Jeanne Pindar; 25.09.2009

Я не уверен, что это «лучшая практика», но проект Scintilla уже много лет использует что-то, что все еще вполне управляемо. Он имеет только одну ветку, общую для всех платформ (в основном Windows/GTK+/Mac, но с вариантами для VMS, Fox и т. д.).

Каким-то образом он использует первый вариант, который довольно распространен: он использует определения для управления небольшими специфическими для платформы частями внутри исходных кодов, где нецелесообразно или невозможно размещать общий код.
Обратите внимание, что этот вариант недоступен для некоторых языков ( например, Ява).

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

person PhiLho    schedule 21.09.2009
comment
OO было бы неплохо, но это встроенный код на ассемблере, даже без компилятора C. Я понимаю, что концепции объектно-ориентированного программирования можно применять в любом случае. Сборка и линковка только нужных файлов - вариант, но я считаю, что два таких файла (по одному на вариант) будут очень похожи, таким образом повторяя друг друга. - person Gauthier; 21.09.2009
comment
Иметь «совместимые» файлы, будь то один или один для каждой уникальной платформы, я считаю хорошей идеей. - person Jakub Narębski; 21.09.2009
comment
Обходной путь для Java — использовать дополнительный плагин, такой как Antenna for Eclipse. Не идеально, но вполне полезно. - person David; 09.07.2013

Я думаю, что правильный ответ частично зависит от того, насколько радикально различаются варианты.

Если есть небольшие отличающиеся части, разумно использовать условную компиляцию для одного исходного файла. Если варианты реализации согласуются только в интерфейсе вызова, возможно, лучше использовать отдельные файлы. Вы можете включить радикально отличающиеся реализации в один файл с помощью условной компиляции; насколько это беспорядочно, зависит от объема вариантного кода. Если это, скажем, четыре варианта примерно по 100 строк в каждом, то, может быть, один файл подойдет. Если это четыре варианта по 100, 300, 500 и 900 строк, то один файл, наверное, плохая идея.

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

#include "config.h"
#if defined(USE_VARIANT_A)
#include "variant_a.c"
#elif defined(USE_VARIANT_B)
#include "variant_b.c"
#else
#include "basecase.c"
#endif
person Jonathan Leffler    schedule 21.09.2009
comment
Я бы предпочел сделать это в make-файле, мне не нравится включать c-файлы с помощью #include: они не будут отображаться в моем make-файле. Я согласен, что серебряной пули не существует, решение зависит от дисперсии вариантов :) Чего я боюсь, так это начать с #define и увидеть, как семейство продуктов вырастет до 10 вариантов. - person Gauthier; 21.09.2009
comment
Если вы создаете make-файлы (в отличие от использования make-файла с контролируемой версией), используйте прямой подход, перечислив правильные файлы в make-файле. Этот подход работает нормально (не обязательно рекомендуется, но работает), когда make-файл не генерируется динамически. Мое основное послание заключается в том, что один размер не подходит для всех, и в зависимости от деталей актуальны разные решения. Если вы думаете, что у вас может быть 10 вариантов элементов, спроектируйте свою систему так, чтобы она работала с таким количеством вариантов. Вы не говорите, 10 ли это бинарных решений, или 1 из 10 альтернатив, или что-то еще. - person Jonathan Leffler; 21.09.2009
comment
До сих пор я имел в виду 1 из 10 альтернатив. Слава богу :) Я не понимаю, почему наличие make-файлов с контролем версий (по одному на вариант) делает этот метод недействительным? - person Gauthier; 22.09.2009
comment
Это зависит от того, как вы структурируете свои make-файлы. Если вы строите один из десяти разных таргетов в зависимости от нужного вам варианта — и у каждого варианта есть свои списки файлов, которые нужны для его сборки — то все в порядке. Это также зависит от того, собираетесь ли вы всегда собирать все десять вариантов в одной сборке или только по одному за раз. Я работаю над системой, в которой один и тот же make-файл используется на 6 или 7 разных платформах (не утруждая себя подсчетом различий между 32-битными и 64-битными), но в любой момент он создается только для одной платформы и одна и та же цель используется на каждой платформе... - person Jonathan Leffler; 22.09.2009
comment
.. Там неудобно, когда make решает, какой набор файлов имеет отношение к каждой платформе, поэтому в некоторых местах используется метод включения. У нас также есть система, использующая #ifdef. Как правило, сценариев #ifdef намного больше, чем сценариев #include. Таким образом, ваш пробег будет варьироваться, как говорится. [И редактор разметки для комментариев неправильно обрабатывает обратные кавычки вокруг препроцессоров C! Нахуй!!! Либо так, либо я совершил одну и ту же ошибку дважды подряд.] - person Jonathan Leffler; 22.09.2009

Вас ждет мир боли!

Что бы вы ни делали, вам нужна среда автоматической сборки. По крайней мере, вам нужен какой-то автоматический способ сборки всех разных версий вашей прошивки. У меня были проблемы с исправлением ошибки в одной версии и нарушением сборки другой версии.

В идеале вы могли бы загрузить разные цели и запустить несколько дымовых тестов.

Если вы пойдете по маршруту #define, я бы поместил следующее где-нибудь, где проверяется вариант:

#else 
    #error You MUST specify a variant!
#endif

Это гарантирует, что все файлы будут созданы для одного и того же варианта в процессе сборки.

person Robert    schedule 22.09.2009
comment
Да, #ошибка. И даже, если определены несколько вариантов определения, undefined все, кроме одного (или отправить еще одну ошибку #). - person Gauthier; 24.09.2009

Я столкнулся с той же проблемой. Я хочу делиться исходным кодом не только между целевыми объектами, но и между платформами (например, Mono и Visual Studio. Кажется, что нет никакого способа легко сделать это только с версией, которую ветвление/тегирование предоставляет Git. Потому что в идеале вы "Хотелось бы поддерживать общую ветку кода и ветки, специфичные для хоста/цели, и просто объединять общий код в этих ветках и из них. Но я не знаю, как это сделать. Может быть, комбинация ветвления и тегов? Кто-нибудь?"

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

Таким образом, ответ, похоже, заключается в том, что вам нужен какой-то механизм управления конфигурацией/сборкой поверх git, чтобы управлять этим. Но это, казалось бы, добавляет много сложности, поэтому, возможно, слияния вишни в общую ветку не будут плохим способом. Особенно, если вы используете другие методы, чтобы свести исходные варианты к минимуму. (Может ли тегирование помочь автоматизировать это?).

person Darrel Lee    schedule 10.04.2011