Compare commits
49 Commits
Author | SHA1 | Date | |
---|---|---|---|
e8202060d8 | |||
c4a92c67d4 | |||
85878f69d3 | |||
6bccce1db4 | |||
b5474b1eb4 | |||
51fdea781b | |||
4c1f2ea90f | |||
7fa7f8ba82 | |||
25d001c1c9 | |||
f86b3003b1 | |||
bd79dcf401 | |||
d1fe02fa57 | |||
1ca7801fba | |||
661d79ce24 | |||
bece64c132 | |||
6d29b98cf7 | |||
d286041864 | |||
392a522723 | |||
e259184a89 | |||
65ab4de561 | |||
1cdab8dfed | |||
237e2c338d | |||
703ab8e8c4 | |||
06fcab4ce7 | |||
7741c7368b | |||
69f4af0a4d | |||
bcf627f176 | |||
6ec95e4bd9 | |||
578da7ff52 | |||
c51caf5c52 | |||
09dba577c6 | |||
ca82bf3024 | |||
c0023db6cd | |||
152e5077ec | |||
15ba8700e8 | |||
a8aefc0524 | |||
9540030579 | |||
4f9d4260b8 | |||
38b5e6f581 | |||
408bd12302 | |||
fb83d544de | |||
2cb963d4b2 | |||
e74d74cdf6 | |||
5b3d53d253 | |||
36e704f735 | |||
07356d5e55 | |||
52bd6aca93 | |||
2f1770b43b | |||
9d5b5b51ff |
@ -16,11 +16,12 @@
|
|||||||
|
|
||||||
name: E2E
|
name: E2E
|
||||||
|
|
||||||
|
# on:
|
||||||
|
# push:
|
||||||
|
# branches: [ main ]
|
||||||
|
# pull_request:
|
||||||
on:
|
on:
|
||||||
push:
|
workflow_dispatch:
|
||||||
branches: [ main ]
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
tests:
|
tests:
|
||||||
@ -39,19 +40,18 @@ jobs:
|
|||||||
uses: https://github.com/actions/setup-go@v5
|
uses: https://github.com/actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.24'
|
go-version: '1.24'
|
||||||
|
cache: false
|
||||||
|
|
||||||
- name: Cache Podman images
|
# - name: Cache Podman images
|
||||||
uses: actions/cache@v4
|
# uses: actions/cache@v4
|
||||||
with:
|
# with:
|
||||||
path: |
|
# path: |
|
||||||
~/.local/share/containers/storage
|
# ~/.local/share/containers/storage
|
||||||
/var/lib/containers/storage
|
# /var/lib/containers/storage
|
||||||
key: ${{ runner.os }}-primes
|
# key: ${{ runner.os }}-primes
|
||||||
|
|
||||||
- name: Run E2E tests
|
- name: Run E2E tests
|
||||||
env:
|
env:
|
||||||
DOCKER_HOST: unix:///tmp/podman.sock
|
|
||||||
IGNORE_ROOT_CHECK: 1
|
IGNORE_ROOT_CHECK: 1
|
||||||
run: |
|
run: |
|
||||||
podman system service -t 0 unix:///tmp/podman.sock &
|
|
||||||
make e2e-test
|
make e2e-test
|
||||||
|
@ -26,6 +26,8 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
pre-commit:
|
pre-commit:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: docker.gitea.com/runner-images:ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
@ -45,9 +45,15 @@ jobs:
|
|||||||
echo "Version - $version"
|
echo "Version - $version"
|
||||||
echo "VERSION=$version" >> $GITHUB_ENV
|
echo "VERSION=$version" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Build alr binary
|
- name: Prepare for install
|
||||||
run: |
|
run: |
|
||||||
CGO_ENABLED=0 go build -ldflags "-X gitea.plemya-x.ru/Plemya-x/ALR/internal/config.Version=${{ env.VERSION }}" -o alr
|
apt-get update && apt-get install -y libcap2-bin bindfs
|
||||||
|
|
||||||
|
- name: Build alr
|
||||||
|
env:
|
||||||
|
IGNORE_ROOT_CHECK: 1
|
||||||
|
run: |
|
||||||
|
make build
|
||||||
|
|
||||||
- name: Create tar.gz
|
- name: Create tar.gz
|
||||||
run: |
|
run: |
|
||||||
@ -64,4 +70,51 @@ jobs:
|
|||||||
body: ${{ steps.changes.outputs.changes }}
|
body: ${{ steps.changes.outputs.changes }}
|
||||||
files: |-
|
files: |-
|
||||||
alr-${{ env.VERSION }}-linux-x86_64.tar.gz
|
alr-${{ env.VERSION }}-linux-x86_64.tar.gz
|
||||||
|
|
||||||
|
- name: Checkout alr-default repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: Plemya-x/alr-default
|
||||||
|
token: ${{ secrets.GITEAPUBLIC }}
|
||||||
|
path: alr-default
|
||||||
|
|
||||||
|
- name: Update version 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
|
||||||
|
|
||||||
|
# - name: Install alr
|
||||||
|
# run: |
|
||||||
|
# make install
|
||||||
|
#
|
||||||
|
# # temporary fix
|
||||||
|
# groupadd wheel
|
||||||
|
# usermod -aG wheel root
|
||||||
|
|
||||||
|
# - 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 assets
|
||||||
|
# uses: akkuman/gitea-release-action@v1
|
||||||
|
# with:
|
||||||
|
# body: ${{ steps.changes.outputs.changes }}
|
||||||
|
# files: |-
|
||||||
|
# alr-bin+alr-default_${{ env.VERSION }}-1.red80_amd64.deb \
|
||||||
|
# alr-bin+alr-default-${{ env.VERSION }}-1-x86_64.pkg.tar.zst \
|
||||||
|
# alr-bin+alr-default-${{ env.VERSION }}-1.red80.x86_64.rpm \
|
||||||
|
# alr-bin+alr-default-${{ env.VERSION }}-alt1.x86_64.rpm
|
||||||
|
|
||||||
|
- name: Commit changes
|
||||||
|
run: |
|
||||||
|
cd alr-default
|
||||||
|
git config user.name "gitea"
|
||||||
|
git config user.email "admin@plemya-x.ru"
|
||||||
|
git add .
|
||||||
|
git commit -m "Обновление версии до ${{ env.VERSION }}"
|
||||||
|
git push
|
||||||
|
69
.gitea/workflows/update-alr-git.yaml
Normal file
69
.gitea/workflows/update-alr-git.yaml
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# 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: Update alr-git
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
changelog:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Install the latest version of uv
|
||||||
|
uses: astral-sh/setup-uv@v6
|
||||||
|
|
||||||
|
- name: Setup alr-spec
|
||||||
|
run: |
|
||||||
|
uv tool install alr-spec==0.0.5
|
||||||
|
|
||||||
|
- name: Install alr
|
||||||
|
run: |
|
||||||
|
apt-get update && apt-get install -y libcap2-bin
|
||||||
|
curl -fsS https://gitea.plemya-x.ru/Plemya-x/ALR/raw/branch/master/scripts/install.sh | bash
|
||||||
|
|
||||||
|
- name: Checkout this repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set ALR version
|
||||||
|
run: |
|
||||||
|
echo "NEW_ALR_VERSION=$(alr helper git-version)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Checkout alr-default repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: Plemya-x/alr-default
|
||||||
|
token: ${{ secrets.GITEAPUBLIC }}
|
||||||
|
path: alr-default
|
||||||
|
|
||||||
|
- name: Update version
|
||||||
|
working-directory: ./alr-default/alr-git
|
||||||
|
run: |
|
||||||
|
alr-spec set-field version $NEW_ALR_VERSION
|
||||||
|
alr-spec set-field release 1
|
||||||
|
|
||||||
|
- name: Commit changes
|
||||||
|
run: |
|
||||||
|
cd alr-default
|
||||||
|
git config user.name "gitea"
|
||||||
|
git config user.email "admin@plemya-x.ru"
|
||||||
|
git add .
|
||||||
|
git commit -m "Обновление версии до $NEW_ALR_VERSION"
|
||||||
|
git push
|
9
Makefile
9
Makefile
@ -21,10 +21,11 @@ build: check-no-root $(BIN)
|
|||||||
|
|
||||||
export CGO_ENABLED := 0
|
export CGO_ENABLED := 0
|
||||||
$(BIN):
|
$(BIN):
|
||||||
|
go generate ./...
|
||||||
go build -ldflags="-X 'gitea.plemya-x.ru/Plemya-x/ALR/internal/config.Version=$(GIT_VERSION)'" -o $@
|
go build -ldflags="-X 'gitea.plemya-x.ru/Plemya-x/ALR/internal/config.Version=$(GIT_VERSION)'" -o $@
|
||||||
|
|
||||||
check-no-root:
|
check-no-root:
|
||||||
@if [[ "$(IGNORE_ROOT_CHECK)" != "1" ]] && [[ "$$(whoami)" == 'root' ]]; then \
|
@if [ "$$IGNORE_ROOT_CHECK" != "1" ] && [ "`whoami`" = "root" ]; then \
|
||||||
echo "This target shouldn't run as root" 1>&2; \
|
echo "This target shouldn't run as root" 1>&2; \
|
||||||
echo "Set IGNORE_ROOT_CHECK=1 to override" 1>&2; \
|
echo "Set IGNORE_ROOT_CHECK=1 to override" 1>&2; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
@ -39,6 +40,12 @@ install: \
|
|||||||
$(INSTALED_BIN): $(BIN)
|
$(INSTALED_BIN): $(BIN)
|
||||||
install -Dm755 $< $@
|
install -Dm755 $< $@
|
||||||
setcap cap_setuid,cap_setgid+ep $(INSTALED_BIN)
|
setcap cap_setuid,cap_setgid+ep $(INSTALED_BIN)
|
||||||
|
@if id alr >/dev/null 2>&1; then \
|
||||||
|
echo "User 'alr' already exists. Skipping."; \
|
||||||
|
else \
|
||||||
|
useradd -r -s /usr/sbin/nologin alr; \
|
||||||
|
fi
|
||||||
|
install -d -o alr -g alr -m 755 /var/cache/alr /etc/alr
|
||||||
|
|
||||||
$(INSTALLED_BASH_COMPLETION): $(BASH_COMPLETION)
|
$(INSTALLED_BASH_COMPLETION): $(BASH_COMPLETION)
|
||||||
install -Dm755 $< $@
|
install -Dm755 $< $@
|
||||||
|
16
README.md
16
README.md
@ -20,10 +20,10 @@ ALR написан на чистом Go и после сборки не имее
|
|||||||
Установочный скрипт автоматически загрузит и установит соответствующий пакет ALR в вашей системе. Чтобы использовать его, просто выполните следующую команду:
|
Установочный скрипт автоматически загрузит и установит соответствующий пакет ALR в вашей системе. Чтобы использовать его, просто выполните следующую команду:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fsSL plemya-x.ru/alr/install.sh | bash
|
curl -fsSL https://gitea.plemya-x.ru/Plemya-x/ALR/raw/branch/master/scripts/install.sh | bash
|
||||||
```
|
```
|
||||||
|
|
||||||
**ВАЖНО**: При этом скрипт будет загружен и запущен с <https://plemya-x.ru/alr/install.sh>. Пожалуйста, просматривайте любые скрипты, которые вы скачиваете из Интернета (включая этот), прежде чем запускать их.
|
**ВАЖНО**: При этом скрипт будет загружен и запущен [скрипт](https://gitea.plemya-x.ru/Plemya-x/ALR/src/branch/master/scripts/install.sh). Пожалуйста, просматривайте любые скрипты, которые вы скачиваете из Интернета (включая этот), прежде чем запускать их.
|
||||||
|
|
||||||
### Сборка из исходного кода
|
### Сборка из исходного кода
|
||||||
|
|
||||||
@ -52,9 +52,17 @@ ALR был создан потому, что упаковка программн
|
|||||||
|
|
||||||
Репозитории alr - это git-хранилища, которые содержат каталог для каждого пакета с файлом `alr.sh` внутри. Файл `alr.sh` содержит все инструкции по сборке пакета и информацию о нем. Скрипты `alr.sh` аналогичны скриптам Aur PKGBUILD.
|
Репозитории alr - это git-хранилища, которые содержат каталог для каждого пакета с файлом `alr.sh` внутри. Файл `alr.sh` содержит все инструкции по сборке пакета и информацию о нем. Скрипты `alr.sh` аналогичны скриптам Aur PKGBUILD.
|
||||||
|
|
||||||
Например, репозиторий [Plemya-x/alr-repo](https://gitea.plemya-x.ru/Plemya-x/alr-repo.git) можно подключить так:
|
Например, репозиторий с ALR [Plemya-x/alr-default](https://gitea.plemya-x.ru/Plemya-x/alr-default.git)
|
||||||
```
|
```
|
||||||
alr addrepo --name alr-repo --url https://gitea.plemya-x.ru/Plemya-x/alr-repo.git
|
alr repo add alr-default https://gitea.plemya-x.ru/Plemya-x/alr-default.git
|
||||||
|
```
|
||||||
|
Репозиторий пакетов [Plemya-x/alr-repo](https://gitea.plemya-x.ru/Plemya-x/alr-repo.git) можно подключить так:
|
||||||
|
```
|
||||||
|
alr repo add alr-repo https://gitea.plemya-x.ru/Plemya-x/alr-repo.git
|
||||||
|
```
|
||||||
|
Репозиторий Linux-Gaming [Plemya-x/alr-LG](https://gitea.plemya-x.ru/Plemya-x/alr-LG.git) можно подключить так:
|
||||||
|
```
|
||||||
|
alr repo add alr-LG https://gitea.plemya-x.ru/Plemya-x/alr-LG.git
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
|
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
|
||||||
<text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
|
<text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
|
||||||
<text x="33.5" y="14">coverage</text>
|
<text x="33.5" y="14">coverage</text>
|
||||||
<text x="86" y="15" fill="#010101" fill-opacity=".3">16.9%</text>
|
<text x="86" y="15" fill="#010101" fill-opacity=".3">19.7%</text>
|
||||||
<text x="86" y="14">16.9%</text>
|
<text x="86" y="14">19.7%</text>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 926 B After Width: | Height: | Size: 926 B |
12
build.go
12
build.go
@ -28,12 +28,12 @@ import (
|
|||||||
"github.com/leonelquinteros/gotext"
|
"github.com/leonelquinteros/gotext"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/build"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
||||||
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
|
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func BuildCmd() *cli.Command {
|
func BuildCmd() *cli.Command {
|
||||||
@ -97,7 +97,7 @@ func BuildCmd() *cli.Command {
|
|||||||
var script string
|
var script string
|
||||||
var packages []string
|
var packages []string
|
||||||
|
|
||||||
var res *build.BuildResult
|
var res []*build.BuiltDep
|
||||||
|
|
||||||
var scriptArgs *build.BuildPackageFromScriptArgs
|
var scriptArgs *build.BuildPackageFromScriptArgs
|
||||||
var dbArgs *build.BuildPackageFromDbArgs
|
var dbArgs *build.BuildPackageFromDbArgs
|
||||||
@ -222,9 +222,9 @@ func BuildCmd() *cli.Command {
|
|||||||
return cliutils.FormatCliExit(gotext.Get("Error building package"), err)
|
return cliutils.FormatCliExit(gotext.Get("Error building package"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pkgPath := range res.PackagePaths {
|
for _, pkg := range res {
|
||||||
name := filepath.Base(pkgPath)
|
name := filepath.Base(pkg.Path)
|
||||||
err = osutils.Move(pkgPath, filepath.Join(wd, name))
|
err = osutils.Move(pkg.Path, filepath.Join(wd, name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cliutils.FormatCliExit(gotext.Get("Error moving the package"), err)
|
return cliutils.FormatCliExit(gotext.Get("Error moving the package"), err)
|
||||||
}
|
}
|
||||||
|
@ -182,7 +182,7 @@ func runTestCommands(t *testing.T, r e2e.Runnable, timeout time.Duration, expect
|
|||||||
}
|
}
|
||||||
|
|
||||||
const REPO_NAME_FOR_E2E_TESTS = "alr-repo"
|
const REPO_NAME_FOR_E2E_TESTS = "alr-repo"
|
||||||
const REPO_URL_FOR_E2E_TESTS = "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git"
|
const REPO_URL_FOR_E2E_TESTS = "https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git"
|
||||||
|
|
||||||
func defaultPrepare(t *testing.T, r e2e.Runnable) {
|
func defaultPrepare(t *testing.T, r e2e.Runnable) {
|
||||||
execShouldNoError(t, r,
|
execShouldNoError(t, r,
|
||||||
|
41
e2e-tests/firejailed_package_test.go
Normal file
41
e2e-tests/firejailed_package_test.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// 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/>.
|
||||||
|
|
||||||
|
//go:build e2e
|
||||||
|
|
||||||
|
package e2etests_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/efficientgo/e2e"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestE2EFirejailedPackage(t *testing.T) {
|
||||||
|
dockerMultipleRun(
|
||||||
|
t,
|
||||||
|
"firejailed-package",
|
||||||
|
COMMON_SYSTEMS,
|
||||||
|
func(t *testing.T, r e2e.Runnable) {
|
||||||
|
defaultPrepare(t, r)
|
||||||
|
execShouldNoError(t, r, "alr", "build", "-p", fmt.Sprintf("%s/firejailed-pkg", REPO_NAME_FOR_E2E_TESTS))
|
||||||
|
execShouldError(t, r, "alr", "build", "-p", fmt.Sprintf("%s/firejailed-pkg-incorrect", REPO_NAME_FOR_E2E_TESTS))
|
||||||
|
execShouldNoError(t, r, "sh", "-c", "dpkg -c *.deb | grep -q '/usr/lib/alr/firejailed/_usr_bin_danger.sh'")
|
||||||
|
execShouldNoError(t, r, "sh", "-c", "dpkg -c *.deb | grep -q '/usr/lib/alr/firejailed/_usr_bin_danger.sh.profile'")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
@ -31,8 +31,8 @@ func TestE2EGroupAndSummaryField(t *testing.T) {
|
|||||||
RPM_SYSTEMS,
|
RPM_SYSTEMS,
|
||||||
func(t *testing.T, r e2e.Runnable) {
|
func(t *testing.T, r e2e.Runnable) {
|
||||||
defaultPrepare(t, r)
|
defaultPrepare(t, r)
|
||||||
execShouldNoError(t, r, "sh", "-c", "alr search --name test-group-and-summary --format \"{{.Group}}\" | grep ^System/Base$")
|
execShouldNoError(t, r, "sh", "-c", "alr search --name test-group-and-summary --format \"{{.Group.Resolved}}\" | grep ^System/Base$")
|
||||||
execShouldNoError(t, r, "sh", "-c", "alr search --name test-group-and-summary --format \"{{.Summary}}\" | grep \"^Custom summary$\"")
|
execShouldNoError(t, r, "sh", "-c", "alr search --name test-group-and-summary --format \"{{.Summary.Resolved}}\" | grep \"^Custom summary$\"")
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
53
e2e-tests/issue_76_single_package_repo_test.go
Normal file
53
e2e-tests/issue_76_single_package_repo_test.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// 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/>.
|
||||||
|
|
||||||
|
//go:build e2e
|
||||||
|
|
||||||
|
package e2etests_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/efficientgo/e2e"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test75SinglePackageRepo(t *testing.T) {
|
||||||
|
dockerMultipleRun(
|
||||||
|
t,
|
||||||
|
"issue-76-single-package-repo",
|
||||||
|
COMMON_SYSTEMS,
|
||||||
|
func(t *testing.T, r e2e.Runnable) {
|
||||||
|
execShouldNoError(t, r,
|
||||||
|
"sudo",
|
||||||
|
"alr",
|
||||||
|
"repo",
|
||||||
|
"add",
|
||||||
|
REPO_NAME_FOR_E2E_TESTS,
|
||||||
|
"https://gitea.plemya-x.ru/Maks1mS/test-single-package-alr-repo.git",
|
||||||
|
)
|
||||||
|
execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", REPO_NAME_FOR_E2E_TESTS, "1075c918be")
|
||||||
|
execShouldNoError(t, r, "alr", "ref")
|
||||||
|
execShouldNoError(t, r, "sudo", "alr", "in", "test-single-repo")
|
||||||
|
execShouldNoError(t, r, "sh", "-c", "alr list -U")
|
||||||
|
execShouldNoError(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 0 || exit 1")
|
||||||
|
execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", REPO_NAME_FOR_E2E_TESTS, "5e361c50d7")
|
||||||
|
execShouldNoError(t, r, "sudo", "alr", "ref")
|
||||||
|
execShouldNoError(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 1 || exit 1")
|
||||||
|
execShouldNoError(t, r, "sudo", "alr", "up")
|
||||||
|
execShouldNoError(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 0 || exit 1")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
53
e2e-tests/issue_78_mirrors_test.go
Normal file
53
e2e-tests/issue_78_mirrors_test.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// 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/>.
|
||||||
|
|
||||||
|
//go:build e2e
|
||||||
|
|
||||||
|
package e2etests_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/efficientgo/e2e"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestE2EIssue78Mirrors(t *testing.T) {
|
||||||
|
dockerMultipleRun(
|
||||||
|
t,
|
||||||
|
"issue-78-mirrors",
|
||||||
|
COMMON_SYSTEMS,
|
||||||
|
func(t *testing.T, r e2e.Runnable) {
|
||||||
|
defaultPrepare(t, r)
|
||||||
|
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "add", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
|
||||||
|
execShouldNoError(t, r, "sudo", "alr", "repo", "set-url", REPO_NAME_FOR_E2E_TESTS, "https://example.com")
|
||||||
|
execShouldNoError(t, r, "sudo", "alr", "ref")
|
||||||
|
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "clear", REPO_NAME_FOR_E2E_TESTS)
|
||||||
|
execShouldError(t, r, "sudo", "alr", "ref")
|
||||||
|
|
||||||
|
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "add", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
|
||||||
|
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "rm", "--partial", REPO_NAME_FOR_E2E_TESTS, "gitea.plemya-x.ru/Maks1mS")
|
||||||
|
execShouldError(t, r, "sudo", "alr", "ref")
|
||||||
|
|
||||||
|
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "add", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
|
||||||
|
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "rm", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
|
||||||
|
execShouldError(t, r, "sudo", "alr", "ref")
|
||||||
|
|
||||||
|
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "add", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
|
||||||
|
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "rm", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
|
||||||
|
execShouldError(t, r, "sudo", "alr", "ref")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
49
e2e-tests/issue_94_twice_build_test.go
Normal file
49
e2e-tests/issue_94_twice_build_test.go
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/>.
|
||||||
|
|
||||||
|
//go:build e2e
|
||||||
|
|
||||||
|
package e2etests_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/efficientgo/e2e"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestE2EIssue94TwiceBuild(t *testing.T) {
|
||||||
|
dockerMultipleRun(
|
||||||
|
t,
|
||||||
|
"issue-94-twice-build",
|
||||||
|
COMMON_SYSTEMS,
|
||||||
|
func(t *testing.T, r e2e.Runnable) {
|
||||||
|
defaultPrepare(t, r)
|
||||||
|
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
err := r.Exec(
|
||||||
|
e2e.NewCommand("sudo", "alr", "in", "test-94-app"),
|
||||||
|
e2e.WithExecOptionStderr(&stderr),
|
||||||
|
)
|
||||||
|
assert.NoError(t, err, "command failed")
|
||||||
|
|
||||||
|
output := stderr.String()
|
||||||
|
assert.Equal(t, 1, strings.Count(output, "Building package name=test-94-dep"))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
30
fix.go
30
fix.go
@ -20,6 +20,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/fs"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -57,7 +58,6 @@ func FixCmd() *cli.Command {
|
|||||||
paths := cfg.GetPaths()
|
paths := cfg.GetPaths()
|
||||||
|
|
||||||
slog.Info(gotext.Get("Clearing cache directory"))
|
slog.Info(gotext.Get("Clearing cache directory"))
|
||||||
// Remove all nested directories of paths.CacheDir
|
|
||||||
|
|
||||||
dir, err := os.Open(paths.CacheDir)
|
dir, err := os.Open(paths.CacheDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -71,7 +71,13 @@ func FixCmd() *cli.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
err = os.RemoveAll(filepath.Join(paths.CacheDir, entry))
|
fullPath := filepath.Join(paths.CacheDir, entry)
|
||||||
|
|
||||||
|
if err := makeWritableRecursive(fullPath); err != nil {
|
||||||
|
slog.Debug("Failed to make path writable", "path", fullPath, "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.RemoveAll(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cliutils.FormatCliExit(gotext.Get("Unable to remove cache item (%s)", entry), err)
|
return cliutils.FormatCliExit(gotext.Get("Unable to remove cache item (%s)", entry), err)
|
||||||
}
|
}
|
||||||
@ -101,3 +107,23 @@ func FixCmd() *cli.Command {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeWritableRecursive(path string) error {
|
||||||
|
return filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := d.Info()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
newMode := info.Mode() | 0o200
|
||||||
|
if d.IsDir() {
|
||||||
|
newMode |= 0o100
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Chmod(path, newMode)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
2
gen.go
2
gen.go
@ -25,7 +25,7 @@ import (
|
|||||||
"github.com/leonelquinteros/gotext"
|
"github.com/leonelquinteros/gotext"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/gen"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/gen"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GenCmd() *cli.Command {
|
func GenCmd() *cli.Command {
|
||||||
|
251
generators/alrsh-package/main.go
Normal file
251
generators/alrsh-package/main.go
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/format"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resolvedStructGenerator(buf *bytes.Buffer, fields []*ast.Field) {
|
||||||
|
contentTemplate := template.Must(template.New("").Parse(`
|
||||||
|
type {{ .EntityNameLower }}Resolved struct {
|
||||||
|
{{ .StructFields }}
|
||||||
|
}
|
||||||
|
`))
|
||||||
|
|
||||||
|
var structFieldsBuilder strings.Builder
|
||||||
|
|
||||||
|
for _, field := range fields {
|
||||||
|
for _, name := range field.Names {
|
||||||
|
// Поле с типом
|
||||||
|
fieldTypeStr := exprToString(field.Type)
|
||||||
|
|
||||||
|
// Структура поля
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.WriteString("\t")
|
||||||
|
buf.WriteString(name.Name)
|
||||||
|
buf.WriteString(" ")
|
||||||
|
buf.WriteString(fieldTypeStr)
|
||||||
|
|
||||||
|
// Обработка json-тега
|
||||||
|
jsonTag := ""
|
||||||
|
if field.Tag != nil {
|
||||||
|
raw := strings.Trim(field.Tag.Value, "`")
|
||||||
|
tag := reflect.StructTag(raw)
|
||||||
|
if val := tag.Get("json"); val != "" {
|
||||||
|
jsonTag = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if jsonTag == "" {
|
||||||
|
jsonTag = strings.ToLower(name.Name)
|
||||||
|
}
|
||||||
|
buf.WriteString(fmt.Sprintf(" `json:\"%s\"`", jsonTag))
|
||||||
|
buf.WriteString("\n")
|
||||||
|
structFieldsBuilder.Write(buf.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
params := struct {
|
||||||
|
EntityNameLower string
|
||||||
|
StructFields string
|
||||||
|
}{
|
||||||
|
EntityNameLower: "package",
|
||||||
|
StructFields: structFieldsBuilder.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := contentTemplate.Execute(buf, params)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("execute template: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toResolvedFuncGenerator(buf *bytes.Buffer, fields []*ast.Field) {
|
||||||
|
contentTemplate := template.Must(template.New("").Parse(`
|
||||||
|
func {{ .EntityName }}ToResolved(src *{{ .EntityName }}) {{ .EntityNameLower }}Resolved {
|
||||||
|
return {{ .EntityNameLower }}Resolved{
|
||||||
|
{{ .Assignments }}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`))
|
||||||
|
|
||||||
|
var assignmentsBuilder strings.Builder
|
||||||
|
|
||||||
|
for _, field := range fields {
|
||||||
|
for _, name := range field.Names {
|
||||||
|
var assignBuf bytes.Buffer
|
||||||
|
assignBuf.WriteString("\t\t")
|
||||||
|
assignBuf.WriteString(name.Name)
|
||||||
|
assignBuf.WriteString(": ")
|
||||||
|
if isOverridableField(field.Type) {
|
||||||
|
assignBuf.WriteString(fmt.Sprintf("src.%s.Resolved()", name.Name))
|
||||||
|
} else {
|
||||||
|
assignBuf.WriteString(fmt.Sprintf("src.%s", name.Name))
|
||||||
|
}
|
||||||
|
assignBuf.WriteString(",\n")
|
||||||
|
assignmentsBuilder.Write(assignBuf.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
params := struct {
|
||||||
|
EntityName string
|
||||||
|
EntityNameLower string
|
||||||
|
Assignments string
|
||||||
|
}{
|
||||||
|
EntityName: "Package",
|
||||||
|
EntityNameLower: "package",
|
||||||
|
Assignments: assignmentsBuilder.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := contentTemplate.Execute(buf, params)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("execute template: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveFuncGenerator(buf *bytes.Buffer, fields []*ast.Field) {
|
||||||
|
contentTemplate := template.Must(template.New("").Parse(`
|
||||||
|
func Resolve{{ .EntityName }}(pkg *{{ .EntityName }}, overrides []string) {
|
||||||
|
{{.Code}}}
|
||||||
|
`))
|
||||||
|
|
||||||
|
var codeBuilder strings.Builder
|
||||||
|
|
||||||
|
for _, field := range fields {
|
||||||
|
for _, name := range field.Names {
|
||||||
|
if isOverridableField(field.Type) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.WriteString(fmt.Sprintf("\t\tpkg.%s.Resolve(overrides)\n", name.Name))
|
||||||
|
codeBuilder.Write(buf.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
params := struct {
|
||||||
|
EntityName string
|
||||||
|
Code string
|
||||||
|
}{
|
||||||
|
EntityName: "Package",
|
||||||
|
Code: codeBuilder.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := contentTemplate.Execute(buf, params)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("execute template: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
path := os.Getenv("GOFILE")
|
||||||
|
if path == "" {
|
||||||
|
log.Fatal("GOFILE must be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
node, err := parser.ParseFile(fset, path, nil, parser.AllErrors)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("parsing file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
entityName := "Package" // имя структуры, которую анализируем
|
||||||
|
|
||||||
|
found := false
|
||||||
|
|
||||||
|
fields := make([]*ast.Field, 0)
|
||||||
|
|
||||||
|
// Ищем структуру с нужным именем
|
||||||
|
for _, decl := range node.Decls {
|
||||||
|
genDecl, ok := decl.(*ast.GenDecl)
|
||||||
|
if !ok || genDecl.Tok != token.TYPE {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, spec := range genDecl.Specs {
|
||||||
|
typeSpec := spec.(*ast.TypeSpec)
|
||||||
|
if typeSpec.Name.Name != entityName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
structType, ok := typeSpec.Type.(*ast.StructType)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fields = structType.Fields.List
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
log.Fatalf("struct %s not found", entityName)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
buf.WriteString("// DO NOT EDIT MANUALLY. This file is generated.\n")
|
||||||
|
buf.WriteString("package alrsh")
|
||||||
|
|
||||||
|
resolvedStructGenerator(&buf, fields)
|
||||||
|
toResolvedFuncGenerator(&buf, fields)
|
||||||
|
resolveFuncGenerator(&buf, fields)
|
||||||
|
|
||||||
|
// Форматируем вывод
|
||||||
|
formatted, err := format.Source(buf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("formatting: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
outPath := strings.TrimSuffix(path, ".go") + "_gen.go"
|
||||||
|
outFile, err := os.Create(outPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("create file: %v", err)
|
||||||
|
}
|
||||||
|
_, err = outFile.Write(formatted)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("writing output: %v", err)
|
||||||
|
}
|
||||||
|
outFile.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func exprToString(expr ast.Expr) string {
|
||||||
|
if t, ok := expr.(*ast.IndexExpr); ok {
|
||||||
|
if ident, ok := t.X.(*ast.Ident); ok && ident.Name == "OverridableField" {
|
||||||
|
return exprToString(t.Index) // T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := format.Node(&buf, token.NewFileSet(), expr); err != nil {
|
||||||
|
return "<invalid>"
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func isOverridableField(expr ast.Expr) bool {
|
||||||
|
indexExpr, ok := expr.(*ast.IndexExpr)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ident, ok := indexExpr.X.(*ast.Ident)
|
||||||
|
return ok && ident.Name == "OverridableField"
|
||||||
|
}
|
19
go.mod
19
go.mod
@ -8,8 +8,8 @@ require (
|
|||||||
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3
|
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||||
github.com/PuerkitoBio/purell v1.2.0
|
github.com/PuerkitoBio/purell v1.2.0
|
||||||
github.com/alecthomas/assert/v2 v2.2.1
|
|
||||||
github.com/alecthomas/chroma/v2 v2.9.1
|
github.com/alecthomas/chroma/v2 v2.9.1
|
||||||
|
github.com/bmatcuk/doublestar/v4 v4.8.1
|
||||||
github.com/caarlos0/env v3.5.0+incompatible
|
github.com/caarlos0/env v3.5.0+incompatible
|
||||||
github.com/charmbracelet/bubbles v0.20.0
|
github.com/charmbracelet/bubbles v0.20.0
|
||||||
github.com/charmbracelet/bubbletea v1.2.4
|
github.com/charmbracelet/bubbletea v1.2.4
|
||||||
@ -18,12 +18,12 @@ require (
|
|||||||
github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0
|
github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0
|
||||||
github.com/go-git/go-billy/v5 v5.6.0
|
github.com/go-git/go-billy/v5 v5.6.0
|
||||||
github.com/go-git/go-git/v5 v5.13.0
|
github.com/go-git/go-git/v5 v5.13.0
|
||||||
|
github.com/goccy/go-yaml v1.18.0
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||||
github.com/goreleaser/nfpm/v2 v2.41.0
|
github.com/goreleaser/nfpm/v2 v2.41.0
|
||||||
github.com/hashicorp/go-hclog v0.14.1
|
github.com/hashicorp/go-hclog v0.14.1
|
||||||
github.com/hashicorp/go-plugin v1.6.3
|
github.com/hashicorp/go-plugin v1.6.3
|
||||||
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08
|
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08
|
||||||
github.com/jmoiron/sqlx v1.3.5
|
|
||||||
github.com/leonelquinteros/gotext v1.7.0
|
github.com/leonelquinteros/gotext v1.7.0
|
||||||
github.com/mattn/go-isatty v0.0.20
|
github.com/mattn/go-isatty v0.0.20
|
||||||
github.com/mholt/archiver/v4 v4.0.0-alpha.8
|
github.com/mholt/archiver/v4 v4.0.0-alpha.8
|
||||||
@ -39,9 +39,9 @@ require (
|
|||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
||||||
golang.org/x/sys v0.31.0
|
golang.org/x/sys v0.31.0
|
||||||
golang.org/x/text v0.23.0
|
golang.org/x/text v0.23.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
|
||||||
modernc.org/sqlite v1.25.0
|
modernc.org/sqlite v1.25.0
|
||||||
mvdan.cc/sh/v3 v3.10.0
|
mvdan.cc/sh/v3 v3.10.0
|
||||||
|
xorm.io/xorm v1.3.9
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@ -52,7 +52,6 @@ require (
|
|||||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||||
github.com/ProtonMail/go-crypto v1.1.3 // indirect
|
github.com/ProtonMail/go-crypto v1.1.3 // indirect
|
||||||
github.com/alecthomas/repr v0.2.0 // indirect
|
|
||||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
|
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
|
||||||
@ -63,7 +62,7 @@ require (
|
|||||||
github.com/charmbracelet/harmonica v0.2.0 // indirect
|
github.com/charmbracelet/harmonica v0.2.0 // indirect
|
||||||
github.com/charmbracelet/x/ansi v0.4.5 // indirect
|
github.com/charmbracelet/x/ansi v0.4.5 // indirect
|
||||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||||
github.com/cloudflare/circl v1.3.8 // indirect
|
github.com/cloudflare/circl v1.6.1 // indirect
|
||||||
github.com/connesc/cipherio v0.2.1 // indirect
|
github.com/connesc/cipherio v0.2.1 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||||
github.com/creack/pty v1.1.24 // indirect
|
github.com/creack/pty v1.1.24 // indirect
|
||||||
@ -79,6 +78,7 @@ require (
|
|||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||||
github.com/gobwas/glob v0.2.3 // indirect
|
github.com/gobwas/glob v0.2.3 // indirect
|
||||||
|
github.com/goccy/go-json v0.8.1 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
@ -90,10 +90,10 @@ require (
|
|||||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||||
github.com/hexops/gotextdiff v1.0.3 // indirect
|
|
||||||
github.com/huandu/xstrings v1.3.3 // indirect
|
github.com/huandu/xstrings v1.3.3 // indirect
|
||||||
github.com/imdario/mergo v0.3.16 // indirect
|
github.com/imdario/mergo v0.3.16 // indirect
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||||
github.com/klauspost/compress v1.17.11 // indirect
|
github.com/klauspost/compress v1.17.11 // indirect
|
||||||
@ -105,6 +105,8 @@ require (
|
|||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||||
github.com/muesli/termenv v0.15.2 // indirect
|
github.com/muesli/termenv v0.15.2 // indirect
|
||||||
@ -117,9 +119,10 @@ require (
|
|||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||||
github.com/shopspring/decimal v1.2.0 // indirect
|
github.com/shopspring/decimal v1.3.1 // indirect
|
||||||
github.com/skeema/knownhosts v1.3.0 // indirect
|
github.com/skeema/knownhosts v1.3.0 // indirect
|
||||||
github.com/spf13/cast v1.6.0 // indirect
|
github.com/spf13/cast v1.6.0 // indirect
|
||||||
|
github.com/syndtr/goleveldb v1.0.0 // indirect
|
||||||
github.com/therootcompany/xz v1.0.1 // indirect
|
github.com/therootcompany/xz v1.0.1 // indirect
|
||||||
github.com/ulikunitz/xz v0.5.12 // indirect
|
github.com/ulikunitz/xz v0.5.12 // indirect
|
||||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||||
@ -136,6 +139,7 @@ require (
|
|||||||
google.golang.org/grpc v1.58.3 // indirect
|
google.golang.org/grpc v1.58.3 // indirect
|
||||||
google.golang.org/protobuf v1.36.1 // indirect
|
google.golang.org/protobuf v1.36.1 // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
lukechampine.com/uint128 v1.2.0 // indirect
|
lukechampine.com/uint128 v1.2.0 // indirect
|
||||||
modernc.org/cc/v3 v3.40.0 // indirect
|
modernc.org/cc/v3 v3.40.0 // indirect
|
||||||
modernc.org/ccgo/v3 v3.16.13 // indirect
|
modernc.org/ccgo/v3 v3.16.13 // indirect
|
||||||
@ -145,4 +149,5 @@ require (
|
|||||||
modernc.org/opt v0.1.3 // indirect
|
modernc.org/opt v0.1.3 // indirect
|
||||||
modernc.org/strutil v1.1.3 // indirect
|
modernc.org/strutil v1.1.3 // indirect
|
||||||
modernc.org/token v1.0.1 // indirect
|
modernc.org/token v1.0.1 // indirect
|
||||||
|
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 // indirect
|
||||||
)
|
)
|
||||||
|
54
go.sum
54
go.sum
@ -17,6 +17,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
|
|||||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
|
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
||||||
|
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
||||||
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3 h1:56BjRJJ2Sv50DfSvNUydUMJwwFuiBMWC1uYtH2GYjk8=
|
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3 h1:56BjRJJ2Sv50DfSvNUydUMJwwFuiBMWC1uYtH2GYjk8=
|
||||||
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3/go.mod h1:iKQM6uttMJgE5CFrPw6SQqAV7TKtlJNICRAie/dTciw=
|
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3/go.mod h1:iKQM6uttMJgE5CFrPw6SQqAV7TKtlJNICRAie/dTciw=
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
|
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
|
||||||
@ -65,6 +67,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
|||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
|
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
|
||||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
|
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
|
||||||
|
github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38=
|
||||||
|
github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||||
github.com/bodgit/plumbing v1.2.0 h1:gg4haxoKphLjml+tgnecR4yLBV5zo4HAZGCtAh3xCzM=
|
github.com/bodgit/plumbing v1.2.0 h1:gg4haxoKphLjml+tgnecR4yLBV5zo4HAZGCtAh3xCzM=
|
||||||
github.com/bodgit/plumbing v1.2.0/go.mod h1:b9TeRi7Hvc6Y05rjm8VML3+47n4XTZPtQ/5ghqic2n8=
|
github.com/bodgit/plumbing v1.2.0/go.mod h1:b9TeRi7Hvc6Y05rjm8VML3+47n4XTZPtQ/5ghqic2n8=
|
||||||
github.com/bodgit/sevenzip v1.3.0 h1:1ljgELgtHqvgIp8W8kgeEGHIWP4ch3xGI8uOBZgLVKY=
|
github.com/bodgit/sevenzip v1.3.0 h1:1ljgELgtHqvgIp8W8kgeEGHIWP4ch3xGI8uOBZgLVKY=
|
||||||
@ -100,8 +104,8 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
|
|||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/cloudflare/circl v1.3.8 h1:j+V8jJt09PoeMFIu2uh5JUyEaIHTXVOHslFoLNAKqwI=
|
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||||
github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
|
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||||
github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw=
|
github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw=
|
||||||
github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA=
|
github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||||
@ -137,6 +141,7 @@ github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
|||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||||
@ -153,10 +158,14 @@ github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi
|
|||||||
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||||
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||||
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||||
|
github.com/goccy/go-json v0.8.1 h1:4/Wjm0JIJaTDm8K1KcGrLHJoa8EsJ13YWeX+6Kfq6uI=
|
||||||
|
github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||||
|
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
@ -176,6 +185,7 @@ github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaW
|
|||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
@ -187,6 +197,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f h1:5CjVwnuUcp5adK4gmY6i72gpVFVnZDP2h5TmPScB6u4=
|
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f h1:5CjVwnuUcp5adK4gmY6i72gpVFVnZDP2h5TmPScB6u4=
|
||||||
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
|
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
@ -229,6 +240,8 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq
|
|||||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
||||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
||||||
|
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
|
github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
|
||||||
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
@ -241,10 +254,10 @@ github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 h1:wMeVzrPO3m
|
|||||||
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
|
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
|
||||||
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
|
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
|
||||||
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
|
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
|
||||||
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
|
||||||
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
|
||||||
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
||||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
@ -269,8 +282,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/leonelquinteros/gotext v1.7.0 h1:jcJmF4AXqyamP7vuw2MMIKs+O3jAEmvrc5JQiI8Ht/8=
|
github.com/leonelquinteros/gotext v1.7.0 h1:jcJmF4AXqyamP7vuw2MMIKs+O3jAEmvrc5JQiI8Ht/8=
|
||||||
github.com/leonelquinteros/gotext v1.7.0/go.mod h1:qJdoQuERPpccw7L70uoU+K/BvTfRBHYsisCQyFLXyvw=
|
github.com/leonelquinteros/gotext v1.7.0/go.mod h1:qJdoQuERPpccw7L70uoU+K/BvTfRBHYsisCQyFLXyvw=
|
||||||
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
|
||||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
||||||
@ -289,7 +300,6 @@ github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+Ei
|
|||||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||||
@ -306,6 +316,11 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
|
|||||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||||
@ -320,6 +335,10 @@ github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q
|
|||||||
github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY=
|
github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY=
|
||||||
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
|
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
|
||||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||||
|
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||||
@ -358,8 +377,9 @@ github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtC
|
|||||||
github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI=
|
github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI=
|
||||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
|
||||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||||
|
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||||
|
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
|
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
|
||||||
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
|
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
|
||||||
@ -374,6 +394,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
@ -382,6 +403,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
|||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||||
|
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||||
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41 h1:/V2rCMMWcsjYaYO2MeovLw+ClP63OtXgCF2Y1eb8+Ns=
|
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41 h1:/V2rCMMWcsjYaYO2MeovLw+ClP63OtXgCF2Y1eb8+Ns=
|
||||||
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41/go.mod h1:/roCdA6gg6lQyw/Oz6gIIGu3ggJKYhF+WC/AQReE5XQ=
|
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41/go.mod h1:/roCdA6gg6lQyw/Oz6gIIGu3ggJKYhF+WC/AQReE5XQ=
|
||||||
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
|
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
|
||||||
@ -453,6 +476,7 @@ golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
|
|||||||
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
@ -488,6 +512,7 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -612,8 +637,13 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
|
|||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
@ -659,3 +689,7 @@ mvdan.cc/sh/v3 v3.10.0/go.mod h1:z/mSSVyLFGZzqb3ZIKojjyqIx/xbmz/UHdCSv9HmqXY=
|
|||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||||
|
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 h1:bvLlAPW1ZMTWA32LuZMBEGHAUOcATZjzHcotf3SWweM=
|
||||||
|
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
||||||
|
xorm.io/xorm v1.3.9 h1:TUovzS0ko+IQ1XnNLfs5dqK1cJl1H5uHpWbWqAQ04nU=
|
||||||
|
xorm.io/xorm v1.3.9/go.mod h1:LsCCffeeYp63ssk0pKumP6l96WZcHix7ChpurcLNuMw=
|
||||||
|
55
info.go
55
info.go
@ -23,16 +23,16 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/goccy/go-yaml"
|
||||||
"github.com/jeandeaual/go-locale"
|
"github.com/jeandeaual/go-locale"
|
||||||
"github.com/leonelquinteros/gotext"
|
"github.com/leonelquinteros/gotext"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
||||||
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
|
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
|
||||||
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
|
|
||||||
"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/internal/utils"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -67,15 +67,8 @@ func InfoCmd() *cli.Command {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err)
|
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err)
|
||||||
}
|
}
|
||||||
defer result.Close()
|
|
||||||
|
|
||||||
for result.Next() {
|
|
||||||
var pkg database.Package
|
|
||||||
err = result.StructScan(&pkg)
|
|
||||||
if err != nil {
|
|
||||||
return cliutils.FormatCliExit(gotext.Get("Error iterating over packages"), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for _, pkg := range result {
|
||||||
fmt.Println(pkg.Name)
|
fmt.Println(pkg.Name)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -96,6 +89,7 @@ func InfoCmd() *cli.Command {
|
|||||||
New(ctx).
|
New(ctx).
|
||||||
WithConfig().
|
WithConfig().
|
||||||
WithDB().
|
WithDB().
|
||||||
|
WithDistroInfo().
|
||||||
WithRepos().
|
WithRepos().
|
||||||
Build()
|
Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -127,34 +121,27 @@ func InfoCmd() *cli.Command {
|
|||||||
systemLang = "en"
|
systemLang = "en"
|
||||||
}
|
}
|
||||||
|
|
||||||
if !all {
|
info, err := distro.ParseOSRelease(ctx)
|
||||||
info, err := distro.ParseOSRelease(ctx)
|
if err != nil {
|
||||||
if err != nil {
|
return cliutils.FormatCliExit(gotext.Get("Error parsing os-release file"), err)
|
||||||
return cliutils.FormatCliExit(gotext.Get("Error parsing os-release file"), err)
|
}
|
||||||
}
|
names, err = overrides.Resolve(
|
||||||
names, err = overrides.Resolve(
|
info,
|
||||||
info,
|
overrides.DefaultOpts.
|
||||||
overrides.DefaultOpts.
|
WithLanguages([]string{systemLang}),
|
||||||
WithLanguages([]string{systemLang}),
|
)
|
||||||
)
|
if err != nil {
|
||||||
if err != nil {
|
return cliutils.FormatCliExit(gotext.Get("Error resolving overrides"), err)
|
||||||
return cliutils.FormatCliExit(gotext.Get("Error resolving overrides"), err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pkg := range pkgs {
|
for _, pkg := range pkgs {
|
||||||
if !all {
|
alrsh.ResolvePackage(&pkg, names)
|
||||||
err = yaml.NewEncoder(os.Stdout).Encode(overrides.ResolvePackage(&pkg, names))
|
view := alrsh.NewPackageView(pkg)
|
||||||
if err != nil {
|
view.Resolved = !all
|
||||||
return cliutils.FormatCliExit(gotext.Get("Error encoding script variables"), err)
|
err = yaml.NewEncoder(os.Stdout, yaml.UseJSONMarshaler(), yaml.OmitEmpty()).Encode(view)
|
||||||
}
|
if err != nil {
|
||||||
} else {
|
return cliutils.FormatCliExit(gotext.Get("Error encoding script variables"), err)
|
||||||
err = yaml.NewEncoder(os.Stdout).Encode(pkg)
|
|
||||||
if err != nil {
|
|
||||||
return cliutils.FormatCliExit(gotext.Get("Error encoding script variables"), err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("---")
|
fmt.Println("---")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
28
install.go
28
install.go
@ -25,13 +25,12 @@ import (
|
|||||||
"github.com/leonelquinteros/gotext"
|
"github.com/leonelquinteros/gotext"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/build"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
||||||
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
|
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
|
||||||
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func InstallCmd() *cli.Command {
|
func InstallCmd() *cli.Command {
|
||||||
@ -98,7 +97,7 @@ func InstallCmd() *cli.Command {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = builder.InstallPkgs(
|
_, err = builder.InstallPkgs(
|
||||||
ctx,
|
ctx,
|
||||||
&build.BuildArgs{
|
&build.BuildArgs{
|
||||||
Opts: &types.BuildOpts{
|
Opts: &types.BuildOpts{
|
||||||
@ -136,15 +135,8 @@ func InstallCmd() *cli.Command {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err)
|
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err)
|
||||||
}
|
}
|
||||||
defer result.Close()
|
|
||||||
|
|
||||||
for result.Next() {
|
|
||||||
var pkg database.Package
|
|
||||||
err = result.StructScan(&pkg)
|
|
||||||
if err != nil {
|
|
||||||
return cliutils.FormatCliExit(gotext.Get("Error iterating over packages"), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for _, pkg := range result {
|
||||||
fmt.Println(pkg.Name)
|
fmt.Println(pkg.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,20 +182,12 @@ func RemoveCmd() *cli.Command {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err)
|
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err)
|
||||||
}
|
}
|
||||||
defer result.Close()
|
|
||||||
|
|
||||||
for result.Next() {
|
|
||||||
var pkg database.Package
|
|
||||||
err = result.StructScan(&pkg)
|
|
||||||
if err != nil {
|
|
||||||
return cliutils.FormatCliExit(gotext.Get("Error iterating over packages"), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for _, pkg := range result {
|
||||||
_, ok := installedAlrPackages[fmt.Sprintf("%s/%s", pkg.Repository, pkg.Name)]
|
_, ok := installedAlrPackages[fmt.Sprintf("%s/%s", pkg.Repository, pkg.Name)]
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(pkg.Name)
|
fmt.Println(pkg.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,14 +32,14 @@ import (
|
|||||||
"github.com/leonelquinteros/gotext"
|
"github.com/leonelquinteros/gotext"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/build"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
||||||
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
|
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build"
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func InternalBuildCmd() *cli.Command {
|
func InternalBuildCmd() *cli.Command {
|
||||||
|
@ -24,18 +24,17 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
"github.com/leonelquinteros/gotext"
|
"github.com/leonelquinteros/gotext"
|
||||||
"mvdan.cc/sh/v3/syntax"
|
|
||||||
"mvdan.cc/sh/v3/syntax/typedjson"
|
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BuildInput struct {
|
type BuildInput struct {
|
||||||
@ -133,54 +132,33 @@ type RepositoryProvider interface {
|
|||||||
|
|
||||||
// ================================================
|
// ================================================
|
||||||
|
|
||||||
type ScriptFile struct {
|
type BuiltDep struct {
|
||||||
File *syntax.File
|
Name string
|
||||||
Path string
|
Path string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ScriptFile) GobEncode() ([]byte, error) {
|
func Map[T, R any](items []T, f func(T) R) []R {
|
||||||
var buf bytes.Buffer
|
res := make([]R, len(items))
|
||||||
enc := gob.NewEncoder(&buf)
|
for i, item := range items {
|
||||||
if err := enc.Encode(s.Path); err != nil {
|
res[i] = f(item)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
var fileBuf bytes.Buffer
|
return res
|
||||||
if err := typedjson.Encode(&fileBuf, s.File); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
fileData := fileBuf.Bytes()
|
|
||||||
if err := enc.Encode(fileData); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ScriptFile) GobDecode(data []byte) error {
|
func GetBuiltPaths(deps []*BuiltDep) []string {
|
||||||
buf := bytes.NewBuffer(data)
|
return Map(deps, func(dep *BuiltDep) string {
|
||||||
dec := gob.NewDecoder(buf)
|
return dep.Path
|
||||||
if err := dec.Decode(&s.Path); err != nil {
|
})
|
||||||
return err
|
|
||||||
}
|
|
||||||
var fileData []byte
|
|
||||||
if err := dec.Decode(&fileData); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fileReader := bytes.NewReader(fileData)
|
|
||||||
file, err := typedjson.Decode(fileReader)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.File = file.(*syntax.File)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type BuildResult struct {
|
func GetBuiltName(deps []*BuiltDep) []string {
|
||||||
PackagePaths []string
|
return Map(deps, func(dep *BuiltDep) string {
|
||||||
PackageNames []string
|
return dep.Name
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type PackageFinder interface {
|
type PackageFinder interface {
|
||||||
FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error)
|
FindPkgs(ctx context.Context, pkgs []string) (map[string][]alrsh.Package, []string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config interface {
|
type Config interface {
|
||||||
@ -195,12 +173,12 @@ type FunctionsOutput struct {
|
|||||||
// EXECUTORS
|
// EXECUTORS
|
||||||
|
|
||||||
type ScriptResolverExecutor interface {
|
type ScriptResolverExecutor interface {
|
||||||
ResolveScript(ctx context.Context, pkg *db.Package) *ScriptInfo
|
ResolveScript(ctx context.Context, pkg *alrsh.Package) *ScriptInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
type ScriptExecutor interface {
|
type ScriptExecutor interface {
|
||||||
ReadScript(ctx context.Context, scriptPath string) (*ScriptFile, error)
|
ReadScript(ctx context.Context, scriptPath string) (*alrsh.ScriptFile, error)
|
||||||
ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *ScriptFile) (string, []*types.BuildVars, error)
|
ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile) (string, []*alrsh.Package, error)
|
||||||
PrepareDirs(
|
PrepareDirs(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
input *BuildInput,
|
input *BuildInput,
|
||||||
@ -209,27 +187,27 @@ type ScriptExecutor interface {
|
|||||||
ExecuteSecondPass(
|
ExecuteSecondPass(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
input *BuildInput,
|
input *BuildInput,
|
||||||
sf *ScriptFile,
|
sf *alrsh.ScriptFile,
|
||||||
varsOfPackages []*types.BuildVars,
|
varsOfPackages []*alrsh.Package,
|
||||||
repoDeps []string,
|
repoDeps []string,
|
||||||
builtNames []string,
|
builtDeps []*BuiltDep,
|
||||||
basePkg string,
|
basePkg string,
|
||||||
) (*SecondPassResult, error)
|
) ([]*BuiltDep, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type CacheExecutor interface {
|
type CacheExecutor interface {
|
||||||
CheckForBuiltPackage(ctx context.Context, input *BuildInput, vars *types.BuildVars) (string, bool, error)
|
CheckForBuiltPackage(ctx context.Context, input *BuildInput, vars *alrsh.Package) (string, bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ScriptViewerExecutor interface {
|
type ScriptViewerExecutor interface {
|
||||||
ViewScript(ctx context.Context, input *BuildInput, sf *ScriptFile, basePkg string) error
|
ViewScript(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile, basePkg string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type CheckerExecutor interface {
|
type CheckerExecutor interface {
|
||||||
PerformChecks(
|
PerformChecks(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
input *BuildInput,
|
input *BuildInput,
|
||||||
vars *types.BuildVars,
|
vars *alrsh.Package,
|
||||||
) (bool, error)
|
) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -307,7 +285,7 @@ func (b *BuildArgs) PkgFormat() string {
|
|||||||
|
|
||||||
type BuildPackageFromDbArgs struct {
|
type BuildPackageFromDbArgs struct {
|
||||||
BuildArgs
|
BuildArgs
|
||||||
Package *db.Package
|
Package *alrsh.Package
|
||||||
Packages []string
|
Packages []string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -320,7 +298,7 @@ type BuildPackageFromScriptArgs struct {
|
|||||||
func (b *Builder) BuildPackageFromDb(
|
func (b *Builder) BuildPackageFromDb(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
args *BuildPackageFromDbArgs,
|
args *BuildPackageFromDbArgs,
|
||||||
) (*BuildResult, error) {
|
) ([]*BuiltDep, error) {
|
||||||
scriptInfo := b.scriptResolver.ResolveScript(ctx, args.Package)
|
scriptInfo := b.scriptResolver.ResolveScript(ctx, args.Package)
|
||||||
|
|
||||||
return b.BuildPackage(ctx, &BuildInput{
|
return b.BuildPackage(ctx, &BuildInput{
|
||||||
@ -336,7 +314,7 @@ func (b *Builder) BuildPackageFromDb(
|
|||||||
func (b *Builder) BuildPackageFromScript(
|
func (b *Builder) BuildPackageFromScript(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
args *BuildPackageFromScriptArgs,
|
args *BuildPackageFromScriptArgs,
|
||||||
) (*BuildResult, error) {
|
) ([]*BuiltDep, error) {
|
||||||
return b.BuildPackage(ctx, &BuildInput{
|
return b.BuildPackage(ctx, &BuildInput{
|
||||||
script: args.Script,
|
script: args.Script,
|
||||||
repository: "default",
|
repository: "default",
|
||||||
@ -350,43 +328,46 @@ func (b *Builder) BuildPackageFromScript(
|
|||||||
func (b *Builder) BuildPackage(
|
func (b *Builder) BuildPackage(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
input *BuildInput,
|
input *BuildInput,
|
||||||
) (*BuildResult, error) {
|
) ([]*BuiltDep, error) {
|
||||||
scriptPath := input.script
|
scriptPath := input.script
|
||||||
|
|
||||||
slog.Debug("ReadScript")
|
slog.Debug("ReadScript")
|
||||||
sf, err := b.scriptExecutor.ReadScript(ctx, scriptPath)
|
sf, err := b.scriptExecutor.ReadScript(ctx, scriptPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed reading script: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Debug("ExecuteFirstPass")
|
slog.Debug("ExecuteFirstPass")
|
||||||
basePkg, varsOfPackages, err := b.scriptExecutor.ExecuteFirstPass(ctx, input, sf)
|
basePkg, varsOfPackages, err := b.scriptExecutor.ExecuteFirstPass(ctx, input, sf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed ExecuteFirstPass: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
builtPaths := make([]string, 0)
|
var builtDeps []*BuiltDep
|
||||||
|
|
||||||
if !input.opts.Clean {
|
if !input.opts.Clean {
|
||||||
var remainingVars []*types.BuildVars
|
var remainingVars []*alrsh.Package
|
||||||
for _, vars := range varsOfPackages {
|
for _, vars := range varsOfPackages {
|
||||||
builtPkgPath, ok, err := b.cacheExecutor.CheckForBuiltPackage(ctx, input, vars)
|
builtPkgPath, ok, err := b.cacheExecutor.CheckForBuiltPackage(ctx, input, vars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if ok {
|
if ok {
|
||||||
builtPaths = append(builtPaths, builtPkgPath)
|
builtDeps = append(builtDeps, &BuiltDep{
|
||||||
|
Path: builtPkgPath,
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
remainingVars = append(remainingVars, vars)
|
remainingVars = append(remainingVars, vars)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(remainingVars) == 0 {
|
if len(remainingVars) == 0 {
|
||||||
return &BuildResult{builtPaths, nil}, nil
|
return builtDeps, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Debug("ViewScript")
|
slog.Debug("ViewScript")
|
||||||
|
slog.Debug("", "varsOfPackages", varsOfPackages[0])
|
||||||
err = b.scriptViewerExecutor.ViewScript(ctx, input, sf, basePkg)
|
err = b.scriptViewerExecutor.ViewScript(ctx, input, sf, basePkg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -410,11 +391,11 @@ func (b *Builder) BuildPackage(
|
|||||||
sources := []string{}
|
sources := []string{}
|
||||||
checksums := []string{}
|
checksums := []string{}
|
||||||
for _, vars := range varsOfPackages {
|
for _, vars := range varsOfPackages {
|
||||||
buildDepends = append(buildDepends, vars.BuildDepends...)
|
buildDepends = append(buildDepends, vars.BuildDepends.Resolved()...)
|
||||||
optDepends = append(optDepends, vars.OptDepends...)
|
optDepends = append(optDepends, vars.OptDepends.Resolved()...)
|
||||||
depends = append(depends, vars.Depends...)
|
depends = append(depends, vars.Depends.Resolved()...)
|
||||||
sources = append(sources, vars.Sources...)
|
sources = append(sources, vars.Sources.Resolved()...)
|
||||||
checksums = append(checksums, vars.Checksums...)
|
checksums = append(checksums, vars.Checksums.Resolved()...)
|
||||||
}
|
}
|
||||||
buildDepends = removeDuplicates(buildDepends)
|
buildDepends = removeDuplicates(buildDepends)
|
||||||
optDepends = removeDuplicates(optDepends)
|
optDepends = removeDuplicates(optDepends)
|
||||||
@ -427,19 +408,32 @@ func (b *Builder) BuildPackage(
|
|||||||
sources, checksums = removeDuplicatesSources(sources, checksums)
|
sources, checksums = removeDuplicatesSources(sources, checksums)
|
||||||
|
|
||||||
slog.Debug("installBuildDeps")
|
slog.Debug("installBuildDeps")
|
||||||
err = b.installBuildDeps(ctx, input, buildDepends)
|
alrBuildDeps, err := b.installBuildDeps(ctx, input, buildDepends)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Debug("installOptDeps")
|
slog.Debug("installOptDeps")
|
||||||
err = b.installOptDeps(ctx, input, optDepends)
|
_, err = b.installOptDeps(ctx, input, optDepends)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
depNames := make(map[string]struct{})
|
||||||
|
for _, dep := range alrBuildDeps {
|
||||||
|
depNames[dep.Name] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We filter so as not to re-build what has already been built at the `installBuildDeps` stage.
|
||||||
|
var filteredDepends []string
|
||||||
|
for _, d := range depends {
|
||||||
|
if _, found := depNames[d]; !found {
|
||||||
|
filteredDepends = append(filteredDepends, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
slog.Debug("BuildALRDeps")
|
slog.Debug("BuildALRDeps")
|
||||||
builtPaths, builtNames, repoDeps, err := b.BuildALRDeps(ctx, input, depends)
|
newBuiltDeps, repoDeps, err := b.BuildALRDeps(ctx, input, filteredDepends)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -450,8 +444,6 @@ func (b *Builder) BuildPackage(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// builtPaths = append(builtPaths, newBuildPaths...)
|
|
||||||
|
|
||||||
slog.Info(gotext.Get("Downloading sources"))
|
slog.Info(gotext.Get("Downloading sources"))
|
||||||
slog.Debug("DownloadSources")
|
slog.Debug("DownloadSources")
|
||||||
err = b.sourceExecutor.DownloadSources(
|
err = b.sourceExecutor.DownloadSources(
|
||||||
@ -467,6 +459,8 @@ func (b *Builder) BuildPackage(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
builtDeps = removeDuplicates(append(builtDeps, newBuiltDeps...))
|
||||||
|
|
||||||
slog.Debug("ExecuteSecondPass")
|
slog.Debug("ExecuteSecondPass")
|
||||||
res, err := b.scriptExecutor.ExecuteSecondPass(
|
res, err := b.scriptExecutor.ExecuteSecondPass(
|
||||||
ctx,
|
ctx,
|
||||||
@ -474,25 +468,21 @@ func (b *Builder) BuildPackage(
|
|||||||
sf,
|
sf,
|
||||||
varsOfPackages,
|
varsOfPackages,
|
||||||
repoDeps,
|
repoDeps,
|
||||||
builtNames,
|
builtDeps,
|
||||||
basePkg,
|
basePkg,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pkgPaths := removeDuplicates(append(builtPaths, res.BuiltPaths...))
|
builtDeps = removeDuplicates(append(builtDeps, res...))
|
||||||
pkgNames := removeDuplicates(append(builtNames, res.BuiltNames...))
|
|
||||||
|
|
||||||
return &BuildResult{
|
return builtDeps, nil
|
||||||
PackagePaths: pkgPaths,
|
|
||||||
PackageNames: pkgNames,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type InstallPkgsArgs struct {
|
type InstallPkgsArgs struct {
|
||||||
BuildArgs
|
BuildArgs
|
||||||
AlrPkgs []db.Package
|
AlrPkgs []alrsh.Package
|
||||||
NativePkgs []string
|
NativePkgs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -503,7 +493,7 @@ func (b *Builder) InstallALRPackages(
|
|||||||
BuildOptsProvider
|
BuildOptsProvider
|
||||||
PkgFormatProvider
|
PkgFormatProvider
|
||||||
},
|
},
|
||||||
alrPkgs []db.Package,
|
alrPkgs []alrsh.Package,
|
||||||
) error {
|
) error {
|
||||||
for _, pkg := range alrPkgs {
|
for _, pkg := range alrPkgs {
|
||||||
res, err := b.BuildPackageFromDb(
|
res, err := b.BuildPackageFromDb(
|
||||||
@ -523,7 +513,7 @@ func (b *Builder) InstallALRPackages(
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = b.installerExecutor.InstallLocal(
|
err = b.installerExecutor.InstallLocal(
|
||||||
res.PackagePaths,
|
GetBuiltPaths(res),
|
||||||
&manager.Opts{
|
&manager.Opts{
|
||||||
NoConfirm: !input.BuildOpts().Interactive,
|
NoConfirm: !input.BuildOpts().Interactive,
|
||||||
},
|
},
|
||||||
@ -544,13 +534,13 @@ func (b *Builder) BuildALRDeps(
|
|||||||
PkgFormatProvider
|
PkgFormatProvider
|
||||||
},
|
},
|
||||||
depends []string,
|
depends []string,
|
||||||
) (builtPaths, builtNames, repoDeps []string, err error) {
|
) (buildDeps []*BuiltDep, repoDeps []string, err error) {
|
||||||
if len(depends) > 0 {
|
if len(depends) > 0 {
|
||||||
slog.Info(gotext.Get("Installing dependencies"))
|
slog.Info(gotext.Get("Installing dependencies"))
|
||||||
|
|
||||||
found, notFound, err := b.repos.FindPkgs(ctx, depends) // Поиск зависимостей
|
found, notFound, err := b.repos.FindPkgs(ctx, depends) // Поиск зависимостей
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, fmt.Errorf("failed FindPkgs: %w", err)
|
||||||
}
|
}
|
||||||
repoDeps = notFound
|
repoDeps = notFound
|
||||||
|
|
||||||
@ -562,7 +552,7 @@ func (b *Builder) BuildALRDeps(
|
|||||||
input.BuildOpts().Interactive,
|
input.BuildOpts().Interactive,
|
||||||
)
|
)
|
||||||
type item struct {
|
type item struct {
|
||||||
pkg *db.Package
|
pkg *alrsh.Package
|
||||||
packages []string
|
packages []string
|
||||||
}
|
}
|
||||||
pkgsMap := make(map[string]*item)
|
pkgsMap := make(map[string]*item)
|
||||||
@ -597,20 +587,17 @@ func (b *Builder) BuildALRDeps(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, fmt.Errorf("failed build package from db: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
builtPaths = append(builtPaths, res.PackagePaths...)
|
buildDeps = append(buildDeps, res...)
|
||||||
builtNames = append(builtNames, res.PackageNames...)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Удаляем возможные дубликаты, которые могут быть введены, если
|
|
||||||
// несколько зависимостей зависят от одних и тех же пакетов.
|
|
||||||
repoDeps = removeDuplicates(repoDeps)
|
repoDeps = removeDuplicates(repoDeps)
|
||||||
builtPaths = removeDuplicates(builtPaths)
|
buildDeps = removeDuplicates(buildDeps)
|
||||||
builtNames = removeDuplicates(builtNames)
|
|
||||||
return builtPaths, builtNames, repoDeps, nil
|
return buildDeps, repoDeps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Builder) installBuildDeps(
|
func (i *Builder) installBuildDeps(
|
||||||
@ -621,19 +608,20 @@ func (i *Builder) installBuildDeps(
|
|||||||
PkgFormatProvider
|
PkgFormatProvider
|
||||||
},
|
},
|
||||||
pkgs []string,
|
pkgs []string,
|
||||||
) error {
|
) ([]*BuiltDep, error) {
|
||||||
|
var builtDeps []*BuiltDep
|
||||||
if len(pkgs) > 0 {
|
if len(pkgs) > 0 {
|
||||||
deps, err := i.installerExecutor.RemoveAlreadyInstalled(pkgs)
|
deps, err := i.installerExecutor.RemoveAlreadyInstalled(pkgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = i.InstallPkgs(ctx, input, deps) // Устанавливаем выбранные пакеты
|
builtDeps, err = i.InstallPkgs(ctx, input, deps) // Устанавливаем выбранные пакеты
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return builtDeps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Builder) installOptDeps(
|
func (i *Builder) installOptDeps(
|
||||||
@ -644,10 +632,11 @@ func (i *Builder) installOptDeps(
|
|||||||
PkgFormatProvider
|
PkgFormatProvider
|
||||||
},
|
},
|
||||||
pkgs []string,
|
pkgs []string,
|
||||||
) error {
|
) ([]*BuiltDep, error) {
|
||||||
|
var builtDeps []*BuiltDep
|
||||||
optDeps, err := i.installerExecutor.RemoveAlreadyInstalled(pkgs)
|
optDeps, err := i.installerExecutor.RemoveAlreadyInstalled(pkgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(optDeps) > 0 {
|
if len(optDeps) > 0 {
|
||||||
optDeps, err := cliutils.ChooseOptDepends(
|
optDeps, err := cliutils.ChooseOptDepends(
|
||||||
@ -657,19 +646,19 @@ func (i *Builder) installOptDeps(
|
|||||||
input.BuildOpts().Interactive,
|
input.BuildOpts().Interactive,
|
||||||
) // Пользователя просят выбрать опциональные зависимости
|
) // Пользователя просят выбрать опциональные зависимости
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(optDeps) == 0 {
|
if len(optDeps) == 0 {
|
||||||
return nil
|
return builtDeps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err = i.InstallPkgs(ctx, input, optDeps) // Устанавливаем выбранные пакеты
|
builtDeps, err = i.InstallPkgs(ctx, input, optDeps) // Устанавливаем выбранные пакеты
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return builtDeps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Builder) InstallPkgs(
|
func (i *Builder) InstallPkgs(
|
||||||
@ -680,18 +669,18 @@ func (i *Builder) InstallPkgs(
|
|||||||
PkgFormatProvider
|
PkgFormatProvider
|
||||||
},
|
},
|
||||||
pkgs []string,
|
pkgs []string,
|
||||||
) error {
|
) ([]*BuiltDep, error) {
|
||||||
builtPaths, _, repoDeps, err := i.BuildALRDeps(ctx, input, pkgs)
|
builtDeps, repoDeps, err := i.BuildALRDeps(ctx, input, pkgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(builtPaths) > 0 {
|
if len(builtDeps) > 0 {
|
||||||
err = i.installerExecutor.InstallLocal(builtPaths, &manager.Opts{
|
err = i.installerExecutor.InstallLocal(GetBuiltPaths(builtDeps), &manager.Opts{
|
||||||
NoConfirm: !input.BuildOpts().Interactive,
|
NoConfirm: !input.BuildOpts().Interactive,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -700,9 +689,9 @@ func (i *Builder) InstallPkgs(
|
|||||||
NoConfirm: !input.BuildOpts().Interactive,
|
NoConfirm: !input.BuildOpts().Interactive,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return builtDeps, nil
|
||||||
}
|
}
|
@ -28,9 +28,9 @@ import (
|
|||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TestPackageFinder struct {
|
type TestPackageFinder struct {
|
@ -23,7 +23,7 @@ import (
|
|||||||
|
|
||||||
"github.com/goreleaser/nfpm/v2"
|
"github.com/goreleaser/nfpm/v2"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Cache struct {
|
type Cache struct {
|
||||||
@ -33,7 +33,7 @@ type Cache struct {
|
|||||||
func (c *Cache) CheckForBuiltPackage(
|
func (c *Cache) CheckForBuiltPackage(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
input *BuildInput,
|
input *BuildInput,
|
||||||
vars *types.BuildVars,
|
vars *alrsh.Package,
|
||||||
) (string, bool, error) {
|
) (string, bool, error) {
|
||||||
filename, err := pkgFileName(input, vars)
|
filename, err := pkgFileName(input, vars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -56,7 +56,7 @@ func pkgFileName(
|
|||||||
PkgFormatProvider
|
PkgFormatProvider
|
||||||
RepositoryProvider
|
RepositoryProvider
|
||||||
},
|
},
|
||||||
vars *types.BuildVars,
|
vars *alrsh.Package,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
pkgInfo := getBasePkgInfo(vars, input)
|
pkgInfo := getBasePkgInfo(vars, input)
|
||||||
|
|
@ -24,8 +24,8 @@ import (
|
|||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Checker struct {
|
type Checker struct {
|
||||||
@ -35,7 +35,7 @@ type Checker struct {
|
|||||||
func (c *Checker) PerformChecks(
|
func (c *Checker) PerformChecks(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
input *BuildInput,
|
input *BuildInput,
|
||||||
vars *types.BuildVars,
|
vars *alrsh.Package,
|
||||||
) (bool, error) {
|
) (bool, error) {
|
||||||
if !cpu.IsCompatibleWith(cpu.Arch(), vars.Architectures) { // Проверяем совместимость архитектуры
|
if !cpu.IsCompatibleWith(cpu.Arch(), vars.Architectures) { // Проверяем совместимость архитектуры
|
||||||
cont, err := cliutils.YesNoPrompt(
|
cont, err := cliutils.YesNoPrompt(
|
@ -19,7 +19,7 @@ package build
|
|||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BaseDirProvider interface {
|
type BaseDirProvider interface {
|
@ -27,7 +27,7 @@ import (
|
|||||||
"github.com/goreleaser/nfpm/v2"
|
"github.com/goreleaser/nfpm/v2"
|
||||||
"github.com/leonelquinteros/gotext"
|
"github.com/leonelquinteros/gotext"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func rpmFindDependenciesALTLinux(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, command string, envs []string, updateFunc func(string)) error {
|
func rpmFindDependenciesALTLinux(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, command string, envs []string, updateFunc func(string)) error {
|
@ -23,7 +23,7 @@ import (
|
|||||||
"github.com/goreleaser/nfpm/v2"
|
"github.com/goreleaser/nfpm/v2"
|
||||||
"github.com/leonelquinteros/gotext"
|
"github.com/leonelquinteros/gotext"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type EmptyFindProvReq struct{}
|
type EmptyFindProvReq struct{}
|
@ -28,7 +28,7 @@ import (
|
|||||||
"github.com/goreleaser/nfpm/v2"
|
"github.com/goreleaser/nfpm/v2"
|
||||||
"github.com/leonelquinteros/gotext"
|
"github.com/leonelquinteros/gotext"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FedoraFindProvReq struct{}
|
type FedoraFindProvReq struct{}
|
@ -21,8 +21,8 @@ import (
|
|||||||
|
|
||||||
"github.com/goreleaser/nfpm/v2"
|
"github.com/goreleaser/nfpm/v2"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProvReqFinder interface {
|
type ProvReqFinder interface {
|
325
internal/build/firejail.go
Normal file
325
internal/build/firejail.go
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
// 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 build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/goreleaser/nfpm/v2/files"
|
||||||
|
"github.com/leonelquinteros/gotext"
|
||||||
|
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
firejailedDir = "/usr/lib/alr/firejailed"
|
||||||
|
defaultDirMode = 0o755
|
||||||
|
defaultScriptMode = 0o755
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidDestination = errors.New("invalid destination path")
|
||||||
|
ErrMissingProfile = errors.New("default profile is missing")
|
||||||
|
ErrEmptyPackageName = errors.New("package name cannot be empty")
|
||||||
|
)
|
||||||
|
|
||||||
|
var binaryDirectories = []string{
|
||||||
|
"/usr/bin/",
|
||||||
|
"/bin/",
|
||||||
|
"/usr/local/bin/",
|
||||||
|
}
|
||||||
|
|
||||||
|
func moveWithSymlinkHandling(src, dst string) error {
|
||||||
|
srcInfo, err := os.Lstat(src)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get source info: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create destination directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if srcInfo.Mode()&os.ModeSymlink != 0 {
|
||||||
|
return moveSymlink(src, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Rename(src, dst); err != nil {
|
||||||
|
return copyAndRemove(src, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func moveSymlink(src, dst string) error {
|
||||||
|
target, err := os.Readlink(src)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read symlink: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Symlink(target, dst); err != nil {
|
||||||
|
return fmt.Errorf("failed to create symlink: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Remove(src); err != nil {
|
||||||
|
os.Remove(dst)
|
||||||
|
return fmt.Errorf("failed to remove original symlink: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyAndRemove(src, dst string) error {
|
||||||
|
srcFile, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open source: %w", err)
|
||||||
|
}
|
||||||
|
defer srcFile.Close()
|
||||||
|
|
||||||
|
dstFile, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create destination: %w", err)
|
||||||
|
}
|
||||||
|
defer dstFile.Close()
|
||||||
|
|
||||||
|
if _, err := io.Copy(dstFile, srcFile); err != nil {
|
||||||
|
return fmt.Errorf("failed to copy content: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
srcInfo, err := srcFile.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get source stats: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dstFile.Chmod(srcInfo.Mode()); err != nil {
|
||||||
|
return fmt.Errorf("failed to set permissions: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Remove(src); err != nil {
|
||||||
|
return fmt.Errorf("failed to remove source: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func moveFileWithErrorHandling(src, dst string) error {
|
||||||
|
err := moveWithSymlinkHandling(src, dst)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsPermission(err) {
|
||||||
|
return fmt.Errorf("permission denied: %w", err)
|
||||||
|
}
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("source file does not exist: %w", err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("failed to move file: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyFirejailIntegration(
|
||||||
|
vars *alrsh.Package,
|
||||||
|
dirs types.Directories,
|
||||||
|
contents []*files.Content,
|
||||||
|
) ([]*files.Content, error) {
|
||||||
|
slog.Info(gotext.Get("Applying FireJail integration"), "package", vars.Name)
|
||||||
|
|
||||||
|
if err := createFirejailedDirectory(dirs.PkgDir); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create firejailed directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newContents, err := processBinaryFiles(vars, contents, dirs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to process binary files: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(contents, newContents...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFirejailedDirectory(pkgDir string) error {
|
||||||
|
firejailedPath := filepath.Join(pkgDir, firejailedDir)
|
||||||
|
return os.MkdirAll(firejailedPath, defaultDirMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func processBinaryFiles(pkg *alrsh.Package, contents []*files.Content, dirs types.Directories) ([]*files.Content, error) {
|
||||||
|
var newContents []*files.Content
|
||||||
|
|
||||||
|
for _, content := range contents {
|
||||||
|
if content.Type == "dir" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isBinaryFile(content.Destination) {
|
||||||
|
slog.Debug("content not binary file", "content", content)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Debug("process content", "content", content)
|
||||||
|
|
||||||
|
newContent, err := createFirejailedBinary(pkg, content, dirs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create firejailed binary for %s: %w", content.Destination, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if newContent != nil {
|
||||||
|
newContents = append(newContents, newContent...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newContents, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isBinaryFile(destination string) bool {
|
||||||
|
for _, binDir := range binaryDirectories {
|
||||||
|
if strings.HasPrefix(destination, binDir) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFirejailedBinary(
|
||||||
|
pkg *alrsh.Package,
|
||||||
|
content *files.Content,
|
||||||
|
dirs types.Directories,
|
||||||
|
) ([]*files.Content, error) {
|
||||||
|
origFilePath, err := generateFirejailedPath(content.Destination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
profiles := pkg.FireJailProfiles.Resolved()
|
||||||
|
sourceProfilePath, ok := profiles[content.Destination]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
sourceProfilePath, ok = profiles["default"]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("default profile is missing")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceProfilePath = filepath.Join(dirs.ScriptDir, sourceProfilePath)
|
||||||
|
dest, err := createFirejailProfilePath(content.Destination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = createProfile(filepath.Join(dirs.PkgDir, dest), sourceProfilePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := moveFileWithErrorHandling(filepath.Join(dirs.PkgDir, content.Destination), filepath.Join(dirs.PkgDir, origFilePath)); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to move original binary: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
content.Type = "file"
|
||||||
|
content.Source = filepath.Join(dirs.PkgDir, content.Destination)
|
||||||
|
|
||||||
|
// Create wrapper script
|
||||||
|
if err := createWrapperScript(filepath.Join(dirs.PkgDir, content.Destination), origFilePath, dest); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create wrapper script: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
profile, err := getContentFromPath(dest, dirs.PkgDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bin, err := getContentFromPath(origFilePath, dirs.PkgDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return []*files.Content{
|
||||||
|
bin,
|
||||||
|
profile,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getContentFromPath(path, base string) (*files.Content, error) {
|
||||||
|
absPath := filepath.Join(base, path)
|
||||||
|
|
||||||
|
fi, err := os.Lstat(absPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get file info: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &files.Content{
|
||||||
|
Source: absPath,
|
||||||
|
Destination: path,
|
||||||
|
FileInfo: &files.ContentFileInfo{
|
||||||
|
MTime: fi.ModTime(),
|
||||||
|
Mode: fi.Mode(),
|
||||||
|
Size: fi.Size(),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateSafeName(destination string) (string, error) {
|
||||||
|
cleanPath := strings.TrimPrefix(destination, ".")
|
||||||
|
if cleanPath == "" {
|
||||||
|
return "", fmt.Errorf("invalid destination path: %s", destination)
|
||||||
|
}
|
||||||
|
return strings.ReplaceAll(cleanPath, "/", "_"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateFirejailedPath(destination string) (string, error) {
|
||||||
|
safeName, err := generateSafeName(destination)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return filepath.Join(firejailedDir, safeName), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createProfile(destProfilePath, profilePath string) error {
|
||||||
|
srcFile, err := os.Open(profilePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer srcFile.Close()
|
||||||
|
|
||||||
|
destFile, err := os.Create(destProfilePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer destFile.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(destFile, srcFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return destFile.Sync()
|
||||||
|
}
|
||||||
|
|
||||||
|
func createWrapperScript(scriptPath, origFilePath, profilePath string) error {
|
||||||
|
scriptContent := fmt.Sprintf("#!/bin/bash\nexec firejail --profile=%q %q \"$@\"\n", profilePath, origFilePath)
|
||||||
|
return os.WriteFile(scriptPath, []byte(scriptContent), defaultDirMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFirejailProfilePath(binaryPath string) (string, error) {
|
||||||
|
name, err := generateSafeName(binaryPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return filepath.Join(firejailedDir, fmt.Sprintf("%s.profile", name)), nil
|
||||||
|
}
|
322
internal/build/firejail_test.go
Normal file
322
internal/build/firejail_test.go
Normal file
@ -0,0 +1,322 @@
|
|||||||
|
// 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 build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/goreleaser/nfpm/v2/files"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsBinaryFile(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
destination string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"usr/bin binary", "/usr/bin/test", true},
|
||||||
|
{"bin binary", "/bin/test", true},
|
||||||
|
{"usr/local/bin binary", "/usr/local/bin/test", true},
|
||||||
|
{"lib file", "/usr/lib/test.so", false},
|
||||||
|
{"etc file", "/etc/config", false},
|
||||||
|
{"empty destination", "", false},
|
||||||
|
{"root level file", "./test", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := isBinaryFile(tt.destination)
|
||||||
|
assert.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateSafeName(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
destination string
|
||||||
|
expected string
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{"usr/bin path", "./usr/bin/test", "_usr_bin_test", false},
|
||||||
|
{"bin path", "./bin/test", "_bin_test", false},
|
||||||
|
{"nested path", "./usr/local/bin/app", "_usr_local_bin_app", false},
|
||||||
|
{"path with spaces", "./usr/bin/my app", "_usr_bin_my app", false},
|
||||||
|
{"empty after trim", ".", "", true},
|
||||||
|
{"empty string", "", "", true},
|
||||||
|
{"only dots", "..", ".", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := generateSafeName(tt.destination)
|
||||||
|
if tt.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.expected, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateWrapperScript(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
origFilePath string
|
||||||
|
profilePath string
|
||||||
|
expectedContent string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"basic wrapper",
|
||||||
|
"/usr/lib/alr/firejailed/_usr_bin_test",
|
||||||
|
"/usr/lib/alr/firejailed/_usr_bin_test.profile",
|
||||||
|
"#!/bin/bash\nexec firejail --profile=\"/usr/lib/alr/firejailed/_usr_bin_test.profile\" \"/usr/lib/alr/firejailed/_usr_bin_test\" \"$@\"\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path with spaces",
|
||||||
|
"/usr/lib/alr/firejailed/_usr_bin_my_app",
|
||||||
|
"/usr/lib/alr/firejailed/_usr_bin_my_app.profile",
|
||||||
|
"#!/bin/bash\nexec firejail --profile=\"/usr/lib/alr/firejailed/_usr_bin_my_app.profile\" \"/usr/lib/alr/firejailed/_usr_bin_my_app\" \"$@\"\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
scriptPath := filepath.Join(tmpDir, "wrapper.sh")
|
||||||
|
|
||||||
|
err := createWrapperScript(scriptPath, tt.origFilePath, tt.profilePath)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.FileExists(t, scriptPath)
|
||||||
|
|
||||||
|
content, err := os.ReadFile(scriptPath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.expectedContent, string(content))
|
||||||
|
|
||||||
|
// Check file permissions
|
||||||
|
info, err := os.Stat(scriptPath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, os.FileMode(defaultDirMode), info.Mode())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateFirejailedBinary(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
setupFunc func(string) (*alrsh.Package, *files.Content, types.Directories)
|
||||||
|
expectError bool
|
||||||
|
errorMsg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"successful creation with default profile",
|
||||||
|
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
|
||||||
|
pkgDir := filepath.Join(tmpDir, "pkg")
|
||||||
|
scriptDir := filepath.Join(tmpDir, "scripts")
|
||||||
|
os.MkdirAll(pkgDir, 0o755)
|
||||||
|
os.MkdirAll(scriptDir, 0o755)
|
||||||
|
|
||||||
|
binDir := filepath.Join(pkgDir, "usr", "bin")
|
||||||
|
os.MkdirAll(binDir, 0o755)
|
||||||
|
|
||||||
|
srcBinary := filepath.Join(binDir, "test-binary")
|
||||||
|
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755)
|
||||||
|
|
||||||
|
defaultProfile := filepath.Join(scriptDir, "default.profile")
|
||||||
|
os.WriteFile(defaultProfile, []byte("include /etc/firejail/default.profile\nnet none"), 0o644)
|
||||||
|
|
||||||
|
pkg := &alrsh.Package{
|
||||||
|
Name: "test-pkg",
|
||||||
|
FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{
|
||||||
|
"": {"default": "default.profile"},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
alrsh.ResolvePackage(pkg, []string{""})
|
||||||
|
|
||||||
|
content := &files.Content{
|
||||||
|
Source: srcBinary,
|
||||||
|
Destination: "/usr/bin/test-binary",
|
||||||
|
Type: "file",
|
||||||
|
}
|
||||||
|
|
||||||
|
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
|
||||||
|
return pkg, content, dirs
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"successful creation with specific profile",
|
||||||
|
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
|
||||||
|
pkgDir := filepath.Join(tmpDir, "pkg")
|
||||||
|
scriptDir := filepath.Join(tmpDir, "scripts")
|
||||||
|
os.MkdirAll(pkgDir, 0o755)
|
||||||
|
os.MkdirAll(scriptDir, 0o755)
|
||||||
|
|
||||||
|
binDir := filepath.Join(pkgDir, "usr", "bin")
|
||||||
|
os.MkdirAll(binDir, 0o755)
|
||||||
|
|
||||||
|
srcBinary := filepath.Join(binDir, "special-binary")
|
||||||
|
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho special"), 0o755)
|
||||||
|
|
||||||
|
defaultProfile := filepath.Join(scriptDir, "default.profile")
|
||||||
|
os.WriteFile(defaultProfile, []byte("include /etc/firejail/default.profile"), 0o644)
|
||||||
|
|
||||||
|
specialProfile := filepath.Join(scriptDir, "special.profile")
|
||||||
|
os.WriteFile(specialProfile, []byte("include /etc/firejail/default.profile\nnet none\nprivate-tmp"), 0o644)
|
||||||
|
|
||||||
|
pkg := &alrsh.Package{
|
||||||
|
Name: "test-pkg",
|
||||||
|
FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{
|
||||||
|
"": {"default": "default.profile", "/usr/bin/special-binary": "special.profile"},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
alrsh.ResolvePackage(pkg, []string{""})
|
||||||
|
|
||||||
|
content := &files.Content{
|
||||||
|
Source: srcBinary,
|
||||||
|
Destination: "/usr/bin/special-binary",
|
||||||
|
Type: "file",
|
||||||
|
}
|
||||||
|
|
||||||
|
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
|
||||||
|
return pkg, content, dirs
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"missing default profile",
|
||||||
|
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
|
||||||
|
pkgDir := filepath.Join(tmpDir, "pkg")
|
||||||
|
scriptDir := filepath.Join(tmpDir, "scripts")
|
||||||
|
os.MkdirAll(pkgDir, 0o755)
|
||||||
|
os.MkdirAll(scriptDir, 0o755)
|
||||||
|
|
||||||
|
srcBinary := filepath.Join(tmpDir, "test-binary")
|
||||||
|
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755)
|
||||||
|
|
||||||
|
pkg := &alrsh.Package{
|
||||||
|
Name: "test-pkg",
|
||||||
|
FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{"": {}}),
|
||||||
|
}
|
||||||
|
alrsh.ResolvePackage(pkg, []string{""})
|
||||||
|
|
||||||
|
content := &files.Content{Source: srcBinary, Destination: "./usr/bin/test-binary", Type: "file"}
|
||||||
|
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
|
||||||
|
return pkg, content, dirs
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
"default profile is missing",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"profile file not found",
|
||||||
|
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
|
||||||
|
pkgDir := filepath.Join(tmpDir, "pkg")
|
||||||
|
scriptDir := filepath.Join(tmpDir, "scripts")
|
||||||
|
os.MkdirAll(pkgDir, 0o755)
|
||||||
|
os.MkdirAll(scriptDir, 0o755)
|
||||||
|
|
||||||
|
srcBinary := filepath.Join(tmpDir, "test-binary")
|
||||||
|
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755)
|
||||||
|
|
||||||
|
pkg := &alrsh.Package{
|
||||||
|
Name: "test-pkg",
|
||||||
|
FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{"": {"default": "nonexistent.profile"}}),
|
||||||
|
}
|
||||||
|
alrsh.ResolvePackage(pkg, []string{""})
|
||||||
|
|
||||||
|
content := &files.Content{Source: srcBinary, Destination: "./usr/bin/test-binary", Type: "file"}
|
||||||
|
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
|
||||||
|
return pkg, content, dirs
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid destination path",
|
||||||
|
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
|
||||||
|
pkgDir := filepath.Join(tmpDir, "pkg")
|
||||||
|
scriptDir := filepath.Join(tmpDir, "scripts")
|
||||||
|
os.MkdirAll(pkgDir, 0o755)
|
||||||
|
os.MkdirAll(scriptDir, 0o755)
|
||||||
|
|
||||||
|
srcBinary := filepath.Join(tmpDir, "test-binary")
|
||||||
|
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755)
|
||||||
|
|
||||||
|
defaultProfile := filepath.Join(scriptDir, "default.profile")
|
||||||
|
os.WriteFile(defaultProfile, []byte("include /etc/firejail/default.profile"), 0o644)
|
||||||
|
|
||||||
|
pkg := &alrsh.Package{Name: "test-pkg", FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{"": {"default": "default.profile"}})}
|
||||||
|
alrsh.ResolvePackage(pkg, []string{""})
|
||||||
|
|
||||||
|
content := &files.Content{Source: srcBinary, Destination: ".", Type: "file"}
|
||||||
|
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
|
||||||
|
return pkg, content, dirs
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
pkg, content, dirs := tt.setupFunc(tmpDir)
|
||||||
|
|
||||||
|
err := createFirejailedDirectory(dirs.PkgDir)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
result, err := createFirejailedBinary(pkg, content, dirs)
|
||||||
|
|
||||||
|
if tt.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
if tt.errorMsg != "" {
|
||||||
|
assert.Contains(t, err.Error(), tt.errorMsg)
|
||||||
|
}
|
||||||
|
assert.Nil(t, result)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, result, 2)
|
||||||
|
|
||||||
|
binContent := result[0]
|
||||||
|
assert.Contains(t, binContent.Destination, "usr/lib/alr/firejailed/")
|
||||||
|
assert.FileExists(t, binContent.Source)
|
||||||
|
|
||||||
|
profileContent := result[1]
|
||||||
|
assert.Contains(t, profileContent.Destination, "usr/lib/alr/firejailed/")
|
||||||
|
assert.Contains(t, profileContent.Destination, ".profile")
|
||||||
|
assert.FileExists(t, profileContent.Source)
|
||||||
|
|
||||||
|
assert.FileExists(t, content.Source)
|
||||||
|
wrapperBytes, err := os.ReadFile(content.Source)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
wrapper := string(wrapperBytes)
|
||||||
|
assert.Contains(t, wrapper, "#!/bin/bash")
|
||||||
|
assert.Contains(t, wrapper, "firejail --profile=")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -17,7 +17,7 @@
|
|||||||
package build
|
package build
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewInstaller(mgr manager.Manager) *Installer {
|
func NewInstaller(mgr manager.Manager) *Installer {
|
@ -17,7 +17,7 @@
|
|||||||
package build
|
package build
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewMainBuilder(
|
func NewMainBuilder(
|
@ -28,7 +28,7 @@ import (
|
|||||||
"github.com/hashicorp/go-plugin"
|
"github.com/hashicorp/go-plugin"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
|
||||||
)
|
)
|
||||||
|
|
||||||
type InstallerPlugin struct {
|
type InstallerPlugin struct {
|
@ -28,7 +28,7 @@ import (
|
|||||||
"github.com/hashicorp/go-plugin"
|
"github.com/hashicorp/go-plugin"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
)
|
)
|
||||||
|
|
||||||
var HandshakeConfig = plugin.HandshakeConfig{
|
var HandshakeConfig = plugin.HandshakeConfig{
|
||||||
@ -50,13 +50,13 @@ type ScriptExecutorRPCServer struct {
|
|||||||
// ReadScript
|
// ReadScript
|
||||||
//
|
//
|
||||||
|
|
||||||
func (s *ScriptExecutorRPC) ReadScript(ctx context.Context, scriptPath string) (*ScriptFile, error) {
|
func (s *ScriptExecutorRPC) ReadScript(ctx context.Context, scriptPath string) (*alrsh.ScriptFile, error) {
|
||||||
var resp *ScriptFile
|
var resp *alrsh.ScriptFile
|
||||||
err := s.client.Call("Plugin.ReadScript", scriptPath, &resp)
|
err := s.client.Call("Plugin.ReadScript", scriptPath, &resp)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ScriptExecutorRPCServer) ReadScript(scriptPath string, resp *ScriptFile) error {
|
func (s *ScriptExecutorRPCServer) ReadScript(scriptPath string, resp *alrsh.ScriptFile) error {
|
||||||
file, err := s.Impl.ReadScript(context.Background(), scriptPath)
|
file, err := s.Impl.ReadScript(context.Background(), scriptPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -72,15 +72,15 @@ func (s *ScriptExecutorRPCServer) ReadScript(scriptPath string, resp *ScriptFile
|
|||||||
|
|
||||||
type ExecuteFirstPassArgs struct {
|
type ExecuteFirstPassArgs struct {
|
||||||
Input *BuildInput
|
Input *BuildInput
|
||||||
Sf *ScriptFile
|
Sf *alrsh.ScriptFile
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExecuteFirstPassResp struct {
|
type ExecuteFirstPassResp struct {
|
||||||
BasePkg string
|
BasePkg string
|
||||||
VarsOfPackages []*types.BuildVars
|
VarsOfPackages []*alrsh.Package
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ScriptExecutorRPC) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *ScriptFile) (string, []*types.BuildVars, error) {
|
func (s *ScriptExecutorRPC) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile) (string, []*alrsh.Package, error) {
|
||||||
var resp *ExecuteFirstPassResp
|
var resp *ExecuteFirstPassResp
|
||||||
err := s.client.Call("Plugin.ExecuteFirstPass", &ExecuteFirstPassArgs{
|
err := s.client.Call("Plugin.ExecuteFirstPass", &ExecuteFirstPassArgs{
|
||||||
Input: input,
|
Input: input,
|
||||||
@ -148,29 +148,29 @@ func (s *ScriptExecutorRPCServer) PrepareDirs(args *PrepareDirsArgs, reply *stru
|
|||||||
|
|
||||||
type ExecuteSecondPassArgs struct {
|
type ExecuteSecondPassArgs struct {
|
||||||
Input *BuildInput
|
Input *BuildInput
|
||||||
Sf *ScriptFile
|
Sf *alrsh.ScriptFile
|
||||||
VarsOfPackages []*types.BuildVars
|
VarsOfPackages []*alrsh.Package
|
||||||
RepoDeps []string
|
RepoDeps []string
|
||||||
BuiltNames []string
|
BuiltDeps []*BuiltDep
|
||||||
BasePkg string
|
BasePkg string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ScriptExecutorRPC) ExecuteSecondPass(
|
func (s *ScriptExecutorRPC) ExecuteSecondPass(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
input *BuildInput,
|
input *BuildInput,
|
||||||
sf *ScriptFile,
|
sf *alrsh.ScriptFile,
|
||||||
varsOfPackages []*types.BuildVars,
|
varsOfPackages []*alrsh.Package,
|
||||||
repoDeps []string,
|
repoDeps []string,
|
||||||
builtNames []string,
|
builtDeps []*BuiltDep,
|
||||||
basePkg string,
|
basePkg string,
|
||||||
) (*SecondPassResult, error) {
|
) ([]*BuiltDep, error) {
|
||||||
var resp *SecondPassResult
|
var resp []*BuiltDep
|
||||||
err := s.client.Call("Plugin.ExecuteSecondPass", &ExecuteSecondPassArgs{
|
err := s.client.Call("Plugin.ExecuteSecondPass", &ExecuteSecondPassArgs{
|
||||||
Input: input,
|
Input: input,
|
||||||
Sf: sf,
|
Sf: sf,
|
||||||
VarsOfPackages: varsOfPackages,
|
VarsOfPackages: varsOfPackages,
|
||||||
RepoDeps: repoDeps,
|
RepoDeps: repoDeps,
|
||||||
BuiltNames: builtNames,
|
BuiltDeps: builtDeps,
|
||||||
BasePkg: basePkg,
|
BasePkg: basePkg,
|
||||||
}, &resp)
|
}, &resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -179,20 +179,20 @@ func (s *ScriptExecutorRPC) ExecuteSecondPass(
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ScriptExecutorRPCServer) ExecuteSecondPass(args *ExecuteSecondPassArgs, resp *SecondPassResult) error {
|
func (s *ScriptExecutorRPCServer) ExecuteSecondPass(args *ExecuteSecondPassArgs, resp *[]*BuiltDep) error {
|
||||||
res, err := s.Impl.ExecuteSecondPass(
|
res, err := s.Impl.ExecuteSecondPass(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
args.Input,
|
args.Input,
|
||||||
args.Sf,
|
args.Sf,
|
||||||
args.VarsOfPackages,
|
args.VarsOfPackages,
|
||||||
args.RepoDeps,
|
args.RepoDeps,
|
||||||
args.BuiltNames,
|
args.BuiltDeps,
|
||||||
args.BasePkg,
|
args.BasePkg,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
*resp = *res
|
*resp = res
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -19,7 +19,6 @@ package build
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
@ -36,12 +35,12 @@ import (
|
|||||||
"mvdan.cc/sh/v3/interp"
|
"mvdan.cc/sh/v3/interp"
|
||||||
"mvdan.cc/sh/v3/syntax"
|
"mvdan.cc/sh/v3/syntax"
|
||||||
|
|
||||||
|
finddeps "gitea.plemya-x.ru/Plemya-x/ALR/internal/build/find_deps"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/helpers"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/helpers"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
finddeps "gitea.plemya-x.ru/Plemya-x/ALR/pkg/build/find_deps"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type LocalScriptExecutor struct {
|
type LocalScriptExecutor struct {
|
||||||
@ -54,107 +53,12 @@ func NewLocalScriptExecutor(cfg Config) *LocalScriptExecutor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *LocalScriptExecutor) ReadScript(ctx context.Context, scriptPath string) (*ScriptFile, error) {
|
func (e *LocalScriptExecutor) ReadScript(ctx context.Context, scriptPath string) (*alrsh.ScriptFile, error) {
|
||||||
fl, err := readScript(scriptPath)
|
return alrsh.ReadFromLocal(scriptPath)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &ScriptFile{
|
|
||||||
Path: scriptPath,
|
|
||||||
File: fl,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *LocalScriptExecutor) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *ScriptFile) (string, []*types.BuildVars, error) {
|
func (e *LocalScriptExecutor) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile) (string, []*alrsh.Package, error) {
|
||||||
varsOfPackages := []*types.BuildVars{}
|
return sf.ParseBuildVars(ctx, input.info, input.packages)
|
||||||
|
|
||||||
scriptDir := filepath.Dir(sf.Path)
|
|
||||||
env := createBuildEnvVars(input.info, types.Directories{ScriptDir: scriptDir})
|
|
||||||
|
|
||||||
runner, err := interp.New(
|
|
||||||
interp.Env(expand.ListEnviron(env...)), // Устанавливаем окружение
|
|
||||||
interp.StdIO(os.Stdin, os.Stderr, os.Stderr), // Устанавливаем стандартный ввод-вывод
|
|
||||||
interp.ExecHandler(helpers.Restricted.ExecHandler(handlers.NopExec)), // Ограничиваем выполнение
|
|
||||||
interp.ReadDirHandler2(handlers.RestrictedReadDir(scriptDir)), // Ограничиваем чтение директорий
|
|
||||||
interp.StatHandler(handlers.RestrictedStat(scriptDir)), // Ограничиваем доступ к статистике файлов
|
|
||||||
interp.OpenHandler(handlers.RestrictedOpen(scriptDir)), // Ограничиваем открытие файлов
|
|
||||||
interp.Dir(scriptDir),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = runner.Run(ctx, sf.File) // Запускаем скрипт
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
dec := decoder.New(input.info, runner) // Создаём новый декодер
|
|
||||||
|
|
||||||
type packages struct {
|
|
||||||
BasePkgName string `sh:"basepkg_name"`
|
|
||||||
Names []string `sh:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var pkgs packages
|
|
||||||
err = dec.DecodeVars(&pkgs)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(pkgs.Names) == 0 {
|
|
||||||
return "", nil, errors.New("package name is missing")
|
|
||||||
}
|
|
||||||
|
|
||||||
var vars types.BuildVars
|
|
||||||
|
|
||||||
if len(pkgs.Names) == 1 {
|
|
||||||
err = dec.DecodeVars(&vars) // Декодируем переменные
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
varsOfPackages = append(varsOfPackages, &vars)
|
|
||||||
|
|
||||||
return vars.Name, varsOfPackages, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var pkgNames []string
|
|
||||||
|
|
||||||
if len(input.packages) != 0 {
|
|
||||||
pkgNames = input.packages
|
|
||||||
} else {
|
|
||||||
pkgNames = pkgs.Names
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, pkgName := range pkgNames {
|
|
||||||
var preVars types.BuildVarsPre
|
|
||||||
funcName := fmt.Sprintf("meta_%s", pkgName)
|
|
||||||
meta, ok := dec.GetFuncWithSubshell(funcName)
|
|
||||||
if !ok {
|
|
||||||
return "", nil, fmt.Errorf("func %s is missing", funcName)
|
|
||||||
}
|
|
||||||
r, err := meta(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
d := decoder.New(&distro.OSRelease{}, r)
|
|
||||||
err = d.DecodeVars(&preVars)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
vars := preVars.ToBuildVars()
|
|
||||||
vars.Name = pkgName
|
|
||||||
vars.Base = pkgs.BasePkgName
|
|
||||||
|
|
||||||
varsOfPackages = append(varsOfPackages, &vars)
|
|
||||||
}
|
|
||||||
|
|
||||||
return pkgs.BasePkgName, varsOfPackages, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type SecondPassResult struct {
|
|
||||||
BuiltPaths []string
|
|
||||||
BuiltNames []string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *LocalScriptExecutor) PrepareDirs(
|
func (e *LocalScriptExecutor) PrepareDirs(
|
||||||
@ -182,13 +86,13 @@ func (e *LocalScriptExecutor) PrepareDirs(
|
|||||||
func (e *LocalScriptExecutor) ExecuteSecondPass(
|
func (e *LocalScriptExecutor) ExecuteSecondPass(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
input *BuildInput,
|
input *BuildInput,
|
||||||
sf *ScriptFile,
|
sf *alrsh.ScriptFile,
|
||||||
varsOfPackages []*types.BuildVars,
|
varsOfPackages []*alrsh.Package,
|
||||||
repoDeps []string,
|
repoDeps []string,
|
||||||
builtNames []string,
|
builtDeps []*BuiltDep,
|
||||||
basePkg string,
|
basePkg string,
|
||||||
) (*SecondPassResult, error) {
|
) ([]*BuiltDep, error) {
|
||||||
dirs, err := getDirs(e.cfg, sf.Path, basePkg)
|
dirs, err := getDirs(e.cfg, sf.Path(), basePkg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -206,14 +110,14 @@ func (e *LocalScriptExecutor) ExecuteSecondPass(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = runner.Run(ctx, sf.File)
|
err = runner.Run(ctx, sf.File())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
dec := decoder.New(input.info, runner)
|
dec := decoder.New(input.info, runner)
|
||||||
|
|
||||||
var builtPaths []string
|
// var builtPaths []string
|
||||||
|
|
||||||
err = e.ExecuteFunctions(ctx, dirs, dec)
|
err = e.ExecuteFunctions(ctx, dirs, dec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -222,7 +126,7 @@ func (e *LocalScriptExecutor) ExecuteSecondPass(
|
|||||||
|
|
||||||
for _, vars := range varsOfPackages {
|
for _, vars := range varsOfPackages {
|
||||||
packageName := ""
|
packageName := ""
|
||||||
if vars.Base != "" {
|
if vars.BasePkgName != "" {
|
||||||
packageName = vars.Name
|
packageName = vars.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,7 +151,7 @@ func (e *LocalScriptExecutor) ExecuteSecondPass(
|
|||||||
dirs,
|
dirs,
|
||||||
append(
|
append(
|
||||||
repoDeps,
|
repoDeps,
|
||||||
builtNames...,
|
GetBuiltName(builtDeps)...,
|
||||||
),
|
),
|
||||||
funcOut.Contents,
|
funcOut.Contents,
|
||||||
)
|
)
|
||||||
@ -273,14 +177,13 @@ func (e *LocalScriptExecutor) ExecuteSecondPass(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
builtPaths = append(builtPaths, pkgPath)
|
builtDeps = append(builtDeps, &BuiltDep{
|
||||||
builtNames = append(builtNames, vars.Name)
|
Name: vars.Name,
|
||||||
|
Path: pkgPath,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return &SecondPassResult{
|
return builtDeps, nil
|
||||||
BuiltPaths: builtPaths,
|
|
||||||
BuiltNames: builtNames,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildPkgMetadata(
|
func buildPkgMetadata(
|
||||||
@ -291,24 +194,24 @@ func buildPkgMetadata(
|
|||||||
PkgFormatProvider
|
PkgFormatProvider
|
||||||
RepositoryProvider
|
RepositoryProvider
|
||||||
},
|
},
|
||||||
vars *types.BuildVars,
|
vars *alrsh.Package,
|
||||||
dirs types.Directories,
|
dirs types.Directories,
|
||||||
deps []string,
|
deps []string,
|
||||||
preferedContents *[]string,
|
preferedContents *[]string,
|
||||||
) (*nfpm.Info, error) {
|
) (*nfpm.Info, error) {
|
||||||
pkgInfo := getBasePkgInfo(vars, input)
|
pkgInfo := getBasePkgInfo(vars, input)
|
||||||
pkgInfo.Description = vars.Description
|
pkgInfo.Description = vars.Description.Resolved()
|
||||||
pkgInfo.Platform = "linux"
|
pkgInfo.Platform = "linux"
|
||||||
pkgInfo.Homepage = vars.Homepage
|
pkgInfo.Homepage = vars.Homepage.Resolved()
|
||||||
pkgInfo.License = strings.Join(vars.Licenses, ", ")
|
pkgInfo.License = strings.Join(vars.Licenses, ", ")
|
||||||
pkgInfo.Maintainer = vars.Maintainer
|
pkgInfo.Maintainer = vars.Maintainer.Resolved()
|
||||||
pkgInfo.Overridables = nfpm.Overridables{
|
pkgInfo.Overridables = nfpm.Overridables{
|
||||||
Conflicts: append(vars.Conflicts, vars.Name),
|
Conflicts: append(vars.Conflicts, vars.Name),
|
||||||
Replaces: vars.Replaces,
|
Replaces: vars.Replaces,
|
||||||
Provides: append(vars.Provides, vars.Name),
|
Provides: append(vars.Provides, vars.Name),
|
||||||
Depends: deps,
|
Depends: deps,
|
||||||
}
|
}
|
||||||
pkgInfo.Section = vars.Group
|
pkgInfo.Section = vars.Group.Resolved()
|
||||||
|
|
||||||
pkgFormat := input.PkgFormat()
|
pkgFormat := input.PkgFormat()
|
||||||
info := input.OSRelease()
|
info := input.OSRelease()
|
||||||
@ -321,12 +224,12 @@ func buildPkgMetadata(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if pkgFormat == "rpm" {
|
if pkgFormat == "rpm" {
|
||||||
pkgInfo.RPM.Group = vars.Group
|
pkgInfo.RPM.Group = vars.Group.Resolved()
|
||||||
|
|
||||||
if vars.Summary != "" {
|
if vars.Summary.Resolved() != "" {
|
||||||
pkgInfo.RPM.Summary = vars.Summary
|
pkgInfo.RPM.Summary = vars.Summary.Resolved()
|
||||||
} else {
|
} else {
|
||||||
lines := strings.SplitN(vars.Description, "\n", 2)
|
lines := strings.SplitN(vars.Description.Resolved(), "\n", 2)
|
||||||
pkgInfo.RPM.Summary = lines[0]
|
pkgInfo.RPM.Summary = lines[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -345,19 +248,29 @@ func buildPkgMetadata(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
pkgInfo.Overridables.Contents = contents
|
|
||||||
|
|
||||||
if len(vars.AutoProv) == 1 && decoder.IsTruthy(vars.AutoProv[0]) {
|
normalizeContents(contents)
|
||||||
f := finddeps.New(info, pkgFormat)
|
|
||||||
err = f.FindProvides(ctx, pkgInfo, dirs, vars.AutoProvSkipList)
|
if vars.FireJailed.Resolved() {
|
||||||
|
contents, err = applyFirejailIntegration(vars, dirs, contents)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(vars.AutoReq) == 1 && decoder.IsTruthy(vars.AutoReq[0]) {
|
pkgInfo.Overridables.Contents = contents
|
||||||
|
|
||||||
|
if len(vars.AutoProv.Resolved()) == 1 && decoder.IsTruthy(vars.AutoProv.Resolved()[0]) {
|
||||||
f := finddeps.New(info, pkgFormat)
|
f := finddeps.New(info, pkgFormat)
|
||||||
err = f.FindRequires(ctx, pkgInfo, dirs, vars.AutoReqSkipList)
|
err = f.FindProvides(ctx, pkgInfo, dirs, vars.AutoProvSkipList.Resolved())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(vars.AutoReq.Resolved()) == 1 && decoder.IsTruthy(vars.AutoReq.Resolved()[0]) {
|
||||||
|
f := finddeps.New(info, pkgFormat)
|
||||||
|
err = f.FindRequires(ctx, pkgInfo, dirs, vars.AutoReqSkipList.Resolved())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
@ -18,9 +18,10 @@ package build
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ScriptResolver struct {
|
type ScriptResolver struct {
|
||||||
@ -34,16 +35,27 @@ type ScriptInfo struct {
|
|||||||
|
|
||||||
func (s *ScriptResolver) ResolveScript(
|
func (s *ScriptResolver) ResolveScript(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
pkg *db.Package,
|
pkg *alrsh.Package,
|
||||||
) *ScriptInfo {
|
) *ScriptInfo {
|
||||||
var repository, script string
|
var repository, script string
|
||||||
|
|
||||||
repodir := s.cfg.GetPaths().RepoDir
|
repodir := s.cfg.GetPaths().RepoDir
|
||||||
repository = pkg.Repository
|
repository = pkg.Repository
|
||||||
if pkg.BasePkgName != "" {
|
|
||||||
script = filepath.Join(repodir, repository, pkg.BasePkgName, "alr.sh")
|
// First, we check if there is a root alr.sh in the repository
|
||||||
|
rootScriptPath := filepath.Join(repodir, repository, "alr.sh")
|
||||||
|
if _, err := os.Stat(rootScriptPath); err == nil {
|
||||||
|
// A repository with a single alr.sh at the root
|
||||||
|
script = rootScriptPath
|
||||||
} else {
|
} else {
|
||||||
script = filepath.Join(repodir, repository, pkg.Name, "alr.sh")
|
// Multi-package repository - we are looking for alr.sh in the subfolder
|
||||||
|
var scriptPath string
|
||||||
|
if pkg.BasePkgName != "" {
|
||||||
|
scriptPath = filepath.Join(repodir, repository, pkg.BasePkgName, "alr.sh")
|
||||||
|
} else {
|
||||||
|
scriptPath = filepath.Join(repodir, repository, pkg.Name, "alr.sh")
|
||||||
|
}
|
||||||
|
script = scriptPath
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ScriptInfo{
|
return &ScriptInfo{
|
@ -20,6 +20,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ScriptViewerConfig interface {
|
type ScriptViewerConfig interface {
|
||||||
@ -33,12 +34,12 @@ type ScriptViewer struct {
|
|||||||
func (s *ScriptViewer) ViewScript(
|
func (s *ScriptViewer) ViewScript(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
input *BuildInput,
|
input *BuildInput,
|
||||||
sf *ScriptFile,
|
a *alrsh.ScriptFile,
|
||||||
basePkg string,
|
basePkg string,
|
||||||
) error {
|
) error {
|
||||||
return cliutils.PromptViewScript(
|
return cliutils.PromptViewScript(
|
||||||
ctx,
|
ctx,
|
||||||
sf.Path,
|
a.Path(),
|
||||||
basePkg,
|
basePkg,
|
||||||
s.config.PagerStyle(),
|
s.config.PagerStyle(),
|
||||||
input.opts.Interactive,
|
input.opts.Interactive,
|
@ -23,8 +23,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/dl"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dl"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dlcache"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SourceDownloader struct {
|
type SourceDownloader struct {
|
@ -33,34 +33,18 @@ import (
|
|||||||
_ "github.com/goreleaser/nfpm/v2/arch"
|
_ "github.com/goreleaser/nfpm/v2/arch"
|
||||||
_ "github.com/goreleaser/nfpm/v2/deb"
|
_ "github.com/goreleaser/nfpm/v2/deb"
|
||||||
_ "github.com/goreleaser/nfpm/v2/rpm"
|
_ "github.com/goreleaser/nfpm/v2/rpm"
|
||||||
"mvdan.cc/sh/v3/syntax"
|
|
||||||
|
|
||||||
"github.com/goreleaser/nfpm/v2"
|
"github.com/goreleaser/nfpm/v2"
|
||||||
"github.com/goreleaser/nfpm/v2/files"
|
"github.com/goreleaser/nfpm/v2/files"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu"
|
||||||
|
"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/internal/types"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Функция readScript анализирует скрипт сборки с использованием встроенной реализации bash
|
|
||||||
func readScript(script string) (*syntax.File, error) {
|
|
||||||
fl, err := os.Open(script) // Открываем файл скрипта
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer fl.Close() // Закрываем файл после выполнения
|
|
||||||
|
|
||||||
file, err := syntax.NewParser().Parse(fl, "alr.sh") // Парсим скрипт с помощью синтаксического анализатора
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return file, nil // Возвращаем синтаксическое дерево
|
|
||||||
}
|
|
||||||
|
|
||||||
// Функция prepareDirs подготавливает директории для сборки.
|
// Функция prepareDirs подготавливает директории для сборки.
|
||||||
func prepareDirs(dirs types.Directories) error {
|
func prepareDirs(dirs types.Directories) error {
|
||||||
err := os.RemoveAll(dirs.BaseDir) // Удаляем базовую директорию, если она существует
|
err := os.RemoveAll(dirs.BaseDir) // Удаляем базовую директорию, если она существует
|
||||||
@ -76,7 +60,7 @@ func prepareDirs(dirs types.Directories) error {
|
|||||||
|
|
||||||
// Функция buildContents создает секцию содержимого пакета, которая содержит файлы,
|
// Функция buildContents создает секцию содержимого пакета, которая содержит файлы,
|
||||||
// которые будут включены в конечный пакет.
|
// которые будут включены в конечный пакет.
|
||||||
func buildContents(vars *types.BuildVars, dirs types.Directories, preferedContents *[]string) ([]*files.Content, error) {
|
func buildContents(vars *alrsh.Package, dirs types.Directories, preferedContents *[]string) ([]*files.Content, error) {
|
||||||
contents := []*files.Content{}
|
contents := []*files.Content{}
|
||||||
|
|
||||||
processPath := func(path, trimmed string, prefered bool) error {
|
processPath := func(path, trimmed string, prefered bool) error {
|
||||||
@ -139,7 +123,7 @@ func buildContents(vars *types.BuildVars, dirs types.Directories, preferedConten
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if slices.Contains(vars.Backup, trimmed) {
|
if slices.Contains(vars.Backup.Resolved(), trimmed) {
|
||||||
fileContent.Type = "config|noreplace"
|
fileContent.Type = "config|noreplace"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,9 +154,15 @@ func buildContents(vars *types.BuildVars, dirs types.Directories, preferedConten
|
|||||||
return contents, nil
|
return contents, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizeContents(contents []*files.Content) {
|
||||||
|
for _, content := range contents {
|
||||||
|
content.Destination = filepath.Join("/", content.Destination)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var RegexpALRPackageName = regexp.MustCompile(`^(?P<package>[^+]+)\+alr-(?P<repo>.+)$`)
|
var RegexpALRPackageName = regexp.MustCompile(`^(?P<package>[^+]+)\+alr-(?P<repo>.+)$`)
|
||||||
|
|
||||||
func getBasePkgInfo(vars *types.BuildVars, input interface {
|
func getBasePkgInfo(vars *alrsh.Package, input interface {
|
||||||
RepositoryProvider
|
RepositoryProvider
|
||||||
OsInfoProvider
|
OsInfoProvider
|
||||||
},
|
},
|
||||||
@ -228,39 +218,39 @@ func createBuildEnvVars(info *distro.OSRelease, dirs types.Directories) []string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Функция setScripts добавляет скрипты-перехватчики к метаданным пакета.
|
// Функция setScripts добавляет скрипты-перехватчики к метаданным пакета.
|
||||||
func setScripts(vars *types.BuildVars, info *nfpm.Info, scriptDir string) {
|
func setScripts(vars *alrsh.Package, info *nfpm.Info, scriptDir string) {
|
||||||
if vars.Scripts.PreInstall != "" {
|
if vars.Scripts.Resolved().PreInstall != "" {
|
||||||
info.Scripts.PreInstall = filepath.Join(scriptDir, vars.Scripts.PreInstall)
|
info.Scripts.PreInstall = filepath.Join(scriptDir, vars.Scripts.Resolved().PreInstall)
|
||||||
}
|
}
|
||||||
|
|
||||||
if vars.Scripts.PostInstall != "" {
|
if vars.Scripts.Resolved().PostInstall != "" {
|
||||||
info.Scripts.PostInstall = filepath.Join(scriptDir, vars.Scripts.PostInstall)
|
info.Scripts.PostInstall = filepath.Join(scriptDir, vars.Scripts.Resolved().PostInstall)
|
||||||
}
|
}
|
||||||
|
|
||||||
if vars.Scripts.PreRemove != "" {
|
if vars.Scripts.Resolved().PreRemove != "" {
|
||||||
info.Scripts.PreRemove = filepath.Join(scriptDir, vars.Scripts.PreRemove)
|
info.Scripts.PreRemove = filepath.Join(scriptDir, vars.Scripts.Resolved().PreRemove)
|
||||||
}
|
}
|
||||||
|
|
||||||
if vars.Scripts.PostRemove != "" {
|
if vars.Scripts.Resolved().PostRemove != "" {
|
||||||
info.Scripts.PostRemove = filepath.Join(scriptDir, vars.Scripts.PostRemove)
|
info.Scripts.PostRemove = filepath.Join(scriptDir, vars.Scripts.Resolved().PostRemove)
|
||||||
}
|
}
|
||||||
|
|
||||||
if vars.Scripts.PreUpgrade != "" {
|
if vars.Scripts.Resolved().PreUpgrade != "" {
|
||||||
info.ArchLinux.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.PreUpgrade)
|
info.ArchLinux.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.Resolved().PreUpgrade)
|
||||||
info.APK.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.PreUpgrade)
|
info.APK.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.Resolved().PreUpgrade)
|
||||||
}
|
}
|
||||||
|
|
||||||
if vars.Scripts.PostUpgrade != "" {
|
if vars.Scripts.Resolved().PostUpgrade != "" {
|
||||||
info.ArchLinux.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.PostUpgrade)
|
info.ArchLinux.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.Resolved().PostUpgrade)
|
||||||
info.APK.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.PostUpgrade)
|
info.APK.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.Resolved().PostUpgrade)
|
||||||
}
|
}
|
||||||
|
|
||||||
if vars.Scripts.PreTrans != "" {
|
if vars.Scripts.Resolved().PreTrans != "" {
|
||||||
info.RPM.Scripts.PreTrans = filepath.Join(scriptDir, vars.Scripts.PreTrans)
|
info.RPM.Scripts.PreTrans = filepath.Join(scriptDir, vars.Scripts.Resolved().PreTrans)
|
||||||
}
|
}
|
||||||
|
|
||||||
if vars.Scripts.PostTrans != "" {
|
if vars.Scripts.Resolved().PostTrans != "" {
|
||||||
info.RPM.Scripts.PostTrans = filepath.Join(scriptDir, vars.Scripts.PostTrans)
|
info.RPM.Scripts.PostTrans = filepath.Join(scriptDir, vars.Scripts.Resolved().PostTrans)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,14 +278,14 @@ func packageNames(pkgs []db.Package) []string {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Функция removeDuplicates убирает любые дубликаты из предоставленного среза.
|
// Функция removeDuplicates убирает любые дубликаты из предоставленного среза.
|
||||||
func removeDuplicates(slice []string) []string {
|
func removeDuplicates[T comparable](slice []T) []T {
|
||||||
seen := map[string]struct{}{}
|
seen := map[T]struct{}{}
|
||||||
result := []string{}
|
result := []T{}
|
||||||
|
|
||||||
for _, s := range slice {
|
for _, item := range slice {
|
||||||
if _, ok := seen[s]; !ok {
|
if _, ok := seen[item]; !ok {
|
||||||
seen[s] = struct{}{}
|
seen[item] = struct{}{}
|
||||||
result = append(result, s)
|
result = append(result, item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -26,9 +26,9 @@ import (
|
|||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/repos"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type AppDeps struct {
|
type AppDeps struct {
|
||||||
@ -123,8 +123,15 @@ func (b *AppBuilder) withRepos(enablePull, forcePull bool) *AppBuilder {
|
|||||||
|
|
||||||
cfg := b.deps.Cfg
|
cfg := b.deps.Cfg
|
||||||
db := b.deps.DB
|
db := b.deps.DB
|
||||||
if cfg == nil || db == nil {
|
info := b.deps.Info
|
||||||
b.err = errors.New("config and db are required before initializing repos")
|
|
||||||
|
if info == nil {
|
||||||
|
b.WithDistroInfo()
|
||||||
|
info = b.deps.Info
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg == nil || db == nil || info == nil {
|
||||||
|
b.err = errors.New("config, db and info are required before initializing repos")
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,8 +28,8 @@ import (
|
|||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/leonelquinteros/gotext"
|
"github.com/leonelquinteros/gotext"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/pager"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/pager"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
)
|
)
|
||||||
|
|
||||||
// YesNoPrompt asks the user a yes or no question, using def as the default answer
|
// YesNoPrompt asks the user a yes or no question, using def as the default answer
|
||||||
@ -102,8 +102,8 @@ func ShowScript(path, name, style string) error {
|
|||||||
|
|
||||||
// FlattenPkgs attempts to flatten the a map of slices of packages into a single slice
|
// FlattenPkgs attempts to flatten the a map of slices of packages into a single slice
|
||||||
// of packages by prompting the user if multiple packages match.
|
// of packages by prompting the user if multiple packages match.
|
||||||
func FlattenPkgs(ctx context.Context, found map[string][]db.Package, verb string, interactive bool) []db.Package {
|
func FlattenPkgs(ctx context.Context, found map[string][]alrsh.Package, verb string, interactive bool) []alrsh.Package {
|
||||||
var outPkgs []db.Package
|
var outPkgs []alrsh.Package
|
||||||
for _, pkgs := range found {
|
for _, pkgs := range found {
|
||||||
if len(pkgs) > 1 && interactive {
|
if len(pkgs) > 1 && interactive {
|
||||||
choice, err := PkgPrompt(ctx, pkgs, verb, interactive)
|
choice, err := PkgPrompt(ctx, pkgs, verb, interactive)
|
||||||
@ -120,7 +120,7 @@ func FlattenPkgs(ctx context.Context, found map[string][]db.Package, verb string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PkgPrompt asks the user to choose between multiple packages.
|
// PkgPrompt asks the user to choose between multiple packages.
|
||||||
func PkgPrompt(ctx context.Context, options []db.Package, verb string, interactive bool) (db.Package, error) {
|
func PkgPrompt(ctx context.Context, options []alrsh.Package, verb string, interactive bool) (alrsh.Package, error) {
|
||||||
if !interactive {
|
if !interactive {
|
||||||
return options[0], nil
|
return options[0], nil
|
||||||
}
|
}
|
||||||
@ -138,7 +138,7 @@ func PkgPrompt(ctx context.Context, options []db.Package, verb string, interacti
|
|||||||
var choice int
|
var choice int
|
||||||
err := survey.AskOne(prompt, &choice)
|
err := survey.AskOne(prompt, &choice)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return db.Package{}, err
|
return alrsh.Package{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return options[choice], nil
|
return options[choice], nil
|
||||||
|
@ -29,7 +29,7 @@ import (
|
|||||||
"github.com/pelletier/go-toml/v2"
|
"github.com/pelletier/go-toml/v2"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ALRConfig struct {
|
type ALRConfig struct {
|
||||||
|
@ -23,41 +23,18 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"github.com/leonelquinteros/gotext"
|
"github.com/leonelquinteros/gotext"
|
||||||
|
_ "modernc.org/sqlite"
|
||||||
|
"xorm.io/xorm"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CurrentVersion is the current version of the database.
|
const CurrentVersion = 5
|
||||||
// The database is reset if its version doesn't match this.
|
|
||||||
const CurrentVersion = 4
|
|
||||||
|
|
||||||
// Package is a ALR package's database representation
|
type Version struct {
|
||||||
type Package struct {
|
Version int `xorm:"'version'"`
|
||||||
BasePkgName string `sh:"base" db:"basepkg_name"`
|
|
||||||
Name string `sh:"name,required" db:"name"`
|
|
||||||
Version string `sh:"version,required" db:"version"`
|
|
||||||
Release int `sh:"release,required" db:"release"`
|
|
||||||
Epoch uint `sh:"epoch" db:"epoch"`
|
|
||||||
Summary JSON[map[string]string] `db:"summary"`
|
|
||||||
Description JSON[map[string]string] `db:"description"`
|
|
||||||
Group JSON[map[string]string] `db:"group_name"`
|
|
||||||
Homepage JSON[map[string]string] `db:"homepage"`
|
|
||||||
Maintainer JSON[map[string]string] `db:"maintainer"`
|
|
||||||
Architectures JSON[[]string] `sh:"architectures" db:"architectures"`
|
|
||||||
Licenses JSON[[]string] `sh:"license" db:"licenses"`
|
|
||||||
Provides JSON[[]string] `sh:"provides" db:"provides"`
|
|
||||||
Conflicts JSON[[]string] `sh:"conflicts" db:"conflicts"`
|
|
||||||
Replaces JSON[[]string] `sh:"replaces" db:"replaces"`
|
|
||||||
Depends JSON[map[string][]string] `db:"depends"`
|
|
||||||
BuildDepends JSON[map[string][]string] `db:"builddepends"`
|
|
||||||
OptDepends JSON[map[string][]string] `db:"optdepends"`
|
|
||||||
Repository string `db:"repository"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type version struct {
|
|
||||||
Version int `db:"version"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config interface {
|
type Config interface {
|
||||||
@ -65,7 +42,7 @@ type Config interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Database struct {
|
type Database struct {
|
||||||
conn *sqlx.DB
|
engine *xorm.Engine
|
||||||
config Config
|
config Config
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,181 +52,100 @@ func New(config Config) *Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) Init(ctx context.Context) error {
|
func (d *Database) Connect() error {
|
||||||
err := d.Connect(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return d.initDB(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Database) Connect(ctx context.Context) error {
|
|
||||||
dsn := d.config.GetPaths().DBPath
|
dsn := d.config.GetPaths().DBPath
|
||||||
db, err := sqlx.Open("sqlite", dsn)
|
engine, err := xorm.NewEngine("sqlite", dsn)
|
||||||
|
// engine.SetLogLevel(log.LOG_DEBUG)
|
||||||
|
// engine.ShowSQL(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
d.conn = db
|
d.engine = engine
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) GetConn() *sqlx.DB {
|
func (d *Database) Init(ctx context.Context) error {
|
||||||
return d.conn
|
if err := d.Connect(); err != nil {
|
||||||
}
|
return err
|
||||||
|
}
|
||||||
func (d *Database) initDB(ctx context.Context) error {
|
if err := d.engine.Sync2(new(alrsh.Package), new(Version)); err != nil {
|
||||||
d.conn = d.conn.Unsafe()
|
|
||||||
conn := d.conn
|
|
||||||
_, err := conn.ExecContext(ctx, `
|
|
||||||
CREATE TABLE IF NOT EXISTS pkgs (
|
|
||||||
basepkg_name TEXT NOT NULL,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
repository TEXT NOT NULL,
|
|
||||||
version TEXT NOT NULL,
|
|
||||||
release INT NOT NULL,
|
|
||||||
epoch INT,
|
|
||||||
summary TEXT CHECK(summary = 'null' OR (JSON_VALID(summary) AND JSON_TYPE(summary) = 'object')),
|
|
||||||
description TEXT CHECK(description = 'null' OR (JSON_VALID(description) AND JSON_TYPE(description) = 'object')),
|
|
||||||
group_name TEXT CHECK(group_name = 'null' OR (JSON_VALID(group_name) AND JSON_TYPE(group_name) = 'object')),
|
|
||||||
homepage TEXT CHECK(homepage = 'null' OR (JSON_VALID(homepage) AND JSON_TYPE(homepage) = 'object')),
|
|
||||||
maintainer TEXT CHECK(maintainer = 'null' OR (JSON_VALID(maintainer) AND JSON_TYPE(maintainer) = 'object')),
|
|
||||||
architectures TEXT CHECK(architectures = 'null' OR (JSON_VALID(architectures) AND JSON_TYPE(architectures) = 'array')),
|
|
||||||
licenses TEXT CHECK(licenses = 'null' OR (JSON_VALID(licenses) AND JSON_TYPE(licenses) = 'array')),
|
|
||||||
provides TEXT CHECK(provides = 'null' OR (JSON_VALID(provides) AND JSON_TYPE(provides) = 'array')),
|
|
||||||
conflicts TEXT CHECK(conflicts = 'null' OR (JSON_VALID(conflicts) AND JSON_TYPE(conflicts) = 'array')),
|
|
||||||
replaces TEXT CHECK(replaces = 'null' OR (JSON_VALID(replaces) AND JSON_TYPE(replaces) = 'array')),
|
|
||||||
depends TEXT CHECK(depends = 'null' OR (JSON_VALID(depends) AND JSON_TYPE(depends) = 'object')),
|
|
||||||
builddepends TEXT CHECK(builddepends = 'null' OR (JSON_VALID(builddepends) AND JSON_TYPE(builddepends) = 'object')),
|
|
||||||
optdepends TEXT CHECK(optdepends = 'null' OR (JSON_VALID(optdepends) AND JSON_TYPE(optdepends) = 'object')),
|
|
||||||
UNIQUE(name, repository)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS alr_db_version (
|
|
||||||
version INT NOT NULL
|
|
||||||
);
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ver, ok := d.GetVersion(ctx)
|
ver, ok := d.GetVersion(ctx)
|
||||||
if ok && ver != CurrentVersion {
|
if ok && ver != CurrentVersion {
|
||||||
slog.Warn(gotext.Get("Database version mismatch; resetting"), "version", ver, "expected", CurrentVersion)
|
slog.Warn(gotext.Get("Database version mismatch; resetting"), "version", ver, "expected", CurrentVersion)
|
||||||
err = d.reset(ctx)
|
if err := d.reset(); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return d.initDB(ctx)
|
return d.Init(ctx)
|
||||||
} else if !ok {
|
} else if !ok {
|
||||||
slog.Warn(gotext.Get("Database version does not exist. Run alr fix if something isn't working."), "version", ver, "expected", CurrentVersion)
|
slog.Warn(gotext.Get("Database version does not exist. Run alr fix if something isn't working."))
|
||||||
return d.addVersion(ctx, CurrentVersion)
|
return d.addVersion(CurrentVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) GetVersion(ctx context.Context) (int, bool) {
|
func (d *Database) GetVersion(ctx context.Context) (int, bool) {
|
||||||
var ver version
|
var v Version
|
||||||
err := d.conn.GetContext(ctx, &ver, "SELECT * FROM alr_db_version LIMIT 1;")
|
has, err := d.engine.Get(&v)
|
||||||
if err != nil {
|
if err != nil || !has {
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
return ver.Version, true
|
return v.Version, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) addVersion(ctx context.Context, ver int) error {
|
func (d *Database) addVersion(ver int) error {
|
||||||
_, err := d.conn.ExecContext(ctx, `INSERT INTO alr_db_version(version) VALUES (?);`, ver)
|
_, err := d.engine.Insert(&Version{Version: ver})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) reset(ctx context.Context) error {
|
func (d *Database) reset() error {
|
||||||
_, err := d.conn.ExecContext(ctx, "DROP TABLE IF EXISTS pkgs;")
|
return d.engine.DropTables(new(alrsh.Package), new(Version))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) InsertPackage(ctx context.Context, pkg alrsh.Package) error {
|
||||||
|
session := d.engine.Context(ctx)
|
||||||
|
|
||||||
|
affected, err := session.Where("name = ? AND repository = ?", pkg.Name, pkg.Repository).Update(&pkg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = d.conn.ExecContext(ctx, "DROP TABLE IF EXISTS alr_db_version;")
|
|
||||||
return err
|
if affected == 0 {
|
||||||
|
_, err = session.Insert(&pkg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) GetPkgs(ctx context.Context, where string, args ...any) (*sqlx.Rows, error) {
|
func (d *Database) GetPkgs(_ context.Context, where string, args ...any) ([]alrsh.Package, error) {
|
||||||
stream, err := d.conn.QueryxContext(ctx, "SELECT * FROM pkgs WHERE "+where, args...)
|
var pkgs []alrsh.Package
|
||||||
if err != nil {
|
err := d.engine.Where(where, args...).Find(&pkgs)
|
||||||
|
return pkgs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) GetPkg(where string, args ...any) (*alrsh.Package, error) {
|
||||||
|
var pkg alrsh.Package
|
||||||
|
has, err := d.engine.Where(where, args...).Get(&pkg)
|
||||||
|
if err != nil || !has {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return stream, nil
|
return &pkg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) GetPkg(ctx context.Context, where string, args ...any) (*Package, error) {
|
func (d *Database) DeletePkgs(_ context.Context, where string, args ...any) error {
|
||||||
out := &Package{}
|
_, err := d.engine.Where(where, args...).Delete(&alrsh.Package{})
|
||||||
err := d.conn.GetContext(ctx, out, "SELECT * FROM pkgs WHERE "+where+" LIMIT 1", args...)
|
|
||||||
return out, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Database) DeletePkgs(ctx context.Context, where string, args ...any) error {
|
|
||||||
_, err := d.conn.ExecContext(ctx, "DELETE FROM pkgs WHERE "+where, args...)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) IsEmpty(ctx context.Context) bool {
|
func (d *Database) IsEmpty() bool {
|
||||||
var count int
|
count, err := d.engine.Count(new(alrsh.Package))
|
||||||
err := d.conn.GetContext(ctx, &count, "SELECT count(1) FROM pkgs;")
|
return err != nil || count == 0
|
||||||
if err != nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return count == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Database) InsertPackage(ctx context.Context, pkg Package) error {
|
|
||||||
_, err := d.conn.NamedExecContext(ctx, `
|
|
||||||
INSERT OR REPLACE INTO pkgs (
|
|
||||||
basepkg_name,
|
|
||||||
name,
|
|
||||||
repository,
|
|
||||||
version,
|
|
||||||
release,
|
|
||||||
epoch,
|
|
||||||
summary,
|
|
||||||
description,
|
|
||||||
group_name,
|
|
||||||
homepage,
|
|
||||||
maintainer,
|
|
||||||
architectures,
|
|
||||||
licenses,
|
|
||||||
provides,
|
|
||||||
conflicts,
|
|
||||||
replaces,
|
|
||||||
depends,
|
|
||||||
builddepends,
|
|
||||||
optdepends
|
|
||||||
) VALUES (
|
|
||||||
:basepkg_name,
|
|
||||||
:name,
|
|
||||||
:repository,
|
|
||||||
:version,
|
|
||||||
:release,
|
|
||||||
:epoch,
|
|
||||||
:summary,
|
|
||||||
:description,
|
|
||||||
:group_name,
|
|
||||||
:homepage,
|
|
||||||
:maintainer,
|
|
||||||
:architectures,
|
|
||||||
:licenses,
|
|
||||||
:provides,
|
|
||||||
:conflicts,
|
|
||||||
:replaces,
|
|
||||||
:depends,
|
|
||||||
:builddepends,
|
|
||||||
:optdepends
|
|
||||||
);
|
|
||||||
`, pkg)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Database) Close() error {
|
func (d *Database) Close() error {
|
||||||
if d.conn != nil {
|
return d.engine.Close()
|
||||||
return d.conn.Close()
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -25,10 +25,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TestALRConfig struct{}
|
type TestALRConfig struct{}
|
||||||
@ -45,35 +46,38 @@ func prepareDb() *db.Database {
|
|||||||
return database
|
return database
|
||||||
}
|
}
|
||||||
|
|
||||||
var testPkg = db.Package{
|
var testPkg = alrsh.Package{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Version: "0.0.1",
|
Version: "0.0.1",
|
||||||
Release: 1,
|
Release: 1,
|
||||||
Epoch: 2,
|
Epoch: 2,
|
||||||
Description: db.NewJSON(map[string]string{
|
Description: alrsh.OverridableFromMap(map[string]string{
|
||||||
"en": "Test package",
|
"en": "Test package",
|
||||||
"ru": "Проверочный пакет",
|
"ru": "Проверочный пакет",
|
||||||
}),
|
}),
|
||||||
Homepage: db.NewJSON(map[string]string{
|
Homepage: alrsh.OverridableFromMap(map[string]string{
|
||||||
"en": "https://gitea.plemya-x.ru/xpamych/ALR",
|
"en": "https://gitea.plemya-x.ru/xpamych/ALR",
|
||||||
}),
|
}),
|
||||||
Maintainer: db.NewJSON(map[string]string{
|
Maintainer: alrsh.OverridableFromMap(map[string]string{
|
||||||
"en": "Evgeniy Khramov <xpamych@yandex.ru>",
|
"en": "Evgeniy Khramov <xpamych@yandex.ru>",
|
||||||
"ru": "Евгений Храмов <xpamych@yandex.ru>",
|
"ru": "Евгений Храмов <xpamych@yandex.ru>",
|
||||||
}),
|
}),
|
||||||
Architectures: db.NewJSON([]string{"arm64", "amd64"}),
|
Architectures: []string{"arm64", "amd64"},
|
||||||
Licenses: db.NewJSON([]string{"GPL-3.0-or-later"}),
|
Licenses: []string{"GPL-3.0-or-later"},
|
||||||
Provides: db.NewJSON([]string{"test"}),
|
Provides: []string{"test"},
|
||||||
Conflicts: db.NewJSON([]string{"test"}),
|
Conflicts: []string{"test"},
|
||||||
Replaces: db.NewJSON([]string{"test-old"}),
|
Replaces: []string{"test-old"},
|
||||||
Depends: db.NewJSON(map[string][]string{
|
Depends: alrsh.OverridableFromMap(map[string][]string{
|
||||||
"": {"sudo"},
|
"": {"sudo"},
|
||||||
}),
|
}),
|
||||||
BuildDepends: db.NewJSON(map[string][]string{
|
BuildDepends: alrsh.OverridableFromMap(map[string][]string{
|
||||||
"": {"golang"},
|
"": {"golang"},
|
||||||
"arch": {"go"},
|
"arch": {"go"},
|
||||||
}),
|
}),
|
||||||
Repository: "default",
|
Repository: "default",
|
||||||
|
Summary: alrsh.OverridableFromMap(map[string]string{}),
|
||||||
|
Group: alrsh.OverridableFromMap(map[string]string{}),
|
||||||
|
OptDepends: alrsh.OverridableFromMap(map[string][]string{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInit(t *testing.T) {
|
func TestInit(t *testing.T) {
|
||||||
@ -99,15 +103,16 @@ func TestInsertPackage(t *testing.T) {
|
|||||||
t.Fatalf("Expected no error, got %s", err)
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dbPkg := db.Package{}
|
pkgs, err := database.GetPkgs(ctx, "name = 'test' AND repository = 'default'")
|
||||||
err = sqlx.Get(database.GetConn(), &dbPkg, "SELECT * FROM pkgs WHERE name = 'test' AND repository = 'default'")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(testPkg, dbPkg) {
|
if len(pkgs) != 1 {
|
||||||
t.Errorf("Expected test package to be the same as database package")
|
t.Fatalf("Expected 1 package, got %d", len(pkgs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, testPkg, pkgs[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetPkgs(t *testing.T) {
|
func TestGetPkgs(t *testing.T) {
|
||||||
@ -130,18 +135,12 @@ func TestGetPkgs(t *testing.T) {
|
|||||||
t.Errorf("Expected no error, got %s", err)
|
t.Errorf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := database.GetPkgs(ctx, "name LIKE 'x%'")
|
pkgs, err := database.GetPkgs(ctx, "name LIKE 'x%'")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for result.Next() {
|
for _, dbPkg := range pkgs {
|
||||||
var dbPkg db.Package
|
|
||||||
err = result.StructScan(&dbPkg)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.HasPrefix(dbPkg.Name, "x") {
|
if !strings.HasPrefix(dbPkg.Name, "x") {
|
||||||
t.Errorf("Expected package name to start with 'x', got %s", dbPkg.Name)
|
t.Errorf("Expected package name to start with 'x', got %s", dbPkg.Name)
|
||||||
}
|
}
|
||||||
@ -168,7 +167,7 @@ func TestGetPkg(t *testing.T) {
|
|||||||
t.Errorf("Expected no error, got %s", err)
|
t.Errorf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pkg, err := database.GetPkg(ctx, "name LIKE 'x%' ORDER BY name")
|
pkg, err := database.GetPkg("name LIKE 'x%'")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
@ -206,16 +205,6 @@ func TestDeletePkgs(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected no error, got %s", err)
|
t.Errorf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var dbPkg db.Package
|
|
||||||
err = database.GetConn().Get(&dbPkg, "SELECT * FROM pkgs WHERE name LIKE 'x%' ORDER BY name LIMIT 1;")
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if dbPkg.Name != "x2" {
|
|
||||||
t.Errorf("Expected x2 package, got %s", dbPkg.Name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJsonArrayContains(t *testing.T) {
|
func TestJsonArrayContains(t *testing.T) {
|
||||||
@ -227,7 +216,7 @@ func TestJsonArrayContains(t *testing.T) {
|
|||||||
x1.Name = "x1"
|
x1.Name = "x1"
|
||||||
x2 := testPkg
|
x2 := testPkg
|
||||||
x2.Name = "x2"
|
x2.Name = "x2"
|
||||||
x2.Provides.Val = append(x2.Provides.Val, "x")
|
x2.Provides = append(x2.Provides, "x")
|
||||||
|
|
||||||
err := database.InsertPackage(ctx, x1)
|
err := database.InsertPackage(ctx, x1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -239,13 +228,24 @@ func TestJsonArrayContains(t *testing.T) {
|
|||||||
t.Errorf("Expected no error, got %s", err)
|
t.Errorf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var dbPkg db.Package
|
pkgs, err := database.GetPkgs(ctx, "name = 'x2'")
|
||||||
err = database.GetConn().Get(&dbPkg, "SELECT * FROM pkgs WHERE json_array_contains(provides, 'x');")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if dbPkg.Name != "x2" {
|
if len(pkgs) != 1 || pkgs[0].Name != "x2" {
|
||||||
t.Errorf("Expected x2 package, got %s", dbPkg.Name)
|
t.Errorf("Expected x2 package, got %v", pkgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the provides field contains 'x'
|
||||||
|
found := false
|
||||||
|
for _, p := range pkgs[0].Provides {
|
||||||
|
if p == "x" {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("Expected provides to contain 'x'")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
// 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 db
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"database/sql/driver"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// JSON represents a JSON value in the database
|
|
||||||
type JSON[T any] struct {
|
|
||||||
Val T
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewJSON creates a new database JSON value
|
|
||||||
func NewJSON[T any](v T) JSON[T] {
|
|
||||||
return JSON[T]{Val: v}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *JSON[T]) Scan(val any) error {
|
|
||||||
if val == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch val := val.(type) {
|
|
||||||
case string:
|
|
||||||
err := json.Unmarshal([]byte(val), &s.Val)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case sql.NullString:
|
|
||||||
if val.Valid {
|
|
||||||
err := json.Unmarshal([]byte(val.String), &s.Val)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return errors.New("sqlite json types must be strings")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s JSON[T]) Value() (driver.Value, error) {
|
|
||||||
data, err := json.Marshal(s.Val)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return string(data), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s JSON[T]) MarshalYAML() (any, error) {
|
|
||||||
return s.Val, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s JSON[T]) String() string {
|
|
||||||
return fmt.Sprint(s.Val)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s JSON[T]) GoString() string {
|
|
||||||
return fmt.Sprintf("%#v", s.Val)
|
|
||||||
}
|
|
@ -65,6 +65,8 @@ func (a *HCLoggerAdapter) Log(level hclog.Level, msg string, args ...interface{}
|
|||||||
var chLogLevel chLog.Level
|
var chLogLevel chLog.Level
|
||||||
if msg == "plugin process exited" ||
|
if msg == "plugin process exited" ||
|
||||||
strings.HasPrefix(msg, "[ERR] plugin: stream copy 'stderr' error") ||
|
strings.HasPrefix(msg, "[ERR] plugin: stream copy 'stderr' error") ||
|
||||||
|
strings.HasPrefix(msg, "[WARN] error closing client during Kill") ||
|
||||||
|
strings.HasPrefix(msg, "[WARN] plugin failed to exit gracefully") ||
|
||||||
strings.HasPrefix(msg, "[DEBUG] plugin") {
|
strings.HasPrefix(msg, "[DEBUG] plugin") {
|
||||||
chLogLevel = chLog.DebugLevel
|
chLogLevel = chLog.DebugLevel
|
||||||
} else {
|
} else {
|
||||||
|
@ -67,7 +67,7 @@ func (a *APTRpm) Sync(opts *Opts) error {
|
|||||||
|
|
||||||
func (a *APTRpm) Install(opts *Opts, pkgs ...string) error {
|
func (a *APTRpm) Install(opts *Opts, pkgs ...string) error {
|
||||||
opts = ensureOpts(opts)
|
opts = ensureOpts(opts)
|
||||||
cmd := a.getCmd(opts, "apt-get", "install")
|
cmd := a.getCmd(opts, "apt-get", "install", "-o", "APT::Install::Virtual=true")
|
||||||
cmd.Args = append(cmd.Args, pkgs...)
|
cmd.Args = append(cmd.Args, pkgs...)
|
||||||
setCmdEnv(cmd)
|
setCmdEnv(cmd)
|
||||||
cmd.Stdout = cmd.Stderr
|
cmd.Stdout = cmd.Stderr
|
@ -21,7 +21,6 @@ package overrides
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -29,7 +28,6 @@ import (
|
|||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -150,65 +148,6 @@ func (o *Opts) WithLanguageTags(langs []string) *Opts {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolvedPackage is a ALR package after its overrides
|
|
||||||
// have been resolved
|
|
||||||
type ResolvedPackage struct {
|
|
||||||
Name string `sh:"name"`
|
|
||||||
Version string `sh:"version"`
|
|
||||||
Release int `sh:"release"`
|
|
||||||
Epoch uint `sh:"epoch"`
|
|
||||||
Group string `db:"group_name"`
|
|
||||||
Summary string `db:"summary"`
|
|
||||||
Description string `db:"description"`
|
|
||||||
Homepage string `db:"homepage"`
|
|
||||||
Maintainer string `db:"maintainer"`
|
|
||||||
Architectures []string `sh:"architectures"`
|
|
||||||
Licenses []string `sh:"license"`
|
|
||||||
Provides []string `sh:"provides"`
|
|
||||||
Conflicts []string `sh:"conflicts"`
|
|
||||||
Replaces []string `sh:"replaces"`
|
|
||||||
Depends []string `sh:"deps"`
|
|
||||||
BuildDepends []string `sh:"build_deps"`
|
|
||||||
OptDepends []string `sh:"opt_deps"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func ResolvePackage(pkg *db.Package, overrides []string) *ResolvedPackage {
|
|
||||||
out := &ResolvedPackage{}
|
|
||||||
outVal := reflect.ValueOf(out).Elem()
|
|
||||||
pkgVal := reflect.ValueOf(pkg).Elem()
|
|
||||||
|
|
||||||
for i := 0; i < outVal.NumField(); i++ {
|
|
||||||
fieldVal := outVal.Field(i)
|
|
||||||
fieldType := fieldVal.Type()
|
|
||||||
pkgFieldVal := pkgVal.FieldByName(outVal.Type().Field(i).Name)
|
|
||||||
pkgFieldType := pkgFieldVal.Type()
|
|
||||||
|
|
||||||
if strings.HasPrefix(pkgFieldType.String(), "db.JSON") {
|
|
||||||
pkgFieldVal = pkgFieldVal.FieldByName("Val")
|
|
||||||
pkgFieldType = pkgFieldVal.Type()
|
|
||||||
}
|
|
||||||
|
|
||||||
if pkgFieldType.AssignableTo(fieldType) {
|
|
||||||
fieldVal.Set(pkgFieldVal)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if pkgFieldVal.Kind() == reflect.Map && pkgFieldType.Elem().AssignableTo(fieldType) {
|
|
||||||
for _, override := range overrides {
|
|
||||||
overrideVal := pkgFieldVal.MapIndex(reflect.ValueOf(override))
|
|
||||||
if !overrideVal.IsValid() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldVal.Set(overrideVal)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseLangs(langs []string, tags []language.Tag) ([]string, error) {
|
func parseLangs(langs []string, tags []language.Tag) ([]string, error) {
|
||||||
out := make([]string, len(tags)+len(langs))
|
out := make([]string, len(tags)+len(langs))
|
||||||
for i, tag := range tags {
|
for i, tag := range tags {
|
||||||
|
@ -22,11 +22,11 @@ package repos
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error) {
|
func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]alrsh.Package, []string, error) {
|
||||||
found := map[string][]db.Package{}
|
found := map[string][]alrsh.Package{}
|
||||||
notFound := []string(nil)
|
notFound := []string(nil)
|
||||||
|
|
||||||
for _, pkgName := range pkgs {
|
for _, pkgName := range pkgs {
|
||||||
@ -40,17 +40,10 @@ func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.P
|
|||||||
}
|
}
|
||||||
|
|
||||||
added := 0
|
added := 0
|
||||||
for result.Next() {
|
for _, pkg := range result {
|
||||||
var pkg db.Package
|
|
||||||
err = result.StructScan(&pkg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
added++
|
added++
|
||||||
found[pkgName] = append(found[pkgName], pkg)
|
found[pkgName] = append(found[pkgName], pkg)
|
||||||
}
|
}
|
||||||
result.Close()
|
|
||||||
|
|
||||||
if added == 0 {
|
if added == 0 {
|
||||||
result, err := rs.db.GetPkgs(ctx, "name LIKE ?", pkgName)
|
result, err := rs.db.GetPkgs(ctx, "name LIKE ?", pkgName)
|
||||||
@ -58,18 +51,10 @@ func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.P
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for result.Next() {
|
for _, pkg := range result {
|
||||||
var pkg db.Package
|
|
||||||
err = result.StructScan(&pkg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
added++
|
added++
|
||||||
found[pkgName] = append(found[pkgName], pkg)
|
found[pkgName] = append(found[pkgName], pkg)
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if added == 0 {
|
if added == 0 {
|
@ -24,9 +24,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/repos"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFindPkgs(t *testing.T) {
|
func TestFindPkgs(t *testing.T) {
|
||||||
@ -41,7 +41,7 @@ func TestFindPkgs(t *testing.T) {
|
|||||||
err := rs.Pull(e.Ctx, []types.Repo{
|
err := rs.Pull(e.Ctx, []types.Repo{
|
||||||
{
|
{
|
||||||
Name: "default",
|
Name: "default",
|
||||||
URL: "https://gitea.plemya-x.ru/xpamych/xpamych-alr-repo.git",
|
URL: "https://gitea.plemya-x.ru/Plemya-x/alr-default.git",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -89,31 +89,31 @@ func TestFindPkgsEmpty(t *testing.T) {
|
|||||||
e.Db,
|
e.Db,
|
||||||
)
|
)
|
||||||
|
|
||||||
err := e.Db.InsertPackage(e.Ctx, db.Package{
|
err := e.Db.InsertPackage(e.Ctx, alrsh.Package{
|
||||||
Name: "test1",
|
Name: "test1",
|
||||||
Repository: "default",
|
Repository: "default",
|
||||||
Version: "0.0.1",
|
Version: "0.0.1",
|
||||||
Release: 1,
|
Release: 1,
|
||||||
Description: db.NewJSON(map[string]string{
|
Provides: []string{""},
|
||||||
|
Description: alrsh.OverridableFromMap(map[string]string{
|
||||||
"en": "Test package 1",
|
"en": "Test package 1",
|
||||||
"ru": "Проверочный пакет 1",
|
"ru": "Проверочный пакет 1",
|
||||||
}),
|
}),
|
||||||
Provides: db.NewJSON([]string{""}),
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = e.Db.InsertPackage(e.Ctx, db.Package{
|
err = e.Db.InsertPackage(e.Ctx, alrsh.Package{
|
||||||
Name: "test2",
|
Name: "test2",
|
||||||
Repository: "default",
|
Repository: "default",
|
||||||
Version: "0.0.1",
|
Version: "0.0.1",
|
||||||
Release: 1,
|
Release: 1,
|
||||||
Description: db.NewJSON(map[string]string{
|
Provides: []string{"test"},
|
||||||
|
Description: alrsh.OverridableFromMap(map[string]string{
|
||||||
"en": "Test package 2",
|
"en": "Test package 2",
|
||||||
"ru": "Проверочный пакет 2",
|
"ru": "Проверочный пакет 2",
|
||||||
}),
|
}),
|
||||||
Provides: db.NewJSON([]string{"test"}),
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
t.Fatalf("Expected no error, got %s", err)
|
513
internal/repos/pull.go
Normal file
513
internal/repos/pull.go
Normal file
@ -0,0 +1,513 @@
|
|||||||
|
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
|
||||||
|
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
|
||||||
|
//
|
||||||
|
// 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 repos
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-git/go-billy/v5"
|
||||||
|
"github.com/go-git/go-git/v5"
|
||||||
|
gitConfig "github.com/go-git/go-git/v5/config"
|
||||||
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
|
"github.com/leonelquinteros/gotext"
|
||||||
|
"github.com/pelletier/go-toml/v2"
|
||||||
|
"go.elara.ws/vercmp"
|
||||||
|
"mvdan.cc/sh/v3/expand"
|
||||||
|
"mvdan.cc/sh/v3/interp"
|
||||||
|
"mvdan.cc/sh/v3/syntax"
|
||||||
|
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type actionType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
actionDelete actionType = iota
|
||||||
|
actionUpdate
|
||||||
|
)
|
||||||
|
|
||||||
|
type action struct {
|
||||||
|
Type actionType
|
||||||
|
File string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pull pulls the provided repositories. If a repo doesn't exist, it will be cloned
|
||||||
|
// and its packages will be written to the DB. If it does exist, it will be pulled.
|
||||||
|
// In this case, only changed packages will be processed if possible.
|
||||||
|
// If repos is set to nil, the repos in the ALR config will be used.
|
||||||
|
func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error {
|
||||||
|
if repos == nil {
|
||||||
|
repos = rs.cfg.Repos()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, repo := range repos {
|
||||||
|
err := rs.pullRepo(ctx, repo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *Repos) pullRepo(ctx context.Context, repo types.Repo) error {
|
||||||
|
urls := []string{repo.URL}
|
||||||
|
urls = append(urls, repo.Mirrors...)
|
||||||
|
|
||||||
|
var lastErr error
|
||||||
|
|
||||||
|
for i, repoURL := range urls {
|
||||||
|
if i > 0 {
|
||||||
|
slog.Info(gotext.Get("Trying mirror"), "repo", repo.Name, "mirror", repoURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := rs.pullRepoFromURL(ctx, repoURL, repo)
|
||||||
|
if err != nil {
|
||||||
|
lastErr = err
|
||||||
|
slog.Warn(gotext.Get("Failed to pull from URL"), "repo", repo.Name, "url", repoURL, "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("failed to pull repository %s from any URL: %w", repo.Name, lastErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readGitRepo(repoDir, repoUrl string) (*git.Repository, bool, error) {
|
||||||
|
gitDir := filepath.Join(repoDir, ".git")
|
||||||
|
if fi, err := os.Stat(gitDir); err == nil && fi.IsDir() {
|
||||||
|
r, err := git.PlainOpen(repoDir)
|
||||||
|
if err == nil {
|
||||||
|
err = updateRemoteURL(r, repoUrl)
|
||||||
|
if err == nil {
|
||||||
|
_, err := r.Head()
|
||||||
|
if err == nil {
|
||||||
|
return r, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, plumbing.ErrReferenceNotFound) {
|
||||||
|
return r, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Debug("error getting HEAD, reinitializing...", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Debug("error while reading repo, reinitializing...", "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.RemoveAll(repoDir); err != nil {
|
||||||
|
return nil, false, fmt.Errorf("failed to remove repo directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(repoDir, 0o755); err != nil {
|
||||||
|
return nil, false, fmt.Errorf("failed to create repo directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := git.PlainInit(repoDir, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, fmt.Errorf("failed to initialize git repo: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = r.CreateRemote(&gitConfig.RemoteConfig{
|
||||||
|
Name: git.DefaultRemoteName,
|
||||||
|
URLs: []string{repoUrl},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *Repos) pullRepoFromURL(ctx context.Context, rawRepoUrl string, repo types.Repo) error {
|
||||||
|
repoURL, err := url.Parse(rawRepoUrl)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid URL %s: %w", rawRepoUrl, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info(gotext.Get("Pulling repository"), "name", repo.Name)
|
||||||
|
repoDir := filepath.Join(rs.cfg.GetPaths().RepoDir, repo.Name)
|
||||||
|
|
||||||
|
var repoFS billy.Filesystem
|
||||||
|
|
||||||
|
r, freshGit, err := readGitRepo(repoDir, repoURL.String())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open repo")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.FetchContext(ctx, &git.FetchOptions{
|
||||||
|
Progress: os.Stderr,
|
||||||
|
Force: true,
|
||||||
|
})
|
||||||
|
if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var old *plumbing.Reference
|
||||||
|
|
||||||
|
w, err := r.Worktree()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
revHash, err := resolveHash(r, repo.Ref)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error resolving hash: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !freshGit {
|
||||||
|
old, err = r.Head()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if old.Hash() == *revHash {
|
||||||
|
slog.Info(gotext.Get("Repository up to date"), "name", repo.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.Checkout(&git.CheckoutOptions{
|
||||||
|
Hash: plumbing.NewHash(revHash.String()),
|
||||||
|
Force: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
repoFS = w.Filesystem
|
||||||
|
|
||||||
|
new, err := r.Head()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the DB was not present at startup, that means it's
|
||||||
|
// empty. In this case, we need to update the DB fully
|
||||||
|
// rather than just incrementally.
|
||||||
|
if rs.db.IsEmpty() || freshGit {
|
||||||
|
err = rs.processRepoFull(ctx, repo, repoDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = rs.processRepoChanges(ctx, repo, r, w, old, new)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fl, err := repoFS.Open("alr-repo.toml")
|
||||||
|
if err != nil {
|
||||||
|
slog.Warn(gotext.Get("Git repository does not appear to be a valid ALR repo"), "repo", repo.Name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var repoCfg types.RepoConfig
|
||||||
|
err = toml.NewDecoder(fl).Decode(&repoCfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fl.Close()
|
||||||
|
|
||||||
|
// If the version doesn't have a "v" prefix, it's not a standard version.
|
||||||
|
// It may be "unknown" or a git version, but either way, there's no way
|
||||||
|
// to compare it to the repo version, so only compare versions with the "v".
|
||||||
|
if strings.HasPrefix(config.Version, "v") {
|
||||||
|
if vercmp.Compare(config.Version, repoCfg.Repo.MinVersion) == -1 {
|
||||||
|
slog.Warn(gotext.Get("ALR repo's minimum ALR version is greater than the current version. Try updating ALR if something doesn't work."), "repo", repo.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateRemoteURL(r *git.Repository, newURL string) error {
|
||||||
|
cfg, err := r.Config()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
remote, ok := cfg.Remotes[git.DefaultRemoteName]
|
||||||
|
if !ok || len(remote.URLs) == 0 {
|
||||||
|
return fmt.Errorf("no remote '%s' found", git.DefaultRemoteName)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentURL := remote.URLs[0]
|
||||||
|
if currentURL == newURL {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Debug("Updating remote URL", "old", currentURL, "new", newURL)
|
||||||
|
|
||||||
|
err = r.DeleteRemote(git.DefaultRemoteName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to delete old remote: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = r.CreateRemote(&gitConfig.RemoteConfig{
|
||||||
|
Name: git.DefaultRemoteName,
|
||||||
|
URLs: []string{newURL},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create new remote: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *Repos) updatePkg(ctx context.Context, repo types.Repo, runner *interp.Runner, scriptFl io.ReadCloser) error {
|
||||||
|
parser := syntax.NewParser()
|
||||||
|
|
||||||
|
pkgs, err := parseScript(ctx, repo, parser, runner, scriptFl)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pkg := range pkgs {
|
||||||
|
err = rs.db.InsertPackage(ctx, *pkg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *Repos) processRepoChangesRunner(repoDir, scriptDir string) (*interp.Runner, error) {
|
||||||
|
env := append(os.Environ(), "scriptdir="+scriptDir)
|
||||||
|
return interp.New(
|
||||||
|
interp.Env(expand.ListEnviron(env...)),
|
||||||
|
interp.ExecHandler(handlers.NopExec),
|
||||||
|
interp.ReadDirHandler2(handlers.RestrictedReadDir(repoDir)),
|
||||||
|
interp.StatHandler(handlers.RestrictedStat(repoDir)),
|
||||||
|
interp.OpenHandler(handlers.RestrictedOpen(repoDir)),
|
||||||
|
interp.StdIO(handlers.NopRWC{}, handlers.NopRWC{}, handlers.NopRWC{}),
|
||||||
|
// Use temp dir instead script dir because runner may be for deleted file
|
||||||
|
interp.Dir(os.TempDir()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository, w *git.Worktree, old, new *plumbing.Reference) error {
|
||||||
|
oldCommit, err := r.CommitObject(old.Hash())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
newCommit, err := r.CommitObject(new.Hash())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
patch, err := oldCommit.Patch(newCommit)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error to create patch: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var actions []action
|
||||||
|
for _, fp := range patch.FilePatches() {
|
||||||
|
from, to := fp.Files()
|
||||||
|
|
||||||
|
var isValidPath bool
|
||||||
|
if from != nil {
|
||||||
|
isValidPath = isValidScriptPath(from.Path())
|
||||||
|
}
|
||||||
|
if to != nil {
|
||||||
|
isValidPath = isValidPath || isValidScriptPath(to.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValidPath {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case to == nil:
|
||||||
|
actions = append(actions, action{
|
||||||
|
Type: actionDelete,
|
||||||
|
File: from.Path(),
|
||||||
|
})
|
||||||
|
case from == nil:
|
||||||
|
actions = append(actions, action{
|
||||||
|
Type: actionUpdate,
|
||||||
|
File: to.Path(),
|
||||||
|
})
|
||||||
|
case from.Path() != to.Path():
|
||||||
|
actions = append(actions,
|
||||||
|
action{
|
||||||
|
Type: actionDelete,
|
||||||
|
File: from.Path(),
|
||||||
|
},
|
||||||
|
action{
|
||||||
|
Type: actionUpdate,
|
||||||
|
File: to.Path(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
slog.Debug("unexpected, but I'll try to do")
|
||||||
|
actions = append(actions, action{
|
||||||
|
Type: actionUpdate,
|
||||||
|
File: to.Path(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repoDir := w.Filesystem.Root()
|
||||||
|
parser := syntax.NewParser()
|
||||||
|
|
||||||
|
for _, action := range actions {
|
||||||
|
var scriptDir string
|
||||||
|
if filepath.Dir(action.File) == "." {
|
||||||
|
scriptDir = repoDir
|
||||||
|
} else {
|
||||||
|
scriptDir = filepath.Dir(filepath.Join(repoDir, action.File))
|
||||||
|
}
|
||||||
|
|
||||||
|
runner, err := rs.processRepoChangesRunner(repoDir, scriptDir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating process repo changes runner: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch action.Type {
|
||||||
|
case actionDelete:
|
||||||
|
scriptFl, err := oldCommit.File(action.File)
|
||||||
|
if err != nil {
|
||||||
|
slog.Warn("Failed to get deleted file from old commit", "file", action.File, "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := scriptFl.Reader()
|
||||||
|
if err != nil {
|
||||||
|
slog.Warn("Failed to read deleted file", "file", action.File, "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgs, err := parseScript(ctx, repo, parser, runner, r)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error parsing deleted script %s: %w", action.File, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pkg := range pkgs {
|
||||||
|
err = rs.db.DeletePkgs(ctx, "name = ? AND repository = ?", pkg.Name, repo.Name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error deleting package %s: %w", pkg.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case actionUpdate:
|
||||||
|
scriptFl, err := newCommit.File(action.File)
|
||||||
|
if err != nil {
|
||||||
|
slog.Warn("Failed to get updated file from new commit", "file", action.File, "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := scriptFl.Reader()
|
||||||
|
if err != nil {
|
||||||
|
slog.Warn("Failed to read updated file", "file", action.File, "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rs.updatePkg(ctx, repo, runner, r)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error updating package from %s: %w", action.File, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidScriptPath(path string) bool {
|
||||||
|
if filepath.Base(path) != "alr.sh" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := filepath.Dir(path)
|
||||||
|
return dir == "." || !strings.Contains(strings.TrimPrefix(dir, "./"), "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *Repos) processRepoFull(ctx context.Context, repo types.Repo, repoDir string) error {
|
||||||
|
rootScript := filepath.Join(repoDir, "alr.sh")
|
||||||
|
if fi, err := os.Stat(rootScript); err == nil && !fi.IsDir() {
|
||||||
|
slog.Debug("Found root alr.sh, processing single-script repository", "repo", repo.Name)
|
||||||
|
|
||||||
|
runner, err := rs.processRepoChangesRunner(repoDir, repoDir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating runner for root alr.sh: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptFl, err := os.Open(rootScript)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error opening root alr.sh: %w", err)
|
||||||
|
}
|
||||||
|
defer scriptFl.Close()
|
||||||
|
|
||||||
|
err = rs.updatePkg(ctx, repo, runner, scriptFl)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error processing root alr.sh: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
glob := filepath.Join(repoDir, "*/alr.sh")
|
||||||
|
matches, err := filepath.Glob(glob)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error globbing for alr.sh files: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(matches) == 0 {
|
||||||
|
slog.Warn("No alr.sh files found in repository", "repo", repo.Name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Debug("Found multiple alr.sh files, processing multi-package repository",
|
||||||
|
"repo", repo.Name, "count", len(matches))
|
||||||
|
|
||||||
|
for _, match := range matches {
|
||||||
|
runner, err := rs.processRepoChangesRunner(repoDir, filepath.Dir(match))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating runner for %s: %w", match, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptFl, err := os.Open(match)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error opening %s: %w", match, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rs.updatePkg(ctx, repo, runner, scriptFl)
|
||||||
|
scriptFl.Close()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error processing %s: %w", match, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -27,7 +27,8 @@ import (
|
|||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TestALRConfig struct{}
|
type TestALRConfig struct{}
|
||||||
@ -84,16 +85,10 @@ build_deps=('golang')
|
|||||||
result, err := database.GetPkgs(ctx, "1 = 1")
|
result, err := database.GetPkgs(ctx, "1 = 1")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
pkgCount := 0
|
pkgCount := 0
|
||||||
for result.Next() {
|
for _, pkg := range result {
|
||||||
var dbPkg db.Package
|
assert.Equal(t, "foo", pkg.Name)
|
||||||
err = result.StructScan(&dbPkg)
|
assert.Equal(t, alrsh.OverridableFromMap(map[string]string{"": "main desc"}), pkg.Description)
|
||||||
if err != nil {
|
assert.Equal(t, alrsh.OverridableFromMap(map[string][]string{"": {"sudo"}}), pkg.Depends)
|
||||||
t.Errorf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, "foo", dbPkg.Name)
|
|
||||||
assert.Equal(t, db.NewJSON(map[string]string{"": "main desc"}), dbPkg.Description)
|
|
||||||
assert.Equal(t, db.NewJSON(map[string][]string{"": {"sudo"}}), dbPkg.Depends)
|
|
||||||
pkgCount++
|
pkgCount++
|
||||||
}
|
}
|
||||||
assert.Equal(t, 1, pkgCount)
|
assert.Equal(t, 1, pkgCount)
|
||||||
@ -125,20 +120,18 @@ meta_buz() {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
pkgCount := 0
|
pkgCount := 0
|
||||||
for result.Next() {
|
for _, pkg := range result {
|
||||||
var dbPkg db.Package
|
|
||||||
err = result.StructScan(&dbPkg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected no error, got %s", err)
|
t.Errorf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
if dbPkg.Name == "bar" {
|
if pkg.Name == "bar" {
|
||||||
assert.Equal(t, db.NewJSON(map[string]string{"": "foo desc"}), dbPkg.Description)
|
assert.Equal(t, alrsh.OverridableFromMap(map[string]string{"": "foo desc"}), pkg.Description)
|
||||||
assert.Equal(t, db.NewJSON(map[string][]string{"": {"sudo"}}), dbPkg.Depends)
|
assert.Equal(t, alrsh.OverridableFromMap(map[string][]string{"": {"sudo"}}), pkg.Depends)
|
||||||
}
|
}
|
||||||
|
|
||||||
if dbPkg.Name == "buz" {
|
if pkg.Name == "buz" {
|
||||||
assert.Equal(t, db.NewJSON(map[string]string{"": "main desc"}), dbPkg.Description)
|
assert.Equal(t, alrsh.OverridableFromMap(map[string]string{"": "main desc"}), pkg.Description)
|
||||||
assert.Equal(t, db.NewJSON(map[string][]string{"": {"sudo", "doas"}}), dbPkg.Depends)
|
assert.Equal(t, alrsh.OverridableFromMap(map[string][]string{"": {"sudo", "doas"}}), pkg.Depends)
|
||||||
}
|
}
|
||||||
pkgCount++
|
pkgCount++
|
||||||
}
|
}
|
@ -26,16 +26,15 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
|
|
||||||
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
|
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/repos"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TestEnv struct {
|
type TestEnv struct {
|
||||||
Ctx context.Context
|
Ctx context.Context
|
||||||
Cfg *TestALRConfig
|
Cfg *TestALRConfig
|
||||||
Db *db.Database
|
Db *database.Database
|
||||||
}
|
}
|
||||||
|
|
||||||
type TestALRConfig struct {
|
type TestALRConfig struct {
|
||||||
@ -129,15 +128,7 @@ func TestPull(t *testing.T) {
|
|||||||
t.Fatalf("Expected no error, got %s", err)
|
t.Fatalf("Expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var pkgAmt int
|
pkgAmt := len(result)
|
||||||
for result.Next() {
|
|
||||||
var dbPkg db.Package
|
|
||||||
err = result.StructScan(&dbPkg)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
pkgAmt++
|
|
||||||
}
|
|
||||||
|
|
||||||
if pkgAmt == 0 {
|
if pkgAmt == 0 {
|
||||||
t.Errorf("Expected at least 1 matching package, but got %d", pkgAmt)
|
t.Errorf("Expected at least 1 matching package, but got %d", pkgAmt)
|
@ -19,7 +19,7 @@ package repos
|
|||||||
import (
|
import (
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
||||||
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
|
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config interface {
|
type Config interface {
|
112
internal/repos/utils.go
Normal file
112
internal/repos/utils.go
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
// 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 repos
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/go-git/go-git/v5"
|
||||||
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
|
"github.com/go-git/go-git/v5/plumbing/transport"
|
||||||
|
"github.com/go-git/go-git/v5/plumbing/transport/client"
|
||||||
|
|
||||||
|
"mvdan.cc/sh/v3/interp"
|
||||||
|
"mvdan.cc/sh/v3/syntax"
|
||||||
|
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseScript(
|
||||||
|
ctx context.Context,
|
||||||
|
repo types.Repo,
|
||||||
|
syntaxParser *syntax.Parser,
|
||||||
|
runner *interp.Runner,
|
||||||
|
r io.ReadCloser,
|
||||||
|
) ([]*alrsh.Package, error) {
|
||||||
|
f, err := alrsh.ReadFromIOReader(r, "/tmp")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, dbPkgs, err := f.ParseBuildVars(ctx, &distro.OSRelease{}, []string{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, pkg := range dbPkgs {
|
||||||
|
pkg.Repository = repo.Name
|
||||||
|
}
|
||||||
|
return dbPkgs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHeadReference(r *git.Repository) (plumbing.ReferenceName, error) {
|
||||||
|
remote, err := r.Remote(git.DefaultRemoteName)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint, err := transport.NewEndpoint(remote.Config().URLs[0])
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
gitClient, err := client.NewClient(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
session, err := gitClient.NewUploadPackSession(endpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := session.AdvertisedReferences()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
refs, err := info.AllReferences()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return refs["HEAD"].Target(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveHash(r *git.Repository, ref string) (*plumbing.Hash, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if ref == "" {
|
||||||
|
reference, err := getHeadReference(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get head reference %w", err)
|
||||||
|
}
|
||||||
|
ref = reference.Short()
|
||||||
|
}
|
||||||
|
|
||||||
|
hsh, err := r.ResolveRevision(git.DefaultRemoteName + "/" + plumbing.Revision(ref))
|
||||||
|
if err != nil {
|
||||||
|
hsh, err = r.ResolveRevision(plumbing.Revision(ref))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hsh, nil
|
||||||
|
}
|
@ -22,13 +22,11 @@ package search
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
|
|
||||||
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type PackagesProvider interface {
|
type PackagesProvider interface {
|
||||||
GetPkgs(ctx context.Context, where string, args ...any) (*sqlx.Rows, error)
|
GetPkgs(ctx context.Context, where string, args ...any) ([]alrsh.Package, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Searcher struct {
|
type Searcher struct {
|
||||||
@ -44,23 +42,8 @@ func New(pp PackagesProvider) *Searcher {
|
|||||||
func (s *Searcher) Search(
|
func (s *Searcher) Search(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
opts *SearchOptions,
|
opts *SearchOptions,
|
||||||
) ([]database.Package, error) {
|
) ([]alrsh.Package, error) {
|
||||||
var packages []database.Package
|
|
||||||
|
|
||||||
where, args := opts.WhereClause()
|
where, args := opts.WhereClause()
|
||||||
result, err := s.pp.GetPkgs(ctx, where, args...)
|
packages, err := s.pp.GetPkgs(ctx, where, args...)
|
||||||
if err != nil {
|
return packages, err
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for result.Next() {
|
|
||||||
var dbPkg database.Package
|
|
||||||
err = result.StructScan(&dbPkg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
packages = append(packages, dbPkg)
|
|
||||||
}
|
|
||||||
|
|
||||||
return packages, nil
|
|
||||||
}
|
}
|
@ -21,7 +21,7 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/search"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/search"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSearhOptionsBuilder(t *testing.T) {
|
func TestSearhOptionsBuilder(t *testing.T) {
|
@ -22,6 +22,7 @@ package decoder
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -52,7 +53,7 @@ type InvalidTypeError struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ite InvalidTypeError) Error() string {
|
func (ite InvalidTypeError) Error() string {
|
||||||
return "variable '" + ite.name + "' is of type " + ite.vartype + ", but " + ite.exptype + " is expected"
|
return fmt.Sprintf("variable '%s' is of type %s, but %s is expected", ite.name, ite.vartype, ite.exptype)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decoder provides methods for decoding variable values
|
// Decoder provides methods for decoding variable values
|
||||||
@ -73,39 +74,99 @@ func New(info *distro.OSRelease, runner *interp.Runner) *Decoder {
|
|||||||
// 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 {
|
||||||
variable := d.getVar(name)
|
origType := reflect.TypeOf(val).Elem()
|
||||||
if variable == nil {
|
isOverridableField := strings.Contains(origType.String(), "OverridableField[")
|
||||||
return VarNotFoundError{name}
|
|
||||||
}
|
|
||||||
|
|
||||||
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
if !isOverridableField {
|
||||||
WeaklyTypedInput: true,
|
variable := d.getVarNoOverrides(name)
|
||||||
DecodeHook: mapstructure.DecodeHookFuncValue(func(from, to reflect.Value) (interface{}, error) {
|
if variable == nil {
|
||||||
if strings.Contains(to.Type().String(), "db.JSON") {
|
return VarNotFoundError{name}
|
||||||
valType := to.FieldByName("Val").Type()
|
}
|
||||||
if !from.Type().AssignableTo(valType) {
|
|
||||||
return nil, InvalidTypeError{name, from.Type().String(), valType.String()}
|
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||||
|
WeaklyTypedInput: true,
|
||||||
|
Result: val, // передаем указатель на новое значение
|
||||||
|
TagName: "sh",
|
||||||
|
DecodeHook: mapstructure.DecodeHookFuncValue(func(from, to reflect.Value) (interface{}, error) {
|
||||||
|
if from.Kind() == reflect.Slice && to.Kind() == reflect.String {
|
||||||
|
s, ok := from.Interface().([]string)
|
||||||
|
if ok && len(s) == 1 {
|
||||||
|
return s[0], nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return from.Interface(), nil
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
to.FieldByName("Val").Set(from)
|
switch variable.Kind {
|
||||||
return to, nil
|
case expand.Indexed:
|
||||||
|
return dec.Decode(variable.List)
|
||||||
|
case expand.Associative:
|
||||||
|
return dec.Decode(variable.Map)
|
||||||
|
default:
|
||||||
|
return dec.Decode(variable.Str)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vars := d.getVarsByPrefix(name)
|
||||||
|
|
||||||
|
if len(vars) == 0 {
|
||||||
|
return VarNotFoundError{name}
|
||||||
|
}
|
||||||
|
|
||||||
|
reflectVal := reflect.ValueOf(val)
|
||||||
|
overridableVal := reflect.ValueOf(val).Elem()
|
||||||
|
|
||||||
|
dataField := overridableVal.FieldByName("data")
|
||||||
|
if !dataField.IsValid() {
|
||||||
|
return fmt.Errorf("data field not found in OverridableField")
|
||||||
|
}
|
||||||
|
mapType := dataField.Type() // map[string]T
|
||||||
|
elemType := mapType.Elem() // T
|
||||||
|
|
||||||
|
var overridablePtr reflect.Value
|
||||||
|
if reflectVal.Kind() == reflect.Ptr {
|
||||||
|
overridablePtr = reflectVal
|
||||||
|
} else {
|
||||||
|
if !reflectVal.CanAddr() {
|
||||||
|
return fmt.Errorf("OverridableField value is not addressable")
|
||||||
}
|
}
|
||||||
return from.Interface(), nil
|
overridablePtr = reflectVal.Addr()
|
||||||
}),
|
}
|
||||||
Result: val,
|
|
||||||
TagName: "sh",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch variable.Kind {
|
setValue := overridablePtr.MethodByName("Set")
|
||||||
case expand.Indexed:
|
if !setValue.IsValid() {
|
||||||
return dec.Decode(variable.List)
|
return fmt.Errorf("method Set not found on OverridableField")
|
||||||
case expand.Associative:
|
}
|
||||||
return dec.Decode(variable.Map)
|
|
||||||
default:
|
for _, v := range vars {
|
||||||
return dec.Decode(variable.Str)
|
varName := v.Name
|
||||||
|
|
||||||
|
key := strings.TrimPrefix(strings.TrimPrefix(varName, name), "_")
|
||||||
|
newVal := reflect.New(elemType)
|
||||||
|
|
||||||
|
if err := d.DecodeVar(varName, newVal.Interface()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
keyValue := reflect.ValueOf(key)
|
||||||
|
setValue.Call([]reflect.Value{keyValue, newVal.Elem()})
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveValue := overridablePtr.MethodByName("Resolve")
|
||||||
|
if !resolveValue.IsValid() {
|
||||||
|
return fmt.Errorf("method Resolve not found on OverridableField")
|
||||||
|
}
|
||||||
|
|
||||||
|
names, err := overrides.Resolve(d.info, overrides.DefaultOpts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveValue.Call([]reflect.Value{reflect.ValueOf(names)})
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,32 +307,49 @@ func (d *Decoder) getFunc(name string) *syntax.Stmt {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getVar gets a variable based on its name, taking into account
|
func (d *Decoder) getVarNoOverrides(name string) *expand.Variable {
|
||||||
// override variables and nameref variables.
|
val, ok := d.Runner.Vars[name]
|
||||||
func (d *Decoder) getVar(name string) *expand.Variable {
|
if ok {
|
||||||
names, err := overrides.Resolve(d.info, overrides.DefaultOpts.WithName(name))
|
// Resolve nameref variables
|
||||||
if err != nil {
|
_, resolved := val.Resolve(expand.FuncEnviron(func(s string) string {
|
||||||
return nil
|
if val, ok := d.Runner.Vars[s]; ok {
|
||||||
}
|
return val.String()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}))
|
||||||
|
val = resolved
|
||||||
|
|
||||||
for _, varName := range names {
|
return &val
|
||||||
val, ok := d.Runner.Vars[varName]
|
|
||||||
if ok {
|
|
||||||
// Resolve nameref variables
|
|
||||||
_, resolved := val.Resolve(expand.FuncEnviron(func(s string) string {
|
|
||||||
if val, ok := d.Runner.Vars[s]; ok {
|
|
||||||
return val.String()
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}))
|
|
||||||
val = resolved
|
|
||||||
|
|
||||||
return &val
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type vars struct {
|
||||||
|
Name string
|
||||||
|
Value *expand.Variable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Decoder) getVarsByPrefix(prefix string) []*vars {
|
||||||
|
result := make([]*vars, 0)
|
||||||
|
for name, val := range d.Runner.Vars {
|
||||||
|
if !strings.HasPrefix(name, prefix) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch prefix {
|
||||||
|
case "auto_req":
|
||||||
|
if strings.HasPrefix(name, "auto_req_skiplist") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case "auto_prov":
|
||||||
|
if strings.HasPrefix(name, "auto_prov_skiplist") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = append(result, &vars{name, &val})
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func IsTruthy(value string) bool {
|
func IsTruthy(value string) bool {
|
||||||
value = strings.ToLower(strings.TrimSpace(value))
|
value = strings.ToLower(strings.TrimSpace(value))
|
||||||
return value == "true" || value == "yes" || value == "1"
|
return value == "true" || value == "yes" || value == "1"
|
||||||
|
@ -32,24 +32,25 @@ import (
|
|||||||
"mvdan.cc/sh/v3/syntax"
|
"mvdan.cc/sh/v3/syntax"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BuildVars struct {
|
type BuildVars struct {
|
||||||
Name string `sh:"name,required"`
|
Name string `sh:"name,required"`
|
||||||
Version string `sh:"version,required"`
|
Version string `sh:"version,required"`
|
||||||
Release int `sh:"release,required"`
|
Release int `sh:"release,required"`
|
||||||
Epoch uint `sh:"epoch"`
|
Epoch uint `sh:"epoch"`
|
||||||
Description string `sh:"desc"`
|
Description alrsh.OverridableField[string] `sh:"desc"`
|
||||||
Homepage string `sh:"homepage"`
|
Homepage string `sh:"homepage"`
|
||||||
Maintainer string `sh:"maintainer"`
|
Maintainer string `sh:"maintainer"`
|
||||||
Architectures []string `sh:"architectures"`
|
Architectures []string `sh:"architectures"`
|
||||||
Licenses []string `sh:"license"`
|
Licenses []string `sh:"license"`
|
||||||
Provides []string `sh:"provides"`
|
Provides []string `sh:"provides"`
|
||||||
Conflicts []string `sh:"conflicts"`
|
Conflicts []string `sh:"conflicts"`
|
||||||
Depends []string `sh:"deps"`
|
Depends []string `sh:"deps"`
|
||||||
BuildDepends []string `sh:"build_deps"`
|
BuildDepends alrsh.OverridableField[[]string] `sh:"build_deps"`
|
||||||
Replaces []string `sh:"replaces"`
|
Replaces alrsh.OverridableField[[]string] `sh:"replaces"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const testScript = `
|
const testScript = `
|
||||||
@ -113,22 +114,34 @@ func TestDecodeVars(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
expected := BuildVars{
|
expected := BuildVars{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Version: "0.0.1",
|
Version: "0.0.1",
|
||||||
Release: 1,
|
Release: 1,
|
||||||
Epoch: 2,
|
Epoch: 2,
|
||||||
Description: "Test package",
|
Description: alrsh.OverridableFromMap(map[string]string{
|
||||||
|
"": "Test package",
|
||||||
|
}),
|
||||||
Homepage: "https://gitea.plemya-x.ru/xpamych/ALR",
|
Homepage: "https://gitea.plemya-x.ru/xpamych/ALR",
|
||||||
Maintainer: "Евгений Храмов <xpamych@yandex.ru>",
|
Maintainer: "Евгений Храмов <xpamych@yandex.ru>",
|
||||||
Architectures: []string{"arm64", "amd64"},
|
Architectures: []string{"arm64", "amd64"},
|
||||||
Licenses: []string{"GPL-3.0-or-later"},
|
Licenses: []string{"GPL-3.0-or-later"},
|
||||||
Provides: []string{"test"},
|
Provides: []string{"test"},
|
||||||
Conflicts: []string{"test"},
|
Conflicts: []string{"test"},
|
||||||
Replaces: []string{"test-legacy"},
|
Replaces: alrsh.OverridableFromMap(map[string][]string{
|
||||||
Depends: []string{"sudo"},
|
"": {"test-old"},
|
||||||
BuildDepends: []string{"go"},
|
"test_os": {"test-legacy"},
|
||||||
|
}),
|
||||||
|
Depends: []string{"sudo"},
|
||||||
|
BuildDepends: alrsh.OverridableFromMap(map[string][]string{
|
||||||
|
"": {"golang"},
|
||||||
|
"arch": {"go"},
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expected.Description.SetResolved("Test package")
|
||||||
|
expected.Replaces.SetResolved([]string{"test-legacy"})
|
||||||
|
expected.BuildDepends.SetResolved([]string{"go"})
|
||||||
|
|
||||||
if !reflect.DeepEqual(bv, expected) {
|
if !reflect.DeepEqual(bv, expected) {
|
||||||
t.Errorf("Expected %v, got %v", expected, bv)
|
t.Errorf("Expected %v, got %v", expected, bv)
|
||||||
}
|
}
|
||||||
|
178
internal/shutils/helpers/files_find.go
Normal file
178
internal/shutils/helpers/files_find.go
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
// 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 helpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/bmatcuk/doublestar/v4"
|
||||||
|
"mvdan.cc/sh/v3/interp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func matchNamePattern(name, pattern string) bool {
|
||||||
|
matched, err := filepath.Match(pattern, name)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return matched
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateDir(dirPath, commandName string) error {
|
||||||
|
info, err := os.Stat(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: %w", commandName, err)
|
||||||
|
}
|
||||||
|
if !info.IsDir() {
|
||||||
|
return fmt.Errorf("%s: %s is not a directory", commandName, dirPath)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func outputFiles(hc interp.HandlerContext, files []string) {
|
||||||
|
for _, file := range files {
|
||||||
|
fmt.Fprintln(hc.Stdout, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeRelativePath(basePath, fullPath string) (string, error) {
|
||||||
|
relPath, err := filepath.Rel(basePath, fullPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return "./" + relPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filesFindLangCmd(hc interp.HandlerContext, cmd string, args []string) error {
|
||||||
|
namePattern := "*.mo"
|
||||||
|
if len(args) > 0 {
|
||||||
|
namePattern = args[0] + ".mo"
|
||||||
|
}
|
||||||
|
|
||||||
|
localePath := "./usr/share/locale/"
|
||||||
|
realPath := path.Join(hc.Dir, localePath)
|
||||||
|
|
||||||
|
if err := validateDir(realPath, "files-find-lang"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var langFiles []string
|
||||||
|
err := filepath.Walk(realPath, func(p string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !info.IsDir() && matchNamePattern(info.Name(), namePattern) {
|
||||||
|
relPath, relErr := makeRelativePath(hc.Dir, p)
|
||||||
|
if relErr != nil {
|
||||||
|
return relErr
|
||||||
|
}
|
||||||
|
langFiles = append(langFiles, relPath)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("files-find-lang: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
outputFiles(hc, langFiles)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filesFindDocCmd(hc interp.HandlerContext, cmd string, args []string) error {
|
||||||
|
namePattern := "*"
|
||||||
|
if len(args) > 0 {
|
||||||
|
namePattern = args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
docPath := "./usr/share/doc/"
|
||||||
|
docRealPath := path.Join(hc.Dir, docPath)
|
||||||
|
|
||||||
|
if err := validateDir(docRealPath, "files-find-doc"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var docFiles []string
|
||||||
|
|
||||||
|
entries, err := os.ReadDir(docRealPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("files-find-doc: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
if matchNamePattern(entry.Name(), namePattern) {
|
||||||
|
targetPath := filepath.Join(docRealPath, entry.Name())
|
||||||
|
targetInfo, err := os.Stat(targetPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("files-find-doc: %w", err)
|
||||||
|
}
|
||||||
|
if targetInfo.IsDir() {
|
||||||
|
err := filepath.Walk(targetPath, func(subPath string, subInfo os.FileInfo, subErr error) error {
|
||||||
|
if subErr != nil {
|
||||||
|
return subErr
|
||||||
|
}
|
||||||
|
relPath, err := makeRelativePath(hc.Dir, subPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
docFiles = append(docFiles, relPath)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("files-find-doc: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outputFiles(hc, docFiles)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filesFindCmd(hc interp.HandlerContext, cmd string, args []string) error {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return fmt.Errorf("find-files: at least one glob pattern is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
var foundFiles []string
|
||||||
|
|
||||||
|
for _, globPattern := range args {
|
||||||
|
searchPath := path.Join(hc.Dir, globPattern)
|
||||||
|
|
||||||
|
basepath, pattern := doublestar.SplitPattern(searchPath)
|
||||||
|
fsys := os.DirFS(basepath)
|
||||||
|
matches, err := doublestar.Glob(fsys, pattern, doublestar.WithNoFollow())
|
||||||
|
if err != nil {
|
||||||
|
slog.Warn("find-files: invalid glob pattern", "pattern", globPattern, "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, match := range matches {
|
||||||
|
relPath, err := makeRelativePath(hc.Dir, path.Join(basepath, match))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
foundFiles = append(foundFiles, relPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outputFiles(hc, foundFiles)
|
||||||
|
return nil
|
||||||
|
}
|
@ -24,7 +24,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -57,6 +56,7 @@ var Helpers = handlers.ExecFuncs{
|
|||||||
"install-library": installLibraryCmd,
|
"install-library": installLibraryCmd,
|
||||||
"git-version": gitVersionCmd,
|
"git-version": gitVersionCmd,
|
||||||
|
|
||||||
|
"files-find": filesFindCmd,
|
||||||
"files-find-lang": filesFindLangCmd,
|
"files-find-lang": filesFindLangCmd,
|
||||||
"files-find-doc": filesFindDocCmd,
|
"files-find-doc": filesFindDocCmd,
|
||||||
}
|
}
|
||||||
@ -65,6 +65,7 @@ var Helpers = handlers.ExecFuncs{
|
|||||||
// that don't modify any state
|
// that don't modify any state
|
||||||
var Restricted = handlers.ExecFuncs{
|
var Restricted = handlers.ExecFuncs{
|
||||||
"git-version": gitVersionCmd,
|
"git-version": gitVersionCmd,
|
||||||
|
"files-find": filesFindCmd,
|
||||||
"files-find-lang": filesFindLangCmd,
|
"files-find-lang": filesFindLangCmd,
|
||||||
"files-find-doc": filesFindDocCmd,
|
"files-find-doc": filesFindDocCmd,
|
||||||
}
|
}
|
||||||
@ -265,114 +266,6 @@ func gitVersionCmd(hc interp.HandlerContext, cmd string, args []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func filesFindLangCmd(hc interp.HandlerContext, cmd string, args []string) error {
|
|
||||||
namePattern := "*.mo"
|
|
||||||
if len(args) > 0 {
|
|
||||||
namePattern = args[0] + ".mo"
|
|
||||||
}
|
|
||||||
|
|
||||||
localePath := "./usr/share/locale/"
|
|
||||||
realPath := path.Join(hc.Dir, localePath)
|
|
||||||
|
|
||||||
info, err := os.Stat(realPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("files-find-lang: %w", err)
|
|
||||||
}
|
|
||||||
if !info.IsDir() {
|
|
||||||
return fmt.Errorf("files-find-lang: %s is not a directory", localePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
var langFiles []string
|
|
||||||
err = filepath.Walk(realPath, func(p string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !info.IsDir() && matchNamePattern(info.Name(), namePattern) {
|
|
||||||
relPath, relErr := filepath.Rel(hc.Dir, p)
|
|
||||||
if relErr != nil {
|
|
||||||
return relErr
|
|
||||||
}
|
|
||||||
langFiles = append(langFiles, "./"+relPath)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("files-find-lang: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range langFiles {
|
|
||||||
fmt.Fprintln(hc.Stdout, file)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func filesFindDocCmd(hc interp.HandlerContext, cmd string, args []string) error {
|
|
||||||
namePattern := "*"
|
|
||||||
if len(args) > 0 {
|
|
||||||
namePattern = args[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
docPath := "./usr/share/doc/"
|
|
||||||
docRealPath := path.Join(hc.Dir, docPath)
|
|
||||||
|
|
||||||
info, err := os.Stat(docRealPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("files-find-doc: %w", err)
|
|
||||||
}
|
|
||||||
if !info.IsDir() {
|
|
||||||
return fmt.Errorf("files-find-doc: %s is not a directory", docPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
var docFiles []string
|
|
||||||
|
|
||||||
entries, err := os.ReadDir(docRealPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, entry := range entries {
|
|
||||||
if matchNamePattern(entry.Name(), namePattern) {
|
|
||||||
targetPath := filepath.Join(docRealPath, entry.Name())
|
|
||||||
targetInfo, err := os.Stat(targetPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if targetInfo.IsDir() {
|
|
||||||
err := filepath.Walk(targetPath, func(subPath string, subInfo os.FileInfo, subErr error) error {
|
|
||||||
relPath, err := filepath.Rel(hc.Dir, subPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
docFiles = append(docFiles, "./"+relPath)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("files-find-doc: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range docFiles {
|
|
||||||
fmt.Fprintln(hc.Stdout, file)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchNamePattern(name, pattern string) bool {
|
|
||||||
matched, err := filepath.Match(pattern, name)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return matched
|
|
||||||
}
|
|
||||||
|
|
||||||
func helperInstall(from, to string, perms os.FileMode) error {
|
func helperInstall(from, to string, perms os.FileMode) error {
|
||||||
err := os.MkdirAll(filepath.Dir(to), 0o755)
|
err := os.MkdirAll(filepath.Dir(to), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -31,12 +31,18 @@ import (
|
|||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type symlink struct {
|
||||||
|
linkPath string
|
||||||
|
targetPath string
|
||||||
|
}
|
||||||
|
|
||||||
type testCase struct {
|
type testCase struct {
|
||||||
name string
|
name string
|
||||||
dirsToCreate []string
|
dirsToCreate []string
|
||||||
filesToCreate []string
|
filesToCreate []string
|
||||||
expectedOutput []string
|
expectedOutput []string
|
||||||
args string
|
symlinksToCreate []symlink
|
||||||
|
args string
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFindFilesDoc(t *testing.T) {
|
func TestFindFilesDoc(t *testing.T) {
|
||||||
@ -214,3 +220,94 @@ files-find-lang ` + tc.args
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFindFiles(t *testing.T) {
|
||||||
|
tests := []testCase{
|
||||||
|
{
|
||||||
|
name: "With file and dir symlinks",
|
||||||
|
dirsToCreate: []string{
|
||||||
|
"usr/share/locale/ru/LC_MESSAGES",
|
||||||
|
"usr/share/locale/tr/LC_MESSAGES",
|
||||||
|
"opt/app",
|
||||||
|
"opt/app/internal",
|
||||||
|
},
|
||||||
|
filesToCreate: []string{
|
||||||
|
"usr/share/locale/ru/LC_MESSAGES/yandex-disk.mo",
|
||||||
|
"usr/share/locale/ru/LC_MESSAGES/yandex-disk-indicator.mo",
|
||||||
|
"usr/share/locale/tr/LC_MESSAGES/yandex-disk.mo",
|
||||||
|
"opt/app/internal/test",
|
||||||
|
},
|
||||||
|
symlinksToCreate: []symlink{
|
||||||
|
{
|
||||||
|
linkPath: "/opt/app/etc",
|
||||||
|
targetPath: "/etc",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedOutput: []string{
|
||||||
|
"./usr/share/locale/ru/LC_MESSAGES/yandex-disk.mo",
|
||||||
|
"./usr/share/locale/ru/LC_MESSAGES/yandex-disk-indicator.mo",
|
||||||
|
"./usr/share/locale/tr/LC_MESSAGES/yandex-disk.mo",
|
||||||
|
"./opt/app/etc",
|
||||||
|
"./opt/app/internal",
|
||||||
|
"./opt/app/internal/test",
|
||||||
|
},
|
||||||
|
args: "\"/usr/share/locale/*/LC_MESSAGES/*.mo\" \"/opt/app/**/*\"",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
tempDir, err := os.MkdirTemp("", "test-files-find")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer os.RemoveAll(tempDir)
|
||||||
|
|
||||||
|
for _, dir := range tc.dirsToCreate {
|
||||||
|
dirPath := filepath.Join(tempDir, dir)
|
||||||
|
err := os.MkdirAll(dirPath, 0o755)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range tc.filesToCreate {
|
||||||
|
filePath := filepath.Join(tempDir, file)
|
||||||
|
err := os.WriteFile(filePath, []byte("test content"), 0o644)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sl := range tc.symlinksToCreate {
|
||||||
|
linkFullPath := filepath.Join(tempDir, sl.linkPath)
|
||||||
|
targetFullPath := sl.targetPath
|
||||||
|
|
||||||
|
// make sure parent dir exists
|
||||||
|
err := os.MkdirAll(filepath.Dir(linkFullPath), 0o755)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = os.Symlink(targetFullPath, linkFullPath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
helpers := handlers.ExecFuncs{
|
||||||
|
"files-find": filesFindCmd,
|
||||||
|
}
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
runner, err := interp.New(
|
||||||
|
interp.Dir(tempDir),
|
||||||
|
interp.StdIO(os.Stdin, buf, os.Stderr),
|
||||||
|
interp.ExecHandler(helpers.ExecHandler(interp.DefaultExecHandler(1000))),
|
||||||
|
)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
scriptContent := `
|
||||||
|
shopt -s globstar
|
||||||
|
files-find ` + tc.args
|
||||||
|
|
||||||
|
script, err := syntax.NewParser().Parse(strings.NewReader(scriptContent), "")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = runner.Run(context.Background(), script)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
contents := strings.Fields(strings.TrimSpace(buf.String()))
|
||||||
|
assert.ElementsMatch(t, tc.expectedOutput, contents)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -58,11 +58,11 @@ msgstr ""
|
|||||||
msgid "Done"
|
msgid "Done"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: fix.go:38
|
#: fix.go:39
|
||||||
msgid "Attempt to fix problems with ALR"
|
msgid "Attempt to fix problems with ALR"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: fix.go:59
|
#: fix.go:60
|
||||||
msgid "Clearing cache directory"
|
msgid "Clearing cache directory"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -74,15 +74,15 @@ msgstr ""
|
|||||||
msgid "Unable to read cache directory contents"
|
msgid "Unable to read cache directory contents"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: fix.go:76
|
#: fix.go:82
|
||||||
msgid "Unable to remove cache item (%s)"
|
msgid "Unable to remove cache item (%s)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: fix.go:80
|
#: fix.go:86
|
||||||
msgid "Rebuilding cache"
|
msgid "Rebuilding cache"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: fix.go:84
|
#: fix.go:90
|
||||||
msgid "Unable to create new cache directory"
|
msgid "Unable to create new cache directory"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -126,58 +126,120 @@ msgstr ""
|
|||||||
msgid "Error getting packages"
|
msgid "Error getting packages"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: info.go:76
|
#: info.go:83
|
||||||
msgid "Error iterating over packages"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: info.go:90
|
|
||||||
msgid "Command info expected at least 1 argument, got %d"
|
msgid "Command info expected at least 1 argument, got %d"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: info.go:110
|
#: info.go:104
|
||||||
msgid "Error finding packages"
|
msgid "Error finding packages"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: info.go:124
|
#: info.go:118
|
||||||
msgid "Can't detect system language"
|
msgid "Can't detect system language"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: info.go:141
|
#: info.go:134
|
||||||
msgid "Error resolving overrides"
|
msgid "Error resolving overrides"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: info.go:149 info.go:154
|
#: info.go:143
|
||||||
msgid "Error encoding script variables"
|
msgid "Error encoding script variables"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: install.go:40
|
#: install.go:39
|
||||||
msgid "Install a new package"
|
msgid "Install a new package"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: install.go:52
|
#: install.go:51
|
||||||
msgid "Command install expected at least 1 argument, got %d"
|
msgid "Command install expected at least 1 argument, got %d"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: install.go:114
|
#: install.go:113
|
||||||
msgid "Error when installing the package"
|
msgid "Error when installing the package"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: install.go:159
|
#: install.go:151
|
||||||
msgid "Remove an installed package"
|
msgid "Remove an installed package"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: install.go:178
|
#: install.go:170
|
||||||
msgid "Error listing installed packages"
|
msgid "Error listing installed packages"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: install.go:215
|
#: install.go:199
|
||||||
msgid "Command remove expected at least 1 argument, got %d"
|
msgid "Command remove expected at least 1 argument, got %d"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: install.go:230
|
#: install.go:214
|
||||||
msgid "Error removing packages"
|
msgid "Error removing packages"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: internal/build/build.go:376
|
||||||
|
msgid "Building package"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: internal/build/build.go:405
|
||||||
|
msgid "The checksums array must be the same length as sources"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: internal/build/build.go:447
|
||||||
|
msgid "Downloading sources"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: internal/build/build.go:539
|
||||||
|
msgid "Installing dependencies"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: internal/build/checker.go:43
|
||||||
|
msgid ""
|
||||||
|
"Your system's CPU architecture doesn't match this package. Do you want to "
|
||||||
|
"build anyway?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: internal/build/checker.go:67
|
||||||
|
msgid "This package is already installed"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: internal/build/find_deps/alt_linux.go:35
|
||||||
|
msgid "Command not found on the system"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: internal/build/find_deps/alt_linux.go:86
|
||||||
|
msgid "Provided dependency found"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: internal/build/find_deps/alt_linux.go:93
|
||||||
|
msgid "Required dependency found"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: internal/build/find_deps/empty.go:32
|
||||||
|
msgid "AutoProv is not implemented for this package format, so it's skipped"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: internal/build/find_deps/empty.go:37
|
||||||
|
msgid "AutoReq is not implemented for this package format, so it's skipped"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: internal/build/firejail.go:144
|
||||||
|
msgid "Applying FireJail integration"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: internal/build/script_executor.go:145
|
||||||
|
msgid "Building package metadata"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: internal/build/script_executor.go:285
|
||||||
|
msgid "Executing prepare()"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: internal/build/script_executor.go:294
|
||||||
|
msgid "Executing build()"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: internal/build/script_executor.go:323 internal/build/script_executor.go:343
|
||||||
|
msgid "Executing %s()"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: internal/cliutils/app_builder/builder.go:75
|
#: internal/cliutils/app_builder/builder.go:75
|
||||||
msgid "Error loading config"
|
msgid "Error loading config"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -186,15 +248,15 @@ msgstr ""
|
|||||||
msgid "Error initialization database"
|
msgid "Error initialization database"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internal/cliutils/app_builder/builder.go:135
|
#: internal/cliutils/app_builder/builder.go:142
|
||||||
msgid "Error pulling repositories"
|
msgid "Error pulling repositories"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internal/cliutils/app_builder/builder.go:152
|
#: internal/cliutils/app_builder/builder.go:159
|
||||||
msgid "Error parsing os release"
|
msgid "Error parsing os release"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internal/cliutils/app_builder/builder.go:165
|
#: internal/cliutils/app_builder/builder.go:172
|
||||||
msgid "Unable to detect a supported package manager on the system"
|
msgid "Unable to detect a supported package manager on the system"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -284,43 +346,45 @@ msgid ""
|
|||||||
"instead!"
|
"instead!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internal/db/db.go:137
|
#: internal/db/db.go:76
|
||||||
msgid "Database version mismatch; resetting"
|
msgid "Database version mismatch; resetting"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internal/db/db.go:144
|
#: internal/db/db.go:82
|
||||||
msgid ""
|
msgid ""
|
||||||
"Database version does not exist. Run alr fix if something isn't working."
|
"Database version does not exist. Run alr fix if something isn't working."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internal/dl/dl.go:170
|
|
||||||
msgid "Source can be updated, updating if required"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: internal/dl/dl.go:201
|
|
||||||
msgid "Source found in cache and linked to destination"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: internal/dl/dl.go:208
|
|
||||||
msgid "Source updated and linked to destination"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: internal/dl/dl.go:222
|
|
||||||
msgid "Downloading source"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: internal/dl/progress_tui.go:100
|
|
||||||
msgid "%s: done!\n"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: internal/dl/progress_tui.go:104
|
|
||||||
msgid "%s %s downloading at %s/s\n"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: internal/logger/log.go:41
|
#: internal/logger/log.go:41
|
||||||
msgid "ERROR"
|
msgid "ERROR"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: internal/repos/pull.go:88
|
||||||
|
msgid "Trying mirror"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: internal/repos/pull.go:94
|
||||||
|
msgid "Failed to pull from URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: internal/repos/pull.go:158
|
||||||
|
msgid "Pulling repository"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: internal/repos/pull.go:195
|
||||||
|
msgid "Repository up to date"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: internal/repos/pull.go:230
|
||||||
|
msgid "Git repository does not appear to be a valid ALR repo"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: internal/repos/pull.go:246
|
||||||
|
msgid ""
|
||||||
|
"ALR repo's minimum ALR version is greater than the current version. Try "
|
||||||
|
"updating ALR if something doesn't work."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: internal/utils/cmd.go:97
|
#: internal/utils/cmd.go:97
|
||||||
msgid "Error on dropping capabilities"
|
msgid "Error on dropping capabilities"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -349,11 +413,11 @@ msgstr ""
|
|||||||
msgid "No packages for upgrade"
|
msgid "No packages for upgrade"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: list.go:102 list.go:187
|
#: list.go:102 list.go:184
|
||||||
msgid "Error parsing format template"
|
msgid "Error parsing format template"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: list.go:108 list.go:191
|
#: list.go:108 list.go:188
|
||||||
msgid "Error executing template"
|
msgid "Error executing template"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -377,147 +441,132 @@ msgstr ""
|
|||||||
msgid "Error while running app"
|
msgid "Error while running app"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pkg/build/build.go:395
|
#: pkg/dl/dl.go:170
|
||||||
msgid "Building package"
|
msgid "Source can be updated, updating if required"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pkg/build/build.go:424
|
#: pkg/dl/dl.go:201
|
||||||
msgid "The checksums array must be the same length as sources"
|
msgid "Source found in cache and linked to destination"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pkg/build/build.go:455
|
#: pkg/dl/dl.go:208
|
||||||
msgid "Downloading sources"
|
msgid "Source updated and linked to destination"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pkg/build/build.go:549
|
#: pkg/dl/dl.go:222
|
||||||
msgid "Installing dependencies"
|
msgid "Downloading source"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pkg/build/checker.go:43
|
#: pkg/dl/progress_tui.go:100
|
||||||
msgid ""
|
msgid "%s: done!\n"
|
||||||
"Your system's CPU architecture doesn't match this package. Do you want to "
|
|
||||||
"build anyway?"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: pkg/build/checker.go:67
|
#: pkg/dl/progress_tui.go:104
|
||||||
msgid "This package is already installed"
|
msgid "%s %s downloading at %s/s\n"
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: pkg/build/find_deps/alt_linux.go:35
|
|
||||||
msgid "Command not found on the system"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: pkg/build/find_deps/alt_linux.go:86
|
|
||||||
msgid "Provided dependency found"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: pkg/build/find_deps/alt_linux.go:93
|
|
||||||
msgid "Required dependency found"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: pkg/build/find_deps/empty.go:32
|
|
||||||
msgid "AutoProv is not implemented for this package format, so it's skipped"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: pkg/build/find_deps/empty.go:37
|
|
||||||
msgid "AutoReq is not implemented for this package format, so it's skipped"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: pkg/build/script_executor.go:241
|
|
||||||
msgid "Building package metadata"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: pkg/build/script_executor.go:372
|
|
||||||
msgid "Executing prepare()"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: pkg/build/script_executor.go:381
|
|
||||||
msgid "Executing build()"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: pkg/build/script_executor.go:410 pkg/build/script_executor.go:430
|
|
||||||
msgid "Executing %s()"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: pkg/repos/pull.go:77
|
|
||||||
msgid "Pulling repository"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: pkg/repos/pull.go:113
|
|
||||||
msgid "Repository up to date"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: pkg/repos/pull.go:204
|
|
||||||
msgid "Git repository does not appear to be a valid ALR repo"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: pkg/repos/pull.go:220
|
|
||||||
msgid ""
|
|
||||||
"ALR repo's minimum ALR version is greater than the current version. Try "
|
|
||||||
"updating ALR if something doesn't work."
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: refresh.go:30
|
#: refresh.go:30
|
||||||
msgid "Pull all repositories that have changed"
|
msgid "Pull all repositories that have changed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:39
|
#: repo.go:41
|
||||||
msgid "Manage repos"
|
msgid "Manage repos"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:51 repo.go:269
|
#: repo.go:55 repo.go:625
|
||||||
msgid "Remove an existing repository"
|
msgid "Remove an existing repository"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:53
|
#: repo.go:57 repo.go:521
|
||||||
msgid "<name>"
|
msgid "<name>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:83
|
#: repo.go:102 repo.go:465 repo.go:568
|
||||||
msgid "Repo \"%s\" does not exist"
|
msgid "Repo \"%s\" does not exist"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:90
|
#: repo.go:109
|
||||||
msgid "Error removing repo directory"
|
msgid "Error removing repo directory"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:94 repo.go:161 repo.go:219
|
#: repo.go:113 repo.go:180 repo.go:253 repo.go:316 repo.go:389 repo.go:504
|
||||||
|
#: repo.go:576
|
||||||
msgid "Error saving config"
|
msgid "Error saving config"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:113
|
#: repo.go:132
|
||||||
msgid "Error removing packages from database"
|
msgid "Error removing packages from database"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:124 repo.go:239
|
#: repo.go:143 repo.go:595
|
||||||
msgid "Add a new repository"
|
msgid "Add a new repository"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:125
|
#: repo.go:144 repo.go:270 repo.go:345 repo.go:402
|
||||||
msgid "<name> <url>"
|
msgid "<name> <url>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:150
|
#: repo.go:169
|
||||||
msgid "Repo \"%s\" already exists"
|
msgid "Repo \"%s\" already exists"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:187
|
#: repo.go:206
|
||||||
msgid "Set the reference of the repository"
|
msgid "Set the reference of the repository"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:188
|
#: repo.go:207
|
||||||
msgid "<name> <ref>"
|
msgid "<name> <ref>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:246
|
#: repo.go:269
|
||||||
|
msgid "Set the main url of the repository"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: repo.go:332
|
||||||
|
msgid "Manage mirrors of repos"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: repo.go:344
|
||||||
|
msgid "Add a mirror URL to repository"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: repo.go:401
|
||||||
|
msgid "Remove mirror from the repository"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: repo.go:420
|
||||||
|
msgid "Ignore if mirror does not exist"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: repo.go:425
|
||||||
|
msgid "Match partial URL (e.g., github.com instead of full URL)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: repo.go:490
|
||||||
|
msgid "No mirrors containing \"%s\" found in repo \"%s\""
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: repo.go:492
|
||||||
|
msgid "URL \"%s\" does not exist in repo \"%s\""
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: repo.go:508 repo.go:580
|
||||||
|
msgid "Removed %d mirrors from repo \"%s\"\n"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: repo.go:520
|
||||||
|
msgid "Remove all mirrors from the repository"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: repo.go:602
|
||||||
msgid "Name of the new repo"
|
msgid "Name of the new repo"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:252
|
#: repo.go:608
|
||||||
msgid "URL of the new repo"
|
msgid "URL of the new repo"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:276
|
#: repo.go:632
|
||||||
msgid "Name of the repo to be deleted"
|
msgid "Name of the repo to be deleted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -545,14 +594,14 @@ msgstr ""
|
|||||||
msgid "Error while executing search"
|
msgid "Error while executing search"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: upgrade.go:47
|
#: upgrade.go:48
|
||||||
msgid "Upgrade all installed packages"
|
msgid "Upgrade all installed packages"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: upgrade.go:105 upgrade.go:122
|
#: upgrade.go:106 upgrade.go:123
|
||||||
msgid "Error checking for updates"
|
msgid "Error checking for updates"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: upgrade.go:125
|
#: upgrade.go:126
|
||||||
msgid "There is nothing to do."
|
msgid "There is nothing to do."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -5,15 +5,15 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: unnamed project\n"
|
"Project-Id-Version: unnamed project\n"
|
||||||
"PO-Revision-Date: 2025-05-16 20:47+0300\n"
|
"PO-Revision-Date: 2025-06-19 18:54+0300\n"
|
||||||
"Last-Translator: Maxim Slipenko <maks1ms@alt-gnome.ru>\n"
|
"Last-Translator: Maxim Slipenko <maks1ms@alt-gnome.ru>\n"
|
||||||
"Language-Team: Russian\n"
|
"Language-Team: Russian\n"
|
||||||
"Language: ru\n"
|
"Language: ru\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
|
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
|
||||||
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||||
"X-Generator: Gtranslator 48.0\n"
|
"X-Generator: Gtranslator 48.0\n"
|
||||||
|
|
||||||
#: build.go:42
|
#: build.go:42
|
||||||
@ -65,11 +65,11 @@ msgstr "Ошибка при перемещении пакета"
|
|||||||
msgid "Done"
|
msgid "Done"
|
||||||
msgstr "Сделано"
|
msgstr "Сделано"
|
||||||
|
|
||||||
#: fix.go:38
|
#: fix.go:39
|
||||||
msgid "Attempt to fix problems with ALR"
|
msgid "Attempt to fix problems with ALR"
|
||||||
msgstr "Попытка устранить проблемы с ALR"
|
msgstr "Попытка устранить проблемы с ALR"
|
||||||
|
|
||||||
#: fix.go:59
|
#: fix.go:60
|
||||||
msgid "Clearing cache directory"
|
msgid "Clearing cache directory"
|
||||||
msgstr "Очистка каталога кэша"
|
msgstr "Очистка каталога кэша"
|
||||||
|
|
||||||
@ -81,15 +81,15 @@ msgstr "Невозможно открыть каталог кэша"
|
|||||||
msgid "Unable to read cache directory contents"
|
msgid "Unable to read cache directory contents"
|
||||||
msgstr "Невозможно прочитать содержимое каталога кэша"
|
msgstr "Невозможно прочитать содержимое каталога кэша"
|
||||||
|
|
||||||
#: fix.go:76
|
#: fix.go:82
|
||||||
msgid "Unable to remove cache item (%s)"
|
msgid "Unable to remove cache item (%s)"
|
||||||
msgstr "Невозможно удалить элемент кэша (%s)"
|
msgstr "Невозможно удалить элемент кэша (%s)"
|
||||||
|
|
||||||
#: fix.go:80
|
#: fix.go:86
|
||||||
msgid "Rebuilding cache"
|
msgid "Rebuilding cache"
|
||||||
msgstr "Восстановление кэша"
|
msgstr "Восстановление кэша"
|
||||||
|
|
||||||
#: fix.go:84
|
#: fix.go:90
|
||||||
msgid "Unable to create new cache directory"
|
msgid "Unable to create new cache directory"
|
||||||
msgstr "Не удалось создать новый каталог кэша"
|
msgstr "Не удалось создать новый каталог кэша"
|
||||||
|
|
||||||
@ -133,58 +133,124 @@ msgstr "Показывать всю информацию, не только дл
|
|||||||
msgid "Error getting packages"
|
msgid "Error getting packages"
|
||||||
msgstr "Ошибка при получении пакетов"
|
msgstr "Ошибка при получении пакетов"
|
||||||
|
|
||||||
#: info.go:76
|
#: info.go:83
|
||||||
msgid "Error iterating over packages"
|
|
||||||
msgstr "Ошибка при переборе пакетов"
|
|
||||||
|
|
||||||
#: info.go:90
|
|
||||||
msgid "Command info expected at least 1 argument, got %d"
|
msgid "Command info expected at least 1 argument, got %d"
|
||||||
msgstr "Для команды info ожидался хотя бы 1 аргумент, получено %d"
|
msgstr "Для команды info ожидался хотя бы 1 аргумент, получено %d"
|
||||||
|
|
||||||
#: info.go:110
|
#: info.go:104
|
||||||
msgid "Error finding packages"
|
msgid "Error finding packages"
|
||||||
msgstr "Ошибка при поиске пакетов"
|
msgstr "Ошибка при поиске пакетов"
|
||||||
|
|
||||||
#: info.go:124
|
#: info.go:118
|
||||||
msgid "Can't detect system language"
|
msgid "Can't detect system language"
|
||||||
msgstr "Ошибка при определении языка системы"
|
msgstr "Ошибка при определении языка системы"
|
||||||
|
|
||||||
#: info.go:141
|
#: info.go:134
|
||||||
msgid "Error resolving overrides"
|
msgid "Error resolving overrides"
|
||||||
msgstr "Ошибка устранения переорпеделений"
|
msgstr "Ошибка устранения переорпеделений"
|
||||||
|
|
||||||
#: info.go:149 info.go:154
|
#: info.go:143
|
||||||
msgid "Error encoding script variables"
|
msgid "Error encoding script variables"
|
||||||
msgstr "Ошибка кодирования переменных скрита"
|
msgstr "Ошибка кодирования переменных скрита"
|
||||||
|
|
||||||
#: install.go:40
|
#: install.go:39
|
||||||
msgid "Install a new package"
|
msgid "Install a new package"
|
||||||
msgstr "Установить новый пакет"
|
msgstr "Установить новый пакет"
|
||||||
|
|
||||||
#: install.go:52
|
#: install.go:51
|
||||||
msgid "Command install expected at least 1 argument, got %d"
|
msgid "Command install expected at least 1 argument, got %d"
|
||||||
msgstr "Для команды install ожидался хотя бы 1 аргумент, получено %d"
|
msgstr "Для команды install ожидался хотя бы 1 аргумент, получено %d"
|
||||||
|
|
||||||
#: install.go:114
|
#: install.go:113
|
||||||
msgid "Error when installing the package"
|
msgid "Error when installing the package"
|
||||||
msgstr "Ошибка при установке пакета"
|
msgstr "Ошибка при установке пакета"
|
||||||
|
|
||||||
#: install.go:159
|
#: install.go:151
|
||||||
msgid "Remove an installed package"
|
msgid "Remove an installed package"
|
||||||
msgstr "Удалить установленный пакет"
|
msgstr "Удалить установленный пакет"
|
||||||
|
|
||||||
#: install.go:178
|
#: install.go:170
|
||||||
msgid "Error listing installed packages"
|
msgid "Error listing installed packages"
|
||||||
msgstr "Ошибка при составлении списка установленных пакетов"
|
msgstr "Ошибка при составлении списка установленных пакетов"
|
||||||
|
|
||||||
#: install.go:215
|
#: install.go:199
|
||||||
msgid "Command remove expected at least 1 argument, got %d"
|
msgid "Command remove expected at least 1 argument, got %d"
|
||||||
msgstr "Для команды remove ожидался хотя бы 1 аргумент, получено %d"
|
msgstr "Для команды remove ожидался хотя бы 1 аргумент, получено %d"
|
||||||
|
|
||||||
#: install.go:230
|
#: install.go:214
|
||||||
msgid "Error removing packages"
|
msgid "Error removing packages"
|
||||||
msgstr "Ошибка при удалении пакетов"
|
msgstr "Ошибка при удалении пакетов"
|
||||||
|
|
||||||
|
#: internal/build/build.go:376
|
||||||
|
msgid "Building package"
|
||||||
|
msgstr "Сборка пакета"
|
||||||
|
|
||||||
|
#: internal/build/build.go:405
|
||||||
|
msgid "The checksums array must be the same length as sources"
|
||||||
|
msgstr "Массив контрольных сумм должен быть той же длины, что и источники"
|
||||||
|
|
||||||
|
#: internal/build/build.go:447
|
||||||
|
msgid "Downloading sources"
|
||||||
|
msgstr "Скачивание источников"
|
||||||
|
|
||||||
|
#: internal/build/build.go:539
|
||||||
|
msgid "Installing dependencies"
|
||||||
|
msgstr "Установка зависимостей"
|
||||||
|
|
||||||
|
#: internal/build/checker.go:43
|
||||||
|
msgid ""
|
||||||
|
"Your system's CPU architecture doesn't match this package. Do you want to "
|
||||||
|
"build anyway?"
|
||||||
|
msgstr ""
|
||||||
|
"Архитектура процессора вашей системы не соответствует этому пакету. Вы все "
|
||||||
|
"равно хотите выполнить сборку?"
|
||||||
|
|
||||||
|
#: internal/build/checker.go:67
|
||||||
|
msgid "This package is already installed"
|
||||||
|
msgstr "Этот пакет уже установлен"
|
||||||
|
|
||||||
|
#: internal/build/find_deps/alt_linux.go:35
|
||||||
|
msgid "Command not found on the system"
|
||||||
|
msgstr "Команда не найдена в системе"
|
||||||
|
|
||||||
|
#: internal/build/find_deps/alt_linux.go:86
|
||||||
|
msgid "Provided dependency found"
|
||||||
|
msgstr "Найденная предоставленная зависимость"
|
||||||
|
|
||||||
|
#: internal/build/find_deps/alt_linux.go:93
|
||||||
|
msgid "Required dependency found"
|
||||||
|
msgstr "Найдена требуемая зависимость"
|
||||||
|
|
||||||
|
#: internal/build/find_deps/empty.go:32
|
||||||
|
msgid "AutoProv is not implemented for this package format, so it's skipped"
|
||||||
|
msgstr ""
|
||||||
|
"AutoProv не реализовано для этого формата пакета, поэтому будет пропущено"
|
||||||
|
|
||||||
|
#: internal/build/find_deps/empty.go:37
|
||||||
|
msgid "AutoReq is not implemented for this package format, so it's skipped"
|
||||||
|
msgstr ""
|
||||||
|
"AutoReq не реализовано для этого формата пакета, поэтому будет пропущено"
|
||||||
|
|
||||||
|
#: internal/build/firejail.go:144
|
||||||
|
msgid "Applying FireJail integration"
|
||||||
|
msgstr "Применение интеграции FireJail"
|
||||||
|
|
||||||
|
#: internal/build/script_executor.go:145
|
||||||
|
msgid "Building package metadata"
|
||||||
|
msgstr "Сборка метаданных пакета"
|
||||||
|
|
||||||
|
#: internal/build/script_executor.go:285
|
||||||
|
msgid "Executing prepare()"
|
||||||
|
msgstr "Выполнение prepare()"
|
||||||
|
|
||||||
|
#: internal/build/script_executor.go:294
|
||||||
|
msgid "Executing build()"
|
||||||
|
msgstr "Выполнение build()"
|
||||||
|
|
||||||
|
#: internal/build/script_executor.go:323 internal/build/script_executor.go:343
|
||||||
|
msgid "Executing %s()"
|
||||||
|
msgstr "Выполнение %s()"
|
||||||
|
|
||||||
#: internal/cliutils/app_builder/builder.go:75
|
#: internal/cliutils/app_builder/builder.go:75
|
||||||
msgid "Error loading config"
|
msgid "Error loading config"
|
||||||
msgstr "Ошибка при загрузке"
|
msgstr "Ошибка при загрузке"
|
||||||
@ -193,15 +259,15 @@ msgstr "Ошибка при загрузке"
|
|||||||
msgid "Error initialization database"
|
msgid "Error initialization database"
|
||||||
msgstr "Ошибка инициализации базы данных"
|
msgstr "Ошибка инициализации базы данных"
|
||||||
|
|
||||||
#: internal/cliutils/app_builder/builder.go:135
|
#: internal/cliutils/app_builder/builder.go:142
|
||||||
msgid "Error pulling repositories"
|
msgid "Error pulling repositories"
|
||||||
msgstr "Ошибка при извлечении репозиториев"
|
msgstr "Ошибка при извлечении репозиториев"
|
||||||
|
|
||||||
#: internal/cliutils/app_builder/builder.go:152
|
#: internal/cliutils/app_builder/builder.go:159
|
||||||
msgid "Error parsing os release"
|
msgid "Error parsing os release"
|
||||||
msgstr "Ошибка при разборе файла выпуска операционной системы"
|
msgstr "Ошибка при разборе файла выпуска операционной системы"
|
||||||
|
|
||||||
#: internal/cliutils/app_builder/builder.go:165
|
#: internal/cliutils/app_builder/builder.go:172
|
||||||
msgid "Unable to detect a supported package manager on the system"
|
msgid "Unable to detect a supported package manager on the system"
|
||||||
msgstr "Не удалось обнаружить поддерживаемый менеджер пакетов в системе"
|
msgstr "Не удалось обнаружить поддерживаемый менеджер пакетов в системе"
|
||||||
|
|
||||||
@ -290,47 +356,51 @@ msgid ""
|
|||||||
"This command is deprecated and would be removed in the future, use \"%s\" "
|
"This command is deprecated and would be removed in the future, use \"%s\" "
|
||||||
"instead!"
|
"instead!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Эта команда устарела и будет удалена в будущем, используйте вместо нее "
|
"Эта команда устарела и будет удалена в будущем, используйте вместо нее \"%s"
|
||||||
"\"%s\"!"
|
"\"!"
|
||||||
|
|
||||||
#: internal/db/db.go:137
|
#: internal/db/db.go:76
|
||||||
msgid "Database version mismatch; resetting"
|
msgid "Database version mismatch; resetting"
|
||||||
msgstr "Несоответствие версий базы данных; сброс настроек"
|
msgstr "Несоответствие версий базы данных; сброс настроек"
|
||||||
|
|
||||||
#: internal/db/db.go:144
|
#: internal/db/db.go:82
|
||||||
msgid ""
|
msgid ""
|
||||||
"Database version does not exist. Run alr fix if something isn't working."
|
"Database version does not exist. Run alr fix if something isn't working."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Версия базы данных не существует. Запустите alr fix, если что-то не работает."
|
"Версия базы данных не существует. Запустите alr fix, если что-то не работает."
|
||||||
|
|
||||||
#: internal/dl/dl.go:170
|
|
||||||
msgid "Source can be updated, updating if required"
|
|
||||||
msgstr "Исходный код можно обновлять, обновляя при необходимости"
|
|
||||||
|
|
||||||
#: internal/dl/dl.go:201
|
|
||||||
msgid "Source found in cache and linked to destination"
|
|
||||||
msgstr "Источник найден в кэше и связан с пунктом назначения"
|
|
||||||
|
|
||||||
#: internal/dl/dl.go:208
|
|
||||||
msgid "Source updated and linked to destination"
|
|
||||||
msgstr "Источник обновлён и связан с пунктом назначения"
|
|
||||||
|
|
||||||
#: internal/dl/dl.go:222
|
|
||||||
msgid "Downloading source"
|
|
||||||
msgstr "Скачивание источника"
|
|
||||||
|
|
||||||
#: internal/dl/progress_tui.go:100
|
|
||||||
msgid "%s: done!\n"
|
|
||||||
msgstr "%s: выполнено!\n"
|
|
||||||
|
|
||||||
#: internal/dl/progress_tui.go:104
|
|
||||||
msgid "%s %s downloading at %s/s\n"
|
|
||||||
msgstr "%s %s загружается — %s/с\n"
|
|
||||||
|
|
||||||
#: internal/logger/log.go:41
|
#: internal/logger/log.go:41
|
||||||
msgid "ERROR"
|
msgid "ERROR"
|
||||||
msgstr "ОШИБКА"
|
msgstr "ОШИБКА"
|
||||||
|
|
||||||
|
#: internal/repos/pull.go:88
|
||||||
|
msgid "Trying mirror"
|
||||||
|
msgstr "Пробую зеркало"
|
||||||
|
|
||||||
|
#: internal/repos/pull.go:94
|
||||||
|
msgid "Failed to pull from URL"
|
||||||
|
msgstr "Не удалось извлечь из URL"
|
||||||
|
|
||||||
|
#: internal/repos/pull.go:158
|
||||||
|
msgid "Pulling repository"
|
||||||
|
msgstr "Скачивание репозитория"
|
||||||
|
|
||||||
|
#: internal/repos/pull.go:195
|
||||||
|
msgid "Repository up to date"
|
||||||
|
msgstr "Репозиторий уже обновлён"
|
||||||
|
|
||||||
|
#: internal/repos/pull.go:230
|
||||||
|
msgid "Git repository does not appear to be a valid ALR repo"
|
||||||
|
msgstr "Репозиторий Git не поддерживается репозиторием ALR"
|
||||||
|
|
||||||
|
#: internal/repos/pull.go:246
|
||||||
|
msgid ""
|
||||||
|
"ALR repo's minimum ALR version is greater than the current version. Try "
|
||||||
|
"updating ALR if something doesn't work."
|
||||||
|
msgstr ""
|
||||||
|
"Минимальная версия ALR для ALR-репозитория выше текущей версии. Попробуйте "
|
||||||
|
"обновить ALR, если что-то не работает."
|
||||||
|
|
||||||
#: internal/utils/cmd.go:97
|
#: internal/utils/cmd.go:97
|
||||||
msgid "Error on dropping capabilities"
|
msgid "Error on dropping capabilities"
|
||||||
msgstr "Ошибка при понижении привилегий"
|
msgstr "Ошибка при понижении привилегий"
|
||||||
@ -359,11 +429,11 @@ msgstr "Ошибка при получении пакетов для обнов
|
|||||||
msgid "No packages for upgrade"
|
msgid "No packages for upgrade"
|
||||||
msgstr "Нет пакетов к обновлению"
|
msgstr "Нет пакетов к обновлению"
|
||||||
|
|
||||||
#: list.go:102 list.go:187
|
#: list.go:102 list.go:184
|
||||||
msgid "Error parsing format template"
|
msgid "Error parsing format template"
|
||||||
msgstr "Ошибка при разборе шаблона"
|
msgstr "Ошибка при разборе шаблона"
|
||||||
|
|
||||||
#: list.go:108 list.go:191
|
#: list.go:108 list.go:188
|
||||||
msgid "Error executing template"
|
msgid "Error executing template"
|
||||||
msgstr "Ошибка при выполнении шаблона"
|
msgstr "Ошибка при выполнении шаблона"
|
||||||
|
|
||||||
@ -387,153 +457,132 @@ msgstr "Показать справку"
|
|||||||
msgid "Error while running app"
|
msgid "Error while running app"
|
||||||
msgstr "Ошибка при запуске приложения"
|
msgstr "Ошибка при запуске приложения"
|
||||||
|
|
||||||
#: pkg/build/build.go:395
|
#: pkg/dl/dl.go:170
|
||||||
msgid "Building package"
|
msgid "Source can be updated, updating if required"
|
||||||
msgstr "Сборка пакета"
|
msgstr "Исходный код можно обновлять, обновляя при необходимости"
|
||||||
|
|
||||||
#: pkg/build/build.go:424
|
#: pkg/dl/dl.go:201
|
||||||
msgid "The checksums array must be the same length as sources"
|
msgid "Source found in cache and linked to destination"
|
||||||
msgstr "Массив контрольных сумм должен быть той же длины, что и источники"
|
msgstr "Источник найден в кэше и связан с пунктом назначения"
|
||||||
|
|
||||||
#: pkg/build/build.go:455
|
#: pkg/dl/dl.go:208
|
||||||
msgid "Downloading sources"
|
msgid "Source updated and linked to destination"
|
||||||
msgstr "Скачивание источников"
|
msgstr "Источник обновлён и связан с пунктом назначения"
|
||||||
|
|
||||||
#: pkg/build/build.go:549
|
#: pkg/dl/dl.go:222
|
||||||
msgid "Installing dependencies"
|
msgid "Downloading source"
|
||||||
msgstr "Установка зависимостей"
|
msgstr "Скачивание источника"
|
||||||
|
|
||||||
#: pkg/build/checker.go:43
|
#: pkg/dl/progress_tui.go:100
|
||||||
msgid ""
|
msgid "%s: done!\n"
|
||||||
"Your system's CPU architecture doesn't match this package. Do you want to "
|
msgstr "%s: выполнено!\n"
|
||||||
"build anyway?"
|
|
||||||
msgstr ""
|
|
||||||
"Архитектура процессора вашей системы не соответствует этому пакету. Вы все "
|
|
||||||
"равно хотите выполнить сборку?"
|
|
||||||
|
|
||||||
#: pkg/build/checker.go:67
|
#: pkg/dl/progress_tui.go:104
|
||||||
msgid "This package is already installed"
|
msgid "%s %s downloading at %s/s\n"
|
||||||
msgstr "Этот пакет уже установлен"
|
msgstr "%s %s загружается — %s/с\n"
|
||||||
|
|
||||||
#: pkg/build/find_deps/alt_linux.go:35
|
|
||||||
msgid "Command not found on the system"
|
|
||||||
msgstr "Команда не найдена в системе"
|
|
||||||
|
|
||||||
#: pkg/build/find_deps/alt_linux.go:86
|
|
||||||
msgid "Provided dependency found"
|
|
||||||
msgstr "Найденная предоставленная зависимость"
|
|
||||||
|
|
||||||
#: pkg/build/find_deps/alt_linux.go:93
|
|
||||||
msgid "Required dependency found"
|
|
||||||
msgstr "Найдена требуемая зависимость"
|
|
||||||
|
|
||||||
#: pkg/build/find_deps/empty.go:32
|
|
||||||
msgid "AutoProv is not implemented for this package format, so it's skipped"
|
|
||||||
msgstr ""
|
|
||||||
"AutoProv не реализовано для этого формата пакета, поэтому будет пропущено"
|
|
||||||
|
|
||||||
#: pkg/build/find_deps/empty.go:37
|
|
||||||
msgid "AutoReq is not implemented for this package format, so it's skipped"
|
|
||||||
msgstr ""
|
|
||||||
"AutoReq не реализовано для этого формата пакета, поэтому будет пропущено"
|
|
||||||
|
|
||||||
#: pkg/build/script_executor.go:241
|
|
||||||
msgid "Building package metadata"
|
|
||||||
msgstr "Сборка метаданных пакета"
|
|
||||||
|
|
||||||
#: pkg/build/script_executor.go:372
|
|
||||||
msgid "Executing prepare()"
|
|
||||||
msgstr "Выполнение prepare()"
|
|
||||||
|
|
||||||
#: pkg/build/script_executor.go:381
|
|
||||||
msgid "Executing build()"
|
|
||||||
msgstr "Выполнение build()"
|
|
||||||
|
|
||||||
#: pkg/build/script_executor.go:410 pkg/build/script_executor.go:430
|
|
||||||
msgid "Executing %s()"
|
|
||||||
msgstr "Выполнение %s()"
|
|
||||||
|
|
||||||
#: pkg/repos/pull.go:77
|
|
||||||
msgid "Pulling repository"
|
|
||||||
msgstr "Скачивание репозитория"
|
|
||||||
|
|
||||||
#: pkg/repos/pull.go:113
|
|
||||||
msgid "Repository up to date"
|
|
||||||
msgstr "Репозиторий уже обновлён"
|
|
||||||
|
|
||||||
#: pkg/repos/pull.go:204
|
|
||||||
msgid "Git repository does not appear to be a valid ALR repo"
|
|
||||||
msgstr "Репозиторий Git не поддерживается репозиторием ALR"
|
|
||||||
|
|
||||||
#: pkg/repos/pull.go:220
|
|
||||||
msgid ""
|
|
||||||
"ALR repo's minimum ALR version is greater than the current version. Try "
|
|
||||||
"updating ALR if something doesn't work."
|
|
||||||
msgstr ""
|
|
||||||
"Минимальная версия ALR для ALR-репозитория выше текущей версии. Попробуйте "
|
|
||||||
"обновить ALR, если что-то не работает."
|
|
||||||
|
|
||||||
#: refresh.go:30
|
#: refresh.go:30
|
||||||
msgid "Pull all repositories that have changed"
|
msgid "Pull all repositories that have changed"
|
||||||
msgstr "Скачать все изменённые репозитории"
|
msgstr "Скачать все изменённые репозитории"
|
||||||
|
|
||||||
#: repo.go:39
|
#: repo.go:41
|
||||||
msgid "Manage repos"
|
msgid "Manage repos"
|
||||||
msgstr "Управление репозиториями"
|
msgstr "Управление репозиториями"
|
||||||
|
|
||||||
#: repo.go:51 repo.go:269
|
#: repo.go:55 repo.go:625
|
||||||
msgid "Remove an existing repository"
|
msgid "Remove an existing repository"
|
||||||
msgstr "Удалить существующий репозиторий"
|
msgstr "Удалить существующий репозиторий"
|
||||||
|
|
||||||
#: repo.go:53
|
#: repo.go:57 repo.go:521
|
||||||
msgid "<name>"
|
msgid "<name>"
|
||||||
msgstr "<имя>"
|
msgstr "<имя>"
|
||||||
|
|
||||||
#: repo.go:83
|
#: repo.go:102 repo.go:465 repo.go:568
|
||||||
msgid "Repo \"%s\" does not exist"
|
msgid "Repo \"%s\" does not exist"
|
||||||
msgstr "Репозитория \"%s\" не существует"
|
msgstr "Репозитория \"%s\" не существует"
|
||||||
|
|
||||||
#: repo.go:90
|
#: repo.go:109
|
||||||
msgid "Error removing repo directory"
|
msgid "Error removing repo directory"
|
||||||
msgstr "Ошибка при удалении каталога репозитория"
|
msgstr "Ошибка при удалении каталога репозитория"
|
||||||
|
|
||||||
#: repo.go:94 repo.go:161 repo.go:219
|
#: repo.go:113 repo.go:180 repo.go:253 repo.go:316 repo.go:389 repo.go:504
|
||||||
|
#: repo.go:576
|
||||||
msgid "Error saving config"
|
msgid "Error saving config"
|
||||||
msgstr "Ошибка при сохранении конфигурации"
|
msgstr "Ошибка при сохранении конфигурации"
|
||||||
|
|
||||||
#: repo.go:113
|
#: repo.go:132
|
||||||
msgid "Error removing packages from database"
|
msgid "Error removing packages from database"
|
||||||
msgstr "Ошибка при удалении пакетов из базы данных"
|
msgstr "Ошибка при удалении пакетов из базы данных"
|
||||||
|
|
||||||
#: repo.go:124 repo.go:239
|
#: repo.go:143 repo.go:595
|
||||||
msgid "Add a new repository"
|
msgid "Add a new repository"
|
||||||
msgstr "Добавить новый репозиторий"
|
msgstr "Добавить новый репозиторий"
|
||||||
|
|
||||||
#: repo.go:125
|
#: repo.go:144 repo.go:270 repo.go:345 repo.go:402
|
||||||
msgid "<name> <url>"
|
msgid "<name> <url>"
|
||||||
msgstr "<имя> <url>"
|
msgstr "<имя> <url>"
|
||||||
|
|
||||||
#: repo.go:150
|
#: repo.go:169
|
||||||
msgid "Repo \"%s\" already exists"
|
msgid "Repo \"%s\" already exists"
|
||||||
msgstr "Репозиторий \"%s\" уже существует"
|
msgstr "Репозиторий \"%s\" уже существует"
|
||||||
|
|
||||||
#: repo.go:187
|
#: repo.go:206
|
||||||
msgid "Set the reference of the repository"
|
msgid "Set the reference of the repository"
|
||||||
msgstr "Установить ссылку на версию репозитория"
|
msgstr "Установить ссылку на версию репозитория"
|
||||||
|
|
||||||
#: repo.go:188
|
#: repo.go:207
|
||||||
msgid "<name> <ref>"
|
msgid "<name> <ref>"
|
||||||
msgstr "<имя> <ссылка_на_версию>"
|
msgstr "<имя> <ссылка_на_версию>"
|
||||||
|
|
||||||
#: repo.go:246
|
#: repo.go:269
|
||||||
|
msgid "Set the main url of the repository"
|
||||||
|
msgstr "Установить главный URL репозитория"
|
||||||
|
|
||||||
|
#: repo.go:332
|
||||||
|
msgid "Manage mirrors of repos"
|
||||||
|
msgstr "Управление зеркалами репозитория"
|
||||||
|
|
||||||
|
#: repo.go:344
|
||||||
|
msgid "Add a mirror URL to repository"
|
||||||
|
msgstr "Добавить зеркало репозитория"
|
||||||
|
|
||||||
|
#: repo.go:401
|
||||||
|
msgid "Remove mirror from the repository"
|
||||||
|
msgstr "Удалить зеркало из репозитория"
|
||||||
|
|
||||||
|
#: repo.go:420
|
||||||
|
msgid "Ignore if mirror does not exist"
|
||||||
|
msgstr "Игнорировать, если зеркала не существует"
|
||||||
|
|
||||||
|
#: repo.go:425
|
||||||
|
msgid "Match partial URL (e.g., github.com instead of full URL)"
|
||||||
|
msgstr "Соответствует частичному URL (например, github.com вместо полного URL)"
|
||||||
|
|
||||||
|
#: repo.go:490
|
||||||
|
msgid "No mirrors containing \"%s\" found in repo \"%s\""
|
||||||
|
msgstr "В репозитории \"%s\" не найдено зеркал, содержащих \"%s\""
|
||||||
|
|
||||||
|
#: repo.go:492
|
||||||
|
msgid "URL \"%s\" does not exist in repo \"%s\""
|
||||||
|
msgstr "URL \"%s\" не существует в репозитории \"%s\""
|
||||||
|
|
||||||
|
#: repo.go:508 repo.go:580
|
||||||
|
msgid "Removed %d mirrors from repo \"%s\"\n"
|
||||||
|
msgstr "Удалены зеркала %d из репозитория \"%s\"\n"
|
||||||
|
|
||||||
|
#: repo.go:520
|
||||||
|
msgid "Remove all mirrors from the repository"
|
||||||
|
msgstr "Удалить все зеркала из репозитория"
|
||||||
|
|
||||||
|
#: repo.go:602
|
||||||
msgid "Name of the new repo"
|
msgid "Name of the new repo"
|
||||||
msgstr "Название нового репозитория"
|
msgstr "Название нового репозитория"
|
||||||
|
|
||||||
#: repo.go:252
|
#: repo.go:608
|
||||||
msgid "URL of the new repo"
|
msgid "URL of the new repo"
|
||||||
msgstr "URL-адрес нового репозитория"
|
msgstr "URL-адрес нового репозитория"
|
||||||
|
|
||||||
#: repo.go:276
|
#: repo.go:632
|
||||||
msgid "Name of the repo to be deleted"
|
msgid "Name of the repo to be deleted"
|
||||||
msgstr "Название репозитория удалён"
|
msgstr "Название репозитория удалён"
|
||||||
|
|
||||||
@ -561,18 +610,25 @@ msgstr "Иcкать по provides"
|
|||||||
msgid "Error while executing search"
|
msgid "Error while executing search"
|
||||||
msgstr "Ошибка при выполнении поиска"
|
msgstr "Ошибка при выполнении поиска"
|
||||||
|
|
||||||
#: upgrade.go:47
|
#: upgrade.go:48
|
||||||
msgid "Upgrade all installed packages"
|
msgid "Upgrade all installed packages"
|
||||||
msgstr "Обновить все установленные пакеты"
|
msgstr "Обновить все установленные пакеты"
|
||||||
|
|
||||||
#: upgrade.go:105 upgrade.go:122
|
#: upgrade.go:106 upgrade.go:123
|
||||||
msgid "Error checking for updates"
|
msgid "Error checking for updates"
|
||||||
msgstr "Ошибка при проверке обновлений"
|
msgstr "Ошибка при проверке обновлений"
|
||||||
|
|
||||||
#: upgrade.go:125
|
#: upgrade.go:126
|
||||||
msgid "There is nothing to do."
|
msgid "There is nothing to do."
|
||||||
msgstr "Здесь нечего делать."
|
msgstr "Здесь нечего делать."
|
||||||
|
|
||||||
|
#, fuzzy
|
||||||
|
#~ msgid "Failed to clear contents of cache directory"
|
||||||
|
#~ msgstr "Не удалось создать каталог кэша репозитория"
|
||||||
|
|
||||||
|
#~ msgid "Error iterating over packages"
|
||||||
|
#~ msgstr "Ошибка при переборе пакетов"
|
||||||
|
|
||||||
#~ msgid "Error pulling repos"
|
#~ msgid "Error pulling repos"
|
||||||
#~ msgstr "Ошибка при извлечении репозиториев"
|
#~ msgstr "Ошибка при извлечении репозиториев"
|
||||||
|
|
||||||
@ -588,9 +644,6 @@ msgstr "Здесь нечего делать."
|
|||||||
#~ msgid "Unable to create config directory"
|
#~ msgid "Unable to create config directory"
|
||||||
#~ msgstr "Не удалось создать каталог конфигурации ALR"
|
#~ msgstr "Не удалось создать каталог конфигурации ALR"
|
||||||
|
|
||||||
#~ msgid "Unable to create repo cache directory"
|
|
||||||
#~ msgstr "Не удалось создать каталог кэша репозитория"
|
|
||||||
|
|
||||||
#~ msgid "Unable to create package cache directory"
|
#~ msgid "Unable to create package cache directory"
|
||||||
#~ msgstr "Не удалось создать каталог кэша пакетов"
|
#~ msgstr "Не удалось создать каталог кэша пакетов"
|
||||||
|
|
||||||
|
15
list.go
15
list.go
@ -29,12 +29,12 @@ import (
|
|||||||
"github.com/leonelquinteros/gotext"
|
"github.com/leonelquinteros/gotext"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/build"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
||||||
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
|
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
|
||||||
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func ListCmd() *cli.Command {
|
func ListCmd() *cli.Command {
|
||||||
@ -69,9 +69,9 @@ func ListCmd() *cli.Command {
|
|||||||
WithConfig().
|
WithConfig().
|
||||||
WithDB().
|
WithDB().
|
||||||
WithManager().
|
WithManager().
|
||||||
|
WithDistroInfo().
|
||||||
// autoPull only
|
// autoPull only
|
||||||
WithRepos().
|
WithRepos().
|
||||||
WithDistroInfo().
|
|
||||||
Build()
|
Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -125,7 +125,6 @@ func ListCmd() *cli.Command {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err)
|
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err)
|
||||||
}
|
}
|
||||||
defer result.Close()
|
|
||||||
|
|
||||||
installedAlrPackages := map[string]string{}
|
installedAlrPackages := map[string]string{}
|
||||||
if c.Bool("installed") {
|
if c.Bool("installed") {
|
||||||
@ -150,9 +149,7 @@ func ListCmd() *cli.Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for result.Next() {
|
for _, pkg := range result {
|
||||||
var pkg database.Package
|
|
||||||
err := result.StructScan(&pkg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.Exit(err, 1)
|
return cli.Exit(err, 1)
|
||||||
}
|
}
|
||||||
@ -162,7 +159,7 @@ func ListCmd() *cli.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type packageInfo struct {
|
type packageInfo struct {
|
||||||
Package *database.Package
|
Package *alrsh.Package
|
||||||
Version string
|
Version string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
main.go
2
main.go
@ -33,8 +33,8 @@ import (
|
|||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/translations"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/translations"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
|
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
|
||||||
)
|
)
|
||||||
|
@ -52,9 +52,9 @@
|
|||||||
./internal/translations/files/lure.en.toml
|
./internal/translations/files/lure.en.toml
|
||||||
./internal/translations/files/lure.ru.toml
|
./internal/translations/files/lure.ru.toml
|
||||||
./internal/translations/translations.go
|
./internal/translations/translations.go
|
||||||
./internal/types/build.go
|
./pkg/types/build.go
|
||||||
./internal/types/config.go
|
./pkg/types/config.go
|
||||||
./internal/types/repo.go
|
./pkg/types/repo.go
|
||||||
./list.go
|
./list.go
|
||||||
./main.go
|
./main.go
|
||||||
./pkg/build/build.go
|
./pkg/build/build.go
|
||||||
|
205
pkg/alrsh/alrsh.go
Normal file
205
pkg/alrsh/alrsh.go
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
// 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 alrsh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"mvdan.cc/sh/v3/expand"
|
||||||
|
"mvdan.cc/sh/v3/interp"
|
||||||
|
"mvdan.cc/sh/v3/syntax"
|
||||||
|
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/helpers"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ScriptFile struct {
|
||||||
|
file *syntax.File
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func createBuildEnvVars(info *distro.OSRelease, dirs types.Directories) []string {
|
||||||
|
env := os.Environ()
|
||||||
|
|
||||||
|
env = append(
|
||||||
|
env,
|
||||||
|
"DISTRO_NAME="+info.Name,
|
||||||
|
"DISTRO_PRETTY_NAME="+info.PrettyName,
|
||||||
|
"DISTRO_ID="+info.ID,
|
||||||
|
"DISTRO_VERSION_ID="+info.VersionID,
|
||||||
|
"DISTRO_ID_LIKE="+strings.Join(info.Like, " "),
|
||||||
|
"ARCH="+cpu.Arch(),
|
||||||
|
"NCPU="+strconv.Itoa(runtime.NumCPU()),
|
||||||
|
)
|
||||||
|
|
||||||
|
if dirs.ScriptDir != "" {
|
||||||
|
env = append(env, "scriptdir="+dirs.ScriptDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
if dirs.PkgDir != "" {
|
||||||
|
env = append(env, "pkgdir="+dirs.PkgDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
if dirs.SrcDir != "" {
|
||||||
|
env = append(env, "srcdir="+dirs.SrcDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
return env
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ScriptFile) ParseBuildVars(ctx context.Context, info *distro.OSRelease, packages []string) (string, []*Package, error) {
|
||||||
|
runner, err := s.createRunner(info)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := runScript(ctx, runner, s.file); err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dec := newDecoder(info, runner)
|
||||||
|
|
||||||
|
pkgNames, err := ParseNames(dec)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pkgNames.Names) == 0 {
|
||||||
|
return "", nil, errors.New("package name is missing")
|
||||||
|
}
|
||||||
|
|
||||||
|
targetPackages := packages
|
||||||
|
if len(targetPackages) == 0 {
|
||||||
|
targetPackages = pkgNames.Names
|
||||||
|
}
|
||||||
|
|
||||||
|
varsOfPackages, err := s.createPackagesForBuildVars(ctx, dec, pkgNames, targetPackages)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
baseName := pkgNames.BasePkgName
|
||||||
|
if len(pkgNames.Names) == 1 {
|
||||||
|
baseName = pkgNames.Names[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseName, varsOfPackages, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ScriptFile) createRunner(info *distro.OSRelease) (*interp.Runner, error) {
|
||||||
|
scriptDir := filepath.Dir(s.path)
|
||||||
|
env := createBuildEnvVars(info, types.Directories{ScriptDir: scriptDir})
|
||||||
|
|
||||||
|
return interp.New(
|
||||||
|
interp.Env(expand.ListEnviron(env...)),
|
||||||
|
interp.StdIO(os.Stdin, os.Stderr, os.Stderr),
|
||||||
|
interp.ExecHandler(helpers.Restricted.ExecHandler(handlers.NopExec)),
|
||||||
|
interp.ReadDirHandler2(handlers.RestrictedReadDir(scriptDir)),
|
||||||
|
interp.StatHandler(handlers.RestrictedStat(scriptDir)),
|
||||||
|
interp.OpenHandler(handlers.RestrictedOpen(scriptDir)),
|
||||||
|
interp.Dir(scriptDir),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ScriptFile) createPackagesForBuildVars(
|
||||||
|
ctx context.Context,
|
||||||
|
dec *decoder.Decoder,
|
||||||
|
pkgNames *PackageNames,
|
||||||
|
targetPackages []string,
|
||||||
|
) ([]*Package, error) {
|
||||||
|
var varsOfPackages []*Package
|
||||||
|
|
||||||
|
if len(pkgNames.Names) == 1 {
|
||||||
|
var pkg Package
|
||||||
|
pkg.Name = pkgNames.Names[0]
|
||||||
|
if err := dec.DecodeVars(&pkg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
varsOfPackages = append(varsOfPackages, &pkg)
|
||||||
|
return varsOfPackages, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pkgName := range targetPackages {
|
||||||
|
pkg, err := s.createPackageFromMeta(ctx, dec, pkgName, pkgNames.BasePkgName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
varsOfPackages = append(varsOfPackages, pkg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return varsOfPackages, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ScriptFile) createPackageFromMeta(
|
||||||
|
ctx context.Context,
|
||||||
|
dec *decoder.Decoder,
|
||||||
|
pkgName, basePkgName string,
|
||||||
|
) (*Package, error) {
|
||||||
|
funcName := fmt.Sprintf("meta_%s", pkgName)
|
||||||
|
meta, ok := dec.GetFuncWithSubshell(funcName)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("func %s is missing", funcName)
|
||||||
|
}
|
||||||
|
|
||||||
|
metaRunner, err := meta(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
metaDecoder := decoder.New(&distro.OSRelease{}, metaRunner)
|
||||||
|
|
||||||
|
var vars Package
|
||||||
|
if err := metaDecoder.DecodeVars(&vars); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vars.Name = pkgName
|
||||||
|
vars.BasePkgName = basePkgName
|
||||||
|
|
||||||
|
return &vars, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runScript(ctx context.Context, runner *interp.Runner, fl *syntax.File) error {
|
||||||
|
runner.Reset()
|
||||||
|
return runner.Run(ctx, fl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDecoder(info *distro.OSRelease, runner *interp.Runner) *decoder.Decoder {
|
||||||
|
d := decoder.New(info, runner)
|
||||||
|
// d.Overrides = false
|
||||||
|
// d.LikeDistros = false
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ScriptFile) Path() string {
|
||||||
|
return a.path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ScriptFile) File() *syntax.File {
|
||||||
|
return a.file
|
||||||
|
}
|
61
pkg/alrsh/gob.go
Normal file
61
pkg/alrsh/gob.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// 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 alrsh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/gob"
|
||||||
|
|
||||||
|
"mvdan.cc/sh/v3/syntax"
|
||||||
|
"mvdan.cc/sh/v3/syntax/typedjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *ScriptFile) GobEncode() ([]byte, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
enc := gob.NewEncoder(&buf)
|
||||||
|
if err := enc.Encode(s.path); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var fileBuf bytes.Buffer
|
||||||
|
if err := typedjson.Encode(&fileBuf, s.file); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fileData := fileBuf.Bytes()
|
||||||
|
if err := enc.Encode(fileData); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ScriptFile) GobDecode(data []byte) error {
|
||||||
|
buf := bytes.NewBuffer(data)
|
||||||
|
dec := gob.NewDecoder(buf)
|
||||||
|
if err := dec.Decode(&s.path); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var fileData []byte
|
||||||
|
if err := dec.Decode(&fileData); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fileReader := bytes.NewReader(fileData)
|
||||||
|
file, err := typedjson.Decode(fileReader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.file = file.(*syntax.File)
|
||||||
|
return nil
|
||||||
|
}
|
191
pkg/alrsh/overridable.go
Normal file
191
pkg/alrsh/overridable.go
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
// 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 alrsh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/gob"
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OverridableField[T any] struct {
|
||||||
|
data map[string]T
|
||||||
|
// It can't be a pointer
|
||||||
|
//
|
||||||
|
// See https://gitea.com/xorm/xorm/issues/2431
|
||||||
|
resolved T
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *OverridableField[T]) Set(key string, value T) {
|
||||||
|
if f.data == nil {
|
||||||
|
f.data = make(map[string]T)
|
||||||
|
}
|
||||||
|
f.data[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *OverridableField[T]) Get(key string) T {
|
||||||
|
if f.data == nil {
|
||||||
|
f.data = make(map[string]T)
|
||||||
|
}
|
||||||
|
return f.data[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *OverridableField[T]) Has(key string) (T, bool) {
|
||||||
|
if f.data == nil {
|
||||||
|
f.data = make(map[string]T)
|
||||||
|
}
|
||||||
|
val, ok := f.data[key]
|
||||||
|
return val, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *OverridableField[T]) SetResolved(value T) {
|
||||||
|
f.resolved = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *OverridableField[T]) Resolved() T {
|
||||||
|
return f.resolved
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *OverridableField[T]) All() map[string]T {
|
||||||
|
return f.data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OverridableField[T]) Resolve(overrides []string) {
|
||||||
|
for _, override := range overrides {
|
||||||
|
if v, ok := o.Has(override); ok {
|
||||||
|
o.SetResolved(v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Database serialization (JSON)
|
||||||
|
func (f *OverridableField[T]) ToDB() ([]byte, error) {
|
||||||
|
var data map[string]T
|
||||||
|
|
||||||
|
if f.data == nil {
|
||||||
|
data = make(map[string]T)
|
||||||
|
} else {
|
||||||
|
data = f.data
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *OverridableField[T]) FromDB(data []byte) error {
|
||||||
|
if len(data) == 0 {
|
||||||
|
*f = OverridableField[T]{data: make(map[string]T)}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var temp map[string]T
|
||||||
|
if err := json.Unmarshal(data, &temp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if temp == nil {
|
||||||
|
temp = make(map[string]T)
|
||||||
|
}
|
||||||
|
|
||||||
|
*f = OverridableField[T]{data: temp}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gob serialization
|
||||||
|
type overridableFieldGobPayload[T any] struct {
|
||||||
|
Data map[string]T
|
||||||
|
Resolved T
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *OverridableField[T]) GobEncode() ([]byte, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
enc := gob.NewEncoder(&buf)
|
||||||
|
|
||||||
|
payload := overridableFieldGobPayload[T]{
|
||||||
|
Data: f.data,
|
||||||
|
Resolved: f.resolved,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := enc.Encode(payload); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *OverridableField[T]) GobDecode(data []byte) error {
|
||||||
|
dec := gob.NewDecoder(bytes.NewBuffer(data))
|
||||||
|
|
||||||
|
var payload overridableFieldGobPayload[T]
|
||||||
|
if err := dec.Decode(&payload); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
f.data = payload.Data
|
||||||
|
f.resolved = payload.Resolved
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type overridableFieldJSONPayload[T any] struct {
|
||||||
|
Resolved *T `json:"resolved,omitempty,omitzero"`
|
||||||
|
Data map[string]T `json:"overrides,omitempty,omitzero"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f OverridableField[T]) MarshalJSON() ([]byte, error) {
|
||||||
|
data := make(map[string]T)
|
||||||
|
|
||||||
|
for k, v := range f.data {
|
||||||
|
if k == "" {
|
||||||
|
data["default"] = v
|
||||||
|
} else {
|
||||||
|
data[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := overridableFieldJSONPayload[T]{
|
||||||
|
Data: data,
|
||||||
|
Resolved: &f.resolved,
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *OverridableField[T]) UnmarshalJSON(data []byte) error {
|
||||||
|
var payload overridableFieldJSONPayload[T]
|
||||||
|
if err := json.Unmarshal(data, &payload); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if payload.Data == nil {
|
||||||
|
payload.Data = make(map[string]T)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.data = payload.Data
|
||||||
|
if payload.Resolved != nil {
|
||||||
|
f.resolved = *payload.Resolved
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverridableFromMap[T any](data map[string]T) OverridableField[T] {
|
||||||
|
if data == nil {
|
||||||
|
data = make(map[string]T)
|
||||||
|
}
|
||||||
|
return OverridableField[T]{
|
||||||
|
data: data,
|
||||||
|
}
|
||||||
|
}
|
156
pkg/alrsh/package.go
Normal file
156
pkg/alrsh/package.go
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
// 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/>.
|
||||||
|
|
||||||
|
//go:generate go run ../../generators/alrsh-package
|
||||||
|
|
||||||
|
package alrsh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PackageNames struct {
|
||||||
|
BasePkgName string `sh:"basepkg_name"`
|
||||||
|
Names []string `sh:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseNames(dec *decoder.Decoder) (*PackageNames, error) {
|
||||||
|
var pkgs PackageNames
|
||||||
|
err := dec.DecodeVars(&pkgs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("fail parse names: %w", err)
|
||||||
|
}
|
||||||
|
return &pkgs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Package struct {
|
||||||
|
Repository string `xorm:"pk 'repository'" json:"repository"`
|
||||||
|
Name string `xorm:"pk 'name'" json:"name"`
|
||||||
|
BasePkgName string `xorm:"notnull 'basepkg_name'" json:"basepkg_name"`
|
||||||
|
|
||||||
|
Version string `sh:"version" xorm:"notnull 'version'" json:"version"`
|
||||||
|
Release int `sh:"release" xorm:"notnull 'release'" json:"release"`
|
||||||
|
Epoch uint `sh:"epoch" xorm:"'epoch'" json:"epoch"`
|
||||||
|
Architectures []string `sh:"architectures" xorm:"json 'architectures'" json:"architectures"`
|
||||||
|
Licenses []string `sh:"license" xorm:"json 'licenses'" json:"license"`
|
||||||
|
Provides []string `sh:"provides" xorm:"json 'provides'" json:"provides"`
|
||||||
|
Conflicts []string `sh:"conflicts" xorm:"json 'conflicts'" json:"conflicts"`
|
||||||
|
Replaces []string `sh:"replaces" xorm:"json 'replaces'" json:"replaces"`
|
||||||
|
|
||||||
|
Summary OverridableField[string] `sh:"summary" xorm:"'summary'" json:"summary"`
|
||||||
|
Description OverridableField[string] `sh:"desc" xorm:"'description'" json:"description"`
|
||||||
|
Group OverridableField[string] `sh:"group" xorm:"'group_name'" json:"group"`
|
||||||
|
Homepage OverridableField[string] `sh:"homepage" xorm:"'homepage'" json:"homepage"`
|
||||||
|
Maintainer OverridableField[string] `sh:"maintainer" xorm:"'maintainer'" json:"maintainer"`
|
||||||
|
Depends OverridableField[[]string] `sh:"deps" xorm:"'depends'" json:"deps"`
|
||||||
|
BuildDepends OverridableField[[]string] `sh:"build_deps" xorm:"'builddepends'" json:"build_deps"`
|
||||||
|
OptDepends OverridableField[[]string] `sh:"opt_deps" xorm:"'optdepends'" json:"opt_deps,omitempty"`
|
||||||
|
Sources OverridableField[[]string] `sh:"sources" xorm:"-" json:"sources"`
|
||||||
|
Checksums OverridableField[[]string] `sh:"checksums" xorm:"-" json:"checksums,omitempty"`
|
||||||
|
Backup OverridableField[[]string] `sh:"backup" xorm:"-" json:"backup"`
|
||||||
|
Scripts OverridableField[Scripts] `sh:"scripts" xorm:"-" json:"scripts,omitempty"`
|
||||||
|
AutoReq OverridableField[[]string] `sh:"auto_req" xorm:"-" json:"auto_req"`
|
||||||
|
AutoProv OverridableField[[]string] `sh:"auto_prov" xorm:"-" json:"auto_prov"`
|
||||||
|
AutoReqSkipList OverridableField[[]string] `sh:"auto_req_skiplist" xorm:"-" json:"auto_req_skiplist,omitempty"`
|
||||||
|
AutoProvSkipList OverridableField[[]string] `sh:"auto_prov_skiplist" xorm:"-" json:"auto_prov_skiplist,omitempty"`
|
||||||
|
|
||||||
|
FireJailed OverridableField[bool] `sh:"firejailed" xorm:"-" json:"firejailed"`
|
||||||
|
FireJailProfiles OverridableField[map[string]string] `sh:"firejail_profiles" xorm:"-" json:"firejail_profiles,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Scripts struct {
|
||||||
|
PreInstall string `sh:"preinstall"`
|
||||||
|
PostInstall string `sh:"postinstall"`
|
||||||
|
PreRemove string `sh:"preremove"`
|
||||||
|
PostRemove string `sh:"postremove"`
|
||||||
|
PreUpgrade string `sh:"preupgrade"`
|
||||||
|
PostUpgrade string `sh:"postupgrade"`
|
||||||
|
PreTrans string `sh:"pretrans"`
|
||||||
|
PostTrans string `sh:"posttrans"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Package) MarshalJSONWithOptions(includeOverrides bool) ([]byte, error) {
|
||||||
|
// Сначала сериализуем обычным способом для получения базовой структуры
|
||||||
|
type PackageAlias Package
|
||||||
|
baseData, err := json.Marshal(PackageAlias(p))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Десериализуем в map для модификации
|
||||||
|
var result map[string]json.RawMessage
|
||||||
|
if err := json.Unmarshal(baseData, &result); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Теперь заменяем OverridableField поля
|
||||||
|
v := reflect.ValueOf(p)
|
||||||
|
t := reflect.TypeOf(p)
|
||||||
|
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
field := v.Field(i)
|
||||||
|
fieldType := t.Field(i)
|
||||||
|
|
||||||
|
jsonTag := fieldType.Tag.Get("json")
|
||||||
|
if jsonTag == "" || jsonTag == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldName := jsonTag
|
||||||
|
if commaIdx := strings.Index(jsonTag, ","); commaIdx != -1 {
|
||||||
|
fieldName = jsonTag[:commaIdx]
|
||||||
|
}
|
||||||
|
|
||||||
|
if field.Type().Name() == "OverridableField" ||
|
||||||
|
(field.Type().Kind() == reflect.Struct &&
|
||||||
|
strings.Contains(field.Type().String(), "OverridableField")) {
|
||||||
|
|
||||||
|
fieldPtr := field.Addr()
|
||||||
|
|
||||||
|
resolvedMethod := fieldPtr.MethodByName("Resolved")
|
||||||
|
if resolvedMethod.IsValid() {
|
||||||
|
resolved := resolvedMethod.Call(nil)[0]
|
||||||
|
|
||||||
|
fieldData := map[string]interface{}{
|
||||||
|
"resolved": resolved.Interface(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if includeOverrides {
|
||||||
|
allMethod := field.MethodByName("All")
|
||||||
|
if allMethod.IsValid() {
|
||||||
|
overrides := allMethod.Call(nil)[0]
|
||||||
|
if !overrides.IsNil() && overrides.Len() > 0 {
|
||||||
|
fieldData["overrides"] = overrides.Interface()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldJSON, err := json.Marshal(fieldData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result[fieldName] = json.RawMessage(fieldJSON)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(result)
|
||||||
|
}
|
105
pkg/alrsh/package_gen.go
Normal file
105
pkg/alrsh/package_gen.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
// 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/>.
|
||||||
|
|
||||||
|
// DO NOT EDIT MANUALLY. This file is generated.
|
||||||
|
package alrsh
|
||||||
|
|
||||||
|
type packageResolved struct {
|
||||||
|
Repository string `json:"repository"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
BasePkgName string `json:"basepkg_name"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
Release int `json:"release"`
|
||||||
|
Epoch uint `json:"epoch"`
|
||||||
|
Architectures []string `json:"architectures"`
|
||||||
|
Licenses []string `json:"license"`
|
||||||
|
Provides []string `json:"provides"`
|
||||||
|
Conflicts []string `json:"conflicts"`
|
||||||
|
Replaces []string `json:"replaces"`
|
||||||
|
Summary string `json:"summary"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Group string `json:"group"`
|
||||||
|
Homepage string `json:"homepage"`
|
||||||
|
Maintainer string `json:"maintainer"`
|
||||||
|
Depends []string `json:"deps"`
|
||||||
|
BuildDepends []string `json:"build_deps"`
|
||||||
|
OptDepends []string `json:"opt_deps,omitempty"`
|
||||||
|
Sources []string `json:"sources"`
|
||||||
|
Checksums []string `json:"checksums,omitempty"`
|
||||||
|
Backup []string `json:"backup"`
|
||||||
|
Scripts Scripts `json:"scripts,omitempty"`
|
||||||
|
AutoReq []string `json:"auto_req"`
|
||||||
|
AutoProv []string `json:"auto_prov"`
|
||||||
|
AutoReqSkipList []string `json:"auto_req_skiplist,omitempty"`
|
||||||
|
AutoProvSkipList []string `json:"auto_prov_skiplist,omitempty"`
|
||||||
|
FireJailed bool `json:"firejailed"`
|
||||||
|
FireJailProfiles map[string]string `json:"firejail_profiles,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func PackageToResolved(src *Package) packageResolved {
|
||||||
|
return packageResolved{
|
||||||
|
Repository: src.Repository,
|
||||||
|
Name: src.Name,
|
||||||
|
BasePkgName: src.BasePkgName,
|
||||||
|
Version: src.Version,
|
||||||
|
Release: src.Release,
|
||||||
|
Epoch: src.Epoch,
|
||||||
|
Architectures: src.Architectures,
|
||||||
|
Licenses: src.Licenses,
|
||||||
|
Provides: src.Provides,
|
||||||
|
Conflicts: src.Conflicts,
|
||||||
|
Replaces: src.Replaces,
|
||||||
|
Summary: src.Summary.Resolved(),
|
||||||
|
Description: src.Description.Resolved(),
|
||||||
|
Group: src.Group.Resolved(),
|
||||||
|
Homepage: src.Homepage.Resolved(),
|
||||||
|
Maintainer: src.Maintainer.Resolved(),
|
||||||
|
Depends: src.Depends.Resolved(),
|
||||||
|
BuildDepends: src.BuildDepends.Resolved(),
|
||||||
|
OptDepends: src.OptDepends.Resolved(),
|
||||||
|
Sources: src.Sources.Resolved(),
|
||||||
|
Checksums: src.Checksums.Resolved(),
|
||||||
|
Backup: src.Backup.Resolved(),
|
||||||
|
Scripts: src.Scripts.Resolved(),
|
||||||
|
AutoReq: src.AutoReq.Resolved(),
|
||||||
|
AutoProv: src.AutoProv.Resolved(),
|
||||||
|
AutoReqSkipList: src.AutoReqSkipList.Resolved(),
|
||||||
|
AutoProvSkipList: src.AutoProvSkipList.Resolved(),
|
||||||
|
FireJailed: src.FireJailed.Resolved(),
|
||||||
|
FireJailProfiles: src.FireJailProfiles.Resolved(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResolvePackage(pkg *Package, overrides []string) {
|
||||||
|
pkg.Summary.Resolve(overrides)
|
||||||
|
pkg.Description.Resolve(overrides)
|
||||||
|
pkg.Group.Resolve(overrides)
|
||||||
|
pkg.Homepage.Resolve(overrides)
|
||||||
|
pkg.Maintainer.Resolve(overrides)
|
||||||
|
pkg.Depends.Resolve(overrides)
|
||||||
|
pkg.BuildDepends.Resolve(overrides)
|
||||||
|
pkg.OptDepends.Resolve(overrides)
|
||||||
|
pkg.Sources.Resolve(overrides)
|
||||||
|
pkg.Checksums.Resolve(overrides)
|
||||||
|
pkg.Backup.Resolve(overrides)
|
||||||
|
pkg.Scripts.Resolve(overrides)
|
||||||
|
pkg.AutoReq.Resolve(overrides)
|
||||||
|
pkg.AutoProv.Resolve(overrides)
|
||||||
|
pkg.AutoReqSkipList.Resolve(overrides)
|
||||||
|
pkg.AutoProvSkipList.Resolve(overrides)
|
||||||
|
pkg.FireJailed.Resolve(overrides)
|
||||||
|
pkg.FireJailProfiles.Resolve(overrides)
|
||||||
|
}
|
57
pkg/alrsh/read.go
Normal file
57
pkg/alrsh/read.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// 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 alrsh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"mvdan.cc/sh/v3/syntax"
|
||||||
|
)
|
||||||
|
|
||||||
|
type localFs struct{}
|
||||||
|
|
||||||
|
func (fs *localFs) Open(name string) (fs.File, error) {
|
||||||
|
return os.Open(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadFromIOReader(r io.Reader, script string) (*ScriptFile, error) {
|
||||||
|
file, err := syntax.NewParser().Parse(r, "alr.sh")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ScriptFile{
|
||||||
|
file: file,
|
||||||
|
path: script,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadFromFS(fsys fs.FS, script string) (*ScriptFile, error) {
|
||||||
|
fl, err := fsys.Open(script)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to open alr.sh: %w", err)
|
||||||
|
}
|
||||||
|
defer fl.Close()
|
||||||
|
|
||||||
|
return ReadFromIOReader(fl, script)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadFromLocal(script string) (*ScriptFile, error) {
|
||||||
|
return ReadFromFS(&localFs{}, script)
|
||||||
|
}
|
37
pkg/alrsh/view.go
Normal file
37
pkg/alrsh/view.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// 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 alrsh
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
type PackageView struct {
|
||||||
|
pkg Package
|
||||||
|
|
||||||
|
Resolved bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPackageView(v Package) PackageView {
|
||||||
|
return PackageView{pkg: v}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PackageView) MarshalJSON() ([]byte, error) {
|
||||||
|
if p.Resolved {
|
||||||
|
return json.Marshal(PackageToResolved(&p.pkg))
|
||||||
|
} else {
|
||||||
|
return json.Marshal(p.pkg)
|
||||||
|
}
|
||||||
|
}
|
@ -55,7 +55,7 @@ var (
|
|||||||
|
|
||||||
// Массив доступных загрузчиков в порядке их проверки
|
// Массив доступных загрузчиков в порядке их проверки
|
||||||
var Downloaders = []Downloader{
|
var Downloaders = []Downloader{
|
||||||
GitDownloader{},
|
&GitDownloader{},
|
||||||
TorrentDownloader{},
|
TorrentDownloader{},
|
||||||
FileDownloader{},
|
FileDownloader{},
|
||||||
}
|
}
|
@ -32,8 +32,8 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/dl"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dl"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dlcache"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TestALRConfig struct{}
|
type TestALRConfig struct{}
|
@ -48,7 +48,7 @@ func (GitDownloader) MatchURL(u string) bool {
|
|||||||
// Download uses git to clone the repository from the specified URL.
|
// Download uses git to clone the repository from the specified URL.
|
||||||
// It allows specifying the revision, depth and recursion options
|
// It allows specifying the revision, depth and recursion options
|
||||||
// via query string
|
// via query string
|
||||||
func (GitDownloader) Download(ctx context.Context, opts Options) (Type, string, error) {
|
func (d *GitDownloader) Download(ctx context.Context, opts Options) (Type, string, error) {
|
||||||
u, err := url.Parse(opts.URL)
|
u, err := url.Parse(opts.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, "", err
|
return 0, "", err
|
||||||
@ -60,6 +60,9 @@ func (GitDownloader) Download(ctx context.Context, opts Options) (Type, string,
|
|||||||
rev := query.Get("~rev")
|
rev := query.Get("~rev")
|
||||||
query.Del("~rev")
|
query.Del("~rev")
|
||||||
|
|
||||||
|
// Right now, this only affects the return value of name,
|
||||||
|
// which will be used by dl_cache.
|
||||||
|
// It seems wrong, but for now it's better to leave it as it is.
|
||||||
name := query.Get("~name")
|
name := query.Get("~name")
|
||||||
query.Del("~name")
|
query.Del("~name")
|
||||||
|
|
||||||
@ -121,6 +124,11 @@ func (GitDownloader) Download(ctx context.Context, opts Options) (Type, string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = VerifyHashFromLocal("", opts)
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", err
|
||||||
|
}
|
||||||
|
|
||||||
if name == "" {
|
if name == "" {
|
||||||
name = strings.TrimSuffix(path.Base(u.Path), ".git")
|
name = strings.TrimSuffix(path.Base(u.Path), ".git")
|
||||||
}
|
}
|
||||||
@ -133,7 +141,7 @@ func (GitDownloader) Download(ctx context.Context, opts Options) (Type, string,
|
|||||||
// and recursion options via query string. It returns
|
// and recursion options via query string. It returns
|
||||||
// true if update was successful and false if the
|
// true if update was successful and false if the
|
||||||
// repository is already up-to-date
|
// repository is already up-to-date
|
||||||
func (GitDownloader) Update(opts Options) (bool, error) {
|
func (d *GitDownloader) Update(opts Options) (bool, error) {
|
||||||
u, err := url.Parse(opts.URL)
|
u, err := url.Parse(opts.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -183,18 +191,21 @@ func (GitDownloader) Update(opts Options) (bool, error) {
|
|||||||
manifestOK := err == nil
|
manifestOK := err == nil
|
||||||
|
|
||||||
err = w.Pull(po)
|
err = w.Pull(po)
|
||||||
if errors.Is(err, git.NoErrAlreadyUpToDate) {
|
if err != nil {
|
||||||
return false, nil
|
if errors.Is(err, git.NoErrAlreadyUpToDate) {
|
||||||
} else if err != nil {
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = VerifyHashFromLocal("", opts)
|
||||||
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if manifestOK {
|
if manifestOK {
|
||||||
err = writeManifest(opts.Destination, m)
|
err = writeManifest(opts.Destination, m)
|
||||||
if err != nil {
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, nil
|
return true, err
|
||||||
}
|
}
|
183
pkg/dl/git_test.go
Normal file
183
pkg/dl/git_test.go
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
// 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 dl_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dl"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGitDownloaderMatchUrl(t *testing.T) {
|
||||||
|
d := dl.GitDownloader{}
|
||||||
|
assert.True(t, d.MatchURL("git+https://example.com/org/project.git"))
|
||||||
|
assert.False(t, d.MatchURL("https://example.com/org/project.git"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGitDownloaderDownload(t *testing.T) {
|
||||||
|
d := dl.GitDownloader{}
|
||||||
|
|
||||||
|
createTempDir := func(t *testing.T, name string) string {
|
||||||
|
t.Helper()
|
||||||
|
dir, err := os.MkdirTemp("", "test-"+name)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
_ = os.RemoveAll(dir)
|
||||||
|
})
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("simple", func(t *testing.T) {
|
||||||
|
dest := createTempDir(t, "simple")
|
||||||
|
|
||||||
|
dlType, name, err := d.Download(context.Background(), dl.Options{
|
||||||
|
URL: "git+https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git",
|
||||||
|
Destination: dest,
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, dl.TypeDir, dlType)
|
||||||
|
assert.Equal(t, "repo-for-tests", name)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with hash", func(t *testing.T) {
|
||||||
|
dest := createTempDir(t, "with-hash")
|
||||||
|
|
||||||
|
hsh, err := hex.DecodeString("33c912b855352663550003ca6b948ae3df1f38e2c036f5a85775df5967e143bf")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
dlType, name, err := d.Download(context.Background(), dl.Options{
|
||||||
|
URL: "git+https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git?~rev=init&~name=test",
|
||||||
|
Destination: dest,
|
||||||
|
Hash: hsh,
|
||||||
|
HashAlgorithm: "sha256",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, dl.TypeDir, dlType)
|
||||||
|
assert.Equal(t, "test", name)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with hash (checksum mismatch)", func(t *testing.T) {
|
||||||
|
dest := createTempDir(t, "with-hash-checksum-mismatch")
|
||||||
|
|
||||||
|
hsh, err := hex.DecodeString("33c912b855352663550003ca6b948ae3df1f38e2c036f5a85775df5967e143bf")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
_, _, err = d.Download(context.Background(), dl.Options{
|
||||||
|
URL: "git+https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git",
|
||||||
|
Destination: dest,
|
||||||
|
Hash: hsh,
|
||||||
|
HashAlgorithm: "sha256",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.ErrorIs(t, err, dl.ErrChecksumMismatch)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGitDownloaderUpdate(t *testing.T) {
|
||||||
|
d := dl.GitDownloader{}
|
||||||
|
|
||||||
|
createTempDir := func(t *testing.T, name string) string {
|
||||||
|
t.Helper()
|
||||||
|
dir, err := os.MkdirTemp("", "test-"+name)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
_ = os.RemoveAll(dir)
|
||||||
|
})
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
|
||||||
|
setupOldRepo := func(t *testing.T, dest string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
cmd := exec.Command("git", "clone", "https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git", dest)
|
||||||
|
err := cmd.Run()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
cmd = exec.Command("git", "-C", dest, "reset", "--hard", "init")
|
||||||
|
err = cmd.Run()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("simple", func(t *testing.T) {
|
||||||
|
dest := createTempDir(t, "update")
|
||||||
|
|
||||||
|
setupOldRepo(t, dest)
|
||||||
|
|
||||||
|
cmd := exec.Command("git", "-C", dest, "rev-parse", "HEAD")
|
||||||
|
oldHash, err := cmd.Output()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
updated, err := d.Update(dl.Options{
|
||||||
|
URL: "git+https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git",
|
||||||
|
Destination: dest,
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, updated)
|
||||||
|
|
||||||
|
cmd = exec.Command("git", "-C", dest, "rev-parse", "HEAD")
|
||||||
|
newHash, err := cmd.Output()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEqual(t, string(oldHash), string(newHash), "Repository should be updated")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with hash", func(t *testing.T) {
|
||||||
|
dest := createTempDir(t, "update")
|
||||||
|
|
||||||
|
setupOldRepo(t, dest)
|
||||||
|
|
||||||
|
hsh, err := hex.DecodeString("0dc4f3c68c435d0cd7a5ee960f965815fa9c4ee0571839cdb8f9de56e06f91eb")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
updated, err := d.Update(dl.Options{
|
||||||
|
URL: "git+https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git~rev=test-update-git-downloader",
|
||||||
|
Destination: dest,
|
||||||
|
Hash: hsh,
|
||||||
|
HashAlgorithm: "sha256",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, updated)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with hash (checksum mismatch)", func(t *testing.T) {
|
||||||
|
dest := createTempDir(t, "update")
|
||||||
|
|
||||||
|
setupOldRepo(t, dest)
|
||||||
|
|
||||||
|
hsh, err := hex.DecodeString("33c912b855352663550003ca6b948ae3df1f38e2c036f5a85775df5967e143bf")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = d.Update(dl.Options{
|
||||||
|
URL: "git+https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git?~rev=test-update-git-downloader",
|
||||||
|
Destination: dest,
|
||||||
|
Hash: hsh,
|
||||||
|
HashAlgorithm: "sha256",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.ErrorIs(t, err, dl.ErrChecksumMismatch)
|
||||||
|
})
|
||||||
|
}
|
@ -71,7 +71,17 @@ func (TorrentDownloader) Download(ctx context.Context, opts Options) (Type, stri
|
|||||||
return 0, "", err
|
return 0, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return determineType(opts.Destination)
|
dlType, name, err := determineType(opts.Destination)
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = VerifyHashFromLocal(name, opts)
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dlType, name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeTorrentFiles(path string) error {
|
func removeTorrentFiles(path string) error {
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user