Created
March 8, 2021 17:22
-
-
Save Tatskaari/d5fd424e8cf8d760739fe8c7a1c5ba20 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package godeps | |
import ( | |
"bytes" | |
"encoding/json" | |
"fmt" | |
"log" | |
"os/exec" | |
"strings" | |
) | |
type Target struct { | |
Labels []string `json:"labels"` | |
X map[string]interface{} `json:"-"` // Rest of the fields should go here. | |
} | |
type Package struct { | |
Targets map[string]*Target `json:"targets"` | |
} | |
type Graph struct { | |
Pkgs map[string]*Package `json:"packages"` | |
} | |
type Resolver interface { | |
Resolve(importPath string) (string, error) | |
} | |
type trie struct { | |
// TODO(jpoole): this could be quite large. Consider sync.Map and making this split workers per package. | |
nodes map[string]*trie | |
target string | |
matchAll bool | |
} | |
func (t *trie) insert(path []string, value string) { | |
if len(path) == 0 { | |
log.Fatalf("CRITICAL: failed to insert %v, called insert with empty path", value) | |
} | |
nodeName := path[0] | |
node, ok := t.nodes[nodeName] | |
if !ok { | |
node = &trie{nodes: map[string]*trie{}} | |
t.nodes[nodeName] = node | |
} | |
if len(path) == 1 { | |
node.target = value | |
return | |
} | |
if len(path) == 2 && path[1] == "..." { | |
node.target = value | |
node.matchAll = true | |
return | |
} | |
node.insert(path[1:], value) | |
} | |
func (t *trie) find(soFar []string, path []string) (string, error) { | |
if len(path) == 0 { | |
return t.target, nil | |
} | |
if t.matchAll { | |
return t.target, nil | |
} | |
node, ok := t.nodes[path[0]] | |
if !ok { | |
soFarStr := strings.Join(soFar, "/") | |
pathStr := strings.Join(append(soFar, path...), "/") | |
return "", fmt.Errorf("cant resolve %s in graph, %s doesn't contain %s", pathStr, soFarStr, path[0]) | |
} | |
return node.find(append(soFar, path[0]), path[1:]) | |
} | |
func (t *trie) Resolve(importPath string) (string, error) { | |
return t.find([]string{}, strings.Split(importPath, "/")) | |
} | |
// BuildResolver build a resolver from the build graph. For performance, it might be desired to only process part of the | |
// build graph. For this reason, you can pass wildcard build labels to targets e.g. //src/..., otherwise the whole graph | |
// is processed | |
func BuildResolver(plz string, targets []string) *trie { | |
cmd := exec.Command(plz, append([]string{"query", "graph"}, targets...)...) | |
stdOut := &bytes.Buffer{} | |
cmd.Stdout = stdOut | |
if err := cmd.Run(); err != nil { | |
panic(err) | |
} | |
graph := new(Graph) | |
if err := json.Unmarshal(stdOut.Bytes(), &graph); err != nil { | |
panic(err) | |
} | |
t := &trie{nodes: map[string]*trie{}} | |
for pkg, target := range graph.goPackages() { | |
t.insert(strings.Split(pkg, "/"), target) | |
} | |
return t | |
} | |
func (pkg *Graph) goPackages() map[string]string { | |
ret := make(map[string]string) | |
for name, pkg := range pkg.Pkgs { | |
for t, pkg := range pkg.goPackages(name) { | |
ret[t] = pkg | |
} | |
} | |
return ret | |
} | |
func (pkg *Package) goPackages(name string) map[string]string { | |
ret := make(map[string]string) | |
for targetName, t := range pkg.Targets { | |
for _, l := range t.Labels { | |
if strings.HasPrefix(l, "go_package:") { | |
// We shouldn't be depending on internal rules. The main rule should provide this targets as "go" | |
if strings.Contains(targetName, "#") { | |
targetName = strings.Split(targetName, "#")[0] | |
targetName = strings.TrimPrefix(targetName, "_") | |
} | |
label := fmt.Sprintf("//%s:%s", name, targetName) | |
ret[strings.TrimPrefix(l, "go_package:")] = label | |
} | |
} | |
} | |
return ret | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment