Skip to content

Commit

Permalink
Version 0.17.0
Browse files Browse the repository at this point in the history
  • Loading branch information
veewee committed Nov 29, 2019
1 parent 4c4ae44 commit 9bd3bd7
Show file tree
Hide file tree
Showing 4 changed files with 280 additions and 12 deletions.
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ compile:
rm -rf '$(BUILD_DIR)/vendor'
composer update --dev --working-dir='$(BUILD_DIR)' --no-interaction
cd $(BUILD_DIR) && ./grumphp.phar run --testsuite=git_pre_commit && cd $(ROOT_DIR)
# Copy composer plugin
cp '$(BUILD_DIR)/src/Composer/GrumPHPPlugin.php' '$(ROOT_DIR)/src/Composer/GrumPHPPlugin.php'
# All good : lets finish up
cp '$(BUILD_DIR)/grumphp.phar' '$(ROOT_DIR)'
gpg --local-user toonverwerft@gmail.com --armor --detach-sign grumphp.phar
Expand Down
Binary file modified grumphp.phar
Binary file not shown.
14 changes: 7 additions & 7 deletions grumphp.phar.asc
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1

iQEcBAABAgAGBQJdt82RAAoJELtfAF1v/dieG/YIALSHTGsfBGUOinFH+RZz8ETE
LgysSk84YyfJGEnDCBB8boZ1+QuWYaCnS992XXFtYURP1MBe8JPB5ryb9zZrcTMP
Vr8DUzQBZzUHWRm88fMnpnwRtDFkNOsil6lgMvo5wNALNrOAM6DcxC3XYi+SAtPt
bo0k5j3elUfAzn3FNWrL3UD7xtJEJp4cMiFmXJAmNyetsRAcDRMc3eNsPdigGVTo
aeUjo6ca7wwwmH+D2q8GFPgID550/tZU+UAaPttSJq3SQov2Ildlu3zanPQNZeZt
TFTwOvj5dn/9Uk8GoAl2WXzRlpcf7AEGXls6t5hD1mX4eho5GYIqLA3Vy6Ttsvg=
=lvht
iQEcBAABAgAGBQJd4RvmAAoJELtfAF1v/dieyFUH/jHnpCrajvGPqw+Uz8whbHTM
9TVGtp2z0GO8dOeOpc9+YN7mXceCjC5zGiepBhYam2zElml/mjdpfaXbDdN3+h7t
SWYGJSZaKafa2P3+XiB0rDt8Rk0R/W0eDNu0lAgqkoO5RHi//3yyTAovxjflnlgB
Hr59Ft+hRWe+M7+7d5o/GyGrVrZaLaL4coAJl824nIqScK/kK+RNzxcGLHsjndx0
lFs+uo8S8Om8iIWyDq50o/AE/IIjVSD1bz5L+No2Rq1S3JXkgp/9z+pax8m2SIcB
3k/q7b5FPwLnc5Gpa/+I5xDnnsZ1+H52dnCvVhaniWKHRByvB1cRQ3xkIYW+XAE=
=yuiX
-----END PGP SIGNATURE-----
276 changes: 271 additions & 5 deletions src/Composer/GrumPHPPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,281 @@
namespace GrumPHP\Composer;

use Composer\Composer;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\EventDispatcher\EventSubscriberInterface;
use Composer\Installer\PackageEvent;
use Composer\Installer\PackageEvents;
use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Plugin\PluginInterface;
use Composer\Script\Event;
use Composer\Script\ScriptEvents;

class GrumPHPPlugin implements PluginInterface
class GrumPHPPlugin implements PluginInterface, EventSubscriberInterface
{
public function activate(Composer $composer, IOInterface $io)
private const PACKAGE_NAME = 'phpro/grumphp';
private const APP_NAME = 'grumphp';
private const COMMAND_CONFIGURE = 'configure';
private const COMMAND_INIT = 'git:init';
private const COMMAND_DEINIT = 'git:deinit';

/**
* @var Composer
*/
private $composer;

/**
* @var IOInterface
*/
private $io;

/**
* @var bool
*/
private $handledPackageEvent = false;

/**
* @var bool
*/
private $configureScheduled = false;

/**
* @var bool
*/
private $initScheduled = false;

/**
* @var bool
*/
private $hasBeenRemoved = false;

/**
* {@inheritdoc}
*/
public function activate(Composer $composer, IOInterface $io): void
{
$this->composer = $composer;
$this->io = $io;
}

/**
* Attach package installation events:.
*
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array
{
return [
PackageEvents::PRE_PACKAGE_INSTALL => 'detectGrumphpAction',
PackageEvents::POST_PACKAGE_INSTALL => 'detectGrumphpAction',
PackageEvents::PRE_PACKAGE_UPDATE => 'detectGrumphpAction',
PackageEvents::PRE_PACKAGE_UNINSTALL => 'detectGrumphpAction',
ScriptEvents::POST_INSTALL_CMD => 'runScheduledTasks',
ScriptEvents::POST_UPDATE_CMD => 'runScheduledTasks',
];
}

/**
* This method can be called by pre/post package events;
* We make sure to only run it once. This way Grumphp won't execute multiple times.
* The goal is to run it as fast as possible.
* For first install, this should also happen on POST install (because otherwise the plugin doesn't exist yet)
*/
public function detectGrumphpAction(PackageEvent $event): void
{
if ($this->handledPackageEvent || !$this->guardPluginIsEnabled()) {
$this->handledPackageEvent = true;
return;
}

$this->handledPackageEvent = true;

$grumPhpOperations = $this->detectGrumphpOperations($event->getOperations());
if (!count($grumPhpOperations)) {
return;
}

// Check all GrumPHP operations to see if they are unanimously removing GrumPHP
// For example: an update might trigger an uninstall first - but we don't care about that.
$removalScheduled = array_reduce(
$grumPhpOperations,
function (?bool $theVote, OperationInterface $operation): bool {
$myVote = $operation instanceof UninstallOperation;

return null === $theVote ? $myVote : ($theVote && $myVote);
},
null
);

// Remove immediately once when we are positive about removal. (now that our dependencies are still there)
if ($removalScheduled) {
$this->runGrumPhpCommand(self::COMMAND_DEINIT);
$this->hasBeenRemoved = true;
return;
}

// Schedule install at the end of the process if we don't need to uninstall
$this->initScheduled = true;
$this->configureScheduled = true;
}

/**
* Runs the scheduled tasks after an update / install command.
*/
public function runScheduledTasks(Event $event): void
{
if ($this->configureScheduled) {
$this->runGrumPhpCommand(self::COMMAND_CONFIGURE);
}

if ($this->initScheduled) {
$this->runGrumPhpCommand(self::COMMAND_INIT);
}
}

/**
* @param OperationInterface[] $operations
*
* @return OperationInterface[]
*/
private function detectGrumphpOperations(array $operations): array
{
return array_values(array_filter(
$operations,
function (OperationInterface $operation): bool {
$package = $this->detectOperationPackage($operation);
return $this->guardIsGrumPhpPackage($package);
}
));
}

private function detectOperationPackage(OperationInterface $operation): ?PackageInterface
{
switch (true) {
case $operation instanceof UpdateOperation:
return $operation->getTargetPackage();
case $operation instanceof InstallOperation:
case $operation instanceof UninstallOperation:
return $operation->getPackage();
default:
return null;
}
}

/**
* This method also detects aliases / replaces statements which makes grumphp-shim possible.
*/
private function guardIsGrumPhpPackage(?PackageInterface $package): bool
{
if (!$package) {
return false;
}

$normalizedNames = array_map('strtolower', $package->getNames());

return in_array(self::PACKAGE_NAME, $normalizedNames, true);
}

private function guardPluginIsEnabled(): bool
{
$extra = $this->composer->getPackage()->getExtra();

return !(bool) ($extra['grumphp']['disable-plugin'] ?? false);
}

private function runGrumPhpCommand(string $command): void
{
if (!$grumphp = $this->detectGrumphpExecutable()) {
$this->pluginErrored('no-exectuable');
return;
}

// Respect composer CLI settings
$ansi = $this->io->isDecorated() ? '--ansi' : '--no-ansi';
$silent = $command === self::COMMAND_CONFIGURE ? '--silent' : '';
$interaction = $this->io->isInteractive() ? '' : '--no-interaction';

// Windows requires double double quotes
// https://bugs.php.net/bug.php?id=49139
$windowsIsInsane = function (string $command) {
return $this->runsOnWindows() ? '"'.$command.'"' : $command;
};

// Run command
$process = @proc_open(
$run = $windowsIsInsane(implode(' ', array_map(
function (string $argument): string {
return escapeshellarg($argument);
},
array_filter([$grumphp, $command, $ansi, $silent, $interaction])
))),
// Map process to current io
$descriptorspec = array(
0 => array('file', 'php://stdin', 'r'),
1 => array('file', 'php://stdout', 'w'),
2 => array('file', 'php://stderr', 'w'),
),
$pipes = []
);

// Check executable which is running:
if ($this->io->isVerbose()) {
$this->io->write('Running process : '.$run);
}

if (!is_resource($process)) {
$this->pluginErrored('no-process');
return;
}

// Loop on process until it exits normally.
do {
$status = proc_get_status($process);
} while ($status && $status['running']);

$exitCode = $status['exitcode'] ?? -1;
proc_close($process);

if ($exitCode !== 0) {
$this->pluginErrored('invalid-exit-code');
return;
}
}

private function detectGrumphpExecutable(): ?string
{
$config = $this->composer->getConfig();
$binDir = $this->ensurePlatformSpecificDirectorySeparator((string) $config->get('bin-dir'));
$suffixes = $this->runsOnWindows() ? ['.bat', ''] : ['.phar', ''];

return array_reduce(
$suffixes,
function (?string $carry, string $suffix) use ($binDir): ?string {
$possiblePath = $binDir.DIRECTORY_SEPARATOR.self::APP_NAME.$suffix;
if ($carry || !file_exists($possiblePath)) {
return $carry;
}

return $possiblePath;
}
);
}

private function runsOnWindows(): bool
{
return defined('PHP_WINDOWS_VERSION_BUILD');
}

private function ensurePlatformSpecificDirectorySeparator(string $path): string
{
return str_replace('/', DIRECTORY_SEPARATOR, $path);
}

private function pluginErrored(string $reason)
{
$io->write('<fg=yellow>GrumPHP shim registered. you\'ll need to activate grumphp manually for now:</fg=yellow>');
$io->write('Optionally: <fg=green>./vendor/bin/grumphp configure</fg=green>');
$io->write('Init hooks: <fg=green>./vendor/bin/grumphp git:init</fg=green>');
$this->io->writeError('<fg=red>GrumPHP can not sniff your commits! ('.$reason.')</fg=red>');
}
}

0 comments on commit 9bd3bd7

Please sign in to comment.