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
|
|
}
|