Something went wrong. Try again.
Maps Linux distribution source packages to their upstream repositories, and through them to each other
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239package arch
import ( "bufio" "bytes" "errors" "fmt" "path" "regexp" "strings")
// SRCINFO is the parsed content of a packaging repo's .SRCINFO (the output of// `makepkg --printsrcinfo`): the pkgbase section plus one section per split package. Values are// already bash-expanded, so no PKGBUILD interpretation is needed.type SRCINFO struct { PkgBase string PkgVer string PkgRel string Epoch string URL string // Sources lists every source entry of every architecture (source, source_x86_64, ...) in // order of appearance; Source.Arch says which. Sources []Source // PkgNames lists the split packages built from this pkgbase, in order. PkgNames []string ValidPGPKeys []string // Fields holds every other pkgbase-section key (pkgdesc, arch, license, depends, // makedepends, sha512sums, b2sums, options, ...) with its values in order of appearance. Fields map[string][]string // Packages holds the per-pkgname override sections keyed by package name. Packages map[string]map[string][]string}
// Version renders the full package version: [epoch:]pkgver-pkgrel.func (s *SRCINFO) Version() string { v := s.PkgVer if s.PkgRel != "" { v += "-" + s.PkgRel } if s.Epoch != "" && s.Epoch != "0" { v = s.Epoch + ":" + v } return v}
// Source is one entry of a source array, following the makepkg grammar//// [<localname>::]<url>[#tag=|#commit=|#branch=|#revision=<ref>][?signed]//// where <url> is [vcs+]scheme://... for remote sources or a bare file name for a file kept in// the packaging repo. The ?signed query may appear before or after the fragment.type Source struct { // Raw is the entry exactly as written. Raw string // Arch is empty for `source =` and the architecture for `source_<arch> =`. Arch string // LocalName is the name makepkg saves the source under in $srcdir: the explicit // "name::" prefix, the repository name for VCS sources, or the URL/file base name. LocalName string // Local is true for files that live in the packaging repo (no URL scheme). Local bool // URL is the download URL without the vcs+ prefix, fragment and ?signed query; empty when // Local. URL string // VCS is git, hg, svn, bzr or fossil for VCS sources, else "". VCS string // RefType/Ref hold the fragment selector: tag, commit, branch or revision. RefType string Ref string // Signed is set when the entry asks makepkg to verify a signature (?signed). Signed bool}
// Remote returns the fetch spec makepkg would use for a remote source: [vcs+]url[#type=ref].// It is the right thing to hand to repourl: the vcs prefix marks git URLs that lack a .git// suffix and the fragment carries the pinned ref. Empty for local files.func (s Source) Remote() string { if s.Local { return "" } u := s.URL if s.VCS != "" { u = s.VCS + "+" + u } if s.RefType != "" { u += "#" + s.RefType + "=" + s.Ref } return u}
var ( vcsPrefixRe = regexp.MustCompile(`^(git|hg|svn|bzr|fossil)\+`) srcinfoKV = regexp.MustCompile(`^([A-Za-z0-9_]+)\s*=\s*(.*)$`))
// ParseSource parses one source entry.func ParseSource(raw string) Source { s := Source{Raw: raw} rest := strings.TrimSpace(raw) if i := strings.Index(rest, "::"); i >= 0 && !strings.ContainsAny(rest[:i], "/:") { s.LocalName = rest[:i] rest = rest[i+2:] } if !strings.Contains(rest, "://") { s.Local = true if s.LocalName == "" { s.LocalName = rest } s.URL = "" return s } if m := vcsPrefixRe.FindStringSubmatch(rest); m != nil { s.VCS = m[1] rest = rest[len(m[0]):] } frag := "" if i := strings.Index(rest, "#"); i >= 0 { frag = rest[i+1:] rest = rest[:i] } // "?signed" may sit on the URL (before the fragment) or trail the fragment. if strings.HasSuffix(frag, "?signed") { s.Signed = true frag = strings.TrimSuffix(frag, "?signed") } if strings.HasSuffix(rest, "?signed") { s.Signed = true rest = strings.TrimSuffix(rest, "?signed") } if k, v, ok := strings.Cut(frag, "="); ok { switch k { case "tag", "commit", "branch", "revision": s.RefType, s.Ref = k, v } } s.URL = rest if s.LocalName == "" { base := path.Base(strings.TrimRight(rest, "/")) if i := strings.Index(base, "?"); i >= 0 { base = base[:i] } if s.VCS != "" { base = strings.TrimSuffix(base, ".git") } s.LocalName = base } return s}
// ParseSRCINFO parses a .SRCINFO file.func ParseSRCINFO(b []byte) (*SRCINFO, error) { si := &SRCINFO{Fields: map[string][]string{}, Packages: map[string]map[string][]string{}} var cur map[string][]string // nil while in the pkgbase section sc := bufio.NewScanner(bytes.NewReader(b)) sc.Buffer(make([]byte, 0, 64*1024), 4*1024*1024) lineNo := 0 for sc.Scan() { lineNo++ line := strings.TrimSpace(sc.Text()) if line == "" || strings.HasPrefix(line, "#") { continue } m := srcinfoKV.FindStringSubmatch(line) if m == nil { return nil, fmt.Errorf("srcinfo: line %d: cannot parse %q", lineNo, line) } key, val := m[1], strings.TrimSpace(m[2]) switch key { case "pkgbase": if si.PkgBase != "" { return nil, fmt.Errorf("srcinfo: line %d: second pkgbase", lineNo) } si.PkgBase = val cur = nil continue case "pkgname": si.PkgNames = append(si.PkgNames, val) cur = map[string][]string{} si.Packages[val] = cur continue } if cur != nil { cur[key] = append(cur[key], val) continue } switch { case key == "pkgver": si.PkgVer = val case key == "pkgrel": si.PkgRel = val case key == "epoch": si.Epoch = val case key == "url": si.URL = val case key == "validpgpkeys": si.ValidPGPKeys = append(si.ValidPGPKeys, val) case key == "source" || strings.HasPrefix(key, "source_"): s := ParseSource(val) s.Arch = strings.TrimPrefix(strings.TrimPrefix(key, "source"), "_") si.Sources = append(si.Sources, s) default: si.Fields[key] = append(si.Fields[key], val) } } if err := sc.Err(); err != nil { return nil, fmt.Errorf("srcinfo: %w", err) } if si.PkgBase == "" { return nil, errors.New("srcinfo: no pkgbase") } return si, nil}
var ( plusWordRe = regexp.MustCompile(`([a-zA-Z0-9]+)\+([a-zA-Z]+)`) plusRe = regexp.MustCompile(`\+`) specialRe = regexp.MustCompile(`[^a-zA-Z0-9_.-]`) dashRunRe = regexp.MustCompile(`[_-]{2,}`) reservedTree = "tree" reservedTrees = "unix-tree")
// ProjectPath maps a pkgbase onto its GitLab project path under archlinux/packaging/packages,// mirroring devtools' gitlab_project_name_to_path(): a '+' between an alphanumeric and an// alphabetic run becomes '-', any other '+' becomes "plus", other special characters become// '-', runs of '_'/'-' collapse to one '-', and the GitLab-reserved name "tree" becomes// "unix-tree". The project's display name keeps the real pkgbase.func ProjectPath(pkgbase string) string { p := plusWordRe.ReplaceAllString(pkgbase, "$1-$2") p = plusRe.ReplaceAllString(p, "plus") p = specialRe.ReplaceAllString(p, "-") p = dashRunRe.ReplaceAllString(p, "-") if p == reservedTree { p = reservedTrees } return p}