Arch Linux helpers and parsers
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

405 lines
10 KiB

package archlinux
import (
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"net/url"
"reflect"
"strconv"
"strings"
"time"
"maze.io/archlinux.v0/tarball"
"maze.io/archlinux.v0/version"
)
// Errors
var (
ErrPackageArchiveNameInvalid = errors.New("archlinux: invalid package archive name")
ErrPackageNameInvalid = errors.New("archlinux: invalid package name")
ErrPackageNoChecksums = errors.New("archlinux: package has no checksums")
ErrNoPackageName = errors.New("archlinux: name is missing")
ErrNoPackageVersion = errors.New("archlinux: version is missing")
ErrNoPackageRelease = errors.New("archlinux: release is missing")
)
// Package describes an Arch Linux package
type Package struct {
// Filename is the name of the underlying archive or PKGBUILD
Filename string
// Name of the package
Name string `pkginfo:"pkgname" srcinfo:"pkgname"`
// Base of the package, if this package is a member of a split package
Base string `pkginfo:"pkgbase" srcinfo:"pkgbase"`
// Version number
Version string `pkgdesc:"VERSION,evr" pkginfo:"pkgver,evr" srcinfo:"pkgver"`
// Release number
Release string `pkgdesc:"-" pkginfo:"-" srcinfo:"pkgrel"`
// Epoch
Epoch int64 `pkgdesc:"-" pkginfo:"-" srcinfo:"epoch"`
// Description of the package
Description string `pkgdesc:"DESC" pkginfo:"pkgdesc" srcinfo:"pkgdesc"`
// Arch slice of supported architectures
Arch []string
// URL of the project
URL *url.URL
// License one or more licenses
License []string
// Groups are package groups
Groups []string
// Depends are the packages this package depends on
Depends []string `pkgdesc:"DEPENDS" pkginfo:"depend" srcinfo:"depends"`
// MakeDepends are the packages this package depends on for building
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" 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" srcinfo:"optdepends"`
// Provides are virtual package names provided by this package
Provides []string
// Conflicts are packages that conflict with this package
Conflicts []string
// Replaces are packages that will be replaced if this package is installed
Replaces []string
// Backup are the files that will be backed up to .pacsave if they exist on
// the file system if this package is installed
Backup []string
// MD5 is the MD-5 hex digest of the package file
MD5 []byte `pkgdesc:"MD5SUM,hex" pkginfo:"md5sum,hex"`
// SHA1 is the SHA-1 hex digest of the package file
SHA1 []byte `pkgdesc:"SHA1SUM,hex" pkginfo:"sha1sum,hex"`
// SHA256 is the SHA-256 hex digest of the package file
SHA256 []byte `pkgdesc:"SHA256SUM,hex" pkginfo:"sha256sum,hex"`
// SHA384 is the SHA-384 hex digest of the package file
SHA384 []byte `pkgdesc:"SHA384SUM,hex" pkginfo:"sha384sum,hex"`
// SHA512 is the SHA-512 hex digest of the package file
SHA512 []byte `pkgdesc:"SHA512SUM,hex" pkginfo:"sha512sum,hex"`
// PGPSignature is the deteched PGP signature base64 encoded digest of the
// package file
PGPSignature []byte `pkgdesc:"PGPSIG,base64" pkginfo:"pgpsig,base64"`
// Size of the installed package
Size int64 `pkgdesc:"ISIZE" pkginfo:"size"`
// CompressedSize of the archive
CompressedSize int64 `pkgdesc:"CSIZE" pkginfo:"-"`
// Options are the makepkg build options used for this package
Options []string
// BuildEnv are the options during the build
BuildEnv []string
// BuildDate is the date the package was built
BuildDate time.Time
// Packager name and email
Packager string
// Maintainers for this package
Maintainers []string
// Contributors to this package
Contributors []string
// Files is a list of files provided by the package
Files []string
}
// Common strings
const (
unknown = "unknown"
base64Encoding = "base64"
evrEncoding = "evr"
hexEncoding = "hex"
)
func (pkg Package) String() string {
var s = make([]string, 3)
if pkg.Name != "" {
s[0] = pkg.Name
} else {
s[0] = unknown
}
if pkg.Version != "" {
s[1] = pkg.Version
} else {
s[1] = unknown
}
if pkg.Release != "" {
s[2] = pkg.Release
} else {
s[2] = unknown
}
return strings.Join(s, "-")
}
// MarshalJSON converts a Package to JSON encoded bytes
func (pkg *Package) MarshalJSON() ([]byte, error) {
type Alias Package
return json.Marshal(&struct {
BuildDate int64
URL string
*Alias
}{
BuildDate: pkg.BuildDate.Unix(),
URL: pkg.URL.String(),
Alias: (*Alias)(pkg),
})
}
// UnmarshalJSON converts JSON encoded bytes to a Package
func (pkg *Package) UnmarshalJSON(data []byte) error {
type Alias Package
var (
aux = &struct {
BuildDate int64
URL string
*Alias
}{
Alias: (*Alias)(pkg),
}
err error
)
if err = json.Unmarshal(data, &aux); err != nil {
return err
}
if pkg.URL, err = url.Parse(aux.URL); err != nil {
return err
}
pkg.BuildDate = time.Unix(aux.BuildDate, 0)
return nil
}
// ReadPackage reads a package description from a package tarball.
func ReadPackage(r io.Reader) (*Package, error) {
t, err := tarball.NewReader(r)
if err != nil {
return nil, err
}
pkg := new(Package)
if n, ok := r.(namer); ok {
pkg.Filename = n.Name()
}
// Scan the contents of the tarball
for {
h, err := t.Next()
if err == io.EOF {
return pkg, nil
} else if err != nil {
return nil, err
}
switch strings.TrimLeft(h.Name, "/") {
case ".BUILDINFO":
if err = pkg.readBuildInfo(t); err != nil {
return nil, err
}
case ".PKGINFO":
if err = pkg.readPackageInfo(t); err != nil {
return nil, err
}
default:
pkg.Files = append(pkg.Files, strings.TrimLeft(h.Name, "/"))
}
}
}
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)
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:
for _, part := range strings.Fields(value) {
if part == "SKIP" && (encoding == "skip" || strings.HasSuffix(encoding, ",skip")) {
continue
}
switch strings.SplitN(encoding, ",", 2)[0] {
case "":
v.Set(reflect.Append(v, reflect.ValueOf([]byte(part))))
case "hex":
var b []byte
if b, err = hex.DecodeString(part); err != nil {
return fmt.Errorf("archlinux: invalid hex encoded value %q: %v", value, err)
}
v.Set(reflect.Append(v, reflect.ValueOf(b)))
}
}
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)
}
v.Set(reflect.ValueOf(u))
default:
return fmt.Errorf("archlinux: unsupported field type %T", v.Interface())
}
return
}
// ParsePackageArchiveName parsea an Arch Linux package archive name
func ParsePackageArchiveName(arc string) (name string, v version.Version, arch, ext string, err error) {
i := strings.Index(arc, ".pkg.tar.")
if i == -1 {
err = ErrPackageArchiveNameInvalid
return
}
name, ext = arc[:i], arc[i:]
if i = strings.LastIndexByte(name, '-'); i == -1 {
// No arch
err = ErrPackageArchiveNameInvalid
return
}
name, arch = name[:i], name[i+1:]
if i = strings.LastIndexByte(name, '-'); i == -1 {
// No release
err = ErrPackageArchiveNameInvalid
return
}
if i = strings.LastIndexByte(name[:i], '-'); i == -1 {
// No version
err = ErrPackageArchiveNameInvalid
return
}
name, v = name[:i], version.Parse(name[i+1:])
return
}
// ParsePackageName parses an Arch Linux package name to its name and version
func ParsePackageName(s string) (name string, v version.Version, err error) {
i := strings.LastIndexByte(s, '-')
if i == -1 {
// No release
err = ErrPackageNameInvalid
return
}
if i = strings.LastIndexByte(s[:i], '-'); i == -1 {
// No version
err = ErrPackageArchiveNameInvalid
return
}
name, v = s[:i], version.Parse(s[i+1:])
return
}