ja ja ja ja ja ja ja ja ja en ja ja ja ja ja ja ja ja ja ja ja ru ja ja ja ja ja ja vi ja cs vi ja ja ja ja ja ja ja ja ja en ja ja ja ja ja ja en ja ja ja ja ja ja ja ja en ja uk ja ja ja ja ja ja ja ja en ja ja ja ja ja en ja ja ja ja ja ja ja ja ja ja ja en ja ja en ja en ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja en ja ja ja en ja en ja ja ja ja ja ja en ja ja ja ja ja ja ja en ja en ja ja en ja ja vi ja sv eu ja en en hu ja ja en en pl ja ja jbo hbs_lat en ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja
Mesonを使ってGObject Introspection対応のビルドシステムを構築する方法 - 2022-08-17 - ククログ
MesonにはGObject Introspectionサポート(Meson Integration) が組み込まれているので、Mesonを使うと簡単にGObject Introspectionを利用できます。
以下で重要な部分を説明します。
# プロジェクトの定義: # https://mesonbuild.com/Reference-manual_functions.html#project # licenseの値の形式: https://spdx.org/licenses/ project('milter-manager', 'c', license: 'LGPL-3.0-or-later', version: '2.1.6') # ライブラリーのバージョンなど、共通して用いる変数を定義する。 # ライブラリーのバージョンは、プロジェクトのバージョンとは別に管理する。(補足1) api_version = '2.0' so_version = 2 library_version = '@0@.0.0'.format(so_version) # prefixオプションの値を取得する。(補足2) prefix = get_option('prefix') # ビルトインオブジェクト「meson」の関数を使い定義したプロジェクト名を参照。 # https://mesonbuild.com/Reference-manual_builtin_meson.html # インストール先のパスについて定義している。(補足3) project_include_sub_dir = meson.project_name() milter_include_sub_dir = project_include_sub_dir / 'milter' milter_include_dir = get_option('includedir') / milter_include_sub_dir data_dir = prefix / get_option('datadir') gir_dir = data_dir / 'gir-1.0' # 用いるモジュールをインポート。 # 例: GNOME module: https://mesonbuild.com/Gnome-module.html fs = import('fs') gnome = import('gnome') pkgconfig = import('pkgconfig') # pcファイルの生成時に用いる。 # Mesonを使ってGObject Introspection対応しようとする別のライブラリが # GIRファイルを探し出せるようにする。(補足4) pkgconfig_variables = ['girdir=@0@'.format(gir_dir)] (...) package_platform = get_option('package_platform') (...) # meson-config.h.inの変数を置換してconfig.hを生成する。 # ソースコードで用いる定数などをビルド時に定義したいため。 config_h_conf = configuration_data() (...) config_h_conf.set_quoted('MILTER_MANAGER_PACKAGE_PLATFORM', package_platform) config_h_conf.set_quoted('PACKAGE', meson.project_name()) config_h_conf.set_quoted('PREFIX', prefix) config_h_conf.set_quoted('VERSION', meson.project_version()) config_h = configure_file(input: 'meson-config.h.in', output: 'config.h', configuration: config_h_conf) # 後で依存関係に加えるため、config.hをdependencyにしておく。(補足5) config = declare_dependency(compile_args: '-DHAVE_CONFIG_H', include_directories: include_directories('.'), sources: config_h) # 指定したディレクトリに入り、そのディレクトリのmeson.buildを実行する。 subdir('milter/core') (...)
補足1: ライブラリーのバージョン
# ライブラリーのバージョンなど、共通して用いる変数を定義する。 # ライブラリーのバージョンは、プロジェクトのバージョンとは別に管理する。(補足1) api_version = '2.0' so_version = 2 library_version = '@0@.0.0'.format(so_version)
ライブラリーのバージョンは、アプリケーションバイナリインターフェース(ABI)の互換性の観点で管理します。 アップデートによってプロジェクトのバージョンが上がっても、ABIの互換性が保たれているならばライブラリーのバージョンは変更しなくても良いです。
特に共有ライブラリーは、以下のようにシンボリックリンクとその本体で構成されます。
libmilter-core.so.2 -> libmilter-core.so.2.0.0 libmilter-core.so.2.0.0
シンボリックリンクの名前libmilter-core.so.2には、メジャーバージョンの2しか含まれていません。 つまり、メジャーバージョンを変更するということは、共有ライブラリーのシンボリックリンクを変更するということになります。 ABIの互換性を保てない更新があった場合のみメジャーバージョンを更新するべきです。
詳しくはライブラリーのバージョンとアプリケーションバイナリインターフェースをご覧下さい。
# prefixオプションの値を取得する。(補足2) prefix = get_option('prefix')
get_option()でオプションの値を取得できます。
オプションには、デフォルトで定義されているビルトインオプションと、ユーザーが定義するオプションの2種類があります。 後者はmeson_options.txtというファイルを作成して定義します。
例えばprefixはビルトインのオプションですが、package_platformはmeson_options.txtで定義したオプションです。
ビルトインのオプションはmesonコマンドの引数として指定できます。 ユーザーが定義したオプションは、-Dオプションで指定します。 例えば次のように使います。
$ meson setup --prefix=/tmp/local -Dpackage_platform=debian ../milter-manager.build .
補足3: インストール先のパス
# インストール先のパスについて定義している。(補足3) project_include_sub_dir = meson.project_name() milter_include_sub_dir = project_include_sub_dir / 'milter' milter_include_dir = get_option('includedir') / milter_include_sub_dir data_dir = prefix / get_option('datadir') gir_dir = data_dir / 'gir-1.0'
この後にインストールに用いる各関数は、デフォルトでprefixオプションの値をインストール先として用います。
例えば、ヘッダーをインストールするinstall_headers()では {prefix}/{includedir}がデフォルトのインストール先になりますし、 ライブラリーをインストールするlibrary()では {prefix}/{libdir}がデフォルトのインストール先になります。
各関数でこのインストール先を変更できるので、あとで変更するためにここでパスを定義しています。 パスの連結は、/演算子を使って行います。 この演算子はjoin_paths()と同じ機能を提供します。
補足4: GIRファイルとTypelibファイル
# pcファイルの生成時に用いる。 # Mesonを使ってGObject Introspection対応しようとする別のライブラリが # GIRファイルを探し出せるようにする。(補足4) pkgconfig_variables = ['girdir=@0@'.format(gir_dir)]
GObject Introspection対応のライブラリーをビルドするときは、GIRファイルとTypelibファイルを生成します。
GIRファイルを生成する際、依存している他のGIRファイルを参照する必要があります。 参照するのでどこにGIRファイルがあるか分からないといけません。 Mesonは依存しているライブラリーの.pcファイル内にgirdir変数が定義されていれば、その変数の値であるディレクトリーをGIRファイルの検索パスに追加してくれます。 このライブラリーのGIRファイルを、Mesonを使っている他のライブラリーから使いやすくするために、このライブラリーの.pcファイルにもgirdir変数を定義しています。
補足5: declare_dependency について
# 後で依存関係に加えるため、config.hをdependencyにしておく。(補足5) config = declare_dependency(compile_args: '-DHAVE_CONFIG_H', include_directories: include_directories('.'), sources: config_h)
Mesonでライブラリーをビルドするにはlibrary()関数を使います。 library()関数にマクロ定義やヘッダーの検索パスや依存関係を指定するには、library()関数の各引数に個別に指定するか Dependency objectを使って指定する方法があります。 Dependency objectを使うと複数のlibrary()で指定内容を共有できるので、何度も指定する情報はDependency objectを使うとスッキリします。
外部でインストール済みのライブラリーであればdependency()を用い、 このプロジェクト内で作成する依存関係であればdeclare_dependency()を用います。
ここでは、compile_argsを指定することで、この依存を使ったライブラリーのビルドに指定したオプションを追加します。 milter managerでは元々GNU Autotoolsを用いており、その慣習で、HAVE_CONFIG_Hが定義済みの場合に限りconfig.hを読み込むようにコーディングしている、 という事情があります。 そのためmilter managerでは、このconfig.hに依存するライブラリーのビルドに、常にHAVE_CONFIG_Hオプションに付ける必要があります。
coreライブラリーのmeson.buildファイル
milter managerのcoreライブラリー用のmeson.buildファイルを次のように作成しました。
https://github.com/milter-manager/milter-manager/blob/86e5b027815b965a342167f36f5813a0d4c964e4/milter/core/meson.build
# ビルド対象に含めるソースファイル。 sources = files( 'milter-agent.c', 'milter-command-decoder.c', (...) ) # ビルド対象に含めるヘッダーファイル。 headers = files( 'milter-agent.h', 'milter-command-decoder.h', (...) ) # milter-version.h.inの変数を置換してmilter-version.hを生成し、headersに含める。 version_h_conf = configuration_data() # str型のsplitメソッド: # https://mesonbuild.com/Reference-manual_elementary_str.html#strsplit version_components = meson.project_version().split('.') version_h_conf.set('MILTER_MANAGER_VERSION', meson.project_version()) version_h_conf.set('MILTER_MANAGER_VERSION_MAJOR', version_components[0]) version_h_conf.set('MILTER_MANAGER_VERSION_MINOR', version_components[1]) version_h_conf.set('MILTER_MANAGER_VERSION_MICRO', version_components[2]) version_h = configure_file(input: 'milter-version.h.in', output: 'milter-version.h', configuration: version_h_conf) headers += version_h # enumのGObject用ファイルを生成する。(補足1) enums = gnome.mkenums_simple('milter-enum-types', body_prefix: '#include  ', identifier_prefix: 'Milter', install_dir: milter_include_dir / 'core', install_header: true, sources: headers, symbol_prefix: 'milter') enums_h = enums[1] # ヘッダーファイルをインストール。 install_headers(headers, subdir: milter_include_sub_dir / 'core') install_headers(['../core.h'], subdir: milter_include_sub_dir) headers += ['../core.h'] # 依存関係を定義する。 (...) dependencies = [ config, (...) ] # ライブラリーを作成してインストール。 libmilter_core = library('milter-core', c_args: '-DMILTER_LOG_DOMAIN="milter-core"', sources: sources + headers + enums, install: true, dependencies: dependencies, soversion: so_version, version: library_version) # 他のライブラリーがこのライブラリーを依存関係として使う。 # 生成したenumのGObject用ファイルのヘッダーを依存関係に含める。(補足2) milter_core = declare_dependency(dependencies: dependencies, link_with: libmilter_core, sources: [enums_h]) # pcファイルを作成してインストール。 pkgconfig.generate(libmilter_core, description: 'common milter features', filebase: 'milter-core', name: 'milter core library', requires: ['gobject-2.0'], subdirs: project_include_sub_dir, variables: pkgconfig_variables) # GIRファイルとTypelibファイルを生成してインストール。(補足3) milter_core_gir = gnome.generate_gir(libmilter_core, export_packages: 'milter-core', extra_args: [ '--warn-all', ], fatal_warnings: true, header: 'milter/core.h', identifier_prefix: 'Milter', includes: [ 'GObject-2.0', ], install: true, namespace: 'MilterCore', nsversion: api_version, sources: sources + headers + enums, symbol_prefix: 'milter')
補足1: enumのGObject用ファイルの生成
# enumのGObject用ファイルを生成する。(補足1) enums = gnome.mkenums_simple('milter-enum-types', body_prefix: '#include  ', identifier_prefix: 'Milter', install_dir: milter_include_dir / 'core', install_header: true, sources: headers, symbol_prefix: 'milter') enums_h = enums[1]
gnome.mkenums_simple() を使い、enumのGObject用のファイルを生成します。
sourcesで指定したファイルで定義されているenumをGEnumにしてGObject対応します。
install_header: trueを指定することで、生成したヘッダーファイルをインストールしています。 このため、後のinstall_headers()で指定しているインストールするヘッダーファイルのリストに、ここで生成したヘッダーファイルを含めていません。
ヘッダーファイルのインストール先をinstall_dirで指定しています。 prefixで指定したパスに対する相対パスを指定します。 一方でinstall_headers()は{prefix}/{includedir}に対する相対パスをsubdirで指定しています。
identifier_prefixには、コード上のenumの定義において、名前空間としてprefixに付与している部分を指定します。 下の例では、MilterAgentErrorのMilterを指定します。
symbol_prefixには、ヘッダーファイルで宣言される関数名や#defineされるenum名のprefixを指定します。 通常はidentifier_prefixで指定した部分に対応する文字列を_区切りの小文字で指定します。
実際に生成されるコードの例は、次のようになります。
typedef enum { MILTER_AGENT_ERROR_IO_ERROR, MILTER_AGENT_ERROR_DECODE_ERROR, MILTER_AGENT_ERROR_NO_EVENT_LOOP_ERROR } MilterAgentError;
生成されるmilter-enum-types.h
ここのprefixがidentifier_prefixやsymbol_prefixによって決まります。
GType milter_agent_error_get_type (void); #define MILTER_TYPE_AGENT_ERROR (milter_agent_error_get_type())
生成されるmilter-enum-types.c
GType milter_agent_error_get_type (void) { static volatile gsize gtype_id = 0; static const GEnumValue values[] = { { C_ENUM(MILTER_AGENT_ERROR_IO_ERROR), "MILTER_AGENT_ERROR_IO_ERROR", "io-error" }, { C_ENUM(MILTER_AGENT_ERROR_DECODE_ERROR), "MILTER_AGENT_ERROR_DECODE_ERROR", "decode-error" }, { C_ENUM(MILTER_AGENT_ERROR_NO_EVENT_LOOP_ERROR), "MILTER_AGENT_ERROR_NO_EVENT_LOOP_ERROR", "no-event-loop-error" }, { 0, NULL, NULL } }; if (g_once_init_enter (>ype_id)) { GType new_type = g_enum_register_static (g_intern_static_string ("MilterAgentError"), values); g_once_init_leave (>ype_id, new_type); } return (GType) gtype_id; }
補足2: 生成したenumのGObject用ファイルのヘッダーを依存関係に含める
# 他のライブラリーがこのライブラリーを依存関係として使う。 # 生成したenumのGObject用ファイルのヘッダーを依存関係に含める。(補足2) milter_core = declare_dependency(dependencies: dependencies, link_with: libmilter_core, sources: [enums_h])
他のライブラリーがこのライブラリーを依存関係として用いるため、dependencyを定義しておきます。
gnome.mkenums_simple()等を使いヘッダーファイルを自動生成している場合は、sourcesに自動生成したヘッダーファイルを含める必要があります。
これに依存するライブラリーのコンパイル時に、このライブラリーの各ヘッダーファイルが必要となります。 そのため、その前にヘッダーファイルの自動生成が完了している必要があります。
Cではコンパイルとリンクはフェーズが別れているため、link_withでリンクの設定をするだけでは、 これに依存するライブラリーのコンパイル時にヘッダーファイルの生成が完了していない可能性があります。
sourcesに自動生成するヘッダーファイルを指定することで、そのファイルのビルドが完了してから、これに依存するライブラリーをコンパイルできます。
補足3: GIRファイルとTypelibファイルの生成
# GIRファイルとTypelibファイルを生成してインストール。(補足2) milter_core_gir = gnome.generate_gir(libmilter_core, export_packages: 'milter-core', extra_args: [ '--warn-all', ], fatal_warnings: true, header: 'milter/core.h', identifier_prefix: 'Milter', includes: [ 'GObject-2.0', ], install: true, namespace: 'MilterCore', nsversion: api_version, sources: sources + headers + enums, symbol_prefix: 'milter')
gnome.generate_gir() を使って、バインディングに用いるファイルであるGIRファイルとTypelibファイルを生成します。
fatal_warnings: trueを指定することで、警告をエラーとすることができます。 バインディングを生成するにはGObject Introspection Annotations というアノテーションを適切に設定する必要があり、アノテーションに問題があればビルド時に警告が発生します。 この設定をすることでそのような警告をエラーとすることができます。
header: 'milter/core.h'は、ただのドキュメント用の情報です。
namespaceで、生成するバインディングの名前空間を設定します。
identifier_prefixとsymbol_prefixは、gnome.mkenums_simple()と同様に設定します。 例えば、前者をMilter、後者をmilterと設定することで、MilterAgentがMilter名前空間内のAgentクラスとして認識されたり、 milter_xxx()関数がxxx()関数として認識されたりするようになります。
例えば、以下のようにビルドできます。
$ meson setup --prefix=/tmp/local ../milter-manager.build . $ ninja -C ../milter-manager.build
再起動時に自動でクリーンされる/tmp以下のパスをprefixに利用することで、以前にインストールした成果物の影響を受けにくくしています。
ビルド用のディレクトリとして、../milter-manager.buildを利用しています。
インストールは次のように行うことができます。
$ meson install -C ../milter-manager.build
上インストールコマンドはビルドも兼ねるので、ビルドだけしたいという場合でなければ、次のようにビルド&インストールできます。
$ meson setup --prefix=/tmp/local ../milter-manager.build . $ meson install -C ../milter-manager.build
本記事では、Mesonを使ってGObject Introspection対応のビルドシステムを構築する方法について紹介しました。
GObject Introspectionを利用してバインディングを生成するには、 GObject Introspection Annotations というアノテーションを適切に管理する必要があります。
それについては今後の記事でご紹介します。
クリアコードではこのように業務の成果を公開することを重視しています。 業務の成果を公開する職場で働きたい人はクリアコードの採用情報をぜひご覧下さい。
milter manager
現在クリアコードではソフトウェア開発者を募集しています! フリーソフトウェア開発での開発の仕方をベースに、成果や知識を公開し共有しながら業務に携われます。
Red Hat Enterprise Linux系OSの種類やバージョンを見分ける方法
RubyKaigi 2022 - Fast data processing with Ruby and Apache Arrow #rubykaigi
Apache Arrow
milter manager
ククログの記事はCC BY-SA 4.0とGFDLのデュアルライセンスで自由に利用できます。
クリアコードはプログラミングが楽しいソフトウェア開発者を1名募集しています。
クリアコードは「クリアコードをいい感じにする人」を1名募集しています。
クリアコードはフリーソフトウェア開発で培った技術力を提供しています。特にMozilla製品(Mozilla FirefoxとMozilla Thunderbird)とRubyとGroonga(全文検索)に関連した開発を得意としています。