blob: 5d8a4d3cb3839c61308f36d35b6561da1d4cdb9b [file] [log] [blame]
#!/usr/bin/env vpython3
# Copyright 2024 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Convenience script to regenerate all auto-generated files."""
import argparse
import contextlib
import functools
from pathlib import Path
import site
import sys
import tempfile
from typing import Any, Callable, Dict, List, Optional
HERE = Path(__file__).resolve().parent
BOXSTER_PYTHON_PATH = HERE.parent.parent / "config" / "python"
site.addsitedir(HERE)
site.addsitedir(BOXSTER_PYTHON_PATH)
# pylint: disable=wrong-import-position
from chromiumos.config.test import fake_config # pylint: disable=import-error
from cros_config_host import cros_config_proto_converter
from cros_config_host import cros_config_schema
from cros_config_host import generate_schema_doc
from cros_config_host import libcros_schema
from cros_config_host import power_manager_prefs_gen_schema
# pylint: enable=wrong-import-position
SCHEMA_YAML = HERE / "cros_config_host" / "cros_config_schema.yaml"
@functools.lru_cache(maxsize=None)
def _load_schema() -> Dict[str, Any]:
"""Load the config schema."""
yaml_doc = power_manager_prefs_gen_schema.generate_yaml()
yaml_doc += SCHEMA_YAML.read_text(encoding="utf-8")
schema = libcros_schema.LoadYaml(yaml_doc)
# Remove useless nodes used for YAML anchors.
schema.pop("powerd_prefs")
schema.pop("powerd_prefs_default")
return schema
def regen_readme(output_path: Path) -> None:
"""Regenerate README.md."""
# Sections of the current README are preserved. Copy to update.
current_readme = HERE / "README.md"
if current_readme != output_path:
output_path.write_text(
current_readme.read_text(encoding="utf-8"),
encoding="utf-8",
)
generate_schema_doc.generate(schema=_load_schema(), output=output_path)
def regen_test_data(
output_path: Path, configs: List[Path], filter_build_details: bool = False
) -> None:
"""Regenerate cros_config_schema test data."""
cros_config_schema.Main(
schema=_load_schema(),
config=None,
output=output_path,
configs=configs,
filter_build_details=filter_build_details,
)
def regen_schema_json(output_path: Path) -> None:
"""Regenerate schema.json."""
schema = dict(_load_schema())
schema["_comment"] = "This file is auto-generated; to update, run regen.py"
schema.pop("typeDefs")
output_path.write_text(libcros_schema.FormatJson(schema), encoding="utf-8")
def regen_proto_converter(output_path: Path) -> None:
"""Regenerate cros_config_proto_converter files."""
# cros_config_proto_converter writes file paths in generated files. We
# change directories and use relative paths to keep paths consistent.
output_dir = output_path.parents[3]
output_path = output_path.relative_to(output_dir)
with contextlib.chdir(output_dir):
cros_config_proto_converter.Main(
project_configs=[fake_config.FAKE_PROJECT_CONFIG],
program_config=fake_config.FAKE_PROGRAM_CONFIG,
output=output_path,
dtd_path=HERE / "cros_config_host" / "media_profiles.dtd",
)
GENERATORS: Dict[str, Callable[[Path], None]] = {
"cros_config_host/schema.json": regen_schema_json,
"test_data/test_import.json": functools.partial(
regen_test_data, configs=[HERE / "test_data" / "test_import.yaml"]
),
"test_data/test_merge.json": functools.partial(
regen_test_data,
configs=[
HERE / "test_data" / "test_merge_base.yaml",
HERE / "test_data" / "test_merge_overlay.yaml",
],
),
"test_data/test_build.json": functools.partial(
regen_test_data, configs=[HERE / "test_data" / "test.yaml"]
),
"test_data/test.json": functools.partial(
regen_test_data,
configs=[HERE / "test_data" / "test.yaml"],
filter_build_details=True,
),
"test_data/test_arm.json": functools.partial(
regen_test_data,
configs=[HERE / "test_data" / "test_arm.yaml"],
filter_build_details=True,
),
"test_data/proto_converter/sw_build_config/fake_project.json": (
regen_proto_converter
),
"README.md": regen_readme,
}
def regenerate_all() -> None:
"""Regenerate all generated files."""
for output_filename, generator in GENERATORS.items():
generator(HERE / output_filename)
def check_all() -> List[Path]:
"""Ensure no generated files need updated.
Returns:
A list of paths that require update.
"""
files_need_updated: List[Path] = []
with tempfile.TemporaryDirectory() as tmpdir:
for output_filename, generator in GENERATORS.items():
current_path = HERE / output_filename
new_path = Path(tmpdir) / output_filename
new_path.parent.mkdir(exist_ok=True, parents=True)
generator(new_path)
if not current_path.exists() or new_path.read_text(
encoding="utf-8"
) != current_path.read_text(encoding="utf-8"):
files_need_updated.append(current_path)
return files_need_updated
def main(argv: Optional[List[str]] = None) -> Optional[int]:
"""CLI frontend."""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--check",
action="store_true",
help="Ensure no generated files need updated.",
)
opts = parser.parse_args(argv)
if opts.check:
files_need_updated = check_all()
if files_need_updated:
print(
"ERROR: Generated files in chromeos-config need updated:\n",
file=sys.stderr,
)
for path in files_need_updated:
print(f"- {path}", file=sys.stderr)
print(f"\nPlease run: {sys.argv[0]}", file=sys.stderr)
return 1
else:
regenerate_all()
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))