2019年2月19日火曜日

[Azure Pipelines] ファイルがなかった場合にステップをスキップする

Add an exists() on Task Custom Condition - Developer Community



とあるファイルがある場合にだけ実行する Job/Step が欲しくなったのですが、condition に簡単に書けたら嬉しかったのですが、今の所ない模様。

こんな感じできたら・・・
- script: |
    echo "xml がある場合にしたいこと"
  condition: exist('./test/*.xml')


状態を variable に記録して条件とする
ワンステップではできないものの、比較的簡単にやりたいことは実現できます。

condition で variables を参照してスキップさせることはできるので、ファイルが存在しない場合にスキップ用の variable をセットするようにしました。

- script: |
    TEST_RESULTS=`find ./test -maxdepth 1 -name *.xml 2>/dev/null`
    if [ $? -ne 0 ] || [ -z "$TEST_RESULTS" ]; then echo "##vso[task.setvariable variable=XmlFileNotExist]true"; fi

- script: |
    echo "xml がある場合にしたいこと"
  condition: ne(variables['XmlFileNotExist'], 'true')



この方法であれば、「ファイルが存在するかどうか」以外でも、条件付けできそうですね。

2019年2月12日火曜日

Windows + Git で実行権限をつけて commit する

何回かハマって、何回も調べ直してるので、記憶の定着・備忘録のために自分のブログにも書き残しておくことにした。

Windows + Git Bash では 644 で commit される
chmod +x としておいても commit したときに書き換えられます。
$ git commit -m 'sample action a'
[master e432bd4] sample action a
 4 files changed, 21 insertions(+), 4 deletions(-)
 create mode 100644 action-a/Dockerfile
 create mode 100644 action-a/README.md
 create mode 100644 action-a/entrypoint.sh

この状態でもう一回 chmod +x しても差分として出てこない。

git update-index --add --chmod=+x
パーミッションの変更は上記コマンドで行います。

しょっちゅう忘れるので alias にしました。
git config --global alias.addx 'update-index --add --chmod=+x'

これで、addx エイリアスから chmod +x できるようになりました。



+x/-x 両方に対応したエイリアス
せっかくなのでもう少し汎用的なエイリアスも用意しました。
git config --global alias.chmod '!f(){ cd ${GIT_PREFIX:-.} && git update-index --add --chmod=$1 ${@:2:($#-1)};};f'

git chmod +x hoge
git chmod -x foo bar
のように使えます。

2019年2月4日月曜日

.editorconfig ファイル自体の Lint を CI で回す



[*.{cpp,hpp,ipp}}
charset = utf-8-bom
単純な typo ですが、今までずーと気づかずに config が仕事してくれてると思い込んでました。。。
直すのは簡単です。簡単ですが、また同じ失敗をする可能性があります。(にんげんだもの)

というわけで、テスト書きました。

テスト方法
.editorconfig の記法は概ね ini ファイルと一致します。
なので、適当な ini ファイルパーサーに読み込ませてエラーが起きないかどうか?で検証できそうです。

iutest は C++ のテスティングフレームワークですが、ツール系には Python も使用しています。
Python には ConfigParser があるのでそちらでテストを書きました。

import sys
import os

try:
    from configparser import ConfigParser
except ImportError:
    from ConfigParser import SafeConfigParser as ConfigParser


class EditorConfig(object):
    def __init__(self, path):
        self.name = os.path.basename(path)
        self.fp = open(path)
        self.first_head = True

    def readline(self):
        if self.first_head:
            self.first_head = False
            return '[global]\n'
        return self.fp.readline()

    def __iter__(self):
        return self

    def __next__(self):
        line = self.readline()
        if not line:
            raise StopIteration
        return line

    next = __next__  # For Python 2 compatibility.


def main():
    path = sys.argv[1]
    ini = ConfigParser()
    if not os.path.exists(path):
        sys.stderr.write('%s not found...' % path)
        sys.exit(2)
    ini.readfp(EditorConfig(path))
    sys.exit(0)

if __name__ == '__main__':
    main()

基本的には ini ファイルをパーサーにかけるだけなんですが、1点だけポイントがあります。
EditorConfig は、ディレクトリを上がって .editorconfig を探索しますが、root = true を最初に書いておくとそこで探索が打ち切られます。
この root = true が ini ファイルとしてはセクションのない要素のためエラーになってしまいます。
そのため、最初に [global] というダミーセクションを挿入して ConfigParser に渡してます

CI に組み込む
Python が実行できる CI サービスであればなんでも問題ありません。
今回 iutest では Codeship で cpplint やインクルードガードのチェックや絶対パスが混入してないかの、雑多なテストを行っているのでそちらに組み込みました。



こんな感じに検出されます!
これでもう安心ですね。