モジュラープロジェクトビルドシステムでgit機能を使用する

私たちのブログでは、大規模プロジェクトのリポジトリを独立したモジュールのセットとして整理する原則についてすでに説明しました。これにより、ソースコードの抽出を作業コピーの任意のファイル構造に整理できます。 もちろん、このアプローチは、実際の場所を考慮してモジュール間の依存関係を追跡するメカニズムを作成する必要があるため、プロジェクトアセンブリシステムに影響を与えるだけでした。 この記事では、gitの機能を使用してこの問題を解決する方法だけでなく、モジュール間の内部依存関係を自動的に考慮してプロジェクトのフラグメントを抽出する方法についても説明します。







例について一言



最初は、この出版物にLINTERで実装されたアセンブリシステムのフラグメントを装備する予定でしたが 、このプロジェクトではネイティブgitモジュールを使用せず、独自のデザインのmakeユーティリティを使用します。 したがって、読者にとっての資料の実用的な価値が損なわれないように、 すべての例は gitサブモジュールとgnu makeの組み合わせでの使用に適合しており、以下に示す特定の困難をもたらしました。



デモの説明



簡素化するために、プロジェクトと呼ばれる条件付き製品の例によって、ビルドシステムとgitの統合を検討します。この製品は、次の機能モジュールで構成されています。

アプリケーション-アプリケーション自体。

demo-デモ。

libfooとlibbarは、アプリケーションが依存するライブラリです。

プロジェクトの依存関係グラフは次のようになります。



図1:プロジェクトの依存関係グラフ



ストレージ組織



バージョンストレージシステムの観点から、プロジェクトは5つの個別のリポジトリに分割されます。4つはモジュール用で、5つ目はproject.gitです。これはコンテナとして機能し、ビルドシステムを含みます。 この構成方法には、単一リポジトリと比較していくつかの利点があります。





サブモジュールと依存関係



アセンブリシステムを整理するための再帰的なアプローチは正当に批判されていますが 、それでもプロジェクトを維持するコストを大幅に削減できるため、この例ではそれを使用します。 同時に、プロジェクトのルートメイクファイルは、プロジェクト内のモジュールの位置を「認識」するだけでなく、目的の順序で、依存ツリーのブランチからルートまでの子メイクアッププロセスの呼び出しも提供する必要があります。 これを行うには、これらのモジュール間の依存関係を明示的に記述する必要があります 。この例では、次のように実行します

MODS = project application libfoo libbar demo submodule.project.deps = application demo submodule.demo.deps = application submodule.application.deps = libfoo libbar submodule.libfoo.deps = submodule.libbar.deps =
      
      





このツリーの正しいトラバースは、makeによって提供され、明示的な依存関係を持つ動的ターゲットを作成します。これに対して、次の形式のgen-dep関数を宣言します。

 define gen-dep $(1):$(foreach dep,$(submodule.$(1).deps),$(dep)) ; endef
      
      





ここで、ルートMakefileの本文にある場合、すべてのモジュールに対してgen-depを呼び出します

 $(foreach mod,$(MODS),$(eval $(call gen-dep,$(mod))))
      
      





これにより、実行時に次の動的ターゲットが形成されます(これは、-pスイッチを指定してmakeを実行することで確認できます)。

 project: application demo demo: application application: libfoo libbar libbar: libfoo:
      
      





これにより、依存関係にアクセスするときに、必要な順序で依存関係を呼び出すことができます。 同時に、ターゲット名が既存のファイルまたはディレクトリと一致する場合、これらの目標がファイルではなくアクションであることを「知らない」ので、これを回避するために実行を中断する可能性があります。

 $(eval .PHONY: $(foreach mod,$(MODS), $(mod)))
      
      





開発者がアプリケーションに変更を加えるというタスクに直面しているとしましょう。このタスクでは、サブモジュールアプリケーション、libbar、libfooのみを取得する必要があります。 これを行うには、ビルドシステムは、上記で発表された依存関係に基づいて、モジュールの説明とgitが後で使用するための配置を作成する必要があります。



最小限の構成で.gitmodulesを生成するために、この例に次の変更加えます。

 … MODURLPREFIX ?= git@git-common.relex.ru/ MODFILE ?= .gitmodules … define tmpl.module "[submodule \"$(1)\"]" endef define tmpl.path "\tpath = $(1)" endef define tmpl.url "\turl = $(1)" endef … define submodule-set submodule.$(1).name := $(2) submodule.$(1).path := $(3) submodule.$(1).url := $(4) endef define set-default $(call submodule-set,$(1),$(1),$(1),$(MODURLPREFIX)$(1).git) endef define gen-dep $(1):$(foreach dep,$(submodule.$(1).deps),$(dep)) @echo "Register module $(1)" @echo $(call tmpl.module,$(submodule.$(1).name)) >> $(MODFILE) @echo $(call tmpl.path,$(submodule.$(1).path)) >> $(MODFILE) @echo $(call tmpl.url,$(submodule.$(1).url)) >> $(MODFILE) endef … $(foreach mod,$(MODS),$(eval $(call set-default,$(mod))))
      
      





これで、条件付き開発者は、makeアプリケーションを呼び出すことにより、次の内容のファイルを作成できるようになります。

 [submodule "libfoo"] path = libfoo url = git@git-common.relex.ru/libfoo.git [submodule "libbar"] path = libbar url = git@git-common.relex.ru/libbar.git [submodule "application"] path = application url = git@git-common.relex.ru/application.git
      
      





git-a toolsによって既に変更および解析できます 。たとえば、次のようになります。

 git config -f .gitmodules --get submodule.application.path application
      
      





リポジトリのルートに.gitmodulesファイルが存在するだけでは、インデックスにモジュールが登録されないため、サブモジュールの初期化とクローン作成まで、ファイルに必要なすべての調整を行うことができます。



サブモジュールの直接の初期化に関しては、gitのネイティブモジュールの実装における最初の重大な不便さが明らかになります。このバージョン管理システムは、インデックスと.gitmodulesファイルの両方にモジュールに関するメタデータを保存します。 ソースコードを確認すると 2つの最適な選択肢がないことが明らかになります。

最初の方法は、次のようにインデックス内のモジュールに関する情報を入力することです。

 #!/bin/sh git config -f .gitmodules --get-regexp '^submodule\..*\.path$' | while read path_key path do url_key=$(echo $path_key | sed 's/\.path/.url/') url=$(git config -f .gitmodules --get "$url_key") git submodule add --force $url $path done
      
      





この場合、通常のgit-submodule(イテレーター、グループ操作など)を使用してサブモジュールを操作することが可能になりますが、モジュールの移動/削除、およびその分岐には追加の補助操作が必要になります。 この状況は、 LINTERリポジトリでgit-submodulesの使用を拒否した理由の1つでした。 サブモジュールの追加に代わる方法は、インデックスに登録せずにモジュールを複製することです。これは次のように実行できます。

 #!/bin/sh git config -f .gitmodules --get-regexp '^submodule\..*\.path$' | while read path_key path do url_key=$(echo $path_key | sed 's/\.path/.url/') url=$(git config -f .gitmodules --get "$url_key") git clone $url $path done
      
      





この場合、.gitignoreのすべての$パスを明示的に指定する必要があります。そうでない場合、gitはクローンサブモジュールを通常のディレクトリとして扱い、それらとその内容を追跡されていないファイルとして扱います。



いずれにせよ、示された方法のいずれかによるクローン作成後、作業コピーはツリーの選択されたフラグメントを抽出する状況に対応します





図2:プロジェクトの依存関係グラフ。 塗りつぶしは、抽出されたモジュールツリーを強調表示します。



また、モジュール間の依存関係が正しく宣言されていれば、アプリケーションのコンパイルに必要なすべてが含まれています。



モジュールの位置を決定する



アセンブリシステムが解決する別のタスクは、モジュールの現在の位置を決定することです。 このために、前に作成した記述子ファイルを使用します。 初期化と同様に、いくつかのオプションがあります。 最も簡単なのは、git config機能を利用することです。

 define get-path $(shell git config -f .gitmodules --get "submodule.$(1).path") endef define get-url $(shell git config -f .gitmodules --get "submodule.$(1).url") endef
      
      





このようなソリューションは移植性の観点からは理想的ではありませんが、GNU makeバージョン4以降を使用する場合にのみ別のオプションを使用できます。この場合、.gitmodulesファイルの解析はGNU make拡張機能を使用して実装できます。



おわりに



もう一度githubで利用できる例は、linmodules + gitmodules用のlinflowバンドル+ GNU makeに基づいたソリューションの適応であるため、これらのツールをペアリングすることの欠点のいくつかは最もエレガントな方法で解決されず、モジュール内の子makefileの呼び出しは「ダミー」に置き換えられました「。







それにもかかわらず、大規模プロジェクトで作業する場合、メカニズムは非常によく証明されており、102 gitサブモジュールのリポジトリを正常に「コピー」し、その間にリンクユニットの直径が5ユニットの308のモジュール間リンク(論理およびアセンブリの両方)があります(参照。上記の図)。



All Articles