func (b *GoBuilder) Build(ctx context.Context, shellRun shell.ShellRun, pkg string) error {
	b.requests <- request{
		Package: pkg,
		Logger:  taskrunner.LoggerFromContext(ctx),
	}
	b.mu.Lock()
	b.shellRun = shellRun
	b.ctx = ctx
	if b.doneCh == nil {
		b.doneCh = make(chan struct{}, 0)
	}
	b.mu.Unlock()
	<-b.doneCh
	return b.err
}
Build schedules a go build and waits for its completion. Note that since builds are batched,
the returned error may or may not be related to the requested package.
package goextensions
import (
	"bytes"
	"context"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"time"
	"github.com/samsarahq/taskrunner"
	"github.com/samsarahq/taskrunner/clireporter"
	"github.com/samsarahq/taskrunner/shell"
	"mvdan.cc/sh/interp"
)
var stdlibLookupMu = sync.Mutex{}
var stdlibLookup = make(map[string]*bool)
func isStdLib(pkg string) bool {
	stdlibLookupMu.Lock()
	defer stdlibLookupMu.Unlock()
	if stdlibLookup[pkg] != nil {
		return *stdlibLookup[pkg]
	}
	_, err := os.Stat(filepath.Join(runtime.GOROOT(), "src", pkg))
	isStdLib := err == nil
	stdlibLookup[pkg] = &isStdLib
	return isStdLib
}
type request struct {
	Package string
	Logger  *taskrunner.Logger
}
type GoBuilder struct {
	packages map[string]struct{}
	loggers  map[*taskrunner.Logger]struct{}
	timer    *time.Timer
	requests chan request
	mu       sync.Mutex
	doneCh   chan struct{}
	shellRun shell.ShellRun
	ctx      context.Context
	err error
	LogToStdout bool
	ModuleRoot  string
}
func NewGoBuilder() *GoBuilder {
	timer := time.NewTimer(time.Second)
	timer.Stop()
	ch := make(chan request, 128)
	builder := &GoBuilder{
		timer:    timer,
		packages: make(map[string]struct{}),
		loggers:  make(map[*taskrunner.Logger]struct{}),
		requests: ch,
	}
	go func() {
		for {
			select {
			case request := <-ch:
				builder.packages[request.Package] = struct{}{}
				builder.loggers[request.Logger] = struct{}{}
				timer.Reset(time.Millisecond * 500)
			case <-timer.C:
				builder.build()
			}
		}
	}()
	return builder
}
func (b *GoBuilder) build() {
	b.mu.Lock()
	defer b.mu.Unlock()
	packages := make([]string, 0, len(b.packages))
	for pkg := range b.packages {
		packages = append(packages, pkg)
	}
	pkgList := strings.Join(packages, " ")
	var stdout, stderr io.Writer
	if b.LogToStdout {
		stdout = &clireporter.PrefixedWriter{Writer: os.Stdout, Prefix: "go/build/dev", Separator: ">"}
		stderr = &clireporter.PrefixedWriter{Writer: os.Stderr, Prefix: "go/build/dev", Separator: ">"}
	} else {
		stdouts := make([]io.Writer, 0, len(b.loggers))
		stderrs := make([]io.Writer, 0, len(b.loggers))
		for l := range b.loggers {
			stdouts = append(stdouts, l.Stdout)
			stderrs = append(stderrs, l.Stdout)
		}
		stdout = io.MultiWriter(stdouts...)
		stderr = io.MultiWriter(stderrs...)
	}
	fmt.Fprintf(stdout, "building packages: %s", pkgList)
	b.err = shell.Run(b.ctx, fmt.Sprintf("go install -v %s", pkgList), func(r *interp.Runner) {
		r.Stdout = stdout
		r.Stderr = stderr
		if b.ModuleRoot != "" {
			r.Dir = b.ModuleRoot
		}
	})
	fmt.Fprintln(stdout, "done building packages")
	close(b.doneCh)
	b.doneCh = nil
	b.packages = make(map[string]struct{})
	b.loggers = make(map[*taskrunner.Logger]struct{})
}