Compare commits
5 Commits
v0.0.32
...
feature/ve
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d9f4a0985 | |||
| b649a459b8 | |||
| e8c20bad25 | |||
| df69f3dcab | |||
| a44da806d4 |
49
.gitverse/workflows/e2e-tests.yaml
Normal file
49
.gitverse/workflows/e2e-tests.yaml
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# ALR - Any Linux Repository
|
||||||
|
# Copyright (C) 2025 The ALR Authors
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
name: E2E
|
||||||
|
|
||||||
|
# on:
|
||||||
|
# push:
|
||||||
|
# branches: [ main ]
|
||||||
|
# pull_request:
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
container:
|
||||||
|
image: altlinux.space/maks1ms/actions-container-runner:latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.24'
|
||||||
|
cache: false
|
||||||
|
|
||||||
|
- name: Run E2E tests
|
||||||
|
env:
|
||||||
|
IGNORE_ROOT_CHECK: 1
|
||||||
|
run: |
|
||||||
|
make e2e-test
|
||||||
49
.gitverse/workflows/pre-commit.yaml
Normal file
49
.gitverse/workflows/pre-commit.yaml
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# ALR - Any Linux Repository
|
||||||
|
# Copyright (C) 2025 The ALR Authors
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
name: Pre-commit
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pre-commit:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.24'
|
||||||
|
|
||||||
|
- name: Set up Python for pre-commit
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.12'
|
||||||
|
|
||||||
|
- name: Install deps
|
||||||
|
run: apt-get update && apt-get install -y gettext bc
|
||||||
|
|
||||||
|
- run: pip install pre-commit
|
||||||
|
- run: pre-commit install
|
||||||
|
- run: pre-commit run --all-files
|
||||||
185
.gitverse/workflows/release.yaml
Normal file
185
.gitverse/workflows/release.yaml
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
# ALR - Any Linux Repository
|
||||||
|
# Copyright (C) 2025 The ALR Authors
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
name: Create Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
changelog:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout this repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.24'
|
||||||
|
|
||||||
|
- name: Get Changes between Tags
|
||||||
|
id: changes
|
||||||
|
run: |
|
||||||
|
# Получаем текущий и предыдущий теги
|
||||||
|
CURRENT_TAG=${GITHUB_REF##*/}
|
||||||
|
PREVIOUS_TAG=$(git describe --tags --abbrev=0 ${CURRENT_TAG}^ 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
if [ -n "$PREVIOUS_TAG" ]; then
|
||||||
|
CHANGES=$(git log ${PREVIOUS_TAG}..${CURRENT_TAG} --pretty=format:"- %s" --no-merges)
|
||||||
|
else
|
||||||
|
CHANGES=$(git log ${CURRENT_TAG} --pretty=format:"- %s" --no-merges)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Экранируем для использования в GitHub Actions
|
||||||
|
CHANGES="${CHANGES//'%'/'%25'}"
|
||||||
|
CHANGES="${CHANGES//$'\n'/'%0A'}"
|
||||||
|
CHANGES="${CHANGES//$'\r'/'%0D'}"
|
||||||
|
|
||||||
|
echo "changes=$CHANGES" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Set version
|
||||||
|
run: |
|
||||||
|
version=$(echo "${GITHUB_REF##*/}" | sed 's/^v//')
|
||||||
|
echo "Version - $version"
|
||||||
|
echo "VERSION=$version" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Prepare for install
|
||||||
|
run: |
|
||||||
|
apt-get update
|
||||||
|
|
||||||
|
- name: Build alr
|
||||||
|
env:
|
||||||
|
IGNORE_ROOT_CHECK: 1
|
||||||
|
run: |
|
||||||
|
make build
|
||||||
|
|
||||||
|
- name: Create tar.gz
|
||||||
|
run: |
|
||||||
|
mkdir -p ./out/completion
|
||||||
|
cp alr ./out
|
||||||
|
cp scripts/completion/bash ./out/completion/alr
|
||||||
|
cp scripts/completion/zsh ./out/completion/_alr
|
||||||
|
|
||||||
|
( cd out && tar -czvf ../alr-${{ env.VERSION }}-linux-x86_64.tar.gz * )
|
||||||
|
|
||||||
|
- name: Create Release via GitVerse API
|
||||||
|
env:
|
||||||
|
GITVERSE_TOKEN: ${{ secrets.GITVERSE_TOKEN }}
|
||||||
|
run: |
|
||||||
|
TAG_NAME=${GITHUB_REF##*/}
|
||||||
|
RELEASE_NAME="ALR ${{ env.VERSION }}"
|
||||||
|
BODY="${{ steps.changes.outputs.changes }}"
|
||||||
|
|
||||||
|
# Создаём релиз через GitVerse API
|
||||||
|
RELEASE_RESPONSE=$(curl -s -X POST \
|
||||||
|
"https://gitverse.ru/api/v1/repos/${{ gitverse.repository_owner }}/${{ gitverse.repository }}/releases" \
|
||||||
|
-H "Authorization: token ${GITVERSE_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{
|
||||||
|
\"tag_name\": \"${TAG_NAME}\",
|
||||||
|
\"name\": \"${RELEASE_NAME}\",
|
||||||
|
\"body\": \"${BODY}\",
|
||||||
|
\"draft\": false,
|
||||||
|
\"prerelease\": false
|
||||||
|
}")
|
||||||
|
|
||||||
|
RELEASE_ID=$(echo "$RELEASE_RESPONSE" | jq -r '.id')
|
||||||
|
echo "RELEASE_ID=$RELEASE_ID" >> $GITHUB_ENV
|
||||||
|
echo "Created release with ID: $RELEASE_ID"
|
||||||
|
|
||||||
|
- name: Upload tar.gz asset
|
||||||
|
env:
|
||||||
|
GITVERSE_TOKEN: ${{ secrets.GITVERSE_TOKEN }}
|
||||||
|
run: |
|
||||||
|
curl -s -X POST \
|
||||||
|
"https://gitverse.ru/api/v1/repos/${{ gitverse.repository_owner }}/${{ gitverse.repository }}/releases/${{ env.RELEASE_ID }}/assets?name=alr-${{ env.VERSION }}-linux-x86_64.tar.gz" \
|
||||||
|
-H "Authorization: token ${GITVERSE_TOKEN}" \
|
||||||
|
-H "Content-Type: application/octet-stream" \
|
||||||
|
--data-binary "@alr-${{ env.VERSION }}-linux-x86_64.tar.gz"
|
||||||
|
|
||||||
|
- name: Checkout alr-default repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: ${{ gitverse.repository_owner }}/alr-default
|
||||||
|
token: ${{ secrets.GITVERSE_PUBLIC_TOKEN }}
|
||||||
|
path: alr-default
|
||||||
|
|
||||||
|
- name: Calculate checksum
|
||||||
|
run: |
|
||||||
|
# Вычисляем SHA256 контрольную сумму архива
|
||||||
|
CHECKSUM=$(sha256sum alr-${{ env.VERSION }}-linux-x86_64.tar.gz | awk '{print $1}')
|
||||||
|
echo "Archive checksum: $CHECKSUM"
|
||||||
|
echo "CHECKSUM=$CHECKSUM" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Update version and checksum in alr-bin
|
||||||
|
run: |
|
||||||
|
# Обновляем версию
|
||||||
|
sed -i "s/version='[0-9]\+\.[0-9]\+\.[0-9]\+'/version='${{ env.VERSION }}'/g" alr-default/alr-bin/alr.sh
|
||||||
|
sed -i "s/release='[0-9]\+'/release='1'/g" alr-default/alr-bin/alr.sh
|
||||||
|
|
||||||
|
# Обновляем контрольную сумму
|
||||||
|
sed -i "s/checksums=('[^']*')/checksums=('${{ env.CHECKSUM }}')/g" alr-default/alr-bin/alr.sh
|
||||||
|
|
||||||
|
- name: Commit and push changes to alr-default
|
||||||
|
run: |
|
||||||
|
cd alr-default
|
||||||
|
git config user.name "gitverse"
|
||||||
|
git config user.email "admin@plemya-x.ru"
|
||||||
|
git add alr-bin/alr.sh
|
||||||
|
git commit -m "Обновление alr-bin до версии ${{ env.VERSION }}"
|
||||||
|
git push
|
||||||
|
|
||||||
|
- name: Install alr
|
||||||
|
env:
|
||||||
|
CREATE_SYSTEM_RESOURCES: 0
|
||||||
|
run: |
|
||||||
|
make install
|
||||||
|
|
||||||
|
- name: Prepare directories for ALR
|
||||||
|
run: |
|
||||||
|
# Создаём необходимые директории для работы alr build
|
||||||
|
mkdir -p /tmp/alr/dl /tmp/alr/pkgs /var/cache/alr
|
||||||
|
chmod -R 777 /tmp/alr
|
||||||
|
chmod -R 755 /var/cache/alr
|
||||||
|
|
||||||
|
- name: Build packages
|
||||||
|
run: |
|
||||||
|
SCRIPT_PATH=alr-default/alr-bin/alr.sh
|
||||||
|
ALR_DISTRO=altlinux ALR_PKG_FORMAT=rpm alr build -s "$SCRIPT_PATH"
|
||||||
|
ALR_PKG_FORMAT=rpm alr build -s "$SCRIPT_PATH"
|
||||||
|
ALR_PKG_FORMAT=deb alr build -s "$SCRIPT_PATH"
|
||||||
|
ALR_PKG_FORMAT=archlinux alr build -s "$SCRIPT_PATH"
|
||||||
|
|
||||||
|
- name: Upload package assets
|
||||||
|
env:
|
||||||
|
GITVERSE_TOKEN: ${{ secrets.GITVERSE_TOKEN }}
|
||||||
|
run: |
|
||||||
|
# Загружаем все собранные пакеты
|
||||||
|
for file in alr-bin*.deb alr-bin*.rpm alr-bin*.pkg.tar.zst; do
|
||||||
|
if [ -f "$file" ]; then
|
||||||
|
echo "Uploading $file..."
|
||||||
|
curl -s -X POST \
|
||||||
|
"https://gitverse.ru/api/v1/repos/${{ gitverse.repository_owner }}/${{ gitverse.repository }}/releases/${{ env.RELEASE_ID }}/assets?name=${file}" \
|
||||||
|
-H "Authorization: token ${GITVERSE_TOKEN}" \
|
||||||
|
-H "Content-Type: application/octet-stream" \
|
||||||
|
--data-binary "@${file}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
@@ -30,19 +30,21 @@ var ALL_SYSTEMS []string = []string{
|
|||||||
"ubuntu-24.04",
|
"ubuntu-24.04",
|
||||||
"alt-sisyphus",
|
"alt-sisyphus",
|
||||||
"fedora-41",
|
"fedora-41",
|
||||||
// "archlinux",
|
"archlinux",
|
||||||
// "alpine",
|
"alpine",
|
||||||
// "opensuse-leap",
|
"opensuse-leap",
|
||||||
// "redos-8",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var AUTOREQ_AUTOPROV_SYSTEMS []string = []string{
|
var AUTOREQ_AUTOPROV_SYSTEMS []string = []string{
|
||||||
// "alt-sisyphus",
|
"alt-sisyphus",
|
||||||
"fedora-41",
|
"fedora-41",
|
||||||
|
"opensuse-leap",
|
||||||
}
|
}
|
||||||
|
|
||||||
var RPM_SYSTEMS []string = []string{
|
var RPM_SYSTEMS []string = []string{
|
||||||
|
"alt-sisyphus",
|
||||||
"fedora-41",
|
"fedora-41",
|
||||||
|
"opensuse-leap",
|
||||||
}
|
}
|
||||||
|
|
||||||
var COMMON_SYSTEMS []string = []string{
|
var COMMON_SYSTEMS []string = []string{
|
||||||
|
|||||||
@@ -39,12 +39,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type BuildInput struct {
|
type BuildInput struct {
|
||||||
opts *types.BuildOpts
|
opts *types.BuildOpts
|
||||||
info *distro.OSRelease
|
info *distro.OSRelease
|
||||||
pkgFormat string
|
pkgFormat string
|
||||||
script string
|
script string
|
||||||
repository string
|
repository string
|
||||||
packages []string
|
packages []string
|
||||||
|
skipDepsBuilding bool // Пропустить сборку зависимостей (используется при вызове из BuildALRDeps)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bi *BuildInput) GobEncode() ([]byte, error) {
|
func (bi *BuildInput) GobEncode() ([]byte, error) {
|
||||||
@@ -242,9 +243,10 @@ type Builder struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type BuildArgs struct {
|
type BuildArgs struct {
|
||||||
Opts *types.BuildOpts
|
Opts *types.BuildOpts
|
||||||
Info *distro.OSRelease
|
Info *distro.OSRelease
|
||||||
PkgFormat_ string
|
PkgFormat_ string
|
||||||
|
SkipDepsBuilding bool // Пропустить сборку зависимостей (используется при вызове из BuildALRDeps)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BuildArgs) BuildOpts() *types.BuildOpts {
|
func (b *BuildArgs) BuildOpts() *types.BuildOpts {
|
||||||
@@ -278,12 +280,13 @@ func (b *Builder) BuildPackageFromDb(
|
|||||||
scriptInfo := b.scriptResolver.ResolveScript(ctx, args.Package)
|
scriptInfo := b.scriptResolver.ResolveScript(ctx, args.Package)
|
||||||
|
|
||||||
return b.BuildPackage(ctx, &BuildInput{
|
return b.BuildPackage(ctx, &BuildInput{
|
||||||
script: scriptInfo.Script,
|
script: scriptInfo.Script,
|
||||||
repository: scriptInfo.Repository,
|
repository: scriptInfo.Repository,
|
||||||
packages: args.Packages,
|
packages: args.Packages,
|
||||||
pkgFormat: args.PkgFormat(),
|
pkgFormat: args.PkgFormat(),
|
||||||
opts: args.Opts,
|
opts: args.Opts,
|
||||||
info: args.Info,
|
info: args.Info,
|
||||||
|
skipDepsBuilding: args.SkipDepsBuilding,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,13 +410,14 @@ func (b *Builder) BuildPackage(
|
|||||||
|
|
||||||
// We filter so as not to re-build what has already been built at the `installBuildDeps` stage.
|
// We filter so as not to re-build what has already been built at the `installBuildDeps` stage.
|
||||||
var filteredDepends []string
|
var filteredDepends []string
|
||||||
|
|
||||||
// Создаем набор подпакетов текущего мультипакета для исключения циклических зависимостей
|
// Создаем набор подпакетов текущего мультипакета для исключения циклических зависимостей
|
||||||
|
// Используем имена из varsOfPackages, так как input.packages может быть пустым
|
||||||
currentPackageNames := make(map[string]struct{})
|
currentPackageNames := make(map[string]struct{})
|
||||||
for _, pkg := range input.packages {
|
for _, vars := range varsOfPackages {
|
||||||
currentPackageNames[pkg] = struct{}{}
|
currentPackageNames[vars.Name] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, d := range depends {
|
for _, d := range depends {
|
||||||
if _, found := depNames[d]; !found {
|
if _, found := depNames[d]; !found {
|
||||||
// Исключаем зависимости, которые являются подпакетами текущего мультипакета
|
// Исключаем зависимости, которые являются подпакетами текущего мультипакета
|
||||||
@@ -423,10 +427,16 @@ func (b *Builder) BuildPackage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Debug("BuildALRDeps")
|
var newBuiltDeps []*BuiltDep
|
||||||
newBuiltDeps, repoDeps, err := b.BuildALRDeps(ctx, input, filteredDepends)
|
var repoDeps []string
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
// Пропускаем сборку зависимостей если флаг установлен (вызов из BuildALRDeps)
|
||||||
|
if !input.skipDepsBuilding {
|
||||||
|
slog.Debug("BuildALRDeps")
|
||||||
|
newBuiltDeps, repoDeps, err = b.BuildALRDeps(ctx, input, filteredDepends)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Debug("PrepareDirs")
|
slog.Debug("PrepareDirs")
|
||||||
@@ -580,65 +590,122 @@ func (b *Builder) BuildALRDeps(
|
|||||||
// Системные зависимости возвращаем как repoDeps
|
// Системные зависимости возвращаем как repoDeps
|
||||||
repoDeps = systemDeps
|
repoDeps = systemDeps
|
||||||
|
|
||||||
// Шаг 2: Собираем список всех пакетов из дерева для топологической сортировки
|
// Шаг 2: Топологическая сортировка (от корней к листьям)
|
||||||
allFound := make(map[string][]alrsh.Package)
|
sortedPkgs, err := TopologicalSort(depTree)
|
||||||
for baseName, node := range depTree {
|
|
||||||
allFound[baseName] = []alrsh.Package{*node.Package}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Шаг 3: Топологическая сортировка (от корней к листьям)
|
|
||||||
sortedPkgs, err := TopologicalSort(depTree, allFound)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to sort dependencies: %w", err)
|
return nil, nil, fmt.Errorf("failed to sort dependencies: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Шаг 2.5: Фильтруем уже установленные пакеты
|
||||||
|
// Собираем пакеты вместе с их ключами (именами поиска)
|
||||||
|
type pkgWithKey struct {
|
||||||
|
key string
|
||||||
|
pkg alrsh.Package
|
||||||
|
}
|
||||||
|
var allPkgsWithKeys []pkgWithKey
|
||||||
|
for key, node := range depTree {
|
||||||
|
if node.Package != nil {
|
||||||
|
allPkgsWithKeys = append(allPkgsWithKeys, pkgWithKey{key: key, pkg: *node.Package})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var allPkgs []alrsh.Package
|
||||||
|
for _, p := range allPkgsWithKeys {
|
||||||
|
allPkgs = append(allPkgs, p.pkg)
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("DEBUG: allPkgs count", "count", len(allPkgs))
|
||||||
|
for _, p := range allPkgsWithKeys {
|
||||||
|
slog.Info("DEBUG: package in depTree", "key", p.key, "name", p.pkg.Name, "repo", p.pkg.Repository)
|
||||||
|
}
|
||||||
|
|
||||||
|
needBuildPkgs, err := b.installerExecutor.FilterPackagesByVersion(ctx, allPkgs, input.OSRelease())
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to filter packages: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создаём множество имён пакетов, которые нужно собрать
|
||||||
|
needBuildNames := make(map[string]bool)
|
||||||
|
for _, pkg := range needBuildPkgs {
|
||||||
|
needBuildNames[pkg.Name] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("DEBUG: needBuildPkgs count", "count", len(needBuildPkgs))
|
||||||
|
for _, pkg := range needBuildPkgs {
|
||||||
|
slog.Info("DEBUG: package needs build", "name", pkg.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Строим needBuildSet по КЛЮЧАМ depTree, а не по pkg.Name
|
||||||
|
// Это важно, т.к. ключ может быть именем из Provides (python3-pyside6),
|
||||||
|
// а pkg.Name - фактическое имя пакета (python3-shiboken6)
|
||||||
|
needBuildSet := make(map[string]bool)
|
||||||
|
for _, p := range allPkgsWithKeys {
|
||||||
|
if needBuildNames[p.pkg.Name] {
|
||||||
|
needBuildSet[p.key] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Шаг 3: Группируем подпакеты по basePkgName для оптимизации сборки
|
||||||
|
// Если несколько подпакетов из одного мультипакета, собираем их вместе
|
||||||
|
slog.Info("DEBUG: sortedPkgs", "pkgs", sortedPkgs)
|
||||||
|
|
||||||
// Шаг 4: Собираем пакеты в правильном порядке, проверяя кеш
|
// Шаг 4: Собираем пакеты в правильном порядке, проверяя кеш
|
||||||
for _, basePkgName := range sortedPkgs {
|
for _, pkgName := range sortedPkgs {
|
||||||
node := depTree[basePkgName]
|
node := depTree[pkgName]
|
||||||
if node == nil {
|
if node == nil {
|
||||||
|
slog.Info("DEBUG: node is nil", "pkgName", pkgName)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
pkg := node.Package
|
pkg := node.Package
|
||||||
|
basePkgName := node.BasePkgName
|
||||||
|
|
||||||
// Находим ВСЕ подпакеты с этим BasePkgName
|
// Пропускаем уже установленные пакеты
|
||||||
allSubpkgs, err := b.findAllSubpackages(ctx, basePkgName, pkg.Repository)
|
if !needBuildSet[pkgName] {
|
||||||
if err != nil {
|
slog.Info("DEBUG: skipping (not in needBuildSet)", "pkgName", pkgName)
|
||||||
return nil, nil, fmt.Errorf("failed to find subpackages for %s: %w", basePkgName, err)
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем кеш для ВСЕХ подпакетов
|
// Собираем только запрошенный подпакет (или все, если запрошен basePkgName)
|
||||||
|
packagesToBuilt := []string{pkgName}
|
||||||
|
|
||||||
|
// Проверяем кеш для запрошенного подпакета
|
||||||
scriptInfo := b.scriptResolver.ResolveScript(ctx, pkg)
|
scriptInfo := b.scriptResolver.ResolveScript(ctx, pkg)
|
||||||
buildInput := &BuildInput{
|
buildInput := &BuildInput{
|
||||||
script: scriptInfo.Script,
|
script: scriptInfo.Script,
|
||||||
repository: scriptInfo.Repository,
|
repository: scriptInfo.Repository,
|
||||||
packages: allSubpkgs,
|
packages: packagesToBuilt,
|
||||||
pkgFormat: input.PkgFormat(),
|
pkgFormat: input.PkgFormat(),
|
||||||
opts: input.BuildOpts(),
|
opts: input.BuildOpts(),
|
||||||
info: input.OSRelease(),
|
info: input.OSRelease(),
|
||||||
}
|
}
|
||||||
|
|
||||||
cachedDeps, allInCache, err := b.checkCacheForAllSubpackages(ctx, buildInput, basePkgName, allSubpkgs)
|
cachedDeps, allInCache, err := b.checkCacheForAllSubpackages(ctx, buildInput, basePkgName, packagesToBuilt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if allInCache {
|
if allInCache {
|
||||||
// Все подпакеты в кеше, используем их
|
// Подпакет в кеше, используем его
|
||||||
|
slog.Info("DEBUG: using cached package", "pkgName", pkgName)
|
||||||
buildDeps = append(buildDeps, cachedDeps...)
|
buildDeps = append(buildDeps, cachedDeps...)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Собираем пакет (без рекурсивной сборки зависимостей, так как они уже собраны)
|
slog.Info("DEBUG: building package", "pkgName", pkgName)
|
||||||
|
|
||||||
|
// Собираем только запрошенный подпакет
|
||||||
|
// SkipDepsBuilding: true предотвращает рекурсивный вызов BuildALRDeps
|
||||||
res, err := b.BuildPackageFromDb(
|
res, err := b.BuildPackageFromDb(
|
||||||
ctx,
|
ctx,
|
||||||
&BuildPackageFromDbArgs{
|
&BuildPackageFromDbArgs{
|
||||||
Package: pkg,
|
Package: pkg,
|
||||||
Packages: allSubpkgs,
|
Packages: packagesToBuilt,
|
||||||
BuildArgs: BuildArgs{
|
BuildArgs: BuildArgs{
|
||||||
Opts: input.BuildOpts(),
|
Opts: input.BuildOpts(),
|
||||||
Info: input.OSRelease(),
|
Info: input.OSRelease(),
|
||||||
PkgFormat_: input.PkgFormat(),
|
PkgFormat_: input.PkgFormat(),
|
||||||
|
SkipDepsBuilding: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -832,8 +899,9 @@ func (i *Builder) InstallPkgs(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Отслеживание установки пакетов из репозитория
|
_ = i.installerExecutor.CheckVersionsAfterInstall(ctx, repoDeps)
|
||||||
|
|
||||||
for _, pkg := range repoDeps {
|
for _, pkg := range repoDeps {
|
||||||
if stats.ShouldTrackPackage(pkg) {
|
if stats.ShouldTrackPackage(pkg) {
|
||||||
stats.TrackInstallation(ctx, pkg, "install")
|
stats.TrackInstallation(ctx, pkg, "install")
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,6 +28,7 @@ import (
|
|||||||
type DependencyNode struct {
|
type DependencyNode struct {
|
||||||
Package *alrsh.Package
|
Package *alrsh.Package
|
||||||
BasePkgName string
|
BasePkgName string
|
||||||
|
PkgName string // Имя конкретного подпакета (может отличаться от BasePkgName)
|
||||||
Dependencies []string // Имена зависимостей
|
Dependencies []string // Имена зависимостей
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +45,12 @@ func (b *Builder) ResolveDependencyTree(
|
|||||||
) (map[string]*DependencyNode, []string, error) {
|
) (map[string]*DependencyNode, []string, error) {
|
||||||
resolved := make(map[string]*DependencyNode)
|
resolved := make(map[string]*DependencyNode)
|
||||||
visited := make(map[string]bool)
|
visited := make(map[string]bool)
|
||||||
systemDeps := make(map[string]bool) // Для дедупликации системных зависимостей
|
systemDeps := make(map[string]bool)
|
||||||
|
|
||||||
|
overrideNames, err := overrides.Resolve(input.OSRelease(), overrides.DefaultOpts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to resolve overrides: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
var resolve func(pkgNames []string) error
|
var resolve func(pkgNames []string) error
|
||||||
resolve = func(pkgNames []string) error {
|
resolve = func(pkgNames []string) error {
|
||||||
@@ -76,19 +83,17 @@ func (b *Builder) ResolveDependencyTree(
|
|||||||
|
|
||||||
pkg := pkgList[0]
|
pkg := pkgList[0]
|
||||||
|
|
||||||
// Определяем базовое имя пакета
|
alrsh.ResolvePackage(&pkg, overrideNames)
|
||||||
|
|
||||||
baseName := pkg.BasePkgName
|
baseName := pkg.BasePkgName
|
||||||
if baseName == "" {
|
if baseName == "" {
|
||||||
baseName = pkg.Name
|
baseName = pkg.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если уже обработали этот базовый пакет, пропускаем
|
if resolved[pkgName] != nil {
|
||||||
if resolved[baseName] != nil {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получаем зависимости для этого дистрибутива
|
|
||||||
// Пакет из БД уже содержит разрешенные значения для текущего дистрибутива
|
|
||||||
deps := pkg.Depends.Resolved()
|
deps := pkg.Depends.Resolved()
|
||||||
buildDeps := pkg.BuildDepends.Resolved()
|
buildDeps := pkg.BuildDepends.Resolved()
|
||||||
|
|
||||||
@@ -96,10 +101,11 @@ func (b *Builder) ResolveDependencyTree(
|
|||||||
allDeps := append([]string{}, deps...)
|
allDeps := append([]string{}, deps...)
|
||||||
allDeps = append(allDeps, buildDeps...)
|
allDeps = append(allDeps, buildDeps...)
|
||||||
|
|
||||||
// Добавляем узел в resolved
|
// Добавляем узел в resolved с ключом = имя подпакета
|
||||||
resolved[baseName] = &DependencyNode{
|
resolved[pkgName] = &DependencyNode{
|
||||||
Package: &pkg,
|
Package: &pkg,
|
||||||
BasePkgName: baseName,
|
BasePkgName: baseName,
|
||||||
|
PkgName: pkgName,
|
||||||
Dependencies: allDeps,
|
Dependencies: allDeps,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,8 +135,8 @@ func (b *Builder) ResolveDependencyTree(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TopologicalSort выполняет топологическую сортировку пакетов по зависимостям
|
// TopologicalSort выполняет топологическую сортировку пакетов по зависимостям
|
||||||
// Возвращает список базовых имен пакетов в порядке сборки (от корней к листьям)
|
// Возвращает список имен подпакетов в порядке сборки (от корней к листьям)
|
||||||
func TopologicalSort(nodes map[string]*DependencyNode, allPkgs map[string][]alrsh.Package) ([]string, error) {
|
func TopologicalSort(nodes map[string]*DependencyNode) ([]string, error) {
|
||||||
// Список для результата
|
// Список для результата
|
||||||
var result []string
|
var result []string
|
||||||
|
|
||||||
@@ -140,51 +146,42 @@ func TopologicalSort(nodes map[string]*DependencyNode, allPkgs map[string][]alrs
|
|||||||
// Множество узлов в текущем пути (для обнаружения циклов)
|
// Множество узлов в текущем пути (для обнаружения циклов)
|
||||||
inStack := make(map[string]bool)
|
inStack := make(map[string]bool)
|
||||||
|
|
||||||
var visit func(basePkgName string) error
|
var visit func(pkgName string) error
|
||||||
visit = func(basePkgName string) error {
|
visit = func(pkgName string) error {
|
||||||
if visited[basePkgName] {
|
if visited[pkgName] {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if inStack[basePkgName] {
|
if inStack[pkgName] {
|
||||||
return fmt.Errorf("circular dependency detected: %s", basePkgName)
|
return fmt.Errorf("circular dependency detected: %s", pkgName)
|
||||||
}
|
}
|
||||||
|
|
||||||
node := nodes[basePkgName]
|
node := nodes[pkgName]
|
||||||
if node == nil {
|
if node == nil {
|
||||||
// Это системный пакет, игнорируем
|
// Это системный пакет или пакет не в дереве, игнорируем
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
inStack[basePkgName] = true
|
inStack[pkgName] = true
|
||||||
|
|
||||||
// Посещаем все зависимости
|
// Посещаем все зависимости
|
||||||
for _, dep := range node.Dependencies {
|
for _, dep := range node.Dependencies {
|
||||||
// Находим базовое имя для зависимости
|
// Используем имя зависимости напрямую (это имя подпакета)
|
||||||
depBaseName := dep
|
if err := visit(dep); err != nil {
|
||||||
|
|
||||||
// Проверяем, есть ли этот пакет в allPkgs
|
|
||||||
if pkgs, ok := allPkgs[dep]; ok && len(pkgs) > 0 {
|
|
||||||
if pkgs[0].BasePkgName != "" {
|
|
||||||
depBaseName = pkgs[0].BasePkgName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := visit(depBaseName); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inStack[basePkgName] = false
|
inStack[pkgName] = false
|
||||||
visited[basePkgName] = true
|
visited[pkgName] = true
|
||||||
result = append(result, basePkgName)
|
result = append(result, pkgName)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Посещаем все узлы
|
// Посещаем все узлы
|
||||||
for basePkgName := range nodes {
|
for pkgName := range nodes {
|
||||||
if err := visit(basePkgName); err != nil {
|
if err := visit(pkgName); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ package finddeps
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
"github.com/goreleaser/nfpm/v2"
|
"github.com/goreleaser/nfpm/v2"
|
||||||
|
|
||||||
@@ -39,10 +41,9 @@ func New(info *distro.OSRelease, pkgFormat string) *ProvReqService {
|
|||||||
finder: &EmptyFindProvReq{},
|
finder: &EmptyFindProvReq{},
|
||||||
}
|
}
|
||||||
if pkgFormat == "rpm" {
|
if pkgFormat == "rpm" {
|
||||||
switch info.ID {
|
if _, err := os.Stat("/usr/lib/rpm/find-provides"); err == nil {
|
||||||
case "altlinux":
|
|
||||||
s.finder = &ALTLinuxFindProvReq{}
|
s.finder = &ALTLinuxFindProvReq{}
|
||||||
case "fedora":
|
} else if _, err := exec.LookPath("/usr/lib/rpm/rpmdeps"); err == nil {
|
||||||
s.finder = &FedoraFindProvReq{}
|
s.finder = &FedoraFindProvReq{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import (
|
|||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/depver"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -43,7 +44,14 @@ func (i *Installer) InstallLocal(ctx context.Context, paths []string, opts *mana
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *Installer) Install(ctx context.Context, pkgs []string, opts *manager.Opts) error {
|
func (i *Installer) Install(ctx context.Context, pkgs []string, opts *manager.Opts) error {
|
||||||
return i.mgr.Install(opts, pkgs...)
|
// Convert dependencies to manager-specific format
|
||||||
|
converted := make([]string, len(pkgs))
|
||||||
|
for idx, pkg := range pkgs {
|
||||||
|
dep := depver.Parse(pkg)
|
||||||
|
converted[idx] = dep.ForManager(i.mgr.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
return i.mgr.Install(opts, converted...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Installer) Remove(ctx context.Context, pkgs []string, opts *manager.Opts) error {
|
func (i *Installer) Remove(ctx context.Context, pkgs []string, opts *manager.Opts) error {
|
||||||
@@ -54,19 +62,71 @@ func (i *Installer) RemoveAlreadyInstalled(ctx context.Context, pkgs []string) (
|
|||||||
filteredPackages := []string{}
|
filteredPackages := []string{}
|
||||||
|
|
||||||
for _, dep := range pkgs {
|
for _, dep := range pkgs {
|
||||||
installed, err := i.mgr.IsInstalled(dep)
|
parsed := depver.Parse(dep)
|
||||||
|
|
||||||
|
// Check if package is installed
|
||||||
|
installed, err := i.mgr.IsInstalled(parsed.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if installed {
|
|
||||||
|
if !installed {
|
||||||
|
filteredPackages = append(filteredPackages, dep)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
filteredPackages = append(filteredPackages, dep)
|
|
||||||
|
// If there's a version constraint, check if installed version satisfies it
|
||||||
|
if parsed.HasVersionConstraint() {
|
||||||
|
installedVer, err := i.mgr.GetInstalledVersion(parsed.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !parsed.Satisfies(installedVer) {
|
||||||
|
// Installed version doesn't satisfy constraint - need to upgrade
|
||||||
|
slog.Debug("installed version doesn't satisfy constraint",
|
||||||
|
"package", parsed.Name,
|
||||||
|
"required", dep,
|
||||||
|
"installed", installedVer)
|
||||||
|
filteredPackages = append(filteredPackages, dep)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return filteredPackages, nil
|
return filteredPackages, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Installer) CheckVersionsAfterInstall(ctx context.Context, pkgs []string) error {
|
||||||
|
for _, pkg := range pkgs {
|
||||||
|
parsed := depver.Parse(pkg)
|
||||||
|
if !parsed.HasVersionConstraint() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
installedVer, err := i.mgr.GetInstalledVersion(parsed.Name)
|
||||||
|
if err != nil {
|
||||||
|
slog.Warn(gotext.Get("Failed to get installed version"),
|
||||||
|
"package", parsed.Name,
|
||||||
|
"error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if installedVer == "" {
|
||||||
|
slog.Warn(gotext.Get("Package was not installed"),
|
||||||
|
"package", parsed.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !parsed.Satisfies(installedVer) {
|
||||||
|
slog.Warn(gotext.Get("Installed version doesn't satisfy requirement"),
|
||||||
|
"package", parsed.Name,
|
||||||
|
"required", pkg,
|
||||||
|
"installed", installedVer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (i *Installer) FilterPackagesByVersion(ctx context.Context, packages []alrsh.Package, osRelease *distro.OSRelease) ([]alrsh.Package, error) {
|
func (i *Installer) FilterPackagesByVersion(ctx context.Context, packages []alrsh.Package, osRelease *distro.OSRelease) ([]alrsh.Package, error) {
|
||||||
installedPkgs, err := i.mgr.ListInstalled(nil)
|
installedPkgs, err := i.mgr.ListInstalled(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ type InstallerExecutor interface {
|
|||||||
Remove(ctx context.Context, pkgs []string, opts *manager.Opts) error
|
Remove(ctx context.Context, pkgs []string, opts *manager.Opts) error
|
||||||
RemoveAlreadyInstalled(ctx context.Context, pkgs []string) ([]string, error)
|
RemoveAlreadyInstalled(ctx context.Context, pkgs []string) ([]string, error)
|
||||||
FilterPackagesByVersion(ctx context.Context, packages []alrsh.Package, osRelease *distro.OSRelease) ([]alrsh.Package, error)
|
FilterPackagesByVersion(ctx context.Context, packages []alrsh.Package, osRelease *distro.OSRelease) ([]alrsh.Package, error)
|
||||||
|
CheckVersionsAfterInstall(ctx context.Context, pkgs []string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type ScriptExecutor interface {
|
type ScriptExecutor interface {
|
||||||
|
|||||||
@@ -238,6 +238,33 @@ func (s *InstallerExecutorRPCServer) FilterPackagesByVersion(args *InstallerExec
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InstallerExecutorCheckVersionsAfterInstallArgs struct {
|
||||||
|
Pkgs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type InstallerExecutorCheckVersionsAfterInstallResp struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InstallerExecutorRPC) CheckVersionsAfterInstall(ctx context.Context, pkgs []string) error {
|
||||||
|
var resp *InstallerExecutorCheckVersionsAfterInstallResp
|
||||||
|
err := s.client.Call("Plugin.CheckVersionsAfterInstall", &InstallerExecutorCheckVersionsAfterInstallArgs{
|
||||||
|
Pkgs: pkgs,
|
||||||
|
}, &resp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InstallerExecutorRPCServer) CheckVersionsAfterInstall(args *InstallerExecutorCheckVersionsAfterInstallArgs, resp *InstallerExecutorCheckVersionsAfterInstallResp) error {
|
||||||
|
err := s.Impl.CheckVersionsAfterInstall(context.Background(), args.Pkgs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*resp = InstallerExecutorCheckVersionsAfterInstallResp{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type ScriptExecutorReadScriptArgs struct {
|
type ScriptExecutorReadScriptArgs struct {
|
||||||
ScriptPath string
|
ScriptPath string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ func defaultConfigKoanf() *koanf.Koanf {
|
|||||||
"logLevel": "info",
|
"logLevel": "info",
|
||||||
"autoPull": true,
|
"autoPull": true,
|
||||||
"updateSystemOnUpgrade": false,
|
"updateSystemOnUpgrade": false,
|
||||||
"repos": []types.Repo{
|
"repo": []types.Repo{
|
||||||
{
|
{
|
||||||
Name: "alr-default",
|
Name: "alr-default",
|
||||||
URL: "https://gitea.plemya-x.ru/Plemya-x/alr-default.git",
|
URL: "https://gitea.plemya-x.ru/Plemya-x/alr-default.git",
|
||||||
|
|||||||
@@ -167,3 +167,33 @@ func (a *APK) IsInstalled(pkg string) (bool, error) {
|
|||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *APK) GetInstalledVersion(pkg string) (string, error) {
|
||||||
|
cmd := exec.Command("apk", "info", "--installed", pkg)
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||||
|
// Exit code 1 means the package is not installed
|
||||||
|
if exitErr.ExitCode() == 1 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("apk: getinstalledversion: %w, output: %s", err, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output format: "package-version" (e.g., "curl-8.5.0-r0")
|
||||||
|
// We need to extract just the version part
|
||||||
|
line := strings.TrimSpace(string(output))
|
||||||
|
if line == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the last hyphen that separates name from version
|
||||||
|
// Alpine package names can contain hyphens, version starts after last one
|
||||||
|
lastDash := strings.LastIndex(line, "-")
|
||||||
|
if lastDash == -1 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return line[lastDash+1:], nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -82,8 +82,15 @@ func (a *APT) InstallLocal(opts *Opts, pkgs ...string) error {
|
|||||||
|
|
||||||
func (a *APT) Remove(opts *Opts, pkgs ...string) error {
|
func (a *APT) Remove(opts *Opts, pkgs ...string) error {
|
||||||
opts = ensureOpts(opts)
|
opts = ensureOpts(opts)
|
||||||
|
|
||||||
|
resolvedPkgs := make([]string, 0, len(pkgs))
|
||||||
|
for _, pkg := range pkgs {
|
||||||
|
resolved := a.resolvePackageName(pkg)
|
||||||
|
resolvedPkgs = append(resolvedPkgs, resolved)
|
||||||
|
}
|
||||||
|
|
||||||
cmd := a.getCmd(opts, "apt", "remove")
|
cmd := a.getCmd(opts, "apt", "remove")
|
||||||
cmd.Args = append(cmd.Args, pkgs...)
|
cmd.Args = append(cmd.Args, resolvedPkgs...)
|
||||||
setCmdEnv(cmd)
|
setCmdEnv(cmd)
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -92,6 +99,39 @@ func (a *APT) Remove(opts *Opts, pkgs ...string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *APT) resolvePackageName(pkg string) string {
|
||||||
|
cmd := exec.Command("dpkg-query", "-f", "${Status}", "-W", pkg)
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err == nil && strings.Contains(string(output), "install ok installed") {
|
||||||
|
return pkg
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd = exec.Command("dpkg-query", "-W", "-f", "${Package}\t${Provides}\n")
|
||||||
|
output, err = cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return pkg
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, line := range strings.Split(string(output), "\n") {
|
||||||
|
parts := strings.SplitN(line, "\t", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pkgName := parts[0]
|
||||||
|
provides := parts[1]
|
||||||
|
|
||||||
|
for _, p := range strings.Split(provides, ", ") {
|
||||||
|
p = strings.TrimSpace(p)
|
||||||
|
provName := strings.Split(p, " ")[0]
|
||||||
|
if provName == pkg {
|
||||||
|
return pkgName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pkg
|
||||||
|
}
|
||||||
|
|
||||||
func (a *APT) Upgrade(opts *Opts, pkgs ...string) error {
|
func (a *APT) Upgrade(opts *Opts, pkgs ...string) error {
|
||||||
opts = ensureOpts(opts)
|
opts = ensureOpts(opts)
|
||||||
return a.Install(opts, pkgs...)
|
return a.Install(opts, pkgs...)
|
||||||
@@ -140,11 +180,11 @@ func (a *APT) ListInstalled(opts *Opts) (map[string]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *APT) IsInstalled(pkg string) (bool, error) {
|
func (a *APT) IsInstalled(pkg string) (bool, error) {
|
||||||
cmd := exec.Command("dpkg-query", "-f", "${Status}", "-W", pkg)
|
resolved := a.resolvePackageName(pkg)
|
||||||
|
cmd := exec.Command("dpkg-query", "-f", "${Status}", "-W", resolved)
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||||
// Код выхода 1 означает что пакет не найден
|
|
||||||
if exitErr.ExitCode() == 1 {
|
if exitErr.ExitCode() == 1 {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
@@ -153,6 +193,21 @@ func (a *APT) IsInstalled(pkg string) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
status := strings.TrimSpace(string(output))
|
status := strings.TrimSpace(string(output))
|
||||||
// Проверяем что пакет действительно установлен (статус должен содержать "install ok installed")
|
|
||||||
return strings.Contains(status, "install ok installed"), nil
|
return strings.Contains(status, "install ok installed"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *APT) GetInstalledVersion(pkg string) (string, error) {
|
||||||
|
resolved := a.resolvePackageName(pkg)
|
||||||
|
cmd := exec.Command("dpkg-query", "-f", "${Version}", "-W", resolved)
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||||
|
if exitErr.ExitCode() == 1 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("apt: getinstalledversion: %w, output: %s", err, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.TrimSpace(string(output)), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -70,3 +70,21 @@ func (a *CommonRPM) IsInstalled(pkg string) (bool, error) {
|
|||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *CommonRPM) GetInstalledVersion(pkg string) (string, error) {
|
||||||
|
cmd := exec.Command("rpm", "-q", "--queryformat", "%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}", pkg)
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||||
|
if exitErr.ExitCode() == 1 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("rpm: getinstalledversion: %w, output: %s", err, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
version := strings.TrimSpace(string(output))
|
||||||
|
// Remove epoch 0: prefix if present
|
||||||
|
version = strings.TrimPrefix(version, "0:")
|
||||||
|
return version, nil
|
||||||
|
}
|
||||||
|
|||||||
59
internal/manager/manager_test.go
Normal file
59
internal/manager/manager_test.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
// ALR - Any Linux Repository
|
||||||
|
// Copyright (C) 2025 The ALR Authors
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package manager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewZypperReturnsCorrectType(t *testing.T) {
|
||||||
|
z := NewZypper()
|
||||||
|
if z == nil {
|
||||||
|
t.Fatal("NewZypper() returned nil")
|
||||||
|
}
|
||||||
|
if z.Name() != "zypper" {
|
||||||
|
t.Errorf("Expected name 'zypper', got '%s'", z.Name())
|
||||||
|
}
|
||||||
|
if z.Format() != "rpm" {
|
||||||
|
t.Errorf("Expected format 'rpm', got '%s'", z.Format())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManagersOrder(t *testing.T) {
|
||||||
|
// Проверяем, что APT-RPM идёт раньше APT в списке менеджеров
|
||||||
|
aptRpmIndex := -1
|
||||||
|
aptIndex := -1
|
||||||
|
|
||||||
|
for i, m := range managers {
|
||||||
|
switch m.Name() {
|
||||||
|
case "apt-rpm":
|
||||||
|
aptRpmIndex = i
|
||||||
|
case "apt":
|
||||||
|
aptIndex = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if aptRpmIndex == -1 {
|
||||||
|
t.Fatal("APT-RPM not found in managers list")
|
||||||
|
}
|
||||||
|
if aptIndex == -1 {
|
||||||
|
t.Fatal("APT not found in managers list")
|
||||||
|
}
|
||||||
|
if aptRpmIndex >= aptIndex {
|
||||||
|
t.Errorf("APT-RPM (index %d) should come before APT (index %d)", aptRpmIndex, aptIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,12 +37,12 @@ var DefaultOpts = &Opts{
|
|||||||
|
|
||||||
var managers = []Manager{
|
var managers = []Manager{
|
||||||
NewPacman(),
|
NewPacman(),
|
||||||
|
NewAPTRpm(), // APT-RPM должен проверяться раньше APT, т.к. на ALT Linux есть оба
|
||||||
NewAPT(),
|
NewAPT(),
|
||||||
NewDNF(),
|
NewDNF(),
|
||||||
NewYUM(),
|
NewYUM(),
|
||||||
NewAPK(),
|
NewAPK(),
|
||||||
NewZypper(),
|
NewZypper(),
|
||||||
NewAPTRpm(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register registers a new package manager
|
// Register registers a new package manager
|
||||||
@@ -74,8 +74,11 @@ type Manager interface {
|
|||||||
UpgradeAll(*Opts) error
|
UpgradeAll(*Opts) error
|
||||||
// ListInstalled returns all installed packages mapped to their versions
|
// ListInstalled returns all installed packages mapped to their versions
|
||||||
ListInstalled(*Opts) (map[string]string, error)
|
ListInstalled(*Opts) (map[string]string, error)
|
||||||
//
|
// IsInstalled checks if a package is installed
|
||||||
IsInstalled(string) (bool, error)
|
IsInstalled(string) (bool, error)
|
||||||
|
// GetInstalledVersion returns the version of an installed package.
|
||||||
|
// Returns empty string and no error if package is not installed.
|
||||||
|
GetInstalledVersion(string) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect returns the package manager detected on the system
|
// Detect returns the package manager detected on the system
|
||||||
|
|||||||
@@ -160,3 +160,24 @@ func (p *Pacman) IsInstalled(pkg string) (bool, error) {
|
|||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Pacman) GetInstalledVersion(pkg string) (string, error) {
|
||||||
|
cmd := exec.Command("pacman", "-Q", pkg)
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
// Pacman returns exit code 1 if the package is not found
|
||||||
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||||
|
if exitErr.ExitCode() == 1 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("pacman: getinstalledversion: %w, output: %s", err, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output format: "package-name version"
|
||||||
|
_, version, ok := strings.Cut(strings.TrimSpace(string(output)), " ")
|
||||||
|
if !ok {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return version, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ type Zypper struct {
|
|||||||
CommonRPM
|
CommonRPM
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewZypper() *YUM {
|
func NewZypper() *Zypper {
|
||||||
return &YUM{
|
return &Zypper{
|
||||||
CommonPackageManager: CommonPackageManager{
|
CommonPackageManager: CommonPackageManager{
|
||||||
noConfirmArg: "-y",
|
noConfirmArg: "-y",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/depver"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]alrsh.Package, []string, error) {
|
func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]alrsh.Package, []string, error) {
|
||||||
@@ -36,39 +37,49 @@ func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]alrs
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse version constraint from package name
|
||||||
|
dep := depver.Parse(pkgName)
|
||||||
|
searchName := dep.Name
|
||||||
|
|
||||||
var result []alrsh.Package
|
var result []alrsh.Package
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case strings.Contains(pkgName, "/"):
|
case strings.Contains(searchName, "/"):
|
||||||
// repo/pkg
|
// repo/pkg
|
||||||
parts := strings.SplitN(pkgName, "/", 2)
|
parts := strings.SplitN(searchName, "/", 2)
|
||||||
repo := parts[0]
|
repo := parts[0]
|
||||||
name := parts[1]
|
name := parts[1]
|
||||||
result, err = rs.db.GetPkgs(ctx, "name = ? AND repository = ?", name, repo)
|
result, err = rs.db.GetPkgs(ctx, "name = ? AND repository = ?", name, repo)
|
||||||
|
|
||||||
case strings.Contains(pkgName, "+"):
|
case strings.Contains(searchName, "+"):
|
||||||
// pkg+repo
|
// pkg+repo
|
||||||
parts := strings.SplitN(pkgName, "+", 2)
|
parts := strings.SplitN(searchName, "+", 2)
|
||||||
name := parts[0]
|
name := parts[0]
|
||||||
repo := parts[1]
|
repo := parts[1]
|
||||||
result, err = rs.db.GetPkgs(ctx, "name = ? AND repository = ?", name, repo)
|
result, err = rs.db.GetPkgs(ctx, "name = ? AND repository = ?", name, repo)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
result, err = rs.db.GetPkgs(ctx, "json_array_contains(provides, ?)", pkgName)
|
// Сначала ищем по точному имени пакета
|
||||||
|
result, err = rs.db.GetPkgs(ctx, "name = ?", searchName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("FindPkgs: get by provides: %w", err)
|
return nil, nil, fmt.Errorf("FindPkgs: get by name: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Затем по provides
|
||||||
if len(result) == 0 {
|
if len(result) == 0 {
|
||||||
result, err = rs.db.GetPkgs(ctx, "basepkg_name = ?", pkgName)
|
result, err = rs.db.GetPkgs(ctx, "json_array_contains(provides, ?)", searchName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("FindPkgs: get by basepkg_name: %w", err)
|
return nil, nil, fmt.Errorf("FindPkgs: get by provides: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// В последнюю очередь по basepkg_name (для мультипакетов)
|
||||||
if len(result) == 0 {
|
if len(result) == 0 {
|
||||||
result, err = rs.db.GetPkgs(ctx, "name LIKE ?", pkgName)
|
result, err = rs.db.GetPkgs(ctx, "basepkg_name = ?", searchName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("FindPkgs: get by basepkg_name: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,6 +87,11 @@ func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]alrs
|
|||||||
return nil, nil, fmt.Errorf("FindPkgs: lookup for %q failed: %w", pkgName, err)
|
return nil, nil, fmt.Errorf("FindPkgs: lookup for %q failed: %w", pkgName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter by version if constraint is specified
|
||||||
|
if dep.HasVersionConstraint() && len(result) > 0 {
|
||||||
|
result = filterByVersion(result, dep)
|
||||||
|
}
|
||||||
|
|
||||||
if len(result) == 0 {
|
if len(result) == 0 {
|
||||||
notFound = append(notFound, pkgName)
|
notFound = append(notFound, pkgName)
|
||||||
} else {
|
} else {
|
||||||
@@ -85,3 +101,14 @@ func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]alrs
|
|||||||
|
|
||||||
return found, notFound, nil
|
return found, notFound, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// filterByVersion filters packages by version constraint.
|
||||||
|
func filterByVersion(pkgs []alrsh.Package, dep depver.Dependency) []alrsh.Package {
|
||||||
|
var filtered []alrsh.Package
|
||||||
|
for _, pkg := range pkgs {
|
||||||
|
if dep.Satisfies(pkg.Version) {
|
||||||
|
filtered = append(filtered, pkg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filtered
|
||||||
|
}
|
||||||
|
|||||||
@@ -71,6 +71,10 @@ func New(info *distro.OSRelease, runner *interp.Runner) *Decoder {
|
|||||||
return &Decoder{info, runner, true, len(info.Like) > 0}
|
return &Decoder{info, runner, true, len(info.Like) > 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Decoder) Info() *distro.OSRelease {
|
||||||
|
return d.info
|
||||||
|
}
|
||||||
|
|
||||||
// DecodeVar decodes a variable to val using reflection.
|
// DecodeVar decodes a variable to val using reflection.
|
||||||
// Structs should use the "sh" struct tag.
|
// Structs should use the "sh" struct tag.
|
||||||
func (d *Decoder) DecodeVar(name string, val any) error {
|
func (d *Decoder) DecodeVar(name string, val any) error {
|
||||||
|
|||||||
5
main.go
5
main.go
@@ -141,7 +141,6 @@ func setLogLevel(newLevel string) {
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
logger.SetupDefault()
|
logger.SetupDefault()
|
||||||
setLogLevel(os.Getenv("ALR_LOG_LEVEL"))
|
|
||||||
translations.Setup()
|
translations.Setup()
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
@@ -154,6 +153,10 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
setLogLevel(cfg.LogLevel())
|
setLogLevel(cfg.LogLevel())
|
||||||
|
// Переменная окружения имеет приоритет над конфигом
|
||||||
|
if envLevel := os.Getenv("ALR_LOG_LEVEL"); envLevel != "" {
|
||||||
|
setLogLevel(envLevel)
|
||||||
|
}
|
||||||
|
|
||||||
ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
|
ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -171,7 +172,25 @@ func (s *ScriptFile) createPackageFromMeta(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
metaDecoder := decoder.New(&distro.OSRelease{}, metaRunner)
|
// DEBUG: Выводим что в metaRunner.Vars и dec.Runner.Vars для deps_debian
|
||||||
|
if depsDebianMeta, ok := metaRunner.Vars["deps_debian"]; ok {
|
||||||
|
slog.Info("DEBUG createPackageFromMeta: metaRunner.Vars[deps_debian]", "value", depsDebianMeta.String(), "list", depsDebianMeta.List)
|
||||||
|
} else {
|
||||||
|
slog.Info("DEBUG createPackageFromMeta: metaRunner.Vars[deps_debian] NOT FOUND")
|
||||||
|
}
|
||||||
|
if depsDebianParent, ok := dec.Runner.Vars["deps_debian"]; ok {
|
||||||
|
slog.Info("DEBUG createPackageFromMeta: parent Vars[deps_debian]", "value", depsDebianParent.String(), "list", depsDebianParent.List)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сливаем переменные родительского runner'а с переменными мета-функции.
|
||||||
|
// Переменные мета-функции имеют приоритет (для случаев переопределения).
|
||||||
|
for name, val := range dec.Runner.Vars {
|
||||||
|
if _, exists := metaRunner.Vars[name]; !exists {
|
||||||
|
metaRunner.Vars[name] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
metaDecoder := decoder.New(dec.Info(), metaRunner)
|
||||||
|
|
||||||
var vars Package
|
var vars Package
|
||||||
if err := metaDecoder.DecodeVars(&vars); err != nil {
|
if err := metaDecoder.DecodeVars(&vars); err != nil {
|
||||||
|
|||||||
137
pkg/depver/depver.go
Normal file
137
pkg/depver/depver.go
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
// ALR - Any Linux Repository
|
||||||
|
// Copyright (C) 2025 The ALR Authors
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Package depver provides parsing and comparison of versioned dependencies
|
||||||
|
// in PKGBUILD-style format (e.g., "gcc>=5.0", "openssl>=1.1.0").
|
||||||
|
package depver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gitea.plemya-x.ru/xpamych/vercmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Operator represents a version comparison operator.
|
||||||
|
type Operator string
|
||||||
|
|
||||||
|
const (
|
||||||
|
OpNone Operator = "" // No version constraint
|
||||||
|
OpEq Operator = "=" // Equal to
|
||||||
|
OpGt Operator = ">" // Greater than
|
||||||
|
OpGe Operator = ">=" // Greater than or equal to
|
||||||
|
OpLt Operator = "<" // Less than
|
||||||
|
OpLe Operator = "<=" // Less than or equal to
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dependency represents a package dependency with optional version constraint.
|
||||||
|
type Dependency struct {
|
||||||
|
Name string // Package name (e.g., "gcc")
|
||||||
|
Operator Operator // Comparison operator (e.g., OpGe for ">=")
|
||||||
|
Version string // Version string (e.g., "5.0")
|
||||||
|
}
|
||||||
|
|
||||||
|
// operators lists all supported operators in order of decreasing length
|
||||||
|
// (to ensure ">=" is matched before ">").
|
||||||
|
var operators = []Operator{OpGe, OpLe, OpGt, OpLt, OpEq}
|
||||||
|
|
||||||
|
// Parse parses a dependency string in PKGBUILD format.
|
||||||
|
// Examples:
|
||||||
|
// - "gcc>=5.0" -> Dependency{Name: "gcc", Operator: OpGe, Version: "5.0"}
|
||||||
|
// - "openssl" -> Dependency{Name: "openssl", Operator: OpNone, Version: ""}
|
||||||
|
// - "cmake>=3.10" -> Dependency{Name: "cmake", Operator: OpGe, Version: "3.10"}
|
||||||
|
func Parse(dep string) Dependency {
|
||||||
|
dep = strings.TrimSpace(dep)
|
||||||
|
if dep == "" {
|
||||||
|
return Dependency{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try each operator (longer ones first)
|
||||||
|
for _, op := range operators {
|
||||||
|
if idx := strings.Index(dep, string(op)); idx > 0 {
|
||||||
|
return Dependency{
|
||||||
|
Name: strings.TrimSpace(dep[:idx]),
|
||||||
|
Operator: op,
|
||||||
|
Version: strings.TrimSpace(dep[idx+len(op):]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No operator found - just a package name
|
||||||
|
return Dependency{
|
||||||
|
Name: dep,
|
||||||
|
Operator: OpNone,
|
||||||
|
Version: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseMultiple parses multiple dependency strings.
|
||||||
|
func ParseMultiple(deps []string) []Dependency {
|
||||||
|
result := make([]Dependency, 0, len(deps))
|
||||||
|
for _, dep := range deps {
|
||||||
|
if dep != "" {
|
||||||
|
result = append(result, Parse(dep))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the dependency in PKGBUILD format.
|
||||||
|
func (d Dependency) String() string {
|
||||||
|
if d.Operator == OpNone || d.Version == "" {
|
||||||
|
return d.Name
|
||||||
|
}
|
||||||
|
return d.Name + string(d.Operator) + d.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
// Satisfies checks if the given version satisfies the dependency constraint.
|
||||||
|
// Returns true if:
|
||||||
|
// - The dependency has no version constraint (OpNone)
|
||||||
|
// - The installed version satisfies the operator/version requirement
|
||||||
|
func (d Dependency) Satisfies(installedVersion string) bool {
|
||||||
|
if d.Operator == OpNone || d.Version == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if installedVersion == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// vercmp.Compare returns:
|
||||||
|
// -1 if installedVersion < d.Version
|
||||||
|
// 0 if installedVersion == d.Version
|
||||||
|
// 1 if installedVersion > d.Version
|
||||||
|
cmp := vercmp.Compare(installedVersion, d.Version)
|
||||||
|
|
||||||
|
switch d.Operator {
|
||||||
|
case OpEq:
|
||||||
|
return cmp == 0
|
||||||
|
case OpGt:
|
||||||
|
return cmp > 0
|
||||||
|
case OpGe:
|
||||||
|
return cmp >= 0
|
||||||
|
case OpLt:
|
||||||
|
return cmp < 0
|
||||||
|
case OpLe:
|
||||||
|
return cmp <= 0
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasVersionConstraint returns true if the dependency has a version constraint.
|
||||||
|
func (d Dependency) HasVersionConstraint() bool {
|
||||||
|
return d.Operator != OpNone && d.Version != ""
|
||||||
|
}
|
||||||
347
pkg/depver/depver_test.go
Normal file
347
pkg/depver/depver_test.go
Normal file
@@ -0,0 +1,347 @@
|
|||||||
|
// ALR - Any Linux Repository
|
||||||
|
// Copyright (C) 2025 The ALR Authors
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package depver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParse(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expected Dependency
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "simple package name",
|
||||||
|
input: "gcc",
|
||||||
|
expected: Dependency{
|
||||||
|
Name: "gcc",
|
||||||
|
Operator: OpNone,
|
||||||
|
Version: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "greater or equal",
|
||||||
|
input: "gcc>=5.0",
|
||||||
|
expected: Dependency{
|
||||||
|
Name: "gcc",
|
||||||
|
Operator: OpGe,
|
||||||
|
Version: "5.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "less or equal",
|
||||||
|
input: "openssl<=1.1.0",
|
||||||
|
expected: Dependency{
|
||||||
|
Name: "openssl",
|
||||||
|
Operator: OpLe,
|
||||||
|
Version: "1.1.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "greater than",
|
||||||
|
input: "cmake>3.10",
|
||||||
|
expected: Dependency{
|
||||||
|
Name: "cmake",
|
||||||
|
Operator: OpGt,
|
||||||
|
Version: "3.10",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "less than",
|
||||||
|
input: "python<4.0",
|
||||||
|
expected: Dependency{
|
||||||
|
Name: "python",
|
||||||
|
Operator: OpLt,
|
||||||
|
Version: "4.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "equal",
|
||||||
|
input: "nodejs=18.0.0",
|
||||||
|
expected: Dependency{
|
||||||
|
Name: "nodejs",
|
||||||
|
Operator: OpEq,
|
||||||
|
Version: "18.0.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with spaces around",
|
||||||
|
input: " gcc>=5.0 ",
|
||||||
|
expected: Dependency{
|
||||||
|
Name: "gcc",
|
||||||
|
Operator: OpGe,
|
||||||
|
Version: "5.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "complex version",
|
||||||
|
input: "glibc>=2.17-326",
|
||||||
|
expected: Dependency{
|
||||||
|
Name: "glibc",
|
||||||
|
Operator: OpGe,
|
||||||
|
Version: "2.17-326",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty string",
|
||||||
|
input: "",
|
||||||
|
expected: Dependency{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := Parse(tt.input)
|
||||||
|
assert.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiple(t *testing.T) {
|
||||||
|
input := []string{"gcc>=5.0", "openssl", "cmake>=3.10", ""}
|
||||||
|
expected := []Dependency{
|
||||||
|
{Name: "gcc", Operator: OpGe, Version: "5.0"},
|
||||||
|
{Name: "openssl", Operator: OpNone, Version: ""},
|
||||||
|
{Name: "cmake", Operator: OpGe, Version: "3.10"},
|
||||||
|
}
|
||||||
|
|
||||||
|
result := ParseMultiple(input)
|
||||||
|
assert.Equal(t, expected, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDependency_String(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
dep Dependency
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no version",
|
||||||
|
dep: Dependency{Name: "gcc", Operator: OpNone, Version: ""},
|
||||||
|
expected: "gcc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with version",
|
||||||
|
dep: Dependency{Name: "gcc", Operator: OpGe, Version: "5.0"},
|
||||||
|
expected: "gcc>=5.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "equal operator",
|
||||||
|
dep: Dependency{Name: "python", Operator: OpEq, Version: "3.11"},
|
||||||
|
expected: "python=3.11",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := tt.dep.String()
|
||||||
|
assert.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDependency_Satisfies(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
dep Dependency
|
||||||
|
installedVersion string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no constraint - always satisfied",
|
||||||
|
dep: Dependency{Name: "gcc", Operator: OpNone, Version: ""},
|
||||||
|
installedVersion: "5.0",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ge - satisfied",
|
||||||
|
dep: Dependency{Name: "gcc", Operator: OpGe, Version: "5.0"},
|
||||||
|
installedVersion: "5.0",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ge - greater version satisfied",
|
||||||
|
dep: Dependency{Name: "gcc", Operator: OpGe, Version: "5.0"},
|
||||||
|
installedVersion: "6.0",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ge - not satisfied",
|
||||||
|
dep: Dependency{Name: "gcc", Operator: OpGe, Version: "5.0"},
|
||||||
|
installedVersion: "4.9",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gt - satisfied",
|
||||||
|
dep: Dependency{Name: "gcc", Operator: OpGt, Version: "5.0"},
|
||||||
|
installedVersion: "5.1",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gt - equal not satisfied",
|
||||||
|
dep: Dependency{Name: "gcc", Operator: OpGt, Version: "5.0"},
|
||||||
|
installedVersion: "5.0",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "le - satisfied",
|
||||||
|
dep: Dependency{Name: "gcc", Operator: OpLe, Version: "5.0"},
|
||||||
|
installedVersion: "5.0",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "le - lesser satisfied",
|
||||||
|
dep: Dependency{Name: "gcc", Operator: OpLe, Version: "5.0"},
|
||||||
|
installedVersion: "4.9",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "le - not satisfied",
|
||||||
|
dep: Dependency{Name: "gcc", Operator: OpLe, Version: "5.0"},
|
||||||
|
installedVersion: "5.1",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "lt - satisfied",
|
||||||
|
dep: Dependency{Name: "gcc", Operator: OpLt, Version: "5.0"},
|
||||||
|
installedVersion: "4.9",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "lt - equal not satisfied",
|
||||||
|
dep: Dependency{Name: "gcc", Operator: OpLt, Version: "5.0"},
|
||||||
|
installedVersion: "5.0",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "eq - satisfied",
|
||||||
|
dep: Dependency{Name: "gcc", Operator: OpEq, Version: "5.0"},
|
||||||
|
installedVersion: "5.0",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "eq - not satisfied",
|
||||||
|
dep: Dependency{Name: "gcc", Operator: OpEq, Version: "5.0"},
|
||||||
|
installedVersion: "5.1",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty installed version",
|
||||||
|
dep: Dependency{Name: "gcc", Operator: OpGe, Version: "5.0"},
|
||||||
|
installedVersion: "",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := tt.dep.Satisfies(tt.installedVersion)
|
||||||
|
assert.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDependency_ForManager(t *testing.T) {
|
||||||
|
dep := Dependency{Name: "gcc", Operator: OpGe, Version: "5.0"}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
manager string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"pacman", "gcc>=5.0"},
|
||||||
|
{"apt", "gcc"},
|
||||||
|
{"dnf", "gcc >= 5.0"},
|
||||||
|
{"yum", "gcc >= 5.0"},
|
||||||
|
{"apk", "gcc>=5.0"},
|
||||||
|
{"zypper", "gcc >= 5.0"},
|
||||||
|
{"apt-rpm", "gcc >= 5.0"},
|
||||||
|
{"unknown", "gcc>=5.0"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.manager, func(t *testing.T) {
|
||||||
|
result := dep.ForManager(tt.manager)
|
||||||
|
assert.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test without version constraint
|
||||||
|
depNoVersion := Dependency{Name: "gcc", Operator: OpNone, Version: ""}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.manager+"_no_version", func(t *testing.T) {
|
||||||
|
result := depNoVersion.ForManager(tt.manager)
|
||||||
|
assert.Equal(t, "gcc", result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDependency_ForNfpm(t *testing.T) {
|
||||||
|
dep := Dependency{Name: "gcc", Operator: OpGe, Version: "5.0"}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
format string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"deb", "gcc (>= 5.0)"},
|
||||||
|
{"rpm", "gcc >= 5.0"},
|
||||||
|
{"apk", "gcc>=5.0"},
|
||||||
|
{"archlinux", "gcc>=5.0"},
|
||||||
|
{"unknown", "gcc>=5.0"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.format, func(t *testing.T) {
|
||||||
|
result := dep.ForNfpm(tt.format)
|
||||||
|
assert.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHasVersionConstraint(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
dep Dependency
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "has constraint",
|
||||||
|
dep: Dependency{Name: "gcc", Operator: OpGe, Version: "5.0"},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no operator",
|
||||||
|
dep: Dependency{Name: "gcc", Operator: OpNone, Version: ""},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "operator but no version",
|
||||||
|
dep: Dependency{Name: "gcc", Operator: OpGe, Version: ""},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := tt.dep.HasVersionConstraint()
|
||||||
|
assert.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
132
pkg/depver/format.go
Normal file
132
pkg/depver/format.go
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
// ALR - Any Linux Repository
|
||||||
|
// Copyright (C) 2025 The ALR Authors
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package depver
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// ForManager formats the dependency for a specific package manager.
|
||||||
|
// Different package managers have different syntax for version constraints:
|
||||||
|
//
|
||||||
|
// pacman (Arch): "gcc>=5.0" (no changes)
|
||||||
|
// apt (Debian): "gcc" (version ignored for install command)
|
||||||
|
// dnf/yum (Fedora): "gcc >= 5.0" (with spaces)
|
||||||
|
// apk (Alpine): "gcc>=5.0" (no changes)
|
||||||
|
// zypper (openSUSE): "gcc >= 5.0" (with spaces)
|
||||||
|
// apt-rpm (ALT): "gcc >= 5.0" (with spaces)
|
||||||
|
func (d Dependency) ForManager(managerName string) string {
|
||||||
|
if d.Name == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// No version constraint - just return the name
|
||||||
|
if d.Operator == OpNone || d.Version == "" {
|
||||||
|
return d.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
switch managerName {
|
||||||
|
case "apt":
|
||||||
|
// APT doesn't support version constraints in 'apt install' command
|
||||||
|
// Versions are checked after installation
|
||||||
|
return d.Name
|
||||||
|
|
||||||
|
case "pacman":
|
||||||
|
// Pacman uses PKGBUILD-style: package>=version (no spaces)
|
||||||
|
return fmt.Sprintf("%s%s%s", d.Name, d.Operator, d.Version)
|
||||||
|
|
||||||
|
case "apk":
|
||||||
|
// Alpine APK uses similar syntax to pacman
|
||||||
|
return fmt.Sprintf("%s%s%s", d.Name, d.Operator, d.Version)
|
||||||
|
|
||||||
|
case "dnf", "yum":
|
||||||
|
// DNF/YUM use RPM-style: "package >= version" (with spaces)
|
||||||
|
return fmt.Sprintf("%s %s %s", d.Name, d.Operator, d.Version)
|
||||||
|
|
||||||
|
case "zypper":
|
||||||
|
// Zypper uses RPM-style with spaces
|
||||||
|
return fmt.Sprintf("%s %s %s", d.Name, d.Operator, d.Version)
|
||||||
|
|
||||||
|
case "apt-rpm":
|
||||||
|
// ALT Linux apt-rpm uses RPM-style
|
||||||
|
return fmt.Sprintf("%s %s %s", d.Name, d.Operator, d.Version)
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Default: PKGBUILD-style (no spaces)
|
||||||
|
return fmt.Sprintf("%s%s%s", d.Name, d.Operator, d.Version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForManagerMultiple formats multiple dependencies for a specific package manager.
|
||||||
|
func ForManagerMultiple(deps []Dependency, managerName string) []string {
|
||||||
|
result := make([]string, 0, len(deps))
|
||||||
|
for _, dep := range deps {
|
||||||
|
if formatted := dep.ForManager(managerName); formatted != "" {
|
||||||
|
result = append(result, formatted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForNfpm formats the dependency for nfpm package building.
|
||||||
|
// Different package formats have different dependency syntax:
|
||||||
|
//
|
||||||
|
// deb: "package (>= version)"
|
||||||
|
// rpm: "package >= version"
|
||||||
|
// apk: "package>=version"
|
||||||
|
// archlinux: "package>=version"
|
||||||
|
func (d Dependency) ForNfpm(pkgFormat string) string {
|
||||||
|
if d.Name == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// No version constraint - just return the name
|
||||||
|
if d.Operator == OpNone || d.Version == "" {
|
||||||
|
return d.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
switch pkgFormat {
|
||||||
|
case "deb":
|
||||||
|
// Debian uses: package (>= version)
|
||||||
|
return fmt.Sprintf("%s (%s %s)", d.Name, d.Operator, d.Version)
|
||||||
|
|
||||||
|
case "rpm":
|
||||||
|
// RPM uses: package >= version
|
||||||
|
return fmt.Sprintf("%s %s %s", d.Name, d.Operator, d.Version)
|
||||||
|
|
||||||
|
case "apk":
|
||||||
|
// Alpine uses: package>=version
|
||||||
|
return fmt.Sprintf("%s%s%s", d.Name, d.Operator, d.Version)
|
||||||
|
|
||||||
|
case "archlinux":
|
||||||
|
// Arch uses: package>=version
|
||||||
|
return fmt.Sprintf("%s%s%s", d.Name, d.Operator, d.Version)
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Default: no spaces
|
||||||
|
return fmt.Sprintf("%s%s%s", d.Name, d.Operator, d.Version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForNfpmMultiple formats multiple dependencies for nfpm.
|
||||||
|
func ForNfpmMultiple(deps []Dependency, pkgFormat string) []string {
|
||||||
|
result := make([]string, 0, len(deps))
|
||||||
|
for _, dep := range deps {
|
||||||
|
if formatted := dep.ForNfpm(pkgFormat); formatted != "" {
|
||||||
|
result = append(result, formatted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user