技術的な詳細に直接進む前に、2つの重要な点に注意する必要があります。 まず、システムは、私たちが開発したlinmakeメイクアップユーティリティの上で実行されます。これについては、別途説明します。 そして、第2に、開発はDBMS LINTER ( www.linter.ru )の生産上の問題を解決するために行われました。これは、特定の特異性を導入しましたが、ソリューションがどのプロジェクトにも適応できないほど重要ではありませんでした。
なぜ新しいビルドシステムを作成する必要があったのですか?
よくあることですが、ある時点でのプロジェクトの開発と複雑化により、ビルドインフラストラクチャのサポートが非常に高価になり、これはいくつかの理由で促進されたという事実につながりました。プロジェクト参加者からの苦情の数:
- 1999年には受け入れ可能なクロスプラットフォームツールがなかったため、長い間2つの並列ビルドシステムをサポートする必要がありました。Windowsのwmakeと* nixのmakeに基づいています。
- サポートされているUNIXライクなプラットフォームの多様性により、プロジェクトモジュールでのコンパイルおよびリンクオプションの増加(およびそのための複雑さ)につながっています。
- また、多数のコンパイラをサポートする必要があるため、Windowsバージョンのビルドは複雑でした。
- プロジェクトの外部と内部の両方の依存関係を記述および解決するための簡単なメカニズムはありませんでした。
もちろん、問題に加えて、新しい「機能」の実装も望まれていたため、unimakeと呼ばれる新しい統合アセンブリシステムの開発が決定されたとき、どのような目標を達成したいかを明確に想像しました。
- システムは、サポートされているすべてのプラットフォームで均一に動作するはずです。
- 作業ツリー内のモジュールの位置の変更(以降、モジュールとはプロジェクトの機能的に自給自足の部分を意味します)は、パフォーマンスに影響を与えません。
- 新しいターゲットプラットフォーム、アーキテクチャ、コンパイラ、およびそれらのバージョンを追加するには、単純なメカニズムが必要です。
- 製品バージョンとエディションの両方の標準構成を保存し、必要に応じてそれらを構成する機能を提供する必要があります。
- プロジェクトの外部および内部の依存関係を自動的に考慮する簡単な方法が必要です。これにより、操作の順序が自動的に決定されます。
- システムは、すべての依存関係を持つプロジェクトの一部を簡単に構築する機能を提供する必要があります。
組立モデル、一般
アセンブリは、ソース( srcroot )-アセンブリディレクトリ( bldroot )以外のディレクトリで実行されます。 各プロジェクトアセンブリは、一連のセットによって完全に決定されます。
- 製品構成/バージョン(CONFIGS);
- ターゲットプラットフォーム(プラットフォーム);
- ターゲットアーキテクチャ(ARCHS);
- コンパイラ(COMPILERS);
- コンパイラーのバージョン($(CMPL)_VERS);
- ビルドプラットフォーム(HOST.PLT);
- $アセンブリプラットフォームアーキテクチャ(HOST.ARCH)。
プロジェクト構成オプション
... 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 )で入手できます。