From d12f0bdf889e6c3cf26d29ddd6cde51d7d37ed62 Mon Sep 17 00:00:00 2001 From: aki Date: Mon, 5 May 2025 01:16:07 +0800 Subject: [PATCH] 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. --- .gitignore | 4 +- Dockerfile | 21 --- Dockerfile.arch | 91 +++++++++ Dockerfile.debian | 98 ++++++++++ Dockerfile.fedora | 93 ++++++++++ LICENSE | 21 +++ Makefile | 100 +++++++++- compile.sh | 53 ------ diff.txt | 448 +++++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 8 - prepare_sources.sh | 314 +++++++++++++++++++++++++++++++ 11 files changed, 1157 insertions(+), 94 deletions(-) delete mode 100644 Dockerfile create mode 100644 Dockerfile.arch create mode 100644 Dockerfile.debian create mode 100644 Dockerfile.fedora create mode 100644 LICENSE delete mode 100755 compile.sh create mode 100644 diff.txt delete mode 100644 docker-compose.yml create mode 100755 prepare_sources.sh diff --git a/.gitignore b/.gitignore index 0b34b0f..8e4a455 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ .idea -dependencies -output \ No newline at end of file +src/ +target/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 261de20..0000000 --- a/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -FROM python - -#Required for tzdata -ENV TZ=Europe/Amsterdam -RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone - -# Install dependencies -RUN apt-get update -RUN apt-get upgrade -y -RUN apt-get install -y git unzip curl build-essential cmake ninja-build libx11-dev libxcursor-dev libxi-dev libgl1-mesa-dev libfontconfig1-dev - -COPY compile.sh / - -VOLUME /dependencies -VOLUME /output - -WORKDIR /output - -RUN ["chmod", "+x", "/compile.sh"] - -ENTRYPOINT ["/compile.sh"] diff --git a/Dockerfile.arch b/Dockerfile.arch new file mode 100644 index 0000000..fcc5c80 --- /dev/null +++ b/Dockerfile.arch @@ -0,0 +1,91 @@ +# Use Arch Linux as the base image +FROM archlinux:latest + +# Update system and install necessary dependencies using pacman +# Includes base-devel (common build tools), git, python, cmake, ninja, curl, unzip, +# and development libraries for X11, GL, fontconfig, and Skia dependencies. +RUN pacman -Syu --noconfirm \ + base-devel \ + git \ + python \ + cmake \ + ninja \ + curl \ + unzip \ + libx11 \ + libxcursor \ + libxi \ + mesa \ + fontconfig \ + expat \ + icu \ + libjpeg-turbo \ + libpng \ + libwebp \ + zlib \ + freetype2 \ + harfbuzz \ +& yes | pacman -Scc + +# Set the working directory +WORKDIR /app + +# Copy the pre-downloaded source code into the image +# This assumes prepare_sources.sh has been run locally first +COPY ./src /src + +# Set PATH for depot_tools +ENV PATH="/src/depot_tools:${PATH}" + +# Initialize depot_tools within the container environment +# This ensures gn and other tools are correctly set up +RUN update_depot_tools + +# --- Compile Skia --- +WORKDIR /src/skia + +# Skia dependencies (including gn) are now synced locally by prepare_sources.sh +# Skia system dependencies are handled by system packages installed via pacman + +# Generate Skia build files using Arch's system libraries +# Note: Changed skia_use_system_* flags to true and enabled AVX +RUN /src/depot_tools/gn gen out/Release-x64 --args='is_debug=false is_official_build=true skia_use_system_expat=true skia_use_system_icu=true skia_use_system_libjpeg_turbo=true skia_use_system_libpng=true skia_use_system_libwebp=true skia_use_system_zlib=true skia_use_sfntly=false skia_use_freetype=true skia_use_harfbuzz=true skia_pdf_subset_harfbuzz=true skia_use_system_freetype2=true skia_use_system_harfbuzz=true extra_cflags=["-mavx"] extra_cxxflags=["-mavx"]' + +# Compile Skia +RUN ninja -C out/Release-x64 skia modules + +# --- Compile Aseprite --- +WORKDIR /src/aseprite +RUN mkdir -p build +WORKDIR /src/aseprite/build + +# Generate Aseprite build files +# Pointing to the Skia build output within the image and enabling AVX +RUN cmake \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DCMAKE_CXX_FLAGS="-mavx" \ + -DLAF_BACKEND=skia \ + -DSKIA_DIR=/src/skia \ + -DSKIA_LIBRARY_DIR=/src/skia/out/Release-x64 \ + -DSKIA_LIBRARY=/src/skia/out/Release-x64/libskia.a \ + -DLIBJPEG_TURBO_LIBRARY=/usr/lib/libjpeg.so \ + -DCMAKE_EXE_LINKER_FLAGS="-lfreetype" \ + -G Ninja \ + .. + +# Compile Aseprite +RUN ninja aseprite + +# --- Prepare Output --- +# Create a target directory and copy the final binaries/assets +RUN mkdir -p /target/aseprite/build/bin +RUN cp /src/aseprite/build/bin/aseprite /target/aseprite/build/bin/ +# Ensure data directory exists before copying into it +RUN mkdir -p /target/aseprite/build/bin/data +RUN cp /src/aseprite/build/bin/data/* /target/aseprite/build/bin/data/ + +# Expose a volume for the output (optional, as Makefile will copy) +VOLUME /target + +# Set final working directory (optional) +WORKDIR /target diff --git a/Dockerfile.debian b/Dockerfile.debian new file mode 100644 index 0000000..e0e7efa --- /dev/null +++ b/Dockerfile.debian @@ -0,0 +1,98 @@ +# Use Debian 12 (Bookworm) slim as the base image +FROM debian:12-slim + +# Set frontend to noninteractive to avoid prompts during package installation +ENV DEBIAN_FRONTEND=noninteractive +ENV TZ=Etc/UTC + +# Install necessary dependencies using apt-get +# Includes build tools, git, python, cmake, ninja, curl, unzip, ca-certs, +# and development libraries for X11, GL, fontconfig, and Skia dependencies. +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + build-essential \ + git \ + python3 \ + cmake \ + ninja-build \ + curl \ + unzip \ + ca-certificates \ + libx11-dev \ + libxcursor-dev \ + libxi-dev \ + libgl1-mesa-dev \ + libfontconfig1-dev \ + libexpat1-dev \ + libicu-dev \ + libjpeg-turbo8-dev \ + libpng-dev \ + libwebp-dev \ + zlib1g-dev \ + libfreetype6-dev \ + libharfbuzz-dev \ + # Clean up apt lists to reduce image size + && rm -rf /var/lib/apt/lists/* + +# Set the working directory +WORKDIR /app + +# Copy the pre-downloaded source code into the image +# This assumes prepare_sources.sh has been run locally first +COPY ./src /src + +# Set PATH for depot_tools +ENV PATH="/src/depot_tools:${PATH}" + +# Initialize depot_tools within the container environment +# This ensures gn and other tools are correctly set up +RUN update_depot_tools + +# --- Compile Skia --- +WORKDIR /src/skia + +# Skia dependencies (including gn) are now synced locally by prepare_sources.sh +# Skia system dependencies are handled by system packages installed via apt-get + +# Generate Skia build files using Debian's system libraries +# Note: Changed skia_use_system_* flags to true and enabled AVX +RUN /src/depot_tools/gn gen out/Release-x64 --args='is_debug=false is_official_build=true skia_use_system_expat=true skia_use_system_icu=true skia_use_system_libjpeg_turbo=true skia_use_system_libpng=true skia_use_system_libwebp=true skia_use_system_zlib=true skia_use_sfntly=false skia_use_freetype=true skia_use_harfbuzz=true skia_pdf_subset_harfbuzz=true skia_use_system_freetype2=true skia_use_system_harfbuzz=true extra_cflags=["-mavx"] extra_cxxflags=["-mavx"]' + +# Compile Skia +RUN ninja -C out/Release-x64 skia modules + +# --- Compile Aseprite --- +WORKDIR /src/aseprite +RUN mkdir -p build +WORKDIR /src/aseprite/build + +# Generate Aseprite build files +# Pointing to the Skia build output within the image and enabling AVX +RUN cmake \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DCMAKE_CXX_FLAGS="-mavx" \ + -DLAF_BACKEND=skia \ + -DSKIA_DIR=/src/skia \ + -DSKIA_LIBRARY_DIR=/src/skia/out/Release-x64 \ + -DSKIA_LIBRARY=/src/skia/out/Release-x64/libskia.a \ + -DLIBJPEG_TURBO_LIBRARY=/usr/lib/x86_64-linux-gnu/libjpeg.so \ + -DCMAKE_EXE_LINKER_FLAGS="-lfreetype" \ + -G Ninja \ + .. + +# Compile Aseprite +RUN ninja aseprite + +# --- Prepare Output --- +# Create a target directory and copy the final binaries/assets +RUN mkdir -p /target/aseprite/build/bin +RUN cp /src/aseprite/build/bin/aseprite /target/aseprite/build/bin/ +# Ensure data directory exists before copying into it +RUN mkdir -p /target/aseprite/build/bin/data +RUN cp /src/aseprite/build/bin/data/* /target/aseprite/build/bin/data/ + +# Expose a volume for the output (optional, as Makefile will copy) +VOLUME /target + +# Set final working directory (optional) +WORKDIR /target diff --git a/Dockerfile.fedora b/Dockerfile.fedora new file mode 100644 index 0000000..bb43b9a --- /dev/null +++ b/Dockerfile.fedora @@ -0,0 +1,93 @@ +# Use Fedora 41 as the base image +FROM fedora:41 + +# Install necessary dependencies using dnf +# Includes build tools (make, gcc-c++), git, python, cmake, ninja, curl, unzip, +# and development libraries for X11, GL, fontconfig, and Skia dependencies. +RUN dnf install -y \ + make \ + gcc-c++ \ + cmake \ + ninja-build \ + git \ + curl \ + unzip \ + python3 \ + libX11-devel \ + libXcursor-devel \ + libXi-devel \ + mesa-libGL-devel \ + fontconfig-devel \ + expat-devel \ + libicu-devel \ + libjpeg-turbo-devel \ + libpng-devel \ + libwebp-devel \ + zlib-devel \ + freetype-devel \ + harfbuzz-devel \ + # Clean up dnf cache + && dnf clean all + +# Set the working directory +WORKDIR /app + +# Copy the pre-downloaded source code into the image +# This assumes prepare_sources.sh has been run locally first +COPY ./src /src + +# Set PATH for depot_tools +ENV PATH="/src/depot_tools:${PATH}" + +# Initialize depot_tools within the container environment +# This ensures gn and other tools are correctly set up +RUN update_depot_tools + +# --- Compile Skia --- +WORKDIR /src/skia + +# Skia dependencies (including gn) are now synced locally by prepare_sources.sh +# Skia system dependencies are handled by system packages installed via dnf + +# Generate Skia build files using Fedora's system libraries +# Note: Changed skia_use_system_* flags to true and enabled AVX +RUN /src/depot_tools/gn gen out/Release-x64 --args='is_debug=false is_official_build=true skia_use_system_expat=true skia_use_system_icu=true skia_use_system_libjpeg_turbo=true skia_use_system_libpng=true skia_use_system_libwebp=true skia_use_system_zlib=true skia_use_sfntly=false skia_use_freetype=true skia_use_harfbuzz=true skia_pdf_subset_harfbuzz=true skia_use_system_freetype2=true skia_use_system_harfbuzz=true extra_cflags=["-mavx"] extra_cxxflags=["-mavx"]' + +# Compile Skia +RUN ninja -C out/Release-x64 skia modules + +# --- Compile Aseprite --- +WORKDIR /src/aseprite +RUN mkdir -p build +WORKDIR /src/aseprite/build + +# Generate Aseprite build files +# Pointing to the Skia build output within the image and enabling AVX +RUN cmake \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DCMAKE_CXX_FLAGS="-mavx" \ + -DLAF_BACKEND=skia \ + -DSKIA_DIR=/src/skia \ + -DSKIA_LIBRARY_DIR=/src/skia/out/Release-x64 \ + -DSKIA_LIBRARY=/src/skia/out/Release-x64/libskia.a \ + -DLIBJPEG_TURBO_LIBRARY=/usr/lib64/libjpeg.so \ + -DCMAKE_EXE_LINKER_FLAGS="-lfreetype" \ + -G Ninja \ + .. + +# Compile Aseprite +RUN ninja aseprite + +# --- Prepare Output --- +# Create a target directory and copy the final binaries/assets +RUN mkdir -p /target/aseprite/build/bin +RUN cp /src/aseprite/build/bin/aseprite /target/aseprite/build/bin/ +# Ensure data directory exists before copying into it +RUN mkdir -p /target/aseprite/build/bin/data +RUN cp /src/aseprite/build/bin/data/* /target/aseprite/build/bin/data/ + +# Expose a volume for the output (optional, as Makefile will copy) +VOLUME /target + +# Set final working directory (optional) +WORKDIR /target diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1887c49 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 github.com/akippnn + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile index dab6b4c..0984390 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,94 @@ -IMAGE_NAME := docker-aseprite +# --- OS Detection --- +# Attempt to detect host OS family. Fallback to 'arch'. +# This logic checks /etc/os-release for ID or ID_LIKE fields. +# Use $(strip ...) to remove potential leading/trailing whitespace from shell output. +DETECTED_DISTRO := $(strip $(shell \ + if [ -f /etc/os-release ]; then \ + . /etc/os-release; \ + if echo "$$ID$$ID_LIKE" | grep -q -e arch; then \ + echo arch; \ + elif echo "$$ID$$ID_LIKE" | grep -q -e debian -e ubuntu; then \ + echo debian; \ + elif echo "$$ID$$ID_LIKE" | grep -q -e fedora; then \ + echo fedora; \ + else \ + echo arch; \ + fi; \ + else \ + echo arch; \ + fi \ +)) -build: build-image - docker run --rm \ - -v ${PWD}/output:/output \ - -v ${PWD}/dependencies:/dependencies \ - ${IMAGE_NAME} +# Set default TARGET_DISTRO based on detection, allowing user override +TARGET_DISTRO ?= $(DETECTED_DISTRO) -build-compose: - docker-compose build - docker-compose up +# --- Configuration --- +IMAGE_NAME := docker-aseprite-${TARGET_DISTRO} +DOCKERFILE := Dockerfile.${TARGET_DISTRO} +OUTPUT_DIR := ${PWD}/output +TARGET_DIR_IN_IMAGE := /target/aseprite/build/bin +# --- Main Targets --- +# Default target +all: build + +# Prepare source files by running the script +# Use .PHONY to ensure it always runs if called directly +.PHONY: prepare-sources +prepare-sources: + @echo "--- Preparing sources ---" + @./prepare_sources.sh + +# Build the Docker image for the target distribution +# Depends on sources being ready (though prepare-sources isn't a file dependency) build-image: - docker build -t ${IMAGE_NAME} . \ No newline at end of file +# --- DEBUGGING --- + @echo "DEBUG: TARGET_DISTRO='${TARGET_DISTRO}'" + @echo "DEBUG: DOCKERFILE='${DOCKERFILE}'" +# --- END DEBUGGING --- +ifeq (,$(wildcard ${DOCKERFILE})) + $(error Dockerfile for target distribution '${TARGET_DISTRO}' (${DOCKERFILE}) not found) +endif + @echo "--- Building Docker image (${IMAGE_NAME}) using ${DOCKERFILE} ---" + @docker build -t ${IMAGE_NAME} -f ${DOCKERFILE} . + +# Extract the compiled binary from the image +# Depends on the image being built +.PHONY: extract-binary +extract-binary: build-image + @echo "--- Extracting Aseprite binary from ${IMAGE_NAME} ---" + @mkdir -p ${OUTPUT_DIR}/bin + @CONTAINER_ID=$$(docker create ${IMAGE_NAME}) && \ + docker cp "$${CONTAINER_ID}:${TARGET_DIR_IN_IMAGE}/." "${OUTPUT_DIR}/bin/" && \ + docker rm "$${CONTAINER_ID}" > /dev/null + @echo "Aseprite binary extracted to ${OUTPUT_DIR}/bin" + +# Main build target: Prepare sources, build image, extract binary for selected distro +.PHONY: build +build: prepare-sources build-image extract-binary + @echo "--- Build complete for ${TARGET_DISTRO} ---" + @echo "Aseprite binary is in ${OUTPUT_DIR}/bin" + +# --- Specific Distribution Targets --- +.PHONY: build-arch build-debian build-fedora + +# Build for Arch Linux (default if not detected otherwise) +build-arch: + $(MAKE) build TARGET_DISTRO=arch + +# Build for Debian/Ubuntu based systems +build-debian: + $(MAKE) build TARGET_DISTRO=debian + +# Build for Fedora based systems +build-fedora: + $(MAKE) build TARGET_DISTRO=fedora + +# --- Utility Targets --- +# Clean up downloaded sources and output directory +.PHONY: clean +clean: + @echo "--- Cleaning up ---" + @rm -rf ./src + @rm -rf ${OUTPUT_DIR} + @echo "Cleanup complete." diff --git a/compile.sh b/compile.sh deleted file mode 100755 index d53eafb..0000000 --- a/compile.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/bash - -# Fail on errors -set -e - -echo "Download and compile Skia & other dependencies" -cd /dependencies - -if [ ! -d "/dependencies/depot_tools" ] -then - git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git -fi - -if [ ! -d "/dependencies/skia" ] -then - git clone -b aseprite-m102 https://github.com/aseprite/skia.git -fi - -export PATH="${PWD}/depot_tools:${PATH}" - -cd skia -pwd -echo "Syncing skia dependencies" -python3 tools/git-sync-deps - -echo "Compiling skia" -gn gen out/Release-x64 --args="is_debug=false is_official_build=true skia_use_system_expat=false skia_use_system_icu=false skia_use_system_libjpeg_turbo=false skia_use_system_libpng=false skia_use_system_libwebp=false skia_use_system_zlib=false skia_use_sfntly=false skia_use_freetype=true skia_use_harfbuzz=true skia_pdf_subset_harfbuzz=true skia_use_system_freetype2=false skia_use_system_harfbuzz=false" -ninja -C out/Release-x64 skia modules - -echo "Download Aseprite and compile" -cd /output - -if [ ! -d "/output/aseprite" ] -then - git clone -b v1.2.40 --recursive https://github.com/aseprite/aseprite.git -fi - -cd aseprite -mkdir -p build -cd build - -echo "Compiling Asperite" -cmake \ - -DCMAKE_BUILD_TYPE=RelWithDebInfo \ - -DLAF_BACKEND=skia \ - -DSKIA_DIR=/dependencies/skia \ - -DSKIA_LIBRARY_DIR=/dependencies/skia/out/Release-x64 \ - -DSKIA_LIBRARY=/dependencies/skia/out/Release-x64/libskia.a \ - -G Ninja \ - .. - -echo "Linking Aseprite" -ninja aseprite diff --git a/diff.txt b/diff.txt new file mode 100644 index 0000000..8b3f01f --- /dev/null +++ b/diff.txt @@ -0,0 +1,448 @@ +diff --git a/.gitignore b/.gitignore +index 0b34b0f..8e4a455 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -1,3 +1,3 @@ + .idea +-dependencies +-output +\ No newline at end of file ++src/ ++target/ +\ No newline at end of file +diff --git a/Dockerfile b/Dockerfile +deleted file mode 100644 +index 261de20..0000000 +--- a/Dockerfile ++++ /dev/null +@@ -1,21 +0,0 @@ +-FROM python +- +-#Required for tzdata +-ENV TZ=Europe/Amsterdam +-RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone +- +-# Install dependencies +-RUN apt-get update +-RUN apt-get upgrade -y +-RUN apt-get install -y git unzip curl build-essential cmake ninja-build libx11-dev libxcursor-dev libxi-dev libgl1-mesa-dev libfontconfig1-dev +- +-COPY compile.sh / +- +-VOLUME /dependencies +-VOLUME /output +- +-WORKDIR /output +- +-RUN ["chmod", "+x", "/compile.sh"] +- +-ENTRYPOINT ["/compile.sh"] +diff --git a/Makefile b/Makefile +index dab6b4c..0984390 100644 +--- a/Makefile ++++ b/Makefile +@@ -1,14 +1,94 @@ +-IMAGE_NAME := docker-aseprite ++# --- OS Detection --- ++# Attempt to detect host OS family. Fallback to 'arch'. ++# This logic checks /etc/os-release for ID or ID_LIKE fields. ++# Use $(strip ...) to remove potential leading/trailing whitespace from shell output. ++DETECTED_DISTRO := $(strip $(shell \ ++ if [ -f /etc/os-release ]; then \ ++ . /etc/os-release; \ ++ if echo "$$ID$$ID_LIKE" | grep -q -e arch; then \ ++ echo arch; \ ++ elif echo "$$ID$$ID_LIKE" | grep -q -e debian -e ubuntu; then \ ++ echo debian; \ ++ elif echo "$$ID$$ID_LIKE" | grep -q -e fedora; then \ ++ echo fedora; \ ++ else \ ++ echo arch; \ ++ fi; \ ++ else \ ++ echo arch; \ ++ fi \ ++)) + +-build: build-image +- docker run --rm \ +- -v ${PWD}/output:/output \ +- -v ${PWD}/dependencies:/dependencies \ +- ${IMAGE_NAME} ++# Set default TARGET_DISTRO based on detection, allowing user override ++TARGET_DISTRO ?= $(DETECTED_DISTRO) + +-build-compose: +- docker-compose build +- docker-compose up ++# --- Configuration --- ++IMAGE_NAME := docker-aseprite-${TARGET_DISTRO} ++DOCKERFILE := Dockerfile.${TARGET_DISTRO} ++OUTPUT_DIR := ${PWD}/output ++TARGET_DIR_IN_IMAGE := /target/aseprite/build/bin + ++# --- Main Targets --- ++# Default target ++all: build ++ ++# Prepare source files by running the script ++# Use .PHONY to ensure it always runs if called directly ++.PHONY: prepare-sources ++prepare-sources: ++ @echo "--- Preparing sources ---" ++ @./prepare_sources.sh ++ ++# Build the Docker image for the target distribution ++# Depends on sources being ready (though prepare-sources isn't a file dependency) + build-image: +- docker build -t ${IMAGE_NAME} . +\ No newline at end of file ++# --- DEBUGGING --- ++ @echo "DEBUG: TARGET_DISTRO='${TARGET_DISTRO}'" ++ @echo "DEBUG: DOCKERFILE='${DOCKERFILE}'" ++# --- END DEBUGGING --- ++ifeq (,$(wildcard ${DOCKERFILE})) ++ $(error Dockerfile for target distribution '${TARGET_DISTRO}' (${DOCKERFILE}) not found) ++endif ++ @echo "--- Building Docker image (${IMAGE_NAME}) using ${DOCKERFILE} ---" ++ @docker build -t ${IMAGE_NAME} -f ${DOCKERFILE} . ++ ++# Extract the compiled binary from the image ++# Depends on the image being built ++.PHONY: extract-binary ++extract-binary: build-image ++ @echo "--- Extracting Aseprite binary from ${IMAGE_NAME} ---" ++ @mkdir -p ${OUTPUT_DIR}/bin ++ @CONTAINER_ID=$$(docker create ${IMAGE_NAME}) && \ ++ docker cp "$${CONTAINER_ID}:${TARGET_DIR_IN_IMAGE}/." "${OUTPUT_DIR}/bin/" && \ ++ docker rm "$${CONTAINER_ID}" > /dev/null ++ @echo "Aseprite binary extracted to ${OUTPUT_DIR}/bin" ++ ++# Main build target: Prepare sources, build image, extract binary for selected distro ++.PHONY: build ++build: prepare-sources build-image extract-binary ++ @echo "--- Build complete for ${TARGET_DISTRO} ---" ++ @echo "Aseprite binary is in ${OUTPUT_DIR}/bin" ++ ++# --- Specific Distribution Targets --- ++.PHONY: build-arch build-debian build-fedora ++ ++# Build for Arch Linux (default if not detected otherwise) ++build-arch: ++ $(MAKE) build TARGET_DISTRO=arch ++ ++# Build for Debian/Ubuntu based systems ++build-debian: ++ $(MAKE) build TARGET_DISTRO=debian ++ ++# Build for Fedora based systems ++build-fedora: ++ $(MAKE) build TARGET_DISTRO=fedora ++ ++# --- Utility Targets --- ++# Clean up downloaded sources and output directory ++.PHONY: clean ++clean: ++ @echo "--- Cleaning up ---" ++ @rm -rf ./src ++ @rm -rf ${OUTPUT_DIR} ++ @echo "Cleanup complete." +diff --git a/README.md b/README.md +index 80f66b9..a0b00f8 100644 +--- a/README.md ++++ b/README.md +@@ -1,53 +1,182 @@ +-# Docker Aseprite container ++# Docker Aseprite Builder + +-This repository allows you to compile Aseprite without installing any build tools. All that is required is Docker. ++This repository provides a Docker-based environment to compile Aseprite easily. It aims to improve compatibility with your host Linux system by building Aseprite within a Docker container that matches your distribution family (Arch, Debian/Ubuntu, or Fedora). + +-After spending hours trying to get Aseprite to compile, I decided to just make a Docker image for it ++## Requirements + +-Currently the script checks out Skia version `m102` and Aseprite version `1.2.40`. You can easily change this in `compile.sh` by changing the `-b` flag to the desired versions. ++* Docker ++* Make ++* Git ++* Curl ++* Access to `/etc/os-release` on the host system (for the optional auto-detection feature). ++* **CPU with AVX support:** The compiled binary requires a CPU supporting the AVX instruction set (generally CPUs from 2011/Sandy Bridge/Bulldozer onwards). + +-If any of the folders of the projects folder isn't empty, the script will skip checking out the latest versions. In order to re-download, delete the according folder. +-* ./dependencies/depot_tools +-* ./dependencies/skia +-* ./output/aseprite ++## Quick Start + +-## Usage +- * Install docker +- * Clone this repository +- * cd into cloned repository +- * Run `make build` or `make build-compose` (The latter will use docker-compose to build the image) +- * Grab a cup of coffee, since this can take quite a while (Compiling build deps, skia, and aseprite) ++1. **Clone:** + +-You can now find the compiled version of Aseprite in the `output/aseprite/build/bin` folder ++ ```bash ++ git clone ++ cd docker-aseprite-linux ++ ``` + +-## FAQ +-If you get the following error when running Aseprite: `./aseprite: error while loading shared libraries: libdeflate.so.0: cannot open shared object file: No such file or directory`, make sure you have libdeflate installed on your system. Please run +-`sudo apt install -y libdeflate0 libdeflate-dev` ++2. **Build:** + +-If you get the following error: `./aseprite: error while loading shared libraries: libcrypto.so.1.1: cannot open shared object file: No such file or directory`, you'll want to install the OpenSSL 1.1 package/library. You may have only OpenSSL 3.x installed, meanwhile Aseprite still uses the v1.1 library. +-* On Arch / Arch based distros, run `sudo pacman -Syu openssl-1.1` +-* On Ubuntu try: `sudo apt install -y libssl1.1` ++ ```bash ++ make build ++ ``` + +-## License ++3. **Run:** Find the compiled binary in `./output/bin/aseprite`. ++ ++> [!WARNING] ++> **Compatibility Notice:** The compiled binary is dynamically linked. While this builder tries to match your OS family, minor differences between the build environment and your host system's libraries *can* still cause runtime errors. Please see the [Troubleshooting & Compatibility](#troubleshooting--compatibility) section for details. ++ ++## Build Process Explained ++ ++### OS Family Auto-Detection ++ ++When you run `make build`, the `Makefile` attempts to detect your host operating system family by reading `/etc/os-release`. It looks for identifiers to classify your system as: ++ ++* **Arch-based:** (`ID` or `ID_LIKE` contains `arch`) -> Uses `Dockerfile.arch` (See file for base image version) ++* **Debian/Ubuntu-based:** (`ID` or `ID_LIKE` contains `debian` or `ubuntu`) -> Uses `Dockerfile.debian` (See file for base image version) ++* **Fedora-based:** (`ID` or `ID_LIKE` contains `fedora`) -> Uses `Dockerfile.fedora` (See file for base image version) ++ ++**Default:** If detection fails or your OS family isn't recognized, it defaults to using the **Arch Linux** build environment. ++ ++### Build Steps ++ ++The `make build` command performs the following steps automatically: ++ ++1. **Prepare Sources (via `./prepare_sources.sh`):** ++ * Clones `depot_tools`, Skia, and Aseprite source code into the `./src` directory if they don't exist. See the script for specific versions. ++ * Runs Skia's `git-sync-deps` command locally to download Skia's internal build dependencies (like the `gn` tool). ++ * **Note on Skia Sync:** The `git-sync-deps` step can sometimes fail due to network issues or rate limiting (HTTP 429 errors) from Google's servers. The script automatically retries this step up to 3 times (1 initial attempt + 2 retries) with a 10-second delay between attempts. You can control the number of *retries* by setting the `PREPARE_RETRIES` environment variable when running `make` (e.g., `PREPARE_RETRIES=5 make build`). ++2. **Build Docker Image:** Builds a Docker image (e.g., `docker-aseprite-arch`) using the selected `Dockerfile.`. Inside the container: ++ * Necessary build tools and development libraries are installed using the distribution's package manager (`pacman`, `apt`, `dnf`). ++ * `depot_tools` is initialized. ++ * Skia is compiled, configured to use the system-provided libraries (avoids network issues and improves compatibility). ++ * Aseprite is compiled, linking against the compiled Skia and other system libraries. ++3. **Extract Binary:** Copies the compiled `aseprite` binary and its `data` directory from the container to `./output/bin` on your host machine. ++ ++### Overriding Auto-Detection ++ ++If the auto-detection chooses the wrong target, or you want to build for a different distribution family, you can override it: ++ ++```bash ++# Force build using Debian environment ++make build TARGET_DISTRO=debian ++ ++# Force build using Fedora environment ++make build TARGET_DISTRO=fedora ++ ++# Force build using Arch environment ++make build TARGET_DISTRO=arch ++``` ++ ++Alternatively, use the specific make targets: ++ ++```bash ++make build-debian ++make build-fedora ++make build-arch ++``` ++ ++## Troubleshooting & Compatibility ++ ++### Detailed Compatibility Explanation ++ ++Aseprite, like many Linux applications, relies on shared libraries (`.so` files) provided by the operating system (e.g., `libpng.so`, `libfreetype.so`, `libX11.so`). This process is called dynamic linking. ++ ++* **Goal:** This builder aims to improve compatibility by compiling Aseprite inside a Docker container using the same OS *family* as your host system. This means Aseprite links against libraries (e.g., Fedora libraries) that are likely similar to those on your host (e.g., your Fedora installation). ++* **The Challenge (Versioning):** Linux distributions and their libraries are constantly updated. The Docker image used for building (e.g., the one specified in `Dockerfile.fedora`) captures a snapshot of libraries at a specific point in time. Your host system might have slightly older or newer *minor versions* of these libraries (e.g., `libpng.so.1.6.40` in the container vs. `libpng.so.1.6.43` on the host). ++* **ABI Stability:** Library developers usually try very hard to maintain Application Binary Interface (ABI) stability across minor versions. This means newer libraries can often run programs compiled against slightly older versions (backward compatibility). ++* **Potential Issues:** ++ * **Build Newer, Run Older (More Risky):** If the build container has a *newer* library version than your older host system, and Aseprite uses a function only present in that newer library, you will likely get runtime errors ("undefined symbol") on the host. ++ * **Build Older, Run Newer (Usually Safer):** If the build container used an older library version than your newer host system, it's *more likely* to work due to backward compatibility efforts. ++ * **Subtle Breaks:** Very rarely, even minor version changes can introduce subtle ABI breaks causing crashes. ++* **Conclusion:** While building for the correct OS family significantly reduces problems, minor version mismatches between the build environment and your host *can* still cause issues. 100% compatibility is hard to guarantee with dynamic linking. + +-MIT License ++### Adapting Dockerfiles for Older Systems (Advanced / Use with Caution) + +-Copyright (c) 2025 nilsve ++If you are using an older version of a distribution and encounter runtime errors *after* building with the corresponding family target (e.g., `make build TARGET_DISTRO=fedora`), you *could* try modifying the `Dockerfile.`: + +-Permission is hereby granted, free of charge, to any person obtaining a copy +-of this software and associated documentation files (the "Software"), to deal +-in the Software without restriction, including without limitation the rights +-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +-copies of the Software, and to permit persons to whom the Software is +-furnished to do so, subject to the following conditions: ++1. **Identify the correct Dockerfile** (e.g., `Dockerfile.fedora`). ++2. **Change the `FROM` line:** Update the base image tag to an older version that matches your system (e.g., change the tag in `FROM fedora:...` to an older one like `fedora:39`). Find available tags on Docker Hub. ++3. **Adjust Dependencies:** Older distributions might have different package names or versions. You may need to adjust the `dnf install` (or `apt`, `pacman`) commands in the Dockerfile if the build fails due to missing packages. ++4. **Rebuild:** Run `make build TARGET_DISTRO=fedora` again. + +-The above copyright notice and this permission notice shall be included in all +-copies or substantial portions of the Software. ++**Disclaimer:** This is an advanced procedure. It might not work, may require significant troubleshooting of package dependencies in the Dockerfile, and is generally less recommended than upgrading your host operating system. ++ ++### Common Runtime Errors ++ ++* **`./aseprite: error while loading shared libraries: libfoo.so.X: cannot open shared object file: No such file or directory`**: Your host system is missing the required library `libfoo.so.X`. Install the package that provides this library using your system's package manager (e.g., `sudo dnf install foo`, `sudo apt install libfooX`, `sudo pacman -S foo`). Common examples needed by Aseprite or its dependencies include `libXcursor`, `libXi`, `freetype`, `harfbuzz`, `libstdc++`. ++* **`./aseprite: ./aseprite: symbol lookup error: ./aseprite: undefined symbol: some_function_name`**: This usually indicates an ABI incompatibility, often because the library version on your host is older than the one used during the build and is missing `some_function_name`. See the compatibility notes above. Upgrading your host system or trying the advanced Dockerfile adaptation might be necessary. ++* **`./aseprite: Illegal instruction`**: This typically means the compiled binary is trying to use CPU instructions not supported by your processor. This build requires a CPU with **AVX** support. ++ ++### Build Failures ++ ++* **Resource Exhaustion:** Compiling Skia and Aseprite requires significant RAM and disk space. Ensure your system (and Docker) has adequate resources allocated. ++* **Network Issues / Rate Limiting:** `prepare_sources.sh` needs to clone repositories and sync Skia's dependencies (`git-sync-deps`), which involves many network requests. ++ * Ensure you have a stable internet connection. Check for proxy issues if applicable. ++ * The Skia sync step might hit rate limits (HTTP 429 errors) from Google's servers. The script automatically retries this step (default: 3 total attempts). If it consistently fails, wait a while before trying again, or increase the number of retries via the `PREPARE_RETRIES` environment variable (e.g., `PREPARE_RETRIES=5 make build`). ++* **Package Not Found (Dockerfile):** If a `dnf`, `apt`, or `pacman` command fails inside the Docker build, a package name might be incorrect for the chosen base image version, or the distribution's repositories might be temporarily unavailable. ++* **Compilation Errors:** Check the detailed build log output from Docker for C++/CMake errors. These could indicate issues with the source code versions, compiler compatibility, or missing dependencies. ++ ++### Auto-Detection Failure ++ ++If the Makefile defaults to Arch but you use a different distribution, it means the detection logic couldn't identify your OS from `/etc/os-release`. Use the `TARGET_DISTRO` variable or specific make targets (`make build-debian`, `make build-fedora`) to select the correct environment. ++ ++## Advanced Usage ++ ++The `make build` command automatically runs `./prepare_sources.sh` without any special flags. If you need to control the source preparation step (e.g., force a re-download or check integrity), you can run the script manually *before* running `make build`, or pass flags through `make`. ++ ++### Running `prepare_sources.sh` Manually ++ ++You can run the script directly with its flags: ++ ++* **Force Re-download Sources:** Deletes `./src` and clones fresh. Useful for corruption or persistent sync issues. ++ ```bash ++ ./prepare_sources.sh --force-redownload ++ make build # Now run the build with fresh sources ++ ``` ++* **Check Source Integrity:** Runs `git fsck` on existing source repos. Does not re-download unless corruption is found and you *then* run with `--force-redownload`. ++ ```bash ++ ./prepare_sources.sh --check-integrity ++ # If no errors, proceed with make build ++ # If errors, consider running with --force-redownload ++ ``` ++* **Set Sync Retries:** Control the number of retries for the Skia `git-sync-deps` step (default is 2 retries, total 3 attempts). ++ ```bash ++ PREPARE_RETRIES=5 ./prepare_sources.sh ++ make build ++ ``` ++ ++### Passing Options via `make` ++ ++While you can run the script manually first, you can also influence the `prepare_sources.sh` step when running `make build` by passing variables: ++ ++* **Set Sync Retries via `make`:** ++ ```bash ++ PREPARE_RETRIES=5 make build ++ ``` ++* **Force Re-download via `make` (More Complex):** The `Makefile` doesn't directly support passing flags like `--force-redownload` to the script. The recommended way is to run the script manually first as shown above, or to run `make clean` followed by `make build`. ++ ```bash ++ make clean && make build # Clean first, then build (includes fresh source prep) ++ ``` ++ ++### Clean Up ++ ++To remove the downloaded sources (`./src`) and the build output (`./output`): ++ ++```bash ++make clean ++``` ++ ++## Notes ++ ++* **Source Versions:** See `prepare_sources.sh` for the specific Git tags of Skia and Aseprite that are cloned. ++* **System Libraries:** Skia is built using system libraries from the container to improve compatibility and avoid build-time network issues. ++ ++## License + +-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-SOFTWARE. ++MIT License (Refer to the original license text if needed). +diff --git a/compile.sh b/compile.sh +deleted file mode 100755 +index d53eafb..0000000 +--- a/compile.sh ++++ /dev/null +@@ -1,53 +0,0 @@ +-#!/bin/bash +- +-# Fail on errors +-set -e +- +-echo "Download and compile Skia & other dependencies" +-cd /dependencies +- +-if [ ! -d "/dependencies/depot_tools" ] +-then +- git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git +-fi +- +-if [ ! -d "/dependencies/skia" ] +-then +- git clone -b aseprite-m102 https://github.com/aseprite/skia.git +-fi +- +-export PATH="${PWD}/depot_tools:${PATH}" +- +-cd skia +-pwd +-echo "Syncing skia dependencies" +-python3 tools/git-sync-deps +- +-echo "Compiling skia" +-gn gen out/Release-x64 --args="is_debug=false is_official_build=true skia_use_system_expat=false skia_use_system_icu=false skia_use_system_libjpeg_turbo=false skia_use_system_libpng=false skia_use_system_libwebp=false skia_use_system_zlib=false skia_use_sfntly=false skia_use_freetype=true skia_use_harfbuzz=true skia_pdf_subset_harfbuzz=true skia_use_system_freetype2=false skia_use_system_harfbuzz=false" +-ninja -C out/Release-x64 skia modules +- +-echo "Download Aseprite and compile" +-cd /output +- +-if [ ! -d "/output/aseprite" ] +-then +- git clone -b v1.2.40 --recursive https://github.com/aseprite/aseprite.git +-fi +- +-cd aseprite +-mkdir -p build +-cd build +- +-echo "Compiling Asperite" +-cmake \ +- -DCMAKE_BUILD_TYPE=RelWithDebInfo \ +- -DLAF_BACKEND=skia \ +- -DSKIA_DIR=/dependencies/skia \ +- -DSKIA_LIBRARY_DIR=/dependencies/skia/out/Release-x64 \ +- -DSKIA_LIBRARY=/dependencies/skia/out/Release-x64/libskia.a \ +- -G Ninja \ +- .. +- +-echo "Linking Aseprite" +-ninja aseprite +diff --git a/docker-compose.yml b/docker-compose.yml +deleted file mode 100644 +index 18c269a..0000000 +--- a/docker-compose.yml ++++ /dev/null +@@ -1,8 +0,0 @@ +-version: '2' +- +-services: +- installer: +- build: . +- volumes: +- - "./output:/output:rw" +- - "./dependencies:/dependencies:rw" diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 18c269a..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,8 +0,0 @@ -version: '2' - -services: - installer: - build: . - volumes: - - "./output:/output:rw" - - "./dependencies:/dependencies:rw" diff --git a/prepare_sources.sh b/prepare_sources.sh new file mode 100755 index 0000000..45aed4c --- /dev/null +++ b/prepare_sources.sh @@ -0,0 +1,314 @@ +#!/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"