external.go

 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// This file enables an external tool to intercept package requests.
 6// If the tool is present then its results are used in preference to
 7// the go list command.
 8
 9package packages
10
11import (
12	"bytes"
13	"encoding/json"
14	"fmt"
15	"os/exec"
16	"strings"
17)
18
19// Driver
20type driverRequest struct {
21	Command    string            `json:"command"`
22	Mode       LoadMode          `json:"mode"`
23	Env        []string          `json:"env"`
24	BuildFlags []string          `json:"build_flags"`
25	Tests      bool              `json:"tests"`
26	Overlay    map[string][]byte `json:"overlay"`
27}
28
29// findExternalDriver returns the file path of a tool that supplies
30// the build system package structure, or "" if not found."
31// If GOPACKAGESDRIVER is set in the environment findExternalTool returns its
32// value, otherwise it searches for a binary named gopackagesdriver on the PATH.
33func findExternalDriver(cfg *Config) driver {
34	const toolPrefix = "GOPACKAGESDRIVER="
35	tool := ""
36	for _, env := range cfg.Env {
37		if val := strings.TrimPrefix(env, toolPrefix); val != env {
38			tool = val
39		}
40	}
41	if tool != "" && tool == "off" {
42		return nil
43	}
44	if tool == "" {
45		var err error
46		tool, err = exec.LookPath("gopackagesdriver")
47		if err != nil {
48			return nil
49		}
50	}
51	return func(cfg *Config, words ...string) (*driverResponse, error) {
52		req, err := json.Marshal(driverRequest{
53			Mode:       cfg.Mode,
54			Env:        cfg.Env,
55			BuildFlags: cfg.BuildFlags,
56			Tests:      cfg.Tests,
57			Overlay:    cfg.Overlay,
58		})
59		if err != nil {
60			return nil, fmt.Errorf("failed to encode message to driver tool: %v", err)
61		}
62
63		buf := new(bytes.Buffer)
64		cmd := exec.CommandContext(cfg.Context, tool, words...)
65		cmd.Dir = cfg.Dir
66		cmd.Env = cfg.Env
67		cmd.Stdin = bytes.NewReader(req)
68		cmd.Stdout = buf
69		cmd.Stderr = new(bytes.Buffer)
70		if err := cmd.Run(); err != nil {
71			return nil, fmt.Errorf("%v: %v: %s", tool, err, cmd.Stderr)
72		}
73		var response driverResponse
74		if err := json.Unmarshal(buf.Bytes(), &response); err != nil {
75			return nil, err
76		}
77		return &response, nil
78	}
79}