heroku-buildpack-php icon indicating copy to clipboard operation
heroku-buildpack-php copied to clipboard

Support Debianisms for local execution

Open dzuelke opened this issue 10 years ago • 20 comments

It has php5 and php5-fpm instead, and apache2 instead of httpd (with crazy sourcing of an env vars file).

dzuelke avatar Jun 25 '14 18:06 dzuelke

https://wiki.apache.org/httpd/DistrosDefaultLayout :(

dzuelke avatar Mar 09 '16 14:03 dzuelke

For anyone else struggling with this, I created local-heroku-php-nginx to show a setup with heroku-php-nginx on Ubuntu 14.04.

@dzuelke I haven't quite figured out why, but something in v93 of the buildpack is preventing local execution (but only Ubuntu 14.04). Specifically, it is throwing this error:

Unable to determine Composer vendor-dir setting; is 'composer' executable on path or 'composer.phar' in current working directory?

I'll be digging around more today in preparation for v96 but if you have any ideas, I'm all ears.

jonahgeorge avatar Mar 09 '16 15:03 jonahgeorge

How did you install Composer, @jonahgeorge? Is there a local composer.phar?

Please run this and report what it returns:

file $(which composer)

Also, what does composer config vendor-dir yield if you just run it in your project?

dzuelke avatar Mar 09 '16 16:03 dzuelke

Also, do you have a "php" executable, or is it "php5" or "php5-cli"?

dzuelke avatar Mar 09 '16 16:03 dzuelke

Installing composer using the following commands; however, this issues is also present when using their new installation instructions.

curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer
vagrant@vagrant-ubuntu-trusty-64:/vagrant$ file $(which composer)
/usr/local/bin/composer: data

vagrant@vagrant-ubuntu-trusty-64:/vagrant$ composer config vendor-dir
vendor

vagrant@vagrant-ubuntu-trusty-64:/vagrant$ composer config bin-dir
vendor/bin

vagrant@vagrant-ubuntu-trusty-64:/vagrant$ php -v
PHP 7.0.4-4+deb.sury.org~trusty+1 (cli) ( NTS )

vagrant@vagrant-ubuntu-trusty-64:/vagrant$ ls /usr/bin | grep php
php
php7.0

vagrant@vagrant-ubuntu-trusty-64:/vagrant$ ls /usr/sbin | grep php
phpdismod
phpenmod
php-fpm
php-fpm7.0
phpquery

jonahgeorge avatar Mar 09 '16 16:03 jonahgeorge

While I'm not entirely sure, it seems to be related to the variables that heroku-php-nginx is setting. If I hardcode the values on lines 211 and 212, I get the following error:

211: COMPOSER_VENDOR_DIR="vendor"
212: COMPOSER_BIN_DIR="vendor/bin"
213: # COMPOSER_VENDOR_DIR=$(composer config vendor-dir 2> /dev/null | tail -n 1) && export COMPOSER_VENDOR_DIR || { echo "Unable to determine Composer vendor-dir setting; is 'composer' executable on path or 'composer.phar' in current working directory?" >&2; exit 1; } # tail, as composer echos outdated version warnings to STDOUT; export after the assignment or exit status will that be of 'export
214: # COMPOSER_BIN_DIR=$(composer config bin-dir 2> /dev/null | tail -n 1) && export COMPOSER_BIN_DIR || { echo "Unable to determine Composer vendor-dir setting; is 'composer' executable on path or 'composer.phar' in current working directory?" >&2; exit 1; } # tail, as composer echos outdated version warnings to STDOUT; export after the assignment or exit status will that be of 'export
vendor/bin/heroku-php-nginx: line 280: Could: unbound variable

Which is:

277:    # determine number of FPM processes to run
278:    # prevent loading of extension INIs (and thus e.g. newrelic) using empty PHP_INI_SCAN_DIR
279:    read WEB_CONCURRENCY php_memory_limit <<<$(PHP_INI_SCAN_DIR= php ${php_config:+-c "$php_config"} $(composer config vendor-dir 2> /dev/null | tail -n 1)/heroku/heroku-buildpack-php/bin/util/autotune.php -y "$fpm_config" -t "$DOCUMENT_ROOT" "$ram") # tail, as composer echos outdated version warnings to STDOUT
280:    [[ $WEB_CONCURRENCY -lt 1 ]] && WEB_CONCURRENCY=1
281:    export WEB_CONCURRENCY

Update By switching $(composer config vendor-dir 2> /dev/null | tail -n 1) to vendor on line 279, it starts up but is not binding to the correct port (attempts and fails to bind to 8080, rather than foreman's default of 5000). This also happens when hardcoding the PORT variable on line 101:

100: # set a default port if none is given
101: # export PORT=${PORT:-$(( $RANDOM+1024 ))}
102: export PORT=5000
vagrant@vagrant-ubuntu-trusty-64:/vagrant$ foreman start
17:19:13 web.1  | started with pid 2889
17:19:13 web.1  | DOCUMENT_ROOT changed to './'
17:19:13 web.1  | Using Nginx server-level configuration include 'nginx_app.conf'
17:19:13 web.1  | 1 processes at -1B memory limit.
17:19:13 web.1  | Starting php-fpm...
17:19:15 web.1  | Starting nginx...
17:19:15 web.1  | Application ready for connections on port 5000.
17:19:15 web.1  | nginx: [emerg] bind() to 0.0.0.0:8080 failed (98: Address already in use)

jonahgeorge avatar Mar 09 '16 16:03 jonahgeorge

What happens if you run composer config vendor-dir 2> /dev/null | tail -n 1, and what happens if you run echo $(composer config vendor-dir 2> /dev/null | tail -n 1)? And, in both cases, without the 2> /dev/null redirect?

dzuelke avatar Mar 09 '16 18:03 dzuelke

Ah, this has to do with the alias we make that uses a local composer.phar

Please help me by running the following and reporting:

$ which ./composer.phar composer
$ composer_bin=$(which ./composer.phar composer | head -n1)
$ echo $composer_bin
$ file --brief --dereference $composer_bin
$ php -n $composer_bin

dzuelke avatar Mar 09 '16 18:03 dzuelke

Well, you beat me to it! :D Here's the debug info

vagrant@vagrant-ubuntu-trusty-64:/vagrant$ which ./composer.phar composer
/usr/local/bin/composer

vagrant@vagrant-ubuntu-trusty-64:/vagrant$ composer_bin=$(which ./composer.phar composer | head -n1)
vagrant@vagrant-ubuntu-trusty-64:/vagrant$ echo $composer_bin
/usr/local/bin/composer

vagrant@vagrant-ubuntu-trusty-64:/vagrant$ file --brief --dereference $composer_bin
data

vagrant@vagrant-ubuntu-trusty-64:/vagrant$ php -n $composer_bin

Fatal error: Uncaught Error: Class 'Phar' not found in /usr/local/bin/composer:23
Stack trace:
#0 {main}
  thrown in /usr/local/bin/composer on line 23

Below is how I found it:

echo $(composer config vendor-dir)
# make sure we run a local composer.phar if present, or global composer if not
composer() {
    local composer_bin=$(which ./composer.phar composer | head -n1)
    # check if we the composer binary is executable by PHP
    if file --brief --dereference $composer_bin | grep "bash" > /dev/null ; then # newer versions of file return "data" for .phar
        # run it directly; it's probably a bash script or similar (homebrew-php does this)
        $composer_bin "$@"
    else
        php -n $composer_bin "$@"
    fi
}
vagrant@vagrant-ubuntu-trusty-64:/vagrant$ foreman start
18:10:51 web.1  | started with pid 4104
18:10:51 web.1  | vendor
18:10:51 web.1  | Unable to determine Composer vendor-dir setting; is 'composer' executable on path or 'composer.phar' in current working directory?
18:10:51 web.1  | exited with code 1
18:10:51 system | sending SIGTERM to all processes

When I move it below the composer() function:

# make sure we run a local composer.phar if present, or global composer if not
composer() {
    local composer_bin=$(which ./composer.phar composer | head -n1)
    # check if we the composer binary is executable by PHP
    if file --brief --dereference $composer_bin | grep "bash" > /dev/null ; then # newer versions of file return "data" for .phar
        # run it directly; it's probably a bash script or similar (homebrew-php does this)
        $composer_bin "$@"
    else
        php -n $composer_bin "$@"
    fi
}
echo $(composer config vendor-dir)
vagrant@vagrant-ubuntu-trusty-64:/vagrant$ foreman start
18:12:30 web.1  | started with pid 4149
18:12:30 web.1  | Fatal error: Uncaught Error: Class 'Phar' not found in /usr/local/bin/composer:23 Stack trace: #0 {main} thrown in /usr/local/bin/composer on line 23
18:12:30 web.1  | Unable to determine Composer vendor-dir setting; is 'composer' executable on path or 'composer.phar' in current working directory?
18:12:30 web.1  | exited with code 1
18:12:30 system | sending SIGTERM to all processes

jonahgeorge avatar Mar 09 '16 18:03 jonahgeorge

Wow, even phar is compiled as shared on Ubuntu? :(

dzuelke avatar Mar 09 '16 18:03 dzuelke

The reason we do php -n (which prevents loading of INIs, so it also won't load conf.d/ configs that enable extensions) is that we don't want new relic to start and spew debug info on dyno startup (or even send telemetry about it to the server as "console events").

dzuelke avatar Mar 09 '16 18:03 dzuelke

That makes sense-- Do you think building php from source would be a workaround or is this a hard block?

jonahgeorge avatar Mar 09 '16 18:03 jonahgeorge

Oh yeah sure, that'll work, otherwise dynos would not boot on Heroku ;) I can't think of a quick and clean workaround from my side right now...

dzuelke avatar Mar 09 '16 18:03 dzuelke

Ok, well in the meantime I'm updating our Vagrantfiles to compile PHP. Thank you a ton for resolving that

jonahgeorge avatar Mar 09 '16 18:03 jonahgeorge

What about putting the extensions on the command line ? Would it be a possible workaround ? :

php -n -d extension=phar.so -d extension=json.so $composer_bin "$@"

It seems to work on my end.... I just encountered the same issue with the apache2 buildpack on ubuntu 16.04, but I don't use new relic and don't find any way to check if using these inline provided extensions actually loads the conf.d/ folder. Any idea ?

theobat avatar May 13 '16 13:05 theobat

That works for you @theobat, but if there is no phar.so because it's built into core then that'd produce an error... :/

dzuelke avatar Jul 06 '16 23:07 dzuelke

I was able to deal with some of these challenges by adding a bin directory within my project to PATH that contains:

  • httpd which just calls apache2
  • php-fpm which just calls php7.0-fpm
  • composer which just calls /usr/local/bin/composer

The last one bypasses the "php -n" because heroku-php-apache2 only calls "composer" that way if it is a php script and not a shell script.

I'm calling heroku-php-apache2 like this:

#!/bin/bash

set -e
cd /vagrant
PATH="/vagrant/bin:$PATH"

# envvars unsets HOME
orig_home="$HOME"
source /etc/apache2/envvars
HOME="$orig_home"

apache_dir=/vagrant/apache2
[ -d "$apache_dir" ] || mkdir "$apache_dir"
export APACHE_LOG_DIR="$apache_dir"
export APACHE_RUN_DIR="$apache_dir"
export APACHE_PID_FILE="$APACHE_RUN_DIR/apache2.pid"
export APACHE_LOCK_DIR="$apache_dir"

# Simulate a 1X dyno.
ulimit -S -u 256
export DYNO=512M

source .env
vendor/heroku-buildpack-php/bin/heroku-php-apache2 -i ./php.ini public/

... and Apache seems to start ok and respond with my app's index page.

mcary avatar Jan 07 '17 01:01 mcary

Very interesting. I wonder how feasible it would be for the buildpack to do that out of the box.

My biggest worry is changes to the Debian or Ubuntu config that'd eventually make this unusable.

dzuelke avatar Jan 09 '17 15:01 dzuelke

I'm having the same issue trying to run my app locally. First was the php-fpm that in Ubuntu is now php-fpm7.0, now is the Unable to determine Composer vendor-dir setting; is 'composer' executable on path or 'composer.phar' in current working directory?

Instead of hacking the heroku-php-nginx I can just change my Vagrant image. With what distro this would work out-of-box? CentOS?

luizs81 avatar Jan 12 '18 01:01 luizs81

I was able to deal with some of these challenges by adding a bin directory within my project to PATH that contains:

  • httpd which just calls apache2
  • php-fpm which just calls php7.0-fpm

For these two, I made symlinks

  • composer which just calls /usr/local/bin/composer

Because I'm a noob and it took me longer than it should have to figure out how to do this:

contents of /usr/bin/local/composer

#!/usr/bin/env bash
/usr/bin/composer "$@"

don't forget chmod +x /usr/bin/local/composer

lol I forgot to have the call to composer in the shell script be an absolute path, so it was recursively calling itself over and over. Easy to see once I did set -x.

deargle avatar Mar 27 '18 19:03 deargle