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.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219package arch
import ( "bufio" "bytes" "regexp" "strings")
// ParsePKGBUILD reads the fields bulk resolution needs (pkgbase, pkgname, pkgver, pkgrel,// epoch, url, source arrays and their checksums) from a PKGBUILD without running bash. It is// the fallback for packaging commits that carry no .SRCINFO. Only top-level assignments are// read; $var and ${var...} expand to earlier values (parameter-expansion operators are// ignored), and one level of brace alternatives ({,.sig}) is expanded. Anything more dynamic// stays unexpanded, which is enough to recognise a source's host and repository.func ParsePKGBUILD(b []byte) (*SRCINFO, error) { vars := map[string]string{} arrays := map[string][]string{} sc := bufio.NewScanner(bytes.NewReader(b)) sc.Buffer(make([]byte, 64<<10), 1<<20) depth := 0 // brace depth of function bodies being skipped for sc.Scan() { line := sc.Text() trimmed := strings.TrimSpace(line) if depth > 0 { depth += strings.Count(line, "{") - strings.Count(line, "}") continue } if funcRe.MatchString(line) { depth = strings.Count(line, "{") - strings.Count(line, "}") if depth == 0 && !strings.Contains(line, "}") { depth = 1 // the brace is on the next line if sc.Scan() { depth = strings.Count(sc.Text(), "{") - strings.Count(sc.Text(), "}") } } continue } if trimmed == "" || strings.HasPrefix(trimmed, "#") { continue } m := assignRe.FindStringSubmatch(line) if m == nil { continue } name, appendTo, val := m[1], m[2] == "+", m[3] if strings.HasPrefix(val, "(") { body := val[1:] for !closesArray(body) && sc.Scan() { body += "\n" + sc.Text() } body = body[:strings.LastIndex(body, ")")] var words []string for _, w := range splitWords(body) { words = append(words, braceExpand(expand(w, vars))...) } if appendTo { arrays[name] = append(arrays[name], words...) } else { arrays[name] = words } continue } if w := splitWords(val); len(w) > 0 { vars[name] = expand(w[0], vars) } else { vars[name] = "" } } si := &SRCINFO{ PkgBase: vars["pkgbase"], PkgVer: vars["pkgver"], PkgRel: vars["pkgrel"], Epoch: vars["epoch"], URL: vars["url"], PkgNames: arrays["pkgname"], Fields: map[string][]string{}, Packages: map[string]map[string][]string{}, } if len(si.PkgNames) == 0 && vars["pkgname"] != "" { si.PkgNames = []string{vars["pkgname"]} } if si.PkgBase == "" && len(si.PkgNames) > 0 { si.PkgBase = si.PkgNames[0] } for name, vals := range arrays { arch := "" switch { case name == "source": case strings.HasPrefix(name, "source_"): arch = strings.TrimPrefix(name, "source_") default: if strings.HasSuffix(name, "sums") || strings.Contains(name, "sums_") { si.Fields[name] = vals } continue } for _, v := range vals { s := ParseSource(v) s.Arch = arch si.Sources = append(si.Sources, s) } } // Keep the architecture-independent source array first, as .SRCINFO does, so checksums pair // by position. sortSources(si.Sources) return si, nil}
var ( funcRe = regexp.MustCompile(`^\s*[A-Za-z_][A-Za-z0-9_-]*\s*\(\)\s*\{?`) assignRe = regexp.MustCompile(`^([A-Za-z_][A-Za-z0-9_]*)(\+?)=(.*)$`) varRe = regexp.MustCompile(`\$\{([A-Za-z_][A-Za-z0-9_]*)[^}]*\}|\$([A-Za-z_][A-Za-z0-9_]*)`) braceRe = regexp.MustCompile(`\{([^{}]*,[^{}]*)\}`))
// closesArray reports whether an array body read so far contains its closing parenthesis// outside quotes.func closesArray(s string) bool { quote := byte(0) for i := 0; i < len(s); i++ { switch c := s[i]; { case quote != 0: if c == quote { quote = 0 } case c == '\'' || c == '"': quote = c case c == '#': if j := strings.IndexByte(s[i:], '\n'); j >= 0 { i += j } else { return false } case c == ')': return true } } return false}
// splitWords splits a bash word list on unquoted whitespace, removing quotes and comments.func splitWords(s string) []string { var out []string var cur strings.Builder quote := byte(0) in := false for i := 0; i < len(s); i++ { c := s[i] switch { case quote != 0: if c == quote { quote = 0 } else { cur.WriteByte(c) } case c == '\'' || c == '"': quote, in = c, true case c == '#' && !in: for i < len(s) && s[i] != '\n' { i++ } case c == ' ' || c == '\t' || c == '\n': if in { out = append(out, cur.String()) cur.Reset() in = false } default: cur.WriteByte(c) in = true } } if in { out = append(out, cur.String()) } return out}
func expand(s string, vars map[string]string) string { return varRe.ReplaceAllStringFunc(s, func(m string) string { sub := varRe.FindStringSubmatch(m) name := sub[1] if name == "" { name = sub[2] } if v, ok := vars[name]; ok { return v } return m })}
// braceExpand expands the first {a,b} group of w.func braceExpand(w string) []string { loc := braceRe.FindStringSubmatchIndex(w) if loc == nil { return []string{w} } var out []string for _, alt := range strings.Split(w[loc[2]:loc[3]], ",") { out = append(out, w[:loc[0]]+alt+w[loc[1]:]) } return out}
// sortSources moves architecture-specific sources after the generic ones, keeping order.func sortSources(s []Source) { var gen, spec []Source for _, x := range s { if x.Arch == "" { gen = append(gen, x) } else { spec = append(spec, x) } } copy(s, append(gen, spec...))}