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
Show file tree
Hide file tree
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
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",
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())
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
Loading