#!/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 _TOTAL_STEPS=$((_TOTAL_STEPS + 2)) # +1 for initializing depot_tools, +1 for Skia sync 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 # Initialize depot_tools after all repositories are processed print_step "Initializing depot_tools" if [[ -n "$DEPOT_TOOLS_DIR" && -d "$DEPOT_TOOLS_DIR" ]]; then print_info "Running depot_tools initialization..." # Add depot_tools to PATH for proper initialization export PATH="${DEPOT_TOOLS_DIR}:${PATH}" # Check if initialization was already done if [[ ! -f "${DEPOT_TOOLS_DIR}/python3_bin_reldir.txt" ]]; then # Run ensure_bootstrap to initialize depot_tools if (cd "$DEPOT_TOOLS_DIR" && ./ensure_bootstrap); then print_success "depot_tools initialized successfully." else print_error "Failed to initialize depot_tools. Build may fail." exit 1 fi else print_info "depot_tools already initialized (python3_bin_reldir.txt exists)." fi else print_error "depot_tools directory not found or not processed correctly. Cannot initialize." exit 1 fi # 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"