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