xref: /aosp_15_r20/external/boringssl/src/util/godeps.go (revision 8fb009dc861624b67b6cdb62ea21f0f22d0c584b)
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//go:build ignore
16
17// godeps prints out dependencies of a package in either CMake or Make depfile
18// format, for incremental rebuilds.
19//
20// The depfile format is preferred. It works correctly when new files are added.
21// However, CMake only supports depfiles for custom commands with Ninja and
22// starting CMake 3.7. For other configurations, we also support CMake's format,
23// but CMake must be rerun when file lists change.
24package main
25
26import (
27	"flag"
28	"fmt"
29	"go/build"
30	"os"
31	"path/filepath"
32	"sort"
33	"strings"
34)
35
36var (
37	format  = flag.String("format", "cmake", "The format to output to, either 'cmake' or 'depfile'")
38	mainPkg = flag.String("pkg", "", "The package to print dependencies for")
39	target  = flag.String("target", "", "The name of the output file")
40	out     = flag.String("out", "", "The path to write the output to. If unset, this is stdout")
41)
42
43func cMakeQuote(in string) string {
44	// See https://cmake.org/cmake/help/v3.0/manual/cmake-language.7.html#quoted-argument
45	var b strings.Builder
46	b.Grow(len(in))
47	// Iterate over in as bytes.
48	for i := 0; i < len(in); i++ {
49		switch c := in[i]; c {
50		case '\\', '"':
51			b.WriteByte('\\')
52			b.WriteByte(c)
53		case '\t':
54			b.WriteString("\\t")
55		case '\r':
56			b.WriteString("\\r")
57		case '\n':
58			b.WriteString("\\n")
59		default:
60			b.WriteByte(in[i])
61		}
62	}
63	return b.String()
64}
65
66func writeCMake(outFile *os.File, files []string) error {
67	for i, file := range files {
68		if i != 0 {
69			if _, err := outFile.WriteString(";"); err != nil {
70				return err
71			}
72		}
73		if _, err := outFile.WriteString(cMakeQuote(file)); err != nil {
74			return err
75		}
76	}
77	return nil
78}
79
80func makeQuote(in string) string {
81	// See https://www.gnu.org/software/make/manual/make.html#Rule-Syntax
82	var b strings.Builder
83	b.Grow(len(in))
84	// Iterate over in as bytes.
85	for i := 0; i < len(in); i++ {
86		switch c := in[i]; c {
87		case '$':
88			b.WriteString("$$")
89		case '#', '\\', ' ':
90			b.WriteByte('\\')
91			b.WriteByte(c)
92		default:
93			b.WriteByte(c)
94		}
95	}
96	return b.String()
97}
98
99func writeDepfile(outFile *os.File, files []string) error {
100	if _, err := fmt.Fprintf(outFile, "%s:", makeQuote(*target)); err != nil {
101		return err
102	}
103	for _, file := range files {
104		if _, err := fmt.Fprintf(outFile, " %s", makeQuote(file)); err != nil {
105			return err
106		}
107	}
108	_, err := outFile.WriteString("\n")
109	return err
110}
111
112func appendPrefixed(list, newFiles []string, prefix string) []string {
113	for _, file := range newFiles {
114		list = append(list, filepath.Join(prefix, file))
115	}
116	return list
117}
118
119func main() {
120	flag.Parse()
121
122	if len(*mainPkg) == 0 {
123		fmt.Fprintf(os.Stderr, "-pkg argument is required.\n")
124		os.Exit(1)
125	}
126
127	var isDepfile bool
128	switch *format {
129	case "depfile":
130		isDepfile = true
131	case "cmake":
132		isDepfile = false
133	default:
134		fmt.Fprintf(os.Stderr, "Unknown format: %q\n", *format)
135		os.Exit(1)
136	}
137
138	if isDepfile && len(*target) == 0 {
139		fmt.Fprintf(os.Stderr, "-target argument is required for depfile.\n")
140		os.Exit(1)
141	}
142
143	done := make(map[string]struct{})
144	var files []string
145	var recurse func(pkgName string) error
146	recurse = func(pkgName string) error {
147		pkg, err := build.Default.Import(pkgName, ".", 0)
148		if err != nil {
149			return err
150		}
151
152		// Skip standard packages.
153		if pkg.Goroot {
154			return nil
155		}
156
157		// Skip already-visited packages.
158		if _, ok := done[pkg.Dir]; ok {
159			return nil
160		}
161		done[pkg.Dir] = struct{}{}
162
163		files = appendPrefixed(files, pkg.GoFiles, pkg.Dir)
164		files = appendPrefixed(files, pkg.CgoFiles, pkg.Dir)
165		// Include ignored Go files. A subsequent change may cause them
166		// to no longer be ignored.
167		files = appendPrefixed(files, pkg.IgnoredGoFiles, pkg.Dir)
168
169		// Recurse into imports.
170		for _, importName := range pkg.Imports {
171			if err := recurse(importName); err != nil {
172				return err
173			}
174		}
175		return nil
176	}
177	if err := recurse(*mainPkg); err != nil {
178		fmt.Fprintf(os.Stderr, "Error getting dependencies: %s\n", err)
179		os.Exit(1)
180	}
181
182	sort.Strings(files)
183
184	outFile := os.Stdout
185	if len(*out) != 0 {
186		var err error
187		outFile, err = os.Create(*out)
188		if err != nil {
189			fmt.Fprintf(os.Stderr, "Error writing output: %s\n", err)
190			os.Exit(1)
191		}
192		defer outFile.Close()
193	}
194
195	var err error
196	if isDepfile {
197		err = writeDepfile(outFile, files)
198	} else {
199		err = writeCMake(outFile, files)
200	}
201	if err != nil {
202		fmt.Fprintf(os.Stderr, "Error writing output: %s\n", err)
203		os.Exit(1)
204	}
205}
206