From 69ea804536a99aeb6d665f9f15b8647b5d2de5df Mon Sep 17 00:00:00 2001 From: Costas Papastathis Date: Wed, 15 Oct 2025 15:39:49 +0200 Subject: [PATCH 1/5] fix: adding BP_NODE_INCLUDE_BUILD_PYTHON env variable --- detect.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/detect.go b/detect.go index ecf2f04e..ff8ada23 100644 --- a/detect.go +++ b/detect.go @@ -2,7 +2,6 @@ package nodeengine import ( "os" - "os/exec" "path/filepath" "github.com/paketo-buildpacks/packit/v2" @@ -83,10 +82,15 @@ func Detect(nvmrcParser, nodeVersionParser VersionParser) packit.DetectFunc { }) } - targetOs := os.Getenv("CNB_TARGET_DISTRO_NAME") - _, pythonNotFound := exec.LookPath("python") + bpNodeIncludeBuildPython, bpNodeIncludeBuildPythonExists := os.LookupEnv("BP_NODE_INCLUDE_BUILD_PYTHON") + + installPython := false + if bpNodeIncludeBuildPythonExists && (bpNodeIncludeBuildPython == "" || bpNodeIncludeBuildPython == "true") { + installPython = true + } else if bpNodeIncludeBuildPythonExists && bpNodeIncludeBuildPython == "false" { + installPython = false + } - installPython := (targetOs != "rhel" && pythonNotFound != nil) if installPython { requirements = append(requirements, packit.BuildPlanRequirement{ Name: Cpython, From 11b57ce9ade2c643fd6ae7af8ddf03837edee167 Mon Sep 17 00:00:00 2001 From: Costas Papastathis Date: Wed, 15 Oct 2025 16:03:29 +0200 Subject: [PATCH 2/5] fix: adding unit tests for detect phase for the BP_NODE_INCLUDE_BUILD_PYTHON env var --- detect_test.go | 130 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/detect_test.go b/detect_test.go index 405fa7a3..92f6ffc1 100644 --- a/detect_test.go +++ b/detect_test.go @@ -279,6 +279,136 @@ func testDetect(t *testing.T, context spec.G, it spec.S) { }) }) + context("when $BP_NODE_INCLUDE_BUILD_PYTHON env variable is present", func() { + it.After(func() { + os.Unsetenv("BP_NODE_INCLUDE_BUILD_PYTHON") + }) + + it("has been set to true, it should include cpython buildpack", func() { + os.Setenv("BP_NODE_INCLUDE_BUILD_PYTHON", "true") + + result, err := detect(packit.DetectContext{ + WorkingDir: "/working-dir", + }) + Expect(err).NotTo(HaveOccurred()) + Expect(result.Plan).To(Equal(packit.BuildPlan{ + Provides: []packit.BuildPlanProvision{ + {Name: nodeengine.Node}, + }, + Requires: []packit.BuildPlanRequirement{ + { + Name: nodeengine.Cpython, + Metadata: nodeengine.BuildPlanMetadata{ + Build: true, + Launch: false, + }, + }, + }, + Or: []packit.BuildPlan{ + { + Provides: []packit.BuildPlanProvision{ + {Name: nodeengine.Node}, + {Name: nodeengine.Npm}, + }, + Requires: []packit.BuildPlanRequirement{ + { + Name: nodeengine.Cpython, + Metadata: nodeengine.BuildPlanMetadata{ + Build: true, + Launch: false, + }, + }, + }, + }, + }, + })) + }) + + it("has no value, it should include cpython buildpack", func() { + os.Setenv("BP_NODE_INCLUDE_BUILD_PYTHON", "") + + result, err := detect(packit.DetectContext{ + WorkingDir: "/working-dir", + }) + Expect(err).NotTo(HaveOccurred()) + Expect(result.Plan).To(Equal(packit.BuildPlan{ + Provides: []packit.BuildPlanProvision{ + {Name: nodeengine.Node}, + }, + Requires: []packit.BuildPlanRequirement{ + { + Name: nodeengine.Cpython, + Metadata: nodeengine.BuildPlanMetadata{ + Build: true, + Launch: false, + }, + }, + }, + Or: []packit.BuildPlan{ + { + Provides: []packit.BuildPlanProvision{ + {Name: nodeengine.Node}, + {Name: nodeengine.Npm}, + }, + Requires: []packit.BuildPlanRequirement{ + { + Name: nodeengine.Cpython, + Metadata: nodeengine.BuildPlanMetadata{ + Build: true, + Launch: false, + }, + }, + }, + }, + }, + })) + }) + + it("has been set to false, it does not include cpython buildpack", func() { + os.Setenv("BP_NODE_INCLUDE_BUILD_PYTHON", "false") + + result, err := detect(packit.DetectContext{ + WorkingDir: "/working-dir", + }) + Expect(err).NotTo(HaveOccurred()) + Expect(result.Plan).To(Equal(packit.BuildPlan{ + Provides: []packit.BuildPlanProvision{ + {Name: nodeengine.Node}, + }, + Or: []packit.BuildPlan{ + { + Provides: []packit.BuildPlanProvision{ + {Name: nodeengine.Node}, + {Name: nodeengine.Npm}, + }, + }, + }, + })) + }) + + it("has been set to random string, it does not include cpython buildpack", func() { + os.Setenv("BP_NODE_INCLUDE_BUILD_PYTHON", "random-string") + + result, err := detect(packit.DetectContext{ + WorkingDir: "/working-dir", + }) + Expect(err).NotTo(HaveOccurred()) + Expect(result.Plan).To(Equal(packit.BuildPlan{ + Provides: []packit.BuildPlanProvision{ + {Name: nodeengine.Node}, + }, + Or: []packit.BuildPlan{ + { + Provides: []packit.BuildPlanProvision{ + {Name: nodeengine.Node}, + {Name: nodeengine.Npm}, + }, + }, + }, + })) + }) + }) + context("failure cases", func() { context("when the dir specified by BP_NODE_PROJECT_PATH does not exist", func() { var workingDir string From 8509f43a868fdfc596bd137e1e1d450e42b9ec8e Mon Sep 17 00:00:00 2001 From: Costas Papastathis Date: Wed, 15 Oct 2025 16:07:40 +0200 Subject: [PATCH 3/5] fix: removing including cpython in some of the tests that is not necessary --- integration/inspector_test.go | 1 - integration/openssl_test.go | 2 -- integration/optimize_memory_test.go | 1 - integration/project_path_test.go | 1 - integration/provides_test.go | 1 - integration/reuse_layer_rebuild_test.go | 34 ++++++++++--------------- integration/simple_app_test.go | 6 ----- 7 files changed, 14 insertions(+), 32 deletions(-) diff --git a/integration/inspector_test.go b/integration/inspector_test.go index ad866eee..1a9478c9 100644 --- a/integration/inspector_test.go +++ b/integration/inspector_test.go @@ -52,7 +52,6 @@ func testInspector(t *testing.T, context spec.G, it spec.S) { image, logs, err = pack.WithNoColor().Build. WithPullPolicy("never"). WithBuildpacks( - settings.Buildpacks.Cpython.Online, settings.Buildpacks.NodeEngine.Online, settings.Buildpacks.Processes.Online, ). diff --git a/integration/openssl_test.go b/integration/openssl_test.go index 4dee57f6..b9537f5c 100644 --- a/integration/openssl_test.go +++ b/integration/openssl_test.go @@ -67,7 +67,6 @@ func testOpenSSL(t *testing.T, context spec.G, it spec.S) { image, logs, err = pack.WithNoColor().Build. WithBuildpacks( - settings.Buildpacks.Cpython.Online, settings.Buildpacks.NodeEngine.Online, settings.Buildpacks.BuildPlan.Online, ). @@ -107,7 +106,6 @@ func testOpenSSL(t *testing.T, context spec.G, it spec.S) { image, logs, err = pack.WithNoColor().Build. WithBuildpacks( - settings.Buildpacks.Cpython.Online, settings.Buildpacks.NodeEngine.Online, settings.Buildpacks.BuildPlan.Online, ). diff --git a/integration/optimize_memory_test.go b/integration/optimize_memory_test.go index 237b0777..5cf7dab4 100644 --- a/integration/optimize_memory_test.go +++ b/integration/optimize_memory_test.go @@ -53,7 +53,6 @@ func testOptimizeMemory(t *testing.T, context spec.G, it spec.S) { image, logs, err = pack.WithNoColor().Build. WithPullPolicy("never"). WithBuildpacks( - settings.Buildpacks.Cpython.Online, settings.Buildpacks.NodeEngine.Online, settings.Buildpacks.Processes.Online, ). diff --git a/integration/project_path_test.go b/integration/project_path_test.go index 9cf098f3..701475cf 100644 --- a/integration/project_path_test.go +++ b/integration/project_path_test.go @@ -60,7 +60,6 @@ func testProjectPath(t *testing.T, context spec.G, it spec.S) { image, logs, err = pack.WithNoColor().Build. WithPullPolicy("never"). WithBuildpacks( - settings.Buildpacks.Cpython.Online, settings.Buildpacks.NodeEngine.Online, settings.Buildpacks.BuildPlan.Online, ). diff --git a/integration/provides_test.go b/integration/provides_test.go index 125d8e45..5b922772 100644 --- a/integration/provides_test.go +++ b/integration/provides_test.go @@ -55,7 +55,6 @@ func testProvides(t *testing.T, context spec.G, it spec.S) { image, logs, err = pack.WithNoColor().Build. WithPullPolicy("never"). WithBuildpacks( - settings.Buildpacks.Cpython.Online, settings.Buildpacks.NodeEngine.Online, settings.Buildpacks.BuildPlan.Online, ). diff --git a/integration/reuse_layer_rebuild_test.go b/integration/reuse_layer_rebuild_test.go index 90aa0537..22e6783c 100644 --- a/integration/reuse_layer_rebuild_test.go +++ b/integration/reuse_layer_rebuild_test.go @@ -72,7 +72,6 @@ func testReusingLayerRebuild(t *testing.T, context spec.G, it spec.S) { firstImage, logs, err = pack.WithNoColor().Build. WithPullPolicy("never"). WithBuildpacks( - settings.Buildpacks.Cpython.Online, settings.Buildpacks.NodeEngine.Online, settings.Buildpacks.BuildPlan.Online, ). @@ -81,10 +80,9 @@ func testReusingLayerRebuild(t *testing.T, context spec.G, it spec.S) { imageIDs[firstImage.ID] = struct{}{} - - Expect(firstImage.Buildpacks).To(HaveLen(3)) - Expect(firstImage.Buildpacks[1].Key).To(Equal(settings.Buildpack.ID)) - Expect(firstImage.Buildpacks[1].Layers).To(HaveKey("node")) + Expect(firstImage.Buildpacks).To(HaveLen(2)) + Expect(firstImage.Buildpacks[0].Key).To(Equal(settings.Buildpack.ID)) + Expect(firstImage.Buildpacks[0].Layers).To(HaveKey("node")) Expect(logs).To(ContainLines( fmt.Sprintf("%s 1.2.3", settings.Buildpack.Name), @@ -139,7 +137,6 @@ func testReusingLayerRebuild(t *testing.T, context spec.G, it spec.S) { secondImage, logs, err = pack.WithNoColor().Build. WithPullPolicy("never"). WithBuildpacks( - settings.Buildpacks.Cpython.Online, settings.Buildpacks.NodeEngine.Online, settings.Buildpacks.BuildPlan.Online, ). @@ -148,9 +145,9 @@ func testReusingLayerRebuild(t *testing.T, context spec.G, it spec.S) { imageIDs[secondImage.ID] = struct{}{} - Expect(secondImage.Buildpacks).To(HaveLen(3)) - Expect(secondImage.Buildpacks[1].Key).To(Equal(settings.Buildpack.ID)) - Expect(secondImage.Buildpacks[1].Layers).To(HaveKey("node")) + Expect(secondImage.Buildpacks).To(HaveLen(2)) + Expect(secondImage.Buildpacks[0].Key).To(Equal(settings.Buildpack.ID)) + Expect(secondImage.Buildpacks[0].Layers).To(HaveKey("node")) Expect(logs).To(ContainLines( fmt.Sprintf("%s 1.2.3", settings.Buildpack.Name), @@ -184,8 +181,7 @@ func testReusingLayerRebuild(t *testing.T, context spec.G, it spec.S) { Expect(err).NotTo(HaveOccurred()) Expect(content).To(ContainSubstring("hello world")) - Expect(secondImage.Buildpacks[0].Layers["cpython"].SHA).To(Equal(firstImage.Buildpacks[0].Layers["cpython"].SHA)) - Expect(secondImage.Buildpacks[1].Layers["node"].SHA).To(Equal(firstImage.Buildpacks[1].Layers["node"].SHA)) + Expect(secondImage.Buildpacks[0].Layers["node"].SHA).To(Equal(firstImage.Buildpacks[0].Layers["node"].SHA)) }) }) @@ -207,7 +203,6 @@ func testReusingLayerRebuild(t *testing.T, context spec.G, it spec.S) { firstImage, logs, err = pack.WithNoColor().Build. WithPullPolicy("never"). WithBuildpacks( - settings.Buildpacks.Cpython.Online, settings.Buildpacks.NodeEngine.Online, settings.Buildpacks.BuildPlan.Online, ). @@ -217,9 +212,9 @@ func testReusingLayerRebuild(t *testing.T, context spec.G, it spec.S) { imageIDs[firstImage.ID] = struct{}{} - Expect(firstImage.Buildpacks).To(HaveLen(3)) - Expect(firstImage.Buildpacks[1].Key).To(Equal(settings.Buildpack.ID)) - Expect(firstImage.Buildpacks[1].Layers).To(HaveKey("node")) + Expect(firstImage.Buildpacks).To(HaveLen(2)) + Expect(firstImage.Buildpacks[0].Key).To(Equal(settings.Buildpack.ID)) + Expect(firstImage.Buildpacks[0].Layers).To(HaveKey("node")) Expect(logs).To(ContainLines( fmt.Sprintf("%s 1.2.3", settings.Buildpack.Name), @@ -275,7 +270,6 @@ func testReusingLayerRebuild(t *testing.T, context spec.G, it spec.S) { secondImage, logs, err = pack.WithNoColor().Build. WithPullPolicy("never"). WithBuildpacks( - settings.Buildpacks.Cpython.Online, settings.Buildpacks.NodeEngine.Online, settings.Buildpacks.BuildPlan.Online, ). @@ -285,9 +279,9 @@ func testReusingLayerRebuild(t *testing.T, context spec.G, it spec.S) { imageIDs[secondImage.ID] = struct{}{} - Expect(secondImage.Buildpacks).To(HaveLen(3)) - Expect(secondImage.Buildpacks[1].Key).To(Equal(settings.Buildpack.ID)) - Expect(secondImage.Buildpacks[1].Layers).To(HaveKey("node")) + Expect(secondImage.Buildpacks).To(HaveLen(2)) + Expect(secondImage.Buildpacks[0].Key).To(Equal(settings.Buildpack.ID)) + Expect(secondImage.Buildpacks[0].Layers).To(HaveKey("node")) Expect(logs).To(ContainLines( fmt.Sprintf("%s 1.2.3", settings.Buildpack.Name), @@ -347,7 +341,7 @@ func testReusingLayerRebuild(t *testing.T, context spec.G, it spec.S) { Expect(err).NotTo(HaveOccurred()) Expect(content).To(ContainSubstring("hello world")) - Expect(secondImage.Buildpacks[1].Layers["node"].SHA).NotTo(Equal(firstImage.Buildpacks[1].Layers["node"].SHA)) + Expect(secondImage.Buildpacks[0].Layers["node"].SHA).NotTo(Equal(firstImage.Buildpacks[0].Layers["node"].SHA)) }) }) } diff --git a/integration/simple_app_test.go b/integration/simple_app_test.go index 869392c2..381c85c9 100644 --- a/integration/simple_app_test.go +++ b/integration/simple_app_test.go @@ -71,7 +71,6 @@ func testSimple(t *testing.T, context spec.G, it spec.S) { image, logs, err = pack.WithNoColor().Build. WithPullPolicy("never"). WithBuildpacks( - settings.Buildpacks.Cpython.Online, settings.Buildpacks.NodeEngine.Online, settings.Buildpacks.BuildPlan.Online, ). @@ -184,7 +183,6 @@ func testSimple(t *testing.T, context spec.G, it spec.S) { WithPullPolicy("never"). WithEnv(map[string]string{"NODE_ENV": "development", "NODE_VERBOSE": "true"}). WithBuildpacks( - settings.Buildpacks.Cpython.Online, settings.Buildpacks.NodeEngine.Online, settings.Buildpacks.BuildPlan.Online, ). @@ -250,7 +248,6 @@ func testSimple(t *testing.T, context spec.G, it spec.S) { image, logs, err = pack.WithNoColor().Build. WithPullPolicy("never"). WithBuildpacks( - settings.Buildpacks.Cpython.Online, settings.Buildpacks.NodeEngine.Online, settings.Buildpacks.BuildPlan.Online, ). @@ -337,7 +334,6 @@ func testSimple(t *testing.T, context spec.G, it spec.S) { image, logs, err = pack.WithNoColor().Build. WithPullPolicy("never"). WithBuildpacks( - settings.Buildpacks.Cpython.Online, settings.Buildpacks.NodeEngine.Online, settings.Buildpacks.BuildPlan.Online, ). @@ -419,7 +415,6 @@ func testSimple(t *testing.T, context spec.G, it spec.S) { image, logs, err = pack.WithNoColor().Build. WithPullPolicy("never"). WithBuildpacks( - settings.Buildpacks.Cpython.Online, settings.Buildpacks.NodeEngine.Deprecated, settings.Buildpacks.BuildPlan.Online, ). @@ -448,7 +443,6 @@ func testSimple(t *testing.T, context spec.G, it spec.S) { image, logs, err = pack.WithNoColor().Build. WithPullPolicy("never"). WithBuildpacks( - settings.Buildpacks.Cpython.Online, settings.Buildpacks.NodeEngine.Online, settings.Buildpacks.BuildPlan.Online, ). From a21559949073c3a0cc06194bcec7f901651574ab Mon Sep 17 00:00:00 2001 From: Costas Papastathis Date: Wed, 15 Oct 2025 16:27:17 +0200 Subject: [PATCH 4/5] fix: including cpython offline and on simple app test --- integration/offline_test.go | 5 ++++- integration/simple_app_test.go | 17 ++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/integration/offline_test.go b/integration/offline_test.go index 4e043d82..dacdd9e0 100644 --- a/integration/offline_test.go +++ b/integration/offline_test.go @@ -60,7 +60,10 @@ func testOffline(t *testing.T, context spec.G, it spec.S) { settings.Buildpacks.NodeEngine.Offline, settings.Buildpacks.BuildPlan.Online, ). - WithEnv(map[string]string{"BP_NODE_OPTIMIZE_MEMORY": "true"}). + WithEnv(map[string]string{ + "BP_NODE_OPTIMIZE_MEMORY": "true", + "BP_NODE_INCLUDE_BUILD_PYTHON": "true", + }). WithNetwork("none"). Execute(name, source) diff --git a/integration/simple_app_test.go b/integration/simple_app_test.go index 381c85c9..6653c377 100644 --- a/integration/simple_app_test.go +++ b/integration/simple_app_test.go @@ -71,16 +71,31 @@ func testSimple(t *testing.T, context spec.G, it spec.S) { image, logs, err = pack.WithNoColor().Build. WithPullPolicy("never"). WithBuildpacks( + settings.Buildpacks.Cpython.Online, settings.Buildpacks.NodeEngine.Online, settings.Buildpacks.BuildPlan.Online, ). WithEnv(map[string]string{ - "BP_LOG_LEVEL": "DEBUG", + "BP_LOG_LEVEL": "DEBUG", + "BP_NODE_INCLUDE_BUILD_PYTHON": "true", }). WithSBOMOutputDir(sbomDir). Execute(name, source) Expect(err).ToNot(HaveOccurred(), logs.String) + Expect(logs).To(ContainLines( + MatchRegexp(` Selected CPython version \(using \): \d+\.\d+\.\d+`), + )) + Expect(logs).To(ContainLines( + " Executing build process", + MatchRegexp(` Installing CPython \d+\.\d+\.\d+`), + MatchRegexp(` Completed in \d+(\.\d+)?`), + )) + + Expect(logs).To(ContainLines( + " Generating SBOM for /layers/paketo-buildpacks_cpython/cpython", + )) + Expect(logs).To(ContainLines( fmt.Sprintf("%s 1.2.3", settings.Buildpack.Name), " Resolving Node Engine version", From 7e2ebe40963209fb08fdeeb0a923241a0ad556c1 Mon Sep 17 00:00:00 2001 From: Costas Papastathis Date: Wed, 15 Oct 2025 16:58:29 +0200 Subject: [PATCH 5/5] fix: Documenting BP_NODE_INCLUDE_BUILD_PYTHON env variable --- README.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 72550815..19fe0631 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ ## `docker.io/paketobuildpacks/node-engine` -The Node Engine CNB provides the Node binary distribution. The buildpack +The Node Engine CNB provides the Node binary distribution. The buildpack installs the Node binary distribution onto the `$PATH` which makes it available -for subsequent buildpacks and in the final running container. Examples of +for subsequent buildpacks and in the final running container. Examples of buildpacks that might use the Node binary distribution are the [NPM CNB](https://github.com/paketo-buildpacks/npm) and [Yarn Install CNB](https://github.com/paketo-buildpacks/yarn-install) @@ -159,14 +159,34 @@ $BPL_DEBUG_PORT="9009" For more information on debugging, see [Official Documentation](https://nodejs.org/en/docs/guides/debugging-getting-started) +### Include python during build process + +To require [python](https://github.com/paketo-buildpacks/cpython) during the build process, set the `BP_NODE_INCLUDE_BUILD_PYTHON` environment variable at build time. You can set it either directly: + +```shell +pack build my-app --builder paketobuildpcks/builder-jammy-base \ + --env BP_NODE_INCLUDE_BUILD_PYTHON=true +``` + +or through a [`project.toml`](https://github.com/buildpacks/spec/blob/main/extensions/project-descriptor.md) file. + +This is necessary for compiling native modules during `npm install` process, as `node-gyp` requires Python to complete this process. + +Note that the `BP_NODE_INCLUDE_BUILD_PYTHON` variable is not required in the following cases: + +- The [builder-jammy-full](https://github.com/paketo-buildpacks/builder-jammy-full) builder as python is already provided by the build image. +- The UBI builders ([ubi-8-builder](https://github.com/paketo-buildpacks/builder-ubi8-base), [ubi-9-builder](https://github.com/paketo-buildpacks/ubi-9-builder), etc.), as Python is being provided by the extension during build time. + ## Run Tests To run all unit tests, run: + ``` ./scripts/unit.sh ``` To run all integration tests, run: + ``` /scripts/integration.sh ```