| 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] } |