Make hassfest.dependencies faster with multiprocessing (#81486)
* hassfest.dependencies: split to two loops * hassfest.dependencies: use multiprocessing for import scan
This commit is contained in:
parent
0e7d7f32c1
commit
196f5702b8
1 changed files with 59 additions and 27 deletions
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import ast
|
import ast
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
import multiprocessing
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
|
@ -227,24 +228,43 @@ def find_non_referenced_integrations(
|
||||||
return referenced
|
return referenced
|
||||||
|
|
||||||
|
|
||||||
def validate_dependencies(
|
def _compute_integration_dependencies(
|
||||||
integrations: dict[str, Integration],
|
|
||||||
integration: Integration,
|
integration: Integration,
|
||||||
check_dependencies: bool,
|
) -> tuple[str, dict[Path, set[str]] | None]:
|
||||||
) -> None:
|
"""Compute integration dependencies."""
|
||||||
"""Validate all dependencies."""
|
|
||||||
# Some integrations are allowed to have violations.
|
# Some integrations are allowed to have violations.
|
||||||
if integration.domain in IGNORE_VIOLATIONS:
|
if integration.domain in IGNORE_VIOLATIONS:
|
||||||
return
|
return (integration.domain, None)
|
||||||
|
|
||||||
# Find usage of hass.components
|
# Find usage of hass.components
|
||||||
collector = ImportCollector(integration)
|
collector = ImportCollector(integration)
|
||||||
collector.collect()
|
collector.collect()
|
||||||
|
return (integration.domain, collector.referenced)
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_dependency_imports(
|
||||||
|
integrations: dict[str, Integration],
|
||||||
|
) -> None:
|
||||||
|
"""Validate all dependencies."""
|
||||||
|
|
||||||
|
# Find integration dependencies with multiprocessing
|
||||||
|
# (because it takes some time to parse thousands of files)
|
||||||
|
with multiprocessing.Pool() as pool:
|
||||||
|
integration_imports = dict(
|
||||||
|
pool.imap_unordered(
|
||||||
|
_compute_integration_dependencies,
|
||||||
|
integrations.values(),
|
||||||
|
chunksize=10,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
for integration in integrations.values():
|
||||||
|
referenced = integration_imports[integration.domain]
|
||||||
|
if not referenced: # Either ignored or has no references
|
||||||
|
continue
|
||||||
|
|
||||||
for domain in sorted(
|
for domain in sorted(
|
||||||
find_non_referenced_integrations(
|
find_non_referenced_integrations(integrations, integration, referenced)
|
||||||
integrations, integration, collector.referenced
|
|
||||||
)
|
|
||||||
):
|
):
|
||||||
integration.add_error(
|
integration.add_error(
|
||||||
"dependencies",
|
"dependencies",
|
||||||
|
@ -252,11 +272,6 @@ def validate_dependencies(
|
||||||
"or 'after_dependencies'",
|
"or 'after_dependencies'",
|
||||||
)
|
)
|
||||||
|
|
||||||
if check_dependencies:
|
|
||||||
_check_circular_deps(
|
|
||||||
integrations, integration.domain, integration, set(), deque()
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _check_circular_deps(
|
def _check_circular_deps(
|
||||||
integrations: dict[str, Integration],
|
integrations: dict[str, Integration],
|
||||||
|
@ -266,6 +281,7 @@ def _check_circular_deps(
|
||||||
checking: deque[str],
|
checking: deque[str],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Check for circular dependencies pointing at starting_domain."""
|
"""Check for circular dependencies pointing at starting_domain."""
|
||||||
|
|
||||||
if integration.domain in checked or integration.domain in checking:
|
if integration.domain in checked or integration.domain in checking:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -297,17 +313,21 @@ def _check_circular_deps(
|
||||||
checking.remove(integration.domain)
|
checking.remove(integration.domain)
|
||||||
|
|
||||||
|
|
||||||
def validate(integrations: dict[str, Integration], config: Config) -> None:
|
def _validate_circular_dependencies(integrations: dict[str, Integration]) -> None:
|
||||||
"""Handle dependencies for integrations."""
|
|
||||||
# check for non-existing dependencies
|
|
||||||
for integration in integrations.values():
|
for integration in integrations.values():
|
||||||
validate_dependencies(
|
if integration.domain in IGNORE_VIOLATIONS:
|
||||||
integrations,
|
continue
|
||||||
integration,
|
|
||||||
check_dependencies=not config.specific_integrations,
|
_check_circular_deps(
|
||||||
|
integrations, integration.domain, integration, set(), deque()
|
||||||
)
|
)
|
||||||
|
|
||||||
if config.specific_integrations:
|
|
||||||
|
def _validate_dependencies_exist(
|
||||||
|
integrations: dict[str, Integration],
|
||||||
|
) -> None:
|
||||||
|
for integration in integrations.values():
|
||||||
|
if not integration.manifest:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# check that all referenced dependencies exist
|
# check that all referenced dependencies exist
|
||||||
|
@ -323,3 +343,15 @@ def validate(integrations: dict[str, Integration], config: Config) -> None:
|
||||||
integration.add_error(
|
integration.add_error(
|
||||||
"dependencies", f"Dependency {dep} does not exist"
|
"dependencies", f"Dependency {dep} does not exist"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def validate(
|
||||||
|
integrations: dict[str, Integration],
|
||||||
|
config: Config,
|
||||||
|
) -> None:
|
||||||
|
"""Handle dependencies for integrations."""
|
||||||
|
_validate_dependency_imports(integrations)
|
||||||
|
|
||||||
|
if not config.specific_integrations:
|
||||||
|
_validate_dependencies_exist(integrations)
|
||||||
|
_validate_circular_dependencies(integrations)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue