CORDEA blog

Android applications engineer

claat で生成した codelab を netlify で CD する

先日私達が公開した Android Dagger codelab は netlify を使用して公開しており、今回はその方法を紹介します。

github.com

claat は google が公開している codelabs を生成するための command line tool です。

github.com

行っていることは非常に簡単で、deploy script を netlify で走らせているだけです。
はじめに検討したのは github.io で、実際にはじめの方は github.io で公開されていましたが、
その時は GitHub Actions も有効化されておらず、自動化を実現できなかったため netlify に移行しました。

dagger-codelabs には scripts 配下にいくつかの shell script が配置されています。

dagger-codelabs/scripts at master · outerheavenproject/dagger-codelabs · GitHub

その中でも deploy.sh を netlify の build command としており、これで deploy しています。
それぞれの shell script がやっていることを簡単に説明します。

deploy.sh

dagger-codelabs/deploy.sh at master · outerheavenproject/dagger-codelabs · GitHub

download_claat, codelab_gen を叩いているだけです。

download_claat.sh

dagger-codelabs/download_claat.sh at master · outerheavenproject/dagger-codelabs · GitHub

readonly url=$(curl https://api.github.com/repos/googlecodelabs/tools/releases/latest | \
    jq -r '.assets[] | select(.name == "claat-linux-amd64") | .url')
curl -LJ -o /tmp/claat -H 'Accept: application/octet-stream' $url
chmod u+x /tmp/claat

claat command を使用できるよう、googlecodelabs/tools から download しています。
まず latest release を見て、返ってくる json から download 用の url を取得しています。
実際に json を見てみると分かりやすいでしょう。jq は新規に install などしなくても使用できます。
次に claat command を download します。このあたりに書いてあります。

あとは実行権限を付与します。

codelab_gen.sh

dagger-codelabs/codelab_gen.sh at master · outerheavenproject/dagger-codelabs · GitHub

readonly base_dir="/tmp/dagger_codelabs"
readonly file="$base_dir/$(date +%s).md"

mkdir $base_dir

cp md/chapters/*.png "$base_dir/."

cat md/INDEX.md > $file
cat md/chapters/*.md >> $file
/tmp/claat export -o docs/ $file

rm -r $base_dir

ここでは章毎に分割された markdown をくっつけています。これは章によって担当が決まっているため、少なくとも章単位で別々の人が編集できる必要があったからです。
ついでに png もあるので、参照可能な位置に copy したあと、各章を /tmp/ にある deploy 用の md に追記していきます。
最後に claat command で markdown から html に変換します。

欠点として、最後に改行を入れないとうまく出力できないという問題があります (cat で結合しているだけなため)。
今回はメンバー間の認識を合わせるのも簡単だったため、このあたりの雑さを許容していましたが、
もっと広く Contributor が集まるような場合にはもう少しちゃんと作ったほうがいいだろうと考えています。

もっと簡単に実現できる方法もあると思います。何かあればぜひコメント、Issue、PR 等ください。

【Rust】imageproc で任意の位置に text を描画する際の注意点

imageproc に限らず、rusttype で font の layout を使用する際には概ね同じ問題に当たりそうなんですが、
そこまで調べてないので imageproc に限った話として紹介します。

今回はテキストの描画位置をちょうど真ん中にすることを考えてみます。
なお、ここで紹介している描画部分のコードは GitHub にあります。

github.com


まず、imageproc の draw_text_mut で任意の文字を描画してみます。

draw_text_mut(&mut image, Rgba([0u8, 0u8, 0u8, 255u8]), 0, 0, scale, &font, "rustpythonclojureC#");


f:id:CORDEA:20190616145632p:plain

中央にする場合、テキストの正確な width と height が必要になります。
これは font.layout で取得できるので、取得して各文字の min.x, min.y, max.x, max.y をもとに周りを囲ってみます。

f:id:CORDEA:20190616151012p:plain

見れば分かる通り、例えば max.x - min.x で文字ごとの width は取れるものの、
文字間の padding (kerning 処理?) は考慮されないため、ここを考慮して width を計算する必要があります。
また、height に関しては一文字目の max.y - min.y だけを見た場合、'r' の高さしか考慮されないため、
min.y の一番小さい文字 ('h' など) - max.y の一番大きい文字 ('p' など) を取得して高さを計算する必要があります。
高さに関してはどこを baseline と置くかで計算方法も変わってくるでしょう。

このあたりが 1 つ目の注意したほうが良さそうな点です。

以上を踏まえて、文字列全体の width と height を計算し、周りを囲ってみます。

f:id:CORDEA:20190616152046p:plain

これで正確な width, height が取得できました。
ただ、ここから中央揃えにするために (image.width / 2) - (width / 2) などとして x, y を計算して描画すると微妙にずれることに気がつくはずです。
というかそもそも画像でも margin が入っていますよね...

これが 2 つめの注意点で、描画する際に x, y に margin が入るため、描画する際にここを考慮する必要があります。

実際にこの margin がどの程度かですが
x に関しては一番はじめの文字の min.x を見れば良いです。
y に関しては、min.y が一番小さい文字の min.y を見れば良さそうです (ex. 'h')。

以上を踏まえて、x, y を margin 分左と上にずらすことで、ちょうど真ん中に描画することが出来ます。

f:id:CORDEA:20190616152910p:plain

【RoN BHS】set_object_health の挙動

Rise of Nations という Microsoft から発売された名作 RTS ゲームがあります。

このゲームには Big Huge Script という、ゲーム内でシナリオやルールを作るための言語があり、
今回はこの Script で使える関数の中から、 set_object_health という関数の挙動について説明します。

set_object_health とは

ゲーム内の Script Editor では次のように表示されます。

f:id:CORDEA:20190518122404j:plain

指定した ID の object の HP を 0-100 % の範囲で指定することができます。

tl;dr

  • 3 人 1 unit の場合、0-68% までは HP - math.floor(HP / 3)

挙動

この set_object_health は歩兵などの 3 人 1 unit の場合に不思議な挙動をします。
なお、ここでは Slingers を使用して確認しています。

どういうことかというと、単純に % 指定してそのとおりに動くとは限らないということです。

まず、ターゲットの HP を 0 に設定する場合を考えてみます。

set_object_health(who, id, 0);

これを騎兵に適用すると正常に行われます。
これは想定通りの挙動と言えます。

では、歩兵に適用してみます。

f:id:CORDEA:20190518122433j:plain

これは 3 人で 1 unit という扱いであるものの set_object_health は 1 人に対して適用される場合がある、ということの気がしています。
様々な数値で試してみると判るのですが、0-68% までは HP は 57 です。必ずしも指定する値と HP は一致しません。
なぜ 57 なのかは、1 人減っていることから

85 - (85 / 3) = 57

と考えられます。(より正確には 85.0 - math.floor(85.0 / 3.0))

さて、先程 1 人に対して適用される場合がありそうと書きましたが、では 80% など 69% 以上を設定する場合を見てみます。

set_object_health(who, id, 80);

この結果は以下のようになります。

すなわち、

85 * 0.8 = 68

で、1 人ではなく、1 unit に対して適用されていそうなことがわかります。
以上のように、歩兵などの unit に適用する場合は指定 % に対して HP が比例しないため、注意が必要です。

確認に使用した Script は以下にあります。

RoN_rules/Example at master · CORDEA/RoN_rules · GitHub

Chrome のコンテキストメニューから印刷...を消す

定期的に検索と印刷...を押し間違えて辛いので消したメモ。
もしかしたら Chrome の設定とかでできるかもしれないので、直接書き換えたくない人は探してみてください。

やり方については StackExchange に載っている通りで、Preferences の printing.enabled を false にすれば良いという話。

superuser.com

この Preferences は macOS の場合 ~/Library/Application Support/Google/Chrome/Default 配下にあります。 (Windows の場合の PATH は先のリンクに書いてありました)
printer とかがある人はすでに "printing" があると思うので、そこに "enabled" を書き加えると良いです。

Before

f:id:CORDEA:20190414121735p:plain

After

f:id:CORDEA:20190414121757p:plain

backup を用意した後 jq で format して書き換えて入れておいたのですが、起動時に勝手に minify してくれました。

おわり

Thread を toString した時の Thread[foo,0,bar]

RxJava とか使ってると稀によく見る Thread[main,5,main] みたいなやつ、どれがどれだっけってなったのでメモ

そもそも RxJava の source から読み始めたのでアレなんですが、
タイトルに書いてあるように Thread の doc か toString の実装を見ればいいです
Thread (Java Platform SE 8)
jdk8/jdk8/jdk: 687fd7c7986d src/share/classes/java/lang/Thread.java

"Thread[" + スレッド名 + "," + 優先順位 + "," + スレッドグループ + "]"

RxJava でよく見る RxCachedThreadScheduler-0 はここです

RxJava/IoScheduler.java at v2.2.8 · ReactiveX/RxJava · GitHub

後ろの番号を set しているのは

RxJava/RxThreadFactory.java at v2.2.8 · ReactiveX/RxJava · GitHub

おわり

static method 等を呼び出しているテストケースで Robolectric の Shadow を使う

あんまり知られてないような知られてるような、そんな感じがしたので Robolectric の Shadow の使い道をちょっと紹介します。

robolectric.org

紹介するのは

  • static method を呼び出ている
  • kotlin の object 宣言がされた singleton を内部で直接使用している
  • 内部で new している

などなど、色々な記事で PowerMock の出番とされていそうなケースについてです。

今回はこれを Robolectric の Shadow で解決します。
前提として、私は PowerMock を入れようと思った時、それは設計を見直しをするべき時だと考えています。
とはいえ、古いアプリや大規模なアプリなどでテストは書きたいが static method の呼び出しがネックになっている、直す工数もない。しかし PowerMock は入れたくないというケースは稀ですが存在します。
そんな時、使うべきかは置いておいて Shadow が使えます。

いくつか例を紹介します。
この例は全て GitHub にあります (難読化された感じの雑な命名はあとで直します)
github.com

1 つ目

object AUtil {
    fun a() {
        throw IllegalArgumentException()
    }

    @JvmStatic
    fun b() {
        throw IllegalArgumentException()
    }
}

class A {
    fun a() {
        AUtil.a()
    }

    fun b() {
        AUtil.b()
    }
}

1 つ目の例はこれです。
こんなの存在しないと思いますが、テストからアクセスできない何かにアクセスして throw されてる、でもその後の処理がテストしたい...!みたいな、そんな感じで考えてください。

これに対しては、以下のように Shadow を定義できます。

@Implements(AUtil::class)
class ShadowAUtil {
    companion object {
        @JvmStatic
        @Implementation
        fun b() {
        }
    }

    @Implementation
    fun a() {
    }
}

これを使用するとテストを通過させることができます。

@RunWith(AndroidJUnit4::class)
@Config(shadows = [ATest.ShadowAUtil::class])
class ATest {
    @Test
    fun a() {
        A().a()
    }

    @Test
    fun b() {
        A().b()
    }

    ...
}

2 つ目

2 つ目は内部で new していて、なんか失敗しているケースです。
Shadow は constructor の呼び出しにも対応しています。

class D(d: String) {
    init {
        throw IllegalArgumentException()
    }
}

以下のような Shadow を定義します

@Implements(D::class)
class ShadowD {
    @Implementation
    fun __constructor__(d: String) {
    }
}

これを使用すると通過します
また、これは変な使い方として、

class B(private val b: String)

このようなクラスを Java 側から使用するとして

public class C {
    public void c() {
        new B(null);
    }
}

null を入れてしまうと当たり前ですが java.lang.IllegalArgumentException: Parameter specified as non-null is null ... が発生します
が、以下のような Shadow を定義すると回避できます。

@Implements(B::class)
class ShadowB {
    @Implementation
    fun __constructor__(b: String?) {
    }
}

使い所はありません。

3 つ目

実際に呼び出されたかどうか知りたい時、引数を assert したいときなど。ここまで来たら PowerMock 使えばという話なんですが、可能です。

@Implements(AUtil::class)
class ShadowAUtil {
    var isPassed = false

    @Implementation
    fun a() {
        isPassed = true
    }
}

とりあえずこんな感じで定義しました。
Robolectric は object に対応する Shadow を受け取ることができます。

@Test
@Config(shadows = [ShadowAUtil::class])
fun a() {
    val util = Shadow.extract<ShadowAUtil>(AUtil)
    assert(!util.isPassed)
    A().a()
    assert(util.isPassed)
}

便利です。

Shadow は多くの場面で活躍しますが、一方で何をしているのかが慣れるまで分かりづらいという欠点がありますので、用法用量を守って使用すると良さそうです。

Mackerel Client 1.2 をリリースしました

この度、MackerelClient 1.2 をリリースしましたので、リリースノートに書かなかった諸々を書きます。
ちなみに 1.1 (2016年4月24日) 以来、2 年半ぶりの更新となります。

Get it on Google Play

反映されるのはもう少し後かもしれません
スクリーンショット等はそのうち更新します

github.com

リリース内容

  • アラート一覧にホスト名や監視設定の値を表示するようにした
  • アラートの取得、グラフの描画を早くした
  • デザイン修正
    • card 部分のスタイルをちょっと変えた
    • グラフ部分のスタイルをちょっと変えた
  • バグ修正
    • ドロワーの文字色がいつの間にか毒々しくなってたので直した (というか直った)
  • min sdk version: 19 -> 21
  • target sdk version 23 -> 28

内部的な変更

  • 264 files changed, 8403 insertions(+), 6158 deletions(-)
  • ktlint の導入
  • dagger の導入
  • databinding の導入
  • groupie の導入
  • parcelize の導入
  • AndroidX 対応
  • 諸々のバージョンアップ
    • kotlin 1.0.1 -> 1.2.71
    • realm 0.87.2 -> 5.1.0

大規模な設計の見直しが入っており、差分がかなり大きくなりました。
グラフの描画部分など、主機能については大きく書き直しています。
まだ完全ではありませんが、まぁまぁまともになってきています。


気が向いたら使ってみてください。