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.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157package opensuse
import ( "bytes" "context" "encoding/xml" "fmt" "io" "regexp" "strings" "time"
"github.com/klauspost/compress/zstd"
"tangled.org/odd.computer/isomorph/internal/distro" "tangled.org/odd.computer/isomorph/internal/fetch" "tangled.org/odd.computer/isomorph/internal/model")
// Resolution starts from Tumbleweed's repository metadata rather than OBS one package at a// time: the src-oss repository's primary.xml lists every source package with its version and spec// Url:, and the oss repository's lists every binary package with the source RPM it was built// from. Both are single downloads from download.opensuse.org (redirected to a mirror).
// DefaultRepoBase is the Tumbleweed repository tree.const DefaultRepoBase = "https://download.opensuse.org/tumbleweed/repo"
// RPM is one package entry of a primary.xml.type RPM struct { Name string Arch string Version string // [epoch:]ver-rel URL string // SourceRPM is the source RPM file a binary was built from (empty for source packages). SourceRPM string}
// Primary downloads and parses the primary metadata of repo ("src-oss", "oss").func Primary(ctx context.Context, c *fetch.Client, base, repo string) ([]RPM, error) { if base == "" { base = DefaultRepoBase } md, err := c.Get(ctx, base+"/"+repo+"/repodata/repomd.xml", fetch.WithTTL(6*time.Hour)) if err != nil { return nil, fmt.Errorf("opensuse: %s repomd: %w", repo, err) } m := primaryHrefRe.FindSubmatch(md) if m == nil { return nil, fmt.Errorf("opensuse: %s repomd lists no primary metadata", repo) } // The file name carries its checksum, so a cached copy is valid for good. b, err := c.Get(ctx, base+"/"+repo+"/"+string(m[1]), fetch.WithTTL(30*24*time.Hour)) if err != nil { return nil, fmt.Errorf("opensuse: %s primary: %w", repo, err) } var r io.Reader = bytes.NewReader(b) if bytes.HasSuffix(m[1], []byte(".zst")) { zr, err := zstd.NewReader(r) if err != nil { return nil, err } defer zr.Close() r = zr } return ParsePrimary(r)}
var primaryHrefRe = regexp.MustCompile(`<data type="primary">[\s\S]*?<location href="([^"]+)"`)
// ParsePrimary streams a primary.xml.func ParsePrimary(r io.Reader) ([]RPM, error) { dec := xml.NewDecoder(r) var out []RPM for { tok, err := dec.Token() if err == io.EOF { break } if err != nil { return nil, fmt.Errorf("opensuse: primary: %w", err) } se, ok := tok.(xml.StartElement) if !ok || se.Name.Local != "package" { continue } var p struct { Name string `xml:"name"` Arch string `xml:"arch"` Version struct { Epoch string `xml:"epoch,attr"` Ver string `xml:"ver,attr"` Rel string `xml:"rel,attr"` } `xml:"version"` URL string `xml:"url"` Format struct { SourceRPM string `xml:"sourcerpm"` } `xml:"format"` } if err := dec.DecodeElement(&p, &se); err != nil { return nil, fmt.Errorf("opensuse: primary: %w", err) } v := p.Version.Ver + "-" + p.Version.Rel if p.Version.Epoch != "" && p.Version.Epoch != "0" { v = p.Version.Epoch + ":" + v } out = append(out, RPM{Name: p.Name, Arch: p.Arch, Version: v, URL: strings.TrimSpace(p.URL), SourceRPM: p.Format.SourceRPM}) } return out, nil}
// SourceName returns the package name of a source RPM file ("foo-bar-1.2-3.1.src.rpm" ->// "foo-bar"): the name is everything before the last two dash-separated fields.func SourceName(srpm string) string { s := strings.TrimSuffix(srpm, ".src.rpm") s = strings.TrimSuffix(s, ".nosrc.rpm") for i := 0; i < 2; i++ { j := strings.LastIndexByte(s, '-') if j < 0 { return "" } s = s[:j] } return s}
// Candidates scores what the repository metadata, giturls and, where they were fetched, the// package's OBS files say about a package: the spec Url: as a homepage, giturls entries as// corroborating git URLs, the _service's source-control URL and the spec's Git-Clone and// SourceN lines as the packaging's own statements. (Source URLs taken from the giturls debug// lines instead of the spec were tried and measured: +1% coverage, lower agreement with// Debian, mostly through archive.apache.org directories that are not repositories.)func Candidates(homepage string, giturls []string, files *OBSFiles) []distro.Candidate { var r Resolver var cands []distro.Candidate for _, u := range giturls { cands = distro.AddCandidate(cands, u, model.Evidence{Source: "zq1:giturls", Value: u, Weight: 0.7}) } if files != nil { if len(files.Service) > 0 { cands, _, _ = r.serviceCandidates(cands, files.Service) } if len(files.Spec) > 0 { spec := ParseSpec(string(files.Spec)) cands, _ = r.specCandidates(cands, spec, []*Spec{spec}) } } if homepage != "" { cands = r.addHomepage(cands, homepage, "spec:URL") } return distro.RankCandidates(cands)}
// GitURLs returns giturls.good (package -> git URLs) from the default results directory.func (r *Resolver) GitURLs(ctx context.Context) (map[string][]string, error) { return r.loadGitURLs(ctx)}