Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
FROM --platform=linux/amd64 archlinux@sha256:c84ad63503efc5d386e30a5f44945fa8eee10fcb21b3d5fceda260783de394ff
FROM archlinux@sha256:c84ad63503efc5d386e30a5f44945fa8eee10fcb21b3d5fceda260783de394ff
LABEL maintainer="Jguer,docker@jguer.space"

ENV GO111MODULE=on
WORKDIR /app

COPY go.mod .

# Disable pacman's landlock/seccomp sandbox: it doesn't work inside BuildKit.
# asciidoc, doxygen, meson needed for pacman-git.
RUN set -eux; \
sed -i 's/^#\?DisableSandbox.*/DisableSandbox/' /etc/pacman.conf || true; \
sed -i 's/^#\?DownloadUser.*/#DownloadUser = alpm/' /etc/pacman.conf || true; \
pacman -Syu --noconfirm --needed \
pacman-contrib zsh git-delta openssh bat go github-cli ripgrep \
pacman-contrib zsh git-delta openssh bat go github-cli ripgrep pre-commit \
archlinux-keyring git gcc make sudo asciidoc doxygen meson curl nodejs npm; \
curl -sSfL https://golangci-lint.run/install.sh | sh -s v2.12.2; \
go mod download; \
rm -rf /var/lib/pacman/sync/* /var/cache/pacman/* /tmp/* /var/tmp/*; \
rm -rf /usr/share/man/* /usr/share/doc/* || true

Expand All @@ -24,8 +21,7 @@ RUN useradd -m -s /bin/bash docker \

USER docker

RUN go install github.com/leonelquinteros/gotext/cli/xgotext@latest \
&& go install golang.org/x/tools/gopls@latest
RUN go install github.com/leonelquinteros/gotext/cli/xgotext@v1.7.2

ENV PATH="/app/bin:/home/docker/go/bin:/home/docker/.local/bin:$PATH"

Expand Down
31 changes: 30 additions & 1 deletion .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,33 @@
name: yay
path: ./yay
if-no-files-found: error
overwrite: true
overwrite: true

e2e:
name: E2E PKGBUILD repositories
runs-on: ubuntu-latest
container:
image: ghcr.io/jguer/yay-builder:latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version-file: go.mod
- uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
# makepkg refuses to run as root, so the e2e suite runs as an
# unprivileged user with passwordless sudo for pacman.
- name: Set up unprivileged build user
run: |
pacman -Sy --needed --noconfirm base-devel git
id -u e2e >/dev/null 2>&1 || useradd -m e2e
echo 'e2e ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/e2e
chmod 0440 /etc/sudoers.d/e2e
chown -R e2e:e2e .
install -d -o e2e -g e2e /home/e2e/go
- name: Run E2E tests
run: su e2e -c "env HOME=/home/e2e GOFLAGS=-buildvcs=false make test-e2e"
Comment on lines +54 to +80
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ test:
test-integration:
$(GO) test -tags=integration $(FLAGS) ./...

# End-to-end tests for PKGBUILD repository support. Requires an Arch Linux
# environment with makepkg/pacman/git and passwordless sudo, run as a non-root
# user (makepkg refuses to run as root). See e2e/README.md.
.PHONY: test-e2e
test-e2e:
./e2e/run.sh

.PHONY: build
build: $(BIN)

Expand Down
47 changes: 46 additions & 1 deletion doc/lua.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,51 @@ startup and reports the offending keys/values so misconfigurations fail fast.
A ready-to-copy example
lives at [`doc/init.lua`](init.lua).

## PKGBUILD repositories with `yay.opt.pkgbuild_repos`

A PKGBUILD repository is a directory of PKGBUILDs — local or served over git —
that yay treats much like the AUR. Packages found in a PKGBUILD repository
**take priority over the AUR**, so a repository can mask an AUR package with a
custom build. Installed packages owned by a repository are upgraded from it on
`yay -Syu` (or `yay -Sua`) instead of the AUR. PKGBUILD repositories are
configured only through `init.lua`.

Assign a table keyed by repository name. Each entry has a `url` and an optional
`depth`:

```lua
yay.opt.pkgbuild_repos = {
["yay-pkgbuild"] = {
url = "https://github.com/Jguer/yay-PKGBUILD",
depth = 3,
},
-- A local git repository:
["local-git"] = { url = "git+file:///srv/pkgbuild-repo" },
-- A plain local directory, used in place with no clone or refresh:
["scratch"] = { url = "file:///home/user/pkgbuilds" },
}
```

### `url`

The repository location, following the makepkg source convention. The scheme
decides how yay treats it:

- `https://`, `ssh://`, a `.git` suffix, or a `git+…` prefix are cloned into
yay's build cache. The clone is created on first use and pulled again on
`yay -Sy`, so routine installs work offline. Use authenticated transports;
insecure `http://` and `git://` URLs are not supported.
- `file://…` points at a local directory that is scanned in place — no clone,
no refresh.

### `depth`

How many directory levels below the repository root yay scans for PKGBUILDs.
Defaults to `3`. A repository is a directory tree of package directories, each
containing a `PKGBUILD` and a committed `.SRCINFO`. yay never generates
`.SRCINFO` while indexing: `makepkg --printsrcinfo` executes PKGBUILD content,
so metadata generation belongs in the repository's reviewed build workflow.

## Logging with `yay.log`

<p class="api-since">Available from yay v13.0.0</p>
Expand Down Expand Up @@ -405,7 +450,7 @@ logs the message but cannot roll back anything.
name = "pkgname",
version = "1.2.3-1", -- resolved version
local_version = "1.0.0-1", -- previously installed ("" if not installed)
source = "aur", -- "aur" | "sync" | "local" | "srcinfo" | "missing"
source = "aur", -- "aur" | "sync" | "local" | "srcinfo" | "pkgbuild_repo" | "missing"
reason = "explicit", -- "explicit" | "dependency" | "make_dependency" | "check_dependency" | "unknown"
},
-- one entry per package yay resolved; sorted alphabetically
Expand Down
33 changes: 33 additions & 0 deletions e2e/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# End-to-end tests

`run.sh` exercises PKGBUILD repository support (`yay.opt.pkgbuild_repos`) against
real `makepkg` and `pacman`, rather than mocks. It builds `yay` and then:

1. **`file://` repository** — installs a package from a local directory
repository used in place.
2. **`-Sua` upgrade** — bumps that package and upgrades it from the repository.
3. **`git+file://` repository** — installs a package from a local git
repository, which yay clones into its cache.

Each package is trivial (`arch=('any')`, no sources) and does not exist in the
AUR, so a successful install proves the package was resolved from the configured
repository. The test packages (`yay-e2e-file`, `yay-e2e-git`) are removed on
exit.

## Requirements

- An Arch Linux environment with `makepkg`, `pacman`, `fakeroot`, `git`.
- Passwordless `sudo` (yay runs `sudo pacman` to install built packages).
- A **non-root** user — `makepkg` refuses to run as root.

## Running

```sh
make test-e2e
# or, against an existing binary:
YAY_BIN=/path/to/yay ./e2e/run.sh
```

CI runs this via the `E2E PKGBUILD repositories` job in
`.github/workflows/testing.yml`, using the Arch-based `yay-builder` image and an
unprivileged user.
14 changes: 14 additions & 0 deletions e2e/fixtures/PKGBUILD.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Template PKGBUILD for yay PKGBUILD-repository e2e tests.
# @PKGNAME@ and @PKGVER@ are substituted by e2e/run.sh. The package builds
# instantly (no sources, no compilation) and just drops a marker file so a
# successful install proves the package came from the configured repository.
pkgname=@PKGNAME@
pkgver=@PKGVER@
pkgrel=1
pkgdesc="yay e2e PKGBUILD repository test package"
arch=('any')
license=('MIT')

package() {
install -Dm644 /dev/null "$pkgdir/usr/share/@PKGNAME@/marker"
}
146 changes: 146 additions & 0 deletions e2e/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#!/usr/bin/env bash
#
# End-to-end tests for PKGBUILD repository support (yay.opt.pkgbuild_repos).
#
# Builds yay, then drives real makepkg/pacman installs and upgrades from local
# file:// and git+file:// PKGBUILD repositories, asserting the results. A
# successful install proves the package was resolved from the configured
# repository (the test packages do not exist in the AUR).
#
# Requirements: an Arch Linux environment with makepkg, pacman, git and
# passwordless sudo, run as a NON-root user (makepkg refuses to run as root).
#
# Usage:
# e2e/run.sh # builds yay itself
# YAY_BIN=/path/to/yay e2e/run.sh # uses an existing binary
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
readonly REPO_ROOT
readonly FIXTURES="${REPO_ROOT}/e2e/fixtures"

GO="${GO:-go}"
YAY_BIN="${YAY_BIN:-}"

fail() {
echo "E2E FAIL: $*" >&2
exit 1
}

info() {
echo "==> $*"
}

# --- preconditions --------------------------------------------------------

[[ ${EUID} -ne 0 ]] || fail "must run as a non-root user (makepkg refuses to run as root)"

for tool in makepkg pacman fakeroot git sudo; do
command -v "${tool}" >/dev/null 2>&1 || fail "missing required tool: ${tool}"
done

sudo -n true 2>/dev/null || fail "passwordless sudo is required"

# --- workspace ------------------------------------------------------------

WORK="$(mktemp -d)"
readonly WORK
INSTALLED=()

cleanup() {
if [[ ${#INSTALLED[@]} -gt 0 ]]; then
sudo pacman -R --noconfirm "${INSTALLED[@]}" >/dev/null 2>&1 || true
fi
rm -rf "${WORK}"
}
trap cleanup EXIT

if [[ -z "${YAY_BIN}" ]]; then
info "building yay"
YAY_BIN="${WORK}/yay"
(cd "${REPO_ROOT}" && "${GO}" build -o "${YAY_BIN}" .)
fi
readonly YAY_BIN

# Isolate config and cache so the host environment is untouched.
export XDG_CONFIG_HOME="${WORK}/config"
export XDG_CACHE_HOME="${WORK}/cache"
mkdir -p "${XDG_CONFIG_HOME}/yay"

# --- helpers --------------------------------------------------------------

# make_pkg <repo_dir> <pkgname> <pkgver>
# Instantiates the fixture PKGBUILD into repo_dir/pkgname with a fresh .SRCINFO,
# clearing any previous build artifacts so upgrades rebuild cleanly.
make_pkg() {
local repo_dir="$1" name="$2" version="$3"
local pkg_dir="${repo_dir}/${name}"

mkdir -p "${pkg_dir}"
rm -rf "${pkg_dir}"/pkg "${pkg_dir}"/src "${pkg_dir}"/*.pkg.tar* 2>/dev/null || true
sed -e "s/@PKGNAME@/${name}/g" -e "s/@PKGVER@/${version}/g" \
"${FIXTURES}/PKGBUILD.in" >"${pkg_dir}/PKGBUILD"
(cd "${pkg_dir}" && makepkg --printsrcinfo >.SRCINFO)
}

# write_config <repo_name> <url>
write_config() {
cat >"${XDG_CONFIG_HOME}/yay/init.lua" <<EOF
yay.opt.clean_menu = false
yay.opt.diff_menu = false
yay.opt.edit_menu = false
yay.opt.pkgbuild_repos = { ["$1"] = { url = "$2" } }
EOF
}

# assert_version <pkgname> <expected version-rel>
assert_version() {
local name="$1" want="$2" got
got="$(pacman -Q "${name}" 2>/dev/null | awk '{print $2}')" || fail "${name} is not installed"
[[ "${got}" == "${want}" ]] || fail "${name}: expected version ${want}, got ${got}"
info "OK: ${name} ${got}"
}

# --- scenario 1: install from a file:// local directory repository --------

info "scenario 1: install from a file:// local directory repository"
FILE_REPO="${WORK}/file-repo"
make_pkg "${FILE_REPO}" "yay-e2e-file" "1"
write_config "file-repo" "file://${FILE_REPO}"

sudo pacman -R --noconfirm yay-e2e-file >/dev/null 2>&1 || true
"${YAY_BIN}" -S yay-e2e-file --noconfirm
INSTALLED+=("yay-e2e-file")
assert_version yay-e2e-file "1-1"

# --- scenario 2: upgrade a repo package with -Sua -------------------------

info "scenario 2: upgrade a PKGBUILD-repo package with -Sua"
make_pkg "${FILE_REPO}" "yay-e2e-file" "2"
"${YAY_BIN}" -Sua --noconfirm
assert_version yay-e2e-file "2-1"

# --- scenario 3: install from a git+file:// repository --------------------

info "scenario 3: install from a git+file:// repository"
GIT_REPO="${WORK}/git-repo"
make_pkg "${GIT_REPO}" "yay-e2e-git" "1"
git -C "${GIT_REPO}" init -q
git -C "${GIT_REPO}" config user.email "e2e@example.invalid"
git -C "${GIT_REPO}" config user.name "yay e2e"
git -C "${GIT_REPO}" config commit.gpgsign false
git -C "${GIT_REPO}" add -A
git -C "${GIT_REPO}" commit -qm "init"

write_config "git-repo" "git+file://${GIT_REPO}"

sudo pacman -R --noconfirm yay-e2e-git >/dev/null 2>&1 || true
"${YAY_BIN}" -S yay-e2e-git --noconfirm
INSTALLED+=("yay-e2e-git")
assert_version yay-e2e-git "1-1"

[[ -d "${XDG_CACHE_HOME}/yay/.pkgbuild-repos/git-repo/.git" ]] ||
fail "git repository was not cloned into the cache"
info "OK: git repository cloned into cache"

echo "E2E PASS: all PKGBUILD repository scenarios succeeded"
7 changes: 7 additions & 0 deletions local_install.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ func installLocalPKGBUILD(
grapher := dep.NewGrapher(dbExecutor, aurCache, false, settings.NoConfirm,
cmdArgs.ExistsDouble("d", "nodeps"), noCheck, cmdArgs.ExistsArg("needed"),
run.Logger.Child("grapher"))

pkgbuildRepos, err := loadPkgbuildRepoIndex(ctx, run, false)
if err != nil {
return fmt.Errorf("%s: %w", gotext.Get("error loading PKGBUILD repositories"), err)
}
grapher.SetPkgbuildRepos(pkgbuildRepos)

graph, err := grapher.GraphFromSrcInfos(ctx, nil, srcInfos)
if err != nil {
return err
Expand Down
7 changes: 7 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ func main() {
defer luaEngine.Close()
}

if err = cfg.NormalizePkgbuildRepos(); err != nil {
fallbackLog.Errorln(err)
ret = 1

return
}

cmdArgs := parser.MakeArguments()

// Parse command line
Expand Down
7 changes: 7 additions & 0 deletions meta/yay.d.lua
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@
---@field debug boolean Enable debug logging and local init.lua lookup convenience.
---@field rpc boolean Use AUR RPC for dependency/query operations.
---@field double_confirm boolean Ask for confirmation before and after builds during upgrades.
---@field pkgbuild_repos table<string, yay.PkgbuildRepo> Named PKGBUILD repositories that take priority over the AUR. init.lua only.

-- PKGBUILD repositories: yay.opt.pkgbuild_repos

---@class yay.PkgbuildRepo
---@field url string Repo location, following the makepkg source convention: an https git URL, a git+file:// local git repo, or a file:// local directory used in place.
---@field depth? integer Recursive PKGBUILD scan depth (default 3).

-- Logging: yay.log

Expand Down
Loading
Loading