Skip to content

Commit 24b892e

Browse files
authored
Perform editable package .pth and .egg-link path rewriting at runtime (#1252)
Currently the build system performs builds in a different directory (`/tmp/build_<hash>`) to which the app will be run at runtime (`/app`). This means that any hardcoded paths in the slug must be rewritten by the buildpack, so the app still works after being moved. One such case of hardcoded paths, are the `.pth` and `.egg-link` files that are created in the `site-packages` directory by Pip/setuptools for editable package installs (aka develop mode). The most common way someone might use editable mode is via `-e <package specifier>` entries in their `requirements.txt` file. Until now, the Python buildpack rewrote paths inside these files during the compile itself, which meant the build-time paths were no longer present when subsequent buildpacks ran. This happened to work due to an interaction of legacy setuptools behaviour and a buildpack bug, but stops working in setuptools 47.2.0 or later - as described in #1006. Longer term we would like to stop building in one location and running the app from another, so that the path rewriting isn't required at all. However that change breaks some other buildpacks so requires a long-term transition plan. In the meantime, this change moves path rewriting to a `.profile.d/` script, so that it occurs at runtime, and so after all other buildpacks have run. Additional test coverage of editable packages was added previously in #1251 and #1253, and has confirmed that this new `profile.d/` script approach will prevent the issues in #1006 when setuptools is updated in a future PR. There is one subtle implication of moving this path rewriting, in that subsequent cached builds will no longer see the existing package as being already installed, so won't uninstall if before reinstalling it (as seen in the test log output change). However this is not believed to have any significant impact. Fixes #1006. (And so unblocks updating to the newer setuptools required for #1248.) GUS-W-7828034.
1 parent e315f1f commit 24b892e

File tree

6 files changed

+17
-42
lines changed

6 files changed

+17
-42
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Unreleased
44

5+
- Perform editable package `.pth` and `.egg-link` path rewriting at runtime ([#1252](https://github.com/heroku/heroku-buildpack-python/pull/1252)).
56

67
## v200 (2021-10-04)
78

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ lint: lint-scripts lint-ruby
1414

1515
lint-scripts:
1616
@shellcheck -x bin/compile bin/detect bin/release bin/test-compile bin/utils bin/warnings bin/default_pythons
17-
@shellcheck -x bin/steps/collectstatic bin/steps/eggpath-fix bin/steps/eggpath-fix2 bin/steps/nltk bin/steps/pip-install bin/steps/pipenv bin/steps/pipenv-python-version bin/steps/python
17+
@shellcheck -x bin/steps/collectstatic bin/steps/nltk bin/steps/pip-install bin/steps/pipenv bin/steps/pipenv-python-version bin/steps/python
1818
@shellcheck -x bin/steps/hooks/*
1919

2020
lint-ruby:

bin/compile

+11-10
Original file line numberDiff line numberDiff line change
@@ -264,12 +264,6 @@ if [ ! -f requirements.txt ] && [ ! -f Pipfile ]; then
264264
echo "-e ." > requirements.txt
265265
fi
266266

267-
# Fix egg-links.
268-
# Because we're installing things into a different path than we're running them (temp dir vs app dir),
269-
# We must re-write all of Python's eggpath links to target the proper directory.
270-
# shellcheck source=bin/steps/eggpath-fix
271-
source "$BIN_DIR/steps/eggpath-fix"
272-
273267
# SQLite3 support.
274268
# This sets up and installs sqlite3 dev headers and the sqlite3 binary but not the
275269
# libsqlite3-0 library since that exists on the stack image.
@@ -345,6 +339,17 @@ if [[ \$HOME != "/app" ]]; then
345339
fi
346340
EOT
347341

342+
# At runtime, rewrite paths in editable package .egg-link and .pth files from the build time paths
343+
# (such as `/tmp/build_<hash>`) back to `/app`. This is not done during the build itself, since later
344+
# buildpacks still need the build time paths. The `/lib*/*/` glob is to ensure it matches against both:
345+
# - lib/python3.NN/site-packages/ (CPython)
346+
# - lib-python/3/site-packages/ (PyPy)
347+
if [[ "${BUILD_DIR}" != "/app" ]]; then
348+
cat <<EOT >> "$PROFILE_PATH"
349+
find .heroku/python/lib*/*/site-packages/ -type f -and \( -name '*.egg-link' -or -name '*.pth' \) -exec sed -i -e 's#${BUILD_DIR}#/app#' {} \+
350+
EOT
351+
fi
352+
348353
# Install sane-default script for $WEB_CONCURRENCY and $FORWARDED_ALLOW_IPS.
349354
cp "$ROOT_DIR/vendor/WEB_CONCURRENCY.sh" "$WEB_CONCURRENCY_PROFILE_PATH"
350355
cp "$ROOT_DIR/vendor/python.gunicorn.sh" "$GUNICORN_PROFILE_PATH"
@@ -353,10 +358,6 @@ cp "$ROOT_DIR/vendor/python.gunicorn.sh" "$GUNICORN_PROFILE_PATH"
353358
# shellcheck source=bin/steps/hooks/post_compile
354359
source "$BIN_DIR/steps/hooks/post_compile"
355360

356-
# Fix egg-links, again.
357-
# shellcheck source=bin/steps/eggpath-fix2
358-
source "$BIN_DIR/steps/eggpath-fix2"
359-
360361
# Store new artifacts in the cache.
361362
rm -rf "$CACHE_DIR/.heroku/python"
362363
rm -rf "$CACHE_DIR/.heroku/python-version"

bin/steps/eggpath-fix

-13
This file was deleted.

bin/steps/eggpath-fix2

-10
This file was deleted.

spec/hatchet/pip_spec.rb

+4-8
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@
118118
remote: -----> Inline app detected
119119
remote: ==> .heroku/python/lib/python.*/site-packages/easy-install.pth <==
120120
remote: /app/.heroku/src/gunicorn
121-
remote: /app/local_package
121+
remote: /tmp/build_.*/local_package
122122
remote:
123123
remote: ==> .heroku/python/lib/python.*/site-packages/gunicorn.egg-link <==
124124
remote: /app/.heroku/src/gunicorn
@@ -140,7 +140,7 @@
140140
/app/.heroku/src/gunicorn
141141
.
142142
==> .heroku/python/lib/python.*/site-packages/local-package.egg-link <==
143-
/tmp/build_.*/local_package
143+
/app/local_package
144144
.
145145
Running entrypoint for the local package: Hello!
146146
Running entrypoint for the VCS package: gunicorn \\(version 20.1.0\\)
@@ -159,17 +159,13 @@
159159
remote: Obtaining gunicorn from git\\+https://github.com/benoitc/[email protected]#egg=gunicorn \\(from -r /tmp/build_.*/requirements.txt \\(line 2\\)\\)
160160
remote: Cloning https://github.com/benoitc/gunicorn \\(to revision 20.1.0\\) to /app/.heroku/src/gunicorn
161161
remote: Installing collected packages: gunicorn, local-package
162-
remote: Attempting uninstall: gunicorn
163-
remote: Found existing installation: gunicorn 20.1.0
164-
remote: Uninstalling gunicorn-20.1.0:
165-
remote: Successfully uninstalled gunicorn-20.1.0
166162
remote: Running setup.py develop for gunicorn
167163
remote: Running setup.py develop for local-package
168164
remote: Successfully installed gunicorn local-package
169165
remote: -----> Running post-compile hook
170166
remote: ==> .heroku/python/lib/python.*/site-packages/easy-install.pth <==
171-
remote: /tmp/build_.*/local_package
172167
remote: /app/.heroku/src/gunicorn
168+
remote: /tmp/build_.*/local_package
173169
remote:
174170
remote: ==> .heroku/python/lib/python.*/site-packages/gunicorn.egg-link <==
175171
remote: /app/.heroku/src/gunicorn
@@ -181,8 +177,8 @@
181177
remote: Running entrypoint for the VCS package: gunicorn \\(version 20.1.0\\)
182178
remote: -----> Inline app detected
183179
remote: ==> .heroku/python/lib/python.*/site-packages/easy-install.pth <==
184-
remote: /app/local_package
185180
remote: /app/.heroku/src/gunicorn
181+
remote: /tmp/build_.*/local_package
186182
remote:
187183
remote: ==> .heroku/python/lib/python.*/site-packages/gunicorn.egg-link <==
188184
remote: /app/.heroku/src/gunicorn

0 commit comments

Comments
 (0)