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