Skip to content

Commit e266ccf

Browse files
thitch97robdimsdale
authored andcommitted
Persist pip-source layer; add layer path to PIP_FIND_LINKS env var
1 parent 54a2354 commit e266ccf

4 files changed

Lines changed: 104 additions & 43 deletions

File tree

build.go

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package pip
22

33
import (
44
"fmt"
5-
"os"
65
"path/filepath"
76
"strings"
87
"time"
@@ -94,13 +93,20 @@ func Build(
9493
return packit.BuildResult{}, err
9594
}
9695

96+
pipSrcLayer, err := context.Layers.Get(PipSrc)
97+
if err != nil {
98+
return packit.BuildResult{}, err
99+
}
100+
97101
cachedChecksum, ok := pipLayer.Metadata[DependencyChecksumKey].(string)
98102
if ok && cargo.Checksum(cachedChecksum).Match(cargo.Checksum(dependency.Checksum)) {
99103
logger.Process("Reusing cached layer %s", pipLayer.Path)
104+
logger.Process("Reusing cached layer %s", pipSrcLayer.Path)
100105
pipLayer.Launch, pipLayer.Build, pipLayer.Cache = launch, build, build
106+
pipSrcLayer.Launch, pipSrcLayer.Build, pipSrcLayer.Cache = false, build, build
101107

102108
return packit.BuildResult{
103-
Layers: []packit.Layer{pipLayer},
109+
Layers: []packit.Layer{pipLayer, pipSrcLayer},
104110
Build: buildMetadata,
105111
Launch: launchMetadata,
106112
}, nil
@@ -111,25 +117,25 @@ func Build(
111117
return packit.BuildResult{}, err
112118
}
113119

114-
pipLayer.Launch, pipLayer.Build, pipLayer.Cache = launch, build, build
115-
116-
// Install the pip source to a temporary dir, since we only need access to
117-
// it as an intermediate step when installing pip.
118-
// It doesn't need to go into a layer, since we won't need it in future builds.
119-
pipSrcDir, err := os.MkdirTemp("", "pip-source")
120+
pipSrcLayer, err = pipSrcLayer.Reset()
120121
if err != nil {
121-
return packit.BuildResult{}, fmt.Errorf("failed to create temp pip-source dir: %w", err)
122+
return packit.BuildResult{}, err
122123
}
123124

125+
pipLayer.Launch, pipLayer.Build, pipLayer.Cache = launch, build, build
126+
//Pip-source layer flags should mirror the Pip layer, but should never be
127+
//available at launch.
128+
pipSrcLayer.Launch, pipSrcLayer.Build, pipSrcLayer.Cache = false, build, build
129+
124130
logger.Process("Executing build process")
125131
logger.Subprocess(fmt.Sprintf("Installing Pip %s", dependency.Version))
126132

127133
duration, err := clock.Measure(func() error {
128-
err = dependencies.Deliver(dependency, context.CNBPath, pipSrcDir, context.Platform.Path)
134+
err = dependencies.Deliver(dependency, context.CNBPath, pipSrcLayer.Path, context.Platform.Path)
129135
if err != nil {
130136
return err
131137
}
132-
return installProcess.Execute(pipSrcDir, pipLayer.Path)
138+
return installProcess.Execute(pipSrcLayer.Path, pipLayer.Path)
133139
})
134140
if err != nil {
135141
return packit.BuildResult{}, err
@@ -167,14 +173,21 @@ func Build(
167173
}
168174
pipLayer.SharedEnv.Prepend("PYTHONPATH", strings.TrimRight(sitePackagesPath, "\n"), ":")
169175

176+
// Append the pip source layer path to PIP_FIND_LINKS so that invocations
177+
// of pip in downstream buildpacks have access to the packages bundled with
178+
// the pip dependency (setuptools, wheel, etc.).
179+
180+
pipSrcLayer.BuildEnv.Append("PIP_FIND_LINKS", strings.TrimRight(pipSrcLayer.Path, "\n"), " ")
181+
182+
logger.EnvironmentVariables(pipSrcLayer)
170183
logger.EnvironmentVariables(pipLayer)
171184

172185
pipLayer.Metadata = map[string]interface{}{
173186
DependencyChecksumKey: dependency.Checksum,
174187
}
175188

176189
return packit.BuildResult{
177-
Layers: []packit.Layer{pipLayer},
190+
Layers: []packit.Layer{pipLayer, pipSrcLayer},
178191
Build: buildMetadata,
179192
Launch: launchMetadata,
180193
}, nil

build_test.go

Lines changed: 62 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -133,35 +133,52 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
133133
result, err := build(buildContext)
134134
Expect(err).NotTo(HaveOccurred())
135135

136-
Expect(result.Layers).To(HaveLen(1))
137-
layer := result.Layers[0]
136+
Expect(result.Layers).To(HaveLen(2))
137+
pipLayer := result.Layers[0]
138138

139-
Expect(layer.Name).To(Equal("pip"))
139+
Expect(pipLayer.Name).To(Equal("pip"))
140140

141-
Expect(layer.Path).To(Equal(filepath.Join(layersDir, "pip")))
141+
Expect(pipLayer.Path).To(Equal(filepath.Join(layersDir, "pip")))
142142

143-
Expect(layer.SharedEnv).To(HaveLen(2))
144-
Expect(layer.SharedEnv["PYTHONPATH.delim"]).To(Equal(":"))
145-
Expect(layer.SharedEnv["PYTHONPATH.prepend"]).To(Equal(filepath.Join(layersDir, "pip", "lib/python1.23/site-packages")))
143+
Expect(pipLayer.BuildEnv).To(BeEmpty())
144+
Expect(pipLayer.LaunchEnv).To(BeEmpty())
145+
Expect(pipLayer.ProcessLaunchEnv).To(BeEmpty())
146146

147-
Expect(layer.BuildEnv).To(BeEmpty())
148-
Expect(layer.LaunchEnv).To(BeEmpty())
149-
Expect(layer.ProcessLaunchEnv).To(BeEmpty())
147+
Expect(pipLayer.Build).To(BeFalse())
148+
Expect(pipLayer.Launch).To(BeFalse())
149+
Expect(pipLayer.Cache).To(BeFalse())
150150

151-
Expect(layer.Build).To(BeFalse())
152-
Expect(layer.Launch).To(BeFalse())
153-
Expect(layer.Cache).To(BeFalse())
151+
Expect(pipLayer.Metadata).To(HaveLen(1))
152+
Expect(pipLayer.Metadata["dependency_checksum"]).To(Equal("some-sha"))
154153

155-
Expect(layer.Metadata).To(HaveLen(1))
156-
Expect(layer.Metadata["dependency_checksum"]).To(Equal("some-sha"))
154+
Expect(pipLayer.SharedEnv).To(HaveLen(2))
155+
Expect(pipLayer.SharedEnv["PYTHONPATH.delim"]).To(Equal(":"))
156+
Expect(pipLayer.SharedEnv["PYTHONPATH.prepend"]).To(Equal(filepath.Join(layersDir, "pip", "lib/python1.23/site-packages")))
157157

158-
Expect(layer.SBOM.Formats()).To(HaveLen(2))
158+
Expect(pipLayer.SBOM.Formats()).To(HaveLen(2))
159159
var actualExtensions []string
160-
for _, format := range layer.SBOM.Formats() {
160+
for _, format := range pipLayer.SBOM.Formats() {
161161
actualExtensions = append(actualExtensions, format.Extension)
162162
}
163163
Expect(actualExtensions).To(ConsistOf("cdx.json", "spdx.json"))
164164

165+
pipSrcLayer := result.Layers[1]
166+
167+
Expect(pipSrcLayer.Name).To(Equal("pip-source"))
168+
169+
Expect(pipSrcLayer.Path).To(Equal(filepath.Join(layersDir, "pip-source")))
170+
171+
Expect(pipSrcLayer.LaunchEnv).To(BeEmpty())
172+
Expect(pipSrcLayer.ProcessLaunchEnv).To(BeEmpty())
173+
174+
Expect(pipSrcLayer.Build).To(BeFalse())
175+
Expect(pipSrcLayer.Launch).To(BeFalse())
176+
Expect(pipSrcLayer.Cache).To(BeFalse())
177+
178+
Expect(pipSrcLayer.BuildEnv).To(HaveLen(2))
179+
Expect(pipSrcLayer.BuildEnv["PIP_FIND_LINKS.delim"]).To(Equal(" "))
180+
Expect(pipSrcLayer.BuildEnv["PIP_FIND_LINKS.append"]).To(Equal(filepath.Join(layersDir, "pip-source")))
181+
165182
Expect(dependencyManager.ResolveCall.Receives.Path).To(Equal(filepath.Join(cnbDir, "buildpack.toml")))
166183
Expect(dependencyManager.ResolveCall.Receives.Id).To(Equal("pip"))
167184
Expect(dependencyManager.ResolveCall.Receives.Version).To(Equal(""))
@@ -201,14 +218,22 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
201218
result, err := build(buildContext)
202219
Expect(err).NotTo(HaveOccurred())
203220

204-
Expect(result.Layers).To(HaveLen(1))
205-
layer := result.Layers[0]
221+
Expect(result.Layers).To(HaveLen(2))
222+
pipLayer := result.Layers[0]
206223

207-
Expect(layer.Name).To(Equal("pip"))
224+
Expect(pipLayer.Name).To(Equal("pip"))
208225

209-
Expect(layer.Build).To(BeTrue())
210-
Expect(layer.Launch).To(BeTrue())
211-
Expect(layer.Cache).To(BeTrue())
226+
Expect(pipLayer.Build).To(BeTrue())
227+
Expect(pipLayer.Launch).To(BeTrue())
228+
Expect(pipLayer.Cache).To(BeTrue())
229+
230+
pipSrcLayer := result.Layers[1]
231+
232+
Expect(pipSrcLayer.Name).To(Equal("pip-source"))
233+
234+
Expect(pipSrcLayer.Build).To(BeTrue())
235+
Expect(pipSrcLayer.Launch).To(BeFalse())
236+
Expect(pipSrcLayer.Cache).To(BeTrue())
212237

213238
Expect(result.Build.BOM).To(Equal(
214239
[]packit.BOMEntry{
@@ -261,14 +286,22 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
261286
result, err := build(buildContext)
262287
Expect(err).NotTo(HaveOccurred())
263288

264-
Expect(result.Layers).To(HaveLen(1))
265-
layer := result.Layers[0]
289+
Expect(result.Layers).To(HaveLen(2))
290+
pipLayer := result.Layers[0]
291+
292+
Expect(pipLayer.Name).To(Equal("pip"))
293+
294+
Expect(pipLayer.Build).To(BeTrue())
295+
Expect(pipLayer.Launch).To(BeFalse())
296+
Expect(pipLayer.Cache).To(BeTrue())
297+
298+
pipSrcLayer := result.Layers[1]
266299

267-
Expect(layer.Name).To(Equal("pip"))
300+
Expect(pipSrcLayer.Name).To(Equal("pip-source"))
268301

269-
Expect(layer.Build).To(BeTrue())
270-
Expect(layer.Launch).To(BeFalse())
271-
Expect(layer.Cache).To(BeTrue())
302+
Expect(pipSrcLayer.Build).To(BeTrue())
303+
Expect(pipSrcLayer.Launch).To(BeFalse())
304+
Expect(pipSrcLayer.Cache).To(BeTrue())
272305

273306
Expect(buffer.String()).ToNot(ContainSubstring("Executing build process"))
274307
Expect(buffer.String()).To(ContainSubstring("Reusing cached layer"))

constants.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package pip
33
// Pip is the name of the layer into which pip dependency is installed.
44
const Pip = "pip"
55

6+
const PipSrc = "pip-source"
7+
68
// CPython is the name of the python runtime dependency provided by the CPython buildpack: https://github.com/paketo-buildpacks/cpython
79
const CPython = "cpython"
810

integration/default_test.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,20 @@ func testDefault(t *testing.T, context spec.G, it spec.S) {
2121

2222
pack occam.Pack
2323
docker occam.Docker
24+
source string
2425
)
2526

2627
it.Before(func() {
2728
pack = occam.NewPack().WithVerbose()
2829
docker = occam.NewDocker()
30+
31+
var err error
32+
source, err = occam.Source(filepath.Join("testdata", "default_app"))
33+
Expect(err).NotTo(HaveOccurred())
34+
})
35+
36+
it.After(func() {
37+
Expect(os.RemoveAll(source)).To(Succeed())
2938
})
3039

3140
context("when the buildpack is run with pack build", func() {
@@ -49,6 +58,7 @@ func testDefault(t *testing.T, context spec.G, it spec.S) {
4958

5059
it("builds with the defaults", func() {
5160
var err error
61+
5262
var logs fmt.Stringer
5363
image, logs, err = pack.WithNoColor().Build.
5464
WithPullPolicy("never").
@@ -57,7 +67,7 @@ func testDefault(t *testing.T, context spec.G, it spec.S) {
5767
settings.Buildpacks.Pip.Online,
5868
settings.Buildpacks.BuildPlan.Online,
5969
).
60-
Execute(name, filepath.Join("testdata", "default_app"))
70+
Execute(name, source)
6171
Expect(err).ToNot(HaveOccurred(), logs.String)
6272

6373
Expect(logs).To(ContainLines(
@@ -75,6 +85,9 @@ func testDefault(t *testing.T, context spec.G, it spec.S) {
7585
MatchRegexp(` Completed in \d+\.\d+`),
7686
))
7787
Expect(logs).To(ContainLines(
88+
" Configuring build environment",
89+
MatchRegexp(fmt.Sprintf(` PIP_FIND_LINKS -> "\$PIP_FIND_LINKS \/layers\/%s\/pip-source"`, strings.ReplaceAll(buildpackInfo.Buildpack.ID, "/", "_"))),
90+
"",
7891
" Configuring build environment",
7992
MatchRegexp(fmt.Sprintf(` PYTHONPATH -> "\/layers\/%s\/pip\/lib\/python\d+\.\d+\/site-packages:\$PYTHONPATH"`, strings.ReplaceAll(buildpackInfo.Buildpack.ID, "/", "_"))),
8093
"",
@@ -127,7 +140,7 @@ func testDefault(t *testing.T, context spec.G, it spec.S) {
127140
"BP_LOG_LEVEL": "DEBUG",
128141
}).
129142
WithSBOMOutputDir(sbomDir).
130-
Execute(name, filepath.Join("testdata", "default_app"))
143+
Execute(name, source)
131144
Expect(err).ToNot(HaveOccurred(), logs.String)
132145

133146
container, err = docker.Container.Run.

0 commit comments

Comments
 (0)