blob: 54f304adc9940649e0c097583a81ccfaf00bb965 [file] [log] [blame] [edit]
package vendor
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"os"
"reflect"
"sort"
"strings"
)
// gb-vendor manifest support
// Manifest describes the layout of $PROJECT/vendor/manifest.
type Manifest struct {
// Manifest version. Current manifest version is 0.
Version int `json:"version"`
// Depenencies is a list of vendored dependencies.
Dependencies []Dependency `json:"dependencies"`
}
var (
DepPresent = errors.New("dependency already present")
DepSubPkgPresent = errors.New("subpackages of this dependency are already present")
DepMissing = errors.New("dependency does not exist")
)
// AddDependency adds a Dependency to the current Manifest.
// If the dependency exists already then it returns and error.
func (m *Manifest) AddDependency(dep Dependency) error {
if m.HasImportpath(dep.Importpath) {
return DepPresent
}
if m.GetSubpackages(dep.Importpath) != nil {
return DepSubPkgPresent
}
m.Dependencies = append(m.Dependencies, dep)
return nil
}
// RemoveDependency removes a Dependency from the current Manifest.
// If the dependency does not exist then it returns an error.
func (m *Manifest) RemoveDependency(dep Dependency) error {
for i, d := range m.Dependencies {
if reflect.DeepEqual(d, dep) {
m.Dependencies = append(m.Dependencies[:i], m.Dependencies[i+1:]...)
return nil
}
}
return DepMissing
}
// HasImportpath reports whether the Manifest contains the import path,
// or a parent of it.
func (m *Manifest) HasImportpath(path string) bool {
_, err := m.GetDependencyForImportpath(path)
return err == nil
}
// GetDependencyForRepository return a dependency for specified import
// path. Note that it might be a parent of the specified path.
// If the dependency does not exist it returns an error.
func (m *Manifest) GetDependencyForImportpath(path string) (Dependency, error) {
for _, d := range m.Dependencies {
if path == d.Importpath || strings.HasPrefix(path, d.Importpath+"/") {
return d, nil
}
}
return Dependency{}, fmt.Errorf("dependency for %s does not exist", path)
}
// GetSubpackages returns any Dependency in the Manifest that is a subpackage
// of the given import path.
func (m *Manifest) GetSubpackages(path string) (deps []Dependency) {
for _, d := range m.Dependencies {
if path != d.Importpath && strings.HasPrefix(d.Importpath, path+"/") {
deps = append(deps, d)
}
}
return
}
// Dependency describes one vendored import path of code
// A Dependency is an Importpath sources from a Respository
// at Revision from Path.
type Dependency struct {
// Importpath is name by which this dependency is known.
Importpath string `json:"importpath"`
// Repository is the remote DVCS location that this
// dependency was fetched from.
Repository string `json:"repository"`
// VCS is the DVCS system found at Repository.
VCS string `json:"vcs"`
// Revision is the revision that describes the dependency's
// remote revision.
Revision string `json:"revision"`
// Branch is the branch the Revision was located on.
// Can be blank if not needed.
Branch string `json:"branch"`
// Path is the path inside the Repository where the
// dependency was fetched from.
Path string `json:"path,omitempty"`
// NoTests indicates that test files were ignored.
// In the negative for backwards compatibility.
NoTests bool `json:"notests,omitempty"`
// AllFiles indicates that no files were ignored.
AllFiles bool `json:"allfiles,omitempty"`
}
// WriteManifest writes a Manifest to the path. If the manifest does
// not exist, it is created. If it does exist, it will be overwritten.
// If the manifest file is empty (0 dependencies) it will be deleted.
// The dependencies will be ordered by import path to reduce churn when making
// changes.
// TODO(dfc) write to temporary file and move atomically to avoid
// destroying a working vendorfile.
func WriteManifest(path string, m *Manifest) error {
if len(m.Dependencies) == 0 {
err := os.Remove(path)
if !os.IsNotExist(err) {
return err
}
return nil
}
f, err := os.Create(path)
if err != nil {
return err
}
if err := writeManifest(f, m); err != nil {
f.Close()
return err
}
return f.Close()
}
func writeManifest(w io.Writer, m *Manifest) error {
sort.Sort(byImportpath(m.Dependencies))
buf, err := json.MarshalIndent(m, "", "\t")
if err != nil {
return err
}
_, err = io.Copy(w, bytes.NewReader(buf))
return err
}
// ReadManifest reads a Manifest from path. If the Manifest is not
// found, a blank Manifest will be returned.
func ReadManifest(path string) (*Manifest, error) {
f, err := os.Open(path)
if err != nil {
if os.IsNotExist(err) {
return new(Manifest), nil
}
return nil, err
}
defer f.Close()
var m Manifest
d := json.NewDecoder(f)
if err := d.Decode(&m); err != nil {
return nil, err
}
// Pass all dependencies through AddDependency to detect overlap
deps := m.Dependencies
m.Dependencies = nil
sort.Sort(byImportpath(deps)) // so that subpackages come after parents
for _, d := range deps {
if err := m.AddDependency(d); err == DepPresent {
log.Println("WARNING: overlapping dependency detected:", d.Importpath)
log.Println("The subpackage will be ignored to fix undefined behavior. See https://git.io/vr8Mu")
} else if err != nil {
return nil, err
}
}
return &m, err
}
type byImportpath []Dependency
func (s byImportpath) Len() int { return len(s) }
func (s byImportpath) Less(i, j int) bool { return s[i].Importpath < s[j].Importpath }
func (s byImportpath) Swap(i, j int) { s[i], s[j] = s[j], s[i] }