makeRepo.php 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. <?php
  2. require __DIR__ . '/../Maintenance.php';
  3. class HHVMMakeRepo extends Maintenance {
  4. function __construct() {
  5. parent::__construct();
  6. $this->addDescription( 'Compile PHP sources for this MediaWiki instance, ' .
  7. 'and generate an HHVM bytecode file to be used with HHVM\'s ' .
  8. 'RepoAuthoritative mode. The MediaWiki core installation path and ' .
  9. 'all registered extensions are automatically searched for the file ' .
  10. 'extensions *.php, *.inc, *.php5 and *.phtml.' );
  11. $this->addOption( 'output', 'Output filename', true, true, 'o' );
  12. $this->addOption( 'input-dir', 'Add an input directory. ' .
  13. 'This can be specified multiple times.', false, true, 'd', true );
  14. $this->addOption( 'exclude-dir', 'Directory to exclude. ' .
  15. 'This can be specified multiple times.', false, true, false, true );
  16. $this->addOption( 'extension', 'Extra file extension', false, true, false, true );
  17. $this->addOption( 'hhvm', 'Location of HHVM binary', false, true );
  18. $this->addOption( 'base-dir', 'The root of all source files. ' .
  19. 'This must match hhvm.server.source_root in the server\'s configuration file. ' .
  20. 'By default, the MW core install path will be used.',
  21. false, true );
  22. $this->addOption( 'verbose', 'Log level 0-3', false, true, 'v' );
  23. }
  24. private static function startsWith( $subject, $search ) {
  25. return substr( $subject, 0, strlen( $search ) === $search );
  26. }
  27. function execute() {
  28. global $wgExtensionCredits, $IP;
  29. $dirs = [ $IP ];
  30. foreach ( $wgExtensionCredits as $type => $extensions ) {
  31. foreach ( $extensions as $extension ) {
  32. if ( isset( $extension['path'] )
  33. && !self::startsWith( $extension['path'], $IP )
  34. ) {
  35. $dirs[] = dirname( $extension['path'] );
  36. }
  37. }
  38. }
  39. $dirs = array_merge( $dirs, $this->getOption( 'input-dir', [] ) );
  40. $fileExts =
  41. [
  42. 'php' => true,
  43. 'inc' => true,
  44. 'php5' => true,
  45. 'phtml' => true
  46. ] +
  47. array_flip( $this->getOption( 'extension', [] ) );
  48. $dirs = array_unique( $dirs );
  49. $baseDir = $this->getOption( 'base-dir', $IP );
  50. $excludeDirs = array_map( 'realpath', $this->getOption( 'exclude-dir', [] ) );
  51. if ( $baseDir !== '' && substr( $baseDir, -1 ) !== '/' ) {
  52. $baseDir .= '/';
  53. }
  54. $unfilteredFiles = [ "$IP/LocalSettings.php" ];
  55. foreach ( $dirs as $dir ) {
  56. $this->appendDir( $unfilteredFiles, $dir );
  57. }
  58. $files = [];
  59. foreach ( $unfilteredFiles as $file ) {
  60. $dotPos = strrpos( $file, '.' );
  61. $slashPos = strrpos( $file, '/' );
  62. if ( $dotPos === false || $slashPos === false || $dotPos < $slashPos ) {
  63. continue;
  64. }
  65. $extension = substr( $file, $dotPos + 1 );
  66. if ( !isset( $fileExts[$extension] ) ) {
  67. continue;
  68. }
  69. $canonical = realpath( $file );
  70. foreach ( $excludeDirs as $excluded ) {
  71. if ( self::startsWith( $canonical, $excluded ) ) {
  72. continue 2;
  73. }
  74. }
  75. if ( self::startsWith( $file, $baseDir ) ) {
  76. $file = substr( $file, strlen( $baseDir ) );
  77. }
  78. $files[] = $file;
  79. }
  80. $files = array_unique( $files );
  81. print "Found " . count( $files ) . " files in " .
  82. count( $dirs ) . " directories\n";
  83. $tmpDir = wfTempDir() . '/mw-make-repo' . mt_rand( 0, 1<<31 );
  84. if ( !mkdir( $tmpDir ) ) {
  85. $this->error( 'Unable to create temporary directory', 1 );
  86. }
  87. file_put_contents( "$tmpDir/file-list", implode( "\n", $files ) );
  88. $hhvm = $this->getOption( 'hhvm', 'hhvm' );
  89. $verbose = $this->getOption( 'verbose', 3 );
  90. $cmd = wfEscapeShellArg(
  91. $hhvm,
  92. '--hphp',
  93. '--target', 'hhbc',
  94. '--format', 'binary',
  95. '--force', '1',
  96. '--keep-tempdir', '1',
  97. '--log', $verbose,
  98. '-v', 'AllVolatile=true',
  99. '--input-dir', $baseDir,
  100. '--input-list', "$tmpDir/file-list",
  101. '--output-dir', $tmpDir );
  102. print "$cmd\n";
  103. passthru( $cmd, $ret );
  104. if ( $ret ) {
  105. $this->cleanupTemp( $tmpDir );
  106. $this->error( "Error: HHVM returned error code $ret", 1 );
  107. }
  108. if ( !rename( "$tmpDir/hhvm.hhbc", $this->getOption( 'output' ) ) ) {
  109. $this->cleanupTemp( $tmpDir );
  110. $this->error( "Error: unable to rename output file", 1 );
  111. }
  112. $this->cleanupTemp( $tmpDir );
  113. return 0;
  114. }
  115. private function cleanupTemp( $tmpDir ) {
  116. if ( file_exists( "$tmpDir/hhvm.hhbc" ) ) {
  117. unlink( "$tmpDir/hhvm.hhbc" );
  118. }
  119. if ( file_exists( "$tmpDir/Stats.js" ) ) {
  120. unlink( "$tmpDir/Stats.js" );
  121. }
  122. unlink( "$tmpDir/file-list" );
  123. rmdir( $tmpDir );
  124. }
  125. private function appendDir( &$files, $dir ) {
  126. $iter = new RecursiveIteratorIterator(
  127. new RecursiveDirectoryIterator(
  128. $dir,
  129. FilesystemIterator::UNIX_PATHS
  130. ),
  131. RecursiveIteratorIterator::LEAVES_ONLY
  132. );
  133. foreach ( $iter as $file => $fileInfo ) {
  134. if ( $fileInfo->isFile() ) {
  135. $files[] = $file;
  136. }
  137. }
  138. }
  139. }
  140. $maintClass = 'HHVMMakeRepo';
  141. require RUN_MAINTENANCE_IF_MAIN;