Каков ваш опыт в отношении нерекурсивного создания?

Несколько лет назад я прочитал статью " Рекурсивное вредное воздействие" и реализовал эту идею в своем собственном процессе сборки. Недавно я прочитал другую статью с идеями о том, как реализовать нерекурсивный make. Таким образом, у меня есть несколько данных, которые нерекурсивно make работу по крайней мере для нескольких проектов.

Но мне интересно узнать об опыте других. Вы пробовали нерекурсивную make? Это сделало вещи лучше или хуже? Это стоило времени?

Ответ 1

Мы используем нерекурсивную систему GNU Make в компании, в которой я работаю. Он основан на бумаге Миллера и особенно на ссылке "Реализация нерекурсивного make", которую вы дали. Нам удалось уточнить код Бергена в систему, где нет котельной пластины вообще в make файлах подкаталогов. По большому счету, он отлично работает и намного лучше, чем наша предыдущая система (рекурсивная вещь с GNU Automake).

Мы поддерживаем все "основные" операционные системы (коммерчески): AIX, HP-UX, Linux, OS X, Solaris, Windows и даже мэйнфрейм AS/400. Мы собираем один и тот же код для всех этих систем, причем зависящие от платформы части выделяются в библиотеки.

В нашем дереве содержится более двух миллионов строк кода C в примерно 2000 подкаталогах и 20000 файлах. Мы серьезно рассматривали использование SCons, но просто не могли заставить его работать достаточно быстро. В более медленных системах Python будет использовать пару десятков секунд, просто разбираясь в файлах SCons, где GNU Make сделал то же самое примерно за одну секунду. Это было около трех лет назад, поэтому с тех пор все изменилось. Обратите внимание, что мы обычно сохраняем исходный код на общем ресурсе NFS/CIFS и создаем один и тот же код на нескольких платформах. Это означает, что инструмент сборки еще медленнее сканировать исходное дерево для изменений.

Наша нерекурсивная система GNU Make не без проблем. Вот некоторые из самых больших препятствий, с которыми вы можете столкнуться:

  • Сделать его переносным, особенно для Windows, очень много.
  • В то время как GNU Make - это практически удобный функциональный язык программирования, он не подходит для программирования в целом. В частности, нет пространств имен, модулей или чего-либо подобного, чтобы помочь вам изолировать фрагменты друг от друга. Это может вызвать проблемы, хотя и не так сильно, как вы думаете.

Основные победы над нашей старой рекурсивной системой makefile:

  • быстро. Требуется около двух секунд, чтобы проверить все дерево (каталоги 2k, файлы 20k) и либо принять решение о его обновлении, либо начать компиляцию. Старая рекурсивная вещь займет больше минуты, чтобы ничего не делать.
  • Правильно обрабатывает зависимости. Наша старая система опиралась на поддиректории заказов, которые были построены и т.д. Так же, как вы ожидали от чтения бумаги Миллера, обработка всего дерева как единого объекта действительно является правильным способом решения этой проблемы.
  • Он переносится на все наши поддерживаемые системы, после всей тяжелой работы, которую мы вложили в нее. Это довольно круто.
  • Система абстракции позволяет писать очень сжатые make файлы. Типичный подкаталог, который определяет только библиотеку, - это всего две строки. Одна строка дает имя библиотеки, а другая - библиотеки, на которые это зависит.

Относительно последнего элемента в приведенном выше списке. Мы завершили внедрение своего рода средств для расширения макросов в системе сборки. Подкатегории make файлы перечисляют программы, подкаталоги, библиотеки и другие распространенные вещи в таких переменных, как ПРОГРАММЫ, СУБДЕРЫ, LIBS. Затем каждый из них расширяется в "реальные" правила GNU Make. Это позволяет избежать многих проблем с пространством имен. Например, в нашей системе прекрасно иметь несколько исходных файлов с тем же именем, без проблем.

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

Ответ 2

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

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

TARGET := foo

TGT_LDLIBS := -lbar

SOURCES := foo.c baz.cpp

SRC_CFLAGS   := -std=c99
SRC_CXXFLAGS := -fstrict-aliasing
SRC_INCDIRS  := inc /usr/local/include/bar

Файл Makefile проекта, такой как выше, будет делать именно то, что вы ожидаете: создайте исполняемый файл с именем "foo", скомпилируйте foo.c(с CFLAGS = -std = c99) и baz.cpp(с CXXFLAGS = -fstrict -ализирование) и добавление "./inc" и "/usr/local/include/bar" к пути поиска #include с окончательной связью, включая библиотеку "libbar". Он также заметил бы, что есть исходный файл на С++ и знаете, как использовать компоновщик С++ вместо C-компоновщика. Структура позволяет мне указать намного больше, чем показано на этом простом примере.

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

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

  • Это быстро. Он может практически мгновенно сказать, что что-то устарело.
  • 100% надежные зависимости. Нет равных шансов, что параллельные сборки будут таинственно ломаться, и он всегда строит ровно минимум, необходимый для того, чтобы все было обновлено.
  • Мне больше не придется переписывать полный файл makefile: D

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

Ответ 3

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

Кроме того, рекурсивные make-системы подвержены разваливанию при одновременном создании нескольких процессоров/ядер. Хотя эти системы построения могут работать надежно на одном процессоре, многие отсутствующие зависимости остаются необнаруженными до тех пор, пока вы не начнете параллельно строить проект. Я работал с рекурсивной системой сборки make, которая работала на четырех процессорах, но внезапно разбилась на машине с двумя четырехъядерными процессорами. Затем я столкнулся с другой проблемой: эти concurrency проблемы почти невозможно отладить, и я закончил рисовать блок-схему всей системы, чтобы выяснить, что пошло не так.

Чтобы вернуться к вашему вопросу, мне трудно думать о хороших причинах, по которым вы хотите использовать рекурсивный make. Производительность исполнения нерекурсивных систем GNU Make build трудно превзойти, и, наоборот, многие рекурсивные системы делают серьезные проблемы с производительностью (слабая поддержка параллельной сборки снова является частью проблемы). Существует статья в которой я оценил определенную (рекурсивную) систему Make build и сравнил ее с портом SCons. Результаты производительности не являются репрезентативными, потому что система сборки была очень нестандартной, но в этом конкретном случае порт SCons был на самом деле быстрее.

Нижняя строка. Если вы действительно хотите использовать Make для управления вашими сборками программного обеспечения, перейдите к нерекурсивному Make, потому что это делает вашу жизнь намного проще в долгосрочной перспективе. Лично я предпочел бы использовать SCons для удобства использования (или Rake - в основном любая система сборки, использующая современный язык сценариев и имеющий неявную поддержку зависимостей).

Ответ 4

Я сделал половинчатую попытку выполнить мою предыдущую работу, сделав систему сборки (на основе GNU make) полностью нерекурсивной, но я столкнулся с рядом проблем:

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

Одна особенность GNU make, которая упрощает нерекурсивное использование, - это значения переменной для цели:

foo: FOO=banana
bar: FOO=orange

Это означает, что при создании целевого "foo" $(FOO) будет расширяться до "банана", но при построении целевого "бара" $(FOO) будет расширяться до "оранжевого".

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

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

EDIT: Еще одна очень полезная (и часто недоиспользуемая) функция GNU make в этом контексте - средства макрорасширения (см. eval, например). Это очень полезно, когда у вас есть несколько целей, которые имеют схожие правила/цели, но отличаются способами, которые не могут быть выражены с помощью регулярного синтаксиса GNU make.

Ответ 5

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

В настоящее время я работаю над небольшим исследовательским проектом, где я экспериментирую с непрерывной интеграцией; автоматически выполнить единичный тест на ПК, а затем запустить системный тест на (внедренной) цели. Это нетривиально в make, и я искал хорошее решение. Поиск этой модели по-прежнему является хорошим выбором для переносимых мультиплатформенных построений. Наконец, я нашел хорошую отправную точку в http://code.google.com/p/nonrec-make

Это было истинное облегчение. Теперь мои make файлы

  • очень просто изменить (даже с ограниченным уровнем знаний)
  • быстро компилировать
  • полная проверка (.h) зависимостей без усилий

Я, конечно же, буду использовать его для следующего (большого) проекта (предполагая C/С++)

Ответ 6

Я знаю, по крайней мере, один крупномасштабный проект (ROOT), который рекламирует, используя [powerpoint link] механизм, описанный в Recursive Make, считается вредным. Структура превышает миллион строк кода и компилируется довольно умно.


И, конечно же, все большие проекты, с которыми я работаю, используют рекурсивный make, очень медленно компилируются.:: вздох::

Ответ 7

Я разработал нерекурсивную систему make для одного проекта С++ среднего размера, который предназначен для использования в Unix-подобных системах (включая macs). Код в этом проекте находится в дереве каталогов, установленном в каталоге src/. Я хотел написать нерекурсивную систему, в которой можно ввести "сделать все" из любого подкаталога верхнего уровня src/directory, чтобы скомпилировать все исходные файлы в дереве каталогов, внедренном в рабочий каталог, так как в рекурсивной системе. Поскольку мое решение, похоже, немного отличается от других, которые я видел, я хотел бы описать его здесь и посмотреть, не возникли ли у меня какие-либо реакции.

Основными элементами моего решения были следующие:

1) Каждый каталог в src/tree имеет файл с именем sources.mk. Каждый такой файл определяет переменную makefile, в которой перечислены все исходные файлы в дереве, внедренном в каталог. Имя этой переменной имеет вид [каталог] _SRCS, в котором [каталог] представляет каноническую форму пути из каталога верхнего уровня src/в этот каталог, причем обратные косые символы заменяются символами подчеркивания. Например, файл src/util/param/sources.mk определяет переменную с именем util_param_SRCS, которая содержит список всех исходных файлов в src/util/param и его подкаталогах, если они есть. Каждый файл sources.mk также определяет переменную с именем [directory] _OBJS, которая содержит список соответствующих объектных файлов *.o целей. В каждом каталоге, который содержит подкаталоги, source.mk включает файл sources.mk из каждого из подкаталогов и объединяет переменные [подкаталог] _SRCS для создания своей собственной переменной [directory] _SRCS.

2) Все пути выражаются в файлах sources.mk как абсолютные пути, в которых каталог src/представлен переменной $(SRC_DIR). Например, в файле src/util/param/sources.mk файл src/util/param/Componenent.cpp будет указан как $(SRC_DIR)/util/param/Component.cpp. Значение $(SRC_DIR) не задано ни в каком файле sources.mk.

3) Каждый каталог также содержит Makefile. Каждый Makefile содержит глобальный файл конфигурации, который устанавливает значение переменной $(SRC_DIR) в абсолютный путь к корневому каталогу src/. Я решил использовать символическую форму абсолютных путей, потому что это, по-видимому, самый простой способ создать несколько make файлов в нескольких каталогах, которые будут одинаково интерпретировать пути для зависимостей и целей, при этом позволяя при необходимости перемещать все исходное дерево, изменив значение $(SRC_DIR) в одном файле. Это значение устанавливается автоматически с помощью простого script, который пользователь получает указание запускать, когда пакет загружается или клонируется из репозитория git или когда перемещается все исходное дерево.

4) Файл make в каждом каталоге содержит файл sources.mk для этого каталога. Цель "all" для каждого такого Makefile перечисляет файл [каталог] _OBJS для этого каталога как зависимость, что требует компиляции всех исходных файлов в этом каталоге и его подкаталогах.

5) Правило для компиляции файлов *.cpp создает файл зависимостей для каждого исходного файла с суффиксом *.d в качестве побочного эффекта компиляции, как описано здесь: http://mad-scientist.net/make/autodep.html. Я решил использовать компилятор gcc для генерации зависимостей, используя опцию -M. Я использую gcc для генерации зависимостей даже при использовании другого компилятора для компиляции исходных файлов, поскольку gcc почти всегда доступен в unix-подобных системах, и потому что это помогает стандартизировать эту часть системы сборки. Для компиляции исходных файлов может использоваться другой компилятор.

6) Использование абсолютных путей для всех файлов переменных _OBJS и _SRCS требовало, чтобы я написал script для редактирования файлов зависимостей, созданных gcc, который создает файлы с относительными путями. Я написал для этого python script, но другой человек, возможно, использовал sed. Пути для зависимостей в результирующих файлах зависимостей являются буквальными абсолютными путями. Это прекрасно в этом контексте, потому что файлы зависимостей (в отличие от файлов sources.mk) генерируются локально, а не распространяются как часть пакета.

7) Makefile в каждом директоре включает файл sources.mk из того же каталога и содержит строку "-ключить $([каталог] _OBJS:.o =.d)", которая пытается включить файлы зависимостей для каждый исходный файл в каталоге и его подкаталогах, как описано в приведенном выше URL-адресе.

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

В настоящее время моя система для этого проекта всегда выполняет сборку "на месте": объектный файл, созданный путем компиляции каждого исходного файла, помещается в тот же каталог, что и исходный файл. Было бы легко включить создание вне места, изменив script, который редактирует файлы зависимостей gcc, чтобы заменить абсолютный путь к src/dirctory переменной $(BUILD_DIR), которая представляет каталог сборки в выражении для целевого объекта объекта в правиле для каждого объектного файла.

До сих пор я нашел эту систему простой в использовании и обслуживании. Необходимые фрагменты файла makefile являются короткими и сравнительно легкими для понимания сотрудниками.

Проект, для которого я разработал эту систему, написан полностью автономным ANSI С++ без внешних зависимостей. Я думаю, что такая самодельная нерекурсивная система makefile является разумным вариантом для автономного, очень портативного кода. Однако я бы рассмотрел более мощную систему сборки, такую ​​как CMake или gnu autotools, для любого проекта, который имеет нетривиальные зависимости от внешних программ или библиотек или от нестандартных функций операционной системы.

Ответ 8

Я написал не очень хорошую нерекурсивную систему сборки make, и с тех пор очень чистая модульная рекурсивная система сборки make для проекта под названием Pd-extended. В основном это похоже на скриптовый язык с включенной библиотекой. Теперь я также работаю с нерекурсивной системой Android, так что контекст моих мыслей по этой теме.

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

Теперь я работаю над созданием пользовательской версии внутренних элементов Android, например, версией классов Android SQLite, которые основаны на зашифрованном sqlite SQLCipher. Поэтому мне приходится писать нерекурсивные файлы Android.mk, которые обертывают всевозможные странные системы сборки, такие как sqlite. Я не могу понять, как заставить Android.mk выполнить произвольный script, в то время как это было бы легко в традиционной рекурсивной системе make, по моему опыту.