| // Copyright 2017 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // This program generates deps.go. |
| // Run as "go run mkdeps.go" or, to redirect the output, "go run mkdeps.go x.txt". |
| |
| // +build ignore |
| |
| package main |
| |
| import ( |
| "bytes" |
| "flag" |
| "fmt" |
| "io/ioutil" |
| "log" |
| "os" |
| "os/exec" |
| "sort" |
| "strings" |
| ) |
| |
| // We need to test enough GOOS/GOARCH combinations |
| // to find all the package dependencies of cmd/go on all systems. |
| var targetList = strings.Fields(` |
| linux/386 |
| linux/amd64 |
| windows/amd64 |
| `) |
| |
| func usage() { |
| fmt.Fprintf(os.Stderr, "usage: mkdeps [deps.go]\n") |
| os.Exit(2) |
| } |
| |
| func main() { |
| log.SetPrefix("mkdeps: ") |
| log.SetFlags(0) |
| flag.Usage = usage |
| flag.Parse() |
| if flag.NArg() > 1 { |
| usage() |
| } |
| |
| outfile := "deps.go" |
| if flag.NArg() == 1 { |
| outfile = flag.Arg(0) |
| } |
| |
| _, deps := importsAndDepsOf("cmd/go") |
| all := deps["cmd/go"] |
| all = append(all, "cmd/go") |
| imports, deps := importsAndDepsOf(all...) |
| |
| // Sort topologically, then by import path. |
| var topo []string |
| walked := make(map[string]bool) |
| var walk func(string) |
| walk = func(p string) { |
| if walked[p] { |
| return |
| } |
| walked[p] = true |
| sort.Strings(deps[p]) |
| for _, d := range deps[p] { |
| walk(d) |
| } |
| topo = append(topo, p) |
| } |
| walk("cmd/go") |
| |
| // We're only going to print imports, not deps, |
| // in hopes of making deps.go intelligible to people |
| // who need to debug it or attempt to resolve merge conflicts. |
| // For the most part, deps is just the transitive closure of imports, |
| // but sometimes there are implicit deps supplied by the go command |
| // that are not derivable from imports. |
| // Find those (if any) and copy them explicitly into imports. |
| for _, p := range topo { |
| for _, dp := range deps[p] { |
| found := false |
| for _, ip := range imports[p] { |
| if dp == ip || inList(deps[ip], dp) { |
| found = true |
| break |
| } |
| } |
| if !found { |
| imports[p] = append(imports[p], dp) |
| } |
| } |
| sort.Strings(imports[p]) |
| } |
| sort.Strings(all) |
| |
| // Print table. |
| var buf bytes.Buffer |
| fmt.Fprintf(&buf, "// Code generated by mkdeps.bash; DO NOT EDIT.\n\n") |
| fmt.Fprintf(&buf, "package main\n\n") |
| fmt.Fprintf(&buf, "var builddeps = map[string][]string{\n") |
| for _, p := range all { |
| if p == "unsafe" { // unsafe should not be built |
| continue |
| } |
| // We're printing a multiline format here to make the output more |
| // intelligible both to people and to merge tools. |
| // We put the name of the parent package as a comment on every line |
| // to keep a merge tool from applying the diff for one package |
| // to the dependency list for a different package. |
| // The extra blank line at the start stops any attempt by gofmt at |
| // lining up the slice literals from different packages, |
| // even if they are empty slices (on a single line with the key). |
| fmt.Fprintf(&buf, "\n\t%q: {\n", p) |
| for _, d := range imports[p] { |
| if d != "unsafe" { |
| fmt.Fprintf(&buf, "\t\t%q, // %s\n", d, p) |
| } |
| } |
| fmt.Fprintf(&buf, "\t},\n") |
| } |
| fmt.Fprintf(&buf, "\n}\n") |
| |
| // Run the installed gofmt instead of using go/format, |
| // because, on the off chance they disagree, |
| // the installed gofmt binary is by definition the correct one. |
| cmd := exec.Command("gofmt") |
| cmd.Stdin = &buf |
| var out bytes.Buffer |
| cmd.Stdout = &out |
| if err := cmd.Run(); err != nil { |
| log.Fatalf("gofmt: %v", err) |
| } |
| |
| if err := ioutil.WriteFile(outfile, out.Bytes(), 0666); err != nil { |
| log.Fatal(err) |
| } |
| } |
| |
| func inList(xs []string, s string) bool { |
| for _, x := range xs { |
| if x == s { |
| return true |
| } |
| } |
| return false |
| } |
| |
| // importsAndDepsOf returns two maps, one giving the imports for each package in pkgs, |
| // and one giving the dependencies for each package in pkgs. |
| // Both the keys and the entries in the value slices are full import paths. |
| func importsAndDepsOf(pkgs ...string) (map[string][]string, map[string][]string) { |
| imports := make(map[string][]string) |
| deps := make(map[string][]string) |
| for _, target := range targetList { |
| args := []string{"list", "-tags", "cmd_go_bootstrap", "-f", "{{range .Imports}}import {{$.ImportPath}} {{.}}\n{{end}}{{range .Deps}}dep {{$.ImportPath}} {{.}}\n{{end}}"} |
| args = append(args, pkgs...) |
| cmd := exec.Command("go", args...) |
| t := strings.Split(target, "/") |
| cmd.Env = append(os.Environ(), "GOOS="+t[0], "GOARCH="+t[1]) |
| var stderr bytes.Buffer |
| cmd.Stderr = &stderr |
| out, err := cmd.Output() |
| if err != nil && !strings.Contains(stderr.String(), "build constraints exclude all Go files") { |
| log.Fatalf("GOOS=%s GOARCH=%s go list: %v\n%s\n%s", t[0], t[1], err, stderr.Bytes(), out) |
| } |
| helped := false |
| for _, line := range strings.Split(string(out), "\n") { |
| f := strings.Fields(line) |
| if len(f) != 3 { |
| continue |
| } |
| if f[0] == "import" && !inList(imports[f[1]], f[2]) { |
| helped = true |
| imports[f[1]] = append(imports[f[1]], f[2]) |
| } |
| if f[0] == "dep" && !inList(deps[f[1]], f[2]) { |
| helped = true |
| deps[f[1]] = append(deps[f[1]], f[2]) |
| } |
| } |
| if !helped { |
| fmt.Fprintf(os.Stderr, "mkdeps: note: %s did not contribute any new dependencies\n", target) |
| } |
| } |
| return imports, deps |
| } |