16 Commits

Author SHA1 Message Date
c88478a450 ci: fix release workflow
All checks were successful
Create Release / changelog (push) Successful in 54s
2025-05-16 21:45:14 +03:00
3e61fec67c ci: fix release workflow
Some checks failed
Create Release / changelog (push) Has been cancelled
2025-05-16 21:42:56 +03:00
6f484a1169 ci: fix release workflow
All checks were successful
Create Release / changelog (push) Successful in 49s
2025-05-16 21:30:57 +03:00
dddcb9b7b0 ci: fix release workflow
Some checks failed
Create Release / changelog (push) Failing after 40s
2025-05-16 21:27:04 +03:00
b03d94e48b ci: add release workflow
Some checks failed
E2E / tests (pull_request) Successful in 1m45s
Pre-commit / pre-commit (pull_request) Successful in 1m38s
Create Release / changelog (push) Failing after 11s
2025-05-16 21:14:37 +03:00
f92bd7089a add set-ref command and refactor tests
All checks were successful
E2E / tests (pull_request) Successful in 1m42s
Pre-commit / pre-commit (pull_request) Successful in 1m23s
2025-05-16 20:48:14 +03:00
eb2356458c ci: add e2e (#90)
Reviewed-on: #90
Co-authored-by: Maxim Slipenko <no-reply@maxim.slipenko.com>
Co-committed-by: Maxim Slipenko <no-reply@maxim.slipenko.com>
2025-05-16 16:19:24 +00:00
131f455eff add repo subcommand
All checks were successful
Pre-commit / pre-commit (pull_request) Successful in 1m36s
2025-05-14 23:04:28 +03:00
1e52d30f4c fix list command
All checks were successful
Pre-commit / pre-commit (pull_request) Successful in 1m35s
2025-05-13 23:31:56 +03:00
40ec0ac6e1 add --upgradable option for list
All checks were successful
Pre-commit / pre-commit (pull_request) Successful in 1m36s
2025-05-13 23:26:12 +03:00
443e481561 fix support of multiple packages in one alr.sh
All checks were successful
Pre-commit / pre-commit (pull_request) Successful in 1m37s
2025-05-13 21:55:23 +03:00
c892310f69 fix Makefile
All checks were successful
Pre-commit / pre-commit (pull_request) Successful in 2m49s
2025-05-12 20:11:55 +03:00
750513b119 fix ci
Some checks failed
Pre-commit / pre-commit (pull_request) Failing after 2m36s
2025-05-12 19:46:52 +03:00
ce1836b646 ci: use go 1.24
Some checks failed
Pre-commit / pre-commit (pull_request) Failing after 3m52s
2025-05-12 19:30:15 +03:00
56b9f3211c ci: add simple workflow for pre-commit
Some checks failed
Pre-commit / pre-commit (pull_request) Failing after 5m29s
2025-05-12 19:22:50 +03:00
fae63e28f9 fix license-header.tmpl 2025-05-12 19:22:28 +03:00
40 changed files with 1009 additions and 584 deletions

View 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/>.
name: E2E
on:
push:
branches: [ main ]
pull_request:
jobs:
tests:
runs-on: ubuntu-latest
container:
image: altlinux.space/maks1ms/actions-container-runner:latest
steps:
- name: Checkout
uses: https://github.com/actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: https://github.com/actions/setup-go@v5
with:
go-version: '1.24'
- name: Cache Podman images
uses: actions/cache@v4
with:
path: |
~/.local/share/containers/storage
/var/lib/containers/storage
key: ${{ runner.os }}-primes
- name: Run E2E tests
env:
DOCKER_HOST: unix:///tmp/podman.sock
IGNORE_ROOT_CHECK: 1
run: |
podman system service -t 0 unix:///tmp/podman.sock &
make e2e-test

View File

@ -0,0 +1,49 @@
# ALR - Any Linux Repository
# Copyright (C) 2025 The ALR Authors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
name: Pre-commit
on:
push:
branches: [ main ]
pull_request:
jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: https://github.com/actions/checkout@v4
- name: Set up Go
uses: https://github.com/actions/setup-go@v5
with:
go-version: '1.24'
- name: Set up Python for pre-commit
uses: https://github.com/actions/setup-python@v5
with:
python-version: '3.12'
- name: Install deps
run: apt-get update && apt-get install -y gettext bc
- run: pip install pre-commit
- run: pre-commit install
- run: pre-commit run --all-files

View File

@ -0,0 +1,67 @@
# ALR - Any Linux Repository
# Copyright (C) 2025 The ALR Authors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
name: Create Release
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
jobs:
changelog:
runs-on: ubuntu-latest
steps:
- name: Checkout this repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
- name: Get Changes between Tags
id: changes
uses: simbo/changes-between-tags-action@v1
- name: Set version
run: |
version=$(echo "${GITHUB_REF##*/}" | sed 's/^v//')
echo "Version - $version"
echo "VERSION=$version" >> $GITHUB_ENV
- name: Build alr binary
run: |
CGO_ENABLED=0 go build -ldflags "-X gitea.plemya-x.ru/Plemya-x/ALR/internal/config.Version=${{ env.VERSION }}" -o alr
- name: Create tar.gz
run: |
mkdir -p ./out/completion
cp alr ./out
cp scripts/completion/bash ./out/completion/alr
cp scripts/completion/zsh ./out/completion/_alr
( cd out && tar -czvf ../alr-${{ env.VERSION }}-linux-x86_64.tar.gz * )
- name: Release
uses: akkuman/gitea-release-action@v1
with:
body: ${{ steps.changes.outputs.changes }}
files: |-
alr-${{ env.VERSION }}-linux-x86_64.tar.gz

View File

@ -1,6 +1,6 @@
NAME := alr
GIT_VERSION = $(shell git describe --tags )
IGNORE_ROOT_CHECK ?= 0
DESTDIR ?=
PREFIX ?= /usr/local
BIN := ./$(NAME)
@ -24,8 +24,9 @@ $(BIN):
go build -ldflags="-X 'gitea.plemya-x.ru/Plemya-x/ALR/internal/config.Version=$(GIT_VERSION)'" -o $@
check-no-root:
@if [[ "$$(whoami)" == 'root' ]]; then \
@if [[ "$(IGNORE_ROOT_CHECK)" != "1" ]] && [[ "$$(whoami)" == 'root' ]]; then \
echo "This target shouldn't run as root" 1>&2; \
echo "Set IGNORE_ROOT_CHECK=1 to override" 1>&2; \
exit 1; \
fi
@ -54,7 +55,7 @@ uninstall:
clean clear:
rm -f $(BIN)
OLD_FILES=$$(< old-files)
OLD_FILES=$(shell cat old-files)
IGNORE_OLD_FILES := $(foreach file,$(shell cat old-files),-ignore $(file))
update-license:
$(ADD_LICENSE_BIN) -v -f license-header-old-files.tmpl $(OLD_FILES)
@ -76,7 +77,9 @@ test-coverage:
update-deps-cve:
bash scripts/update-deps-cve.sh
e2e-test: clean build
prepare-for-e2e-test: clean build
rm -f ./e2e-tests/alr
cp alr e2e-tests
e2e-test: prepare-for-e2e-test
go test -tags=e2e ./...

View File

@ -11,7 +11,7 @@
<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="14">coverage</text>
<text x="86" y="15" fill="#010101" fill-opacity=".3">17.0%</text>
<text x="86" y="14">17.0%</text>
<text x="86" y="15" fill="#010101" fill-opacity=".3">16.9%</text>
<text x="86" y="14">16.9%</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 926 B

After

Width:  |  Height:  |  Size: 926 B

View File

@ -118,7 +118,11 @@ func BuildCmd() *cli.Command {
return cliutils.FormatCliExit(gotext.Get("Cannot get absolute script path"), err)
}
packages = append(packages, c.String("script-package"))
subpackage := c.String("subpackage")
if subpackage != "" {
packages = append(packages, subpackage)
}
scriptArgs = &build.BuildPackageFromScriptArgs{
Script: script,

View File

@ -21,7 +21,6 @@ package e2etests_test
import (
"testing"
"github.com/alecthomas/assert/v2"
"github.com/efficientgo/e2e"
)
@ -31,10 +30,7 @@ func TestE2EBashCompletion(t *testing.T) {
"bash-completion",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
err := r.Exec(e2e.NewCommand(
"alr", "install", "--generate-bash-completion",
))
assert.NoError(t, err)
execShouldNoError(t, r, "alr", "install", "--generate-bash-completion")
},
)
}

View File

@ -25,7 +25,6 @@ import (
"io"
"log"
"os"
"os/exec"
"testing"
"time"
@ -121,29 +120,6 @@ var COMMON_SYSTEMS []string = []string{
"ubuntu-24.04",
}
func init() {
for _, id := range ALL_SYSTEMS {
buildAlrTestImage(id)
}
}
func buildAlrTestImage(id string) {
cmd := exec.Command(
"docker",
"build",
"-t", fmt.Sprintf("alr-testimage-%s", id),
"-f", fmt.Sprintf("images/Dockerfile.%s", id),
".",
)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
fmt.Println("Error:", err)
return
}
}
func dockerMultipleRun(t *testing.T, name string, ids []string, f func(t *testing.T, runnable e2e.Runnable)) {
t.Run(name, func(t *testing.T) {
for _, id := range ids {
@ -158,23 +134,39 @@ func dockerMultipleRun(t *testing.T, name string, ids []string, f func(t *testin
e, err := e2e.New(e2e.WithVerbose(), e2e.WithName(fmt.Sprintf("alr-%s", truncatedHash)))
assert.NoError(t, err)
t.Cleanup(e.Close)
imageId := fmt.Sprintf("alr-testimage-%s", id)
imageId := fmt.Sprintf("ghcr.io/maks1ms/alr-e2e-test-image-%s", id)
runnable := e.Runnable(dockerName).Init(
e2e.StartOptions{
Image: imageId,
Volumes: []string{
// "./alr:/usr/bin/alr",
"./alr:/tmp/alr",
},
Privileged: true,
},
)
assert.NoError(t, e2e.StartAndWaitReady(runnable))
err = runnable.Exec(e2e.NewCommand("/bin/alr-test-setup", "alr-install"))
if err != nil {
panic(err)
}
err = runnable.Exec(e2e.NewCommand("/bin/alr-test-setup", "passwordless-sudo-setup"))
if err != nil {
panic(err)
}
f(t, runnable)
})
}
})
}
func execShouldNoError(t *testing.T, r e2e.Runnable, cmd string, args ...string) {
assert.NoError(t, r.Exec(e2e.NewCommand(cmd, args...)))
}
func execShouldError(t *testing.T, r e2e.Runnable, cmd string, args ...string) {
assert.Error(t, r.Exec(e2e.NewCommand(cmd, args...)))
}
func runTestCommands(t *testing.T, r e2e.Runnable, timeout time.Duration, expects []expect.Batcher) {
exp, _, err, _ := e2eSpawn(
r,
@ -188,3 +180,23 @@ func runTestCommands(t *testing.T, r e2e.Runnable, timeout time.Duration, expect
)
assert.NoError(t, err)
}
const REPO_NAME_FOR_E2E_TESTS = "alr-repo"
const REPO_URL_FOR_E2E_TESTS = "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git"
func defaultPrepare(t *testing.T, r e2e.Runnable) {
execShouldNoError(t, r,
"sudo",
"alr",
"repo",
"add",
REPO_NAME_FOR_E2E_TESTS,
REPO_URL_FOR_E2E_TESTS,
)
execShouldNoError(t, r,
"sudo",
"alr",
"ref",
)
}

View File

@ -21,7 +21,6 @@ package e2etests_test
import (
"testing"
"github.com/alecthomas/assert/v2"
"github.com/efficientgo/e2e"
)
@ -31,26 +30,9 @@ func TestE2EGroupAndSummaryField(t *testing.T) {
"group-and-summary-field",
RPM_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
err := r.Exec(e2e.NewCommand(
"sudo",
"alr",
"addrepo",
"--name",
"alr-repo",
"--url",
"https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand(
"sh", "-c", "alr search --name test-group-and-summary --format \"{{.Group}}\" | grep ^System/Base$",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand(
"sh", "-c", "alr search --name test-group-and-summary --format \"{{.Summary}}\" | grep \"^Custom summary$\"",
))
assert.NoError(t, err)
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 \"{{.Summary}}\" | grep \"^Custom summary$\"")
},
)
}

View File

@ -1,4 +0,0 @@
FROM alpine:latest
RUN adduser -s /bin/bash alr-user
USER alr-user
ENTRYPOINT ["tail", "-f", "/dev/null"]

View File

@ -1,6 +0,0 @@
FROM registry.altlinux.org/sisyphus/alt:latest
RUN apt-get update && apt-get install -y ca-certificates rpm-build
RUN useradd -m -s /bin/bash alr-user
USER alr-user
WORKDIR /home/alr-user
ENTRYPOINT ["tail", "-f", "/dev/null"]

View File

@ -1,4 +0,0 @@
FROM archlinux:latest
RUN useradd -m -s /bin/bash alr-user
USER alr-user
ENTRYPOINT ["tail", "-f", "/dev/null"]

View File

@ -1,18 +0,0 @@
FROM fedora:41
RUN dnf install -y ca-certificates sudo rpm-build bindfs
RUN <<EOF
useradd -m -s /bin/bash -G wheel user
echo "user ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/user
chmod 0440 /etc/sudoers.d/user
useradd -m -s /bin/bash alr
mkdir -p /var/cache/alr /etc/alr
chown alr:alr /var/cache/alr /etc/alr
EOF
COPY ./alr /usr/bin
RUN <<EOF
setcap cap_setuid,cap_setgid+ep /usr/bin/alr
EOF
USER user
WORKDIR /home/user
ENTRYPOINT ["tail", "-f", "/dev/null"]

View File

@ -1,4 +0,0 @@
FROM opensuse/leap:latest
RUN useradd -m -s /bin/bash alr-user
USER alr-user
ENTRYPOINT ["tail", "-f", "/dev/null"]

View File

@ -1,4 +0,0 @@
FROM registry.red-soft.ru/ubi8/ubi:latest
RUN useradd -m -s /bin/bash alr-user
USER alr-user
ENTRYPOINT ["tail", "-f", "/dev/null"]

View File

@ -1,17 +0,0 @@
FROM ubuntu:24.10
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates sudo libcap2-bin
RUN <<EOF
useradd -m -s /bin/bash user
echo "user ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/user
chmod 0440 /etc/sudoers.d/user
useradd -m -s /bin/bash alr
mkdir -p /var/cache/alr /etc/alr
chown alr:alr /var/cache/alr /etc/alr
EOF
COPY ./alr /usr/bin
RUN <<EOF
setcap cap_setuid,cap_setgid+ep /usr/bin/alr
EOF
USER user
ENTRYPOINT ["tail", "-f", "/dev/null"]

View File

@ -21,7 +21,6 @@ package e2etests_test
import (
"testing"
"github.com/alecthomas/assert/v2"
"github.com/efficientgo/e2e"
)
@ -31,21 +30,11 @@ func TestE2EIssue32Interactive(t *testing.T) {
"issue-32-interactive",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
assert.NoError(t, r.Exec(e2e.NewCommand(
"sudo", "alr", "--interactive=false", "remove", "ca-certificates",
)))
assert.NoError(t, r.Exec(e2e.NewCommand(
"sudo", "alr", "--interactive=false", "remove", "openssl",
)))
assert.NoError(t, r.Exec(e2e.NewCommand(
"alr", "fix",
)))
assert.NoError(t, r.Exec(e2e.NewCommand(
"sudo", "alr", "--interactive=false", "install", "ca-certificates",
)))
execShouldNoError(t, r, "alr", "--interactive=false", "remove", "ca-certificates")
execShouldNoError(t, r, "sudo", "alr", "--interactive=false", "remove", "openssl")
execShouldNoError(t, r, "alr", "fix")
execShouldNoError(t, r, "sudo", "apt-get", "update")
execShouldNoError(t, r, "sudo", "alr", "--interactive=false", "install", "ca-certificates")
},
)
}

View File

@ -21,7 +21,6 @@ package e2etests_test
import (
"testing"
"github.com/alecthomas/assert/v2"
"github.com/efficientgo/e2e"
)
@ -31,51 +30,11 @@ func TestE2EIssue41AutoreqSkiplist(t *testing.T) {
"issue-41-autoreq-skiplist",
AUTOREQ_AUTOPROV_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
err := r.Exec(e2e.NewCommand(
"sudo",
"alr",
"addrepo",
"--name",
"alr-repo",
"--url",
"https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand(
"alr",
"ref",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand(
"alr",
"build",
"-p",
"alr-repo/test-autoreq-autoprov",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand(
"sh",
"-c",
"rpm -qp --requires *.rpm | grep \"^/bin/sh$\"",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand(
"sh",
"-c",
"rpm -qp --requires *.rpm | grep \"^/bin/bash$\"",
))
assert.Error(t, err)
err = r.Exec(e2e.NewCommand(
"sh",
"-c",
"rpm -qp --requires *.rpm | grep \"^/bin/zsh$\"",
))
assert.Error(t, err)
defaultPrepare(t, r)
execShouldNoError(t, r, "alr", "build", "-p", "alr-repo/test-autoreq-autoprov")
execShouldNoError(t, r, "sh", "-c", "rpm -qp --requires *.rpm | grep \"^/bin/sh$\"")
execShouldError(t, r, "sh", "-c", "rpm -qp --requires *.rpm | grep \"^/bin/bash$\"")
execShouldError(t, r, "sh", "-c", "rpm -qp --requires *.rpm | grep \"^/bin/zsh$\"")
},
)
}

View File

@ -21,7 +21,6 @@ package e2etests_test
import (
"testing"
"github.com/alecthomas/assert/v2"
"github.com/efficientgo/e2e"
)
@ -31,26 +30,10 @@ func TestE2EIssue50InstallMultiple(t *testing.T) {
"issue-50-install-multiple",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
err := r.Exec(e2e.NewCommand(
"sudo",
"alr",
"addrepo",
"--name",
"alr-repo",
"--url",
"https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand(
"sudo", "alr", "in", "foo-pkg", "bar-pkg",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand("cat", "/opt/foo"))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand("cat", "/opt/bar"))
assert.NoError(t, err)
defaultPrepare(t, r)
execShouldNoError(t, r, "sudo", "alr", "in", "foo-pkg", "bar-pkg")
execShouldNoError(t, r, "cat", "/opt/foo")
execShouldNoError(t, r, "cat", "/opt/bar")
},
)
}

View File

@ -21,7 +21,6 @@ package e2etests_test
import (
"testing"
"github.com/alecthomas/assert/v2"
"github.com/efficientgo/e2e"
)
@ -31,23 +30,8 @@ func TestE2EIssue53LcAllCInfo(t *testing.T) {
"issue-53-lc-all-c-info",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
err := r.Exec(e2e.NewCommand(
"sudo",
"alr",
"addrepo",
"--name",
"alr-repo",
"--url",
"https://gitea.plemya-x.ru/Plemya-x/alr-repo.git",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand(
"bash",
"-c",
"LANG=C alr info alr-bin",
))
assert.NoError(t, err)
defaultPrepare(t, r)
execShouldNoError(t, r, "bash", "-c", "LANG=C alr info foo-pkg")
},
)
}

View File

@ -21,7 +21,6 @@ package e2etests_test
import (
"testing"
"github.com/alecthomas/assert/v2"
"github.com/efficientgo/e2e"
)
@ -31,28 +30,11 @@ func TestE2EIssue59RmCompletion(t *testing.T) {
"issue-59-rm-completion",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
err := r.Exec(e2e.NewCommand(
"sudo",
"alr",
"addrepo",
"--name",
"alr-repo",
"--url",
"https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand(
"sudo", "alr", "in", "foo-pkg", "bar-pkg",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand("sh", "-c", "alr rm --generate-bash-completion | grep ^foo-pkg$"))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand("sh", "-c", "alr rm --generate-bash-completion | grep ^bar-pkg$"))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand("sh", "-c", "alr rm --generate-bash-completion | grep ^test-autoreq-autoprov$"))
assert.Error(t, err)
defaultPrepare(t, r)
execShouldNoError(t, r, "sudo", "alr", "in", "foo-pkg", "bar-pkg")
execShouldNoError(t, r, "sh", "-c", "alr rm --generate-bash-completion | grep ^foo-pkg$")
execShouldNoError(t, r, "sh", "-c", "alr rm --generate-bash-completion | grep ^bar-pkg$")
execShouldError(t, r, "sh", "-c", "alr rm --generate-bash-completion | grep ^test-autoreq-autoprov$")
},
)
}

View File

@ -21,7 +21,6 @@ package e2etests_test
import (
"testing"
"github.com/alecthomas/assert/v2"
"github.com/efficientgo/e2e"
)
@ -31,21 +30,8 @@ func TestE2EIssue72InstallWithDeps(t *testing.T) {
"issue-72-install-with-deps",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
err := r.Exec(e2e.NewCommand(
"sudo",
"alr",
"addrepo",
"--name",
"alr-repo",
"--url",
"https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand(
"sudo", "alr", "in", "test-app-with-lib",
))
assert.NoError(t, err)
defaultPrepare(t, r)
execShouldNoError(t, r, "sudo", "alr", "in", "test-app-with-lib")
},
)
}

View File

@ -0,0 +1,43 @@
// 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 TestE2EIssue74Upgradable(t *testing.T) {
dockerMultipleRun(
t,
"issue-74-upgradable",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
defaultPrepare(t, r)
execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", "alr-repo", "bd26236cd7")
execShouldNoError(t, r, "alr", "ref")
execShouldNoError(t, r, "sudo", "alr", "in", "bar-pkg")
execShouldNoError(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 0 || exit 1")
execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", "alr-repo", "d9a3541561")
execShouldNoError(t, r, "sudo", "alr", "ref")
execShouldNoError(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 1 || exit 1")
},
)
}

View File

@ -21,7 +21,6 @@ package e2etests_test
import (
"testing"
"github.com/alecthomas/assert/v2"
"github.com/efficientgo/e2e"
)
@ -31,32 +30,9 @@ func TestE2EIssue75InstallWithDeps(t *testing.T) {
"issue-75-ref-specify",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
err := r.Exec(e2e.NewCommand(
"sudo",
"alr",
"addrepo",
"--name",
"alr-repo",
"--url",
"https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand(
"sudo", "alr", "ref",
))
assert.NoError(t, err)
// TODO: replace with alr command when it be added
err = r.Exec(e2e.NewCommand(
"sudo", "sh", "-c", "sed -i 's/ref = .*/ref = \"bd26236cd7\"/' /etc/alr/alr.toml",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand(
"sh", "-c", "test $(alr list | wc -l) -eq 2 || exit 1",
))
assert.NoError(t, err)
defaultPrepare(t, r)
execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", "alr-repo", "bd26236cd7")
execShouldNoError(t, r, "sh", "-c", "test $(alr list | wc -l) -eq 2 || exit 1")
},
)
}

View File

@ -0,0 +1,38 @@
// 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 TestE2EIssue81MultiplePackages(t *testing.T) {
dockerMultipleRun(
t,
"issue-81-multiple-packages",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
defaultPrepare(t, r)
execShouldNoError(t, r, "sudo", "alr", "in", "first-package-with-dashes")
execShouldNoError(t, r, "cat", "/opt/first-package")
},
)
}

View File

@ -0,0 +1,40 @@
// 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 TestE2EIssue91MultiplePackages(t *testing.T) {
dockerMultipleRun(
t,
"issue-91-set-repo-ref",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
defaultPrepare(t, r)
execShouldError(t, r, "sudo", "alr", "repo", "set-ref")
execShouldError(t, r, "sudo", "alr", "repo", "set-ref", "alr-repo")
execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", "alr-repo", "bd26236cd7")
execShouldNoError(t, r, "sh", "-c", "test $(alr list | wc -l) -eq 2 || exit 1")
},
)
}

View File

@ -21,6 +21,7 @@ import (
"fmt"
"log/slog"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2"
)
@ -61,3 +62,11 @@ func FormatCliExitWithCode(msg string, err error, exitCode int) cli.ExitCoder {
}
return cli.Exit(fmt.Errorf("%s: %w", msg, err), exitCode)
}
func WarnLegacyCommand(newSyntax string) {
slog.Warn(
gotext.Get(
"This command is deprecated and would be removed in the future, use \"%s\" instead!", newSyntax,
),
)
}

View File

@ -104,7 +104,7 @@ func Resolve(info *distro.OSRelease, opts *Opts) ([]string, error) {
out = append(out, opts.Name)
for index, item := range out {
out[index] = strings.TrimPrefix(strings.ReplaceAll(item, "-", "_"), "_")
out[index] = strings.TrimPrefix(item, "_")
}
return out, nil

View File

@ -38,23 +38,23 @@ msgstr ""
msgid "Cannot get absolute script path"
msgstr ""
#: build.go:148
#: build.go:152
msgid "Package not found"
msgstr ""
#: build.go:161
#: build.go:165
msgid "Nothing to build"
msgstr ""
#: build.go:218
#: build.go:222
msgid "Error building package"
msgstr ""
#: build.go:225
#: build.go:229
msgid "Error moving the package"
msgstr ""
#: build.go:229
#: build.go:233
msgid "Done"
msgstr ""
@ -278,6 +278,12 @@ msgstr ""
msgid "OPTIONS"
msgstr ""
#: internal/cliutils/utils.go:69
msgid ""
"This command is deprecated and would be removed in the future, use \"%s\" "
"instead!"
msgstr ""
#: internal/db/db.go:137
msgid "Database version mismatch; resetting"
msgstr ""
@ -327,10 +333,30 @@ msgstr ""
msgid "You need to be root to perform this action"
msgstr ""
#: list.go:41
#: list.go:43
msgid "List ALR repo packages"
msgstr ""
#: list.go:57
msgid "Format output using a Go template"
msgstr ""
#: list.go:89
msgid "Error getting packages for upgrade"
msgstr ""
#: list.go:92
msgid "No packages for upgrade"
msgstr ""
#: list.go:102 list.go:187
msgid "Error parsing format template"
msgstr ""
#: list.go:108 list.go:191
msgid "Error executing template"
msgstr ""
#: main.go:45
msgid "Print the current ALR version and exit"
msgstr ""
@ -343,11 +369,11 @@ msgstr ""
msgid "Enable interactive questions and prompts"
msgstr ""
#: main.go:145
#: main.go:146
msgid "Show help"
msgstr ""
#: main.go:149
#: main.go:150
msgid "Error while running app"
msgstr ""
@ -397,82 +423,102 @@ msgstr ""
msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr ""
#: pkg/build/script_executor.go:237
#: pkg/build/script_executor.go:241
msgid "Building package metadata"
msgstr ""
#: pkg/build/script_executor.go:368
#: pkg/build/script_executor.go:372
msgid "Executing prepare()"
msgstr ""
#: pkg/build/script_executor.go:377
#: pkg/build/script_executor.go:381
msgid "Executing build()"
msgstr ""
#: pkg/build/script_executor.go:406 pkg/build/script_executor.go:426
#: pkg/build/script_executor.go:410 pkg/build/script_executor.go:430
msgid "Executing %s()"
msgstr ""
#: pkg/repos/pull.go:80
#: pkg/repos/pull.go:77
msgid "Pulling repository"
msgstr ""
#: pkg/repos/pull.go:116
#: pkg/repos/pull.go:113
msgid "Repository up to date"
msgstr ""
#: pkg/repos/pull.go:207
#: pkg/repos/pull.go:204
msgid "Git repository does not appear to be a valid ALR repo"
msgstr ""
#: pkg/repos/pull.go:223
#: 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 ""
#: refresh.go:30
msgid "Pull all repositories that have changed"
msgstr ""
#: repo.go:39
msgid "Add a new repository"
msgid "Manage repos"
msgstr ""
#: repo.go:46
msgid "Name of the new repo"
msgstr ""
#: repo.go:52
msgid "URL of the new repo"
msgstr ""
#: repo.go:75
msgid "Repo \"%s\" already exists"
msgstr ""
#: repo.go:86 repo.go:159
msgid "Error saving config"
msgstr ""
#: repo.go:112
#: repo.go:51 repo.go:269
msgid "Remove an existing repository"
msgstr ""
#: repo.go:119
msgid "Name of the repo to be deleted"
#: repo.go:53
msgid "<name>"
msgstr ""
#: repo.go:148
#: repo.go:83
msgid "Repo \"%s\" does not exist"
msgstr ""
#: repo.go:155
#: repo.go:90
msgid "Error removing repo directory"
msgstr ""
#: repo.go:178
#: repo.go:94 repo.go:161 repo.go:219
msgid "Error saving config"
msgstr ""
#: repo.go:113
msgid "Error removing packages from database"
msgstr ""
#: repo.go:189
msgid "Pull all repositories that have changed"
#: repo.go:124 repo.go:239
msgid "Add a new repository"
msgstr ""
#: repo.go:125
msgid "<name> <url>"
msgstr ""
#: repo.go:150
msgid "Repo \"%s\" already exists"
msgstr ""
#: repo.go:187
msgid "Set the reference of the repository"
msgstr ""
#: repo.go:188
msgid "<name> <ref>"
msgstr ""
#: repo.go:246
msgid "Name of the new repo"
msgstr ""
#: repo.go:252
msgid "URL of the new repo"
msgstr ""
#: repo.go:276
msgid "Name of the repo to be deleted"
msgstr ""
#: search.go:40
@ -495,22 +541,10 @@ msgstr ""
msgid "Search by provides"
msgstr ""
#: search.go:71
msgid "Format output using a Go template"
msgstr ""
#: search.go:130
msgid "Error while executing search"
msgstr ""
#: search.go:138
msgid "Error parsing format template"
msgstr ""
#: search.go:153
msgid "Error executing template"
msgstr ""
#: upgrade.go:47
msgid "Upgrade all installed packages"
msgstr ""

View File

@ -5,15 +5,15 @@
msgid ""
msgstr ""
"Project-Id-Version: unnamed project\n"
"PO-Revision-Date: 2025-04-27 18:27+0300\n"
"PO-Revision-Date: 2025-05-16 20:47+0300\n"
"Last-Translator: Maxim Slipenko <maks1ms@alt-gnome.ru>\n"
"Language-Team: Russian\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Generator: Gtranslator 48.0\n"
#: build.go:42
@ -45,23 +45,23 @@ msgstr "Ошибка при получении рабочего каталога
msgid "Cannot get absolute script path"
msgstr "Невозможно получить абсолютный путь к скрипту"
#: build.go:148
#: build.go:152
msgid "Package not found"
msgstr "Пакет не найден"
#: build.go:161
#: build.go:165
msgid "Nothing to build"
msgstr "Нечего собирать"
#: build.go:218
#: build.go:222
msgid "Error building package"
msgstr "Ошибка при сборке пакета"
#: build.go:225
#: build.go:229
msgid "Error moving the package"
msgstr "Ошибка при перемещении пакета"
#: build.go:229
#: build.go:233
msgid "Done"
msgstr "Сделано"
@ -285,6 +285,14 @@ msgstr "КАТЕГОРИЯ"
msgid "OPTIONS"
msgstr "ПАРАМЕТРЫ"
#: internal/cliutils/utils.go:69
msgid ""
"This command is deprecated and would be removed in the future, use \"%s\" "
"instead!"
msgstr ""
"Эта команда устарела и будет удалена в будущем, используйте вместо нее "
"\"%s\"!"
#: internal/db/db.go:137
msgid "Database version mismatch; resetting"
msgstr "Несоответствие версий базы данных; сброс настроек"
@ -335,10 +343,30 @@ msgstr "Вы должны быть членом %s чтобы выполнить
msgid "You need to be root to perform this action"
msgstr "Вы должны быть root чтобы выполнить это"
#: list.go:41
#: list.go:43
msgid "List ALR repo packages"
msgstr "Список пакетов репозитория ALR"
#: list.go:57
msgid "Format output using a Go template"
msgstr "Формат выходных данных с использованием шаблона Go"
#: list.go:89
msgid "Error getting packages for upgrade"
msgstr "Ошибка при получении пакетов для обновления"
#: list.go:92
msgid "No packages for upgrade"
msgstr "Нет пакетов к обновлению"
#: list.go:102 list.go:187
msgid "Error parsing format template"
msgstr "Ошибка при разборе шаблона"
#: list.go:108 list.go:191
msgid "Error executing template"
msgstr "Ошибка при выполнении шаблона"
#: main.go:45
msgid "Print the current ALR version and exit"
msgstr "Показать текущую версию ALR и выйти"
@ -351,11 +379,11 @@ msgstr "Аргументы, которые будут переданы мене
msgid "Enable interactive questions and prompts"
msgstr "Включение интерактивных вопросов и запросов"
#: main.go:145
#: main.go:146
msgid "Show help"
msgstr "Показать справку"
#: main.go:149
#: main.go:150
msgid "Error while running app"
msgstr "Ошибка при запуске приложения"
@ -409,35 +437,35 @@ msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr ""
"AutoReq не реализовано для этого формата пакета, поэтому будет пропущено"
#: pkg/build/script_executor.go:237
#: pkg/build/script_executor.go:241
msgid "Building package metadata"
msgstr "Сборка метаданных пакета"
#: pkg/build/script_executor.go:368
#: pkg/build/script_executor.go:372
msgid "Executing prepare()"
msgstr "Выполнение prepare()"
#: pkg/build/script_executor.go:377
#: pkg/build/script_executor.go:381
msgid "Executing build()"
msgstr "Выполнение build()"
#: pkg/build/script_executor.go:406 pkg/build/script_executor.go:426
#: pkg/build/script_executor.go:410 pkg/build/script_executor.go:430
msgid "Executing %s()"
msgstr "Выполнение %s()"
#: pkg/repos/pull.go:80
#: pkg/repos/pull.go:77
msgid "Pulling repository"
msgstr "Скачивание репозитория"
#: pkg/repos/pull.go:116
#: pkg/repos/pull.go:113
msgid "Repository up to date"
msgstr "Репозиторий уже обновлён"
#: pkg/repos/pull.go:207
#: pkg/repos/pull.go:204
msgid "Git repository does not appear to be a valid ALR repo"
msgstr "Репозиторий Git не поддерживается репозиторием ALR"
#: pkg/repos/pull.go:223
#: 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."
@ -445,49 +473,69 @@ msgstr ""
"Минимальная версия ALR для ALR-репозитория выше текущей версии. Попробуйте "
"обновить ALR, если что-то не работает."
#: refresh.go:30
msgid "Pull all repositories that have changed"
msgstr "Скачать все изменённые репозитории"
#: repo.go:39
msgid "Add a new repository"
msgstr "Добавить новый репозиторий"
msgid "Manage repos"
msgstr "Управление репозиториями"
#: repo.go:46
msgid "Name of the new repo"
msgstr "Название нового репозитория"
#: repo.go:52
msgid "URL of the new repo"
msgstr "URL-адрес нового репозитория"
#: repo.go:75
msgid "Repo \"%s\" already exists"
msgstr "Репозиторий \"%s\" уже существует"
#: repo.go:86 repo.go:159
msgid "Error saving config"
msgstr "Ошибка при сохранении конфигурации"
#: repo.go:112
#: repo.go:51 repo.go:269
msgid "Remove an existing repository"
msgstr "Удалить существующий репозиторий"
#: repo.go:119
msgid "Name of the repo to be deleted"
msgstr "Название репозитория удалён"
#: repo.go:53
msgid "<name>"
msgstr "<имя>"
#: repo.go:148
#: repo.go:83
msgid "Repo \"%s\" does not exist"
msgstr "Репозитория \"%s\" не существует"
#: repo.go:155
#: repo.go:90
msgid "Error removing repo directory"
msgstr "Ошибка при удалении каталога репозитория"
#: repo.go:178
#: repo.go:94 repo.go:161 repo.go:219
msgid "Error saving config"
msgstr "Ошибка при сохранении конфигурации"
#: repo.go:113
msgid "Error removing packages from database"
msgstr "Ошибка при удалении пакетов из базы данных"
#: repo.go:189
msgid "Pull all repositories that have changed"
msgstr "Скачать все изменённые репозитории"
#: repo.go:124 repo.go:239
msgid "Add a new repository"
msgstr "Добавить новый репозиторий"
#: repo.go:125
msgid "<name> <url>"
msgstr "<имя> <url>"
#: repo.go:150
msgid "Repo \"%s\" already exists"
msgstr "Репозиторий \"%s\" уже существует"
#: repo.go:187
msgid "Set the reference of the repository"
msgstr "Установить ссылку на версию репозитория"
#: repo.go:188
msgid "<name> <ref>"
msgstr "<имя> <ссылкааерсию>"
#: repo.go:246
msgid "Name of the new repo"
msgstr "Название нового репозитория"
#: repo.go:252
msgid "URL of the new repo"
msgstr "URL-адрес нового репозитория"
#: repo.go:276
msgid "Name of the repo to be deleted"
msgstr "Название репозитория удалён"
#: search.go:40
msgid "Search packages"
@ -509,22 +557,10 @@ msgstr "Искать по репозиторию"
msgid "Search by provides"
msgstr "Иcкать по provides"
#: search.go:71
msgid "Format output using a Go template"
msgstr "Формат выходных данных с использованием шаблона Go"
#: search.go:130
msgid "Error while executing search"
msgstr "Ошибка при выполнении поиска"
#: search.go:138
msgid "Error parsing format template"
msgstr "Ошибка при разборе шаблона"
#: search.go:153
msgid "Error executing template"
msgstr "Ошибка при выполнении шаблона"
#: upgrade.go:47
msgid "Upgrade all installed packages"
msgstr "Обновить все установленные пакеты"

View File

@ -1,5 +1,5 @@
ALR - Any Linux Repository
Copyright (C) {{ .Year }} Евгений Храмов
Copyright (C) {{ .Year }} 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

72
list.go
View File

@ -22,10 +22,12 @@ package main
import (
"fmt"
"log/slog"
"os"
"slices"
"text/template"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2"
"golang.org/x/exp/slices"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
@ -45,6 +47,15 @@ func ListCmd() *cli.Command {
Name: "installed",
Aliases: []string{"I"},
},
&cli.BoolFlag{
Name: "upgradable",
Aliases: []string{"U"},
},
&cli.StringFlag{
Name: "format",
Aliases: []string{"f"},
Usage: gotext.Get("Format output using a Go template"),
},
},
Action: func(c *cli.Context) error {
if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil {
@ -57,8 +68,10 @@ func ListCmd() *cli.Command {
New(ctx).
WithConfig().
WithDB().
WithManager().
// autoPull only
WithRepos().
WithDistroInfo().
Build()
if err != nil {
return err
@ -67,6 +80,39 @@ func ListCmd() *cli.Command {
cfg := deps.Cfg
db := deps.DB
mgr := deps.Manager
info := deps.Info
if c.Bool("upgradable") {
updates, err := checkForUpdates(ctx, mgr, db, info)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting packages for upgrade"), err)
}
if len(updates) == 0 {
slog.Info(gotext.Get("No packages for upgrade"))
return nil
}
format := c.String("format")
if format == "" {
format = "{{.Package.Repository}}/{{.Package.Name}} {{.FromVersion}} -> {{.ToVersion}}\n"
}
tmpl, err := template.New("format").Parse(format)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error parsing format template"), err)
}
for _, updateInfo := range updates {
err = tmpl.Execute(os.Stdout, updateInfo)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error executing template"), err)
}
}
return nil
}
// TODO: refactor code below
where := "true"
args := []any(nil)
@ -115,17 +161,35 @@ func ListCmd() *cli.Command {
continue
}
version := pkg.Version
type packageInfo struct {
Package *database.Package
Version string
}
pkgInfo := &packageInfo{}
pkgInfo.Package = &pkg
pkgInfo.Version = pkg.Version
if c.Bool("installed") {
instVersion, ok := installedAlrPackages[fmt.Sprintf("%s/%s", pkg.Repository, pkg.Name)]
if !ok {
continue
} else {
version = instVersion
pkgInfo.Version = instVersion
}
}
fmt.Printf("%s/%s %s\n", pkg.Repository, pkg.Name, version)
format := c.String("format")
if format == "" {
format = "{{.Package.Repository}}/{{.Package.Name}} {{.Version}}\n"
}
tmpl, err := template.New("format").Parse(format)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error parsing format template"), err)
}
err = tmpl.Execute(os.Stdout, pkgInfo)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error executing template"), err)
}
}
return nil

View File

@ -74,14 +74,15 @@ func GetApp() *cli.App {
InfoCmd(),
ListCmd(),
BuildCmd(),
AddRepoCmd(),
RemoveRepoCmd(),
LegacyAddRepoCmd(),
LegacyRemoveRepoCmd(),
RefreshCmd(),
FixCmd(),
GenCmd(),
HelperCmd(),
VersionCmd(),
SearchCmd(),
RepoCmd(),
// Internal commands
InternalBuildCmd(),
InternalInstallCmd(),

View File

@ -118,16 +118,20 @@ func (e *LocalScriptExecutor) ExecuteFirstPass(ctx context.Context, input *Build
return vars.Name, varsOfPackages, nil
}
if len(input.packages) == 0 {
return "", nil, errors.New("script has multiple packages but package is not specified")
var pkgNames []string
if len(input.packages) != 0 {
pkgNames = input.packages
} else {
pkgNames = pkgs.Names
}
for _, pkgName := range input.packages {
for _, pkgName := range pkgNames {
var preVars types.BuildVarsPre
funcName := fmt.Sprintf("meta_%s", pkgName)
meta, ok := dec.GetFuncWithSubshell(funcName)
if !ok {
return "", nil, errors.New("func is missing")
return "", nil, fmt.Errorf("func %s is missing", funcName)
}
r, err := meta(ctx)
if err != nil {

38
pkg/parser/parser.go Normal file
View File

@ -0,0 +1,38 @@
// 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 parser
import (
"fmt"
"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
}

View File

@ -43,11 +43,8 @@ import (
"mvdan.cc/sh/v3/syntax"
"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/shutils/decoder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
)
type actionType uint8
@ -231,79 +228,19 @@ func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error {
func (rs *Repos) updatePkg(ctx context.Context, repo types.Repo, runner *interp.Runner, scriptFl io.ReadCloser) error {
parser := syntax.NewParser()
defer scriptFl.Close()
fl, err := parser.Parse(scriptFl, "alr.sh")
pkgs, err := parseScript(ctx, repo, parser, runner, scriptFl)
if err != nil {
return err
}
runner.Reset()
err = runner.Run(ctx, fl)
if err != nil {
return err
}
type packages struct {
BasePkgName string `sh:"basepkg_name"`
Names []string `sh:"name"`
}
var pkgs packages
d := decoder.New(&distro.OSRelease{}, runner)
d.Overrides = false
d.LikeDistros = false
err = d.DecodeVars(&pkgs)
if err != nil {
return err
}
if len(pkgs.Names) > 1 {
if pkgs.BasePkgName == "" {
pkgs.BasePkgName = pkgs.Names[0]
}
for _, pkgName := range pkgs.Names {
pkgInfo := PackageInfo{}
funcName := fmt.Sprintf("meta_%s", pkgName)
runner.Reset()
err = runner.Run(ctx, fl)
if err != nil {
return err
}
meta, ok := d.GetFuncWithSubshell(funcName)
if !ok {
return errors.New("func is missing")
}
r, err := meta(ctx)
if err != nil {
return err
}
d := decoder.New(&distro.OSRelease{}, r)
d.Overrides = false
d.LikeDistros = false
err = d.DecodeVars(&pkgInfo)
if err != nil {
return err
}
pkg := pkgInfo.ToPackage(repo.Name)
resolveOverrides(r, pkg)
pkg.Name = pkgName
pkg.BasePkgName = pkgs.BasePkgName
for _, pkg := range pkgs {
err = rs.db.InsertPackage(ctx, *pkg)
if err != nil {
return err
}
}
return nil
}
pkg := EmptyPackage(repo.Name)
err = d.DecodeVars(pkg)
if err != nil {
return err
}
resolveOverrides(runner, pkg)
return rs.db.InsertPackage(ctx, *pkg)
return nil
}
func (rs *Repos) processRepoChangesRunner(repoDir, scriptDir string) (*interp.Runner, error) {
@ -399,16 +336,17 @@ func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git
return nil
}
var pkg db.Package
err = parseScript(ctx, parser, runner, r, &pkg)
pkgs, err := parseScript(ctx, repo, parser, runner, r)
if err != nil {
return err
}
for _, pkg := range pkgs {
err = rs.db.DeletePkgs(ctx, "name = ? AND repository = ?", pkg.Name, repo.Name)
if err != nil {
return err
}
}
case actionUpdate:
if filepath.Base(action.File) != "alr.sh" {
action.File = filepath.Join(filepath.Dir(action.File), "alr.sh")

View File

@ -18,6 +18,7 @@ package repos
import (
"context"
"errors"
"fmt"
"io"
"path/filepath"
@ -35,7 +36,9 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder"
"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/parser"
)
// isValid makes sure the path of the file being updated is valid.
@ -54,23 +57,85 @@ func isValid(from, to diff.File) bool {
return match
}
func parseScript(ctx context.Context, parser *syntax.Parser, runner *interp.Runner, r io.ReadCloser, pkg *db.Package) error {
defer r.Close()
fl, err := parser.Parse(r, "alr.sh")
func parseScript(
ctx context.Context,
repo types.Repo,
syntaxParser *syntax.Parser,
runner *interp.Runner,
r io.ReadCloser,
) ([]*db.Package, error) {
fl, err := syntaxParser.Parse(r, "alr.sh")
if err != nil {
return err
return nil, err
}
runner.Reset()
err = runner.Run(ctx, fl)
if err != nil {
return err
return nil, err
}
d := decoder.New(&distro.OSRelease{}, runner)
d.Overrides = false
d.LikeDistros = false
return d.DecodeVars(pkg)
pkgNames, err := parser.ParseNames(d)
if err != nil {
return nil, fmt.Errorf("failed parsing package names: %w", err)
}
if len(pkgNames.Names) == 0 {
return nil, errors.New("package name is missing")
}
var dbPkgs []*db.Package
if len(pkgNames.Names) > 1 {
if pkgNames.BasePkgName == "" {
pkgNames.BasePkgName = pkgNames.Names[0]
}
for _, pkgName := range pkgNames.Names {
pkgInfo := PackageInfo{}
funcName := fmt.Sprintf("meta_%s", pkgName)
runner.Reset()
err = runner.Run(ctx, fl)
if err != nil {
return nil, err
}
meta, ok := d.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)
d.Overrides = false
d.LikeDistros = false
err = d.DecodeVars(&pkgInfo)
if err != nil {
return nil, err
}
pkg := pkgInfo.ToPackage(repo.Name)
resolveOverrides(r, pkg)
pkg.Name = pkgName
pkg.BasePkgName = pkgNames.BasePkgName
dbPkgs = append(dbPkgs, pkg)
}
return dbPkgs, nil
}
pkg := EmptyPackage(repo.Name)
err = d.DecodeVars(pkg)
if err != nil {
return nil, err
}
resolveOverrides(runner, pkg)
dbPkgs = append(dbPkgs, pkg)
return dbPkgs, nil
}
type PackageInfo struct {

52
refresh.go Normal file
View File

@ -0,0 +1,52 @@
// 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 (
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
)
func RefreshCmd() *cli.Command {
return &cli.Command{
Name: "refresh",
Usage: gotext.Get("Pull all repositories that have changed"),
Aliases: []string{"ref"},
Action: func(c *cli.Context) error {
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
return err
}
ctx := c.Context
deps, err := appbuilder.
New(ctx).
WithConfig().
WithDB().
WithReposForcePull().
Build()
if err != nil {
return err
}
defer deps.Defer()
return nil
},
}
}

247
repo.go
View File

@ -33,96 +33,31 @@ import (
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
)
func AddRepoCmd() *cli.Command {
func RepoCmd() *cli.Command {
return &cli.Command{
Name: "addrepo",
Usage: gotext.Get("Add a new repository"),
Aliases: []string{"ar"},
Flags: []cli.Flag{
&cli.StringFlag{
Name: "name",
Aliases: []string{"n"},
Required: true,
Usage: gotext.Get("Name of the new repo"),
Name: "repo",
Usage: gotext.Get("Manage repos"),
Subcommands: []*cli.Command{
RemoveRepoCmd(),
AddRepoCmd(),
SetRepoRefCmd(),
},
&cli.StringFlag{
Name: "url",
Aliases: []string{"u"},
Required: true,
Usage: gotext.Get("URL of the new repo"),
},
},
Action: utils.RootNeededAction(func(c *cli.Context) error {
name := c.String("name")
repoURL := c.String("url")
ctx := c.Context
deps, err := appbuilder.
New(ctx).
WithConfig().
Build()
if err != nil {
return err
}
defer deps.Defer()
cfg := deps.Cfg
reposSlice := cfg.Repos()
for _, repo := range reposSlice {
if repo.URL == repoURL || repo.Name == name {
return cliutils.FormatCliExit(gotext.Get("Repo \"%s\" already exists", repo.Name), nil)
}
}
reposSlice = append(reposSlice, types.Repo{
Name: name,
URL: repoURL,
})
cfg.SetRepos(reposSlice)
err = cfg.SaveUserConfig()
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error saving config"), err)
}
if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil {
return err
}
deps, err = appbuilder.
New(ctx).
UseConfig(cfg).
WithDB().
WithReposForcePull().
Build()
if err != nil {
return err
}
defer deps.Defer()
return nil
}),
}
}
func RemoveRepoCmd() *cli.Command {
return &cli.Command{
Name: "removerepo",
Name: "remove",
Usage: gotext.Get("Remove an existing repository"),
Aliases: []string{"rr"},
Flags: []cli.Flag{
&cli.StringFlag{
Name: "name",
Aliases: []string{"n"},
Required: true,
Usage: gotext.Get("Name of the repo to be deleted"),
},
},
Aliases: []string{"rm"},
ArgsUsage: gotext.Get("<name>"),
Action: utils.RootNeededAction(func(c *cli.Context) error {
ctx := c.Context
if c.Args().Len() < 1 {
return cliutils.FormatCliExit("missing args", nil)
}
name := c.Args().Get(0)
name := c.String("name")
ctx := c.Context
deps, err := appbuilder.
New(ctx).
@ -183,21 +118,56 @@ func RemoveRepoCmd() *cli.Command {
}
}
func RefreshCmd() *cli.Command {
func AddRepoCmd() *cli.Command {
return &cli.Command{
Name: "refresh",
Usage: gotext.Get("Pull all repositories that have changed"),
Aliases: []string{"ref"},
Action: func(c *cli.Context) error {
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
return err
Name: "add",
Usage: gotext.Get("Add a new repository"),
ArgsUsage: gotext.Get("<name> <url>"),
Action: utils.RootNeededAction(func(c *cli.Context) error {
if c.Args().Len() < 2 {
return cliutils.FormatCliExit("missing args", nil)
}
name := c.Args().Get(0)
repoURL := c.Args().Get(1)
ctx := c.Context
deps, err := appbuilder.
New(ctx).
WithConfig().
Build()
if err != nil {
return err
}
defer deps.Defer()
cfg := deps.Cfg
reposSlice := cfg.Repos()
for _, repo := range reposSlice {
if repo.URL == repoURL || repo.Name == name {
return cliutils.FormatCliExit(gotext.Get("Repo \"%s\" already exists", repo.Name), nil)
}
}
reposSlice = append(reposSlice, types.Repo{
Name: name,
URL: repoURL,
})
cfg.SetRepos(reposSlice)
err = cfg.SaveUserConfig()
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error saving config"), err)
}
if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil {
return err
}
deps, err = appbuilder.
New(ctx).
UseConfig(cfg).
WithDB().
WithReposForcePull().
Build()
@ -205,7 +175,110 @@ func RefreshCmd() *cli.Command {
return err
}
defer deps.Defer()
return nil
}),
}
}
func SetRepoRefCmd() *cli.Command {
return &cli.Command{
Name: "set-ref",
Usage: gotext.Get("Set the reference of the repository"),
ArgsUsage: gotext.Get("<name> <ref>"),
Action: utils.RootNeededAction(func(c *cli.Context) error {
if c.Args().Len() < 2 {
return cliutils.FormatCliExit("missing args", nil)
}
name := c.Args().Get(0)
ref := c.Args().Get(1)
deps, err := appbuilder.
New(c.Context).
WithConfig().
WithDB().
WithReposNoPull().
Build()
if err != nil {
return err
}
defer deps.Defer()
repos := deps.Cfg.Repos()
newRepos := []types.Repo{}
for _, repo := range repos {
if repo.Name == name {
repo.Ref = ref
}
newRepos = append(newRepos, repo)
}
deps.Cfg.SetRepos(newRepos)
err = deps.Cfg.SaveUserConfig()
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error saving config"), err)
}
err = deps.Repos.Pull(c.Context, newRepos)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error pulling repositories"), err)
}
return nil
}),
}
}
// TODO: remove
//
// Deprecated: use "alr repo add"
func LegacyAddRepoCmd() *cli.Command {
return &cli.Command{
Hidden: true,
Name: "addrepo",
Usage: gotext.Get("Add a new repository"),
Aliases: []string{"ar"},
Flags: []cli.Flag{
&cli.StringFlag{
Name: "name",
Aliases: []string{"n"},
Required: true,
Usage: gotext.Get("Name of the new repo"),
},
&cli.StringFlag{
Name: "url",
Aliases: []string{"u"},
Required: true,
Usage: gotext.Get("URL of the new repo"),
},
},
Action: utils.RootNeededAction(func(c *cli.Context) error {
cliutils.WarnLegacyCommand("alr repo add <name> <url>")
return c.App.RunContext(c.Context, []string{"", "repo", "add", c.String("name"), c.String("url")})
}),
}
}
// TODO: remove
//
// Deprecated: use "alr repo rm"
func LegacyRemoveRepoCmd() *cli.Command {
return &cli.Command{
Hidden: true,
Name: "removerepo",
Usage: gotext.Get("Remove an existing repository"),
Aliases: []string{"rr"},
Flags: []cli.Flag{
&cli.StringFlag{
Name: "name",
Aliases: []string{"n"},
Required: true,
Usage: gotext.Get("Name of the repo to be deleted"),
},
},
Action: utils.RootNeededAction(func(c *cli.Context) error {
cliutils.WarnLegacyCommand("alr repo remove <name>")
return c.App.RunContext(c.Context, []string{"", "repo", "remove", c.String("name")})
}),
}
}

View File

@ -116,7 +116,7 @@ func UpgradeCmd() *cli.Command {
Info: deps.Info,
PkgFormat_: build.GetPkgFormat(deps.Manager),
},
updates,
mapUptatesInfoToPackages(updates),
)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error checking for updates"), err)
@ -130,12 +130,27 @@ func UpgradeCmd() *cli.Command {
}
}
func mapUptatesInfoToPackages(updates []UpdateInfo) []database.Package {
var pkgs []database.Package
for _, info := range updates {
pkgs = append(pkgs, *info.Package)
}
return pkgs
}
type UpdateInfo struct {
Package *database.Package
FromVersion string
ToVersion string
}
func checkForUpdates(
ctx context.Context,
mgr manager.Manager,
db *database.Database,
info *distro.OSRelease,
) ([]database.Package, error) {
) ([]UpdateInfo, error) {
installed, err := mgr.ListInstalled(nil)
if err != nil {
return nil, err
@ -145,7 +160,7 @@ func checkForUpdates(
s := search.New(db)
var out []database.Package
var out []UpdateInfo
for _, pkgName := range pkgNames {
matches := build.RegexpALRPackageName.FindStringSubmatch(pkgName)
if matches != nil {
@ -179,10 +194,13 @@ func checkForUpdates(
}
c := vercmp.Compare(repoVer, installed[pkgName])
if c == 0 || c == -1 {
continue
} else if c == 1 {
out = append(out, pkg)
if c == 1 {
out = append(out, UpdateInfo{
Package: &pkg,
FromVersion: installed[pkgName],
ToVersion: repoVer,
})
}
}