-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
compile
executable file
·804 lines (668 loc) · 34.2 KB
/
compile
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
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
#!/usr/bin/env bash
# bin/compile <build-dir> <cache-dir> <env-dir>
# fail hard
set -o pipefail
# fail harder
set -eu
# move hidden files too, just in case
shopt -s dotglob
STACK=${STACK:-heroku-22} # Anvil has none
build_dir=$1
cache_dir=$2/php
mkdir -p "$cache_dir"
env_dir=${3:-} # Anvil has none
bp_dir=$(cd $(dirname $0); cd ..; pwd)
export BPLOG_PREFIX="buildpack.php"
export BUILDPACK_LOG_FILE=${BUILDPACK_LOG_FILE:-/dev/null}
# convenience functions
source $bp_dir/bin/util/common.sh
# use err_trap from common.sh on error
# we do not 'set -o errtrace', because that would cause subshell failures to fire the trap twice, e.g. with someval=$(func_that_fails)
trap 'err_trap' ERR
# failure counting
source $bp_dir/bin/util/failures.sh
# for extensions that need special treatment
source $bp_dir/bin/util/newrelic.sh
source $bp_dir/bin/util/blackfire.sh
# if this is set it prevents Git clones (e.g. for Composer installs from source) during the build in some circumstances, and it is set in SSH Git deploys to Heroku
unset GIT_DIR
cd $build_dir
export_env_dir "$env_dir" '^COMPOSER$'
if [[ -n ${COMPOSER:-} ]]; then
status "Using '$COMPOSER' (from "'$COMPOSER env var) for installation.'
else
export COMPOSER="composer.json"
fi
export COMPOSER_LOCK=$(basename "$COMPOSER" ".json")".lock" # replace .json with .lock if it exists, append .lock otherwise
# we're using this error message in two places
composer_lock_parse_error=$(
cat <<-EOF
Failed to parse '$COMPOSER_LOCK'!
There was an error parsing '$COMPOSER_LOCK'; it must be a valid
file generated by Composer and be in an up-to-date state.
Look above for any parse errors and address them if necessary.
You most likely created or edited the file by hand, or a merge
conflict was not resolved properly, resulting in a syntax error
in the file. Refer to the docs for information on re-generating
the lock file: https://getcomposer.org/doc/01-basic-usage.md
Please perform the following steps locally on your computer to
resolve this issue before attempting another deploy:
1) Run 'composer update' to re-generate the lock file
2) stage the lock file changes using 'git add $COMPOSER_LOCK'
3) commit the change using 'git commit'
You can run 'composer validate' locally on your computer for
further diagnosis. Remember to also always keep your lock file
up to date with any changes according to the instructions at
https://getcomposer.org/doc/01-basic-usage.md
Please remember to always keep your '$COMPOSER_LOCK' updated in
lockstep with '$COMPOSER' to avoid common problems related
to dependencies during collaboration and deployment.
EOF
)
# a bunch of sanity checks first
if [[ -s "$COMPOSER" ]]; then
cat "$COMPOSER" | python3 -mjson.tool &> /dev/null || {
mcount "failures.composer_json.lint"
error <<-EOF
Basic validation for '$COMPOSER' failed!
It must be a valid JSON document compatible with Composer.
You most likely created or edited the file by hand, and it now
contains a syntax error. Please refer to the documentation at
https://getcomposer.org/doc/ for information on the format.
You can run 'composer validate' locally on your computer for
further diagnosis. Remember to also always keep your lock file
up to date with any changes according to the instructions at
https://getcomposer.org/doc/01-basic-usage.md
EOF
}
if [[ ! -f "$COMPOSER_LOCK" ]]; then
cat "$COMPOSER" | python3 -c 'import sys, json; sys.exit(bool(json.load(sys.stdin).get("require", {})))' 2> /dev/null || {
mcount "failures.composer_lock.missing"
error <<-EOF
No '$COMPOSER_LOCK' found!
A '$COMPOSER_LOCK' file was not found in your project, but there
is a '$COMPOSER' file with dependencies inside 'require'.
The lock file is required in order to guarantee reliable and
reproducible installation of dependencies across platforms and
deploys. You must follow the Composer best practice of having
your lock file under version control in order to deploy. The
lock file must not be in your '.gitignore'.
Please perform the following steps locally on your computer to
resolve this issue before attempting another deploy:
1) remove '$COMPOSER_LOCK' from file '.gitignore', if present
2) if no '$COMPOSER_LOCK' exists, run 'composer update'
3) stage the lock file changes using 'git add $COMPOSER_LOCK'
4) if you edited '.gitignore', also run 'git add .gitignore'
5) commit the change using 'git commit'
Please remember to always keep your '$COMPOSER_LOCK' updated in
lockstep with '$COMPOSER' to avoid common problems related
to dependencies during collaboration and deployment.
Please refer to the Composer documentation for further details:
https://getcomposer.org/doc/
https://getcomposer.org/doc/01-basic-usage.md
EOF
}
else
cat "$COMPOSER_LOCK" | python3 -mjson.tool &> /dev/null || {
mcount "failures.composer_lock.lint"
error "$composer_lock_parse_error"
}
fi
else
if [[ ! -f "$COMPOSER" ]]; then
mcount "warnings.composer_json.missing"
warning <<-EOF
No '$COMPOSER' found!
Your project only contains an 'index.php', no '$COMPOSER'.
Using 'index.php' to declare app type as PHP is deprecated and
may lead to unexpected behavior.
Please consider updating your codebase to utilize Composer and
modern dependency management in order to benefit from the latest
PHP runtimes and improved application performance, as well as
control over the PHP versions and extensions available.
For an introduction to dependency management with Composer and
how to get the most out of PHP on Heroku, refer to the docs at
https://getcomposer.org/doc/00-intro.md and
https://devcenter.heroku.com/articles/getting-started-with-php
EOF
else
mcount "warnings.composer_json.empty"
notice <<-EOF
Your '$COMPOSER' is completely empty!
A completely empty file is not a valid JSON document.
Heroku automatically corrected this problem, but it is strongly
recommended you change the contents to at least '{}'.
For documentation on Composer and dependency management, check
out the introduction at https://getcomposer.org/doc/00-intro.md
EOF
fi
echo "{}" > $COMPOSER
fi
# PHP expects to be installed in /app/.heroku/php because of compiled paths, let's set that up!
mkdir -p /app/.heroku
# other buildpacks use this magic directory, too
mkdir -p $build_dir/.heroku
# all our system packages live in there
mkdir $build_dir/.heroku/php || {
# looks like platform packages are already there
# this can happen if the buildpack is set twice, or if a user is trying to build an already built app
# (e.g. by downloading a slug from Heroku and checking it into Git)
mcount "failures.platform.slug_as_source"
error <<-EOF
Your app source code contains artifacts from a previous build!
A '.heroku/php/' directory exists in your app source, but this
directory is created during a build of your app and should not
be in your source repository, as it contains binaries for PHP,
web servers, PHP extensions, and so forth.
This situation can happen if you:
1) have accidentally set this buildpack on your app twice;
2) downloaded a built app ("slug") and used it as source code.
To address situation 1, check the 'heroku buildpacks' command.
To address situation 2, ensure the following directories are
not part of your source code in your version control system:
- .heroku/
- .profile.d/
- vendor/
It is recommended you create a new Git repository that doesn't
have these directories in its commit history. If you only
perform a 'git rm', the binaries will remain part of your code
repository, and inflate its size by hundreds of megabytes. You
may also use common techniques for rewriting commit history to
retroactively remove the directories from the old commits that
introduced them.
If you were using other buildpacks together with PHP, you may
want to ensure that those buildpacks' build artifacts, such as
a Node.js 'node_modules/' directory, are also not part of your
source code repository.
EOF
}
# set up Composer
export COMPOSER_HOME=$cache_dir/.composer
mkdir -p $COMPOSER_HOME
# if the build dir is not "/app", we symlink in the .heroku/php subdir (and only that, to avoid problems with other buildpacks) so that PHP correctly finds its INI files etc
[[ $build_dir == '/app' ]] || ln -s $build_dir/.heroku/php /app/.heroku/php
status "Bootstrapping..."
if [[ $STACK == heroku-2[02] ]]; then
stack_locator=$STACK
else
stack_locator="${STACK}-$(dpkg --print-architecture)"
fi
s3_url="https://lang-php.s3.us-east-1.amazonaws.com/dist-${stack_locator}-stable/"
# prepend the default repo to the list configured by the user
# list of repositories to use is in ascening order of precedence
export_env_dir "$env_dir" '^HEROKU_PHP_PLATFORM_REPOSITORIES$'
have_custom_platform_repos="${HEROKU_PHP_PLATFORM_REPOSITORIES:+1}"
HEROKU_PHP_PLATFORM_REPOSITORIES="${s3_url} ${HEROKU_PHP_PLATFORM_REPOSITORIES:-}"
if [[ "${HEROKU_PHP_PLATFORM_REPOSITORIES}" == *" - "* ]]; then
# a single "-" in the user supplied string removes everything to the left of it; can be used to delete the default repo
notice_inline "Default platform repository disabled."
HEROKU_PHP_PLATFORM_REPOSITORIES=${HEROKU_PHP_PLATFORM_REPOSITORIES#*" - "}
s3_url=$(echo "$HEROKU_PHP_PLATFORM_REPOSITORIES" | cut -f1 -d" " | sed 's/[^/]*$//')
notice_inline 'Bootstrapping using first repository in $HEROKU_PHP_PLATFORM_REPOSITORIES.'
fi
# minimal PHP needed for installs, and make "composer" invocations use that for now
mkdir -p $build_dir/.heroku/php-min
ln -s $build_dir/.heroku/php-min /app/.heroku/php-min
curl_retry_on_18 --retry-connrefused --retry 3 --connect-timeout 10 --fail --silent --location -o $build_dir/.heroku/php-min.tar.gz "${s3_url}php-min-8.3.15.tar.gz" || {
mcount "failures.bootstrap.download.php-min"
error <<-EOF
Failed to download minimal PHP for bootstrapping!
This is most likely a temporary internal error. If the problem
persists, make sure that you are not running a custom or forked
version of the Heroku PHP buildpack which may need updating.
EOF
}
tar xzf $build_dir/.heroku/php-min.tar.gz -C $build_dir/.heroku/php-min
rm $build_dir/.heroku/php-min.tar.gz
curl_retry_on_18 --retry-connrefused --retry 3 --connect-timeout 10 --fail --silent --location -o $build_dir/.heroku/composer.tar.gz "${s3_url}composer-2.8.4.tar.gz" || {
mcount "failures.bootstrap.download.composer"
error <<-EOF
Failed to download Composer for bootstrapping!
This is most likely a temporary internal error. If this problem
persists, make sure that you are not running a custom or forked
version of the Heroku PHP buildpack which may need updating.
EOF
}
tar xzf $build_dir/.heroku/composer.tar.gz -C $build_dir/.heroku/php bin/composer
rm $build_dir/.heroku/composer.tar.gz
mv $build_dir/.heroku/php/bin/composer{,2} # we will need this at the end in case user installs happen with Composer v1
# this alias is just for now while we install platform packages
composer() {
/app/.heroku/php-min/bin/php /app/.heroku/php/bin/composer2 "$@"
}
export -f composer
# we use --no-plugins just in case the vendor dir is there, see e.g. https://github.com/Ocramius/PackageVersions/issues/64
composer_vendordir=$(composer config --no-plugins vendor-dir)
composer_bindir=$(composer config --no-plugins bin-dir)
mkdir -p $build_dir/.profile.d
# we perform this check early so people with stale lock files are reminded why if their lock file errors in the next step
composer_lock_outdated=false
composer validate --no-plugins --no-check-publish --no-check-all --quiet "$COMPOSER" 2>/dev/null || {
mcount "warnings.composer_lock.outdated"
composer_lock_outdated=true
warning <<-EOF
Your '$COMPOSER_LOCK' is out of date!
The '$COMPOSER_LOCK' file in your project is not up to date with
the main '$COMPOSER' file. This may result in installation
of incorrect packages or package versions.
The lock file is required in order to guarantee reliable and
reproducible installation of dependencies across systems and
deploys. It must always be kept in sync with '$COMPOSER'.
Whenever you change '$COMPOSER', ensure that you perform
the following steps locally on your computer:
1) run 'composer update'
2) add all changes using 'git add $COMPOSER $COMPOSER_LOCK'
3) commit using 'git commit'
Ensure that you updated the lock file correctly, and that you
ran 'git add' on both files, before deploying again.
Please remember to always keep your '$COMPOSER_LOCK' updated in
lockstep with '$COMPOSER' to avoid common problems related
to dependencies during collaboration and deployment.
Please refer to the Composer documentation for further details:
https://getcomposer.org/doc/
https://getcomposer.org/doc/01-basic-usage.md
EOF
}
# if prefer-stable is false and minimum-stability is not stable, warn about potential unstable platform installs
[[ ! -f "$COMPOSER_LOCK" ]] || minimum_stability=$(cat "$COMPOSER_LOCK" | python3 -c 'import sys, json; l = json.load(sys.stdin); print(l.get("minimum-stability")); sys.exit(l.get("minimum-stability", "stable") != "stable" and l.get("prefer-stable", False) == False);' 2> /dev/null) || {
possible_stabilities="dev, alpha, beta, or RC"
case $minimum_stability in
alpha)
possible_stabilities="alpha, beta, or RC"
;;
beta)
possible_stabilities="beta or RC"
;;
[rR][cC])
possible_stabilities="release candidate"
;;
esac
mcount "warnings.composer_lock.minimum_stability"
warning <<-EOF
Non-stable 'minimum-stability'!
Your '$COMPOSER' contains a 'minimum-stability' setting of
'$minimum_stability', and the 'prefer-stable' setting is not enabled.
This combination of options may negatively impact the stability
of your app and result in crashes as it permits installation of
$possible_stabilities versions of PHP runtimes and extensions.
If possible, you should always use explicit stability flags on
only those dependencies that you want unstable versions of, and
leave 'minimum-stability' at its default 'stable' setting.
If you really need a global 'minimum-stability' setting lower
than 'stable', it is strongly recommended that you enable the
'prefer-stable' setting in '$COMPOSER'.
For more information, refer to the following documentation:
https://getcomposer.org/doc/articles/versions.md
https://getcomposer.org/doc/04-schema.md#package-links
https://getcomposer.org/doc/04-schema.md#minimum-stability
https://getcomposer.org/doc/04-schema.md#prefer-stable
EOF
}
status "Preparing platform package installation..."
if [[ $STACK == "heroku-20" ]]; then
HEROKU_PHP_DEFAULT_RUNTIME_VERSION="^7.3.0 | ^8.0.0 <8.5"
else
HEROKU_PHP_DEFAULT_RUNTIME_VERSION="^8.0.0 <8.5"
fi
export HEROKU_PHP_DEFAULT_RUNTIME_VERSION
# extract requirements from composer.lock
/app/.heroku/php-min/bin/php $bp_dir/bin/util/platform.php ${have_custom_platform_repos:+"--list-repositories"} "$bp_dir/support/installer/" $HEROKU_PHP_PLATFORM_REPOSITORIES 2>&1 >$build_dir/.heroku/php/composer.json | indent || {
code=$?
if (( code == 3 )); then
mcount "failures.platform.composer_lock.runtime_only_in_dev"
error <<-EOF
Runtime specified in 'require-dev' but not in 'require'!
Your '$COMPOSER' contains a 'require-dev' section which
specifies a PHP runtime version (either directly, or through
a dependency), but no such requirement is present in 'require'
or in any of the packages listed in 'require'.
Even if dev requirements are not being installed, the entirety
of all dependencies needs to resolve to an installable set.
Heroku cannot select a default runtime version in this case.
Please perform the following steps locally on your computer to
resolve this issue before attempting another deploy:
1) add a dependency for 'php' to 'require' in '$COMPOSER'
2) run 'composer update' to re-generate the lock file
3) stage changes using 'git add $COMPOSER $COMPOSER_LOCK'
4) commit changes using 'git commit'
For more information on selecting PHP runtimes, please refer to
https://devcenter.heroku.com/articles/php-support
EOF
elif (( code == 4 )); then
mcount "failures.platform.repositories_url_invalid"
error 'Invalid $HEROKU_PHP_PLATFORM_REPOSITORIES value'
else
mcount "failures.platform.composer_lock.parse"
error "$composer_lock_parse_error"
fi
}
# reset $COMPOSER for the platform install step
COMPOSER_bak="$COMPOSER"
export COMPOSER=composer.json
status "Installing platform packages..."
# pass export_file_path, profile_dir_path and providedextensionslog_file_path (used to record packages that provide native extensions) to composer install; they will be used by the installer plugin
# they are also used in later install attempts for add-on extensions (blackfire, newrelic, ...)
export export_file_path=$bp_dir/export
export profile_dir_path=$build_dir/.profile.d
export providedextensionslog_file_path=$(mktemp -t "provided-extensions.log.XXXXXX" -u)
# grep and sed in a subshell with || true, or a failing grep match will cause the exit code to be 1 if "composer install" fails with code 2
if composer install -d "$build_dir/.heroku/php" ${HEROKU_PHP_INSTALL_DEV-"--no-dev"} 2>&1 | tee $build_dir/.heroku/php/install.log | ( grep --line-buffered -E '^ - (Instal|Enab)ling heroku-sys/' | sed -u -E -e 's/^ - (Instal|Enab)ling /- /' -e 's/heroku-sys\///g' || true ) | indent; then
:
else
code=$?
install_log=$(
cat $build_dir/.heroku/php/install.log | sed -E \
-e 's/heroku-sys\///g' \
-e 's/^Loading composer repositories with package information/Loading repositories with available runtimes and extensions/' \
-e 's/^Installing dependencies.*//' \
-e '/^No composer\.lock file present/d' \
-e '/^Potential causes:/,$d' \
-e "\#satisfiable by ${COMPOSER}/${COMPOSER_LOCK}#d" \
-e "s#(${COMPOSER}/${COMPOSER_LOCK}) dev-[0-9a-f]+#\1#" \
-e 's/^/> /'
)
if [[ $code -eq 2 ]]; then
# dependency solving failed
mcount "failures.platform.solving.$(detect_platform_solving_failures <<< "$install_log")"
else
# something else
mcount "failures.platform.install.$(detect_platform_install_failures <<< "$install_log")"
fi
error <<-EOF
Failed to install system packages!
Your platform requirements (for runtimes and extensions) could
not be resolved to an installable set of dependencies, or a
platform package repository was unreachable.
This usually means that you (or packages you are using) depend
on a combination of PHP versions and/or extensions that are
currently not available on Heroku.
The following is the full output from the installation attempt:
$install_log
$(
$composer_lock_outdated && cat <<-EOF2
$(echo -e "\033[1;33m")
A possible cause for this error is your '$COMPOSER_LOCK' file,
which is currently out of date, as changes have been made to
your '$COMPOSER' that are not yet reflected in the lock file.
In order to guarantee reliable installation of system packages,
Heroku uses information from '$COMPOSER_LOCK'.
If you already attempted to fix the error above by updating the
requirements in '$COMPOSER', remember to also update the
lock file by running 'composer update', followed by a 'git add'
and 'git commit' of the changes. The warning message further
above contains step-by-step instructions.
Please remember to always keep your '$COMPOSER_LOCK' updated in
lockstep with '$COMPOSER' to avoid common problems related
to dependencies during collaboration and deployment.
$(echo -e "\033[1;31m")
EOF2
)
For reference, the following runtimes are currently available:
PHP: $(composer show -d "$build_dir/.heroku/php" --available heroku-sys/php 2>&1 | sed -n 's/^versions : //p' | fold -s -w 58 || true)
Please verify that all requirements for runtime versions in
'$COMPOSER_LOCK' are compatible with the list above, and ensure
all required extensions are available for the desired runtimes.
When choosing a PHP runtimes and extensions, please also ensure
they are available on your app's stack ($STACK), and select
a different stack if needed after consulting the article below.
For a list of supported runtimes & extensions on Heroku, please
refer to: https://devcenter.heroku.com/articles/php-support
EOF
fi
export -n providedextensionslog_file_path # export no longer needed
if [[ -s "$providedextensionslog_file_path" ]]; then
notice_inline "detected userland polyfill packages for PHP extensions"
notice_inline "now attempting to install native extension packages"
current_install_log=$(mktemp -t "currentinstall.log.XXXXXX" -u) # for capturing the single "composer install" output
# the platform installer recorded userland packages that "provide" a PHP extension
# for each occurrence (source package providing other package(s)) in the list,
# we will now attempt to install the real native extension packages (rest of the line)
# this isolated install will not change anything else about the existing dependency graph
while read -r -u "$fd_num" line; do # $fd_num comes from the end of the loop
read -r -a provides <<< "$line" # read words of line into array
echo "Installing extensions provided by ${provides[0]}:" | indent # first "word" is userland package name
for extension in "${provides[@]:1}"; do # remaining words in line are native extensions it declares "provide"d
ext_package=$(cut -d":" -f1 <<< "$extension") # extract name from "ext-foo:1.2.3" string
ext_name=${ext_package#"heroku-sys/"} # no "heroku-sys/" prefix for human output
echo -n "- ${ext_name}... " | indent
# run a `composer require`, confined to the package we're attempting, allowing any version
# we're appending to install.log using tee, and using the same grep/sed filtering of packages as for regular installs
# we're letting sed begin the line with a \r character, so that the above `echo -n` gets overwritten
if composer require -d "$build_dir/.heroku/php" "${ext_package}.native:*" 2>&1 | tee -a $build_dir/.heroku/php/install.log | ( grep --line-buffered -E '^ - (Instal|Enab)ling heroku-sys/' | sed -u -E -e 's/^ - (Instal|Enab)ling /- /' -e 's/heroku-sys\///g' || true ) | tee "$current_install_log" | indent "" $'\r '; then
# package added successfully
[[ -s "$current_install_log" ]] || { echo -e "- ${ext_name} (already enabled)" | indent "" $'\r '; } # if nothing was actually installed above, that means the dependency was already satisfied (i.e. extension is statically built into PHP); we need to echo something to that effect
else
# composer did not succeed; this means no package that matches all existing package's requirements was found
echo -n -e "\r" # reset the line
notice_inline "no suitable native version of ${ext_name} available"
fi
done
done {fd_num}< "$providedextensionslog_file_path" # use bash 4.1+ automatic file descriptor allocation (better than hardcoding e.g. "3<"), otherwise this loop's stdin (the lines of the file) will be consumed by programs called inside the loop
rm "$current_install_log"
fi
# log number of installed platform packages
mmeasure "platform.count" $(composer show -d "$build_dir/.heroku/php" --installed "heroku-sys/*" 2> /dev/null | wc -l)
# done with platform installs; restore COMPOSER from previous value
export COMPOSER="$COMPOSER_bak"
unset COMPOSER_bak
# clean up
rm -rf /app/.heroku/php-min $build_dir/.heroku/php-min
# unset composer function that used php-min for invocation
unset -f composer
# export our "do not auto.start APM extensions" magic INI directory to PHP_INI_SCAN_DIR for ourself and for later buildpacks (but not for runtime)
export_env_dir "$env_dir" '^PHP_INI_SCAN_DIR$'
echo "export PHP_INI_SCAN_DIR=\${PHP_INI_SCAN_DIR-}:$bp_dir/conf/php/apm-nostart-overrides/" >> $bp_dir/export
# earlier we wrote at least one $PATH entry that we'll need now (and just above we added a PHP_INI_SCAN_DIR export), and installed packages will likely have added to it too
source $bp_dir/export
# from here onwards we're using the composer we installed via platform install with the PHP runtime that we installed via platform install
# log runtime version
mcount "platform.packages.php.$(php -r "echo PHP_VERSION;")"
if eoldate=$(php $bp_dir/bin/util/eol.php); then
:
else
code=$?
eolmsg=$(
cat <<-EOF
It is strongly recommended you update your app to a version of
PHP with "active support" status immediately to ensure you get
the latest bugfixes and security updates each time you deploy.
You may check the list of versions supported by the PHP Group
and their EOL dates here: http://php.net/supported-versions.php
For a list of supported runtimes & extensions on Heroku, please
refer to: https://devcenter.heroku.com/articles/php-support
EOF
)
if (( $code == 2 )); then
mcount "warnings.runtime_eol.eol_reached"
warning <<-EOF
Your selected PHP version has reached end-of-life
No updates or security fixes have been provided for your PHP
version series by the PHP Group since $eoldate.
$eolmsg
EOF
elif (( $code == 3 )); then
mcount "warnings.runtime_eol.eol_close"
warning <<-EOF
Your selected PHP version is close to end-of-life
No updates or security fixes will be provided for your PHP
version series by the PHP Group as of $eoldate.
$eolmsg
EOF
elif (( $code == 4 )); then
mcount "warnings.runtime_eol.eom_reached"
warning <<-EOF
Your app's PHP version is no longer actively maintained
Only security updates will be provided for your PHP version
series by the PHP Group until its end-of-life on $eoldate.
$eolmsg
EOF
elif (( $code == 5 )); then
mcount "warnings.runtime_eol.eom_close"
warning <<-EOF
Your app's PHP version is close to end of maintenance
Only security updates will be provided for your PHP version
series by the PHP Group as of $eoldate.
$eolmsg
EOF
fi
fi
status "Installing dependencies..."
# echo composer version for info purposes
# tail to get rid of outdated version warnings (Composer sends those to STDOUT instead of STDERR)
composer --no-plugins --version 2> /dev/null | tail -n 1 | indent
# throw a notice if people have added their vendor dir to Git; that's bad practice and makes everything slow and cluttered
if [[ -f "$composer_vendordir/autoload.php" && -d "$composer_vendordir/composer" ]]; then
# we should not do this check separately; there is no reliable way of telling whether or not it really is the real Composer bin dir or if it comes from somewhere else
mcount "warnings.vendor_dir"
warning <<-EOF
Composer vendor dir found in project!
Your Git repository contains Composer's '$composer_vendordir' directory.
This directory should not be under version control; only your
'$COMPOSER' and '$COMPOSER_LOCK' files need to be added, as
Composer will handle installation of dependencies on deploy.
To suppress this notice, first remove the folder from the index
by running 'git rm -r --cached $composer_vendordir/'.
Next, edit your project's '.gitignore' file and add the folder
'/$composer_vendordir/' to the list, then commit the changes.
$(
[[ ! "$composer_bindir/" == "$composer_vendordir"/* && -d "$composer_bindir" ]] && cat <<-EOF2
Your Composer bin dir is configured to reside outside of vendor
dir, so please repeat the two steps above for '$composer_bindir/'.
EOF2
)
For more info, refer to the Composer FAQ: http://bit.ly/1rlCSZU
EOF
fi
# handle custom oauth keys
export_env_dir "$env_dir" '^COMPOSER_GITHUB_OAUTH_TOKEN$'
COMPOSER_GITHUB_OAUTH_TOKEN=${COMPOSER_GITHUB_OAUTH_TOKEN:-}
if [[ -n "$COMPOSER_GITHUB_OAUTH_TOKEN" ]]; then
if curl --fail --silent -H "Authorization: token $COMPOSER_GITHUB_OAUTH_TOKEN" https://api.github.com/rate_limit > /dev/null; then
composer config --no-plugins -g github-oauth.github.com "$COMPOSER_GITHUB_OAUTH_TOKEN" &> /dev/null # redirect outdated version warnings (Composer sends those to STDOUT instead of STDERR)
notice_inline 'Using $COMPOSER_GITHUB_OAUTH_TOKEN for GitHub OAuth.'
else
mcount "failures.dependencies.auth.COMPOSER_GITHUB_OAUTH_TOKEN"
error <<-EOF
Invalid token for GitHub OAuth!
The OAuth token set in the '\$COMPOSER_GITHUB_OAUTH_TOKEN'
config var is invalid. Please ensure that you obtain a valid
token from https://github.com/settings/tokens and set it on the
app using 'heroku config:set COMPOSER_GITHUB_OAUTH_TOKEN=...'.
EOF
fi
else
# don't forget to remove any stored key if it's gone from the env
composer config --no-plugins -g --unset github-oauth.github.com &> /dev/null # redirect outdated version warnings (Composer sends those to STDOUT instead of STDERR)
fi
# no need for the token to stay around in the env
unset COMPOSER_GITHUB_OAUTH_TOKEN
# install dependencies unless composer.json is completely empty (in which case it'd talk to packagist.org which may be slow and is unnecessary)
export_env_dir "$env_dir" '^[A-Z_][A-Z0-9_]*$' '^(HOME|PATH|GIT_DIR|CPATH|CPPATH|LD_PRELOAD|LIBRARY_PATH|LD_LIBRARY_PATH|STACK|REQUEST_ID|IFS|HEROKU_PHP_INSTALL_DEV|BPLOG_PREFIX|BUILDPACK_LOG_FILE|PHP_INI_SCAN_DIR)$'
if cat "$COMPOSER" | python3 -c 'import sys,json; sys.exit(not json.load(sys.stdin));'; then
install_log=$(mktemp -t heroku-buildpack-php-composer-install-log-XXXX)
composer install ${HEROKU_PHP_INSTALL_DEV-"--no-dev"} --prefer-dist --optimize-autoloader --no-progress --no-interaction 2>&1 | tee "$install_log" | indent || {
code=$?
if [[ $code -eq 2 ]]; then
# dependency solving failed, which should only ever happen if people have platform overrides in their config
mcount "failures.dependencies.solving.$(detect_dependencies_solving_failures < "$install_log")"
else
mcount "failures.dependencies.install.$(detect_dependencies_install_failures < "$install_log")"
fi
# FIXME: can we provide more advice depending on the error?
error <<-EOF
Dependency installation failed!
The 'composer install' process failed with an error. The cause
may be the download or installation of packages, or a pre- or
post-install hook (e.g. a 'post-install-cmd' item in 'scripts')
in your '$COMPOSER'.
Typical error cases are out-of-date or missing parts of code,
timeouts when making external connections, or memory limits.
Check the above error output closely to determine the cause of
the problem, ensure the code you're pushing is functioning
properly, and that all local changes are committed correctly.
For more information on builds for PHP on Heroku, refer to
https://devcenter.heroku.com/articles/php-support
EOF
}
fi
# only perform the check for buildpack package if we're not running in Heroku CI
if [[ -z "${HEROKU_PHP_INSTALL_DEV+are-we-running-in-ci}" ]]; then
composer show --installed heroku/heroku-buildpack-php &> /dev/null && {
mcount "failures.dependencies.buildpack_as_dependency"
error <<-EOF
Package 'heroku/heroku-buildpack-php' found!
Your '$COMPOSER' requires 'heroku/heroku-buildpack-php' in
the 'require' section. This package may only be used as a
dependency in 'require-dev', because Heroku installs the latest
version of the buildpack during builds.
Please perform the following steps locally on your computer to
resolve this issue before attempting another deploy:
1) move the requirement for 'heroku/heroku-buildpack-php' from
section 'require' to 'require-dev' in '$COMPOSER'
2) run 'composer update' to re-generate the lock file
3) stage changes using 'git add $COMPOSER $COMPOSER_LOCK'
4) commit changes using 'git commit'
EOF
}
fi
# log number of installed dependencies
mmeasure "dependencies.count" $(composer show --installed 2> /dev/null | wc -l)
if cat "$COMPOSER" | python3 -c 'import sys,json; sys.exit("compile" not in json.load(sys.stdin).get("scripts", {}));'; then
status "Running 'composer compile'..."
composer run-script ${HEROKU_PHP_INSTALL_DEV-"--no-dev"} --no-interaction compile 2>&1 | indent || {
mcount "failures.compile_step"
error <<-EOF
Compile step failed!
Installation of dependencies was successful, but the custom
script you're using to perform actions after 'composer install'
failed with the error above.
Check that the 'compile' command(s) in the 'scripts' section of
your '$COMPOSER' are working properly and not running into
timeouts or memory limits.
For more information on the 'composer compile' step, refer to
https://devcenter.heroku.com/articles/php-support
EOF
}
fi
status "Preparing runtime environment..."
# install this buildpack like a composer package
# it will contain the apache/nginx/php configs and the boot script
# TODO: warn if require-dev has the package using a different branch
shopt -u dotglob # we don't want .git, .gitignore et al
# figure out the package dir name to write to and copy to it
hbpdir="$composer_vendordir/$(cat $bp_dir/composer.json | python3 -c 'import sys, json; print(json.load(sys.stdin)["name"])')"
mkdir -p "$build_dir/$hbpdir"
cp -r "$bp_dir"/* "$build_dir/$hbpdir/"
# make bin dir, just in case
mkdir -p "$build_dir/$composer_bindir"
# figure out shortest relative path from vendor/heroku/heroku-buildpack-php to vendor/bin (or whatever the bin dir is)
relbin=$(python3 -c "import os.path; print(os.path.relpath('$hbpdir', '$composer_bindir'))")
# collect bin names from composer.json
relbins=$(cat $bp_dir/composer.json | python3 -c 'import sys, json; { print(sys.argv[1]+"/"+bin) for bin in json.load(sys.stdin)["bin"] }' $relbin)
# link to bins
cd $build_dir/$composer_bindir
ln -fs $relbins .
cd $build_dir
if [[ ! -f "Procfile" ]]; then
echo "web: heroku-php-apache2" > Procfile
notice_inline "No Procfile, using 'web: heroku-php-apache2'."
fi
# write empty WEB_CONCURRENCY.sh to overwrite the defaults logic from a prior buildpack, e.g. Node (all buildpacks use the same filename to allow this)
> $build_dir/.profile.d/WEB_CONCURRENCY.sh
# reset COMPOSER for the platform install step
COMPOSER_bak="$COMPOSER"
export COMPOSER=composer.json
# unless we're running a CI build...
if [[ "${HEROKU_PHP_INSTALL_DEV+CI}" != "CI" ]]; then
status "Checking for additional extensions to install..."
# special treatment for Blackfire; we enable it if we detect a server id and a server token for it
install_blackfire_ext
# special treatment for New Relic; we enable it if we detect a license key for it
install_newrelic_ext
install_newrelic_userini
fi
# done with platform installs; restore COMPOSER from previous value
export COMPOSER="$COMPOSER_bak"
unset COMPOSER_bak
# and remove the composer2 program we needed for platform installs
rm $build_dir/.heroku/php/bin/composer2