1
0
aseprite-flatpak-builder/prepare_sources.sh
aki d12f0bdf88 refactor: Overhaul build system for reliability and future portability
This commit introduces a major refactoring of the Aseprite build process. It replaces the previous fragile system (prone to dependency fetching failures) with an interim multi-distribution Docker approach, paving the way for a future transition to Flatpak.

**Problems Addressed:**

*   **Build Fragility & Dependency Fetching:** The prior method, compiling dependencies from source within a generic container, frequently failed due to network issues and rate limiting during source/sub-dependency acquisition (e.g., Skia's `git-sync-deps`), often late in the process. Source state inconsistencies could also cause failures.
*   **Complexity of Full Source Builds:** Managing the compilation of the entire dependency tree from source was complex.

**New Architecture & Rationale:**

*   **Host-Side Source Preparation (`prepare_sources.sh`):** Isolates the problematic source fetching and state management to a host-side script run *before* the main build. Key features:
    *   Handles cloning/updating core sources (`depot_tools`, Skia, Aseprite).
    *   Runs Skia `git-sync-deps` with **robust retry logic** to specifically address rate limit errors.
    *   Includes an `--check-integrity` flag which performs **aggressive checks and resets** (fetching, checking out specific tags/commits, resetting state) to ensure the local source directories precisely match the required state for the build, potentially involving significant network activity.
*   **Distribution-Specific Builds (Interim Step):** Introduced `Dockerfile.arch`, `Dockerfile.debian`, `Dockerfile.fedora`. These use native package managers to install pre-built *common* development libraries within the container, simplifying the Docker build stage itself. Requires OS detection or manual selection (`TARGET_DISTRO`).
*   **Clear Build Stages (Makefile):** Orchestrates source preparation, image building, and final binary extraction (`docker cp` to `./output/bin`).
*   **Cleaned Structure:** Removed obsolete scripts/files (`compile.sh`, generic `Dockerfile`, `docker-compose.yml`) and updated `.gitignore`.

**Limitations & Future Direction (Flatpak):**

*   **Fetching Challenges Persist:** While reliability is improved by isolating source prep and adding retries/integrity checks in `prepare_sources.sh`, the core challenge of potential rate limits or network issues during this initial step remains.
*   **Flatpak for Portability:** The current multi-distro Docker setup is an **intermediate solution**. The ultimate goal and **forward-maintained approach** is migrating to **Flatpak (`flatpak-builder`)**. Flatpak will provide a **unified, distribution-agnostic build environment** using standard runtimes and produce a **portable `.flatpak` bundle**, eliminating the need for OS detection/separate Dockerfiles and ensuring consistent builds *after* sources are successfully prepared.
2025-05-05 01:16:07 +08:00

315 lines
14 KiB
Bash
Executable File

#!/bin/bash
# Exit immediately if a command exits with a non-zero status.
set -e
# --- Configuration ---
# Define repositories and their properties
# Format: "name|url|tag_or_branch|directory|has_submodules|is_tag"
REPOS=(
"depot_tools|https://chromium.googlesource.com/chromium/tools/depot_tools.git|origin/main|depot_tools|false|false"
"skia|https://github.com/aseprite/skia.git|m124-08a5439a6b|skia|false|true" # Skia uses git-sync-deps, not git submodules
"aseprite|https://github.com/aseprite/aseprite.git|v1.3.14-beta1|aseprite|true|true"
)
SRC_DIR="./src"
MAX_SYNC_RETRIES=${PREPARE_RETRIES:-2} # Default to 2 retries (3 total attempts) if PREPARE_RETRIES is not set
RETRY_DELAY=10 # Seconds to wait between retries
# --- Helper Functions (New Formatting) ---
print_usage() {
echo "Usage: $0 [--check-integrity] [-h|--help]"
echo ""
echo "Prepares source code repositories (depot_tools, Skia, Aseprite)."
echo ""
echo "Default behavior:"
echo " - Clones repositories if they don't exist."
echo " - If repositories exist, performs minimal updates (e.g., ensures Aseprite submodules are present)."
echo " - Errors out if cloning or minimal updates fail."
echo ""
echo "Options:"
echo " --check-integrity If repositories exist, performs a full integrity check and reset:"
echo " - Verifies repository integrity (git fsck)."
echo " - Fetches latest updates and tags."
echo " - Resets the repository to the specified tag/branch (discarding local changes)."
echo " - Updates and resets submodules to the state expected by the parent repository."
echo " - Errors out if directories are missing or if critical checks/resets fail."
echo " -h, --help Display this help message and exit."
}
_CURRENT_STEP=0
_TOTAL_STEPS=${#REPOS[@]} # Number of repositories +1 for Skia sync
_TOTAL_STEPS=$((_TOTAL_STEPS + 1))
print_step() {
_CURRENT_STEP=$((_CURRENT_STEP + 1))
# Adjust total steps display if needed, though this simple count is often sufficient
echo -e "\n[\033[1;34mSTEP ${_CURRENT_STEP}/${_TOTAL_STEPS}\033[0m] $1..."
}
print_info() {
echo -e "[\033[0;32mINFO\033[0m] $1"
}
print_warning() {
echo -e "[\033[1;33mWARN\033[0m] $1"
}
print_success() {
echo -e "[\033[1;32mSUCCESS\033[0m] $1"
}
print_error() {
echo -e "[\033[1;31mERROR\033[0m] $1" >&2
}
print_process_start() {
echo -e "[\033[1mPREPARE\033[0m] $1"
}
print_process_end() {
echo -e "[\033[1mPREPARE\033[0m] $1"
}
# --- Argument Parsing ---
CHECK_INTEGRITY=false
while [[ "$#" -gt 0 ]]; do
case $1 in
--check-integrity) CHECK_INTEGRITY=true; shift ;;
-h|--help) print_usage; exit 0 ;;
*) print_error "Unknown parameter passed: $1"; print_usage; exit 1 ;;
esac
done
# --- Main Logic ---
print_process_start "Starting Source Preparation"
mkdir -p "$SRC_DIR"
cd "$SRC_DIR"
SRC_DIR_ABS=$(pwd) # Use absolute path for clarity inside script
cd .. # Go back to project root
print_info "Source directory: ${SRC_DIR_ABS}"
if [[ "$CHECK_INTEGRITY" == "true" ]]; then
print_info "Mode: Full Integrity Check and Reset (on existing directories)"
else
print_info "Mode: Default (Clone if missing, minimal update if exists)"
fi
# --- Process Repositories ---
DEPOT_TOOLS_DIR="" # Will be set when processing depot_tools
for repo_info in "${REPOS[@]}"; do
IFS='|' read -r name url target_ref dir has_submodules is_tag <<< "$repo_info"
REPO_DIR="${SRC_DIR_ABS}/${dir}"
# Set DEPOT_TOOLS_DIR for later use in PATH
if [[ "$name" == "depot_tools" ]]; then
DEPOT_TOOLS_DIR="$REPO_DIR"
fi
print_step "Processing ${name} (Target: ${target_ref})"
if [ -d "$REPO_DIR" ]; then
# Directory Exists
if [[ "$CHECK_INTEGRITY" == "true" ]]; then
# --- Check Integrity Logic ---
print_info "Verifying integrity of existing ${name} repository..."
if ! (cd "$REPO_DIR" && git fsck); then
print_warning "git fsck reported issues for ${name}, but continuing. Manual check recommended."
fi
print_info "Fetching updates for ${name}..."
fetch_args=("origin")
if [[ "$is_tag" == "true" ]]; then
fetch_args+=("--tags")
fi
if ! (cd "$REPO_DIR" && git fetch "${fetch_args[@]}"); then
print_error "Failed to fetch updates for ${name}. Check network or repository access."
exit 1
fi
needs_reset=false
if [[ "$is_tag" == "true" ]]; then
# Check against specific tag commit
CURRENT_COMMIT=$(cd "$REPO_DIR" && git rev-parse HEAD)
# Handle potential errors if tag doesn't exist locally yet after fetch
TARGET_COMMIT=$(cd "$REPO_DIR" && git rev-list -n 1 "${target_ref}" 2>/dev/null || echo "NOT_FOUND")
if [[ "$TARGET_COMMIT" == "NOT_FOUND" ]]; then
print_error "Target tag '${target_ref}' not found for ${name} after fetch."
exit 1
fi
if [[ "$CURRENT_COMMIT" != "$TARGET_COMMIT" ]]; then
needs_reset=true
print_info "${name} is not on the target commit for tag ${target_ref}."
fi
else
# Check if behind the target branch (e.g., origin/main for depot_tools)
# A simple way is to check if reset --hard changes HEAD
current_head=$(cd "$REPO_DIR" && git rev-parse HEAD)
target_head=$(cd "$REPO_DIR" && git rev-parse "${target_ref}")
if [[ "$current_head" != "$target_head" ]]; then
needs_reset=true
print_info "${name} is not on the target commit for branch ${target_ref}."
fi
fi
# Check for local modifications
MODIFIED=$(cd "$REPO_DIR" && git status --porcelain)
if [[ -n "$MODIFIED" ]]; then
needs_reset=true
print_info "${name} has local modifications."
fi
# Perform reset if needed
if [[ "$needs_reset" == "true" ]]; then
print_info "Resetting ${name} repository to target ${target_ref}..."
# Checkout first, especially important for tags, suppresses detached HEAD advice
if ! (cd "$REPO_DIR" && git checkout "${target_ref}"); then
print_error "Failed to checkout ${target_ref} for ${name}."
exit 1
fi
# Reset hard to ensure clean state matching the target ref
if ! (cd "$REPO_DIR" && git reset --hard "${target_ref}"); then
print_error "Failed to reset ${name} to ${target_ref}."
exit 1
fi
print_info "${name} repository reset successfully."
else
print_info "${name} repository is already on target ${target_ref} and clean."
fi
# Handle Submodules (if applicable)
if [[ "$has_submodules" == "true" ]]; then
print_info "Ensuring ${name} submodules are initialized and updated..."
if ! (cd "$REPO_DIR" && git submodule update --init --recursive); then
print_error "Failed to update submodules for ${name}. Check network or repository access."
exit 1
fi
print_info "Submodule update command completed."
print_info "Checking ${name} submodule status and internal state..."
# Check 1: Overall status (uninitialized, wrong commit). Append '|| true' to prevent grep exit code 1 from stopping the script with set -e.
SUBMODULE_STATUS_ISSUES=$(cd "$REPO_DIR" && git submodule status | grep -v '^ ' || true)
# Check 2: Internal state (modified content, untracked files within submodules). Append '|| true' for robustness.
# Use --quiet to suppress "Entering 'path'" messages. Redirect stderr in case of errors within foreach.
SUBMODULE_INTERNAL_CHANGES=$(cd "$REPO_DIR" && git submodule foreach --quiet 'git status --porcelain' 2>&1 || true)
if [[ -n "$SUBMODULE_STATUS_ISSUES" || -n "$SUBMODULE_INTERNAL_CHANGES" ]]; then
if [[ -n "$SUBMODULE_STATUS_ISSUES" ]]; then
print_info "Detected submodules not initialized or on wrong commit:"
echo "$SUBMODULE_STATUS_ISSUES" # Show which ones have status issues
fi
if [[ -n "$SUBMODULE_INTERNAL_CHANGES" ]]; then
# We don't print the full output of internal changes as it can be verbose,
# just knowing *that* there are changes is enough to trigger the reset.
print_info "Detected submodules with internal changes (modified/untracked files)."
fi
print_info "Resetting submodules for ${name} to clean state..."
# --- Reset Logic ---
if ! (cd "$REPO_DIR" && git submodule foreach --quiet git reset --hard HEAD); then
print_error "Failed to reset submodules for ${name}. Manual check required in ${REPO_DIR}."
exit 1
fi
print_info "Running integrity check on reset submodules..."
# fsck after reset is less critical, treat as warning
if ! (cd "$REPO_DIR" && git submodule foreach --quiet git fsck); then
print_warning "git fsck reported issues for some submodules in ${name} after reset, but continuing."
fi
print_info "Submodules reset successfully."
# --- End Reset Logic ---
else
print_info "${name} submodules already in correct state."
fi
fi
print_success "Integrity check and state validation complete for ${name}."
else
# --- Default Minimal Update Logic ---
print_info "${name} directory exists. Performing minimal update..."
# Only Aseprite needs minimal submodule update in default mode currently
if [[ "$name" == "aseprite" && "$has_submodules" == "true" ]]; then
print_info "Ensuring ${name} submodules are initialized and updated..."
if ! (cd "$REPO_DIR" && git submodule update --init --recursive); then
print_error "Failed to perform minimal submodule update for ${name}. Use --check-integrity or check manually."
exit 1
fi
print_success "Minimal submodule update for ${name} complete."
else
print_info "No minimal update action needed for ${name}."
fi
fi
else
# Directory Does Not Exist
if [[ "$CHECK_INTEGRITY" == "true" ]]; then
print_error "Directory ${REPO_DIR} for ${name} is missing. Cannot perform --check-integrity."
exit 1
else
# --- Clone Logic ---
print_info "Cloning ${name} from ${url} (Target: ${target_ref})..."
clone_args=("--depth" "1")
if [[ "$is_tag" == "true" || "$target_ref" != "origin/main" ]]; then
# For tags or specific branches, use --branch
clone_args+=("--branch" "${target_ref}")
fi
if [[ "$has_submodules" == "true" ]]; then
clone_args+=("--recursive")
fi
if ! git clone "${clone_args[@]}" "${url}" "${REPO_DIR}"; then
print_error "Failed to clone ${name} from ${url}."
exit 1
fi
print_success "${name} cloned successfully."
fi
fi
done
# Sync Skia dependencies locally with retry logic (after Skia repo is processed)
# Find Skia dir again (could be improved by storing dirs in an associative array if using Bash 4+)
SKIA_DIR=""
for repo_info in "${REPOS[@]}"; do
IFS='|' read -r name url target_ref dir has_submodules is_tag <<< "$repo_info"
if [[ "$name" == "skia" ]]; then
SKIA_DIR="${SRC_DIR_ABS}/${dir}"
break
fi
done
if [[ -z "$SKIA_DIR" || ! -d "$SKIA_DIR" ]]; then
print_error "Skia directory not found or not processed correctly. Cannot sync dependencies."
exit 1
fi
if [[ -z "$DEPOT_TOOLS_DIR" || ! -d "$DEPOT_TOOLS_DIR" ]]; then
print_error "Depot tools directory not found or not processed correctly. Cannot sync Skia dependencies."
exit 1
fi
print_step "Syncing Skia Dependencies"
print_info "Attempting to run git-sync-deps in ${SKIA_DIR} (up to $((MAX_SYNC_RETRIES + 1)) attempts)..."
# Ensure depot_tools is in PATH for git-sync-deps internal calls if needed
export PATH="${DEPOT_TOOLS_DIR}:${PATH}"
sync_success=false
for (( attempt=0; attempt<=MAX_SYNC_RETRIES; attempt++ )); do
if (cd "$SKIA_DIR" && python3 tools/git-sync-deps); then
sync_success=true
print_success "Skia dependencies synced successfully on attempt $((attempt + 1))."
break
else
exit_code=$?
if [[ $attempt -lt $MAX_SYNC_RETRIES ]]; then
print_warning "git-sync-deps failed on attempt $((attempt + 1)) with exit code ${exit_code}. Retrying in ${RETRY_DELAY} seconds..."
sleep $RETRY_DELAY
else
print_error "git-sync-deps failed after $((attempt + 1)) attempts with exit code ${exit_code}. Please check network connection, rate limits, or Skia repository state."
# Consider adding advice to run with --check-integrity if the repo exists but might be broken.
exit 1
fi
fi
done
print_process_end "Source Preparation Complete"