Files
yay/pkg/sync/workdir/aur_preinstall.go
2026-06-18 19:58:23 +02:00

366 lines
9.9 KiB
Go

package workdir
import (
"cmp"
"context"
"fmt"
"io"
"maps"
"os"
"path/filepath"
"slices"
"github.com/Jguer/yay/v13/pkg/dep"
"github.com/Jguer/yay/v13/pkg/runtime"
settingslua "github.com/Jguer/yay/v13/pkg/settings/lua"
gosrc "github.com/Morganamilo/go-srcinfo"
mapset "github.com/deckarep/golang-set/v2"
)
func (preper *Preparer) runPreDownloadSourcesHooks(ctx context.Context, run *runtime.Runtime, w io.Writer,
pkgbuildDirsByBase map[string]string, installed mapset.Set[string],
targets []map[string]*dep.InstallInfo,
) error {
if err := runAURPreInstallLuaHooks(run, pkgbuildDirsByBase, installed, targets); err != nil {
return err
}
for _, hookFn := range preper.hooks {
if hookFn.Type != PreDownloadSourcesHook {
continue
}
if err := hookFn.Hookfn(ctx, run, w, pkgbuildDirsByBase, installed); err != nil {
return err
}
}
return nil
}
func runAURPreInstallLuaHooks(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.EventAURPreInstall) {
return nil
}
events, err := aurPreInstallEvents(pkgbuildDirsByBase, installed, targets)
if err != nil {
return err
}
for i := range events {
if err := run.Lua.RunAURPreInstall(&events[i]); err != nil {
return err
}
}
return nil
}
func aurPreInstallEvents(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 := aurPreInstallEvent(base, pkgbuildDirsByBase[base],
packagesByBase[base], installed, targets)
if err != nil {
return nil, err
}
events = append(events, event)
}
return events, nil
}
func sortedAURBases(pkgbuildDirsByBase map[string]string) []string {
return slices.Sorted(maps.Keys(pkgbuildDirsByBase))
}
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", eventName, base, err)
}
srcinfo, err := gosrc.ParseFile(srcinfoPath)
if err != nil {
return settingslua.AURPreInstallEvent{},
fmt.Errorf("%s %s: parse .SRCINFO: %w", eventName, base, err)
}
if len(packages) == 0 {
packages = packagesFromSRCINFO(srcinfo)
}
slices.SortFunc(packages, func(a, b settingslua.AURPreInstallPackage) int { return cmp.Compare(a.Name, b.Name) })
return settingslua.AURPreInstallEvent{
Base: base,
Dir: dir,
PKGBUILDPath: pkgbuildPath,
SRCINFOPath: srcinfoPath,
PKGBUILD: string(pkgbuildBytes),
Version: srcinfo.Version(),
LastModified: aurPreInstallLastModified(base, targets),
Installed: aurPreInstallInstalled(packages, srcinfo, installed),
Packages: packages,
SRCINFO: srcinfoEventData(srcinfo),
}, nil
}
func aurPreInstallPaths(path string) (dir, pkgbuildPath, srcinfoPath string) {
info, err := os.Stat(path)
if err == nil && !info.IsDir() {
dir = filepath.Dir(path)
srcinfoPath = path
pkgbuildPath = filepath.Join(dir, "PKGBUILD")
return dir, pkgbuildPath, srcinfoPath
}
dir = path
pkgbuildPath = filepath.Join(dir, "PKGBUILD")
srcinfoPath = filepath.Join(dir, ".SRCINFO")
return dir, pkgbuildPath, srcinfoPath
}
func aurTargetPackagesByBase(targets []map[string]*dep.InstallInfo) map[string][]settingslua.AURPreInstallPackage {
packages := map[string][]settingslua.AURPreInstallPackage{}
for _, layer := range targets {
for name, info := range layer {
if info == nil || info.AURBase == "" {
continue
}
if info.Source != dep.AUR && info.Source != dep.SrcInfo {
continue
}
base := info.AURBase
packages[base] = append(packages[base], settingslua.AURPreInstallPackage{
Name: name,
Version: info.Version,
LocalVersion: info.LocalVersion,
Reason: luaReason(info.Reason),
Upgrade: info.Upgrade,
Devel: info.Devel,
})
}
}
return packages
}
func packagesFromSRCINFO(srcinfo *gosrc.Srcinfo) []settingslua.AURPreInstallPackage {
packages := make([]settingslua.AURPreInstallPackage, 0, len(srcinfo.Packages))
for i := range srcinfo.Packages {
packages = append(packages, settingslua.AURPreInstallPackage{
Name: srcinfo.Packages[i].Pkgname,
Version: srcinfo.Version(),
})
}
return packages
}
func aurPreInstallInstalled(packages []settingslua.AURPreInstallPackage, srcinfo *gosrc.Srcinfo,
installed mapset.Set[string],
) bool {
isInstalled := false
for _, pkg := range packages {
if pkg.LocalVersion != "" || installed.Contains(pkg.Name) {
isInstalled = true
break
}
}
for i := range srcinfo.Packages {
if installed.Contains(srcinfo.Packages[i].Pkgname) {
isInstalled = true
break
}
}
return isInstalled
}
func aurPreInstallLastModified(base string, targets []map[string]*dep.InstallInfo) int64 {
var lastModified int64
for _, layer := range targets {
for _, info := range layer {
if info == nil || info.AURBase == "" {
continue
}
if info.AURBase == base && info.LastModified > lastModified {
lastModified = info.LastModified
}
}
}
return lastModified
}
func luaReason(reason dep.Reason) string {
switch reason {
case dep.Explicit:
return "explicit"
case dep.Dep:
return "dependency"
case dep.MakeDep:
return "make_dependency"
case dep.CheckDep:
return "check_dependency"
default:
return "unknown"
}
}
func srcinfoEventData(srcinfo *gosrc.Srcinfo) settingslua.AURPreInstallSRCINFO {
pkgdesc, url := srcinfoDescriptionAndURL(srcinfo)
packageArch := packageStringValues(srcinfo.Packages, func(pkg *gosrc.Package) []string {
return pkg.Arch
})
packageLicense := packageStringValues(srcinfo.Packages, func(pkg *gosrc.Package) []string {
return pkg.License
})
packageDepends := packageArchStringValues(srcinfo.Packages, func(pkg *gosrc.Package) []gosrc.ArchString {
return pkg.Depends
})
packageOptDepends := packageArchStringValues(srcinfo.Packages, func(pkg *gosrc.Package) []gosrc.ArchString {
return pkg.OptDepends
})
packageProvides := packageArchStringValues(srcinfo.Packages, func(pkg *gosrc.Package) []gosrc.ArchString {
return pkg.Provides
})
packageConflicts := packageArchStringValues(srcinfo.Packages, func(pkg *gosrc.Package) []gosrc.ArchString {
return pkg.Conflicts
})
packageReplaces := packageArchStringValues(srcinfo.Packages, func(pkg *gosrc.Package) []gosrc.ArchString {
return pkg.Replaces
})
return settingslua.AURPreInstallSRCINFO{
Pkgbase: srcinfo.Pkgbase,
Pkgver: srcinfo.Pkgver,
Pkgrel: srcinfo.Pkgrel,
Epoch: srcinfo.Epoch,
Version: srcinfo.Version(),
Pkgdesc: pkgdesc,
URL: url,
Arch: mergedStringValues(srcinfo.Arch, packageArch...),
License: mergedStringValues(srcinfo.License, packageLicense...),
Depends: mergedArchStringValues(srcinfo.Depends, packageDepends...),
MakeDepends: archStringValues(srcinfo.MakeDepends),
CheckDepends: archStringValues(srcinfo.CheckDepends),
OptDepends: mergedArchStringValues(srcinfo.OptDepends, packageOptDepends...),
Provides: mergedArchStringValues(srcinfo.Provides, packageProvides...),
Conflicts: mergedArchStringValues(srcinfo.Conflicts, packageConflicts...),
Replaces: mergedArchStringValues(srcinfo.Replaces, packageReplaces...),
}
}
func srcinfoDescriptionAndURL(srcinfo *gosrc.Srcinfo) (pkgdesc, url string) {
pkgdesc = srcinfo.Pkgdesc
url = srcinfo.URL
if pkgdesc != "" && url != "" {
return pkgdesc, url
}
for i := range srcinfo.Packages {
pkg := &srcinfo.Packages[i]
if pkgdesc == "" {
pkgdesc = pkg.Pkgdesc
}
if url == "" {
url = pkg.URL
}
if pkgdesc != "" && url != "" {
break
}
}
return pkgdesc, url
}
func packageStringValues(packages []gosrc.Package, selectValues func(*gosrc.Package) []string) [][]string {
values := make([][]string, 0, len(packages))
for i := range packages {
values = append(values, selectValues(&packages[i]))
}
return values
}
func packageArchStringValues(packages []gosrc.Package, selectValues func(*gosrc.Package) []gosrc.ArchString) [][]gosrc.ArchString {
values := make([][]gosrc.ArchString, 0, len(packages))
for i := range packages {
values = append(values, selectValues(&packages[i]))
}
return values
}
func mergedStringValues(global []string, packageValues ...[]string) []string {
out := make([]string, 0, len(global))
seen := map[string]struct{}{}
appendValue := func(value string) {
if _, ok := seen[value]; ok {
return
}
seen[value] = struct{}{}
out = append(out, value)
}
for _, value := range global {
appendValue(value)
}
for _, values := range packageValues {
for _, value := range values {
appendValue(value)
}
}
return out
}
func mergedArchStringValues(global []gosrc.ArchString, packageValues ...[]gosrc.ArchString) []string {
values := make([][]string, 0, len(packageValues))
for _, packageValue := range packageValues {
values = append(values, archStringValues(packageValue))
}
return mergedStringValues(archStringValues(global), values...)
}
func archStringValues(values []gosrc.ArchString) []string {
out := make([]string, 0, len(values))
for _, value := range values {
out = append(out, value.Value)
}
return out
}