Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

psalm/phar broken on composer 2.2 #10407

Closed
GrahamCampbell opened this issue Dec 29, 2021 · 16 comments
Closed

psalm/phar broken on composer 2.2 #10407

GrahamCampbell opened this issue Dec 29, 2021 · 16 comments

Comments

@GrahamCampbell
Copy link
Contributor

My composer.json:

{
    "name": "acme/example",
    "type": "project",
    "license": "proprietary",
    "require": {
        "php": "^8.0.2"
    },
    "require-dev": {
        "psalm/phar": "4.16.1"
    },
    "config": {
        "allow-plugins": {
            "composer/package-versions-deprecated": true
        },
        "optimize-autoloader": true,
        "preferred-install": "dist",
        "sort-packages": true
    },
    "autoload": {
        "psr-4": {
            "App\\": "app/"
        }
    }
}

Output of composer diagnose:

Checking composer.json: OK
Checking platform settings: OK
Checking git settings: OK
Checking http connectivity to packagist: OK
Checking https connectivity to packagist: OK
Checking github.com oauth access: OK
Checking disk free space: OK
Checking pubkeys: 
Tags Public Key Fingerprint: 57815BA2 7E54DC31 7ECC7CC5 573090D0  87719BA6 8F3BB723 4E5D42D0 84A14642
Dev Public Key Fingerprint: 4AC45767 E5EC2265 2F0C1167 CBBB8A2B  0C708369 153E328C AD90147D AFE50952
OK
Checking composer version: OK
Composer version: 2.2.2
PHP version: 8.0.14
PHP binary path: /opt/homebrew/Cellar/php@8.0/8.0.14/bin/php
OpenSSL version: OpenSSL 1.1.1m  14 Dec 2021
cURL version: 7.80.0 libz 1.2.11 ssl (SecureTransport) OpenSSL/1.1.1m
zip: extension present, unzip present, 7-Zip not available

When I run this command:

./vendor/bin/psalm.phar

I get the following output (Composer 2.2.2):

Target PHP version: 8.0 (inferred from composer.json)
Scanning files...
Analyzing files...

░

ERROR: MissingFile - vendor/bin/psalm.phar:93:9 - Cannot find file composer-bin-proxy:/Users/acme/example/vendor/psalm/phar/psalm.phar to include (see https://psalm.dev/107)
        include("composer-bin-proxy://" . __DIR__ . '/..'.'/psalm/phar/psalm.phar');


------------------------------
1 errors found
------------------------------

And I expected this to happen (Composer 2.1.14):

Scanning files...
Analyzing files...

░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

------------------------------
                              
       No errors found!       
                              
------------------------------
@johnstevenson
Copy link
Member

include("composer-bin-proxy://" . DIR . '/..'.'/psalm/phar/psalm.phar');

That code should be wrapped in an if (PHP_VERSION_ID < 80000) condition and therefore shouldn't run!?

So what does vendor/bin/psalm.phar actually look like.

@GrahamCampbell
Copy link
Contributor Author

This also doesn't work on PHP 7.4 too. Exactly the same behaviour, so I don't think this is related to PHP 8.0?

@johnstevenson
Copy link
Member

FWIW, I cannot reproduce this myself on Windows (using Git bash) or Ubuntu WSL on either PHP 7.4 or PHP 8.0. My vendor/bin/psalm.phar from Composer 2.2.2 looks like:

#!/usr/bin/env php
<?php

/**
 * Proxy PHP file generated by Composer
 *
 * This file includes the referenced bin path (../psalm/phar/psalm.phar)
 * using a stream wrapper to prevent the shebang from being output on PHP<8
 *
 * @generated
 */

namespace Composer;

$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';

if (PHP_VERSION_ID < 80000) {
    if (!class_exists('Composer\BinProxyWrapper')) {
        /**
         * @internal
         */
        final class BinProxyWrapper
        {
            private $handle;
            private $position;

            public function stream_open($path, $mode, $options, &$opened_path)
            {
                // get rid of composer-bin-proxy:// prefix for __FILE__ & __DIR__ resolution
                $opened_path = substr($path, 21);
                $opened_path = realpath($opened_path) ?: $opened_path;
                $this->handle = fopen($opened_path, $mode);
                $this->position = 0;

                // remove all traces of this stream wrapper once it has been used
                stream_wrapper_unregister('composer-bin-proxy');

                return (bool) $this->handle;
            }

            public function stream_read($count)
            {
                $data = fread($this->handle, $count);

                if ($this->position === 0) {
                    $data = preg_replace('{^#!.*\r?\n}', '', $data);
                }

                $this->position += strlen($data);

                return $data;
            }

            public function stream_cast($castAs)
            {
                return $this->handle;
            }

            public function stream_close()
            {
                fclose($this->handle);
            }

            public function stream_lock($operation)
            {
                return $operation ? flock($this->handle, $operation) : true;
            }

            public function stream_tell()
            {
                return $this->position;
            }

            public function stream_eof()
            {
                return feof($this->handle);
            }

            public function stream_stat()
            {
                return fstat($this->handle);
            }

            public function stream_set_option($option, $arg1, $arg2)
            {
                return true;
            }
        }
    }

    if (function_exists('stream_wrapper_register') && stream_wrapper_register('composer-bin-proxy', 'Composer\BinProxyWrapper')) {
        include("composer-bin-proxy://" . __DIR__ . '/..'.'/psalm/phar/psalm.phar');
        exit(0);
    }
}

include __DIR__ . '/..'.'/psalm/phar/psalm.phar';

@johnstevenson
Copy link
Member

I've replicated this in a repo and setup an action that installs with Composer 2.2.2, then runs vendor/bin/psalm.phar on Ubuntu, MacOS and Windows without any issues:

https://github.com/johnstevenson/bug-10407/actions/runs/1634904758

Weird that this doesn't work for you.

@herndlm
Copy link
Contributor

herndlm commented Dec 29, 2021

Is the proxy file in vendor/bin maybe outdated (composer install) or are stream wrappers disabled in PHP?

@johnstevenson
Copy link
Member

@herndlm The meat of the proxy file is virtually the same between Composer 2.1.14 and 2.2.2, and if stream wrappers are disabled then the composer-bin-proxy wrapper should not be used. In fact on PHP 8 none of this stuff should even get executed.

It would be useful to see the contents of the vendor/bin/psalm.phar that causes the issue.

@GrahamCampbell
Copy link
Contributor Author

Here is the phar. Problem persists after deleting vendor folder. Also happens consistently on github actions. https://github.com/psalm/phar/raw/4.16.1/psalm.phar

@johnstevenson
Copy link
Member

I meant the psalm.phar proxy file that Composer creates in vendor/bin, rather than the actual phar in vendor/psalm/phar/psalm.phar

@Seldaek
Copy link
Member

Seldaek commented Dec 30, 2021

@GrahamCampbell can you maybe make a PR to @johnstevenson 's repo with changes that trigger the issue on GH Actions? Because I also cannot reproduce this locally.

@GrahamCampbell
Copy link
Contributor Author

The problem is the example repo adds an exclude for the vendor directory. That is not how psalm should typically be used, since it stops it fetching type information for dependencies.

@GrahamCampbell
Copy link
Contributor Author

johnstevenson/bug-10407#1 is enough to replicate the issue.

@johnstevenson
Copy link
Member

Thanks @GrahamCampbell Got it now!

Excluding vendor\bin is one solution. Another would be Psalm providing an internal workaround, I guess.

        <ignoreFiles>
            <directory name="vendor/bin" />
        </ignoreFiles>

@Seldaek
Copy link
Member

Seldaek commented Dec 30, 2021

I see, I'm not familiar with how psalm parses files but it sounds like a similar issue to the phpunit one. I assume psalm scans everything and has an exclusion for itself in phar form but doesn't exclude the binary proxy which ends up including the phar and that is then marked as unsupported?

Anyway it'd be great if this could be handled in psalm as I don't see a way we can mitigate this on our end, except perhaps adding some annotations to the proxy files?

Pinging @weirdan @orklah your input would be good to have here

@orklah
Copy link

orklah commented Dec 30, 2021

Thanks for your ping, I was not aware this issue had been opened here.

I believe the fix has been merged in Psalm already: vimeo/psalm#7210

We'll make a release soon

@Seldaek
Copy link
Member

Seldaek commented Dec 30, 2021

Ah perfect, thanks.

@GrahamCampbell
Copy link
Contributor Author

Thanks everyone. 🍻

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants