Contents
Pythonには縁もゆかりもないC/C++をdistutilsでビルドする
前置き
「Microsoft Visual C++ Compiler for Python 2.7はひとのためならず」に関係するといえばする。元はと言えばDistutilsは「Pythonモジュールを作るため」のものであろうから、これは「やや目的外使用」ということにはなるけれど、もちろんこれは「出来る」。
Makefile に苦痛を感じないのはきっと「ある特定の linux でしか使ったことがない」からだ。SYSV make、BSD make だけで存分に嫌になって、FSF の make に安心しつつあったのに MS の nmake でどん底に突き落とされた経験がないからだ。移植性のある makefile なんか無理だし、だいいち記述量が多過ぎるではないの、鬱陶しいたらありゃしない。Eclipse が解決? 別にそうでもないでしょーよ。
気が短い方へ
今回の「全部」は build_non_python_with_distutils_example.zip に固めてあります。気が短い方はこれだけでもいいと思うよ。
あ、Microsoft Visual C++ Compiler for Python 2.7 使う場合は、setuptools最新はまだCythonのためならずを先に読んで下さい。
SConsという手もあるが
SConsも良いのだが、「やりすぎ」が仇となっているところもあるし、(少なくとも2.3.0では)Microsoft Visual C++ Compiler for Python 2.7 に対応出来ないので、今回はパス。
一番しょうもなくて簡単なやつ
一個の C ソースファイルから一個の実行ファイルを作るだけの。
C はこんなな。(何するものかは察してください。)
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int main(int ac, char** av)
5 {
6 int i = 1;
7 for (; i < ac; ++i) {
8 if (i > 1) {
9 fputs(" ", stdout);
10 }
11 /* */ {
12 const char* p = av[i] + strlen(av[i]);
13 char* rev = (char*)malloc(strlen(av[i]) + 1);
14 char* revp = rev;
15 while (p >= av[i]) {
16 *revp++ = *--p;
17 }
18 *revp = 0;
19 fputs(rev, stdout);
20 free(rev);
21 }
22 }
23 fputs("\n", stdout);
24 return 0;
25 }
これを distutils でビルドするための setup.py はこれだけ:
1 # -*- coding: utf-8 -*-
2 # -----------------------------------
3 from distutils import log
4 from distutils import ccompiler, sysconfig
5
6 log.set_verbosity(1) # INFO
7
8 cc = ccompiler.new_compiler()
9 sysconfig.customize_compiler(cc)
10 #cc.set_include_dirs([])
11 objects = cc.compile(['ohce.c'])
12 execname = 'ohce'
13 cc.link_executable(objects, execname)
1 me@host: ~$ python setup.py build
でビルドするのだぞ。
C++ になったくらいではへこたれない
1 #include <cstdio>
2 #include <cstdlib>
3 #include <string>
4 #include <algorithm>
5
6 int main(int ac, char** av)
7 {
8 for (int i = 1; i < ac; ++i) {
9 if (i > 1) {
10 fputs(" ", stdout);
11 }
12 std::string rev;
13 std::reverse_copy(
14 &av[i][0], &av[i][std::strlen(av[i])],
15 std::back_inserter(rev));
16 fputs(rev.c_str(), stdout);
17 }
18 fputs("\n", stdout);
19 return 0;
20 }
1 # -*- coding: utf-8 -*-
2 # -----------------------------------
3 from distutils import ccompiler, sysconfig
4
5 cc = ccompiler.new_compiler()
6 sysconfig.customize_compiler(cc)
7 #cc.set_include_dirs([])
8 if cc.compiler_type == 'msvc':
9 extra_preargs = ['-EHsc']
10 objects = cc.compile(['ohce.cpp'], extra_preargs=extra_preargs)
11 execname = 'ohce'
12 cc.link_executable(objects, execname)
MSVC 特有の癖には毎度苦痛は感じるねぇ。
static link ライブラリのビルドとそれにリンクする実行ファイル
サンプルを書くのが面倒くさくなって、「GeographicLib抱え込み」を例にしてみることにした。
これの setup.py はこんな:
1 # -*- coding: utf-8 -*-
2 # 何も問題なさげにみえて、そうでもない。
3 # distutils の制限で、Geographiclib-1.40 がこの階層よりも上、
4 # つまり「../」を含んでいると、distutils はこれを制御しようと
5 # しない。(output_dir="_build" は意味をなくす。)
6 # -----------------------------------
7 import os
8 import re
9 from distutils import log
10 from distutils import ccompiler, sysconfig, dep_util
11
12 log.set_verbosity(1) # INFO
13
14 cc = ccompiler.new_compiler()
15 sysconfig.customize_compiler(cc)
16 cc.set_include_dirs(['Geographiclib-1.40/include'])
17 if cc.compiler_type == 'msvc':
18 extra_preargs = ['-EHsc']
19
20 # build geographiclib static link library
21 import glob
22
23 srcs = list(glob.glob("Geographiclib-1.40/src/*.cpp"))
24 objs = ["_build/" + re.sub(r"\.cpp$", cc.obj_extension, src) for src in srcs]
25
26 # compile if each source is newer than its corresponding target.
27 for src in dep_util.newer_pairwise(srcs, objs)[0]:
28 cc.compile([src], "_build", extra_preargs=extra_preargs)
29 cc.create_static_lib(objs, "GeographicLib", ".")
30 #
31 log.set_verbosity(0) #
32
33 for fn in glob.glob("GeographicLib-1.40/examples/*.cpp"):
34 objects = cc.compile([fn], extra_preargs=extra_preargs)
35 execname = os.path.splitext(os.path.basename(fn))[0]
36 cc.link_executable(objects, execname, libraries=['GeographicLib'])
dep_util がポイントだろうな。これしないと、毎回必要もないのにコンパイルしに行っちゃうぞ。
dynamic link ライブラリのビルドとそれにリンクする実行ファイル
構造は static link のと同じで、setup.py の「下」に Geographiclib-1.40 がいる。
これの setup.py は以下:
1 # -*- coding: utf-8 -*-
2 # 何も問題なさげにみえて、そうでもない。
3 # distutils の制限で、Geographiclib-1.40 がこの階層よりも上、
4 # つまり「../」を含んでいると、distutils はこれを制御しようと
5 # しない。(output_dir="_build" は意味をなくす。)
6 # -----------------------------------
7 import os
8 import re
9 from distutils import log
10 from distutils import ccompiler, sysconfig, dep_util, errors
11
12 log.set_verbosity(1) # INFO
13
14 cc = ccompiler.new_compiler()
15 sysconfig.customize_compiler(cc)
16 cc.set_include_dirs(['Geographiclib-1.40/include'])
17 if cc.compiler_type == 'msvc':
18 extra_preargs = ['-EHsc']
19
20 # build geographiclib dynamic link library
21 import glob
22
23 srcs = list(glob.glob("Geographiclib-1.40/src/*.cpp"))
24 objs = ["_build/" + re.sub(r"\.cpp$", cc.obj_extension, src) for src in srcs]
25
26 # compile if each source is newer than its corresponding target.
27 for src in dep_util.newer_pairwise(srcs, objs)[0]:
28 # Windows 向け「__declspec(dllexport)」の対応方法はプロジェクトによりマチマチだが、
29 # GeographicLib と同じやり方が最も標準的。(MS 推奨でもある。)
30 # GeographicLib についてのそれについては、Constants.hpp 参照。
31 cc.compile([src],
32 "_build",
33 extra_preargs=extra_preargs + [
34 "-DGEOGRAPHICLIB_SHARED_LIB=1", "-DGeographicLib_EXPORTS"])
35 cc.link_shared_lib(objs, "GeographicLib", ".")
36 #
37 log.set_verbosity(0) #
38
39 for fn in glob.glob("GeographicLib-1.40/examples/*.cpp"):
40 objects = cc.compile([fn], extra_preargs=extra_preargs)
41 execname = os.path.splitext(os.path.basename(fn))[0]
42 try:
43 cc.link_executable(objects, execname, libraries=['GeographicLib'])
44 except errors.LinkError as e:
45 # static link 版では起こらないリンクエラーが出るのがいる。
46 # 「distutils で 非python な C/C++ ビルド」のお題の範疇外
47 # なので気にしていないが、まぁこれは良く起こる。
48 # おそらくコンパイラオプションかリンカオプションに何か追加
49 # すれば取れると思う。(典型的には /MD とかだな。)
50 print(e)
こうなってくると、「非 Windows プログラマ」もしくは「非 C/C++ プログラマ」にはキツくなってくるわな。これぞ C/C++ の移植性地獄。
それでも GeographicLib の移植性は優秀。
distutilsだと何が嬉しいか?
結局 Python フル機能使えるので、てことな。移植性についても、まぁまぁ良い塩梅で維持出来る。
繰り返すけど、「Microsoft の nmake と SYSV make と BSD make と GNU make」に苦しんだことがないから「Makefile便利!」とか「別に大変じゃないよ」なんて言えるのだ。(CMake も入れておこうか? べつもんだけどな、これは。)
build_non_python_with_distutils_example.zipについての注意
zip の中にも同内容の注意書き入れてますが、
1 サンプルを自作するのが面倒だったので、非常に移植性が高く linux、
2 Windows で一度も苦労したことがない GeographicLib を例にしている、
3 が、GeographicLib 自身もちゃんと「Windows 用ビルド環境」も、
4 「インストーラ」も提供してくれているので、ここでやっているのは
5 あくまでも例である。
6
7 ライブラリを抱え込みたい場合にはこのようなことは良くやる。
8 この方式を採れば、「公式バイナリが依存するランタイム」に振り回される
9 こともないので、良い事もある。無論それは「公式のバグフィックスリリー
10 ス」を無碍にすることも意味するので、抱え込むべきかどうかはいつだって
11 悩みどころである。一般には「やらないほうがいい」とはなる。
12 (静的ライブラリよりは良い。DLL を置き換えるだけで良い、となる可能性が
13 あるから。)
14
15 ※「GeographicLib-1.40」の中身は公式配布の全ては含まれてないです。
16 必要な場合は http://sourceforge.net/projects/geographiclib/ へ。
です。