From df24559f5baf7df3ce113cf8d23fd203cef5b0fd Mon Sep 17 00:00:00 2001 From: Vladislav Date: Thu, 24 Sep 2026 11:19:44 +0200 Subject: [PATCH 1/2] feat(build): add Docker-based Android build environment Add Dockerfile, build entrypoint, convenience script, and documentation for building the APK without installing Android tooling on the host. --- .gitignore | 2 + docker-build.sh | 101 +++++++++++++++++++++++++ docker/build/Dockerfile | 94 +++++++++++++++++++++++ docker/build/README.md | 104 ++++++++++++++++++++++++++ docker/build/entrypoint.sh | 148 +++++++++++++++++++++++++++++++++++++ 5 files changed, 449 insertions(+) create mode 100755 docker-build.sh create mode 100644 docker/build/Dockerfile create mode 100644 docker/build/README.md create mode 100755 docker/build/entrypoint.sh diff --git a/.gitignore b/.gitignore index dbf320b..4a94f81 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,8 @@ __pycache__/ # Android/Gradle build/ +!/docker/build/ +!/docker/build/** .gradle/ local.properties *.iml diff --git a/docker-build.sh b/docker-build.sh new file mode 100755 index 0000000..60d1c1f --- /dev/null +++ b/docker-build.sh @@ -0,0 +1,101 @@ +#!/bin/bash +# ============================================================================= +# tucaVR — Script de conveniência para build via Docker +# +# Uso: +# ./docker-build.sh # Builda a imagem (se necessário) e compila o APK +# ./docker-build.sh --rebuild # Força rebuild da imagem Docker +# ./docker-build.sh --shell # Abre um shell interativo no container +# ============================================================================= +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +IMAGE_NAME="tucavr-builder" +DOCKERFILE_DIR="docker/build" + +# ── Argumentos ─────────────────────────────────────────────────────────────── +FORCE_REBUILD=false +INTERACTIVE_SHELL=false + +for arg in "$@"; do + case "$arg" in + --rebuild) FORCE_REBUILD=true ;; + --shell) INTERACTIVE_SHELL=true ;; + --help|-h) + echo "Uso: $0 [opções]" + echo "" + echo "Opções:" + echo " --rebuild Força rebuild da imagem Docker" + echo " --shell Abre shell interativo no container (não builda)" + echo " --help Mostra esta ajuda" + exit 0 + ;; + *) + echo "❌ Argumento desconhecido: $arg" + echo " Use --help para ver as opções" + exit 1 + ;; + esac +done + +# ── Verifica Docker ────────────────────────────────────────────────────────── +if ! command -v docker &>/dev/null; then + echo "❌ Docker não encontrado. Instale com:" + echo "" + echo " sudo apt-get update" + echo " sudo apt-get install -y docker.io" + echo " sudo systemctl enable --now docker" + echo " sudo usermod -aG docker \$USER" + echo " # Faça logout e login novamente" + echo "" + exit 1 +fi + +# ── Verifica Meta OpenXR SDK ───────────────────────────────────────────────── +if [ ! -d "sdk/meta-openxr-sdk/Samples/SampleXrFramework" ]; then + echo "⚠️ Meta OpenXR SDK não encontrado em sdk/meta-openxr-sdk/" + echo "" + echo " Baixe manualmente em:" + echo " https://developers.meta.com/horizon/downloads/package/oculus-openxr-mobile-sdk/" + echo "" + echo " Extraia para sdk/meta-openxr-sdk/ antes de buildar." + exit 1 +fi + +# ── Build da imagem Docker ─────────────────────────────────────────────────── +if $FORCE_REBUILD || ! docker image inspect "$IMAGE_NAME" &>/dev/null; then + echo "🐳 Construindo imagem Docker '$IMAGE_NAME'..." + echo " (primeira vez leva ~15-20 minutos — SDK, NDK, Rust, FFmpeg)" + echo "" + docker build -t "$IMAGE_NAME" "$DOCKERFILE_DIR" + echo "" + echo "✅ Imagem '$IMAGE_NAME' construída com sucesso!" + echo "" +else + echo "✅ Imagem '$IMAGE_NAME' já existe (use --rebuild para recriar)" +fi + +# ── Monta volumes e executa ────────────────────────────────────────────────── +DOCKER_RUN_ARGS=( + --rm + -v "$(pwd):/project" + -e "HOST_UID=$(id -u)" + -e "HOST_GID=$(id -g)" + # Caches persistentes para builds incrementais + -v "tucavr-gradle-cache:/root/.gradle" + -v "tucavr-cargo-cache:/root/.cargo/registry" +) + +if $INTERACTIVE_SHELL; then + echo "🐚 Abrindo shell interativo no container..." + echo " Projeto montado em /project" + echo " FFmpeg pré-compilado em /opt/ffmpeg-android-maker" + echo "" + docker run -it "${DOCKER_RUN_ARGS[@]}" --entrypoint /bin/bash "$IMAGE_NAME" +else + echo "🚀 Iniciando build do APK no container..." + echo "" + docker run "${DOCKER_RUN_ARGS[@]}" "$IMAGE_NAME" +fi diff --git a/docker/build/Dockerfile b/docker/build/Dockerfile new file mode 100644 index 0000000..f16264b --- /dev/null +++ b/docker/build/Dockerfile @@ -0,0 +1,94 @@ +# ============================================================================= +# tucaVR — Dockerfile de build completo (APK debug) +# +# Instala todas as toolchains necessárias para compilar o projeto: +# JDK 17, Android SDK 34, NDK 26.3, CMake 3.22.1, +# Rust stable + aarch64-linux-android, cargo-ndk, +# FFmpeg cross-compilado para arm64-v8a. +# +# O código-fonte do projeto e o Meta OpenXR SDK são montados em runtime +# via volume (-v), NÃO copiados para a imagem. +# +# Uso: +# docker build -t tucavr-builder docker/build/ +# docker run --rm \ +# -v "$(pwd)":/project \ +# -e HOST_UID=$(id -u) -e HOST_GID=$(id -g) \ +# -v tucavr-gradle-cache:/root/.gradle \ +# -v tucavr-cargo-cache:/root/.cargo/registry \ +# tucavr-builder +# ============================================================================= + +FROM ubuntu:22.04 + +ENV DEBIAN_FRONTEND=noninteractive +ENV LANG=C.UTF-8 +ENV LC_ALL=C.UTF-8 + +# ── 1. Pacotes do sistema ──────────────────────────────────────────────────── +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl wget unzip git ca-certificates \ + build-essential pkg-config \ + python3 cmake ninja-build nasm yasm \ + openjdk-17-jdk-headless \ + libclang-dev clang \ + && rm -rf /var/lib/apt/lists/* + +# ── 2. Android SDK + NDK + CMake ───────────────────────────────────────────── +ENV ANDROID_HOME=/opt/android-sdk +ENV ANDROID_SDK_ROOT=${ANDROID_HOME} +ENV PATH="${ANDROID_HOME}/cmdline-tools/latest/bin:${ANDROID_HOME}/platform-tools:${PATH}" + +# Baixa o cmdline-tools +RUN mkdir -p "${ANDROID_HOME}/cmdline-tools" && \ + wget -q "https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip" \ + -O /tmp/cmdline-tools.zip && \ + unzip -q /tmp/cmdline-tools.zip -d "${ANDROID_HOME}/cmdline-tools" && \ + mv "${ANDROID_HOME}/cmdline-tools/cmdline-tools" "${ANDROID_HOME}/cmdline-tools/latest" && \ + rm /tmp/cmdline-tools.zip + +# Aceita licenças e instala SDK 34, NDK 26.3, CMake 3.22.1, build-tools +RUN yes | sdkmanager --licenses > /dev/null 2>&1 && \ + sdkmanager --no_https \ + "platform-tools" \ + "platforms;android-34" \ + "build-tools;34.0.0" \ + "ndk;26.3.11579264" \ + "cmake;3.22.1" + +ENV ANDROID_NDK_HOME="${ANDROID_HOME}/ndk/26.3.11579264" +ENV ANDROID_NDK_ROOT="${ANDROID_NDK_HOME}" +# CMake do SDK no PATH (Gradle encontra automaticamente, mas cargo/oboe pode precisar) +ENV PATH="${ANDROID_HOME}/cmake/3.22.1/bin:${PATH}" + +# ── 3. Rust toolchain ──────────────────────────────────────────────────────── +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ + sh -s -- -y --default-toolchain stable --profile minimal +ENV PATH="/root/.cargo/bin:${PATH}" + +RUN rustup target add aarch64-linux-android && \ + cargo install cargo-ndk + +# ── 4. FFmpeg cross-compile para arm64-v8a ──────────────────────────────────── +# Pinado no mesmo commit usado por scripts/setup-deps.sh +ENV FFMPEG_MAKER_DIR=/opt/ffmpeg-android-maker +RUN git clone https://github.com/Javernaut/ffmpeg-android-maker.git "${FFMPEG_MAKER_DIR}" && \ + cd "${FFMPEG_MAKER_DIR}" && \ + git checkout dd72b161ae5c759fd25a5cab971a3ff710f0bdba + +RUN cd "${FFMPEG_MAKER_DIR}" && \ + ANDROID_SDK_HOME="${ANDROID_HOME}" \ + ANDROID_NDK_HOME="${ANDROID_NDK_HOME}" \ + ./ffmpeg-android-maker.sh --target-abis=arm64-v8a --android-api-level=26 + +# Cria symlink output/include/arm64-v8a → build/.../include (build.sh referencia ambos) +RUN mkdir -p "${FFMPEG_MAKER_DIR}/output/include" && \ + ln -sf "${FFMPEG_MAKER_DIR}/build/ffmpeg/arm64-v8a/include" \ + "${FFMPEG_MAKER_DIR}/output/include/arm64-v8a" + +# ── 5. Entrypoint ──────────────────────────────────────────────────────────── +WORKDIR /project +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker/build/README.md b/docker/build/README.md new file mode 100644 index 0000000..9efa657 --- /dev/null +++ b/docker/build/README.md @@ -0,0 +1,104 @@ +# tucaVR — Docker Build + +Build do APK totalmente dentro de um container Docker, sem instalar +Android SDK, NDK, Rust ou FFmpeg no host. + +## Pré-requisitos + +1. **Docker** instalado e funcionando: + ```bash + sudo apt-get update && sudo apt-get install -y docker.io + sudo systemctl enable --now docker + sudo usermod -aG docker $USER + # logout + login para o grupo ter efeito + ``` + +2. **Meta OpenXR Mobile SDK** em `sdk/meta-openxr-sdk/`: + - Baixe em https://developers.meta.com/horizon/downloads/package/oculus-openxr-mobile-sdk/ + - Extraia de forma que exista `sdk/meta-openxr-sdk/Samples/SampleXrFramework/` + +## Uso Rápido + +Da raiz do projeto: + +```bash +# Build completo (imagem + APK) +./docker-build.sh + +# Forçar rebuild da imagem Docker +./docker-build.sh --rebuild + +# Shell interativo para debug +./docker-build.sh --shell +``` + +O APK gerado aparece em: `app/build/outputs/apk/debug/app-debug.apk` + +## Uso Manual (docker CLI) + +```bash +# 1. Construir a imagem (uma vez) +docker build -t tucavr-builder docker/build/ + +# 2. Compilar o APK +docker run --rm \ + -v "$(pwd)":/project \ + -e HOST_UID=$(id -u) -e HOST_GID=$(id -g) \ + -v tucavr-gradle-cache:/root/.gradle \ + -v tucavr-cargo-cache:/root/.cargo/registry \ + tucavr-builder + +# 3. (Opcional) Shell interativo +docker run --rm -it \ + -v "$(pwd)":/project \ + --entrypoint /bin/bash \ + tucavr-builder +``` + +## O que está dentro da imagem + +| Componente | Versão | +|------------|--------| +| Ubuntu | 22.04 | +| JDK | 17 (OpenJDK Headless) | +| Android SDK | Platform 34 | +| Android NDK | 26.3.11579264 | +| CMake | 3.22.1 (via SDK) | +| Rust | stable + `aarch64-linux-android` | +| cargo-ndk | latest | +| FFmpeg | Pré-compilado para `arm64-v8a` (API 26) | + +**Tamanho estimado da imagem:** ~12-15 GB (SDK + NDK + FFmpeg são pesados). + +## Caches + +Volumes Docker persistem caches entre builds: + +| Volume | Conteúdo | +|--------|----------| +| `tucavr-gradle-cache` | Downloads de dependências Gradle e build cache | +| `tucavr-cargo-cache` | Registry de crates Rust | + +Para limpar caches: +```bash +docker volume rm tucavr-gradle-cache tucavr-cargo-cache +``` + +## Variáveis de Ambiente Opcionais + +| Variável | Descrição | +|----------|-----------| +| `APP_VERSION_NAME` | Override da versionName (ex: `1.0.0`) | +| `APP_VERSION_CODE` | Override do versionCode (ex: `100`) | +| `HOST_UID` | UID do host para corrigir ownership dos artefatos | +| `HOST_GID` | GID do host para corrigir ownership dos artefatos | + +Exemplo: +```bash +docker run --rm \ + -v "$(pwd)":/project \ + -e APP_VERSION_NAME=1.0.0 \ + -e APP_VERSION_CODE=100 \ + -e HOST_UID=$(id -u) -e HOST_GID=$(id -g) \ + tucavr-builder +``` diff --git a/docker/build/entrypoint.sh b/docker/build/entrypoint.sh new file mode 100755 index 0000000..368ae28 --- /dev/null +++ b/docker/build/entrypoint.sh @@ -0,0 +1,148 @@ +#!/bin/bash +# ============================================================================= +# tucaVR — Entrypoint do container Docker de build +# +# Orquestra o build completo: +# 1. Cria symlink do ffmpeg-android-maker pré-compilado +# 2. Cross-compila o workspace Rust via cargo ndk +# 3. Copia .so do FFmpeg para jniLibs +# 4. Executa ./gradlew assembleDebug (Kotlin + C++/CMake) +# 5. Corrige ownership dos artefatos de saída +# ============================================================================= +set -euo pipefail + +echo "" +echo "╔══════════════════════════════════════════════════════════════╗" +echo "║ tucaVR — Docker Build ║" +echo "╚══════════════════════════════════════════════════════════════╝" +echo "" + +ROOT_DIR="/project" +cd "$ROOT_DIR" + +# ── Verifica que o projeto está montado ────────────────────────────────────── +if [ ! -f "build.gradle.kts" ]; then + echo "❌ ERRO: Código-fonte do projeto não encontrado em /project" + echo " Monte o projeto com: -v \"\$(pwd)\":/project" + exit 1 +fi + +# ── Verifica o Meta OpenXR SDK ─────────────────────────────────────────────── +if [ ! -d "sdk/meta-openxr-sdk/Samples/SampleXrFramework" ]; then + echo "❌ ERRO: Meta OpenXR SDK não encontrado em sdk/meta-openxr-sdk/" + echo "" + echo " Esse SDK requer download manual com aceite de licença:" + echo " https://developers.meta.com/horizon/downloads/package/oculus-openxr-mobile-sdk/" + echo "" + echo " Extraia para sdk/meta-openxr-sdk/ e tente novamente." + exit 1 +fi + +# ── Symlink do ffmpeg-android-maker pré-compilado ──────────────────────────── +FFMPEG_MAKER_DIR="/opt/ffmpeg-android-maker" +if [ ! -d "$ROOT_DIR/ffmpeg-android-maker" ]; then + echo "📎 Criando symlink ffmpeg-android-maker → ${FFMPEG_MAKER_DIR}" + ln -sf "$FFMPEG_MAKER_DIR" "$ROOT_DIR/ffmpeg-android-maker" +elif [ ! -d "$ROOT_DIR/ffmpeg-android-maker/build/ffmpeg/arm64-v8a" ]; then + echo "📎 ffmpeg-android-maker existe mas sem build — redirecionando para pré-compilado" + rm -rf "$ROOT_DIR/ffmpeg-android-maker" + ln -sf "$FFMPEG_MAKER_DIR" "$ROOT_DIR/ffmpeg-android-maker" +else + echo "✅ Usando ffmpeg-android-maker existente no projeto" +fi + +# ── Variáveis de ambiente para cross-compilation ───────────────────────────── +export ANDROID_NDK_HOME="${ANDROID_NDK_HOME:-/opt/android-sdk/ndk/26.3.11579264}" +export ANDROID_NDK_ROOT="$ANDROID_NDK_HOME" +export PKG_CONFIG_ALLOW_CROSS=1 + +# Detecta onde estão os pkgconfig do FFmpeg +FFMPEG_LIB_DIR="" +if [ -d "$ROOT_DIR/ffmpeg-android-maker/build/ffmpeg/arm64-v8a/lib" ]; then + FFMPEG_LIB_DIR="$ROOT_DIR/ffmpeg-android-maker/build/ffmpeg/arm64-v8a/lib" +fi +export PKG_CONFIG_PATH="${FFMPEG_LIB_DIR}/pkgconfig" + +# Detecta onde estão os headers do FFmpeg +FFMPEG_INCLUDE_DIR="" +if [ -d "$ROOT_DIR/ffmpeg-android-maker/build/ffmpeg/arm64-v8a/include" ]; then + FFMPEG_INCLUDE_DIR="$ROOT_DIR/ffmpeg-android-maker/build/ffmpeg/arm64-v8a/include" +elif [ -d "$ROOT_DIR/ffmpeg-android-maker/output/include/arm64-v8a" ]; then + FFMPEG_INCLUDE_DIR="$ROOT_DIR/ffmpeg-android-maker/output/include/arm64-v8a" +fi + +NDK_SYSROOT="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/sysroot" +export BINDGEN_EXTRA_CLANG_ARGS="-I${FFMPEG_INCLUDE_DIR} -I${NDK_SYSROOT}/usr/include --sysroot=${NDK_SYSROOT}" + +echo "" +echo "📋 Configuração:" +echo " ANDROID_NDK_HOME = $ANDROID_NDK_HOME" +echo " PKG_CONFIG_PATH = $PKG_CONFIG_PATH" +echo " FFMPEG_INCLUDE_DIR = $FFMPEG_INCLUDE_DIR" +echo "" + +# ── 1. Cross-compilar Rust via cargo ndk ───────────────────────────────────── +echo "══════════════════════════════════════════════════════════════" +echo "🦀 [1/3] Compilando Rust workspace (aarch64-linux-android)..." +echo "══════════════════════════════════════════════════════════════" +cd "$ROOT_DIR/rust" +cargo ndk -t aarch64-linux-android -P 26 -o ../app/src/main/jniLibs build --release +cd "$ROOT_DIR" + +# ── 2. Copiar .so do FFmpeg para jniLibs ───────────────────────────────────── +echo "" +echo "══════════════════════════════════════════════════════════════" +echo "📦 [2/3] Copiando bibliotecas FFmpeg para jniLibs..." +echo "══════════════════════════════════════════════════════════════" +mkdir -p app/src/main/jniLibs/arm64-v8a +if [ -n "$FFMPEG_LIB_DIR" ]; then + cp "${FFMPEG_LIB_DIR}"/*.so app/src/main/jniLibs/arm64-v8a/ 2>/dev/null || true + echo "✅ FFmpeg .so copiados para app/src/main/jniLibs/arm64-v8a/" + ls -la app/src/main/jniLibs/arm64-v8a/*.so 2>/dev/null | awk '{print " " $NF " (" $5 " bytes)"}' +else + echo "⚠️ Nenhum .so FFmpeg encontrado — build pode falhar" +fi + +# ── 3. Gradle assembleDebug ────────────────────────────────────────────────── +echo "" +echo "══════════════════════════════════════════════════════════════" +echo "🤖 [3/3] Compilando Android App (Gradle assembleDebug)..." +echo "══════════════════════════════════════════════════════════════" +GRADLE_ARGS=() +if [ -n "${APP_VERSION_NAME:-}" ]; then + GRADLE_ARGS+=("-PappVersionName=$APP_VERSION_NAME") +fi +if [ -n "${APP_VERSION_CODE:-}" ]; then + GRADLE_ARGS+=("-PappVersionCode=$APP_VERSION_CODE") +fi + +# Marca o diretório como seguro para git (evita erro "dubious ownership") +git config --global --add safe.directory /project + +./gradlew assembleDebug "${GRADLE_ARGS[@]}" + +# ── Resultado ──────────────────────────────────────────────────────────────── +APK_PATH="app/build/outputs/apk/debug/app-debug.apk" +echo "" +echo "══════════════════════════════════════════════════════════════" +if [ -f "$APK_PATH" ]; then + APK_SIZE=$(du -h "$APK_PATH" | cut -f1) + echo "✅ Build concluído com sucesso!" + echo "" + echo " APK: $APK_PATH ($APK_SIZE)" + echo "" + + # Corrige ownership dos artefatos para o usuário do host + if [ -n "${HOST_UID:-}" ] && [ -n "${HOST_GID:-}" ]; then + chown -R "$HOST_UID:$HOST_GID" app/build/ 2>/dev/null || true + chown -R "$HOST_UID:$HOST_GID" app/src/main/jniLibs/ 2>/dev/null || true + chown -R "$HOST_UID:$HOST_GID" rust/target/ 2>/dev/null || true + chown -R "$HOST_UID:$HOST_GID" .gradle/ 2>/dev/null || true + echo " (ownership corrigido para UID=$HOST_UID:GID=$HOST_GID)" + fi +else + echo "❌ Build falhou — APK não encontrado em $APK_PATH" + exit 1 +fi +echo "══════════════════════════════════════════════════════════════" +echo "" From 98b91b3be9c214416ddd59723a1cb3e5fab78119 Mon Sep 17 00:00:00 2001 From: luis henrique Date: Fri, 25 Sep 2026 16:34:38 -0300 Subject: [PATCH 2/2] refactor(build): endurece e enxuga o build via Docker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Roda como não-root com o UID/GID do host (--user), --cap-drop ALL e no-new-privileges; remove o chown (que só rodava em caso de sucesso) e os arquivos de root deixados no projeto após falha de build. - Remove o symlink ffmpeg-android-maker dentro do projeto e o rm -rf do entrypoint; scripts/build.sh passa a aceitar FFMPEG_MAKER_DIR. - Entrypoint só valida o ambiente (git worktree, Meta SDK, FFmpeg) e delega para scripts/build.sh; o cp dos .so não esconde mais falhas. - Dockerfile multi-stage (sdk, ffmpeg, rust, final) com base por digest e Rust, cargo-ndk, rustup-init e commandlinetools fixados (SHA-256); imagem de ~6,4 GB. Adiciona .dockerignore e cache mounts do apt. - Move docker-build.sh para scripts/, repassa APP_VERSION_*, suporta DOCKER=podman e adiciona `make docker-build`. - Atualiza o README do Docker e o CLAUDE.md. Co-Authored-By: Claude Sonnet 5 --- CLAUDE.md | 5 + Makefile | 6 +- docker-build.sh | 101 ---------------- docker/build/.dockerignore | 3 + docker/build/Dockerfile | 241 +++++++++++++++++++++++++------------ docker/build/README.md | 128 +++++++++----------- docker/build/entrypoint.sh | 158 ++++-------------------- scripts/build.sh | 14 ++- scripts/docker-build.sh | 90 ++++++++++++++ 9 files changed, 362 insertions(+), 384 deletions(-) delete mode 100755 docker-build.sh create mode 100644 docker/build/.dockerignore create mode 100755 scripts/docker-build.sh diff --git a/CLAUDE.md b/CLAUDE.md index 9929ab9..9ab3990 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -43,6 +43,11 @@ make build # same thing make deploy # build + adb install ``` +Containerized build (no SDK/NDK/Rust/FFmpeg on the host; runs `scripts/build.sh` inside `docker/build/`, still needs the Meta SDK under `sdk/`; does not work from a `git worktree`): +```bash +./scripts/docker-build.sh # or: make docker-build +``` + Rust alone (must use `cargo ndk`, not plain `cargo build`, for anything touching `core`/`audio`/`bridge`): ```bash cd rust && cargo ndk -t aarch64-linux-android -P 26 -o ../app/src/main/jniLibs build --release diff --git a/Makefile b/Makefile index 200a09b..adbf3ab 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,14 @@ -.PHONY: all build install deploy test +.PHONY: all build docker-build install deploy test all: deploy build: ./scripts/build.sh +# Mesmo pipeline do `build`, dentro de um container (nada instalado no host). +docker-build: + ./scripts/docker-build.sh + install: adb install -r app/build/outputs/apk/debug/app-debug.apk diff --git a/docker-build.sh b/docker-build.sh deleted file mode 100755 index 60d1c1f..0000000 --- a/docker-build.sh +++ /dev/null @@ -1,101 +0,0 @@ -#!/bin/bash -# ============================================================================= -# tucaVR — Script de conveniência para build via Docker -# -# Uso: -# ./docker-build.sh # Builda a imagem (se necessário) e compila o APK -# ./docker-build.sh --rebuild # Força rebuild da imagem Docker -# ./docker-build.sh --shell # Abre um shell interativo no container -# ============================================================================= -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$SCRIPT_DIR" - -IMAGE_NAME="tucavr-builder" -DOCKERFILE_DIR="docker/build" - -# ── Argumentos ─────────────────────────────────────────────────────────────── -FORCE_REBUILD=false -INTERACTIVE_SHELL=false - -for arg in "$@"; do - case "$arg" in - --rebuild) FORCE_REBUILD=true ;; - --shell) INTERACTIVE_SHELL=true ;; - --help|-h) - echo "Uso: $0 [opções]" - echo "" - echo "Opções:" - echo " --rebuild Força rebuild da imagem Docker" - echo " --shell Abre shell interativo no container (não builda)" - echo " --help Mostra esta ajuda" - exit 0 - ;; - *) - echo "❌ Argumento desconhecido: $arg" - echo " Use --help para ver as opções" - exit 1 - ;; - esac -done - -# ── Verifica Docker ────────────────────────────────────────────────────────── -if ! command -v docker &>/dev/null; then - echo "❌ Docker não encontrado. Instale com:" - echo "" - echo " sudo apt-get update" - echo " sudo apt-get install -y docker.io" - echo " sudo systemctl enable --now docker" - echo " sudo usermod -aG docker \$USER" - echo " # Faça logout e login novamente" - echo "" - exit 1 -fi - -# ── Verifica Meta OpenXR SDK ───────────────────────────────────────────────── -if [ ! -d "sdk/meta-openxr-sdk/Samples/SampleXrFramework" ]; then - echo "⚠️ Meta OpenXR SDK não encontrado em sdk/meta-openxr-sdk/" - echo "" - echo " Baixe manualmente em:" - echo " https://developers.meta.com/horizon/downloads/package/oculus-openxr-mobile-sdk/" - echo "" - echo " Extraia para sdk/meta-openxr-sdk/ antes de buildar." - exit 1 -fi - -# ── Build da imagem Docker ─────────────────────────────────────────────────── -if $FORCE_REBUILD || ! docker image inspect "$IMAGE_NAME" &>/dev/null; then - echo "🐳 Construindo imagem Docker '$IMAGE_NAME'..." - echo " (primeira vez leva ~15-20 minutos — SDK, NDK, Rust, FFmpeg)" - echo "" - docker build -t "$IMAGE_NAME" "$DOCKERFILE_DIR" - echo "" - echo "✅ Imagem '$IMAGE_NAME' construída com sucesso!" - echo "" -else - echo "✅ Imagem '$IMAGE_NAME' já existe (use --rebuild para recriar)" -fi - -# ── Monta volumes e executa ────────────────────────────────────────────────── -DOCKER_RUN_ARGS=( - --rm - -v "$(pwd):/project" - -e "HOST_UID=$(id -u)" - -e "HOST_GID=$(id -g)" - # Caches persistentes para builds incrementais - -v "tucavr-gradle-cache:/root/.gradle" - -v "tucavr-cargo-cache:/root/.cargo/registry" -) - -if $INTERACTIVE_SHELL; then - echo "🐚 Abrindo shell interativo no container..." - echo " Projeto montado em /project" - echo " FFmpeg pré-compilado em /opt/ffmpeg-android-maker" - echo "" - docker run -it "${DOCKER_RUN_ARGS[@]}" --entrypoint /bin/bash "$IMAGE_NAME" -else - echo "🚀 Iniciando build do APK no container..." - echo "" - docker run "${DOCKER_RUN_ARGS[@]}" "$IMAGE_NAME" -fi diff --git a/docker/build/.dockerignore b/docker/build/.dockerignore new file mode 100644 index 0000000..65ea728 --- /dev/null +++ b/docker/build/.dockerignore @@ -0,0 +1,3 @@ +# Whitelist: o único arquivo do contexto que a imagem usa. +* +!entrypoint.sh diff --git a/docker/build/Dockerfile b/docker/build/Dockerfile index f16264b..309b8a7 100644 --- a/docker/build/Dockerfile +++ b/docker/build/Dockerfile @@ -1,94 +1,185 @@ +# syntax=docker/dockerfile:1 # ============================================================================= -# tucaVR — Dockerfile de build completo (APK debug) +# tucaVR — imagem de build do APK debug # -# Instala todas as toolchains necessárias para compilar o projeto: -# JDK 17, Android SDK 34, NDK 26.3, CMake 3.22.1, -# Rust stable + aarch64-linux-android, cargo-ndk, -# FFmpeg cross-compilado para arm64-v8a. +# Estágios independentes (o BuildKit constrói sdk, ffmpeg e rust em paralelo): +# sdk -> Android SDK 34, NDK 26.3, CMake 3.22.1, build-tools +# ffmpeg -> FFmpeg cross-compilado para arm64-v8a (ffmpeg-android-maker) +# rust -> Rust fixo + target aarch64-linux-android + cargo-ndk +# final -> só o que o build precisa em runtime (sem nasm/yasm/curl/wget, +# sem .git nem fontes do FFmpeg) # -# O código-fonte do projeto e o Meta OpenXR SDK são montados em runtime -# via volume (-v), NÃO copiados para a imagem. -# -# Uso: -# docker build -t tucavr-builder docker/build/ -# docker run --rm \ -# -v "$(pwd)":/project \ -# -e HOST_UID=$(id -u) -e HOST_GID=$(id -g) \ -# -v tucavr-gradle-cache:/root/.gradle \ -# -v tucavr-cargo-cache:/root/.cargo/registry \ -# tucavr-builder +# O código-fonte e o Meta OpenXR SDK são montados em /project por volume. +# O container roda como usuário não-root; use scripts/docker-build.sh, que +# passa --user com o UID/GID do host (arquivos gerados ficam seus, sem chown). # ============================================================================= -FROM ubuntu:22.04 +# Pins. Atualize digest/versões de propósito, nunca "latest". +ARG UBUNTU_IMAGE=ubuntu:22.04@sha256:b8b6ee6aa931ecd9d0d952abc34dc0e5f7c6a30c6bb71b079fe399fde0329c02 + +FROM ${UBUNTU_IMAGE} AS base +ENV DEBIAN_FRONTEND=noninteractive \ + LANG=C.UTF-8 \ + LC_ALL=C.UTF-8 +# Sem isso o docker-clean apaga o cache do apt e o cache mount fica vazio. +RUN rm -f /etc/apt/apt.conf.d/docker-clean -ENV DEBIAN_FRONTEND=noninteractive -ENV LANG=C.UTF-8 -ENV LC_ALL=C.UTF-8 +# ── sdk: Android SDK + NDK + CMake ─────────────────────────────────────────── +FROM base AS sdk +ARG CMDLINE_TOOLS_VERSION=11076708 +ARG CMDLINE_TOOLS_SHA256=2d2d50857e4eb553af5a6dc3ad507a17adf43d115264b1afc116f95c92e5e258 +ARG NDK_VERSION=26.3.11579264 +ENV ANDROID_HOME=/opt/android-sdk -# ── 1. Pacotes do sistema ──────────────────────────────────────────────────── -RUN apt-get update && apt-get install -y --no-install-recommends \ - curl wget unzip git ca-certificates \ - build-essential pkg-config \ - python3 cmake ninja-build nasm yasm \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + curl \ openjdk-17-jdk-headless \ - libclang-dev clang \ - && rm -rf /var/lib/apt/lists/* + unzip -# ── 2. Android SDK + NDK + CMake ───────────────────────────────────────────── -ENV ANDROID_HOME=/opt/android-sdk -ENV ANDROID_SDK_ROOT=${ANDROID_HOME} -ENV PATH="${ANDROID_HOME}/cmdline-tools/latest/bin:${ANDROID_HOME}/platform-tools:${PATH}" - -# Baixa o cmdline-tools -RUN mkdir -p "${ANDROID_HOME}/cmdline-tools" && \ - wget -q "https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip" \ - -O /tmp/cmdline-tools.zip && \ - unzip -q /tmp/cmdline-tools.zip -d "${ANDROID_HOME}/cmdline-tools" && \ - mv "${ANDROID_HOME}/cmdline-tools/cmdline-tools" "${ANDROID_HOME}/cmdline-tools/latest" && \ +RUN < /dev/null 2>&1 && \ - sdkmanager --no_https \ - "platform-tools" \ + yes | "${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager" --licenses > /dev/null + "${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager" \ "platforms;android-34" \ "build-tools;34.0.0" \ - "ndk;26.3.11579264" \ + "ndk;${NDK_VERSION}" \ "cmake;3.22.1" + # Nada aqui escreve no SDK depois; deixa legível para qualquer UID. + chmod -R a+rX "${ANDROID_HOME}" +EOT -ENV ANDROID_NDK_HOME="${ANDROID_HOME}/ndk/26.3.11579264" -ENV ANDROID_NDK_ROOT="${ANDROID_NDK_HOME}" -# CMake do SDK no PATH (Gradle encontra automaticamente, mas cargo/oboe pode precisar) -ENV PATH="${ANDROID_HOME}/cmake/3.22.1/bin:${PATH}" - -# ── 3. Rust toolchain ──────────────────────────────────────────────────────── -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ - sh -s -- -y --default-toolchain stable --profile minimal -ENV PATH="/root/.cargo/bin:${PATH}" - -RUN rustup target add aarch64-linux-android && \ - cargo install cargo-ndk - -# ── 4. FFmpeg cross-compile para arm64-v8a ──────────────────────────────────── -# Pinado no mesmo commit usado por scripts/setup-deps.sh -ENV FFMPEG_MAKER_DIR=/opt/ffmpeg-android-maker -RUN git clone https://github.com/Javernaut/ffmpeg-android-maker.git "${FFMPEG_MAKER_DIR}" && \ - cd "${FFMPEG_MAKER_DIR}" && \ - git checkout dd72b161ae5c759fd25a5cab971a3ff710f0bdba - -RUN cd "${FFMPEG_MAKER_DIR}" && \ - ANDROID_SDK_HOME="${ANDROID_HOME}" \ - ANDROID_NDK_HOME="${ANDROID_NDK_HOME}" \ +# ── ffmpeg: cross-compile para arm64-v8a ───────────────────────────────────── +FROM base AS ffmpeg +# Mesmo commit de scripts/setup-deps.sh (FFMPEG_MAKER_COMMIT). +ARG FFMPEG_MAKER_REF=dd72b161ae5c759fd25a5cab971a3ff710f0bdba +ARG NDK_VERSION=26.3.11579264 +ENV ANDROID_SDK_HOME=/opt/android-sdk \ + ANDROID_NDK_HOME=/opt/android-sdk/ndk/${NDK_VERSION} + +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + ca-certificates \ + cmake \ + curl \ + git \ + nasm \ + ninja-build \ + pkg-config \ + python3 \ + unzip \ + wget \ + yasm + +# Só o NDK: o resto do SDK não participa do build do FFmpeg. +COPY --from=sdk /opt/android-sdk/ndk /opt/android-sdk/ndk + +# O prefixo dos .pc gerados é /opt/ffmpeg-android-maker/...; o estágio final +# copia para o mesmo caminho para o pkg-config continuar resolvendo. +RUN <. +2. **Meta OpenXR Mobile SDK** em `sdk/meta-openxr-sdk/` (download manual, exige + aceite de licença): + - + - deve existir `sdk/meta-openxr-sdk/Samples/SampleXrFramework/`. +3. Um **checkout normal ou clone** do repositório. `git worktree` não funciona: + o `.git` de um worktree aponta para um caminho que não existe no container + (o entrypoint detecta e avisa). -2. **Meta OpenXR Mobile SDK** em `sdk/meta-openxr-sdk/`: - - Baixe em https://developers.meta.com/horizon/downloads/package/oculus-openxr-mobile-sdk/ - - Extraia de forma que exista `sdk/meta-openxr-sdk/Samples/SampleXrFramework/` - -## Uso Rápido +## Uso Da raiz do projeto: ```bash -# Build completo (imagem + APK) -./docker-build.sh - -# Forçar rebuild da imagem Docker -./docker-build.sh --rebuild - -# Shell interativo para debug -./docker-build.sh --shell +./scripts/docker-build.sh # constrói a imagem (cache) e compila o APK +./scripts/docker-build.sh --rebuild # refaz a imagem do zero (--no-cache --pull) +./scripts/docker-build.sh --shell # shell interativo no container +make docker-build # atalho para o primeiro ``` -O APK gerado aparece em: `app/build/outputs/apk/debug/app-debug.apk` +O APK sai em `app/build/outputs/apk/debug/app-debug.apk`. -## Uso Manual (docker CLI) +`APP_VERSION_NAME` e `APP_VERSION_CODE`, se definidos no host, são repassados ao +Gradle. `DOCKER=podman ./scripts/docker-build.sh` usa outro runtime (o caminho +com Podman rootless não foi testado). -```bash -# 1. Construir a imagem (uma vez) -docker build -t tucavr-builder docker/build/ +## Como funciona -# 2. Compilar o APK -docker run --rm \ - -v "$(pwd)":/project \ - -e HOST_UID=$(id -u) -e HOST_GID=$(id -g) \ - -v tucavr-gradle-cache:/root/.gradle \ - -v tucavr-cargo-cache:/root/.cargo/registry \ - tucavr-builder - -# 3. (Opcional) Shell interativo -docker run --rm -it \ - -v "$(pwd)":/project \ - --entrypoint /bin/bash \ - tucavr-builder -``` +- **Multi-stage**: `sdk`, `ffmpeg` e `rust` são independentes (o BuildKit os + constrói em paralelo). A imagem final leva só o necessário para compilar: sem + `nasm`/`yasm`/`curl`/`wget`, sem `.git` do `ffmpeg-android-maker` e sem fontes + do FFmpeg. +- **Não-root**: o container roda com o UID/GID do host (`--user`), então tudo o + que ele gera já nasce seu, sem `chown` e sem arquivos de root no projeto. + Também usa `--cap-drop ALL` e `no-new-privileges`. Sem `--user`, a imagem usa + `10001:10001` (que não escreve no projeto montado). +- **Nada é escrito fora dos caches e do projeto**: o FFmpeg pré-compilado fica em + `/opt/ffmpeg-android-maker` e o `build.sh` o encontra por `FFMPEG_MAKER_DIR`; não há + symlink dentro do seu projeto. +- **Reprodutível**: base Ubuntu por digest, Rust, `cargo-ndk`, `rustup-init` e + `commandlinetools` fixados (os dois últimos com SHA-256), FFmpeg no mesmo commit + de `scripts/setup-deps.sh`. Atualize os `ARG` do Dockerfile de propósito. -## O que está dentro da imagem +## Conteúdo da imagem | Componente | Versão | |------------|--------| -| Ubuntu | 22.04 | -| JDK | 17 (OpenJDK Headless) | -| Android SDK | Platform 34 | +| Ubuntu | 22.04 (por digest) | +| JDK | 17 (OpenJDK headless) | +| Android SDK | Platform 34, build-tools 34.0.0 | | Android NDK | 26.3.11579264 | | CMake | 3.22.1 (via SDK) | -| Rust | stable + `aarch64-linux-android` | -| cargo-ndk | latest | -| FFmpeg | Pré-compilado para `arm64-v8a` (API 26) | - -**Tamanho estimado da imagem:** ~12-15 GB (SDK + NDK + FFmpeg são pesados). +| Rust | 1.97.1 + `aarch64-linux-android` | +| cargo-ndk | 4.1.2 | +| FFmpeg | `arm64-v8a`, API 26 | ## Caches -Volumes Docker persistem caches entre builds: +Volumes nomeados persistem entre builds: | Volume | Conteúdo | |--------|----------| -| `tucavr-gradle-cache` | Downloads de dependências Gradle e build cache | -| `tucavr-cargo-cache` | Registry de crates Rust | +| `tucavr-gradle-cache` | Dependências e cache do Gradle (`/cache/gradle`) | +| `tucavr-cargo-cache` | Registry e git de crates (`/cache/cargo`) | -Para limpar caches: -```bash -docker volume rm tucavr-gradle-cache tucavr-cargo-cache -``` +Para limpar: `docker volume rm tucavr-gradle-cache tucavr-cargo-cache`. -## Variáveis de Ambiente Opcionais +## Uso manual (sem o script) -| Variável | Descrição | -|----------|-----------| -| `APP_VERSION_NAME` | Override da versionName (ex: `1.0.0`) | -| `APP_VERSION_CODE` | Override do versionCode (ex: `100`) | -| `HOST_UID` | UID do host para corrigir ownership dos artefatos | -| `HOST_GID` | GID do host para corrigir ownership dos artefatos | - -Exemplo: ```bash -docker run --rm \ +docker build -t tucavr-builder docker/build/ +docker run --rm --init --user "$(id -u):$(id -g)" \ + --cap-drop ALL --security-opt no-new-privileges:true \ -v "$(pwd)":/project \ - -e APP_VERSION_NAME=1.0.0 \ - -e APP_VERSION_CODE=100 \ - -e HOST_UID=$(id -u) -e HOST_GID=$(id -g) \ + -v tucavr-gradle-cache:/cache/gradle \ + -v tucavr-cargo-cache:/cache/cargo \ tucavr-builder ``` + +## Solução de problemas + +- `/project é um git worktree`: use um clone completo (veja Pré-requisitos). +- `Meta OpenXR SDK não encontrado`: veja Pré-requisitos, item 2. +- Cache corrompido ou lento: apague os volumes acima e rode com `--rebuild`. diff --git a/docker/build/entrypoint.sh b/docker/build/entrypoint.sh index 368ae28..750f1a4 100755 --- a/docker/build/entrypoint.sh +++ b/docker/build/entrypoint.sh @@ -1,148 +1,38 @@ #!/bin/bash # ============================================================================= -# tucaVR — Entrypoint do container Docker de build +# tucaVR — Entrypoint do container de build # -# Orquestra o build completo: -# 1. Cria symlink do ffmpeg-android-maker pré-compilado -# 2. Cross-compila o workspace Rust via cargo ndk -# 3. Copia .so do FFmpeg para jniLibs -# 4. Executa ./gradlew assembleDebug (Kotlin + C++/CMake) -# 5. Corrige ownership dos artefatos de saída +# Só valida o ambiente e delega para scripts/build.sh, a única fonte de verdade +# do pipeline (cargo ndk -> .so do FFmpeg -> gradle). O container roda com o +# UID do host, então nada aqui precisa de chown nem toca em arquivos do projeto. # ============================================================================= set -euo pipefail -echo "" -echo "╔══════════════════════════════════════════════════════════════╗" -echo "║ tucaVR — Docker Build ║" -echo "╚══════════════════════════════════════════════════════════════╝" -echo "" - -ROOT_DIR="/project" -cd "$ROOT_DIR" - -# ── Verifica que o projeto está montado ────────────────────────────────────── -if [ ! -f "build.gradle.kts" ]; then - echo "❌ ERRO: Código-fonte do projeto não encontrado em /project" - echo " Monte o projeto com: -v \"\$(pwd)\":/project" - exit 1 -fi - -# ── Verifica o Meta OpenXR SDK ─────────────────────────────────────────────── -if [ ! -d "sdk/meta-openxr-sdk/Samples/SampleXrFramework" ]; then - echo "❌ ERRO: Meta OpenXR SDK não encontrado em sdk/meta-openxr-sdk/" - echo "" - echo " Esse SDK requer download manual com aceite de licença:" - echo " https://developers.meta.com/horizon/downloads/package/oculus-openxr-mobile-sdk/" - echo "" - echo " Extraia para sdk/meta-openxr-sdk/ e tente novamente." +die() { + echo "❌ $1" >&2 + shift + for line in "$@"; do echo " $line" >&2; done exit 1 -fi - -# ── Symlink do ffmpeg-android-maker pré-compilado ──────────────────────────── -FFMPEG_MAKER_DIR="/opt/ffmpeg-android-maker" -if [ ! -d "$ROOT_DIR/ffmpeg-android-maker" ]; then - echo "📎 Criando symlink ffmpeg-android-maker → ${FFMPEG_MAKER_DIR}" - ln -sf "$FFMPEG_MAKER_DIR" "$ROOT_DIR/ffmpeg-android-maker" -elif [ ! -d "$ROOT_DIR/ffmpeg-android-maker/build/ffmpeg/arm64-v8a" ]; then - echo "📎 ffmpeg-android-maker existe mas sem build — redirecionando para pré-compilado" - rm -rf "$ROOT_DIR/ffmpeg-android-maker" - ln -sf "$FFMPEG_MAKER_DIR" "$ROOT_DIR/ffmpeg-android-maker" -else - echo "✅ Usando ffmpeg-android-maker existente no projeto" -fi - -# ── Variáveis de ambiente para cross-compilation ───────────────────────────── -export ANDROID_NDK_HOME="${ANDROID_NDK_HOME:-/opt/android-sdk/ndk/26.3.11579264}" -export ANDROID_NDK_ROOT="$ANDROID_NDK_HOME" -export PKG_CONFIG_ALLOW_CROSS=1 +} -# Detecta onde estão os pkgconfig do FFmpeg -FFMPEG_LIB_DIR="" -if [ -d "$ROOT_DIR/ffmpeg-android-maker/build/ffmpeg/arm64-v8a/lib" ]; then - FFMPEG_LIB_DIR="$ROOT_DIR/ffmpeg-android-maker/build/ffmpeg/arm64-v8a/lib" -fi -export PKG_CONFIG_PATH="${FFMPEG_LIB_DIR}/pkgconfig" +cd /project -# Detecta onde estão os headers do FFmpeg -FFMPEG_INCLUDE_DIR="" -if [ -d "$ROOT_DIR/ffmpeg-android-maker/build/ffmpeg/arm64-v8a/include" ]; then - FFMPEG_INCLUDE_DIR="$ROOT_DIR/ffmpeg-android-maker/build/ffmpeg/arm64-v8a/include" -elif [ -d "$ROOT_DIR/ffmpeg-android-maker/output/include/arm64-v8a" ]; then - FFMPEG_INCLUDE_DIR="$ROOT_DIR/ffmpeg-android-maker/output/include/arm64-v8a" -fi +[ -f build.gradle.kts ] || die "Código-fonte do projeto não encontrado em /project" \ + "Monte o projeto com: -v \"\$(pwd)\":/project" -NDK_SYSROOT="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/sysroot" -export BINDGEN_EXTRA_CLANG_ARGS="-I${FFMPEG_INCLUDE_DIR} -I${NDK_SYSROOT}/usr/include --sysroot=${NDK_SYSROOT}" +[ -d sdk/meta-openxr-sdk/Samples/SampleXrFramework ] || die "Meta OpenXR SDK não encontrado em sdk/meta-openxr-sdk/" \ + "Esse SDK exige download manual com aceite de licença:" \ + "https://developers.meta.com/horizon/downloads/package/oculus-openxr-mobile-sdk/" -echo "" -echo "📋 Configuração:" -echo " ANDROID_NDK_HOME = $ANDROID_NDK_HOME" -echo " PKG_CONFIG_PATH = $PKG_CONFIG_PATH" -echo " FFMPEG_INCLUDE_DIR = $FFMPEG_INCLUDE_DIR" -echo "" +# Num git worktree o .git é um arquivo que aponta para um caminho do host, +# inexistente aqui; o Gradle usa git e falharia tarde e com erro confuso. +[ ! -f .git ] || die "/project é um git worktree (.git é um arquivo)" \ + "O caminho gitdir dele não existe dentro do container." \ + "Use o checkout principal ou um clone completo (git clone)." -# ── 1. Cross-compilar Rust via cargo ndk ───────────────────────────────────── -echo "══════════════════════════════════════════════════════════════" -echo "🦀 [1/3] Compilando Rust workspace (aarch64-linux-android)..." -echo "══════════════════════════════════════════════════════════════" -cd "$ROOT_DIR/rust" -cargo ndk -t aarch64-linux-android -P 26 -o ../app/src/main/jniLibs build --release -cd "$ROOT_DIR" +[ -d "${FFMPEG_MAKER_DIR}/build/ffmpeg/arm64-v8a/lib" ] || die "FFmpeg pré-compilado ausente em ${FFMPEG_MAKER_DIR}" \ + "A imagem está corrompida; refaça com: scripts/docker-build.sh --rebuild" -# ── 2. Copiar .so do FFmpeg para jniLibs ───────────────────────────────────── -echo "" -echo "══════════════════════════════════════════════════════════════" -echo "📦 [2/3] Copiando bibliotecas FFmpeg para jniLibs..." -echo "══════════════════════════════════════════════════════════════" -mkdir -p app/src/main/jniLibs/arm64-v8a -if [ -n "$FFMPEG_LIB_DIR" ]; then - cp "${FFMPEG_LIB_DIR}"/*.so app/src/main/jniLibs/arm64-v8a/ 2>/dev/null || true - echo "✅ FFmpeg .so copiados para app/src/main/jniLibs/arm64-v8a/" - ls -la app/src/main/jniLibs/arm64-v8a/*.so 2>/dev/null | awk '{print " " $NF " (" $5 " bytes)"}' -else - echo "⚠️ Nenhum .so FFmpeg encontrado — build pode falhar" -fi +echo "📋 NDK=${ANDROID_NDK_HOME} FFMPEG_MAKER_DIR=${FFMPEG_MAKER_DIR} UID=$(id -u):$(id -g)" -# ── 3. Gradle assembleDebug ────────────────────────────────────────────────── -echo "" -echo "══════════════════════════════════════════════════════════════" -echo "🤖 [3/3] Compilando Android App (Gradle assembleDebug)..." -echo "══════════════════════════════════════════════════════════════" -GRADLE_ARGS=() -if [ -n "${APP_VERSION_NAME:-}" ]; then - GRADLE_ARGS+=("-PappVersionName=$APP_VERSION_NAME") -fi -if [ -n "${APP_VERSION_CODE:-}" ]; then - GRADLE_ARGS+=("-PappVersionCode=$APP_VERSION_CODE") -fi - -# Marca o diretório como seguro para git (evita erro "dubious ownership") -git config --global --add safe.directory /project - -./gradlew assembleDebug "${GRADLE_ARGS[@]}" - -# ── Resultado ──────────────────────────────────────────────────────────────── -APK_PATH="app/build/outputs/apk/debug/app-debug.apk" -echo "" -echo "══════════════════════════════════════════════════════════════" -if [ -f "$APK_PATH" ]; then - APK_SIZE=$(du -h "$APK_PATH" | cut -f1) - echo "✅ Build concluído com sucesso!" - echo "" - echo " APK: $APK_PATH ($APK_SIZE)" - echo "" - - # Corrige ownership dos artefatos para o usuário do host - if [ -n "${HOST_UID:-}" ] && [ -n "${HOST_GID:-}" ]; then - chown -R "$HOST_UID:$HOST_GID" app/build/ 2>/dev/null || true - chown -R "$HOST_UID:$HOST_GID" app/src/main/jniLibs/ 2>/dev/null || true - chown -R "$HOST_UID:$HOST_GID" rust/target/ 2>/dev/null || true - chown -R "$HOST_UID:$HOST_GID" .gradle/ 2>/dev/null || true - echo " (ownership corrigido para UID=$HOST_UID:GID=$HOST_GID)" - fi -else - echo "❌ Build falhou — APK não encontrado em $APK_PATH" - exit 1 -fi -echo "══════════════════════════════════════════════════════════════" -echo "" +exec ./scripts/build.sh "$@" diff --git a/scripts/build.sh b/scripts/build.sh index bce22a0..924ef2a 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -11,15 +11,23 @@ echo "🦀 Compilando Rust Core (aarch64-linux-android)..." export ANDROID_NDK_HOME=${ANDROID_NDK_HOME:-$ANDROID_HOME/ndk/26.3.11579264} export ANDROID_NDK_ROOT=$ANDROID_NDK_HOME export PKG_CONFIG_ALLOW_CROSS=1 -export PKG_CONFIG_PATH="$ROOT_DIR/ffmpeg-android-maker/build/ffmpeg/arm64-v8a/lib/pkgconfig" -export BINDGEN_EXTRA_CLANG_ARGS="-I$ROOT_DIR/ffmpeg-android-maker/output/include/arm64-v8a -I$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include --sysroot=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/sysroot" +# FFMPEG_MAKER_DIR permite apontar para um FFmpeg pré-compilado fora do projeto +# (é o que o container de docker/build faz); por padrão usa o de setup-deps.sh. +FFMPEG_MAKER_DIR="${FFMPEG_MAKER_DIR:-$ROOT_DIR/ffmpeg-android-maker}" +if ! ls "$FFMPEG_MAKER_DIR"/build/ffmpeg/arm64-v8a/lib/*.so > /dev/null 2>&1; then + echo "❌ FFmpeg para arm64-v8a não encontrado em $FFMPEG_MAKER_DIR" >&2 + echo " Rode ./scripts/setup-deps.sh e compile o FFmpeg (veja o README)." >&2 + exit 1 +fi +export PKG_CONFIG_PATH="$FFMPEG_MAKER_DIR/build/ffmpeg/arm64-v8a/lib/pkgconfig" +export BINDGEN_EXTRA_CLANG_ARGS="-I$FFMPEG_MAKER_DIR/output/include/arm64-v8a -I$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include --sysroot=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/sysroot" cd rust # cargo ndk copia automaticamente os .so se usarmos o -o cargo ndk -t aarch64-linux-android -P 26 -o ../app/src/main/jniLibs build --release # Copy FFmpeg shared libraries -cp ../ffmpeg-android-maker/build/ffmpeg/arm64-v8a/lib/*.so ../app/src/main/jniLibs/arm64-v8a/ || true +cp "$FFMPEG_MAKER_DIR"/build/ffmpeg/arm64-v8a/lib/*.so ../app/src/main/jniLibs/arm64-v8a/ cd .. diff --git a/scripts/docker-build.sh b/scripts/docker-build.sh new file mode 100755 index 0000000..01ebbc5 --- /dev/null +++ b/scripts/docker-build.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# ============================================================================= +# tucaVR — Build do APK debug dentro de um container (sem instalar SDK/NDK/Rust +# no host). +# +# Uso: +# ./scripts/docker-build.sh # constrói a imagem (cache) e compila o APK +# ./scripts/docker-build.sh --rebuild # refaz a imagem do zero (--no-cache --pull) +# ./scripts/docker-build.sh --shell # shell interativo no container +# +# Variáveis: APP_VERSION_NAME, APP_VERSION_CODE (repassadas ao Gradle) e +# DOCKER (padrão: docker; ex.: DOCKER=podman). +# ============================================================================= +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT_DIR" + +DOCKER="${DOCKER:-docker}" +IMAGE_NAME="tucavr-builder" +DOCKERFILE_DIR="docker/build" + +FORCE_REBUILD=false +INTERACTIVE_SHELL=false + +for arg in "$@"; do + case "$arg" in + --rebuild) FORCE_REBUILD=true ;; + --shell) INTERACTIVE_SHELL=true ;; + --help|-h) + sed -n '2,13p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//' + exit 0 + ;; + *) + echo "❌ Argumento desconhecido: $arg (use --help)" >&2 + exit 1 + ;; + esac +done + +if ! command -v "$DOCKER" &> /dev/null; then + echo "❌ '$DOCKER' não encontrado. Instale o Docker: https://docs.docker.com/engine/install/" >&2 + exit 1 +fi + +# A checagem do Meta OpenXR SDK fica no entrypoint (uma só fonte); aqui só +# evitamos esperar o build da imagem para descobrir que ele falta. +if [ ! -d "sdk/meta-openxr-sdk/Samples/SampleXrFramework" ]; then + echo "⚠️ Meta OpenXR SDK não encontrado em sdk/meta-openxr-sdk/" >&2 + echo " Baixe em https://developers.meta.com/horizon/downloads/package/oculus-openxr-mobile-sdk/" >&2 + echo " e extraia para sdk/meta-openxr-sdk/." >&2 + exit 1 +fi + +# Sempre roda o build da imagem: sem mudanças é só cache (segundos), e mudanças +# no Dockerfile/entrypoint nunca ficam com imagem velha. +BUILD_ARGS=() +if $FORCE_REBUILD; then + BUILD_ARGS+=(--no-cache --pull) +fi +echo "🐳 Preparando a imagem '$IMAGE_NAME' (a primeira vez leva vários minutos)..." +"$DOCKER" build "${BUILD_ARGS[@]}" -t "$IMAGE_NAME" "$DOCKERFILE_DIR" + +# Roda com o UID/GID do host: arquivos gerados já nascem seus, sem chown/root. +RUN_ARGS=( + --rm + --init + --user "$(id -u):$(id -g)" + --cap-drop ALL + --security-opt no-new-privileges:true + -v "$ROOT_DIR:/project" + # Caches persistentes para builds incrementais + -v tucavr-gradle-cache:/cache/gradle + -v tucavr-cargo-cache:/cache/cargo + -e APP_VERSION_NAME + -e APP_VERSION_CODE +) +# Podman rootless: mantém o UID do host dentro do container. +if [[ "$(basename "$DOCKER")" == podman* ]]; then + RUN_ARGS+=(--userns=keep-id) +fi + +if $INTERACTIVE_SHELL; then + echo "🐚 Shell no container (projeto em /project, FFmpeg em /opt/ffmpeg-android-maker)" + "$DOCKER" run -it "${RUN_ARGS[@]}" --entrypoint /bin/bash "$IMAGE_NAME" +else + echo "🚀 Compilando o APK no container..." + "$DOCKER" run "${RUN_ARGS[@]}" "$IMAGE_NAME" + echo "📦 APK: app/build/outputs/apk/debug/app-debug.apk" +fi