web-dev-qa-db-ja.com

非再帰的なmakeの経験は何ですか?

数年前、私は Recursive Make Consumed Harmful のペーパーを読み、自分のビルドプロセスにそのアイデアを実装しました。最近、私は 非再帰的なmake の実装方法に関するアイデアを含む別の記事を読みました。したがって、非再帰的なmakeが少なくともいくつかのプロジェクトで機能するデータポイントがいくつかあります。

しかし、私は他の人の経験に興味があります。再帰的でないmakeを試しましたか?それは物事を良くしたり悪くしたりしましたか?時間の価値はありましたか?

61
Jon Ericson

私たちは非再帰を使用しますGNU私が働いている会社でシステムを作成します。これはMillerの論文と、特にあなたが与えた "非再帰的makeを実装する"リンクに基づいています。サブディレクトリのmakefileにボイラープレートがまったくないシステムへのBergenのコード。概して、それは正常に機能し、以前のシステムよりもはるかに優れています(GNU Automake)で再帰的に実行されます) 。

私たちは、AIX、HP-UX、Linux、OS X、Solaris、Windows、さらにはAS/400メインフレームまで、すべての「主要な」オペレーティングシステム(商用)をサポートしています。これらのシステムすべてに対して同じコードをコンパイルし、プラットフォームに依存する部分をライブラリに分離します。

ツリーには、約2000のサブディレクトリと20000ファイルに200万行を超えるCコードがあります。 SConsの使用を真剣に検討しましたが、十分な速度で動作させることができませんでした。遅いシステムでは、PythonはSConsファイルで解析するだけで数十秒を使用します。ここでGNU Makeは約1秒で同じことを行いました。これはは約3年前だったので、それ以降は状況が変わっている可能性があります。通常、ソースコードをNFS/CIFS共有に保持し、sameをビルドします。これは、ビルドツールがソースツリーの変更をスキャンするのがさらに遅くなることを意味します。

私たちの非再帰的なGNUシステムに問題がないわけではありません。実行すると予想される最大のハードルは次のとおりです。

  • 特にWindowsへの移植は、大変な作業です。
  • GNU Makeはほとんど使用可能な関数型プログラミング言語ですが、大規模なプログラミングには適していません。特に、名前空間、モジュール、またはそれぞれからピースを分離するのに役立つような名前空間はありませんその他考えられるほどではありませんが、これにより問題が発生する可能性があります。

私たちの古い再帰的なmakefileシステムに対する主な勝利は次のとおりです。

  • fastです。ツリー全体(2kディレクトリ、20kファイル)をチェックし、それが最新であるか、コンパイルを開始するかを決定するには、約2秒かかります。古い再帰的なものは何もしないのに1分以上かかります。
  • 依存関係を正しく処理します。私たちの古いシステムは、サブディレクトリの作成などに依存していました。ミラーの論文を読んで期待するように、ツリー全体を単一のエンティティとして扱うことは、この問題に取り組むための正しい方法です。
  • 私たちが注ぎ込んだすべてのハードワークの後に、サポートされているすべてのシステムに移植可能です。すごいかっこいい。
  • 抽象化システムにより、非常に簡潔なmakefileを作成できます。ライブラリのみを定義する典型的なサブディレクトリは、たった2行です。 1行はライブラリの名前を示し、もう1行はこれが依存するライブラリを示します。

上記リストの最後の項目について。ビルドシステム内に一種のマクロ拡張機能を実装することになりました。サブディレクトリmakefileは、プログラム、サブディレクトリ、ライブラリ、およびその他の一般的なものをPROGRAMS、SUBDIRS、LIBSなどの変数にリストします。次に、これらのそれぞれを「実際の」に展開しますGNUルールを作成します。これにより、名前空間の問題の多くを回避することができます。たとえば、私たちのシステムでは、同じソースファイルを複数持つことは問題ありません名前、そこに問題はありません。

いずれにせよ、これは結局大変な作業でした。コードでSConsまたは同様の機能を使用できる場合は、まずそれを確認することをお勧めします。

52
Ville Laurikari

RMCHペーパーを読んだ後、私は、当時取り組んでいた小さなプロジェクト用の適切な非再帰的なMakefileを書くことを目標に着手しました。終了後、ビルドする最終的なターゲット、ターゲットの種類(ライブラリや実行可能ファイルなど)を非常に簡単かつ簡潔に伝えるために使用できる汎用のMakefile "フレームワーク"を作成できるはずであることに気付きました)およびそれらを作成するためにコンパイルする必要があるソースファイル。

数回繰り返した後、最終的にそれだけを作成しました:約150行の単一のボイラープレートMakefile GNU変更を必要としない構文を作成します-使用したいあらゆる種類のプロジェクトで機能します各ソースファイルに正確なコンパイルフラグ(必要な場合)と各実行可能ファイルに正確なリンカーフラグを指定するのに十分な粒度で、さまざまなタイプの複数のターゲットを構築するのに十分な柔軟性があります。プロジェクトごとに、必要なのはこれと同じようなビットを含む小さな個別のMakefileを使用します。

TARGET := foo

TGT_LDLIBS := -lbar

SOURCES := foo.c baz.cpp

SRC_CFLAGS   := -std=c99
SRC_CXXFLAGS := -fstrict-aliasing
SRC_INCDIRS  := inc /usr/local/include/bar

上記のようなプロジェクトのMakefileは、期待どおりに動作します。 "foo"という名前の実行可能ファイルをビルドし、foo.c(CFLAGS = -std = c99を使用)およびbaz.cpp(CXXFLAGS = -fstrict-aliasingを使用)をコンパイルします。 「./inc」と「/ usr/local/include/bar」を#include検索パスに追加し、「libbar」ライブラリを含む最終リンクを追加します。また、C++ソースファイルが存在し、Cリンカーの代わりにC++リンカーを使用することがわかっています。フレームワークを使用すると、この簡単な例でここに示されているものよりもはるかに多くを指定できます。

ボイラープレートMakefileは、指定されたターゲットを構築するために必要なすべてのルール構築と自動依存関係生成を行います。ビルドで生成されたすべてのファイルは、個別の出力ディレクトリ階層に配置されるため、ソースファイルと混ざり合うことはありません(これは、VPATHを使用せずに行われるため、同じ名前の複数のソースファイルを使用しても問題はありません)。

私は今、この同じMakefileを、自分が取り組んだ少なくとも20の異なるプロジェクトで(再)使用しました。このシステムについて私が最も気に入っている点(新しいプロジェクトのproper Makefileを簡単に作成できることを除いて)は次のとおりです。

  • fastです。それは事実上、何かが古くなっているかどうかを即座に知ることができます。
  • 100%信頼できる依存関係。並列ビルドが不思議なことに壊れる可能性はゼロであり、常にビルドされます正確にすべてを最新の状態に戻すために必要な最小値。
  • 私はnever完全なmakefileを再度書き換える必要があります:D

最後に、再帰的なmakeに固有の問題があるので、これを取り除くことができなかったと思います。欠陥のあるメイクファイルを何度も書き換えて、実際に正しく機能するものを作成しようとしても無駄でした。

29
Dan Moulding

Millerの論文の1つの論点を強調しておきます。異なるモジュール間の依存関係を手動で解決し始めて、ビルドの順序を確認するのに苦労した場合、ビルドシステムが最初に解決するために作成されたロジックを効果的に再実装しています。 信頼性の高い再帰的なmakeビルドシステムを構築することは非常に困難です。実際のプロジェクトには相互に依存する多くのパーツがあり、そのビルド順序は簡単に理解できないため、このタスクはビルドシステムに任せる必要があります。ただし、システムに関するグローバルな知識がある場合にのみ、その問題を解決できます。

さらに、再帰的なmakeビルドシステムは、複数のプロセッサ/コア上で同時にビルドする場合、バラバラになりがちです。これらのビルドシステムは単一のプロセッサで確実に動作するように見えるかもしれませんが、プロジェクトの並列ビルドを開始するまで、多くの依存関係の欠落は検出されません。私は、最大4つのプロセッサで動作する再帰的なmakeビルドシステムを使用してきましたが、2つのクアッドコアを搭載したマシンで突然クラッシュしました。次に、別の問題に直面していました:これらの同時実行の問題はデバッグがほぼ不可能であり、システム全体のフローチャートを描いて理解しました何が悪かったのか。

質問に戻ると、再帰的なmakeを使用する理由を考えるのは難しいと思います。非再帰的なランタイムパフォーマンスGNU Makeビルドシステムは打ち勝つことが難しく、かなり逆に、多くの再帰的なmakeシステムには深刻なパフォーマンスの問題があります(弱い並列ビルドのサポートも問題の一部です) ) paper があり、特定の(再帰的な)Makeビルドシステムを評価してSConsポートと比較しました。パフォーマンスの結果は、ビルドシステムが非常に標準的ではないため、代表的なものではありませんが、この特定のケースでは、SConsの移植は実際には高速でした。

結論:ソフトウェアビルドを制御するためにMakeを本当に使用したい場合は、非再帰的なMakeを使用してください。これにより、長期的にははるかに使いやすくなります。個人的に、私は使いやすさの理由からSConsを使用したいと思います(またはRake-基本的に、最新のスクリプト言語を使用し、暗黙的な依存関係のサポートがあるビルドシステム)。

18
Pankrat

ビルドシステム(GNU makeに基づく)を完全に再帰的でないものにする)を作成する前の仕事で中途半端な試みをしましたが、いくつかの問題が発生しました:

  • アーティファクト(つまり、ビルドされたライブラリと実行可能ファイル)は、ソースを複数のディレクトリに分散させ、vpathを使用してそれらを見つけました
  • 同じ名前のいくつかのソースファイルが異なるディレクトリに存在しました
  • 複数のソースファイルがアーティファクト間で共有され、多くの場合、異なるコンパイラフラグでコンパイルされました
  • 成果物ごとにコンパイラフラグや最適化設定などが異なることがよくありました。

GNU makeの非再帰的使用を簡素化するmakeの1つの機能は、ターゲット固有の変数値)です。

foo: FOO=banana
bar: FOO=orange

これは、ターゲット「foo」を構築すると$(FOO)は「banana」に展開されますが、ターゲット「bar」を構築すると$(FOO)は「オレンジ」に展開されます。

これの1つの制限は、ターゲット固有のVPATH定義を持つことが不可能であることです。つまり、ターゲットごとに個別にVPATHを一意に定義する方法はありません。これは、正しいソースファイルを見つけるために必要でした。

GNU非再帰性をサポートするために必要なmakeの主な欠落機能は、namespaces。特定の変数を使用して名前空間を「シミュレート」できますが、実際に必要なのは、ローカルスコープを使用してサブディレクトリにMakefileを含めることができることです。

編集:GNU makeのもう1つの非常に便利な(そしてしばしばあまり使用されない)機能は、マクロ展開機能です(たとえば、 eval 関数を参照)) 。これは、ルール/目標が似ているが、通常のGNU make構文を使用して表現できない方法が異なるターゲットがいくつかある場合に非常に役立ちます。

10
JesperE

参照した記事の説明に同意しますが、これをすべて行い、まだ使いやすい優れたテンプレートを見つけるのに長い時間がかかりました。

現在、私は小さな研究プロジェクトに取り組んでおり、継続的な統合を実験しています。 pcで自動的に単体テストを実行し、(埋め込まれた)ターゲットでシステムテストを実行します。これは簡単なことではなく、私は良い解決策を探しました。 makeがまだポータブルマルチプラットフォームビルドに適した選択肢であることがわかったので、最終的に http://code.google.com/p/nonrec-make で良い出発点を見つけました。

これは本当に安心した。今私のメイクファイルは

  • 非常に簡単に変更できます(makeの知識が限られている場合でも)
  • コンパイルが速い
  • (.h)の依存関係を簡単にチェック

私は確かに次の(大きな)プロジェクトにも使用します(C/C++を想定)

9
Adriaan

UNIXライクなシステム(Macを含む)での使用を目的とした、中規模の1つのC++プロジェクト用の非再帰的なmakeシステムを開発しました。このプロジェクトのコードはすべて、src /ディレクトリをルートとするディレクトリツリーにあります。次のように、作業ディレクトリをルートとするディレクトリツリーのすべてのソースファイルをコンパイルするために、最上位のsrc /ディレクトリの任意のサブディレクトリから「make all」と入力できる非再帰的なシステムを作成したいと思いました。再帰的なmakeシステム。私の解決策は私が見た他の解決策とは少し異なるようですので、ここでそれを説明し、反応が得られるかどうかを確認したいと思います。

私の解決策の主な要素は次のとおりです:

1)src /ツリーの各ディレクトリには、sources.mkという名前のファイルがあります。このような各ファイルは、ディレクトリをルートとするツリー内のすべてのソースファイルをリストするmakefile変数を定義します。この変数の名前は[ディレクトリ] _SRCSの形式です。[ディレクトリ]は、トップレベルのsrc /ディレクトリからそのディレクトリへのパスの正規化された形式を表し、バックスラッシュはアンダースコアに置き換えられます。たとえば、ファイルsrc/util/param/sources.mkは、src/util/paramとそのサブディレクトリ(存在する場合)内のすべてのソースファイルのリストを含むutil_param_SRCSという名前の変数を定義します。各sources.mkファイルは、対応するオブジェクトファイル* .oターゲットのリストを含む[ディレクトリ] _OBJSという名前の変数も定義します。サブディレクトリを含む各ディレクトリでは、sources.mkに各サブディレクトリのsources.mkファイルが含まれ、[サブディレクトリ] _SRCS変数を連結して独自の[ディレクトリ] _SRCS変数を作成します。

2)すべてのパスは、src /ディレクトリが変数$(SRC_DIR)で表される絶対パスとしてsources.mkファイルで表されます。たとえば、ファイルsrc/util/param/sources.mkでは、ファイルsrc/util/param/Componenent.cppは$(SRC_DIR)/util/param/Component.cppとしてリストされます。 $(SRC_DIR)の値がsources.mkファイルに設定されていません。

3)各ディレクトリにはMakefileも含まれています。すべてのMakefileには、変数$(SRC_DIR)の値をルートsrc /ディレクトリーへの絶対パスに設定するグローバル構成ファイルが含まれています。これは、依存関係とターゲットのパスを同じ方法で解釈しながら、必要に応じてソースツリー全体を移動できるようにする複数のディレクトリに複数のメイクファイルを作成する最も簡単な方法であると思われたため、絶対パスのシンボリック形式の使用を選択しました、1つのファイルの$(SRC_DIR)の値を変更する。この値は、パッケージがgitリポジトリからダウンロードまたは複製されたとき、またはソースツリー全体が移動されたときに、ユーザーに実行を指示する単純なスクリプトによって自動的に設定されます。

4)各ディレクトリのmakefileには、そのディレクトリのsources.mkファイルが含まれています。そのような各Makefileの「すべて」のターゲットは、そのディレクトリの[directory] ​​_OBJSファイルを依存関係としてリストするため、そのディレクトリとそのサブディレクトリ内のすべてのソースファイルのコンパイルが必要です。

5)* .cppファイルをコンパイルするためのルールは、ここで説明するように、コンパイルの副作用として* .dのサフィックスが付いた各ソースファイルの依存関係ファイルを作成します。 http:// mad-scientist。 net/make/autodep.html 。 -Mオプションを使用して、依存関係の生成にgccコンパイラーを使用することを選択しました。他のコンパイラを使用してソースファイルをコンパイルする場合でも、依存関係の生成にgccを使用します。これは、ほとんどの場合、UNIXライクなシステムでgccが使用可能であり、ビルドシステムのこの部分の標準化に役立つためです。別のコンパイラを使用して、実際にソースファイルをコンパイルできます。

6)_OBJSおよび_SRCS変数内のすべてのファイルに絶対パスを使用するには、相対パスでファイルを作成するgccによって生成された依存関係ファイルを編集するスクリプトを作成する必要がありました。 pythonスクリプトをこの目的で作成しましたが、別の人がsedを使用した可能性があります。結果の依存関係ファイルの依存関係のパスは、文字どおりの絶対パスです。依存関係ファイルはこのコンテキストでは問題ありません。 (sources.mkファイルとは異なり)パッケージの一部として配布されるのではなく、ローカルで生成されます。

7)各ディレクタのMakefileには、同じディレクトリのsources.mkファイルが含まれ、すべてのソースファイルの依存関係ファイルを含めようとする「-include $([directory] ​​_OBJS:.o = .d)」という行が含まれています上記のURLで説明されているように、ディレクトリとそのサブディレクトリ内。

これと、「make all」を任意のディレクトリから呼び出せるようにする他のスキームの主な違いは、絶対パスを使用して、Makeが異なるディレクトリから呼び出されたときに同じパスを一貫して解釈できるようにすることです。これらのパスがトップレベルのソースディレクトリを表す変数を使用して表現されている限り、ソースツリーの移動を妨げることはなく、同じ目標を達成するいくつかの代替方法よりも簡単です。

現在、このプロジェクトの私のシステムは常に「インプレース」ビルドを実行しています。各ソースファイルをコンパイルして生成されたオブジェクトファイルは、ソースファイルと同じディレクトリに配置されています。 src /ディレクトリへの絶対パスを変数の式でビルドディレクトリを表す変数$(BUILD_DIR)に置き換えるようにgcc依存ファイルを編集するスクリプトを変更することにより、インプレースビルドを有効にするのは簡単です。各オブジェクトファイルのルールのオブジェクトファイルターゲット。

これまでのところ、このシステムは使いやすく、保守も簡単です。必要なメイクファイルの断片は短く、共同編集者が理解しやすいものです。

このシステムを開発したプロジェクトは、完全に自己完結型のANSI C++で記述されており、外部依存関係はありません。この種の自家製の非再帰的なmakefileシステムは、自己完結型で移植性の高いコードに適したオプションだと思います。ただし、CMakeやgnu autotoolsなどのより強力なビルドシステムを検討します。ただし、外部プログラムやライブラリ、または非標準のオペレーティングシステムの機能に重要な依存関係があるプロジェクトについては。

7
D Morse

私は少なくとも1つの大規模プロジェクト( [〜#〜] root [〜#〜] )を知っています。これは [PowerPointリンク]を使用して宣伝します 再帰的メイクで説明されているメカニズム有害と見なされます。フレームワークは100万行のコードを超え、非常にスマートにコンパイルされます。


そしてもちろん、私がそのdoを使用して作業するすべての大規模なプロジェクトは、再帰的なmakeを使用するとコンパイルが非常に遅くなります。 ::はぁ::

7
dmckee

私はあまり良くない非再帰的なmakeビルドシステムを書いて、それ以来 Pd-extended というプロジェクト用の非常にクリーンなモジュール式の再帰的なmakeビルドシステムを作成しました。基本的には、たくさんのライブラリが含まれているスクリプト言語のようなものです。今はAndroidの非再帰システムも使用しているので、それがこのトピックに関する私の考えのコンテキストです。

2つのパフォーマンスの違いについてはあまり説明できません。フルビルドはビルドサーバーでのみ行われるため、あまり注意を払っていません。私は通常、コア言語または特定のライブラリのどちらかで作業しているので、パッケージ全体のそのサブセットを構築することにのみ関心があります。再帰的なmakeテクニックには、ビルドシステムをスタンドアロンにするだけでなく、より大きな全体に統合するという大きな利点があります。ライブラリが統合されているか、外部の作成者によって作成されているかに関係なく、すべてのライブラリに1つのビルドシステムを使用したいので、これは私たちにとって重要です。

現在、カスタムバージョンのAndroid internalsの構築に取り組んでいます。たとえば、SQLCipher暗号化sqliteに基づくAndroidのSQLiteクラスのバージョンです。そのため、再帰的でないAndroid.mkを記述する必要がありますsqliteのようなあらゆる種類の奇妙なビルドシステムをラップしているファイル。Android.mkが任意のスクリプトを実行する方法を理解することはできませんが、これは、私の経験から、従来の再帰的なmakeシステムでは簡単です。