mirror of
https://github.com/yiisoft/yii2.git
synced 2025-08-26 06:15:19 +08:00
release controller
This commit is contained in:
@ -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;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user