Skip to content

Commit b66b202

Browse files
authored
Error when multiple package manager files are found (#1993)
Currently the buildpack will use whichever package manager it finds first, if the files of multiple package managers are found. This occasionally results in support tickets where the user believes the build to not be installing dependencies correctly, when in fact they are adding dependencies to the wrong package manager file. It also means historic users of third-party Poetry and uv buildpacks might not realise that the buildpack now natively supports those package managers (in a much more efficient way), if they missed our release announcements. As such, in November 2024 in #1692 a warning was added, which we're now converting to an error. Users with multiple package manager files committed to their Git repo will need to pick one package manager and delete the files relating to the others. And users who are still using the third-party Poetry or uv buildpacks will need to remove those buildpacks from their app: https://devcenter.heroku.com/articles/managing-buildpacks#remove-classic-buildpacks Closes #1691. GUS-W-18915632.
1 parent 6d06c3b commit b66b202

File tree

6 files changed

+38
-98
lines changed

6 files changed

+38
-98
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## [Unreleased]
44

5+
- The build now errors if the files for multiple package managers are found. This replaces the warning displayed since November 2024. ([#1993](https://github.com/heroku/heroku-buildpack-python/pull/1993))
56
- Sunset the previously deprecated support for falling back to installing dependencies from a `setup.py` file if no Python package manager files were found. ([#1992](https://github.com/heroku/heroku-buildpack-python/pull/1992))
67

78
## [v324] - 2025-12-09

lib/package_manager.sh

Lines changed: 16 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,6 @@ function package_manager::determine_package_manager() {
4444
package_managers_found_display_text+=("requirements.txt (pip)")
4545
fi
4646

47-
# This must be after the requirements.txt check, so that the requirements.txt exported by
48-
# `python-poetry-buildpack` takes precedence over poetry.lock, for consistency with the
49-
# behaviour prior to this buildpack supporting Poetry natively. In the future the presence
50-
# of multiple package manager files will be turned into an error, at which point the
51-
# ordering here won't matter.
5247
if [[ -f "${build_dir}/poetry.lock" ]]; then
5348
package_managers_found+=(poetry)
5449
package_managers_found_display_text+=("poetry.lock (Poetry)")
@@ -132,57 +127,38 @@ function package_manager::determine_package_manager() {
132127
exit 1
133128
;;
134129
*)
135-
# If multiple package managers are found, use the first found above.
136-
# TODO: Turn this case into an error since it results in support tickets from users
137-
# who don't realise they have multiple package manager files and think their changes
138-
# aren't taking effect. (We'll need to wait until after Poetry support has landed,
139-
# and people have had a chance to migrate from the Poetry buildpack mentioned above.)
140-
echo "${package_managers_found[0]}"
141-
142-
output::warning <<-EOF
143-
Warning: Multiple Python package manager files were found.
130+
output::error <<-EOF
131+
Error: Multiple Python package manager files were found.
144132
145133
Exactly one package manager file should be present in your app's
146134
source code, however, several were found:
147135
148136
$(printf -- "%s\n" "${package_managers_found_display_text[@]}")
149137
150-
For now, we will build your app using the first package manager
151-
listed above, however, in the future this warning will become
152-
an error.
138+
Previously, the buildpack guessed which package manager to use
139+
and installed your dependencies with the first package manager
140+
listed above. However, this implicit behaviour was deprecated
141+
in November 2024 and is now no longer supported.
153142
154-
Decide which package manager you want to use with your app, and
155-
then delete the file(s) and any config from the others.
143+
You must decide which package manager you want to use with your
144+
app, and then delete the file(s) and any config from the others.
156145
157146
If you aren't sure which package manager to use, we recommend
158147
trying uv, since it supports lockfiles, is extremely fast, and
159148
is actively maintained by a full-time team:
160149
https://docs.astral.sh/uv/
161-
EOF
162-
163-
if [[ "${package_managers_found[*]}" == *"poetry"* ]]; then
164-
output::notice <<-EOF
165-
Note: We recently added support for the package manager Poetry.
166-
If you are using a third-party Poetry buildpack you must remove
167-
it, otherwise the requirements.txt file it generates will cause
168-
the warning above.
169-
EOF
170-
fi
171150
172-
if [[ "${package_managers_found[*]}" == *"uv"* ]]; then
173-
output::notice <<-EOF
174-
Note: We recently added support for the package manager uv.
175-
If you are using a third-party uv buildpack you must remove
176-
it, otherwise the requirements.txt file it generates will cause
177-
the warning above.
178-
EOF
179-
fi
180-
181-
build_data::set_string "package_manager_multiple_found" "$(
151+
Note: If you use a third-party uv or Poetry buildpack, you must
152+
remove it from your app, since it's no longer required and the
153+
requirements.txt file it generates will trigger this error. See:
154+
https://devcenter.heroku.com/articles/managing-buildpacks#remove-classic-buildpacks
155+
EOF
156+
build_data::set_string "failure_reason" "package-manager::multiple-found"
157+
build_data::set_string "failure_detail" "$(
182158
IFS=,
183159
echo "${package_managers_found[*]}"
184160
)"
185-
return 0
161+
exit 1
186162
;;
187163
esac
188164
}

spec/fixtures/multiple_package_managers/.python-version

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +0,0 @@
1-
[[source]]
2-
url = "https://pypi.org/simple"
3-
verify_ssl = true
4-
name = "pypi"
5-
6-
[packages]

spec/fixtures/multiple_package_managers/Pipfile.lock

Lines changed: 0 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

spec/hatchet/package_manager_spec.rb

Lines changed: 21 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -87,56 +87,44 @@
8787
end
8888
end
8989

90-
# This case will be turned into an error in the future.
9190
context 'when there are multiple package manager files' do
92-
let(:app) { Hatchet::Runner.new('spec/fixtures/multiple_package_managers') }
91+
let(:app) { Hatchet::Runner.new('spec/fixtures/multiple_package_managers', allow_failure: true) }
9392

94-
it 'outputs a warning and builds with the first listed' do
93+
it 'fails the build with an informative error message' do
9594
app.deploy do |app|
96-
expect(clean_output(app.output)).to match(Regexp.new(<<~REGEX))
95+
expect(clean_output(app.output)).to include(<<~OUTPUT)
9796
remote: -----> Python app detected
9897
remote:
99-
remote: ! Warning: Multiple Python package manager files were found.
98+
remote: ! Error: Multiple Python package manager files were found.
10099
remote: !
101100
remote: ! Exactly one package manager file should be present in your app's
102101
remote: ! source code, however, several were found:
103102
remote: !
104-
remote: ! Pipfile.lock \\(Pipenv\\)
105-
remote: ! requirements.txt \\(pip\\)
106-
remote: ! poetry.lock \\(Poetry\\)
107-
remote: ! uv.lock \\(uv\\)
103+
remote: ! Pipfile.lock (Pipenv)
104+
remote: ! requirements.txt (pip)
105+
remote: ! poetry.lock (Poetry)
106+
remote: ! uv.lock (uv)
108107
remote: !
109-
remote: ! For now, we will build your app using the first package manager
110-
remote: ! listed above, however, in the future this warning will become
111-
remote: ! an error.
108+
remote: ! Previously, the buildpack guessed which package manager to use
109+
remote: ! and installed your dependencies with the first package manager
110+
remote: ! listed above. However, this implicit behaviour was deprecated
111+
remote: ! in November 2024 and is now no longer supported.
112112
remote: !
113-
remote: ! Decide which package manager you want to use with your app, and
114-
remote: ! then delete the file\\(s\\) and any config from the others.
113+
remote: ! You must decide which package manager you want to use with your
114+
remote: ! app, and then delete the file(s) and any config from the others.
115115
remote: !
116116
remote: ! If you aren't sure which package manager to use, we recommend
117117
remote: ! trying uv, since it supports lockfiles, is extremely fast, and
118118
remote: ! is actively maintained by a full-time team:
119119
remote: ! https://docs.astral.sh/uv/
120+
remote: !
121+
remote: ! Note: If you use a third-party uv or Poetry buildpack, you must
122+
remote: ! remove it from your app, since it's no longer required and the
123+
remote: ! requirements.txt file it generates will trigger this error. See:
124+
remote: ! https://devcenter.heroku.com/articles/managing-buildpacks#remove-classic-buildpacks
120125
remote:
121-
remote:
122-
remote: ! Note: We recently added support for the package manager Poetry.
123-
remote: ! If you are using a third-party Poetry buildpack you must remove
124-
remote: ! it, otherwise the requirements.txt file it generates will cause
125-
remote: ! the warning above.
126-
remote:
127-
remote:
128-
remote: ! Note: We recently added support for the package manager uv.
129-
remote: ! If you are using a third-party uv buildpack you must remove
130-
remote: ! it, otherwise the requirements.txt file it generates will cause
131-
remote: ! the warning above.
132-
remote:
133-
remote: -----> Using Python #{DEFAULT_PYTHON_MAJOR_VERSION} specified in .python-version
134-
remote: -----> Installing Python #{DEFAULT_PYTHON_FULL_VERSION}
135-
remote: -----> Installing Pipenv #{PIPENV_VERSION}
136-
remote: -----> Installing dependencies using 'pipenv install --deploy'
137-
remote: Installing dependencies from Pipfile.lock \\(.+\\)...
138-
remote: -----> Saving cache
139-
REGEX
126+
remote: ! Push rejected, failed to compile Python app.
127+
OUTPUT
140128
end
141129
end
142130
end

0 commit comments

Comments
 (0)