#!/bin/bash
#
# mkfakerpm - утилита для создания пустого RPM-пакета,
# который предоставляет заданные Provides и требует заданные Requires.
#
# Используется для установки «вражеских» RPM-пакетов (собранных под Fedora,
# RHEL, ALT и т.п.), у которых в Requires прописаны имена пакетов,
# отсутствующие в РОСА.
#
# Идея: фейк-пакет предоставляет чужие имена с чужими версиями
# (чтобы DNF увидел удовлетворение требований), а зависит от реальных
# пакетов РОСА (чтобы они установились вместе с целевым пакетом).
#
# Соглашение об опциях:
#   Большие буквы (-N, -V, -R)  — атрибуты самого фейк-пакета
#   Маленькие буквы (-p, -r, -l) — содержимое (Provides, Requires, Links)
#
# Соглашение о формате -p и -r:
#   В командной строке пробелы вокруг операторов НЕ допускаются:
#       foo=1.0   foo>=1.0   libfoo.so.1()(64bit)   perl(Foo::Bar)
#   В спеке операторы выводятся С пробелами:
#       Provides: foo = 1.0
#       Requires: foo >= 1.0
#

set -o pipefail

# ---------- сохранение исходной командной строки ----------
# $@ меняется после shift, поэтому сохраняем до разбора аргументов.
# Используем массив, чтобы сохранить аргументы с пробелами как одно целое.
original_args=("$@")

# ---------- значения по умолчанию ----------

name=""
version="1"
release=""
provides=""
requires=""
links=""
build=no
save_build=no
run_rpmlint=no
output_dir="."
build_dir=""

# ---------- справка ----------

HLP(){
    cat << 'EOF'
mkfakerpm - создать пустой RPM-пакет с заданными Provides и Requires.

Использование:
    mkfakerpm [опции]

Опции:
    -N, --name <имя>         имя фейк-пакета (обязательно при -b)
    -V, --version <версия>   версия фейк-пакета (по умолчанию: 1)
    -R, --release <релиз>    release фейк-пакета.
                             Без -R: <дата>.1 (например, 20260916.1)
                             -R +N: <дата>.N (например, 20260916.2)
                             -R <число>: используется как есть (например, 122)
    -p, --provides <список>  Provides через пробел.
                             Формат: имя | имя=версия | имя>=версия
                             Пробелы вокруг оператора НЕ допускаются.
    -r, --requires <список>  Requires через пробел. Формат тот же.
    -l, --links <список>     симлинки через пробел в формате target:link.
    -b, --build              собрать RPM (без -b генерируется только .spec)
    --output-dir <путь>      куда класть готовый RPM (по умолчанию: .)
    --build-dir <путь>       где собирать (по умолчанию: <output-dir>/build-<N>-<V>-<R>)
    --save-build             не удалять сборочный каталог после сборки
    --rpmlint                запустить rpmlint после сборки
    -h, --help               эта справка

Примеры:

    1. Простейший фейк: только Provides, без сборки.
       mkfakerpm -N fake-foo -p "foo=1.0"

    2. Типичный случай: Provides + Requires + сборка.
       mkfakerpm -N fake-fftw-devel -V 3.3.10 -R +1 \
           -p "fftw(x86-64)=3.3.10-17.fc44 fftw-libs(x86-64)=3.3.10-17.fc44" \
           -r "fftw-wisdom>=3.3.10-1 libfftw3>=3.3.10-1" \
           -b

    3. Фейк с симлинками.
       mkfakerpm -N fake-foo -p "foo=1.0" \
           -l "/usr/bin/foo:/bin/foo /usr/bin/bar:/bin/bar"

    4. Release вручную и нестандартный output-dir.
       mkfakerpm -N fake-foo -V 10.10 -p "foo=1.0" -R 42 \
           --output-dir /tmp/rpms -b

EOF
    exit 0
}

# ---------- нормализация зависимостей ----------
# Вход:  foo=1.0  |  foo>=1.0  |  foo  |  libfoo.so.1()(64bit)
# Выход: foo = 1.0 | foo >= 1.0 | foo  |  libfoo.so.1()(64bit)

normalize_dep() {
    local s="$1"
    local op
    # порядок важен: сначала двусимвольные операторы
    for op in '>=' '<=' '=' '>' '<' ; do
        if [[ "$s" == *"$op"* ]]; then
            local name="${s%%"$op"*}"
            local ver="${s#*"$op"}"
            echo "${name} ${op} ${ver}"
            return
        fi
    done
    echo "$s"
}

# ---------- валидация зависимостей ----------
# Проверяет, что каждый элемент не является чистым оператором.
# Это ловит случай "foo = 1.0" с пробелами, который for разбивает
# на "foo", "=", "1.0".

validate_deps() {
    local list="$1"
    local label="$2"
    local a
    for a in $list ; do
        case "$a" in
            "="|">="|"<="|">"|"<")
                echo "mkfakerpm: недопустимый элемент в $label: '$a'" >&2
                echo "            пробелы вокруг оператора не допускаются" >&2
                echo "            используйте foo=1.0, а не foo = 1.0" >&2
                exit 1
                ;;
        esac
    done
}

# ---------- разбор аргументов ----------

while [ -n "$1" ] ; do
    case "${1}" in
        "-N" | "--name")      shift; name="$1" ;;
        "-V" | "--version")   shift; version="$1" ;;
        "-R" | "--release")   shift; release="$1" ;;
        "-p" | "--provides")  shift; provides="$1" ;;
        "-r" | "--requires")  shift; requires="$1" ;;
        "-l" | "--links")     shift; links="$1" ;;
        "-b" | "--build")     build=yes ;;
        "--output-dir")       shift; output_dir="$1" ;;
        "--build-dir")        shift; build_dir="$1" ;;
        "--save-build")       save_build=yes ;;
        "--rpmlint")          run_rpmlint=yes ;;
        "-h" | "--help")      HLP ;;
        *)
            echo "$(basename "$0"): неизвестная опция: ${1}" >&2
            exit 1
            ;;
    esac
    shift
done

# ---------- вычисление release ----------

today="$(date +%Y%m%d)"

if [ -z "$release" ]; then
    release="${today}.1"
elif [[ "$release" == +* ]]; then
    release="${today}.${release#+}"
fi
# иначе — используется как есть

# ---------- значение по умолчанию для имени ----------

if [ -z "$name" ]; then
    name="fake-${today}"
fi

# ---------- валидация ----------

if [[ ! "$name" =~ ^[a-zA-Z0-9._+%-]+$ ]]; then
    echo "mkfakerpm: недопустимое имя пакета: $name" >&2
    echo "            допустимы только [a-zA-Z0-9._+%-]" >&2
    exit 1
fi

if [ -z "$provides" ] && [ -z "$requires" ] && [ -z "$links" ]; then
    echo "mkfakerpm: не задано ни Provides, ни Requires, ни ссылок" >&2
    echo "            нечего генерировать" >&2
    exit 1
fi

if [ -n "$provides" ]; then
    validate_deps "$provides" "Provides"
fi
if [ -n "$requires" ]; then
    validate_deps "$requires" "Requires"
fi

# ---------- подготовка каталогов ----------

mkdir -p "$output_dir" || {
    echo "mkfakerpm: не удалось создать каталог $output_dir" >&2
    exit 1
}

if [ -z "$build_dir" ]; then
    build_dir="${output_dir}/build-${name}-${version}-${release}"
fi

spec_file="${build_dir}/${name}.spec"

mkdir -p "$build_dir" || {
    echo "mkfakerpm: не удалось создать каталог сборки $build_dir" >&2
    exit 1
}

# ---------- генерация .spec ----------

{
    echo "Summary:        Fake RPM package ${name}"
    echo "Name:           ${name}"
    echo "Version:        ${version}"
    echo "Release:        ${release}"
    echo "License:        GPLv3+"
    echo "Group:          System/Base"
    echo "BuildArch:      noarch"
    echo ""

    # Provides — с нормализацией (пробелы вокруг оператора)
    if [ -n "$provides" ]; then
        for a in $provides ; do
            echo "Provides:       $(normalize_dep "$a")"
        done
        echo ""
    fi

    # Requires — с нормализацией
    if [ -n "$requires" ]; then
        for a in $requires ; do
            echo "Requires:       $(normalize_dep "$a")"
        done
        echo ""
    fi

    echo "%description"
    echo "Fake RPM package ${name}, built by mkfakerpm."
    echo ""
    echo "Command line:"
    echo "    $0"
    for arg in "${original_args[@]}" ; do
        echo "        $arg"
    done
    echo ""
    echo "Needed to install enemy rpm package with non-native ROSA rpm names"
    echo "in the Requires tags."
    echo ""

    # %prep, %build, %install — пустые, но нужны
    echo "%prep"
    echo ""
    echo "%build"
    echo ""
    echo "%install"

    # Если есть ссылки — создаём каталоги под них в buildroot
    if [ -n "$links" ]; then
        for a in $links ; do
            link="${a#*:}"
            echo "mkdir -p %{buildroot}$(dirname "$link")"
        done
    fi
    echo ""

    # %post — создаём симлинки
    if [ -n "$links" ]; then
        echo "%post"
        for a in $links ; do
            target="${a%%:*}"
            link="${a#*:}"
            echo "ln -sf ${target} ${link}"
        done
        echo ""
    fi

    # %files — только ghost-записи для ссылок
    echo "%files"
    if [ -n "$links" ]; then
        for a in $links ; do
            link="${a#*:}"
            echo "%ghost ${link}"
        done
    fi
} > "$spec_file"

# ---------- сборка ----------

if [ "$build" != "yes" ]; then
    echo "mkfakerpm: сгенерирован .spec: $spec_file"
    echo "mkfakerpm: сборка не запущена (нет -b)"
    exit 0
fi

echo "mkfakerpm: сборка ${name}-${version}-${release}"
echo "mkfakerpm: spec:      $spec_file"
echo "mkfakerpm: build_dir: $build_dir"

rpmbuild -bb "$spec_file" \
    --define="_topdir $build_dir" \
    --define="_sourcedir $build_dir/SOURCES" \
    --define="_builddir $build_dir/BUILD" \
    --define="_rpmdir $build_dir/RPMS" \
    --define="_srcrpmdir $build_dir/SRPMS" \
    --define="_buildrootdir $build_dir/BUILDROOT"

rc=$?
if [ $rc -ne 0 ]; then
    echo "mkfakerpm: rpmbuild завершился с ошибкой (код $rc)" >&2
    echo "mkfakerpm: сборочный каталог сохранён: $build_dir" >&2
    exit $rc
fi

# ---------- копирование результата ----------

found=0
for rpm in "$build_dir"/RPMS/*/*.rpm ; do
    [ -e "$rpm" ] || continue
    cp -v "$rpm" "$output_dir/"
    found=1
done

if [ $found -eq 0 ]; then
    echo "mkfakerpm: rpmbuild не создал ни одного RPM" >&2
    echo "mkfakerpm: сборочный каталог сохранён: $build_dir" >&2
    exit 1
fi

# ---------- rpmlint ----------

if [ "$run_rpmlint" = "yes" ]; then
    if command -v rpmlint >/dev/null 2>&1 ; then
        for rpm in "$output_dir"/"${name}"-*.rpm ; do
            [ -e "$rpm" ] || continue
            echo "mkfakerpm: rpmlint $rpm"
            rpmlint "$rpm" || true
        done
    else
        echo "mkfakerpm: rpmlint не найден, пропускаем" >&2
    fi
fi

# ---------- очистка ----------

if [ "$save_build" = "yes" ]; then
    echo "mkfakerpm: сборочный каталог сохранён: $build_dir"
else
    rm -rf "$build_dir"
fi

exit 0
