xref: /aosp_15_r20/external/boringssl/src/util/pregenerate/build.go (revision 8fb009dc861624b67b6cdb62ea21f0f22d0c584b)
1// Copyright (c) 2024, 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
15package main
16
17import (
18	"bytes"
19	"cmp"
20	"encoding/json"
21	"fmt"
22	"path"
23	"path/filepath"
24	"slices"
25	"strings"
26
27	"boringssl.googlesource.com/boringssl/util/build"
28)
29
30// An InputTarget is a build target with build inputs that still need to be
31// pregenerated. All file lists in InputTarget are interpreted with glob
32// patterns as in filepath.Glob.
33type InputTarget struct {
34	build.Target
35	// ErrData contains a list of errordata files to combine into err_data.c.
36	ErrData []string `json:"err_data,omitempty"`
37	// The following fields define perlasm sources for the corresponding
38	// architecture.
39	PerlasmAarch64 []PerlasmSource `json:"perlasm_aarch64,omitempty"`
40	PerlasmArm     []PerlasmSource `json:"perlasm_arm,omitempty"`
41	PerlasmX86     []PerlasmSource `json:"perlasm_x86,omitempty"`
42	PerlasmX86_64  []PerlasmSource `json:"perlasm_x86_64,omitempty"`
43}
44
45type PerlasmSource struct {
46	// Src the path to the input perlasm file.
47	Src string `json:"src"`
48	// Dst, if not empty, is base name of the destination file. If empty, this
49	// is determined from Src by default. It should be overriden if a single
50	// source file generates multiple functions (e.g. SHA-256 vs SHA-512) or
51	// multiple architectures (e.g. the "armx" files).
52	Dst string `json:"dst,omitempty"`
53	// Args is a list of extra parameters to pass to the script.
54	Args []string `json:"args,omitempty"`
55}
56
57// Pregenerate converts an input target to an output target. It returns the
58// result alongside a list of tasks that must be run to build the referenced
59// files.
60func (in *InputTarget) Pregenerate(name string) (out build.Target, tasks []Task, err error) {
61	// Expand wildcards.
62	out.Srcs, err = glob(in.Srcs)
63	if err != nil {
64		return
65	}
66	out.Hdrs, err = glob(in.Hdrs)
67	if err != nil {
68		return
69	}
70	out.InternalHdrs, err = glob(in.InternalHdrs)
71	if err != nil {
72		return
73	}
74	out.Asm, err = glob(in.Asm)
75	if err != nil {
76		return
77	}
78	out.Nasm, err = glob(in.Nasm)
79	if err != nil {
80		return
81	}
82	out.Data, err = glob(in.Data)
83	if err != nil {
84		return
85	}
86
87	addTask := func(list *[]string, t Task) {
88		tasks = append(tasks, t)
89		*list = append(*list, t.Destination())
90	}
91
92	if len(in.ErrData) != 0 {
93		var inputs []string
94		inputs, err = glob(in.ErrData)
95		if err != nil {
96			return
97		}
98		addTask(&out.Srcs, &ErrDataTask{TargetName: name, Inputs: inputs})
99	}
100
101	addPerlasmTask := func(list *[]string, p *PerlasmSource, fileSuffix string, args []string) {
102		dst := p.Dst
103		if len(p.Dst) == 0 {
104			dst = strings.TrimSuffix(path.Base(p.Src), ".pl")
105		}
106		dst = path.Join("gen", name, dst+fileSuffix)
107		args = append(slices.Clone(args), p.Args...)
108		addTask(list, &PerlasmTask{Src: p.Src, Dst: dst, Args: args})
109	}
110
111	for _, p := range in.PerlasmAarch64 {
112		addPerlasmTask(&out.Asm, &p, "-apple.S", []string{"ios64"})
113		addPerlasmTask(&out.Asm, &p, "-linux.S", []string{"linux64"})
114		addPerlasmTask(&out.Asm, &p, "-win.S", []string{"win64"})
115	}
116	for _, p := range in.PerlasmArm {
117		addPerlasmTask(&out.Asm, &p, "-linux.S", []string{"linux32"})
118	}
119	for _, p := range in.PerlasmX86 {
120		addPerlasmTask(&out.Asm, &p, "-apple.S", []string{"macosx", "-fPIC"})
121		addPerlasmTask(&out.Asm, &p, "-linux.S", []string{"elf", "-fPIC"})
122		addPerlasmTask(&out.Nasm, &p, "-win.asm", []string{"win32n", "-fPIC"})
123	}
124	for _, p := range in.PerlasmX86_64 {
125		addPerlasmTask(&out.Asm, &p, "-apple.S", []string{"macosx"})
126		addPerlasmTask(&out.Asm, &p, "-linux.S", []string{"elf"})
127		addPerlasmTask(&out.Nasm, &p, "-win.asm", []string{"nasm"})
128	}
129
130	// Re-sort the modified fields.
131	slices.Sort(out.Srcs)
132	slices.Sort(out.Asm)
133	slices.Sort(out.Nasm)
134
135	return
136}
137
138func glob(paths []string) ([]string, error) {
139	var ret []string
140	for _, path := range paths {
141		if !strings.ContainsRune(path, '*') {
142			ret = append(ret, path)
143			continue
144		}
145		matches, err := filepath.Glob(path)
146		if err != nil {
147			return nil, err
148		}
149		if len(matches) == 0 {
150			return nil, fmt.Errorf("glob matched no files: %q", path)
151		}
152		// Switch from Windows to POSIX paths.
153		for _, match := range matches {
154			ret = append(ret, strings.ReplaceAll(match, "\\", "/"))
155		}
156	}
157	slices.Sort(ret)
158	return ret, nil
159}
160
161func sortedKeys[K cmp.Ordered, V any](m map[K]V) []K {
162	keys := make([]K, 0, len(m))
163	for k := range m {
164		keys = append(keys, k)
165	}
166	slices.Sort(keys)
167	return keys
168}
169
170func writeHeader(b *bytes.Buffer, comment string) {
171	fmt.Fprintf(b, "%s Copyright (c) 2024, Google Inc.\n", comment)
172	fmt.Fprintf(b, "%s\n", comment)
173	fmt.Fprintf(b, "%s Permission to use, copy, modify, and/or distribute this software for any\n", comment)
174	fmt.Fprintf(b, "%s purpose with or without fee is hereby granted, provided that the above\n", comment)
175	fmt.Fprintf(b, "%s copyright notice and this permission notice appear in all copies.\n", comment)
176	fmt.Fprintf(b, "%s\n", comment)
177	fmt.Fprintf(b, "%s THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n", comment)
178	fmt.Fprintf(b, "%s WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n", comment)
179	fmt.Fprintf(b, "%s MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY\n", comment)
180	fmt.Fprintf(b, "%s SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n", comment)
181	fmt.Fprintf(b, "%s WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION\n", comment)
182	fmt.Fprintf(b, "%s OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN\n", comment)
183	fmt.Fprintf(b, "%s CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n", comment)
184	fmt.Fprintf(b, "%s\n", comment)
185	fmt.Fprintf(b, "%s Generated by go ./util/pregenerate. Do not edit manually.\n", comment)
186}
187
188func buildVariablesTask(targets map[string]build.Target, dst, comment string, writeVariable func(b *bytes.Buffer, name string, val []string)) Task {
189	return NewSimpleTask(dst, func() ([]byte, error) {
190		var b bytes.Buffer
191		writeHeader(&b, comment)
192
193		for _, name := range sortedKeys(targets) {
194			target := targets[name]
195			if len(target.Srcs) != 0 {
196				writeVariable(&b, name+"_sources", target.Srcs)
197			}
198			if len(target.Hdrs) != 0 {
199				writeVariable(&b, name+"_headers", target.Hdrs)
200			}
201			if len(target.InternalHdrs) != 0 {
202				writeVariable(&b, name+"_internal_headers", target.InternalHdrs)
203			}
204			if len(target.Asm) != 0 {
205				writeVariable(&b, name+"_sources_asm", target.Asm)
206			}
207			if len(target.Nasm) != 0 {
208				writeVariable(&b, name+"_sources_nasm", target.Nasm)
209			}
210			if len(target.Data) != 0 {
211				writeVariable(&b, name+"_data", target.Data)
212			}
213		}
214
215		return b.Bytes(), nil
216	})
217}
218
219func writeBazelVariable(b *bytes.Buffer, name string, val []string) {
220	fmt.Fprintf(b, "\n%s = [\n", name)
221	for _, v := range val {
222		fmt.Fprintf(b, "    %q,\n", v)
223	}
224	fmt.Fprintf(b, "]\n")
225}
226
227func writeCMakeVariable(b *bytes.Buffer, name string, val []string) {
228	fmt.Fprintf(b, "\nset(\n")
229	fmt.Fprintf(b, "  %s\n\n", strings.ToUpper(name))
230	for _, v := range val {
231		fmt.Fprintf(b, "  %s\n", v)
232	}
233	fmt.Fprintf(b, ")\n")
234}
235
236func writeMakeVariable(b *bytes.Buffer, name string, val []string) {
237	fmt.Fprintf(b, "\n%s := \\\n", name)
238	for i, v := range val {
239		if i == len(val)-1 {
240			fmt.Fprintf(b, "  %s\n", v)
241		} else {
242			fmt.Fprintf(b, "  %s \\\n", v)
243		}
244	}
245}
246
247func writeGNVariable(b *bytes.Buffer, name string, val []string) {
248	fmt.Fprintf(b, "\n%s = [\n", name)
249	for _, v := range val {
250		fmt.Fprintf(b, "  %q,\n", v)
251	}
252	fmt.Fprintf(b, "]\n")
253}
254
255func jsonTask(targets map[string]build.Target, dst string) Task {
256	return NewSimpleTask(dst, func() ([]byte, error) {
257		return json.MarshalIndent(targets, "", "  ")
258	})
259}
260
261func soongTask(targets map[string]build.Target, dst string) Task {
262	return NewSimpleTask(dst, func() ([]byte, error) {
263		var b bytes.Buffer
264		writeHeader(&b, "//")
265
266		writeAttribute := func(indent, name string, val []string) {
267			fmt.Fprintf(&b, "%s%s: [\n", indent, name)
268			for _, v := range val {
269				fmt.Fprintf(&b, "%s    %q,\n", indent, v)
270			}
271			fmt.Fprintf(&b, "%s],\n", indent)
272
273		}
274
275		for _, name := range sortedKeys(targets) {
276			target := targets[name]
277			fmt.Fprintf(&b, "\ncc_defaults {\n")
278			fmt.Fprintf(&b, "    name: %q\n", "boringssl_"+name+"_sources")
279			if len(target.Srcs) != 0 {
280				writeAttribute("    ", "srcs", target.Srcs)
281			}
282			if len(target.Data) != 0 {
283				writeAttribute("    ", "data", target.Data)
284			}
285			if len(target.Asm) != 0 {
286				fmt.Fprintf(&b, "    target: {\n")
287				// Only emit asm for Linux. On Windows, BoringSSL requires NASM, which is
288				// not available in AOSP. On Darwin, the assembly works fine, but it
289				// conflicts with Android's FIPS build. See b/294399371.
290				fmt.Fprintf(&b, "        linux: {\n")
291				writeAttribute("            ", "srcs", target.Asm)
292				fmt.Fprintf(&b, "        },\n")
293				fmt.Fprintf(&b, "        darwin: {\n")
294				fmt.Fprintf(&b, "            cflags: [\"-DOPENSSL_NO_ASM\"],\n")
295				fmt.Fprintf(&b, "        },\n")
296				fmt.Fprintf(&b, "        windows: {\n")
297				fmt.Fprintf(&b, "            cflags: [\"-DOPENSSL_NO_ASM\"],\n")
298				fmt.Fprintf(&b, "        },\n")
299				fmt.Fprintf(&b, "    },\n")
300			}
301			fmt.Fprintf(&b, "},\n")
302		}
303
304		return b.Bytes(), nil
305	})
306}
307
308func MakeBuildFiles(targets map[string]build.Target) []Task {
309	// TODO(crbug.com/boringssl/542): Generate the build files for the other
310	// types as well.
311	return []Task{
312		buildVariablesTask(targets, "gen/sources.bzl", "#", writeBazelVariable),
313		buildVariablesTask(targets, "gen/sources.cmake", "#", writeCMakeVariable),
314		jsonTask(targets, "gen/sources.json"),
315	}
316}
317