Accio 依存関係マネージャー

ここ数日、「標準ライブラリに何を入れるべきか」という投稿が多数投稿されていることに気付いたかもしれません。これらの投稿は、Reddit でかなりの数のコメントを生成し、Slack で議論しています。より多くの人が参加してくれることを願っています。

結論としては、STL が何であるか、または STL がどうあるべきかについて、私たち全員が意見を異にすることにほとんど同意しているということです。 C++ が何であるかについてさえ、私たちが同意できるかどうかはわかりません。これは、C++ について知っておくべきことをすべて教えてくれます。

しかし、これらの議論から発せられる共通の感情があります。普遍的で強力で使いやすいもの。

その間、C++ 委員会は調査を行っています (C++ 開発者なら受けるべきです)。「魔法の杖があったら C++ をどのように変更しますか?」

私たちの魔法の力を使って、空中から依存関係マネージャーを具現化できるのではないでしょうか?それはとても素晴らしいことです。

最初の問題は、依存関係マネージャーがどのように見え、どのように振る舞うかについて非常に正確なメンタル モデルを形成できる場合にのみ、私たちの呪文が機能することです。

架空の依存関係マネージャー

CppSlack で議論しているうちに、人々は依存関係マネージャーが何であるかを知らない可能性があることが明らかになりました。または、それが何であるか、またはあるべきかについてさまざまな意見を持っています.

一部の人にとっては、特定のプロジェクトでヘッダーを管理する方法や、不要なヘッダーを削除するためのツールです。 Arch ユーザーは、pacmac だけで十分だと教えてくれました。

私にとって、依存関係マネージャーは、プロジェクトに外部依存関係を含めることができるツールです。たった 1 つの簡単なコマンドで。

しかし…依存関係とは何ですか?ライブラリ、またはヘッダーのみのライブラリの場合があります。しかし、Qt を例にとってみましょう。これには、moc、rcc、uic、およびその他のサポート バイナリが多数含まれています。したがって、依存関係はツール/バイナリでもある可能性があります。しかし、プロジェクトが llvm 7 に依存している場合、それは依存関係である必要がありますか?多分 ?つまり、なぜですか?もちろん、利用可能な場合は、システム提供のライブラリとパッケージを優先する必要があります。

タイタス ウィンターズが指摘したように、「パッケージ マネージャー」について言えば、 」、人々はツールがバイナリを扱っていると想定するかもしれませんが、それはおそらく私たちが望んでいるものではありません(これについては後で詳しく説明します)。したがって、依存関係という用語 確かに「パッケージ」よりも適切です。

システム パッケージ マネージャー」も混同しないでください。 」を「依存関係マネージャー」で。

「*システム パッケージ マネージャー」* ( apt など) は、開発プロジェクトの構成要素を管理するために使用するものではなく、ツールとアプリケーションのインストール用に予約する必要があります。また、「依存関係マネージャー」は、システム全体にインストールしようとするべきではありません。 Linux ディストリビューション (またはbrew、chocolatey など) には独自のやり方があり、それを妨害するべきではありません。

当然のことながら、どの時点においても、ツールがルート アクセスを要求する必要はありません。一度もない。

権威ある依存関係マネージャー

神話上のツールがどのように機能するかはまだわかりませんが、信頼できる必要があることはわかっています.さまざまなプロジェクトがさまざまな互換性のないシステムを使用することになり、プロジェクトに依存関係を簡単に含めるという最初の前提が満たされないため、複数のシステムを用意する余裕はありません。パッケージ マネージャーは 1 つだけ必要です。

それを管理できるエンティティはほとんどありません。理想的には、C++ 委員会 (その特権は言語の設計を超えない) と主要な業界関係者 (お互いに同意しないかもしれません) と既存のツールの保守担当者による協調的な取り組みである必要があると思います。夢見ることはたくさんあります。しかし、誰もが独自のツールを展開し続ければ、私は決して遠くに行くことはないと思います.

すべての人にとって完璧である必要はないことに注意してください。しかし、大多数のユーザーは、それで十分であることに同意し、ライブラリを公開する価値を理解する必要があります.

これは、システムを集中化する必要があることを必ずしも意味するものではありません。集中型システムには、それを維持するための専任のエンティティが必要であり、それにはお金が必要です。また、信頼性が低く、多くの企業が従業員に提供していないインターネットへのアクセスが必要になります.また、時の試練に耐えられないかもしれません。

人々はまた、内部プロジェクト用に全社的なリポジトリを持ちたいと思うでしょう。

したがって、私たちの魔法の依存関係マネージャーが提供します

    <リ>

    リポジトリの簡単なセットアップ

    <リ>

    ミラーの簡単なセットアップ

しかし、人々がライブラリを簡単に見つけて発見できることも非常に重要であるため、一元化されたインデックスが必要です。理想的には、そのインデックスには、図書館の説明と、おそらくその人気を証明するいくつかの統計を読むことができる洗練されたウェブサイトが付随する.おそらく、カテゴリまたはタグ システム、ライセンス情報、サポートされているコンパイラ/システム/アーキテクチャ…

不必要に思えるかもしれませんが、言語をよりアクセスしやすくするようなものです.

健全な命名スキーム

理想的には、競合する識別子を持つ 2 つの C++ パッケージはありません。最初にできることは、Github のように、組織名を使用した名前空間パッケージ識別子です。したがって、bjarne/foo は johndoe/foo とは異なります。

しかし、ネットワーク全体に 1 つの bjarne があることをどのように保証するのでしょうか?ネットワーク全体で ID の一意性を確保する必要があるのではないでしょうか?これは難しいかもしれません。

それとも、[email protected]/foo と [email protected]/foo が 2 つの異なるパッケージになるように、パッケージ発行者の ID をサーバーに関連付ける必要があるのでしょうか?

とにかく、名前だけでパッケージを識別するのは合理的ではないと思います。多くのプロジェクトが同じ名前を使用しており、名前を付けるのは難しい.

GitHub の時代には、人々は衝突を恐れずに自由にプロジェクトをフォークできるべきです。 パッケージ優先のシステムだと思います apt-cache ポリシーのようなものは、同じプロジェクトの複数のフォークで作業したい場合があるため、依存関係マネージャーにとって扱いにくいものです。

健全なバージョン管理スキーム

https://semver.org/ と同じもの — 別名メジャー バージョンの変更は、API の破損を示します。バージョン番号の意味はパッケージ全体で一貫しています。

特別なバージョン (ブランチ名、ナイトリー) のサポートを提供する必要があるかもしれません。

簡単でした。

真面目な依存関係管理者

他の言語/フレームワークによって提供されるツールの大きな問題は、セキュリティと信頼性を常に十分に考慮していないことだと思います.

その結果、依存関係マネージャーの評判がやや悪くなります。

明らかに、公開後にパッケージを変更することはできません。したがって、署名する必要があります。残念なことに、これをまだ述べておく必要がありますが、既存のツールの中には、署名されていないパッケージを安全でない接続で配信するものがあります。

私たちの魔法の杖が強力であることは幸いです。なぜなら、依存関係発行者の身元を確認する方法も必要だからです。 herb は bjarne のように物事をプッシュできないはずです。既存のパッケージ マネージャーを見ると、繰り返し発生する問題はネーム スクワッティングのようです。ID スクワッティングも問題になると予想するのは妥当です。分散型ネットワークでの単一アイデンティティの強制に戻ります。 PGP がその問題を解決してくれるかもしれません

セキュリティはシステムのプロパティである必要があります。オプトインまたはオプトアウトのいずれかではありません。そのため、使いやすいことが重要です。言うは易し、実際に行うのは非常に難しい。これが、GitHub リポジトリではなくブログ投稿である理由です。

依存関係マネージャーを介して直接セキュリティの問題を公開して通知を受ける方法があるかどうか疑問に思っていました。外部の解決策は存在しますが、既知の脆弱性がたとえば暗号化コードやネットワーク コードで見つかった場合、できるだけ早くユーザーに通知することが非常に重要です。

システムは追加のみである必要があります。プッシュされた依存関係/パッケージをネットワークから削除するプロトコルはありません。

何かが (パブリック リポジトリで) オンラインになるとすぐに、誰かがそれに依存していると想定する必要があるため、削除することはできません。もちろん、特定のサーバーが何かを削除しないよう強制することはできないため、最善の解決策はミラーリング プロトコルで削除を防止することです。ストレージは安価ですが、依存関係の連鎖を断ち切ると簡単に数百万ドルの費用がかかります。人々が本当に削除を望んでいるのであれば、ファイルが実際に削除されるずっと前に減価償却の通知を伴う長いプロセスになるはずです。

分散化されているため、このツールは、単一のエンティティの消滅やサーバーの損失に対して回復力があることが期待されます.

最後に、パッケージを監査できます。これにより、次のポイントに進みます。

ソースベースの依存関係マネージャー

システムはソースのみを配布する必要があります。

前述したように、バイナリは監査できないため、セキュリティ リスクを表しています。ほとんどの場合、これは理論的および心理的な問題であることを認識しています。ソース配布されたパッケージのほとんどはいずれにせよ監査されないからです。ただし、できることが重要です

ソースの保存も安価です。最も重要なことは、特定のソースに対して、依存するバイナリ バージョンがほぼ無限に存在することです

    <リ>

    lib C バージョン

    <リ>

    オペレーティング システム、CPU、アーキテクチャ、場合によっては CPU 世代、命令セット….

    <リ>

    コンパイラ / コンパイラ バージョン

    <リ>

    たくさんのコンパイラ フラグ

もちろん、これはすべての依存関係に推移的に適用されます。

また、一般的なケースで、特定の 2 つのバイナリ アーティファクトが相互に互換性があることを説明することも困難です。正式な ABI 仕様と、おそらくコンパイラ フラグの効果に関する正式な説明の両方が必要になると思います。私はそれが起こっているとは思わない

でもコンパイル時間は遅いですか?

それは本当かもしれませんが、問題は直交しています。ローカルまたはリモートのキャッシュは、遅いビルド時間を解決するためのより優れた安全な方法です。概念がより一般的になるにつれて (そうなることを願っています)、いずれにせよ実装ファイルに入れるものはほとんどなくなります。たぶん、コンパイラはもっと速くできるかもしれません (コンパイラの作者:殺し屋を送らないでください)。

依存関係とは

依存関係は、おそらく、ソース コードといくつかのメタデータ (依存関係のリストを含む) を含む、署名された tarball です。そして、私が言ったように、その依存関係はどこかのミラー サーバーに保存されます。

具体的には *GitHub へのリンクではありません。皮肉なことに、GitHub は単一障害点です。また、最近の npm の問題で証明されているように、不変ではありません。

依存関係には、そのビルド スクリプトも付属しています…これが主な問題につながります。

依存関係の構築

信頼できるビルド システムが必要になると主張する人もいます。そして少年、それは素晴らしいことでしょうか。ただし、人々は通常 cmake を候補として挙げますが、cmake はひどいものです。

ですから、完璧な魔法のビルド システムがどのようなものかを考えるとき、より適切な質問は次のようなものだと思います:必要ですか?

ビルド システムがどれほど複雑であっても、最終的には、いくつかのパラメーターを取得してアーティファクトを作成するコマンドです。

    <リ>

    依存関係のビルド システムを呼び出す方法

    <リ>

    そのビルド システムにコンパイラ フラグを渡す方法。つまり、あなたが取り組んでいる最上位プロジェクトが、デバッグ/最適化レベル、警告などを含むすべての依存関係のフラグを決定するということです

    <リ>

    ビルド成果物を依存関係識別子にマップする方法。ライブラリとモジュールに沿って、依存関係が (残念ながら) マクロをエクスポートし、場合によってはコンパイラ フラグをエクスポートする可能性があることに注意してください。

制御された環境でこれを達成するのは簡単ですが、一般的に解決すべき問題がいくつかあります:

    <リ>

    依存関係マネージャーのライブラリよりも、システムで利用可能なライブラリを優先したい場合があります。残念ながら、名前とバージョン番号の両方が一致しない場合があります。また、通常、さまざまな Linux ディストリビューションが同じライブラリに異なる名前を使用しています。

    <リ>

    依存関係 (直接または推移的) を構成して、静的または動的にリンクするか、コンパイル時の動作を有効にする必要がある場合があります。

    <リ>

    ビルド スクリプトは正常である必要があります。つまり、構成固有のオプション (警告、最適化、デバッグ、サニタイザーなど) を指定しないことが必要です。

また、単一のビルド システムを使用すると、並列処理が向上するため、高速になる可能性があるという懸念もあります。しかしねえ。依存関係マネージャーがないと、いつも失われる時間を考えてみてください!

そしてもちろん、依存関係チェーンに関与するすべてのビルド システム (またはメタ ビルド システム) が実際に依存関係管理ツールをサポートする必要があります (つまり、アーティファクト / フラグ / などのリストをバブルアップします)。幸いなことに、私たちの公式の依存関係マネージャーは十分に人気があるため、ツール ベンダーはそれをサポートするインセンティブを持っています。魔法。

どのように機能しますか?

ワークフローから始めてソフトウェアを設計しようとします。では、そうしましょう。

C++ には複雑なビルド システムがあります。そして、それを修正することは範囲外であると判断しました (そうなる可能性があると仮定して)。

したがって、ビルドシステムが必要です。ひどいことをして、例として cmake を使用します。申し訳ありません。

Boost.Asio (システムにインストール済み) を使用するには、次のようにします。

find_package(Boost 1.66 COMPONENTS system)
target_include_directories(foo ${Boost_INCLUDE_DIR})
add_executable(foo foo.cpp)
target_link_libraries(foo ${Boost_LIBRARIES})

これは正気じゃない!でも気にしないで。同じ構文を使用して、次のことができます

find_cpp_dependency(BOOST_ASIO "boost/boost.asio" VERSION 1.66)
add_executable(foo foo.cpp)
target_link_library(foo BOOST_ASIO)

cmake を実行すると:

    <リ>

    ローカル マシンのキャッシュで Boost.Asio のコピーを探します。

    <リ>

    リモートサーバーでその依存関係を探します。ツールにはミラーのリストが組み込まれている必要があり、依存関係を取得するために最も近い/最速のノードを選択する必要があります。ユーザー、特に初心者は、依存関係がどこから来るのか気にする必要はありません

    <リ>

    依存関係を推移的に検索/ダウンロード

    <リ>

    いつものように、CMake はコンパイラ フラグのリストを計算する必要があります

    <リ>

    次に、すべての依存関係が構築されます。したがって、bjam -with-system toolset=clang (bjam は boost によって使用されるビルド ツール) を呼び出す可能性があります。

    <リ>

    ビルドが成功し、コンパイル フラグのリストが pkgconfig によく似た形式で出力されることを願っています

    <リ>

    これらのフラグは cmake によって抽出され、asio に応じてターゲットのビルド (この場合は foo) に転送されます。

それよりも難しいことはありません。単純な場合ではありません。誰かがスレッドをサポートしないで asio をビルドしたいと思うかもしれません。その場合、追加の引数を依存ビルドに渡します:

find_cpp_dependency(BOOST_ASIO "boost/boost.asio"
 VERSION 1.66
 ARGS --disable-threads )

Python のやり方

Python では、pip install foo を使用してパッケージをインストールできます。次に、 import foo を使用してコード内のモジュールをインポートします。

モジュール名とパッケージ名の間には直接の同等性がないことに注意してください。たとえば、すばらしい BeautifulSoup ライブラリは pip install beautifulsoup4 によってインストールされ、import bs4 によってインポートされます。

その振る舞いは一致する可能性があります。ワークフローは次のようになります:

    <リ>

    ユーザーは idm get foo を実行して依存関係をダウンロードします。ここで、idm は 架空の依存関係マネージャー を表します。 .この時点では、何もコンパイルしていません。いくつかのソース ファイルをダウンロードして固定の場所に置いているだけです。

    <リ>

    ダウンロードしたライブラリのメタデータには、モジュールのリストが記述されています。これを使用して、マッピング モジュール名 -> 依存関係名を作成できます。

    <リ>

    これで、次の CMakeLists.txt を想像できます

set(SCAN_MODULES ON)
add_executable(foo foo.cpp)

そして、次の foo.cpp

import fmt.fmt
int main () {
 fmt::print(u8"Hello imaginary world {}!", u8'🦄');
}

ビルド プロセスは次のようになります。

# cmake .

-- Scanning foo.cpp
-- idm search-module fmt
-- idm get fmtlib/fmmt 4.10.05
Download http://deps.isocpp.orgs/dist/fmtlib/fmt-4.10.05.tar
Done.

# make

clang++ --precompile \
 $IDM_DIR/fmtlib/fmt/4.10.05/fmt.cppm -o .deps/modules/fmt.pcm
clang++ -o foo foo.cpp -fmodule-file=.deps/modules/fmt.pcm

もちろん、それが機能するためには、モジュール名がライブラリ間で競合してはならないため、モジュール名の最初のコンポーネントが組織名であるなど、モジュールに一貫した命名スキームを適用する必要があります

import boost.spirit //package boost/boost-spirit
import catch.catch2.main //package catch/catch2
import nlohmann.json //package nlohmann/json

これはおそらくうまくいきませんよね?しかし、このように見てください。これらのライブラリはすべてヘッダーのみであり、Python と大きく異なる動作はしません。モジュール ファイルは pyc であり、py ファイルはヘッダーであると考えることさえできます。

最初のスキャンの後、依存関係を説明するファイルを取得し、使用するバージョンを変更できます。

要点は、依存関係はプロジェクトに簡単に追加できるようにし、単一の場所に記述する必要があるということです。つまり、使用するビルド システムまたは他のファイルのいずれかであり、両方ではありません。

5 ページの「はじめに」を読む必要はありません。 」 ガイド。特に、標準に含まれるライブラリを減らしながら、より高品質のライブラリが必要な場合.

もちろん、お気に入りの IDE でインストールされていないモジュール名を自動補完するなど、さらに先に進むこともできます。

まとめ

私の見方では、依存関係マネージャーは次のようになるはずです:

    <リ>

    分散化

    <リ>

    エンド ユーザーが URL/URI を処理する必要がないように、一元化されている、または一元化されていると思われる検出および集約機能を備えている。

    <リ>

    ノードの損失の影響を受けず、堅牢で透過的にミラーリングされます

    <リ>

    強力なセキュリティ基盤に基づく

    <リ>

    システムを構築する直交

    <リ>

    バイナリではなく、ソースのみを処理

    <リ>

    さまざまなユースケースで使いやすい。

これで 2 つ目の問題が発生します