YAMAGUCHI::weblog

海水パンツとゴーグルで、巨万の富を築きました。カリブの怪物、フリーアルバイター瞳です。

C/API拡張の練習でcbuchoを書いた

はじめに

こんにちは、Python界のSIGTERMです。先日CPython 3.2ソースコードリーディングに参加して「そもそもソース読む前にC/APIの書き方わかってないとだめだな」と痛感したので手習い的に書き始めてみました。題材は今や絶賛サンプルパッケージとしておなじみの bucho をC/APIで書きなおした cbucho です。

書き散らかしたもの

参考

とりあえず公式ドキュメントと既存のソースを読めばいいと思います。

はまったところ

ディレクトリ構造

次のようなディレクトリ構造にした。モジュール名のディレクトリを掘って、そこにCのソースを全部置きました。

% tree
.
├── MANIFEST.in
├── README.rst
├── cbucho
│  ├── cbuchomodule.c
│  └── cbuchomodule.h
└── setup.py
Cコンパイラに渡すオプションの自動チェック

setup.pyの書き方は公式ドキュメントにありますが、Cで読み込むライブラリのprefix等をどうやって取ってくるかではまりました。 id:mopemope が書いたソースを参考にさせてもらいました。

import sys
import os
from subprocess import Popen, PIPE

def conf_option(config_cmd, option):
  cmd = ' '.join([config_cmd, option])
  p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE)
  res = p.stdout.read()
  p.wait();
  
  opts = []
  for opt in res.split():
    opt = opt.strip()
    if opt.startswith("-l") and option == '--libs':
      opts.append(opt[2:])
    elif opt.startswith("-L") and option == '--static-libs':
      opts.append(opt[2:])
    elif opt.startswith("-I") and option == '--cflags':
      opts.append(opt[2:])
    elif option == '--prefix':
      opts.append(opt)

  return opts

libraries = conf_option('curl-config', '--libs')
library_dirs = [opt + "/lib" for opt in conf_option('curl-config', '--prefix')]
include_dirs = [opt + "/include" for opt in conf_option('curl-config', '--prefix')]

これでcurl-config --prefixやxml2-config --libsといったコマンド経由で得られる情報を取得できました。

開発時ビルド

初回のビルドは普通にbuildしてdevelopでインストールしておく。

% python setup.py build
% python setup.py develop

2回目以降はbuildディレクトリ消してから再ビルドと再インストール。ほんとはrmじゃなくてsetup.py cleanでいけるはずなんだけど、どうもうまく消えてくれなかったのでこうしましたよ。

% rm -rf build && python setup.py build && python setup.py develop
ヘッダファイルの追加

setup.pyに書くのではなくMANIFEST.inに書くというのに気付かなかったのでsdistをつくってもうまいことビルドできずはまりました。Extensionのキーワード引数sourcesにはあくまで*.cのソースだけ渡すので、MANIFEST.inに読み込むヘッダファイルを書きました。

include README.rst
include cbucho/cbuchomodule.h

ソース改変後に再ビルドする際は次のような形で。

% rm -rf build && python setup.py build && python setup.py develop

ドキュメントを見る限りclearオプションでうまくいくはずだけど、ちょっとはまったのでbuildディレクトリを消した。

PyPIへのアップロード

初回登録はregisterをしておく。

% python setup.py register

@shimizukawaの助言よりWindows以外ではbdist_eggは使わないようにしたほうがいいとの事なのでsdistのみを登録してみます。

% python setup.py sdist upload

uploadする前にsdistだけ行って、できたtar.gzでインストールを確認しておくこと。

これから

次の内容はまだやってない。

  • ドキュメントの同梱
  • 複数のオブジェクトを作成してのsetup.py経由での*.so作成
  • setup.pyを用いてのtest