プロジェクト配下の任意のディレクトリからsbtを起動できるようにする


普段 Scala でプログラミングをするときにビルドツールとして **sbt** を使用していて不便に感じることのひとつが
**`build.sbt` のあるディレクトリ（=プロジェクトのルートディレクトリ）で sbt を起動しないといけない** こと。

プロジェクト配下のサブディレクトリに移動して作業していたりすると、
いちいちルートディレクトリに移動してから sbt を起動しないといけなくてめんどくさい。

Python のコマンドラインツール **Fabric** なんかだと、カレントディレクトリから親ディレクトリに向かって
`fabfile.py` を探してくれて便利なので、同じようなことを sbt でもできるようにしたい。

sbt 自体にはそのようなオプションはないようなので、ちょっとしたシェルスクリプトを書いて実現しました。

1. あるファイルを、カレントディレクトリから親ディレクトリに向かって再帰的に探すシェルスクリプトを作成。
1. そのシェルスクリプトを使って `build.sbt` を探し、見つかったディレクトリで sbt を起動するシェル関数を定義。
1. 定義したシェル関数で sbt を起動。

## 親ディレクトリからファイルを探すスクリプトを作成

まずは、カレントディレクトリから親ディレクトリに向かって再帰的にファイルを探すシェルスクリプトを作成し、
パスが通っている場所（`/usr/local/bin`とか）にお好みの名前で置いて実行権限を付けておきます。

ここでは `/usr/local/bin/pfind`（Parent FIND） と言う名前で、↓な感じでシェルスクリプトを作ります。
特に特定のライブラリなどは使ってないので、Bashさえ動く環境であれば使えるはずです。
ちなみに、このシェルスクリプトは sbt 専用でもないので汎用的に使えます。

> シェルプログラミングが得意な方ならもっとシンプルに書けると思いますが...
> と言うか親ディレクトリからファイルを探すコマンドってないのかな

```bash
#!/bin/bash

find_in_parents () {
  slashes=${PWD//[^\/]/}
  directory=$BASE_DIR
  for (( n=${#slashes}; n>0; --n ))
  do
    test -e "$directory/$FILE" && echo "$directory" && return
    directory="$directory../"
  done

  # Not found.
  exit -1
}

show_usage() {
  echo "Find file recursively in the parent directory."
  echo ""
  echo "Usage: "
  echo "  pfind -f FILE -b BASE_DIR -p"
  echo "    -f: Target file name."
  echo "    -b: Base directory for starting search. (OPTIONAL Default current directory)"
  echo "    -p: With file name.(OPTIONAL Default direcotry only)"
  echo "Exampe:"
  echo " pfind -f foobar.txt"
  echo " pfind -f foobar.txt -b /foo/bar"
  echo " pfind -f foobar.txt -b /foo/bar -p"
  echo ""

  exit -1
}

while getopts f:b:p OPT
do
  case $OPT in
    f) FILE=$OPTARG
      ;;
    b) BASE_DIR=$OPTARG
       ;;
    p) WITH_FILE=true
  esac
done
[ "$FILE" == "" ] && show_usage
[ "$BASE_DIR" == "" ] && BASE_DIR=`pwd`/
[ "$WITH_FILE" == "" ] && WITH_FILE=false

FOUND_DIR=`find_in_parents`
RC=$?
if [ $RC == 0 ]; then
  if $WITH_FILE ; then
    echo `(cd $FOUND_DIR; pwd)`/$FILE
  else
    echo `(cd $FOUND_DIR; pwd)`
  fi
else
  echo "$FILE not found." >&2
  exit $RC
fi
```

使い方はこんな感じになります。（`/Users/xxxx/work/example/build.sbt`がある例）

```bash
$ pwd
/Users/xxxx/work/example/src/main/resources

$ pfind -f build.sbt
/Users/xxxx/work/example

$ pfind -f build.sbt -p
/Users/xxxx/work/example/build.sbt
```

## build.sbtを探してsbtを起動するシェル関数を定義

↑で作成したシェルスクリプトを使って `build.sbt` を探し、見つかったディレクトリで sbt を起動するシェル関数を定義します。
これまた関数名はお好みで。ここでは `ssbt`（特に意味なし） って名前にしています。

エイリアスでも良いですが、エイリアスだと sbt にパラメタを渡すことができないのでシェル関数にしておくのが吉です。

```bash:~/.bashrc
function ssbt() {
  BUILDSBT_IN_DIR=`pfind -f build.sbt`
  RC=$?
  if [ $RC == 0 ]; then
    (cd $BUILDSBT_IN_DIR; echo "build.sbt found in `pwd`"; sbt $@)
  else
    echo "build.sbt not found."
  fi
}
```

* `cd`してから`sbt`起動は、`( )`で囲んで書いておけば `build.sbt` の場所にカレントディレクトリが移動しないので使いやすいと思います。
* `$@` でパラメタを sbt に渡します。

`zsh`な人はこんな感じ。（ifの構文が違う）

```zsh:~/.zshrc
function ssbt() {
  BUILDSBT_IN_DIR=`pfind -f build.sbt`
  RC=$?
  if [[ $RC -eq 0 ]]; then
    (cd $BUILDSBT_IN_DIR; echo "build.sbt found in `pwd`"; sbt $@)
  else
    echo "build.sbt not found."
  fi
}
```

## sbtを起動

これで `build.sbt` を置いてあるディレクトリの下からであれば、どこからでも sbt を起動できます。

```bash
$ pwd
/Users/xxxx/work/example/src/main/resources

$ ssbt
build.sbt found in /Users/xxxx/work/example
[info] Loading global plugins from /Users/xxxx/.sbt/0.13/plugins
[info] Loading project definition from /Users/xxxx/work/example/project
> 

# パラメタを指定することもできます
$ ssbt clean compile
```

以上です。