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

Implements an intermediate build cache #305

Merged
merged 6 commits into from
Apr 25, 2022
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
21 changes: 19 additions & 2 deletions build.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type SourceRemover interface {

//go:generate faux --interface PublishProcess --output fakes/publish_process.go
type PublishProcess interface {
Execute(workingDir, rootDir, nugetCachePath, projectPath, outputPath string, flags []string) error
Execute(workingDir, rootDir, nugetCachePath, intermediateBuildCachePath, projectPath, outputPath string, flags []string) error
}

//go:generate faux --interface BindingResolver --output fakes/binding_resolver.go
Expand Down Expand Up @@ -99,8 +99,15 @@ func Build(

nugetCache.Cache = true

intermediateBuildCache, err := context.Layers.Get("intermediate-build-cache")
if err != nil {
return packit.BuildResult{}, err
}

intermediateBuildCache.Cache = true

logger.Process("Executing build process")
err = publishProcess.Execute(context.WorkingDir, os.Getenv("DOTNET_ROOT"), nugetCache.Path, projectPath, tempDir, flags)
err = publishProcess.Execute(context.WorkingDir, os.Getenv("DOTNET_ROOT"), nugetCache.Path, intermediateBuildCache.Path, projectPath, tempDir, flags)
if err != nil {
return packit.BuildResult{}, err
}
Expand Down Expand Up @@ -135,6 +142,16 @@ func Build(
return packit.BuildResult{}, err
}

exists, err = fs.Exists(intermediateBuildCache.Path)
if exists {
if !fs.IsEmptyDir(intermediateBuildCache.Path) {
layers = append(layers, intermediateBuildCache)
}
}
if err != nil {
return packit.BuildResult{}, err
}

return packit.BuildResult{
Layers: layers,
}, nil
Expand Down
50 changes: 31 additions & 19 deletions build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"os"
"path/filepath"
"testing"
"time"

dotnetpublish "github.com/paketo-buildpacks/dotnet-publish"
"github.com/paketo-buildpacks/dotnet-publish/fakes"
Expand All @@ -23,7 +22,6 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
var (
Expect = NewWithT(t).Expect

timestamp time.Time
buffer *bytes.Buffer
workingDir string
homeDir string
Expand Down Expand Up @@ -66,17 +64,15 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
Expect(os.MkdirAll(filepath.Join(layersDir, "nuget-cache"), os.ModePerm)).To(Succeed())
Expect(os.WriteFile(filepath.Join(layersDir, "nuget-cache", "some-cache"), []byte{}, 0600)).To(Succeed())

Expect(os.MkdirAll(filepath.Join(layersDir, "intermediate-build-cache"), os.ModePerm)).To(Succeed())
Expect(os.WriteFile(filepath.Join(layersDir, "intermediate-build-cache", "some-cache"), []byte{}, 0600)).To(Succeed())

os.Setenv("DOTNET_ROOT", "some-existing-root-dir")

buffer = bytes.NewBuffer(nil)
logger := scribe.NewLogger(buffer)

timestamp = time.Now()
clock := chronos.NewClock(func() time.Time {
return timestamp
})

build = dotnetpublish.Build(sourceRemover, bindingResolver, homeDir, symlinker, publishProcess, buildpackYMLParser, commandConfigParser, clock, logger)
build = dotnetpublish.Build(sourceRemover, bindingResolver, homeDir, symlinker, publishProcess, buildpackYMLParser, commandConfigParser, chronos.DefaultClock, logger)
})

it.After(func() {
Expand All @@ -100,12 +96,17 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
})
Expect(err).NotTo(HaveOccurred())

Expect(result.Layers).To(HaveLen(1))
layer := result.Layers[0]
Expect(result.Layers).To(HaveLen(2))

Expect(layer.Name).To(Equal("nuget-cache"))
Expect(layer.Path).To(Equal(filepath.Join(layersDir, "nuget-cache")))
Expect(layer.Cache).To(BeTrue())
nugetCacheLayer := result.Layers[0]
Expect(nugetCacheLayer.Name).To(Equal("nuget-cache"))
Expect(nugetCacheLayer.Path).To(Equal(filepath.Join(layersDir, "nuget-cache")))
Expect(nugetCacheLayer.Cache).To(BeTrue())

intermediateBuildCacheLayer := result.Layers[1]
Expect(intermediateBuildCacheLayer.Name).To(Equal("intermediate-build-cache"))
Expect(intermediateBuildCacheLayer.Path).To(Equal(filepath.Join(layersDir, "intermediate-build-cache")))
Expect(intermediateBuildCacheLayer.Cache).To(BeTrue())

Expect(sourceRemover.RemoveCall.Receives.WorkingDir).To(Equal(workingDir))
Expect(sourceRemover.RemoveCall.Receives.PublishOutputDir).To(MatchRegexp(`dotnet-publish-output\d+`))
Expand All @@ -119,6 +120,9 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
Expect(publishProcess.ExecuteCall.Receives.WorkingDir).To(Equal(workingDir))
Expect(publishProcess.ExecuteCall.Receives.RootDir).To(Equal("some-existing-root-dir"))
Expect(publishProcess.ExecuteCall.Receives.ProjectPath).To(Equal("some/project/path"))
Expect(publishProcess.ExecuteCall.Receives.NugetCachePath).To(Equal(nugetCacheLayer.Path))
Expect(publishProcess.ExecuteCall.Receives.IntermediateBuildCachePath).To(Equal(intermediateBuildCacheLayer.Path))
Expect(publishProcess.ExecuteCall.Receives.ProjectPath).To(Equal("some/project/path"))
Expect(publishProcess.ExecuteCall.Receives.OutputPath).To(MatchRegexp(`dotnet-publish-output\d+`))
Expect(publishProcess.ExecuteCall.Receives.Flags).To(Equal([]string{"--publishflag", "value"}))

Expand All @@ -128,9 +132,10 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
Expect(buffer.String()).To(ContainSubstring("Please specify the project path through the $BP_DOTNET_PROJECT_PATH environment variable instead. See README.md or the documentation on paketo.io for more information."))
})

context("the cache layer is empty", func() {
context("the cache layers are empty", func() {
it.Before(func() {
Expect(os.Remove(filepath.Join(layersDir, "nuget-cache", "some-cache"))).To(Succeed())
Expect(os.Remove(filepath.Join(layersDir, "intermediate-build-cache", "some-cache"))).To(Succeed())
})

it("does not return a layer", func() {
Expand Down Expand Up @@ -171,12 +176,17 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
})
Expect(err).NotTo(HaveOccurred())

Expect(result.Layers).To(HaveLen(1))
layer := result.Layers[0]
Expect(result.Layers).To(HaveLen(2))

nugetCacheLayer := result.Layers[0]
Expect(nugetCacheLayer.Name).To(Equal("nuget-cache"))
Expect(nugetCacheLayer.Path).To(Equal(filepath.Join(layersDir, "nuget-cache")))
Expect(nugetCacheLayer.Cache).To(BeTrue())

Expect(layer.Name).To(Equal("nuget-cache"))
Expect(layer.Path).To(Equal(filepath.Join(layersDir, "nuget-cache")))
Expect(layer.Cache).To(BeTrue())
intermediateBuildCacheLayer := result.Layers[1]
Expect(intermediateBuildCacheLayer.Name).To(Equal("intermediate-build-cache"))
Expect(intermediateBuildCacheLayer.Path).To(Equal(filepath.Join(layersDir, "intermediate-build-cache")))
Expect(intermediateBuildCacheLayer.Cache).To(BeTrue())

Expect(sourceRemover.RemoveCall.Receives.WorkingDir).To(Equal(workingDir))
Expect(sourceRemover.RemoveCall.Receives.PublishOutputDir).To(MatchRegexp(`dotnet-publish-output\d+`))
Expand All @@ -185,6 +195,8 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
Expect(publishProcess.ExecuteCall.Receives.WorkingDir).To(Equal(workingDir))
Expect(publishProcess.ExecuteCall.Receives.RootDir).To(Equal("some-existing-root-dir"))
Expect(publishProcess.ExecuteCall.Receives.ProjectPath).To(Equal("some/project/path"))
Expect(publishProcess.ExecuteCall.Receives.NugetCachePath).To(Equal(nugetCacheLayer.Path))
Expect(publishProcess.ExecuteCall.Receives.IntermediateBuildCachePath).To(Equal(intermediateBuildCacheLayer.Path))
Expect(publishProcess.ExecuteCall.Receives.OutputPath).To(MatchRegexp(`dotnet-publish-output\d+`))
Expect(publishProcess.ExecuteCall.Receives.Flags).To(Equal([]string{"--publishflag", "value"}))

Expand Down
62 changes: 60 additions & 2 deletions dotnet_publish_process.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strings"

"github.com/paketo-buildpacks/packit/v2/chronos"
"github.com/paketo-buildpacks/packit/v2/fs"
"github.com/paketo-buildpacks/packit/v2/pexec"
"github.com/paketo-buildpacks/packit/v2/scribe"
)
Expand All @@ -30,9 +31,15 @@ func NewDotnetPublishProcess(executable Executable, logger scribe.Logger, clock
}
}

func (p DotnetPublishProcess) Execute(workingDir, root, nugetCachePath, projectPath, outputPath string, flags []string) error {
func (p DotnetPublishProcess) Execute(workingDir, root, nugetCachePath, intermediateBuildCachePath, projectPath, outputPath string, flags []string) error {
err := loadBuildCache(workingDir, projectPath, intermediateBuildCachePath)
if err != nil {
return fmt.Errorf("failed to load build cache: %w", err)
}

args := []string{
"publish", filepath.Join(workingDir, projectPath), // change to workingDir plus project path
"publish",
filepath.Join(workingDir, projectPath),
}

if !containsFlag(flags, "--configuration") && !containsFlag(flags, "-c") {
Expand Down Expand Up @@ -73,5 +80,56 @@ func (p DotnetPublishProcess) Execute(workingDir, root, nugetCachePath, projectP
p.logger.Action("Completed in %s", duration)
p.logger.Break()

err = recreateBuildCache(workingDir, projectPath, intermediateBuildCachePath)
if err != nil {
return err
}

return nil
}

func loadBuildCache(workingDir, projectPath, cachePath string) error {
obj, err := fs.Exists(filepath.Join(cachePath, "obj"))
if err != nil {
return err
}

if obj {
// RemoveAll to clear the contents of the directory, which fs.Copy won't do
err = os.RemoveAll(filepath.Join(workingDir, projectPath, "obj"))
if err != nil {
return err
}
err = fs.Copy(filepath.Join(cachePath, "obj"), filepath.Join(workingDir, projectPath, "obj"))
if err != nil {
return err
}
}
return nil
}

func recreateBuildCache(workingDir, projectPath, cachePath string) error {
obj, err := fs.Exists(filepath.Join(workingDir, projectPath, "obj"))
if err != nil {
return fmt.Errorf("failed to locate build cache: %w", err)
}

if obj {
// RemoveAll to clear the contents of the directory, which fs.Copy won't do
err = os.RemoveAll(filepath.Join(cachePath, "obj"))
if err != nil {
// not tested
return fmt.Errorf("failed to reset build cache: %w", err)
}
err = os.MkdirAll(filepath.Join(cachePath, "obj"), os.ModePerm)
if err != nil {
// not tested
return fmt.Errorf("failed to reset build cache: %w", err)
}
err = fs.Copy(filepath.Join(workingDir, projectPath, "obj"), filepath.Join(cachePath, "obj"))
if err != nil {
return fmt.Errorf("failed to store build cache: %w", err)
}
}
return nil
}
Loading