Skip to content

Commit

Permalink
Add go module support
Browse files Browse the repository at this point in the history
  • Loading branch information
jonjohnsonjr committed Jul 12, 2019
1 parent d356007 commit b35f647
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 76 deletions.
56 changes: 49 additions & 7 deletions pkg/build/gobuild.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"archive/tar"
"bytes"
"compress/gzip"
"encoding/json"
"errors"
gb "go/build"
"io"
Expand All @@ -26,6 +27,7 @@ import (
"os"
"os/exec"
"path/filepath"
"strings"

v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/mutate"
Expand All @@ -46,6 +48,7 @@ type gobuild struct {
creationTime v1.Time
build builder
disableOptimizations bool
mod *modInfo
}

// Option is a functional option for NewGo.
Expand All @@ -56,6 +59,7 @@ type gobuildOpener struct {
creationTime v1.Time
build builder
disableOptimizations bool
mod *modInfo
}

func (gbo *gobuildOpener) Open() (Interface, error) {
Expand All @@ -67,15 +71,40 @@ func (gbo *gobuildOpener) Open() (Interface, error) {
creationTime: gbo.creationTime,
build: gbo.build,
disableOptimizations: gbo.disableOptimizations,
mod: gbo.mod,
}, nil
}

// https://golang.org/pkg/cmd/go/internal/modinfo/#ModulePublic
type modInfo struct {
Path string
Dir string
}

// moduleInfo returns the module path and module root directory for a project
// using go modules, otherwise returns nil.
//
// Related: https://github.com/golang/go/issues/26504
func moduleInfo() *modInfo {
// If this returns an error, output will be nil, so we'll return an empty string anyway.
output, err := exec.Command("go", "list", "-mod=readonly", "-m", "-json").Output()
if err != nil {
return nil
}
var info modInfo
if err := json.Unmarshal(output, &info); err != nil {
return nil
}
return &info
}

// NewGo returns a build.Interface implementation that:
// 1. builds go binaries named by importpath,
// 2. containerizes the binary on a suitable base,
func NewGo(options ...Option) (Interface, error) {
gbo := &gobuildOpener{
build: build,
mod: moduleInfo(),
}

for _, option := range options {
Expand All @@ -90,14 +119,27 @@ func NewGo(options ...Option) (Interface, error) {
//
// Only valid importpaths that provide commands (i.e., are "package main") are
// supported.
func (*gobuild) IsSupportedReference(s string) bool {
p, err := gb.Import(s, gb.Default.GOPATH, gb.ImportComment)
func (g *gobuild) IsSupportedReference(s string) bool {
p, err := g.Import(s)
if err != nil {
return false
}
return p.IsCommand()
}

// Import wraps go/build.Import to handle go modules.
func (g *gobuild) Import(s string) (*gb.Package, error) {
if g.mod != nil && strings.HasPrefix(s, g.mod.Path) {
relative := strings.TrimPrefix(s, g.mod.Path)
srcDir := filepath.Join(g.mod.Dir, relative)
pkg, err := gb.Import(s, srcDir, gb.ImportComment)
if err == nil {
return pkg, err
}
}
return gb.Import(s, gb.Default.GOPATH, gb.ImportComment)
}

func build(ip string, disableOptimizations bool) (string, error) {
tmpDir, err := ioutil.TempDir("", "ko")
if err != nil {
Expand Down Expand Up @@ -216,8 +258,8 @@ func tarBinary(name, binary string) (*bytes.Buffer, error) {
return buf, nil
}

func kodataPath(s string) (string, error) {
p, err := gb.Import(s, gb.Default.GOPATH, gb.ImportComment)
func (g *gobuild) kodataPath(s string) (string, error) {
p, err := g.Import(s)
if err != nil {
return "", err
}
Expand All @@ -227,7 +269,7 @@ func kodataPath(s string) (string, error) {
// Where kodata lives in the image.
const kodataRoot = "/var/run/ko"

func tarKoData(importpath string) (*bytes.Buffer, error) {
func (g *gobuild) tarKoData(importpath string) (*bytes.Buffer, error) {
buf := bytes.NewBuffer(nil)
// Compress this before calling tarball.LayerFromOpener, since it eagerly
// calculates digests and diffids. This prevents us from double compressing
Expand All @@ -239,7 +281,7 @@ func tarKoData(importpath string) (*bytes.Buffer, error) {
tw := tar.NewWriter(gw)
defer tw.Close()

root, err := kodataPath(importpath)
root, err := g.kodataPath(importpath)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -307,7 +349,7 @@ func (gb *gobuild) Build(s string) (v1.Image, error) {

var layers []mutate.Addendum
// Create a layer from the kodata directory under this import path.
dataLayerBuf, err := tarKoData(s)
dataLayerBuf, err := gb.tarKoData(s)
if err != nil {
return nil, err
}
Expand Down
11 changes: 9 additions & 2 deletions pkg/build/gobuild_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,21 @@ func TestGoBuildIsSupportedRef(t *testing.T) {
t.Fatalf("random.Image() = %v", err)
}

ng, err := NewGo(WithBaseImages(func(string) (v1.Image, error) { return base, nil }))
mod := &modInfo{
Path: filepath.FromSlash("github.com/google/ko/cmd/ko/test"),
Dir: ".",
}

ng, err := NewGo(WithBaseImages(func(string) (v1.Image, error) { return base, nil }),
withModuleInfo(mod))
if err != nil {
t.Fatalf("NewGo() = %v", err)
}

// Supported import paths.
for _, importpath := range []string{
filepath.FromSlash("github.com/google/ko/cmd/ko"), // ko can build itself.
filepath.FromSlash("github.com/google/ko/cmd/ko"), // ko can build itself.
filepath.FromSlash("github.com/google/ko/cmd/ko/test"), // ko can build the test package.
} {
t.Run(importpath, func(t *testing.T) {
if !ng.IsSupportedReference(importpath) {
Expand Down
13 changes: 12 additions & 1 deletion pkg/build/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,21 @@ func WithDisabledOptimizations() Option {
}

// withBuilder is a functional option for overriding the way go binaries
// are built. This is exposed for testing.
// are built.
// This is exposed for testing.
func withBuilder(b builder) Option {
return func(gbo *gobuildOpener) error {
gbo.build = b
return nil
}
}

// withModulePath is a functional option for overriding the module path for
// the current ko invocation.
// This is exposed for testing.
func withModuleInfo(mi *modInfo) Option {
return func(gbo *gobuildOpener) error {
gbo.mod = mi
return nil
}
}
33 changes: 15 additions & 18 deletions pkg/commands/publisher.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,34 @@ package commands
import (
"fmt"
gb "go/build"
"os"
"path/filepath"
"strings"

"github.com/google/go-containerregistry/pkg/name"
"github.com/google/ko/pkg/build"
"github.com/google/ko/pkg/publish"

"golang.org/x/tools/go/packages"
)

func qualifyLocalImport(importpath, gopathsrc, pwd string) (string, error) {
if !strings.HasPrefix(pwd, gopathsrc) {
return "", fmt.Errorf("pwd (%q) must be on $GOPATH/src (%q) to support local imports", pwd, gopathsrc)
func qualifyLocalImport(importpath string) (string, error) {
cfg := &packages.Config{
Mode: packages.NeedName,
}
pkgs, err := packages.Load(cfg, importpath)
if err != nil {
return "", err
}
if len(pkgs) != 1 {
return "", fmt.Errorf("found %d local packages, expected 1", len(pkgs))
}
// Given $GOPATH/src and $PWD (which must be within $GOPATH/src), trim
// off $GOPATH/src/ from $PWD and append local importpath to get the
// fully-qualified importpath.
return filepath.Join(strings.TrimPrefix(pwd, gopathsrc+string(filepath.Separator)), importpath), nil
return pkgs[0].PkgPath, nil
}

func publishImages(importpaths []string, pub publish.Interface, b build.Interface) (map[string]name.Reference, error) {
imgs := make(map[string]name.Reference)
for _, importpath := range importpaths {
if gb.IsLocalImport(importpath) {
// Qualify relative imports to their fully-qualified
// import path, assuming $PWD is within $GOPATH/src.
gopathsrc := filepath.Join(gb.Default.GOPATH, "src")
pwd, err := os.Getwd()
if err != nil {
return nil, fmt.Errorf("error getting current working directory: %v", err)
}
importpath, err = qualifyLocalImport(importpath, gopathsrc, pwd)
var err error
importpath, err = qualifyLocalImport(importpath)
if err != nil {
return nil, err
}
Expand Down
48 changes: 0 additions & 48 deletions pkg/commands/publisher_test.go

This file was deleted.

0 comments on commit b35f647

Please sign in to comment.