compile.php 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. <?php
  2. // explicitly give VERSION via ENV or ask git for current version
  3. $version = getenv('VERSION');
  4. if ($version === false) {
  5. $version = ltrim(exec('git describe --always --dirty', $unused, $code), 'v');
  6. if ($code !== 0) {
  7. fwrite(STDERR, 'Error: Unable to get version info from git. Try passing VERSION via ENV' . PHP_EOL);
  8. exit(1);
  9. }
  10. }
  11. // use first argument as output file or use "leproxy-{version}.php"
  12. $out = isset($argv[1]) ? $argv[1] : ('leproxy-' . $version . '.php');
  13. system('composer install --no-dev --classmap-authoritative');
  14. $classes = require __DIR__ . '/vendor/composer/autoload_classmap.php';
  15. $includes = require __DIR__ . '/vendor/composer/autoload_files.php';
  16. system('composer install');
  17. echo 'Loading ' . count($classes) . ' classes to determine load order...';
  18. // register autoloader which remembers load order
  19. $ordered = array();
  20. spl_autoload_register(function ($name) use ($classes, &$ordered) {
  21. require $classes[$name];
  22. $ordered[$name] = $classes[$name];
  23. });
  24. // use actual include file instead of include wrapper
  25. foreach ($includes as $i => $path) {
  26. $includes[$i] = str_replace('_include.php', '.php', $path);
  27. require $path;
  28. }
  29. // load each class (and its superclasses once) into memory
  30. foreach ($classes as $class => $path) {
  31. class_exists($class, true);
  32. }
  33. echo ' DONE' . PHP_EOL;
  34. // resulting list of all includes and ordered class list
  35. $files = array_merge($includes, $ordered);
  36. echo 'Concatenating ' . count($files) . ' files into ' . $out . '...';
  37. system('sed -n "/\#\!/,/^$/p" leproxy.php | sed -e "s/version dev/version ' . $version . '/" > ' . escapeshellarg($out));
  38. foreach ($files as $file) {
  39. $file = substr($file, strlen(__DIR__) + 1);
  40. system('(echo "# ' . $file . '"; grep -v "<?php" ' . escapeshellarg($file) . ') >> ' . escapeshellarg($out));
  41. }
  42. $file = 'leproxy.php';
  43. system('(echo "# ' . $file . '"; egrep -v "^<\?php|^require " ' . escapeshellarg($file) . ') | sed -e "s/development/release/;/define(\'VERSION\'/c const VERSION=\"' . $version . '\";" >> ' . escapeshellarg($out));
  44. chmod($out, 0755);
  45. echo ' DONE (' . filesize($out) . ' bytes)' . PHP_EOL;
  46. echo 'Optimizing resulting file...';
  47. $small = '';
  48. $all = token_get_all(file_get_contents($out));
  49. // search next non-whitespace/non-comment token
  50. $next = function ($i) use (&$all) {
  51. for ($i = $i + 1; !isset($all[$i]) || is_array($all[$i]) && ($all[$i][0] === T_COMMENT || $all[$i][0] === T_DOC_COMMENT || $all[$i][0] === T_WHITESPACE); ++$i);
  52. return $i;
  53. };
  54. // search previous non-whitespace/non-comment token
  55. $prev = function ($i) use (&$all) {
  56. for ($i = $i -1; $i >= 0 && (!isset($all[$i]) || (is_array($all[$i]) && ($all[$i][0] === T_COMMENT || $all[$i][0] === T_DOC_COMMENT || $all[$i][0] === T_WHITESPACE))); --$i);
  57. return $i;
  58. };
  59. $first = true;
  60. foreach ($all as $i => $token) {
  61. if (is_array($token) && ($token[0] === T_COMMENT || $token[0] === T_DOC_COMMENT)) {
  62. // remove all comments except first
  63. if ($first === true) {
  64. $first = false;
  65. continue;
  66. }
  67. unset($all[$i]);
  68. } elseif (is_array($token) && $token[0] === T_PUBLIC) {
  69. // get next non-whitespace token after `public` visibility
  70. $token = $all[$next($i)];
  71. if (is_array($token) && $token[0] === T_VARIABLE) {
  72. // use shorter variable notation `public $a` => `var $a`
  73. $all[$i] = array(T_VAR, 'var');
  74. } else {
  75. // remove unneeded public identifier `public static function a()` => `static function a()`
  76. unset($all[$i]);
  77. }
  78. } elseif (is_array($token) && $token[0] === T_LNUMBER) {
  79. // Use shorter integer notation `0x0F` => `15` and `011` => `9`.
  80. // Technically, hex codes may be shorter for very large ints, but adding
  81. // another 2 leading chars is rarely worth it.
  82. // Optimizing floats is not really worth it, as they have many special
  83. // cases, such as e-notation and we would lose types for `0.0` => `0`.
  84. $all[$i][1] = (string)intval($token[1], 0);
  85. } elseif (is_array($token) && $token[0] === T_NEW) {
  86. // remove unneeded parenthesis for constructors without args `new a();` => `new a;`
  87. // jump over next token (class name), then next must be open parenthesis, followed by closing
  88. $open = $next($next($i));
  89. $close = $next($open);
  90. if ($all[$open] === '(' && $all[$close] === ')') {
  91. unset($all[$open], $all[$close]);
  92. }
  93. } elseif (is_array($token) && $token[0] === T_STRING) {
  94. // replace certain functions with their shorter alias function name
  95. // http://php.net/manual/en/aliases.php
  96. static $replace = array(
  97. 'implode' => 'join',
  98. 'fwrite' => 'fputs',
  99. 'array_key_exists' => 'key_exists',
  100. 'current' => 'pos',
  101. );
  102. // check this has a replacement and "looks like" a function call
  103. // this works on a number of assumptions, such as not being aliased/namespaced
  104. if (isset($replace[$token[1]])) {
  105. $p = $all[$prev($i)];
  106. if ($all[$next($i)] === '(' && (!is_array($p) || !in_array($p[0], array(T_FUNCTION, T_OBJECT_OPERATOR, T_DOUBLE_COLON, T_NEW)))) {
  107. $all[$i][1] = $replace[$all[$i][1]];
  108. }
  109. }
  110. } elseif (is_array($token) && $token[0] === T_EXIT) {
  111. // replace `exit` with shorter alias `die`
  112. // it's a language construct, not a function (see above)
  113. $all[$i][1] = 'die';
  114. } elseif (is_array($token) && $token[0] === T_RETURN) {
  115. // replace `return null;` with `return;`
  116. $t = $next($i);
  117. if (is_array($all[$t]) && $all[$t][0] === T_STRING && $all[$t][1] === 'null' && $all[$next($t)] === ';') {
  118. unset($all[$t]);
  119. }
  120. }
  121. }
  122. $all = array_values($all);
  123. foreach ($all as $i => $token) {
  124. if (is_array($token) && $token[0] === T_WHITESPACE) {
  125. if (strpos($token[1], "\n") !== false) {
  126. $token = strpos("()[]<>=+-*/%|,.:?!'\"\n", substr($small, -1)) === false ? "\n" : '';
  127. } else {
  128. $last = substr($small, -1);
  129. $next = isset($all[$i + 1]) ? substr(is_array($all[$i + 1]) ? $all[$i + 1][1] : $all[$i + 1], 0, 1) : ' ';
  130. $token = (strpos('()[]{}<>;=+-*/%&|,.:?!@\'"' . "\r\n", $last) !== false || strpos('()[]{}<>;=+-*/%&|,.:?!@\'"' . '\\$', $next) !== false) ? '' : ' ';
  131. }
  132. }
  133. $small .= isset($token[1]) ? $token[1] : $token;
  134. }
  135. file_put_contents($out, $small);
  136. echo ' DONE (' . strlen($small) . ' bytes)' . PHP_EOL;