mirror of
https://github.com/esphome/esphome.git
synced 2025-11-18 07:45:56 +00:00
cache
This commit is contained in:
11
.github/workflows/ci.yml
vendored
11
.github/workflows/ci.yml
vendored
@@ -192,6 +192,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||||
|
- name: Restore components graph cache
|
||||||
|
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
|
with:
|
||||||
|
path: .temp/components_graph.json
|
||||||
|
key: components-graph-${{ hashFiles('esphome/components/**/__init__.py') }}
|
||||||
- name: Determine which tests to run
|
- name: Determine which tests to run
|
||||||
id: determine
|
id: determine
|
||||||
env:
|
env:
|
||||||
@@ -216,6 +221,12 @@ jobs:
|
|||||||
echo "cpp-unit-tests-run-all=$(echo "$output" | jq -r '.cpp_unit_tests_run_all')" >> $GITHUB_OUTPUT
|
echo "cpp-unit-tests-run-all=$(echo "$output" | jq -r '.cpp_unit_tests_run_all')" >> $GITHUB_OUTPUT
|
||||||
echo "cpp-unit-tests-components=$(echo "$output" | jq -c '.cpp_unit_tests_components')" >> $GITHUB_OUTPUT
|
echo "cpp-unit-tests-components=$(echo "$output" | jq -c '.cpp_unit_tests_components')" >> $GITHUB_OUTPUT
|
||||||
echo "component-test-batches=$(echo "$output" | jq -c '.component_test_batches')" >> $GITHUB_OUTPUT
|
echo "component-test-batches=$(echo "$output" | jq -c '.component_test_batches')" >> $GITHUB_OUTPUT
|
||||||
|
- name: Save components graph cache
|
||||||
|
if: github.ref == 'refs/heads/dev'
|
||||||
|
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
|
with:
|
||||||
|
path: .temp/components_graph.json
|
||||||
|
key: components-graph-${{ hashFiles('esphome/components/**/__init__.py') }}
|
||||||
|
|
||||||
integration-tests:
|
integration-tests:
|
||||||
name: Run integration tests
|
name: Run integration tests
|
||||||
|
|||||||
@@ -52,6 +52,10 @@ BASE_BUS_COMPONENTS = {
|
|||||||
"remote_receiver",
|
"remote_receiver",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Cache version for components graph
|
||||||
|
# Increment this when the cache format or graph building logic changes
|
||||||
|
COMPONENTS_GRAPH_CACHE_VERSION = 1
|
||||||
|
|
||||||
|
|
||||||
def parse_list_components_output(output: str) -> list[str]:
|
def parse_list_components_output(output: str) -> list[str]:
|
||||||
"""Parse the output from list-components.py script.
|
"""Parse the output from list-components.py script.
|
||||||
@@ -752,20 +756,69 @@ def resolve_auto_load(
|
|||||||
return auto_load()
|
return auto_load()
|
||||||
|
|
||||||
|
|
||||||
|
def _get_components_graph_cache_key() -> str:
|
||||||
|
"""Generate cache key based on all component __init__.py file hashes.
|
||||||
|
|
||||||
|
Uses git ls-files with sha1 hashes to generate a stable cache key that works
|
||||||
|
across different machines and CI runs. This is faster and more reliable than
|
||||||
|
reading file contents or using modification times.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
SHA256 hex string uniquely identifying the current component state
|
||||||
|
"""
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
# Use git ls-files -s to get sha1 hashes of all component __init__.py files
|
||||||
|
# Format: <mode> <sha1> <stage> <path>
|
||||||
|
# This is fast and works consistently across CI and local dev
|
||||||
|
cmd = ["git", "ls-files", "-s", "esphome/components/**/__init__.py"]
|
||||||
|
result = subprocess.run(
|
||||||
|
cmd, capture_output=True, text=True, check=True, cwd=root_path
|
||||||
|
)
|
||||||
|
|
||||||
|
# Hash the git output (includes file paths and their sha1 hashes)
|
||||||
|
# This changes only when component __init__.py files actually change
|
||||||
|
hasher = hashlib.sha256()
|
||||||
|
hasher.update(result.stdout.encode())
|
||||||
|
|
||||||
|
return hasher.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
def create_components_graph() -> dict[str, list[str]]:
|
def create_components_graph() -> dict[str, list[str]]:
|
||||||
"""Create a graph of component dependencies.
|
"""Create a graph of component dependencies (cached).
|
||||||
|
|
||||||
|
This function is expensive (5-6 seconds) because it imports all ESPHome components
|
||||||
|
to extract their DEPENDENCIES and AUTO_LOAD metadata. The result is cached based
|
||||||
|
on component file modification times, so unchanged components don't trigger a rebuild.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary mapping parent components to their children (dependencies)
|
Dictionary mapping parent components to their children (dependencies)
|
||||||
"""
|
"""
|
||||||
from pathlib import Path
|
# Check cache first - use fixed filename since GitHub Actions cache doesn't support wildcards
|
||||||
|
cache_file = Path(temp_folder) / "components_graph.json"
|
||||||
|
|
||||||
|
if cache_file.exists():
|
||||||
|
try:
|
||||||
|
cached_data = json.loads(cache_file.read_text())
|
||||||
|
except (OSError, json.JSONDecodeError):
|
||||||
|
# Cache file corrupted or unreadable, rebuild
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# Verify cache version matches
|
||||||
|
if cached_data.get("_version") == COMPONENTS_GRAPH_CACHE_VERSION:
|
||||||
|
# Verify cache is for current component state
|
||||||
|
cache_key = _get_components_graph_cache_key()
|
||||||
|
if cached_data.get("_cache_key") == cache_key:
|
||||||
|
return cached_data.get("graph", {})
|
||||||
|
# Cache key mismatch - stale cache, rebuild
|
||||||
|
# Cache version mismatch - incompatible format, rebuild
|
||||||
|
|
||||||
from esphome import const
|
from esphome import const
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
from esphome.loader import ComponentManifest, get_component, get_platform
|
from esphome.loader import ComponentManifest, get_component, get_platform
|
||||||
|
|
||||||
# The root directory of the repo
|
# The root directory of the repo
|
||||||
root = Path(__file__).parent.parent
|
root = Path(root_path)
|
||||||
components_dir = root / ESPHOME_COMPONENTS_PATH
|
components_dir = root / ESPHOME_COMPONENTS_PATH
|
||||||
# Fake some directory so that get_component works
|
# Fake some directory so that get_component works
|
||||||
CORE.config_path = root
|
CORE.config_path = root
|
||||||
@@ -842,6 +895,15 @@ def create_components_graph() -> dict[str, list[str]]:
|
|||||||
# restore config
|
# restore config
|
||||||
CORE.data[KEY_CORE] = TARGET_CONFIGURATIONS[0]
|
CORE.data[KEY_CORE] = TARGET_CONFIGURATIONS[0]
|
||||||
|
|
||||||
|
# Save to cache with version and cache key for validation
|
||||||
|
cache_data = {
|
||||||
|
"_version": COMPONENTS_GRAPH_CACHE_VERSION,
|
||||||
|
"_cache_key": _get_components_graph_cache_key(),
|
||||||
|
"graph": components_graph,
|
||||||
|
}
|
||||||
|
cache_file.parent.mkdir(exist_ok=True)
|
||||||
|
cache_file.write_text(json.dumps(cache_data))
|
||||||
|
|
||||||
return components_graph
|
return components_graph
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user