xref: /aosp_15_r20/external/cronet/net/third_party/quiche/src/depstool/deps/parse.go (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1// 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}
124