[インデックス 15346] ファイルの概要
このコミットは、Goプロジェクトのmisc/dashboard/builder
ディレクトリにあるビルダツールの様々なクリーンアップと改善を目的としています。具体的には、コミットウォッチャーの無効化オプションの追加、ビルダが常にローカルのmasterワーキングコピーを更新する動作の導入、そしてMercurial (hg) リポジトリ操作を新しいRepo
型にリファクタリングすることが含まれています。
コミット
commit 4036d876eab44f83d71e668e666acc5cd3997373
Author: Dave Cheney <dave@cheney.net>
Date: Thu Feb 21 13:11:58 2013 +1100
misc/dashboard/builder: various cleanups
* allow commit watcher to be disabled, useful for small slow builders who will never be the first to notice a commit.
* builders always update their local master working copy before cloning a specific revision.
* refactor hg repo operations into a new type, Repo.
R=adg, shanemhansen, luitvd
CC=golang-dev
https://golang.org/cl/7326053
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4036d876eab44f83d71e668e666acc5cd3997373
元コミット内容
このコミットは、Goプロジェクトのビルドダッシュボードシステムの一部であるmisc/dashboard/builder
コンポーネントに対する複数の改善を含んでいます。主な変更点は以下の通りです。
- コミットウォッチャーの無効化機能: コミットウォッチャー(新しいコミットをポーリングする機能)を無効にできるようにしました。これは、処理能力が低い「遅いビルダ」にとって特に有用です。これらのビルダは、新しいコミットを最初に検出する役割を担うことがないため、ポーリングのオーバーヘッドを避けることができます。
- ビルダの動作改善: ビルダが特定のリビジョンをクローンする前に、常にローカルのmasterワーキングコピーを更新するように変更されました。これにより、ビルドの信頼性と一貫性が向上します。
- Mercurialリポジトリ操作のリファクタリング: Mercurial (hg) リポジトリに関連する操作(クローン、更新、プル、ログ取得、ハッシュ取得など)が、新しく導入された
Repo
型に集約され、コードのモジュール化と再利用性が向上しました。
変更の背景
Goプロジェクトは、継続的インテグレーション(CI)システムとして、様々なプラットフォームや環境でコードのビルドとテストを行うためのダッシュボードとビルダ群を運用していました。このシステムは、新しいコミットがリポジトリにプッシュされるたびにそれを検出し、対応するビルドジョブをトリガーする仕組みを持っていました。
しかし、以下のような課題がありました。
- リソースの非効率性: すべてのビルダが新しいコミットをポーリングすることは、特にリソースが限られている、またはネットワークが遅い環境で動作するビルダ(「small slow builders」)にとって非効率的でした。これらのビルダは、他の高速なビルダが既にコミットを検出して処理を開始しているため、自身が最初にコミットを検出する必要がありませんでした。
- Mercurial操作の分散: Mercurialリポジトリに対する操作が
main.go
ファイル内に散在しており、コードの重複や保守性の低下を招いていました。リポジトリ操作のロジックが複数の関数にまたがって存在するため、変更やデバッグが困難でした。 - ビルドの一貫性: ビルダが常に最新のmasterブランチの状態から作業を開始することを保証するメカニズムが不足しており、特定のコミットをビルドする際に、そのコミットがローカルリポジトリに存在しない場合に問題が発生する可能性がありました。
これらの課題に対処するため、本コミットでは、システムの効率性、堅牢性、およびコードの保守性を向上させるための改善が導入されました。
前提知識の解説
- Go言語: Googleによって開発されたオープンソースのプログラミング言語。並行処理に強く、高速なコンパイルと実行が特徴です。
- Mercurial (hg): 分散型バージョン管理システム(DVCS)の一つ。Gitと同様に、リポジトリのクローン、コミット、プッシュ、プルなどの操作をサポートします。Goプロジェクトは、初期にはMercurialを主要なバージョン管理システムとして使用していましたが、後にGitに移行しました。このコミットが作成された2013年時点では、まだMercurialが使用されていました。
- 継続的インテグレーション (CI): ソフトウェア開発手法の一つで、開発者がコードベースに加えた変更を頻繁にメインブランチにマージし、自動化されたビルドとテストを実行することで、早期に問題を検出します。
- ビルドダッシュボード: CIシステムの一部として、ビルドのステータス、テスト結果、コミット履歴などを視覚的に表示するウェブインターフェース。Goプロジェクトのダッシュボードは、様々なビルダからの結果を集約して表示していました。
- コミットウォッチャー: バージョン管理システムのリポジトリを定期的にポーリングし、新しいコミットがプッシュされたことを検出するコンポーネント。検出されたコミットは、ビルドジョブのトリガーとなります。
go.googlecode.com/p/go
: GoプロジェクトのMercurialリポジトリのURL。このコミットが作成された時点では、Google Codeでホストされていました。hg clone
: Mercurialコマンドで、リモートリポジトリのコピーをローカルに作成します。hg pull
: Mercurialコマンドで、リモートリポジトリから変更をローカルリポジトリに取得します。hg update
: Mercurialコマンドで、ワーキングディレクトリを特定のリビジョンに更新します。hg log
: Mercurialコマンドで、リポジトリのコミット履歴を表示します。
技術的詳細
このコミットの技術的詳細は、主にGo言語で書かれたmisc/dashboard/builder
アプリケーションの内部構造と、Mercurialリポジトリとのインタラクションの改善に焦点を当てています。
-
Repo
型の導入:vcs.go
という新しいファイルが作成され、Repo
という構造体が定義されました。この構造体は、Mercurialリポジトリのパスと、並行アクセスを制御するためのsync.Mutex
を含んでいます。Repo
型には、Clone
,UpdateTo
,Exists
,Pull
,Log
,FullHash
,hgCmd
といったメソッドが追加されました。これにより、Mercurialリポジトリに対するすべての操作がこの型にカプセル化され、main.go
からMercurialコマンドを直接呼び出すロジックが大幅に削減されました。- 特に、
Log
メソッドは内部でPull
を呼び出すことで、ログを取得する前にリポジトリが最新の状態であることを保証しています。 hgCmd
メソッドは、Mercurialコマンドの引数を構築し、extensions.codereview
拡張を無効にするための--config
オプションを自動的に追加します。
-
コミットウォッチャーの制御:
main.go
のcommitInterval
フラグが変更され、0
を設定することでコミットウォッチャーを無効にできるようになりました。commitWatcher
関数は、Repo
型のインスタンスを引数として受け取るように変更され、goroot
リポジトリの操作がRepo
メソッドを通じて行われるようになりました。commitWatcher
の開始ロジックが修正され、commitInterval
が0
の場合はウォッチャーが起動しないようになりました。
-
ビルダの動作ロジックの変更:
Builder
構造体にgoroot *Repo
フィールドが追加され、各ビルダが自身のgoroot
リポジトリインスタンスを持つようになりました。NewBuilder
関数もgoroot *Repo
を引数として受け取るように変更されました。build
メソッド内で、特定のハッシュをビルドする前にb.goroot.Pull()
が呼び出されるようになりました。これにより、ビルド対象のハッシュがローカルリポジトリに存在しない場合に、まずリモートから変更を取得する動作が保証されます。- リポジトリのクローン操作も
b.goroot.Clone()
メソッドを使用するように変更され、より堅牢になりました。
-
並行処理の改善:
- 以前は
gorootMu sync.Mutex
というグローバルなミューテックスを使用していましたが、Repo
型にミューテックスが内包されたことで、リポジトリごとのロックが可能になり、より粒度の細かい並行処理制御が実現されました。これにより、異なるリポジトリに対する操作が互いにブロックし合うことが少なくなります。
- 以前は
-
XML解析の変更:
main.go
からencoding/xml
パッケージのインポートが削除されました。これは、HgLog
構造体とxmlLogTemplate
がvcs.go
に移動し、Repo.Log()
メソッド内でXMLのアンマーシャリングが行われるようになったためです。
これらの変更により、コードの構造が整理され、Mercurialリポジトリ操作のロジックが集中管理されることで、保守性と拡張性が向上しました。また、ビルダの動作がより予測可能になり、リソースの効率的な利用が可能になりました。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は以下の2つのファイルに集中しています。
-
misc/dashboard/builder/main.go
:goroot
変数が*Repo
型に変更され、Repo
構造体のインスタンスとして初期化されるようになりました。NewBuilder
関数のシグネチャが変更され、*Repo
型のgoroot
引数を受け取るようになりました。commitWatcher
関数も*Repo
型のgoroot
引数を受け取るように変更されました。- Mercurial操作(
hgClone
,hgRepoExists
,fullHash
など)を直接呼び出していた箇所が、新しく導入されたRepo
型のメソッド呼び出しに置き換えられました。 sync.Mutex
であるgorootMu
が削除されました。encoding/xml
パッケージのインポートが削除されました。
-
misc/dashboard/builder/vcs.go
(新規ファイル):Repo
構造体が定義されました。RemoteRepo
,Clone
,UpdateTo
,Exists
,Pull
,Log
,FullHash
,hgCmd
といったRepo
型のメソッドが実装されました。HgLog
構造体とxmlLogTemplate
定数がmain.go
からこのファイルに移動されました。
コアとなるコードの解説
misc/dashboard/builder/main.go
の変更点
main.go
では、Mercurialリポジトリ操作の責任がRepo
型に委譲されたことが最も大きな変更点です。
// 変更前: グローバルなgoroot文字列とミューテックス
// var (
// goroot string
// gorootMu sync.Mutex
// )
// 変更後: main関数内でRepo型として初期化
func main() {
// ...
goroot := &Repo{
Path: filepath.Join(*buildroot, "goroot"),
}
// ...
}
// 変更前: hgRepoExists(goroot)
// 変更後: goroot.Exists()
// if hgRepoExists(goroot) {
if goroot.Exists() {
log.Print("Found old workspace, will use it")
} else {
// ...
// 変更前: if err := hgClone(hgUrl, goroot); err != nil {
// 変更後: goroot, err = RemoteRepo(hgUrl).Clone(goroot.Path, "tip")
var err error
goroot, err = RemoteRepo(hgUrl).Clone(goroot.Path, "tip")
if err != nil {
log.Fatal("Error cloning repository:", err)
}
}
// Builderの初期化も変更
// 変更前: b, err := NewBuilder(builder)
// 変更後: b, err := NewBuilder(goroot, name)
func NewBuilder(goroot *Repo, name string) (*Builder, error) {
b := &Builder{
goroot: goroot,
name: name,
}
// ...
}
// buildメソッド内の変更
func (b *Builder) build() bool {
// ...
// 変更前: if _, err := fullHash(goroot, hash[:12]); err != nil { ... hg pull ... }
// 変更後: b.goroot.Pull() が buildHash の中で呼ばれる
// ...
// 変更前: if err := hgClone(goroot, filepath.Join(workpath, "go")); err != nil {
// 変更後: if err := b.goroot.Pull(); err != nil { // pull before cloning to ensure we have the revision
if err := b.goroot.Pull(); err != nil {
return err
}
// 変更前: if err := run(*cmdTimeout, nil, filepath.Join(workpath, "go"), hgCmd("update", hash)...); err != nil {
// 変更後: if _, err := b.goroot.Clone(filepath.Join(workpath, "go"), hash); err != nil {
if _, err := b.goroot.Clone(filepath.Join(workpath, "go"), hash); err != nil {
return err
}
// ...
}
// commitWatcher関数の変更
// 変更前: func commitWatcher() {
// 変更後: func commitWatcher(goroot *Repo) {
func commitWatcher(goroot *Repo) {
if *commitInterval == 0 {
log.Printf("commitInterval is %s, disabling commitWatcher", *commitInterval)
return
}
// ...
// 変更前: commitPoll(key, "")
// 変更後: commitPoll(goroot, "", key)
commitPoll(goroot, "", key)
// ...
// 変更前: commitPoll(key, pkg)
// 変更後: commitPoll(pkgroot, pkg, key)
for _, pkg := range dashboardPackages("subrepo") {
pkgroot := &Repo{
Path: filepath.Join(*buildroot, pkg),
}
commitPoll(pkgroot, pkg, key)
}
// ...
}
// commitPoll関数の変更
// 変更前: func commitPoll(key, pkg string) {
// 変更後: func commitPoll(repo *Repo, pkg, key string) {
func commitPoll(repo *Repo, pkg, key string) {
// ...
// 変更前: if !hgRepoExists(pkgRoot) { ... hgClone ... }
// 変更後: if !repo.Exists() { ... RemoteRepo(...).Clone(...) ... }
if !repo.Exists() {
var err error
repo, err = RemoteRepo(repoURL(pkg)).Clone(repo.Path, "tip")
// ...
}
// ...
// 変更前: err := run(*cmdTimeout, nil, pkgRoot, hgCmd("pull")...)
// 変更後: logs, err := repo.Log() // repo.Log calls repo.Pull internally
logs, err := repo.Log() // repo.Log calls repo.Pull internally
if err != nil {
log.Printf("hg log: %v", err) // エラーメッセージもhg logに変わっている
return
}
// ...
// 変更前: l.Parent, _ = fullHash(pkgRoot, l.Parent)
// 変更後: l.Parent, _ = repo.FullHash(l.Parent)
l.Parent, _ = repo.FullHash(l.Parent)
// ...
}
misc/dashboard/builder/vcs.go
の新規追加と解説
このファイルは、Mercurialリポジトリ操作をカプセル化するためのRepo
型とそのメソッドを定義しています。
// Repo represents a mercurial repository.
type Repo struct {
Path string // リポジトリのローカルパスまたはリモートURL
sync.Mutex // 並行アクセス制御のためのミューテックス
}
// RemoteRepo constructs a *Repo representing a remote repository.
func RemoteRepo(url string) *Repo {
return &Repo{
Path: url,
}
}
// Clone clones the current Repo to a new destination
// returning a new *Repo if successful.
// 指定されたパスとリビジョンでリポジトリをクローンし、新しいRepoインスタンスを返す。
func (r *Repo) Clone(path, rev string) (*Repo, error) {
r.Lock()
defer r.Unlock()
// hgCmdはRepoのメソッドとして定義されており、適切な引数を生成する
if err := run(*cmdTimeout, nil, *buildroot, r.hgCmd("clone", "-r", rev, r.Path, path)...); err != nil {
return nil, err
}
return &Repo{
Path: path,
}, nil
}
// UpdateTo updates the working copy of this Repo to the
// supplied revision.
// ワーキングコピーを指定されたハッシュに更新する。
func (r *Repo) UpdateTo(hash string) error {
r.Lock()
defer r.Unlock()
return run(*cmdTimeout, nil, r.Path, r.hgCmd("update", hash)...)
}
// Exists reports whether this Repo represents a valid Mecurial repository.
// .hgディレクトリの存在を確認し、有効なMercurialリポジトリであるかを報告する。
func (r *Repo) Exists() bool {
fi, err := os.Stat(filepath.Join(r.Path, ".hg"))
if err != nil {
return false
}
return fi.IsDir()
}
// Pull pulls changes from the default path, that is, the path
// this Repo was cloned from.
// リモートから変更をプルする。
func (r *Repo) Pull() error {
r.Lock()
defer r.Unlock()
return run(*cmdTimeout, nil, r.Path, r.hgCmd("pull")...)
}
// Log returns the changelog for this repository.
// リポジトリの変更ログ(コミット履歴)を取得する。
// 内部でPull()を呼び出し、最新の状態を保証する。
func (r *Repo) Log() ([]HgLog, error) {
if err := r.Pull(); err != nil {
return nil, err
}
const N = 50 // how many revisions to grab
r.Lock()
defer r.Unlock()
data, _, err := runLog(*cmdTimeout, nil, r.Path, r.hgCmd("log",
"--encoding=utf-8",
"--limit="+strconv.Itoa(N),
"--template="+xmlLogTemplate)...,
)
if err != nil {
return nil, err
}
var logStruct struct {
Log []HgLog
}
err = xml.Unmarshal([]byte("<Top>"+data+"</Top>"), &logStruct)
if err != nil {
log.Printf("unmarshal hg log: %v", err)
return nil, err
}
return logStruct.Log, nil
}
// FullHash returns the full hash for the given Mercurial revision.
// 指定されたリビジョンの完全なハッシュを取得する。
func (r *Repo) FullHash(rev string) (string, error) {
r.Lock()
defer r.Unlock()
s, _, err := runLog(*cmdTimeout, nil, r.Path,
r.hgCmd("log",
"--encoding=utf-8",
"--rev="+rev,
"--limit=1",
"--template={node}")...,
)
if err != nil {
return "", nil
}
s = strings.TrimSpace(s)
if s == "" {
return "", fmt.Errorf("cannot find revision")
}
if len(s) != 40 {
return "", fmt.Errorf("hg returned invalid hash " + s)
}
return s, nil
}
// hgCmd constructs the command arguments for Mercurial.
// Mercurialコマンドの引数を構築し、codereview拡張を無効にするオプションを追加する。
func (r *Repo) hgCmd(args ...string) []string {
return append([]string{"hg", "--config", "extensions.codereview=!"}, args...)
}
// HgLog represents a single Mercurial revision.
// Mercurialのログエントリを表す構造体。
type HgLog struct {
Hash string
Author string
Date string
Desc string
Parent string
// Internal metadata
added bool
}
// xmlLogTemplate is a template to pass to Mercurial to make
// hg log print the log in valid XML for parsing with xml.Unmarshal.
// hg logコマンドでXML形式の出力を得るためのテンプレート。
const xmlLogTemplate = `
<Log>
<Hash>{node|escape}</Hash>
<Parent>{parent|escape}</Parent>
<Author>{author|escape}</Author>
<Date>{date|rfc3339date}</Date>
<Desc>{desc|escape}</Desc>
</Log>
`
これらの変更により、MercurialリポジトリとのインタラクションがRepo
型に集約され、main.go
のコードが大幅に簡素化され、より読みやすく、保守しやすくなりました。また、Repo
型内のミューテックスにより、リポジトリ操作の並行処理が安全に管理されるようになりました。
関連リンク
- Goプロジェクトの公式ウェブサイト: https://go.dev/
- Mercurial公式ウェブサイト: https://www.mercurial-scm.org/
- Goの継続的インテグレーションに関するドキュメント(当時のものとは異なる可能性があります): https://go.dev/doc/contribute#continuous_integration
参考にした情報源リンク
- GoプロジェクトのGitHubリポジトリ: https://github.com/golang/go
- Mercurialコマンドリファレンス(
hg clone
,hg pull
,hg update
,hg log
など): https://www.mercurial-scm.org/doc/ - Go言語の
sync
パッケージドキュメント: https://pkg.go.dev/sync - Go言語の
filepath
パッケージドキュメント: https://pkg.go.dev/path/filepath - Go言語の
os
パッケージドキュメント: https://pkg.go.dev/os - Go言語の
encoding/xml
パッケージドキュメント: https://pkg.go.dev/encoding/xml