Skip to content

Commit

Permalink
Use workspace modules with BP_GO_WORK_USE
Browse files Browse the repository at this point in the history
Signed-off-by: Max Brauer <mbrauer@vmware.com>
  • Loading branch information
mamachanko authored and ForestEckhardt committed Feb 6, 2024
1 parent 8ca030d commit 2f087af
Show file tree
Hide file tree
Showing 15 changed files with 378 additions and 16 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ imports its own sub-packages.
BP_GO_BUILD_IMPORT_PATH= example.com/some-app
```

### `BP_GO_WORK_USE`
The `BP_GO_WORK_USE` variable allows you to initialise a workspace file and add
modules to it. This is helpful for building submodules which use relative
replace directives and `go.work` is not checked in. Usually, this is set
together with `BP_GO_TARGETS`.

```shell
BP_GO_WORK_USE=./cmd/controller:./cmd/webhook
```

### `BP_KEEP_FILES`
The `BP_KEEP_FILES` variable allows to you to specity a path list of files
(including file globs) that you would like to appear in the workspace of the
Expand Down
13 changes: 7 additions & 6 deletions build.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,13 @@ func Build(
}

config := GoBuildConfiguration{
Workspace: path,
Output: filepath.Join(targetsLayer.Path, "bin"),
GoPath: goPath,
GoCache: goCacheLayer.Path,
Flags: configuration.Flags,
Targets: configuration.Targets,
Workspace: path,
Output: filepath.Join(targetsLayer.Path, "bin"),
GoPath: goPath,
GoCache: goCacheLayer.Path,
Flags: configuration.Flags,
Targets: configuration.Targets,
WorkspaceUseModules: configuration.WorkspaceUseModules,
}

if isStaticStack(context.Stack) && !containsFlag(config.Flags, "-buildmode") {
Expand Down
11 changes: 8 additions & 3 deletions build_configuration_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ type TargetManager interface {
}

type BuildConfiguration struct {
Targets []string
Flags []string
ImportPath string
Targets []string
Flags []string
ImportPath string
WorkspaceUseModules []string
}

type BuildConfigurationParser struct {
Expand Down Expand Up @@ -67,6 +68,10 @@ func (p BuildConfigurationParser) Parse(buildpackVersion, workingDir string) (Bu
buildConfiguration.ImportPath = val
}

if val, ok := os.LookupEnv("BP_GO_WORK_USE"); ok {
buildConfiguration.WorkspaceUseModules = filepath.SplitList(val)
}

return buildConfiguration, nil
}

Expand Down
21 changes: 21 additions & 0 deletions build_configuration_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,27 @@ func testBuildConfigurationParser(t *testing.T, context spec.G, it spec.S) {
})
})

context("when BP_GO_WORK_USE is set", func() {
it.Before(func() {
os.Setenv("BP_GO_WORK_USE", "./some/module1:./some/module2")
})

it.After(func() {
os.Unsetenv("BP_GO_WORK_USE")
})

it("uses the values in the env var", func() {
configuration, err := parser.Parse("1.2.3", workingDir)
Expect(err).NotTo(HaveOccurred())
Expect(configuration).To(Equal(gobuild.BuildConfiguration{
Targets: []string{"."},
WorkspaceUseModules: []string{"./some/module1", "./some/module2"},
}))

Expect(targetManager.GenerateDefaultsCall.Receives.WorkingDir).To(Equal(workingDir))
})
})

context("failure cases", func() {
context("when the working directory contains a buildpack.yml", func() {
it.Before(func() {
Expand Down
53 changes: 46 additions & 7 deletions go_build_process.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@ type Executable interface {
}

type GoBuildConfiguration struct {
Workspace string
Output string
GoPath string
GoCache string
Targets []string
Flags []string
DisableCGO bool
Workspace string
Output string
GoPath string
GoCache string
Targets []string
Flags []string
DisableCGO bool
WorkspaceUseModules []string
}

type GoBuildProcess struct {
Expand Down Expand Up @@ -75,6 +76,44 @@ func (p GoBuildProcess) Execute(config GoBuildConfiguration) ([]string, error) {
env = append(env, "CGO_ENABLED=0")
}

if len(config.WorkspaceUseModules) > 0 {
// go work init
workInitArgs := []string{"work", "init"}
p.logs.Subprocess("Running '%s'", strings.Join(append([]string{"go"}, workInitArgs...), " "))

duration, err := p.clock.Measure(func() error {
return p.executable.Execute(pexec.Execution{
Args: workInitArgs,
Dir: config.Workspace,
Env: env,
Stdout: p.logs.ActionWriter,
Stderr: p.logs.ActionWriter,
})
})
if err != nil {
p.logs.Action("Failed after %s", duration.Round(time.Millisecond))
return nil, fmt.Errorf("failed to execute '%s': %w", workInitArgs, err)
}

// go work use <modules...>
workUseArgs := append([]string{"work", "use"}, config.WorkspaceUseModules...)
p.logs.Subprocess("Running '%s'", strings.Join(append([]string{"go"}, workUseArgs...), " "))

duration, err = p.clock.Measure(func() error {
return p.executable.Execute(pexec.Execution{
Args: workUseArgs,
Dir: config.Workspace,
Env: env,
Stdout: p.logs.ActionWriter,
Stderr: p.logs.ActionWriter,
})
})
if err != nil {
p.logs.Action("Failed after %s", duration.Round(time.Millisecond))
return nil, fmt.Errorf("failed to execute '%s': %w", workUseArgs, err)
}
}

printedArgs := []string{"go"}
for _, arg := range args {
printedArgs = append(printedArgs, formatArg(arg))
Expand Down
126 changes: 126 additions & 0 deletions go_build_process_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,66 @@ func testGoBuildProcess(t *testing.T, context spec.G, it spec.S) {
})
})

context("when workspaces should be used", func() {
it.Before(func() {
Expect(os.WriteFile(filepath.Join(workspacePath, "go.mod"), nil, 0644)).To(Succeed())
Expect(os.Mkdir(filepath.Join(workspacePath, "vendor"), os.ModePerm)).To(Succeed())
})

it("inits and uses the workspaces before executing the go build process", func() {
binaries, err := buildProcess.Execute(gobuild.GoBuildConfiguration{
Workspace: workspacePath,
Output: filepath.Join(layerPath, "bin"),
GoCache: goCache,
Targets: []string{"."},
WorkspaceUseModules: []string{"./some/module1", "./some/module2"},
})
Expect(err).NotTo(HaveOccurred())
Expect(binaries).To(Equal([]string{
filepath.Join(layerPath, "bin", "some-dir"),
}))

Expect(filepath.Join(layerPath, "bin")).To(BeADirectory())

Expect(executions[0].Args).To(Equal([]string{
"work",
"init",
}))

Expect(executions[1].Args).To(Equal([]string{
"work",
"use",
"./some/module1",
"./some/module2",
}))

Expect(executions[2].Args).To(Equal([]string{
"build",
"-o", filepath.Join(layerPath, "bin"),
"-buildmode", "pie",
"-trimpath",
".",
}))

Expect(executions[3].Args).To(Equal([]string{
"list",
"--json",
".",
}))

Expect(executable.ExecuteCall.Receives.Execution.Dir).To(Equal(workspacePath))
Expect(executable.ExecuteCall.Receives.Execution.Env).To(ContainElement(fmt.Sprintf("GOCACHE=%s", goCache)))

Expect(logs).To(ContainLines(
" Executing build process",
" Running 'go work init'",
" Running 'go work use ./some/module1 ./some/module2'",
fmt.Sprintf(` Running 'go build -o %s -buildmode pie -trimpath .'`, filepath.Join(layerPath, "bin")),
" Completed in 0s",
))
})
})

context("when the GOPATH is empty", func() {
it.Before(func() {
Expect(os.WriteFile(filepath.Join(workspacePath, "go.mod"), nil, 0644)).To(Succeed())
Expand Down Expand Up @@ -244,6 +304,72 @@ func testGoBuildProcess(t *testing.T, context spec.G, it spec.S) {
})
})

context("when workspaces should be used", func() {
context("when the executable fails go work init", func() {
it.Before(func() {
executable.ExecuteCall.Stub = func(execution pexec.Execution) error {
if execution.Args[0] == "work" && execution.Args[1] == "init" {
fmt.Fprintln(execution.Stdout, "work init error stdout")
fmt.Fprintln(execution.Stderr, "work init error stderr")
return errors.New("command failed")
}

return nil
}
})

it("returns an error", func() {
_, err := buildProcess.Execute(gobuild.GoBuildConfiguration{
Workspace: workspacePath,
Output: filepath.Join(layerPath, "bin"),
GoPath: goPath,
GoCache: goCache,
Targets: []string{"."},
WorkspaceUseModules: []string{"./some/module1", "./some/module2"},
})
Expect(err).To(MatchError("failed to execute '[work init]': command failed"))

Expect(logs).To(ContainLines(
" work init error stdout",
" work init error stderr",
" Failed after 1s",
))
})
})

context("when the executable fails go work use", func() {
it.Before(func() {
executable.ExecuteCall.Stub = func(execution pexec.Execution) error {
if execution.Args[0] == "work" && execution.Args[1] == "use" {
fmt.Fprintln(execution.Stdout, "work use error stdout")
fmt.Fprintln(execution.Stderr, "work use error stderr")
return errors.New("command failed")
}

return nil
}
})

it("returns an error", func() {
_, err := buildProcess.Execute(gobuild.GoBuildConfiguration{
Workspace: workspacePath,
Output: filepath.Join(layerPath, "bin"),
GoPath: goPath,
GoCache: goCache,
Targets: []string{"."},
WorkspaceUseModules: []string{"./some/module1", "./some/module2"},
})
Expect(err).To(MatchError("failed to execute '[work use ./some/module1 ./some/module2]': command failed"))

Expect(logs).To(ContainLines(
" work use error stdout",
" work use error stderr",
" Failed after 0s",
))
})
})
})

context("when the executable fails go build", func() {
it.Before(func() {
executable.ExecuteCall.Stub = func(execution pexec.Execution) error {
Expand Down
1 change: 1 addition & 0 deletions integration/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ func TestIntegration(t *testing.T) {
suite("Rebuild", testRebuild)
suite("Targets", testTargets)
suite("Vendor", testVendor)
suite("WorkUse", testWorkUse)
if builder.BuilderName != "paketobuildpacks/builder-jammy-buildpackless-static" {
suite("BuildFlags", testBuildFlags)
}
Expand Down
1 change: 1 addition & 0 deletions integration/testdata/work_use/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
go.work*
7 changes: 7 additions & 0 deletions integration/testdata/work_use/cmd/cli/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module github.com/paketo-buildpacks/go-build/integration/testdata/work_use/binary

go 1.16

replace github.com/paketo-buildpacks/go-build/integration/testdata/work_use => ../../

require github.com/paketo-buildpacks/go-build/integration/testdata/work_use v0.0.0-00010101000000-000000000000
4 changes: 4 additions & 0 deletions integration/testdata/work_use/cmd/cli/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
13 changes: 13 additions & 0 deletions integration/testdata/work_use/cmd/cli/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package main

import (
"fmt"

"github.com/paketo-buildpacks/go-build/integration/testdata/work_use/find"
)

func main() {
pattern := "buildpacks"
data := []string{"paketo", "buildpacks"}
fmt.Printf("found: %d", len(find.Fuzzy(pattern, data...)))
}
13 changes: 13 additions & 0 deletions integration/testdata/work_use/find/find.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package find

import (
"github.com/sahilm/fuzzy"
)

func Fuzzy(pattern string, data ...string) []string {
var matches []string
for _, match := range fuzzy.Find(pattern, data) {
matches = append(matches, match.Str)
}
return matches
}
8 changes: 8 additions & 0 deletions integration/testdata/work_use/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module github.com/paketo-buildpacks/go-build/integration/testdata/work_use

go 1.16

require (
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/sahilm/fuzzy v0.1.0
)
4 changes: 4 additions & 0 deletions integration/testdata/work_use/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
Loading

0 comments on commit 2f087af

Please sign in to comment.