blob: d8da0122e8efda9cfa19d2b574bb9809c06e5c9e [file]
// 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
}