Skip to content
Snippets Groups Projects
  • Serge Bazanski's avatar
    78ca72e9
    Build using Bazel · 78ca72e9
    Serge Bazanski authored
    This change integrates the Bazel build system into bio-rd.
    
    We also add support for:
      - running go dep from vendored libraries
      - running goveralls from vendored libraries
      - running bazel-based coverage from travis
    78ca72e9
    History
    Build using Bazel
    Serge Bazanski authored
    This change integrates the Bazel build system into bio-rd.
    
    We also add support for:
      - running go dep from vendored libraries
      - running goveralls from vendored libraries
      - running bazel-based coverage from travis
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
lock.go 6.11 KiB
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gps

import (
	"bytes"
	"fmt"
	"sort"
)

// Lock represents data from a lock file (or however the implementing tool
// chooses to store it) at a particular version that is relevant to the
// satisfiability solving process.
//
// In general, the information produced by gps on finding a successful
// solution is all that would be necessary to constitute a lock file, though
// tools can include whatever other information they want in their storage.
type Lock interface {
	// The hash digest of inputs to gps that resulted in this lock data.
	InputsDigest() []byte

	// Projects returns the list of LockedProjects contained in the lock data.
	Projects() []LockedProject
}

// LocksAreEq checks if two locks are equivalent. This checks that
// all contained LockedProjects are equal, and optionally (if the third
// parameter is true) whether the locks' input hashes are equal.
func LocksAreEq(l1, l2 Lock, checkHash bool) bool {
	// Cheapest ops first
	if checkHash && !bytes.Equal(l1.InputsDigest(), l2.InputsDigest()) {
		return false
	}

	p1, p2 := l1.Projects(), l2.Projects()
	if len(p1) != len(p2) {
		return false
	}

	p1 = sortedLockedProjects(p1)
	p2 = sortedLockedProjects(p2)

	for k, lp := range p1 {
		if !lp.Eq(p2[k]) {
			return false
		}
	}
	return true
}

// sortedLockedProjects returns a sorted copy of lps, or itself if already sorted.
func sortedLockedProjects(lps []LockedProject) []LockedProject {
	if len(lps) <= 1 || sort.SliceIsSorted(lps, func(i, j int) bool {
		return lps[i].Ident().Less(lps[j].Ident())
	}) {
		return lps
	}
	cp := make([]LockedProject, len(lps))
	copy(cp, lps)
	sort.Slice(cp, func(i, j int) bool {
		return cp[i].Ident().Less(cp[j].Ident())
	})
	return cp
}

// LockedProject is a single project entry from a lock file. It expresses the
// project's name, one or both of version and underlying revision, the network
// URI for accessing it, the path at which it should be placed within a vendor
// directory, and the packages that are used in it.
type LockedProject struct {
	pi   ProjectIdentifier
	v    UnpairedVersion
	r    Revision
	pkgs []string
}

// SimpleLock is a helper for tools to easily describe lock data when they know
// that no hash, or other complex information, is available.
type SimpleLock []LockedProject

var _ Lock = SimpleLock{}

// InputsDigest always returns an empty string for SimpleLock. This makes it useless
// as a stable lock to be written to disk, but still useful for some ephemeral
// purposes.
func (SimpleLock) InputsDigest() []byte {
	return nil
}

// Projects returns the entire contents of the SimpleLock.
func (l SimpleLock) Projects() []LockedProject {
	return l
}

// NewLockedProject creates a new LockedProject struct with a given
// ProjectIdentifier (name and optional upstream source URL), version. and list
// of packages required from the project.
//
// Note that passing a nil version will cause a panic. This is a correctness
// measure to ensure that the solver is never exposed to a version-less lock
// entry. Such a case would be meaningless - the solver would have no choice but
// to simply dismiss that project. By creating a hard failure case via panic
// instead, we are trying to avoid inflicting the resulting pain on the user by
// instead forcing a decision on the Analyzer implementation.
func NewLockedProject(id ProjectIdentifier, v Version, pkgs []string) LockedProject {
	if v == nil {
		panic("must provide a non-nil version to create a LockedProject")
	}

	lp := LockedProject{
		pi:   id,
		pkgs: pkgs,
	}

	switch tv := v.(type) {
	case Revision:
		lp.r = tv
	case branchVersion:
		lp.v = tv
	case semVersion:
		lp.v = tv
	case plainVersion:
		lp.v = tv
	case versionPair:
		lp.r = tv.r
		lp.v = tv.v
	}

	return lp
}

// Ident returns the identifier describing the project. This includes both the
// local name (the root name by which the project is referenced in import paths)
// and the network name, where the upstream source lives.
func (lp LockedProject) Ident() ProjectIdentifier {
	return lp.pi
}
// Version assembles together whatever version and/or revision data is
// available into a single Version.
func (lp LockedProject) Version() Version {
	if lp.r == "" {
		return lp.v
	}

	if lp.v == nil {
		return lp.r
	}

	return lp.v.Pair(lp.r)
}

// Eq checks if two LockedProject instances are equal.
func (lp LockedProject) Eq(lp2 LockedProject) bool {
	if lp.pi != lp2.pi {
		return false
	}

	if lp.r != lp2.r {
		return false
	}

	if len(lp.pkgs) != len(lp2.pkgs) {
		return false
	}

	for k, v := range lp.pkgs {
		if lp2.pkgs[k] != v {
			return false
		}
	}

	v1n := lp.v == nil
	v2n := lp2.v == nil

	if v1n != v2n {
		return false
	}

	if !v1n && !lp.v.Matches(lp2.v) {
		return false
	}

	return true
}

// Packages returns the list of packages from within the LockedProject that are
// actually used in the import graph. Some caveats:
//
//  * The names given are relative to the root import path for the project. If
//    the root package itself is imported, it's represented as ".".
//  * Just because a package path isn't included in this list doesn't mean it's
//    safe to remove - it could contain C files, or other assets, that can't be
//    safely removed.
//  * The slice is not a copy. If you need to modify it, copy it first.
func (lp LockedProject) Packages() []string {
	return lp.pkgs
}

func (lp LockedProject) String() string {
	return fmt.Sprintf("%s@%s with packages: %v",
		lp.Ident(), lp.Version(), lp.pkgs)
}

type safeLock struct {
	h []byte
	p []LockedProject
}

func (sl safeLock) InputsDigest() []byte {
	return sl.h
}

func (sl safeLock) Projects() []LockedProject {
	return sl.p
}

// prepLock ensures a lock is prepared and safe for use by the solver. This is
// mostly about defensively ensuring that no outside routine can modify the lock
// while the solver is in-flight.
//
// This is achieved by copying the lock's data into a new safeLock.
func prepLock(l Lock) safeLock {
	pl := l.Projects()

	rl := safeLock{
		h: l.InputsDigest(),
		p: make([]LockedProject, len(pl)),
	}
	copy(rl.p, pl)

	return rl
}