четверг, 4 августа 2016 г.

[prog] Mxx_ru-1.6.12 с еще одним ObjPlacement из коробки

Сделал очередной релиз своей кроссплатформенной системы сборки C/C++ проектов: Mxx_ru версии 1.6.12

Установить Mxx_ru можно командой gem install Mxx_ru

Обновить Mxx_ru можно командой gem update Mxx_ru

Так же Mxx_ru можно загрузить с SourceForge (gem-файл).

Единственное, но важное, обновление версии 1.6.12 по сравнению с предшествующей версией 1.6.11 -- это появление класса PrjAwareRuntimeSubdirObjPlacement. Этот ObjPlacement позволяет получить возможность сборки одних и тех же исходников в lib или в so/dll прямо "из коробки".

Для того, чтобы пояснить нужность PrjAwareRuntimeSubdirObjPlacement требуется сперва объяснить роль ObjPlacement в MxxRu...

ObjPlacement в MxxRu отвечает за то, где будут располагаться промежуточные и финальные результаты компиляции/линковки. Т.е. куда пойдут объектники, куда библиотеки, куда бинарники и т.д. Если проект собирается всего одним компилятором, то смысла париться по поводу расположения результатов компиляции нет. Разве что может быть интересно, чтобы результирующие бинарники шли куда-то в подкаталог target, а объектники не валялись рядом с исходниками.

А вот если проект собирается разными компиляторами (например, несколькими версиями GCC, или GCC и VC++, или GCC и clang-ом), да еще и в разных режимах (debug/release), то тут уж приходится следить за тем, где лежат все эти файлы и не подхватит ли по ошибке текущий компилятор пару объектников, собранных совсем другим компилятором, с совсем другими параметрами. Тут уж в дело вступают ObjPlacement объекты.

Обычно создается всего один объект ObjPlacement, который в build.rb назначается в качестве глобального для всего проекта. Это означает, что все подпроекты будут размещать свои промежуточные и результирующие файлы именно там, где определит этот самый глобальный ObjPlacement. На практике этого достаточно, хотя MxxRu поддерживает и возможность задать собственный ObjPlacement для отдельного проекта (но этой возможностью не пользуется, т.к. она только лишней головной боли в больших проектах добавляет).

MxxRu предоставляет пользователю несколько готовых реализаций ObjPlacement. По умолчанию используется дефолтная реализация, которая удобна для мелких проектиков, которые собираются только одним компилятором без каких-либо вариаций. Так, все объектники идут в подкаталоги с именем o, создающиеся рядом с исходным файлом. Т.е., если компилируется файл some/prj/f.cpp, то объектник для него попадет в some/prj/o. Финальные результаты компиляции (т.е. исполнимые файлы, статические и динамические библиотеки) просто сбрасываются в текущий каталог.

Понятное дело, для чего-то более менее-сложного нужно что-то другое. Тут в дело вступает RuntimeSubdirObjPlacement. Этот ObjPlacement создает новую структуру каталогов в указанном месте. Так, если пользователь создал RuntimeSubdirObjPlacement и указал, что результирующим каталогом должен быть каталог target, то будет создан каталог target, в нем будет создан каталог release или debug, а уже в них будут созданы подкаталоги для объектников. Так, если мы компилируем файл some/prj/f.cpp в release-режиме, то объектник для него пойдет в target/release/some/prj.

RuntimeSubdirObjPlacement хорош до тех пор, пока мы работаем всего с одним компилятором. Например, пользуемся только Visual C++ одной конкретной версии. Или только GCC конкретной версии. Однако, если мы хотим использовать несколько разных компиляторов или разных версий одного компилятора, то нам нужно подумать о том, как разнести выхлопы разных компиляторов по разным местам. Мы можем либо в build.rb задавать для RuntimeSubdirObjPlacement разные имена (например, target-vc14, target-gcc-5.4.0, target-clang-3.8.0). Либо же можно воспользоваться ToolsetRuntimeSubdirObjPlacement.

Класс ToolsetRuntimeSubdirObjPlacement сам создает в указанном для него каталоге специальные покаталоги с именами, соответствующими текущему тулсету. Так, если мы создаем ToolsetRuntimeSubdirObjPlacement и передаем ему параметр target в качестве целевого каталога, то этот ObjPlacement будет создавать подкаталоги вида target/gcc_4_8_2__x86_64_w64_mingw32/release или target/vc14_0_19_00_24210_x64/debug. И уже в них будет создавать нужную систему подкаталогов. Так, объектник для файла some/prj/f.cpp может попасть в каталог target/gcc_4_8_2__x86_64_w64_mingw32/release/some/prj.

В общем, ToolsetRuntimeSubdirObjPlacement -- это хорошая штука, но до тех пор, пока мы не сталкиваемся с необходимость собрать из одних и тех же исходников как статическую, так и динамическую библиотеку. Тут проблема возникает в том, что если в эту библиотеку входит файл some/prj/f.cpp, то он должен компилироваться два раза -- один раз для статической библиотеки, второй раз для динамической. Соответственно, должно быть два объектных файла. Но это будет два объектных файла с одним и тем же именем f.o (или f.obj). И попадут эти файлы в один и тот же каталог, что не есть хорошо, т.к. в одном каталоге не может быть двух файлов с одинаковым именем.

Как раз новый класс, PrjAwareRuntimeSubdirObjPlacement, решает эту проблему. Он генерирует более сложное дерево каталогов для промежуточных результатов. На каком-то уровне этого дерева происходит развилка по имени проектного файла. Так, если у нас есть проектные файлы prj/static.rb и prj/dynamic.rb, то внутри этого дерева будет подкаталог prj_static_rb, внутри которого будут находиться все объектники, собранные для проекта prj/static.rb. И будет подкаталог prj_dynamic_rb, внутри которого будут все объектники для проекта prj/dynamic.rb. Это дает возможность в проектных файлах static.rb и dynamic.rb перечислять одни и те же исходные файлы. Это не страшно, т.к. объектники будут разнесены по разным подкаталогам.

Примеры этого дела можно посмотреть в соседнем посте.

Раньше аналогичного результата надо было добиваться вручную, через управление ObjPlacement в своих prj.rb-файлах. Теперь эта функциональность доступна в MxxRu сразу, из коробки, как говорят.

Использовать PrjAwareRuntimeSubdirObjPlacement можно вот так:

#!/usr/bin/ruby
gem 'Mxx_ru''>= 1.6.12'
require 'mxx_ru/cpp'

MxxRu::Cpp::composite_target( MxxRu::BUILD_ROOT ) {

   global_include_path "."

   global_obj_placement MxxRu::Cpp::PrjAwareRuntimeSubdirObjPlacement.new( 'target' )
   default_runtime_mode MxxRu::Cpp::RUNTIME_RELEASE 

   required_prj '...'
   ...
}

В этом случае будут формироваться каталоги вида target/release/...

Или вот так:

#!/usr/bin/ruby
gem 'Mxx_ru''>= 1.6.12'
require 'mxx_ru/cpp'

MxxRu::Cpp::composite_target( MxxRu::BUILD_ROOT ) {

   global_include_path "."

   global_obj_placement MxxRu::Cpp::PrjAwareRuntimeSubdirObjPlacement.new(
      'target',
      MxxRu::Cpp::PrjAwareRuntimeSubdirObjPlacement::USE_COMPILER_ID )
   default_runtime_mode MxxRu::Cpp::RUNTIME_RELEASE 

   required_prj '...'
   ...
}

В последнем случе будут формироваться имена каталогов вида target/gcc_4_8_2__x86_64_w64_mingw32/release/...

Комментариев нет: