PageRenderTime 29ms CodeModel.GetById 0ms RepoModel.GetById 0ms app.codeStats 0ms

/build/controllers/DevController.php

https://gitlab.com/brucealdridge/yii2
PHP | 367 lines | 252 code | 33 blank | 82 comment | 28 complexity | 980933269ac055d8e71a7bb7d6ae2b63 MD5 | raw file
  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\build\controllers;
  8. use Yii;
  9. use yii\base\InvalidParamException;
  10. use yii\console\Controller;
  11. use yii\helpers\Console;
  12. use yii\helpers\FileHelper;
  13. /**
  14. * This command helps to set up a dev environment with all extensions and applications
  15. *
  16. * It will clone an extension or app repo and link the yii2 dev installation to the containted applications/extensions vendor dirs
  17. * to help working on yii using the application to test it.
  18. *
  19. * @author Carsten Brandt <mail@cebe.cc>
  20. * @since 2.0
  21. */
  22. class DevController extends Controller
  23. {
  24. public $defaultAction = 'all';
  25. /**
  26. * @var bool whether to use HTTP when cloning github repositories
  27. */
  28. public $useHttp = false;
  29. public $apps = [
  30. 'basic' => 'git@github.com:yiisoft/yii2-app-basic.git',
  31. 'advanced' => 'git@github.com:yiisoft/yii2-app-advanced.git',
  32. 'benchmark' => 'git@github.com:yiisoft/yii2-app-benchmark.git',
  33. ];
  34. public $extensions = [
  35. 'apidoc' => 'git@github.com:yiisoft/yii2-apidoc.git',
  36. 'authclient' => 'git@github.com:yiisoft/yii2-authclient.git',
  37. 'bootstrap' => 'git@github.com:yiisoft/yii2-bootstrap.git',
  38. 'codeception' => 'git@github.com:yiisoft/yii2-codeception.git',
  39. 'composer' => 'git@github.com:yiisoft/yii2-composer.git',
  40. 'debug' => 'git@github.com:yiisoft/yii2-debug.git',
  41. 'elasticsearch' => 'git@github.com:yiisoft/yii2-elasticsearch.git',
  42. 'faker' => 'git@github.com:yiisoft/yii2-faker.git',
  43. 'gii' => 'git@github.com:yiisoft/yii2-gii.git',
  44. 'imagine' => 'git@github.com:yiisoft/yii2-imagine.git',
  45. 'jui' => 'git@github.com:yiisoft/yii2-jui.git',
  46. 'mongodb' => 'git@github.com:yiisoft/yii2-mongodb.git',
  47. 'redis' => 'git@github.com:yiisoft/yii2-redis.git',
  48. 'smarty' => 'git@github.com:yiisoft/yii2-smarty.git',
  49. 'sphinx' => 'git@github.com:yiisoft/yii2-sphinx.git',
  50. 'swiftmailer' => 'git@github.com:yiisoft/yii2-swiftmailer.git',
  51. 'twig' => 'git@github.com:yiisoft/yii2-twig.git',
  52. ];
  53. /**
  54. * Install all extensions and advanced + basic app
  55. */
  56. public function actionAll()
  57. {
  58. if (!$this->confirm('Install all applications and all extensions now?')) {
  59. return 1;
  60. }
  61. foreach($this->extensions as $ext => $repo) {
  62. $ret = $this->actionExt($ext);
  63. if ($ret !== 0) {
  64. return $ret;
  65. }
  66. }
  67. foreach($this->apps as $app => $repo) {
  68. $ret = $this->actionApp($app);
  69. if ($ret !== 0) {
  70. return $ret;
  71. }
  72. }
  73. return 0;
  74. }
  75. /**
  76. * Runs a command in all extension and application directories
  77. *
  78. * Can be used to run e.g. `git pull`.
  79. *
  80. * ./build/build dev/run git pull
  81. *
  82. * @param string $command the command to run
  83. */
  84. public function actionRun($command)
  85. {
  86. $command = implode(' ', func_get_args());
  87. // root of the dev repo
  88. $base = dirname(dirname(__DIR__));
  89. $dirs = $this->listSubDirs("$base/extensions");
  90. $dirs = array_merge($dirs, $this->listSubDirs("$base/apps"));
  91. asort($dirs);
  92. $oldcwd = getcwd();
  93. foreach($dirs as $dir) {
  94. $displayDir = substr($dir, strlen($base));
  95. $this->stdout("Running '$command' in $displayDir...\n", Console::BOLD);
  96. chdir($dir);
  97. passthru($command);
  98. $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);
  99. }
  100. chdir($oldcwd);
  101. }
  102. /**
  103. * This command installs a project template in the `apps` directory and links the framework and extensions
  104. *
  105. * It basically runs the following commands in the dev repo root:
  106. *
  107. * - Run `composer update`
  108. * - `rm -rf apps/basic/vendor/yiisoft/yii2`
  109. * - `rm -rf apps/basic/vendor/yiisoft/yii2-*`
  110. *
  111. * And replaces them with symbolic links to the extensions and framework path in the dev repo.
  112. *
  113. * Extensions required by the application are automatically installed using the `ext` action.
  114. *
  115. * @param string $app the application name e.g. `basic` or `advanced`.
  116. * @param string $repo url of the git repo to clone if it does not already exist.
  117. * @return int return code
  118. */
  119. public function actionApp($app, $repo = null)
  120. {
  121. // root of the dev repo
  122. $base = dirname(dirname(__DIR__));
  123. $appDir = "$base/apps/$app";
  124. if (!file_exists($appDir)) {
  125. if (empty($repo)) {
  126. if (isset($this->apps[$app])) {
  127. $repo = $this->apps[$app];
  128. if ($this->useHttp) {
  129. $repo = str_replace('git@github.com:', 'https://github.com/', $repo);
  130. }
  131. } else {
  132. $this->stderr("Repo argument is required for app '$app'.\n", Console::FG_RED);
  133. return 1;
  134. }
  135. }
  136. $this->stdout("cloning application repo '$app' from '$repo'...\n", Console::BOLD);
  137. passthru('git clone ' . escapeshellarg($repo) . ' ' . $appDir);
  138. $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);
  139. }
  140. // cleanup
  141. $this->stdout("cleaning up application '$app' vendor directory...\n", Console::BOLD);
  142. $this->cleanupVendorDir($appDir);
  143. $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);
  144. // composer update
  145. $this->stdout("updating composer for app '$app'...\n", Console::BOLD);
  146. chdir($appDir);
  147. passthru('composer update --prefer-dist');
  148. $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);
  149. // link directories
  150. $this->stdout("linking framework and extensions to '$app' app vendor dir...\n", Console::BOLD);
  151. $this->linkFrameworkAndExtensions($appDir, $base);
  152. $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);
  153. return 0;
  154. }
  155. /**
  156. * This command installs an extension in the `extensions` directory and links the framework and other extensions
  157. *
  158. * @param string $extension the application name e.g. `basic` or `advanced`.
  159. * @param string $repo url of the git repo to clone if it does not already exist.
  160. *
  161. * @return int
  162. */
  163. public function actionExt($extension, $repo = null)
  164. {
  165. // root of the dev repo
  166. $base = dirname(dirname(__DIR__));
  167. $extensionDir = "$base/extensions/$extension";
  168. if (!file_exists($extensionDir)) {
  169. if (empty($repo)) {
  170. if (isset($this->extensions[$extension])) {
  171. $repo = $this->extensions[$extension];
  172. if ($this->useHttp) {
  173. $repo = str_replace('git@github.com:', 'https://github.com/', $repo);
  174. }
  175. } else {
  176. $this->stderr("Repo argument is required for extension '$extension'.\n", Console::FG_RED);
  177. return 1;
  178. }
  179. }
  180. $this->stdout("cloning extension repo '$extension' from '$repo'...\n", Console::BOLD);
  181. passthru('git clone ' . escapeshellarg($repo) . ' ' . $extensionDir);
  182. $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);
  183. }
  184. // cleanup
  185. $this->stdout("cleaning up extension '$extension' vendor directory...\n", Console::BOLD);
  186. $this->cleanupVendorDir($extensionDir);
  187. $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);
  188. // composer update
  189. $this->stdout("updating composer for extension '$extension'...\n", Console::BOLD);
  190. chdir($extensionDir);
  191. passthru('composer update --prefer-dist');
  192. $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);
  193. // link directories
  194. $this->stdout("linking framework and extensions to '$extension' vendor dir...\n", Console::BOLD);
  195. $this->linkFrameworkAndExtensions($extensionDir, $base);
  196. $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN);
  197. return 0;
  198. }
  199. /**
  200. * @inheritdoc
  201. */
  202. public function options($actionID)
  203. {
  204. $options = parent::options($actionID);
  205. if (in_array($actionID, ['ext', 'app', 'all'], true)) {
  206. $options[] = 'useHttp';
  207. }
  208. return $options;
  209. }
  210. /**
  211. * Remove all symlinks in the vendor subdirectory of the directory specified
  212. * @param string $dir base directory
  213. */
  214. protected function cleanupVendorDir($dir)
  215. {
  216. if (is_link($link = "$dir/vendor/yiisoft/yii2")) {
  217. $this->stdout("Removing symlink $link.\n");
  218. $this->unlink($link);
  219. }
  220. $extensions = $this->findDirs("$dir/vendor/yiisoft");
  221. foreach($extensions as $ext) {
  222. if (is_link($link = "$dir/vendor/yiisoft/yii2-$ext")) {
  223. $this->stdout("Removing symlink $link.\n");
  224. $this->unlink($link);
  225. }
  226. }
  227. }
  228. /**
  229. * Creates symlinks to freamework and extension sources for the application
  230. * @param string $dir application directory
  231. * @param string $base Yii sources base directory
  232. *
  233. * @return int
  234. */
  235. protected function linkFrameworkAndExtensions($dir, $base)
  236. {
  237. if (is_dir($link = "$dir/vendor/yiisoft/yii2")) {
  238. $this->stdout("Removing dir $link.\n");
  239. FileHelper::removeDirectory($link);
  240. $this->stdout("Creating symlink for $link.\n");
  241. symlink("$base/framework", $link);
  242. }
  243. $extensions = $this->findDirs("$dir/vendor/yiisoft");
  244. foreach($extensions as $ext) {
  245. if (is_dir($link = "$dir/vendor/yiisoft/yii2-$ext")) {
  246. $this->stdout("Removing dir $link.\n");
  247. FileHelper::removeDirectory($link);
  248. $this->stdout("Creating symlink for $link.\n");
  249. if (!file_exists("$base/extensions/$ext")) {
  250. $ret = $this->actionExt($ext);
  251. if ($ret !== 0) {
  252. return $ret;
  253. }
  254. }
  255. symlink("$base/extensions/$ext", $link);
  256. }
  257. }
  258. }
  259. /**
  260. * Properly removes symlinked directory under Windows, MacOS and Linux
  261. *
  262. * @param string $file path to symlink
  263. */
  264. protected function unlink($file)
  265. {
  266. if (is_dir($file) && DIRECTORY_SEPARATOR === '\\') {
  267. rmdir($file);
  268. } else {
  269. unlink($file);
  270. }
  271. }
  272. /**
  273. * Get a list of subdirectories for directory specified
  274. * @param string $dir directory to read
  275. *
  276. * @return array list of subdirectories
  277. */
  278. protected function listSubDirs($dir)
  279. {
  280. $list = [];
  281. $handle = opendir($dir);
  282. if ($handle === false) {
  283. throw new InvalidParamException("Unable to open directory: $dir");
  284. }
  285. while (($file = readdir($handle)) !== false) {
  286. if ($file === '.' || $file === '..') {
  287. continue;
  288. }
  289. // ignore hidden directories
  290. if ($file[0] === '.') {
  291. continue;
  292. }
  293. if (is_dir("$dir/$file")) {
  294. $list[] = "$dir/$file";
  295. }
  296. }
  297. closedir($handle);
  298. return $list;
  299. }
  300. /**
  301. * Finds linkable applications
  302. *
  303. * @param string $dir directory to search in
  304. * @return array list of applications command can link
  305. */
  306. protected function findDirs($dir)
  307. {
  308. $list = [];
  309. $handle = @opendir($dir);
  310. if ($handle === false) {
  311. return [];
  312. }
  313. while (($file = readdir($handle)) !== false) {
  314. if ($file === '.' || $file === '..') {
  315. continue;
  316. }
  317. $path = $dir . DIRECTORY_SEPARATOR . $file;
  318. if (is_dir($path) && preg_match('/^yii2-(.*)$/', $file, $matches)) {
  319. $list[] = $matches[1];
  320. }
  321. }
  322. closedir($handle);
  323. foreach($list as $i => $e) {
  324. if ($e === 'composer') { // skip composer to not break composer update
  325. unset($list[$i]);
  326. }
  327. }
  328. return $list;
  329. }
  330. }