#!/bin/bash # Валидатор для alr.sh файлов в репозитории ALR # Проверяет синтаксис и структуру пакетов set -e RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color # Глобальные счетчики declare -g errors=0 declare -g warnings=0 # Функция для вывода ошибок error() { echo -e "${RED}[ERROR]${NC} $1" ((errors++)) } # Функция для вывода предупреждений warning() { echo -e "${YELLOW}[WARNING]${NC} $1" ((warnings++)) } # Функция для вывода успешных проверок success() { echo -e "${GREEN}[OK]${NC} $1" } # Проверка одного alr.sh файла check_alr_file() { local file="$1" local pkg_dir=$(dirname "$file") local pkg_name=$(basename "$pkg_dir") echo "Проверка пакета: $pkg_name" # 1. Проверка синтаксиса bash if ! bash -n "$file" 2>/dev/null; then error "$pkg_name: синтаксические ошибки в alr.sh" return 1 fi # Загружаем файл в изолированной среде для проверки { # Определяем заглушки для функций ALR srcdir="/tmp/alr-validate-src" pkgdir="/tmp/alr-validate-pkg" scriptdir="$pkg_dir" # Заглушки для функций установки install() { :; } install-desktop() { :; } install-license() { :; } # Загружаем файл source "$file" 2>/dev/null || { error "$pkg_name: не удалось загрузить alr.sh" return 1 } # 2. Проверка обязательных полей local required_fields=(name version release desc maintainer) for field in "${required_fields[@]}"; do if [[ -z "${!field}" ]]; then error "$pkg_name: отсутствует обязательное поле '$field'" fi done # 3. Проверка формата версии # Поддерживаемые форматы: # - стандартный: 1.2.3, 1.0.0-alpha1 # - git версии: git # - release префикс: release-1.2.3 # - версии с r префиксом: r20250906.ff1bee31 if [[ ! "$version" =~ ^([0-9]+(\.[0-9]+)*([a-zA-Z0-9._-]+)?|git|release-[0-9]+(\.[0-9]+)*([a-zA-Z0-9._-]+)?|r[0-9]{8}\.[a-z0-9]+)$ ]]; then # Не выводим предупреждения для git пакетов if [[ ! "$pkg_name" =~ -git$ ]]; then warning "$pkg_name: необычный формат версии '$version'" fi fi # 4. Проверка формата release if [[ ! "$release" =~ ^[0-9]+$ ]]; then error "$pkg_name: release должен быть числом, получено '$release'" fi # 5. Проверка наличия функций # Определяем тип пакета: мульти-пакет если name объявлен как массив с несколькими элементами # Проверяем, объявлен ли name как массив в исходном файле local is_multipackage=false if grep -q "^name=(" "$file"; then # Если в файле есть name=( значит это массив if [[ "${#name[@]}" -gt 1 ]]; then is_multipackage=true fi fi if [[ "$is_multipackage" == true ]]; then # Мульти-пакет - проверяем функции с префиксами for subpkg in "${name[@]}"; do # Проверяем наличие meta функции if ! declare -F "meta_${subpkg}" >/dev/null 2>&1; then warning "$pkg_name: отсутствует функция meta_${subpkg}()" fi # Проверяем наличие package функции if ! declare -F "package_${subpkg}" >/dev/null 2>&1; then warning "$pkg_name: отсутствует функция package_${subpkg}()" fi # Проверяем наличие files функции if ! declare -F "files_${subpkg}" >/dev/null 2>&1; then error "$pkg_name: отсутствует функция files_${subpkg}()" fi done else # Обычный (моно)пакет - проверяем обычные функции # Проверяем наличие функции package() или build() if ! declare -F package >/dev/null 2>&1; then if ! declare -F build >/dev/null 2>&1; then warning "$pkg_name: отсутствуют функции package() и build()" fi fi # Проверяем наличие функции files() if ! declare -F files >/dev/null 2>&1; then error "$pkg_name: отсутствует функция files()" fi fi # 6. Проверка архитектур if [[ -n "${architectures[*]}" ]]; then for arch in "${architectures[@]}"; do if [[ ! "$arch" =~ ^(amd64|arm64|arm6|386|riscv64|aarch64|all)$ ]]; then warning "$pkg_name: неизвестная архитектура '$arch'" fi done fi # 7. Проверка источников и контрольных сумм if [[ -n "${sources[*]}" ]]; then if [[ -z "${checksums[*]}" ]]; then warning "$pkg_name: sources определены, но checksums отсутствуют" elif [[ "${#sources[@]}" -ne "${#checksums[@]}" ]]; then if [[ "${checksums[0]}" != "SKIP" ]]; then error "$pkg_name: количество sources (${#sources[@]}) не совпадает с checksums (${#checksums[@]})" fi fi fi # 8. Проверка, что функции не вызывают явные ошибки # Создаем временные директории для тестирования mkdir -p "$srcdir" "$pkgdir" # Пробуем выполнить функции в dry-run режиме if declare -F build >/dev/null 2>&1; then # Переопределяем команды, которые могут быть опасными curl() { echo "curl $*" >/dev/null; } wget() { echo "wget $*" >/dev/null; } git() { echo "git $*" >/dev/null; } make() { echo "make $*" >/dev/null; } cmake() { echo "cmake $*" >/dev/null; } pip() { echo "pip $*" >/dev/null; } npm() { echo "npm $*" >/dev/null; } cargo() { echo "cargo $*" >/dev/null; } tar() { echo "tar $*" >/dev/null; } unzip() { echo "unzip $*" >/dev/null; } cd() { builtin cd "$@" 2>/dev/null || :; } export -f curl wget git make cmake pip npm cargo tar unzip cd # Пробуем выполнить build в режиме проверки синтаксиса if ! bash -c "source '$file' && declare -f build | bash -n" 2>/dev/null; then warning "$pkg_name: синтаксические ошибки в функции build()" fi fi if declare -F package >/dev/null 2>&1; then if ! bash -c "source '$file' && declare -f package | bash -n" 2>/dev/null; then warning "$pkg_name: синтаксические ошибки в функции package()" fi fi if declare -F files >/dev/null 2>&1; then if ! bash -c "source '$file' && declare -f files | bash -n" 2>/dev/null; then warning "$pkg_name: синтаксические ошибки в функции files()" fi fi # Очистка rm -rf "$srcdir" "$pkgdir" 2>/dev/null || true } 2>/dev/null || true } # Главная функция main() { local check_all=false local files_to_check=() # Парсим аргументы while [[ $# -gt 0 ]]; do case "$1" in --all) check_all=true shift ;; *) files_to_check+=("$1") shift ;; esac done # Если указан флаг --all, проверяем все alr.sh файлы if [[ "$check_all" == true ]]; then echo "Проверка всех alr.sh файлов в репозитории..." while IFS= read -r file; do check_alr_file "$file" echo "---" done < <(find . -name "alr.sh" -type f | grep -v "^\./\." | sort) else # Проверяем только указанные файлы или измененные в git if [[ ${#files_to_check[@]} -eq 0 ]]; then # Получаем список измененных alr.sh файлов из git while IFS= read -r file; do if [[ -f "$file" ]] && [[ "$(basename "$file")" == "alr.sh" ]]; then files_to_check+=("$file") fi done < <(git diff --cached --name-only) fi if [[ ${#files_to_check[@]} -eq 0 ]]; then echo "Нет alr.sh файлов для проверки" exit 0 fi for file in "${files_to_check[@]}"; do if [[ -f "$file" ]]; then check_alr_file "$file" echo "---" fi done fi # Итоговая статистика echo "" if [[ $errors -gt 0 ]]; then echo -e "${RED}Найдено ошибок: $errors${NC}" fi if [[ $warnings -gt 0 ]]; then echo -e "${YELLOW}Найдено предупреждений: $warnings${NC}" fi if [[ $errors -gt 0 ]]; then echo -e "${RED}Проверка не пройдена!${NC}" exit 1 elif [[ $warnings -gt 0 ]]; then echo -e "${YELLOW}Проверка пройдена с предупреждениями${NC}" exit 0 else echo -e "${GREEN}Все проверки пройдены успешно!${NC}" exit 0 fi } main "$@"