blob: 238837bfb1945a83dcfd553adb3efa373f3c4d3d [file] [log] [blame]
vasilvv0f74ff62022-10-14 11:35:36 -07001// Package deps package provides methods to extract and manipulate external code dependencies from the QUICHE WORKSPACE.bazel file.
2package deps
3
4import (
5 "fmt"
6 "regexp"
7
8 "github.com/bazelbuild/buildtools/build"
9)
10
11var lastUpdatedRE = regexp.MustCompile(`Last updated (\d{4}-\d{2}-\d{2})`)
12
13// Entry is a parsed representation of a dependency entry in the WORKSPACE.bazel file.
14type Entry struct {
15 Name string
16 SHA256 string
17 Prefix string
18 URL string
19 LastUpdated string
20}
21
22// HTTPArchiveRule returns a CallExpr describing the provided http_archive
23// rule, or nil if the expr in question is not an http_archive rule.
24func HTTPArchiveRule(expr build.Expr) (*build.CallExpr, bool) {
25 callexpr, ok := expr.(*build.CallExpr)
26 if !ok {
27 return nil, false
28 }
29 name, ok := callexpr.X.(*build.Ident)
30 if !ok || name.Name != "http_archive" {
31 return nil, false
32 }
33 return callexpr, true
34}
35
36func parseString(expr build.Expr) (string, error) {
37 str, ok := expr.(*build.StringExpr)
38 if !ok {
39 return "", fmt.Errorf("expected string as the function argument")
40 }
41 return str.Value, nil
42}
43
44func parseSingleElementList(expr build.Expr) (string, error) {
45 list, ok := expr.(*build.ListExpr)
46 if !ok {
47 return "", fmt.Errorf("expected a list as the function argument")
48 }
49 if len(list.List) != 1 {
50 return "", fmt.Errorf("expected a single-element list as the function argument, got %d elements", len(list.List))
51 }
52 return parseString(list.List[0])
53}
54
55// ParseHTTPArchiveRule parses the provided http_archive rule and returns all of the dependency metadata embedded.
56func ParseHTTPArchiveRule(callexpr *build.CallExpr) (*Entry, error) {
57 result := Entry{}
58 for _, arg := range callexpr.List {
59 assign, ok := arg.(*build.AssignExpr)
60 if !ok {
61 return nil, fmt.Errorf("a non-named argument passed as a function parameter")
62 }
63 argname, _ := build.GetParamName(assign.LHS)
64 var err error = nil
65 switch argname {
66 case "name":
67 result.Name, err = parseString(assign.RHS)
68 case "sha256":
69 result.SHA256, err = parseString(assign.RHS)
70
71 if len(assign.Comments.Suffix) != 1 {
72 return nil, fmt.Errorf("missing the \"Last updated\" comment on the sha256 field")
73 }
74 comment := assign.Comments.Suffix[0].Token
75 match := lastUpdatedRE.FindStringSubmatch(comment)
76 if match == nil {
77 return nil, fmt.Errorf("unable to parse the \"Last updated\" comment, comment value: %s", comment)
78 }
79 result.LastUpdated = match[1]
80 case "strip_prefix":
81 result.Prefix, err = parseString(assign.RHS)
82 case "urls":
83 result.URL, err = parseSingleElementList(assign.RHS)
84 default:
85 continue
86 }
87 if err != nil {
88 return nil, err
89 }
90 }
91 if result.Name == "" {
92 return nil, fmt.Errorf("missing the name field")
93 }
94 if result.SHA256 == "" {
95 return nil, fmt.Errorf("missing the sha256 field")
96 }
97 if result.URL == "" {
98 return nil, fmt.Errorf("missing the urls field")
99 }
100 return &result, nil
101}
102
103// ParseHTTPArchiveRules parses the entire WORKSPACE.bazel file and returns all of the http_archive rules in it.
104func ParseHTTPArchiveRules(source []byte) ([]*Entry, error) {
105 file, err := build.ParseWorkspace("WORKSPACE.bazel", source)
106 if err != nil {
107 return []*Entry{}, err
108 }
109
110 result := make([]*Entry, 0)
111 for _, expr := range file.Stmt {
112 callexpr, ok := HTTPArchiveRule(expr)
113 if !ok {
114 continue
115 }
116 parsed, err := ParseHTTPArchiveRule(callexpr)
117 if err != nil {
118 return []*Entry{}, err
119 }
120 result = append(result, parsed)
121 }
122 return result, nil
123}