9

ドキュメントに記載されているように、Gradleは有向非巡回グラフ(DAG)を使用して依存関係グラフを作成します。私の理解では、評価と実行のサイクルを別々にすることは、ビルドツールの主要な機能です。たとえば、Gradleのドキュメントには、これにより、他の方法では不可能ないくつかの機能が有効になると記載されています。

この機能の威力を説明する実際の例に興味があります。依存関係グラフが重要なユースケースは何ですか?Gradleを使用する場合でも、同様に装備されたツールを使用する場合でも、この分野の個人的な話に特に興味があります。

「正しい」答えを評価するのは難しいので、私は最初からこの「コミュニティウィキ」を作成しています。

4

3 に答える 3

6

この挑発的な質問が、最終的に Gradle を検討する動機となりました。私はまだそれを使用していないので、個人的な話ではなく、ドキュメントを閲覧しているときに記録された分析のみを提供できます.

私の最初の質問は、なぜ Gradle タスクの依存関係グラフが非循環であることが保証されているのかということでした。私はそれに対する答えを見つけられませんでしたが、反対のケースは簡単に構築できるので、サイクル検出はグラフの構築時に実行される検証であり、次の場合は最初のタスクの実行前に構築が失敗すると仮定します不正な循環依存があります。最初にグラフを構築しないと、構築がほぼ完了するまで、この障害状態が検出されない可能性があります。さらに、すべてのタスクが実行された後に検出ルーチンを実行する必要があり、これは非常に非効率的です (グラフが段階的に作成され、グローバルに利用可能である限り、深さ優先検索は開始点を見つけるためだけに必要であり、その後、サイクル評価は最小限の作業しか必要としません。しかし、全体の作業は、最初にリレーションのセット全体に対して単一の削減を行うよりも依然として多くなります)。私は早期発見を主な利点として挙げたいと思います。

A task dependency can be lazy (see: 4.3 Task dependencies, and a related example in 13.14). Lazy task dependencies could not be evaluated correctly until the entire graph is built. The same is true for transitive (non-task) dependency resolution, which could cause innumerable problems, and require repeated recompilations as additional dependencies are discovered and resolved (also requiring repeated requests to a repository). The task rules feature (13.8) wouldn't be possible either. These issues, and likely many others, can be generalized by considering that Gradle uses a dynamic language, and can dynamically add and modify tasks, so prior to a first-pass evaluation, results could be non-deterministic since the execution path is built and modified during runtime, thus, different sequences of evaluation could produce arbitrarily different results if there are dependencies or behavioral directives that are unknown until later, because they haven't been created yet. (This may be worthy of investigating with some concrete examples. If it is true, then even two passes would not always be sufficient. If A -> B, B -> C, where C changes the behavior of A so that it no longer depends on B, then you have a problem. I hope there are some best practices on restricting metaprogramming with non-local scope, to not allow it in arbitrary tasks. A fun example would be a simulation of a time travel paradox, where a grandchild kills his grandfather or marries his grandmother, vividly illustrating some practical ethical principles!)

現在実行中のビルドのステータスと進行状況のレポートを改善できます。TaskExecutionListener は、各タスクの処理に before/after フックを提供しますが、残りのタスクの数を知らずに、「6 つのタスクが完了しました。タスク foo を実行しようとしています」以外のステータスについて言うことはあまりありません。代わりに、gradle.taskGraph.whenReady のタスク数で TaskExecutionListener を初期化し、それを TaskExecutionGraph にアタッチすることができます。これで、「72 個のタスクのうち 6 個が完了しました。現在、タスク foo を実行しています。推定残り時間: 2 時間 38 分」などのレポートの詳細を有効にするための情報を提供できます。これは、継続的インテグレーション サーバーのコンソールに表示する場合や、Gradle を使用して大規模なマルチ プロジェクト ビルドを調整し、時間の見積もりが重要な場合に役立ちます。

Jerry Bullard が指摘したように、環境は実行コンテキストによって部分的に決定されるため、ライフサイクルの評価部分は、環境に関する情報を提供する実行計画を決定するために重要です (「DAG による構成」セクションの例 4.15)。さらに、これが実行の最適化に役立つことがわかりました。独立したサブパスは、異なるスレッドに安全に渡すことができます。実行のためのウォーキング アルゴリズムは、ナイーブでない場合、メモリ消費量が少なくなる可能性があります (私の直感では、サブパスが最も多いパスを常にウォークすると、サブパスが最も少ないパスを常に優先するよりもスタックが大きくなることがわかります)。

An interesting use of this might be a situation where many components of a system are initially stubbed out to support demos and incremental development. Then during development, rather than updating the build configuration as each component becomes implemented, the build itself could determine if a subproject is ready for inclusion yet (perhaps it tries to grab the code, compile it, and run a pre-determined test suite). If it is, the evaluation stage would reveal this, and the appropriate tasks would be included, otherwise, it selects the tasks for the stubs. Perhaps there's a dependency on an Oracle database that isn't available yet, and you're using an embedded database in the meantime. You could let the build check the availability, transparently switch over when it can, and tell you that it switched databases, rather than you telling it. There could be a lot of creative uses along those lines.

Gradle looks awesome. Thanks for provoking some research!

于 2010-03-18T20:06:57.987 に答える
3

同じドキュメントの例は、このアプローチの威力を示しています。

後で詳しく説明するように (第 30 章「ビルドのライフサイクル」を参照)、Gradle には構成フェーズと実行フェーズがあります。構成フェーズの後、Gradle は実行すべきすべてのタスクを認識します。Gradle は、この情報を利用するためのフックを提供します。これの使用例は、リリース タスクが実行されるタスクの一部であるかどうかを確認することです。これに応じて、いくつかの変数に異なる値を割り当てることができます。

つまり、ビルド プロセスの早い段階でフックできるため、必要に応じてコースを変更できます。実際のビルド作業がすでに実行されている場合は、変更するには遅すぎる可能性があります。

于 2010-03-18T05:23:04.173 に答える
1

私は現在、さまざまなビルドシステムを評価しており、gradleを使用して、「jar」タイプのすべてのタスクを列挙し、それらを変更する醜いコードを追加して、すべてのjarマニフェストに「Build-Number」属性(後でfinalを作成するために使用)が含まれるようにしました。ファイル名):

gradle.taskGraph.whenReady {
    taskGraph ->
    taskGraph.getAllTasks().findAll {
        it instanceof org.gradle.api.tasks.bundling.Jar
    }.each {
        it.getManifest().getAttributes().put('Build-Number', project.buildVersion.buildNumber)
    }
}
于 2011-04-17T16:48:07.457 に答える