mirror of
https://github.com/php/doc-base.git
synced 2026-03-24 07:12:14 +01:00
1161 lines
35 KiB
PHP
Executable File
1161 lines
35 KiB
PHP
Executable File
#!/usr/bin/env php
|
|
<?php // vim: ts=4 sw=4 et tw=78 fdm=marker
|
|
|
|
/*
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 1997-2023 The PHP Group |
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to version 3.01 of the PHP license, |
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
| available through the world-wide-web at the following url: |
|
|
| https://www.php.net/license/3_01.txt. |
|
|
| If you did not receive a copy of the PHP license and are unable to |
|
|
| obtain it through the world-wide-web, please send a note to |
|
|
| license@php.net, so we can mail you a copy immediately. |
|
|
+----------------------------------------------------------------------+
|
|
| Authors: Dave Barr <dave@php.net> |
|
|
| Hannes Magnusson <bjori@php.net> |
|
|
| Gwynne Raskind <gwynne@php.net> |
|
|
| André L F S Bacci <gwynne@php.net> |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
ini_set( 'display_errors' , 1 );
|
|
ini_set( 'display_startup_errors' , 1 );
|
|
error_reporting( E_ALL );
|
|
ob_implicit_flush();
|
|
|
|
echo "configure.php on PHP " . phpversion() . ", libxml " . LIBXML_DOTTED_VERSION . "\n\n";
|
|
|
|
// init_argv()
|
|
// init_checks()
|
|
// init_clean()
|
|
// xml_configure()
|
|
// xml_parse()
|
|
// xml_xinclude()
|
|
// xml_validate()
|
|
// phd_sources()
|
|
// phd_version()
|
|
// php_history()
|
|
|
|
const RNG_SCHEMA_DIR = __DIR__ . DIRECTORY_SEPARATOR . 'docbook' . DIRECTORY_SEPARATOR . 'docbook-v5.2-os' . DIRECTORY_SEPARATOR . 'rng' . DIRECTORY_SEPARATOR;
|
|
const RNG_SCHEMA_FILE = RNG_SCHEMA_DIR . 'docbook.rng';
|
|
const RNG_SCHEMA_XINCLUDE_FILE = RNG_SCHEMA_DIR . 'docbookxi.rng';
|
|
|
|
function usage() // {{{
|
|
{
|
|
global $acd;
|
|
|
|
echo <<<HELPCHUNK
|
|
configure.php configures this package to adapt to many kinds of systems, and PhD
|
|
builds too.
|
|
|
|
Usage: ./configure [OPTION]...
|
|
|
|
Defaults for the options are specified in brackets.
|
|
|
|
Configuration:
|
|
-h, --help Display this help and exit
|
|
-V, --version Display version information and exit
|
|
-q, --quiet, --silent Do not print `checking...' messages
|
|
--srcdir=DIR Find the sources in DIR [configure dir or `.']
|
|
--basedir Doc-base directory
|
|
[{$acd['BASEDIR']}]
|
|
--rootdir Root directory of SVN Doc checkouts
|
|
[{$acd['ROOTDIR']}]
|
|
|
|
Package-specific:
|
|
--enable-force-dom-save Force .manual.xml to be saved in a full build
|
|
even if it fails validation [{$acd['FORCE_DOM_SAVE']}]
|
|
--enable-chm Enable Windows HTML Help Edition pages [{$acd['CHMENABLED']}]
|
|
--enable-xml-details Enable detailed XML error messages [{$acd['DETAILED_ERRORMSG']}]
|
|
--disable-segfault-error LIBXML may segfault with broken XML, use this
|
|
if it does [{$acd['SEGFAULT_ERROR']}]
|
|
--disable-version-files Do not merge the extension specific
|
|
version.xml files
|
|
--disable-sources-file Do not generate sources.xml file
|
|
--disable-history-file Do not copy file modification history file
|
|
--disable-libxml-check Disable the libxml 2.7.4+ requirement check
|
|
--with-php=PATH Path to php CLI executable [detect]
|
|
--with-lang=LANG Language to build [{$acd['LANG']}]
|
|
--with-partial=my-xml-id Root ID to build (e.g. <book xml:id="MY-ID">) [{$acd['PARTIAL']}]
|
|
--disable-broken-file-listing Do not ignore translated files in
|
|
broken-files.txt
|
|
--disable-xpointer-reporting Do not show XInclude/XPointer failures. Only effective
|
|
on translations
|
|
--redirect-stderr-to-stdout Redirect STDERR to STDOUT. Use STDOUT as the
|
|
standard output for XML errors [{$acd['STDERR_TO_STDOUT']}]
|
|
--output=FILENAME Save to given file (i.e. not .manual.xml)
|
|
[{$acd['OUTPUT_FILENAME']}]
|
|
--generate=FILENAME Create an XML only for provided file
|
|
|
|
HELPCHUNK;
|
|
} // }}}
|
|
|
|
function realpain( string $path , bool $touch = false , bool $mkdir = false ) : string
|
|
{
|
|
// pain is real
|
|
|
|
// care for external XML tools (realpath() everywhere)
|
|
// care for Windows builds (foward slashes everywhere)
|
|
// avoid `cd` and chdir() like the plague
|
|
|
|
$path = str_replace( "\\" , '/' , $path );
|
|
|
|
if ( $mkdir && ! file_exists( $path ) )
|
|
mkdir( $path , recursive: true );
|
|
|
|
if ( $touch && ! file_exists( $path ) )
|
|
touch( $path );
|
|
|
|
$res = realpath( $path );
|
|
if ( is_string( $res ) )
|
|
$path = str_replace( "\\" , '/' , $res );
|
|
|
|
return $path;
|
|
}
|
|
|
|
function errbox($msg) {
|
|
$len = strlen($msg)+4;
|
|
$line = "+" . str_repeat("-", $len) . "+";
|
|
|
|
echo $line, "\n";
|
|
echo "| ", $msg, " |", "\n";
|
|
echo $line, "\n\n";
|
|
}
|
|
|
|
function errors_are_bad($status) {
|
|
echo "\nEyh man. No worries. Happ shittens. Try again after fixing the errors above.\n";
|
|
exit($status);
|
|
}
|
|
|
|
function checking($for) // {{{
|
|
{
|
|
global $ac;
|
|
|
|
if ($ac['quiet'] != 'yes') {
|
|
echo "Checking {$for}... ";
|
|
}
|
|
} // }}}
|
|
|
|
function checkerror($msg) // {{{
|
|
{
|
|
global $ac;
|
|
|
|
if ($ac['quiet'] != 'yes') {
|
|
echo "\n";
|
|
}
|
|
echo "error: {$msg}\n";
|
|
exit(1);
|
|
} // }}}
|
|
|
|
function checkvalue($v) // {{{
|
|
{
|
|
global $ac;
|
|
|
|
if ($ac['quiet'] != 'yes') {
|
|
echo "{$v}\n";
|
|
}
|
|
} // }}}
|
|
|
|
function abspath($path) // {{{
|
|
{
|
|
// realpath() doesn't return empty for empty on Windows
|
|
if ($path == '') {
|
|
return '';
|
|
}
|
|
return str_replace('\\', '/', function_exists('realpath') ? realpath($path) : $path);
|
|
} // }}}
|
|
|
|
function find_file($file_array) // {{{
|
|
{
|
|
$paths = explode(PATH_SEPARATOR, getenv('PATH'));
|
|
|
|
if (is_array($paths)) {
|
|
foreach ($paths as $path) {
|
|
foreach ($file_array as $name) {
|
|
if (file_exists("{$path}/{$name}") && is_file("{$path}/{$name}")) {
|
|
return "{$path}/{$name}";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return '';
|
|
} // }}}
|
|
|
|
// Recursive glob() with a callback function {{{
|
|
function globbetyglob($globber, $userfunc)
|
|
{
|
|
foreach (glob("$globber/*") as $file) {
|
|
if (is_dir($file)) {
|
|
globbetyglob($file, $userfunc);
|
|
} else {
|
|
call_user_func($userfunc, $file);
|
|
}
|
|
}
|
|
} // }}}
|
|
|
|
function make_scripts_executable($filename) // {{{
|
|
{
|
|
if (substr($filename, -3) == '.sh') {
|
|
chmod($filename, 0755);
|
|
}
|
|
} // }}}
|
|
|
|
function print_xml_errors()
|
|
{
|
|
global $ac;
|
|
$report = $ac['LANG'] == 'en' || $ac['XPOINTER_REPORTING'] == 'yes';
|
|
$output = ( $ac['STDERR_TO_STDOUT'] == 'yes' ) ? STDOUT : STDERR ;
|
|
|
|
$errors = libxml_get_errors();
|
|
libxml_clear_errors();
|
|
|
|
$filePrefix = "file:///";
|
|
$tempPrefix = realpath( __DIR__ . "/temp" ) . "/";
|
|
$rootPrefix = realpath( __DIR__ . "/.." ) . "/";
|
|
|
|
if ( count( $errors ) > 0 )
|
|
fprintf( $output , "\n" );
|
|
|
|
foreach( $errors as $error )
|
|
{
|
|
$mssg = rtrim( $error->message );
|
|
$file = $error->file;
|
|
$line = $error->line;
|
|
$clmn = $error->column;
|
|
|
|
if ( str_starts_with( $mssg , 'XPointer evaluation failed:' ) && ! $report )
|
|
continue; // Translations can omit these, to focus on fatal errors
|
|
|
|
if ( str_starts_with( $file , $filePrefix ) )
|
|
$file = substr( $file , strlen( $filePrefix ) );
|
|
if ( str_starts_with( $file , $tempPrefix ) )
|
|
$file = substr( $file , strlen( $tempPrefix ) );
|
|
if ( str_starts_with( $file , $rootPrefix ) )
|
|
$file = substr( $file , strlen( $rootPrefix ) );
|
|
|
|
$prefix = $error->level === LIBXML_ERR_FATAL ? "FATAL" : "error";
|
|
|
|
fwrite( $output , "[$prefix $file {$line}:{$clmn}] {$mssg}\n" );
|
|
}
|
|
}
|
|
|
|
function find_xml_files($path) // {{{
|
|
{
|
|
$path = rtrim($path, '/');
|
|
$prefix_len = strlen($path . '/');
|
|
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path));
|
|
foreach ($files as $fileinfo) {
|
|
if ($fileinfo->getExtension() === 'xml') {
|
|
yield substr($fileinfo->getPathname(), $prefix_len);
|
|
}
|
|
}
|
|
} // }}}
|
|
|
|
if ( true ) # Initial clean up
|
|
{
|
|
$dir = escapeshellarg( __DIR__ );
|
|
$cmd = "git -C $dir clean temp -fdx --quiet";
|
|
$ret = 0;
|
|
passthru( $cmd , $ret );
|
|
if ( $ret != 0 )
|
|
{
|
|
echo "doc-base/temp clean up FAILED.\n";
|
|
exit( 1 );
|
|
}
|
|
}
|
|
|
|
$srcdir = dirname(__FILE__);
|
|
$workdir = $srcdir;
|
|
$basedir = $srcdir;
|
|
$rootdir = dirname($basedir);
|
|
|
|
/**
|
|
* When checking out this repository on GitHub Actions, the workspace directory is "/home/runner/work/doc-base/doc-base".
|
|
*
|
|
* To avoid applying dirname() here, we check if we are running on GitHub Actions.
|
|
*
|
|
* @see https://docs.github.com/en/free-pro-team@latest/actions/reference/environment-variables#default-environment-variables
|
|
*/
|
|
if (getenv('GITHUB_ACTIONS') !== 'true' && basename($rootdir) === 'doc-base') {
|
|
$rootdir = dirname($rootdir);
|
|
}
|
|
|
|
// Settings {{{
|
|
$cygwin_php_bat = "{$srcdir}/../phpdoc-tools/php.bat";
|
|
$php_bin_names = array('php', 'php5', 'cli/php', 'php.exe', 'php5.exe', 'php-cli.exe', 'php-cgi.exe');
|
|
// }}}
|
|
|
|
$acd = array( // {{{
|
|
'srcdir' => $srcdir,
|
|
'basedir' => $basedir,
|
|
'rootdir' => $rootdir,
|
|
'workdir' => $workdir,
|
|
'quiet' => 'no',
|
|
'WORKDIR' => $srcdir,
|
|
'SRCDIR' => $srcdir,
|
|
'BASEDIR' => $basedir,
|
|
'ROOTDIR' => $rootdir,
|
|
'ONLYDIR' => "{$rootdir}/en",
|
|
'PHP' => '',
|
|
'CHMENABLED' => 'no',
|
|
'CHMONLY_INCL_BEGIN' => '<!--',
|
|
'CHMONLY_INCL_END' => '-->',
|
|
'LANG' => 'en',
|
|
'LANGDIR' => "{$rootdir}/en",
|
|
'ENCODING' => 'utf-8',
|
|
'FORCE_DOM_SAVE' => 'no',
|
|
'PARTIAL' => 'no',
|
|
'DETAILED_ERRORMSG' => 'no',
|
|
'SEGFAULT_ERROR' => 'yes',
|
|
'VERSION_FILES' => 'yes',
|
|
'SOURCES_FILE' => 'yes',
|
|
'HISTORY_FILE' => 'yes',
|
|
'LIBXML_CHECK' => 'yes',
|
|
'USE_BROKEN_TRANSLATION_FILENAME' => 'yes',
|
|
'OUTPUT_FILENAME' => $srcdir . '/.manual.xml',
|
|
'GENERATE' => 'no',
|
|
'STDERR_TO_STDOUT' => 'no',
|
|
'INPUT_FILENAME' => 'manual.xml',
|
|
'TRANSLATION_ONLY_INCL_BEGIN' => '',
|
|
'TRANSLATION_ONLY_INCL_END' => '',
|
|
'XPOINTER_REPORTING' => 'yes',
|
|
); // }}}
|
|
|
|
$ac = $acd;
|
|
|
|
$srcdir_dependant_settings = array( 'LANGDIR' );
|
|
$overridden_settings = array();
|
|
|
|
foreach ($_SERVER['argv'] as $k => $opt) { // {{{
|
|
$parts = explode('=', $opt, 2);
|
|
if (strncmp($opt, '--enable-', 9) == 0) {
|
|
$o = substr($parts[0], 9);
|
|
$v = 'yes';
|
|
} else if (strncmp($opt, '--disable-', 10) == 0 || strncmp($opt, '--without-', 10) == 0) {
|
|
$o = substr($parts[0], 10);
|
|
$v = 'no';
|
|
} else if (strncmp($opt, '--with-', 7) == 0) {
|
|
$o = substr($parts[0], 7);
|
|
$v = isset($parts[1]) ? $parts[1] : 'yes';
|
|
} else if (strncmp($opt, '--redirect-', 11) == 0) {
|
|
$o = substr($parts[0], 11);
|
|
$v = 'yes';
|
|
} else if (strncmp($opt, '--', 2) == 0) {
|
|
$o = substr($parts[0], 2);
|
|
$v = isset($parts[1]) ? $parts[1] : 'yes';
|
|
} else if ($opt[0] == '-') {
|
|
$o = $opt[1];
|
|
$v = strlen($opt) > 2 ? substr($opt, 2) : 'yes';
|
|
} else {
|
|
continue;
|
|
}
|
|
|
|
$overridden_settings[] = strtoupper($o);
|
|
switch ($o) {
|
|
case 'h':
|
|
case 'help':
|
|
usage();
|
|
exit();
|
|
|
|
case 'V':
|
|
case 'version':
|
|
// Version/revision is always printed out
|
|
exit();
|
|
|
|
case 'q':
|
|
case 'quiet':
|
|
case 'silent':
|
|
$ac['quiet'] = $v;
|
|
break;
|
|
|
|
case 'srcdir':
|
|
foreach ($srcdir_dependant_settings as $s) {
|
|
if (!in_array($s, $overridden_settings)) {
|
|
$ac[$s] = $v . substr($ac[$s], strlen($ac['srcdir']));
|
|
}
|
|
}
|
|
$ac['srcdir'] = $v;
|
|
break;
|
|
|
|
case 'force-dom-save':
|
|
$ac['FORCE_DOM_SAVE'] = $v;
|
|
break;
|
|
|
|
case 'chm':
|
|
$ac['CHMENABLED'] = $v;
|
|
break;
|
|
|
|
case 'php':
|
|
$ac['PHP'] = $v;
|
|
break;
|
|
|
|
case 'lang':
|
|
$ac['LANG'] = $v;
|
|
break;
|
|
|
|
case 'partial':
|
|
if ($v == "yes") {
|
|
if (isset($_SERVER['argv'][$k+1])) {
|
|
$val = $_SERVER['argv'][$k+1];
|
|
errbox("TYPO ALERT: Didn't you mean --{$o}={$val}?");
|
|
} else {
|
|
errbox("TYPO ALERT: --partial without a chunk ID?");
|
|
}
|
|
}
|
|
|
|
$ac['PARTIAL'] = $v;
|
|
break;
|
|
|
|
case 'xml-details':
|
|
$ac['DETAILED_ERRORMSG'] = $v;
|
|
break;
|
|
|
|
case 'segfault-error':
|
|
$ac['SEGFAULT_ERROR'] = $v;
|
|
break;
|
|
|
|
case 'version-files':
|
|
$ac['VERSION_FILES'] = $v;
|
|
break;
|
|
|
|
case 'sources-file':
|
|
$ac['SOURCES_FILE'] = $v;
|
|
break;
|
|
|
|
case 'history-file':
|
|
$ac['SOURCES_FILE'] = $v;
|
|
break;
|
|
|
|
case 'libxml-check':
|
|
$ac['LIBXML_CHECK'] = $v;
|
|
break;
|
|
|
|
case 'rootdir':
|
|
$ac['rootdir'] = $v;
|
|
break;
|
|
|
|
case 'basedir':
|
|
$ac['basedir'] = $v;
|
|
break;
|
|
|
|
case 'output':
|
|
$ac['OUTPUT_FILENAME'] = $v;
|
|
break;
|
|
|
|
case 'generate':
|
|
$ac['GENERATE'] = $v;
|
|
break;
|
|
|
|
case 'broken-file-listing':
|
|
$ac['USE_BROKEN_TRANSLATION_FILENAME'] = $v;
|
|
break;
|
|
|
|
case 'stderr-to-stdout':
|
|
$ac['STDERR_TO_STDOUT'] = $v;
|
|
break;
|
|
|
|
case 'xpointer-reporting':
|
|
$ac['XPOINTER_REPORTING'] = $v;
|
|
break;
|
|
|
|
case '':
|
|
break;
|
|
|
|
default:
|
|
echo "WARNING: Unknown option '{$o}'!\n";
|
|
break;
|
|
}
|
|
} // }}}
|
|
|
|
// Reject 'old' LibXML installations, due to LibXML feature #502960 {{{
|
|
if (version_compare(LIBXML_DOTTED_VERSION, '2.7.4', '<') && $ac['LIBXML_CHECK'] === 'yes') {
|
|
echo "LibXML 2.7.4+ added a 'feature' to break things, typically namespace related, and unfortunately we must require it.\n";
|
|
echo "For a few related details, see: http://www.mail-archive.com/debian-bugs-dist@lists.debian.org/msg777646.html\n";
|
|
echo "Please recompile PHP with a LibXML version 2.7.4 or greater. Version detected: " . LIBXML_DOTTED_VERSION . "\n";
|
|
echo "Or, pass in --disable-libxml-check if doing so feels safe.\n\n";
|
|
#exit(100);
|
|
} // }}}
|
|
|
|
checking('for source directory');
|
|
if (!file_exists($ac['srcdir']) || !is_dir($ac['srcdir']) || !is_writable($ac['srcdir'])) {
|
|
checkerror("Source directory doesn't exist or can't be written to.");
|
|
}
|
|
$ac['SRCDIR'] = $ac['srcdir'];
|
|
$ac['WORKDIR'] = $ac['srcdir'];
|
|
$ac['ROOTDIR'] = $ac['rootdir'];
|
|
$ac['BASEDIR'] = $ac['basedir'];
|
|
checkvalue($ac['srcdir']);
|
|
|
|
checking('for output filename');
|
|
checkvalue($ac['OUTPUT_FILENAME']);
|
|
|
|
checking('whether to include CHM');
|
|
$ac['CHMONLY_INCL_BEGIN'] = ($ac['CHMENABLED'] == 'yes' ? '' : '<!--');
|
|
$ac['CHMONLY_INCL_END'] = ($ac['CHMENABLED'] == 'yes' ? '' : '-->');
|
|
checkvalue($ac['CHMENABLED']);
|
|
|
|
checking("for PHP executable");
|
|
if ($ac['PHP'] == '' || $ac['PHP'] == 'no') {
|
|
$ac['PHP'] = find_file($php_bin_names);
|
|
} else if (file_exists($cygwin_php_bat)) {
|
|
$ac['PHP'] = $cygwin_php_bat;
|
|
}
|
|
|
|
if ($ac['PHP'] == '') {
|
|
checkerror("Could not find a PHP executable. Use --with-php=/path/to/php.");
|
|
}
|
|
if (!file_exists($ac['PHP']) || !is_executable($ac['PHP'])) {
|
|
checkerror("PHP executable is invalid - how are you running configure? " .
|
|
"Use --with-php=/path/to/php.");
|
|
}
|
|
$ac['PHP'] = abspath($ac['PHP']);
|
|
checkvalue($ac['PHP']);
|
|
|
|
checking("for language to build");
|
|
if ($ac['LANG'] == '' /* || $ac['LANG'] == 'no' */) {
|
|
checkerror("Using '--with-lang=' or '--without-lang' is just going to cause trouble.");
|
|
} else if ($ac['LANG'] == 'yes') {
|
|
$ac['LANG'] = 'en';
|
|
}
|
|
if ($ac["LANG"] == "en") {
|
|
$ac["TRANSLATION_ONLY_INCL_BEGIN"] = "<!--";
|
|
$ac["TRANSLATION_ONLY_INCL_END"] = "-->";
|
|
}
|
|
checkvalue($ac['LANG']);
|
|
file_put_contents( __DIR__ . "/temp/lang" , $ac['LANG'] );
|
|
|
|
checking("whether the language is supported");
|
|
$LANGDIR = "{$ac['rootdir']}/{$ac['LANG']}";
|
|
if (!file_exists($LANGDIR) || !is_readable($LANGDIR)) {
|
|
checkerror("No language directory found.");
|
|
}
|
|
|
|
$ac['LANGDIR'] = basename($LANGDIR);
|
|
$ac['EN_DIR'] = 'en';
|
|
checkvalue("yes");
|
|
|
|
checking("for partial build");
|
|
checkvalue($ac['PARTIAL']);
|
|
|
|
checking('whether to enable detailed XML error messages');
|
|
checkvalue($ac['DETAILED_ERRORMSG']);
|
|
|
|
checking('whether to enable detailed error reporting (may segfault)');
|
|
checkvalue($ac['SEGFAULT_ERROR']);
|
|
|
|
if ($ac["GENERATE"] != "no") {
|
|
$ac["ONLYDIR"] = dirname(realpath($ac["GENERATE"]));
|
|
}
|
|
|
|
|
|
// Show local repository status to facilitate remote debugging
|
|
|
|
$repos = array();
|
|
$repos['doc-base'] = $ac['basedir'];
|
|
$repos['en'] = "{$ac['rootdir']}/{$ac['EN_DIR']}";
|
|
$repos[$ac['LANG']] = "{$ac['rootdir']}/{$ac['LANG']}";
|
|
$repos = array_unique($repos);
|
|
|
|
$output = "";
|
|
foreach ( $repos as $name => $path )
|
|
{
|
|
$path = escapeshellarg( $path );
|
|
$branch = trim( shell_exec( "git -C $path rev-parse --abbrev-ref HEAD" ));
|
|
$branch = $branch == "master" ? "" : " (branch $branch)";
|
|
$output .= str_pad( "$name:" , 10 );
|
|
$output .= rtrim( shell_exec( "git -C $path rev-parse HEAD" ) ?? "" . $branch ) . "\n";
|
|
$output .= rtrim( shell_exec( "git -C $path status -s") ?? "" ) . "\n";
|
|
}
|
|
while( str_contains( $output , "\n\n" ) )
|
|
$output = str_replace( "\n\n" , "\n" , $output );
|
|
echo "\n" , trim( $output ) . "\n\n";
|
|
|
|
|
|
xml_configure();
|
|
function xml_configure()
|
|
{
|
|
global $ac;
|
|
$lang = $ac["LANG"];
|
|
$conf = [];
|
|
|
|
$conf[] = "<!ENTITY LANG '$lang'>";
|
|
|
|
if ( $lang == 'en' )
|
|
{
|
|
realpain( __DIR__ . "/temp/empty" , touch: true );
|
|
$trans1 = realpain( __DIR__ . "/temp/empty" );
|
|
$trans2 = realpain( __DIR__ . "/temp/empty" );
|
|
$trans3 = realpain( __DIR__ . "/temp/empty" );
|
|
}
|
|
else
|
|
{
|
|
$trans1 = realpain( __DIR__ . "/../$lang/language-defs.ent" );
|
|
$trans2 = realpain( __DIR__ . "/../$lang/language-snippets.ent" );
|
|
$trans3 = realpain( __DIR__ . "/../$lang/extensions.ent" );
|
|
}
|
|
$conf[] = "<!ENTITY % translation-defs SYSTEM '$trans1'>";
|
|
$conf[] = "<!ENTITY % translation-snippets SYSTEM '$trans2'>";
|
|
$conf[] = "<!ENTITY % translation-extensions SYSTEM '$trans3'>";
|
|
|
|
if ( $ac['CHMENABLED'] == 'yes' )
|
|
{
|
|
$chmpath = realpain( __DIR__ . "/chm/manual.chm.xml" );
|
|
$conf[] = "<!ENTITY manual.chmonly SYSTEM '$chmpath'>";
|
|
}
|
|
else
|
|
$conf[] = "<!ENTITY manual.chmonly ''>";
|
|
|
|
file_put_contents( __DIR__ . "/temp/manual.conf" , implode( "\n" , $conf ) );
|
|
}
|
|
|
|
if ($ac['SEGFAULT_ERROR'] === 'yes') {
|
|
libxml_use_internal_errors(true);
|
|
}
|
|
|
|
globbetyglob("{$ac['basedir']}/scripts", 'make_scripts_executable');
|
|
|
|
|
|
{ # file-entities.php
|
|
|
|
$cmd = array();
|
|
$cmd[] = $ac['PHP'];
|
|
$cmd[] = __DIR__ . "/scripts/file-entities.php";
|
|
if ( $ac["LANG"] != "en" )
|
|
$cmd[] = $ac["LANG"];
|
|
if ( $ac['CHMENABLED'] == 'yes' )
|
|
$cmd[] = '--chmonly';
|
|
foreach ( $cmd as & $part )
|
|
$part = escapeshellarg( $part );
|
|
$ret = 0;
|
|
$cmd = implode( ' ' , $cmd );
|
|
passthru( $cmd , $ret );
|
|
if ( $ret != 0 )
|
|
{
|
|
echo "doc-base/scripts/file-entities.php FAILED.\n";
|
|
exit( 1 );
|
|
}
|
|
}
|
|
|
|
|
|
checking("for if we should generate a simplified file");
|
|
if ($ac["GENERATE"] != "no") {
|
|
if (!file_exists($ac["GENERATE"])) {
|
|
checkerror("Can't find {$ac["GENERATE"]}");
|
|
}
|
|
$tmp = realpath($ac["GENERATE"]);
|
|
$ac["GENERATE"] = str_replace($ac["ROOTDIR"].$ac["LANGDIR"], "", $tmp);
|
|
$str = "\n<!ENTITY developer.include.file SYSTEM 'file:///{$ac["GENERATE"]}'>";
|
|
file_put_contents("{$ac["basedir"]}/entities/file-entities.ent", $str, FILE_APPEND);
|
|
$ac["FORCE_DOM_SAVE"] = "yes";
|
|
}
|
|
checkvalue($ac["GENERATE"]);
|
|
|
|
checking('whether to save an invalid .manual.xml');
|
|
checkvalue($ac['FORCE_DOM_SAVE']);
|
|
|
|
function dom_load( DOMDocument $dom , string $filename , string $baseURI = "" ) : bool
|
|
{
|
|
$filename = realpath( $filename );
|
|
$options = LIBXML_NOENT | LIBXML_COMPACT | LIBXML_BIGLINES | LIBXML_PARSEHUGE;
|
|
return $dom->load( $filename , $options );
|
|
}
|
|
|
|
function dom_saveload( DOMDocument $dom , string $filename = "" ) : string
|
|
{
|
|
if ( $filename == "" )
|
|
$filename = __DIR__ . "/temp/manual.xml";
|
|
|
|
$dom->save( $filename );
|
|
dom_load( $dom , $filename );
|
|
|
|
return $filename;
|
|
}
|
|
|
|
echo "Loading and parsing {$ac["INPUT_FILENAME"]}... ";
|
|
$dom = new DOMDocument();
|
|
|
|
if ( dom_load( $dom , "{$ac['srcdir']}/{$ac["INPUT_FILENAME"]}" ) )
|
|
{
|
|
echo "1 ";
|
|
dom_saveload( $dom ); // correct file/line/column on error messages
|
|
echo "2 done.\n";
|
|
}
|
|
else
|
|
{
|
|
echo "failed.\n";
|
|
print_xml_errors();
|
|
individual_xml_broken_check();
|
|
errors_are_bad(1);
|
|
}
|
|
|
|
function individual_xml_broken_check()
|
|
{
|
|
$cmd = array();
|
|
$cmd[] = $GLOBALS['ac']['PHP'];
|
|
$cmd[] = __DIR__ . "/scripts/broken.php";
|
|
$cmd[] = $GLOBALS['ac']['LANG'];
|
|
foreach ( $cmd as & $part )
|
|
$part = escapeshellarg( $part );
|
|
$ret = 0;
|
|
$cmd = implode( ' ' , $cmd );
|
|
passthru( $cmd , $ret );
|
|
if ( $ret != 0 )
|
|
{
|
|
echo "doc-base/scripts/broken.php FAILED.\n";
|
|
exit( 1 );
|
|
}
|
|
}
|
|
|
|
echo "Running XInclude/XPointer... ";
|
|
|
|
$total = xinclude_run_byid( $dom );
|
|
$total += xinclude_run_xpointer( $dom );
|
|
|
|
if ( $total == 0 )
|
|
echo "failed.\n";
|
|
else
|
|
echo "done. Performed $total XIncludes.\n";
|
|
|
|
xinclude_residual_fixup( $dom );
|
|
|
|
function xinclude_run_byid( DOMDocument $dom )
|
|
{
|
|
$total = 0;
|
|
$maxrun = 10; //LIBXML_VERSION >= 21100 ? 1 : 10;
|
|
for( $run = 0 ; $run < $maxrun ; $run++ )
|
|
{
|
|
echo "$run ";
|
|
$xpath = new DOMXPath( $dom );
|
|
$xpath->registerNamespace( "xi" , "http://www.w3.org/2001/XInclude" );
|
|
$xincludes = $xpath->query( "//xi:include" );
|
|
|
|
$changed = false;
|
|
foreach( $xincludes as $xinclude )
|
|
{
|
|
$xpointer = $xinclude->getAttribute( "xpointer" );
|
|
$target = $xinclude->ownerDocument->getElementById( $xpointer );
|
|
|
|
if ( $target == null )
|
|
continue;
|
|
|
|
$other = new DOMDocument( '1.0' , 'utf8' );
|
|
$frags = $other->createDocumentFragment();
|
|
$other->append( $frags );
|
|
$frags->append( $other->importNode( $target , true ) ); // dup add
|
|
|
|
// "attributes in xml: namespace are not copied"
|
|
|
|
$oxpth = new DOMXPath( $other );
|
|
$attribs = $oxpth->query( "//@*" );
|
|
|
|
foreach( $attribs as $attrib )
|
|
if ( $attrib->prefix == "xml" )
|
|
$attrib->parentNode->removeAttribute( $attrib->nodeName );
|
|
|
|
$insert = $dom->importNode( $frags , true ); // dup
|
|
$xinclude->parentNode->insertBefore( $insert , $xinclude ); // add
|
|
$xinclude->parentNode->removeChild( $xinclude ); // del
|
|
|
|
$total++;
|
|
$changed = true;
|
|
libxml_clear_errors();
|
|
}
|
|
|
|
if ( ! $changed )
|
|
return $total;
|
|
}
|
|
echo "XInclude nested too deeply (xml:id).\n";
|
|
errors_are_bad( 1 );
|
|
}
|
|
|
|
function xinclude_run_xpointer( DOMDocument $dom ) : int
|
|
{
|
|
// The return of xinclude() cannot be used for counting or stoping, as it
|
|
// sometimes return zero/negative in cases of partial executions
|
|
|
|
$total = 0;
|
|
for( $run = 0 ; $run < 10 ; $run++ )
|
|
{
|
|
echo "$run ";
|
|
libxml_clear_errors();
|
|
|
|
$was = count( xinclude_residual_list( $dom ) );
|
|
$dom->xinclude();
|
|
$now = count( xinclude_residual_list( $dom ) );
|
|
|
|
$total += $was - $now;
|
|
|
|
if ( $was === $now )
|
|
return $total;
|
|
}
|
|
echo "XInclude nested too deeply (xpointer).\n";
|
|
errors_are_bad( 1 );
|
|
}
|
|
|
|
function xinclude_residual_fixup( DOMDocument $dom )
|
|
{
|
|
// XInclude failures are soft errors on translations, so remove
|
|
// residual XInclude tags on translations to keep them building.
|
|
|
|
$debugFile = "temp/xinclude-debug.xml";
|
|
$debugPath = __DIR__ . "/{$debugFile}";
|
|
$nodes = xinclude_residual_list( $dom );
|
|
|
|
if ( count( $nodes ) > 0 )
|
|
{
|
|
unset( $nodes );
|
|
dom_saveload( $dom , $debugPath );
|
|
$nodes = $nodes = xinclude_residual_list( $dom );
|
|
}
|
|
|
|
$count = 0;
|
|
$explain = false;
|
|
|
|
foreach( $nodes as $node )
|
|
{
|
|
if ( $count === 0 )
|
|
{
|
|
echo "\nFailed XIncludes, manual parts will be missing.";
|
|
echo " Inspect {$debugFile} for context. Failed targets are:\n";
|
|
}
|
|
echo "- {$node->getAttribute("xpointer")}\n";
|
|
$count++;
|
|
|
|
$fixup = null;
|
|
$parent = $node->parentNode;
|
|
$tagName = $parent->nodeName;
|
|
switch( $tagName )
|
|
{
|
|
case "refentry":
|
|
$fixup = "";
|
|
break;
|
|
case "refsect1":
|
|
$fixup = "<title>_</title><simpara>_</simpara>"; // https://github.com/php/phd/issues/181
|
|
break;
|
|
case "tbody":
|
|
$fixup = "<row><entry></entry></row>";
|
|
break;
|
|
case "variablelist":
|
|
$fixup = "<varlistentry><term></term><listitem><simpara></simpara></listitem></varlistentry>";
|
|
break;
|
|
default:
|
|
echo "Unknown parent of failed XInclude: $tagName\n";
|
|
$explain = true;
|
|
continue 2;
|
|
}
|
|
if ( $fixup !== null )
|
|
{
|
|
$other = new DOMDocument( '1.0' , 'utf8' );
|
|
$other->loadXML( "<f>$fixup</f>" );
|
|
foreach( $other->documentElement->childNodes as $otherNode )
|
|
{
|
|
$imported = $dom->importNode( $otherNode , true );
|
|
$node->parentNode->insertBefore( $imported , $node );
|
|
}
|
|
}
|
|
$node->parentNode->removeChild( $node );
|
|
}
|
|
|
|
if ( $explain )
|
|
{
|
|
echo <<<MSG
|
|
\nIf you are seeing this message on a translation, this means that
|
|
XInclude/XPointers failures reported above are so many or unknown,
|
|
that configure.php cannot patch the translated manual into a validating
|
|
state. Please report any "Unknown parent" messages on the doc-base
|
|
repository, and focus on fixing XInclude/XPointers failures above.\n\n
|
|
MSG;
|
|
exit( 1 ); // stop here, do not let more messages further confuse the matter
|
|
}
|
|
|
|
if ( $count > 0 )
|
|
echo "\n";
|
|
|
|
// XInclude by xml:id never duplicates xml:id, horever, also using
|
|
// XInclude by XPath/XPointer may start causing duplications
|
|
// (see docs/structure.md). Crude and ugly fixup ahead, beware!
|
|
|
|
$list = [];
|
|
$see = false;
|
|
$xpath = new DOMXPath( $dom );
|
|
$nodes = $xpath->query( "//*[@xml:id]" );
|
|
foreach( $nodes as $node )
|
|
{
|
|
$id = $node->getAttribute( "xml:id" );
|
|
if ( isset( $list[ $id ] ) )
|
|
{
|
|
if ( ! str_contains( $id , '..' ) )
|
|
{
|
|
echo " Random removing duplicated xml:id: $id\n";
|
|
$see = true;
|
|
}
|
|
$node->removeAttribute( "xml:id" );
|
|
}
|
|
$list[ $id ] = $id;
|
|
}
|
|
if ( $see )
|
|
echo " See: https://github.com/php/doc-base/blob/master/docs/structure.md#xmlid-structure\n";
|
|
|
|
$fatal = $GLOBALS['ac']['LANG'] == 'en';
|
|
|
|
if ( $see && $fatal )
|
|
errors_are_bad( 1 ); // Duplicated strucutral xml:ids are fatal on doc-en
|
|
}
|
|
|
|
function xinclude_residual_list( DOMDocument $dom ) : DOMNodeList
|
|
{
|
|
$xpath = new DOMXPath( $dom );
|
|
$xpath->registerNamespace( "xi" , "http://www.w3.org/2001/XInclude" );
|
|
$nodes = $xpath->query( "//xi:include" );
|
|
|
|
return $nodes;
|
|
}
|
|
|
|
echo "Validating {$ac["INPUT_FILENAME"]}... ";
|
|
|
|
if ($ac['PARTIAL'] != '' && $ac['PARTIAL'] != 'no') { // {{{
|
|
$dom->relaxNGValidate(RNG_SCHEMA_FILE); // we don't care if the validation works or not
|
|
$node = $dom->getElementById($ac['PARTIAL']);
|
|
if (!$node) {
|
|
echo "failed.\n";
|
|
echo "Failed to find partial ID in source XML: {$ac['PARTIAL']}\n";
|
|
errors_are_bad(1);
|
|
}
|
|
if ($node->tagName !== 'book' && $node->tagName !== 'set') {
|
|
// this node is not normally allowed here, attempt to wrap it
|
|
// in something else
|
|
$parents = array();
|
|
switch ($node->tagName) {
|
|
case 'refentry':
|
|
$parents[] = 'reference';
|
|
// Break omitted intentionally
|
|
case 'part':
|
|
$parents[] = 'book';
|
|
break;
|
|
}
|
|
foreach ($parents as $name) {
|
|
$newNode = $dom->createElement($name);
|
|
$newNode->appendChild($node);
|
|
$node = $newNode;
|
|
}
|
|
}
|
|
$set = $dom->documentElement;
|
|
$set->nodeValue = '';
|
|
$set->appendChild($dom->createElement('title', 'PHP Manual (Partial)')); // prevent validate from complaining unnecessarily
|
|
$set->appendChild($node);
|
|
|
|
$filename = "{$ac['srcdir']}/.manual.{$ac['PARTIAL']}.xml";
|
|
$dom->save($filename);
|
|
echo "done.\n";
|
|
echo "Partial manual saved to {$filename}. To build it, run 'phd -d {$filename}'\n";
|
|
exit(0);
|
|
} // }}}
|
|
|
|
// Saves and reload, so libxml's RelaxNG validation to work correctly
|
|
|
|
$mxml = $ac["OUTPUT_FILENAME"];
|
|
$dom->save($mxml); // non idempotent, historical path
|
|
dom_saveload( $dom ); // idempotent path
|
|
|
|
if ($dom->relaxNGValidate(RNG_SCHEMA_FILE)) {
|
|
echo "done.\n";
|
|
} else {
|
|
echo "failed.\n";
|
|
echo "\nThe document didn't validate\n";
|
|
|
|
/**
|
|
* TODO: Integrate jing to explain schema violations as libxml is *useless*
|
|
* And this is not going to change for a while as the maintainer of libxml2 even acknowledges:
|
|
* > As it stands, libxml2's Relax NG validator doesn't seem suitable for production.
|
|
* cf. https://gitlab.gnome.org/GNOME/libxml2/-/issues/448
|
|
*/
|
|
$output = shell_exec('java -jar ' . $srcdir . '/docbook/jing.jar ' . RNG_SCHEMA_FILE. ' ' . $acd['OUTPUT_FILENAME']);
|
|
if ($output === null) {
|
|
echo "Command failed do you have Java installed?";
|
|
} else {
|
|
echo $output;
|
|
}
|
|
//echo 'Please use Jing and the:' . PHP_EOL
|
|
// . 'java -jar ./build/jing.jar /path/to/doc-base/docbook/docbook-v5.2-os/rng/docbookxi.rng /path/to/doc-base/.manual.xml' . PHP_EOL
|
|
// . 'command to check why the RelaxNG schema failed.' . PHP_EOL;
|
|
|
|
// Exit normally when don't care about validation
|
|
if ($ac["FORCE_DOM_SAVE"] == "yes") {
|
|
exit(0);
|
|
}
|
|
|
|
errors_are_bad(1); // Tell the shell that this script finished with an error.
|
|
}
|
|
|
|
// All PhD stuff, after XML validation
|
|
|
|
phd_acronym();
|
|
php_history();
|
|
phd_sources();
|
|
phd_version();
|
|
|
|
function phd_acronym()
|
|
{
|
|
//TODO: Move acronym.xml code here
|
|
}
|
|
|
|
function php_history()
|
|
{
|
|
global $ac;
|
|
if ($ac['HISTORY_FILE'] !== 'yes')
|
|
return;
|
|
|
|
echo 'PhD history:';
|
|
|
|
$lang_mod_file = (($ac['LANG'] !== 'en') ? ("{$ac['rootdir']}/{$ac['EN_DIR']}") : ("{$ac['rootdir']}/{$ac['LANGDIR']}")) . "/fileModHistory.php";
|
|
$doc_base_mod_file = __DIR__ . "/fileModHistory.php";
|
|
|
|
$history_file = null;
|
|
if (file_exists($lang_mod_file)) {
|
|
$history_file = include $lang_mod_file;
|
|
if (is_array($history_file)) {
|
|
echo ' copy,';
|
|
$isFileCopied = copy($lang_mod_file, $doc_base_mod_file);
|
|
echo $isFileCopied ? "" : " failed,";
|
|
} else {
|
|
echo " corrupted file '$lang_mod_file,'";
|
|
}
|
|
} else {
|
|
echo " not found,";
|
|
}
|
|
|
|
if (!is_array($history_file)) {
|
|
$history_file = [];
|
|
echo " creating empty,";
|
|
file_put_contents($doc_base_mod_file, "<?php\n\nreturn [];\n");
|
|
}
|
|
echo " done.\n";
|
|
}
|
|
|
|
function phd_sources()
|
|
{
|
|
global $ac;
|
|
if ($ac['SOURCES_FILE'] !== 'yes')
|
|
return;
|
|
|
|
echo 'PhD sources:';
|
|
|
|
echo ' reading,';
|
|
$source_map = array();
|
|
$en_dir = "{$ac['rootdir']}/{$ac['EN_DIR']}";
|
|
$source_langs = array(
|
|
array('base', $ac['srcdir'], array('manual.xml', 'funcindex.xml')),
|
|
array('en', $en_dir, find_xml_files($en_dir)),
|
|
);
|
|
if ($ac['LANG'] !== 'en') {
|
|
$lang_dir = "{$ac['rootdir']}/{$ac['LANGDIR']}";
|
|
$source_langs[] = array($ac['LANG'], $lang_dir, find_xml_files($lang_dir));
|
|
}
|
|
foreach ($source_langs as list($source_lang, $source_dir, $source_files)) {
|
|
foreach ($source_files as $source_path) {
|
|
$source = file_get_contents("{$source_dir}/{$source_path}");
|
|
if (preg_match_all('/ xml:id=(["\'])([^"]+)\1/', $source, $matches)) {
|
|
foreach ($matches[2] as $xml_id) {
|
|
$source_map[$xml_id] = array(
|
|
'lang' => $source_lang,
|
|
'path' => $source_path,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
asort($source_map);
|
|
echo ' transforming,';
|
|
$dom = new DOMDocument;
|
|
$dom->formatOutput = true;
|
|
$sources_elem = $dom->appendChild($dom->createElement("sources"));
|
|
foreach ($source_map as $id => $source) {
|
|
$el = $dom->createElement('item');
|
|
$el->setAttribute('id', $id);
|
|
$el->setAttribute('lang', $source["lang"]);
|
|
$el->setAttribute('path', $source["path"]);
|
|
$sources_elem->appendChild($el);
|
|
}
|
|
echo " saving,";
|
|
if ($dom->save($ac['srcdir'] . '/sources.xml')) {
|
|
echo " done.\n";
|
|
} else {
|
|
echo " fail!\n";
|
|
}
|
|
}
|
|
|
|
function phd_version()
|
|
{
|
|
global $ac;
|
|
if ($ac['VERSION_FILES'] !== 'yes')
|
|
return;
|
|
|
|
echo 'PhD version:';
|
|
|
|
$dom = new DOMDocument;
|
|
$dom->preserveWhiteSpace = false;
|
|
$dom->formatOutput = true;
|
|
|
|
$tmp = new DOMDocument;
|
|
$tmp->preserveWhiteSpace = false;
|
|
|
|
$versions = $dom->appendChild($dom->createElement("versions"));
|
|
|
|
echo ' reading,';
|
|
if ($ac["GENERATE"] != "no") {
|
|
$globdir = dirname($ac["GENERATE"]) . "/{../../}versions.xml";
|
|
}
|
|
else {
|
|
$globdir = $ac['rootdir'] . '/en';
|
|
$globdir .= "/*/*/versions.xml";
|
|
}
|
|
echo ' transforming,';
|
|
if (!defined('GLOB_BRACE')) {
|
|
define('GLOB_BRACE', 0);
|
|
}
|
|
foreach(glob($globdir, GLOB_BRACE) as $file) {
|
|
if($tmp->load($file)) {
|
|
foreach($tmp->getElementsByTagName("function") as $function) {
|
|
$function = $dom->importNode($function, true);
|
|
$versions->appendChild($function);
|
|
}
|
|
} else {
|
|
print_xml_errors();
|
|
errors_are_bad(1);
|
|
}
|
|
}
|
|
echo ' saving,';
|
|
|
|
if ($dom->save($ac['srcdir'] . '/version.xml')) {
|
|
echo " done.\n";
|
|
} else {
|
|
echo " fail!\n";
|
|
}
|
|
}
|
|
|
|
|
|
printf("\nAll good. Saved %s\n", basename($ac["OUTPUT_FILENAME"]));
|
|
echo "All you have to do now is run 'phd -d {$mxml}'\n";
|
|
echo "If the script hangs here, you can abort with ^C.\n";
|
|
echo <<<CAT
|
|
_ _..._ __
|
|
\)` (` /
|
|
/ `\
|
|
| d b |
|
|
=\ Y =/--..-="````"-.
|
|
'.=__.-' `\
|
|
o/ /\ \
|
|
| | \ \ / )
|
|
\ .--""`\ < \ '-' /
|
|
// | || \ '---'
|
|
jgs ((,,_/ ((,,___/
|
|
|
|
|
|
CAT;
|
|
|
|
individual_xml_broken_check();
|
|
exit(0); // Finished successfully.
|