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