type holyshared = Engineer<mixed>

PHP、Hack、Ruby、OCaml、Javascript周りの技術ブログ

jbuilderでビルドに必要なファイルを生成する

jbuilderでビルドに必要なファイルを生成する方法を調べました。
なんで調べたかというと、atdgenjsonのパーサーのコードを出力したかったからです。

パッケージのインストール

jbuilderatdgenをインストールします。

opam install atdgen jbuilder

ruleをjbuildファイルに追加する

次のようなJSONスキーマファイルがある場合、jbuildファイルにruleを追加するだけでOKです。
スキーマファイルはuser.atdします。(拡張子はなんでもいいはず)

スキーマファイル

type user = {
  name: string;
  description: string;
}

jbuildファイル

targetsに生成されるファイルを指定して、depsに依存しているファイルを指定します。
また、actionで実行するコマンドを指定します。

パラメータ

パラメータ 説明
\${@} targetsで指定したファイルリスト
\${<} depsで指定したファイルリスト
\${bin:} プログラム名、PATHなどからプログラムを検索する

他にもあるので、variables-expansionを参照してください。

JSONパーサーの生成

JSONパーサーを生成するruleは下記の通りです。

(rule (
  (targets (user_j.ml user_j.mli))
  (deps (user.atd))
  (action (run ${bin:atdgen} -j ${<}))
))

実際に発行されるコマンドは下記の通りです。

atdgen -j user.atd 

型の定義ファイルの生成

型の定義ファイルの生成するruleは下記の通りです。

(rule (
  (targets (user_t.ml user_t.mli))
  (deps (user.atd))
  (action (run ${bin:atdgen} -t ${<}))
))

実際に発行されるコマンドは下記の通りです。

atdgen -t user.atd 

jbuildファイルのlibrariesを追加する

jbuildファイルのlibrary設定のlibrariesにatdgenを指定します。

(library (
  (public_name user)
  (name user)
  (libraries (atdgen))
))

また、user.mlファイルには生成されたコードを利用するようにしてみます。
User_tは生成される予定のモジュールです。

let make ~name ~description =
  User_t.({ name=name; description=description })

ビルドできるか試す

ビルドコマンドでビルドできるか試します。

jbuilder build

Unbound moduleなどのコンパイルエラーが出なければ問題ないはずです。
生成されたコードは_buildのサブディレクトリにオブジェクトファイルなどと一緒に出力されるようで、 コードの管理からは外れていて問題ないようです。

実際に試したコードは下記にあります。
example_jbuilder_with_atdgen

typesafety_cliのビルドツールの変更計画

CI上でテスト前の型チェックのエラーをGitHubのPRのコメントとして投稿するツールtypesafety-cliのビルドツールを変えようとしています。

具体的にはOASISからjbuilderに移行しようとしています。
開発当初はOASISで良かったのですが、以下の理由で辛く感じるようになってきました。

  1. 内部的なライブラリのビルド設定を1つのファイルに書く必要がある。
  2. 新しくライブラリを追加する際に_oasisを別途編集しなければならない。
  3. _oasisメタデータから、opam用のファイルをopam2oasisで生成するが面倒である。
  4. _oasisと.opamファイルの内容重複しているものが多い。

jbuilderはファイルをビルド単位で分割できる

下記の2つに関してjbuilderはファイルを分割できます。
またライブラリを分割したくなっても、ビルド用のファイルを追加するだけなので、変更の差分が基本的に追加だけになりそうである。

  1. 内部的なライブラリのビルド設定を1つのファイルに書く必要がある。
  2. 新しくライブラリを追加する際に_oasisを別途編集しなければならない。

.opamファイル一つでいい

OASISの場合、_oasisファイルと.opamファイルが必要になりますが、jbuilderの場合は.opamファイルだけで良いので、 リリース作業もOASISと比べて楽になりそうである。

自動化すればいんですが、あんまり自動化のための作業を増やしくたくないです。

  1. _oasisメタデータから、opam用のファイルをopam2oasisで生成するのが面倒である。
  2. _oasisと.opamファイルの内容重複しているものが多い。

OCamlのビルドツール

OCamlのビルドツールはjbuilderjengaocamlbuildOMakeOASISなどいろいろありますが、 これ使っておけばOK見たいなのがあるといいですね。

今だとOMakejbuilderになりそうですが。
OASIS使用しているプロジェクト減っている気がします。

Hackのコード自動生成できるようにした

毎回エディタからファイル作成したり、名前空間指定したりするのが面倒なので、コードを生成できるようにした。
コードの生成というか、ファイル生成に近いです。

github.com

使い方

名前空間ディレクトリのペアに対応させるジェネレータをマッピングする設定ファイルを書いて、 オートロードで読み込めるようにするだけで、あとはコマンド実行するだけでクラスファイルを出力してくれる。

設定ファイルを用意する

<?hh //strict

namespace MyPackage\Generators;

use HHPack\Codegen\Cli\{ GeneratorProvider };
use HHPack\Codegen\HackUnit\{ TestClassGenerator };
use HHPack\Codegen\Project\{ PackageClassGenerator };
use function HHPack\Codegen\Cli\{ namespace_of, library, library_test };

final class Generators implements GeneratorProvider {

  // Your package namespace
  const string PACKAGE_NAMESPACE = 'MyPackage';
  const string PACKAGE_TEST_NAMESPACE = 'MyPackage\Test';

  public function generators(): Iterator<Pair<GenerateType, ClassFileGenerator>> {
    // Link package namespace to generator
    yield library(
      namespace_of(static::PACKAGE_NAMESPACE, 'src')
        ->map(PackageClassGenerator::class));

    // Link package test namespace to generator
    yield library_test(
      namespace_of(static::PACKAGE_TEST_NAMESPACE, 'test')
        ->map(TestClassGenerator::class));
  }
}

hh_autoload.jsonに設定を追加

次にhh_autoload.jsonのdevRootsにパスを設定します。
下記の設定だと、configディレクトリが読みこみ対象になります。

{
  "roots": "src",
  "devRoots": "config"
}

hhvm_autoloadが必要なので、インストールしてください。

github.com

これだけでコードが生成できるようになります。

コードの生成

パッケージのクラスを生成する

生成するタイプとクラス名を指定すると、設定ファイルで指定したディレクトリにファイルを生成できます。
ファイルにはあらかじめ名前空間が設定されるので、指定する手間が省けます。

クラスファイル

libを指定すると、クラスファイルを生成します。

vendor/bin/codegen lib Foo\LibClass

テストクラスファイル

testを指定すると、テスト用のクラスファイルを生成します。

vendor/bin/codegen test Foo\LibClassTest

独自ジェネレータ

ClassFileGeneratableインターフェースを実装すれば独自のジェネレータを定義できます。
下記は、プレーンなクラスファイルを生成するジェネレータです。

どういうファイルが生成可能かはhack-codegenを参考にしてください。
shapeを定義したり、trait定義できたり、結構いろんなものが生成できます。

github.com

<?hh //strict

namespace MyPackage\Generators;

use HHPack\Codegen\{GenerateClass, ClassFileGeneratable};
use Facebook\HackCodegen\{ICodegenFactory, CodegenFile, CodegenClass};

final class CustomGenerator implements ClassFileGeneratable {

  public function __construct(private ICodegenFactory $cg) {}

  public static function from(ICodegenFactory $factory): this {
    return new self($factory);
  }

  public function generate(GenerateClass $class): CodegenFile {
    // ファイルを生成するコードを書く
    return $this->cg
      ->codegenFile($class->fileName())
      ->setIsStrict(true)
      ->setNamespace($class->belongsNamespace());
  }

  private function classOf(string $className): CodegenClass {
    return $this->cg->codegenClass($className)->setIsFinal(true);
  }
}

HackでDBのマイグレーションツールを作った

HackでDBのマイグレーションツールを作ってみた。
データベースの作成、削除、スキーマ変更の適用、適用の取り消しが一通りできます。

現在のバージョンでサポートしているRDBMSMySQLのみです。
また、マイグレーションSQLベースです、DSLはサポートしていません。

github.com

使い方

まずはじめに、JSON形式の設定ファイルをルートディレクトリのconfigの下に、database.jsonという名前でおきます。 開発環境、ステージング環境とかは自分で追加してください。

{
  "type": "sql",
  "path": "db/migrate",
  "enviroments": {
    "development": {
      "host": "localhost",
      "port": 3306,
      "name": "migrate",
      "user": { "ENV": "DB_USERNAME" },
      "password": { "ENV": "DB_PASSWORD" }
    }
  }
}

データベースの作成/削除する

create/dropコマンドで、データベースの作成、削除ができます。

データベースの作成

vendor/bin/migrate create

データベースの削除

vendor/bin/migrate drop

マイグレーションを適用する

genコマンドでマイグレーションファイルを作成して、適用する変更をSQLで書いたら、upコマンドで適用できます。 –toオプションで指定したところまで、変更を適用できます。

マイグレーションファイルの生成

vendor/bin/migrate gen create-users

マイグレーションの適用

vendor/bin/migrate up

or

vendor/bin/migrate up --to=20150824010439-create-users

マイグレーションの変更を戻す

downコマンドで適用したマイグレーションを適用前までに戻すことができます。 resetコマンドはすべての変更を元に戻します。

resetコマンドですべて戻らない場合、SQLファイルに漏れがないかを確認してください。

vendor/bin/migrate down 20150824010439-create-users
vendor/bin/migrate reset

今後について

–dry-runオプションを追加して、dry runできるようにしたり、DB作成時にCHARACTER SET、COLLATEを指定できるようにする予定です。
PostgreSQLのサポートは設計プランがまだできてないので、後回しになると思います。

プロフェッショナルSSL/TLSを読み終わった

ラムダノートさんから出ている、プロフェッショナルSSL/TLSを読み終わりました。 SSL/TLSを理解するのに、基本的に必要な情報が網羅されていて、とてもいい本だと思います。

プロフェッショナルSSL/TLS(紙書籍+電子書籍)www.lambdanote.com

よかった点

  1. 鍵交換、暗号化アルゴリズムについての解説があった。

    一度調べたものの、あまり自信がなかったので、再度理解するのに役に立ちました。
    これはXXXだから、鍵交換アルゴリズムはこれで、暗号化方式はこれのはず、ぐらいは記憶に定着した感があります。

    ある程度知っておかないと、CipherSuiteの設定確認する時、大変だと思います。

  2. MITM(中間者攻撃)の理解が進んだ、また実際にあった攻撃の事例が載っていた。

    Lucky13、POODLEなどの攻撃方法、受動的か能動的かで攻撃方法が異なるなど。
    これも、どの時期にこういう攻撃があったなどの事実関係で載っているので、歴史を学べてよかったです。

  3. CA(認証局)の事例が載っていた。

    CAがドメインの証明書を発行する際に、ドメインのチェックをこなっておらず、不正な証明書が取得できていた事例が載っており、発覚してからCAがどういう末路を辿るのかがよく理解できました。

    また、CAの調査も必要に感じました、普段あんまり気にしてなかったので、過去問題が起きてないかとかそういう調査するのはありだと思います。

  4. 公開鍵ピンニング(HPKP)、HSTSなどの防御方法

    攻撃から守る、または攻撃を受けにくくするにはどうするかも載っていました。

    特にHSTSあたりは、やりやすいので、HTTPS対応しないとけない人は、参考にするといいのではと思いました。
    max-ageを小さめにして、徐々に長くしていくと、リカバリしやすいなど、適用する際の参考手順が載っています。

まとめ

価格はちょっと高いですが、内容的には満足でした。
紙の方で読んで、会社の本棚に置き、電子版で読み直すとかががいいと思います。 3回以上読んだ方がいい内容です。

Hackで書いた自作のオプションパーサーをリファクタリングした

結構昔にHackでオプションパーサー書いていたのですが、しばらく面倒でメンテナンスを放置していました。 それをHHVM-3.21.0がリリースされてたタイミングで、メンテナンスしている他のパッケージと一緒にリファクタリングしました。

使いかたはこんな感じで、shortオプション、longオプション、オプションの説明、コールバック用のlambdaを指定していきます。 あとは使い方の設定を指定するして、parseメソッドに引数を指定するだけです。

<?hh //partial

use HHPack\Getopt as cli;
use HHPack\Getopt\App\{ ArgumentParser };

function main() : void {
    $help = false;
    $version = false;
    $fileName = 'test';

    $argParser = cli\app('example', '1.0.0')
        ->description("This cli application is example.\n\n")
        ->usage("  {app.name} [OPTIONS]\n\n")
        ->options([
            cli\on(['-h', '--help'], 'display help message', () ==> {
                $help = true;
            }),
            cli\on(['-v', '--version'], 'display version', () ==> {
                $version = true;
            }),
            cli\take_on(['-n', '--name'], 'file name', ($name) ==> {
                $fileName = $name;
            })
        ]);

    $args = $argParser->parse($argv);

    if ($help) {
        echo 'help on', PHP_EOL;
    }

    if ($version) {
        echo 'version on', PHP_EOL;
    }

    if ($fileName !== 'test') {
        echo 'name = ', $fileName, PHP_EOL;
    }
}
main();

CLIの場合、エントリポイントのソースはpartialモードを指定しないといけないので、(strictの場合はTOPレベルに実行可能なコードを書けない為)適当な関数にラップして、実行するのがいいと思います。

そうすると、型のチェックもやってくれるようになります。(ドキュメントに書いてある)

自分が真面目に書く場合は、strictモードに寄せたいので、アプリケーションのクラス書いて、runメソッドを実行みたいな薄い実装にすると思います。

https://github.com/hhpack/getopt/blob/master/example/app.hh

HHVM-3.21.0で色々チェックしてたけど、タイプチェッカーの性能が上がってて、古いバージョンでエラー出てなかった奴が出るようになったので、やっぱりバージョン上がるたびにチェックした方が良さそうです。

Hackの型のチェックのエラー内容をレビューコメントとして投稿できるようにした

Hackの型チェックをCI上で行っていたのですが、エラー内容を見るのにCI環境側までいちいち見に行かないといけないので、OCamlでレビューコメントとして投稿できるtypesafety-cliを作りました。

レビューコメントは下記のような感じになります。
https://github.com/hhpack/hackunit-docker-example/pull/3

このプログラムをHHVM + Composerと一緒にパッケージしたDockerイメージを作って、TravisCIでテストの自動化を行っています。(メンテナンスしているパッケージはだいたい置き換え済み)

Docker化したことにより、HHVMのリリースにもすぐに追従できるようになりました。

作った理由

もともと下記のものはHackで作っていました。

  • typechecker-client - hh_clientを扱えるクライアントパッケージ
  • typesafety - エラーメッセージをわかりやすい形に整形して出力するCLIパッケージ

これを機能拡張すればいいのですが、Hackで実装している為、HHVMのバージョンが上がるたびに「これ今動くんかな?」と気にする必要があるので、しばらくメンテナンス放置してると動かなくなる可能性があります。

メンテナとしては、ある程度放置していても動いてくれる方が楽なので、今回の場合はHackをやめて別実装にしました。

OCamlにした理由

最近、趣味でOCamlでコードを書いているのですが、書いていて気持ちが楽なのでOCamlにしています。
基本的にツール周りのコードは年々「雑に書きたい、しかし雑に書いてもそこそこ動いて欲しい」、と思うようになっており、OCamlはその辺があうと思っています。

OOPな言語で書くことが多くて、設計はすぐに思いつくのですが、Rubyですらも書くのはだるいと思ってしまうことが最近多いので、特にクラスの設計とか気にせず、コードを書き始められるのがOCamlのいいところですね。