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:
Aarni Koskela 2023-03-30 12:25:14 +03:00 committed by GitHub
parent 0e7d7f32c1
commit 196f5702b8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -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)