Skip to content

Commit 68e0166

Browse files
authored
refactor: smarter pyproject.toml parsing (#64)
Currently the poetry detection only accounts for the presence of a pyproject.toml file which nowadays is used by many other build backends. The detection now checks whether the file contains the corresponding build-system entry or no entry which matches the poetry v1 implementation. This should result in backward compatible behaviour. * chore: move all testdata apps in own folder per installer * refactor: make poetry detection smarter
1 parent ef4cf85 commit 68e0166

40 files changed

Lines changed: 328 additions & 69 deletions

detect.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import (
2121
//
2222
// If this buildpack detects files that indicate your app is a Python project,
2323
// it will pass detection.
24-
func Detect(logger scribe.Emitter, pyprojectVersionParser poetry.PyProjectPythonVersionParser) packit.DetectFunc {
24+
func Detect(logger scribe.Emitter, pyProjectParser poetry.PyProjectParser) packit.DetectFunc {
2525
return func(context packit.DetectContext) (packit.DetectResult, error) {
2626
plans := []packit.BuildPlan{}
2727

@@ -49,7 +49,7 @@ func Detect(logger scribe.Emitter, pyprojectVersionParser poetry.PyProjectPython
4949
logger.Detail("%s", err)
5050
}
5151

52-
poetryResult, err := poetry.Detect(pyprojectVersionParser)(context)
52+
poetryResult, err := poetry.Detect(pyProjectParser)(context)
5353

5454
if err == nil {
5555
plans = append(plans, poetryResult.Plan)

detect_test.go

Lines changed: 101 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func testDetect(t *testing.T, context spec.G, it spec.S) {
3636
workingDir string
3737
buffer *bytes.Buffer
3838

39-
parsePythonVersion *poetryfakes.PyProjectPythonVersionParser
39+
parsePoetryProject *poetryfakes.PoetryPyProjectParser
4040

4141
detect packit.DetectFunc
4242

@@ -51,10 +51,11 @@ func testDetect(t *testing.T, context spec.G, it spec.S) {
5151
buffer = bytes.NewBuffer(nil)
5252
logger := scribe.NewEmitter(buffer)
5353

54-
parsePythonVersion = &poetryfakes.PyProjectPythonVersionParser{}
55-
parsePythonVersion.ParsePythonVersionCall.Returns.String = "1.2.3"
54+
parsePoetryProject = &poetryfakes.PoetryPyProjectParser{}
55+
parsePoetryProject.ParsePythonVersionCall.Returns.String = "1.2.3"
56+
parsePoetryProject.IsPoetryProjectCall.Returns.Bool = true
5657

57-
detect = pythoninstallers.Detect(logger, parsePythonVersion)
58+
detect = pythoninstallers.Detect(logger, parsePoetryProject)
5859

5960
plans = append(plans, packit.BuildPlan{
6061
Provides: []packit.BuildPlanProvision{
@@ -120,50 +121,116 @@ func testDetect(t *testing.T, context spec.G, it spec.S) {
120121
})
121122

122123
context("with pyproject.toml", func() {
123-
it.Before(func() {
124-
Expect(os.WriteFile(filepath.Join(workingDir, "pyproject.toml"), []byte{}, os.ModePerm)).To(Succeed())
125-
Expect(os.WriteFile(filepath.Join(workingDir, "pyproject.toml"), []byte(""), 0755)).To(Succeed())
124+
context("without build backend", func() {
125+
it.Before(func() {
126+
Expect(os.WriteFile(filepath.Join(workingDir, "pyproject.toml"), []byte{}, os.ModePerm)).To(Succeed())
127+
})
128+
129+
it("passes detection", func() {
130+
result, err := detect(packit.DetectContext{
131+
WorkingDir: workingDir,
132+
})
133+
134+
withPoetry := append(plans,
135+
packit.BuildPlan{
136+
Provides: []packit.BuildPlanProvision{
137+
{Name: poetry.Pip},
138+
{Name: poetry.PoetryDependency},
139+
},
140+
Requires: []packit.BuildPlanRequirement{
141+
{
142+
Name: poetry.CPython,
143+
Metadata: common.BuildPlanMetadata{
144+
Build: true,
145+
Version: "1.2.3",
146+
VersionSource: "pyproject.toml",
147+
},
148+
},
149+
{
150+
Name: poetry.Pip,
151+
Metadata: common.BuildPlanMetadata{
152+
Build: true,
153+
},
154+
},
155+
},
156+
},
157+
)
158+
159+
Expect(err).NotTo(HaveOccurred())
160+
Expect(result.Plan).To(Equal(pythoninstallers.Or(withPoetry...)))
161+
})
126162
})
127163

128-
it("passes detection", func() {
129-
result, err := detect(packit.DetectContext{
130-
WorkingDir: workingDir,
164+
context("with poetry build backend", func() {
165+
it.Before(func() {
166+
content := []byte(`
167+
[build-system]
168+
requires = ["poetry-core>=1.0.0"]
169+
build-backend = "poetry.core.masonry.api"
170+
`)
171+
Expect(os.WriteFile(filepath.Join(workingDir, "pyproject.toml"), content, os.ModePerm)).To(Succeed())
172+
parsePoetryProject.IsPoetryProjectCall.Returns.Bool = true
131173
})
132174

133-
withPoetry := append(plans,
134-
packit.BuildPlan{
135-
Provides: []packit.BuildPlanProvision{
136-
{Name: poetry.Pip},
137-
{Name: poetry.PoetryDependency},
138-
},
139-
Requires: []packit.BuildPlanRequirement{
140-
{
141-
Name: poetry.CPython,
142-
Metadata: common.BuildPlanMetadata{
143-
Build: true,
144-
Version: "1.2.3",
145-
VersionSource: "pyproject.toml",
146-
},
175+
it("passes detection", func() {
176+
result, err := detect(packit.DetectContext{
177+
WorkingDir: workingDir,
178+
})
179+
180+
withPoetry := append(plans,
181+
packit.BuildPlan{
182+
Provides: []packit.BuildPlanProvision{
183+
{Name: poetry.Pip},
184+
{Name: poetry.PoetryDependency},
147185
},
148-
{
149-
Name: poetry.Pip,
150-
Metadata: common.BuildPlanMetadata{
151-
Build: true,
186+
Requires: []packit.BuildPlanRequirement{
187+
{
188+
Name: poetry.CPython,
189+
Metadata: common.BuildPlanMetadata{
190+
Build: true,
191+
Version: "1.2.3",
192+
VersionSource: "pyproject.toml",
193+
},
194+
},
195+
{
196+
Name: poetry.Pip,
197+
Metadata: common.BuildPlanMetadata{
198+
Build: true,
199+
},
152200
},
153201
},
154202
},
155-
},
156-
)
203+
)
157204

158-
Expect(err).NotTo(HaveOccurred())
159-
Expect(result.Plan).To(Equal(pythoninstallers.Or(withPoetry...)))
205+
Expect(err).NotTo(HaveOccurred())
206+
Expect(result.Plan).To(Equal(pythoninstallers.Or(withPoetry...)))
207+
})
208+
})
209+
210+
context("with other build backend", func() {
211+
it.Before(func() {
212+
content := []byte(`
213+
[build-system]
214+
requires = ["setuptools", "setuptools-scm"]
215+
build-backend = "setuptools.build_meta"
216+
`)
217+
Expect(os.WriteFile(filepath.Join(workingDir, "pyproject.toml"), content, os.ModePerm)).To(Succeed())
218+
parsePoetryProject.IsPoetryProjectCall.Returns.Bool = false
219+
})
220+
221+
it("passes detection", func() {
222+
result, err := detect(packit.DetectContext{
223+
WorkingDir: workingDir,
224+
})
225+
Expect(err).NotTo(HaveOccurred())
226+
Expect(result.Plan).To(Equal(pythoninstallers.Or(plans...)))
227+
})
160228
})
161229
})
162230

163231
context("with uv.lock", func() {
164232
it.Before(func() {
165-
Expect(os.WriteFile(filepath.Join(workingDir, uv.LockfileName), []byte{}, os.ModePerm)).To(Succeed())
166-
Expect(os.WriteFile(filepath.Join(workingDir, uv.LockfileName), []byte(`requires-python = "3.13.0"`), 0755)).To(Succeed())
233+
Expect(os.WriteFile(filepath.Join(workingDir, uv.LockfileName), []byte(`requires-python = "3.13.0"`), os.ModePerm)).To(Succeed())
167234
})
168235

169236
it("passes detection", func() {

integration/installers/init_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ func TestIntegration(t *testing.T) {
108108
suite("Poetry Default", poetryTestDefault, spec.Parallel())
109109
suite("Poetry LayerReuse", poetryTestLayerReuse, spec.Parallel())
110110
suite("Poetry Versions", poetryTestVersions, spec.Parallel())
111+
suite("Poetry pyproject.toml", poetryTestPyProject, spec.Parallel())
111112

112113
// uv
113114
suite("uv Default", uvTestDefault, spec.Parallel())

integration/installers/miniconda_default_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func minicondaTestDefault(t *testing.T, context spec.G, it spec.S) {
4646
name, err = occam.RandomName()
4747
Expect(err).NotTo(HaveOccurred())
4848

49-
source, err = occam.Source(filepath.Join("testdata", "miniconda_app"))
49+
source, err = occam.Source(filepath.Join("testdata", "conda", "miniconda_app"))
5050
Expect(err).NotTo(HaveOccurred())
5151

5252
})
@@ -110,7 +110,7 @@ func minicondaTestDefault(t *testing.T, context spec.G, it spec.S) {
110110
var err error
111111
var logs fmt.Stringer
112112

113-
source, err = occam.Source(filepath.Join("testdata", "miniconda_app"))
113+
source, err = occam.Source(filepath.Join("testdata", "conda", "miniconda_app"))
114114
Expect(err).NotTo(HaveOccurred())
115115

116116
image, logs, err = pack.WithNoColor().Build.

integration/installers/miniconda_logging_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func minicondaTestLogging(t *testing.T, context spec.G, it spec.S) {
4343
name, err = occam.RandomName()
4444
Expect(err).NotTo(HaveOccurred())
4545

46-
source, err = occam.Source(filepath.Join("testdata", "miniconda_app"))
46+
source, err = occam.Source(filepath.Join("testdata", "conda", "miniconda_app"))
4747
Expect(err).NotTo(HaveOccurred())
4848

4949
})

integration/installers/miniconda_offline_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func minicondaTestOffline(t *testing.T, context spec.G, it spec.S) {
4343
name, err = occam.RandomName()
4444
Expect(err).NotTo(HaveOccurred())
4545

46-
source, err = occam.Source(filepath.Join("testdata", "miniconda_app"))
46+
source, err = occam.Source(filepath.Join("testdata", "conda", "miniconda_app"))
4747
Expect(err).NotTo(HaveOccurred())
4848
})
4949

integration/installers/miniconda_reuse_layer_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func minicondaTestReusingLayerRebuild(t *testing.T, context spec.G, it spec.S) {
4444
imageIDs = map[string]struct{}{}
4545
containerIDs = map[string]struct{}{}
4646

47-
source, err = occam.Source(filepath.Join("testdata", "miniconda_app"))
47+
source, err = occam.Source(filepath.Join("testdata", "conda", "miniconda_app"))
4848
Expect(err).NotTo(HaveOccurred())
4949
})
5050

integration/installers/pip_default_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func pipTestDefault(t *testing.T, context spec.G, it spec.S) {
3333
docker = occam.NewDocker()
3434

3535
var err error
36-
source, err = occam.Source(filepath.Join("testdata", "pip_app"))
36+
source, err = occam.Source(filepath.Join("testdata", "pip", "pip_app"))
3737
Expect(err).NotTo(HaveOccurred())
3838
})
3939

integration/installers/pip_layer_reuse_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func pipTestLayerReuse(t *testing.T, context spec.G, it spec.S) {
4646
imageIDs = map[string]struct{}{}
4747
containerIDs = map[string]struct{}{}
4848

49-
source, err = occam.Source(filepath.Join("testdata", "pip_app"))
49+
source, err = occam.Source(filepath.Join("testdata", "pip", "pip_app"))
5050
Expect(err).NotTo(HaveOccurred())
5151
})
5252

integration/installers/pip_offline_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func pipTestOffline(t *testing.T, context spec.G, it spec.S) {
4444
name, err = occam.RandomName()
4545
Expect(err).NotTo(HaveOccurred())
4646

47-
source, err = occam.Source(filepath.Join("testdata", "pip_app"))
47+
source, err = occam.Source(filepath.Join("testdata", "pip", "pip_app"))
4848
Expect(err).NotTo(HaveOccurred())
4949
})
5050

0 commit comments

Comments
 (0)