Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 11144] ファイルの概要

このコミットは、Goコマンドラインツール群(go fix, go fmt, go vet)の出力において、ファイルパスを絶対パスではなく相対パスで表示するように変更するものです。これにより、出力の可読性と移植性が向上します。

コミット

commit c2ffd9d0c27e8bdecaf0e717481def74b40b364d
Author: Russ Cox <rsc@golang.org>
Date:   Thu Jan 12 15:28:52 2012 -0800

    cmd/go: use relative paths in go fix, go fmt, go vet output
    
    Fixes #2686.
    
    R=golang-dev, bradfitz, r
    CC=golang-dev
    https://golang.org/cl/5528089

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/c2ffd9d0c27e8bdecaf0e717481def74b40b364d

元コミット内容

cmd/go: use relative paths in go fix, go fmt, go vet output

Fixes #2686.

R=golang-dev, bradfitz, r
CC=golang-dev
https://golang.org/cl/5528089

変更の背景

Go言語のツールであるgo fixgo fmtgo vetは、コードの修正、フォーマット、静的解析を行う際に、処理対象のファイルパスやエラーメッセージに絶対パスを含んで出力していました。しかし、絶対パスは以下のような問題を引き起こす可能性がありました。

  1. 可読性の低下: 長い絶対パスは、特に深いディレクトリ構造を持つプロジェクトにおいて、出力が冗長になり、重要な情報が埋もれてしまう原因となります。
  2. 移植性の問題: ビルド環境や開発環境が異なる場合、絶対パスが一致しないため、ログの解析やスクリプトの実行に問題が生じることがありました。例えば、CI/CD環境とローカル環境でパスが異なる場合、出力の比較や自動処理が困難になります。
  3. セキュリティとプライバシー: 絶対パスには、ユーザー名やプロジェクトの物理的な場所など、環境固有の情報が含まれることがあり、これが意図せず公開されるリスクがありました。

これらの問題を解決し、よりクリーンでポータブルな出力を提供するために、ツールが現在の作業ディレクトリからの相対パスを使用するように変更する必要がありました。この変更は、ユーザーエクスペリエンスの向上と、Goツールの柔軟性の強化を目的としています。

前提知識の解説

go fix, go fmt, go vetの役割

  • go fix: 古いGoプログラムを新しいGoのバージョンで動作するように自動的に修正するツールです。APIの変更や言語仕様の変更に対応するために使用されます。
  • go fmt: Goのソースコードを標準的なスタイルに自動的にフォーマットするツールです。これにより、Goコミュニティ全体で一貫したコードスタイルが維持され、可読性が向上します。
  • go vet: Goのソースコードを静的に解析し、潜在的なバグや疑わしい構造を報告するツールです。例えば、到達不能なコード、フォーマット文字列の不一致、ロックの誤用などを検出します。

これらのツールは、Go開発において日常的に使用される重要なユーティリティです。

絶対パスと相対パス

  • 絶対パス (Absolute Path): ファイルシステム上のルートディレクトリから目的のファイルまたはディレクトリまでの完全なパスです。例えば、Linux/macOSでは/home/user/project/main.go、WindowsではC:\Users\user\project\main.goのようになります。絶対パスは、どのディレクトリから参照しても常に同じ場所を指します。
  • 相対パス (Relative Path): 現在の作業ディレクトリを基準として、目的のファイルまたはディレクトリまでのパスです。例えば、現在の作業ディレクトリが/home/user/projectの場合、main.goへの相対パスはmain.goとなります。サブディレクトリ内のファイルであればsrc/utils/helper.goのようになります。相対パスは、現在の作業ディレクトリによって指す場所が変わります。

Go言語におけるfilepathパッケージとosパッケージの基本的な関数

  • os.Getwd(): 現在の作業ディレクトリの絶対パスを返します。
  • filepath.Rel(basepath, targetpath): targetpathbasepathからの相対パスとしてどのように表現されるかを計算します。例えば、filepath.Rel("/home/user/project", "/home/user/project/src/main.go")src/main.goを返します。エラーが発生した場合や、相対パスが絶対パスよりも長くなる場合はエラーを返します。
  • strings.Replace(s, old, new, n): 文字列s内でoldのすべての非オーバーラップインスタンスをnewに置き換えた新しい文字列を返します。nが負の場合、すべてのインスタンスが置き換えられます。

これらの関数は、ファイルパスの操作や文字列の置換においてGoプログラムで頻繁に使用されます。

技術的詳細

このコミットの主要な変更点は、src/cmd/go/build.go内のshowOutput関数とrelPaths関数の修正、およびgo fix, go fmt, go vetコマンドがファイルパスを処理する際に新しいrelPaths関数を使用するように変更された点です。

src/cmd/go/build.goにおける変更

  1. showOutput関数の変更:

    • この関数は、goコマンドが外部ツール(go fix, go fmt, go vetなど)を実行した際の出力を整形して表示するために使用されます。
    • 変更前は、relPaths関数がout文字列全体に対して適用されていましたが、変更後はout文字列内の特定の絶対パス部分のみを相対パスに変換するロジックが追加されました。
    • 具体的には、現在の作業ディレクトリ(pwd)と出力元のディレクトリ(dir)を比較し、dirpwdからの相対パスとして短くなる場合に、出力文字列(suffix)内のdirの絶対パス表記を相対パス表記に置き換える処理が追加されました。これにより、goコマンドの出力がより簡潔になります。
    • また、$WORKディレクトリ(Goのビルドプロセスで使用される一時作業ディレクトリ)への参照も$WORKというプレースホルダーに置き換えられ、出力の汎用性が高められています。
  2. relPaths関数の変更:

    • 変更前は、relPaths(dir, out string)というシグネチャで、単一のディレクトリと出力文字列を受け取り、出力文字列内のパスを相対化していました。
    • 変更後は、relPaths(paths []string) []stringというシグネチャに変更され、文字列のスライス(複数のファイルパス)を受け取り、それぞれのパスを相対化して新しいスライスとして返すようになりました。
    • この新しいrelPaths関数は、入力された各パスに対してos.Getwd()で現在の作業ディレクトリを取得し、filepath.Rel()を使用してそのパスが現在の作業ディレクトリからの相対パスとして短くなるかどうかを判断します。もし短くなるのであれば、そのパスを相対パスに変換して返します。これにより、個々のファイルパスがより効率的かつ正確に相対化されるようになりました。

go fix, go fmt, go vetにおける変更

  • src/cmd/go/fix.gosrc/cmd/go/fmt.gosrc/cmd/go/vet.goの各ファイルにおいて、runFixrunFmtrunVet関数内で外部コマンド(gofix, gofmt, govet)を呼び出す際に、処理対象のファイルパスリスト(pkg.gofiles)を直接渡すのではなく、新しく変更されたrelPaths関数を通して渡すように修正されました。
  • これにより、これらのツールが実行するコマンドに渡されるファイルパスが、現在の作業ディレクトリからの相対パスとなり、ツールの出力も相対パスで表示されるようになります。

これらの変更により、Goツール群の出力がよりユーザーフレンドリーで、環境に依存しないものとなりました。

コアとなるコードの変更箇所

src/cmd/go/build.go

--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -794,8 +794,13 @@ func (b *builder) showcmd(dir string, format string, args ...interface{}) {
 // showOutput also replaces references to the work directory with $WORK.
 //
 func (b *builder) showOutput(dir, desc, out string) {
-\tprefix := "# " + desc + "\n"
-\tsuffix := relPaths(dir, out)
+\tprefix := "# " + desc
+\tsuffix := "\n" + out
+\tpwd, _ := os.Getwd()
+\tif reldir, err := filepath.Rel(pwd, dir); err == nil && len(reldir) < len(dir) {
+\t\tsuffix = strings.Replace(suffix, " "+dir, " "+reldir, -1)
+\t\tsuffix = strings.Replace(suffix, "\n"+dir, "\n"+reldir, -1)
+\t}
 \tsuffix = strings.Replace(suffix, " "+b.work, " $WORK", -1)
 
 \tb.output.Lock()
@@ -803,16 +808,19 @@ func (b *builder) showOutput(dir, desc, out string) {
 \tfmt.Print(prefix, suffix)
 }\n
-// relPaths returns a copy of out with references to dir
-// made relative to the current directory if that would be shorter.\n-func relPaths(dir, out string) string {
-\tx := "\n" + out
+// relPaths returns a copy of paths with absolute paths
+// made relative to the current directory if they would be shorter.
+func relPaths(paths []string) []string {
+\tvar out []string
 \tpwd, _ := os.Getwd()
-\tif reldir, err := filepath.Rel(pwd, dir); err == nil && len(reldir) < len(dir) {
-\t\tx = strings.Replace(x, " "+dir, " "+reldir, -1)
-\t\tx = strings.Replace(x, "
+
+\tfor _, p := range paths {
+\t\trel, err := filepath.Rel(pwd, p)
+\t\tif err == nil && len(rel) < len(p) {
+\t\t\tp = rel
+\t\t}
+\t\tout = append(out, p)
 \t}
-\treturn x[1:]
+\treturn out
 }\n
 // errPrintedOutput is a special error indicating that a command failed

src/cmd/go/fix.go

--- a/src/cmd/go/fix.go
+++ b/src/cmd/go/fix.go
@@ -25,6 +25,6 @@ func runFix(cmd *Command, args []string) {
 		// Use pkg.gofiles instead of pkg.Dir so that
 		// the command only applies to this package,
 		// not to packages in subdirectories.
-\t\trun(stringList("gofix", pkg.gofiles))\n+\t\trun(stringList("gofix", relPaths(pkg.gofiles)))\n \t}\n }\n```

### `src/cmd/go/fmt.go`

```diff
--- a/src/cmd/go/fmt.go
+++ b/src/cmd/go/fmt.go
@@ -26,7 +26,7 @@ func runFmt(cmd *Command, args []string) {
 		// Use pkg.gofiles instead of pkg.Dir so that
 		// the command only applies to this package,
 		// not to packages in subdirectories.
-\t\trun(stringList("gofmt", "-I", "w", pkg.gofiles))\n+\t\trun(stringList("gofmt", "-l", "-w", relPaths(pkg.gofiles)))\n \t}\n }\n \n```

### `src/cmd/go/vet.go`

```diff
--- a/src/cmd/go/vet.go
+++ b/src/cmd/go/vet.go
@@ -25,6 +25,6 @@ func runVet(cmd *Command, args []string) {
 		// Use pkg.gofiles instead of pkg.Dir so that
 		// the command only applies to this package,
 		// not to packages in subdirectories.
-\t\trun("govet", pkg.gofiles)\n+\t\trun("govet", relPaths(pkg.gofiles))\n \t}\n }\n```

## コアとなるコードの解説

### `src/cmd/go/build.go`

*   **`showOutput`関数の変更**:
    *   `prefix := "# " + desc` と `suffix := "\n" + out` は、出力のヘッダーと本体を初期化しています。
    *   `pwd, _ := os.Getwd()` で現在の作業ディレクトリを取得します。
    *   `if reldir, err := filepath.Rel(pwd, dir); err == nil && len(reldir) < len(dir)` の条件は、`dir`(出力元のディレクトリ)が現在の作業ディレクトリ`pwd`からの相対パスとして表現でき、かつその相対パスが元の絶対パスよりも短い場合に真となります。
    *   この条件が真の場合、`strings.Replace` を用いて、`suffix`文字列内の`dir`の絶対パス表記を`reldir`(相対パス)に置き換えています。これにより、出力されるパスが短縮されます。
    *   `strings.Replace(suffix, " "+b.work, " $WORK", -1)` は、ビルドの一時ディレクトリ`b.work`への参照を`$WORK`という汎用的なプレースホルダーに置き換えることで、出力の環境依存性をさらに低減しています。

*   **`relPaths`関数の変更**:
    *   変更前の`relPaths`は文字列全体を処理していましたが、新しい`relPaths`は`[]string`(文字列のスライス)を受け取り、`[]string`を返します。これは、複数のファイルパスを個別に処理するのに適しています。
    *   `var out []string` で結果を格納する新しいスライスを初期化します。
    *   `for _, p := range paths` ループで、入力された各パス`p`を処理します。
    *   `rel, err := filepath.Rel(pwd, p)` で、現在の作業ディレクトリ`pwd`からパス`p`への相対パスを計算します。
    *   `if err == nil && len(rel) < len(p)` の条件は、相対パスの計算が成功し、かつ相対パスが元の絶対パスよりも短い場合に真となります。
    *   この条件が真の場合、`p = rel` とすることで、現在のパス`p`を相対パス`rel`で上書きします。
    *   最後に `out = append(out, p)` で、処理されたパス(相対パスまたは元の絶対パス)を結果のスライスに追加します。
    *   この関数は、各ファイルパスを個別に評価し、必要に応じて相対パスに変換することで、より正確で柔軟なパスの相対化を実現しています。

### `src/cmd/go/fix.go`, `src/cmd/go/fmt.go`, `src/cmd/go/vet.go`

これらのファイルでは、`run`関数(外部コマンドを実行するヘルパー関数)の呼び出しにおいて、`pkg.gofiles`(処理対象のGoファイルパスのリスト)を直接渡す代わりに、`relPaths(pkg.gofiles)`を介して渡すように変更されています。

*   例: `run(stringList("gofix", pkg.gofiles))` が `run(stringList("gofix", relPaths(pkg.gofiles)))` に変更。
*   これにより、`gofix`, `gofmt`, `govet`といった外部ツールに渡されるファイルパスが、`relPaths`関数によって現在の作業ディレクトリからの相対パスに変換されます。結果として、これらのツールの標準出力に表示されるファイルパスも相対パスとなり、出力の簡潔さと移植性が向上します。

## 関連リンク

*   Go CL: [https://golang.org/cl/5528089](https://golang.org/cl/5528089) (ただし、このリンクは現在、別のコミット内容を示している可能性があります。これはGoのGerritシステムにおける古いCL番号の再利用またはシステムの変更によるものです。)
*   関連するGo Issue: `#2686` (このコミットが修正したとされるIssue番号ですが、現在のGoリポジトリのIssueトラッカーで直接この番号のIssueを見つけるのは困難な場合があります。)

## 参考にした情報源リンク

*   Go言語 `os` パッケージドキュメント: [https://pkg.go.dev/os](https://pkg.go.dev/os)
*   Go言語 `path/filepath` パッケージドキュメント: [https://pkg.go.dev/path/filepath](https://pkg.go.dev/path/filepath)
*   Go言語 `strings` パッケージドキュメント: [https://pkg.go.dev/strings](https://pkg.go.dev/strings)
*   Goコマンドドキュメント (go fix, go fmt, go vet): [https://go.dev/cmd/go/](https://go.dev/cmd/go/) (各ツールの詳細については、このページからリンクされているサブコマンドのドキュメントを参照してください。)