mirror of
https://github.com/Jguer/yay.git
synced 2026-07-16 18:58:36 -04:00
feat: Add AURPostDownload hook (#2856)
* run hooks after source download * lint * nitpick: remove event table indirection
This commit is contained in:
14
doc/init.lua
14
doc/init.lua
@@ -73,7 +73,7 @@ yay.opt.double_confirm = true -- Ask for confirmation before and after builds du
|
||||
-- end
|
||||
-- end
|
||||
--
|
||||
-- return { exclude = exclude, skip_menu = true }
|
||||
-- return { exclude = exclude, skip_menu = false }
|
||||
-- end,
|
||||
-- })
|
||||
--
|
||||
@@ -95,3 +95,15 @@ yay.opt.double_confirm = true -- Ask for confirmation before and after builds du
|
||||
-- -- f:close()
|
||||
-- end,
|
||||
-- })
|
||||
--
|
||||
-- Run Lua after yay downloads/verifies package sources and before builds or
|
||||
-- installs. AURPostDownload receives the same payload shape as AURPreInstall.
|
||||
--
|
||||
-- yay.create_autocmd("AURPostDownload", {
|
||||
-- desc = "block forbidden source URLs after download",
|
||||
-- callback = function(event)
|
||||
-- if event.data.pkgbuild:match("forbidden.example") then
|
||||
-- yay.abort(event.match .. ": forbidden source URL")
|
||||
-- end
|
||||
-- end,
|
||||
-- })
|
||||
|
||||
48
doc/lua.md
48
doc/lua.md
@@ -83,7 +83,7 @@ yay.create_autocmd("UpgradeSelect", {
|
||||
end
|
||||
end
|
||||
|
||||
return { exclude = exclude, skip_menu = true }
|
||||
return { exclude = exclude, skip_menu = false }
|
||||
end,
|
||||
})
|
||||
```
|
||||
@@ -244,3 +244,49 @@ yay.create_autocmd("AURPreInstall", {
|
||||
end,
|
||||
})
|
||||
```
|
||||
|
||||
## AUR post-download hooks
|
||||
|
||||
`AURPostDownload` runs once per AUR package base, in sorted package-base order,
|
||||
after yay runs `makepkg --verifysource` for package sources and before
|
||||
compatibility checks, PGP key import prompts, builds, or package installs.
|
||||
|
||||
Use `yay.abort("message")` to stop the operation without a Lua traceback.
|
||||
`AURPostDownload` receives the same payload shape as `AURPreInstall`; only the
|
||||
`event` value differs.
|
||||
|
||||
### AURPostDownload event
|
||||
|
||||
The callback receives this table:
|
||||
|
||||
```lua
|
||||
{
|
||||
event = "AURPostDownload",
|
||||
match = "pkgbase",
|
||||
data = {
|
||||
base = "pkgbase",
|
||||
dir = "/path/to/build/pkgbase",
|
||||
pkgbuild_path = "/path/to/build/pkgbase/PKGBUILD",
|
||||
srcinfo_path = "/path/to/build/pkgbase/.SRCINFO",
|
||||
pkgbuild = "...PKGBUILD contents...",
|
||||
version = "1:1.2.3-4",
|
||||
last_modified = 1700000000,
|
||||
installed = true,
|
||||
packages = { ... },
|
||||
srcinfo = { ... },
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
```lua
|
||||
yay.create_autocmd("AURPostDownload", {
|
||||
desc = "block forbidden source URLs after download",
|
||||
callback = function(event)
|
||||
if event.data.pkgbuild:match("forbidden.example") then
|
||||
yay.abort(event.match .. ": forbidden source URL")
|
||||
end
|
||||
end,
|
||||
})
|
||||
```
|
||||
|
||||
@@ -37,3 +37,13 @@ func luaAbortError(err error) (abortError, bool) {
|
||||
|
||||
return abortErr, ok
|
||||
}
|
||||
|
||||
// wrapLuaErr strips the gopher-lua API wrapper from abort errors so callers
|
||||
// see the clean abort message instead of a Lua traceback.
|
||||
func wrapLuaErr(err error) error {
|
||||
if abortErr, ok := luaAbortError(err); ok {
|
||||
return abortErr
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -8,8 +8,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
EventAURPreInstall = "AURPreInstall"
|
||||
EventUpgradeSelect = "UpgradeSelect"
|
||||
EventAURPreInstall = "AURPreInstall"
|
||||
EventAURPostDownload = "AURPostDownload"
|
||||
EventUpgradeSelect = "UpgradeSelect"
|
||||
)
|
||||
|
||||
type Autocmd struct {
|
||||
@@ -82,7 +83,7 @@ type UpgradeSelectResult struct {
|
||||
|
||||
func (e *Engine) createAutocmd(state *glua.LState) int {
|
||||
event := state.CheckString(1)
|
||||
if event != EventAURPreInstall && event != EventUpgradeSelect {
|
||||
if event != EventAURPreInstall && event != EventAURPostDownload && event != EventUpgradeSelect {
|
||||
state.ArgError(1, fmt.Sprintf("unsupported event %q", event))
|
||||
return 0
|
||||
}
|
||||
@@ -128,13 +129,26 @@ func (e *Engine) RunAURPreInstall(event *AURPreInstallEvent) error {
|
||||
Fn: autocmd.callback,
|
||||
NRet: 0,
|
||||
Protect: true,
|
||||
}, e.aurPreInstallTable(event)); err != nil {
|
||||
wrapped := err
|
||||
if abortErr, ok := luaAbortError(err); ok {
|
||||
wrapped = abortErr
|
||||
}
|
||||
}, e.aurEventTable(EventAURPreInstall, event)); err != nil {
|
||||
return fmt.Errorf("%s %s: %w", EventAURPreInstall, event.Base, wrapLuaErr(err))
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("%s %s: %w", EventAURPreInstall, event.Base, wrapped)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Engine) RunAURPostDownload(event *AURPreInstallEvent) error {
|
||||
if !e.HasAutocmd(EventAURPostDownload) {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, autocmd := range e.autocmds[EventAURPostDownload] {
|
||||
if err := e.L.CallByParam(glua.P{
|
||||
Fn: autocmd.callback,
|
||||
NRet: 0,
|
||||
Protect: true,
|
||||
}, e.aurEventTable(EventAURPostDownload, event)); err != nil {
|
||||
return fmt.Errorf("%s %s: %w", EventAURPostDownload, event.Base, wrapLuaErr(err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,12 +173,7 @@ func (e *Engine) RunUpgradeSelect(event *UpgradeSelectEvent) (UpgradeSelectResul
|
||||
NRet: 1,
|
||||
Protect: true,
|
||||
}, e.upgradeSelectTable(event)); err != nil {
|
||||
wrapped := err
|
||||
if abortErr, ok := luaAbortError(err); ok {
|
||||
wrapped = abortErr
|
||||
}
|
||||
|
||||
return result, fmt.Errorf("%s: %w", EventUpgradeSelect, wrapped)
|
||||
return result, fmt.Errorf("%s: %w", EventUpgradeSelect, wrapLuaErr(err))
|
||||
}
|
||||
|
||||
value := e.L.Get(-1)
|
||||
@@ -190,12 +199,12 @@ func (e *Engine) RunUpgradeSelect(event *UpgradeSelectEvent) (UpgradeSelectResul
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (e *Engine) aurPreInstallTable(event *AURPreInstallEvent) *glua.LTable {
|
||||
func (e *Engine) aurEventTable(eventName string, event *AURPreInstallEvent) *glua.LTable {
|
||||
state := e.L
|
||||
eventTable := state.NewTable()
|
||||
data := state.NewTable()
|
||||
|
||||
eventTable.RawSetString("event", glua.LString(EventAURPreInstall))
|
||||
eventTable.RawSetString("event", glua.LString(eventName))
|
||||
eventTable.RawSetString("match", glua.LString(event.Base))
|
||||
eventTable.RawSetString("data", data)
|
||||
|
||||
@@ -312,18 +321,19 @@ func (e *Engine) parseUpgradeSelectResult(value glua.LValue, validExcludes mapse
|
||||
return
|
||||
}
|
||||
|
||||
name, ok := val.(glua.LString)
|
||||
lname, ok := val.(glua.LString)
|
||||
if !ok {
|
||||
parseErr = fmt.Errorf("exclude entries must be strings")
|
||||
return
|
||||
}
|
||||
|
||||
if !validExcludes.Contains(string(name)) {
|
||||
parseErr = fmt.Errorf("unknown upgrade exclusion %q", string(name))
|
||||
name := string(lname)
|
||||
if !validExcludes.Contains(name) {
|
||||
parseErr = fmt.Errorf("unknown upgrade exclusion %q", name)
|
||||
return
|
||||
}
|
||||
|
||||
result.Exclude = append(result.Exclude, string(name))
|
||||
result.Exclude = append(result.Exclude, name)
|
||||
})
|
||||
if parseErr != nil {
|
||||
return result, parseErr
|
||||
|
||||
@@ -65,6 +65,23 @@ func TestCreateAutocmdRegistersUpgradeSelect(t *testing.T) {
|
||||
require.True(t, e.HasAutocmd(EventUpgradeSelect))
|
||||
}
|
||||
|
||||
func TestCreateAutocmdRegistersAURPostDownload(t *testing.T) {
|
||||
e := New()
|
||||
defer e.Close()
|
||||
|
||||
require.NoError(t, e.L.DoString(`
|
||||
yay.create_autocmd("AURPostDownload", {
|
||||
desc = "inspect downloaded sources",
|
||||
callback = function() end,
|
||||
})
|
||||
`))
|
||||
|
||||
autocmds := e.autocmds[EventAURPostDownload]
|
||||
require.Len(t, autocmds, 1)
|
||||
require.Equal(t, "inspect downloaded sources", autocmds[0].Desc)
|
||||
require.True(t, e.HasAutocmd(EventAURPostDownload))
|
||||
}
|
||||
|
||||
func TestCreateAutocmdRejectsInvalidEvent(t *testing.T) {
|
||||
e := New()
|
||||
defer e.Close()
|
||||
@@ -125,6 +142,76 @@ func TestRunAURPreInstallReturnsAbortWithoutTraceback(t *testing.T) {
|
||||
require.EqualError(t, err, "AURPreInstall demo-base: blocked by policy")
|
||||
}
|
||||
|
||||
func TestRunAURPostDownloadEventTableShape(t *testing.T) {
|
||||
e := New()
|
||||
defer e.Close()
|
||||
|
||||
seen := []string{}
|
||||
e.L.SetGlobal("record", e.L.NewFunction(func(L *glua.LState) int {
|
||||
seen = append(seen, L.CheckString(1))
|
||||
return 0
|
||||
}))
|
||||
|
||||
require.NoError(t, e.L.DoString(`
|
||||
yay.create_autocmd("AURPostDownload", {
|
||||
callback = function(event)
|
||||
if event.event ~= "AURPostDownload" then error("bad event") end
|
||||
if event.match ~= "demo-base" then error("bad match") end
|
||||
if event.data.base ~= "demo-base" then error("bad base") end
|
||||
if event.data.dir ~= "/build/demo-base" then error("bad dir") end
|
||||
if event.data.pkgbuild_path ~= "/build/demo-base/PKGBUILD" then error("bad pkgbuild path") end
|
||||
if event.data.srcinfo_path ~= "/build/demo-base/.SRCINFO" then error("bad srcinfo path") end
|
||||
if event.data.pkgbuild ~= "pkgbase=demo-base" then error("bad pkgbuild") end
|
||||
if event.data.version ~= "1.0-1" then error("bad version") end
|
||||
if event.data.last_modified ~= 123 then error("bad last modified") end
|
||||
if event.data.installed ~= true then error("bad installed") end
|
||||
if event.data.packages[1].name ~= "demo" then error("bad package") end
|
||||
if event.data.srcinfo.pkgbase ~= "demo-base" then error("bad srcinfo") end
|
||||
if event.data.install_paths ~= nil then error("unexpected install paths") end
|
||||
if event.data.source_paths ~= nil then error("unexpected source paths") end
|
||||
if event.data.sources ~= nil then error("unexpected sources") end
|
||||
|
||||
record(event.match .. ":" .. event.data.pkgbuild_path)
|
||||
end,
|
||||
})
|
||||
`))
|
||||
|
||||
err := e.RunAURPostDownload(&AURPreInstallEvent{
|
||||
Base: "demo-base",
|
||||
Dir: "/build/demo-base",
|
||||
PKGBUILDPath: "/build/demo-base/PKGBUILD",
|
||||
SRCINFOPath: "/build/demo-base/.SRCINFO",
|
||||
PKGBUILD: "pkgbase=demo-base",
|
||||
Version: "1.0-1",
|
||||
LastModified: 123,
|
||||
Installed: true,
|
||||
Packages: []AURPreInstallPackage{{
|
||||
Name: "demo",
|
||||
}},
|
||||
SRCINFO: AURPreInstallSRCINFO{
|
||||
Pkgbase: "demo-base",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []string{"demo-base:/build/demo-base/PKGBUILD"}, seen)
|
||||
}
|
||||
|
||||
func TestRunAURPostDownloadReturnsAbortWithoutTraceback(t *testing.T) {
|
||||
e := New()
|
||||
defer e.Close()
|
||||
|
||||
require.NoError(t, e.L.DoString(`
|
||||
yay.create_autocmd("AURPostDownload", {
|
||||
callback = function()
|
||||
yay.abort("blocked by policy")
|
||||
end,
|
||||
})
|
||||
`))
|
||||
|
||||
err := e.RunAURPostDownload(&AURPreInstallEvent{Base: "demo-base"})
|
||||
require.EqualError(t, err, "AURPostDownload demo-base: blocked by policy")
|
||||
}
|
||||
|
||||
func TestRunUpgradeSelectEventTableShapeAndReturn(t *testing.T) {
|
||||
e := New()
|
||||
defer e.Close()
|
||||
|
||||
50
pkg/sync/workdir/aur_post_download.go
Normal file
50
pkg/sync/workdir/aur_post_download.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package workdir
|
||||
|
||||
import (
|
||||
"github.com/Jguer/yay/v12/pkg/dep"
|
||||
"github.com/Jguer/yay/v12/pkg/runtime"
|
||||
settingslua "github.com/Jguer/yay/v12/pkg/settings/lua"
|
||||
|
||||
mapset "github.com/deckarep/golang-set/v2"
|
||||
)
|
||||
|
||||
func runAURPostDownloadLuaHooks(run *runtime.Runtime, pkgbuildDirsByBase map[string]string,
|
||||
installed mapset.Set[string], targets []map[string]*dep.InstallInfo,
|
||||
) error {
|
||||
if run == nil || run.Lua == nil || !run.Lua.HasAutocmd(settingslua.EventAURPostDownload) {
|
||||
return nil
|
||||
}
|
||||
|
||||
events, err := aurPostDownloadEvents(pkgbuildDirsByBase, installed, targets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range events {
|
||||
if err := run.Lua.RunAURPostDownload(&events[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func aurPostDownloadEvents(pkgbuildDirsByBase map[string]string, installed mapset.Set[string],
|
||||
targets []map[string]*dep.InstallInfo,
|
||||
) ([]settingslua.AURPreInstallEvent, error) {
|
||||
packagesByBase := aurTargetPackagesByBase(targets)
|
||||
bases := sortedAURBases(pkgbuildDirsByBase)
|
||||
|
||||
events := make([]settingslua.AURPreInstallEvent, 0, len(bases))
|
||||
for _, base := range bases {
|
||||
event, err := aurPackageEvent(settingslua.EventAURPostDownload, base, pkgbuildDirsByBase[base],
|
||||
packagesByBase[base], installed, targets)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events = append(events, event)
|
||||
}
|
||||
|
||||
return events, nil
|
||||
}
|
||||
103
pkg/sync/workdir/aur_post_download_test.go
Normal file
103
pkg/sync/workdir/aur_post_download_test.go
Normal file
@@ -0,0 +1,103 @@
|
||||
//go:build !integration
|
||||
// +build !integration
|
||||
|
||||
package workdir
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
mapset "github.com/deckarep/golang-set/v2"
|
||||
"github.com/stretchr/testify/require"
|
||||
glua "github.com/yuin/gopher-lua"
|
||||
|
||||
"github.com/Jguer/yay/v12/pkg/dep"
|
||||
"github.com/Jguer/yay/v12/pkg/runtime"
|
||||
settingslua "github.com/Jguer/yay/v12/pkg/settings/lua"
|
||||
)
|
||||
|
||||
func TestAURPostDownloadEventsUseAURPreInstallPayload(t *testing.T) {
|
||||
base := "demo-base"
|
||||
dir := writeAURPostDownloadPackage(t, base)
|
||||
|
||||
events, err := aurPostDownloadEvents(map[string]string{base: dir},
|
||||
mapset.NewThreadUnsafeSet[string](),
|
||||
[]map[string]*dep.InstallInfo{
|
||||
{
|
||||
"demo": {Source: dep.AUR, AURBase: &base, Version: "1.0-1"},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, events, 1)
|
||||
|
||||
event := events[0]
|
||||
require.Equal(t, base, event.Base)
|
||||
require.Equal(t, dir, event.Dir)
|
||||
require.Equal(t, filepath.Join(dir, "PKGBUILD"), event.PKGBUILDPath)
|
||||
require.Equal(t, filepath.Join(dir, ".SRCINFO"), event.SRCINFOPath)
|
||||
require.Contains(t, event.PKGBUILD, "pkgbase=demo-base")
|
||||
require.Equal(t, "1.0-1", event.Version)
|
||||
require.Equal(t, base, event.SRCINFO.Pkgbase)
|
||||
require.Equal(t, "1.0", event.SRCINFO.Pkgver)
|
||||
require.Equal(t, "1", event.SRCINFO.Pkgrel)
|
||||
require.Equal(t, "1.0-1", event.SRCINFO.Version)
|
||||
require.Equal(t, []string{"any"}, event.SRCINFO.Arch)
|
||||
require.Equal(t, []settingslua.AURPreInstallPackage{{Name: "demo", Version: "1.0-1", Reason: "explicit"}}, event.Packages)
|
||||
}
|
||||
|
||||
func TestRunAURPostDownloadLuaHooksRunsBasesInSortedOrder(t *testing.T) {
|
||||
firstDir := writeAURPostDownloadPackage(t, "a-base")
|
||||
secondDir := writeAURPostDownloadPackage(t, "z-base")
|
||||
|
||||
engine := settingslua.New()
|
||||
defer engine.Close()
|
||||
|
||||
order := []string{}
|
||||
engine.L.SetGlobal("record", engine.L.NewFunction(func(L *glua.LState) int {
|
||||
order = append(order, L.CheckString(1))
|
||||
return 0
|
||||
}))
|
||||
require.NoError(t, engine.L.DoString(`
|
||||
yay.create_autocmd("AURPostDownload", {
|
||||
callback = function(event)
|
||||
record(event.match .. ":" .. event.data.pkgbuild_path)
|
||||
end,
|
||||
})
|
||||
`))
|
||||
|
||||
err := runAURPostDownloadLuaHooks(&runtime.Runtime{Lua: engine},
|
||||
map[string]string{"z-base": secondDir, "a-base": firstDir},
|
||||
mapset.NewThreadUnsafeSet[string](),
|
||||
[]map[string]*dep.InstallInfo{
|
||||
{
|
||||
"a": {Source: dep.AUR, AURBase: ptrString("a-base")},
|
||||
"z": {Source: dep.AUR, AURBase: ptrString("z-base")},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []string{
|
||||
"a-base:" + filepath.Join(firstDir, "PKGBUILD"),
|
||||
"z-base:" + filepath.Join(secondDir, "PKGBUILD"),
|
||||
}, order)
|
||||
}
|
||||
|
||||
func writeAURPostDownloadPackage(t *testing.T, base string) string {
|
||||
t.Helper()
|
||||
|
||||
dir := t.TempDir()
|
||||
require.NoError(t, os.WriteFile(filepath.Join(dir, "PKGBUILD"), []byte(`pkgbase=`+base+`
|
||||
pkgname=(demo)
|
||||
pkgver=1.0
|
||||
pkgrel=1
|
||||
`), 0o600))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(dir, ".SRCINFO"), []byte(`pkgbase = `+base+`
|
||||
pkgver = 1.0
|
||||
pkgrel = 1
|
||||
arch = any
|
||||
|
||||
pkgname = demo
|
||||
`), 0o600))
|
||||
|
||||
return dir
|
||||
}
|
||||
@@ -90,19 +90,25 @@ func sortedAURBases(pkgbuildDirsByBase map[string]string) []string {
|
||||
|
||||
func aurPreInstallEvent(base, path string, packages []settingslua.AURPreInstallPackage,
|
||||
installed mapset.Set[string], targets []map[string]*dep.InstallInfo,
|
||||
) (settingslua.AURPreInstallEvent, error) {
|
||||
return aurPackageEvent(settingslua.EventAURPreInstall, base, path, packages, installed, targets)
|
||||
}
|
||||
|
||||
func aurPackageEvent(eventName, base, path string, packages []settingslua.AURPreInstallPackage,
|
||||
installed mapset.Set[string], targets []map[string]*dep.InstallInfo,
|
||||
) (settingslua.AURPreInstallEvent, error) {
|
||||
dir, pkgbuildPath, srcinfoPath := aurPreInstallPaths(path)
|
||||
|
||||
pkgbuildBytes, err := os.ReadFile(pkgbuildPath)
|
||||
if err != nil {
|
||||
return settingslua.AURPreInstallEvent{},
|
||||
fmt.Errorf("%s %s: read PKGBUILD: %w", settingslua.EventAURPreInstall, base, err)
|
||||
fmt.Errorf("%s %s: read PKGBUILD: %w", eventName, base, err)
|
||||
}
|
||||
|
||||
srcinfo, err := gosrc.ParseFile(srcinfoPath)
|
||||
if err != nil {
|
||||
return settingslua.AURPreInstallEvent{},
|
||||
fmt.Errorf("%s %s: parse .SRCINFO: %w", settingslua.EventAURPreInstall, base, err)
|
||||
fmt.Errorf("%s %s: parse .SRCINFO: %w", eventName, base, err)
|
||||
}
|
||||
|
||||
if len(packages) == 0 {
|
||||
|
||||
@@ -235,6 +235,10 @@ func (preper *Preparer) PrepareWorkspace(ctx context.Context,
|
||||
preper.log.Errorln(errP)
|
||||
}
|
||||
|
||||
if err := runAURPostDownloadLuaHooks(run, pkgBuildDirsByBase, remoteNamesCache, targets); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pkgBuildDirsByBase, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user