Go
golang

wire に `-: cannot find package "pattern=." in any of:` って言われたら

TR;DR

golang.org/x/tools を 2018-10-13 以降のバージョン,具体的には 13ebad8 が含まれるバージョンまでアップデートしましょう.

発端

ある日,grapi で .go generate ./...` を実行したら突然死した.wire がすべて失敗しているようだった.

-: cannot find package "pattern=." in any of:
                /usr/local/Cellar/go/1.11.1/libexec/src/pattern=. (from $GOROOT)
                /Users/izumin/src/pattern=. (from $GOPATH)
wire: generate failed
cmd/grapi-gen-type/di/wire_gen.go:3: running "wire": exit status 1

pattern=. とは :thinking:

調査

当時の wire のバージョンは v0.2.0 を使っていた.

とりあえず pattern= で grep する.

func load(ctx context.Context, wd string, env []string, patterns []string) ([]*packages.Package, []error) {
  cfg := &packages.Config{
      Context:    ctx,
      Mode:       packages.LoadAllSyntax,
      Dir:        wd,
      Env:        env,
      BuildFlags: []string{"-tags=wireinject"},
      // TODO(light): Use ParseFile to skip function bodies and comments in indirect packages.
  }
  escaped := make([]string, len(patterns))
  for i := range patterns {
      escaped[i] = "pattern=" + patterns[i]
  }
  pkgs, err := packages.Load(cfg, escaped...)
  if err != nil {
      return nil, []error{err}
  }

internal/wire/parse.go#L332-L348

internal/wire.load はコード生成時の初っ端に呼ばれる関数.patterns には引数 or . が入っている.これの各要素に対して pattern= という prefix をつけている.

packagesgolang.org/x/tools/go/packages で, 静的解析まわりを使いやすくするためのラッパー.ここでもおもむろに "pattern" とかで grep する.

  // Extract file= and other [querytype]= patterns. Report an error if querytype
  // doesn't exist.
extractQueries:
  for _, pattern := range patterns {
      eqidx := strings.Index(pattern, "=")
      if eqidx < 0 {
          restPatterns = append(restPatterns, pattern)
      } else {
          query, value := pattern[:eqidx], pattern[eqidx+len("="):]
          switch query {
          case "file":
              containFiles = append(containFiles, value)
          case "pattern":
              restPatterns = append(restPatterns, value)
          case "name":
              packagesNamed = append(packagesNamed, value)
          case "": // not a reserved query
              restPatterns = append(restPatterns, pattern)
          default:
              for _, rune := range query {
                  if rune < 'a' || rune > 'z' { // not a reserved query
                      restPatterns = append(restPatterns, pattern)
                      continue extractQueries
                  }
              }
              // Reject all other patterns containing "="
              return nil, fmt.Errorf("invalid query type %q in query pattern %q", query, pattern)
          }
      }
  }

go/packages/golist.go#L55-L84 - github.com/golang/tools@3c39ce7

引数をパースしてる.おもむろに blame すると,10/13のコミット で追加されたことがわかる.

と,いうことで golang.org/x/tools のバージョンを上げましょう.

余談

packages は内部で go list 叩くんですね.
go list の内部実装が internal になってるのを export しないのはなんでなんだろう.

// golistDriverCurrent uses the "go list" command to expand the
// pattern words and return metadata for the specified packages.
// dir may be "" and env may be nil, as per os/exec.Command.
func golistDriverCurrent(cfg *Config, words ...string) (*driverResponse, error) {
  // go list uses the following identifiers in ImportPath and Imports:
  //
  //  "p"         -- importable package or main (command)
  //      "q.test"        -- q's test executable
  //  "p [q.test]"        -- variant of p as built for q's test executable
  //  "q_test [q.test]"   -- q's external test package
  //
  // The packages p that are built differently for a test q.test
  // are q itself, plus any helpers used by the external test q_test,
  // typically including "testing" and all its dependencies.

  // Run "go list" for complete
  // information on the specified packages.
  buf, err := golist(cfg, golistargs(cfg, words))
  if err != nil {
      return nil, err
  }

go/packages/golist.go#L185-L205@13ebad8 - github.com/golang/tools@13ebad8