external.go (5338B)
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 }