chore: add breaking change detector #6
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Breaking Change Detector | |
| on: | |
| pull_request: | |
| branches: | |
| - main | |
| paths: | |
| - 'src/**' | |
| jobs: | |
| check-api: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # Required for accessing the main branch history | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.10' | |
| - name: Install the latest version of uv | |
| uses: astral-sh/setup-uv@v5 | |
| - name: Install dependencies | |
| # Syncing installs the package and its dependencies (populating google.* namespace) | |
| # Installing 'griffe' ensures the tool is available in the environment | |
| run: | | |
| uv sync --extra test | |
| uv pip install griffe | |
| - name: Run Breaking Change Detection | |
| # Uses 'uv run python' to execute in the environment with all dependencies installed | |
| run: | | |
| uv run python - <<EOF | |
| import sys | |
| import griffe | |
| from griffe import find_breaking_changes, load, load_git | |
| def check_return_types(old_obj, new_obj): | |
| """Custom check to strictly compare return type annotations.""" | |
| errors = [] | |
| # Check Runner.run specifically, or extend to check all methods | |
| try: | |
| old_run = old_obj.members["Runner"].members["run"] | |
| new_run = new_obj.members["Runner"].members["run"] | |
| # Convert to string and strip to compare the annotation text | |
| old_ret = str(old_run.returns).strip() | |
| new_ret = str(new_run.returns).strip() | |
| if old_ret != new_ret: | |
| errors.append( | |
| f"Breaking Change in {new_run.path}: Return type changed from '{old_ret}' to '{new_ret}'" | |
| ) | |
| except KeyError: | |
| pass # Member missing (will be caught by standard checks) | |
| except AttributeError: | |
| pass # Member might not be a function | |
| return errors | |
| try: | |
| print("Loading new API (from local source)...") | |
| # allow_inspection=False forces static analysis, preventing sys.modules caching issues | |
| new_api = load("google.adk", search_paths=["src"], allow_inspection=False) | |
| print("Loading baseline API (from main branch)...") | |
| old_api = load_git("google.adk", ref="main", search_paths=["src"], allow_inspection=False) | |
| print("Running detection...") | |
| # 1. Standard Griffe Checks (Removals, Parameter changes) | |
| breakages = list(find_breaking_changes(old_api, new_api)) | |
| # 2. Custom Return Type Checks (Workaround for Griffe limitation) | |
| custom_errors = check_return_types(old_api, new_api) | |
| found_issues = False | |
| if breakages: | |
| found_issues = True | |
| print(f"::error::Found {len(breakages)} standard breaking changes!") | |
| for breakage in breakages: | |
| print(breakage.explain()) | |
| if custom_errors: | |
| found_issues = True | |
| print(f"::error::Found {len(custom_errors)} return type mismatches!") | |
| for error in custom_errors: | |
| print(f"- {error}") | |
| if found_issues: | |
| sys.exit(1) | |
| print("Success: No breaking changes detected.") | |
| except Exception as e: | |
| print(f"::error::Breaking change detection failed: {e}") | |
| sys.exit(1) | |
| EOF |