ひだまりソケットは壊れない

ソフトウェア開発に関する話を書きます。 最近は主に Android アプリ、Windows アプリ (UWP アプリ)、Java 関係です。

まじめなことを書くつもりでやっています。 適当なことは 「一角獣は夜に啼く」 に書いています。

React Native の Universal Windows Platform サポートを試してみた

React Native が Universal Windows Platform をサポートするという発表が、2016 年 4 月の F8 にて MicrosoftFacebook により行われました。 すなわち、React Native を使って Universal Windows Platform (UWP) アプリを開発できるようになります。

blogs.windows.com

上のブログ記事の公開時点では、React Native の本家とは独立した Git リポジトリに UWP サポートが入っているものが公開されているのみで、本家の方には UWP サポートは入っていません。 今後、React Native プロジェクトの本家に UWP サポートが追加されていくようです。

仕組み

React Native の仕組みが、冒頭で紹介したブログ記事で簡単に紹介されています。

Under the hood, React Native enables app builders to declare their UI using JavaScript and React, and the framework translates the React DOM from JavaScript into method calls to view managers on the native platform, allowing developers to proxy direct calls to native modules through JavaScript function invocations. In the case of React Native on UWP, the view managers and native modules are implemented in C#, and the view managers instantiate and operate on XAML elements. We use Chakra for the JavaScript runtime, which can be consumed by any UWP app without any additional binaries being added to the app package.

React Native on the Universal Windows Platform | Building Apps for Windows

React Native でアプリ開発をする場合、開発者は JavaScript と React で UI を定義し、React Native のフレームワークが React DOM をもとにネイティブプラットフォームの View マネージャのメソッドコールを行ってネイティブの UI 要素を生成します。 また、ネイティブモジュールのメソッド呼び出しを JS から行うこともできます。

UWP での React Native の場合は、ネイティブの UI マネージャやモジュールは C# で実装されており、それらを JS から使うことができます。 JS 実行環境には Chakra が使われるため、アプリに JS 実行用の追加のバイナリを含める必要はないとのことです。

試してみた

まだ React Native 本家に UWP サポートは入っていませんが、UWP サポートが入っている Git リポジトリが公開されているので試すことができます。 というわけでリポジトリに入っているデモプロジェクトを動かすところまでやってみました。

リポジトリ

下記のリポジトリです。

UWP サポートに関するドキュメントは、今のところ次のものしかなさそうです。

試し方

上のドキュメントに書かれている通りに進めれば試せます。 簡単に流れを書くと次のような感じです。

  1. まずは Node.js や Python などの必要なツールをインストールする。
  2. リポジトリを Git で clone してくる。
  3. プロジェクトのディレクトリで npm install
  4. プロジェクトのディレクトリで node local-cli\cli.js start
  5. Visual Studio 2015 で ReactWindows\ReactNative.sln を開き、Playground プロジェクトを実行するとアプリが起動する。

Visual Studio 側で開かれるプロジェクトは、React Native と UWP の橋渡しのための機能群やアプリのガワの部分だけです。 アプリ内部の JS で書かれている部分はプロジェクトの Examples\UIExplorer\UIExplorerApp.windows というファイル (Playground プロジェクトの AppReactPage.cs ファイルで指定されている) で、JS 部分は node local-cli\cli.js start で起動されたプロセスがアプリのガワの方に渡すっぽいです。 (詳しいことはわかってない。)

アプリが起動すると、最初に次のような画面が開きます。

f:id:nobuoka:20160504020228p:plain

しばらくすると次のような表示になるはずです。

f:id:nobuoka:20160504020423p:plain

JS ファイルを変更したら、Ctrl + R で更新できます *1

とりあえずデバッグビルドで動かすことは簡単にできましたが、リリースビルドの作り方とかはよくわかってないです。

雑感

UWP アプリ開発においては、もともと JS + HTML + CSS での開発がサポートされています。 Windows Runtime API も基本的には JS からそのまま使用できます。 よって、単に 「JS + React でアプリ開発ができる」 という点では、React Native を使った場合ももともとの JS サポートも大差はありません。

もともとの JS サポートと React Native を使った場合とを比べて一番違ってくるのは、UI 周りかなと思います。 前者は HTML + CSS で UI を記述しますが、後者は本来であれば XAML で記述される UI を JS 側から操作する、ということになります。 Windows 8 や 8.1 の Windows ストアアプリの頃は、C# + XAML での UI コンポーネント相当のものが WinJS で提供されていましたが、Windows 10 の UWP アプリでは、C# + XAML では使えるカレンダーなどのコンポーネントが WinJS では提供されていないなどの差が出てきました。 同じ UI コンポーネントを異なる環境用に提供してメンテナンスするのはコストが大きいのかもしれません。

WinJS が C# + XAML の UI コンポーネントを全てカバーしきれていないという状況の中で、React Native は 「慣れている JS + React でアプリ開発したいけどネイティブの UI コンポーネントを使いたい」 という人の有力な選択肢になるかなと思います。 もちろん、もともとの JS + HTML + CSS でのアプリ開発が React Native に劣っているというわけではなく、HTML や CSS に慣れている人にとっては JS + HTML + CSS の方が view 周りをごりごり書いていくことができたり、web 側の UI ライブラリをそのまま使えたりできるといった利点もありますので、どちらを選ぶかは状況次第という感じになりそうです。

ともかく React Native で UWP アプリ開発ができるようになるのは喜ばしいことですね!

*1:文字入力用のフォームにフォーカスが当たっているときでないと反応しませんでした。

ISO 8601 DateFormat 1.0.0 (Java 向けライブラリ) をリリースしました

2016 年 5 月 3 日に ISO 8601 DateFormat の 1.0.0 をリリースしました。 ISO 8601 形式 (もしくは RFC 3339W3C-DTF 形式) の日付時刻文字列のパースとフォーマットのための DateFormat のサブクラスを提供するライブラリです。

Bintray の JCenter リポジトリで公開しています。

現在は、時刻オフセット付きの拡張形式の日付時刻文字列のみをサポートしています。

  • 2016-01-01T00:30:21Z
  • 2016-01-01T09:30:21+09:00

動機

Java では、ISO 8601 形式の日付時刻文字列をパースする方法がいろいろあります。 例えば、SimpleDateFormat で “yyyy-MM-dd'T'HH:mm:ssX” というフォーマットを使う (Java SE 7 以降) とか、Date and Time API (JSR 310; Java SE 8 以降) を使うとか、Joda-Time ライブラリを使うとか、Apache Commons Lang ライブラリを使うなどです。

しかし、Java SE 6 環境や Android プラットフォームでは、大きなライブラリを導入することなく ISO 8601 形式の文字列をパースすることが簡単ではありませんでした。 そのため、このようなライブラリを公開しました。

使い方

Gradle を使っている場合、以下のようにリポジトリと依存を追加します。

repositories {
    jcenter()
}
dependencies {
    compile 'info.vividcode:date-format-iso8601:1.0.0'
}

あとは、以下のように使うだけです。

import info.vividcode.time.iso8601.Iso8601ExtendedOffsetDateTimeFormat;

DateFormat f = new Iso8601ExtendedOffsetDateTimeFormat();
Date d1 = f.parse("1970-01-01T00:00:00Z");
Date d2 = f.parse("1970-01-01T09:00:00+09:00");

最新の情報はリポジトリの README ファイルを見てください。

ISO 8601 関連の情報

小さなライブラリが欲しいのでなければ、Joda-Time や Apache Commons Lang、JSR 310 のバックポートライブラリなどを使うのが良いかもしれません。

Rust 入門 #2 : 所有権システムと可変性

Rust 入門してる #1」 に続き、ぼちぼちと Rust を学んでます。 今日は所有権システムと可変性についてのメモです。 4 月 14 日に Rust 1.8 がリリースされたので、バージョン 1.8 のドキュメントを読んでます。 *1

所有権システムについては下記の 3 ページを読みました。

可変性については次のページ。

基本的にはそんなに難しいことは書かれてないですが、後で説明される内容が出てきたり説明があまりうまくなかったりで理解が難しい感じがしますね。 まあざっくりと理解して読み進めていけば良さそうです。

所有権システム (Ownership system)

  • Rust の最も独特で感動的な機能であり、Rust 開発者はこれに精通すべきとのこと。
  • メモリ安全がこの機能の大きな目的である。
  • コンセプトは次の 3 つ。
    • 所有権 (ownership)
    • 借用 (borrowing) とそれに関連する機能である 「参照 (reference)」
    • 生存期間 (lifetimes)

Meta

  • Rust は安全性と速度に焦点を当てている。
  • 所有権システムはゼロコストの抽象化の最たる例で、コンパイル時に解析することで実行時にコストがかからないようになっている。
  • 一方で、このシステムには学習曲線というコストがかかる。 実際の実装と開発者のメンタルモデルが違っていて、開発者が正しいと思っているコードがコンパイラに弾かれるということが最初のうちは多いかもしれない。 経験を積むことで徐々にそれは減っていく。

所有権 (Ownership)

  • 変数束縛 (variable binding) には、それらの結合先の 「所有権を持っている」 かどうかを表すプロパティがある。
  • 束縛がスコープを抜けると、Rust は束縛されているリソースを解放するであろう。
fn foo() {
    let v = vec![1, 2, 3];
}

上の例だと、新しい vector がスタック上に作られ、各要素のためのスペースはヒープ上に生成される。 v がスコープから外れると、Rust は vector に関するリソースを解放する (ヒープに割り当てられたメモリも)。

Move semantics
  • 任意のリソースに対して所有権を持つ束縛は、プログラム中に 1 つしか存在できない?
  • あるリソースに対して所有権を持つ束縛を別の束縛に割り当てた場合、所有権は新しい束縛に移り、古い束縛経由でリソースにアクセスしようとしてもコンパイルエラーが発生する。
fn foo() {
    let v = vec![1, 2, 3];
    let v2 = v; // 所有権が v から v2 に移る。
    // v 経由で vector の操作をしようとしてもコンパイルエラーになる。
}

上は別の変数束縛への割り当ての例だが、関数の引数として渡す場合もやはり所有権は移る。

Copy
  • 上で見たように、ある束縛の値を別の束縛に割り当てると普通は所有権が移る。 そのため、古い束縛を使おうとするとコンパイルエラーになる。
  • 所有権が移らないような型を定義する場合は、Copy トレイトを実装することになるらしい。
    • プリミティブ型は Copy トレイトを実装している。

参照と借用 (References and Borrowing)

単純に引数で所有権を受け取る関数を書いた場合、呼び出し元に所有権を返すために戻り値で所有権を返す必要がある。 それはめっちゃ面倒なので、参照を受け取るようにして回避する。

参照と借用

参照はリソースを所有するのではなく、所有権を借りる (借用)。 スコープを抜けたときにはリソースを解放するのではなく所有権を返す。

2 つの参照

2 種類の参照がある。

  • (通常の) 参照
    • &T
  • ミュータブルな参照
    • &mut T

通常の参照とミュータブルな参照は同時に存在はできない。 通常の参照は同時に複数個存在できるが、ミュータブルな参照は 1 つのみしか存在できない。

参照を使う際には、前置の単項演算子 * を使ってデリファレンスすればよい。 ただし、自動でのデリファレンス機能もあるっぽくて、* を使って明示的にデリファレンスしなくてもそのまま使える状況が多いっぽい。 (まだよくわかってない。)

生存期間 (Lifetimes)

  • 生存期間とは、参照が有効であるスコープを記述するもの。
  • 全ての参照は生存期間を持つが、一般的には明示しなくても良い。 (省略可能。)
  • 特別な生存期間として 「static」 がある。 これはプログラム全体を表す生存期間である。
  • 生存期間を指定する場合、参照を表す型の & の後ろに 'lifetime を付ける。
    • 例えば fn calc(val: &'static i32) { ... } という感じ。
  • ジェネリックパラメータとして生存期間を宣言することもできる。
    • 例えば fn calc<'a>(val: &'a i32) { ... } という感じ。
生存期間の省略 (lifetime elision)
  • Rust ではシグネチャでの型推論は基本的に禁止されているが、関数のシグネチャにおいては 「生存期間の省略」 が適用される。
  • 関数の引数に関連する生存期間を入力生存期間 (input lifetime)、関数の戻り値に関連する生存期間を出力生存期間 (output lifetime) という。
  • 「生存期間の省略」 のルールは次のとおり。
    • 関数の引数のうち、省略された生存期間は全て異なる生存期間のパラメータを持つ。
    • 入力生存期間が 1 つのみの場合 (省略されているかどうかに関わらない)、返り値に関連する生存期間のうち省略されているものは全て、入力生存期間と同じ生存期間となる。
    • 入力生存期間が複数ある場合でも、引数の 1 つが &self&mut self の場合は、返り値に関連する生存期間のうち省略されているものは全て、self の生存期間と同じ生存期間となる。 (これはまだよくわかってない。)
    • 上記以外は全てエラーとなる。

可変性 (Mutability)

  • Rust で 「イミュータブル」 というのは、必ずしも 「変更できない」 ことを意味するわけではない。
  • 通常の参照と mut 参照が Rust における不可変性の基礎となっている。
  • すなわち、「イミュータブル」 であれば、複数の参照が存在しても安全であるということである。

*1:前回は 1.7 のドキュメントを読んでました。 基本的には変わりないはずです。

Rust 入門してる #1

最近 Rust を学び始めました。 まだちまちま構文を追っているところですが、ドキュメントがしっかりしていて学びやすい印象です。 (手元では、現在の最新の安定板である Rust 1.7 を使っています。)

Web 上で実行できる環境も提供されています。

進捗 (4.1 節から 4.6 節まで)

とりあえず変数結合 (variable bindings) から初めて、関数やプリミティブ型、コメント、if やループといった制御構文あたりをざっと見てみました。

まだここら辺はそんなに特徴的な部分はないですね。 char 型の保持する値が Unicode スカラ値である、とか、例外により処理が戻らない関数 (diverging functions) のための戻り値の型は ! で宣言、といったあたりが個人的にはちょっと新鮮でした。

次の節は 「Ownership」 だったり、その次は 「References and Borrowing」 だったりするので、そのあたりから Rust っぽさが出てきそうです。

サンプルコード

各節の内容をコードに落とし込んだものです。

fn main() {
    hello_variable_bindings();
    hello_functions();
    hello_primitive_types();
    hello_comments();
    hello_if();
    hello_loops();
}

// 4.1 Variable bindings
// See : https://doc.rust-lang.org/book/variable-bindings.html
fn hello_variable_bindings() {
    println!("=== 4.1 Variable bindings ===");

    // `let` で変数結合 (variable binding) の割り当てを行う。
    let a = 100;
    println!("`a` is {}", a);

    // `let` 式の左辺にはパターンも使える。
    let (b, c) = (1, 2);
    println!("`b` is {}, c is {}", b, c);

    // コロンの後に型を書いて型注釈 (type annotation) も可能。
    let d: i32 = 5;
    println!("`d` is {}", d);

    // 通常は結合は不変 (immutable) だが、`mut` で可変 (mutable) にできる。
    let mut e = 100;
    println!("`e` is {}", e);
    e = 200;
    println!("`e` is {}", e);

    // 結合の初期化は使用までに行われればよい。 (使用までに行われなければならない。)
    let f;
    f = 10;
    println!("`f` is {}", f);

    // スコープとシャドーイング。
    { // スコープはブロック内。
        let g = 20;
        println!("`g` is {}", g);
        // ブロックの外の結合を使用できる。
        println!("outer `f` is {}", f);
        // 同じ名前の結合で前の結合を覆い隠せる (shadow)。 (外のブロックの変数結合を隠す例。)
        let f = 30;
        println!("inner `f` is {}", f);
    }
    // ここからはもう上のブロック内の `z` は見えない。
    // 同じスコープの結合も隠せる (shadow)。
    let f = "Another type"; // Mutable な変数結合に割り当てし直す場合と異なり、別の型の値を割り当てられる。
    println!("`f` is {}", f);
}

// 4.2 Functions
// See : https://doc.rust-lang.org/book/functions.html
fn hello_functions() {
    println!("=== 4.2 Functions ===");

    // `fn` の後に関数名、括弧で囲まれた引数、ブレースを続けて関数定義。
    // 引数には型の宣言が必要。
    fn print_sum(x: i32, y: i32) {
        println!("sum is {}", x + y);
    }
    print_sum(1, 2);

    // 引数の戻り値の型は `->` に続けて宣言する。
    // 最後の式が戻り値になる。
    // (返り値の型が `unit` 型 (`()`) の場合は省略できるっぽい?)
    fn sum(x: i32, y: i32) -> i32 {
        x + y
    }
    print_sum(sum(1, 2), 3);

    // 最後にセミコロンがあると戻り値は `unit` 型になる。
    fn sum2(x: i32, y: i32) -> () {
        x + y;
    }
    sum2(1, 2);

    // Rust は主には式 (expression) ベースで、2 種類の文 (statement) を除いて他は全て式。
    // 1 つめの文、‘declaration statements’ について。
    // 例えば `let` による変数結合は文。 よって `let x = (let y = 100)` みたいな記述はできない。
    // 結合済みの変数への割り当ては式だが、その値は空のタプルになる。
    let mut v1 = 100;
    println!("v1 is {}", v1);
    let v2 = v1 = 200; // `v2` は `200` ではなく `()`。
    println!("v1 is {}, v2 is {:?}", v1, v2);

    // 2 つめの文、‘expression statements’ は、任意の式を文にするもの。
    // Rust では式と式をセミコロンで分割し、文の連なりとして記述していく。
    // (なので、上で見たように関数の最後にセミコロンがあるかどうかで返り値が変化する。)

    // `return` キーワード。
    fn foo(x: i32) -> i32 {
        if x < 0 { return x }
        x + 1
    }
    println!("`foo(-5)` is {}", foo(-5));
    println!("`foo(5)` is {}", foo(5));
    
    // 処理が戻らない diverging functions のための戻り値は `!` で宣言。
    fn do_panic() -> ! {
        // `panic!` マクロは現在のスレッドの実行をクラッシュさせる。
        panic!("Always panic");
    }
    if false { // 実際にはクラッシュしないように。
        // Diverging function の返り値は任意の型として扱える。
        let panic_return: i32 = do_panic();
    }
    
    // 関数を指す変数結合も生成できる。
    let sum_func: fn (i32, i32) -> i32 = sum;
    println!("`sum_func(5, 6)` is {}", sum_func(5, 6));
}

// 4.3 Primitive Types
// See : https://doc.rust-lang.org/book/primitive-types.html
fn hello_primitive_types() {
    println!("=== 4.3 Primitive Types ===");

    // `bool` 型
    let b1 = true;
    let b2: bool = false;
    println!("`b1` is {}, `b2` is {}", b1, b2);

    // `char` 型
    // 単一のユニコードスカラ値 (コードポイントから上位と下位のサロゲートコードポイントを除いたもの) を表す。 4 バイトらしい。
    let c1 = 'a';
    let c2: char = '💛';
    println!("`c1` is {}, `c2` is {}", c1, c2);
    
    // 各種数値型
    // 符号の有無 (signed/unsigned)、サイズ (fixed/variable)、
    // 整数か浮動小数点数 (integer/floating-point) でカテゴリ分けされる。
    // 整数は符号付きと符号無しがある。 
    // 符号付整数の型には `i` が、符号無整数には `u` が使用される。
    // Fixed size types は型にそれぞれのビット数 (8、16、32、64) が使用される。
    let num1: i32 = -100; // 32 ビット符号付整数
    let num2: u8 = 3; // 8 ビット符号無整数
    println!("`num1` is {}, `num2` is {}", num1, num2);
    // Variable sized types は、マシンのポインタサイズに依存するサイズ。
    // 符号付は `isize`、符号無は `usize`。
    let num3: usize = 8;
    println!("`num3` is {}", num3);
    // 浮動小数点数は `f32` と `f64`。
    // IEEE-754 single and double precision numbers に相当。
    let num4: f32 = 10.5;
    println!("`num4` is {}", num4);

    // 配列
    // 同一型の要素からなりサイズ固定。 `[T; N]` で型が表現される。
    // `T` は要素の型。 `N` はサイズ。
    let array1: [i32; 7] = [0, 1, 2, 3, 4, 5, 6];
    println!("`array1` is {:?}", array1);
    // `[elem; size]` という省略表記で、全ての要素を `elem` で初期化したサイズ `size` の配列を生成。
    let array2: [i32; 5] = [10; 5];
    println!("`array2` is {:?}", array2);
    // サイズは `len()` で取得可能、各要素は `[n]` で取得可能 (インデックスは 0 から開始)。
    println!("Size of `array1` is {}, `array1[0]` is {}", array1.len(), array1[0]);

    // Slice
    // メモリ上で隣接する指定の型の値の並びに対するビュー。
    // 以下は配列に対する例だが、配列以外の様々なものに対して `&` と `[]` の組み合わせで slice を生成できる。
    let complete_slice = &array1[..]; // `array1` のすべての要素を含む slice 
    let middle_slice = &array1[1..4]; // `array1` のインデックス 1 から 3 までの要素を含む slice
    println!("`complete_slice` is {:?}, `middle_slice` is {:?}", complete_slice, middle_slice);

    // `str`
    // これ自身は便利ではないが、`&str` という感じでリファレンスで使用すると便利である。
    let str1: &'static str = "Hello, world!";
    println!("`str1` is {}", str1);

    // タプル
    // 固定サイズの順序付きリスト。
    let tuple1: (i32, &str) = (1, "hello");
    println!("`tuple1` is {:?}", tuple1);
    // destructuring let によりタプル内のフィールドにアクセスできる。
    let (tuple1_elem1, tuple1_elem2) = tuple1;
    println!("`tuple1_elem1` is {}, `tuple1_elem2` is {}", tuple1_elem1, tuple1_elem2);
    // Indexing syntax により、各要素にアクセスすることもできる。
    println!("`tuple1.0` is {}, `tuple1.1` is {}", tuple1.0, tuple1.1);
    // 1 要素のタプルを括弧で囲まれた式と区別するために、カンマを使う。
    let tuple2 = (2,);
    let not_tuple = (2);
    println!("`tuple2` is {:?}, `not_tuple` is {}", tuple2, not_tuple);

    // 上で見たように関数も型を持つ。
    fn function1(x: i32) -> i32 { x }
    let function2: fn(i32) -> i32 = function1;
    println!("`function2` is {:?}", function2);
}

// 4.4 Comments
// See : https://doc.rust-lang.org/book/comments.html
fn hello_comments() {
    println!("=== 4.4 Comments ===");

    // 行コメントと doc コメントがある。
    // `//` で始まるコメントは行コメント。
    
    // doc コメントは `///` で始める。

    /// doc コメントのサンプル用の関数。
    ///
    /// # 例
    /// 以下のように使用する。
    /// ```
    /// do_test(); // 呼び出し
    /// ```
    fn do_test() {
        println!("hello");
    }
    do_test();

    // `///` による doc コメントは、そのコメントの後ろにあるものに対するコメント。
    // 別の形式の doc コメントとして、`//!` というものがある。
    // これは、そのコメントを含んでいるものに対するコメント。
    // モジュールや crate (crate とは?) の内部で使用されるらしい。

    // doc コメントについて、さらに詳しいことは次のページに書かれている。
    // https://doc.rust-lang.org/book/documentation.html
}

// 4.5 if
// See : https://doc.rust-lang.org/book/if.html
fn hello_if() {
    println!("=== 4.5 if ===");

    // 条件が括弧で囲まれないのと本体がブロック必須なこと以外はわりと C に近いっぽい。
    let x = 5;
    if x == 5 {
        println!("`x` is five!");
    } else if x == 6 {
        println!("`x` is six!");
    } else {
        println!("`x` is not five or six :(");
    }

    // 式なので値を返す。
    // (`else` がない場合は常に `()` になるらしい。)
    let y = if true { 10 } else { 20 };
    println!("`y` is {}", y);
}

// 4.6 Loops
// See : https://doc.rust-lang.org/book/loops.html
fn hello_loops() {
    println!("=== 4.6 Loops ===");

    // 現在のところ `loop`、`while`、`for` の 3 種類。
 
    // 明示的に抜けない限り無限にループする。
    loop {
        println!("In `loop`");
        break;
    }

    // 条件付き。
    let mut count = 0;
    while count < 4 {
        println!("In `while, with `count` {}", count);
        count += 1;
    }

    // `for`
    for x in 0..4 {
        println!("In `for`, with `x` {}", x);
    }

    // `.enumerate()` で index を取得できる。
    for (index, value) in (5..8).enumerate() {
        println!("`index` is {}, `value` is {}", index, value);
    }

    // ループの制御には `break` と `continue` が使用できる。
    let mut y = 0;
    loop {
        y += 1;
        if y % 2 == 0 { continue }
        if y > 4 { break }
        println!("`y` is {}", y);
    }

    // ループにラベルを付けて、`continue` や `break` で指定できる。
    'outer: for x in 0..5 {
        'inner: for y in 0..5 {
            if x % 2 == 0 { continue 'outer }
            if y % 2 == 0 { continue 'inner }
            println!("x: {}, y: {}", x, y);
        }
    }
}

Gradle のマルチプロジェクト構成におけるプロジェクトの評価順 (設定フェーズにおけるプロジェクト間の依存関係)

Gradle の公式のドキュメントに書いてあるけどどこに書かれてるのか探すのにいつも手間取るのでメモっておく。

前提知識

  • Gradle のビルドには、「初期化」 (Initialization) と 「設定」 (Configuration) と 「実行」 (Execution) の 3 つのフェーズがある。 (Chapter 20. The Build Lifecycle)
  • マルチプロジェクトの場合、各プロジェクトが評価されていって 「設定」 フェーズが終わり、そのあと 「実行」 フェーズに移る。
  • 「実行」 フェーズではタスクの実行が行われるので、タスク間の依存関係により実行順が決まる。

プロジェクトの評価順

  • Chapter 24. Multi-project Builds に書かれてる。
  • 基本としては、各プロジェクトが名前のアルファベット順に評価される。
  • 入れ子になったプロジェクトの場合、上位のプロジェクトから下位のプロジェクトの順でプロジェクトが評価される。
  • あるプロジェクト (A とする) の評価時に、別のプロジェクト (B とする) で定義される何かを参照したり変更を加えたりする場合、A より先に B が評価されている必要がある。
  • プロジェクトの評価順序に依存関係を持たせるには、Project#evaluationDependsOn(String) メソッドが使用できる。
  • あるプロジェクトの評価後に処理を実行したい場合は、Project#afterEvaluate(Cloure) メソッドなどを使用すると良い。