Skip to content
This repository has been archived by the owner on Sep 9, 2020. It is now read-only.

Commit

Permalink
Add satisfiability check for case variants
Browse files Browse the repository at this point in the history
  • Loading branch information
sdboyer committed Aug 29, 2017
1 parent 892b5ca commit fad46df
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 3 deletions.
28 changes: 28 additions & 0 deletions internal/gps/satisfy.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ func (s *solver) check(a atomWithPackages, pkgonly bool) error {
if err = s.checkIdentMatches(a, dep); err != nil {
return err
}
if err = s.checkCaseConflicts(a, dep); err != nil {
return err
}
if err = s.checkDepsConstraintsAllowable(a, dep); err != nil {
return err
}
Expand Down Expand Up @@ -218,6 +221,31 @@ func (s *solver) checkIdentMatches(a atomWithPackages, cdep completeDep) error {
return nil
}

// checkCaseConflicts ensures that the ProjectRoot specified in the completeDep
// does not have case conflicts with any existing dependencies.
//
// We only need to check the ProjectRoot, rather than any packages therein, as
// the later check for package existence is case-sensitive.
func (s *solver) checkCaseConflicts(a atomWithPackages, cdep completeDep) error {
pr := cdep.workingConstraint.Ident.ProjectRoot
hasConflict, current := s.sel.findCaseConflicts(pr)
if !hasConflict {
return nil
}

curid, _ := s.sel.getIdentFor(pr)
deps := s.sel.getDependenciesOn(curid)
for _, d := range deps {
s.fail(d.depender.id)
}

return &caseMismatchFailure{
goal: dependency{depender: a.a, dep: cdep},
current: current,
failsib: deps,
}
}

// checkPackageImportsFromDepExist ensures that, if the dep is already selected,
// the newly-required set of packages being placed on it exist and are valid.
func (s *solver) checkPackageImportsFromDepExist(a atomWithPackages, cdep completeDep) error {
Expand Down
35 changes: 34 additions & 1 deletion internal/gps/selection.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@

package gps

import "strings"

type selection struct {
projects []selected
deps map[ProjectRoot][]dependency
prLenMap map[int][]ProjectRoot
vu *versionUnifier
}

Expand Down Expand Up @@ -59,13 +62,43 @@ func (s *selection) popSelection() (atomWithPackages, bool) {
return sel.a, sel.first
}

func (s *selection) findCaseConflicts(pr ProjectRoot) (bool, ProjectRoot) {
prlist, has := s.prLenMap[len(pr)]
if !has {
return false, ""
}

// TODO(sdboyer) bug here if it's possible that strings.ToLower() could
// change the length of the string
lowpr := strings.ToLower(string(pr))
for _, existing := range prlist {
if lowpr != strings.ToLower(string(existing)) {
continue
}
// If the converted strings match, then whatever we figure out here will
// be definitive - we needn't walk the rest of the slice.
if pr == existing {
return false, ""
} else {
return true, existing
}
}

return false, ""
}

func (s *selection) pushDep(dep dependency) {
s.deps[dep.dep.Ident.ProjectRoot] = append(s.deps[dep.dep.Ident.ProjectRoot], dep)
pr := dep.dep.Ident.ProjectRoot
s.deps[pr] = append(s.deps[pr], dep)
s.prLenMap[len(pr)] = append(s.prLenMap[len(pr)], pr)
}

func (s *selection) popDep(id ProjectIdentifier) (dep dependency) {
deps := s.deps[id.ProjectRoot]
dep, s.deps[id.ProjectRoot] = deps[len(deps)-1], deps[:len(deps)-1]

prlist := s.prLenMap[len(id.ProjectRoot)]
s.prLenMap[len(id.ProjectRoot)] = prlist[:len(prlist)-1]
return dep
}

Expand Down
43 changes: 43 additions & 0 deletions internal/gps/solve_failures.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,49 @@ func (e *noVersionError) traceString() string {
return buf.String()
}

// caseMismatchFailure occurs when there are import paths that differ only by
// case. The compiler disallows this case.
type caseMismatchFailure struct {
// goal is the depender atom that tried to introduce the case-varying name,
// along with the case-varying name.
goal dependency
// current is the specific casing of a ProjectRoot that is presently
// selected for all possible case variations of its contained unicode code
// points.
current ProjectRoot
// failsib is the list of active dependencies that have determined the
// specific casing for the target project.
failsib []dependency
}

func (e *caseMismatchFailure) Error() string {
if len(e.failsib) == 1 {
str := "Could not introduce %s due to a case-only variation: it depends on %q, but %q was already established as the case variant for that project root by depender %s"
return fmt.Sprintf(str, a2vs(e.goal.depender), e.goal.dep.Ident.ProjectRoot, e.current, a2vs(e.failsib[0].depender))
}

var buf bytes.Buffer

str := "Could not introduce %s due to a case-only variation: it depends on %q, but %q was already established as the case variant for that project root by the following other dependers:\n"
fmt.Fprintf(&buf, str, e.goal.dep.Ident.ProjectRoot, e.current, a2vs(e.goal.depender))

for _, c := range e.failsib {
fmt.Fprintf(&buf, "\t%s\n", a2vs(c.depender))
}

return buf.String()
}

func (e *caseMismatchFailure) traceString() string {
var buf bytes.Buffer
fmt.Fprintf(&buf, "case-only variation in dependency on %q; %q already established by:\n", e.goal.dep.Ident.ProjectRoot, e.current)
for _, f := range e.failsib {
fmt.Fprintf(&buf, "%s\n", a2vs(f.depender))
}

return buf.String()
}

// disjointConstraintFailure occurs when attempting to introduce an atom that
// itself has an acceptable version, but one of its dependency constraints is
// disjoint with one or more dependency constraints already active for that
Expand Down
5 changes: 3 additions & 2 deletions internal/gps/solver.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,8 +307,9 @@ func Prepare(params SolveParameters, sm SourceManager) (Solver, error) {

// Initialize stacks and queues
s.sel = &selection{
deps: make(map[ProjectRoot][]dependency),
vu: s.vUnify,
deps: make(map[ProjectRoot][]dependency),
prLenMap: make(map[int][]ProjectRoot),
vu: s.vUnify,
}
s.unsel = &unselected{
sl: make([]bimodalIdentifier, 0),
Expand Down

0 comments on commit fad46df

Please sign in to comment.