Initial commit: local_machine docs and scripts
This commit is contained in:
679
docs/connectivity/BUILD_AAB_FROM_RUSSIA_GUIDE.md
Normal file
679
docs/connectivity/BUILD_AAB_FROM_RUSSIA_GUIDE.md
Normal file
@@ -0,0 +1,679 @@
|
||||
# 🏗️ Сборка AAB/APK из России — Полное руководство
|
||||
|
||||
> **Дата создания:** 2026-02-28
|
||||
> **Проблема:** Java TLS заблокирован российским DPI → Gradle не может скачать зависимости
|
||||
> **Решение:** Maven proxy на прод-сервере + SSH-туннель + обход SDK auto-download
|
||||
> **Время сборки:** ~1.5 мин (AAB), ~46 сек (APK после AAB)
|
||||
>
|
||||
> **📖 См. также:** [SETUP_AND_BUILD_GUIDE.md](./SETUP_AND_BUILD_GUIDE.md) — общее руководство по установке и сборке (для работы не из России)
|
||||
|
||||
---
|
||||
|
||||
## 📋 Содержание
|
||||
|
||||
1. [Суть проблемы](#-суть-проблемы)
|
||||
2. [Предварительные требования](#-предварительные-требования)
|
||||
3. [Шаг 1: Maven proxy на прод-сервере](#-шаг-1-maven-proxy-на-прод-сервере)
|
||||
4. [Шаг 2: SSH-туннель](#-шаг-2-ssh-туннель)
|
||||
5. [Шаг 3: Сборка AAB](#-шаг-3-сборка-aab)
|
||||
6. [Шаг 4: Сборка APK для эмулятора](#-шаг-4-сборка-apk-для-эмулятора)
|
||||
7. [Шаг 5: Установка в эмулятор](#-шаг-5-установка-в-эмулятор)
|
||||
8. [Быстрый скрипт «всё одной командой»](#-быстрый-скрипт-всё-одной-командой)
|
||||
9. [DOs & DON'Ts](#-dos--donts)
|
||||
10. [Конфигурация Gradle (что изменено и почему)](#-конфигурация-gradle-что-изменено-и-почему)
|
||||
11. [Troubleshooting](#-troubleshooting)
|
||||
12. [Версионирование](#-версионирование)
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Суть проблемы
|
||||
|
||||
### Почему Gradle не работает из России напрямую
|
||||
|
||||
```
|
||||
Gradle → Java TLS → dl.google.com / repo1.maven.org
|
||||
↑
|
||||
DPI (Deep Packet Inspection) блокирует TLS handshake
|
||||
Java получает: "Remote host terminated the handshake"
|
||||
```
|
||||
|
||||
**Ключевой факт:** `curl` работает нормально (macOS LibreSSL), а Java — нет (JSSE TLS implementation). DPI по-разному обрабатывает TLS Client Hello от разных реализаций.
|
||||
|
||||
### Решение: Maven proxy
|
||||
|
||||
```
|
||||
Gradle → HTTP (127.0.0.1:8765) → SSH-туннель → Прод-сервер (python proxy)
|
||||
↓
|
||||
HTTPS → dl.google.com
|
||||
HTTPS → repo1.maven.org
|
||||
HTTPS → plugins.gradle.org
|
||||
```
|
||||
|
||||
Java работает по **HTTP** (без TLS) до localhost. Прод-сервер в Digital Ocean скачивает по **HTTPS** без DPI.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Предварительные требования
|
||||
|
||||
> **📖 Перед началом:** Убедитесь, что выполнены базовые требования из [SETUP_AND_BUILD_GUIDE.md](./SETUP_AND_BUILD_GUIDE.md):
|
||||
> - Java OpenJDK 21+ установлен
|
||||
> - Android SDK установлен (через Android Studio GUI)
|
||||
> - Build Tools установлены
|
||||
> - Эмулятор настроен (см. [EMULATOR_STARTUP_GUIDE.md](./EMULATOR_STARTUP_GUIDE.md))
|
||||
|
||||
### Локальная машина (macOS)
|
||||
|
||||
| Компонент | Версия | Путь |
|
||||
|-----------|--------|------|
|
||||
| Java (OpenJDK) | 21.0.9 | `/opt/homebrew/opt/openjdk@21` |
|
||||
| Android SDK | API 34-36 | `$HOME/Library/Android/sdk` |
|
||||
| Build Tools | 35.0.1 | `$HOME/Library/Android/sdk/build-tools/35.0.1` |
|
||||
| Gradle Wrapper | 8.11.1 | `frontend/android/gradle/wrapper/` |
|
||||
| AGP | 8.7.2 | `frontend/android/build.gradle` |
|
||||
| Capacitor | 7.4.x | `frontend/package.json` |
|
||||
|
||||
### Прод-сервер
|
||||
|
||||
| Компонент | Значение |
|
||||
|-----------|----------|
|
||||
| SSH хост | `hunab-prod` (209.38.32.21) |
|
||||
| Python | 3.12+ (предустановлен) |
|
||||
| Порт proxy | 8765 (localhost) |
|
||||
|
||||
### Проверка готовности
|
||||
|
||||
```bash
|
||||
# Java
|
||||
/opt/homebrew/opt/openjdk@21/bin/java -version
|
||||
# → openjdk version "21.0.9"
|
||||
|
||||
# Android SDK
|
||||
ls $HOME/Library/Android/sdk/platforms/
|
||||
# → android-34 android-35 android-36
|
||||
|
||||
# Build Tools
|
||||
ls $HOME/Library/Android/sdk/build-tools/
|
||||
# → 35.0.1 36.1.0 (34.0.0 и 35.0.0 УДАЛЕНЫ — были пустые!)
|
||||
|
||||
# SSH к проду
|
||||
ssh hunab-prod "echo OK"
|
||||
# → OK
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Шаг 1: Maven proxy на прод-сервере
|
||||
|
||||
### 1.1 Скопировать скрипт прокси на прод
|
||||
|
||||
Создать файл `/tmp/maven-proxy.py` локально:
|
||||
|
||||
```bash
|
||||
cat > /tmp/maven-proxy.py << 'PYEOF'
|
||||
#!/usr/bin/env python3
|
||||
"""Многопоточный Maven reverse proxy для Gradle.
|
||||
Обходит Java TLS/DPI блокировку при сборке Android из РФ.
|
||||
Поддерживает GET и HEAD запросы (Gradle использует оба).
|
||||
"""
|
||||
import http.server, socketserver, urllib.request, ssl, sys
|
||||
|
||||
REPOS = {
|
||||
'/google/': 'https://dl.google.com/dl/android/maven2/',
|
||||
'/central/': 'https://repo1.maven.org/maven2/',
|
||||
'/gradle/': 'https://plugins.gradle.org/m2/',
|
||||
}
|
||||
|
||||
class H(http.server.BaseHTTPRequestHandler):
|
||||
def _p(self, m='GET'):
|
||||
for pfx, base in REPOS.items():
|
||||
if self.path.startswith(pfx):
|
||||
url = base + self.path[len(pfx):]
|
||||
try:
|
||||
r = urllib.request.urlopen(
|
||||
urllib.request.Request(url, headers={'User-Agent':'Gradle'}, method=m),
|
||||
timeout=300,
|
||||
context=ssl.create_default_context()
|
||||
)
|
||||
d = r.read() if m=='GET' else b''
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', r.headers.get('Content-Type','application/octet-stream'))
|
||||
self.send_header('Content-Length', r.headers.get('Content-Length',str(len(d))))
|
||||
self.end_headers()
|
||||
if m=='GET': self.wfile.write(d)
|
||||
except urllib.error.HTTPError as e:
|
||||
self.send_response(e.code); self.end_headers()
|
||||
except Exception as e:
|
||||
sys.stderr.write(f"ERR {m} {url}: {e}\n"); sys.stderr.flush()
|
||||
self.send_response(502); self.end_headers()
|
||||
return
|
||||
self.send_response(404); self.end_headers()
|
||||
def do_GET(self): self._p('GET')
|
||||
def do_HEAD(self): self._p('HEAD')
|
||||
def log_message(self, fmt, *a):
|
||||
sys.stderr.write(f"{a[0]}\n"); sys.stderr.flush()
|
||||
|
||||
class S(socketserver.ThreadingMixIn, http.server.HTTPServer):
|
||||
daemon_threads = True; allow_reuse_address = True
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("Maven proxy v2 on :8765 (threaded+logging)")
|
||||
sys.stdout.flush()
|
||||
S(('127.0.0.1',8765),H).serve_forever()
|
||||
PYEOF
|
||||
```
|
||||
|
||||
Скопировать на прод:
|
||||
|
||||
```bash
|
||||
scp /tmp/maven-proxy.py hunab-prod:/tmp/maven-proxy.py
|
||||
```
|
||||
|
||||
### 1.2 Запустить прокси на проде
|
||||
|
||||
```bash
|
||||
# Убить предыдущий экземпляр (НЕЛЬЗЯ использовать pkill -f maven-proxy — убьёт SSH сессию!)
|
||||
ssh hunab-prod "ps aux | grep maven-proxy | grep python | grep -v grep | awk '{print \$2}' | xargs -r kill"
|
||||
sleep 1
|
||||
|
||||
# Запустить
|
||||
ssh hunab-prod "nohup python3 /tmp/maven-proxy.py > /tmp/maven-proxy.log 2>&1 & echo PID=\$!"
|
||||
```
|
||||
|
||||
### 1.3 Проверить прокси на проде
|
||||
|
||||
```bash
|
||||
ssh hunab-prod "curl -s -I http://127.0.0.1:8765/google/com/android/tools/build/gradle/8.7.2/gradle-8.7.2.pom | head -2"
|
||||
# Ожидаемый ответ:
|
||||
# HTTP/1.0 200 OK
|
||||
# Server: BaseHTTP/0.6 Python/3.12.7
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Шаг 2: SSH-туннель
|
||||
|
||||
### 2.1 Создать туннель
|
||||
|
||||
```bash
|
||||
# Убить старые туннели
|
||||
pkill -f "ssh.*-L.*8765" 2>/dev/null
|
||||
|
||||
# Новый туннель (фоновый режим)
|
||||
ssh -f -N -L 8765:127.0.0.1:8765 hunab-prod
|
||||
```
|
||||
|
||||
### 2.2 Проверить туннель локально
|
||||
|
||||
```bash
|
||||
curl -s -I http://127.0.0.1:8765/google/com/android/tools/build/gradle/8.7.2/gradle-8.7.2.pom | head -2
|
||||
# Ожидаемый ответ:
|
||||
# HTTP/1.0 200 OK
|
||||
# Server: BaseHTTP/0.6 Python/3.12.7
|
||||
```
|
||||
|
||||
**⚠️ Если ответ пустой или "Connection refused"** — прокси на проде не запущен или туннель не работает. Повторите шаги 1.2 и 2.1.
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Шаг 3: Сборка AAB
|
||||
|
||||
### 3.1 Установка переменных окружения
|
||||
|
||||
```bash
|
||||
export JAVA_HOME=/opt/homebrew/opt/openjdk@21
|
||||
export PATH="$JAVA_HOME/bin:$PATH"
|
||||
export ANDROID_HOME=$HOME/Library/Android/sdk
|
||||
export PATH=$PATH:$ANDROID_HOME/platform-tools
|
||||
```
|
||||
|
||||
### 3.2 Сборка фронтенда (если не собран)
|
||||
|
||||
```bash
|
||||
cd /Users/eternal/code/hunabapp-dev/frontend
|
||||
pnpm build
|
||||
```
|
||||
|
||||
### 3.3 Синхронизация Capacitor (если не сделано)
|
||||
|
||||
```bash
|
||||
cd /Users/eternal/code/hunabapp-dev/frontend
|
||||
npx cap sync android
|
||||
```
|
||||
|
||||
### 3.4 Сборка AAB
|
||||
|
||||
```bash
|
||||
cd /Users/eternal/code/hunabapp-dev/frontend/android
|
||||
|
||||
./gradlew clean bundleRelease --no-daemon --console=plain
|
||||
```
|
||||
|
||||
**Ожидаемое время:** ~1 мин 20 сек
|
||||
|
||||
### 3.5 Результат
|
||||
|
||||
```bash
|
||||
ls -lh ./app/build/outputs/bundle/release/app-release.aab
|
||||
# → 37M app-release.aab
|
||||
```
|
||||
|
||||
### 3.6 Копирование в temp/
|
||||
|
||||
```bash
|
||||
VERSION_NAME=$(grep 'versionName' ./app/build.gradle | grep -oE '"[^"]*"' | tr -d '"')
|
||||
VERSION_CODE=$(grep 'versionCode' ./app/build.gradle | grep -oE '[0-9]+')
|
||||
|
||||
mkdir -p /Users/eternal/code/hunabapp-dev/temp/android
|
||||
cp ./app/build/outputs/bundle/release/app-release.aab \
|
||||
/Users/eternal/code/hunabapp-dev/temp/android/hunab-app-v${VERSION_NAME}-v${VERSION_CODE}-release.aab
|
||||
|
||||
echo "📦 AAB: temp/android/hunab-app-v${VERSION_NAME}-v${VERSION_CODE}-release.aab"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Шаг 4: Сборка APK для эмулятора
|
||||
|
||||
> **AAB нельзя установить в эмулятор напрямую** — нужен APK.
|
||||
|
||||
```bash
|
||||
cd /Users/eternal/code/hunabapp-dev/frontend/android
|
||||
|
||||
./gradlew assembleRelease --no-daemon --console=plain
|
||||
```
|
||||
|
||||
**Ожидаемое время:** ~46 сек (после AAB сборки — кэш уже разогрет)
|
||||
|
||||
Результат:
|
||||
|
||||
```bash
|
||||
cp ./app/build/outputs/apk/release/app-release.apk \
|
||||
/Users/eternal/code/hunabapp-dev/temp/android/hunab-app-v${VERSION_NAME}-release.apk
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Шаг 5: Установка в эмулятор
|
||||
|
||||
### 5.1 Запуск эмулятора
|
||||
|
||||
```bash
|
||||
# Список доступных AVD
|
||||
$ANDROID_HOME/emulator/emulator -list-avds
|
||||
# → Pixel_5_API_35
|
||||
|
||||
# Запуск (в фоне)
|
||||
$ANDROID_HOME/emulator/emulator -avd Pixel_5_API_35 -no-snapshot-load -no-snapshot-save &
|
||||
|
||||
# Ожидание загрузки (30-60 сек)
|
||||
adb wait-for-device
|
||||
adb shell getprop sys.boot_completed
|
||||
# → 1 (когда загружено)
|
||||
```
|
||||
|
||||
### 5.2 Установка APK
|
||||
|
||||
```bash
|
||||
adb install -r /Users/eternal/code/hunabapp-dev/temp/android/hunab-app-v${VERSION_NAME}-release.apk
|
||||
```
|
||||
|
||||
### 5.3 Запуск приложения
|
||||
|
||||
```bash
|
||||
adb shell am start -n com.hunab.app/.MainActivity
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Быстрый скрипт «всё одной командой»
|
||||
|
||||
> **Предполагает:** прокси уже запущен и SSH-туннель активен (шаги 1-2).
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# === Настройка ===
|
||||
export JAVA_HOME=/opt/homebrew/opt/openjdk@21
|
||||
export PATH="$JAVA_HOME/bin:$PATH"
|
||||
export ANDROID_HOME=$HOME/Library/Android/sdk
|
||||
export PATH=$PATH:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator
|
||||
|
||||
PROJECT_ROOT="/Users/eternal/code/hunabapp-dev"
|
||||
|
||||
# === Проверка прокси ===
|
||||
echo "🔍 Проверяю Maven proxy..."
|
||||
if ! curl -s -I http://127.0.0.1:8765/google/com/android/tools/build/gradle/8.7.2/gradle-8.7.2.pom | grep -q "200 OK"; then
|
||||
echo "❌ Maven proxy не работает! Запустите шаги 1-2 из BUILD_AAB_FROM_RUSSIA_GUIDE.md"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Proxy OK"
|
||||
|
||||
# === Сборка фронтенда ===
|
||||
echo "🔨 Сборка фронтенда..."
|
||||
cd "$PROJECT_ROOT/frontend"
|
||||
pnpm build
|
||||
|
||||
# === Capacitor sync ===
|
||||
echo "🔄 Capacitor sync..."
|
||||
npx cap sync android
|
||||
|
||||
# === Версия ===
|
||||
cd android
|
||||
VERSION_NAME=$(grep 'versionName' ./app/build.gradle | grep -oE '"[^"]*"' | tr -d '"')
|
||||
VERSION_CODE=$(grep 'versionCode' ./app/build.gradle | grep -oE '[0-9]+')
|
||||
echo "📊 Version: $VERSION_NAME (code: $VERSION_CODE)"
|
||||
|
||||
# === AAB ===
|
||||
echo "📦 Сборка AAB..."
|
||||
./gradlew clean bundleRelease --no-daemon --console=plain
|
||||
|
||||
mkdir -p "$PROJECT_ROOT/temp/android"
|
||||
cp ./app/build/outputs/bundle/release/app-release.aab \
|
||||
"$PROJECT_ROOT/temp/android/hunab-app-v${VERSION_NAME}-v${VERSION_CODE}-release.aab"
|
||||
echo "✅ AAB: temp/android/hunab-app-v${VERSION_NAME}-v${VERSION_CODE}-release.aab"
|
||||
|
||||
# === APK (для эмулятора) ===
|
||||
echo "📱 Сборка APK..."
|
||||
./gradlew assembleRelease --no-daemon --console=plain
|
||||
|
||||
cp ./app/build/outputs/apk/release/app-release.apk \
|
||||
"$PROJECT_ROOT/temp/android/hunab-app-v${VERSION_NAME}-release.apk"
|
||||
|
||||
# === Эмулятор ===
|
||||
echo "📲 Установка в эмулятор..."
|
||||
if adb devices 2>/dev/null | grep -q "device$"; then
|
||||
adb install -r "$PROJECT_ROOT/temp/android/hunab-app-v${VERSION_NAME}-release.apk"
|
||||
adb shell am start -n com.hunab.app/.MainActivity
|
||||
echo "🎮 Приложение запущено!"
|
||||
else
|
||||
echo "⚠️ Эмулятор не запущен. Запустите вручную и повторите установку."
|
||||
fi
|
||||
|
||||
ls -lh "$PROJECT_ROOT/temp/android/"
|
||||
echo ""
|
||||
echo "✅✅✅ ГОТОВО!"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚫 DOs & DON'Ts
|
||||
|
||||
### ✅ DO (ДЕЛАЙ)
|
||||
|
||||
| # | Правило | Почему |
|
||||
|---|---------|--------|
|
||||
| 1 | **Используй Maven proxy через SSH-туннель** | Java TLS заблокирован DPI в РФ |
|
||||
| 2 | **ТОЛЬКО прокси repos в build.gradle** (без `google()`, `mavenCentral()`) | Прямые HTTPS repos зависают навсегда |
|
||||
| 3 | **`allowInsecureProtocol = true`** для proxy repos | Прокси работает по HTTP, Gradle 8+ блокирует HTTP по умолчанию |
|
||||
| 4 | **`android.builder.sdkDownload=false`** в gradle.properties | SDK auto-download зависает на TLS |
|
||||
| 5 | **`compileSdkVersion = 36`** в variables.gradle | android-36 — base SDK, корректно установлен |
|
||||
| 6 | **`buildToolsVersion "35.0.1"`** явно в app/build.gradle | 34.0.0 и 35.0.0 не установлены (пустые папки были удалены) |
|
||||
| 7 | **Global buildToolsVersion override** через `subprojects{}` в root build.gradle | Capacitor модули тоже требуют build tools |
|
||||
| 8 | **`android.suppressUnsupportedCompileSdk=36`** в gradle.properties | Убирает warning от AGP 8.7.2 |
|
||||
| 9 | **`-Djdk.tls.client.protocols=TLSv1.2`** в org.gradle.jvmargs | Частично помогает с DPI для прямых Java TLS |
|
||||
| 10 | **`IsBaseSdk=true`** в source.properties platforms | Gradle не находит extension SDK без манифеста |
|
||||
| 11 | **`ThreadingMixIn`** в Python proxy | Gradle делает десятки параллельных запросов |
|
||||
| 12 | **HEAD + GET** в proxy | Gradle использует HEAD для проверки существования артефактов |
|
||||
| 13 | **Фоновая сборка** при запуске из Cursor | `./gradlew` через Cursor tool таймаутится через ~2 мин |
|
||||
| 14 | **Убивай pkill через `ps aux | grep | awk | xargs kill`** | Безопасный способ |
|
||||
|
||||
### ❌ DON'T (НЕ ДЕЛАЙ)
|
||||
|
||||
| # | Правило | Почему |
|
||||
|---|---------|--------|
|
||||
| 1 | **НЕ оставляй `google()` / `mavenCentral()` в repositories** | Gradle попробует их → зависнет на TLS handshake на НЕОПРЕДЕЛЁННОЕ время |
|
||||
| 2 | **НЕ используй `pkill -f maven-proxy`** через SSH | Паттерн совпадает с самой SSH командой → убивает SSH сессию (exit 255) |
|
||||
| 3 | **НЕ используй однопоточный Python HTTP server** | Gradle делает 10-50 параллельных запросов → очередь → таймаут |
|
||||
| 4 | **НЕ используй SOCKS proxy для Gradle** (`socksProxyHost`) | Java TLS через SOCKS тоже блокируется DPI |
|
||||
| 5 | **НЕ пытайся скачать Gradle cache на проде и скопировать** | Кэш привязан к хэшам и абсолютным путям — не переносим |
|
||||
| 6 | **НЕ устанавливай Android SDK через sdkmanager из РФ** | Тот же TLS блок. Устанавливай через Android Studio GUI (у студии свои настройки) |
|
||||
| 7 | **НЕ удаляй `android.builder.sdkDownload=false`** | Gradle будет пытаться скачать SDK манифесты → зависнет |
|
||||
| 8 | **НЕ используй `compileSdkVersion = 35`** | Установленный android-35 — extension SDK, Gradle без манифеста не может его найти |
|
||||
| 9 | **НЕ забывай `buildToolsVersion` в subprojects** | Capacitor-android и другие модули тоже требуют build tools |
|
||||
| 10 | **НЕ создавай APK для Google Play** | Google Play требует только AAB формат |
|
||||
| 11 | **НЕ оставляй пустые build-tools директории** | Gradle пытается их использовать → "Build Tools not found" |
|
||||
| 12 | **НЕ запускай Gradle через tool call Cursor напрямую** | Таймаут ~2 мин; запускай в фоне через `nohup ... &` |
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Конфигурация Gradle (что изменено и почему)
|
||||
|
||||
### `frontend/android/build.gradle` (корневой)
|
||||
|
||||
```groovy
|
||||
buildscript {
|
||||
repositories {
|
||||
// ТОЛЬКО прокси — прямые repos заблокированы DPI
|
||||
maven { url 'http://127.0.0.1:8765/google/'; allowInsecureProtocol = true }
|
||||
maven { url 'http://127.0.0.1:8765/central/'; allowInsecureProtocol = true }
|
||||
maven { url 'http://127.0.0.1:8765/gradle/'; allowInsecureProtocol = true }
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.7.2'
|
||||
classpath 'com.google.gms:google-services:4.4.2'
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
// ТОЛЬКО прокси
|
||||
maven { url 'http://127.0.0.1:8765/google/'; allowInsecureProtocol = true }
|
||||
maven { url 'http://127.0.0.1:8765/central/'; allowInsecureProtocol = true }
|
||||
maven { url 'http://127.0.0.1:8765/gradle/'; allowInsecureProtocol = true }
|
||||
}
|
||||
}
|
||||
|
||||
// Глобальный override buildToolsVersion для ВСЕХ модулей
|
||||
subprojects {
|
||||
afterEvaluate { project ->
|
||||
if (project.hasProperty('android')) {
|
||||
project.android.buildToolsVersion = "35.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Почему:**
|
||||
- `allowInsecureProtocol = true` — Gradle 8+ блокирует HTTP repos по умолчанию
|
||||
- Три прокси: Google Maven, Maven Central, Gradle Plugin Portal
|
||||
- `subprojects { afterEvaluate }` — Capacitor модули не указывают buildToolsVersion явно, AGP 8.7.2 по умолчанию ищет 34.0.0 (не установлен)
|
||||
|
||||
### `frontend/android/gradle.properties`
|
||||
|
||||
```properties
|
||||
org.gradle.jvmargs=-Xmx1536m -Djdk.tls.client.protocols=TLSv1.2
|
||||
android.useAndroidX=true
|
||||
android.builder.sdkDownload=false
|
||||
android.suppressUnsupportedCompileSdk=36
|
||||
```
|
||||
|
||||
**Почему:**
|
||||
- `-Djdk.tls.client.protocols=TLSv1.2` — принудительный TLS 1.2 (частично обходит DPI)
|
||||
- `sdkDownload=false` — блокирует авто-загрузку SDK (зависает на TLS)
|
||||
- `suppressUnsupportedCompileSdk=36` — AGP 8.7.2 не знает о SDK 36, подавляем warning
|
||||
|
||||
### `frontend/android/variables.gradle`
|
||||
|
||||
```groovy
|
||||
ext {
|
||||
compileSdkVersion = 36 // Было 35! android-35 — extension SDK, не находится без манифеста
|
||||
targetSdkVersion = 35 // Оставляем 35 для совместимости с Google Play
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**Почему:**
|
||||
- `android-35` установлен как extension SDK (path: `platforms;android-35-ext15`) — Gradle без сетевого манифеста не может его распознать как `android-35`
|
||||
- `android-36` установлен как base SDK (path: `platforms;android-36`, `IsBaseSdk=true`) — работает
|
||||
- `targetSdkVersion` остаётся 35 — это то, что Google Play учитывает
|
||||
|
||||
### `frontend/android/app/build.gradle`
|
||||
|
||||
```groovy
|
||||
android {
|
||||
compileSdk rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion "35.0.1" // Добавлено явно
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### SDK Platform Patches
|
||||
|
||||
**`$HOME/Library/Android/sdk/platforms/android-35/source.properties`:**
|
||||
|
||||
```properties
|
||||
AndroidVersion.IsBaseSdk=true # Было false — Gradle не находил SDK без манифеста
|
||||
```
|
||||
|
||||
**`$HOME/Library/Android/sdk/platforms/android-35/package.xml`:**
|
||||
|
||||
```xml
|
||||
<localPackage path="platforms;android-35" ...> <!-- Было: platforms;android-35-ext15 -->
|
||||
```
|
||||
|
||||
> ⚠️ Аналогичные изменения для android-34
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Troubleshooting
|
||||
|
||||
### Проблема: «Still waiting for package manifests to be fetched remotely»
|
||||
|
||||
**Причина:** `android.builder.sdkDownload=true` (или не задан) → Gradle пытается скачать манифесты по HTTPS → DPI блокирует.
|
||||
|
||||
**Решение:**
|
||||
|
||||
```properties
|
||||
# gradle.properties
|
||||
android.builder.sdkDownload=false
|
||||
```
|
||||
|
||||
### Проблема: «Failed to find target with hash string 'android-35'»
|
||||
|
||||
**Причина:** Установленный SDK — extension SDK (`IsBaseSdk=false`, path `android-35-ext15`), а Gradle ищет base SDK `android-35`.
|
||||
|
||||
**Решение варианты:**
|
||||
1. Использовать `compileSdkVersion = 36` (android-36 = base SDK) ← **текущее решение**
|
||||
2. Или патчить `source.properties`: `IsBaseSdk=true` + `package.xml`: `path="platforms;android-35"`
|
||||
|
||||
### Проблема: «Failed to find Build Tools revision 34.0.0»
|
||||
|
||||
**Причина:** AGP 8.7.2 по умолчанию требует build-tools 34.0.0. При `sdkDownload=false` не может скачать.
|
||||
|
||||
**Решение:** Явно указать установленную версию в `app/build.gradle` и глобально через `subprojects` в root `build.gradle`:
|
||||
|
||||
```groovy
|
||||
buildToolsVersion "35.0.1"
|
||||
```
|
||||
|
||||
### Проблема: Gradle зависает, прокси лог пустой (0 строк)
|
||||
|
||||
**Причина:** Gradle обращается к `google()` / `mavenCentral()` напрямую, минуя прокси.
|
||||
|
||||
**Решение:** Убрать `google()` и `mavenCentral()` из repositories. Оставить ТОЛЬКО proxy URLs.
|
||||
|
||||
### Проблема: SSH exit code 255 при pkill
|
||||
|
||||
**Причина:** `pkill -f maven-proxy` совпадает с самой SSH командой и убивает сессию.
|
||||
|
||||
**Решение:**
|
||||
|
||||
```bash
|
||||
ssh hunab-prod "ps aux | grep maven-proxy | grep python | grep -v grep | awk '{print \$2}' | xargs -r kill"
|
||||
```
|
||||
|
||||
### Проблема: Cursor tool timeout при сборке
|
||||
|
||||
**Причина:** Gradle билд занимает ~1.5 мин, Cursor tool таймаутится через ~2 мин.
|
||||
|
||||
**Решение:** Запускать в фоне:
|
||||
|
||||
```bash
|
||||
nohup bash -c '
|
||||
cd /path/to/frontend/android
|
||||
./gradlew bundleRelease --no-daemon --console=plain 2>&1 | tee /tmp/aab-build.log
|
||||
' &>/dev/null &
|
||||
|
||||
# Мониторинг
|
||||
tail -f /tmp/aab-build.log
|
||||
```
|
||||
|
||||
### Проблема: Однопоточный proxy таймаутится
|
||||
|
||||
**Причина:** Python `http.server.HTTPServer` обрабатывает 1 запрос за раз. Gradle делает 10-50 параллельных запросов.
|
||||
|
||||
**Решение:** `ThreadingMixIn`:
|
||||
|
||||
```python
|
||||
class S(socketserver.ThreadingMixIn, http.server.HTTPServer):
|
||||
daemon_threads = True
|
||||
allow_reuse_address = True
|
||||
```
|
||||
|
||||
### Проблема: Proxy возвращает 404 на HEAD запросы
|
||||
|
||||
**Причина:** `BaseHTTPRequestHandler` по умолчанию не имеет `do_HEAD`.
|
||||
|
||||
**Решение:** Добавить `do_HEAD`:
|
||||
|
||||
```python
|
||||
def do_HEAD(self): self._p('HEAD')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Версионирование
|
||||
|
||||
### Текущая версия (2026-02-28)
|
||||
|
||||
| Параметр | Значение |
|
||||
|----------|----------|
|
||||
| versionName | 1.3.8 |
|
||||
| versionCode | 10308 |
|
||||
| compileSdkVersion | 36 |
|
||||
| targetSdkVersion | 35 |
|
||||
| minSdkVersion | 23 |
|
||||
| buildToolsVersion | 35.0.1 |
|
||||
| AGP | 8.7.2 |
|
||||
| Gradle | 8.11.1 |
|
||||
| Java | OpenJDK 21.0.9 |
|
||||
| Capacitor | 7.4.x |
|
||||
|
||||
### Для Google Play
|
||||
|
||||
- **AAB файл:** `temp/android/hunab-app-v1.3.8-v10308-release.aab`
|
||||
- **Размер:** ~37 MB
|
||||
- **Подписан:** `hunab-app.keystore` (release signing config)
|
||||
- **Формат:** AAB (NEVER APK for Google Play!)
|
||||
- **versionCode** должен быть **больше** предыдущего релиза!
|
||||
|
||||
### Перед новым релизом
|
||||
|
||||
1. Обновить `versionCode` и `versionName` в `frontend/android/app/build.gradle`
|
||||
2. Собрать фронтенд (`pnpm build`)
|
||||
3. `npx cap sync android`
|
||||
4. Следовать шагам 1-4 этого руководства
|
||||
|
||||
---
|
||||
|
||||
## 📁 Связанные файлы
|
||||
|
||||
| Файл | Назначение |
|
||||
|------|------------|
|
||||
| `frontend/android/build.gradle` | Корневой Gradle — repos, plugins, subprojects override |
|
||||
| `frontend/android/app/build.gradle` | App модуль — version, signing, buildTools |
|
||||
| `frontend/android/gradle.properties` | JVM args, SDK download, TLS, suppressWarnings |
|
||||
| `frontend/android/variables.gradle` | compileSdk, targetSdk, dependency versions |
|
||||
| `docs/android/guides/SETUP_AND_BUILD_GUIDE.md` | **Общее руководство по установке и сборке** (для работы не из России) |
|
||||
| `docs/android/guides/EMULATOR_STARTUP_GUIDE.md` | **Полное руководство по запуску эмулятора** |
|
||||
| `docs/android/TROUBLESHOOTING.md` | Общий troubleshooting (включая другие проблемы) |
|
||||
| `docs/android/MOBILE_BUILD_REGULATIONS.md` | Регламент сборки мобильного приложения |
|
||||
| `scripts/android/quick-build-and-launch.sh` | Старый скрипт быстрой сборки (требует адаптации под proxy) |
|
||||
|
||||
---
|
||||
|
||||
> **Последнее обновление:** 2026-02-28
|
||||
> **Автор:** Aura (Cursor AI Agent)
|
||||
> **Проверено:** Успешная сборка AAB v1.3.8 (BUILD SUCCESSFUL in 1m 21s, 112 tasks)
|
||||
|
||||
Reference in New Issue
Block a user