MPS 2023.3 ヘルプ

ハウツー MPS Make フレームワークへの統合

ファセットを構築

概要

基本的に他のビルドシステムや make システムと同様に、MPS make はアーティファクトをビルドするために一連のステップ、つまりターゲットを実行します。必要な make ステップのグローバルな順序付けは、各ビルドターゲットに指定された相対的な優先順位から導き出されます(ターゲット A は B の前に実行し、B は C の前に実行する必要があるため、グローバル順序は A、B、C です)。

完全なビルドプロセスでは、モデルのテキストへの生成、これらのモデルのコンパイル、サーバーへのデプロイ、graphviz ソースファイルからの .png ファイルの生成など、いくつかの問題に対処できます。MPS では、このようなさまざまなビルドアスペクトはビルドファセットで実装されます。ファセットは、共通の懸念事項に対処するターゲットの集合です。

facet10.png

ファセット内のターゲットは設定パラメーターを交換できます。例: 全体的な make プロセスの早い段階で実行するように宣言されているターゲットは、設定パラメーターを収集し、第 2 のファセットに渡します。このファセット内パラメーター交換を実現するためのメカニズムは、プロパティと呼ばれます。さらに、ターゲットは、make プロセス中にクエリを使用してユーザーから情報を取得できます。

facet11.png

全体的な make プロセスは、パイプとフィルターのパターンに沿って編成されています。ターゲットはフィルターとして機能し、配信されているデータストリームを処理します。ターゲット間を流れるデータはリソースと呼ばれます。さまざまな種類のリソースがあり、すべて異なる Java インターフェースおよびタプルとして表されます。

facet12.png
  • MResource には、ユーザーが作成した MPS モデルが含まれています。これらのモデルは、プロジェクトのソリューションと言語に含まれています。

  • GResource は、出力モデルを含む生成プロセスの結果、つまり生成が完了した後のモデルの最終状態を表します。これらは一時的なモデルであり、一時的なモデルを保存ビルドオプションを使用してインスペクションできます。

  • TResource は text-gen の結果を表します

  • CResource は、Java クラスのコレクションを表します

  • DResource は、モデルに対するデルタ変更のコレクションを表します (IDelta)

  • TextGenOutcomeResource は、textgen によって生成されたテキストファイルを表します

ビルドターゲットはインターフェースを指定します。パイプとフィルターのパターンに従って、インターフェースは make ターゲットに出入りするデータの種類を記述します。これは、上記のリソースタイプ、ターゲットがこれらのリソースに適用する処理の種類の観点から指定されます。次の 4 つの処理ポリシーが定義されています。

  • transform がデフォルトです。このポリシーは、入力リソースタイプのインスタンスを消費し、出力リソースタイプのインスタンスを生成します。(たとえば MResources を消費し、TResources を生成する可能性があります。)

  • 宣言する入力を消費しますが、出力は生成しません。* produce は何も消費しませんが、出力を生成します

  • パススルーはいかなるリソースにもアクセスせず、生産も消費もしません。

作成プロセスはモデル生成よりも粒度が粗いことに注意してください。言い換えれば、すべてのモデルジェネレーターを実行する 1 つのファセットがあります。(モデル生成の前後に何かを行うのではなく) MPS 生成プロセスに追加のターゲットを「挿入」する必要がある場合は、generate ファセットをリファクタリングする必要があります。これはこの議論の範囲を超えています。

サンプルファセットの構築

MPS の C 基本言語をビルドする mbeddr.com プロジェクトの一部として、実際の C コンパイラーを MPS ビルドプロセスに統合する必要があります。より具体的には、C 基本言語で記述されたプログラムには、Makefile を生成する方法が含まれています。この Makefile は、対応するすべての .c ファイルと .h ファイルが生成されたら、つまり MPS make プロセスの最後に実行する必要があります。

これを行うために、2 つのターゲットを持つ make ファセットを構築しました。1 つ目は、入力モデルをインスペクションし、textgen の後に Makefile を含む可能性のあるディレクトリの絶対パスを収集します。次に、2 番目のターゲットは、このディレクトリに Makefile というファイルが実際にあるかどうかを確認し、そこで make を実行します。上記の概要で説明したように、2 つのターゲットはプロパティを介してディレクトリを交換します。

最初のターゲット: ディレクトリの収集

ファセットは、言語定義の plugins の側面に存在します。FacetDeclaration のインスタンスを作成できるように、プラグインモデルに {{jetbrains.mps.make.facets} 言語が含まれていることを確認してください。モデル uses がファセットを宣言する言語である場合、ファセットはモデルの作成プロセスの一部として実行されます。

ファセットは runMake と呼ばれます。TextGenGenerate に依存します。これら 2 つのファセットへの依存関係は、それらのファセット内のターゲットに対するターゲットの優先順位を宣言できるように指定する必要があります。

facet runMake extends <none> Required: TextGen, Generate

最初のターゲットは collectPaths と呼ばれます。入力モデルと連絡を取るために、{{transform IMResource-> IMResource} として指定されています。ファセットは、優先順位として after configurebefore generate を指定します。後者は明らかです。なぜなら、モデルがテキストに生成される前にモデルを取得したいからです。前者の優先順位は、基本的に、make プロセスが初期化された後にこのターゲットを実行することを示しています (言い換えれば、「最初に」何かをしたい場合は、これら 2 つの優先順位を使用してください。)

target collectPathes overrides <none> { resources policy: transform IMResource -> IMResource Dependencies: after configure before generate

次に、make ファイルを含むモジュールに関する情報と生成されたコードが存在するディレクトリへのパスを格納するために使用するプロパティ pathes を宣言します。

Properies: list<[string, string]> pathes;

次に、ターゲットの実装コードを見てみましょう。基本的な構造は次のとおりです。まず、pathes リストを初期化します。次に、入力(リソースのコレクション)を繰り返し、各入力(以下で説明)に対して何かを実行します。次に、output ステートメントを使用して入力データを出力します。つまり、ターゲットに出力されたものをすべて通過させます。success ステートメントを使用して、このターゲットを正常に終了します(これがデフォルトであるため、最後に success を使用することはオプションです)。何か問題が発生した場合は、failure ステートメントを使用してターゲットを正常に終了できません。

(input)->void { pathes = new linkedlist<[string, string]>; input.forEach({~inpt => (module, models) res = ((module, models)) inpt; // do stuff. See below. }); output input; success; }

実際の処理は、MPS データ構造に対する単純な Java プログラミングです。

res.models.forEach({~model => string path = res.module.getFacet(GenerationTargetFacet.class).getOutputLocation() + "/" + model.getLongName().replaceAll("\\.", "/"); string locationInfo = res.module.getModuleFqName() + "/" + model.getLongName(); pathes.add([path, locationInfo]); });

モジュールの GenerationTargetFacet ファセットの getOutputLocation メソッドを使用して、特定のモジュールがそのコードを生成するパスを取得します (これはモデルプロパティでユーザーが構成できます)。次に、モデルのドット名を取得し、ドットをスラッシュに置き換えます。これは、そのモジュールでモデルの生成されたファイルが最終的に配置される場所であるためです (これを確認するには、サンプル MPS プロジェクトを調べましょう)。次に、モジュールの名前とモデルの名前をスラッシュで区切って保存し、2 番目のターゲット (変数 locationInfo}). We add the two strings to the {{pathes コレクションを介して) のログメッセージを改善します。この pathes プロパティは、ファセットの 2 番目のターゲットによってクエリされます。

2 番目のターゲット: 実行

リソースを扱う必要がないため、これは pass through ポリシーを使用します。それが必要とするすべての入力は、上記の collectPaths ターゲットのプロパティから取得できます。この 2 番目のターゲットは after collectPaths}, {{after textGenbefore reconcile を実行します。それが collectPaths}, since it uses the property data populated by it. It has to run after {{textGen}, otherwise the make files aren't there yet. And it has to run before {{reconcile}, because basically everything has to run before {{reconcile の後に走らなければならないのは明らかです

target callMake overrides <none> resources policy: pass through Dependencies: after collectPathes after textGen before reconcile

実装コードを見てみましょう。まず、実際に Makefile を含むすべてのエントリを collectPathes.pathes プロパティから取得します。何も見つからない場合は、success を返します。

sequence<[string, string]> modelDirectoriesWithMakefile = collectPathes.pathes. where({~it => new File(it[0] + "/Makefile").exists(); }); if (modelDirectoriesWithMakefile.isEmpty) { success; }

次に、プログレスインジケータ言語を使用して、make ファイルを含むディレクトリがあるのと同じ数のワークユニットでプログレスバーを設定します。

begin work "run make" covering ALL units of total work left, expecting modelDirectoriesWithMakefile.size units;

次に、{{modelDirectoriesWithMakefile} コレクションのすべてのエントリを繰り返し処理します。ループでは、進行状況インジケーターを進めてから、Java 標準 API を使用して make ファイルを実行します。

foreach dirInfoTuple in modelDirectoriesWithMakefile { try { advance 1 units of "run make" with comment "running make for " + dirInfoTuple[1]; Process process = Runtime.getRuntime(). exec("make", new string[0], new File(dirInfoTuple[0])); if (process.waitFor() > 0) { error "make failed with exit code " + process.exitValue() + " for " + dirInfoTuple[1]; } else { info "make finished successfully for " + dirInfoTuple[1]; } } catch (Exception ex) { error ex.getMessage(), ex; } }

ターゲットをまとめるには、finish ステートメントを使用してプログレスバーをクリーンアップします。

finish "run make";