Глава 3
Несколько простых примеров
3.1 Простой exe-файл
Пусть необходимо скомпилировать один C++ файл и получить exe-файл:
1 #include <iostream>
2 #include <string>
3
4 void
5 say_hello( std::ostream & to )
6 {
7 to << "Hello!" << std::endl;
8 }
9
10 void
11 say_bye( std::ostream & to )
12 {
13 to << "Bye!" << std::endl;
14 }
15
16 int
17 main()
18 {
19 say_hello( std::cout );
20
21 std::cout << "Simple exe main..." << std::endl;
22
23 say_bye( std::cout );
24
25 return 0;
26 }
Для этого потребуется следующий Mxx_ru-проектный файл (с именем prj.rb):
1 require ’mxx_ru/cpp’
2
3 Mxx_ru::setup_target(
4 Mxx_ru::Cpp::Exe_target.new( "prj.rb" ) {
5 target( "simple_exe" )
6
7 cpp_source( "main.cpp" )
8 }
9 )
Для компиляции проекта необходимо запустить интерпретатор ruby.rb и указать ему имя
проектного файла:
В результате, на платформе mswin будет построен файл simple_exe.exe, а на платформе
unix — simple_exe.
3.2 exe-файл и простая статическая библиотека
Пусть теперь необходимо разбить приведенный выше пример на статическую библиотеку,
предоставляющую функции say_hello(), say_bye(), и основной модуль, который будет
пользоваться услугами этой библиотеки.
Для статической библиотеки создаются файлы:
- заголовочный say.hpp
1 #if !defined( _SAY_HPP_ )
2 #define _SAY_HPP_
3
4 #include <iostream>
5
6 void
7 say_hello( std::ostream & to );
8 void
9 say_bye( std::ostream & to );
10
11 #endif
- файл реализации say.cpp
1 #include <iostream>
2
3 void
4 say_hello( std::ostream & to )
5 {
6 to << "Hello!" << std::endl;
7 }
8
9 void
10 say_bye( std::ostream & to )
11 {
12 to << "Bye!" << std::endl;
13 }
- проектный файл say.rb
1 require ’mxx_ru/cpp’
2
3 Mxx_ru::setup_target(
4 Mxx_ru::Cpp::Lib_target.new( "say.rb" ) {
5 target( "say" )
6
7 cpp_source( "say.cpp" )
8 }
9 )
Для основного модуля создаются файлы:
- файл реализации main.cpp
1 #include <iostream>
2
3 #include <say.hpp>
4
5 int
6 main()
7 {
8 say_hello( std::cout );
9
10 std::cout << "Exe and lib main..." << std::endl;
11
12 say_bye( std::cout );
13
14 return 0;
15 }
- проектный файл prj.rb
1 require ’mxx_ru/cpp’
2
3 Mxx_ru::setup_target(
4 Mxx_ru::Cpp::Exe_target.new( "prj.rb" ) {
5 target( "exe_and_lib" )
6
7 required_prj( "say.rb" )
8
9 include_path( "." )
10
11 cpp_source( "main.cpp" )
12 }
13 )
Для компиляции всего проекта, включая статическую библиотеку и основной
модуль необходимо указать интерпретатору Ruby имя проектного файла prj.rb.
Mxx_ru автоматически скомпилирует библиотеку say и слинкует с ней основной
модуль.
Необходимо отметить, что в проектном файле prj.rb не делается никаких указаний
относительно имени библиотеки say. Более того, основной модуль даже не знает,
является ли библиотека статической или динамической. Основной модуль просто
указывает, что ему необходим проект, описанный в проектном файле say.rb. За все
остальное отвечает Mxx_ru — компиляция подчиненного проекта, определение
имени получившейся библиотеки, линкование основного модуля с библиотекой
say.
3.3 exe-файл, динамическая и статическая библиотеки
Пусть теперь требуется усложнить два предыдущих примера — требуется создать
динамически-загружаемую библиотеку, предоставляющую класс inout_t. Задачей
данного класса является печать строки “Hello!” в конструкторе и строки “Bye!” в
деструкторе. Объекты класса inout_t можно будет объявлять в функциях для того,
чтобы на стандартный поток ошибок печатались сообщения в входе/выходе в/из
функции.
Для своей работы библиотека inout будет использовать модифицированный вариант
библиотеки say — в функции say_hello, say_bye добавляется еще один аргумент. В
результате библиотека say будет состоять из следующих файлов:
- заголовочный say.hpp
1 #if !defined( _SAY_HPP_ )
2 #define _SAY_HPP_
3
4 #include <iostream>
5 #include <string>
6
7 void
8 say_hello( std::ostream & to, const std::string & where );
9 void
10 say_bye( std::ostream & to, const std::string & where );
11
12 #endif
- файл реализации say.cpp
1 #include <say.hpp>
2
3 void
4 say_hello( std::ostream & to, const std::string & where )
5 {
6 to << where << ": Hello!" << std::endl;
7 }
8
9 void
10 say_bye( std::ostream & to, const std::string & where )
11 {
12 to << where << ": Bye!" << std::endl;
13 }
- проектный файл say.rb
1 require ’mxx_ru/cpp’
2
3 Mxx_ru::setup_target(
4 Mxx_ru::Cpp::Lib_target.new( "say.rb" ) {
5 target( "say" )
6
7 include_path( "." )
8
9 cpp_source( "say.cpp" )
10 }
11 )
Библиотека inout состоит из следующих файлов:
- заголовочный inout.hpp
1 #if !defined( _INOUT_HPP_ )
2 #define _INOUT_HPP_
3
4 #include <string>
5
6 #if defined( INOUT_MSWIN )
7 #if defined( INOUT_PRJ )
8 #define INOUT_TYPE __declspec(dllexport)
9 #else
10 #define INOUT_TYPE __declspec(dllimport)
11 #endif
12 #else
13 #define INOUT_TYPE
14 #endif
15
16 class INOUT_TYPE inout_t
17 {
18 public :
19 inout_t( const std::string & method );
20 ~inout_t();
21
22 private :
23 std::string m_method;
24 };
25
26 #endif
- файл реализации inout.cpp
1 #include <say.hpp>
2
3 #include <inout.hpp>
4
5 inout_t::inout_t(
6 const std::string & method )
7 : m_method( method )
8 {
9 say_hello( std::cout, method );
10 }
11
12 inout_t::~inout_t()
13 {
14 say_bye( std::cout, m_method );
15 }
- проектный файл inout.rb
1 require ’mxx_ru/cpp’
2
3 Mxx_ru::setup_target(
4 Mxx_ru::Cpp::Dll_target.new( "inout.rb" ) {
5 target( "inout" )
6 implib_path( "." )
7
8 required_prj( "say.rb" )
9
10 include_path( "." )
11
12 define( "INOUT_PRJ" )
13
14 if "mswin" == toolset.tag( "target_os" )
15 define( "INOUT_MSWIN" )
16 end
17
18 cpp_source( "inout.cpp" )
19 }
20 )
Основной модуль состоит из файлов:
- файл реализации main.cpp
1 #include <iostream>
2
3 #include <inout.hpp>
4
5 void
6 some_func()
7 {
8 inout_t braces( "some_func" );
9
10 std::cout << "Some functionality..." << std::endl;
11 }
12
13 int
14 main()
15 {
16 inout_t braces( "main" );
17
18 std::cout << "Exe, dll, lib main..." << std::endl;
19
20 some_func();
21
22 return 0;
23 }
- проектный файл prj.rb
1 require ’mxx_ru/cpp’
2
3 Mxx_ru::setup_target(
4 Mxx_ru::Cpp::Exe_target.new( "prj.rb" ) {
5 target( "exe_dll_lib" )
6
7 required_prj( "inout.rb" )
8
9 include_path( "." )
10
11 cpp_source( "main.cpp" )
12 }
13 )
Важно отметить, что основной модуль напрямую зависит только от подпроекта inout.
Неявная зависимость основного модуля от библиотеки say отслеживается Mxx_ru. Для
таких платформ, как mswin и os2 эта неявная зависимость не столь важна, как на
unix-платформах. В частности, при линковании основного модуля на платформах unix
нужно будет указывать линкеру не только библиотеку inout, но и библиотеку say. В случае с
Mxx_ru основной модуль об этом знать не должен — Mxx_ru в состоянии самостоятельно
извлечь из подпроектов всю необходимую информацию и распорядиться ей в зависимости от
используемой платформы.
3.4 exe-файл, динамическая, статическая библиотеки и исходные файлы в разных
каталогах
В предыдущем примере есть два серьезных недостатка:
- Все файлы (заголовочные, исходные, проектные, результаты компиляции и
линковки) находятся в одном каталоге. Так, после компиляции проекта g++ на
платформе Cygwin в каталоге размещаются следующие файлы:
exe_dll_lib.exe*
inout.cpp
inout.hpp
inout.rb
libinout.a
libinout.so*
libsay.a
main.cpp
o/
prj.rb
say.cpp
say.hpp
say.rb
Очевидно, что если количество файлов в проекте будет увеличиваться, то
ориентироваться в такой каше из файлов разных типов будет все сложнее и
сложнее.
- В каждом из проектных файлов есть инструкция: include_path("."). Было бы
удобнее, если бы существовало одно место, в котором можно было бы задать общие для
всей группы подпроектов параметры.
Для преодоления первого недостатка нужно разместить все подпроекты в собственных
подкаталогах одного общего каталога (корневого каталога проекта). Например, пусть будет
следующая иерархия каталогов и файлов:
/
|--inout/
| |--inout.cpp
| |--inout.hpp
| ‘--prj.rb
|--lib/
|--main/
| |--main.cpp
| ‘--prj.rb
|--say/
| |--prj.rb
| |--say.cpp
| ‘--say.hpp
‘--build.rb
Каталог lib предназначен для размещения библиотек (статических и библиотек импорта
для dll).
3.4.1 Подпроект say
Подпроект say состоит из следующих файлов:
3.4.2 Подпроект inout
Подпроект main состоит из следующих файлов:
- заголовочный inout.hpp
1 #if !defined( _INOUT_HPP_ )
2 #define _INOUT_HPP_
3
4 #include <string>
5
6 #if defined( INOUT_MSWIN )
7 #if defined( INOUT_PRJ )
8 #define INOUT_TYPE __declspec(dllexport)
9 #else
10 #define INOUT_TYPE __declspec(dllimport)
11 #endif
12 #else
13 #define INOUT_TYPE
14 #endif
15
16 class INOUT_TYPE inout_t
17 {
18 public :
19 inout_t( const std::string & method );
20 ~inout_t();
21
22 private :
23 std::string m_method;
24 };
25
26 #endif
- файл реализации inout.cpp
1 #include <say/say.hpp>
2
3 #include <inout/inout.hpp>
4
5 inout_t::inout_t(
6 const std::string & method )
7 : m_method( method )
8 {
9 say_hello( std::cout, method );
10 }
11
12 inout_t::~inout_t()
13 {
14 say_bye( std::cout, m_method );
15 }
Здесь так же следует обратить внимание, что для загрузки заголовочных файлов из
какого-либо подпроекта, в директиве #include указывается имя подкаталога этого
проекта: <say/say.hpp>, <inout/inout.hpp>.
- проектный файл prj.rb
1 require ’mxx_ru/cpp’
2
3 Mxx_ru::setup_target(
4 Mxx_ru::Cpp::Dll_target.new( "inout/prj.rb" ) {
5 target( "inout" )
6 implib_path( "lib" )
7
8 required_prj( "say/prj.rb" )
9
10 define( "INOUT_PRJ" )
11
12 if "mswin" == toolset.tag( "target_os" )
13 define( "INOUT_MSWIN" )
14 end
15
16 cpp_source( "inout.cpp" )
17 }
18 )
3.4.3 Подпроект main
Подпроект main состоит из следующих файлов:
- файл реализации main.cpp
1 #include <iostream>
2
3 #include <inout/inout.hpp>
4
5 void
6 some_func()
7 {
8 inout_t braces( "some_func" );
9
10 std::cout << "Some functionality..." << std::endl;
11 }
12
13 int
14 main()
15 {
16 inout_t braces( "main" );
17
18 std::cout << "Exe, dll, lib main..." << std::endl;
19
20 some_func();
21
22 return 0;
23 }
- проектный файл prj.rb
1 require ’mxx_ru/cpp’
2
3 Mxx_ru::setup_target(
4 Mxx_ru::Cpp::Exe_target.new( "main/prj.rb" ) {
5 target( "exe_dll_lib" )
6
7 required_prj( "inout/prj.rb" )
8
9 cpp_source( "main.cpp" )
10 }
11 )
3.4.4 Файл build.rb
Файл build.rb — это специальный проектный файл. Он должен располагаться в
корневом каталоге проекта, в том каталоге, из которого запускается интерпретатор
Ruby. В файле build.rb должны быть заданы все параметры, глобальные для всего
проекта.
Если проект состоит из нескольких подпроектов, как в данном случае, то build.rb как
правило, является проектом-композитом.
Для данного примера build.rb имеет вид:
1 require ’mxx_ru/cpp’
2
3 Mxx_ru::setup_target(
4 Mxx_ru::Cpp::Composite_target.new( Mxx_ru::BUILD_ROOT ) {
5 required_prj( "main/prj.rb" )
6
7 global_include_path( "." )
8 }
9 )
3.4.5 Компиляция всего проекта
Для компиляции примера необходимо войти в корневой каталог примера (в котором
находится файл build.rb) и запустить интерпретатор Ruby с файлом build.rb в качестве
параметра. В результате компиляции g++ на платформе Cygwin в корневом каталоге
окажутся файлы:
build.rb
exe_dll_lib.exe*
inout/
lib/
libinout.so*
main/
say/
а в каталоге lib файлы:
3.4.6 Компиляция только одного подпроекта
Для компиляции только одного подпроекта необходимо находясь в корневом каталоге
примера (т.е. там, где расположен build.rb) запустить интерпретатор Ruby, указав ему имя
проектного файла и аргумент --mxx-cpp-1. Например, для компиляции только
inout:
ruby inout/prj.rb --mxx-cpp-1
В этом случае будет обработан только сам подпроект inout без своих подпроектов. Т.е.
если подпроект say не был перед этим скомпилирован, то линковка inout завершиться
неудачей из-за того, что нет библиотеки say.
3.5 Операция clean
По-умолчанию, Mxx_ru выполняет построение проекта (в случае C/C++ это компиляция и
линкование). Но Mxx_ru так же поддерживает и обратную операцию – clean, т.е. удаление
всего, что было построено. Для C/C++ операция clean приводит к уничтожению всех
объектных файлов, библиотек, исполнимых файлов и других результатов компиляции и
линкования.
Для выполнения операции clean необходимо запустить интерпретатор ruby, передать ему
имя проектного файла и указать аргумент --mxx-clean:
ruby build.rb --mxx-clean
Операция clean распространяется как на сам проект, имя проектного файла которого
передано Ruby-интерпретатору, так и на все подпроекты. Если для C/C++ проектов
необходимо выполнить очистку только одного проекта, без его подпроектов, то
интерпретатору Ruby нужно передать имя проектного файла этого проекта и аргументы
--mxx-clean, --mxx-cpp-1.
ruby build.rb --mxx-clean --mxx-cpp-1