Fix scaffolding generations (#138820)
parent
bc5146db3c
commit
4ed4c2cc5c
|
@ -19,6 +19,7 @@ from voluptuous.humanize import humanize_error
|
|||
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from script.util import sort_manifest as util_sort_manifest
|
||||
|
||||
from .model import Config, Integration, ScaledQualityScaleTiers
|
||||
|
||||
|
@ -376,20 +377,20 @@ def validate_manifest(integration: Integration, core_components_dir: Path) -> No
|
|||
validate_version(integration)
|
||||
|
||||
|
||||
_SORT_KEYS = {"domain": ".domain", "name": ".name"}
|
||||
|
||||
|
||||
def _sort_manifest_keys(key: str) -> str:
|
||||
return _SORT_KEYS.get(key, key)
|
||||
|
||||
|
||||
def sort_manifest(integration: Integration, config: Config) -> bool:
|
||||
"""Sort manifest."""
|
||||
keys = list(integration.manifest.keys())
|
||||
if (keys_sorted := sorted(keys, key=_sort_manifest_keys)) != keys:
|
||||
manifest = {key: integration.manifest[key] for key in keys_sorted}
|
||||
if integration.manifest_path is None:
|
||||
integration.add_error(
|
||||
"manifest",
|
||||
"Manifest path not set, unable to sort manifest keys",
|
||||
)
|
||||
return False
|
||||
|
||||
if util_sort_manifest(integration.manifest):
|
||||
if config.action == "generate":
|
||||
integration.manifest_path.write_text(json.dumps(manifest, indent=2))
|
||||
integration.manifest_path.write_text(
|
||||
json.dumps(integration.manifest, indent=2) + "\n"
|
||||
)
|
||||
text = "have been sorted"
|
||||
else:
|
||||
text = "are not sorted correctly"
|
||||
|
|
|
@ -8,6 +8,7 @@ import sys
|
|||
from script.util import valid_integration
|
||||
|
||||
from . import docs, error, gather_info, generate
|
||||
from .model import Info
|
||||
|
||||
TEMPLATES = [
|
||||
p.name for p in (Path(__file__).parent / "templates").glob("*") if p.is_dir()
|
||||
|
@ -28,6 +29,40 @@ def get_arguments() -> argparse.Namespace:
|
|||
return parser.parse_args()
|
||||
|
||||
|
||||
def run_process(name: str, cmd: list[str], info: Info) -> None:
|
||||
"""Run a sub process and handle the result.
|
||||
|
||||
:param name: The name of the sub process used in reporting.
|
||||
:param cmd: The sub process arguments.
|
||||
:param info: The Info object.
|
||||
:raises subprocess.CalledProcessError: If the subprocess failed.
|
||||
|
||||
If the sub process was successful print a success message, otherwise
|
||||
print an error message and raise a subprocess.CalledProcessError.
|
||||
"""
|
||||
print(f"Command: {' '.join(cmd)}")
|
||||
print()
|
||||
result: subprocess.CompletedProcess = subprocess.run(cmd, check=False)
|
||||
if result.returncode == 0:
|
||||
print()
|
||||
print(f"Completed {name} successfully.")
|
||||
print()
|
||||
return
|
||||
|
||||
print()
|
||||
print(f"Fatal Error: {name} failed with exit code {result.returncode}")
|
||||
print()
|
||||
if info.is_new:
|
||||
print("This is a bug, please report an issue!")
|
||||
else:
|
||||
print(
|
||||
"This may be an existing issue with your integration,",
|
||||
"if so fix and run `script.scaffold` again,",
|
||||
"otherwise please report an issue.",
|
||||
)
|
||||
result.check_returncode()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
"""Scaffold an integration."""
|
||||
if not Path("requirements_all.txt").is_file():
|
||||
|
@ -60,36 +95,36 @@ def main() -> int:
|
|||
|
||||
generate.generate(template, info)
|
||||
|
||||
hassfest_args = [
|
||||
"python",
|
||||
"-m",
|
||||
"script.hassfest",
|
||||
]
|
||||
|
||||
# If we wanted a new integration, we've already done our work.
|
||||
if args.template != "integration":
|
||||
generate.generate(args.template, info)
|
||||
else:
|
||||
hassfest_args.extend(
|
||||
[
|
||||
"--integration-path",
|
||||
info.integration_dir,
|
||||
"--skip-plugins",
|
||||
"quality_scale", # Skip quality scale as it will fail for newly generated integrations.
|
||||
]
|
||||
)
|
||||
|
||||
# Always output sub commands as the output will contain useful information if a command fails.
|
||||
print("Running hassfest to pick up new information.")
|
||||
subprocess.run(hassfest_args, check=True)
|
||||
print()
|
||||
run_process(
|
||||
"hassfest",
|
||||
[
|
||||
"python",
|
||||
"-m",
|
||||
"script.hassfest",
|
||||
"--integration-path",
|
||||
str(info.integration_dir),
|
||||
"--skip-plugins",
|
||||
"quality_scale", # Skip quality scale as it will fail for newly generated integrations.
|
||||
],
|
||||
info,
|
||||
)
|
||||
|
||||
print("Running gen_requirements_all to pick up new information.")
|
||||
subprocess.run(["python", "-m", "script.gen_requirements_all"], check=True)
|
||||
print()
|
||||
run_process(
|
||||
"gen_requirements_all",
|
||||
["python", "-m", "script.gen_requirements_all"],
|
||||
info,
|
||||
)
|
||||
|
||||
print("Running script/translations_develop to pick up new translation strings.")
|
||||
subprocess.run(
|
||||
print("Running translations to pick up new translation strings.")
|
||||
run_process(
|
||||
"translations",
|
||||
[
|
||||
"python",
|
||||
"-m",
|
||||
|
@ -98,14 +133,13 @@ def main() -> int:
|
|||
"--integration",
|
||||
info.domain,
|
||||
],
|
||||
check=True,
|
||||
info,
|
||||
)
|
||||
print()
|
||||
|
||||
if args.develop:
|
||||
print("Running tests")
|
||||
print(f"$ python3 -b -m pytest -vvv tests/components/{info.domain}")
|
||||
subprocess.run(
|
||||
run_process(
|
||||
"pytest",
|
||||
[
|
||||
"python3",
|
||||
"-b",
|
||||
|
@ -114,9 +148,8 @@ def main() -> int:
|
|||
"-vvv",
|
||||
f"tests/components/{info.domain}",
|
||||
],
|
||||
check=True,
|
||||
info,
|
||||
)
|
||||
print()
|
||||
|
||||
docs.print_relevant_docs(args.template, info)
|
||||
|
||||
|
@ -126,6 +159,8 @@ def main() -> int:
|
|||
if __name__ == "__main__":
|
||||
try:
|
||||
sys.exit(main())
|
||||
except subprocess.CalledProcessError as err:
|
||||
sys.exit(err.returncode)
|
||||
except error.ExitApp as err:
|
||||
print()
|
||||
print(f"Fatal Error: {err.reason}")
|
||||
|
|
|
@ -4,9 +4,12 @@ from __future__ import annotations
|
|||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import attr
|
||||
|
||||
from script.util import sort_manifest
|
||||
|
||||
from .const import COMPONENT_DIR, TESTS_DIR
|
||||
|
||||
|
||||
|
@ -44,16 +47,19 @@ class Info:
|
|||
"""Path to the manifest."""
|
||||
return COMPONENT_DIR / self.domain / "manifest.json"
|
||||
|
||||
def manifest(self) -> dict:
|
||||
def manifest(self) -> dict[str, Any]:
|
||||
"""Return integration manifest."""
|
||||
return json.loads(self.manifest_path.read_text())
|
||||
|
||||
def update_manifest(self, **kwargs) -> None:
|
||||
"""Update the integration manifest."""
|
||||
print(f"Updating {self.domain} manifest: {kwargs}")
|
||||
self.manifest_path.write_text(
|
||||
json.dumps({**self.manifest(), **kwargs}, indent=2) + "\n"
|
||||
)
|
||||
|
||||
# Sort keys in manifest so we don't trigger hassfest errors.
|
||||
manifest: dict[str, Any] = {**self.manifest(), **kwargs}
|
||||
sort_manifest(manifest)
|
||||
|
||||
self.manifest_path.write_text(json.dumps(manifest, indent=2) + "\n")
|
||||
|
||||
@property
|
||||
def strings_path(self) -> Path:
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""Utility functions for the scaffold script."""
|
||||
|
||||
import argparse
|
||||
from typing import Any
|
||||
|
||||
from .const import COMPONENT_DIR
|
||||
|
||||
|
@ -13,3 +14,23 @@ def valid_integration(integration):
|
|||
)
|
||||
|
||||
return integration
|
||||
|
||||
|
||||
_MANIFEST_SORT_KEYS = {"domain": ".domain", "name": ".name"}
|
||||
|
||||
|
||||
def _sort_manifest_keys(key: str) -> str:
|
||||
"""Sort manifest keys."""
|
||||
return _MANIFEST_SORT_KEYS.get(key, key)
|
||||
|
||||
|
||||
def sort_manifest(manifest: dict[str, Any]) -> bool:
|
||||
"""Sort manifest."""
|
||||
keys = list(manifest)
|
||||
if (keys_sorted := sorted(keys, key=_sort_manifest_keys)) != keys:
|
||||
sorted_manifest = {key: manifest[key] for key in keys_sorted}
|
||||
manifest.clear()
|
||||
manifest.update(sorted_manifest)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
|
Loading…
Reference in New Issue