あかり描像のブログ

思ったことや学習記録を適当に書いていきます。お気軽にコメントください

pkg-config はすごい

凄い

筆者の環境

  • Windows11 Pro
  • WSL2
  • VcXsrv
  • Ubuntu 20.04.6 LTS
  • pkg-config 0.29.2
  • gcc 9.4.0

pkg-config とは

www.freedesktop.org

開発というのは時に、外部のオープンソースに頼りながら進めていくことになります。

外部ライブラリを利用して開発する際、たいていの場合は人 (環境) によってインクルードディレクトリやライブラリディレクトリが異なってきます。
そのため、Makefile (やシェルスクリプト等) を使ってビルドする際にこれらディレクトリを直接パスで指定してしまうと、他の人の環境では同じ方法で make できなくなってしまうことになります。

pkg-config はそれを解消するためのツールです。とにかくすごい。

ja.wikipedia.org

pkg-config の使い方

pkg-config は PKG_CONFIG_PATH という環境変数で指定されるパスにある *.pc ファイルを参照して、該当の include, lib ディレクトリを持ってきてくれます。(上の Wikipedia 等を読もう)
PKG_CONFIG_PATH は (恐らく) 自分で指定する必要がありますが、大抵は次の2つのうちどちらかを指定することになるかと思います。

  • /usr/lib/pkgconfig
  • /usr/local/lib/pkgconfig

上は所謂すべてのユーザー用、下はローカルユーザー用ってことになりますが、不安なら両方を指定してやってもいいのかもしれない?ログインの度に環境変数を指定し直すのはしんどいので、 ~/.bash_profile (か、~/.bashrc) に以下を追記しておくとよいかと思われます。

# ~/.bash_profile か ~/.bashrc
export PKG_CONFIG_PATH="/usr/lib/pkgconfig"				#  上の場合
export PKG_CONFIG_PATH="/usr/local/lib/pkgconfig"			#  下の場合
export PKG_CONFIG_PATH="/usr/lib/pkgconfig:/usr/local/lib/pkgconfig"	#  両方の場合

さて、PKG_CONFIG_PATH を通しても、そこに *.pc ファイルがないと pkg-config は動いてくれません。巨大なライブラリをインストールしていたらいつの間にか自動で生成されているかもしれませんが、そうでないことも稀によくあることが知られています。

次の節では、glib-2.0 に含まれる glib.h を例に、pkg-config を使ってコンパイルする流れを追ってみることにしましょう。

glib-2.0 の glib.h を pkg-config で使ってみよう

この節では、glib-2.0 に含まれる glib.h を使った C 言語のコードを pkg-config を使ってコンパイルする方法を追ってみることにします。

[事前の注意] libc と glib と glibc は違うぞ!

そもそもこの記事を書こうと思った訳


タップで開く
そもそも私がこの記事を書こうと思った動機は、fcitx-imlist の ./confingure をしようとした時に glib-2.0 が見つからないと怒られたからです。

configure: error: Package requirements (glib-2.0 >= 2.26) were not met:

No package 'glib-2.0' found

Consider adjusting the PKG_CONFIG_PATH environment variable if you
installed software in a non-standard prefix.

Alternatively, you may set the environment variables GLIB_CFLAGS
and GLIB_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.

これを見て、

$ echo $PKG_CONFIG_PATH

をやってみたら何も出力されなかったので、ああそういうことかとなった次第。ここで同時に pkg-config について学び、ついでに glib-2.0 を pkg-config 経由で動かす方法を模索した、という訳でありまして、ついでにここにその記録を残しとけ、ってなったという訳であります。

(動機おわり)

そもそも GLib って何やねん

GLib is a general-purpose, portable utility library, which provides many useful data types, macros, type conversions, string utilities, file utilities, a mainloop abstraction, and so on.

https://docs.gtk.org/glib/ ←2023/06/14 アクセス.

GLib provides the core application building blocks for libraries and applications written in C. It provides the core object system used in GNOME, the main loop implementation, and a large set of utility functions for strings and common data structures.
https://wiki.gnome.org/Projects/GLib ←2023/06/14 アクセス.

だそうです。

glib-2.0 の導入と pkg-config の設定

glib-2.0 は次で手に入れられます。

$ sudo apt install libglib2.0-dev

以下では、PKG_CONFIG_PATH に /usr/lib/pkgconfig を指定した場合について述べます。


前節で PKG_CONFIG_PATH に指定した場所*1に新しく glib-2.0.pc を作成して、次のように記述する。(sudo が必要になると思います) (Wikipedia のコピーです。)


/usr/lib/pkgconfig/glib-2.0.pc

prefix=/usr
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${prefix}/include

glib_genmarshal=glib-genmarshal
gobject_query=gobject-query
glib_mkenums=glib-mkenums

Name: GLib
Description: C Utility Library
Version: 2.30.2
Libs: -L${libdir} -lglib-2.0
Libs.private:  -lrt
Cflags: -I${includedir}/glib-2.0 -I${libdir}/glib-2.0/include

何を言っているのかよく分かりませんが、ざっと概観をいうと、

  • ライブラリディレクトリには /usr/lib を
  • インクルードディレクトリには /usr/include/glib-2.0 と /usr/lib/glib-2.0/include を

指定すると言っています。


試しにちゃんと動くか確かめてみましょう。

--cflagsコンパイラに渡すべきインクルードのオプションを出力できます。

$ pkg-config --cflags glib-2.0
-I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include

同様に、--libs でライブラリのオプションを出力できます。

$ pkg-config --libs glib-2.0
-lglib-2.0

同時に出力させるには --cflags--libs を並べれば OK です。

$ pkg-config --cflags --libs glib-2.0
-I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -lglib-2.0

glib.h を使ってみよう

次に適当な場所に次のような C 言語のファイルを用意します。(a.cとします。) (またもやほぼ Wikipedia のコピーです。)

// a.c
#include <glib.h>
int main()
{
	g_print("hello\n");
	return 0;
}

さて、これを gcc するのですが、glib.h の場所を -I で指定したりするのではなく、pkg-config を使って次のようにします。

$ gcc $(pkg-config --cflags --libs glib-2.0) a.c

すると

In file included from /usr/include/glib-2.0/glib/galloca.h:32,
                 from /usr/include/glib-2.0/glib.h:30,
                 from a.c:1:
/usr/include/glib-2.0/glib/gtypes.h:32:10: fatal error: glibconfig.h: そのようなファイルやディレクトリはありません
   32 | #include <glibconfig.h>
      |          ^~~~~~~~~~~~~~
compilation terminated.

はい、怒られましたね。日常茶飯事です。glibconfig.h がないと言われました。
先ほど glib-2.0.pc で指定したディレクトリを見てみても、確かにありません。

これがどこにあるかと言いますと、、、

/usr/lib/x86_64-linux-gnu/glib-2.0/include

です。なんでやねん!!って感じですが、確かにここには置いてあります。
なので、先ほどの glib-2.0.pc にこれを書き加えるか、include ディレクトリに glibconfig.h をコピペすると無理やり通せそうな気がします。


今回はとりあえず glib-2.0.pc を追記することにしました。


/usr/lib/pkgconfig/glib-2.0.pc

prefix=/usr
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${prefix}/include
aptdir=${libdir}/x86_64-linux-gnu

glib_genmarshal=glib-genmarshal
gobject_query=gobject-query
glib_mkenums=glib-mkenums

Name: GLib
Description: C Utility Library
Version: 2.30.2
Libs: -L${libdir} -lglib-2.0
Libs.private:  -lrt
Cflags: -I${includedir}/glib-2.0 -I${libdir}/glib-2.0/include -I${aptdir}/glib-2.0/include

実際やってみると

$ gcc $(pkg-config --cflags --libs glib-2.0) a.c
/home/linuxbrew/.linuxbrew/bin/ld: /tmp/ccP3SXxK.o: in function `main':
a.c:(.text+0x15): undefined reference to `g_print'
collect2: error: ld returned 1 exit status

。。。もう訳分かんない!

tmytokai.github.io

はい。すみません。ライブラリは後でリンクする必要があるんでした。何と初歩的な...
Wikipedia 間違えてんじゃん!!

という訳なので、ちゃんと順番を守って指定してあげて

$ gcc a.c -o a $(pkg-config --cflags --libs glib-2.0)
$ ./a
hello

よーやっと通った。。。。あー大変だった。

Makefile で pkg-config を使ってみよう

さて、pkg-config は Makefile と相性が良いです。

Makefile が何なのかという分かりやすい解説はインターネット上に無限に存在するので、詳細はそちらに譲るとして、これを使うと何がどうなるかだけを述べると、上で

$ gcc a.c -o a $(pkg-config --cflags --libs glib-2.0)

と打たなければならなかったものが、単に

$ make

と打つだけでコンパイルできるようになります。


。。。いや、Makefile の偉大さは本来そこではないのですが*2、まぁ、pkg-config の利用例としては格好の題材だと思ったので。

Makefile 速習

Makefile は、次のような基本構造の羅列で構成されます。

[作りたいもの]: [材料]
(---Tab---)[作り方]

([作り方] の前の空白は必ず Tab キーで入力してください)



例えば、旨いカップ麺の Makefile を書くとなると

旨いカップ麺: 日清カップヌードル お湯
	日清カップヌードルにお湯をかけて3分待つ

お湯: 水
	水を沸騰させる

みたいな感じになるでしょう。(←)

もし手元に日清カップヌードルがなければ、それを手に入れるための方法をさらに下に追記していくことになります。



何が言いたいのかというと、作りたいものがまず先にあって、それを作るにはこの材料が必要で、こう作れば良い、という要領で、「結果から逆算」していってる訳です。

この「結果から逆算」をすることによって、プログラムのビルドの依存関係を直観的に記述することが容易になります。これが Makefile の強みの一つでもあります。



Makefile の特長はこれだけではないのですが、、、

Makefile を作ってみる

。。。ひとまず、先ほど作った a.c と同じディレクトリに、次のような Makefile を作ってみましょう。

# Makefile
INCLUDES	= $(shell pkg-config --cflags --libs glib-2.0)
CC		= gcc
PROGRAM		= a
SRC		= a.c

all: $(PROGRAM)

$(PROGRAM): $(SRC)
	$(CC) $(SRC) -o $(PROGRAM) $(INCLUDES)
  • 最初の4行で変数 (正確にはマクロといいます) を定義しています。
  • all が早速特殊なタイプで、作り方がなく材料だけが指定されています。
  • その次の $(PROGRAM) 云々が本体です。上で定義したマクロがそのまま展開されて、今まで shell で打ってきた通りのことがここで実行されます。

詳しくは他サイト様をみてね。


そして make してみます。するとどうでしょうか。

$ make
gcc a.c -o a -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -lglib-2.0
$ ./a
hello

はい。pkg-config がコンパイルに必要なインクルードやライブラリのオプションを自動で指定してくれ、ちゃんと実行できましたね。

まとめ

pkg-config を使いこなして充実したプログラミングライフを送ろう!

*1: /usr/lib/pkgconfig/ 直下です。

*2:依存関係を直観的に記述できるとか、並列処理を自動化できる、とか。