Browse Source

Added .SRCINFO parser

v0
maze 3 years ago
parent
commit
99ad037091
5 changed files with 342 additions and 150 deletions
  1. +115
    -23
      package.go
  2. +57
    -63
      package_desc.go
  3. +3
    -64
      package_info.go
  4. +118
    -0
      packagebuild.go
  5. +49
    -0
      packagebuild_test.go

+ 115
- 23
package.go View File

@@ -1,11 +1,15 @@
package archlinux

import (
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"net/url"
"reflect"
"strconv"
"strings"
"time"

@@ -29,22 +33,22 @@ type Package struct {
Filename string

// Name of the package
Name string `pkginfo:"pkgname"`
Name string `pkginfo:"pkgname" srcinfo:"pkgname"`

// Base of the package, if this package is a member of a split package
Base string `pkginfo:"pkgbase"`
Base string `pkginfo:"pkgbase" srcinfo:"pkgbase"`

// Version number
Version string `pkgdesc:"VERSION,evr" pkginfo:"pkgver,evr"`
Version string `pkgdesc:"VERSION,evr" pkginfo:"pkgver,evr" srcinfo:"pkgver"`

// Release number
Release string `pkgdesc:"-" pkginfo:"-"`
Release string `pkgdesc:"-" pkginfo:"-" srcinfo:"pkgrel"`

// Epoch
Epoch int `pkgdesc:"-" pkginfo:"-"`
Epoch int64 `pkgdesc:"-" pkginfo:"-" srcinfo:"epoch"`

// Description of the package
Description string `pkgdesc:"DESC" pkginfo:"pkgdesc"`
Description string `pkgdesc:"DESC" pkginfo:"pkgdesc" srcinfo:"pkgdesc"`

// Arch slice of supported architectures
Arch []string
@@ -59,19 +63,19 @@ type Package struct {
Groups []string

// Depends are the packages this package depends on
Depends []string `pkgdesc:"DEPENDS" pkginfo:"depend"`
Depends []string `pkgdesc:"DEPENDS" pkginfo:"depend" srcinfo:"depends"`

// MakeDepends are the packages this package depends on for building
MakeDepends []string `pkgdesc:"MAKEDEPENDS" pkginfo:"makedepend"`
MakeDepends []string `pkgdesc:"MAKEDEPENDS" pkginfo:"makedepend" srcinfo:"makedepends"`

// CheckDepends are the packages this package depends on for running the
// checks during build
CheckDepends []string `pkgdesc:"CHECKDEPENDS" pkginfo:"checkdepend"`
CheckDepends []string `pkgdesc:"CHECKDEPENDS" pkginfo:"checkdepend" srcinfo:"checkdepends"`

// OptionalDepends are the packages this package recommends having
// installed. Note that these values may contain comments, indicated by
// "spec: comment" (the separator is ": ")
OptionalDepends []string `pkgdesc:"OPTDEPENDS" pkginfo:"optdepend"`
OptionalDepends []string `pkgdesc:"OPTDEPENDS" pkginfo:"optdepend" srcinfo:"optdepends"`

// Provides are virtual package names provided by this package
Provides []string
@@ -232,22 +236,110 @@ func ReadPackage(r io.Reader) (*Package, error) {
}
}

func (pkg *Package) parseFields(parseField func(reflect.StructField) (string, string)) (values map[string]reflect.Value, encodings map[string]string) {
func parsePackageFields(parseField func(reflect.StructField) (string, string), v ...interface{}) (values map[string]reflect.Value, encodings map[string]string) {
values = make(map[string]reflect.Value)
encodings = make(map[string]string)
var (
value = reflect.Indirect(reflect.ValueOf(pkg))
typ = value.Type()
)
for i := 0; i < value.NumField(); i++ {
fieldValue := value.Field(i)
name, encoding := parseField(typ.Field(i))
if name == "" {
// No name means ignored field
continue
for _, o := range v {
var (
value = reflect.Indirect(reflect.ValueOf(o))
typ = value.Type()
)
for i := 0; i < value.NumField(); i++ {
var (
fieldValue = value.Field(i)
name, encoding = parseField(typ.Field(i))
)
if name == "" {
// No name means ignored field
continue
}
values[name] = fieldValue
encodings[name] = encoding
}
}
return
}

// setPackageValue updates a Package-like struct field with the corresponding
// value; the struct is passed as interface{} o; for evr decoding it will
// iterate over the struct fields and attempts to locate "Release" string and
// "Epoch" int and update them accordingly
func setPackageValue(o interface{}, v reflect.Value, value, encoding string) (err error) {
switch v.Interface().(type) {
case string:
switch encoding {
case "":
v.SetString(value)
case "evr":
p := version.Parse(value)
if p.Version == "" {
return ErrNoPackageVersion
}
v.SetString(p.Version)

ov := reflect.Indirect(reflect.ValueOf(o))
ot := ov.Type()
for i := 0; i < ov.NumField(); i++ {
switch ot.Field(i).Name {
case "Release":
ov.Field(i).SetString(p.Release)
case "Epoch":
if p.Epoch == "" || p.Epoch == "0" {
ov.Field(i).SetInt(0)
} else {
var epoch int64
if epoch, err = strconv.ParseInt(p.Epoch, 10, 64); err != nil {
return fmt.Errorf("archlinux: invalid epoch %q: %v", p.Epoch, err)
}
ov.Field(i).SetInt(epoch)
}
}
}
}
case int64:
var i int64
if i, err = strconv.ParseInt(value, 10, 64); err != nil {
return fmt.Errorf("archlinux: invalid %s %q: %v", v.Type().Name(), value, err)
}
v.SetInt(i)
case []string:
v.Set(reflect.Append(v, reflect.ValueOf(value)))
case []byte:
switch encoding {
case "":
v.SetBytes([]byte(value))
case "base64":
var b []byte
if b, err = base64.StdEncoding.DecodeString(value); err != nil {
return fmt.Errorf("archlinux: invalid base64 value %q: %v", value, err)
}
v.SetBytes(b)
case "hex":
var b []byte
if b, err = hex.DecodeString(value); err != nil {
return fmt.Errorf("archlinux: invalid hex encoded value %q: %v", value, err)
}
v.SetBytes(b)
}
case [][]byte:
switch encoding {
case "":
v.Set(reflect.Append(v, reflect.ValueOf([]byte(value))))
}
case time.Time:
var i int64
if i, err = strconv.ParseInt(value, 10, 64); err != nil {
return fmt.Errorf("archlinux: invalid time %q: %v", value, err)
}
v.Set(reflect.ValueOf(time.Unix(i, 0).UTC()))
case *url.URL:
var u *url.URL
if u, err = url.Parse(value); err != nil {
return fmt.Errorf("archlinux: invalid URL %q: %v", value, err)
}
values[name] = fieldValue
encodings[name] = encoding
v.Set(reflect.ValueOf(u))
default:
return fmt.Errorf("archlinux: unsupported field type %T", v.Interface())
}
return
}


+ 57
- 63
package_desc.go View File

@@ -8,11 +8,8 @@ import (
"io"
"net/url"
"reflect"
"strconv"
"strings"
"time"

"maze.io/archlinux.v0/version"
)

// ReadPackageDesc reads a package description from a desc file
@@ -37,30 +34,13 @@ func ReadPackageDesc(r io.Reader) (*Package, error) {
}

func (pkg *Package) readPackageDesc(r io.Reader) (err error) {
var (
fields = make(map[string]reflect.Value)
encodings = make(map[string]string)
value = reflect.Indirect(reflect.ValueOf(pkg))
typ = value.Type()
)
for i := 0; i < value.NumField(); i++ {
fieldValue := value.Field(i)
fieldType := typ.Field(i)
name, encoding := parseFieldDesc(fieldType)
if name == "" {
// No name means ignored field
continue
}
fields[name] = fieldValue
encodings[name] = encoding
}

var (
s = bufio.NewScanner(r)
line string
lines []string
section string
)
values, encodings := parsePackageFields(parseFieldDesc, pkg)
for s.Scan() {
if err = s.Err(); err != nil {
if err == io.EOF {
@@ -71,59 +51,73 @@ func (pkg *Package) readPackageDesc(r io.Reader) (err error) {
line = s.Text()

if len(line) == 0 {
v, ok := fields[section]
v, ok := values[section]
if !ok {
return fmt.Errorf("archlinux: unsupported section %q", section)
}
switch v.Interface().(type) {
case string:
switch encodings[section] {
case "":
v.SetString(lines[0])
case evrEncoding:
p := version.Parse(lines[0])
pkg.Version = p.Version
pkg.Release = p.Release
if p.Epoch != "" {
if pkg.Epoch, err = strconv.Atoi(p.Epoch); err != nil {
return fmt.Errorf("archlinux: invalid epoch %q: %v", p.Epoch, err)
}
case []string:
for _, line = range lines {
if err = setPackageValue(pkg, v, line, encodings[section]); err != nil {
return
}
}
case []string:
c := make([]string, len(lines))
copy(c, lines)
v.Set(reflect.ValueOf(c))
case []byte:
switch encodings[section] {
case "":
v.SetBytes([]byte(strings.Join(lines, "\n")))
case base64Encoding:
var b []byte
if b, err = base64.StdEncoding.DecodeString(lines[0]); err != nil {
default:
if err = setPackageValue(pkg, v, strings.Join(lines, "\n"), encodings[section]); err != nil {
return
}
}
/*
switch v.Interface().(type) {
case string:
switch encodings[section] {
case "":
v.SetString(lines[0])
case evrEncoding:
p := version.Parse(lines[0])
pkg.Version = p.Version
pkg.Release = p.Release
if p.Epoch != "" {
if pkg.Epoch, err = strconv.Atoi(p.Epoch); err != nil {
return fmt.Errorf("archlinux: invalid epoch %q: %v", p.Epoch, err)
}
}
}
case []string:
c := make([]string, len(lines))
copy(c, lines)
v.Set(reflect.ValueOf(c))
case []byte:
switch encodings[section] {
case "":
v.SetBytes([]byte(strings.Join(lines, "\n")))
case base64Encoding:
var b []byte
if b, err = base64.StdEncoding.DecodeString(lines[0]); err != nil {
return fmt.Errorf("archlinux: error parsing section %q: %v", section, err)
}
v.SetBytes(b)
case hexEncoding:
var b []byte
if b, err = hex.DecodeString(lines[0]); err != nil {
return fmt.Errorf("archlinux: error parsing section %q: %v", section, err)
}
v.SetBytes(b)
}
case time.Time:
var i int64
if i, err = strconv.ParseInt(lines[0], 10, 64); err != nil {
return fmt.Errorf("archlinux: error parsing section %q: %v", section, err)
}
v.SetBytes(b)
case hexEncoding:
var b []byte
if b, err = hex.DecodeString(lines[0]); err != nil {
v.Set(reflect.ValueOf(time.Unix(i, 0)))
case *url.URL:
var u *url.URL
if u, err = url.Parse(lines[0]); err != nil {
return fmt.Errorf("archlinux: error parsing section %q: %v", section, err)
}
v.SetBytes(b)
v.Set(reflect.ValueOf(u))
}
case time.Time:
var i int64
if i, err = strconv.ParseInt(lines[0], 10, 64); err != nil {
return fmt.Errorf("archlinux: error parsing section %q: %v", section, err)
}
v.Set(reflect.ValueOf(time.Unix(i, 0)))
case *url.URL:
var u *url.URL
if u, err = url.Parse(lines[0]); err != nil {
return fmt.Errorf("archlinux: error parsing section %q: %v", section, err)
}
v.Set(reflect.ValueOf(u))
}
*/
} else if line[0] == '%' && line[len(line)-1] == '%' {
section = line[1 : len(line)-1]
lines = lines[:0]


+ 3
- 64
package_info.go View File

@@ -8,11 +8,8 @@ import (
"io"
"net/url"
"reflect"
"strconv"
"strings"
"time"

"maze.io/archlinux.v0/version"
)

// ReadPackageInfo reads a package description from a .PKGINFO file
@@ -62,7 +59,7 @@ func (pkg *Package) readBuildInfo(r io.Reader) (err error) {
}

func (pkg *Package) readPackageInfo(r io.Reader) (err error) {
values, encodings := pkg.parseFields(parseFieldInfo)
values, encodings := parsePackageFields(parseFieldInfo, pkg)
s := bufio.NewScanner(r)
for s.Scan() {
if err = s.Err(); err != nil {
@@ -81,66 +78,8 @@ func (pkg *Package) readPackageInfo(r io.Reader) (err error) {
if !ok {
return fmt.Errorf("archlinux: unknown PKGINFO field %q", fields[0])
}

encoding := encodings[fields[0]]
switch v.Interface().(type) {
case string:
switch encoding {
case "":
v.SetString(fields[1])
case "evr":
p := version.Parse(fields[1])
if p.Version == "" {
return ErrNoPackageVersion
}
pkg.Version = p.Version
if p.Release == "" {
return ErrNoPackageRelease
}
pkg.Release = p.Release
if p.Epoch != "" {
if pkg.Epoch, err = strconv.Atoi(p.Epoch); err != nil {
return fmt.Errorf("archlinux: invalid section %q: %v", fields[0], err)
}
}
}
case int64:
var i int64
if i, err = strconv.ParseInt(fields[1], 10, 64); err != nil {
return fmt.Errorf("archlinux: invalid section %q: %v", fields[0], err)
}
v.SetInt(i)
case []string:
v.Set(reflect.Append(v, reflect.ValueOf(fields[1])))
case []byte:
switch encoding {
case "":
v.SetBytes([]byte(fields[1]))
case "base64":
var b []byte
if b, err = base64.StdEncoding.DecodeString(fields[1]); err != nil {
return fmt.Errorf("archlinux: invalid section %q: %v", fields[0], err)
}
v.SetBytes(b)
case "hex":
var b []byte
if b, err = hex.DecodeString(fields[1]); err != nil {
return fmt.Errorf("archlinux: invalid section %q: %v", fields[0], err)
}
v.SetBytes(b)
}
case time.Time:
var i int64
if i, err = strconv.ParseInt(fields[1], 10, 64); err != nil {
return fmt.Errorf("archlinux: invalid section %q: %v", fields[0], err)
}
v.Set(reflect.ValueOf(time.Unix(i, 0).UTC()))
case *url.URL:
var u *url.URL
if u, err = url.Parse(fields[1]); err != nil {
return fmt.Errorf("archlinux: invalid section %q: %v", fields[0], err)
}
v.Set(reflect.ValueOf(u))
if err = setPackageValue(pkg, v, fields[1], encodings[fields[0]]); err != nil {
return
}
}
return


+ 118
- 0
packagebuild.go View File

@@ -0,0 +1,118 @@
package archlinux

import (
"bufio"
"fmt"
"io"
"reflect"
"strings"
)

// PackageBuild describes a PKGBUILD file
type PackageBuild struct {
// Package is the base package
Package

// Packages are the packages contained in this build
Packages map[string]*Package

// Source files
Source []string

// SourceArch are arch specific sources
SourceArch map[string][]string

MD5Sums [][]byte
SHA1Sums [][]byte
SHA256Sums [][]byte
SHA384Sums [][]byte
SHA512Sums [][]byte
}

// ReadPackageBuild reads a PKGBUILD file
func ReadPackageBuild(r io.Reader) (*PackageBuild, error) {
return nil, nil
}

func parseFieldSrcInfo(field reflect.StructField) (name, encoding string) {
tag := field.Tag.Get("srcinfo")
switch tag {
case "-":
// Ignored tag
case "":
// No desc tag, default to upper case field name
name = strings.ToLower(field.Name)
default:
if i := strings.IndexByte(tag, ','); i != -1 {
name, encoding = tag[:i], tag[i+1:]
} else {
name = tag
}
}
return
}

// ReadSrcInfo reads a .SRCINFO file
func ReadSrcInfo(r io.Reader) (*PackageBuild, error) {
var (
build = &PackageBuild{
Packages: make(map[string]*Package),
}
pkg = &build.Package
s = bufio.NewScanner(r)
)
values, encodings := parsePackageFields(parseFieldSrcInfo, build, &build.Package)
for s.Scan() {
if err := s.Err(); err != nil {
if err == io.EOF {
break
}
return nil, err
}
line := s.Text()
if line == "" {
continue
}
if line[0] == '#' {
continue
}
if line[0] == '\t' || line[0] == ' ' {
fields := strings.SplitN(strings.TrimSpace(line), " = ", 2)
if len(fields) != 2 {
continue
}
v, ok := values[fields[0]]
if !ok {
return nil, fmt.Errorf("archlinux: unknown .SRCINFO field %q", fields[0])
}
if err := setPackageValue(pkg, v, fields[1], encodings[fields[0]]); err != nil {
return nil, err
}
} else if strings.HasPrefix(line, "pkgbase = ") {
build.Base = line[len("pkgbase = "):]
build.Name = build.Base
pkg = &build.Package
} else if strings.HasPrefix(line, "pkgname = ") {
var (
ok bool
name = line[len("pkgname = "):]
)
if pkg, ok = build.Packages[name]; !ok {
pkg = &Package{
Name: name,
Base: build.Base,
Version: build.Version,
Release: build.Release,
Epoch: build.Epoch,
Arch: make([]string, len(build.Arch)),
License: make([]string, len(build.License)),
}
copy(pkg.Arch, build.Arch)
copy(pkg.License, build.License)
build.Packages[name] = pkg
}
values, encodings = parsePackageFields(parseFieldSrcInfo, pkg)
}
}
return build, nil
}

+ 49
- 0
packagebuild_test.go View File

@@ -0,0 +1,49 @@
package archlinux

import (
"bytes"
"testing"
)

func TestReadSrcInfo(t *testing.T) {
build, err := ReadSrcInfo(bytes.NewBufferString(testReadSrcInfo))
if err != nil {
t.Fatal(err)
}
t.Run(build.String(), func(t *testing.T) {
if l := len(build.Packages); l != 2 {
t.Fatalf(`expected 2 packages, got %d`, l)
}
t.Run("packages", func(t *testing.T) {
for name, pkg := range build.Packages {
t.Run(name, func(t *testing.T) {
t.Log(pkg)
if l := len(pkg.Depends); l != 2 {
t.Fatalf(`expected 2 dependencies, got %d`, l)
}
})
}
})
})
}

var (
testReadSrcInfo = `pkgbase = python-pyjnius
pkgdesc = Python module to access Java class as Python class, using JNI.
pkgver = 1.1.1
pkgrel = 1
url = https://github.com/kivy/pyjnius
arch = any
license = LGPL3
source = pyjnius-1.1.1.tar.gz::https://github.com/kivy/pyjnius/tarball/1.1.1
md5sums = 2d457e4761b27e6760cf54efb6201f17

pkgname = python2-pyjnius
depends = java-environment
depends = python2

pkgname = python-pyjnius
depends = java-environment
depends = python
`
)

Loading…
Cancel
Save