大規模なモジュールプロジェクト向けのアセンブリシステム

私たちのブログのページで、ソースを作業コピーの可変構造に抽出する可能性を含む方法で、大規模プロジェクトのリポジトリを編成することの利点についてすでに書いています。 このアプローチを、単純な構成、断片化されたアセンブリ、幅広いハードウェアプラットフォームの多数のオペレーティングシステムのサポートのニーズと組み合わせることで、独自のビルドシステムを開発することになりました。 この記事では、大規模プロジェクトのインフラストラクチャをサポートするのが困難な開発者にとって興味深いと思われるソリューションについて説明します。







技術的な詳細に直接進む前に、2つの重要な点に注意する必要があります。 まず、システムは、私たちが開発したlinmakeメイクアップユーティリティの上で実行されます。これについては、別途説明します。 そして、第2に、開発はDBMS LINTERwww.linter.ru )の生産上の問題を解決するために行われました。これは、特定の特異性を導入しましたが、ソリューションがどのプロジェクトにも適応できないほど重要ではありませんでした。



なぜ新しいビルドシステムを作成する必要があったのですか?



よくあることですが、ある時点でのプロジェクトの開発と複雑化により、ビルドインフラストラクチャのサポートが非常に高価になり、これはいくつかの理由で促進されたという事実につながりました。プロジェクト参加者からの苦情の数:



もちろん、問題に加えて、新しい「機能」の実装も望まれていたため、unimakeと呼ばれる新しい統合アセンブリシステムの開発が決定されたとき、どのような目標を達成したいかを明確に想像しました。



組立モデル、一般



アセンブリは、ソース( srcroot )-アセンブリディレクトリ( bldroot )以外のディレクトリで実行されます。 各プロジェクトアセンブリは、一連のセットによって完全に決定されます。



プロジェクト構成オプション

... CONFIGS = base60 full60 PLATFORMS = LINUX ARCHS = AMD64 JAVA .NET COMPILERS = GCC JAVAC MONO JAVAC_VERS = 1.4 1.5 1.6 GCC_VERS = 4 MONO_VERS = 3 … HOST.PLT = LINUX HOST.ARCH = AMD64 DEBUG = RELEASE
      
      





これらのパラメーターの組み合わせにより、不必要で意味のない組み合わせを除外するために、アセンブリシステムによって事前にフィルターされるすべての可能なオプションが決まります。

次に、各モジュールは、宣言型スタイルで記述され、ルールを含まない(まれな例外を除く)モジュール用とアセンブリプロセス用の2つの記述子ファイルの助けを借りて、「それ自体」のパラメーターを展開します。 モジュール記述子には、モジュールに関する一般情報(名前とバージョン、サポートされているプラ​​ットフォーム、コンパイラーとアーキテクチャー、フローモデル、目標)が含まれています。 すべてのアナウンス(名前を除く)はオプションであり、存在しない場合はデフォルト値が使用されます。



モジュール記述子オプション

 MODULE = example #  VERSIONS = #        VERSIONS_REQ:= $(CFG.VER) #      LINK_TYPES = static dynamic #    /  THREAD_TYPES = mt #   DST_SRC = example.h #         DONT_BUILD_WATCOM = #   ,   — watcom ( ) DONT_BUILD_WINCE = #       — WinCE
      
      





アセンブリ記述子は、目標、その構成、ディレクティブ、検索ディレクトリ、外部および内部モジュールの依存関係を宣言します。



アセンブリ記述子オプション

 ... TARGET = $(MODULE) #     ,     + ,      (.so, .a, .dll  ..) DEFINES = _VER=$(CFG_VER) SOME_DEFINES #     DEFINES_WINNT = EXAMPLE_WIN #   Windows DEFINES_UNIX = EXAMPLE_POSIX #   *nix CDIR = $(MODROOT);$(MODROOT)/utils; #   INCLDIR = $(MODROOT);$(ANOTHER_MOD); #  OBJS = & example.obj #      OBJS_UNIX = & charset.obj #     *nix  SLIBS_WINNT = $(ANOTHER_LIB) oldnames #   windows ... SLIBS_UNIX = $(ANOTHER_LIB) #   *nix ...
      
      





bldrootでは 、ディレクトリ構造はsrcrootを各モジュールのルートレベル( modsrc )まで繰り返しますが、共通プロジェクトとモジュール構成の有効な組み合わせで指定された実際のオプションはすべて既に含まれています。 これらの各オプションについて、$(MODULE)/ $(PLT)_ $(ARCH)_ $(CMPL)$(CMPLV)_ $(TYPE)_ $(CFG)の形式のディレクトリが作成されます(例:/ LINUX_AMD64_GCC4_MD_R_base60)さらにこれらのディレクトリをmodbldとして。



Modsrcコンテンツオプション

 <srcroot> └── example ├── example.c ├── example.h ├── makefile.lmk └── makelibs
      
      







Modbldコンテンツオプション

 <bldroot> └── example ├── LINUX_AMD64_GCC4_MD_R_base60 │  ├── charset.obj │  ├── example.cfl │  ├── example.h │  ├── example.lnk │  ├── example.obj │  ├── example.so │  └── makefile ├── LINUX_AMD64_GCC4_MD_R_full60 │  ├── charset.obj │  ├── example.cfl │  ├── example.h │  ├── example.lnk │  ├── example.obj │  ├── example.so │  └── makefile ├── LINUX_AMD64_GCC4_MT_R_base60 │  ├── charset.obj │  ├── example.a │  ├── example.cfl │  ├── example.h │  ├── example.lnk │  ├── example.obj │  └── makefile └── LINUX_AMD64_GCC4_MT_R_full60 ├── charset.obj ├── example.a ├── example.cfl ├── example.h ├── example.lnk ├── example.obj └── makefile
      
      





各有効なmodbldでは、ディレクトリトラバーサルプロセス中に、コンパイラオプション(この場合は* .cfl)、リンカーオプション(例では* .lnk)、および一般的なシステムをバイパスしてターゲットをコンパイルおよびリンクするように設計された補助makefileの3つのファイルが作成されますアセンブリ。これは多くの場合、タスクのデバッグに需要があります。 したがって、システムを使用するための2つのオプションがあります。



両方のケースのコール図を下の図に示します。





図1:プロジェクト全体をビルド(1)すると、ビルドオプション(2)のすべての可能な組み合わせに対してルートメイクファイル(3)の呼び出しシーケンスが形成されます。 ろ過の結果(3)、明らかに不適切なオプションが排除されます。 モジュールのファイル記述子(4)依存関係と追加パラメーターに基づいてオプションを修正します。 アセンブリ記述子(5)は規則(6)に従い、実行結果(7)でターゲットディレクトリを形成します。





図2:既存のモジュールの更新(1)は、単純化されたスキームに従って機能します。modbldの補助規則(3)モジュール記述子とフィルターを使用せずに目標を更新(4)します。



上記のように、すべてのルールはプロジェクトレベルの別のモジュール( unimake )に配置されます。これは、ルール自体の設定に加えて、モジュール間の依存関係ツリーの格納を担当します。 同時に、宣言されたモジュールの各モジュールは、生成された依存目標を持つ個別のターゲットを生成します。



モジュール間の依存関係の保存と使用

 … dep_example = another dep_another = … module-deps = $(foreach name,$(DEP_$(1)), $(MOD_$(name))) gen-module-deps = $(foreach name,$(DEP_$(1)), $(2)_$(MOD_$(name))) !define gen-target $(1): .SYMBOLIC @$(MAKE) MODULE=$(1) !endef !define gen-targets TARGETS_$(1) := $(foreach mod,$(ALL_MODULE_NAMES), $(1)_$(mod)) $(1): $$(TARGETS_$(1)) @%null !endef gen-targets-without-deps = $(foreach mod,$(ALL_MODULE_NAMES),$(gen-target ,$(mod))) !eval $(gen-targets-without-deps) !eval $(gen-targets dep)
      
      





linmodules モジュール配置ファイルの組み込みパーサーのおかげで、ソースツリー内のモジュールの現在の位置を追跡し、簡単なパス定義を使用できます。



モジュールとパスの読み取りと登録

 #git modules LINMODS=$(modlist $(SRCROOT)/.linmodule) !define add-mod MOD_$(1) = $$(modpath $(1)) !endef !eval $(foreach i,$(LINMODS),$(add-mod $(i)))
      
      





実装



前のセクションで説明したアプローチは、Linterプロジェクトインフラストラクチャ向けに実装されました。 そして、これは比較的最近(約6か月前)に発生したという事実にもかかわらず、システムは使いやすさ、スケーラビリティ、パフォーマンスの面ですでに証明されています。



実装の初期段階でさえ、gnu makeのよく知られている欠点に遭遇したため、このソリューションは、私たち自身の開発のメイクアップツールであるlinmakeに基づいています。linmakeの構文には、この記事のすべてのリストが含まれています。 おそらく、近い将来、linmakeトピックとそのブログページの機能に戻りますが、これまでのところ、開発で使用される形式のシステムは公開されていません。 ただし、提案されたモデルをテストする機会を読者から奪うのは間違っているので、gnu makeの作業用プロトタイプはここ( github.com )で入手できます。



All Articles