makefileにhelpを付けるワンライナー

makeファイルはデフォルトではhelpが存在しない。bashの補完の設定などを入れているとタブで利用可能なタスクの一覧が出る環境もあるが、その設定もしていない場合には利用可能なタスクの一覧も表示できない。

タスクに対するcommentの流派

似たようなことを考える人はいるもので、helpというタスク(ターゲット)を定義してhelpメッセージを表示しようという試みをしている人たちがいる。

ちょっとだけ検索してみた所以下の2つの流派があるみたい。

<task>: ## <comment>

## <comment>\n<task>:

<task>: ## <comment> 派が多いように見えたけれど、概ね元となる一番上の記事のコピーのようだった。

marmelab.com

雰囲気

前者は(<task>: ## <comment> 派) 以下の様に書く形。依存するターゲットがあった場合には ## の前に書く。

task0:  ## comment of task0
  .. do something

task1:  ## comment of task1
  .. do something

task2: task0 task1  ## comment of task2
  .. do something

後者(## <comment>\n<task>: 派)は以下の様に書く形。依存の記述方法で特に気をつけることは存在しない。

## comment of task0
task0:
  .. do something

## comment of task1
task1:
  .. do something

## comment of task2
task2: task0 task1
  .. do something

個人的には、後者(## <comment>\n<task>: 派)の方が良いのだけれど。これを導入するために新たなコマンドのインストールが必要というのは微妙だなーと思ったりした。

.DEFAULT_GOALMAKEFILE_LIST

ちなみにこれはtips的な話だけれど。両者は共に.DEFAULT_GOALMAKEFILE_LISTを使っている。

.DEFAULT_GOAL

.DEFAULT_GOAL は引数無しで make を実行した時に実行されるターゲットのこと。

.DEFAULT_GOAL := bar

foo:
   @echo foo
bar:
   @echo bar

ちなみにこれは、-p (--print-data-base)オプションで調べられる

$ make -p | grep -i default_goal
.DEFAULT_GOAL := bar

そして通常は先頭のタスク。

--- 00defaultgoal/Makefile   2019-04-15 21:58:29.207999840 +0900
+++ 01defaultgoal/Makefile    2019-04-15 21:58:24.384597546 +0900
@@ -1,5 +1,3 @@
-.DEFAULT_GOAL := bar
-
 foo:
    @echo foo
 bar:

.DEFAULT_GOAL := bar の指定がない時にはfoo

$ make -p | grep -i default_goal
.DEFAULT_GOAL := foo

MAKEFILE_LIST

これはmakeで渡されたファイルのこと。通常はMakefileなりmakefileなどが入る。ファイル名を気にせずタスクを記述できるようになっている。

build.mk

default:
  echo @@ $(MAKEFILE_LIST) @@

例えば以下の様に -f 付きでファイルを指定したときなどに値が変わる。

$ make -f build.mk
echo @@  build.mk @@
@@ build.mk @@

ちなみに同様のものとして $(MAKE) というものもある。

## <comment>\n<task>:ワンライナー

個人的には ## <comment>\n<task>: の方がMakefileとしては扱いやすい気がするので好みなのだけれど、気持ちparseするのが面倒なので正直な所unixのコマンドの範囲だとツライ。なにかテキトーなLLでのワンライナーでやってしまいたくなる。

このあたりになるとお里が知れるという感じになるかもしれない。

python自体はワンライナーに向いている言語ではないけれど、慣れているのでpythonでやってみる。rubyで言うeash_consに似たものを作るのにitertoolsのteeとchainを使うのはかなりオーバーエンジニアリング(?)感がある。まぁどこにでもpythonくらいは入っていそうなので。

help:
   @cat $(MAKEFILE_LIST) | python -u -c 'import sys; import re; from itertools import tee,chain; rx = re.compile(r"^[a-zA-Z0-9\-_]+:"); xs, ys = tee(sys.stdin); xs = chain([""], xs); [print(f"""\x1b[36m{line.split(":", 1)[0]:20s}\x1b[0m\t{prev.lstrip("# ").rstrip() if prev.startswith("##") else "" }""") for prev, line in zip(xs, ys) if rx.search(line)]'

一応こんな感じで表示される様になる。

helpの例

ちなみに個人的にはソートされずにファイルに記述された順序でhelpメッセージが出るほうが好きだったりします。

横着な人のための逆引きpipでpackageをinstallするときの細かな試行錯誤のメモ

横着な人(自分)のためのpipのメモ。

試行錯誤のためのフレッシュな環境が欲しい

正に今がそれ。pipの実行を試すために /tmp/foo に仮想環境を作る。

$ mkdir -p /tmp/foo
$ python -m venv /tmp/foo
$ cd /tmp/foo

activateせずに直接./bin/pip./bin/pythonで使っても良い(存在しない場合はエラーになるのでバッチなどで指定する場合にはむしろこの方が良いと思っていたりする)。

前提

$ python -V
Python 3.7.2

現在のインストール対象のパッケージ(marshmallow)は以下の様な状態

name version status
marshmallow 2.19.1 古い
marshmallow 2.19.2 pypiで最新(stable)
marshmallow 3.0.0rc5 pypiで最新(pre-release)
marshmallow 3.0.0rc5 githubのdevブランチ上のコード
mecab-python 0.996 壊れている
mecab-python3 0.996.1 python3でinstall可能

packageのinstall

versionを指定してのinstallは==

$ pip install marshmallow==2.19.1

installされているpackageのversionが知りたい

pip freezeとgrep

$ pip freeze
marshmallow==2.19.1

outdatedなpackageを探したい場合はlist --outdated

$ pip list --outdated
Package     Version Latest Type 
----------- ------- ------ -----
marshmallow 2.19.1  2.19.2 wheel
pip         18.1    19.0.3 wheel
setuptools  40.6.2  41.0.0 wheel

You are using pip version 18.1, however version 19.0.3 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.

pip自体の更新もあった。

packageの更新をしたい場合には --upgrade

--upgradeを付けないと新しいバージョンが存在してもpackageをupdateしてくれない。

$ pip install marshmallow
Requirement already satisfied: marshmallow in ./lib/python3.7/site-packages (2.19.1)

--upgrade 付きで実行でupdate

$ pip install --upgrade  marshmallow pip
$ pip freeze
marshmallow==2.19.2

(細かい話で--upgrade-strategyなどもあるが省略)

constraintsの指定はconstraints.txt

constraintsを満たしていたら何もしない

$ echo "marshmallow>2.19" >> constraints.txt
$ pip install marshmallow -c constraints.txt
Requirement already satisfied: marshmallow>2.19 in ./lib/python3.7/site-packages (from -c constraints.txt (line 1)) (2.19.1)

packageをどうしても特定のpackageのversionにしたい場合には --force-reinstall

versionを直接してinstallしようとしてもdowngradeはできない。

$ pip install --upgrade -c constraints.txt marshmallow==2.19.1
Requirement already satisfied: marshmallow>2.19 in ./lib/python3.7/site-packages (from -c constraints.txt (line 1)) (2.19.2)
$ pip install -c constraints.txt marshmallow==2.19.1
Requirement already satisfied: marshmallow>2.19 in ./lib/python3.7/site-packages (from -c constraints.txt (line 1)) (2.19.2)
$ pip freeze
marshmallow==2.19.1

--force-reinstall を付ける

$ pip install --force-reinstall -c constraints.txt marshmallow==2.19.2
$ pip freeze
marshmallow==2.19.2

githubのrepositoryなどにアクセスする場合

VCSで管理されているpackageから直接リポジトリを指定してinstallする方法は幾つかある(@<branch> で該当するブランチを指定)。

以下はgithubの例。

$ pip install "git+ssh://git@github.com/marshmallow-code/marshmallow.git@dev#egg=marshmallow"
$ pip install "git+https://github.com/marhshmallow-code/marshmallow.git@dev#egg=marshmallow"
$ pip install "git+git://github.com/marhshmallow-code/marshmallow.git@dev#egg=marshmallow"

場合によっては --upgrade--force-reinstall が必要。

$ pip install --upgrade "git+ssh://git@github.com/marshmallow-code/marshmallow.git@dev#egg=marshmallow"
$ pip freeze
marshmallow==3.0.0rc5

特定のrepositoryのsubdirectoryにある場合

&subdirectory=<subdirectory> という形で指定する。

$ pip "git+https://github.com/marhshmallow-code/marshmallow.git@dev#egg=marshmallow&subdirectory=<subdirectory>"

詳しくはこのあたり

元に戻したい場合

元のpypiに登録されているpackageに戻したい場合には --force-reinstall を指定してあげれば良い。

$ pip install marshmallow --force-reinstall
marshmallow==2.19.2

pre-release packageを指定したい場合

--pre を付ける。

$ pip install --upgrade --pre marshmallow
$ pip freeze
marshmallow==3.0.0rc5

pre-releasepackageかどうかはversionの付け方で決まる。詳しくはこのあたり

packageをdownloadだけしたい

pip download でできる。

$ pip download marshmallow
$ ls marshmallow-2.19.2-py2.py3-none-any.whl
marshmallow-2.19.2-py2.py3-none-any.whl

packageのinstallが失敗した場合の対処方法

$ pip install mecab-python
Collecting mecab-python
  Using cached https://files.pythonhosted.org/packages/86/e7/bfeba61fb1c5d1ddcd92bc9b9502f99f80bf71a03429a2b31218fc2d4da2/mecab-python.tar.gz
    Complete output from command python setup.py egg_info:
    /bin/sh: mecab-config: command not found
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/tmp/pip-install-d9mp9gn_/mecab-python/setup.py", line 13, in <module>
        version = cmd1("mecab-config --version"),
      File "/tmp/pip-install-d9mp9gn_/mecab-python/setup.py", line 7, in cmd1
        return os.popen(str).readlines()[0][:-1]
    IndexError: list index out of range
    
    ----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-install-d9mp9gn_/mecab-python/

詳しく状況をトレースしたい場合には -v などを付ける。

$ pip install -v mecab-python
# 標準出力以外にもログを残したい場合には --log
$ pip instal --log=x.log mecab-python

エラー対応の試行錯誤には --no-clean

ふつうにpip installをして失敗したときには、かっこいいことに、エラーメッセージで言及されているディレクトリが消されている!!

このエラーメッセージ部分の /tmp

Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-install-d9mp9gn_/mecab-python/

探してみるとない。

$ ls /tmp/pip-install-d9mp9gn_/mecab-python/
ls: cannot access '/tmp/pip-install-d9mp9gn_/mecab-python/': No such file or directory

--no-clean を付けると消さずに残してくれる。

$ pip install --no-clean mecab-python
$ ls /tmp/pip-install-xjdxqail/mecab-python/
MeCab.py        PKG-INFO  pip-delete-this-directory.txt  setup.py
MeCab_wrap.cxx  README    pip-egg-info

なのでここでgit管理して手元で試行錯誤するのが早い。

$ cd /tmp/pip-install-xjdxqail/mecab-python/
$ git init
$ git add .
$ git commmit -m hmm
$ pip install -e .
Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-install-xjdxqail/mecab-python/

暗黙に依存したpackageをインストールしない方法

依存packageのinstallを止めたい場合は --no-deps を付ける。

$ pip install --no-deps statsmodels
Collecting statsmodels
  Using cached https://files.pythonhosted.org/packages/3c/68/bebc0f0e412fc8375f7daffc7ec2946acc20ac1a55fb4949098df23b9768/statsmodels-0.9.0-cp37-cp37m-manylinux1_x86_64.whl
Installing collected packages: statsmodels
Successfully installed statsmodels-0.9.0

本来は以下の様な感じに

$ pip install --cache-dir=xxx statsmodels
...
Installing collected packages: numpy, six, patsy, python-dateutil, pytz, pandas
Successfully installed numpy-1.16.2 pandas-0.24.2 patsy-0.5.1 python-dateutil-2.8.0 pytz-2019.1 six-1.12.0

参考

まじめな人はこのあたりを全部読むと良い

あと拡張packageのbuildが壊れた時にどうやって直すかは昔書いていた