• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (c) 2018, Google Inc.
2//
3// Permission to use, copy, modify, and/or distribute this software for any
4// purpose with or without fee is hereby granted, provided that the above
5// copyright notice and this permission notice appear in all copies.
6//
7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
10// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
15// godeps prints out dependencies of a package in either CMake or Make depfile
16// format, for incremental rebuilds.
17//
18// The depfile format is preferred. It works correctly when new files are added.
19// However, CMake only supports depfiles for custom commands with Ninja and
20// starting CMake 3.7. For other configurations, we also support CMake's format,
21// but CMake must be rerun when file lists change.
22package main
23
24import (
25	"flag"
26	"fmt"
27	"go/build"
28	"os"
29	"path/filepath"
30	"sort"
31	"strings"
32)
33
34var (
35	format  = flag.String("format", "cmake", "The format to output to, either 'cmake' or 'depfile'")
36	mainPkg = flag.String("pkg", "", "The package to print dependencies for")
37	target  = flag.String("target", "", "The name of the output file")
38	out     = flag.String("out", "", "The path to write the output to. If unset, this is stdout")
39)
40
41func cMakeQuote(in string) string {
42	// See https://cmake.org/cmake/help/v3.0/manual/cmake-language.7.html#quoted-argument
43	var b strings.Builder
44	b.Grow(len(in))
45	// Iterate over in as bytes.
46	for i := 0; i < len(in); i++ {
47		switch c := in[i]; c {
48		case '\\', '"':
49			b.WriteByte('\\')
50			b.WriteByte(c)
51		case '\t':
52			b.WriteString("\\t")
53		case '\r':
54			b.WriteString("\\r")
55		case '\n':
56			b.WriteString("\\n")
57		default:
58			b.WriteByte(in[i])
59		}
60	}
61	return b.String()
62}
63
64func writeCMake(outFile *os.File, files []string) error {
65	for i, file := range files {
66		if i != 0 {
67			if _, err := outFile.WriteString(";"); err != nil {
68				return err
69			}
70		}
71		if _, err := outFile.WriteString(cMakeQuote(file)); err != nil {
72			return err
73		}
74	}
75	return nil
76}
77
78func makeQuote(in string) string {
79	// See https://www.gnu.org/software/make/manual/make.html#Rule-Syntax
80	var b strings.Builder
81	b.Grow(len(in))
82	// Iterate over in as bytes.
83	for i := 0; i < len(in); i++ {
84		switch c := in[i]; c {
85		case '$':
86			b.WriteString("$$")
87		case '#', '\\', ' ':
88			b.WriteByte('\\')
89			b.WriteByte(c)
90		default:
91			b.WriteByte(c)
92		}
93	}
94	return b.String()
95}
96
97func writeDepfile(outFile *os.File, files []string) error {
98	if _, err := fmt.Fprintf(outFile, "%s:", makeQuote(*target)); err != nil {
99		return err
100	}
101	for _, file := range files {
102		if _, err := fmt.Fprintf(outFile, " %s", makeQuote(file)); err != nil {
103			return err
104		}
105	}
106	_, err := outFile.WriteString("\n")
107	return err
108}
109
110func appendPrefixed(list, newFiles []string, prefix string) []string {
111	for _, file := range newFiles {
112		list = append(list, filepath.Join(prefix, file))
113	}
114	return list
115}
116
117func main() {
118	flag.Parse()
119
120	if len(*mainPkg) == 0 {
121		fmt.Fprintf(os.Stderr, "-pkg argument is required.\n")
122		os.Exit(1)
123	}
124
125	var isDepfile bool
126	switch *format {
127	case "depfile":
128		isDepfile = true
129	case "cmake":
130		isDepfile = false
131	default:
132		fmt.Fprintf(os.Stderr, "Unknown format: %q\n", *format)
133		os.Exit(1)
134	}
135
136	if isDepfile && len(*target) == 0 {
137		fmt.Fprintf(os.Stderr, "-target argument is required for depfile.\n")
138		os.Exit(1)
139	}
140
141	done := make(map[string]struct{})
142	var files []string
143	var recurse func(pkgName string) error
144	recurse = func(pkgName string) error {
145		pkg, err := build.Default.Import(pkgName, ".", 0)
146		if err != nil {
147			return err
148		}
149
150		// Skip standard packages.
151		if pkg.Goroot {
152			return nil
153		}
154
155		// Skip already-visited packages.
156		if _, ok := done[pkg.Dir]; ok {
157			return nil
158		}
159		done[pkg.Dir] = struct{}{}
160
161		files = appendPrefixed(files, pkg.GoFiles, pkg.Dir)
162		files = appendPrefixed(files, pkg.CgoFiles, pkg.Dir)
163		// Include ignored Go files. A subsequent change may cause them
164		// to no longer be ignored.
165		files = appendPrefixed(files, pkg.IgnoredGoFiles, pkg.Dir)
166
167		// Recurse into imports.
168		for _, importName := range pkg.Imports {
169			if err := recurse(importName); err != nil {
170				return err
171			}
172		}
173		return nil
174	}
175	if err := recurse(*mainPkg); err != nil {
176		fmt.Fprintf(os.Stderr, "Error getting dependencies: %s\n", err)
177		os.Exit(1)
178	}
179
180	sort.Strings(files)
181
182	outFile := os.Stdout
183	if len(*out) != 0 {
184		var err error
185		outFile, err = os.Create(*out)
186		if err != nil {
187			fmt.Fprintf(os.Stderr, "Error writing output: %s\n", err)
188			os.Exit(1)
189		}
190		defer outFile.Close()
191	}
192
193	var err error
194	if isDepfile {
195		err = writeDepfile(outFile, files)
196	} else {
197		err = writeCMake(outFile, files)
198	}
199	if err != nil {
200		fmt.Fprintf(os.Stderr, "Error writing output: %s\n", err)
201		os.Exit(1)
202	}
203}
204