internal/deps/cargo.go
81 lines · 2078 bytes
1package deps
2
3import "github.com/BurntSushi/toml"
4
5// parseCargo takes the direct dependency names from Cargo.toml and their
6// resolved versions from Cargo.lock where one exists. Dependencies sourced
7// from a path or a git remote are skipped: crates.io has nothing to say
8// about them.
9func parseCargo(read ReadFile) []Dep {
10 raw, err := read("Cargo.toml")
11 if err != nil {
12 return nil
13 }
14 var manifest struct {
15 Dependencies map[string]any `toml:"dependencies"`
16 DevDependencies map[string]any `toml:"dev-dependencies"`
17 BuildDependencies map[string]any `toml:"build-dependencies"`
18 }
19 if toml.Unmarshal(raw, &manifest) != nil {
20 return nil
21 }
22 locked := cargoLock(read)
23 var out []Dep
24 for _, set := range []map[string]any{manifest.Dependencies, manifest.DevDependencies, manifest.BuildDependencies} {
25 for name, spec := range set {
26 req, ok := cargoRequirement(spec)
27 if !ok {
28 continue
29 }
30 current := locked[name]
31 if current == "" {
32 current = pin(req)
33 }
34 if current != "" {
35 out = append(out, Dep{Ecosystem: EcoCargo, Name: name, Current: current})
36 }
37 }
38 }
39 return out
40}
41
42// cargoRequirement reads the version out of either dependency form —
43// `serde = "1.0"` or `serde = { version = "1.0", features = [...] }` —
44// and rejects the ones that name a source other than the registry.
45func cargoRequirement(spec any) (string, bool) {
46 switch v := spec.(type) {
47 case string:
48 return v, true
49 case map[string]any:
50 if v["path"] != nil || v["git"] != nil {
51 return "", false
52 }
53 s, ok := v["version"].(string)
54 return s, ok
55 }
56 return "", false
57}
58
59func cargoLock(read ReadFile) map[string]string {
60 raw, err := read("Cargo.lock")
61 if err != nil {
62 return nil
63 }
64 var lock struct {
65 Package []struct {
66 Name string `toml:"name"`
67 Version string `toml:"version"`
68 Source string `toml:"source"`
69 } `toml:"package"`
70 }
71 if toml.Unmarshal(raw, &lock) != nil {
72 return nil
73 }
74 out := map[string]string{}
75 for _, p := range lock.Package {
76 if v := pin(p.Version); v != "" {
77 out[p.Name] = v
78 }
79 }
80 return out
81}