diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..0661943 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,54 @@ +# Docker ignore file for Android build + +# Build artifacts +android-build/ +*.apk +*.aab +*.o +*.so +moc_*.cpp +ui_*.h +qrc_*.cpp +.qmake.stash +Makefile + +# Qt Creator files +*.pro.user +*.pro.user.* + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Git +.git/ +.gitignore +.gitattributes + +# CI/CD +.github/ + +# Documentation (optional, comment out if you want to include) +*.md +!BUILD_ANDROID_LOCAL.md + +# Temporary files +tmp/ +temp/ +*.tmp + +# macOS +.DS_Store + +# Windows +Thumbs.db +desktop.ini + +# Qt cache +qt/ + +# Logs +*.log diff --git a/.github/workflows/mobile.yml b/.github/workflows/mobile.yml index 1b12702..25be7b5 100644 --- a/.github/workflows/mobile.yml +++ b/.github/workflows/mobile.yml @@ -9,57 +9,277 @@ on: env: BUILD_TYPE: Release -# ACTIONS_RUNNER_DEBUG: true -# ACTIONS_STEP_DEBUG: true - jobs: - ci: - runs-on: ${{ matrix.os }} + android-build: + runs-on: ubuntu-22.04 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - strategy: - matrix: - os: [ubuntu-20.04] - #os: [macos-10.15, ubuntu-20.04, windows-2019] - # version: ['5.9.0', '5.15.1', '6.2.0'] - version: ['5.9.0'] - - # Ubuntu 18 is not a supported target for Qt 6: https://www.qt.io/blog/qt6-development-hosts-and-targets - exclude: - - os: ubuntu-18.04 - version: '6.2.0' + QT_VERSION: '6.5.3' + ANDROID_API_LEVEL: '34' + ANDROID_BUILD_TOOLS_VERSION: '34.0.0' + ANDROID_NDK_VERSION: '21.4.7075529' + steps: - - uses: actions/checkout@v2 - - - name: Install Qt - if: startsWith(matrix.os, 'ubuntu') - uses: jurplel/install-qt-action@v2 + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 with: - version: ${{matrix.version}} - host: linux - target: 'android' - arch: 'android_armv7' - dir: '${{github.workspace}}/qt/' - install-deps: 'true' + api-level: ${{ env.ANDROID_API_LEVEL }} + build-tools: ${{ env.ANDROID_BUILD_TOOLS_VERSION }} + ndk-version: ${{ env.ANDROID_NDK_VERSION }} + + - name: Configure Android SDK Environment + run: | + # Ensure correct Android platform is available + echo "ANDROID_HOME: $ANDROID_HOME" + echo "ANDROID_SDK_ROOT: $ANDROID_SDK_ROOT" + + # List installed platforms BEFORE cleanup + echo "Installed Android platforms BEFORE cleanup:" + ls -la $ANDROID_HOME/platforms/ || true + + # CRITICAL: Remove corrupted SDK platforms 35 and 36 that cause aapt2 linking errors + echo "Removing corrupted Android SDK platforms 35 and 36..." + rm -rf $ANDROID_HOME/platforms/android-35* || true + rm -rf $ANDROID_HOME/platforms/android-36* || true + + # Ensure we have SDK platform 34 + if [ ! -d "$ANDROID_HOME/platforms/android-34" ]; then + echo "Installing Android SDK Platform 34..." + yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "platforms;android-34" + fi + + # List installed platforms AFTER cleanup + echo "Installed Android platforms AFTER cleanup:" + ls -la $ANDROID_HOME/platforms/ || true + + # Set ANDROID_SDK_ROOT if not set + if [ -z "$ANDROID_SDK_ROOT" ]; then + echo "ANDROID_SDK_ROOT=$ANDROID_HOME" >> $GITHUB_ENV + fi + + - name: Install aqt + run: | + python3 -m pip install --upgrade pip + python3 -m pip install aqtinstall==3.1.* + + - name: Install Qt for Android + run: | + # Use Qt 6.5.3 which has better Android support and availability + # androidextras is not needed in Qt 6 (integrated into Qt Core) + echo "Installing Qt 6.5.3 for Android..." + + # Try android_arm64_v8a first (most common modern architecture) + echo "Attempting to install Qt 6.5.3 for android_arm64_v8a..." + if python3 -m aqt install-qt linux android 6.5.3 android_arm64_v8a \ + --outputdir ${{github.workspace}}/qt/; then + echo "Successfully installed Qt 6.5.3 for android_arm64_v8a" + elif python3 -m aqt install-qt linux android 6.5.3 android_armv7 \ + --outputdir ${{github.workspace}}/qt/; then + echo "Successfully installed Qt 6.5.3 for android_armv7" + else + echo "ERROR: Failed to install Qt 6.5.3 for Android" + echo "Trying to list available versions and architectures..." + python3 -m aqt list-qt linux android || true + exit 1 + fi + + # Verify Android Qt was actually installed + echo "Verifying Android Qt installation..." + if find ${{github.workspace}}/qt -path "*/android*/bin/qmake" -type f | grep -q .; then + echo "Android Qt qmake found - installation successful" + else + echo "ERROR: Android Qt installation failed - no Android qmake found" + echo "Directory structure:" + ls -la ${{github.workspace}}/qt/ + exit 1 + fi + + - name: Install Qt Desktop (needed for qmake) + run: | + # Install Qt 6.5.3 for desktop to match Android version + echo "Installing Qt 6.5.3 for desktop..." + python3 -m aqt install-qt linux desktop 6.5.3 gcc_64 \ + --outputdir ${{github.workspace}}/qt/ + + - name: Verify Android environment + run: | + echo "ANDROID_HOME: $ANDROID_HOME" + echo "ANDROID_NDK_HOME: $ANDROID_NDK_HOME" + echo "JAVA_HOME: $JAVA_HOME" + + # We're using Qt 6.5.3 for Android + echo "Looking for Qt 6.5.3 Android installation..." + + # Find Qt installations + echo "Qt directory structure:" + find ${{github.workspace}}/qt -name "6.5.3" -type d + + # Find Android qmake specifically (not desktop qmake) + # Android Qt is typically in paths containing "android" + ANDROID_QMAKE=$(find ${{github.workspace}}/qt -path "*/android*/bin/qmake" -type f | head -1) + + if [ -n "$ANDROID_QMAKE" ]; then + ANDROID_QT_DIR=$(dirname $(dirname "$ANDROID_QMAKE")) + echo "Android Qt installation found at: $ANDROID_QT_DIR" + echo "Android qmake found at: $ANDROID_QMAKE" + echo "ANDROID_QT_DIR=$ANDROID_QT_DIR" >> $GITHUB_ENV + echo "ANDROID_QMAKE=$ANDROID_QMAKE" >> $GITHUB_ENV + + # Verify qmake version + echo "Android qmake version:" + "$ANDROID_QMAKE" -version + else + echo "ERROR: Android qmake not found" + echo "Listing all qmake files:" + find ${{github.workspace}}/qt -name "qmake" -type f + exit 1 + fi + + # Check if required tools exist + which make || echo "make not found" + ls -la $ANDROID_HOME/platforms/ || echo "No Android platforms found" + ls -la $ANDROID_NDK_HOME/ || echo "NDK not found" - - name: Build - if: startsWith(matrix.os, 'ubuntu') - env: - ACTIONS_RUNNER_DEBUG: true - ACTIONS_STEP_DEBUG: true - working-directory: ${{github.workspace}} - run: | - qmake CONFIG+=release - make - ls -l - mv Interrogator.apk AndroidInterrogator.apk - - - name: Upload Artifact ${{github.os}} - if: startsWith(matrix.os, 'ubuntu') - uses: actions/upload-artifact@v3 + - name: Configure project for Android + run: | + set -e # Exit immediately if any command fails + + export ANDROID_SDK_ROOT=$ANDROID_HOME + export ANDROID_NDK_ROOT=$ANDROID_NDK_HOME + + # Use the Android qmake we found earlier + echo "Using Android qmake from: $ANDROID_QMAKE" + echo "Using Android Qt at: $ANDROID_QT_DIR" + + if [ -n "$ANDROID_QMAKE" ] && [ -f "$ANDROID_QMAKE" ]; then + export PATH="$(dirname $ANDROID_QMAKE):$PATH" + + # Generate Makefile for Android + echo "Configuring project with qmake..." + if ! "$ANDROID_QMAKE" -spec android-clang \ + CONFIG+=release \ + ANDROID_ABIS=arm64-v8a \ + Interrogator.pro; then + echo "ERROR: qmake configuration failed" + exit 1 + fi + else + echo "ERROR: Android qmake not found at $ANDROID_QMAKE" + find ${{github.workspace}}/qt -name "qmake" -type f + exit 1 + fi + + - name: Build APK + run: | + set -e # Exit immediately if any command fails + + export ANDROID_SDK_ROOT=$ANDROID_HOME + export ANDROID_NDK_ROOT=$ANDROID_NDK_HOME + + # Use the Android qmake path + if [ -n "$ANDROID_QMAKE" ]; then + export PATH="$(dirname $ANDROID_QMAKE):$PATH" + fi + + # Build the Android project + echo "Building Android project with make..." + if ! make -j$(nproc); then + echo "ERROR: Make build failed" + exit 1 + fi + + # Find androiddeployqt tool in the Android Qt directory + ANDROIDDEPLOYQT=$(find $ANDROID_QT_DIR -name "androiddeployqt" -type f | head -1) + + if [ -z "$ANDROIDDEPLOYQT" ]; then + # Try searching in the whole Qt directory + ANDROIDDEPLOYQT=$(find ${{github.workspace}}/qt -path "*/android*/bin/androiddeployqt" -type f | head -1) + fi + + echo "Looking for androiddeployqt..." + if [ -n "$ANDROIDDEPLOYQT" ] && [ -f "$ANDROIDDEPLOYQT" ]; then + echo "Found androiddeployqt at: $ANDROIDDEPLOYQT" + # Package APK using androiddeployqt + echo "Generating APK with androiddeployqt..." + if ! "$ANDROIDDEPLOYQT" \ + --input android-Interrogator-deployment-settings.json \ + --output android-build \ + --android-platform android-${{ env.ANDROID_API_LEVEL }} \ + --jdk $JAVA_HOME \ + --gradle \ + --release; then + echo "ERROR: androiddeployqt failed" + exit 1 + fi + else + echo "ERROR: androiddeployqt not found" + echo "Cannot package APK without androiddeployqt" + exit 1 + fi + + # List what was built + echo "Files generated:" + find . -name "*.apk" -o -name "*.aab" | head -10 + + - name: Find and copy APK + run: | + set -e # Exit immediately if any command fails + + # Look for generated APK files + echo "Searching for APK files..." + find . -name "*.apk" -type f + + # Copy APK to standard location + APK_FILE=$(find . -name "*.apk" -type f | head -1) + if [ -n "$APK_FILE" ]; then + cp "$APK_FILE" AndroidInterrogator.apk + echo "Found APK: $APK_FILE" + ls -la AndroidInterrogator.apk + else + echo "WARNING: No APK found, checking for AAB files..." + find . -name "*.aab" -type f + + # If AAB exists, we'd need bundletool to convert to APK + AAB_FILE=$(find . -name "*.aab" -type f | head -1) + if [ -n "$AAB_FILE" ]; then + echo "Found AAB: $AAB_FILE" + # For now, just copy the AAB as the output + cp "$AAB_FILE" AndroidInterrogator.aab + else + echo "ERROR: No APK or AAB files were generated" + echo "Build appears to have failed" + exit 1 + fi + fi + + - name: Upload APK Artifact + uses: actions/upload-artifact@v4 + if: always() with: - name: AndroidInterrogator.apk ${{github.os}} + name: AndroidInterrogator-Build path: | AndroidInterrogator.apk + AndroidInterrogator.aab + android-build/build/outputs/**/* + if-no-files-found: warn + + - name: Upload Build Logs + uses: actions/upload-artifact@v4 + if: failure() + with: + name: Android-Build-Logs + path: | + android-build/build/outputs/logs/ + *.log + if-no-files-found: ignore diff --git a/.gitignore b/.gitignore index 587c98e..b2c1680 100644 --- a/.gitignore +++ b/.gitignore @@ -53,4 +53,13 @@ compile_commands.json *_qmlcache.qrc -Interrogator.app \ No newline at end of file +Interrogator.app + +# Android build artifacts +android-build/ +*.apk +*.aab +*-deployment-settings.json +.gradle/ +build/ +local.properties \ No newline at end of file diff --git a/BUILD_ANDROID_DOCKER.md b/BUILD_ANDROID_DOCKER.md new file mode 100644 index 0000000..e2b9606 --- /dev/null +++ b/BUILD_ANDROID_DOCKER.md @@ -0,0 +1,232 @@ +# Building Android APK with Docker + +This guide explains how to build the Interrogator Android APK locally using Docker. + +## Prerequisites + +- Docker installed on your system +- At least 10GB of free disk space +- Git (to clone the repository) + +## Quick Start + +### 1. Build the Docker Image + +From the project root directory, run: + +```bash +docker build -f Dockerfile.android -t interrogator-android . +``` + +This will create a Docker image with all necessary dependencies: +- Android SDK and NDK +- Qt 6.5.3 for Android +- Build tools and Java JDK 17 + +**Note:** First build will take 15-30 minutes as it downloads all dependencies (~8GB). + +### 2. Build the APK + +Run the container to build the APK: + +```bash +docker run --rm -v $(pwd):/workspace -v $(pwd)/build:/build interrogator-android +``` + +The APK will be generated at: `./build/Interrogator.apk` + +## Detailed Usage + +### Building with Custom Options + +You can override the build script or run custom commands: + +```bash +# Interactive shell inside container +docker run --rm -it -v $(pwd):/workspace interrogator-android /bin/bash + +# Run specific build commands +docker run --rm -v $(pwd):/workspace interrogator-android \ + bash -c "cd /workspace && qmake && make" +``` + +### Build Output + +The build process creates: +- `build-android/` - Build artifacts and intermediate files +- `build/Interrogator.apk` - Final APK file (copied to host) + +### Cleaning Build Artifacts + +To clean previous builds: + +```bash +# Remove build directory on host +rm -rf build-android build + +# Or clean inside container +docker run --rm -v $(pwd):/workspace interrogator-android \ + bash -c "rm -rf /workspace/build-android /workspace/build" +``` + +## Architecture Support + +The default build targets **arm64-v8a** (64-bit ARM), which works on most modern Android devices. + +To build for other architectures, modify the `ANDROID_ABIS` in `docker-build.sh`: + +```bash +# For 32-bit ARM +ANDROID_ABIS="armeabi-v7a" + +# For multiple architectures +ANDROID_ABIS="arm64-v8a armeabi-v7a" +``` + +## Troubleshooting + +### Build fails with "androiddeployqt not found" + +Ensure the Qt Android installation completed successfully: + +```bash +docker run --rm interrogator-android ls -la /opt/qt/6.5.3/android_arm64_v8a/bin/ +``` + +### APK not generated + +Check the build logs for errors: + +```bash +docker run --rm -v $(pwd):/workspace interrogator-android +``` + +Look for compilation errors or missing dependencies in the output. + +### Out of disk space + +Docker images and builds can be large. Free up space: + +```bash +# Remove unused Docker images +docker system prune -a + +# Check Docker disk usage +docker system df +``` + +## Environment Variables + +You can customize the build environment: + +```bash +docker run --rm \ + -e ANDROID_ABIS="arm64-v8a" \ + -e BUILD_TYPE="release" \ + -v $(pwd):/workspace \ + -v $(pwd)/build:/build \ + interrogator-android +``` + +Available variables: +- `ANDROID_HOME` - Android SDK location +- `QT_ANDROID` - Qt Android installation path +- `ANDROID_NDK_ROOT` - NDK location +- `BUILD_TYPE` - Build type (release/debug) + +## Advanced Usage + +### Using a Different Qt Version + +Edit `Dockerfile.android` and change the Qt version: + +```dockerfile +RUN aqt install-qt linux desktop 6.6.0 gcc_64 -O ${QT_ROOT} && \ + aqt install-qt linux android 6.6.0 android_arm64_v8a -O ${QT_ROOT} +``` + +Then rebuild the Docker image. + +### Caching Qt Installation + +To speed up rebuilds, use Docker volumes for Qt: + +```bash +# Create named volume for Qt +docker volume create qt-cache + +# Build with volume +docker run --rm \ + -v qt-cache:/opt/qt \ + -v $(pwd):/workspace \ + -v $(pwd)/build:/build \ + interrogator-android +``` + +### CI/CD Integration + +You can use this Docker image in CI/CD pipelines: + +```yaml +# Example for GitHub Actions +jobs: + build-android: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build Docker image + run: docker build -f Dockerfile.android -t interrogator-android . + - name: Build APK + run: docker run --rm -v $(pwd):/workspace -v $(pwd)/build:/build interrogator-android + - name: Upload APK + uses: actions/upload-artifact@v3 + with: + name: android-apk + path: build/Interrogator.apk +``` + +## File Structure + +``` +. +├── Dockerfile.android # Docker image definition +├── docker-build.sh # Build script (runs inside container) +├── BUILD_ANDROID_DOCKER.md # This documentation +├── Interrogator.pro # Qt project file +├── android/ # Android-specific files +│ ├── AndroidManifest.xml +│ ├── build.gradle +│ └── res/ +└── build/ # Output directory (host) + └── Interrogator.apk # Generated APK +``` + +## Benefits of Docker Build + +1. **Reproducible**: Same environment every time +2. **Isolated**: Doesn't affect your host system +3. **Portable**: Works on Linux, macOS, and Windows +4. **Clean**: Easy to reset and rebuild +5. **Version Controlled**: Dockerfile tracks exact dependencies + +## Comparison: Docker vs GitHub Actions + +| Aspect | Docker (Local) | GitHub Actions (CI) | +|--------|---------------|---------------------| +| Setup Time | 15-30 min (first time) | 0 min (pre-configured) | +| Build Time | 5-10 min | 5-10 min | +| Cost | Free (uses your hardware) | Free (public repos) | +| Debugging | Easy (interactive shell) | Harder (log-based) | +| Consistency | High | Very High | +| Portability | Very High | Tied to GitHub | + +## Support + +For issues with Docker builds, check: +1. Docker daemon is running: `docker ps` +2. Sufficient disk space: `df -h` +3. Docker version: `docker --version` (requires v20.10+) + +For Qt or Android-specific issues, refer to: +- [Qt Documentation](https://doc.qt.io/qt-6/) +- [Android NDK Guide](https://developer.android.com/ndk/guides) diff --git a/BUILD_ANDROID_LOCAL.md b/BUILD_ANDROID_LOCAL.md new file mode 100644 index 0000000..df67ab7 --- /dev/null +++ b/BUILD_ANDROID_LOCAL.md @@ -0,0 +1,240 @@ +# Local Android APK Build with Docker + +This guide explains how to build the Android APK for the Interrogator application locally using Docker. + +## Prerequisites + +1. **Docker**: Install Docker Desktop or Docker Engine + - Windows/Mac: [Docker Desktop](https://www.docker.com/products/docker-desktop/) + - Linux: [Docker Engine](https://docs.docker.com/engine/install/) + +2. **Docker Compose**: Usually included with Docker Desktop + - Linux: May need to install separately + +3. **Disk Space**: Ensure you have at least 15GB of free disk space + - Qt 6.5.3: ~3GB + - Android SDK/NDK: ~5GB + - Build artifacts: ~2GB + +## Quick Start + +### Method 1: Using Docker Compose (Recommended) + +1. **Build the Docker image** (first time only): + ```bash + docker-compose build + ``` + +2. **Run the build**: + ```bash + docker-compose up + ``` + +3. **Find your APK**: + The APK will be in the project root directory as `Interrogator-debug.apk` + +### Method 2: Using Docker Directly + +1. **Build the Docker image**: + ```bash + docker build -f Dockerfile.android -t interrogator-android-builder . + ``` + +2. **Run the build container**: + ```bash + docker run --rm -v $(pwd):/workspace interrogator-android-builder /workspace/scripts/build-android-local.sh + ``` + +3. **Find your APK**: + The APK will be in the project root directory as `Interrogator-debug.apk` + +## Interactive Build (for debugging) + +If you want to enter the container and build manually: + +```bash +# Start an interactive shell in the container +docker run -it --rm -v $(pwd):/workspace interrogator-android-builder /bin/bash + +# Inside the container, run the build script +/workspace/scripts/build-android-local.sh + +# Or build step by step manually: +mkdir -p android-build && cd android-build +/opt/qt/6.5.3/android_arm64_v8a/bin/qmake .. ANDROID_ABIS=arm64-v8a +make -j$(nproc) +/opt/qt/6.5.3/android_arm64_v8a/bin/androiddeployqt --input android-Interrogator-deployment-settings.json --output android-build --android-platform android-34 --gradle +``` + +## Build Configuration + +### Environment Variables + +You can customize the build by setting environment variables: + +```bash +# Using docker-compose +QT_VERSION=6.5.3 docker-compose up + +# Using docker directly +docker run --rm \ + -e QT_VERSION=6.5.3 \ + -v $(pwd):/workspace \ + interrogator-android-builder \ + /workspace/scripts/build-android-local.sh +``` + +Available variables: +- `QT_VERSION`: Qt version to use (default: 6.5.3) +- `ANDROID_SDK_ROOT`: Android SDK location (default: /opt/android-sdk) +- `QT_ROOT`: Qt installation directory (default: /opt/qt) + +### Build Options + +The Docker image includes: +- **Qt 6.5.3** (Android arm64-v8a and Desktop gcc_64) +- **Android SDK API 34** (Android 14) +- **Android Build Tools 34.0.0** +- **NDK 21.4.7075529** +- **Java JDK 17** + +## Troubleshooting + +### Build Fails with "qmake not found" + +Ensure the Qt installation completed successfully: +```bash +docker run -it interrogator-android-builder ls -la /opt/qt/6.5.3/ +``` + +### Build Fails with "androiddeployqt not found" + +Check if androiddeployqt exists: +```bash +docker run -it interrogator-android-builder ls -la /opt/qt/6.5.3/android_arm64_v8a/bin/ +``` + +### APK Not Generated + +1. Check the build logs for errors +2. Verify Gradle build succeeded +3. Check android-build directory: + ```bash + docker run -it -v $(pwd):/workspace interrogator-android-builder find /workspace/android-build -name "*.apk" + ``` + +### Out of Disk Space + +If you encounter disk space issues: + +1. **Clean Docker cache**: + ```bash + docker system prune -a --volumes + ``` + +2. **Remove unused images**: + ```bash + docker image prune -a + ``` + +### Rebuilding the Docker Image + +If you need to rebuild the image from scratch: + +```bash +# Using docker-compose +docker-compose build --no-cache + +# Using docker directly +docker build --no-cache -f Dockerfile.android -t interrogator-android-builder . +``` + +## Performance Tips + +### Use Build Caches + +The docker-compose.yml includes volume caches for Qt and Android SDK: + +```yaml +volumes: + qt-cache:/opt/qt + android-sdk-cache:/opt/android-sdk +``` + +This speeds up subsequent builds significantly. + +### Multi-core Builds + +The build script uses `make -j$(nproc)` to utilize all available CPU cores. For very slow machines, you can limit this: + +```bash +# Edit scripts/build-android-local.sh and change: +make -j2 # Use only 2 cores +``` + +## Clean Build + +To perform a completely clean build: + +```bash +# Remove build artifacts +rm -rf android-build Interrogator-debug.apk + +# Rebuild with docker-compose +docker-compose down -v # Remove volumes +docker-compose build --no-cache +docker-compose up +``` + +## Building Different Architectures + +The default build targets arm64-v8a. To build for other architectures: + +1. Modify `Dockerfile.android` to install the desired Qt architecture: + ```dockerfile + # For armv7: + RUN aqt install-qt linux android ${QT_VERSION} android_armv7 -O ${QT_ROOT} + + # For x86_64: + RUN aqt install-qt linux android ${QT_VERSION} android_x86_64 -O ${QT_ROOT} + ``` + +2. Update `scripts/build-android-local.sh` to use the correct architecture in qmake: + ```bash + ${ANDROID_QMAKE} .. ANDROID_ABIS=armeabi-v7a # for armv7 + # or + ${ANDROID_QMAKE} .. ANDROID_ABIS=x86_64 # for x86_64 + ``` + +## CI/CD Integration + +You can use this Docker setup in CI/CD pipelines: + +```yaml +# Example GitHub Actions workflow +- name: Build Android APK with Docker + run: | + docker-compose build + docker-compose up + +- name: Upload APK + uses: actions/upload-artifact@v3 + with: + name: android-apk + path: Interrogator-debug.apk +``` + +## Additional Resources + +- [Qt for Android Documentation](https://doc.qt.io/qt-6/android.html) +- [Android SDK Documentation](https://developer.android.com/studio/command-line) +- [Docker Documentation](https://docs.docker.com/) + +## Support + +If you encounter issues not covered in this guide, please: + +1. Check the build logs carefully +2. Verify all prerequisites are met +3. Try a clean rebuild +4. Open an issue in the repository with full error logs diff --git a/Dockerfile.android b/Dockerfile.android new file mode 100644 index 0000000..d3ffc3d --- /dev/null +++ b/Dockerfile.android @@ -0,0 +1,70 @@ +# Dockerfile for building Qt Android APK locally +FROM ubuntu:22.04 + +# Prevent interactive prompts during package installation +ENV DEBIAN_FRONTEND=noninteractive + +# Install required system packages +RUN apt-get update && apt-get install -y \ + wget \ + curl \ + python3 \ + python3-pip \ + openjdk-17-jdk \ + unzip \ + git \ + build-essential \ + libgl1-mesa-dev \ + libglu1-mesa-dev \ + libxkbcommon-x11-0 \ + libxcb-xinerama0 \ + libxcb-cursor0 \ + && rm -rf /var/lib/apt/lists/* + +# Set up environment variables +ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 +ENV ANDROID_SDK_ROOT=/opt/android-sdk +ENV ANDROID_NDK_ROOT=/opt/android-sdk/ndk/21.4.7075529 +ENV QT_VERSION=6.5.3 +ENV QT_ROOT=/opt/qt/${QT_VERSION} +ENV PATH=${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin:${ANDROID_SDK_ROOT}/platform-tools:${ANDROID_SDK_ROOT}/build-tools/34.0.0:${PATH} + +# Install Android SDK Command-line Tools +RUN mkdir -p ${ANDROID_SDK_ROOT}/cmdline-tools && \ + cd ${ANDROID_SDK_ROOT}/cmdline-tools && \ + wget -q https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip && \ + unzip -q commandlinetools-linux-9477386_latest.zip && \ + rm commandlinetools-linux-9477386_latest.zip && \ + mv cmdline-tools latest + +# Accept Android SDK licenses +RUN yes | sdkmanager --licenses || true + +# Install Android SDK components +RUN sdkmanager \ + "platform-tools" \ + "platforms;android-34" \ + "build-tools;34.0.0" \ + "ndk;21.4.7075529" + +# Remove corrupted SDK platforms if they exist +RUN rm -rf ${ANDROID_SDK_ROOT}/platforms/android-35* || true && \ + rm -rf ${ANDROID_SDK_ROOT}/platforms/android-36* || true + +# Install aqt (Another Qt Installer) +RUN pip3 install aqtinstall + +# Install Qt for Android and desktop +RUN mkdir -p /opt/qt && \ + aqt install-qt linux desktop ${QT_VERSION} gcc_64 -O /opt/qt && \ + aqt install-qt linux android ${QT_VERSION} android_arm64_v8a -O /opt/qt + +# Set up Qt environment +ENV ANDROID_QT_ROOT=${QT_ROOT}/android_arm64_v8a +ENV PATH=${ANDROID_QT_ROOT}/bin:${QT_ROOT}/gcc_64/bin:${PATH} + +# Create working directory +WORKDIR /workspace + +# Default command +CMD ["/bin/bash"] diff --git a/Interrogator.pro b/Interrogator.pro index 19f932c..ba06641 100644 --- a/Interrogator.pro +++ b/Interrogator.pro @@ -11,6 +11,21 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = Interrogator TEMPLATE = app +# Android specific configurations +android { + # androidextras module was removed in Qt 6.0 + # Its functionality is now part of Qt Core + greaterThan(QT_MAJOR_VERSION, 5): QT += core-private + equals(QT_MAJOR_VERSION, 5): QT += androidextras + + ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android + + OTHER_FILES += \ + android/AndroidManifest.xml \ + android/build.gradle \ + android/res/values/libs.xml +} + SOURCES += main.cpp\ mainwindow.cpp \ diff --git a/README-Android-Docker.md b/README-Android-Docker.md new file mode 100644 index 0000000..936a727 --- /dev/null +++ b/README-Android-Docker.md @@ -0,0 +1,174 @@ +# Building Android APK Locally with Docker + +This guide explains how to build the Interrogator Android APK on your local machine using Docker. + +## Prerequisites + +1. **Docker** - Install Docker Desktop or Docker Engine: + - **Windows/Mac**: [Docker Desktop](https://www.docker.com/products/docker-desktop) + - **Linux**: Docker Engine (via package manager) + +2. **Disk Space** - Ensure you have at least 15GB of free disk space for: + - Docker image (~8GB) + - Build artifacts (~2GB) + - Android SDK and Qt (~5GB) + +## Quick Start + +### 1. Make the build script executable + +```bash +chmod +x build-android-local.sh +``` + +### 2. Run the build + +```bash +./build-android-local.sh +``` + +The script will: +1. Build a Docker image with all required tools (Android SDK, NDK, Qt 6.5.3) +2. Compile the application inside the container +3. Package the APK +4. Copy the APK to your current directory as `Interrogator-release.apk` + +## Build Process Details + +### What the Docker Image Contains + +The Docker image (`Dockerfile.android`) includes: +- Ubuntu 22.04 base +- Java JDK 17 +- Android SDK Command-line Tools +- Android SDK Platform 34 +- Android Build Tools 34.0.0 +- Android NDK 21.4.7075529 +- Qt 6.5.3 for Android (arm64-v8a) +- Qt 6.5.3 for desktop (gcc_64) + +### Build Steps + +1. **Image Building**: Creates a Docker image with all dependencies (first run takes 10-15 minutes) +2. **Configuration**: Uses Android qmake to configure the project +3. **Compilation**: Builds the native library +4. **Packaging**: Uses androiddeployqt to create the APK +5. **Output**: Copies the APK to `Interrogator-release.apk` + +## Troubleshooting + +### Docker Build Fails + +**Issue**: Docker build fails with "no space left on device" +**Solution**: +```bash +docker system prune -a # Clean up unused Docker resources +``` + +### Build Script Permission Denied + +**Issue**: `./build-android-local.sh: Permission denied` +**Solution**: +```bash +chmod +x build-android-local.sh +``` + +### APK Not Generated + +**Issue**: Build completes but no APK found +**Solution**: Check the build output inside the Docker container: +```bash +docker run --rm -v "$(pwd):/workspace" -w /workspace interrogator-android-builder /bin/bash -c "ls -la android-build/" +``` + +## Advanced Usage + +### Interactive Docker Shell + +To debug build issues, launch an interactive shell: + +```bash +docker run --rm -it -v "$(pwd):/workspace" -w /workspace interrogator-android-builder /bin/bash +``` + +Then manually run build commands: + +```bash +export ANDROID_QT_ROOT=/opt/qt/6.5.3/android_arm64_v8a +export ANDROID_SDK_ROOT=/opt/android-sdk + +# Configure +${ANDROID_QT_ROOT}/bin/qmake Interrogator.pro -spec android-clang + +# Build +make -j$(nproc) + +# Package +${ANDROID_QT_ROOT}/bin/androiddeployqt --input android-build/android-deployment-settings.json --output android-build +``` + +### Rebuild Docker Image + +If you need to rebuild the Docker image (e.g., to update Qt version): + +```bash +docker build --no-cache -f Dockerfile.android -t interrogator-android-builder . +``` + +### Different Android Architectures + +To build for different architectures, modify the build script: + +- **ARMv7**: Change `android_arm64_v8a` to `android_armv7` and `arm64-v8a` to `armeabi-v7a` +- **x86_64**: Change to `android_x86_64` and `x86_64` + +## Output + +After a successful build, you'll find: +- **Interrogator-release.apk** - The unsigned release APK (ready for testing) +- **android-build/** - Build artifacts directory + +## Next Steps + +### Install on Device + +To install the APK on an Android device: + +```bash +adb install -r Interrogator-release.apk +``` + +### Sign the APK (for distribution) + +For publishing to Google Play or distribution, sign the APK: + +```bash +# Generate keystore (one-time) +keytool -genkey -v -keystore my-release-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000 + +# Sign APK +jarsigner -verbose -sigalg SHA256withRSA -digestalg SHA-256 -keystore my-release-key.keystore Interrogator-release.apk my-key-alias + +# Zipalign (optimize) +zipalign -v 4 Interrogator-release.apk Interrogator-release-signed.apk +``` + +## Performance Notes + +- **First build**: 15-20 minutes (includes downloading ~8GB of dependencies) +- **Subsequent builds**: 2-5 minutes (Docker image is cached) +- **Incremental builds**: 1-2 minutes (if only source code changed) + +## Comparison with GitHub Actions + +| Aspect | Local Docker Build | GitHub Actions | +|--------|-------------------|----------------| +| Speed (first) | 15-20 min | 20-25 min | +| Speed (subsequent) | 2-5 min | 20-25 min | +| Requires | Docker, disk space | GitHub account | +| Cost | Free (local resources) | Free (public repos) | +| Debugging | Full shell access | Limited logs | + +## Support + +For issues or questions about local Docker builds, please open an issue on the GitHub repository. diff --git a/README.md b/README.md index 5420c04..9fccf38 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,33 @@ Known bugs: ![alt tag](https://github.com/arcsibo/Interrogator/blob/master/screenshot.png) +## Build Instructions -## Build mac: +### Build for Mac: +```bash qmake CONFIG+=release && make +``` + +### Build for Android: +The project includes an automated GitHub Actions workflow for building Android APKs. The workflow (`/.github/workflows/mobile.yml`) automatically: + +1. Sets up Android SDK and NDK +2. Installs Qt for Android development +3. Configures the project for Android builds +4. Generates the APK file +5. Uploads the APK as a build artifact + +To build manually for Android, you need: +- Qt 5.15.2 or later with Android support +- Android SDK (API level 30) +- Android NDK (version 21.4.7075529) +- Java JDK 11 + +Build commands: +```bash +qmake -spec android-clang CONFIG+=release ANDROID_ABIS=arm64-v8a +make +androiddeployqt --input android-Interrogator-deployment-settings.json --output android-build --android-platform android-30 --jdk $JAVA_HOME --gradle --release +``` + +The Android build will generate an APK in the `android-build/build/outputs/apk/release/` directory. diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml new file mode 100644 index 0000000..f0129b4 --- /dev/null +++ b/android/AndroidManifest.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..84be721 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,69 @@ +buildscript { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.4.2' + } +} + +repositories { + gradlePluginPortal() + google() + mavenCentral() +} + +apply plugin: 'com.android.application' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) +} + +android { + /******************************************************* + * The following variables: + * - androidBuildToolsVersion, + * - androidCompileSdkVersion + * - qtAndroidDir - holds the path to qt android files + * needed to build any Qt application + * on Android. + * + * are defined in gradle.properties file. This file is + * updated by QtCreator and androiddeployqt tools. + * Changing them manually might break the compilation! + *******************************************************/ + + compileSdkVersion androidCompileSdkVersion + buildToolsVersion androidBuildToolsVersion + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = [qtAndroidDir + '/src', 'src', 'java'] + aidl.srcDirs = [qtAndroidDir + '/src', 'src', 'aidl'] + res.srcDirs = [qtAndroidDir + '/res', 'res'] + resources.srcDirs = ['src'] + renderscript.srcDirs = ['src'] + assets.srcDirs = ['assets'] + jniLibs.srcDirs = ['libs'] + } + } + + lint { + abortOnError false + } + + // Do not compress Qt binary resources file + androidResources { + noCompress 'rcc' + } + + defaultConfig { + resConfig "en" + minSdkVersion qtMinSdkVersion + targetSdkVersion qtTargetSdkVersion + } +} \ No newline at end of file diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..279c821 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,5 @@ +androidBuildToolsVersion=34.0.0 +androidCompileSdkVersion=34 +# Note: qtAndroidDir is set by androiddeployqt during build +qtMinSdkVersion=23 +qtTargetSdkVersion=34 \ No newline at end of file diff --git a/android/res/drawable/logo.xml b/android/res/drawable/logo.xml new file mode 100644 index 0000000..b7062fe --- /dev/null +++ b/android/res/drawable/logo.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/android/res/values/libs.xml b/android/res/values/libs.xml new file mode 100644 index 0000000..f5f4633 --- /dev/null +++ b/android/res/values/libs.xml @@ -0,0 +1,21 @@ + + + + https://download.qt.io/ministro/android/qt5/qt-5.15 + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docker-build.sh b/docker-build.sh new file mode 100644 index 0000000..666010a --- /dev/null +++ b/docker-build.sh @@ -0,0 +1,118 @@ +#!/bin/bash +set -e + +echo "==================================" +echo "Android APK Build Script (Docker)" +echo "==================================" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Verify Qt installation +echo -e "${YELLOW}Verifying Qt installation...${NC}" +if [ ! -f "${QT_ANDROID}/bin/qmake" ]; then + echo -e "${RED}ERROR: Qt Android qmake not found at ${QT_ANDROID}/bin/qmake${NC}" + exit 1 +fi +echo -e "${GREEN}✓ Qt Android found at ${QT_ANDROID}${NC}" + +# Verify androiddeployqt +if [ ! -f "${QT_ANDROID}/bin/androiddeployqt" ]; then + echo -e "${RED}ERROR: androiddeployqt not found at ${QT_ANDROID}/bin/androiddeployqt${NC}" + exit 1 +fi +echo -e "${GREEN}✓ androiddeployqt found${NC}" + +# Clean previous builds +echo -e "${YELLOW}Cleaning previous builds...${NC}" +rm -rf /workspace/build-android +mkdir -p /workspace/build-android +cd /workspace/build-android + +# Configure with qmake +echo -e "${YELLOW}Configuring project with qmake...${NC}" +${QT_ANDROID}/bin/qmake /workspace/Interrogator.pro \ + -spec android-clang \ + CONFIG+=release \ + ANDROID_ABIS="arm64-v8a" + +if [ $? -ne 0 ]; then + echo -e "${RED}ERROR: qmake configuration failed${NC}" + exit 1 +fi +echo -e "${GREEN}✓ Project configured${NC}" + +# Build the project +echo -e "${YELLOW}Building project...${NC}" +make -j$(nproc) + +if [ $? -ne 0 ]; then + echo -e "${RED}ERROR: Build failed${NC}" + exit 1 +fi +echo -e "${GREEN}✓ Build completed${NC}" + +# Generate android deployment settings +echo -e "${YELLOW}Generating Android deployment configuration...${NC}" +make install INSTALL_ROOT=/workspace/build-android/android-build + +# Create deployment JSON +cat > /workspace/build-android/android-deployment-settings.json <statusBar()->showMessage("there is " + QString(wp->getWrongWordsSize()) + " wrong word(s)."); + this->statusBar()->showMessage("there is " + QString::number(wp->getWrongWordsSize()) + " wrong word(s)."); } void MainWindow::on_actionAbout_triggered() @@ -250,7 +250,7 @@ void MainWindow::on_actionExport_wrong_answers_append_triggered() void MainWindow::on_actionAk_wrong_answers_only_append_triggered() { bool ret = false; - this->statusBar()->showMessage("there is " + QString(wp->getWrongWordsSize()) + " wrong word(s)."); + this->statusBar()->showMessage("there is " + QString::number(wp->getWrongWordsSize()) + " wrong word(s)."); ret = wp->getWrongAnswersOnly(true); if(ret) { diff --git a/wordprocessor.cpp b/wordprocessor.cpp index 4eebdda..1216cb1 100644 --- a/wordprocessor.cpp +++ b/wordprocessor.cpp @@ -6,6 +6,8 @@ #include #include /* srand, rand */ #include /* time */ +#include /* std::shuffle */ +#include /* std::default_random_engine */ wordProcessor::wordProcessor() { @@ -157,7 +159,10 @@ void wordProcessor::init() _allWords = _words.size(); _askedIndex = 0; std::srand(time(0)); - std::random_shuffle(_words.begin(),_words.end()); + // Use std::shuffle instead of std::random_shuffle (removed in C++17) + std::random_device rd; + std::default_random_engine rng(rd()); + std::shuffle(_words.begin(), _words.end(), rng); _wrongWords.clear(); } @@ -225,7 +230,12 @@ bool wordProcessor::openFile(QString fileName) } QTextStream in(&file); + // Qt 6: Use setEncoding instead of setCodec +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + in.setEncoding(QStringConverter::Utf8); +#else in.setCodec("UTF-8"); +#endif while(!in.atEnd()) { QString line = in.readLine(); QStringList fields = line.split(_separate[EQUAL]);