blob: 132a516f57f590ecf4006a15989cf57c79effde9 [file]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""Per-version Docusaurus build orchestrator.
Replaces the legacy ``scripts/split-version-build.sh`` + ``scripts/split-version.js``
pair: reads versions.json directly, decides which versions to build based on the
ChangeSet, runs ``yarn build`` once per included version, and assembles
``build/docs/`` from the per-version outputs."""
import json
import shutil
from pathlib import Path
from typing import List
from command import find_command, run
from constant import site_path
from execute.changed_files import ChangeSet
def _should_build(bv: str, change_set: ChangeSet) -> bool:
if bv == 'next':
return True
if change_set.build_all:
return True
if bv in change_set.force_versions:
return True
needle = f'versioned_docs/version-{bv}/'
return any(needle in f for f in change_set.files)
def _write_build_versions(site: Path, build_versions: List[str]) -> None:
(site / '.build-versions.json').write_text(json.dumps(build_versions) + '\n')
def _move_tree(src: Path, dst: Path) -> None:
"""Replace dst with src. dst is removed first if it exists."""
if dst.exists():
if dst.is_dir():
shutil.rmtree(dst)
else:
dst.unlink()
dst.parent.mkdir(parents=True, exist_ok=True)
shutil.move(str(src), str(dst))
def _merge_tree(src: Path, dst: Path) -> None:
"""Copy src/* into dst/, creating dst if needed. Files in dst are overwritten."""
dst.mkdir(parents=True, exist_ok=True)
for child in src.iterdir():
target = dst / child.name
if child.is_dir():
shutil.copytree(child, target, dirs_exist_ok=True)
else:
shutil.copy2(child, target)
def execute(change_set: ChangeSet) -> None:
"""Build every required version into a single ``build/`` tree.
The flow mirrors what split-version-build.sh did, just in Python:
1. Iterate ``versions[1:] + ["next"]`` (latest is built as part of "next").
2. For each included version, write ``.build-versions.json`` and run ``yarn build``.
Each yarn build OVERWRITES ``build/`` — so we stash per-version output to
``build-{v}/`` and accumulated assets to ``build-assets/`` between runs.
3. After the loop, fold every ``build-{v}/*`` back into ``build/docs/`` and
merge ``build-assets/*`` into ``build/assets/``. Copy ``static/.htaccess``
into ``build/`` (Docusaurus copies the rest of static/ but skips dot-files).
The ``next`` iteration always runs (with ``buildVersions=["current", latest]``);
its output places the latest version's docs under ``build/docs/{latest}/`` and
the "current" docs under ``build/docs/next/`` (per ``versionsMap.current.path``
in docusaurus.config.ts)."""
site = site_path()
yarn = find_command('yarn', msg='yarn is required')
versions: List[str] = json.loads((site / 'versions.json').read_text())
if not versions:
raise RuntimeError('versions.json is empty')
latest = versions[0]
iter_versions = versions[1:] + ['next']
build_dir = site / 'build'
assets_stash = site / 'build-assets'
for bv in iter_versions:
if not _should_build(bv, change_set):
print(f'... {bv} no change, skip')
continue
if bv == 'next':
print(f'... {bv} and {latest} begin build...')
_write_build_versions(site, ['current', latest])
else:
print(f'... {bv} begin build...')
_write_build_versions(site, [bv])
run(yarn, 'build', cwd=site)
# bv == latest never happens here (latest is excluded from iter_versions),
# so we always stash. Stashing the per-version subtree lets the next yarn
# build overwrite build/ without losing what we just produced.
built_subdir = build_dir / 'docs' / bv
if not built_subdir.is_dir():
raise RuntimeError(f'expected {built_subdir} after yarn build, not found')
stash_root = site / f'build-{bv}'
stash_subdir = stash_root / bv
if stash_root.exists():
shutil.rmtree(stash_root)
stash_subdir.parent.mkdir(parents=True, exist_ok=True)
shutil.move(str(built_subdir), str(stash_subdir))
assets_src = build_dir / 'assets'
if assets_src.is_dir():
_merge_tree(assets_src, assets_stash)
print(f'... {bv} build done...')
# Fold per-version stashes back under build/docs/.
for bv in iter_versions:
stash_root = site / f'build-{bv}'
if not stash_root.is_dir():
continue
for child in stash_root.iterdir():
_move_tree(child, build_dir / 'docs' / child.name)
shutil.rmtree(stash_root)
if assets_stash.is_dir():
_merge_tree(assets_stash, build_dir / 'assets')
shutil.rmtree(assets_stash)
htaccess_src = site / 'static' / '.htaccess'
if htaccess_src.is_file():
shutil.copy2(htaccess_src, build_dir / '.htaccess')