blob: ca50cdf4c38939222eb0e62a01a8d6c66d716e08 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Generates a depfile for a Go package that can be consumed by Ninja.
package main
import (
"errors"
"flag"
"fmt"
"go/build"
"log"
"os"
"runtime"
"sort"
"strings"
"sync"
)
type stringsFlag []string
func (v *stringsFlag) String() string { return strings.Join(*v, " ") }
func (v *stringsFlag) Set(s string) error {
*v = strings.Split(s, " ")
if *v == nil {
*v = []string{}
}
return nil
}
type prefixmapFlag []struct{ from, to string }
func (v *prefixmapFlag) String() string {
var s []string
for _, p := range *v {
s = append(s, fmt.Sprintf("%s=%s", p.from, p.to))
}
return strings.Join(s, " ")
}
func (v *prefixmapFlag) Set(s string) error {
if strings.Count(s, "=") != 1 {
return errors.New("argument must be of the form from=to")
}
i := strings.Index(s, "=")
from, to := s[:i], s[i+1:]
if from == "" || to == "" {
return errors.New("from and to must be non-empty")
}
*v = append(*v, struct{ from, to string }{from, to})
return nil
}
var (
ctx = build.Default
output string
test bool
prefixmap = prefixmapFlag{}
)
func init() {
flag.Var((*stringsFlag)(&ctx.BuildTags), "tags", "build tags")
flag.StringVar(&output, "o", "", "name of the resulting executable")
flag.BoolVar(&test, "test", false, "whether this is a test target")
flag.Var(&prefixmap, "prefixmap", "path prefix mapping in the from=to format")
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "usage: godepfile [packages]\n")
flag.PrintDefaults()
}
}
func appendAndPrefix(slice []string, prefix string, src []string) []string {
for _, s := range src {
slice = append(slice, prefix+s)
}
return slice
}
func main() {
flag.Parse()
if len(flag.Args()) == 0 {
flag.Usage()
os.Exit(1)
}
var mu sync.Mutex
deps := make(map[string]bool)
paths := make(map[string]bool)
fdlimit := make(chan struct{}, 128)
var wg sync.WaitGroup
var scan func(path, srcDir string, test bool)
scan = func(path, srcDir string, test bool) {
defer wg.Done()
mu.Lock()
_, done := paths[path]
if !done {
paths[path] = true
}
mu.Unlock()
if done {
return
}
if path == "C" {
return
}
fdlimit <- struct{}{}
defer func() { <-fdlimit }()
pkg, err := ctx.Import(path, srcDir, 0)
if err != nil {
log.Fatalf("%s: %v", path, err)
}
var files []string
srcdir := pkg.Dir + "/"
for _, p := range prefixmap {
if strings.HasPrefix(srcdir, p.from) {
srcdir = p.to + srcdir[len(p.from):]
}
}
files = appendAndPrefix(files, srcdir, pkg.GoFiles)
files = appendAndPrefix(files, srcdir, pkg.CgoFiles)
files = appendAndPrefix(files, srcdir, pkg.CFiles)
files = appendAndPrefix(files, srcdir, pkg.CXXFiles)
files = appendAndPrefix(files, srcdir, pkg.HFiles)
files = appendAndPrefix(files, srcdir, pkg.SFiles)
files = appendAndPrefix(files, srcdir, pkg.SwigFiles)
files = appendAndPrefix(files, srcdir, pkg.SwigCXXFiles)
if test {
files = appendAndPrefix(files, srcdir, pkg.TestGoFiles)
files = appendAndPrefix(files, srcdir, pkg.XTestGoFiles)
}
mu.Lock()
for _, file := range files {
deps[file] = true
}
mu.Unlock()
if pkg.Name == "main" && output == "" {
bindir := os.Getenv("GOBIN")
if bindir == "" {
bindir = pkg.BinDir
}
if ctx.GOOS == runtime.GOOS && ctx.GOARCH == runtime.GOARCH {
output = ctx.JoinPath(bindir, path)
} else {
output = ctx.JoinPath(bindir, ctx.GOOS+"_"+ctx.GOARCH, path)
}
}
for _, imp := range pkg.Imports {
wg.Add(1)
go scan(imp, pkg.Dir, false)
}
if test {
for _, imp := range pkg.TestImports {
wg.Add(1)
go scan(imp, pkg.Dir, false)
}
for _, imp := range pkg.XTestImports {
wg.Add(1)
go scan(imp, pkg.Dir, false)
}
}
}
for _, root := range flag.Args() {
wg.Add(1)
go scan(root, "", test)
}
wg.Wait()
fmt.Printf("%s:", output)
var depnames []string
for path := range deps {
depnames = append(depnames, path)
}
sort.Strings(depnames)
for path := range deps {
fmt.Printf(" %s", path)
}
fmt.Printf("\n")
}