github.com/stevenshuang/glide@v0.13.3/cfg/config.go (about) 1 package cfg 2 3 import ( 4 "crypto/sha256" 5 "fmt" 6 "io/ioutil" 7 "reflect" 8 "sort" 9 "strings" 10 11 "github.com/Masterminds/glide/mirrors" 12 "github.com/Masterminds/glide/util" 13 "github.com/Masterminds/vcs" 14 "gopkg.in/yaml.v2" 15 ) 16 17 // Config is the top-level configuration object. 18 type Config struct { 19 20 // Name is the name of the package or application. 21 Name string `yaml:"package"` 22 23 // Description is a short description for a package, application, or library. 24 // This description is similar but different to a Go package description as 25 // it is for marketing and presentation purposes rather than technical ones. 26 Description string `json:"description,omitempty"` 27 28 // Home is a url to a website for the package. 29 Home string `yaml:"homepage,omitempty"` 30 31 // License provides either a SPDX license or a path to a file containing 32 // the license. For more information on SPDX see http://spdx.org/licenses/. 33 // When more than one license an SPDX expression can be used. 34 License string `yaml:"license,omitempty"` 35 36 // Owners is an array of owners for a project. See the Owner type for 37 // more detail. These can be one or more people, companies, or other 38 // organizations. 39 Owners Owners `yaml:"owners,omitempty"` 40 41 // Ignore contains a list of packages to ignore fetching. This is useful 42 // when walking the package tree (including packages of packages) to list 43 // those to skip. 44 Ignore []string `yaml:"ignore,omitempty"` 45 46 // Exclude contains a list of directories in the local application to 47 // exclude from scanning for dependencies. 48 Exclude []string `yaml:"excludeDirs,omitempty"` 49 50 // Imports contains a list of all non-development imports for a project. For 51 // more detail on how these are captured see the Dependency type. 52 Imports Dependencies `yaml:"import"` 53 54 // DevImports contains the test or other development imports for a project. 55 // See the Dependency type for more details on how this is recorded. 56 DevImports Dependencies `yaml:"testImport,omitempty"` 57 } 58 59 // A transitive representation of a dependency for importing and exporting to yaml. 60 type cf struct { 61 Name string `yaml:"package"` 62 Description string `yaml:"description,omitempty"` 63 Home string `yaml:"homepage,omitempty"` 64 License string `yaml:"license,omitempty"` 65 Owners Owners `yaml:"owners,omitempty"` 66 Ignore []string `yaml:"ignore,omitempty"` 67 Exclude []string `yaml:"excludeDirs,omitempty"` 68 Imports Dependencies `yaml:"import"` 69 DevImports Dependencies `yaml:"testImport,omitempty"` 70 } 71 72 // ConfigFromYaml returns an instance of Config from YAML 73 func ConfigFromYaml(yml []byte) (*Config, error) { 74 cfg := &Config{} 75 err := yaml.Unmarshal([]byte(yml), &cfg) 76 return cfg, err 77 } 78 79 // Marshal converts a Config instance to YAML 80 func (c *Config) Marshal() ([]byte, error) { 81 yml, err := yaml.Marshal(&c) 82 if err != nil { 83 return []byte{}, err 84 } 85 return yml, nil 86 } 87 88 // UnmarshalYAML is a hook for gopkg.in/yaml.v2 in the unmarshalling process 89 func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error { 90 newConfig := &cf{} 91 if err := unmarshal(&newConfig); err != nil { 92 return err 93 } 94 c.Name = newConfig.Name 95 c.Description = newConfig.Description 96 c.Home = newConfig.Home 97 c.License = newConfig.License 98 c.Owners = newConfig.Owners 99 c.Ignore = newConfig.Ignore 100 c.Exclude = newConfig.Exclude 101 c.Imports = newConfig.Imports 102 c.DevImports = newConfig.DevImports 103 104 // Cleanup the Config object now that we have it. 105 err := c.DeDupe() 106 107 return err 108 } 109 110 // MarshalYAML is a hook for gopkg.in/yaml.v2 in the marshaling process 111 func (c *Config) MarshalYAML() (interface{}, error) { 112 newConfig := &cf{ 113 Name: c.Name, 114 Description: c.Description, 115 Home: c.Home, 116 License: c.License, 117 Owners: c.Owners, 118 Ignore: c.Ignore, 119 Exclude: c.Exclude, 120 } 121 i, err := c.Imports.Clone().DeDupe() 122 if err != nil { 123 return newConfig, err 124 } 125 126 di, err := c.DevImports.Clone().DeDupe() 127 if err != nil { 128 return newConfig, err 129 } 130 131 newConfig.Imports = i 132 newConfig.DevImports = di 133 134 return newConfig, nil 135 } 136 137 // HasDependency returns true if the given name is listed as an import or dev import. 138 func (c *Config) HasDependency(name string) bool { 139 for _, d := range c.Imports { 140 if d.Name == name { 141 return true 142 } 143 } 144 for _, d := range c.DevImports { 145 if d.Name == name { 146 return true 147 } 148 } 149 return false 150 } 151 152 // HasIgnore returns true if the given name is listed on the ignore list. 153 func (c *Config) HasIgnore(name string) bool { 154 for _, v := range c.Ignore { 155 156 // Check for both a name and to make sure sub-packages are ignored as 157 // well. 158 if v == name || strings.HasPrefix(name, v+"/") { 159 return true 160 } 161 } 162 163 return false 164 } 165 166 // HasExclude returns true if the given name is listed on the exclude list. 167 func (c *Config) HasExclude(ex string) bool { 168 ep := normalizeSlash(ex) 169 for _, v := range c.Exclude { 170 if vp := normalizeSlash(v); vp == ep { 171 return true 172 } 173 } 174 175 return false 176 } 177 178 // Clone performs a deep clone of the Config instance 179 func (c *Config) Clone() *Config { 180 n := &Config{} 181 n.Name = c.Name 182 n.Description = c.Description 183 n.Home = c.Home 184 n.License = c.License 185 n.Owners = c.Owners.Clone() 186 n.Ignore = c.Ignore 187 n.Exclude = c.Exclude 188 n.Imports = c.Imports.Clone() 189 n.DevImports = c.DevImports.Clone() 190 return n 191 } 192 193 // WriteFile writes a Glide YAML file. 194 // 195 // This is a convenience function that marshals the YAML and then writes it to 196 // the given file. If the file exists, it will be clobbered. 197 func (c *Config) WriteFile(glidepath string) error { 198 o, err := c.Marshal() 199 if err != nil { 200 return err 201 } 202 return ioutil.WriteFile(glidepath, o, 0666) 203 } 204 205 // DeDupe consolidates duplicate dependencies on a Config instance 206 func (c *Config) DeDupe() error { 207 208 // Remove duplicates in the imports 209 var err error 210 c.Imports, err = c.Imports.DeDupe() 211 if err != nil { 212 return err 213 } 214 c.DevImports, err = c.DevImports.DeDupe() 215 if err != nil { 216 return err 217 } 218 219 // If the name on the config object is part of the imports remove it. 220 found := -1 221 for i, dep := range c.Imports { 222 if dep.Name == c.Name { 223 found = i 224 } 225 } 226 if found >= 0 { 227 c.Imports = append(c.Imports[:found], c.Imports[found+1:]...) 228 } 229 230 found = -1 231 for i, dep := range c.DevImports { 232 if dep.Name == c.Name { 233 found = i 234 } 235 } 236 if found >= 0 { 237 c.DevImports = append(c.DevImports[:found], c.DevImports[found+1:]...) 238 } 239 240 // If something is on the ignore list remove it from the imports. 241 for _, v := range c.Ignore { 242 found = -1 243 for k, d := range c.Imports { 244 if v == d.Name { 245 found = k 246 } 247 } 248 if found >= 0 { 249 c.Imports = append(c.Imports[:found], c.Imports[found+1:]...) 250 } 251 252 found = -1 253 for k, d := range c.DevImports { 254 if v == d.Name { 255 found = k 256 } 257 } 258 if found >= 0 { 259 c.DevImports = append(c.DevImports[:found], c.DevImports[found+1:]...) 260 } 261 } 262 263 return nil 264 } 265 266 // AddImport appends dependencies to the import list, deduplicating as we go. 267 func (c *Config) AddImport(deps ...*Dependency) error { 268 t := c.Imports 269 t = append(t, deps...) 270 t, err := t.DeDupe() 271 if err != nil { 272 return err 273 } 274 c.Imports = t 275 return nil 276 } 277 278 // Hash generates a sha256 hash for a given Config 279 func (c *Config) Hash() (string, error) { 280 yml, err := c.Marshal() 281 if err != nil { 282 return "", err 283 } 284 285 hash := sha256.New() 286 hash.Write(yml) 287 return fmt.Sprintf("%x", hash.Sum(nil)), nil 288 } 289 290 // Dependencies is a collection of Dependency 291 type Dependencies []*Dependency 292 293 // Get a dependency by name 294 func (d Dependencies) Get(name string) *Dependency { 295 for _, dep := range d { 296 if dep.Name == name { 297 return dep 298 } 299 } 300 return nil 301 } 302 303 // Has checks if a dependency is on a list of dependencies such as import or testImport 304 func (d Dependencies) Has(name string) bool { 305 for _, dep := range d { 306 if dep.Name == name { 307 return true 308 } 309 } 310 return false 311 } 312 313 // Remove removes a dependency from a list of dependencies 314 func (d Dependencies) Remove(name string) Dependencies { 315 found := -1 316 for i, dep := range d { 317 if dep.Name == name { 318 found = i 319 } 320 } 321 322 if found >= 0 { 323 copy(d[found:], d[found+1:]) 324 d[len(d)-1] = nil 325 return d[:len(d)-1] 326 } 327 return d 328 } 329 330 // Clone performs a deep clone of Dependencies 331 func (d Dependencies) Clone() Dependencies { 332 n := make(Dependencies, 0, len(d)) 333 for _, v := range d { 334 n = append(n, v.Clone()) 335 } 336 return n 337 } 338 339 // DeDupe cleans up duplicates on a list of dependencies. 340 func (d Dependencies) DeDupe() (Dependencies, error) { 341 checked := map[string]int{} 342 imports := make(Dependencies, 0, 1) 343 i := 0 344 for _, dep := range d { 345 // The first time we encounter a dependency add it to the list 346 if val, ok := checked[dep.Name]; !ok { 347 checked[dep.Name] = i 348 imports = append(imports, dep) 349 i++ 350 } else { 351 // In here we've encountered a dependency for the second time. 352 // Make sure the details are the same or return an error. 353 v := imports[val] 354 if dep.Reference != v.Reference { 355 return d, fmt.Errorf("Import %s repeated with different versions '%s' and '%s'", dep.Name, dep.Reference, v.Reference) 356 } 357 if dep.Repository != v.Repository || dep.VcsType != v.VcsType { 358 return d, fmt.Errorf("Import %s repeated with different Repository details", dep.Name) 359 } 360 if !reflect.DeepEqual(dep.Os, v.Os) || !reflect.DeepEqual(dep.Arch, v.Arch) { 361 return d, fmt.Errorf("Import %s repeated with different OS or Architecture filtering", dep.Name) 362 } 363 imports[checked[dep.Name]].Subpackages = stringArrayDeDupe(v.Subpackages, dep.Subpackages...) 364 } 365 } 366 367 return imports, nil 368 } 369 370 // Dependency describes a package that the present package depends upon. 371 type Dependency struct { 372 Name string `yaml:"package"` 373 Reference string `yaml:"version,omitempty"` 374 Pin string `yaml:"-"` 375 Repository string `yaml:"repo,omitempty"` 376 VcsType string `yaml:"vcs,omitempty"` 377 Subpackages []string `yaml:"subpackages,omitempty"` 378 Arch []string `yaml:"arch,omitempty"` 379 Os []string `yaml:"os,omitempty"` 380 } 381 382 // A transitive representation of a dependency for importing and exploting to yaml. 383 type dep struct { 384 Name string `yaml:"package"` 385 Reference string `yaml:"version,omitempty"` 386 Ref string `yaml:"ref,omitempty"` 387 Repository string `yaml:"repo,omitempty"` 388 VcsType string `yaml:"vcs,omitempty"` 389 Subpackages []string `yaml:"subpackages,omitempty"` 390 Arch []string `yaml:"arch,omitempty"` 391 Os []string `yaml:"os,omitempty"` 392 } 393 394 // DependencyFromLock converts a Lock to a Dependency 395 func DependencyFromLock(lock *Lock) *Dependency { 396 return &Dependency{ 397 Name: lock.Name, 398 Reference: lock.Version, 399 Repository: lock.Repository, 400 VcsType: lock.VcsType, 401 Subpackages: lock.Subpackages, 402 Arch: lock.Arch, 403 Os: lock.Os, 404 } 405 } 406 407 // UnmarshalYAML is a hook for gopkg.in/yaml.v2 in the unmarshaling process 408 func (d *Dependency) UnmarshalYAML(unmarshal func(interface{}) error) error { 409 newDep := &dep{} 410 err := unmarshal(&newDep) 411 if err != nil { 412 return err 413 } 414 d.Name = newDep.Name 415 d.Reference = newDep.Reference 416 d.Repository = newDep.Repository 417 d.VcsType = newDep.VcsType 418 d.Subpackages = newDep.Subpackages 419 d.Arch = newDep.Arch 420 d.Os = newDep.Os 421 422 if d.Reference == "" && newDep.Ref != "" { 423 d.Reference = newDep.Ref 424 } 425 426 // Make sure only legitimate VCS are listed. 427 d.VcsType = filterVcsType(d.VcsType) 428 429 // Get the root name for the package 430 tn, subpkg := util.NormalizeName(d.Name) 431 d.Name = tn 432 if subpkg != "" { 433 d.Subpackages = append(d.Subpackages, subpkg) 434 } 435 436 // Older versions of Glide had a / prefix on subpackages in some cases. 437 // Here that's cleaned up. Someday we should be able to remove this. 438 for k, v := range d.Subpackages { 439 d.Subpackages[k] = strings.TrimPrefix(v, "/") 440 } 441 442 return nil 443 } 444 445 // MarshalYAML is a hook for gopkg.in/yaml.v2 in the marshaling process 446 func (d *Dependency) MarshalYAML() (interface{}, error) { 447 448 // Make sure we only write the correct vcs type to file 449 t := filterVcsType(d.VcsType) 450 newDep := &dep{ 451 Name: d.Name, 452 Reference: d.Reference, 453 Repository: d.Repository, 454 VcsType: t, 455 Subpackages: d.Subpackages, 456 Arch: d.Arch, 457 Os: d.Os, 458 } 459 460 return newDep, nil 461 } 462 463 // Remote returns the remote location to fetch source from. This location is 464 // the central place where mirrors can alter the location. 465 func (d *Dependency) Remote() string { 466 var r string 467 468 if d.Repository != "" { 469 r = d.Repository 470 } else { 471 r = "https://" + d.Name 472 } 473 474 f, nr, _ := mirrors.Get(r) 475 if f { 476 return nr 477 } 478 479 return r 480 } 481 482 // Vcs returns the VCS type to fetch source from. 483 func (d *Dependency) Vcs() string { 484 var r string 485 486 if d.Repository != "" { 487 r = d.Repository 488 } else { 489 r = "https://" + d.Name 490 } 491 492 f, _, nv := mirrors.Get(r) 493 if f { 494 return nv 495 } 496 497 return d.VcsType 498 } 499 500 // GetRepo retrieves a Masterminds/vcs repo object configured for the root 501 // of the package being retrieved. 502 func (d *Dependency) GetRepo(dest string) (vcs.Repo, error) { 503 504 // The remote location is either the configured repo or the package 505 // name as an https url. 506 remote := d.Remote() 507 508 VcsType := d.Vcs() 509 510 // If the VCS type has a value we try that first. 511 if len(VcsType) > 0 && VcsType != "None" { 512 switch vcs.Type(VcsType) { 513 case vcs.Git: 514 return vcs.NewGitRepo(remote, dest) 515 case vcs.Svn: 516 return vcs.NewSvnRepo(remote, dest) 517 case vcs.Hg: 518 return vcs.NewHgRepo(remote, dest) 519 case vcs.Bzr: 520 return vcs.NewBzrRepo(remote, dest) 521 default: 522 return nil, fmt.Errorf("Unknown VCS type %s set for %s", VcsType, d.Name) 523 } 524 } 525 526 // When no type set we try to autodetect. 527 return vcs.NewRepo(remote, dest) 528 } 529 530 // Clone creates a clone of a Dependency 531 func (d *Dependency) Clone() *Dependency { 532 return &Dependency{ 533 Name: d.Name, 534 Reference: d.Reference, 535 Pin: d.Pin, 536 Repository: d.Repository, 537 VcsType: d.VcsType, 538 Subpackages: d.Subpackages, 539 Arch: d.Arch, 540 Os: d.Os, 541 } 542 } 543 544 // HasSubpackage returns if the subpackage is present on the dependency 545 func (d *Dependency) HasSubpackage(sub string) bool { 546 547 for _, v := range d.Subpackages { 548 if sub == v { 549 return true 550 } 551 } 552 553 return false 554 } 555 556 // Owners is a list of owners for a project. 557 type Owners []*Owner 558 559 // Clone performs a deep clone of Owners 560 func (o Owners) Clone() Owners { 561 n := make(Owners, 0, 1) 562 for _, v := range o { 563 n = append(n, v.Clone()) 564 } 565 return n 566 } 567 568 // Owner describes an owner of a package. This can be a person, company, or 569 // other organization. This is useful if someone needs to contact the 570 // owner of a package to address things like a security issue. 571 type Owner struct { 572 573 // Name describes the name of an organization. 574 Name string `yaml:"name,omitempty"` 575 576 // Email is an email address to reach the owner at. 577 Email string `yaml:"email,omitempty"` 578 579 // Home is a url to a website for the owner. 580 Home string `yaml:"homepage,omitempty"` 581 } 582 583 // Clone creates a clone of a Dependency 584 func (o *Owner) Clone() *Owner { 585 return &Owner{ 586 Name: o.Name, 587 Email: o.Email, 588 Home: o.Home, 589 } 590 } 591 592 func stringArrayDeDupe(s []string, items ...string) []string { 593 for _, item := range items { 594 exists := false 595 for _, v := range s { 596 if v == item { 597 exists = true 598 } 599 } 600 if !exists { 601 s = append(s, item) 602 } 603 } 604 sort.Strings(s) 605 return s 606 } 607 608 func filterVcsType(vcs string) string { 609 switch vcs { 610 case "git", "hg", "bzr", "svn": 611 return vcs 612 case "mercurial": 613 return "hg" 614 case "bazaar": 615 return "bzr" 616 case "subversion": 617 return "svn" 618 default: 619 return "" 620 } 621 } 622 623 func normalizeSlash(k string) string { 624 return strings.Replace(k, "\\", "/", -1) 625 }