internal/deps/cargo.go

6821a6f76082b1e10ff899ff51021b11695c4ad6
gitbay/internal/deps/cargo.go history · blame · raw

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}