forked from aki/docker-aseprite-linux
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.
315 lines
14 KiB
Bash
Executable File
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"
|