Skip to content

Commit 8c373e3

Browse files
committed
Add support for serving web-doc
1 parent 458c082 commit 8c373e3

7 files changed

Lines changed: 304 additions & 29 deletions

File tree

‎scripts/dev/Application.php‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use PhpDoc\Dev\Command\RenderCommand;
1313
use PhpDoc\Dev\Command\ServeCommand;
1414
use PhpDoc\Dev\Command\ShellCommand;
15+
use PhpDoc\Dev\Command\WebDocServeCommand;
1516
use PhpDoc\Dev\Environment\DockerEnvironment;
1617
use PhpDoc\Dev\Environment\LocalEnvironment;
1718

@@ -61,6 +62,13 @@ public function run(array $args): int
6162
$options->format = $subcommand;
6263
}
6364

65+
// "serve" takes an optional subject: plain serve shows the rendered
66+
// manual, "serve web-doc" runs the doc.php.net site.
67+
if ($command === 'serve' && ($options->args[0] ?? null) === 'web-doc') {
68+
array_shift($options->args);
69+
$subcommand = 'web-doc';
70+
}
71+
6472
if ($command === 'cs') {
6573
$subcommand = array_shift($options->args);
6674

@@ -96,7 +104,9 @@ public function run(array $args): int
96104
return (new LintCommand($workspace, $environment, $configure, fix: $subcommand === 'fix'))
97105
->execute($options);
98106
case 'serve':
99-
return (new ServeCommand($workspace, $environment))->execute($options);
107+
return $subcommand === 'web-doc'
108+
? (new WebDocServeCommand($workspace, $environment))->execute($options)
109+
: (new ServeCommand($workspace, $environment))->execute($options);
100110
case 'docker':
101111
return $subcommand === 'build'
102112
? (new BuildCommand($environment))->execute($options)

‎scripts/dev/Command/HelpCommand.php‎

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,24 @@ public function execute(Options $options): int
1212
{
1313
echo <<<HELP
1414
Dev build tool for the PHP manual, for every language, with or without
15-
Docker. Missing sibling repositories (en, the translation, and without
16-
Docker also phd/docbook-cs) are cloned automatically on first use.
15+
Docker. Missing sibling repositories (en, the translation, web-doc, and
16+
without Docker also phd/docbook-cs) are cloned automatically on first
17+
use.
1718
1819
Usage:
1920
php dev.php <command> [options] [extra arguments]
2021
2122
Commands:
22-
pull Clone missing sibling repositories and update existing ones
23-
configure Assemble and validate the manual, without rendering
24-
render xhtml configure + render the chunked XHTML manual to <lang>/output
25-
render php configure + render the web (PHP) version to <lang>/output
26-
cs lint Run docbook-cs; extra arguments are passed through (paths, --wide)
27-
cs fix Same as cs lint, with --fix: rewrite violations that have fixers
28-
serve Serve <lang>/output over HTTP
29-
docker build Build the Docker image
30-
docker shell Interactive shell inside the container
23+
pull Clone missing sibling repositories and update existing ones
24+
configure Assemble and validate the manual, without rendering
25+
render xhtml configure + render the chunked XHTML manual to <lang>/output
26+
render php configure + render the web (PHP) version to <lang>/output
27+
cs lint Run docbook-cs; extra arguments are passed through (paths, --wide)
28+
cs fix Same as cs lint, with --fix: rewrite violations that have fixers
29+
serve Serve <lang>/output over HTTP
30+
serve web-doc Run a local doc.php.net site from the web-doc checkout
31+
docker build Build the Docker image
32+
docker shell Interactive shell inside the container
3133
3234
Options:
3335
--lang=XX Language to operate on (default: en)
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpDoc\Dev\Command;
6+
7+
use PhpDoc\Dev\Environment\Environment;
8+
use PhpDoc\Dev\Options;
9+
use PhpDoc\Dev\Workspace;
10+
11+
final class WebDocServeCommand implements Command
12+
{
13+
public function __construct(
14+
private readonly Workspace $workspace,
15+
private readonly Environment $environment,
16+
) {
17+
}
18+
19+
public function execute(Options $options): int
20+
{
21+
$mapNames = $this->environment->canMapDirectoryNames();
22+
23+
// The site needs en at request time (revcheck shells out to git in
24+
// it), web-doc itself, and web-shared inside it.
25+
if (!$this->workspace->ensureWebDocRepos() || !$this->workspace->ensureLang('en', $mapNames)) {
26+
return 1;
27+
}
28+
29+
$sqlite = $this->workspace->webDocDir() . '/sqlite/status.sqlite';
30+
31+
if (!is_file($sqlite)) {
32+
$generate = $this->workspace->confirm(
33+
"Generate $sqlite\n (translation status data; parses git history and can take minutes)?"
34+
);
35+
36+
if ($generate) {
37+
if ($this->generateDb($options, $mapNames) !== 0) {
38+
return 1;
39+
}
40+
} else {
41+
echo "Note: serving without status.sqlite; translation status pages will be empty.\n";
42+
}
43+
}
44+
45+
echo "Serving doc.php.net at http://localhost:{$options->port}/ (Ctrl-C to stop)\n";
46+
47+
return $this->environment->serveWebDoc($options->port);
48+
}
49+
50+
private function generateDb(Options $options, bool $mapNames): int
51+
{
52+
$langs = array_keys($this->workspace->translationCheckouts());
53+
54+
if ($langs === [] && !$this->workspace->isBaseLang($options->lang)) {
55+
if (!$this->workspace->ensureLang($options->lang, $mapNames)) {
56+
return 1;
57+
}
58+
59+
$langs = [$options->lang];
60+
}
61+
62+
if ($langs === []) {
63+
echo "Note: no translation checkouts found; skipping status.sqlite "
64+
. "(clone one, or pass --lang=XX, and re-run).\n";
65+
return 0;
66+
}
67+
68+
if ($this->workspace->isShallowRepo($this->workspace->langDir('en'))) {
69+
echo "Note: the en checkout has shallow git history; status data will be "
70+
. "incomplete (fix with: git -C " . $this->workspace->langDir('en') . " fetch --unshallow).\n";
71+
}
72+
73+
$sqliteDir = $this->workspace->webDocDir() . '/sqlite';
74+
@mkdir($sqliteDir);
75+
@unlink("$sqliteDir/status.sqlite.new");
76+
77+
echo 'Generating translation status data for: ' . implode(', ', $langs) . "\n";
78+
79+
if ($this->environment->generateRevisionDb($langs) !== 0) {
80+
return 1;
81+
}
82+
83+
// genrevdb can exit 0 even on failure, so also judge success by its
84+
// output file; renaming afterwards keeps a half-written database from
85+
// ever being published under the name the site reads.
86+
if (!is_file("$sqliteDir/status.sqlite.new")) {
87+
fwrite(STDERR, "error: generating status.sqlite failed.\n");
88+
return 1;
89+
}
90+
91+
if (!rename("$sqliteDir/status.sqlite.new", "$sqliteDir/status.sqlite")) {
92+
fwrite(STDERR, "error: could not move status.sqlite.new into place.\n");
93+
return 1;
94+
}
95+
96+
return 0;
97+
}
98+
}

‎scripts/dev/Environment/DockerEnvironment.php‎

Lines changed: 83 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public function configure(string $lang, array $args): int
2828
return 1;
2929
}
3030

31-
return $this->dockerRun($lang, array_merge(['php', 'doc-base/configure.php'], $args));
31+
return $this->dockerRun($this->mounts($lang), array_merge(['php', 'doc-base/configure.php'], $args));
3232
}
3333

3434
public function render(string $lang, string $docbook, string $format): int
@@ -37,7 +37,7 @@ public function render(string $lang, string $docbook, string $format): int
3737
return 1;
3838
}
3939

40-
return $this->dockerRun($lang, [
40+
return $this->dockerRun($this->mounts($lang), [
4141
'php',
4242
'phd/render.php',
4343
'--docbook',
@@ -57,16 +57,9 @@ public function lint(string $lang, array $args): int
5757
}
5858

5959
return $this->dockerRun(
60-
$lang,
60+
$this->mounts($lang),
6161
array_merge(['php', '/var/www/docbook-cs/bin/docbook-cs'], $args),
62-
[
63-
'-e',
64-
'GIT_CONFIG_COUNT=1',
65-
'-e',
66-
'GIT_CONFIG_KEY_0=safe.directory',
67-
'-e',
68-
'GIT_CONFIG_VALUE_0=*',
69-
],
62+
$this->gitSafeDirectoryEnv(),
7063
"/var/www/$lang"
7164
);
7265
}
@@ -80,19 +73,63 @@ public function serve(string $lang, int $port, string $subdir): int
8073
// Inside the container the server must bind 0.0.0.0 to be reachable
8174
// through the published port; the host side stays localhost-only.
8275
return $this->dockerRun(
83-
$lang,
76+
$this->mounts($lang),
8477
['php', '-S', "0.0.0.0:$port", '-t', "/var/www/$lang/output$subdir"],
8578
['-p', "127.0.0.1:$port:$port"]
8679
);
8780
}
8881

82+
public function serveWebDoc(int $port): int
83+
{
84+
if (!$this->ensureImage()) {
85+
return 1;
86+
}
87+
88+
// The site shells out to git inside the mounted checkouts at
89+
// request time, hence the safe.directory override while serving.
90+
return $this->dockerRun(
91+
$this->webDocMounts(),
92+
['php', '-S', "0.0.0.0:$port", 'router.php'],
93+
array_merge(
94+
[
95+
'-p',
96+
"127.0.0.1:$port:$port",
97+
'-e',
98+
'PHPDOC_GIT_DIR=/var/www',
99+
'-e',
100+
'SQLITE_DIR=/var/www/web-doc/sqlite',
101+
'-e',
102+
'BASE_DOCS_PATH=/var/www/doc-base/docs',
103+
],
104+
$this->gitSafeDirectoryEnv()
105+
),
106+
'/var/www/web-doc'
107+
);
108+
}
109+
110+
public function generateRevisionDb(array $langs): int
111+
{
112+
if (!$this->ensureImage()) {
113+
return 1;
114+
}
115+
116+
return $this->dockerRun(
117+
$this->webDocMounts(),
118+
array_merge(
119+
['php', 'doc-base/scripts/translation/genrevdb.php', 'web-doc/sqlite/status.sqlite.new'],
120+
$langs
121+
),
122+
$this->gitSafeDirectoryEnv()
123+
);
124+
}
125+
89126
public function shell(string $lang): int
90127
{
91128
if (!$this->ensureImage()) {
92129
return 1;
93130
}
94131

95-
return $this->dockerRun($lang, ['bash'], ['-it']);
132+
return $this->dockerRun($this->mounts($lang), ['bash'], ['-it']);
96133
}
97134

98135
public function buildImage(): int
@@ -157,17 +194,48 @@ private function mounts(string $lang): array
157194
return $mounts;
158195
}
159196

197+
private function webDocMounts(): array
198+
{
199+
$mounts = [
200+
realpath($this->workspace->basedir()) => '/var/www/doc-base',
201+
$this->workspace->webDocDir() => '/var/www/web-doc',
202+
$this->workspace->langDir('en') => '/var/www/en',
203+
];
204+
205+
// Mount every translation checkout so PHPDOC_GIT_DIR=/var/www looks
206+
// like a full doc.php.net workspace to the site.
207+
foreach ($this->workspace->translationCheckouts() as $lang => $dir) {
208+
$mounts[$dir] = "/var/www/$lang";
209+
}
210+
211+
return $mounts;
212+
}
213+
214+
/** @return list<string> */
215+
private function gitSafeDirectoryEnv(): array
216+
{
217+
return [
218+
'-e',
219+
'GIT_CONFIG_COUNT=1',
220+
'-e',
221+
'GIT_CONFIG_KEY_0=safe.directory',
222+
'-e',
223+
'GIT_CONFIG_VALUE_0=*',
224+
];
225+
}
226+
160227
/**
228+
* @param array<string, string> $mounts Host path => container path.
161229
* @param list<string> $inner Command to run inside the container.
162230
* @param list<string> $extra Extra docker run arguments.
163231
*/
164-
private function dockerRun(string $lang, array $inner, array $extra = [], string $workdir = '/var/www'): int
232+
private function dockerRun(array $mounts, array $inner, array $extra = [], string $workdir = '/var/www'): int
165233
{
166234
// --init: without it the command runs as PID 1, which ignores
167235
// SIGINT, so Ctrl-C would leave the container running forever.
168236
$cmd = ['docker', 'run', '--rm', '--init'];
169237

170-
foreach ($this->mounts($lang) as $host => $container) {
238+
foreach ($mounts as $host => $container) {
171239
array_push($cmd, '-v', "$host:$container");
172240
}
173241

‎scripts/dev/Environment/Environment.php‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,17 @@ public function lint(string $lang, array $args): int;
2828
/** @param string $subdir Path inside <lang>/output to use as web root, or "". */
2929
public function serve(string $lang, int $port, string $subdir): int;
3030

31+
/** Serve the doc.php.net site from the web-doc checkout. */
32+
public function serveWebDoc(int $port): int;
33+
34+
/**
35+
* Run genrevdb.php from the workspace root, writing the translation
36+
* status database to web-doc/sqlite/status.sqlite.new.
37+
*
38+
* @param list<string> $langs Translation language codes.
39+
*/
40+
public function generateRevisionDb(array $langs): int;
41+
3142
public function shell(string $lang): int;
3243

3344
public function buildImage(): int;

‎scripts/dev/Environment/LocalEnvironment.php‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,38 @@ public function serve(string $lang, int $port, string $subdir): int
102102
]);
103103
}
104104

105+
public function serveWebDoc(int $port): int
106+
{
107+
$webdoc = $this->workspace->webDocDir();
108+
109+
// router.php resolves www/ relative to the working directory, so
110+
// the server has to run from inside the web-doc checkout.
111+
return $this->runner->run(
112+
[PHP_BINARY, '-S', "localhost:$port", 'router.php'],
113+
$webdoc,
114+
[
115+
'PHPDOC_GIT_DIR' => $this->workspace->rootdir(),
116+
'SQLITE_DIR' => "$webdoc/sqlite",
117+
'BASE_DOCS_PATH' => $this->workspace->basedir() . '/docs',
118+
]
119+
);
120+
}
121+
122+
public function generateRevisionDb(array $langs): int
123+
{
124+
if (!$this->requireLocalPhp(80100, 'generating status.sqlite')) {
125+
return 1;
126+
}
127+
128+
// genrevdb resolves en and each language relative to the working
129+
// directory, so it must run from the workspace root.
130+
return $this->runner->run(array_merge([
131+
PHP_BINARY,
132+
$this->workspace->basedir() . '/scripts/translation/genrevdb.php',
133+
$this->workspace->webDocDir() . '/sqlite/status.sqlite.new',
134+
], $langs), $this->workspace->rootdir());
135+
}
136+
105137
public function shell(string $lang): int
106138
{
107139
fwrite(STDERR, "error: docker shell requires Docker.\n");

0 commit comments

Comments
 (0)