setup.pyの拡張

pythonのプログラムを書いたらsetup.pyでインストールできるようにしよう!ということで書いたけどMakefileのがよくね?とかそんな疑問を持ちつつほげー

やりたいこと

  • 適当なコマンドの追加
    • setup()のattrs引数で、このコマンドの対象とするファイルを指定する
  • buildを実行すると、追加したコマンドを実行する
  • cleanを実行すると、追加したコマンドの生成したファイルを削除する

まず、コマンドの追加方法。distutils.cmd.Commandを派生させる。

from distutils.cmd import Command

class MyCommand(Command):

    # 以下のクラス変数は宣言しないと怒られる
    description = 'My command'
    user_options = []

    def initialize_options(self):
        pass

    def finalize_options(self):
        pass

    def run(self):
        print('Do my command.')

initialize_option, finalize_options, runをoverwrideする。実行したい処理はrunに記述する。その上で、setup()のcmdclassにコマンド名とともに作成したクラスを指定する。

from distutils.core import setup

setup(
    cmdclass = {'mycommand': MyCommand}
)

以上でmycommandが実行可能に

$ ./setup.py mycommand
running mycommand
Do my command.

つぎに、コマンドの中からsetup()に独自の引数を追加し、MyCommandの中から参照する。具体的には以下のように、greetingを指定したらその内容を出力するようにしてみる。

setup(cmdclass = {'mycommand': MyCommand },
      name = 'hoge',
      version = '1.0.0',
      packages = [ 'hoge' ],
      greeting = 'Popopopoooon'
     )

まず、Distributionを派生させて、インスタンス変数を追加する。ここでの注意は、スーパークラスの__init__()を呼ぶ前にインスタンス変数を作っておくこと。つまり、スーパークラスの__init__()でそれっぽい処理が走ってる。

from distutils.dist import Distribution

class MyDistribution(Distribution):
    def __init__(self, attrs=None):
        self.greeting= None
        Distribution.__init__(self, attrs)

次に、MyCommandでそれを取得する。

from distutils.cmd import Command

class MyCommand(Command):

    # 以下のクラス変数は宣言しないと怒られる
    description = 'My command'
    user_options = []

    def initialize_options(self):
        self.greeting = None

    def finalize_options(self):
        self.greeting = self.distribution.greeting

    def run(self):
        print(self.greeting)

で、このクラスをsetup()のdistclassに指定。

setup(distclass = MyDistribution,
      cmdclass = {'mycommand': MyCommand },
      name = 'hoge',
      version = '1.0.0',
      packages = [ 'hoge' ],
      greeting = 'Popopopoooon'
     )

実行!

$ ./setup.py mycommand
running mycommand
Popopopoooon

最後に以下の2つ

  • buildを実行すると、追加したコマンドを実行する
  • cleanを実行すると、追加したコマンドの生成したファイルを削除する

やってることは同じで、デフォルトのコマンドのrun()を上書きしている。

from distutils.command.build import build
from distutils.command.clean import clean

class MyBuild(build):
    def run(self):
        self.run_command('mycommand')
        return build.run(self)

class MyClean(clean):
    def run(self):
        clean.run(self) 
        if self.all:            
            print('Remove nanka')

MyBuildではself.run_command()でmycommandを実行している。
また、MyCleanでは一応、--allオプションが指定されたときだけ処理が走るように、self.allを見ている。

で、最後にsetup()のcmdclassに作ったクラスを指定する。

setup(distclass = MyDistribution,
      cmdclass = {'mycommand': MyCommand, 'build': MyBuild, 'clean': MyClean },
      name = 'hoge',
      version = '1.0.0',
      packages = [ 'hoge' ],
      greeting = 'Popopopoooon'
     )

実行!

$ ./setup.py build 
running build
running mycommand
Popopopoooon
running build_py
$ ./setup.py clean --all
running clean
removing 'build/lib.linux-x86_64-2.6' (and everything under it)
'build/bdist.linux-x86_64' does not exist -- can't clean it
'build/scripts-2.6' does not exist -- can't clean it
removing 'build'
Remove nanka

しかし、makeから劣化してねーかなぁ…