Skip to content

Commit

Permalink
Add self:update command to update Robo phar distributions to the late…
Browse files Browse the repository at this point in the history
…st available version on GitHub.
  • Loading branch information
amenk authored and greg-1-anderson committed Sep 6, 2017
1 parent 19c7a18 commit a417652
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 9 deletions.
15 changes: 11 additions & 4 deletions docs/framework.md
Expand Up @@ -42,18 +42,22 @@ if ($pharPath) {
}
}

$output = new \Symfony\Component\Console\Output\ConsoleOutput();

$commandClasses = [ \MyProject\Commands\RoboFile::class ];
$statusCode = \Robo\Robo::run(
$_SERVER['argv'],
$commandClasses,
'MyAppName',
'0.0.0-alpha0'
'0.0.0-alpha0',
$output,
'org/project'
);
exit($statusCode);
```
When using Robo as a framework, the Robo file should be included in the autoloader, as Robo does not include a `RoboFile.php` file when used in this mode. Instead, specify the class or classes to load as a parameter to the Robo::run() method. By default, all output will be sent to a Symfony ConsoleOutput() that Robo will create for you. If you would like to use some other OutputInterface to capture the output, it may be specified via an optional fifth parameter.

Use [box-project/box2](https://github.com/box-project/box2) to create a phar for your application. Note that if you use Robo's taskPackPhar to create your phar, then `\Phar::running()` will always return an empty string due to a bug in this phar builder. If you encounter any problems with this, then hardcode the path to your autoload file. See the [robo](https://github.com/consolidation-org/Robo/blob/master/robo) script for details.
Use [box-project/box2](https://github.com/box-project/box2) or Robo's taskPackPhar to create a phar for your application. If your application's repository is hosted on GitHub, then passing the appropriate GitHub `org/project` to the `\Robo\Robo::run()` method, as shown above, will enable the `self:update` command to automatically update to the latest available version. Note that `self:update` only works with phar distributions.

## Using Multiple RoboFiles in a Standalone Application

Expand Down Expand Up @@ -106,6 +110,9 @@ use Symfony\Component\Console\Output\OutputInterface;

class MyApplication {

const APPLICATION_NAME = 'My Application';
const REPOSITORY = 'org/project';

use ConfigAwareTrait;

private $runner;
Expand All @@ -118,7 +125,7 @@ class MyApplication {

// Create applicaton.
$this->setConfig($config);
$application = new Application('My Application', $config->get('version'));
$application = new Application(self::APPLICATION_NAME, $config->get('version'));

// Create and configure container.
$container = Robo::createDefaultContainer($input, $output, $application,
Expand All @@ -131,6 +138,7 @@ class MyApplication {
My\Custom\Command::class
]);
$this->runner->setContainer($container);
$this->runner->setSelfUpdateRepository(self::REPOSITORY);
}

public function run(InputInterface $input, OutputInterface $output) {
Expand All @@ -140,7 +148,6 @@ class MyApplication {
}

}

```

If you are using League\Container (recommended), then you may simply add and share your own classes to the same container. If you are using some other DI container, then you should use [delegate lookup](https://github.com/container-interop/fig-standards/blob/master/proposed/container.md#14-additional-feature-delegate-lookup) to combine them.
Expand Down
1 change: 1 addition & 0 deletions robo
Expand Up @@ -17,5 +17,6 @@ if (strpos(basename(__FILE__), 'phar')) {
}
}
$runner = new \Robo\Runner();
$runner->setSelfUpdateRepository('consolidation/robo');
$statusCode = $runner->execute($_SERVER['argv']);
exit($statusCode);
14 changes: 14 additions & 0 deletions src/Application.php
Expand Up @@ -56,4 +56,18 @@ public function addInitRoboFileCommand($roboFile, $roboClass)
});
$this->add($createRoboFile);
}

/**
* Add self update command, do nothing if null is provided
*
* @param string $repository GitHub Repository for self update
*/
public function addSelfUpdateCommand($repository = null)
{
if (!$repository) {
return;
}
$selfUpdateCommand = new SelfUpdateCommand($this->getName(), $this->getVersion(), $repository);
$this->add($selfUpdateCommand);
}
}
3 changes: 2 additions & 1 deletion src/Robo.php
Expand Up @@ -39,9 +39,10 @@ class Robo
*
* @return int
*/
public static function run($argv, $commandClasses, $appName = null, $appVersion = null, $output = null)
public static function run($argv, $commandClasses, $appName = null, $appVersion = null, $output = null, $repository = null)
{
$runner = new \Robo\Runner($commandClasses);
$runner->setSelfUpdateRepository($repository);
$statusCode = $runner->execute($argv, $appName, $appVersion, $output);
return $statusCode;
}
Expand Down
32 changes: 28 additions & 4 deletions src/Runner.php
Expand Up @@ -38,6 +38,11 @@ class Runner implements ContainerAwareInterface
*/
protected $errorConditions = [];

/**
* @var string GitHub Repo for SelfUpdate
*/
protected $selfUpdateRepository = null;

/**
* Class Constructor
*
Expand Down Expand Up @@ -156,10 +161,13 @@ public function run($input = null, $output = null, $app = null, $commandFiles =
if (!$app) {
$app = Robo::application();
}
if (!isset($commandFiles)) {
$this->errorCondtion("Robo is not initialized here. Please run `robo init` to create a new RoboFile.", 'yellow');
$app->addInitRoboFileCommand($this->roboFile, $this->roboClass);
$commandFiles = [];
if ($app instanceof \Robo\Application) {
$app->addSelfUpdateCommand($this->getSelfUpdateRepository());
if (!isset($commandFiles)) {
$this->errorCondtion("Robo is not initialized here. Please run `robo init` to create a new RoboFile.", 'yellow');
$app->addInitRoboFileCommand($this->roboFile, $this->roboClass);
$commandFiles = [];
}
}
$this->registerCommandClasses($app, $commandFiles);

Expand Down Expand Up @@ -438,4 +446,20 @@ public function handleError()
}
return false;
}

/**
* @return string
*/
public function getSelfUpdateRepository()
{
return $this->selfUpdateRepository;
}

/**
* @param string $selfUpdateRepository
*/
public function setSelfUpdateRepository($selfUpdateRepository)
{
$this->selfUpdateRepository = $selfUpdateRepository;
}
}
154 changes: 154 additions & 0 deletions src/SelfUpdateCommand.php
@@ -0,0 +1,154 @@
<?php

namespace Robo;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Filesystem\Filesystem as sfFilesystem;

/**
* Update the robo.phar from the latest github release
*
* @author Alexander Menk <alex.menk@gmail.com>
*/
class SelfUpdateCommand extends Command
{
const SELF_UPDATE_COMMAND_NAME = 'self:update';

private $command;

protected $gitHubRepository;

protected $currentVersion;

protected $applicationName;

public function __construct($applicationName = null, $currentVersion = null, $gitHubRepository = null)
{
parent::__construct(self::SELF_UPDATE_COMMAND_NAME);

$this->applicationName = $applicationName;
$this->currentVersion = $currentVersion;
$this->gitHubRepository = $gitHubRepository;
}

/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setAliases(array('update'))
->setDescription('Updates the robo.phar to the latest version.')
->setHelp(
<<<EOT
The <info>self-update</info> command checks github for newer
versions of robo and if found, installs the latest.
EOT
);
}

protected function getLatestReleaseFromGithub()
{
$opts = [
'http' => [
'method' => 'GET',
'header' => [
'User-Agent: ' . $this->applicationName . ' (' . $this->gitHubRepository . ')' . ' Self-Update (PHP)'
]
]
];

$context = stream_context_create($opts);

$releases = file_get_contents('https://api.github.com/repos/' . $this->gitHubRepository . '/releases', false, $context);
$releases = json_decode($releases);

if (! isset($releases[0])) {
throw new \Exception('API error - no release found at GitHub repository ' . $this->gitHubRepository);
}

$version = $releases[0]->tag_name;
$url = $releases[0]->assets[0]->browser_download_url;

return [ $version, $url ];
}

/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
if (empty(\Phar::running())) {
throw new \Exception(self::SELF_UPDATE_COMMAND_NAME . ' only works when running the phar version of ' . $this->applicationName . '.');
}

$localFilename = realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0];
$programName = basename($localFilename);
$tempFilename = dirname($localFilename) . '/' . basename($localFilename, '.phar') . '-temp.phar';

// check for permissions in local filesystem before start connection process
if (! is_writable($tempDirectory = dirname($tempFilename))) {
throw new \Exception(
$programName . ' update failed: the "' . $tempDirectory .
'" directory used to download the temp file could not be written'
);
}

if (! is_writable($localFilename)) {
throw new \Exception(
$programName . ' update failed: the "' . $localFilename . '" file could not be written (execute with sudo)'
);
}

list( $latest, $downloadUrl ) = $this->getLatestReleaseFromGithub();


if ($this->currentVersion == $latest) {
$output->writeln('No update available');
return;
}

$fs = new sfFilesystem();

$output->writeln('Downloading ' . $this->applicationName . ' (' . $this->gitHubRepository . ') ' . $latest);

$fs->copy($downloadUrl, $tempFilename);

$output->writeln('Download finished');

try {
\error_reporting(E_ALL); // supress notices

@chmod($tempFilename, 0777 & ~umask());
// test the phar validity
$phar = new \Phar($tempFilename);
// free the variable to unlock the file
unset($phar);
@rename($tempFilename, $localFilename);
$output->writeln('<info>Successfully updated ' . $programName . '</info>');
$this->_exit();
} catch (\Exception $e) {
@unlink($tempFilename);
if (! $e instanceof \UnexpectedValueException && ! $e instanceof \PharException) {
throw $e;
}
$output->writeln('<error>The download is corrupted (' . $e->getMessage() . ').</error>');
$output->writeln('<error>Please re-run the self-update command to try again.</error>');
}
}

/**
* Stop execution
*
* This is a workaround to prevent warning of dispatcher after replacing
* the phar file.
*
* @return void
*/
protected function _exit()
{
exit;
}
}

0 comments on commit a417652

Please sign in to comment.