-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
php
executable file
·269 lines (238 loc) · 12.3 KB
/
php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
#!/usr/bin/env bash
# fail hard
set -o pipefail
# fail harder
set -eu
source $(dirname $BASH_SOURCE)/_util/include/manifest.sh
OUT_PREFIX=$1
dep_formula=${0#$WORKSPACE_DIR/}
dep_name=$(basename $BASH_SOURCE)
dep_version=${dep_formula#"${dep_name}-"}
dep_package=${dep_name}-${dep_version}
dep_dirname=php-${dep_version}
dep_archive_name=${dep_dirname}.tar.gz
if [[ $dep_version == *alpha* ]] || [[ $dep_version == *beta* ]] || [[ $dep_version == *RC* ]]; then
if [[ $dep_version == 7.1.* ]]; then
dep_url=https://downloads.php.net/~krakjoe/${dep_archive_name}
elif [[ $dep_version == 7.2.* ]]; then
dep_url=https://downloads.php.net/~pollita/${dep_archive_name}
elif [[ $dep_version == 7.3.* ]]; then
dep_url=https://downloads.php.net/~cmb/${dep_archive_name}
elif [[ $dep_version == 7.4.* ]]; then
dep_url=https://downloads.php.net/~derick/${dep_archive_name}
elif [[ $dep_version == 8.0.* ]]; then
dep_url=https://downloads.php.net/~carusogabriel/${dep_archive_name}
elif [[ $dep_version == 8.1.* ]]; then
dep_url=https://downloads.php.net/~ramsey/${dep_archive_name}
fi
else
dep_url=https://www.php.net/distributions/${dep_archive_name}
fi
dep_manifest=${dep_package}.composer.json
echo "-----> Building ${dep_package}..."
curl -L ${dep_url} | tar xz
pushd ${dep_dirname}
# we need libgmp for GMP, libpam0g/libc-client for IMAP, libicu for intl, libsasl2/krb/ldap for LDAP
needed=( libgmp10 libpam0g libc-client2007e libsasl2-2 libkrb5-3 )
if [[ $STACK == "heroku-18" ]]; then
needed+=( libicu60 )
needed+=( libldap-2.4-2 )
needed+=( libonig4 )
needed+=( libsodium23 )
needed+=( libzip4 )
else
needed+=( libonig5 )
needed+=( libsodium23 )
if [[ $STACK == "heroku-20" ]]; then
needed+=( libicu66 )
needed+=( libldap-2.4-2 )
needed+=( libzip5 )
else
needed+=( libicu70 )
needed+=( libldap-2.5-0 )
needed+=( libzip4 ) # went back from ABI v5 to v4 in 1.6 or 1.7
fi
fi
if [[ $dep_version == 7.1.* ]]; then
needed+=( libmcrypt4 )
fi
missing=$(comm -1 -3 <(dpkg-query -W -f '${package}\n' | sort) <(IFS=$'\n'; echo "${needed[*]}" | sort))
if [[ "$missing" ]]; then
echo "Error! Missing libraries: $missing"
exit 1
fi
# we need libgmp-dev for GMP, libpam0g-dev/libc-client-dev for IMAP, libicu-dev for intl, libsasl2/krb5/ldap2-dev for LDAP
needed=( libgmp-dev libpam0g-dev libc-client2007e-dev libicu-dev libsasl2-dev libkrb5-dev libldap2-dev libonig-dev libsodium-dev libsqlite3-dev libzip-dev libwebp-dev )
if [[ $dep_version == 7.1.* ]]; then
needed+=( libmcrypt-dev )
fi
missing=$(comm -1 -3 <(dpkg-query -W -f '${package}\n' | sort) <(IFS=$'\n'; echo "${needed[*]}" | sort))
if [[ "$missing" ]]; then
apt-get update -qq || { echo "Failed to 'apt-get update'. You must build this formula using Docker."; exit 1; }
apt-get install -q -y $missing
fi
# freetype-config is gone on 20.04, but 7.3 doesn't use pkg-config yet
if [[ $STACK == "heroku-20" && $dep_version == 7.3.* ]]; then
patch -p1 < "$(dirname "$BASH_SOURCE")/patches/php-freetype-pkgconfig.patch"
# we changed config.m4 for GD, re-generate configure
./buildconf --force
fi
# all following sed "patches" modify configure directly, so we must do that after the above buildconf re-gen
if dpkg --compare-versions "$dep_version" "eq" 7.1.0 || dpkg --compare-versions "$dep_version" "eq" 7.1.1; then
# look for GMP libs in /usr/lib/x86_64-linux-gnu and not /usr/lib
sed -i 's/$GMP_DIR\/$PHP_LIBDIR/$GMP_DIR\/$PHP_LIBDIR\/x86_64-linux-gnu/' configure
# symlink in gmp.h
ln -s /usr/include/x86_64-linux-gnu/gmp.h /usr/include/gmp.h
fi
if dpkg --compare-versions "$dep_version" "lt" 7.1.15 || dpkg --compare-versions "$dep_version" "eq" 7.2.0 || dpkg --compare-versions "$dep_version" "eq" 7.2.1 || dpkg --compare-versions "$dep_version" "eq" 7.2.2; then
# look for LDAP and SASL libs in /usr/lib/x86_64-linux-gnu and not /usr/lib
sed -i 's/LDAP_LIBDIR=$i\/$PHP_LIBDIR/LDAP_LIBDIR=$i\/$PHP_LIBDIR\/x86_64-linux-gnu/' configure
sed -i 's/LDAP_SASL_LIBDIR=$LDAP_SASL_DIR\/$PHP_LIBDIR/LDAP_SASL_LIBDIR=$LDAP_SASL_DIR\/$PHP_LIBDIR\/x86_64-linux-gnu/' configure
fi
# we want to build FPM with tracing using pread, which uses /proc/$pid/mem
# depending on host/container capabilities, it will likely detect ptrace as present, but that's blocked on the platform
# the only easy way to force it to use pread is by patching configure so it assumes the ptrace check has failed
# see the AC_DEFUN([AC_FPM_TRACE] bits in sapi/fpm/config.m4
sed -i 's/have_ptrace=yes/have_ptrace=no/' configure
patch -p1 < $(dirname $BASH_SOURCE)/patches/php-ignoresigterm.patch
configureopts=()
if [[ $dep_version == 7.[1-3].* ]]; then
configureopts+=("--enable-opcache-file" "--enable-zip")
configureopts+=("--with-sqlite3=shared,/usr" "--with-pdo-sqlite=shared,/usr")
configureopts+=("--with-gd=shared" "--with-freetype-dir=/usr" "--with-jpeg-dir=/usr" "--with-png-dir=/usr" "--with-webp-dir=/usr") # for ext-gd
else
configureopts+=("--with-zip")
configureopts+=("--with-sqlite3=shared" "--with-pdo-sqlite=shared")
configureopts+=("--enable-gd=shared" "--with-freetype" "--with-jpeg" "--with-webp") # for ext-gd
fi
if [[ $dep_version == 7.1.* ]]; then
configureopts+=("--with-mcrypt=shared")
else
configureopts+=("--with-sodium=shared" "--with-password-argon2")
fi
if [[ $dep_version != 8.* ]]; then
configureopts+=("--with-xmlrpc=shared")
fi
if [[ $dep_version == 7.[1-3].* ]]; then
configureopts+=("--with-libzip=/usr" "--with-onig=/usr") # PHP before 7.4 bundles these, so give explicit locations
fi
export PATH=${OUT_PREFIX}/bin:$PATH
# cannot be built shared: date, ereg, opcache (always), pcre, reflection, sockets (?), spl, standard,
# sqlite3 and pdo_sqlite are on by default but we're building them shared on purpose
./configure \
--prefix=${OUT_PREFIX} \
--with-config-file-path=/app/.heroku/php/etc/php \
--with-config-file-scan-dir=/app/.heroku/php/etc/php/conf.d \
--disable-phpdbg \
--enable-fpm \
--with-bz2 \
--with-curl \
--with-pdo-mysql \
--with-mysqli \
--with-openssl \
--with-kerberos \
--with-pgsql \
--with-pdo-pgsql \
--with-readline \
--enable-sockets \
--with-zlib \
--enable-bcmath=shared \
--enable-calendar=shared \
--enable-exif=shared \
--enable-ftp=shared \
--with-gettext=shared \
--with-gmp=shared \
--with-imap=shared \
--with-imap-ssl \
--enable-intl=shared \
--with-ldap=shared \
--with-ldap-sasl \
--enable-mbstring=shared \
--enable-pcntl=shared \
--enable-shmop=shared \
--enable-soap=shared \
--with-xsl=shared \
"${configureopts[@]}"
make -s -j 9
make install -s
find ${OUT_PREFIX} -type f \( -executable -o -name '*.a' \) -exec sh -c "file -i '{}' | grep -Eq 'application/x-(archive|(pie-)?executable|sharedlib); charset=binary'" \; -print | xargs strip --strip-unneeded
popd
rm -rf ${OUT_PREFIX}/php/man ${OUT_PREFIX}/lib/php/extensions/*/*.a
if [[ $dep_version == 7.[1-3].* ]]; then
echo "-----> Preparing PECL..."
${OUT_PREFIX}/bin/pecl channel-update pecl.php.net
fi
echo "-----> Preparing php.ini..."
mkdir -p ${OUT_PREFIX}/etc/php/conf.d
# we begin with PHP's recommended production config as the default
cp ${dep_dirname}/php.ini-production ${OUT_PREFIX}/etc/php/php.ini
# next, include any more specific config files that we have created
IFS='.' read -r -a version <<< "$dep_version" # read the parts of $dep_version into an array, e.g. (7 2 33)
# iterate over version parts so we try "" (from index 0) first, then "7", then "7/2/", then "7/2/11" for a version "7.2.11"
# in each case, we copy anything that's in that directory; more specific version configs will this overwrite less specific ones
echo "-----> Copying version-specific php.ini files..."
for (( i = 0; i < ${#version[@]}; i++)); do
version_dir=$(IFS=/; echo "${version[*]:0:$i}") # set IFS to "/" for merging, but echo is a builtin, so it must be a subshell and a separate command
cp -v $(dirname $BASH_SOURCE)/_conf/php/${version_dir}/php.ini ${OUT_PREFIX}/etc/php/ 2> /dev/null || true
cp -v $(dirname $BASH_SOURCE)/_conf/php/${version_dir}/conf.d/*.ini ${OUT_PREFIX}/etc/php/conf.d/ 2> /dev/null || true
done
echo "-----> Generating export and profile scripts..."
# this gets sourced after package install, so that the buildpack and following buildpacks can invoke
cat > ${OUT_PREFIX}/bin/export.php.sh <<'EOF'
export PATH="/app/.heroku/php/bin:/app/.heroku/php/sbin:$PATH"
EOF
# this gets sourced on dyno boot
cat > ${OUT_PREFIX}/bin/profile.php.sh <<'EOF'
export PATH="$HOME/.heroku/php/bin:$HOME/.heroku/php/sbin:$PATH"
# read memory limit of dyno
mlib="/sys/fs/cgroup/memory/memory.limit_in_bytes"
# get php.ini location; don't forget to suppress INI scan dir to prevent e.g. New Relic from starting
php_ini_path=$(PHP_INI_SCAN_DIR= php -r 'echo get_cfg_var("cfg_file_path");')
if [[ -f "$mlib" && -n "$php_ini_path" ]]; then
php_cli_ini_path=$(dirname "$php_ini_path")/php-cli.ini
# create php-cli.ini from php.ini unless it exists
# we can't cp -n instead because php_ini_path would already be php-cli.ini and that would error
if [[ "$php_ini_path" != "$php_cli_ini_path" ]]; then
cp "$php_ini_path" "$php_cli_ini_path"
fi
# compute memory limit, up to 16 GB
max_memory_limit=$(( 16 * 1024 * 1024 * 1024 ))
sys_memory_limit=$(cat "$mlib")
memory_limit=$(( sys_memory_limit > max_memory_limit ? max_memory_limit : sys_memory_limit ))
# append to php-cli.ini
echo $'\n;inserted by ~/.profile.d/ script' >> "$php_cli_ini_path"
echo "memory_limit=$memory_limit" >> "$php_cli_ini_path"
fi
EOF
# we need composer to extract all extensions with versions
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php -r "if (hash_file('sha384', 'composer-setup.php') === '55ce33d7678c5a611085589f1f3ddf8b3c52d662cd01d4ba75c0ee0459970c2200a51f492d557530c71c15d8dba01eae') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
php composer-setup.php --2.2 # https://github.com/composer/composer/issues/11046
# first, read all platform packages (just "ext-" and "php-") that are already there; could be statically built, or enabled through one of the INIs loaded
platform_default=$(php composer.phar show --platform | grep -E '^(ext-\S+|php-\S+)' | sed s@^@heroku-sys/@ | tr -s " " | cut -d " " -f1,2 | sort)
# next enable all bundled shared extensions temporarily so we can fetch their info too
echo "-----> Preparing built-in extensions..."
for f in ${OUT_PREFIX}/lib/php/extensions/*/*.so; do
if [[ $(basename $f) == "opcache.so" ]]; then
# opcache needs to be loaded using zend_extension
echo -n "zend_" >> ${OUT_PREFIX}/etc/php/conf.d/999-sharedexts.ini
fi
# if an extension is already loaded (due to a config loaded further above), there will be a warning on startup, but that doesn't matter for our purposes
echo "extension=$(basename $f)" >> ${OUT_PREFIX}/etc/php/conf.d/999-sharedexts.ini
done
# now we'll get all platform packages (just "ext-" and "php-")
platform_all=$(php composer.phar show --platform | grep -E '^(ext-\S+|php-\S+)' | sed s@^@heroku-sys/@ | tr -s " " | cut -d " " -f1,2 | sort)
# remove temporary ini file that enables all extensions, and the composer download
rm ${OUT_PREFIX}/etc/php/conf.d/999-sharedexts.ini composer.phar
# extract only additions (that's the extensions people have to explicitly enable) from the diff
exts_shared=$(diff --new-line-format="%L" --old-line-format="" --unchanged-line-format="" <(cat <<<"$platform_default") <(cat <<<"$platform_all")) || diff_result=$?
(( $diff_result == 1 )) || { echo "Failed to diff list of shared extensions"; exit 1; }
echo "-----> Preparing manifest contents..."
MANIFEST_REQUIRE="${MANIFEST_REQUIRE:-"{}"}"
MANIFEST_CONFLICT="${MANIFEST_CONFLICT:-"{}"}"
MANIFEST_REPLACE=$(echo "$platform_default" | sed "s/ $dep_version\$/ self.version/" | python -c 'import sys, json; json.dump({item[0]+suffix:item[1] for item in [line.split() for line in sys.stdin] for suffix in ("", ".native")}, sys.stdout)')
MANIFEST_PROVIDE="${MANIFEST_PROVIDE:-"{}"}"
MANIFEST_EXTRA=$(echo "$exts_shared" | python -c 'import sys, json; json.dump({"shared": dict(item.split() for item in sys.stdin), "export": "bin/export.php.sh", "profile": "bin/profile.php.sh"}, sys.stdout)')
echo "-----> Generating manifest..."
python $(dirname $BASH_SOURCE)/_util/include/manifest.py "heroku-sys-php" "heroku-sys/${dep_name}" "$dep_version" "${dep_formula}.tar.gz" "$MANIFEST_REQUIRE" "$MANIFEST_CONFLICT" "$MANIFEST_REPLACE" "$MANIFEST_PROVIDE" "$MANIFEST_EXTRA" > $dep_manifest
print_or_export_manifest_cmd "$(generate_manifest_cmd "$dep_manifest")"