LoginSignup
1
0

More than 3 years have passed since last update.

【備忘録】DomainObjectCollectionの一族にオブジェクトをaddする時に詰まった話【Gradleプラグイン】

Posted at

こんな感じのコードを書いてました

Gradleプラグインの開発中、以下のようなコードを書いていました。

allメソッドはクロージャ(ラムダ式?)を渡された時点でコレクションが持っているオブジェクトを流し込んでくれる上、
後から追加したオブジェクトも随時クロージャに流し込んでくれるオブザーバっぽい動きもします。

この動きを利用して、buildTypesとflavorsに更新があった際にvariantsへオブジェクトを追加するというもの。

open class Yakiniku(val project: Project) {
    val buildTypes: NamedDomainObjectContainer<ProjectBuildOptions> =
        project.container(ProjectBuildOptions::class.java)
    val flavors: NamedDomainObjectContainer<ProjectFlavorOptions> =
        project.container(ProjectFlavorOptions::class.java)
    val variants: DomainObjectSet<BuildVariant> =
        DefaultDomainObjectSet<BuildVariant>(BuildVariant::class.java, CollectionCallbackActionDecorator.NOOP)

    init {
        buildTypes.all { buildOption ->
            variants.add(BuildVariant(buildOption, null))
        }

        flavors.all { flavorOption ->
            buildTypes.all { buildOption ->
                variants.add(BuildVariant(buildOption, flavorOption))
            }
        }
    }
}

class BuildVariant(val build: ProjectBuildOptions?, val flavor: ProjectFlavorOptions?) : ObjBase() {
    fun getName() = listOfNotNull(build, flavor).joinToString("") { it.name.capitalize() }
    override fun toString(): String {
        return getName()
    }
}

で、build.gradle側からこんな感じで呼び出したかったんです。

build.gradle
apply plugin: 'com.osm.sample.gralde.yakiniku'

yakiniku {
    buildTypes {
        debug {
            target "hogehoge"
        }

        release {
            target "fugafuga"
        }
    }

    flavors {
        free {
            target "piyopiyo"
        }

        paid {
            target "こんにちは!"
        }
    }

    // ★★★ここ★★★
    variants.all {
        println(it)
    }

"debugFree"とか"releasePaid"とかを期待していたわけなんですが…動かしてみると、null
呼び出される回数は想定通り。しかし全部null

あるぇ…

ソースを追跡して原因を特定するため、スタックトレースを吐き出しつつ紐解いていったところ、
以下のような箇所を見つけました。

ConfigureUtil.java
    public static <T> T configure(@Nullable Closure configureClosure, T target) {
        if (configureClosure == null) {
            return target;
        } else {
            if (target instanceof Configurable) {
                ((Configurable)target).configure(configureClosure);
            } else {
                configureTarget(configureClosure, target, new ConfigureDelegate(configureClosure, target));
            }

            return target;
        }
    }

    /** 中略 */

    private static <T> void configureTarget(Closure configureClosure, T target, ConfigureDelegate closureDelegate) {
        if (!(configureClosure instanceof GeneratedClosure)) {
            (new ClosureBackedAction(configureClosure, 1, false)).execute(target);
        } else {
            Closure withNewOwner = configureClosure.rehydrate(target, closureDelegate, configureClosure.getThisObject());
            (new ClosureBackedAction(withNewOwner, 2, false)).execute(target);
        }
    }

このtargetという変数には、巡り巡ってaddされたオブジェクトが入ってくるようです。
これだけ見てると、targetがConfigurableじゃなければうまくいきそう。

しかし、スタックトレースを見ているとtargetがConfigurableとして判定されて、
configure()が呼ばれた形跡があったので「んん?」となったわけです。

おかしいなあと思ってクラスの定義を追ってみました。ObjBaseになんかあるのかなあ。

class BuildVariant(val build: ProjectBuildOptions?, val flavor: ProjectFlavorOptions?) : ObjBase() {
    fun getName() = listOfNotNull(build, flavor).joinToString("") { it.name.capitalize() }
    override fun toString(): String {
        return getName()
    }
}

どれどれ…

open class ObjBase: Configurable<Any> {
    var resolveStrategy = Closure.DELEGATE_FIRST

    override fun configure(closure: Closure<*>?): Any {
        closure?.also {
            it.delegate = this
            it.resolveStrategy = resolveStrategy
            it.run()
        }
        return this
    }
}

ちくしょう!

ObjBaseの継承をやめたところ、想定通りallメソッドに渡したクロージャの中でaddされたオブジェクトが取れました。

まとめ

  • addするオブジェクトがConfigurableの実装であるかどうかによって、DomainObjectCollection#all()の挙動が変わる。
  • Configurableの実装である場合、addされたオブジェクトが持ってるconfigureが呼び出される。
  • Configurableの実装でない場合、DomainObjectCollection#all()に渡したクロージャに、addされたオブジェクトが渡される
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0