Skip to content

Commit

Permalink
bring back functionality and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
letFunny committed Feb 11, 2025
1 parent e4c1c09 commit 04ebb62
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 97 deletions.
8 changes: 8 additions & 0 deletions internal/setup/export_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
package setup

type YAMLPath = yamlPath

func (r *Release) SetPathOrdering(ordering map[string][]string) {
r.pathOrdering = ordering
}

func (s *Selection) ClearCache() {
s.cachedSelectPackage = nil
}
149 changes: 94 additions & 55 deletions internal/setup/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path/filepath"
"slices"
"strings"

"golang.org/x/crypto/openpgp/packet"
Expand All @@ -19,6 +20,12 @@ type Release struct {
Path string
Packages map[string]*Package
Archives map[string]*Archive

// pathOrdering stores the sorted packages if there is a 'prefer'
// relationship. Otherwise, it will be nil.
// Given a selection of packages, the path should be extracted from the one
// that is found first on the list.
pathOrdering map[string][]string
}

// Archive is the location from which binary packages are obtained.
Expand Down Expand Up @@ -118,8 +125,35 @@ func (s *Slice) String() string { return s.Package + "_" + s.Name }
// the real information coming from packages is still unknown, so referenced
// paths could potentially be missing, for example.
type Selection struct {
Release *Release
Slices []*Slice
Release *Release
Slices []*Slice
cachedSelectPackage map[string]string
}

// SelectPackage returns true if path should be extracted from pkg.
func (s *Selection) SelectPackage(path, pkg string) bool {
// If the path has no prefer relationships then it is always selected.
ordering, ok := s.Release.pathOrdering[path]
if !ok {
return true
}

if cached, ok := s.cachedSelectPackage[path]; ok {
return cached == pkg
}

var selected string
for _, pkg := range ordering {
i := slices.IndexFunc(s.Slices, func(s *Slice) bool {
return s.Package == pkg
})
if i != -1 {
selected = s.Slices[i].Package
break
}
}
s.cachedSelectPackage[path] = selected
return selected == pkg
}

func ReadRelease(dir string) (*Release, error) {
Expand All @@ -129,11 +163,6 @@ func ReadRelease(dir string) (*Release, error) {
}
logf("Processing %s release...", logDir)

release := &Release{
Path: dir,
Packages: make(map[string]*Package),
}

release, err := readRelease(dir)
if err != nil {
return nil, err
Expand All @@ -147,7 +176,7 @@ func ReadRelease(dir string) (*Release, error) {
}

func (r *Release) validate() error {
prefers, err := r.prefers()
prefers, pkgSampleSlice, err := r.prefers()
if err != nil {
return err
}
Expand Down Expand Up @@ -204,22 +233,54 @@ func (r *Release) validate() error {
}
}

for path, slice := range paths {
_, hasPrefers := prefers[preferKey{preferSource, path, ""}]
if !hasPrefers {
continue
}
pkg := slice.Package
for {
r.pathOrdering[path] = append(r.pathOrdering[path], pkg)
pkg = prefers[preferKey{preferTarget, path, pkg}]
if pkg == "" {
break
}
}
slices.Reverse(r.pathOrdering[path])
}

// Check for glob and generate conflicts.
for oldPath, old := range globs {
oldInfo := old.Contents[oldPath]
for newPath, new := range paths {
if oldPath == newPath {
// Identical paths have been filtered earlier. This must be the
// Identical globs have been filtered earlier. This must be the
// exact same entry.
continue
}
newInfo := new.Contents[newPath]
if oldInfo.Kind == GlobPath && (newInfo.Kind == GlobPath || newInfo.Kind == CopyPath) {
if new.Package == old.Package {
continue
if !strdist.GlobPath(newPath, oldPath) {
continue
}
toCheck := []*Slice{new}
_, hasPrefers := prefers[preferKey{preferSource, newPath, ""}]
if hasPrefers {
toCheck = []*Slice{}
for _, pkg := range r.pathOrdering[newPath] {
// We have already checked above that the node exists
// when verifying the ordering.
s, _ := pkgSampleSlice[preferKey{0, newPath, pkg}]
toCheck = append(toCheck, s)
}
}
if strdist.GlobPath(newPath, oldPath) {
for _, new := range toCheck {
// It is okay to check only one slice per packages because the
// content has been validated to be the same earlier.
newInfo := new.Contents[newPath]
if oldInfo.Kind == GlobPath && (newInfo.Kind == GlobPath || newInfo.Kind == CopyPath) {
if new.Package == old.Package {
continue
}
}
if (old.Package > new.Package) || (old.Package == new.Package && old.Name > new.Name) ||
(old.Package == new.Package && old.Name == new.Name && oldPath > newPath) {
old, new = new, old
Expand Down Expand Up @@ -381,13 +442,9 @@ func stripBase(baseDir, path string) string {
func Select(release *Release, slices []SliceKey) (*Selection, error) {
logf("Selecting slices...")

prefers, err := release.prefers()
if err != nil {
return nil, err
}

selection := &Selection{
Release: release,
Release: release,
cachedSelectPackage: make(map[string]string),
}

sorted, err := order(release.Packages, slices)
Expand All @@ -399,31 +456,8 @@ func Select(release *Release, slices []SliceKey) (*Selection, error) {
selection.Slices[i] = release.Packages[key.Package].Slices[key.Slice]
}

paths := make(map[string]*Slice)
for _, new := range selection.Slices {
for newPath, newInfo := range new.Contents {
if old, ok := paths[newPath]; ok {
if new.Package != old.Package {
if p, err := preferredPathPackage(newPath, new.Package, old.Package, prefers); err == nil {
if p == new.Package {
paths[newPath] = new
}
continue
} else if err != preferNone {
return nil, err
}
}

oldInfo := old.Contents[newPath]
if !newInfo.SameContent(&oldInfo) || (newInfo.Kind == CopyPath || newInfo.Kind == GlobPath) && new.Package != old.Package {
if old.Package > new.Package || old.Package == new.Package && old.Name > new.Name {
old, new = new, old
}
return nil, fmt.Errorf("slices %s and %s conflict on %s", old, new, newPath)
}
} else {
paths[newPath] = new
}
// An invalid "generate" value should only throw an error if that
// particular slice is selected. Hence, the check is here.
switch newInfo.Generate {
Expand All @@ -449,24 +483,27 @@ type preferKey struct {
pkg string
}

func (r *Release) prefers() (map[preferKey]string, error) {
func (r *Release) prefers() (map[preferKey]string, map[preferKey]*Slice, error) {
prefers := make(map[preferKey]string)
pkgSampleSlice := make(map[preferKey]*Slice)
for _, pkg := range r.Packages {
for _, slice := range pkg.Slices {
for path, info := range slice.Contents {
// Store a sample slice that contains the path.
pkgSampleSlice[preferKey{0, path, pkg.Name}] = slice
if info.Prefer != "" {
tkey := preferKey{preferTarget, path, pkg.Name}
skey := preferKey{preferSource, path, info.Prefer}
if target, ok := prefers[tkey]; ok {
if target != info.Prefer {
pkg1, pkg2 := sortPair(target, info.Prefer)
return nil, fmt.Errorf("package %q has conflicting prefers for %s: %s != %s",
return nil, nil, fmt.Errorf("package %q has conflicting prefers for %s: %s != %s",
pkg.Name, path, pkg1, pkg2)
}
} else if source, ok := prefers[skey]; ok {
if source != pkg.Name {
pkg1, pkg2 := sortPair(source, pkg.Name)
return nil, fmt.Errorf("packages %q and %q cannot both prefer %q for %s",
return nil, nil, fmt.Errorf("packages %q and %q cannot both prefer %q for %s",
pkg1, pkg2, info.Prefer, path)
}
} else {
Expand All @@ -479,7 +516,7 @@ func (r *Release) prefers() (map[preferKey]string, error) {
}
}
}
return prefers, nil
return prefers, pkgSampleSlice, nil
}

func preferredPathPackage(path, pkg1, pkg2 string, prefers map[preferKey]string) (choice string, err error) {
Expand All @@ -501,32 +538,34 @@ func preferredPathPackage(path, pkg1, pkg2 string, prefers map[preferKey]string)
}
sample, enforce := prefers[preferKey{preferSource, path, ""}]
if enforce {
_, hasTarget := prefers[preferKey{preferTarget, path, pkg1}]
_, hasSource := prefers[preferKey{preferSource, path, pkg1}]
conflict := pkg1
if hasTarget || hasSource {
if conflict == sample {
conflict = pkg2
}
return "", fmt.Errorf("package %q and %q conflict on %s without prefer relationship", sample, conflict, path)
pkg1, pkg2 = sortPair(conflict, sample)
return "", fmt.Errorf("package %q and %q conflict on %s without prefer relationship", pkg1, pkg2, path)
}
return "", preferNone
}

var preferNone = errors.New("no prefer relationship")

func findPrefer(path, pkg, prefer string, prefers map[preferKey]string) (found bool, err error) {
if len(prefers) == 0 {
return false, nil
}
// This logic is optimized for the happy case, which is
// always the case unless the release is broken. Note that
// the pkg reported in the error is the one inside the loop,
// not necessarily the input parameter.
for i := 0; i < len(prefers); i++ {
for i := 0; i < len(prefers)-1; i++ {
pkg = prefers[preferKey{preferTarget, path, pkg}]
if pkg == "" {
return false, nil
}
if pkg == prefer {
return true, nil
}
if pkg == "" {
return false, nil
}
}
return false, fmt.Errorf("package %q is part of a prefer loop on %s", pkg, path)
}
Expand Down
Loading

0 comments on commit 04ebb62

Please sign in to comment.