|  | 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/vwK4B") | 
|  | } 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] } |