The clean & modern RSS server that doesn't give you any crap. https://thearsse.com/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

250 lines
10KB

  1. <?php
  2. use Robo\Result;
  3. const BASE = __DIR__.\DIRECTORY_SEPARATOR;
  4. const BASE_TEST = BASE."tests".\DIRECTORY_SEPARATOR;
  5. define("IS_WIN", defined("PHP_WINDOWS_VERSION_MAJOR"));
  6. define("IS_MAC", php_uname("s") === "Darwin");
  7. function norm(string $path): string {
  8. $out = realpath($path);
  9. if (!$out) {
  10. $out = str_replace(["/", "\\"], \DIRECTORY_SEPARATOR, $path);
  11. }
  12. return $out;
  13. }
  14. class RoboFile extends \Robo\Tasks {
  15. /** Runs the typical test suite
  16. *
  17. * Arguments passed to the task are passed on to PHPUnit. Thus one may, for
  18. * example, run the following command and get the expected results:
  19. *
  20. * ./robo test --testsuite TTRSS --exclude-group slow --testdox
  21. *
  22. * Please see the PHPUnit documentation for available options.
  23. */
  24. public function test(array $args): Result {
  25. return $this->runTests(escapeshellarg(\PHP_BINARY), "typical", $args);
  26. }
  27. /** Runs the full test suite
  28. *
  29. * This includes pedantic tests which may help to identify problems.
  30. * See help for the "test" task for more details.
  31. */
  32. public function testFull(array $args): Result {
  33. return $this->runTests(escapeshellarg(\PHP_BINARY), "full", $args);
  34. }
  35. /**
  36. * Runs a quick subset of the test suite
  37. *
  38. * See help for the "test" task for more details.
  39. */
  40. public function testQuick(array $args): Result {
  41. return $this->runTests(escapeshellarg(\PHP_BINARY), "quick", $args);
  42. }
  43. /** Produces a code coverage report
  44. *
  45. * By default this task produces an HTML-format coverage report in
  46. * tests/coverage/. Additional reports may be produced by passing
  47. * arguments to this task as one would to PHPUnit.
  48. *
  49. * Robo first tries to use pcov and will fall back first to xdebug then
  50. * phpdbg. Neither pcov nor xdebug need to be enabled to be used; they
  51. * only need to be present in the extension load path to be used.
  52. */
  53. public function coverage(array $args): Result {
  54. // run tests with code coverage reporting enabled
  55. $exec = $this->findCoverageEngine();
  56. return $this->runTests($exec, "coverage", array_merge(["--coverage-html", BASE_TEST."coverage"], $args));
  57. }
  58. /** Produces a code coverage report, with redundant tests
  59. *
  60. * Depending on the environment, some tests that normally provide
  61. * coverage may be skipped, while working alternatives are normally
  62. * suppressed for reasons of time. This coverage report will try to
  63. * run all tests which may cover code.
  64. *
  65. * See also help for the "coverage" task for more details.
  66. */
  67. public function coverageFull(array $args): Result {
  68. // run tests with code coverage reporting enabled
  69. $exec = $this->findCoverageEngine();
  70. return $this->runTests($exec, "typical", array_merge(["--coverage-html", BASE_TEST."coverage"], $args));
  71. }
  72. /** Runs the coding standards fixer */
  73. public function clean($opts = ['demo|d' => false]): Result {
  74. $t = $this->taskExec(norm(BASE."vendor/bin/php-cs-fixer"));
  75. $t->arg("fix");
  76. if ($opts['demo']) {
  77. $t->args("--dry-run", "--diff")->option("--diff-format", "udiff");
  78. }
  79. return $t->run();
  80. }
  81. protected function findCoverageEngine(): string {
  82. $dir = rtrim(ini_get("extension_dir"), "/").\DIRECTORY_SEPARATOR;
  83. $ext = IS_WIN ? "dll" : (IS_MAC ? "dylib" : "so");
  84. $php = escapeshellarg(\PHP_BINARY);
  85. $code = escapeshellarg(BASE."lib");
  86. if (extension_loaded("pcov")) {
  87. return "$php -d pcov.enabled=1 -d pcov.directory=$code";
  88. } elseif (extension_loaded("xdebug")) {
  89. return $php;
  90. } elseif (file_exists($dir."pcov.$ext")) {
  91. return "$php -d extension=pcov.$ext -d pcov.enabled=1 -d pcov.directory=$code";
  92. } elseif (file_exists($dir."pcov.$ext")) {
  93. return "$php -d zend_extension=xdebug.$ext";
  94. } else {
  95. if (IS_WIN) {
  96. $dbg = dirname(\PHP_BINARY)."\\phpdbg.exe";
  97. $dbg = file_exists($dbg) ? $dbg : "";
  98. } else {
  99. $dbg = trim(`which phpdbg 2>/dev/null`);
  100. }
  101. if ($dbg) {
  102. return escapeshellarg($dbg)." -qrr";
  103. } else {
  104. return $php;
  105. }
  106. }
  107. }
  108. protected function blackhole(bool $all = false): string {
  109. $hole = IS_WIN ? "nul" : "/dev/null";
  110. return $all ? ">$hole 2>&1" : "2>$hole";
  111. }
  112. protected function runTests(string $executor, string $set, array $args): Result {
  113. switch ($set) {
  114. case "typical":
  115. $set = ["--exclude-group", "optional"];
  116. break;
  117. case "quick":
  118. $set = ["--exclude-group", "optional,slow"];
  119. break;
  120. case "coverage":
  121. $set = ["--exclude-group", "optional,coverageOptional"];
  122. break;
  123. case "full":
  124. $set = [];
  125. break;
  126. default:
  127. throw new \Exception;
  128. }
  129. $execpath = norm(BASE."vendor-bin/phpunit/vendor/phpunit/phpunit/phpunit");
  130. $confpath = realpath(BASE_TEST."phpunit.dist.xml") ?: norm(BASE_TEST."phpunit.xml");
  131. $this->taskServer(8000)->host("localhost")->dir(BASE_TEST."docroot")->rawArg("-n")->arg(BASE_TEST."server.php")->rawArg($this->blackhole())->background()->run();
  132. return $this->taskExec($executor)->option("-d", "zend.assertions=1")->arg($execpath)->option("-c", $confpath)->args(array_merge($set, $args))->run();
  133. }
  134. /** Packages a given commit of the software into a release tarball
  135. *
  136. * The version to package may be any Git tree-ish identifier: a tag, a branch,
  137. * or any commit hash. If none is provided on the command line, Robo will prompt
  138. * for a commit to package; the default is "head".
  139. *
  140. * Note that while it is possible to re-package old versions, the resultant tarball
  141. * may not be equivalent due to subsequent changes in the exclude list, or because
  142. * of new tooling.
  143. */
  144. public function package(string $version = null): Result {
  145. // establish which commit to package
  146. $version = $version ?? $this->askDefault("Commit to package:", "HEAD");
  147. $archive = BASE."arsse-$version.tar.gz";
  148. // start a collection
  149. $t = $this->collectionBuilder();
  150. // create a temporary directory
  151. $dir = $t->tmpDir().\DIRECTORY_SEPARATOR;
  152. // create a Git worktree for the selected commit in the temp location
  153. $t->taskExec("git worktree add ".escapeshellarg($dir)." ".escapeshellarg($version));
  154. // perform Composer installation in the temp location with dev dependencies
  155. $t->taskComposerInstall()->dir($dir);
  156. // generate the manual
  157. $t->taskExec(escapeshellarg($dir."robo")." manual")->dir($dir);
  158. // perform Composer installation in the temp location for final output
  159. $t->taskComposerInstall()->dir($dir)->noDev()->optimizeAutoloader()->arg("--no-scripts");
  160. // delete unwanted files
  161. $t->taskFilesystemStack()->remove([
  162. $dir.".git",
  163. $dir.".gitignore",
  164. $dir.".gitattributes",
  165. $dir."composer.json",
  166. $dir."composer.lock",
  167. $dir.".php_cs.dist",
  168. $dir."phpdoc.dist.xml",
  169. $dir."build.xml",
  170. $dir."RoboFile.php",
  171. $dir."CONTRIBUTING.md",
  172. $dir."docs",
  173. $dir."tests",
  174. $dir."vendor-bin",
  175. $dir."vendor/bin",
  176. $dir."robo",
  177. $dir."robo.bat",
  178. $dir."package.json",
  179. $dir."yarn.lock",
  180. $dir."postcss.config.js",
  181. ]);
  182. // generate a sample configuration file
  183. $t->taskExec(escapeshellarg(\PHP_BINARY)." arsse.php conf save-defaults config.defaults.php")->dir($dir);
  184. // package it all up
  185. $t->taskPack($archive)->addDir("arsse", $dir);
  186. // execute the collection
  187. $out = $t->run();
  188. // clean the Git worktree list
  189. $this->_exec("git worktree prune");
  190. return $out;
  191. }
  192. /** Generates static manual pages in the "manual" directory
  193. *
  194. * The resultant files are suitable for offline viewing and inclusion into release builds
  195. */
  196. public function manual(array $args): Result {
  197. $execpath = escapeshellarg(norm(BASE."vendor/bin/daux"));
  198. $t = $this->collectionBuilder();
  199. $t->taskExec($execpath)->arg("generate")->option("-d", BASE."manual")->args($args);
  200. $t->taskDeleteDir(BASE."manual/daux_libraries");
  201. $t->taskDeleteDir(BASE."manual/theme");
  202. $t->taskDeleteDir(BASE."manual/themes/src");
  203. return $t->run();
  204. }
  205. /** Serves a live view of the manual using the built-in Web server */
  206. public function manualLive(array $args): Result {
  207. $execpath = escapeshellarg(norm(BASE."vendor/bin/daux"));
  208. return $this->taskExec($execpath)->arg("serve")->args($args)->run();
  209. }
  210. /** Rebuilds the entire manual theme
  211. *
  212. * This requires Node and Yarn to be installed, and only needs to be done when
  213. * Daux's theme changes
  214. */
  215. public function manualTheme(array $args): Result {
  216. $postcss = escapeshellarg(norm(BASE."node_modules/.bin/postcss"));
  217. $themesrc = norm(BASE."docs/theme/src/").\DIRECTORY_SEPARATOR;
  218. $themeout = norm(BASE."docs/theme/arsse/").\DIRECTORY_SEPARATOR;
  219. $dauxjs = norm(BASE."vendor-bin/daux/vendor/daux/daux.io/themes/daux/js/").\DIRECTORY_SEPARATOR;
  220. // start a collection; this stops after the first failure
  221. $t = $this->collectionBuilder();
  222. // install dependencies via Yarn
  223. $t->taskExec("yarn install");
  224. // compile the stylesheet
  225. $t->taskExec($postcss)->arg($themesrc."arsse.scss")->option("-o", $themeout."arsse.css");
  226. // copy JavaScript files from the Daux theme
  227. foreach (glob($dauxjs."daux*.js") as $file) {
  228. $t->taskFilesystemStack()->copy($file, $themeout.basename($file), true);
  229. }
  230. // execute the collection
  231. return $t->run();
  232. }
  233. }