Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use workspace modules with BP_GO_WORK_USE #537

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
13 changes: 7 additions & 6 deletions build.go
Original file line number Diff line number Diff line change
@@ -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") {
11 changes: 8 additions & 3 deletions build_configuration_parser.go
Original file line number Diff line number Diff line change
@@ -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 {
@@ -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
}

21 changes: 21 additions & 0 deletions build_configuration_parser_test.go
Original file line number Diff line number Diff line change
@@ -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() {
53 changes: 46 additions & 7 deletions go_build_process.go
Original file line number Diff line number Diff line change
@@ -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 {
@@ -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))
126 changes: 126 additions & 0 deletions go_build_process_test.go
Original file line number Diff line number Diff line change
@@ -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",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 It's consistently expecting 0s while other tests want 1s. I can't quite tell why.

))
})
})

context("when the GOPATH is empty", func() {
it.Before(func() {
Expect(os.WriteFile(filepath.Join(workspacePath, "go.mod"), nil, 0644)).To(Succeed())
@@ -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 {
1 change: 1 addition & 0 deletions integration/init_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
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