[インデックス 10608] ファイルの概要
このコミットは、Go言語のビルドシステムの一部である gobuilder
ツールに対する重要な変更を含んでいます。具体的には、ダッシュボードとの通信プロトコルの更新、パッケージごとのコミット処理の導入、そして一時的なパッケージモードの無効化が主な内容です。
コミット
commit 263c955f2fff2016b5ff77d787d8e1b50555930a
Author: Andrew Gerrand <adg@golang.org>
Date: Mon Dec 5 16:44:10 2011 +1100
gobuilder: use new dashboard protocol
gobuilder: -commit mode for packages
gobuilder: cripple -package mode temporarily
R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/5450092
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/263c955f2fff2016b5ff77d787d8e1b50555930a
元コミット内容
このコミットの元の内容は以下の通りです。
gobuilder
: 新しいダッシュボードプロトコルを使用する。gobuilder
: パッケージ向けの-commit
モードを導入する。gobuilder
:-package
モードを一時的に無効化する。
変更の背景
このコミットの背景には、Go言語のビルドインフラストラクチャにおけるダッシュボードとの連携方法の進化があります。gobuilder
は、Goプロジェクトの継続的インテグレーション(CI)システムの一部として機能し、コミットのビルドとテストの結果をダッシュボードに報告する役割を担っています。
変更の主な動機は以下の点が考えられます。
- 新しいダッシュボードプロトコルへの移行: ダッシュボード側のAPIまたは通信方式が変更されたため、
gobuilder
もそれに合わせて更新する必要がありました。これにより、より効率的で堅牢なデータ交換が可能になったと考えられます。 - パッケージごとのコミット処理の導入: Go言語のエコシステムが拡大し、多くの独立したパッケージが開発される中で、モノリシックなリポジトリ全体のビルドだけでなく、個々のパッケージの変更をより細かく追跡し、ビルド結果を報告するニーズが高まったと考えられます。
-commit
モードの導入は、この粒度の細かい管理を可能にするためのものです。 - 一時的な
-package
モードの無効化: 新しいプロトコルやパッケージごとのコミット処理の導入に伴い、既存の-package
モードが新しいシステムと互換性がなかったか、あるいは新しい機能の開発中に一時的に競合を避けるために無効化されたと考えられます。これは、大規模なシステム変更における一般的な開発プラクティスであり、段階的な移行を示唆しています。
この変更は、GoプロジェクトのCI/CDパイプラインの柔軟性とスケーラビリティを向上させることを目的としています。
前提知識の解説
このコミットを理解するためには、以下の技術的背景知識が役立ちます。
- Go言語: Googleによって開発されたオープンソースのプログラミング言語。並行処理に強く、シンプルで効率的なコード記述が可能です。
- Mercurial (Hg): 分散型バージョン管理システム(DVCS)。Gitと同様に、コードの変更履歴を管理するために使用されます。Goプロジェクトの初期にはMercurialが主要なバージョン管理システムとして使用されていました。コミットログの解析やリポジトリのクローンに
hg
コマンドが使われていることから、Mercurialが深く関わっていることがわかります。 - 継続的インテグレーション (CI): ソフトウェア開発手法の一つで、開発者がコードベースに加えた変更を頻繁にメインブランチにマージし、自動的にビルドとテストを行うことで、統合の問題を早期に発見し解決します。
gobuilder
はこのCIプロセスの一部を自動化しています。 - ダッシュボード: CIシステムにおいて、ビルドのステータス、テスト結果、コードカバレッジなどの情報を視覚的に表示するウェブインターフェース。開発者はダッシュボードを通じてプロジェクトの健全性を一目で確認できます。Goプロジェクトには
build.golang.org
のような公式のビルドダッシュボードが存在します。 - HTTP/JSON:
- HTTP (Hypertext Transfer Protocol): ウェブ上でデータを交換するためのプロトコル。クライアント(
gobuilder
)とサーバー(ダッシュボード)間の通信に使用されます。 - JSON (JavaScript Object Notation): 軽量なデータ交換フォーマット。人間が読み書きしやすく、機械が解析しやすい構造を持っています。このコミットでは、ダッシュボードとの間でJSON形式のデータがやり取りされるように変更されています。
- HTTP (Hypertext Transfer Protocol): ウェブ上でデータを交換するためのプロトコル。クライアント(
- Goの
net/http
パッケージ: Go言語の標準ライブラリで、HTTPクライアントおよびサーバーを実装するための機能を提供します。 - Goの
encoding/json
パッケージ: Go言語の標準ライブラリで、JSONデータのエンコード(Goの構造体からJSONへ)およびデコード(JSONからGoの構造体へ)を行う機能を提供します。 url.Values
: Goのnet/url
パッケージにある型で、URLクエリパラメータやフォームデータを扱うためのマップのような構造です。キーと値のペアを管理し、URLエンコードされた文字列に変換するのに便利です。
技術的詳細
このコミットにおける技術的な変更は多岐にわたりますが、特に misc/dashboard/builder/http.go
と misc/dashboard/builder/main.go
の変更が重要です。
misc/dashboard/builder/http.go
の変更
dash
関数の大幅な変更:- 以前は
param map[string]string
を引数として取り、クエリパラメータまたはPOSTフォームデータを扱っていました。 - 新しい
dash
関数は、url.Values
(クエリパラメータ用) と、req
(POSTリクエストのJSONボディ用)、resp
(レスポンスのJSONデコード用) というより汎用的な引数を取るようになりました。 - これにより、GETリクエストではクエリパラメータを、POSTリクエストではJSON形式のボディを送信できるようになり、新しいダッシュボードプロトコルに合わせた柔軟な通信が可能になりました。
- JSONのエンコード/デコード処理が
json.Marshal
とjson.Unmarshal
を直接使用するように変更され、エラーハンドリングも改善されています。特に、レスポンスボディ全体を読み込んでからJSONデコードを行うように変更されています。 dashStatus
関数が削除され、そのエラーチェックロジックは新しいdash
関数のレスポンス処理に統合されました。これは、ダッシュボードからのレスポンスにStatus
フィールドとError
フィールドが含まれるという従来のプロトコルから、より一般的なJSONレスポンスとHTTPステータスコードに基づくエラーハンドリングへの移行を示唆しています。
- 以前は
todo
関数の変更: ダッシュボードから次にビルドすべきリビジョンを取得する関数です。以前はハッシュのリストを期待していましたが、新しいプロトコルでは単一のハッシュ文字列を直接返すようになりました。recordResult
関数の変更: ビルド結果をダッシュボードに送信する関数です。新しいdash
関数のシグネチャに合わせて、リクエストボディをobj
(map[string]interface{}) としてJSONエンコードして送信するようになりました。packages
およびupdatePackage
関数の無効化: これらの関数は一時的にreturn nil, nil
やreturn nil
を返すようにスタブ化されています。これはコミットメッセージの「cripple -package mode temporarily」に対応しており、新しいプロトコルやパッケージ処理の設計が完了するまで、これらの機能が一時的に利用できないことを示しています。postCommit
関数の変更: コミット情報をダッシュボードに通知する関数です。- 引数に
pkg
(パッケージパス) が追加され、ダッシュボードに送信するデータにPackagePath
フィールドが含まれるようになりました。これにより、特定のパッケージに関連するコミットをダッシュボードが認識できるようになります。 - 返り値が
error
からbool
に変更され、成功/失敗を直接示すようになりました。
- 引数に
dashboardCommit
関数の変更: ダッシュボードが特定のコミットを認識しているかを確認する関数です。引数にpkg
が追加され、パッケージごとのコミット存在チェックが可能になりました。dashboardPackages
関数の追加: ダッシュボードからビルドすべきパッケージのリストを取得するための新しい関数です。これにより、gobuilder
は複数のパッケージを監視し、それぞれに対してコミット処理を行うことができるようになります。repoURL
関数の追加: インポートパスからリポジトリのURLをデコードするための正規表現ベースのヘルパー関数です。これは、パッケージのソースコードをクローンする際に使用されると考えられます。
misc/dashboard/builder/main.go
の変更
main
関数の変更:- リポジトリのクローン処理が
hgClone
という新しいヘルパー関数に分離されました。 fullHash
関数の呼び出しにgoroot
またはpkgRoot
が引数として追加され、特定のパス内でハッシュを解決するようになりました。
- リポジトリのクローン処理が
commitWatcher
関数の変更:- この関数は、新しいコミットを定期的にポーリングする役割を担っています。
- 変更後、
commitPoll
を空のパッケージパス (""
) で呼び出すだけでなく、dashboardPackages()
から取得した各パッケージに対してもcommitPoll
を呼び出すようになりました。これにより、gobuilder
はGoリポジトリ全体だけでなく、ダッシュボードが認識している個々のパッケージのコミットも監視するようになりました。
hgClone
およびhgRepoExists
関数の追加: Mercurialリポジトリのクローンと存在チェックをカプセル化するためのヘルパー関数です。commitPoll
関数の変更:- 引数に
pkg
が追加されました。 pkg
が空でない場合、pkgRoot
を計算し、そのパスにMercurialリポジトリが存在しない場合はhgClone
を試みます。これにより、gobuilder
は必要に応じて個々のパッケージリポジトリを動的にクローンできるようになります。hg pull
やhg log
の実行パスがgoroot
からpkgRoot
に変更され、パッケージごとのリポジトリ操作が可能になりました。fullHash
の呼び出しもpkgRoot
を使用するように変更されています。
- 引数に
addCommit
関数の変更:- 引数に
pkg
が追加されました。 dashboardCommit
とpostCommit
の呼び出しもpkg
を含むように変更され、パッケージごとのコミット情報をダッシュボードに送信するようになりました。- 親コミットの追加ロジックも、パッケージごとのコンテキストで実行されるようになりました。
- 引数に
fullHash
関数の変更: 引数にroot
(リポジトリのルートパス) が追加され、指定されたリポジトリ内でハッシュを解決するようになりました。
これらの変更は、gobuilder
がGo言語のモノリポジトリだけでなく、個々のパッケージリポジトリも効率的に管理し、ダッシュボードにそのビルド状況を報告できるようにするためのアーキテクチャ的な進化を示しています。
コアとなるコードの変更箇所
misc/dashboard/builder/http.go
--- a/misc/dashboard/builder/http.go
+++ b/misc/dashboard/builder/http.go
@@ -8,96 +8,108 @@ import (
"bytes"
"encoding/json"
"errors"
- "fmt"
+ "io"
"log"
"net/http"
"net/url"
- "strconv"
)
-type param map[string]string
+type obj map[string]interface{}
// dash runs the given method and command on the dashboard.
-// If args is not nil, it is the query or post parameters.
-// If resp is not nil, dash unmarshals the body as JSON into resp.
-func dash(meth, cmd string, resp interface{}, args param) error {
+// If args is non-nil it is encoded as the URL query string.
+// If req is non-nil it is JSON-encoded and passed as the body of the HTTP POST.
+// If resp is non-nil the server's response is decoded into the value pointed
+// to by resp (resp must be a pointer).
+func dash(meth, cmd string, args url.Values, req, resp interface{}) error {
var r *http.Response
var err error
if *verbose {
- log.Println("dash", cmd, args)
+ log.Println("dash", meth, cmd, args, req)
}
cmd = "http://" + *dashboard + "/" + cmd
- vals := make(url.Values)
- for k, v := range args {
- vals.Add(k, v)
+ if len(args) > 0 {
+ cmd += "?" + args.Encode()
}
switch meth {
case "GET":
- if q := vals.Encode(); q != "" {
- cmd += "?" + q
+ if req != nil {
+ log.Panicf("%s to %s with req", meth, cmd)
}
r, err = http.Get(cmd)
case "POST":
- r, err = http.PostForm(cmd, vals)
+ var body io.Reader
+ if req != nil {
+ b, err := json.Marshal(req)
+ if err != nil {
+ return err
+ }
+ body = bytes.NewBuffer(b)
+ }
+ r, err = http.Post(cmd, "text/json", body)
default:
- return fmt.Errorf("unknown method %q", meth)
+ log.Panicf("%s: invalid method %q", cmd, meth)
+ panic("invalid method: " + meth)
}
if err != nil {
return err
}
-
defer r.Body.Close()
- var buf bytes.Buffer
- buf.ReadFrom(r.Body)
- if resp != nil {
- if err = json.Unmarshal(buf.Bytes(), resp); err != nil {
- log.Printf("json unmarshal %#q: %s\n", buf.Bytes(), err)
- return err
- }
+ body := new(bytes.Buffer)
+ if _, err := body.ReadFrom(r.Body); err != nil {
+ return err
}
- return nil
-}
-
-func dashStatus(meth, cmd string, args param) error {
- var resp struct {
- Status string
- Error string
+
+ // Read JSON-encoded Response into provided resp
+ // and return an error if present.
+ var result = struct {
+ Response interface{}
+ Error string
+ }{
+ // Put the provided resp in here as it can be a pointer to
+ // some value we should unmarshal into.
+ Response: resp,
}
- err := dash(meth, cmd, &resp, args)
- if err != nil {
+ if err = json.Unmarshal(body.Bytes(), &result); err != nil {
+ log.Printf("json unmarshal %#q: %s\n", body.Bytes(), err)
return err
}
- if resp.Status != "OK" {
- return errors.New("/build: " + resp.Error)
+ if result.Error != "" {
+ return errors.New(result.Error)
}
+
return nil
}
// todo returns the next hash to build.
func (b *Builder) todo() (rev string, err error) {
- var resp []struct {
- Hash string
- }
- if err = dash("GET", "todo", &resp, param{"builder": b.name}); err != nil {
+ // TODO(adg): handle packages
+ args := url.Values{"builder": {b.name}}
+ var resp string
+ if err = dash("GET", "todo", args, nil, &resp); err != nil {
return
}
- if len(resp) > 0 {
- rev = resp[0].Hash
+ if resp != "" {
+ rev = resp
}
return
}
// recordResult sends build results to the dashboard
func (b *Builder) recordResult(buildLog string, hash string) error {
- return dash("POST", "build", nil, param{
- "builder": b.name,
- "key": b.key,
- "node": hash,
- "log": buildLog,
- })
+ // TODO(adg): handle packages
+ return dash("POST", "result", url.Values{"key": {b.key}}, obj{
+ "Builder": b.name,
+ "Hash": hash,
+ "Log": buildLog,
+ }, nil)
}
// packages fetches a list of package paths from the dashboard
func packages() (pkgs []string, err error) {
+ return nil, nil
+ /* TODO(adg): un-stub this once the new package builder design is done
var resp struct {
Packages []struct {
Path string
@@ -111,10 +123,13 @@ func packages() (pkgs []string, err error) {
\tpkgs = append(pkgs, p.Path)\n \t}\n \treturn\n+\t*/
}
// updatePackage sends package build results and info dashboard
func (b *Builder) updatePackage(pkg string, ok bool, buildLog, info string) error {
+\treturn nil
+\t/* TODO(adg): un-stub this once the new package builder design is done
return dash("POST", "package", nil, param{
"builder": b.name,
"key": b.key,
"package": pkg,
@@ -123,26 +138,44 @@ func (b *Builder) updatePackage(pkg string, ok bool, buildLog, info string) erro
"log": buildLog,
"info": info,
})
+\t*/
}
-// postCommit informs the dashboard of a new commit
-func postCommit(key string, l *HgLog) error {
- return dashStatus("POST", "commit", param{
- "key": key,
- "node": l.Hash,
- "date": l.Date,
- "user": l.Author,
- "parent": l.Parent,
- "desc": l.Desc,
- })
-}
-
-// dashboardCommit returns true if the dashboard knows about hash.
-func dashboardCommit(hash string) bool {
- err := dashStatus("GET", "commit", param{"node": hash})
+func postCommit(key, pkg string, l *HgLog) bool {
+ err := dash("POST", "commit", url.Values{"key": {key}}, obj{
+ "PackagePath": pkg,
+ "Hash": l.Hash,
+ "ParentHash": l.Parent,
+ // TODO(adg): l.Date as int64 unix epoch secs in Time field
+ "User": l.Author,
+ "Desc": l.Desc,
+ }, nil)
if err != nil {
- log.Printf("check %s: %s", hash, err)
+ log.Printf("failed to add %s to dashboard: %v", key, err)
return false
}
return true
}
+
+func dashboardCommit(pkg, hash string) bool {
+ err := dash("GET", "commit", url.Values{
+ "packagePath": {pkg},
+ "hash": {hash},
+ }, nil, nil)
+ return err == nil
+}
+
+func dashboardPackages() []string {
+ var resp []struct {
+ Path string
+ }
+ if err := dash("GET", "packages", nil, nil, &resp); err != nil {
+ log.Println("dashboardPackages:", err)
+ return nil
+ }
+ var pkgs []string
+ for _, r := range resp {
+ pkgs = append(pkgs, r.Path)
+ }
+ return pkgs
+}
misc/dashboard/builder/main.go
--- a/misc/dashboard/builder/main.go
+++ b/misc/dashboard/builder/main.go
@@ -13,6 +13,7 @@ import (
"log"
"os"
"path"
+ "path/filepath"
"regexp"
"runtime"
"strconv"
@@ -93,7 +94,7 @@ func main() {
if err := os.Mkdir(*buildroot, mkdirPerm); err != nil {
log.Fatalf("Error making build root (%s): %s", *buildroot, err)
}\n-\tif err := run(nil, *buildroot, "hg", "clone", hgUrl, goroot); err != nil {
+\tif err := hgClone(hgUrl, goroot); err != nil {
log.Fatal("Error cloning repository:", err)
}
@@ -107,7 +108,7 @@ func main() {
// if specified, build revision and return
if *buildRevision != "" {
-\t\thash, err := fullHash(*buildRevision)\n+\t\thash, err := fullHash(goroot, *buildRevision)\n if err != nil {
log.Fatal("Error finding revision: ", err)
}
@@ -246,7 +247,7 @@ func (b *Builder) build() bool {
}
// Look for hash locally before running hg pull.
-\tif _, err := fullHash(hash[:12]); err != nil {
+\tif _, err := fullHash(goroot, hash[:12]); err != nil {
// Don't have hash, so run hg pull.
if err := run(nil, goroot, "hg", "pull"); err != nil {
log.Println("hg pull failed:", err)
@@ -425,11 +426,16 @@ func commitWatcher() {
if err != nil {
log.Fatal(err)
}
+\tkey := b.key
+\n \tfor {
\t\tif *verbose {
\t\t\tlog.Printf("poll...")
\t\t}
-\t\tcommitPoll(b.key)\n+\t\tcommitPoll(key, "")
+\t\tfor _, pkg := range dashboardPackages() {
+\t\t\tcommitPoll(key, pkg)
+\t\t}
\t\tif *verbose {
\t\t\tlog.Printf("sleep...")
\t\t}
@@ -437,6 +443,18 @@ func commitWatcher() {
}
}
+func hgClone(url, path string) error {
+ return run(nil, *buildroot, "hg", "clone", url, path)
+}
+
+func hgRepoExists(path string) bool {
+ fi, err := os.Stat(filepath.Join(path, ".hg"))
+ if err != nil {
+ return false
+ }
+ return fi.IsDir()
+}
+
// HgLog represents a single Mercurial revision.
type HgLog struct {
Hash string
@@ -467,7 +485,7 @@ const xmlLogTemplate = `
// commitPoll pulls any new revisions from the hg server
// and tells the server about them.
-func commitPoll(key string) {
+func commitPoll(key, pkg string) {
// Catch unexpected panics.
defer func() {
\tif err := recover(); err != nil {
@@ -475,14 +493,29 @@ func commitPoll(key string) {
}\n \t}()
\n-\tif err := run(nil, goroot, "hg", "pull"); err != nil {
+\tpkgRoot := goroot
+\n+\tif pkg != "" {
+\t\tpkgRoot = path.Join(*buildroot, pkg)
+\t\tif !hgRepoExists(pkgRoot) {
+\t\t\tif err := hgClone(repoURL(pkg), pkgRoot); err != nil {
+\t\t\t\tlog.Printf("%s: hg clone failed: %v", pkg, err)
+\t\t\t\tif err := os.RemoveAll(pkgRoot); err != nil {
+\t\t\t\t\tlog.Printf("%s: %v", pkg, err)
+\t\t\t\t}\n+\t\t\t\treturn
+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif err := run(nil, pkgRoot, "hg", "pull"); err != nil {
log.Printf("hg pull: %v", err)
return
}
const N = 50 // how many revisions to grab
-\tdata, _, err := runLog(nil, "", goroot, "hg", "log",
+\tdata, _, err := runLog(nil, "", pkgRoot, "hg", "log",
"--encoding=utf-8",
"--limit="+strconv.Itoa(N),
"--template="+xmlLogTemplate,
@@ -511,14 +544,11 @@ func commitPoll(key string) {
\tif l.Parent == "" && i+1 < len(logs) {
\t\tl.Parent = logs[i+1].Hash
\t} else if l.Parent != "" {
-\t\t\tl.Parent, _ = fullHash(l.Parent)\n+\t\t\tl.Parent, _ = fullHash(pkgRoot, l.Parent)\n \t}
-\t\tlog.Printf("hg log: %s < %s\n", l.Hash, l.Parent)
-\t\tif l.Parent == "" {
-\t\t\t// Can't create node without parent.
-\t\t\tcontinue
+\t\tif *verbose {
+\t\t\tlog.Printf("hg log %s: %s < %s\n", pkg, l.Hash, l.Parent)
\t}
-\n \t\tif logByHash[l.Hash] == nil {
\t\t\t// Make copy to avoid pinning entire slice when only one entry is new.\n \t\t\tt := *l
@@ -528,17 +558,14 @@ func commitPoll(key string) {
for i := range logs {
\tl := &logs[i]
-\t\tif l.Parent == "" {
-\t\t\tcontinue
-\t\t}\n-\t\taddCommit(l.Hash, key)\n+\t\taddCommit(pkg, l.Hash, key)
}
}
// addCommit adds the commit with the named hash to the dashboard.
// key is the secret key for authentication to the dashboard.
// It avoids duplicate effort.
-func addCommit(hash, key string) bool {
+func addCommit(pkg, hash, key string) bool {
l := logByHash[hash]
if l == nil {
return false
@@ -548,7 +575,7 @@ func addCommit(hash, key string) bool {
}
// Check for already added, perhaps in an earlier run.
-\tif dashboardCommit(hash) {
+\tif dashboardCommit(pkg, hash) {
\tlog.Printf("%s already on dashboard\n", hash)
\t// Record that this hash is on the dashboard,\n \t\t// as must be all its parents.\n@@ -560,26 +587,24 @@ func addCommit(hash, key string) bool {
}
// Create parent first, to maintain some semblance of order.
-\tif !addCommit(l.Parent, key) {
-\t\treturn false
+\tif l.Parent != "" {
+\t\tif !addCommit(pkg, l.Parent, key) {
+\t\t\treturn false
+\t\t}
}
// Create commit.
-\tif err := postCommit(key, l); err != nil {
-\t\tlog.Printf("failed to add %s to dashboard: %v", key, err)
-\t\treturn false
-\t}\n-\treturn true
+\treturn postCommit(key, pkg, l)
}
// fullHash returns the full hash for the given Mercurial revision.
-func fullHash(rev string) (hash string, err error) {
+func fullHash(root, rev string) (hash string, err error) {
defer func() {
\tif err != nil {
\t\terr = fmt.Errorf("fullHash: %s: %s", rev, err)
\t}
}()
-\ts, _, err := runLog(nil, "", goroot,
+\ts, _, err := runLog(nil, "", root,
"hg", "log",
"--encoding=utf-8",
"--rev="+rev,
@@ -617,9 +642,21 @@ func firstTag(re *regexp.Regexp) (hash string, tag string, err error) {
\t\tcontinue
\t}
\ttag = s[1]
-\t\thash, err = fullHash(s[2])
+\t\thash, err = fullHash(goroot, s[2])
\treturn
}
err = errors.New("no matching tag found")
return
}
+
+var repoRe = regexp.MustCompile(`^code\.google\.com/p/([a-z0-9\\-]+(\\.[a-z0-9\\-]+)?)(/[a-z0-9A-Z_.\\-/]+)?$`)
+
+// repoURL returns the repository URL for the supplied import path.
+func repoURL(importPath string) string {
+ m := repoRe.FindStringSubmatch(importPath)
+ if len(m) < 2 {
+ log.Printf("repoURL: couldn't decipher %q", importPath)
+ return ""
+ }
+ return "https://code.google.com/p/" + m[1]
+}
コアとなるコードの解説
このコミットの核となる変更は、gobuilder
がダッシュボードと通信する方法と、コミットを処理する粒度を根本的に変更した点にあります。
http.go
の dash
関数
新しい dash
関数は、ダッシュボードとのHTTP通信の汎用的なインターフェースとして機能します。
- 柔軟なリクエスト/レスポンス処理: 以前は
param
マップを使ってクエリパラメータとPOSTフォームデータを区別していましたが、新しいdash
関数はurl.Values
でクエリパラメータを、req interface{}
でJSONエンコードされるリクエストボディを、resp interface{}
でJSONデコードされるレスポンスボディを扱います。これにより、ダッシュボードの新しいAPIがJSONベースのボディを期待する場合でも、柔軟に対応できるようになりました。 - エラーハンドリングの改善: レスポンスボディ全体を読み込んでからJSONデコードを行うことで、部分的な読み込みによる問題を防ぎます。また、ダッシュボードからのエラーレスポンスが
result.Error
フィールドに含まれる場合、それをGoのエラーとして返すことで、より明確なエラー伝播を実現しています。
main.go
の commitWatcher
と commitPoll
関数
これらの関数は、gobuilder
が新しいコミットをどのように監視し、処理するかを定義しています。
- パッケージごとのポーリング:
commitWatcher
は、従来のGoリポジトリ全体のポーリング (commitPoll(key, "")
) に加えて、dashboardPackages()
から取得した各パッケージに対してもcommitPoll(key, pkg)
を呼び出すようになりました。これは、ダッシュボードが管理する個々のGoパッケージの変更もgobuilder
が追跡し、ビルド結果を報告できるようになったことを意味します。 - 動的なリポジトリクローン:
commitPoll
関数内で、特定のパッケージ (pkg
) が指定された場合、そのパッケージのリポジトリ (pkgRoot
) が存在しない場合はhgClone
を使って動的にクローンを試みます。これにより、gobuilder
は必要に応じて新しいパッケージリポジトリを自動的にセットアップし、監視対象に加えることができます。これは、Goエコシステムにおけるパッケージの増加に対応するための重要な機能です。 - パッケージコンテキストでのMercurial操作:
hg pull
やhg log
、fullHash
といったMercurial操作が、goroot
(Goリポジトリ全体) だけでなく、pkgRoot
(個々のパッケージリポジトリ) のコンテキストでも実行されるようになりました。これにより、各パッケージの変更履歴を独立して追跡し、ダッシュボードに報告することが可能になります。
postCommit
と addCommit
関数
これらの関数は、コミット情報をダッシュボードに送信するロジックをカプセル化しています。
- パッケージ情報の追加:
postCommit
とaddCommit
の両方にpkg
引数が追加され、ダッシュボードに送信されるコミット情報にPackagePath
が含まれるようになりました。これにより、ダッシュボードはどのパッケージに属するコミットであるかを正確に識別し、関連するビルド結果と紐付けることができます。
これらの変更は、gobuilder
がGo言語のビルドシステムにおいて、よりきめ細かく、スケーラブルなコミットおよびパッケージ管理を可能にするための基盤を構築しています。
関連リンク
- Go言語公式ウェブサイト: https://golang.org/
- Goのビルドダッシュボード (例): https://build.golang.org/ (このコミットが直接関連しているかは不明ですが、概念的な参考として)
- Mercurial公式ウェブサイト: https://www.mercurial-scm.org/
参考にした情報源リンク
- コミットハッシュ:
263c955f2fff2016b5ff77d787d8e1b50555930a
- GitHub上のコミットページ: https://github.com/golang/go/commit/263c955f2fff2016b5ff77d787d8e1b50555930a
- Go Code Review: https://golang.org/cl/5450092 (このリンクはコミットメッセージに記載されており、詳細な議論や背景情報が含まれている可能性があります。)
- Go言語の
net/http
パッケージドキュメント: https://pkg.go.dev/net/http - Go言語の
encoding/json
パッケージドキュメント: https://pkg.go.dev/encoding/json - Go言語の
net/url
パッケージドキュメント: https://pkg.go.dev/net/url - Mercurialコマンドラインリファレンス (一般的なMercurialの知識): https://www.mercurial-scm.org/doc/hgrbook.html# [インデックス 10608] ファイルの概要
このコミットは、Go言語のビルドシステムの一部である gobuilder
ツールに対する重要な変更を含んでいます。具体的には、ダッシュボードとの通信プロトコルの更新、パッケージごとのコミット処理の導入、そして一時的なパッケージモードの無効化が主な内容です。
コミット
commit 263c955f2fff2016b5ff77d787d8e1b50555930a
Author: Andrew Gerrand <adg@golang.org>
Date: Mon Dec 5 16:44:10 2011 +1100
gobuilder: use new dashboard protocol
gobuilder: -commit mode for packages
gobuilder: cripple -package mode temporarily
R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/5450092
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/263c955f2fff2016b5ff77d787d8e1b50555930a
元コミット内容
このコミットの元の内容は以下の通りです。
gobuilder
: 新しいダッシュボードプロトコルを使用する。gobuilder
: パッケージ向けの-commit
モードを導入する。gobuilder
:-package
モードを一時的に無効化する。
変更の背景
このコミットの背景には、Go言語のビルドインフラストラクチャにおけるダッシュボードとの連携方法の進化があります。gobuilder
は、Goプロジェクトの継続的インテグレーション(CI)システムの一部として機能し、コミットのビルドとテストの結果をダッシュボードに報告する役割を担っています。
変更の主な動機は以下の点が考えられます。
- 新しいダッシュボードプロトコルへの移行: ダッシュボード側のAPIまたは通信方式が変更されたため、
gobuilder
もそれに合わせて更新する必要がありました。これにより、より効率的で堅牢なデータ交換が可能になったと考えられます。 - パッケージごとのコミット処理の導入: Go言語のエコシステムが拡大し、多くの独立したパッケージが開発される中で、モノリシックなリポジトリ全体のビルドだけでなく、個々のパッケージの変更をより細かく追跡し、ビルド結果を報告するニーズが高まったと考えられます。
-commit
モードの導入は、この粒度の細かい管理を可能にするためのものです。 - 一時的な
-package
モードの無効化: 新しいプロトコルやパッケージごとのコミット処理の導入に伴い、既存の-package
モードが新しいシステムと互換性がなかったか、あるいは新しい機能の開発中に一時的に競合を避けるために無効化されたと考えられます。これは、大規模なシステム変更における一般的な開発プラクティスであり、段階的な移行を示唆しています。
この変更は、GoプロジェクトのCI/CDパイプラインの柔軟性とスケーラビリティを向上させることを目的としています。
前提知識の解説
このコミットを理解するためには、以下の技術的背景知識が役立ちます。
- Go言語: Googleによって開発されたオープンソースのプログラミング言語。並行処理に強く、シンプルで効率的なコード記述が可能です。
- Mercurial (Hg): 分散型バージョン管理システム(DVCS)。Gitと同様に、コードの変更履歴を管理するために使用されます。Goプロジェクトの初期にはMercurialが主要なバージョン管理システムとして使用されていました。コミットログの解析やリポジトリのクローンに
hg
コマンドが使われていることから、Mercurialが深く関わっていることがわかります。 - 継続的インテグレーション (CI): ソフトウェア開発手法の一つで、開発者がコードベースに加えた変更を頻繁にメインブランチにマージし、自動的にビルドとテストを行うことで、統合の問題を早期に発見し解決します。
gobuilder
はこのCIプロセスの一部を自動化しています。 - ダッシュボード: CIシステムにおいて、ビルドのステータス、テスト結果、コードカバレッジなどの情報を視覚的に表示するウェブインターフェース。開発者はダッシュボードを通じてプロジェクトの健全性を一目で確認できます。Goプロジェクトには
build.golang.org
のような公式のビルドダッシュボードが存在します。 - HTTP/JSON:
- HTTP (Hypertext Transfer Protocol): ウェブ上でデータを交換するためのプロトコル。クライアント(
gobuilder
)とサーバー(ダッシュボード)間の通信に使用されます。 - JSON (JavaScript Object Notation): 軽量なデータ交換フォーマット。人間が読み書きしやすく、機械が解析しやすい構造を持っています。このコミットでは、ダッシュボードとの間でJSON形式のデータがやり取りされるように変更されています。
- HTTP (Hypertext Transfer Protocol): ウェブ上でデータを交換するためのプロトコル。クライアント(
- Goの
net/http
パッケージ: Go言語の標準ライブラリで、HTTPクライアントおよびサーバーを実装するための機能を提供します。 - Goの
encoding/json
パッケージ: Go言語の標準ライブラリで、JSONデータのエンコード(Goの構造体からJSONへ)およびデコード(JSONからGoの構造体へ)を行う機能を提供します。 url.Values
: Goのnet/url
パッケージにある型で、URLクエリパラメータやフォームデータを扱うためのマップのような構造です。キーと値のペアを管理し、URLエンコードされた文字列に変換するのに便利です。
技術的詳細
このコミットにおける技術的な変更は多岐にわたりますが、特に misc/dashboard/builder/http.go
と misc/dashboard/builder/main.go
の変更が重要です。
misc/dashboard/builder/http.go
の変更
dash
関数の大幅な変更:- 以前は
param map[string]string
を引数として取り、クエリパラメータまたはPOSTフォームデータを扱っていました。 - 新しい
dash
関数は、url.Values
(クエリパラメータ用) と、req
(POSTリクエストのJSONボディ用)、resp
(レスポンスのJSONデコード用) というより汎用的な引数を取るようになりました。 - これにより、GETリクエストではクエリパラメータを、POSTリクエストではJSON形式のボディを送信できるようになり、新しいダッシュボードプロトコルに合わせた柔軟な通信が可能になりました。
- JSONのエンコード/デコード処理が
json.Marshal
とjson.Unmarshal
を直接使用するように変更され、エラーハンドリングも改善されています。特に、レスポンスボディ全体を読み込んでからJSONデコードを行うように変更されています。 dashStatus
関数が削除され、そのエラーチェックロジックは新しいdash
関数のレスポンス処理に統合されました。これは、ダッシュボードからのレスポンスにStatus
フィールドとError
フィールドが含まれるという従来のプロトコルから、より一般的なJSONレスポンスとHTTPステータスコードに基づくエラーハンドリングへの移行を示唆しています。
- 以前は
todo
関数の変更: ダッシュボードから次にビルドすべきリビジョンを取得する関数です。以前はハッシュのリストを期待していましたが、新しいプロトコルでは単一のハッシュ文字列を直接返すようになりました。recordResult
関数の変更: ビルド結果をダッシュボードに送信する関数です。新しいdash
関数のシグネチャに合わせて、リクエストボディをobj
(map[string]interface{}) としてJSONエンコードして送信するようになりました。packages
およびupdatePackage
関数の無効化: これらの関数は一時的にreturn nil, nil
やreturn nil
を返すようにスタブ化されています。これはコミットメッセージの「cripple -package mode temporarily」に対応しており、新しいプロトコルやパッケージ処理の設計が完了するまで、これらの機能が一時的に利用できないことを示しています。postCommit
関数の変更: コミット情報をダッシュボードに通知する関数です。- 引数に
pkg
(パッケージパス) が追加され、ダッシュボードに送信するデータにPackagePath
フィールドが含まれるようになりました。これにより、特定のパッケージに関連するコミットをダッシュボードが認識できるようになります。 - 返り値が
error
からbool
に変更され、成功/失敗を直接示すようになりました。
- 引数に
dashboardCommit
関数の変更: ダッシュボードが特定のコミットを認識しているかを確認する関数です。引数にpkg
が追加され、パッケージごとのコミット存在チェックが可能になりました。dashboardPackages
関数の追加: ダッシュボードからビルドすべきパッケージのリストを取得するための新しい関数です。これにより、gobuilder
は複数のパッケージを監視し、それぞれに対してコミット処理を行うことができるようになります。repoURL
関数の追加: インポートパスからリポジトリのURLをデコードするための正規表現ベースのヘルパー関数です。これは、パッケージのソースコードをクローンする際に使用されると考えられます。
misc/dashboard/builder/main.go
の変更
main
関数の変更:- リポジトリのクローン処理が
hgClone
という新しいヘルパー関数に分離されました。 fullHash
関数の呼び出しにgoroot
またはpkgRoot
が引数として追加され、特定のパス内でハッシュを解決するようになりました。
- リポジトリのクローン処理が
commitWatcher
関数の変更:- この関数は、新しいコミットを定期的にポーリングする役割を担っています。
- 変更後、
commitPoll
を空のパッケージパス (""
) で呼び出すだけでなく、dashboardPackages()
から取得した各パッケージに対してもcommitPoll
を呼び出すようになりました。これにより、gobuilder
はGoリポジトリ全体だけでなく、ダッシュボードが認識している個々のパッケージのコミットも監視するようになりました。
hgClone
およびhgRepoExists
関数の追加: Mercurialリポジトリのクローンと存在チェックをカプセル化するためのヘルパー関数です。commitPoll
関数の変更:- 引数に
pkg
が追加されました。 pkg
が空でない場合、そのパスにMercurialリポジトリが存在しない場合はhgClone
を使って動的にクローンを試みます。これにより、gobuilder
は必要に応じて個々のパッケージリポジトリを自動的にセットアップし、監視対象に加えることができます。これは、Goエコシステムにおけるパッケージの増加に対応するための重要な機能です。hg pull
やhg log
の実行パスがgoroot
からpkgRoot
に変更され、パッケージごとのリポジトリ操作が可能になりました。fullHash
の呼び出しもpkgRoot
を使用するように変更されています。
- 引数に
addCommit
関数の変更:- 引数に
pkg
が追加されました。 dashboardCommit
とpostCommit
の呼び出しもpkg
を含むように変更され、パッケージごとのコミット情報をダッシュボードに送信することができます。- 親コミットの追加ロジックも、パッケージごとのコンテキストで実行されるようになりました。
- 引数に
fullHash
関数の変更: 引数にroot
(リポジトリのルートパス) が追加され、指定されたリポジトリ内でハッシュを解決するようになりました。
これらの変更は、gobuilder
がGo言語のモノリポジトリだけでなく、個々のパッケージリポジトリも効率的に管理し、ダッシュボードにそのビルド状況を報告できるようにするためのアーキテクチャ的な進化を示しています。
コアとなるコードの変更箇所
misc/dashboard/builder/http.go
--- a/misc/dashboard/builder/http.go
+++ b/misc/dashboard/builder/http.go
@@ -8,96 +8,108 @@ import (
"bytes"
"encoding/json"
"errors"
- "fmt"
+ "io"
"log"
"net/http"
"net/url"
- "strconv"
)
-type param map[string]string
+type obj map[string]interface{}
// dash runs the given method and command on the dashboard.
-// If args is not nil, it is the query or post parameters.
-// If resp is not nil, dash unmarshals the body as JSON into resp.
-func dash(meth, cmd string, resp interface{}, args param) error {
+// If args is non-nil it is encoded as the URL query string.
+// If req is non-nil it is JSON-encoded and passed as the body of the HTTP POST.
+// If resp is non-nil the server's response is decoded into the value pointed
+// to by resp (resp must be a pointer).
+func dash(meth, cmd string, args url.Values, req, resp interface{}) error {
var r *http.Response
var err error
if *verbose {
- log.Println("dash", cmd, args)
+ log.Println("dash", meth, cmd, args, req)
}
cmd = "http://" + *dashboard + "/" + cmd
- vals := make(url.Values)
- for k, v := range args {
- vals.Add(k, v)
+ if len(args) > 0 {
+ cmd += "?" + args.Encode()
}
switch meth {
case "GET":
- if q := vals.Encode(); q != "" {
- cmd += "?" + q
+ if req != nil {
+ log.Panicf("%s to %s with req", meth, cmd)
}
r, err = http.Get(cmd)
case "POST":
- r, err = http.PostForm(cmd, vals)
+ var body io.Reader
+ if req != nil {
+ b, err := json.Marshal(req)
+ if err != nil {
+ return err
+ }
+ body = bytes.NewBuffer(b)
+ }
+ r, err = http.Post(cmd, "text/json", body)
default:
- return fmt.Errorf("unknown method %q", meth)
+ log.Panicf("%s: invalid method %q", cmd, meth)
+ panic("invalid method: " + meth)
}
if err != nil {
return err
}
-
defer r.Body.Close()
- var buf bytes.Buffer
- buf.ReadFrom(r.Body)
- if resp != nil {
- if err = json.Unmarshal(buf.Bytes(), resp); err != nil {
- log.Printf("json unmarshal %#q: %s\n", buf.Bytes(), err)
- return err
- }
+ body := new(bytes.Buffer)
+ if _, err := body.ReadFrom(r.Body); err != nil {
+ return err
}
- return nil
-}
-
-func dashStatus(meth, cmd string, args param) error {
- var resp struct {
- Status string
- Error string
+
+ // Read JSON-encoded Response into provided resp
+ // and return an error if present.
+ var result = struct {
+ Response interface{}
+ Error string
+ }{
+ // Put the provided resp in here as it can be a pointer to
+ // some value we should unmarshal into.
+ Response: resp,
}
- err := dash(meth, cmd, &resp, args)
- if err != nil {
+ if err = json.Unmarshal(body.Bytes(), &result); err != nil {
+ log.Printf("json unmarshal %#q: %s\n", body.Bytes(), err)
return err
}
- if resp.Status != "OK" {
- return errors.New("/build: " + resp.Error)
+ if result.Error != "" {
+ return errors.New(result.Error)
}
+
return nil
}
// todo returns the next hash to build.
func (b *Builder) todo() (rev string, err error) {
- var resp []struct {
- Hash string
- }
- if err = dash("GET", "todo", &resp, param{"builder": b.name}); err != nil {
+ // TODO(adg): handle packages
+ args := url.Values{"builder": {b.name}}
+ var resp string
+ if err = dash("GET", "todo", args, nil, &resp); err != nil {
return
}
- if len(resp) > 0 {
- rev = resp[0].Hash
+ if resp != "" {
+ rev = resp
}
return
}
// recordResult sends build results to the dashboard
func (b *Builder) recordResult(buildLog string, hash string) error {
- return dash("POST", "build", nil, param{
- "builder": b.name,
- "key": b.key,
- "node": hash,
- "log": buildLog,
- })
+ // TODO(adg): handle packages
+ return dash("POST", "result", url.Values{"key": {b.key}}, obj{
+ "Builder": b.name,
+ "Hash": hash,
+ "Log": buildLog,
+ }, nil)
}
// packages fetches a list of package paths from the dashboard
func packages() (pkgs []string, err error) {
+ return nil, nil
+ /* TODO(adg): un-stub this once the new package builder design is done
var resp struct {
Packages []struct {
Path string
@@ -111,10 +123,13 @@ func packages() (pkgs []string, err error) {
\tpkgs = append(pkgs, p.Path)\n \t}\n \treturn\n+\t*/
}
// updatePackage sends package build results and info dashboard
func (b *Builder) updatePackage(pkg string, ok bool, buildLog, info string) error {
+\treturn nil
+\t/* TODO(adg): un-stub this once the new package builder design is done
return dash("POST", "package", nil, param{
"builder": b.name,
"key": b.key,
"package": pkg,
@@ -123,26 +138,44 @@ func (b *Builder) updatePackage(pkg string, ok bool, buildLog, info string) erro
"log": buildLog,
"info": info,
})
+\t*/
}
-// postCommit informs the dashboard of a new commit
-func postCommit(key string, l *HgLog) error {
- return dashStatus("POST", "commit", param{
- "key": key,
- "node": l.Hash,
- "date": l.Date,
- "user": l.Author,
- "parent": l.Parent,
- "desc": l.Desc,
- })
-}
-
-// dashboardCommit returns true if the dashboard knows about hash.
-func dashboardCommit(hash string) bool {
- err := dashStatus("GET", "commit", param{"node": hash})
+func postCommit(key, pkg string, l *HgLog) bool {
+ err := dash("POST", "commit", url.Values{"key": {key}}, obj{
+ "PackagePath": pkg,
+ "Hash": l.Hash,
+ "ParentHash": l.Parent,
+ // TODO(adg): l.Date as int64 unix epoch secs in Time field
+ "User": l.Author,
+ "Desc": l.Desc,
+ }, nil)
if err != nil {
- log.Printf("check %s: %s", hash, err)
+ log.Printf("failed to add %s to dashboard: %v", key, err)
return false
}
return true
}
+
+func dashboardCommit(pkg, hash string) bool {
+ err := dash("GET", "commit", url.Values{
+ "packagePath": {pkg},
+ "hash": {hash},
+ }, nil, nil)
+ return err == nil
+}
+
+func dashboardPackages() []string {
+ var resp []struct {
+ Path string
+ }
+ if err := dash("GET", "packages", nil, nil, &resp); err != nil {
+ log.Println("dashboardPackages:", err)
+ return nil
+ }
+ var pkgs []string
+ for _, r := range resp {
+ pkgs = append(pkgs, r.Path)
+ }
+ return pkgs
+}
misc/dashboard/builder/main.go
--- a/misc/dashboard/builder/main.go
+++ b/misc/dashboard/builder/main.go
@@ -13,6 +13,7 @@ import (
"log"
"os"
"path"
+ "path/filepath"
"regexp"
"runtime"
"strconv"
@@ -93,7 +94,7 @@ func main() {
if err := os.Mkdir(*buildroot, mkdirPerm); err != nil {
log.Fatalf("Error making build root (%s): %s", *buildroot, err)
}\n-\tif err := run(nil, *buildroot, "hg", "clone", hgUrl, goroot); err != nil {
+\tif err := hgClone(hgUrl, goroot); err != nil {
log.Fatal("Error cloning repository:", err)
}
@@ -107,7 +108,7 @@ func main() {
// if specified, build revision and return
if *buildRevision != "" {
-\t\thash, err := fullHash(*buildRevision)\n+\t\thash, err := fullHash(goroot, *buildRevision)\n if err != nil {
log.Fatal("Error finding revision: ", err)
}
@@ -246,7 +247,7 @@ func (b *Builder) build() bool {
}
// Look for hash locally before running hg pull.
-\tif _, err := fullHash(hash[:12]); err != nil {
+\tif _, err := fullHash(goroot, hash[:12]); err != nil {
// Don't have hash, so run hg pull.
if err := run(nil, goroot, "hg", "pull"); err != nil {
log.Println("hg pull failed:", err)
@@ -425,11 +426,16 @@ func commitWatcher() {
if err != nil {
log.Fatal(err)
}
+\tkey := b.key
+\n \tfor {
\t\tif *verbose {
\t\t\tlog.Printf("poll...")
\t\t}
-\t\tcommitPoll(b.key)\n+\t\tcommitPoll(key, "")
+\t\tfor _, pkg := range dashboardPackages() {
+\t\t\tcommitPoll(key, pkg)
+\t\t}
\t\tif *verbose {
\t\t\tlog.Printf("sleep...")
\t\t}
@@ -437,6 +443,18 @@ func commitWatcher() {
}
}
+func hgClone(url, path string) error {
+ return run(nil, *buildroot, "hg", "clone", url, path)
+}
+
+func hgRepoExists(path string) bool {
+ fi, err := os.Stat(filepath.Join(path, ".hg"))
+ if err != nil {
+ return false
+ }
+ return fi.IsDir()
+}
+
// HgLog represents a single Mercurial revision.
type HgLog struct {
Hash string
@@ -467,7 +485,7 @@ const xmlLogTemplate = `
// commitPoll pulls any new revisions from the hg server
// and tells the server about them.
-func commitPoll(key string) {
+func commitPoll(key, pkg string) {
// Catch unexpected panics.\n \tdefer func() {
\tif err := recover(); err != nil {
\t\tlog.Println("commitPoll: panic:", err)
@@ -475,14 +493,29 @@ func commitPoll(key string) {
}\n \t}()
\n-\tif err := run(nil, goroot, "hg", "pull"); err != nil {
+\tpkgRoot := goroot
+\n+\tif pkg != "" {
+\t\tpkgRoot = path.Join(*buildroot, pkg)
+\t\tif !hgRepoExists(pkgRoot) {
+\t\t\tif err := hgClone(repoURL(pkg), pkgRoot); err != nil {
+\t\t\t\tlog.Printf("%s: hg clone failed: %v", pkg, err)
+\t\t\t\tif err := os.RemoveAll(pkgRoot); err != nil {
+\t\t\t\t\tlog.Printf("%s: %v", pkg, err)
+\t\t\t\t}\n+\t\t\t\treturn
+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif err := run(nil, pkgRoot, "hg", "pull"); err != nil {
log.Printf("hg pull: %v", err)
return
}
const N = 50 // how many revisions to grab
-\tdata, _, err := runLog(nil, "", goroot, "hg", "log",
+\tdata, _, err := runLog(nil, "", pkgRoot, "hg", "log",
"--encoding=utf-8",
"--limit="+strconv.Itoa(N),
"--template="+xmlLogTemplate,
@@ -511,14 +544,11 @@ func commitPoll(key string) {
\tif l.Parent == "" && i+1 < len(logs) {
\t\tl.Parent = logs[i+1].Hash
\t} else if l.Parent != "" {
-\t\t\tl.Parent, _ = fullHash(l.Parent)\n+\t\t\tl.Parent, _ = fullHash(pkgRoot, l.Parent)\n \t}
-\t\tlog.Printf("hg log: %s < %s\n", l.Hash, l.Parent)
-\t\tif l.Parent == "" {
-\t\t\t// Can't create node without parent.
-\t\t\tcontinue
+\t\tif *verbose {
+\t\t\tlog.Printf("hg log %s: %s < %s\n", pkg, l.Hash, l.Parent)
\t}
-\n \t\tif logByHash[l.Hash] == nil {
\t\t\t// Make copy to avoid pinning entire slice when only one entry is new.\n \t\t\tt := *l
@@ -528,17 +558,14 @@ func commitPoll(key string) {
for i := range logs {
\tl := &logs[i]
-\t\tif l.Parent == "" {
-\t\t\tcontinue
-\t\t}\n-\t\taddCommit(l.Hash, key)\n+\t\taddCommit(pkg, l.Hash, key)
}
}
// addCommit adds the commit with the named hash to the dashboard.
// key is the secret key for authentication to the dashboard.
// It avoids duplicate effort.
-func addCommit(hash, key string) bool {
+func addCommit(pkg, hash, key string) bool {
l := logByHash[hash]
if l == nil {
return false
@@ -548,7 +575,7 @@ func addCommit(hash, key string) bool {
}
// Check for already added, perhaps in an earlier run.
-\tif dashboardCommit(hash) {
+\tif dashboardCommit(pkg, hash) {
\tlog.Printf("%s already on dashboard\n", hash)
\t// Record that this hash is on the dashboard,\n \t\t// as must be all its parents.\n@@ -560,26 +587,24 @@ func addCommit(hash, key string) bool {
}
// Create parent first, to maintain some semblance of order.
-\tif !addCommit(l.Parent, key) {
-\t\treturn false
+\tif l.Parent != "" {
+\t\tif !addCommit(pkg, l.Parent, key) {
+\t\t\treturn false
+\t\t}
}
// Create commit.
-\tif err := postCommit(key, l); err != nil {
-\t\tlog.Printf("failed to add %s to dashboard: %v", key, err)
-\t\treturn false
-\t}\n-\treturn true
+\treturn postCommit(key, pkg, l)
}
// fullHash returns the full hash for the given Mercurial revision.
-func fullHash(rev string) (hash string, err error) {
+func fullHash(root, rev string) (hash string, err error) {
defer func() {
\tif err != nil {
\t\terr = fmt.Errorf("fullHash: %s: %s", rev, err)
\t}
}()
-\ts, _, err := runLog(nil, "", goroot,
+\ts, _, err := runLog(nil, "", root,
"hg", "log",
"--encoding=utf-8",
"--rev="+rev,
@@ -617,9 +642,21 @@ func firstTag(re *regexp.Regexp) (hash string, tag string, err error) {
\t\tcontinue
\t}
\ttag = s[1]
-\t\thash, err = fullHash(s[2])
+\t\thash, err = fullHash(goroot, s[2])
\treturn
}
err = errors.New("no matching tag found")
return
}
+
+var repoRe = regexp.MustCompile(`^code\.google\.com/p/([a-z0-9\\-]+(\\.[a-z0-9\\-]+)?)(/[a-z0-9A-Z_.\\-/]+)?$`)
+
+// repoURL returns the repository URL for the supplied import path.
+func repoURL(importPath string) string {
+ m := repoRe.FindStringSubmatch(importPath)
+ if len(m) < 2 {
+ log.Printf("repoURL: couldn't decipher %q", importPath)
+ return ""
+ }
+ return "https://code.google.com/p/" + m[1]
+}
コアとなるコードの解説
このコミットの核となる変更は、gobuilder
がダッシュボードと通信する方法と、コミットを処理する粒度を根本的に変更した点にあります。
http.go
の dash
関数
新しい dash
関数は、ダッシュボードとのHTTP通信の汎用的なインターフェースとして機能します。
- 柔軟なリクエスト/レスポンス処理: 以前は
param
マップを使ってクエリパラメータとPOSTフォームデータを区別していましたが、新しいdash
関数はurl.Values
でクエリパラメータを、req interface{}
でJSONエンコードされるリクエストボディを、resp interface{}
でJSONデコードされるレスポンスボディを扱います。これにより、ダッシュボードの新しいAPIがJSONベースのボディを期待する場合でも、柔軟に対応できるようになりました。 - エラーハンドリングの改善: レスポンスボディ全体を読み込んでからJSONデコードを行うことで、部分的な読み込みによる問題を防ぎます。また、ダッシュボードからのエラーレスポンスが
result.Error
フィールドに含まれる場合、それをGoのエラーとして返すことで、より明確なエラー伝播を実現しています。
main.go
の commitWatcher
と commitPoll
関数
これらの関数は、gobuilder
が新しいコミットをどのように監視し、処理するかを定義しています。
- パッケージごとのポーリング:
commitWatcher
は、従来のGoリポジトリ全体のポーリング (commitPoll(key, "")
) に加えて、dashboardPackages()
から取得した各パッケージに対してもcommitPoll(key, pkg)
を呼び出すようになりました。これは、ダッシュボードが管理する個々のGoパッケージの変更もgobuilder
が追跡し、ビルド結果を報告できるようになったことを意味します。 - 動的なリポジトリクローン:
commitPoll
関数内で、特定のパッケージ (pkg
) が指定された場合、そのパッケージのリポジトリ (pkgRoot
) が存在しない場合はhgClone
を使って動的にクローンを試みます。これにより、gobuilder
は必要に応じて新しいパッケージリポジトリを自動的にセットアップし、監視対象に加えることができます。これは、Goエコシステムにおけるパッケージの増加に対応するための重要な機能です。 - パッケージコンテキストでのMercurial操作:
hg pull
やhg log
、fullHash
といったMercurial操作が、goroot
(Goリポジトリ全体) だけでなく、pkgRoot
(個々のパッケージリポジトリ) のコンテキストでも実行されるようになりました。これにより、各パッケージの変更履歴を独立して追跡し、ダッシュボードに報告することが可能になります。
postCommit
と addCommit
関数
これらの関数は、コミット情報をダッシュボードに送信するロジックをカプセル化しています。
- パッケージ情報の追加:
postCommit
とaddCommit
の両方にpkg
引数が追加され、ダッシュボードに送信されるコミット情報にPackagePath
が含まれるようになりました。これにより、ダッシュボードはどのパッケージに属するコミットであるかを正確に識別し、関連するビルド結果と紐付けることができます。
これらの変更は、gobuilder
がGo言語のビルドシステムにおいて、よりきめ細かく、スケーラブルなコミットおよびパッケージ管理を可能にするための基盤を構築しています。
関連リンク
- Go言語公式ウェブサイト: https://golang.org/
- Goのビルドダッシュボード (例): https://build.golang.org/ (このコミットが直接関連しているかは不明ですが、概念的な参考として)
- Mercurial公式ウェブサイト: https://www.mercurial-scm.org/
参考にした情報源リンク
- コミットハッシュ:
263c955f2fff2016b5ff77d787d8e1b50555930a
- GitHub上のコミットページ: https://github.com/golang/go/commit/263c955f2fff2016b5ff77d787d8e1b50555930a
- Go Code Review: https://golang.org/cl/5450092 (このリンクはコミットメッセージに記載されており、詳細な議論や背景情報が含まれている可能性があります。)
- Go言語の
net/http
パッケージドキュメント: https://pkg.go.dev/net/http - Go言語の
encoding/json
パッケージドキュメント: https://pkg.go.dev/encoding/json - Go言語の
net/url
パッケージドキュメント: https://pkg.go.dev/net/url - Mercurialコマンドラインリファレンス (一般的なMercurialの知識): https://www.mercurial-scm.org/doc/hgrbook.html