Replace gopkgbuild with go-srcinfo (#528)

* Prefer vercmp over gopkgbuild

* Replace gopkgbuild with go-srcinfo
This commit is contained in:
Anna 2018-07-16 15:28:18 +01:00 committed by J Guerreiro
parent b1fb1b9656
commit d6b862357d
16 changed files with 797 additions and 1324 deletions

14
Gopkg.lock generated
View file

@ -1,6 +1,12 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
name = "github.com/Morganamilo/go-srcinfo"
packages = ["."]
revision = "368edc79b2c53cd9c065818fd4e65843ef3e9e11"
[[projects]]
branch = "master"
name = "github.com/jguer/go-alpm"
@ -13,15 +19,9 @@
packages = ["."]
revision = "837b260b8e90895c45737e2e72313fe5bce6f2c4"
[[projects]]
branch = "master"
name = "github.com/mikkeloscar/gopkgbuild"
packages = ["."]
revision = "2bb4f1f1db67f81fe50f9c1c4ad9db4f20fd6b22"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "456465ee334310996a51a2282bf4cfe9f6269db508479c962474d61a4ce0a08c"
inputs-digest = "2a7c6ddb680b62cd3f3cf47a0dfdad617e4a42e29204c75ecf5be3271feda5e1"
solver-name = "gps-cdcl"
solver-version = 1

View file

@ -13,4 +13,4 @@
[[constraint]]
branch = "master"
name = "github.com/mikkeloscar/gopkgbuild"
name = "github.com/Morganamilo/go-srcinfo"

View file

@ -6,7 +6,6 @@ import (
"sync"
alpm "github.com/jguer/go-alpm"
// gopkg "github.com/mikkeloscar/gopkgbuild"
)
func (dp *depPool) checkInnerConflict(name string, conflict string, conflicts mapStringSet) {

View file

@ -8,9 +8,9 @@ import (
"strconv"
"strings"
gosrc "github.com/Morganamilo/go-srcinfo"
alpm "github.com/jguer/go-alpm"
rpc "github.com/mikkeloscar/aur"
gopkg "github.com/mikkeloscar/gopkgbuild"
)
// Install handles package installs
@ -25,7 +25,7 @@ func install(parser *arguments) error {
warnings := &aurWarnings{}
removeMake := false
srcinfosStale := make(map[string]*gopkg.PKGBUILD)
srcinfosStale := make(map[string]*gosrc.Srcinfo)
//remotenames: names of all non repo packages on the system
_, _, localNames, remoteNames, err := filterPackages()
@ -407,7 +407,7 @@ func install(parser *arguments) error {
return nil
}
func getIncompatible(pkgs []*rpc.Pkg, srcinfos map[string]*gopkg.PKGBUILD, bases map[string][]*rpc.Pkg) (stringSet, error) {
func getIncompatible(pkgs []*rpc.Pkg, srcinfos map[string]*gosrc.Srcinfo, bases map[string][]*rpc.Pkg) (stringSet, error) {
incompatible := make(stringSet)
alpmArch, err := alpmHandle.Arch()
if err != nil {
@ -711,14 +711,14 @@ func editPkgBuilds(pkgs []*rpc.Pkg, bases map[string][]*rpc.Pkg) error {
return nil
}
func parseSRCINFOFiles(pkgs []*rpc.Pkg, srcinfos map[string]*gopkg.PKGBUILD, bases map[string][]*rpc.Pkg) error {
func parseSRCINFOFiles(pkgs []*rpc.Pkg, srcinfos map[string]*gosrc.Srcinfo, bases map[string][]*rpc.Pkg) error {
for k, pkg := range pkgs {
dir := filepath.Join(config.BuildDir, pkg.PackageBase)
str := bold(cyan("::") + " Parsing SRCINFO (%d/%d): %s\n")
fmt.Printf(str, k+1, len(pkgs), cyan(formatPkgbase(pkg, bases)))
pkgbuild, err := gopkg.ParseSRCINFO(filepath.Join(dir, ".SRCINFO"))
pkgbuild, err := gosrc.ParseFile(filepath.Join(dir, ".SRCINFO"))
if err != nil {
return fmt.Errorf("%s: %s", pkg.Name, err)
}
@ -729,14 +729,14 @@ func parseSRCINFOFiles(pkgs []*rpc.Pkg, srcinfos map[string]*gopkg.PKGBUILD, bas
return nil
}
func tryParsesrcinfosFile(pkgs []*rpc.Pkg, srcinfos map[string]*gopkg.PKGBUILD, bases map[string][]*rpc.Pkg) {
func tryParsesrcinfosFile(pkgs []*rpc.Pkg, srcinfos map[string]*gosrc.Srcinfo, bases map[string][]*rpc.Pkg) {
for k, pkg := range pkgs {
dir := filepath.Join(config.BuildDir, pkg.PackageBase)
str := bold(cyan("::") + " Parsing SRCINFO (%d/%d): %s\n")
fmt.Printf(str, k+1, len(pkgs), cyan(formatPkgbase(pkg, bases)))
pkgbuild, err := gopkg.ParseSRCINFO(filepath.Join(dir, ".SRCINFO"))
pkgbuild, err := gosrc.ParseFile(filepath.Join(dir, ".SRCINFO"))
if err != nil {
fmt.Printf("cannot parse %s skipping: %s\n", pkg.Name, err)
continue
@ -752,15 +752,11 @@ func pkgBuildsToSkip(pkgs []*rpc.Pkg, targets stringSet) stringSet {
for _, pkg := range pkgs {
if config.ReDownload == "no" || (config.ReDownload == "yes" && !targets.get(pkg.Name)) {
dir := filepath.Join(config.BuildDir, pkg.PackageBase, ".SRCINFO")
pkgbuild, err := gopkg.ParseSRCINFO(dir)
pkgbuild, err := gosrc.ParseFile(dir)
if err == nil {
versionRPC, errR := gopkg.NewCompleteVersion(pkg.Version)
versionPKG, errP := gopkg.NewCompleteVersion(pkgbuild.Version())
if errP == nil && errR == nil {
if !versionRPC.Newer(versionPKG) {
toSkip.set(pkg.PackageBase)
}
if alpm.VerCmp(pkgbuild.Version(), pkg.Version) > 0 {
toSkip.set(pkg.PackageBase)
}
}
}
@ -833,7 +829,7 @@ func downloadPkgBuildsSources(pkgs []*rpc.Pkg, bases map[string][]*rpc.Pkg, inco
return
}
func buildInstallPkgBuilds(dp *depPool, do *depOrder, srcinfos map[string]*gopkg.PKGBUILD, parser *arguments, incompatible stringSet, conflicts mapStringSet) error {
func buildInstallPkgBuilds(dp *depPool, do *depOrder, srcinfos map[string]*gosrc.Srcinfo, parser *arguments, incompatible stringSet, conflicts mapStringSet) error {
for _, pkg := range do.Aur {
dir := filepath.Join(config.BuildDir, pkg.PackageBase)
built := true

View file

@ -7,8 +7,8 @@ import (
"os/exec"
"strings"
gosrc "github.com/Morganamilo/go-srcinfo"
rpc "github.com/mikkeloscar/aur"
gopkg "github.com/mikkeloscar/gopkgbuild"
)
// pgpKeySet maps a PGP key with a list of PKGBUILDs that require it.
@ -41,7 +41,7 @@ func (set pgpKeySet) get(key string) bool {
// checkPgpKeys iterates through the keys listed in the PKGBUILDs and if needed,
// asks the user whether yay should try to import them.
func checkPgpKeys(pkgs []*rpc.Pkg, bases map[string][]*rpc.Pkg, srcinfos map[string]*gopkg.PKGBUILD) error {
func checkPgpKeys(pkgs []*rpc.Pkg, bases map[string][]*rpc.Pkg, srcinfos map[string]*gosrc.Srcinfo) error {
// Let's check the keys individually, and then we can offer to import
// the problematic ones.
problematic := make(pgpKeySet)
@ -51,7 +51,7 @@ func checkPgpKeys(pkgs []*rpc.Pkg, bases map[string][]*rpc.Pkg, srcinfos map[str
for _, pkg := range pkgs {
srcinfo := srcinfos[pkg.PackageBase]
for _, key := range srcinfo.Validpgpkeys {
for _, key := range srcinfo.ValidPGPKeys {
// If key already marked as problematic, indicate the current
// PKGBUILD requires it.
if problematic.get(key) {

View file

@ -11,8 +11,8 @@ import (
"regexp"
"testing"
gosrc "github.com/Morganamilo/go-srcinfo"
rpc "github.com/mikkeloscar/aur"
gopkg "github.com/mikkeloscar/gopkgbuild"
)
const (
@ -124,6 +124,14 @@ func TestImportKeys(t *testing.T) {
}
}
func makeSrcinfo(pkgbase string, pgpkeys ...string) *gosrc.Srcinfo {
srcinfo := gosrc.Srcinfo{}
srcinfo.Pkgbase = pkgbase
srcinfo.ValidPGPKeys = pgpkeys
return &srcinfo
}
func TestCheckPgpKeys(t *testing.T) {
keyringDir, err := ioutil.TempDir("/tmp", "yay-test-keyring")
if err != nil {
@ -139,7 +147,7 @@ func TestCheckPgpKeys(t *testing.T) {
casetests := []struct {
pkgs []*rpc.Pkg
srcinfos map[string]*gopkg.PKGBUILD
srcinfos map[string]*gosrc.Srcinfo
bases map[string][]*rpc.Pkg
wantError bool
}{
@ -147,7 +155,7 @@ func TestCheckPgpKeys(t *testing.T) {
// 487EACC08557AD082088DABA1EB2638FF56C0C53: Dave Reisner.
{
pkgs: []*rpc.Pkg{newPkg("cower")},
srcinfos: map[string]*gopkg.PKGBUILD{"cower": {Pkgbase: "cower", Validpgpkeys: []string{"487EACC08557AD082088DABA1EB2638FF56C0C53"}}},
srcinfos: map[string]*gosrc.Srcinfo{"cower": makeSrcinfo("cower", "487EACC08557AD082088DABA1EB2638FF56C0C53")},
bases: map[string][]*rpc.Pkg{"cower": {newPkg("cower")}},
wantError: false,
},
@ -156,7 +164,7 @@ func TestCheckPgpKeys(t *testing.T) {
// B6C8F98282B944E3B0D5C2530FC3042E345AD05D: Hans Wennborg.
{
pkgs: []*rpc.Pkg{newPkg("libc++")},
srcinfos: map[string]*gopkg.PKGBUILD{"libc++": {Pkgbase: "libc++", Validpgpkeys: []string{"11E521D646982372EB577A1F8F0871F202119294", "B6C8F98282B944E3B0D5C2530FC3042E345AD05D"}}},
srcinfos: map[string]*gosrc.Srcinfo{"libc++": makeSrcinfo("libc++", "11E521D646982372EB577A1F8F0871F202119294", "B6C8F98282B944E3B0D5C2530FC3042E345AD05D")},
bases: map[string][]*rpc.Pkg{"libc++": {newPkg("libc++")}},
wantError: false,
},
@ -164,7 +172,7 @@ func TestCheckPgpKeys(t *testing.T) {
// ABAF11C65A2970B130ABE3C479BE3E4300411886: Linus Torvalds.
{
pkgs: []*rpc.Pkg{newPkg("dummy-1"), newPkg("dummy-2")},
srcinfos: map[string]*gopkg.PKGBUILD{"dummy-1": {Pkgbase: "dummy-1", Validpgpkeys: []string{"ABAF11C65A2970B130ABE3C479BE3E4300411886"}}, "dummy-2": {Pkgbase: "dummy-2", Validpgpkeys: []string{"ABAF11C65A2970B130ABE3C479BE3E4300411886"}}},
srcinfos: map[string]*gosrc.Srcinfo{"dummy-1": makeSrcinfo("dummy-1", "ABAF11C65A2970B130ABE3C479BE3E4300411886"), "dummy-2": makeSrcinfo("dummy-2", "ABAF11C65A2970B130ABE3C479BE3E4300411886")},
bases: map[string][]*rpc.Pkg{"dummy-1": {newPkg("dummy-1")}, "dummy-2": {newPkg("dummy-2")}},
wantError: false,
},
@ -174,21 +182,21 @@ func TestCheckPgpKeys(t *testing.T) {
// C52048C0C0748FEE227D47A2702353E0F7E48EDB: Thomas Dickey.
{
pkgs: []*rpc.Pkg{newPkg("dummy-3")},
srcinfos: map[string]*gopkg.PKGBUILD{"dummy-3": {Pkgbase: "dummy-3", Validpgpkeys: []string{"11E521D646982372EB577A1F8F0871F202119294", "C52048C0C0748FEE227D47A2702353E0F7E48EDB"}}},
srcinfos: map[string]*gosrc.Srcinfo{"dummy-3": makeSrcinfo("dummy-3", "11E521D646982372EB577A1F8F0871F202119294", "C52048C0C0748FEE227D47A2702353E0F7E48EDB")},
bases: map[string][]*rpc.Pkg{"dummy-3": {newPkg("dummy-3")}},
wantError: false,
},
// Two dummy packages with existing keys.
{
pkgs: []*rpc.Pkg{newPkg("dummy-4"), newPkg("dummy-5")},
srcinfos: map[string]*gopkg.PKGBUILD{"dummy-4": {Pkgbase: "dummy-4", Validpgpkeys: []string{"11E521D646982372EB577A1F8F0871F202119294"}}, "dummy-5": {Pkgbase: "dummy-5", Validpgpkeys: []string{"C52048C0C0748FEE227D47A2702353E0F7E48EDB"}}},
srcinfos: map[string]*gosrc.Srcinfo{"dummy-4": makeSrcinfo("dummy-4", "11E521D646982372EB577A1F8F0871F202119294"), "dummy-5": makeSrcinfo("dummy-5", "C52048C0C0748FEE227D47A2702353E0F7E48EDB")},
bases: map[string][]*rpc.Pkg{"dummy-4": {newPkg("dummy-4")}, "dummy-5": {newPkg("dummy-5")}},
wantError: false,
},
// Dummy package with invalid key, should fail.
{
pkgs: []*rpc.Pkg{newPkg("dummy-7")},
srcinfos: map[string]*gopkg.PKGBUILD{"dummy-7": {Pkgbase: "dummy-7", Validpgpkeys: []string{"THIS-SHOULD-FAIL"}}},
srcinfos: map[string]*gosrc.Srcinfo{"dummy-7": makeSrcinfo("dummy-7", "THIS-SHOULD-FAIL")},
bases: map[string][]*rpc.Pkg{"dummy-7": {newPkg("dummy-7")}},
wantError: true,
},
@ -196,7 +204,7 @@ func TestCheckPgpKeys(t *testing.T) {
// A314827C4E4250A204CE6E13284FC34C8E4B1A25: Thomas Bächler.
{
pkgs: []*rpc.Pkg{newPkg("dummy-8")},
srcinfos: map[string]*gopkg.PKGBUILD{"dummy-8": {Pkgbase: "dummy-8", Validpgpkeys: []string{"A314827C4E4250A204CE6E13284FC34C8E4B1A25", "THIS-SHOULD-FAIL"}}},
srcinfos: map[string]*gosrc.Srcinfo{"dummy-8": makeSrcinfo("dummy-8", "A314827C4E4250A204CE6E13284FC34C8E4B1A25", "THIS-SHOULD-FAIL")},
bases: map[string][]*rpc.Pkg{"dummy-8": {newPkg("dummy-8")}},
wantError: true,
},

View file

@ -9,7 +9,6 @@ import (
alpm "github.com/jguer/go-alpm"
rpc "github.com/mikkeloscar/aur"
pkgb "github.com/mikkeloscar/gopkgbuild"
)
// upgrade type describes a system upgrade.
@ -63,64 +62,49 @@ func (u upSlice) Less(i, j int) bool {
}
func getVersionDiff(oldVersion, newversion string) (left, right string) {
old, errOld := pkgb.NewCompleteVersion(oldVersion)
new, errNew := pkgb.NewCompleteVersion(newversion)
if errOld != nil {
left = red("Invalid Version")
}
if errNew != nil {
right = red("Invalid Version")
func getVersionDiff(oldVersion, newVersion string) (left, right string) {
if oldVersion == newVersion {
return oldVersion, newVersion
}
if errOld == nil && errNew == nil {
oldVersion := old.String()
newVersion := new.String()
diffPosition := 0
if oldVersion == newVersion {
return oldVersion, newVersion
}
diffPosition := 0
checkWords := func(str string, index int, words ...string) bool {
for _, word := range words {
wordLength := len(word)
nextIndex := index + 1
if (index < len(str)-wordLength) &&
(str[nextIndex:(nextIndex+wordLength)] == word) {
return true
}
}
return false
}
for index, char := range oldVersion {
charIsSpecial := !(unicode.IsLetter(char) || unicode.IsNumber(char))
if (index >= len(newVersion)) || (char != rune(newVersion[index])) {
if charIsSpecial {
diffPosition = index
}
break
}
if charIsSpecial ||
(((index == len(oldVersion)-1) || (index == len(newVersion)-1)) &&
((len(oldVersion) != len(newVersion)) ||
(oldVersion[index] == newVersion[index]))) ||
checkWords(oldVersion, index, "rc", "pre", "alpha", "beta") {
diffPosition = index + 1
checkWords := func(str string, index int, words ...string) bool {
for _, word := range words {
wordLength := len(word)
nextIndex := index + 1
if (index < len(str)-wordLength) &&
(str[nextIndex:(nextIndex+wordLength)] == word) {
return true
}
}
samePart := oldVersion[0:diffPosition]
left = samePart + red(oldVersion[diffPosition:])
right = samePart + green(newVersion[diffPosition:])
return false
}
for index, char := range oldVersion {
charIsSpecial := !(unicode.IsLetter(char) || unicode.IsNumber(char))
if (index >= len(newVersion)) || (char != rune(newVersion[index])) {
if charIsSpecial {
diffPosition = index
}
break
}
if charIsSpecial ||
(((index == len(oldVersion)-1) || (index == len(newVersion)-1)) &&
((len(oldVersion) != len(newVersion)) ||
(oldVersion[index] == newVersion[index]))) ||
checkWords(oldVersion, index, "rc", "pre", "alpha", "beta") {
diffPosition = index + 1
}
}
samePart := oldVersion[0:diffPosition]
left = samePart + red(oldVersion[diffPosition:])
right = samePart + green(newVersion[diffPosition:])
return
}

8
vcs.go
View file

@ -9,8 +9,8 @@ import (
"strings"
"time"
gosrc "github.com/Morganamilo/go-srcinfo"
rpc "github.com/mikkeloscar/aur"
gopkg "github.com/mikkeloscar/gopkgbuild"
)
// Info contains the last commit sha of a repo
@ -25,7 +25,7 @@ type shaInfo struct {
// createDevelDB forces yay to create a DB of the existing development packages
func createDevelDB() error {
infoMap := make(map[string]*rpc.Pkg)
srcinfosStale := make(map[string]*gopkg.PKGBUILD)
srcinfosStale := make(map[string]*gosrc.Srcinfo)
_, _, _, remoteNames, err := filterPackages()
if err != nil {
@ -103,7 +103,7 @@ func parseSource(source string) (url string, branch string, protocols []string)
return
}
func updateVCSData(pkgName string, sources []string) {
func updateVCSData(pkgName string, sources []gosrc.ArchString) {
if savedInfo == nil {
savedInfo = make(vcsInfo)
}
@ -111,7 +111,7 @@ func updateVCSData(pkgName string, sources []string) {
info := make(shaInfos)
for _, source := range sources {
url, branch, protocols := parseSource(source)
url, branch, protocols := parseSource(source.Value)
if url == "" || branch == "" {
continue
}

38
vendor/github.com/Morganamilo/go-srcinfo/line_error.go generated vendored Normal file
View file

@ -0,0 +1,38 @@
package srcinfo
import (
"fmt"
)
// LineError is an error type that stores the line number at which an error
// occurred as well the full Line that cased the error and an error string.
type LineError struct {
LineNumber int // The line number at which the error occurred
Line string // The line that caused the error
ErrorStr string // An error string
}
// Error Returns an error string in the format:
// "Line <LineNumber>: <ErrorStr>: <Line>".
func (le LineError) Error() string {
return fmt.Sprintf("Line %d: %s: %s", le.LineNumber, le.ErrorStr, le.Line)
}
// Error Returns a new LineError
func Error(LineNumber int, Line string, ErrorStr string) *LineError {
return &LineError{
LineNumber,
Line,
ErrorStr,
}
}
// Errorf Returns a new LineError using the same formatting rules as
// fmt.Printf.
func Errorf(LineNumber int, Line string, ErrorStr string, args ...interface{}) *LineError {
return &LineError{
LineNumber,
Line,
fmt.Sprintf(ErrorStr, args...),
}
}

310
vendor/github.com/Morganamilo/go-srcinfo/parser.go generated vendored Normal file
View file

@ -0,0 +1,310 @@
package srcinfo
import (
"fmt"
"io/ioutil"
"strings"
)
// parser is used to track our current state as we parse the srcinfo.
type parser struct {
// srcinfo is a Pointer to the Srcinfo we are currently building.
srcinfo *Srcinfo
// seenPkgnames is a set of pkgnames we have seen
seenPkgnames map[string]struct{}
}
func (psr *parser) currentPackage() (*Package, error) {
if psr.srcinfo.Pkgbase == "" {
return nil, fmt.Errorf("Not in pkgbase or pkgname")
} else if len(psr.srcinfo.Packages) == 0 {
return &psr.srcinfo.Package, nil
} else {
return &psr.srcinfo.Packages[len(psr.srcinfo.Packages) - 1], nil
}
}
func (psr *parser) setHeaderOrField(key, value string) error {
pkgbase := &psr.srcinfo.PackageBase
switch key {
case "pkgbase":
if psr.srcinfo.Pkgbase != "" {
return fmt.Errorf("key \"%s\" can not occur after pkgbase or pkgname", key)
}
pkgbase.Pkgbase = value
return nil
case "pkgname":
if psr.srcinfo.Pkgbase == "" {
return fmt.Errorf("key \"%s\" can not occur before pkgbase", key)
}
if _, ok := psr.seenPkgnames[value]; ok {
return fmt.Errorf("pkgname \"%s\" can not occur more than once", value)
}
psr.seenPkgnames[value] = struct{}{}
psr.srcinfo.Packages = append(psr.srcinfo.Packages, Package{Pkgname: value})
return nil
}
if psr.srcinfo.Pkgbase == "" {
return fmt.Errorf("key \"%s\" can not occur before pkgbase or pkgname", key)
}
return psr.setField(key, value)
}
func (psr *parser) setField(archKey, value string) error {
pkg, err := psr.currentPackage()
if err != nil {
return err
}
pkgbase := &psr.srcinfo.PackageBase
key, arch := splitArchFromKey(archKey)
err = checkArch(psr.srcinfo.Arch, archKey, arch)
if err != nil {
return err
}
if value == "" {
value = EmptyOverride
}
// pkgbase only + not arch dependent
found := true
switch archKey {
case "pkgver":
pkgbase.Pkgver = value
case "pkgrel":
pkgbase.Pkgrel = value
case "epoch":
pkgbase.Epoch = value
case "validpgpkeys":
pkgbase.ValidPGPKeys = append(pkgbase.ValidPGPKeys, value)
case "noextract":
pkgbase.NoExtract = append(pkgbase.NoExtract, value)
default:
found = false
}
if found {
if len(psr.srcinfo.Packages) > 0 {
return fmt.Errorf("key \"%s\" can not occur after pkgname", archKey)
}
return nil
}
// pkgbase only + arch dependent
found = true
switch key {
case "source":
pkgbase.Source = append(pkgbase.Source, ArchString{arch, value})
case "md5sums":
pkgbase.MD5Sums = append(pkgbase.MD5Sums, ArchString{arch, value})
case "sha1sums":
pkgbase.SHA1Sums = append(pkgbase.SHA1Sums, ArchString{arch, value})
case "sha224sums":
pkgbase.SHA224Sums = append(pkgbase.SHA224Sums, ArchString{arch, value})
case "sha256sums":
pkgbase.SHA256Sums = append(pkgbase.SHA256Sums, ArchString{arch, value})
case "sha384sums":
pkgbase.SHA384Sums = append(pkgbase.SHA384Sums, ArchString{arch, value})
case "sha512sums":
pkgbase.SHA512Sums = append(pkgbase.SHA512Sums, ArchString{arch, value})
case "makedepends":
pkgbase.MakeDepends = append(pkgbase.MakeDepends, ArchString{arch, value})
case "checkdepends":
pkgbase.CheckDepends = append(pkgbase.CheckDepends, ArchString{arch, value})
default:
found = false
}
if found {
if len(psr.srcinfo.Packages) > 0 {
return fmt.Errorf("key \"%s\" can not occur after pkgname", archKey)
}
return nil
}
// pkgbase or pkgname + not arch dependent
found = true
switch archKey {
case "pkgdesc":
pkg.Pkgdesc = value
case "url":
pkg.URL = value
case "license":
pkg.License = append(pkg.License, value)
case "install":
pkg.Install = value
case "changelog":
pkg.Changelog = value
case "groups":
pkg.Groups = append(pkg.Groups, value)
case "arch":
pkg.Arch = append(pkg.Arch, value)
case "backup":
pkg.Backup = append(pkg.Backup, value)
case "options":
pkg.Options = append(pkg.Options, value)
default:
found = false
}
if found {
return nil
}
// pkgbase or pkgname + arch dependent
switch key {
case "depends":
pkg.Depends = append(pkg.Depends, ArchString{arch, value})
case "optdepends":
pkg.OptDepends = append(pkg.OptDepends, ArchString{arch, value})
case "conflicts":
pkg.Conflicts = append(pkg.Conflicts, ArchString{arch, value})
case "provides":
pkg.Provides = append(pkg.Provides, ArchString{arch, value})
case "replaces":
pkg.Replaces = append(pkg.Replaces, ArchString{arch, value})
default:
return fmt.Errorf("Unknown key: \"%s\"", archKey)
}
return nil
}
func parse(data string) (*Srcinfo, error) {
psr := &parser{
&Srcinfo{},
make(map[string]struct{}),
}
lines := strings.Split(data, "\n")
for n, line := range lines {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") {
continue
}
key, value, err := splitPair(line)
if err != nil {
return nil, Error(n+1, line, err.Error())
}
err = psr.setHeaderOrField(key, value)
if err != nil {
return nil, Error(n+1, line, err.Error())
}
}
if psr.srcinfo.Pkgbase == "" {
return nil, fmt.Errorf("No pkgbase field")
}
if len(psr.srcinfo.Packages) == 0 {
return nil, fmt.Errorf("No pkgname field")
}
if psr.srcinfo.Pkgver == "" {
return nil, fmt.Errorf("No pkgver field")
}
if psr.srcinfo.Pkgrel == "" {
return nil, fmt.Errorf("No pkgrel field")
}
if len(psr.srcinfo.Arch) == 0 {
return nil, fmt.Errorf("No arch field")
}
return psr.srcinfo, nil
}
// splitPair splits a key value string in the form of "key = value",
// whitespace being ignored. The key and the value is returned.
func splitPair(line string) (string, string, error) {
split := strings.SplitN(line, "=", 2)
if len(split) != 2 {
return "", "", fmt.Errorf("Line does not contain =")
}
key := strings.TrimSpace(split[0])
value := strings.TrimSpace(split[1])
if key == "" {
return "", "", fmt.Errorf("Key is empty")
}
return key, value, nil
}
// splitArchFromKey splits up architecture dependent field names, separating
// the field name from the architecture they depend on.
func splitArchFromKey(key string) (string, string) {
split := strings.SplitN(key, "_", 2)
if len(split) == 2 {
return split[0], split[1]
}
return split[0], ""
}
// checkArg checks that the arch from an arch dependent string is actually
// defined inside of the srcinfo and speicifly disallows the arch "any" as it
// is not a real arch
func checkArch(arches []string, key string, arch string) error {
if arch == "" {
return nil
}
if arch == "any" {
return fmt.Errorf("Invalid key \"%s\" arch \"%s\" is not allowed", key, arch)
}
for _, a := range arches {
if a == arch {
return nil
}
}
return fmt.Errorf("Invalid key \"%s\" unsupported arch \"%s\"", key, arch)
}
// ParseFile parses a srcinfo file as specified by path.
func ParseFile(path string) (*Srcinfo, error) {
file, err := ioutil.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("Unable to read file: %s: %s", path, err.Error())
}
return Parse(string(file))
}
// Parse parses a srcinfo in string form. Parsing will fail if:
// A srcinfo does not contain all required fields
// The same pkgname is specified more then once
// arch is missing
// pkgver is mising
// pkgrel is missing
// An architecture specific field is defined for an architecture that does not exist
// An unknown key is specified
// An empty value is specified
//
// Required fields are:
// pkgbase
// pkname
// arch
// pkgrel
// pkgver
func Parse(data string) (*Srcinfo, error) {
return parse(data)
}

View file

@ -0,0 +1,155 @@
package srcinfo
import (
"bytes"
)
func appendHeader(buffer *bytes.Buffer, key string, value string) {
if value == "" {
return
}
buffer.WriteString(key + " = " + value + "\n")
}
func appendValue(buffer *bytes.Buffer, key string, value string) {
if value == "" {
return
}
if value == EmptyOverride {
value = ""
}
buffer.WriteString("\t" + key + " = " + value + "\n")
}
func appendMultiValue(buffer *bytes.Buffer, key string, values []string) {
for _, value := range values {
if value == EmptyOverride {
value = ""
}
buffer.WriteString("\t" + key + " = " + value + "\n")
}
}
func appendMultiArchValue(buffer *bytes.Buffer, key string, values []ArchString) {
for _, value := range values {
if value.Value == EmptyOverride {
value.Value = ""
}
if value.Arch == "" {
buffer.WriteString("\t" + key + " = " + value.Value + "\n")
} else {
buffer.WriteString("\t" + key + "_" + value.Arch + " = " + value.Value + "\n")
}
}
}
//String generates a string that should be similar to the srcinfo data used to
//create this Srcinfo struct. Fields will be printed in order and with the same
//whitespcae rules that `makepkg --printsrcinfo` uses.
//
// The order of each global field is as follows:
// pkgdesc
// pkgver
// pkgrel
// epoch
// url
// install
// changelog
// arch
// groups
// license
// checkdepends
// makedepends
// depends
// optdepends
// provides
// conflicts
// replaces
// noextract
// options
// backup
// source
// validpgpkeys
// md5suns
// sha1sums
// sha224sums
// sha256sums
// sha384sums
// sha512sums
//
// The order of each overwritten field is as follows:
// pkgdesc
// url
// install
// changelog
// arch
// groups
// license
// checkdepends
// depends
// optdepends
// provides
// conflicts
// replaces
// options
// backup
func (si *Srcinfo) String() string {
var buffer bytes.Buffer
appendHeader(&buffer, "pkgbase", si.Pkgbase)
appendValue(&buffer, "pkgdesc", si.Pkgdesc)
appendValue(&buffer, "pkgver", si.Pkgver)
appendValue(&buffer, "pkgrel", si.Pkgrel)
appendValue(&buffer, "epoch", si.Epoch)
appendValue(&buffer, "url", si.URL)
appendValue(&buffer, "install", si.Install)
appendValue(&buffer, "changelog", si.Changelog)
appendMultiValue(&buffer, "arch", si.Arch)
appendMultiValue(&buffer, "groups", si.Groups)
appendMultiValue(&buffer, "license", si.License)
appendMultiArchValue(&buffer, "checkdepends", si.CheckDepends)
appendMultiArchValue(&buffer, "makedepends", si.MakeDepends)
appendMultiArchValue(&buffer, "depends", si.Depends)
appendMultiArchValue(&buffer, "optdepends", si.OptDepends)
appendMultiArchValue(&buffer, "provides", si.Provides)
appendMultiArchValue(&buffer, "conflicts", si.Conflicts)
appendMultiArchValue(&buffer, "replaces", si.Replaces)
appendMultiValue(&buffer, "noextract", si.NoExtract)
appendMultiValue(&buffer, "options", si.Options)
appendMultiValue(&buffer, "backup", si.Backup)
appendMultiValue(&buffer, "validpgpkeys", si.ValidPGPKeys)
appendMultiArchValue(&buffer, "source", si.Source)
appendMultiArchValue(&buffer, "md5sums", si.MD5Sums)
appendMultiArchValue(&buffer, "sha1sums", si.SHA1Sums)
appendMultiArchValue(&buffer, "sha224sums", si.SHA224Sums)
appendMultiArchValue(&buffer, "sha256sums", si.SHA256Sums)
appendMultiArchValue(&buffer, "sha384sums", si.SHA384Sums)
appendMultiArchValue(&buffer, "sha512sums", si.SHA512Sums)
for n, pkg := range si.Packages {
appendHeader(&buffer, "\npkgname", si.Packages[n].Pkgname)
appendValue(&buffer, "pkgdesc", pkg.Pkgdesc)
appendValue(&buffer, "url", pkg.URL)
appendValue(&buffer, "install", pkg.Install)
appendValue(&buffer, "changelog", pkg.Changelog)
appendMultiValue(&buffer, "arch", pkg.Arch)
appendMultiValue(&buffer, "groups", pkg.Groups)
appendMultiValue(&buffer, "license", pkg.License)
appendMultiArchValue(&buffer, "depends", pkg.Depends)
appendMultiArchValue(&buffer, "optdepends", pkg.OptDepends)
appendMultiArchValue(&buffer, "provides", pkg.Provides)
appendMultiArchValue(&buffer, "conflicts", pkg.Conflicts)
appendMultiArchValue(&buffer, "replaces", pkg.Replaces)
appendMultiValue(&buffer, "options", pkg.Options)
appendMultiValue(&buffer, "backup", pkg.Backup)
}
return buffer.String()
}

215
vendor/github.com/Morganamilo/go-srcinfo/srcinfo.go generated vendored Normal file
View file

@ -0,0 +1,215 @@
// Package srcinfo is a parser for srcinfo files. Typically generated by
// makepkg, part of the pacman package manager.
//
// Split packages and architecture dependent fields are fully supported.
//
// This Package aimes to parse srcinfos but not interpret them in any way.
// All values are fundamentally strings, other tools should be used for
// things such as dependency parsing, validity checking etc.
package srcinfo
import (
"fmt"
)
// ArchString describes string values that may be architecture dependent.
// For Example depends_x86_64.
// If Arch is an empty string then the field is not architecture dependent.
type ArchString struct {
Arch string // Architecture name
Value string // Value
}
// Package describes the fields of a pkgbuild that may be overwritten by
// in build_<pkgname> function.
type Package struct {
Pkgname string
Pkgdesc string
Arch []string
URL string
License []string
Groups []string
Depends []ArchString
OptDepends []ArchString
Provides []ArchString
Conflicts []ArchString
Replaces []ArchString
Backup []string
Options []string
Install string
Changelog string
}
// PackageBase describes the fields of a pkgbuild that may not be overwritten
// in package_<pkgname> function.
type PackageBase struct {
Pkgbase string
Pkgver string
Pkgrel string
Epoch string
Source []ArchString
ValidPGPKeys []string
NoExtract []string
MD5Sums []ArchString
SHA1Sums []ArchString
SHA224Sums []ArchString
SHA256Sums []ArchString
SHA384Sums []ArchString
SHA512Sums []ArchString
MakeDepends []ArchString
CheckDepends []ArchString
}
// Srcinfo represents a full srcinfo. All global fields are defined here while
// fields overwritten in the package_<pkgname> function are defined in the
// Packages field.
//
// Note: The Packages field only contains the values that each package
// overrides, global fields will be missing. A Package containing both global
// and overwritten fields can be generated using the SplitPackage function.
type Srcinfo struct {
PackageBase // Fields that only apply to the package base
Package // Fields that apply to the package globally
Packages []Package // Fields for each package this package base contains
}
// EmptyOverride is used to signal when a value has been overridden with an
// empty value. An empty ovrride is when a value is defined in the pkgbuild but
// then overridden inside the package function to be empty.
//
// For example "pkgdesc=''" is an empty override on the pkgdesc which would
// lead to the line "pkgdesc=" in the srcinfo.
//
// This value is used internally to store empty overrides, mainly to avoid
// using string pointers. It is possible to check for empty overrides using
// the Packages slice in Packagebase.
//
// During normal use with the SplitPackage function this value will be
// converted back to an empty string, or removed entirely for slice values.
// This means the this value can be completley ignored unless you are
// explicitly looking for empty overrides.
const EmptyOverride = "\x00"
// Version formats a version string from the epoch, pkgver and pkgrel of the
// srcinfo. In the format [epoch:]pkgver-pkgrel.
func (si *Srcinfo) Version() string {
if si.Epoch == "" {
return si.Pkgver + "-" + si.Pkgrel
}
return si.Epoch + ":" + si.Pkgver + "-" + si.Pkgrel
}
// SplitPackages generates a splice of all packages that are part of this
// srcinfo. This is equivalent to calling SplitPackage on every pkgname.
func (si *Srcinfo) SplitPackages() []*Package {
pkgs := make([]*Package, 0, len(si.Packages))
for _, pkg := range si.Packages {
pkgs = append(pkgs, mergeSplitPackage(&si.Package, &pkg))
}
return pkgs
}
// SplitPackage generates a Package that contains all fields that the specified
// pkgname has. But will fall back on global fields if they are not defined in
// the Package.
//
// Note slice values will be passed by reference, it is not recommended you
// modify this struct after it is returned.
func (si *Srcinfo) SplitPackage(pkgname string) (*Package, error) {
for n := range si.Packages {
if si.Packages[n].Pkgname == pkgname {
return mergeSplitPackage(&si.Package, &si.Packages[n]), nil
}
}
return nil, fmt.Errorf("Package \"%s\" is not part of the package base \"%s\"", pkgname, si.Pkgbase)
}
func mergeArchSlice(global, override []ArchString) []ArchString {
overridden := make(map[string]struct{})
merged := make([]ArchString, 0, len(override))
for _, v := range override {
overridden[v.Arch] = struct{}{}
if v.Value == EmptyOverride {
continue
}
merged = append(merged, v)
}
for _, v := range global {
if _, ok := overridden[v.Arch]; !ok {
merged = append(merged, v)
}
}
return merged
}
func mergeSplitPackage(base, split *Package) *Package {
pkg := &Package{}
*pkg = *base
pkg.Pkgname = split.Pkgname
if split.Pkgdesc != "" {
pkg.Pkgdesc = split.Pkgdesc
}
if len(split.Arch) != 0 {
pkg.Arch = split.Arch
}
if split.URL != "" {
pkg.URL = split.URL
}
if len(split.License) != 0 {
pkg.License = split.License
}
if len(split.Groups) != 0 {
pkg.Groups = split.Groups
}
if len(split.Depends) != 0 {
pkg.Depends = mergeArchSlice(pkg.Depends, split.Depends)
}
if len(split.OptDepends) != 0 {
pkg.OptDepends = mergeArchSlice(pkg.OptDepends, split.OptDepends)
}
if len(split.Provides) != 0 {
pkg.Provides = mergeArchSlice(pkg.Provides, split.Provides)
}
if len(split.Conflicts) != 0 {
pkg.Conflicts = mergeArchSlice(pkg.Conflicts, split.Conflicts)
}
if len(split.Replaces) != 0 {
pkg.Replaces = mergeArchSlice(pkg.Replaces, split.Replaces)
}
if len(split.Backup) != 0 {
pkg.Backup = split.Backup
}
if len(split.Options) != 0 {
pkg.Options = split.Options
}
if split.Changelog != "" {
pkg.Changelog = split.Changelog
}
if split.Install != "" {
pkg.Install = split.Install
}
return pkg
}

View file

@ -1,283 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// based on the lexer from: src/pkg/text/template/parse/lex.go (golang source)
package pkgbuild
import (
"fmt"
"strings"
"unicode"
"unicode/utf8"
)
// pos is a position in input being scanned
type pos int
type item struct {
typ itemType
pos pos
val string
}
func (i item) String() string {
switch {
case i.typ == itemEOF:
return "EOF"
case i.typ == itemError:
return i.val
case len(i.val) > 10:
return fmt.Sprintf("%.10q...", i.val)
}
return fmt.Sprintf("%q", i.val)
}
type itemType int
const (
itemError itemType = iota
itemEOF
itemVariable
itemValue
itemEndSplit
// PKGBUILD variables
itemPkgname // pkgname variable
itemPkgver // pkgver variable
itemPkgrel // pkgrel variable
itemPkgdir // pkgdir variable
itemEpoch // epoch variable
itemPkgbase // pkgbase variable
itemPkgdesc // pkgdesc variable
itemArch // arch variable
itemURL // url variable
itemLicense // license variable
itemGroups // groups variable
itemDepends // depends variable
itemOptdepends // optdepends variable
itemMakedepends // makedepends variable
itemCheckdepends // checkdepends variable
itemProvides // provides variable
itemConflicts // conflicts variable
itemReplaces // replaces variable
itemBackup // backup variable
itemOptions // options variable
itemInstall // install variable
itemChangelog // changelog variable
itemSource // source variable
itemNoextract // noextract variable
itemMd5sums // md5sums variable
itemSha1sums // sha1sums variable
itemSha224sums // sha224sums variable
itemSha256sums // sha256sums variable
itemSha384sums // sha384sums variable
itemSha512sums // sha512sums variable
itemValidpgpkeys // validpgpkeys variable
)
// PKGBUILD variables
var variables = map[string]itemType{
"pkgname": itemPkgname,
"pkgver": itemPkgver,
"pkgrel": itemPkgrel,
"pkgdir": itemPkgdir,
"epoch": itemEpoch,
"pkgbase": itemPkgbase,
"pkgdesc": itemPkgdesc,
"arch": itemArch,
"url": itemURL,
"license": itemLicense,
"groups": itemGroups,
"depends": itemDepends,
"optdepends": itemOptdepends,
"makedepends": itemMakedepends,
"checkdepends": itemCheckdepends,
"provides": itemProvides,
"conflicts": itemConflicts,
"replaces": itemReplaces,
"backup": itemBackup,
"options": itemOptions,
"install": itemInstall,
"changelog": itemChangelog,
"source": itemSource,
"noextract": itemNoextract,
"md5sums": itemMd5sums,
"sha1sums": itemSha1sums,
"sha224sums": itemSha224sums,
"sha256sums": itemSha256sums,
"sha384sums": itemSha384sums,
"sha512sums": itemSha512sums,
"validpgpkeys": itemValidpgpkeys,
}
const eof = -1
// stateFn represents the state of the scanner as a function that returns the next state
type stateFn func(*lexer) stateFn
// lexer holds the state of the scanner
type lexer struct {
input string
state stateFn
pos pos
start pos
width pos
lastPos pos
items chan item // channel of scanned items
}
// next returns the next rune in the input
func (l *lexer) next() rune {
if int(l.pos) >= len(l.input) {
l.width = 0
return eof
}
r, w := utf8.DecodeRuneInString(l.input[l.pos:])
l.width = pos(w)
l.pos += l.width
return r
}
// peek returns but does not consume the next rune in the input
func (l *lexer) peek() rune {
r := l.next()
l.backup()
return r
}
// backup steps back one rune. Can only be called once per call of next
func (l *lexer) backup() {
l.pos -= l.width
}
// emit passes an item back to the client
func (l *lexer) emit(t itemType) {
l.items <- item{t, l.start, l.input[l.start:l.pos]}
l.start = l.pos
}
// ignore skips over the pending input before this point
func (l *lexer) ignore() {
l.start = l.pos
}
// errorf returns an error token and terminates the scan by passing
// back a nil pointer that will be the next state, terminating l.nextItem.
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
l.items <- item{itemError, l.start, fmt.Sprintf(format, args...)}
return nil
}
// nextItem returns the next item from the input.
func (l *lexer) nextItem() item {
item := <-l.items
l.lastPos = item.pos
return item
}
func lex(input string) *lexer {
l := &lexer{
input: input,
items: make(chan item),
}
go l.run()
return l
}
func (l *lexer) run() {
for l.state = lexEnv; l.state != nil; {
l.state = l.state(l)
}
}
func lexEnv(l *lexer) stateFn {
var r rune
for {
switch r = l.next(); {
case r == eof:
l.emit(itemEOF)
return nil
case isAlphaNumericUnderscore(r):
return lexVariable
case r == '\n':
buffer := l.input[l.start:l.pos]
if buffer == "\n" {
if l.peek() == '\n' {
l.next()
l.emit(itemEndSplit)
}
l.ignore()
}
case r == '\t':
l.ignore()
case r == ' ':
l.ignore()
case r == '#':
return lexComment
default:
l.errorf("unable to parse character: %c", r)
}
}
}
func lexComment(l *lexer) stateFn {
for {
switch l.next() {
case '\n':
l.ignore()
return lexEnv
case eof:
l.emit(itemEOF)
return nil
}
}
}
func lexVariable(l *lexer) stateFn {
for {
switch r := l.next(); {
case isAlphaNumericUnderscore(r):
// absorb
case r == ' ' && l.peek() == '=':
l.backup()
variable := l.input[l.start:l.pos]
// strip arch from source_arch like constructs
witharch := strings.SplitN(variable, "_", 2)
if len(witharch) == 2 {
variable = witharch[0]
}
if _, ok := variables[variable]; ok {
l.emit(variables[variable])
// TODO to cut off ' = '
l.next()
l.next()
l.next()
l.ignore()
return lexValue
}
return l.errorf("invalid variable: %s", variable)
default:
pattern := l.input[l.start:l.pos]
return l.errorf("invalid pattern: %s", pattern)
}
}
}
func lexValue(l *lexer) stateFn {
for {
switch l.next() {
case '\n':
l.backup()
l.emit(itemValue)
return lexEnv
}
}
}
// isAlphaNumericUnderscore reports whether r is an alphabetic, digit, or underscore.
func isAlphaNumericUnderscore(r rune) bool {
return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
}

View file

@ -1,625 +0,0 @@
package pkgbuild
import (
"bytes"
"fmt"
"io/ioutil"
"strconv"
"strings"
)
// Dependency describes a dependency with min and max version, if any.
type Dependency struct {
Name string // dependency name
MinVer *CompleteVersion // min version
sgt bool // defines if min version is strictly greater than
MaxVer *CompleteVersion // max version
slt bool // defines if max version is strictly less than
}
// Restrict merges two dependencies together into a new dependency where the
// conditions of both a and b are met
func (a *Dependency) Restrict(b *Dependency) *Dependency {
newDep := &Dependency{
Name: a.Name,
}
if a.MaxVer != nil || b.MaxVer != nil {
newDep.MaxVer = &CompleteVersion{}
if a.MaxVer == nil {
*newDep.MaxVer = *b.MaxVer
newDep.slt = b.slt
} else if b.MaxVer == nil {
*newDep.MaxVer = *a.MaxVer
newDep.slt = a.slt
} else {
cmpMax := a.MaxVer.cmp(b.MaxVer)
if cmpMax >= 1 {
*newDep.MaxVer = *b.MaxVer
newDep.slt = b.slt
} else if cmpMax <= -1 {
*newDep.MaxVer = *a.MaxVer
newDep.slt = a.slt
} else if cmpMax == 0 {
if len(a.MaxVer.Pkgrel) > len(b.MaxVer.Pkgrel) {
*newDep.MaxVer = *a.MaxVer
} else {
*newDep.MaxVer = *b.MaxVer
}
if a.slt != b.slt {
newDep.slt = true
} else {
newDep.slt = a.slt
}
}
}
}
if a.MinVer != nil || b.MinVer != nil {
newDep.MinVer = &CompleteVersion{}
if a.MinVer == nil {
*newDep.MinVer = *b.MinVer
newDep.sgt = b.slt
} else if b.MinVer == nil {
*newDep.MinVer = *a.MinVer
newDep.sgt = a.sgt
} else {
cmpMin := a.MinVer.cmp(b.MinVer)
if cmpMin >= 1 {
*newDep.MinVer = *a.MinVer
newDep.sgt = a.sgt
} else if cmpMin <= -1 {
*newDep.MinVer = *b.MinVer
newDep.sgt = b.sgt
} else if cmpMin == 0 {
if len(a.MinVer.Pkgrel) > len(b.MinVer.Pkgrel) {
*newDep.MinVer = *a.MinVer
} else {
*newDep.MinVer = *b.MinVer
}
if a.sgt != b.sgt {
newDep.sgt = true
} else {
newDep.sgt = a.sgt
}
}
}
}
return newDep
}
func (dep *Dependency) String() string {
str := ""
greaterThan := ">"
lessThan := "<"
if !dep.sgt {
greaterThan = ">="
}
if !dep.slt {
lessThan = "<="
}
if dep.MinVer != nil {
str += dep.Name + greaterThan + dep.MinVer.String()
if dep.MaxVer != nil {
str += " "
}
}
if dep.MaxVer != nil {
str += dep.Name + lessThan + dep.MaxVer.String()
}
return str
}
// PKGBUILD is a struct describing a parsed PKGBUILD file.
// Required fields are:
// pkgname
// pkgver
// pkgrel
// arch
// (license) - not required but recommended
//
// parsing a PKGBUILD file without these fields will fail
type PKGBUILD struct {
Pkgnames []string
Pkgver Version // required
Pkgrel Version // required
Pkgdir string
Epoch int
Pkgbase string
Pkgdesc string
Arch []string // required
URL string
License []string // recommended
Groups []string
Depends []*Dependency
Optdepends []string
Makedepends []*Dependency
Checkdepends []*Dependency
Provides []string
Conflicts []string
Replaces []string
Backup []string
Options []string
Install string
Changelog string
Source []string
Noextract []string
Md5sums []string
Sha1sums []string
Sha224sums []string
Sha256sums []string
Sha384sums []string
Sha512sums []string
Validpgpkeys []string
}
// Newer is true if p has a higher version number than p2
func (p *PKGBUILD) Newer(p2 *PKGBUILD) bool {
if p.Epoch < p2.Epoch {
return false
}
if p.Pkgver.bigger(p2.Pkgver) {
return true
}
if p2.Pkgver.bigger(p.Pkgver) {
return false
}
return p.Pkgrel > p2.Pkgrel
}
// Older is true if p has a smaller version number than p2
func (p *PKGBUILD) Older(p2 *PKGBUILD) bool {
if p.Epoch < p2.Epoch {
return true
}
if p2.Pkgver.bigger(p.Pkgver) {
return true
}
if p.Pkgver.bigger(p2.Pkgver) {
return false
}
return p.Pkgrel < p2.Pkgrel
}
// Version returns the full version of the PKGBUILD (including epoch and rel)
func (p *PKGBUILD) Version() string {
if p.Epoch > 0 {
return fmt.Sprintf("%d:%s-%s", p.Epoch, p.Pkgver, p.Pkgrel)
}
return fmt.Sprintf("%s-%s", p.Pkgver, p.Pkgrel)
}
// CompleteVersion returns a Complete version struct including version, rel and
// epoch.
func (p *PKGBUILD) CompleteVersion() CompleteVersion {
return CompleteVersion{
Version: p.Pkgver,
Epoch: uint8(p.Epoch),
Pkgrel: p.Pkgrel,
}
}
// BuildDepends is Depends, MakeDepends and CheckDepends combined.
func (p *PKGBUILD) BuildDepends() []*Dependency {
// TODO real merge
deps := make([]*Dependency, len(p.Depends)+len(p.Makedepends)+len(p.Checkdepends))
deps = append(p.Depends, p.Makedepends...)
deps = append(deps, p.Checkdepends...)
return deps
}
// IsDevel returns true if package contains devel packages (-{bzr,git,svn,hg})
// TODO: more robust check.
func (p *PKGBUILD) IsDevel() bool {
for _, name := range p.Pkgnames {
if strings.HasSuffix(name, "-git") {
return true
}
if strings.HasSuffix(name, "-svn") {
return true
}
if strings.HasSuffix(name, "-hg") {
return true
}
if strings.HasSuffix(name, "-bzr") {
return true
}
}
return false
}
// MustParseSRCINFO must parse the .SRCINFO given by path or it will panic
func MustParseSRCINFO(path string) *PKGBUILD {
pkgbuild, err := ParseSRCINFO(path)
if err != nil {
panic(err)
}
return pkgbuild
}
// ParseSRCINFO parses .SRCINFO file given by path.
// This is a safe alternative to ParsePKGBUILD given that a .SRCINFO file is
// available
func ParseSRCINFO(path string) (*PKGBUILD, error) {
f, err := ioutil.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("unable to read file: %s, %s", path, err.Error())
}
return parsePKGBUILD(string(f))
}
// ParseSRCINFOContent parses a .SRCINFO formatted byte slice.
// This is a safe alternative to ParsePKGBUILD given that the .SRCINFO content
// is available
func ParseSRCINFOContent(content []byte) (*PKGBUILD, error) {
return parsePKGBUILD(string(content))
}
// parse a PKGBUILD and check that the required fields has a non-empty value
func parsePKGBUILD(input string) (*PKGBUILD, error) {
pkgb, err := parse(input)
if err != nil {
return nil, err
}
if !validPkgver(string(pkgb.Pkgver)) {
return nil, fmt.Errorf("invalid pkgver: %s", pkgb.Pkgver)
}
if len(pkgb.Arch) == 0 {
return nil, fmt.Errorf("Arch missing")
}
if len(pkgb.Pkgnames) == 0 {
return nil, fmt.Errorf("missing pkgname")
}
for _, name := range pkgb.Pkgnames {
if !validPkgname(name) {
return nil, fmt.Errorf("invalid pkgname: %s", name)
}
}
return pkgb, nil
}
// parses a SRCINFO formatted PKGBUILD
func parse(input string) (*PKGBUILD, error) {
var pkgbuild *PKGBUILD
var next item
lexer := lex(input)
Loop:
for {
token := lexer.nextItem()
// strip arch from source_arch like constructs
witharch := strings.SplitN(token.val, "_", 2)
if len(witharch) == 2 {
found := false
for _, arch := range pkgbuild.Arch {
if arch == witharch[1] {
token.val = witharch[0]
found = true
break
}
}
if !found {
return nil, fmt.Errorf("unsupported arch for variable: %s", token.val)
}
}
switch token.typ {
case itemPkgbase:
next = lexer.nextItem()
pkgbuild = &PKGBUILD{Epoch: 0, Pkgbase: next.val}
case itemPkgname:
next = lexer.nextItem()
pkgbuild.Pkgnames = append(pkgbuild.Pkgnames, next.val)
case itemPkgver:
next = lexer.nextItem()
version, err := parseVersion(next.val)
if err != nil {
return nil, err
}
pkgbuild.Pkgver = version
case itemPkgrel:
next = lexer.nextItem()
rel, err := parseVersion(next.val)
if err != nil {
return nil, err
}
pkgbuild.Pkgrel = rel
case itemPkgdir:
next = lexer.nextItem()
pkgbuild.Pkgdir = next.val
case itemEpoch:
next = lexer.nextItem()
epoch, err := strconv.ParseInt(next.val, 10, 0)
if err != nil {
return nil, err
}
if epoch < 0 {
return nil, fmt.Errorf("invalid epoch: %d", epoch)
}
pkgbuild.Epoch = int(epoch)
case itemPkgdesc:
next = lexer.nextItem()
pkgbuild.Pkgdesc = next.val
case itemArch:
next = lexer.nextItem()
pkgbuild.Arch = append(pkgbuild.Arch, next.val)
case itemURL:
next = lexer.nextItem()
pkgbuild.URL = next.val
case itemLicense:
next = lexer.nextItem()
pkgbuild.License = append(pkgbuild.License, next.val)
case itemGroups:
next = lexer.nextItem()
pkgbuild.Groups = append(pkgbuild.Groups, next.val)
case itemDepends:
next = lexer.nextItem()
deps, err := parseDependency(next.val, pkgbuild.Depends)
if err != nil {
return nil, err
}
pkgbuild.Depends = deps
case itemOptdepends:
next = lexer.nextItem()
pkgbuild.Optdepends = append(pkgbuild.Optdepends, next.val)
case itemMakedepends:
next = lexer.nextItem()
deps, err := parseDependency(next.val, pkgbuild.Makedepends)
if err != nil {
return nil, err
}
pkgbuild.Makedepends = deps
case itemCheckdepends:
next = lexer.nextItem()
deps, err := parseDependency(next.val, pkgbuild.Checkdepends)
if err != nil {
return nil, err
}
pkgbuild.Checkdepends = deps
case itemProvides:
next = lexer.nextItem()
pkgbuild.Provides = append(pkgbuild.Provides, next.val)
case itemConflicts:
next = lexer.nextItem()
pkgbuild.Conflicts = append(pkgbuild.Conflicts, next.val)
case itemReplaces:
next = lexer.nextItem()
pkgbuild.Replaces = append(pkgbuild.Replaces, next.val)
case itemBackup:
next = lexer.nextItem()
pkgbuild.Backup = append(pkgbuild.Backup, next.val)
case itemOptions:
next = lexer.nextItem()
pkgbuild.Options = append(pkgbuild.Options, next.val)
case itemInstall:
next = lexer.nextItem()
pkgbuild.Install = next.val
case itemChangelog:
next = lexer.nextItem()
pkgbuild.Changelog = next.val
case itemSource:
next = lexer.nextItem()
pkgbuild.Source = append(pkgbuild.Source, next.val)
case itemNoextract:
next = lexer.nextItem()
pkgbuild.Noextract = append(pkgbuild.Noextract, next.val)
case itemMd5sums:
next = lexer.nextItem()
pkgbuild.Md5sums = append(pkgbuild.Md5sums, next.val)
case itemSha1sums:
next = lexer.nextItem()
pkgbuild.Sha1sums = append(pkgbuild.Sha1sums, next.val)
case itemSha224sums:
next = lexer.nextItem()
pkgbuild.Sha224sums = append(pkgbuild.Sha224sums, next.val)
case itemSha256sums:
next = lexer.nextItem()
pkgbuild.Sha256sums = append(pkgbuild.Sha256sums, next.val)
case itemSha384sums:
next = lexer.nextItem()
pkgbuild.Sha384sums = append(pkgbuild.Sha384sums, next.val)
case itemSha512sums:
next = lexer.nextItem()
pkgbuild.Sha512sums = append(pkgbuild.Sha512sums, next.val)
case itemValidpgpkeys:
next = lexer.nextItem()
pkgbuild.Validpgpkeys = append(pkgbuild.Validpgpkeys, next.val)
case itemEndSplit:
case itemError:
return nil, fmt.Errorf(token.val)
case itemEOF:
break Loop
default:
return nil, fmt.Errorf("invalid variable: %s", token.val)
}
}
return pkgbuild, nil
}
// parse and validate a version string
func parseVersion(s string) (Version, error) {
if validPkgver(s) {
return Version(s), nil
}
return "", fmt.Errorf("invalid version string: %s", s)
}
// check if name is a valid pkgname format
func validPkgname(name string) bool {
if len(name) < 1 {
return false
}
if name[0] == '-' {
return false
}
for _, r := range name {
if !isValidPkgnameChar(r) {
return false
}
}
return true
}
// check if version is a valid pkgver format
func validPkgver(version string) bool {
if len(version) < 1 {
return false
}
if !isAlphaNumeric(rune(version[0])) {
return false
}
for _, r := range version[1:] {
if !isValidPkgverChar(r) {
return false
}
}
return true
}
// ParseDeps parses a string slice of dependencies into a slice of Dependency
// objects.
func ParseDeps(deps []string) ([]*Dependency, error) {
var err error
dependencies := make([]*Dependency, 0)
for _, dep := range deps {
dependencies, err = parseDependency(dep, dependencies)
if err != nil {
return nil, err
}
}
return dependencies, nil
}
// parse dependency with possible version restriction
func parseDependency(dep string, deps []*Dependency) ([]*Dependency, error) {
var name string
var dependency *Dependency
index := -1
if dep == "" {
return deps, nil
}
if dep[0] == '-' {
return nil, fmt.Errorf("invalid dependency name")
}
i := 0
for _, c := range dep {
if !isValidPkgnameChar(c) {
break
}
i++
}
// check if the dependency has been set before
name = dep[0:i]
for n, d := range deps {
if d.Name == name {
index = n
break
}
}
dependency = &Dependency{
Name: name,
sgt: false,
slt: false,
}
if len(dep) != len(name) {
var eq bytes.Buffer
for _, c := range dep[i:] {
if c == '<' || c == '>' || c == '=' {
i++
eq.WriteRune(c)
continue
}
break
}
version, err := NewCompleteVersion(dep[i:])
if err != nil {
return nil, err
}
switch eq.String() {
case "=":
dependency.MinVer = version
dependency.MaxVer = version
case "<=":
dependency.MaxVer = version
case ">=":
dependency.MinVer = version
case "<":
dependency.MaxVer = version
dependency.slt = true
case ">":
dependency.MinVer = version
dependency.sgt = true
}
}
if index == -1 {
deps = append(deps, dependency)
} else {
deps[index] = deps[index].Restrict(dependency)
}
return deps, nil
}
// isLowerAlpha reports whether c is a lowercase alpha character
func isLowerAlpha(c rune) bool {
return 'a' <= c && c <= 'z'
}
// check if c is a valid pkgname char
func isValidPkgnameChar(c rune) bool {
return isAlphaNumeric(c) || c == '@' || c == '.' || c == '_' || c == '+' || c == '-'
}
// check if c is a valid pkgver char
func isValidPkgverChar(c rune) bool {
return isAlphaNumeric(c) || c == '_' || c == '+' || c == '.' || c == '~'
}

View file

@ -1,324 +0,0 @@
package pkgbuild
import (
"fmt"
"strconv"
"strings"
"unicode"
)
// Version string
type Version string
type CompleteVersion struct {
Version Version
Epoch uint8
Pkgrel Version
}
func (c *CompleteVersion) String() string {
str := ""
if c.Epoch > 0 {
str = fmt.Sprintf("%d:", c.Epoch)
}
str = fmt.Sprintf("%s%s", str, c.Version)
if c.Pkgrel != "" {
str = fmt.Sprintf("%s-%s", str, c.Pkgrel)
}
return str
}
// NewCompleteVersion creates a CompleteVersion including basic version, epoch
// and rel from string
func NewCompleteVersion(s string) (*CompleteVersion, error) {
var err error
epoch := 0
rel := Version("")
// handle possible epoch
versions := strings.Split(s, ":")
if len(versions) > 2 {
return nil, fmt.Errorf("invalid version format: %s", s)
}
if len(versions) > 1 {
epoch, err = strconv.Atoi(versions[0])
if err != nil {
return nil, err
}
}
// handle possible rel
versions = strings.Split(versions[len(versions)-1], "-")
if len(versions) > 2 {
return nil, fmt.Errorf("invalid version format: %s", s)
}
if len(versions) > 1 {
rel = Version(versions[1])
}
// finally check that the actual version is valid
if validPkgver(versions[0]) {
return &CompleteVersion{
Version: Version(versions[0]),
Epoch: uint8(epoch),
Pkgrel: rel,
}, nil
}
return nil, fmt.Errorf("invalid version format: %s", s)
}
// Older returns true if a is older than the argument version
func (a *CompleteVersion) Older(b *CompleteVersion) bool {
return a.cmp(b) == -1
}
// Newer returns true if a is newer than the argument version
func (a *CompleteVersion) Newer(b *CompleteVersion) bool {
return a.cmp(b) == 1
}
// Equal returns true if a is equal to the argument version
func (a *CompleteVersion) Equal(b *CompleteVersion) bool {
return a.cmp(b) == 0
}
// Satisfies tests whether or not version fits inside the bounds specified by
// dep
func (version *CompleteVersion) Satisfies(dep *Dependency) bool {
var cmpMax int8
var cmpMin int8
if dep.MaxVer != nil {
cmpMax = version.cmp(dep.MaxVer)
if cmpMax == 1 {
return false
}
if cmpMax == 0 && dep.slt {
return false
}
}
if dep.MinVer != nil {
if dep.MaxVer == dep.MinVer {
cmpMin = cmpMax
} else {
cmpMin = version.cmp(dep.MinVer)
}
if cmpMin == -1 {
return false
}
if cmpMin == 0 && dep.sgt {
return false
}
}
return true
}
// Compare a to b:
// return 1: a is newer than b
// 0: a and b are the same version
// -1: b is newer than a
func (a *CompleteVersion) cmp(b *CompleteVersion) int8 {
if a.Epoch > b.Epoch {
return 1
}
if a.Epoch < b.Epoch {
return -1
}
if a.Version.bigger(b.Version) {
return 1
}
if b.Version.bigger(a.Version) {
return -1
}
if a.Pkgrel == "" || b.Pkgrel == "" {
return 0
}
if a.Pkgrel.bigger(b.Pkgrel) {
return 1
}
if b.Pkgrel.bigger(a.Pkgrel) {
return -1
}
return 0
}
// Compare alpha and numeric segments of two versions.
// return 1: a is newer than b
// 0: a and b are the same version
// -1: b is newer than a
//
// This is based on the rpmvercmp function used in libalpm
// https://projects.archlinux.org/pacman.git/tree/lib/libalpm/version.c
func rpmvercmp(av, bv Version) int {
if av == bv {
return 0
}
a, b := []rune(string(av)), []rune(string(bv))
var one, two, ptr1, ptr2 int
var isNum bool
one, two, ptr1, ptr2 = 0, 0, 0, 0
// loop through each version segment of a and b and compare them
for len(a) > one && len(b) > two {
for len(a) > one && !isAlphaNumeric(a[one]) {
one++
}
for len(b) > two && !isAlphaNumeric(b[two]) {
two++
}
// if we ran to the end of either, we are finished with the loop
if !(len(a) > one && len(b) > two) {
break
}
// if the seperator lengths were different, we are also finished
if one-ptr1 != two-ptr2 {
if one-ptr1 < two-ptr2 {
return -1
}
return 1
}
ptr1 = one
ptr2 = two
// grab first completely alpha or completely numeric segment
// leave one and two pointing to the start of the alpha or numeric
// segment and walk ptr1 and ptr2 to end of segment
if isDigit(a[ptr1]) {
for len(a) > ptr1 && isDigit(a[ptr1]) {
ptr1++
}
for len(b) > ptr2 && isDigit(b[ptr2]) {
ptr2++
}
isNum = true
} else {
for len(a) > ptr1 && isAlpha(a[ptr1]) {
ptr1++
}
for len(b) > ptr2 && isAlpha(b[ptr2]) {
ptr2++
}
isNum = false
}
// take care of the case where the two version segments are
// different types: one numeric, the other alpha (i.e. empty)
// numeric segments are always newer than alpha segments
if two == ptr2 {
if isNum {
return 1
}
return -1
}
if isNum {
// we know this part of the strings only contains digits
// so we can ignore the error value since it should
// always be nil
as, _ := strconv.ParseInt(string(a[one:ptr1]), 10, 0)
bs, _ := strconv.ParseInt(string(b[two:ptr2]), 10, 0)
// whichever number has more digits wins
if as > bs {
return 1
}
if as < bs {
return -1
}
} else {
cmp := alphaCompare(a[one:ptr1], b[two:ptr2])
if cmp < 0 {
return -1
}
if cmp > 0 {
return 1
}
}
// advance one and two to next segment
one = ptr1
two = ptr2
}
// this catches the case where all numeric and alpha segments have
// compared identically but the segment separating characters were
// different
if len(a) <= one && len(b) <= two {
return 0
}
// the final showdown. we never want a remaining alpha string to
// beat an empty string. the logic is a bit weird, but:
// - if one is empty and two is not an alpha, two is newer.
// - if one is an alpha, two is newer.
// - otherwise one is newer.
if (len(a) <= one && !isAlpha(b[two])) || len(a) > one && isAlpha(a[one]) {
return -1
}
return 1
}
// alphaCompare compares two alpha version segments and will return a positive
// value if a is bigger than b and a negative if b is bigger than a else 0
func alphaCompare(a, b []rune) int8 {
if string(a) == string(b) {
return 0
}
i := 0
for len(a) > i && len(b) > i && a[i] == b[i] {
i++
}
if len(a) == i && len(b) > i {
return -1
}
if len(b) == i {
return 1
}
return int8(a[i]) - int8(b[i])
}
// check if version number v is bigger than v2
func (v Version) bigger(v2 Version) bool {
return rpmvercmp(v, v2) == 1
}
// isAlphaNumeric reports whether c is an alpha character or digit
func isAlphaNumeric(c rune) bool {
return isDigit(c) || isAlpha(c)
}
// isAlpha reports whether c is an alpha character
func isAlpha(c rune) bool {
return unicode.IsLetter(c)
}
// isDigit reports whether d is an ASCII digit
func isDigit(d rune) bool {
return unicode.IsDigit(d)
}