15
15
# Fail fast and fail hard.
16
16
set -eo pipefail
17
17
18
- # Standard Library.
18
+ # Boostrap the Buildpack Standard Library.
19
19
export BPLOG_PREFIX=" buildpack.python"
20
20
export BUILDPACK_LOG_FILE=${BUILDPACK_LOG_FILE:-/ dev/ null}
21
21
22
22
[ " $BUILDPACK_XTRACE " ] && set -o xtrace
23
23
24
- # Prepend proper path for virtualenv hackery. This will be deprecated soon.
24
+ # Prepend proper path for old-school virtualenv hackery.
25
+ # This may not be neccessary.
25
26
export PATH=:/usr/local/bin:$PATH
26
27
27
- # Paths .
28
+ # Setup Path variables, for later use in the Buildpack .
28
29
BIN_DIR=$( cd " $( dirname " $0 " ) " ; pwd) # absolute path
29
30
ROOT_DIR=$( dirname " $BIN_DIR " )
30
31
BUILD_DIR=$1
31
32
CACHE_DIR=$2
32
33
ENV_DIR=$3
33
34
35
+ # Export Path variables, for use in sub-scripts.
34
36
export BUILD_DIR CACHE_DIR ENV_DIR
35
37
38
+ # Set the Buildpack's internet target for downloading Python distributions.
39
+ # The user can provide BUILDPACK_VENDOR_URL to specify a custom target.
40
+ # Note: this is designed for non-Heroku use, as it does not use the user-provided
41
+ # environment variable mechanism (the ENV_DIR).
36
42
VENDOR_URL=" https://lang-python.s3.amazonaws.com/$STACK "
37
43
if [[ -n ${BUILDPACK_VENDOR_URL:- } ]]; then
38
44
VENDOR_URL=" $BUILDPACK_VENDOR_URL "
39
45
fi
40
46
export VENDOR_URL
41
47
42
- # Python defaults
43
- DEFAULT_PYTHON_VERSION=" python-3.6.5"
44
- LATEST_3=" python-3.6.5"
48
+ # Which versions of Python are we using?
49
+ # These variables are used to specify which versions of Python to install by default,
50
+ # as well as prompt the user to upgrade if they are using an un–supported version.
51
+ # Note: When 3.7 lands, I recommend switing to LATEST_36 and LATEST_37.
52
+ DEFAULT_PYTHON_VERSION=" python-3.6.4"
53
+ LATEST_3=" python-3.6.4"
45
54
LATEST_2=" python-2.7.15"
46
55
56
+ # Which stack is used (for binary downloading), if none is provided (e.g. outside of Heroku)?
47
57
DEFAULT_PYTHON_STACK=" cedar-14"
58
+ # If pip doesn't match this version (the version we install), run the installer.
48
59
PIP_UPDATE=" 9.0.2"
49
60
50
61
export DEFAULT_PYTHON_VERSION DEFAULT_PYTHON_STACK PIP_UPDATE LATEST_2 LATEST_3
51
62
52
- # Common Problem Warnings
63
+ # Common Problem Warnings:
64
+ # This section creates a temporary file in which to stick the output of `pip install`.
65
+ # The `warnings` subscript then greps through this for common problems and guides
66
+ # the user towards resolution of known issues.
53
67
WARNINGS_LOG=$( mktemp)
54
68
export WARNINGS_LOG
55
69
export RECOMMENDED_PYTHON_VERSION=$DEFAULT_PYTHON_VERSION
56
70
57
- # Setup vendored tools and pip-pop (pip-diff)
71
+ # The buildpack ships with a few executable tools (e.g. pip-grep, etc).
72
+ # This installs them into the path, so we can execute them directly.
58
73
export PATH=$PATH :$ROOT_DIR /vendor/:$ROOT_DIR /vendor/pip-pop
59
74
60
- # Support Anvil Build_IDs
75
+ # Set environment variables if they weren't set by the platform.
76
+ # Note: this is legacy, for a deprecated build system known as Anvil.
77
+ # This can likely be removed, with caution.
61
78
[ ! " $SLUG_ID " ] && SLUG_ID=" defaultslug"
62
79
[ ! " $REQUEST_ID " ] && REQUEST_ID=$SLUG_ID
63
80
[ ! " $STACK " ] && STACK=$DEFAULT_PYTHON_STACK
64
81
65
- # Sanitizing environment variables.
82
+ # Sanitize externally-provided environment variables:
83
+ # The following environment variables are either problematic or simply unneccessary
84
+ # for the buildpack to have knowledge of, so we unset them, to keep the environment
85
+ # as clean and pristine as possible.
66
86
unset GIT_DIR PYTHONHOME PYTHONPATH
67
87
unset RECEIVE_DATA RUN_KEY BUILD_INFO DEPLOY LOG_TOKEN
68
88
unset CYTOKINE_LOG_FILE GEM_PATH
69
89
70
- # Syntax sugar .
90
+ # Import the utils script, which contains helper functions used throughout the buildpack .
71
91
# shellcheck source=bin/utils
72
92
source " $BIN_DIR /utils"
73
93
74
- # Import collection of warnings.
94
+ # Import the warnings script, which contains the `pip install` user warning mechanisms
95
+ # (mentioned and explained above)
75
96
# shellcheck source=bin/warnings
76
97
source " $BIN_DIR /warnings"
77
98
78
- # we need to put a bunch of symlinks in there later
99
+ # Make the directory in which we will create symlinks from the temporary build directory
100
+ # to `/app`.
101
+ # Symlinks are required, since Python is not a portable installation.
102
+ # More on this topic later.
79
103
mkdir -p /app/.heroku
80
104
81
- # Set up outputs under new context
105
+ # This buildpack programatically generates (or simply copies) a number of files for
106
+ # buildpack machinery: an export script, and a number of `.profile.d` scripts. This
107
+ # section declares the locations of those files and targets.
82
108
PROFILE_PATH=" $BUILD_DIR /.profile.d/python.sh"
83
109
EXPORT_PATH=" $BIN_DIR /../export"
84
110
GUNICORN_PROFILE_PATH=" $BUILD_DIR /.profile.d/python.gunicorn.sh"
@@ -87,43 +113,75 @@ WEB_CONCURRENCY_PROFILE_PATH="$BUILD_DIR/.profile.d/WEB_CONCURRENCY.sh"
87
113
# We'll need to send these statics to other scripts we `source`.
88
114
export BUILD_DIR CACHE_DIR BIN_DIR PROFILE_PATH EXPORT_PATH
89
115
90
- # Prepend proper environment variables for Python use.
116
+ # Python Environment Variables
117
+ # Set Python-specific environment variables, for running Python within the buildpack.
118
+ # Notes on each variable included.
119
+
120
+ # PATH is relatively obvious, we need to be able to execute 'python'.
91
121
export PATH=/app/.heroku/python/bin:/app/.heroku/vendor/bin:$PATH
122
+ # Tell Python to not buffer it's stdin/stdout.
92
123
export PYTHONUNBUFFERED=1
124
+ # Set the locale to a well-known and expected standard.
93
125
export LANG=en_US.UTF-8
126
+ # `~/.heroku/vendor` is an place where the buildpack may stick pre-build binaries for known
127
+ # C dependencies (e.g. libmemcached on cedar-14). This section configures Python (GCC, more specifically)
128
+ # and pip to automatically include these paths when building binaries.
94
129
export C_INCLUDE_PATH=/app/.heroku/vendor/include:/app/.heroku/python/include:$C_INCLUDE_PATH
95
130
export CPLUS_INCLUDE_PATH=/app/.heroku/vendor/include:/app/.heroku/python/include:$CPLUS_INCLUDE_PATH
96
131
export LIBRARY_PATH=/app/.heroku/vendor/lib:/app/.heroku/python/lib:$LIBRARY_PATH
97
132
export LD_LIBRARY_PATH=/app/.heroku/vendor/lib:/app/.heroku/python/lib:$LD_LIBRARY_PATH
98
133
export PKG_CONFIG_PATH=/app/.heroku/vendor/lib/pkg-config:/app/.heroku/python/lib/pkg-config:$PKG_CONFIG_PATH
99
134
135
+ # The Application Code
136
+ # --------------------
137
+
100
138
# Switch to the repo's context.
101
139
cd " $BUILD_DIR "
102
140
103
- # Prepare the cache.
141
+ # The Cache
142
+ # ---------
143
+
144
+ # The workflow for the Python Buildpack's cache is as follows:
145
+ #
146
+ # - `~/.heroku/{known-paths}` are copied from the cache into the slug.
147
+ # - The build is executed, modifying `~/.heroku/{known-paths}`.
148
+ # - Once the build is complete, `~/.heroku/{known-paths}` is copied back into the cache.
149
+
150
+ # Create the cache directory, if it doesn't exist.
104
151
mkdir -p " $CACHE_DIR "
105
152
106
153
# Restore old artifacts from the cache.
107
154
mkdir -p .heroku
108
155
156
+ # The Python installation.
109
157
cp -R " $CACHE_DIR /.heroku/python" .heroku/ & > /dev/null || true
158
+ # A plain text file which contains the current stack being used (used for cache busting).
110
159
cp -R " $CACHE_DIR /.heroku/python-stack" .heroku/ & > /dev/null || true
160
+ # A plain text file which contains the current python version being used (used for cache busting).
111
161
cp -R " $CACHE_DIR /.heroku/python-version" .heroku/ & > /dev/null || true
162
+ # Any pre-compiled binaries, provided by the buildpack.
112
163
cp -R " $CACHE_DIR /.heroku/vendor" .heroku/ & > /dev/null || true
164
+ # "editable" installations of code repositories, via pip or pipenv.
113
165
if [[ -d " $CACHE_DIR /.heroku/src" ]]; then
114
166
cp -R " $CACHE_DIR /.heroku/src" .heroku/ & > /dev/null || true
115
167
fi
116
168
117
- # Experimental pre_compile hook.
169
+ # The pre_compile hook. Customers rely on this. Don't remove it.
170
+ # This part of the code is used to allow users to customize their build experience
171
+ # without forking the buildpack by providing a `bin/pre_compile` script, which gets
172
+ # run inline with the buildpack automatically.
173
+
118
174
# shellcheck source=bin/steps/hooks/pre_compile
119
175
source " $BIN_DIR /steps/hooks/pre_compile"
120
176
121
- # Sticky runtimes.
177
+ # Sticky runtimes. If there was a previous build, and it used a given version of Python,
178
+ # continue to use that version of Python in perpituity (warnings will be raised if
179
+ # they are out–of–date).
122
180
if [ -f " $CACHE_DIR /.heroku/python-version" ]; then
123
181
DEFAULT_PYTHON_VERSION=$( cat " $CACHE_DIR /.heroku/python-version" )
124
182
fi
125
183
126
- # Stack fallback for non-declared caches .
184
+ # We didn't always record the stack version. This code is in place because of that .
127
185
if [ -f " $CACHE_DIR /.heroku/python-stack" ]; then
128
186
CACHED_PYTHON_STACK=$( cat " $CACHE_DIR /.heroku/python-stack" )
129
187
else
133
191
export CACHED_PYTHON_STACK
134
192
135
193
# Pipenv Python version support.
194
+ # Detect the version of Python requested from a Pipfile (e.g. python_version or python_full_version).
195
+ # Convert it to a runtime.txt file.
196
+
136
197
# shellcheck source=bin/steps/pipenv-python-version
137
198
source " $BIN_DIR /steps/pipenv-python-version"
138
199
139
- # If no runtime given , assume default version.
200
+ # If no runtime was provided by the user , assume the default Python runtime version.
140
201
if [ ! -f runtime.txt ]; then
141
202
echo " $DEFAULT_PYTHON_VERSION " > runtime.txt
142
203
fi
143
204
205
+ # Create the directory for .profile.d, if it doesn't exist.
144
206
mkdir -p " $( dirname " $PROFILE_PATH " ) "
207
+ # Create the directory for editable source code installation, if it doesn't exist.
145
208
mkdir -p /app/.heroku/src
146
209
210
+ # On Heroku CI, builds happen in `/app`. Otherwise, on the Heroku platform,
211
+ # they occur in a temp directory. Beacuse Python is not portable, we must create
212
+ # symlinks to emulate that we are operating in `/app` during the build process.
213
+ # This is (hopefully obviously) because apps end up running from `/app` in production.
147
214
if [[ $BUILD_DIR != ' /app' ]]; then
148
215
# python expects to reside in /app, so set up symlinks
149
216
# we will not remove these later so subsequent buildpacks can still invoke it
@@ -152,60 +219,79 @@ if [[ $BUILD_DIR != '/app' ]]; then
152
219
# Note: .heroku/src is copied in later.
153
220
fi
154
221
155
- # Install Python.
222
+ # Download / Install Python, from pre-build binaries available on Amazon S3.
223
+ # This step also bootstraps pip / setuptools.
156
224
let start=$( nowms)
157
225
# shellcheck source=bin/steps/python
158
226
source " $BIN_DIR /steps/python"
159
227
mtime " python.install.time" " ${start} "
160
228
161
- # Pipenv support .
229
+ # Install Pipenv dependencies, if a Pipfile was provided .
162
230
# shellcheck source=bin/steps/pipenv
163
231
source " $BIN_DIR /steps/pipenv"
164
232
165
233
# Uninstall removed dependencies with Pip.
234
+ # The buildpack will automatically remove any declared dependencies (in requirements.txt)
235
+ # that were explicitly removed. This machinery is a bit complex, but it is not complicated.
166
236
let start=$( nowms)
167
237
# shellcheck source=bin/steps/pip-uninstall
168
238
source " $BIN_DIR /steps/pip-uninstall"
169
239
mtime " pip.uninstall.time" " ${start} "
170
240
171
-
172
241
# If no requirements.txt file given, assume `setup.py develop` is intended.
242
+ # This allows for people to ship a setup.py application to Heroku
243
+ # (which is rare, but I vouch that it should work!)
244
+
173
245
if [ ! -f requirements.txt ] && [ ! -f Pipfile ]; then
174
246
echo " -e ." > requirements.txt
175
247
fi
176
248
177
249
# Fix egg-links.
250
+ # Because we're installing things into a different path than we're running them (temp dir vs app dir),
251
+ # We must re-write all of Python's eggpath links to target the proper directory.
178
252
# shellcheck source=bin/steps/eggpath-fix
179
253
source " $BIN_DIR /steps/eggpath-fix"
180
254
181
255
# Mercurial support.
256
+ # If a customer appears to be using mercurial for dependency resolution, we install it first.
257
+ # Note: this only applies to pip, not pipenv. This can likely be removed, over time. Measure it first.
182
258
# shellcheck source=bin/steps/mercurial
183
259
source " $BIN_DIR /steps/mercurial"
184
260
185
261
# Pylibmc support.
262
+ # On cedar-14, libmemcached was not available. The buildpack provides its own version, instead.
186
263
# shellcheck source=bin/steps/pylibmc
187
264
source " $BIN_DIR /steps/pylibmc"
188
265
189
- # Support for Geo libraries.
266
+ # Support for Geo libraries. This is deprecated functionality, only functional on cedar-14.
267
+ # It is undocumented.
190
268
# shellcheck source=bin/steps/geo-libs
191
269
sub_env " $BIN_DIR /steps/geo-libs"
192
270
193
271
# GDAL support.
272
+ # This is part of the Geo support.
194
273
# shellcheck source=bin/steps/gdal
195
274
source " $BIN_DIR /steps/gdal"
196
275
197
- # Install dependencies with Pip (where the magic happens).
276
+ # pip install
277
+ # -----------
278
+
279
+ # Install dependencies with pip (where the magic happens).
198
280
let start=$( nowms)
199
281
# shellcheck source=bin/steps/pip-install
200
282
source " $BIN_DIR /steps/pip-install"
201
283
mtime " pip.install.time" " ${start} "
202
284
203
285
# Support for NLTK corpora.
286
+ # Note: this may only work on Python 2.7. I don't think many customers use this functionality,
287
+ # and it should probably be undocumented.
288
+ # (there's an import error on 3.6 that should hopefully be fixed upstream at some point)
204
289
let start=$( nowms)
205
290
sub_env " $BIN_DIR /steps/nltk"
206
291
mtime " nltk.download.time" " ${start} "
207
292
208
- # Support for pip install -e.
293
+ # Support for editable installations. Here, we are copying pip–created src directory,
294
+ # and copying it into the proper place (the logical place to do this was early, but it must be done here).
209
295
# In CI, $BUILD_DIR is /app.
210
296
if [[ ! " $BUILD_DIR " == " /app" ]]; then
211
297
rm -fr " $BUILD_DIR /.heroku/src"
214
300
215
301
216
302
# Django collectstatic support.
303
+ # The buildpack automatically runs collectstatic for Django applications.
304
+ # This is the cause for the majority of build failures on the Python platform.
305
+ # These failures are intentional — if collectstatic (which can be tricky, at times) fails,
306
+ # your build fails.
217
307
let start=$( nowms)
218
308
sub_env " $BIN_DIR /steps/collectstatic"
219
309
mtime " collectstatic.time" " ${start} "
220
310
221
- # Create .profile script for application runtime environment variables.
311
+
312
+ # Progamatically create .profile.d script for application runtime environment variables.
313
+
314
+ # Set the PATH to include Python / pip / pipenv / etc.
222
315
set_env PATH " \$ HOME/.heroku/python/bin:\$ PATH"
316
+ # Tell Python to run in unbuffered mode.
223
317
set_env PYTHONUNBUFFERED true
318
+ # Tell Python where it lives.
224
319
set_env PYTHONHOME " \$ HOME/.heroku/python"
225
-
320
+ # Set variables for C libraries.
226
321
set_env LIBRARY_PATH " \$ HOME/.heroku/vendor/lib:\$ HOME/.heroku/python/lib:\$ LIBRARY_PATH"
227
322
set_env LD_LIBRARY_PATH " \$ HOME/.heroku/vendor/lib:\$ HOME/.heroku/python/lib:\$ LD_LIBRARY_PATH"
228
-
323
+ # Locale.
229
324
set_default_env LANG en_US.UTF-8
325
+ # The Python hash seed is set to random.
230
326
set_default_env PYTHONHASHSEED random
327
+ # Tell Python to look for Python modules in the /app dir. Don't change this.
231
328
set_default_env PYTHONPATH " \$ HOME"
232
329
233
- # python expects to be in /app, if at runtime, it is not, set
234
- # up symlinks... this can occur when the subdir buildpack is used
330
+ # Python expects to be in /app, if at runtime, it is not, set
331
+ # up symlinks… this can occur when the subdir buildpack is used.
235
332
cat << EOT >> "$PROFILE_PATH "
236
333
if [[ \$ HOME != "/app" ]]; then
237
334
mkdir -p /app/.heroku
@@ -244,17 +341,15 @@ EOT
244
341
cp " $ROOT_DIR /vendor/WEB_CONCURRENCY.sh" " $WEB_CONCURRENCY_PROFILE_PATH "
245
342
cp " $ROOT_DIR /vendor/python.gunicorn.sh" " $GUNICORN_PROFILE_PATH "
246
343
247
-
248
- # Experimental post_compile hook.
344
+ # Experimental post_compile hook. Don't remove this.
249
345
# shellcheck source=bin/steps/hooks/post_compile
250
346
source " $BIN_DIR /steps/hooks/post_compile"
251
347
252
348
# Fix egg-links, again.
253
349
# shellcheck source=bin/steps/eggpath-fix2
254
350
source " $BIN_DIR /steps/eggpath-fix2"
255
351
256
- # Store new artifacts in cache.
257
-
352
+ # Store new artifacts in the cache.
258
353
rm -rf " $CACHE_DIR /.heroku/python"
259
354
rm -rf " $CACHE_DIR /.heroku/python-version"
260
355
rm -rf " $CACHE_DIR /.heroku/python-stack"
0 commit comments