external.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. // Copyright 2018 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package packages
  5. // This file defines the protocol that enables an external "driver"
  6. // tool to supply package metadata in place of 'go list'.
  7. import (
  8. "bytes"
  9. "encoding/json"
  10. "fmt"
  11. "os"
  12. "os/exec"
  13. "slices"
  14. "strings"
  15. )
  16. // DriverRequest defines the schema of a request for package metadata
  17. // from an external driver program. The JSON-encoded DriverRequest
  18. // message is provided to the driver program's standard input. The
  19. // query patterns are provided as command-line arguments.
  20. //
  21. // See the package documentation for an overview.
  22. type DriverRequest struct {
  23. Mode LoadMode `json:"mode"`
  24. // Env specifies the environment the underlying build system should be run in.
  25. Env []string `json:"env"`
  26. // BuildFlags are flags that should be passed to the underlying build system.
  27. BuildFlags []string `json:"build_flags"`
  28. // Tests specifies whether the patterns should also return test packages.
  29. Tests bool `json:"tests"`
  30. // Overlay maps file paths (relative to the driver's working directory)
  31. // to the contents of overlay files (see Config.Overlay).
  32. Overlay map[string][]byte `json:"overlay"`
  33. }
  34. // DriverResponse defines the schema of a response from an external
  35. // driver program, providing the results of a query for package
  36. // metadata. The driver program must write a JSON-encoded
  37. // DriverResponse message to its standard output.
  38. //
  39. // See the package documentation for an overview.
  40. type DriverResponse struct {
  41. // NotHandled is returned if the request can't be handled by the current
  42. // driver. If an external driver returns a response with NotHandled, the
  43. // rest of the DriverResponse is ignored, and go/packages will fallback
  44. // to the next driver. If go/packages is extended in the future to support
  45. // lists of multiple drivers, go/packages will fall back to the next driver.
  46. NotHandled bool
  47. // Compiler and Arch are the arguments pass of types.SizesFor
  48. // to get a types.Sizes to use when type checking.
  49. Compiler string
  50. Arch string
  51. // Roots is the set of package IDs that make up the root packages.
  52. // We have to encode this separately because when we encode a single package
  53. // we cannot know if it is one of the roots as that requires knowledge of the
  54. // graph it is part of.
  55. Roots []string `json:",omitempty"`
  56. // Packages is the full set of packages in the graph.
  57. // The packages are not connected into a graph.
  58. // The Imports if populated will be stubs that only have their ID set.
  59. // Imports will be connected and then type and syntax information added in a
  60. // later pass (see refine).
  61. Packages []*Package
  62. // GoVersion is the minor version number used by the driver
  63. // (e.g. the go command on the PATH) when selecting .go files.
  64. // Zero means unknown.
  65. GoVersion int
  66. }
  67. // driver is the type for functions that query the build system for the
  68. // packages named by the patterns.
  69. type driver func(cfg *Config, patterns []string) (*DriverResponse, error)
  70. // findExternalDriver returns the file path of a tool that supplies
  71. // the build system package structure, or "" if not found.
  72. // If GOPACKAGESDRIVER is set in the environment findExternalTool returns its
  73. // value, otherwise it searches for a binary named gopackagesdriver on the PATH.
  74. func findExternalDriver(cfg *Config) driver {
  75. const toolPrefix = "GOPACKAGESDRIVER="
  76. tool := ""
  77. for _, env := range cfg.Env {
  78. if val, ok := strings.CutPrefix(env, toolPrefix); ok {
  79. tool = val
  80. }
  81. }
  82. if tool != "" && tool == "off" {
  83. return nil
  84. }
  85. if tool == "" {
  86. var err error
  87. tool, err = exec.LookPath("gopackagesdriver")
  88. if err != nil {
  89. return nil
  90. }
  91. }
  92. return func(cfg *Config, patterns []string) (*DriverResponse, error) {
  93. req, err := json.Marshal(DriverRequest{
  94. Mode: cfg.Mode,
  95. Env: cfg.Env,
  96. BuildFlags: cfg.BuildFlags,
  97. Tests: cfg.Tests,
  98. Overlay: cfg.Overlay,
  99. })
  100. if err != nil {
  101. return nil, fmt.Errorf("failed to encode message to driver tool: %v", err)
  102. }
  103. buf := new(bytes.Buffer)
  104. stderr := new(bytes.Buffer)
  105. cmd := exec.CommandContext(cfg.Context, tool, patterns...)
  106. cmd.Dir = cfg.Dir
  107. // The cwd gets resolved to the real path. On Darwin, where
  108. // /tmp is a symlink, this breaks anything that expects the
  109. // working directory to keep the original path, including the
  110. // go command when dealing with modules.
  111. //
  112. // os.Getwd stdlib has a special feature where if the
  113. // cwd and the PWD are the same node then it trusts
  114. // the PWD, so by setting it in the env for the child
  115. // process we fix up all the paths returned by the go
  116. // command.
  117. //
  118. // (See similar trick in Invocation.run in ../../internal/gocommand/invoke.go)
  119. cmd.Env = append(slices.Clip(cfg.Env), "PWD="+cfg.Dir)
  120. cmd.Stdin = bytes.NewReader(req)
  121. cmd.Stdout = buf
  122. cmd.Stderr = stderr
  123. if err := cmd.Run(); err != nil {
  124. return nil, fmt.Errorf("%v: %v: %s", tool, err, cmd.Stderr)
  125. }
  126. if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTDRIVERERRORS") != "" {
  127. fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(cmd), stderr)
  128. }
  129. var response DriverResponse
  130. if err := json.Unmarshal(buf.Bytes(), &response); err != nil {
  131. return nil, err
  132. }
  133. return &response, nil
  134. }
  135. }