tokuhirom's blog

cmake で prove 使ってテストする

CMake 使ってテストするときでもよく訓練された Perl Monger は TAP でテスト結果を出力するものだ。 その結果を TAP で集計したいという時には以下のようにすればよろしい

cmake_minimum_required (VERSION 2.6)
project(nanoalarm)

enable_testing()

add_executable(01_simple t/01_simple.cc)
set_target_properties(01_simple PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/t/")

add_test(NAME prove COMMAND prove t)

t/01simple.cc から t/01simple が生成されるようになる。

t/01_simple.t に以下のように書けば完成。

q{t/01_simple} or die

prove の依存に 01_simple を入れて make test でビルドされるようにしたいのだが、どうしたらいいかよくわからなかった。

Created: 2015-05-22 08:57:33
Updated: 2015-05-22 08:57:33

Perl 5.20 以後 $& は遅くない

Perl では長らく $& を一度でもプログラム中に記載しているとあらゆる正規表現マッチが遅くなるというイシューが知られていた。

しかし perldoc perlvar してみると、以下のように記載されている。

In Perl 5.20.0 a new copy-on-write system was enabled by default, which
finally fixes all performance issues with these three variables, and makes
them safe to use anywhere.

Perl 5.20 以後では気にせずに $& を利用してもよいようだ。

という話が社内チャットで出た。

Created: 2015-05-21 16:35:07
Updated: 2015-05-21 16:35:07

Java の AST を解析できる javaparser がアツい!!!

https://github.com/javaparser/javaparser

javaparser は Java をパースして AST にしてくれるライブラリである。

この手のライブラリは数多あるのだが、ほとんどのものが Java 1.5 ぐらいでメンテナンスが止まっている。 実際このライブラリもメンテナンスが止まっていたのだが、Java 1.8 対応版とし開発が再開されたものだ。

このライブラリはパーサーライブラリであるから、文字列をパースして AST を構築してくれるというものになっている。

実際どのような AST が構築されるのかが気になるところなので、構築された AST をダンプできるツールを groovy で書いた。

#!/usr/bin/env groovy
@Grab('com.github.javaparser:javaparser-core:2.1.0')

import java.io.FileInputStream;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;

class Dumper {
  int indent;

  def Dumper() {
    this.indent = 0
  }

  def dumpit(CompilationUnit node) {
    dumpit((Node)node)
    print("\n")
  }
  def dumpit(Node node) {
    print("\n")
    print(" " * indent)
    print(node.getClass().getSimpleName() + ":" + asMap(node));

    this.indent++
    for (Node child: node.getChildrenNodes()) {
      dumpit(child)
    }
    this.indent--
  }

  public Map asMap(o) {
    o.class.declaredFields.findAll { it.modifiers == java.lang.reflect.Modifier.PRIVATE }.
      collectEntries { [it.name, o[it.name]] }
  }
}


def parse(InputStream is) {
  CompilationUnit cu = JavaParser.parse(is);
  new Dumper().dumpit(cu);
}

if (args.length == 0) {
  parse(System.in);
} else {
  FileInputStream fis = new FileInputStream(args[0]);
  try {
    parse(fis);
  } finally {
    fis.close();
  }
}

これを実行すると以下のような結果が得られる。

Java 8 で導入されたラムダ式もちゃんとパースできていることがわかる。

CompilationUnit:[pakage:null, imports:null, types:[public class A {

    public void x() {
        Arrays.stream().map( it->it * 2);
    }
}]]
 ClassOrInterfaceDeclaration:[interface_:false, typeParameters:null, extendsList:null, implementsList:null, javadocComment:null]
  MethodDeclaration:[modifiers:1, typeParameters:null, type:void, name:x, parameters:[], arrayCount:0, throws_:[], body:{
    Arrays.stream().map( it->it * 2);
}, isDefault:false, javadocComment:null]
   VoidType:[:]
   BlockStmt:[stmts:[Arrays.stream().map( it->it * 2);]]
    ExpressionStmt:[expr:Arrays.stream().map( it->it * 2)]
     MethodCallExpr:[scope:Arrays.stream(), typeArgs:null, name:map, args:[ it->it * 2]]
      MethodCallExpr:[scope:Arrays, typeArgs:null, name:stream, args:null]
       NameExpr:[name:Arrays]
      LambdaExpr:[parameters:[ it], parametersEnclosed:false, body:it * 2;]
       Parameter:[type:, isVarArgs:false]
        VariableDeclaratorId:[name:it, arrayCount:0]
        UnknownType:[:]
       ExpressionStmt:[expr:it * 2]
        BinaryExpr:[left:it, right:2, op:times]
         NameExpr:[name:it]
         IntegerLiteralExpr:[:]

さて、これで、どのあたりにどのノードがあって、どのようなアトリビュートを取得可能かが一目瞭然となったので、ビジターを書いて解析してみる。

javaparser では AST をトラバースするためのビジタークラスが用意されているので、これを継承し、処理したいノードを捕まえればよろしい。

例として、クラスとメソッドのリストを出力するコードを書いた。注意すべき点は特に無いが、super.visit を呼び忘れると下位ノードにビジターが回らないので注意。

#!/usr/bin/env groovy
@Grab('com.github.javaparser:javaparser-core:2.1.0')

import java.io.FileInputStream;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.body.*;
import com.github.javaparser.ast.expr.*;
import com.github.javaparser.ast.visitor.*;

class MethodVisitor extends VoidVisitorAdapter<Object> {
    @Override
    public void visit(final ClassOrInterfaceDeclaration n, final Object arg) {
        System.out.println(n.getName());
        super.visit(n, arg)
    }

    @Override
    public void visit(MethodDeclaration n, Object arg) {
        System.out.println('  ' + n.getName());
        super.visit(n, arg)
    }
}

def parse(InputStream is) {
  CompilationUnit cu = JavaParser.parse(is);
  new MethodVisitor().visit(cu, null);
}

if (args.length == 0) {
  parse(System.in);
} else {
  FileInputStream fis = new FileInputStream(args[0]);
  try {
    parse(fis);
  } finally {
    fis.close();
  }
}

さて、ここまで見てくると、javafmt 的なコマンドが欲しくなってくると思う。 javafmt 的なものを実装するにはどうすればよいか。

答えは「何もしなくてよい」である。Node クラスの .toString() メソッドが、そもそも適当にインデントつけてそれなりの見た目で 出力してくれる。Node#toString の実装は、com.github.javaparser.ast.visitor.DumpVisitor であり、これをベースに調整していけば、簡単に好みのフォーマッタを構築できることだろう。

@Grab('com.github.javaparser:javaparser-core:2.1.0')

import java.io.FileInputStream;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.CompilationUnit;

def parse(InputStream is) {
  CompilationUnit cu = JavaParser.parse(is);
  // prints the resulting compilation unit to default system output
  System.out.println(cu.toString());
}

if (args.length == 0) {
  parse(System.in);
} else {
  FileInputStream fis = new FileInputStream(args[1]);
  try {
    parse(fis);
  } finally {
    fis.close();
  }
}

AST は書き換え可能なので、AST をいじってから書き戻すとか、javapoet 的にコード生成に使うとか、いろいろできそうです。

以上、簡単ですが javaparser の紹介とさせていただきます。

Created: 2015-05-21 08:14:38
Updated: 2015-05-21 08:14:38

supplementary groups をサポートする setuidgid であるところの setusergroups.c 書いた

https://github.com/tokuhirom/setusergroups/

supplementary groups をサポートした setuidgid が欲しいケースがある。 というのもあると思うけど、とにかく依存がない setuidgid が欲しい、みたいなケースもあると思う。

1ファイルの C コードで動くような。

なんかそういうユースケースがそれなりにあるような気がしていたので、まあ書いた。

元となっているのは kazuho さんの perl code で、 https://gist.github.com/kazuho/6181648 です。 これは、簡潔な perl code でよいのだが、Unix::Groups ってやつが rpm package になってなかったりしてめんどかった。

setuidgid は daemontools のいろんなヘッダに依存しててめんどかった。

まあ、そんなところです。

Created: 2015-05-20 15:36:31
Updated: 2015-05-20 15:36:31

Exclude logback.xml from jars with Gradle

開発時用の logback 設定を src/main/resources/logback.xml に入れているとして、その時に logback.xml をどうやって jar に含めないかという話し。

結論から言うと、以下のようにすればよいという話し。

jar {
    exclude('logback.xml')
}

なお、maven の場合はこちら: http://www.mkyong.com/maven/maven-exclude-logback-xml-in-jar-file/

Created: 2015-05-20 13:51:19
Updated: 2015-05-20 13:51:19

OSX で getgrent(3) を呼ぶと、同じエントリが二度でるように見える

有効なグループのリストを得るには、getgrent(3) を呼べばいいが、この関数 OSX だと奇妙な動きをする。

perl -E 'while (@e=getgrent) { say "@e" }'  | sort| uniq -c

などとすると、複数回同じエントリが出ているように見える。

これはおそらく OSX 本来の directory service のエントリと、/etc/group の両方が出ている感じになっているのかな、という感じ。

いずれにせよ、同じものが二度でるので、気を付ける必要がある。

どっちを優先すべきか、とかそういう知見は特にないです。識者の方、なにか情報あったら教えて欲しい。

Created: 2015-05-19 18:42:59
Updated: 2015-05-19 18:42:59

Linux 上で5秒で chroot jail を使えるコマンド「jailing」が凄い!

https://github.com/kazuho/jailing/

kazuhoさんがやってくれました。 ずいぶん前からlinuxでchroot jailを作るのに簡単な方法ないかなーと思ってました。個人的にはsystemd-spawnというのを使ってたのですが、幾らか気に入らない所があったり構築に時間がかかったり、ディスク容量を消費していました。簡単に使えて、ディスク容量を消費しないやつがほしいなーっておもってたんです。

とあるIRCで昨日、kazuhoさんと「ほしいですよねー」という話から始まって、github にある docker とかも物色しながら「いいのないねー」とか言ってたらkazuhoさんが「もすこし綺麗に書けそう」って言い出して朝から本格的に書き始めてついさっき出来上がりました。速いw

名前はjailing とても小さく、実装コードだと100数十ステップ程です。しかもperlファイルだけなので管理が楽です。

試しに chroot jail を作ってみました。実行コマンドはこんなかんじ。

mkdir test
sudo perl jailing --root=$PWD/test bash

これだけで、簡単に chroot 環境に入れるから最高ですね。

こんなに短いコードでchroot jailが作れる! FreeBSDに慣れた人ならイメージ沸くかと思います。すばらしい! こういうのが欲しかったんです。 ただまだ出来上がったばっかりですしバグはあるかもしれません。また高機能にするつもりもないでしょうから使用目的を選ぶのが先決かと思います。 ライセンスはMITとの事なので安心です。

ありがたや、ありがたや。

https://github.com/kazuho/jailing/

Created: 2015-05-13 16:46:54
Updated: 2015-05-13 16:46:54

Paths.get(uri) について

Paths.get(uri) で Path オブジェクトを取得可能だが、これはどんな URI をサポートしているのかという話。

結論からいうと file:// 以外での利用は微妙。

file:// 以外の場合、インストール済みのファイルシステムプロバイダの中からサポートしているものを利用することになる。サポートしてるものがなければなにも起きない。

FileSystemProvider.installedProviders() でインストール済みのプロバイダは取得できる。 特に何も jar を読んでいなければ、これは以下のようなものが入っている。

sun.nio.fs.MacOSXFileSystemProvider@4f2410ac
com.sun.nio.zipfs.ZipFileSystemProvider@722c41f4

前者は普通の file system からの読み込みに使うやつで、後者は zip からの読み込み。 zip からの読み込みってことは、zip ファイルの読み込みつまり jar からの読み込みに対応しているのかな?と思いきやこれがそうではない。

jar:file:/Users/tokuhirom/.m2/repository/junit/junit/4.11/junit-4.11.jar!/LICENSE.txt

のような形式をサポートしていることは確かなのだが、実際には ZipFileSystemProvider#newFileSystem であらかじめ jar file を登録しておかないとダメなのでまったく使い物にはならない。

FileSystemProvider の static にデフォルトで導入されている provider はグローバル変数なので、ここにわざわざ導入すると removeFileSystem でまたはずさなくてはならず、あまりやりたくない。

ZipFileSystemProvider 以外でも、サービスローダで登録することによって任意の URL を読み込み可能ではあるが、FileSystemProvider というグローバルなオブジェクトに対してサービスローダでガンガン追加していくというのもあんまり好ましくないように思う。

といったところで、個人的には Paths.get(uri)file:// 以外では利用しないほうが良いように思う。

Created: 2015-05-13 16:40:08
Updated: 2015-05-09 07:53:28

Files.lines() の返り値は try-with-resources で閉じる

Files.lines は閉じていない BufferedReader を Stream に詰め込んで返すので、try-with-resources で閉じる必要がある。

    The returned stream encapsulates a Reader. If timely disposal of file system resources is required, the try-with-resources construct should be used to ensure that the stream's close method is invoked after the stream operations are completed.

https://docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html#lines-java.nio.file.Path-java.nio.charset.Charset-

ref. http://mike-neck.hatenadiary.com/entry/2015/04/12/210000

Created: 2015-05-13 16:40:08
Updated: 2015-05-09 07:53:28

InputStream から行単位でファイルを読み出したい時

Java 8 では、BufferedReader#lines が定義されているのでこれを利用する。 BufferedReader#lines は、Stream を返すので、そのまま stream 処理を書けば良い。

try (final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
    bufferedReader.lines().map(String::trim)
        .forEach(System.out::println);
}

ごちゃごちゃと記述が必要だが、通常だとグローバル変数でやっていたり暗黙のうちに処理されている部分を手で書いているだけなのでたいしたもんだいではない。

文字コードは VM のデフォルト文字列を利用すれば記述を省略することが可能だが、グローバル変数に頼ったコーディングは後々身を滅ぼすことであろう。

見た感じ、InputStreamReader#close を呼んでいる部分が無くて不安になるかもしれないが、BufferedReader#close が呼んでいるので問題ない。

このあたり駆使していけば、Java 8 でも意外とスクリプト書きやすくなってくる。

Created: 2015-05-13 16:40:08
Updated: 2015-05-09 07:53:28