feat(arch/src): .SRCINFO parser + PKGBUILD fallback
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <[email protected]>
This commit is contained in:
@@ -0,0 +1,299 @@
|
|||||||
|
package src
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseSRCINFO parses makepkg .SRCINFO content: lines of "key = value"
|
||||||
|
// grouped into a pkgbase section and one or more pkgname sections. Keys in
|
||||||
|
// the pkgbase section are inherited by every package; the first pkgname
|
||||||
|
// section extends/overrides them (v1 targets single-package recipes, so
|
||||||
|
// later pkgname sections only contribute their name to PkgNames — the
|
||||||
|
// converter rejects split packages before they get here).
|
||||||
|
//
|
||||||
|
// Repeatable keys (arch, depends, makedepends, optdepends, provides,
|
||||||
|
// conflicts, replaces, source, sha256sums) accumulate; single-value keys
|
||||||
|
// (pkgver, pkgrel, epoch, pkgdesc, url) take the last occurrence. Keys
|
||||||
|
// suffixed with _<arch> (e.g. depends_x86_64) are folded into their base
|
||||||
|
// key only when the suffix matches the requested arch; suffixes for other
|
||||||
|
// arches are ignored. An empty arch defaults to x86_64.
|
||||||
|
//
|
||||||
|
// ParseSRCINFO returns an error when no pkgver is present.
|
||||||
|
func ParseSRCINFO(src string, arch string) (*SrcInfo, error) {
|
||||||
|
if arch == "" {
|
||||||
|
arch = "x86_64"
|
||||||
|
}
|
||||||
|
info := &SrcInfo{}
|
||||||
|
pkgnameSections := 0
|
||||||
|
firstSection := true
|
||||||
|
for _, line := range strings.Split(src, "\n") {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line == "" || strings.HasPrefix(line, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
eq := strings.Index(line, "=")
|
||||||
|
if eq < 0 {
|
||||||
|
continue // malformed line: skip
|
||||||
|
}
|
||||||
|
key := strings.TrimSpace(line[:eq])
|
||||||
|
value := strings.TrimSpace(line[eq+1:])
|
||||||
|
switch key {
|
||||||
|
case "pkgbase":
|
||||||
|
info.PkgBase = value
|
||||||
|
case "pkgname":
|
||||||
|
info.PkgNames = append(info.PkgNames, value)
|
||||||
|
pkgnameSections++
|
||||||
|
firstSection = false
|
||||||
|
default:
|
||||||
|
// Keys in pkgname sections beyond the first are ignored: v1
|
||||||
|
// targets single-package recipes.
|
||||||
|
if !firstSection && pkgnameSections > 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if isSingleKey(key) {
|
||||||
|
applyKey(info, key, value)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
base, ok := foldArchKey(key, arch)
|
||||||
|
if ok {
|
||||||
|
applyKey(info, base, value)
|
||||||
|
}
|
||||||
|
// else: unknown key or a different arch's variant — ignore.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if info.Version == "" {
|
||||||
|
return nil, errors.New("srcinfo: missing pkgver")
|
||||||
|
}
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isSingleKey reports whether key is a single-value (last-wins) .SRCINFO
|
||||||
|
// key that never carries an _<arch> suffix.
|
||||||
|
func isSingleKey(key string) bool {
|
||||||
|
switch key {
|
||||||
|
case "pkgver", "pkgrel", "epoch", "pkgdesc", "url", "arch":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// applyKey records one .SRCINFO key=value into info. Single-value keys take
|
||||||
|
// the last occurrence; repeatable keys accumulate.
|
||||||
|
func applyKey(info *SrcInfo, key, value string) {
|
||||||
|
switch key {
|
||||||
|
case "pkgver":
|
||||||
|
info.Version = value
|
||||||
|
case "pkgrel":
|
||||||
|
info.Rel = value
|
||||||
|
case "epoch":
|
||||||
|
info.Epoch = value
|
||||||
|
case "pkgdesc":
|
||||||
|
info.Desc = value
|
||||||
|
case "url":
|
||||||
|
info.URL = value
|
||||||
|
case "arch":
|
||||||
|
info.Arch = append(info.Arch, value)
|
||||||
|
case "depends":
|
||||||
|
info.Depends = append(info.Depends, value)
|
||||||
|
case "makedepends":
|
||||||
|
info.Makedepends = append(info.Makedepends, value)
|
||||||
|
case "optdepends":
|
||||||
|
info.Optdepends = append(info.Optdepends, value)
|
||||||
|
case "provides":
|
||||||
|
info.Provides = append(info.Provides, value)
|
||||||
|
case "conflicts":
|
||||||
|
info.Conflicts = append(info.Conflicts, value)
|
||||||
|
case "replaces":
|
||||||
|
info.Replaces = append(info.Replaces, value)
|
||||||
|
case "source":
|
||||||
|
info.Sources = append(info.Sources, value)
|
||||||
|
case "sha256sums":
|
||||||
|
info.SHA256Sums = append(info.SHA256Sums, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// archVariantKeys are the .SRCINFO keys that may carry an _<arch> suffix.
|
||||||
|
var archVariantKeys = map[string]bool{
|
||||||
|
"source": true, "depends": true, "makedepends": true, "optdepends": true,
|
||||||
|
"provides": true, "conflicts": true, "replaces": true,
|
||||||
|
"sha256sums": true, "md5sums": true, "sha1sums": true, "sha224sums": true,
|
||||||
|
"sha384sums": true, "sha512sums": true, "b2sums": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// foldArchKey maps a .SRCINFO key to the base key it contributes to, and
|
||||||
|
// reports whether it applies to the requested arch. A bare key applies to
|
||||||
|
// every arch; a `key_<arch>` key applies only when <arch> matches; a
|
||||||
|
// `key_<other>` key folds to nothing.
|
||||||
|
func foldArchKey(key, arch string) (string, bool) {
|
||||||
|
if base, ok := strings.CutSuffix(key, "_"+arch); ok && archVariantKeys[base] {
|
||||||
|
return base, true
|
||||||
|
}
|
||||||
|
if archVariantKeys[key] {
|
||||||
|
return key, true
|
||||||
|
}
|
||||||
|
if base, _, ok := strings.Cut(key, "_"); ok && archVariantKeys[base] {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// precompiled assignment regexes. PKGBUILDs are bash; these regexes match
|
||||||
|
// only top-level `key=value` declarations at column 0, so assignments
|
||||||
|
// inside function bodies (indented) and `key() {` function definitions are
|
||||||
|
// never evaluated.
|
||||||
|
var (
|
||||||
|
reScalar = map[string]*regexp.Regexp{}
|
||||||
|
reArray = map[string]*regexp.Regexp{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
for _, key := range []string{"pkgbase", "pkgname", "pkgver", "pkgrel", "epoch", "pkgdesc", "url"} {
|
||||||
|
reScalar[key] = regexp.MustCompile(`(?m)^` + regexp.QuoteMeta(key) + `\s*=\s*(?:'([^']*)'|"([^"]*)"|([^\s'"]+))`)
|
||||||
|
}
|
||||||
|
for _, key := range []string{"pkgname", "arch", "depends", "makedepends", "optdepends", "provides", "conflicts", "replaces", "source", "sha256sums"} {
|
||||||
|
reArray[key] = regexp.MustCompile(`(?ms)^` + regexp.QuoteMeta(key) + `\s*=\s*\(([^)]*)\)`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParsePKGBUILD best-effort extracts declaration values from a PKGBUILD by
|
||||||
|
// regex-matching top-level `key=value` assignments. It NEVER executes bash:
|
||||||
|
// function bodies, comments, conditionals and variables are left untouched,
|
||||||
|
// and values referencing variables such as $pkgname are kept verbatim.
|
||||||
|
// Array assignments (`key=(v1 v2)`, possibly spanning lines) are split on
|
||||||
|
// whitespace and unquoted; scalar values may be bare or 'single'/"double"
|
||||||
|
// quoted. Unknown constructs are ignored.
|
||||||
|
//
|
||||||
|
// ParsePKGBUILD returns an error when no usable pkgver declaration is
|
||||||
|
// found (pkgver is mandatory in a PKGBUILD).
|
||||||
|
func ParsePKGBUILD(src string) (*SrcInfo, error) {
|
||||||
|
info := &SrcInfo{}
|
||||||
|
if v, ok := scalar(src, "pkgbase"); ok {
|
||||||
|
info.PkgBase = v
|
||||||
|
}
|
||||||
|
if names, ok := array(src, "pkgname"); ok {
|
||||||
|
info.PkgNames = names
|
||||||
|
} else if v, ok := scalar(src, "pkgname"); ok {
|
||||||
|
info.PkgNames = []string{v}
|
||||||
|
}
|
||||||
|
info.Version, _ = scalar(src, "pkgver")
|
||||||
|
info.Rel, _ = scalar(src, "pkgrel")
|
||||||
|
info.Epoch, _ = scalar(src, "epoch")
|
||||||
|
info.Desc, _ = scalar(src, "pkgdesc")
|
||||||
|
info.URL, _ = scalar(src, "url")
|
||||||
|
info.Arch, _ = array(src, "arch")
|
||||||
|
info.Depends, _ = array(src, "depends")
|
||||||
|
info.Makedepends, _ = array(src, "makedepends")
|
||||||
|
info.Optdepends, _ = array(src, "optdepends")
|
||||||
|
info.Provides, _ = array(src, "provides")
|
||||||
|
info.Conflicts, _ = array(src, "conflicts")
|
||||||
|
info.Replaces, _ = array(src, "replaces")
|
||||||
|
info.Sources, _ = array(src, "source")
|
||||||
|
info.SHA256Sums, _ = array(src, "sha256sums")
|
||||||
|
if info.Version == "" {
|
||||||
|
return nil, errors.New("pkgbuild: missing pkgver")
|
||||||
|
}
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// scalar returns the value of a top-level `key=value` scalar assignment.
|
||||||
|
// The value may be bare, 'single'- or "double"-quoted. An array assignment
|
||||||
|
// is reported as not-found so callers fall back to array().
|
||||||
|
func scalar(src, key string) (string, bool) {
|
||||||
|
m := reScalar[key].FindStringSubmatch(src)
|
||||||
|
if m == nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
value := m[1]
|
||||||
|
if value == "" {
|
||||||
|
value = m[2]
|
||||||
|
}
|
||||||
|
if value == "" {
|
||||||
|
value = m[3]
|
||||||
|
}
|
||||||
|
value = strings.TrimSpace(value)
|
||||||
|
if strings.HasPrefix(value, "(") {
|
||||||
|
return "", false // array form; caller should use array()
|
||||||
|
}
|
||||||
|
return value, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// array returns the values of a top-level `key=(v1 v2 ...)` assignment,
|
||||||
|
// possibly spanning multiple lines. Tokens are split on whitespace and
|
||||||
|
// surrounding single/double quotes are stripped. Best-effort: quoted values
|
||||||
|
// containing spaces are not preserved.
|
||||||
|
func array(src, key string) ([]string, bool) {
|
||||||
|
m := reArray[key].FindStringSubmatch(src)
|
||||||
|
if m == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
tokens := strings.Fields(m[1])
|
||||||
|
values := make([]string, 0, len(tokens))
|
||||||
|
for _, tok := range tokens {
|
||||||
|
values = append(values, stripQuotes(tok))
|
||||||
|
}
|
||||||
|
return values, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// stripQuotes removes a matching pair of surrounding single or double
|
||||||
|
// quotes.
|
||||||
|
func stripQuotes(s string) string {
|
||||||
|
if len(s) >= 2 {
|
||||||
|
if (s[0] == '\'' && s[len(s)-1] == '\'') || (s[0] == '"' && s[len(s)-1] == '"') {
|
||||||
|
return s[1 : len(s)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load parses the Arch source package at input. If input is a directory it
|
||||||
|
// prefers <input>/.SRCINFO and falls back to <input>/PKGBUILD; if input is
|
||||||
|
// a file, its basename selects the parser (.SRCINFO or PKGBUILD). Load
|
||||||
|
// returns a descriptive error when neither file is found or the file type
|
||||||
|
// is unknown.
|
||||||
|
func Load(input string, arch string) (*SrcInfo, error) {
|
||||||
|
st, err := os.Stat(input)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("src: %s: %w", input, err)
|
||||||
|
}
|
||||||
|
path := input
|
||||||
|
useSRCINFO := false
|
||||||
|
if st.IsDir() {
|
||||||
|
srcinfo := filepath.Join(input, ".SRCINFO")
|
||||||
|
pkgbuild := filepath.Join(input, "PKGBUILD")
|
||||||
|
switch {
|
||||||
|
case fileExists(srcinfo):
|
||||||
|
path, useSRCINFO = srcinfo, true
|
||||||
|
case fileExists(pkgbuild):
|
||||||
|
path = pkgbuild
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("src: %s: neither .SRCINFO nor PKGBUILD found", input)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch filepath.Base(input) {
|
||||||
|
case ".SRCINFO":
|
||||||
|
useSRCINFO = true
|
||||||
|
case "PKGBUILD":
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("src: %s: unknown source file (want .SRCINFO or PKGBUILD)", input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("src: reading %s: %w", path, err)
|
||||||
|
}
|
||||||
|
if useSRCINFO {
|
||||||
|
return ParseSRCINFO(string(data), arch)
|
||||||
|
}
|
||||||
|
return ParsePKGBUILD(string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// fileExists reports whether path names a regular file.
|
||||||
|
func fileExists(path string) bool {
|
||||||
|
st, err := os.Stat(path)
|
||||||
|
return err == nil && !st.IsDir()
|
||||||
|
}
|
||||||
@@ -0,0 +1,293 @@
|
|||||||
|
package src
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// srcinfoFixtures is the committed testdata tree, relative to this package
|
||||||
|
// directory (internal/arch/src).
|
||||||
|
const srcinfoFixtures = "../../../testdata/srcinfo"
|
||||||
|
|
||||||
|
func readFixture(t *testing.T, path string) string {
|
||||||
|
t.Helper()
|
||||||
|
b, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read fixture %s: %v", path, err)
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSrcInfoParseMytoolSRCINFO parses the committed mytool .SRCINFO with
|
||||||
|
// arch x86_64 and checks every field the converter consumes.
|
||||||
|
func TestSrcInfoParseMytoolSRCINFO(t *testing.T) {
|
||||||
|
src := readFixture(t, filepath.Join(srcinfoFixtures, "mytool", ".SRCINFO"))
|
||||||
|
got, err := ParseSRCINFO(src, "x86_64")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ParseSRCINFO: %v", err)
|
||||||
|
}
|
||||||
|
if got.PkgBase != "mytool" {
|
||||||
|
t.Errorf("PkgBase = %q, want %q", got.PkgBase, "mytool")
|
||||||
|
}
|
||||||
|
if !slices.Equal(got.PkgNames, []string{"mytool"}) {
|
||||||
|
t.Errorf("PkgNames = %v, want [mytool]", got.PkgNames)
|
||||||
|
}
|
||||||
|
if got.Version != "1.0" || got.Rel != "1" {
|
||||||
|
t.Errorf("Version/Rel = %q/%q, want %q/%q", got.Version, got.Rel, "1.0", "1")
|
||||||
|
}
|
||||||
|
if got.Desc != "Example custom-build package" {
|
||||||
|
t.Errorf("Desc = %q, want %q", got.Desc, "Example custom-build package")
|
||||||
|
}
|
||||||
|
if got.URL != "https://example.org" {
|
||||||
|
t.Errorf("URL = %q, want %q", got.URL, "https://example.org")
|
||||||
|
}
|
||||||
|
if !slices.Equal(got.Arch, []string{"x86_64"}) {
|
||||||
|
t.Errorf("Arch = %v, want [x86_64]", got.Arch)
|
||||||
|
}
|
||||||
|
if !slices.Equal(got.Depends, []string{"glibc"}) {
|
||||||
|
t.Errorf("Depends = %v, want [glibc]", got.Depends)
|
||||||
|
}
|
||||||
|
if !slices.Equal(got.Sources, []string{"mytool-1.0.tar.gz"}) {
|
||||||
|
t.Errorf("Sources = %v, want [mytool-1.0.tar.gz]", got.Sources)
|
||||||
|
}
|
||||||
|
if !slices.Equal(got.SHA256Sums, []string{"SKIP"}) {
|
||||||
|
t.Errorf("SHA256Sums = %v, want [SKIP]", got.SHA256Sums)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSrcInfoParseMytoolPKGBUILD exercises the best-effort bash fallback on
|
||||||
|
// the same package and expects the essential fields to agree.
|
||||||
|
func TestSrcInfoParseMytoolPKGBUILD(t *testing.T) {
|
||||||
|
src := readFixture(t, filepath.Join(srcinfoFixtures, "mytool", "PKGBUILD"))
|
||||||
|
got, err := ParsePKGBUILD(src)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ParsePKGBUILD: %v", err)
|
||||||
|
}
|
||||||
|
if !slices.Equal(got.PkgNames, []string{"mytool"}) {
|
||||||
|
t.Errorf("PkgNames = %v, want [mytool]", got.PkgNames)
|
||||||
|
}
|
||||||
|
if got.Version != "1.0" || got.Rel != "1" {
|
||||||
|
t.Errorf("Version/Rel = %q/%q, want %q/%q", got.Version, got.Rel, "1.0", "1")
|
||||||
|
}
|
||||||
|
if got.Desc != "Example custom-build package" {
|
||||||
|
t.Errorf("Desc = %q, want %q", got.Desc, "Example custom-build package")
|
||||||
|
}
|
||||||
|
if got.URL != "https://example.org" {
|
||||||
|
t.Errorf("URL = %q, want %q", got.URL, "https://example.org")
|
||||||
|
}
|
||||||
|
if !slices.Equal(got.Arch, []string{"x86_64"}) {
|
||||||
|
t.Errorf("Arch = %v, want [x86_64]", got.Arch)
|
||||||
|
}
|
||||||
|
if !slices.Equal(got.Depends, []string{"glibc"}) {
|
||||||
|
t.Errorf("Depends = %v, want [glibc]", got.Depends)
|
||||||
|
}
|
||||||
|
if !slices.Equal(got.Sources, []string{"mytool-1.0.tar.gz"}) {
|
||||||
|
t.Errorf("Sources = %v, want [mytool-1.0.tar.gz]", got.Sources)
|
||||||
|
}
|
||||||
|
if !slices.Equal(got.SHA256Sums, []string{"SKIP"}) {
|
||||||
|
t.Errorf("SHA256Sums = %v, want [SKIP]", got.SHA256Sums)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSrcInfoParseSplitPKGBUILD: a split package must PARSE fine (rejection
|
||||||
|
// happens later in the converter), yielding both pkgnames.
|
||||||
|
func TestSrcInfoParseSplitPKGBUILD(t *testing.T) {
|
||||||
|
src := readFixture(t, filepath.Join(srcinfoFixtures, "split", "PKGBUILD"))
|
||||||
|
got, err := ParsePKGBUILD(src)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ParsePKGBUILD: %v", err)
|
||||||
|
}
|
||||||
|
if !slices.Equal(got.PkgNames, []string{"foo", "bar"}) {
|
||||||
|
t.Errorf("PkgNames = %v, want [foo bar]", got.PkgNames)
|
||||||
|
}
|
||||||
|
if got.PkgBase != "foobar" {
|
||||||
|
t.Errorf("PkgBase = %q, want %q", got.PkgBase, "foobar")
|
||||||
|
}
|
||||||
|
if got.Version != "1.0" {
|
||||||
|
t.Errorf("Version = %q, want %q", got.Version, "1.0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSrcInfoParseVCSPKGBUILD: VCS sources must come through verbatim so the
|
||||||
|
// converter can detect and reject them with a clear message.
|
||||||
|
func TestSrcInfoParseVCSPKGBUILD(t *testing.T) {
|
||||||
|
src := readFixture(t, filepath.Join(srcinfoFixtures, "vcs", "PKGBUILD"))
|
||||||
|
got, err := ParsePKGBUILD(src)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ParsePKGBUILD: %v", err)
|
||||||
|
}
|
||||||
|
if !slices.Equal(got.PkgNames, []string{"mygit"}) {
|
||||||
|
t.Errorf("PkgNames = %v, want [mygit]", got.PkgNames)
|
||||||
|
}
|
||||||
|
if !slices.Contains(got.Sources, "git+https://example.org/repo.git") {
|
||||||
|
t.Errorf("Sources = %v, want it to contain %q", got.Sources, "git+https://example.org/repo.git")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSrcInfoArchFilter feeds a .SRCINFO with arch-specific keys and checks
|
||||||
|
// that only the requested arch's variants are folded in.
|
||||||
|
func TestSrcInfoArchFilter(t *testing.T) {
|
||||||
|
src := `pkgbase = demo
|
||||||
|
pkgver = 2.0
|
||||||
|
pkgrel = 3
|
||||||
|
epoch = 1
|
||||||
|
depends = bash
|
||||||
|
depends_x86_64 = zsh
|
||||||
|
depends_aarch64 = sh
|
||||||
|
source_x86_64 = demo-2.0.tar.gz
|
||||||
|
source_aarch64 = demo-2.0-aarch64.tar.gz
|
||||||
|
sha256sums_x86_64 = SKIP
|
||||||
|
sha256sums_aarch64 = DEADBEEF
|
||||||
|
pkgname = demo
|
||||||
|
`
|
||||||
|
got, err := ParseSRCINFO(src, "x86_64")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ParseSRCINFO(x86_64): %v", err)
|
||||||
|
}
|
||||||
|
if !slices.Equal(got.Depends, []string{"bash", "zsh"}) {
|
||||||
|
t.Errorf("Depends(x86_64) = %v, want [bash zsh] (aarch64 variant must NOT leak in)", got.Depends)
|
||||||
|
}
|
||||||
|
if !slices.Equal(got.Sources, []string{"demo-2.0.tar.gz"}) {
|
||||||
|
t.Errorf("Sources(x86_64) = %v, want [demo-2.0.tar.gz]", got.Sources)
|
||||||
|
}
|
||||||
|
if !slices.Equal(got.SHA256Sums, []string{"SKIP"}) {
|
||||||
|
t.Errorf("SHA256Sums(x86_64) = %v, want [SKIP]", got.SHA256Sums)
|
||||||
|
}
|
||||||
|
if got.Version != "2.0" || got.Rel != "3" || got.Epoch != "1" {
|
||||||
|
t.Errorf("Version/Rel/Epoch = %q/%q/%q, want %q/%q/%q", got.Version, got.Rel, got.Epoch, "2.0", "3", "1")
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err = ParseSRCINFO(src, "aarch64")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ParseSRCINFO(aarch64): %v", err)
|
||||||
|
}
|
||||||
|
if !slices.Equal(got.Depends, []string{"bash", "sh"}) {
|
||||||
|
t.Errorf("Depends(aarch64) = %v, want [bash sh]", got.Depends)
|
||||||
|
}
|
||||||
|
if !slices.Equal(got.Sources, []string{"demo-2.0-aarch64.tar.gz"}) {
|
||||||
|
t.Errorf("Sources(aarch64) = %v, want [demo-2.0-aarch64.tar.gz]", got.Sources)
|
||||||
|
}
|
||||||
|
if !slices.Equal(got.SHA256Sums, []string{"DEADBEEF"}) {
|
||||||
|
t.Errorf("SHA256Sums(aarch64) = %v, want [DEADBEEF]", got.SHA256Sums)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSrcInfoMultiPkgnameSRCINFO: multiple pkgname sections must not fail
|
||||||
|
// parsing; every name is collected, only the FIRST section's keys apply.
|
||||||
|
func TestSrcInfoMultiPkgnameSRCINFO(t *testing.T) {
|
||||||
|
src := `pkgbase = split
|
||||||
|
pkgver = 1.0
|
||||||
|
pkgrel = 1
|
||||||
|
pkgname = foo
|
||||||
|
depends = a
|
||||||
|
pkgname = bar
|
||||||
|
depends = b
|
||||||
|
`
|
||||||
|
got, err := ParseSRCINFO(src, "x86_64")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ParseSRCINFO: %v", err)
|
||||||
|
}
|
||||||
|
if !slices.Equal(got.PkgNames, []string{"foo", "bar"}) {
|
||||||
|
t.Errorf("PkgNames = %v, want [foo bar]", got.PkgNames)
|
||||||
|
}
|
||||||
|
if !slices.Equal(got.Depends, []string{"a"}) {
|
||||||
|
t.Errorf("Depends = %v, want [a] (only the first pkgname section applies)", got.Depends)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSrcInfoPKGBUILDMissingPkgver: a PKGBUILD without a usable pkgver
|
||||||
|
// declaration is malformed and must error.
|
||||||
|
func TestSrcInfoPKGBUILDMissingPkgver(t *testing.T) {
|
||||||
|
src := `pkgname=foo
|
||||||
|
pkgrel=1
|
||||||
|
pkgdesc="no version here"
|
||||||
|
arch=(x86_64)
|
||||||
|
depends=('glibc')
|
||||||
|
`
|
||||||
|
got, err := ParsePKGBUILD(src)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("ParsePKGBUILD = %+v, want error for missing pkgver", got)
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "pkgver") {
|
||||||
|
t.Errorf("error %q does not mention pkgver", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSrcInfoLoadDirPrefersSRCINFO: loading a directory uses .SRCINFO first
|
||||||
|
// (PkgBase is only present in the .SRCINFO fixture, not the PKGBUILD).
|
||||||
|
func TestSrcInfoLoadDirPrefersSRCINFO(t *testing.T) {
|
||||||
|
got, err := Load(filepath.Join(srcinfoFixtures, "mytool"), "x86_64")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load: %v", err)
|
||||||
|
}
|
||||||
|
if got.PkgBase != "mytool" {
|
||||||
|
t.Errorf("PkgBase = %q, want %q (proves .SRCINFO was preferred over PKGBUILD)", got.PkgBase, "mytool")
|
||||||
|
}
|
||||||
|
if got.Version != "1.0" {
|
||||||
|
t.Errorf("Version = %q, want %q", got.Version, "1.0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSrcInfoLoadDirFallsBackToPKGBUILD: a directory with only a PKGBUILD
|
||||||
|
// falls back to the best-effort parser.
|
||||||
|
func TestSrcInfoLoadDirFallsBackToPKGBUILD(t *testing.T) {
|
||||||
|
got, err := Load(filepath.Join(srcinfoFixtures, "split"), "x86_64")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load: %v", err)
|
||||||
|
}
|
||||||
|
if !slices.Equal(got.PkgNames, []string{"foo", "bar"}) {
|
||||||
|
t.Errorf("PkgNames = %v, want [foo bar]", got.PkgNames)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSrcInfoLoadFilePKGBUILD: loading a file named PKGBUILD parses it as
|
||||||
|
// such regardless of directory.
|
||||||
|
func TestSrcInfoLoadFilePKGBUILD(t *testing.T) {
|
||||||
|
got, err := Load(filepath.Join(srcinfoFixtures, "vcs", "PKGBUILD"), "x86_64")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load: %v", err)
|
||||||
|
}
|
||||||
|
if !slices.Equal(got.PkgNames, []string{"mygit"}) {
|
||||||
|
t.Errorf("PkgNames = %v, want [mygit]", got.PkgNames)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSrcInfoLoadFileSRCINFO: loading a file named .SRCINFO parses it as
|
||||||
|
// such.
|
||||||
|
func TestSrcInfoLoadFileSRCINFO(t *testing.T) {
|
||||||
|
got, err := Load(filepath.Join(srcinfoFixtures, "mytool", ".SRCINFO"), "x86_64")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load: %v", err)
|
||||||
|
}
|
||||||
|
if got.PkgBase != "mytool" {
|
||||||
|
t.Errorf("PkgBase = %q, want %q", got.PkgBase, "mytool")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSrcInfoLoadUnknownFile: a file that is neither .SRCINFO nor PKGBUILD
|
||||||
|
// is rejected with a clear error.
|
||||||
|
func TestSrcInfoLoadUnknownFile(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
other := filepath.Join(dir, "notes.txt")
|
||||||
|
if err := os.WriteFile(other, []byte("pkgname=foo\npkgver=1\n"), 0o644); err != nil {
|
||||||
|
t.Fatalf("WriteFile: %v", err)
|
||||||
|
}
|
||||||
|
_, err := Load(other, "x86_64")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Load(%s) = nil error, want unknown-file error", other)
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "unknown") {
|
||||||
|
t.Errorf("error %q does not mention the unknown file type", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSrcInfoLoadMissingDir: a nonexistent path errors clearly.
|
||||||
|
func TestSrcInfoLoadMissingDir(t *testing.T) {
|
||||||
|
_, err := Load(filepath.Join(t.TempDir(), "does-not-exist"), "x86_64")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Load(nonexistent) = nil error, want error")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user