release controller

This commit is contained in:
Carsten Brandt
2016-03-16 23:32:17 +01:00
parent 2f103ac5aa
commit 168c1cddfe

View File

@ -11,6 +11,7 @@ use Yii;
use yii\base\Exception;
use yii\console\Controller;
use yii\helpers\ArrayHelper;
use yii\helpers\Console;
/**
* ReleaseController is there to help preparing releases
@ -22,23 +23,78 @@ class ReleaseController extends Controller
{
public $defaultAction = 'help';
/**
* @var string base path to use for releases.
*/
public $basePath;
/**
* @var bool whether to do actual changes. If true, it will run without changing or pushing anything.
*/
public $dryRun = false;
public function options($actionID)
{
return array_merge(parent::options($actionID), ['basePath', 'dryRun']);
}
public function beforeAction($action)
{
if (!$this->interactive) {
throw new Exception('Sorry, but releases should be run interactive to ensure you actually verify what you are doing ;)');
}
if ($this->basePath === null) {
$this->basePath = dirname(dirname(__DIR__));
}
$this->basePath = rtrim($this->basePath, '\\/');
return parent::beforeAction($action);
}
/**
* Usage:
*
* ```
* ./build/build release/prepare framework 2.0.0-beta
* ./build/build release/prepare redis 2.0.0-beta
* ./build/build release/prepare framework
* ./build/build release/prepare redis,bootstrap,apidoc
* ```
*
*/
public function actionPrepare(array $what, $version)
public function actionPrepare(array $what)
{
$this->resortChangelogs($what, $version);
$this->closeChangelogs($what, $version);
$this->composerSetStability($what, $version);
if (in_array('framework', $what)) {
$this->updateYiiVersion($version);
if (count($what) > 1) {
$this->stdout("Currently only one simultaneous release is supported.\n");
return 1;
}
$this->stdout("This is the Yii release manager\n\n", Console::BOLD);
if ($this->dryRun) {
$this->stdout("Running in \"dry-run\" mode, nothing will actually be changed.\n\n", Console::BOLD, Console::FG_GREEN);
}
$this->validateWhat($what);
$versions = $this->getCurrentVersions($what);
$newVersions = $this->getNextVersions($versions, self::PATCH);// TODO add support for minor
$this->stdout("You are about to prepare a new release for the following things:\n\n");
$this->printWhat($what, $newVersions, $versions);
$this->stdout("\n");
if (!$this->confirm('When you continue, this tool will run cleanup jobs and update the changelog as well as other files (locally). Continue?', false)) {
$this->stdout("Canceled.\n");
return 1;
}
foreach($what as $ext) {
if ($ext === 'framework') {
$this->releaseFramework("{$this->basePath}/framework", $newVersions['framework']);
} else {
$this->releaseExtension($ext, "{$this->basePath}/extensions/$ext", $newVersions[$ext]);
}
}
return 0;
}
/**
@ -58,6 +114,196 @@ class ReleaseController extends Controller
}
}
protected function printWhat(array $what, $newVersions, $versions)
{
foreach($what as $w) {
if ($w === 'framework') {
$this->stdout(" - Yii Framework version {$newVersions[$w]}, current latest release is {$versions[$w]}\n");
} else {
$this->stdout(" - $w extension version {$newVersions[$w]}, current latest release is {$versions[$w]}\n");
}
}
}
protected function validateWhat(array $what)
{
foreach($what as $w) {
if ($w === 'framework') {
if (!is_dir($fwPath = "{$this->basePath}/framework")) {
throw new Exception("Framework path does not exist: \"{$this->basePath}/framework\"\n");
}
$this->ensureGitClean($fwPath);
} else {
if (!is_dir($extPath = "{$this->basePath}/extensions/$w")) {
throw new Exception("Extension path for \"$w\" does not exist: \"{$this->basePath}/extensions/$w\"\n");
}
$this->ensureGitClean($extPath);
}
}
}
protected function ensureGitClean($path)
{
chdir($path);
exec('git status --porcelain -uno', $changes, $ret);
if ($ret != 0) {
throw new Exception('Command "git status --porcelain -uno" failed with code ' . $ret);
}
if (!empty($changes)) {
throw new Exception("You have uncommitted changes in $path: " . print_r($changes, true));
}
}
protected function releaseFramework($frameworkPath, $version)
{
$this->stdout("\n");
$this->stdout($h = "Preparing framework release version $version", Console::BOLD);
$this->stdout("\n" . str_repeat('-', strlen($h)) . "\n\n", Console::BOLD);
if ($this->confirm('Run `git checkout master`?', true)) {
chdir($frameworkPath);
exec('git checkout master', $output, $ret); // TODO add compatibility for other release branches
if ($ret != 0) {
throw new Exception('Command "git checkout master" failed with code ' . $ret);
}
}
if ($this->confirm('Run `git pull`?', true)) {
chdir($frameworkPath);
exec('git pull', $output, $ret);
if ($ret != 0) {
throw new Exception('Command "git pull" failed with code ' . $ret);
}
}
// checks
$this->stdout('check if framework composer.json matches yii2-dev composer.json...');
$this->checkComposer($frameworkPath);
$this->stdout("done.\n", Console::FG_GREEN, Console::BOLD);
// adjustments
$this->stdout('prepare classmap...');
$this->dryRun || Yii::$app->runAction('classmap', [$frameworkPath]);
$this->stdout("done.\n", Console::FG_GREEN, Console::BOLD);
$this->stdout('fixing various PHPdoc style issues...');
$this->dryRun || Yii::$app->runAction('php-doc/fix', [$frameworkPath]);
$this->stdout("done.\n", Console::FG_GREEN, Console::BOLD);
$this->stdout('updating PHPdoc @property annotations...');
$this->dryRun || Yii::$app->runAction('php-doc/property', [$frameworkPath]);
$this->stdout("done.\n", Console::FG_GREEN, Console::BOLD);
$this->stdout('updating mimetype magic file...');
$this->dryRun || Yii::$app->runAction('mime-type', ["$frameworkPath/helpers/mimeTypes.php"]);
$this->stdout("done.\n", Console::FG_GREEN, Console::BOLD);
$this->stdout('sorting changelogs...');
$this->dryRun || $this->resortChangelogs(['framework'], $version);
$this->stdout("done.\n", Console::FG_GREEN, Console::BOLD);
$this->stdout('closing changelogs...');
$this->dryRun || $this->closeChangelogs(['framework'], $version);
$this->stdout("done.\n", Console::FG_GREEN, Console::BOLD);
$this->stdout('updating Yii version...');
$this->dryRun || $this->updateYiiVersion($frameworkPath, $version);;
$this->stdout("done.\n", Console::FG_GREEN, Console::BOLD);
// TODO Commit and push
// TODO tag and push
// TODO release applications
// $this->composerSetStability($what, $version);
// $this->resortChangelogs($what, $version);
// $this->closeChangelogs($what, $version);
// $this->composerSetStability($what, $version);
// if (in_array('framework', $what)) {
// $this->updateYiiVersion($version);
// }
}
protected function releaseExtension($name, $path, $version)
{
$this->stdout("\n");
$this->stdout($h = "Preparing release for extension $name version $version", Console::BOLD);
$this->stdout("\n" . str_repeat('-', strlen($h)) . "\n\n", Console::BOLD);
if ($this->confirm('Run `git checkout master`?', true)) {
chdir($path);
exec('git checkout master', $output, $ret); // TODO add compatibility for other release branches
if ($ret != 0) {
throw new Exception('Command "git checkout master" failed with code ' . $ret);
}
}
if ($this->confirm('Run `git pull`?', true)) {
chdir($path);
exec('git pull', $output, $ret);
if ($ret != 0) {
throw new Exception('Command "git pull" failed with code ' . $ret);
}
}
// adjustments
$this->stdout('fixing various PHPdoc style issues...');
$this->dryRun || Yii::$app->runAction('php-doc/fix', [$path]);
$this->stdout("done.\n", Console::FG_GREEN, Console::BOLD);
$this->stdout('updating PHPdoc @property annotations...');
$this->dryRun || Yii::$app->runAction('php-doc/property', [$path]);
$this->stdout("done.\n", Console::FG_GREEN, Console::BOLD);
$this->stdout('sorting changelogs...');
$this->dryRun || $this->resortChangelogs([$name], $version);
$this->stdout("done.\n", Console::FG_GREEN, Console::BOLD);
$this->stdout('closing changelogs...');
$this->dryRun || $this->closeChangelogs([$name], $version);
$this->stdout("done.\n", Console::FG_GREEN, Console::BOLD);
do {
$this->runGit("git diff", $path);
} while(!$this->confirm('continue?'));
$this->runGit("git commit -a -m \"release version $version\"", $path);
$this->runGit("git push", $path);
$this->runGit("git tag -a $version -m\"version $version\"", $path);
$this->runGit("git push --tags", $path);
}
protected function runGit($cmd, $path)
{
if ($this->confirm("Run `$cmd`?", true)) {
chdir($path);
exec($cmd, $output, $ret);
echo implode("\n", $output);
if ($ret != 0) {
throw new Exception("Command \"$cmd\" failed with code " . $ret);
}
}
}
protected function checkComposer($fwPath)
{
if (!$this->confirm("\nNot yet automated: Please check if composer.json dependencies in framework dir match the on in repo root. Continue?", false)) {
exit;
}
}
protected function closeChangelogs($what, $version)
{
$v = str_replace('\\-', '[\\- ]', preg_quote($version, '/'));
@ -162,12 +408,12 @@ class ReleaseController extends Controller
protected function getFrameworkChangelog()
{
return YII2_PATH . '/CHANGELOG.md';
return $this->basePath . '/framework/CHANGELOG.md';
}
protected function getExtensionChangelogs($what)
{
return array_filter(glob(dirname(YII2_PATH) . '/extensions/*/CHANGELOG.md'), function($elem) use ($what) {
return array_filter(glob($this->basePath . '/extensions/*/CHANGELOG.md'), function($elem) use ($what) {
foreach($what as $ext) {
if (strpos($elem, "extensions/$ext/CHANGELOG.md") !== false) {
return true;
@ -181,13 +427,13 @@ class ReleaseController extends Controller
{
$apps = [];
if (in_array('app-advanced', $what)) {
$apps[] = dirname(YII2_PATH) . '/apps/advanced/composer.json';
$apps[] = $this->basePath . '/apps/advanced/composer.json';
}
if (in_array('app-basic', $what)) {
$apps[] = dirname(YII2_PATH) . '/apps/basic/composer.json';
$apps[] = $this->basePath . '/apps/basic/composer.json';
}
if (in_array('app-benchmark', $what)) {
$apps[] = dirname(YII2_PATH) . '/apps/benchmark/composer.json';
$apps[] = $this->basePath . '/apps/benchmark/composer.json';
}
if (empty($apps)) {
return;
@ -211,12 +457,12 @@ class ReleaseController extends Controller
);
}
protected function updateYiiVersion($version)
protected function updateYiiVersion($frameworkPath, $version)
{
$this->sed(
'/function getVersion\(\)\n \{\n return \'(.+?)\';/',
"function getVersion()\n {\n return '$version';",
YII2_PATH . '/BaseYii.php');
$frameworkPath . '/BaseYii.php');
}
protected function sed($pattern, $replace, $files)
@ -225,4 +471,46 @@ class ReleaseController extends Controller
file_put_contents($file, preg_replace($pattern, $replace, file_get_contents($file)));
}
}
protected function getCurrentVersions(array $what)
{
$versions = [];
foreach($what as $ext) {
if ($ext === 'framework') {
chdir("{$this->basePath}/framework");
} else {
chdir("{$this->basePath}/extensions/$ext");
}
exec('git tag', $tags, $ret);
if ($ret != 0) {
throw new Exception('Command "git tag" failed with code ' . $ret);
}
rsort($tags, SORT_NATURAL); // TODO this can not deal with alpha/beta/rc...
$versions[$ext] = reset($tags);
}
print_r($versions);
return $versions;
}
const MINOR = 'minor';
const PATCH = 'patch';
protected function getNextVersions(array $versions, $type)
{
foreach($versions as $k => $v) {
$parts = explode('.', $v);
switch($type) {
case self::MINOR:
$parts[1]++;
break;
case self::PATCH:
$parts[2]++;
break;
default:
throw new Exception('Unknown version type.');
}
$versions[$k] = implode('.', $parts);
}
return $versions;
}
}