Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Purpose

Integration test project for [premex-ab/setup-android](https://github.com/premex-ab/setup-android). Each `sdk-*` folder is a standalone minimal Android Gradle project that builds with a specific cmdline-tools version. CI runs all of them across Ubuntu, Windows, and macOS.

## Build Commands

```bash
# Build a specific SDK version
cd sdk-{N}
./gradlew --no-daemon assembleDebug

# Sync SDK versions (fetches new cmdline-tools from Google, creates folders, updates CI matrix and docs)
python3 scripts/sync-sdk-versions.py
```

## Architecture

- **`sdk-*` folders** — Each is an independent Gradle project with its own wrapper, AGP version, and Java compatibility. The app itself is a no-op `MainActivity` with zero dependencies.
- **`scripts/sync-sdk-versions.py`** — Parses Google's [repository2-3.xml](https://dl.google.com/android/repository/repository2-3.xml) to discover stable cmdline-tools versions, creates missing `sdk-*` folders from templates, and regenerates `.github/workflows/build.yml`, `README.md`, and `CLAUDE.md`.
- **`.github/workflows/build.yml`** — CI matrix: OS × SDK version. Auto-generated by the sync script — do not edit the matrix entries by hand.
- **`.github/workflows/sync-sdk-versions.yml`** — Weekly (Monday 06:00 UTC) cron job that runs the sync script and opens a PR if new versions are found. Uses a GitHub App token for authentication.

## Version Mapping

The sync script maps cmdline-tools major versions to build tooling via `get_build_config()`:

<!-- BEGIN VERSION MAP -->
| cmdline-tools | AGP | Gradle | Java | compileSdk |
|---------------|-----|--------|------|------------|
| 1–6 | 7.2.2 | 7.5.1 | 11 | 32 |
| 7–9 | 7.4.2 | 7.6.4 | 11 | 33 |
| 10–11 | 8.1.4 | 8.4 | 17 | 34 |
| 12–13 | 8.5.2 | 8.7 | 17 | 34 |
| 14+ | 8.7.3 | 8.12 | 17 | 35 |
<!-- END VERSION MAP -->

When adding support for newer AGP/Gradle versions, update the mapping in `scripts/sync-sdk-versions.py`.
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Sample Application

Minimal Android application used to integration-test [premex-ab/setup-android](https://github.com/premex-ab/setup-android).

## Structure

Each `sdk-*` folder is a standalone Android Gradle project targeting a specific [cmdline-tools](https://developer.android.com/tools) version:

<!-- BEGIN SDK TABLE -->
| Folder | cmdline-tools | AGP | Gradle | Java |
|--------|--------------|-----|--------|------|
| sdk-1 | 1.0 (6200805) | 7.2.2 | 7.5.1 | 11 |
| sdk-2 | 2.1 (6609375) | 7.2.2 | 7.5.1 | 11 |
| sdk-3 | 3.0 (6858069) | 7.2.2 | 7.5.1 | 11 |
| sdk-4 | 4.0 (7302050) | 7.2.2 | 7.5.1 | 11 |
| sdk-5 | 5.0 (7583922) | 7.2.2 | 7.5.1 | 11 |
| sdk-6 | 6.0 (8092744) | 7.2.2 | 7.5.1 | 11 |
| sdk-7 | 7.0 (8512546) | 7.4.2 | 7.6.4 | 11 |
| sdk-8 | 8.0 (9123335) | 7.4.2 | 7.6.4 | 11 |
| sdk-9 | 9.0 (9477386) | 7.4.2 | 7.6.4 | 11 |
| sdk-10 | 10.0 (9862592) | 8.1.4 | 8.4 | 17 |
| sdk-11 | 11.0 (10406996) | 8.1.4 | 8.4 | 17 |
| sdk-12 | 12.0 (11076708) | 8.5.2 | 8.7 | 17 |
| sdk-13 | 13.0 (11479570) | 8.5.2 | 8.7 | 17 |
| sdk-16 | 16.0 (12266719) | 8.7.3 | 8.12 | 17 |
| sdk-17 | 17.0 (12700392) | 8.7.3 | 8.12 | 17 |
| sdk-19 | 19.0 (13114758) | 8.7.3 | 8.12 | 17 |
| sdk-20 | 20.0 (14742923) | 8.7.3 | 8.12 | 17 |
<!-- END SDK TABLE -->

## CI

Every push to `main` builds all SDK versions on Ubuntu, Windows, and macOS using `premex-ab/setup-android`.

## Auto-sync

A weekly workflow (`sync-sdk-versions.yml`) fetches the available cmdline-tools versions from Google's [SDK repository](https://dl.google.com/android/repository/repository2-3.xml). If new versions are found, it creates the corresponding `sdk-*` folder and opens a PR.
93 changes: 91 additions & 2 deletions scripts/sync-sdk-versions.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""
Fetches available Android SDK cmdline-tools versions from Google's repository
and creates/updates sdk-* folders and the CI workflow matrix accordingly.
and creates/updates sdk-* folders, the CI workflow matrix, and documentation.
"""

import json
Expand All @@ -15,6 +15,8 @@
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
REPO_XML_URL = "https://dl.google.com/android/repository/repository2-3.xml"
WORKFLOW_PATH = os.path.join(REPO_ROOT, ".github", "workflows", "build.yml")
README_PATH = os.path.join(REPO_ROOT, "README.md")
CLAUDE_MD_PATH = os.path.join(REPO_ROOT, "CLAUDE.md")

# Template files come from the newest existing sdk-* folder
TEMPLATE_FILES = [
Expand All @@ -26,6 +28,7 @@
]

# AGP / Gradle / Java / compileSdk mapping based on cmdline-tools major version
# Source: https://developer.android.com/build/releases/gradle-plugin
def get_build_config(major_version):
if major_version <= 6:
return {"agp": "7.2.2", "gradle": "7.5.1", "java": "11", "compile_sdk": "32"}
Expand Down Expand Up @@ -251,6 +254,91 @@ def update_workflow(versions):
print(f"Updated {WORKFLOW_PATH} with {len(matrix_entries)} SDK versions")


def generate_sdk_table(versions):
"""Generate the per-folder SDK table for README.md."""
existing = sorted(get_existing_sdk_dirs())
lines = [
"| Folder | cmdline-tools | AGP | Gradle | Java |",
"|--------|--------------|-----|--------|------|",
]
for major in existing:
if major in versions:
ver = versions[major]["version"]
build = versions[major]["build"]
config = get_build_config(major)
lines.append(
f"| sdk-{major} | {ver} ({build}) | {config['agp']} | {config['gradle']} | {config['java']} |"
)
return "\n".join(lines)


def generate_version_map():
"""Generate the version mapping table for CLAUDE.md."""
# Collect unique configs in order
configs = []
seen = set()
for major in range(1, 100):
config = get_build_config(major)
key = (config["agp"], config["gradle"], config["java"], config["compile_sdk"])
if key not in seen:
seen.add(key)
configs.append((major, config))
# Stop after we've seen the "default" catch-all config twice
if len(configs) > 1 and major > 50:
break

lines = [
"| cmdline-tools | AGP | Gradle | Java | compileSdk |",
"|---------------|-----|--------|------|------------|",
]

for i, (start_major, config) in enumerate(configs):
if i + 1 < len(configs):
end_major = configs[i + 1][0] - 1
label = f"{start_major}\u2013{end_major}" if start_major != end_major else str(start_major)
else:
label = f"{start_major}+"
lines.append(
f"| {label} | {config['agp']} | {config['gradle']} | {config['java']} | {config['compile_sdk']} |"
)

return "\n".join(lines)


def update_doc(file_path, begin_marker, end_marker, content):
"""Replace content between markers in a file."""
if not os.path.exists(file_path):
return

with open(file_path, "r") as f:
text = f.read()

pattern = re.compile(
rf"({re.escape(begin_marker)}\n).*?({re.escape(end_marker)})",
re.DOTALL,
)

if not pattern.search(text):
print(f"Warning: markers not found in {file_path}")
return

new_text = pattern.sub(rf"\g<1>{content}\n\g<2>", text)

with open(file_path, "w") as f:
f.write(new_text)

print(f"Updated {file_path}")


def update_docs(versions):
"""Update README.md and CLAUDE.md with current version tables."""
sdk_table = generate_sdk_table(versions)
version_map = generate_version_map()

update_doc(README_PATH, "<!-- BEGIN SDK TABLE -->", "<!-- END SDK TABLE -->", sdk_table)
update_doc(CLAUDE_MD_PATH, "<!-- BEGIN VERSION MAP -->", "<!-- END VERSION MAP -->", version_map)


def main():
versions = fetch_versions()
print(f"Found {len(versions)} stable cmdline-tools versions: {sorted(versions.keys())}")
Expand All @@ -267,8 +355,9 @@ def main():
for major in sorted(new_versions.keys()):
create_sdk_folder(major, new_versions[major]["build"])

# Always regenerate workflow to keep it in sync
# Always regenerate workflow and docs to keep them in sync
update_workflow(versions)
update_docs(versions)

if new_versions:
# Output for GitHub Actions
Expand Down
Loading