原題:The Illusion of Simplicity たぶん君のタイプ または 表面上では、これらのコマンドはほぼ魔法のように感じます:あなたはEnterを押すと、突然あなたのコードがコンパイルされ、リンクされ、時には実行されます。 go build go run Go がどのようにコードを構築、実行、キャッシングするかを理解することは、単なる学術的な練習ではありません。これは、なぜ増加型ビルドが急速に進むのか、なぜ CI パイプラインが一貫して動作するのか、そして、時には見た目に微妙な変化が完全なリコールを引き起こす可能性があるのかを説明します。 最後に、あなたは明確なイメージを持っています: How Go resolves dependencies and structures your code into packages. コードをパッケージに構成する方法 コンパイルとリンクが舞台の裏でどのように働くか、 なぜ、ビルドキャッシュがそんなに信頼できるのか、そして、 go build や go run を入力すると実際に何が起こりますか? なぜGo が「単なる仕事」を構築するのか、あるいは何故あなたの臨時的な バイナリはほぼ瞬間的に見えるが、これは人間と機械の両方にとって、点を結びつける深いダイビングである。 go run 「Go Toolchain Mental Model」 最初の目で見ると、 で、 そして、 見た目は別々のコマンドのように、それぞれ独自の行動を持っています。 各Goコマンドは予測可能な順序を通過します:それはモジュールをロードし、パッケージ依存性を解決し、パッケージをコンパイルし、オプション的にそれらを実行可能なものにリンクし、時には結果を実行します。 構築のメカニズムではない。 go build go run go test same underlying pipeline what happens to the final artifact 内部化するための重要な概念は、 パッケージ内のすべての .go ファイルは集合的に処理され、パッケージ自体はコンパイラとキャッシュ トラックを構築するユニットです。 Go builds packages, not individual files パッケージ内のいずれかのファイルを変更すると、パッケージ全体の再構築が起こります。 パッケージは、キャッシュと並列コンパイルの自然な境界になります。 小型、焦点を当てたパッケージは、コンパイラがより多くのキャッシュされた結果を再利用できるため、大規模なコードベースでよりスケール化する傾向があります。 パイプラインは概念的にシンプルですが、高度に最適化されています:Go はリコールが必要なものと再利用可能なものを正確に知っており、そのため、増加型ビルドはほぼ瞬時に感じられます。 : コンパイル、リンク、キャッシュ、および実行をオーケストラ化するので、あなたはほとんど細部について心配する必要はありません。 そして 魔法のような感覚が止まり、予測可能な意味をつくり始める。 smart coordinator go build go run から 2 構築計画 go.mod トップ > MOD Go がソース ファイルに触れる前に、何を構築するか、どの順序で構築するかを把握する必要があります。 そして ファイル. これらのファイルは、 すべてのモジュールの正確なバージョンとともに、プロジェクトの完全な依存性の木です。これらのファイルを読み取ると、Go ツールチェーンは、どのパッケージがあなたのビルドの一部であり、どの外部コードを取得し、検証し、組み込むかを正確に知っています。 go.mod go.sum module graph モジュールグラフがロードされると、Go は各パッケージを評価し、そのソースセットを決定します。これはパッケージに属するすべての .go ファイルを含み、build タグ、オペレーティング システム、アーキテクチャ、および指定した制約によってフィルタリングされます。この評価の後、コンパイラは実際に処理する必要があるコードを知ります。 異なるマシンで実行されるコマンドは、同じモジュールバージョンを仮定して同一の結果を生成します。 go build Modern Goの重要な側面は、その役割です。 指令 in この指令は、モジュールが設計されている最低限のGoバージョンを宣言します. It influences several characteristics of the build: language semantics, compiler behavior, and even static analysis. 構築のいくつかの特徴:言語セマンティクス, compiler behavior, and even static analysis. 指令、言語セマンティクス、コンパイラの行動、およびチェックは異なる場合があります - ツールチェーンはコンパイル中にこれらを強制します. This is part of Go's focus on reproducerability, ensuring that your code behaves consistently across environments. go go.mod go この段階の終わりに、ツールチェーンは、 : それは、どのパッケージをコンパイルするか、どの順序で、どのファイルが各パッケージに属しているかを知っています. この情報を手元に持って、それは次のステップに移ります:パッケージをコンパイルし、それらをバイナリにリンクし、何も見逃さないか、または誤ってコンパイルされないことを確信しています。 complete, ordered build plan 実践におけるコピーとリンク Go がモジュール システムからビルド プランを取得すると、コードをマシンが実行できるものに変換し始めます。これはコンパイルとリンクの 2 つの異なる段階で起こります。これらの段階を理解することは、なぜ Go ビルドが速く、決定的で、スケーラブルであるかを理解するための鍵です。 コピーはパッケージごとに コピーする 各パッケージ - プロジェクトの一部であろうと、外部依存性であろうと - は独立したユニットとして扱われます。 これは、パッケージが最後のバージョン以来変更されていない場合、Go は、バージョンに依存する他のパッケージが再構築されている場合でも、それを完全に再構成することを省略することができます。 one package at a time intermediate artifacts Parallelism はこのパッケージ別アプローチのもう一つの利点です:コンパイラは依存グラフを知っているので、複数の独立パッケージを同時にコンパイルし、複数のコア CPU を完全に活用することができます。 リンクは選択的 コンパイルされたパッケージを1つの実行可能なパッケージに組み合わせるプロセスです。 図書館パッケージは決して独自にリンクされることはなく、他のパッケージのための単に再利用可能なアーティファクトとして存在します。 プロジェクトでは、Go は数十のパッケージをコンパイルするが、パッケージのいずれもメインではない場合にゼロのバイナリを作成する。 リンク only links main packages into binaries go build ./... Linking は、すべての依存性を 1 つの実行可能なものに統合し、シンボルを解消し、メタデータを埋め込むことを含むため、ビルドで最も高価なステップです。 バイナリーで終わるもの 最終的なバイナリは、単にコンパイルされたコード以上のものです。 メインからアクセスできるすべての依存パッケージ モジュールバージョンを含むメタデータの構築とコミット情報 ターゲットプラットフォームに最適化された機械レベルの指示 この組み合わせは、Go バイナリが自立し、再生可能である理由です:それらは外部のライブラリやランタイム環境に依存することなく実行するために必要なすべてを含みます。 タイトル:The Build Cache: The Center of Gravity Goのスピードと予測性の中心は、 各コンパイルパッケージ、各中間アーティファクト、あるいは一部のツール出力は、コンテンツアドレス化されたキャッシュに保存され、Go がビルド、コマンド、さらにはコマンドを介して作業を再利用できるようにします。 キャッシュがどのように機能するかを理解することは、大きなプロジェクトでさえ、なぜGoビルドがほぼ瞬時に感じられるのかを理解するために不可欠です。 build cache go run What the Cache Stores シングル ビルドキャッシュは、単にコンパイルされたバイナリ以上のものです。 ビルド グラフのすべてのパッケージのコンパイルされたパッケージアーティファクト (.a ファイル) 試験結果、キャッシュされた成功情報を含む go run or go test で実行するために必要な一時的なツール出力 キャッシュはディスク上に存続します(デフォルトでは )および完全に決定主義的であり、同じ入力でコンパイルされた同じパッケージが常に同じキャッシュ入力を生成することを意味します。 $GOCACHE コンテンツベースではなくタイムスタンプベース ファイルタイムスタンプに依存する伝統的なビルドシステムとは異なり、Go は キャッシュ キーを指定する. 各キャッシュ キーは次の機能です: content-based hashing ソースコードコンテンツ コンパイラバージョン Any Build Flag ターゲットプラットフォーム(GOOS/GOARCH) 関連する環境変数 このデザインは、ビルドが再生可能であることを保証し、タイムスタンプやファイルの順序などの無害な変更による偽のキャッシュ欠落を回避します。 Cache Invalidation 説明 強力なキャッシュでさえ、Go は時々パッケージを再構成します。 ソースコードの変更またはビルドタグの作成 コンパイラ フラッグまたは環境変数の変更 パッケージ内のファイルの再命名 Goのキャッシングシステムはスマートで、実際に再構築する必要があるものだけを再構築します. 小さな非セマンティックな変更でさえ、パッケージのビルドハッシュに影響を及ぼす場合でも、再構築を引き起こすことができますが、そうでなければ、キャッシュは暗示的に信頼されます。 なぜキャッシュは信頼できるのが安全なのか ビルドキャッシュは、透明かつ信頼性の高いように設計されています: 手動で削除する必要はめったにありません。 ゼロからの再構築は、同一のアーティファクトを生み出す go run, go test, and go build all leverage it consistently これが、Goの増加型ビルドが非常に速い理由です:コンパイラは必要以上に多くの作業を行うことはありません。開発者の視点から、それは魔法のように感じます。 □ 芸術品の制作 GO BUILD GO BUILD go build コマンドは、Go ツールチェーンのワークホースです。その作業は説明しやすいが、実行において複雑です。 何を理解する 実際には、その行動を予測し、一般的なサプライズを避けるのに役立ちます。 compile packages, link them if necessary, and produce a binary that is correct and reproducible go build ハンドルパッケージを構築する方法 How to build handles packages あなたが走るとき モジュールまたはパッケージで、ツールはまず、あなたのパッケージから生じた依存グラフを調べます。 グラフの各パッケージは、ビルドキャッシュに対してチェックされます: キャッシュにパッケージの有効なコンパイルされたアーティファクトが含まれている場合、Go はそれを再編集する代わりに再使用します。 go build go.mod なぜならGo , パッケージ内の 1 つのファイルに触れることは、パッケージ全体の再構築を引き起こすことができます。逆に、依存性が変更されていない場合、それは他のパッケージがそれに依存している場合でも再構築されません。 大規模なプロジェクトでも、大規模なプロジェクトでも operates at the package level incremental builds リンクと最終バイナリー 前述したように、 主要パッケージのための実行可能なパッケージのみを生成します。 ライブラリパッケージは中間のアーティファクトにコンパイルされますが、決して独自にリンクすることはありません。 主要パッケージをリンクするとき、Go はすべてのコンパイルされたパッケージを1つのバイナリに組み合わせます。 このプロセスはまた、以下を含む実行可能なパッケージにメタデータを組み込む: go build モジュールバージョン情報 COMMIT HASHES(利用可能な場合) プラットフォーム特有のビルドメタデータ デフォルトでは、バージョン制御詳細の含有は、 フラッグは、デフォルトで "auto" に設定され、リポジトリの文脈が許可する場合に VCS 情報を印刷します。 2 無視または 詳細は、ドキュメンタリーでご覧いただけます。 . -buildvcs -buildvcs=false -buildvcs=true ここ これにより、Go バイナリは自給自足し、高度に再生可能になり、依存性の欠如を心配することなく自信を持って展開することができます。 アーティストはどこへ行くのか(すみません 😀) デフォルトで、 現在のディレクトリに、パッケージの後に名前を付けたバイナリを書きます。 全くバイナリを作成しないで、パッケージとその依存性がコンパイルされていることを保証するだけです。 旗または使用 1回で複数のパッケージを作成する go build go build -o ./... Windows では、Executables は A 複数のメインパッケージを同時に構築する場合(例えば、 )無し Go は、現在のディレクトリにメインパッケージあたり 1 つのバイナリを書き込みます。 .exe ./cmd/... -o 予測可能で信頼性の高い建物 パッケージごとにコンパイル、キャッシュ、および選択的なリンクの組み合わせは、go build が予測可能であることを保証します。 構造は機械間で再現できる。 未変更のコードは不要に再構築されることはありません。 中間のアーティファクトは、構築時間を最適化するために再利用されます。 簡単に言うと、 コードを作成するだけでなく、 . go build orchestrating a deterministic pipeline that balances human convenience with machine efficiency ◎特別特権なしの快適性 走れ走れ 走れ走れ もし あなたが展開できるアーティファクトを生産するワークホースです。 多くの開発者はそれを「一歩でコンパイルして実行する」と考えていますが、それはそうではありません:キャップの下で、それは同じビルドシステムを活用しています。 , it is just optimized for convenience rather than artifact persistence. それは単にアーティファクトの持続性よりも便利のために最適化されています。 go build go run go build 何 実際は go run あなたがタイプ (またはファイルのリスト) Go は最初にパッケージとその依存性を評価します。 すべてのキャッシュされたコンパイルパッケージは再利用されるので、コンパイラは変更のないコードのために最小限の作業を行います。 . go run main.go go build links the main package into a temporary binary, executes it, and deletes the binary once the program finishes キャッチコピーの視点から、 これは、同じプログラムの繰り返しの呼び出しが、しばしば瞬時に感じられる理由を説明します:重いリフトはすでに完了しており、パッケージをリンクまたは変更するだけがコンパイルを引き起こす可能性があります。 go run なぜ 違った感じ go run 同じパイプラインを組み合わせると、 特定のシナリオで遅くなることがあります。 毎回一時的なバイナリを生成するので、すべての依存性がキャッシュされている場合でも、リンクは繰り返されます。 小型プログラムの場合、このオーバーヘッドは軽微ですが、大規模な依存性グラフを持つプロジェクトでは、目立つ可能性があります。 go run もう一つの違いは、 これは正確にポイントです:それは実行の容易さのためにバイナリの再利用を取引します. あなたはバイナリをどこに置くか、またはそれを何と呼ぶかを考える必要はありません。 go run does not leave a persistent artifact いつ Is the Right Tool - And When It Isn't は正しいツールですか? go run 理想的である: go run 迅速な実験やスクリプト ファイルシステムを混乱させることなく一度のプログラムを実行 小型プログラムのインタラクティブなテスト それは以下に適している。 生産構築または展開 繰り返しリンクがオーバーヘッドを追加する長期間のサーバー 持続的なバイナリのキャッシュがより効率的であるCIパイプライン この場合、推奨されたパターンは キャッシュ、再現性、そしてパフォーマンスを犠牲にすることなく永続的なアーティファクトの利点を提供します。 go build && ./binary 隠れた正しさ go test テスト行き THE コマンドは、同じ原則に基づいて作られ、 そして テストがビルドシステムとどのように相互作用するかを理解することは、なぜ一部のテストが即座に実行され、他のテストが再構築を引き起こすのか、そしてなぜGoのアプローチが迅速かつ予測可能であるのかを説明するのに役立ちます。 go test go build go run Compilation Reuse in Tests あなたが走るとき , Go First は、輸入されたパッケージを含むテストパッケージの依存度グラフを決定します。 同様に、同様に または これは、大規模なテスト スイートがほとんどすぐに実行を開始することができ、コンパイル作業のほとんどがすでに完了していることを意味します。 go test reused from the build cache go build go run 複数のパッケージが関与している場合でも、Go は実際に変更したパッケージのみを再構築します。パッケージごとにコンパイルとキャッシュの組み合わせにより、大規模なプロジェクトでさえ、増加テストが迅速に実行されます。 テスト結果 Caching 複製パッケージのキャッシュに加えて、Go also テストが通過し、その依存性や関連する旗が変更されていない場合、Goはテストを完全に再実行することを省略することができます。 caches test results テスト結果のキャッシュはパッケージリストモードでのみ適用されます(例えば、 または ホーム > ホーム > ホーム > ホーム > ( パッケージ args がない場合、キャッシュが無効になります。 go test . go test ./... go test この行動は、コントロールされることによって、 たとえば、旗、 実行結果に関係なく、実行結果が決まります。 テスト・ベンチマークの繰り返し is the idiomatic way to bypass cached results. 見る 詳細はこちら) -count go test -count=1 -count -count=1 文書化 テスト結果のキャッシュは、開発者の生産性とCIの効率性を向上させ、特にテストの範囲が広い大型プロジェクトの場合にも役立ちます。 . the system should avoid unnecessary work while preserving correctness Cache Invalidation in Test テストは自動的に再実行される場合があります: テストコード自体が変わりました。 テストの依存性が変わりました。 試験に影響を与える旗が変わりました。 Non-cacheable flags or changed files/env also invalidate reuse. キャッシュできないフラグや変更されたファイル/envも再利用を無効にします。 そうでないと、Go はキャッシュされた結果を信頼し、それが このアプローチは、不要な再構築によって引き起こされる「フラッキー」ビルドを減らし、盲目的な快適性よりも予測可能性を強調します。 deterministic and reproducible オプション Handy Snippets 以下は、いくつか有用な caching behavior を活用する invocations that leverage caching behavior: go test Fresh run: go test -count=1 ./... - 先ほど見たように、これはテスト結果のキャッシュを無効にします。 stress a test: go test -run '^TestFoo$' -count=100 ./pkg - runs TestFoo 100 times to check for flakiness. Bench Stability: go test -bench. -count=3 - すべてのベンチマークを3回実行して安定した測定を取得します。 なぜ開発者にとって重要なのか 開発者の視点から、ビルドキャッシングとテスト結果キャッシングの組み合わせにより、即時的で信頼性の高いワークフローが作成されます。 小さな変更は、必要なコンパイルステップのみを起動します。 受験は、何かが変わらない限り、めったに再開しない。 Developers can iterate rapidly without worrying about hidden state. パッケージとテスト結果を一流のキャッシュ可能なアーティファクトとして扱うことによって、Goはテストを迅速かつ予測可能にします。 そして . go build go run ビルドシステムの観察とデバッグ ほとんどの場合、Goのビルドシステムは、静かで効率的に、あなたが期待していることを正確に行います. 何かが停止すると、ツールチェーンは、あなたが何をしているかを直接、低レベルの可視性を提供します. キーは、どのスイッチがフリップするかを知り、あなたが見ているものを解釈する方法です。 「Toolchain Talk」 Goは、ビルドパイプラインを変更せずに明らかにする小さな旗のセットを提供します。 -x は、ビルド中に実行された実際のコマンドを印刷します。これは、コンパイラーの呼び出し、リンク手順、およびツールの実行を含みます。 -n は、コマンドを実行せずに何が実行されるかを示します. This is useful when you want to understand the build plan without triggering a rebuild. -work は、それを削除する代わりに、一時的なビルドディレクトリを保存します.This allows you to inspect intermediate files, generated code, and temporary artifacts produced during compilation or linking. これらのフラッグは、ブラックボックスから透明なパイプラインにGoツールチェーンを変えます。重要なことは、キャッシュを無効にしません、ただキャッシュヒットやミスが表示されます。 なぜパッケージが再構築されたかを理解する 混乱の最も一般的な原因の1つは、パッケージの再構築である「明らかな理由がない」と正しい精神モデルで、これは診断しやすくなります: パッケージは、そのキャッシュキーの入力が変更されたときに再構築されます。 入力には、ソースコード、ビルドタグ、コンパイラの旗、ターゲットプラットフォーム、および関連する環境変数が含まれます。 依存性の変化はパッケージグラフを通じて上方に広がります。 利用 , you can often see whether Go reused a cached artifact or recompiled a package, and infer why from the context. This removes the temptation to reach for blunt tools such as 最初の答えとして -x go clean -cache Forcing Rebuilds (When You Really Mean It) 例えば、クリーンビルドの検証やツールチェーンのデバッグの問題を解決する場合、Go はこれを明示的にサポートします。 a forces rebuilding of packages, ignoring cached compiled artifacts. パッケージの再構築、キャッシュされたコンパイルされたアーティファクトを無視 go clean -cache clears the entire build cache バックアップ これらのオプションは意図的に明示的で少し不便です Go は、デフォルトを正しく再利用し、手動キャッシュの無効化を例外にするように設計されています。 迷信駆動の修正を避ける なぜなら、Goのビルドシステムは決定的なものであり、推測はめったに役立たないからです。 で、 そして、 何が起きているのかを具体的に証明し、それはほとんど常に驚くべき行動を説明するのに十分である。 -x -n -work これを信じる時: コンテンツアドレスは、 パッケージは仕事の単位であり、 再利用は安全である。 Debugging build behavior is a matter of observation rather than trial and error. バックアップビルド行動は試行とエラーの問題になる。 実際のプロジェクトへの影響 Goのビルドシステムの背後にあるデザイン選択は、偶然ではありません。小さな例を超え、実際のコードベースで作業を開始すると、最も明確に現れます:継続的な統合パイプライン、大規模なリポジトリ、および編集主導のワークフロー。 feel fast locally are what make Go scale so well in production environments. go build CIパイプラインと再生可能性 Go は、特定主義的でコンテンツアドレス化されたビルドに焦点を当てているため、特に CI に適しているため、ビルド 出力はソース コンテンツ、モジュール バージョン、および明示的な構成から完全に由来しているため、CI ビルドはマシンや環境で一貫して動作します。 この予測性はまた、Go ビルドを非常にキャッシュフレンドリーにします。共有ビルドキャッシュ、コンテナレイヤー、またはリモートキャッシュインフラストラクチャを使用している場合、Go のパッケージレベルのコンパイルモデルは自然に適合します。 Monoreposと大規模なコードベース 大きなリポジトリでは、ビルドキャッシュはパフォーマンスの限界になります。Goキャッシュがパッケージを独立してコンパイルしているため、小型でよく定義されたパッケージは、最小限のオーバーヘッドで多くのビルドで再利用できます。これは、依存性が明示的でパッケージが焦点を当てるコード構造を奨励します。 逆側は、あまりにも大きなパッケージまたは密接に結合されたパッケージがボトルネックになることがあります。 重いパッケージの小さな変更は、キャッシュの大部分を無効にし、リポジトリ全体のビルドタイムを増やすことができます。 Go はこのコストを隠さないが、パッケージの境界線を可視し、有意義にし、良い構造を報酬し、早期に悪い分離を暴露します。 エディター、ツール、および自動化 同じビルドモデルがGoのツールエコシステムを強化します。コードエディター、言語サーバー、ランター、コードジェネレーターはすべて、あなたのコードのパッケージレベルの理解に依存しています。 これは、Go tooling が異常に一貫している理由の一つです:編集者と CI システムはコンパイラが同じようにコードを見ます。 タグ: trust the model Goのビルドシステムは、明確な妥協を成し遂げているため、成功しています:それは知性よりも予測性、暗示的な行動よりも明確な構造のために最適化します。表面上では、これは単純さのように見えます。下には、パッケージを仕事の単位として扱い、コンテンツを真実の源として扱い、パフォーマンスハックではなく正確性の機能としてキャッシュします。 あなたがこのモデルを内部化すると、多くの日常的な行動が意味を持ち始めます ビルドは、Goがより少ない仕事をしているためではなく、それを避けるため、速いのです。 仕事 同じ機械を再利用するため、快適に感じる。 Test execution is reliable because test results are cached using the same deterministic rules as compiled packages. テスト実行は、テスト結果がコンパイルされたパッケージと同じ決定的なルールを使用してキャッシュされているため、信頼性があります。 不要 go run go build 人間にとっては、驚きが少なくなり、フィードバックループが速くなり、コードエディター、マシン、CIシステムで一貫して動作するツールを意味します。マシンにとっては、再生可能なビルド、キャッシュフレンドリーなアーティファクト、コードベースが成長するにつれて自然にスケールするシステムを意味します。 もし1つの取り引きがあるなら、それは次の通りです:Goのビルドシステムは戦うことや作業をすることではありません。それ自体は、理解を報いるAPIです。あなたがモデルを信頼すると、ツールチェーンは魔法を感じなくなり、あなたがあなたのコードを構築するインフラストラクチャから望む正確な信頼性を感じ始める。