| // Package deps package provides methods to extract and manipulate external code dependencies from the QUICHE WORKSPACE.bazel file. | |
| package deps | |
| import ( | |
| "fmt" | |
| "regexp" | |
| "github.com/bazelbuild/buildtools/build" | |
| ) | |
| var lastUpdatedRE = regexp.MustCompile(`Last updated (\d{4}-\d{2}-\d{2})`) | |
| // Entry is a parsed representation of a dependency entry in the WORKSPACE.bazel file. | |
| type Entry struct { | |
| Name string | |
| SHA256 string | |
| Prefix string | |
| URL string | |
| LastUpdated string | |
| } | |
| // HTTPArchiveRule returns a CallExpr describing the provided http_archive | |
| // rule, or nil if the expr in question is not an http_archive rule. | |
| func HTTPArchiveRule(expr build.Expr) (*build.CallExpr, bool) { | |
| callexpr, ok := expr.(*build.CallExpr) | |
| if !ok { | |
| return nil, false | |
| } | |
| name, ok := callexpr.X.(*build.Ident) | |
| if !ok || name.Name != "http_archive" { | |
| return nil, false | |
| } | |
| return callexpr, true | |
| } | |
| func parseString(expr build.Expr) (string, error) { | |
| str, ok := expr.(*build.StringExpr) | |
| if !ok { | |
| return "", fmt.Errorf("expected string as the function argument") | |
| } | |
| return str.Value, nil | |
| } | |
| func parseSingleElementList(expr build.Expr) (string, error) { | |
| list, ok := expr.(*build.ListExpr) | |
| if !ok { | |
| return "", fmt.Errorf("expected a list as the function argument") | |
| } | |
| if len(list.List) != 1 { | |
| return "", fmt.Errorf("expected a single-element list as the function argument, got %d elements", len(list.List)) | |
| } | |
| return parseString(list.List[0]) | |
| } | |
| // ParseHTTPArchiveRule parses the provided http_archive rule and returns all of the dependency metadata embedded. | |
| func ParseHTTPArchiveRule(callexpr *build.CallExpr) (*Entry, error) { | |
| result := Entry{} | |
| for _, arg := range callexpr.List { | |
| assign, ok := arg.(*build.AssignExpr) | |
| if !ok { | |
| return nil, fmt.Errorf("a non-named argument passed as a function parameter") | |
| } | |
| argname, _ := build.GetParamName(assign.LHS) | |
| var err error = nil | |
| switch argname { | |
| case "name": | |
| result.Name, err = parseString(assign.RHS) | |
| case "sha256": | |
| result.SHA256, err = parseString(assign.RHS) | |
| if len(assign.Comments.Suffix) != 1 { | |
| return nil, fmt.Errorf("missing the \"Last updated\" comment on the sha256 field") | |
| } | |
| comment := assign.Comments.Suffix[0].Token | |
| match := lastUpdatedRE.FindStringSubmatch(comment) | |
| if match == nil { | |
| return nil, fmt.Errorf("unable to parse the \"Last updated\" comment, comment value: %s", comment) | |
| } | |
| result.LastUpdated = match[1] | |
| case "strip_prefix": | |
| result.Prefix, err = parseString(assign.RHS) | |
| case "urls": | |
| result.URL, err = parseSingleElementList(assign.RHS) | |
| default: | |
| continue | |
| } | |
| if err != nil { | |
| return nil, err | |
| } | |
| } | |
| if result.Name == "" { | |
| return nil, fmt.Errorf("missing the name field") | |
| } | |
| if result.SHA256 == "" { | |
| return nil, fmt.Errorf("missing the sha256 field") | |
| } | |
| if result.URL == "" { | |
| return nil, fmt.Errorf("missing the urls field") | |
| } | |
| return &result, nil | |
| } | |
| // ParseHTTPArchiveRules parses the entire WORKSPACE.bazel file and returns all of the http_archive rules in it. | |
| func ParseHTTPArchiveRules(source []byte) ([]*Entry, error) { | |
| file, err := build.ParseWorkspace("WORKSPACE.bazel", source) | |
| if err != nil { | |
| return []*Entry{}, err | |
| } | |
| result := make([]*Entry, 0) | |
| for _, expr := range file.Stmt { | |
| callexpr, ok := HTTPArchiveRule(expr) | |
| if !ok { | |
| continue | |
| } | |
| parsed, err := ParseHTTPArchiveRule(callexpr) | |
| if err != nil { | |
| return []*Entry{}, err | |
| } | |
| result = append(result, parsed) | |
| } | |
| return result, nil | |
| } |