// Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // This file enables an external tool to intercept package requests. // If the tool is present then its results are used in preference to // the go list command. package packages import ( "bytes" "encoding/json" "fmt" "os" "os/exec" "strings" ) // The Driver Protocol // // The driver, given the inputs to a call to Load, returns metadata about the packages specified. // This allows for different build systems to support go/packages by telling go/packages how the // packages' source is organized. // The driver is a binary, either specified by the GOPACKAGESDRIVER environment variable or in // the path as gopackagesdriver. It's given the inputs to load in its argv. See the package // documentation in doc.go for the full description of the patterns that need to be supported. // A driver receives as a JSON-serialized driverRequest struct in standard input and will // produce a JSON-serialized driverResponse (see definition in packages.go) in its standard output. // driverRequest is used to provide the portion of Load's Config that is needed by a driver. type driverRequest struct { Mode LoadMode `json:"mode"` // Env specifies the environment the underlying build system should be run in. Env []string `json:"env"` // BuildFlags are flags that should be passed to the underlying build system. BuildFlags []string `json:"build_flags"` // Tests specifies whether the patterns should also return test packages. Tests bool `json:"tests"` // Overlay maps file paths (relative to the driver's working directory) to the byte contents // of overlay files. Overlay map[string][]byte `json:"overlay"` } // findExternalDriver returns the file path of a tool that supplies // the build system package structure, or "" if not found." // If GOPACKAGESDRIVER is set in the environment findExternalTool returns its // value, otherwise it searches for a binary named gopackagesdriver on the PATH. func findExternalDriver(cfg *Config) driver { const toolPrefix = "GOPACKAGESDRIVER=" tool := "" for _, env := range cfg.Env { if val := strings.TrimPrefix(env, toolPrefix); val != env { tool = val } } if tool != "" && tool == "off" { return nil } if tool == "" { var err error tool, err = exec.LookPath("gopackagesdriver") if err != nil { return nil } } return func(cfg *Config, words ...string) (*driverResponse, error) { req, err := json.Marshal(driverRequest{ Mode: cfg.Mode, Env: cfg.Env, BuildFlags: cfg.BuildFlags, Tests: cfg.Tests, Overlay: cfg.Overlay, }) if err != nil { return nil, fmt.Errorf("failed to encode message to driver tool: %v", err) } buf := new(bytes.Buffer) stderr := new(bytes.Buffer) cmd := exec.CommandContext(cfg.Context, tool, words...) cmd.Dir = cfg.Dir cmd.Env = cfg.Env cmd.Stdin = bytes.NewReader(req) cmd.Stdout = buf cmd.Stderr = stderr if err := cmd.Run(); err != nil { return nil, fmt.Errorf("%v: %v: %s", tool, err, cmd.Stderr) } if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTDRIVERERRORS") != "" { fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(cmd, words...), stderr) } var response driverResponse if err := json.Unmarshal(buf.Bytes(), &response); err != nil { return nil, err } return &response, nil } }