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
66 changes: 66 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
name: CI
run-name: CI · ${{ github.event.pull_request.title || github.ref_name }}

on:
pull_request:
Expand All @@ -14,7 +15,42 @@ concurrency:
cancel-in-progress: true

jobs:
changes:
name: Change scope
permissions:
contents: read
actions: read
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
android: ${{ steps.scope.outputs.android }}
native: ${{ steps.scope.outputs.native }}
python: ${{ steps.scope.outputs.python }}
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0
persist-credentials: false
- name: "Comparison base: ${{ github.event.pull_request.base.sha || github.event.before }}"
run: ":"
- name: Classify changes since successful checks
id: scope
env:
BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }}
HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
EVENT_NAME: ${{ github.event_name }}
PR_BRANCH: ${{ github.head_ref }}
PR_NUMBER: ${{ github.event.pull_request.number }}
GH_TOKEN: ${{ github.token }}
run: |
args=(--base "$BASE_SHA" --head "$HEAD_SHA" --github-output "$GITHUB_OUTPUT" --summary "$GITHUB_STEP_SUMMARY")
if [[ "$EVENT_NAME" == push ]]; then args+=(--push); else args+=(--history); fi
python3 scripts/ci_changes.py "${args[@]}"

native-tests:
needs: changes
if: needs.changes.outputs.native == 'true'
name: Native Go tests
runs-on: ubuntu-latest
timeout-minutes: 20
Expand All @@ -37,7 +73,37 @@ jobs:
- name: Run native tests with the race detector
run: bundle exec fastlane android native_tests

python-tests:
needs: changes
if: needs.changes.outputs.python == 'true'
name: Python tests and style
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check out repository
uses: actions/checkout@v6

- name: Set up Ruby and Fastlane
uses: ruby/setup-ruby@v1
with:
ruby-version: "3.4.10"
bundler-cache: true

- uses: actions/setup-python@v6
with:
python-version: "3.12"
cache: pip
cache-dependency-path: requirements-dev.txt

- name: Install Python formatting tools
run: python -m pip install -r requirements-dev.txt

- name: Check Python formatting and Python tests
run: bundle exec fastlane android python_checks

android-tests:
needs: changes
if: needs.changes.outputs.android == 'true'
name: Android tests and checks
runs-on: ubuntu-latest
timeout-minutes: 45
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/pr-artifacts-comment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ jobs:
return;
}

const jobs = await github.paginate(github.rest.actions.listJobsForWorkflowRun, {
...context.repo, run_id: run.id, filter: 'latest', per_page: 100
});
if (jobs.some(job => job.name === 'Android tests and checks' && job.conclusion === 'skipped')) {
core.notice('Android checks were skipped by the change filter; no APK artifacts are expected');
return;
}

const pullNumber = pullRequests[0].number;
const expectedArtifacts = {
debug: `megaproxy-pr-${pullNumber}-debug-apk`,
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ native/*.aar
app/libs/*.aar
app/libs/megaproxy-sources.jar
*.idsig

__pycache__/
.venv/
7 changes: 7 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,18 @@ branch names, credentials, signing material, or other secrets.
- Direct scripts and Gradle tasks may remain implementation details behind Fastlane lanes, but
documentation and CI should normally expose the Fastlane commands.

- Python runtime scripts use the standard library and native `gh` for GitHub access. Format with
pinned Black/isort through `python_format`; `python_tests` and `python_checks` use `PYTHON`.

## CI and artifacts

- Pull requests must run native tests and Android JVM unit/lint/build checks. Do not require an
Android emulator in GitHub Actions: hosted-runner KVM availability proved too unreliable for a
trustworthy required check.
- Compare each suite against its last successful ancestor check in the same PR and base.
Failed/skipped/cancelled jobs do not advance coverage. Fall back to the full PR diff when
history is unavailable; unknown paths and shared build/CI inputs enable all suites. Require `Change scope` and `Python tests and style`
alongside native/Android checks when this workflow is adopted.
- PR builds may publish debug and unsigned APK artifacts. They must never have access to release
signing material and must never produce or publish a signed release APK.
- Surface downloadable APK artifacts in the GitHub Actions job summary in addition to uploading
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,3 +351,10 @@ Android unit-test suites before opening a pull request.
## License

MegaProxy is released under the [MIT License](LICENSE).

### CI tools

CI selects checks from changes since each suite’s last successful ancestor check, with a full PR diff fallback. Use `python3 scripts/github_actions.py` to choose an open
PR and rerun all CI jobs or only failed jobs through GitHub CLI. Supports `--dry-run` and `--yes`/`-y`.
See the [English](docs/en/fastlane.md) or [Russian](docs/ru/fastlane.md) reference for scope rules,
Python formatting/tests and launcher setup.
49 changes: 49 additions & 0 deletions docs/en/fastlane.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,52 @@ Review changes to both `Gemfile` and `Gemfile.lock`. The official Fastlane docum
committing the lock file and using `bundle exec fastlane` locally and in CI.

[Русская версия](../ru/fastlane.md)

## Selective CI and Python tooling

For each suite, CI compares the current PR head with the last successful ancestor check for that
suite. Failed, cancelled and skipped jobs do not count as successful coverage. Candidates must
belong to the same PR and repository, use the same recorded PR base and precede the current run.
Rebased-away commits are ignored. The history search examines the latest 30 completed CI runs on
the branch through gh; missing history, API errors and old runs without a recorded base fall back
to the full PR diff. Pushes to main compare push endpoints. Each suite's baseline and decision are
shown in the Actions summary. Reruns exclude their own run ID from baseline selection.

Python-only changes run Python checks; documentation-only changes skip test jobs. Native production
changes enable Go and Android, while Go test-only changes enable Go. Shared CI/Fastlane inputs and
unknown paths enable all suites. Failed diff calculation fails `Change scope` instead of silently
skipping tests. Skipped Android builds do not publish APK artifacts.

Install the pinned development tools in a virtual environment:

```sh
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -r requirements-dev.txt
bundle exec fastlane android python_format
bundle exec fastlane android python_checks
```

`python_format` applies isort and Black. `python_tests` runs Python unit tests only;
`python_checks` checks formatting/import order and runs those tests. All three respect `PYTHON`
(default: `python3`). Runtime scripts use only Python's standard library. The CI job
`Python tests and style` runs independently of Android builds.

## Interactive GitHub Actions launcher

```sh
python3 scripts/github_actions.py
python3 scripts/github_actions.py --dry-run
python3 scripts/github_actions.py --yes
```

Choose an open PR and either rerun all CI jobs or only failed jobs. Requires GitHub CLI (`gh`)
and its existing authentication (`gh auth login`, `GH_TOKEN` or `GITHUB_TOKEN`). The launcher uses
native gh commands, no custom HTTP client or token storage. `--repo OWNER/REPO` overrides the repo.
`--yes` / `-y` skips final confirmation but retains menu selection and the stale-head check;
`--dry-run` always prevents launching. `q` or Ctrl+C cancels. Only open same-repository PRs are listed.
The script targets an existing completed CI run for the exact current PR commit. Running/queued
jobs and missing runs are rejected; CI normally starts on pushes. Failed-only mode requires a failed
run; cancelled runs can be rerun with all jobs. Launch failures/timeouts are never retried automatically.
Rerunning preserves that run's original commit and diff baseline; push a new commit to reassess scope
against an updated PR base. No device or release workflows are offered.
51 changes: 51 additions & 0 deletions docs/ru/fastlane.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,54 @@ bundle exec fastlane android test
хранить lock-файл в репозитории и использовать `bundle exec fastlane` локально и в CI.

[English version](../en/fastlane.md)

## Выбор проверок CI и инструменты Python

Для каждого набора CI сравнивает текущий head PR с последним успешно проверенным коммитом-предком
для этого набора. Упавшие, отменённые и пропущенные задания не считаются успешной проверкой.
Кандидат должен относиться к тому же PR и репозиторию, иметь ту же сохранённую базу PR и быть старше
текущего прогона. Коммиты из отброшенной после rebase истории не используются. Через gh проверяются
последние 30 завершённых CI-прогонов ветки. Если истории нет, API недоступен или старый прогон не
сохранял базу, используется полный diff PR. Пуши в main сравниваются по началу и концу пуша.
База сравнения и решение для каждого набора видны в summary Actions. При повторе собственный run ID
не используется как предыдущая проверка.

Изменения только Python запускают Python-проверки; изменения только документации пропускают
тестовые задания. Production-код Go включает Go и Android, изменения только Go-тестов — Go.
Общие файлы CI/Fastlane и неизвестные пути включают все проверки. Ошибка вычисления diff приводит
к ошибке `Change scope`, а не к тихому пропуску тестов. При пропуске Android-сборки APK не публикуются.

Установите закреплённые версии инструментов в виртуальное окружение:

```sh
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -r requirements-dev.txt
bundle exec fastlane android python_format
bundle exec fastlane android python_checks
```

`python_format` применяет isort и Black. `python_tests` запускает только Python-тесты;
`python_checks` проверяет форматирование/импорты и запускает тесты. Все три команды учитывают `PYTHON`
(по умолчанию `python3`). Скрипты используют только стандартную библиотеку Python. Задание CI
`Python tests and style` выполняется независимо от Android-сборки.

## Интерактивный запуск GitHub Actions

```sh
python3 scripts/github_actions.py
python3 scripts/github_actions.py --dry-run
python3 scripts/github_actions.py --yes
```

Выберите открытый PR и повтор всего CI либо только упавших заданий. Нужен GitHub CLI (`gh`)
с авторизацией (`gh auth login`, `GH_TOKEN` или `GITHUB_TOKEN`). Скрипт вызывает штатные команды gh,
без своего HTTP-клиента и хранения токенов. `--repo OWNER/REPO` переопределяет репозиторий.
`--yes` / `-y` пропускает последнее подтверждение, сохраняя меню и проверку актуальности коммита;
`--dry-run` всегда запрещает запуск. `q` или Ctrl+C отменяет операцию. В меню только открытые PR
этого репозитория, без форков. Используется существующий завершённый CI-прогон текущего коммита PR.
Активные задания и отсутствие прогона приводят к отказу; обычно CI начинается после пуша.
Повтор только упавших заданий требует failed-прогона; cancelled можно повторить целиком.
Ошибки/таймауты запуска не приводят к автоматической повторной отправке.
Повтор сохраняет исходный коммит и базу diff того прогона; для пересчёта относительно обновлённой
базы PR нужен новый пуш. В меню нет запуска устройств или release-workflow.
25 changes: 25 additions & 0 deletions fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,31 @@ platform :android do
android_checks
end

desc "Run Python unit tests"
lane :python_tests do
python = ENV.fetch("PYTHON", "python3")
sh(python, "-m", "unittest", "discover", "-s", File.join(project_root, "scripts", "tests"))
end

desc "Format Python scripts with isort and Black"
lane :python_format do
python = ENV.fetch("PYTHON", "python3")
Dir.chdir(project_root) do
sh(python, "-m", "isort", "scripts")
sh(python, "-m", "black", "scripts")
end
end

desc "Check Python formatting, import order, and unit tests"
lane :python_checks do
python = ENV.fetch("PYTHON", "python3")
Dir.chdir(project_root) do
sh(python, "-m", "isort", "--check-only", "scripts")
sh(python, "-m", "black", "--check", "scripts")
end
python_tests
end

desc "Build a debug APK after preparing the native library"
lane :debug_artifact do
native_library
Expand Down
3 changes: 3 additions & 0 deletions fastlane/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ Fastlane is the supported entry point for tests and build artifacts. Install Rub

| Command | Purpose |
| --- | --- |
| `bundle exec fastlane android python_format` | Format Python with Black and isort |
| `bundle exec fastlane android python_tests` | Run Python unit tests |
| `bundle exec fastlane android python_checks` | Check Python style and run unit tests |
| `bundle exec fastlane android native_tests` | Run Go tests with the race detector |
| `bundle exec fastlane android android_checks` | Run Android tests and lint, build a debug APK and release APK, and prove that the release APK is unsigned |
| `bundle exec fastlane android test` | Run all native and Android checks |
Expand Down
8 changes: 8 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[tool.black]
line-length = 88
target-version = ["py310"]

[tool.isort]
profile = "black"
line_length = 88
py_version = 310
2 changes: 2 additions & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
black==26.5.1
isort==9.0.1
Loading