blob: 5f60960f4b6e67003cfbf6a06de16c6a50c1ec31 [file] [log] [blame]
// Copyright (c) 2024, Google Inc.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package main
import (
"bytes"
"cmp"
"encoding/json"
"fmt"
"path"
"slices"
"strings"
)
// An OutputTarget is a build target for consumption by the downstream build
// systems. All pre-generated files are baked input its source lists.
type OutputTarget struct {
// Srcs is the list of C or C++ files (determined by file extension) that are
// built into the target.
Srcs []string `json:"srcs,omitempty"`
// Hdrs is the list public headers that should be available to external
// projects using this target.
Hdrs []string `json:"hdrs,omitempty"`
// InternalHdrs is the list of internal headers that should be available to
// this target, as well as any internal targets using this target.
InternalHdrs []string `json:"internal_hdrs,omitempty"`
// Asm is the a list of assembly files to be passed to a gas-compatible
// assembler.
Asm []string `json:"asm,omitempty"`
// Nasm is the a list of assembly files to be passed to a nasm-compatible
// assembler.
Nasm []string `json:"nasm,omitempty"`
// Data is a list of test data files that should be available when the test is
// run.
Data []string `json:"data,omitempty"`
}
// An InputTarget is a build target with build inputs that still need to be
// pregenerated.
type InputTarget struct {
OutputTarget
// ErrData contains a list of errordata files to combine into err_data.c.
ErrData []string `json:"err_data,omitempty"`
// The following fields define perlasm sources for the corresponding
// architecture.
PerlasmAarch64 []PerlasmSource `json:"perlasm_aarch64,omitempty"`
PerlasmArm []PerlasmSource `json:"perlasm_arm,omitempty"`
PerlasmX86 []PerlasmSource `json:"perlasm_x86,omitempty"`
PerlasmX86_64 []PerlasmSource `json:"perlasm_x86_64,omitempty"`
}
type PerlasmSource struct {
// Src the path to the input perlasm file.
Src string `json:"src"`
// Dst, if not empty, is base name of the destination file. If empty, this
// is determined from Src by default. It should be overriden if a single
// source file generates multiple functions (e.g. SHA-256 vs SHA-512) or
// multiple architectures (e.g. the "armx" files).
Dst string `json:"dst,omitempty"`
// Args is a list of extra parameters to pass to the script.
Args []string `json:"args,omitempty"`
}
// Pregenerate converts an input target to an output target. It returns the
// result alongside a list of tasks that must be run to build the referenced
// files.
func (in *InputTarget) Pregenerate(name string) (out OutputTarget, tasks []Task) {
out = in.OutputTarget
// Make copies of any fields we will write to.
out.Srcs = slices.Clone(out.Srcs)
out.Asm = slices.Clone(out.Asm)
out.Nasm = slices.Clone(out.Nasm)
addTask := func(list *[]string, t Task) {
tasks = append(tasks, t)
*list = append(*list, t.Destination())
}
if len(in.ErrData) != 0 {
addTask(&out.Srcs, &ErrDataTask{TargetName: name, Inputs: in.ErrData})
}
addPerlasmTask := func(list *[]string, p *PerlasmSource, fileSuffix string, args []string) {
dst := p.Dst
if len(p.Dst) == 0 {
dst = strings.TrimSuffix(path.Base(p.Src), ".pl")
}
dst = path.Join("gen", name, dst+fileSuffix)
args = append(slices.Clone(args), p.Args...)
addTask(list, &PerlasmTask{Src: p.Src, Dst: dst, Args: args})
}
for _, p := range in.PerlasmAarch64 {
addPerlasmTask(&out.Asm, &p, "-apple.S", []string{"ios64"})
addPerlasmTask(&out.Asm, &p, "-linux.S", []string{"linux64"})
addPerlasmTask(&out.Asm, &p, "-win.S", []string{"win64"})
}
for _, p := range in.PerlasmArm {
addPerlasmTask(&out.Asm, &p, "-linux.S", []string{"linux32"})
}
for _, p := range in.PerlasmX86 {
addPerlasmTask(&out.Asm, &p, "-apple.S", []string{"macosx", "-fPIC", "-DOPENSSL_IA32_SSE2"})
addPerlasmTask(&out.Asm, &p, "-linux.S", []string{"elf", "-fPIC", "-DOPENSSL_IA32_SSE2"})
addPerlasmTask(&out.Nasm, &p, "-win.asm", []string{"win32n", "-fPIC", "-DOPENSSL_IA32_SSE2"})
}
for _, p := range in.PerlasmX86_64 {
addPerlasmTask(&out.Asm, &p, "-apple.S", []string{"macosx"})
addPerlasmTask(&out.Asm, &p, "-linux.S", []string{"elf"})
addPerlasmTask(&out.Nasm, &p, "-win.asm", []string{"nasm"})
}
// Re-sort the modified fields.
slices.Sort(out.Srcs)
slices.Sort(out.Asm)
slices.Sort(out.Nasm)
return
}
func sortedKeys[K cmp.Ordered, V any](m map[K]V) []K {
keys := make([]K, 0, len(m))
for k := range m {
keys = append(keys, k)
}
slices.Sort(keys)
return keys
}
func writeHeader(b *bytes.Buffer, comment string) {
fmt.Fprintf(b, "%s Copyright (c) 2024, Google Inc.\n", comment)
fmt.Fprintf(b, "%s\n", comment)
fmt.Fprintf(b, "%s Permission to use, copy, modify, and/or distribute this software for any\n", comment)
fmt.Fprintf(b, "%s purpose with or without fee is hereby granted, provided that the above\n", comment)
fmt.Fprintf(b, "%s copyright notice and this permission notice appear in all copies.\n", comment)
fmt.Fprintf(b, "%s\n", comment)
fmt.Fprintf(b, "%s THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n", comment)
fmt.Fprintf(b, "%s WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n", comment)
fmt.Fprintf(b, "%s MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY\n", comment)
fmt.Fprintf(b, "%s SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n", comment)
fmt.Fprintf(b, "%s WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION\n", comment)
fmt.Fprintf(b, "%s OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN\n", comment)
fmt.Fprintf(b, "%s CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n", comment)
fmt.Fprintf(b, "%s\n", comment)
fmt.Fprintf(b, "%s Generated by go ./util/pregenerate. Do not edit manually.\n", comment)
}
func buildVariablesTask(targets map[string]OutputTarget, dst, comment string, writeVariable func(b *bytes.Buffer, name string, val []string)) Task {
return NewSimpleTask(dst, func() ([]byte, error) {
var b bytes.Buffer
writeHeader(&b, comment)
for _, name := range sortedKeys(targets) {
target := targets[name]
if len(target.Srcs) != 0 {
writeVariable(&b, name+"_sources", target.Srcs)
}
if len(target.Hdrs) != 0 {
writeVariable(&b, name+"_headers", target.Hdrs)
}
if len(target.InternalHdrs) != 0 {
writeVariable(&b, name+"_internal_headers", target.InternalHdrs)
}
if len(target.Asm) != 0 {
writeVariable(&b, name+"_sources_asm", target.Asm)
}
if len(target.Nasm) != 0 {
writeVariable(&b, name+"_sources_nasm", target.Nasm)
}
if len(target.Data) != 0 {
writeVariable(&b, name+"_data", target.Data)
}
}
return b.Bytes(), nil
})
}
func writeBazelVariable(b *bytes.Buffer, name string, val []string) {
fmt.Fprintf(b, "\n%s = [\n", name)
for _, v := range val {
fmt.Fprintf(b, " %q,\n", v)
}
fmt.Fprintf(b, "]\n")
}
func writeCMakeVariable(b *bytes.Buffer, name string, val []string) {
fmt.Fprintf(b, "\nset(\n")
fmt.Fprintf(b, " %s\n\n", strings.ToUpper(name))
for _, v := range val {
fmt.Fprintf(b, " %s\n", v)
}
fmt.Fprintf(b, ")\n")
}
func writeMakeVariable(b *bytes.Buffer, name string, val []string) {
fmt.Fprintf(b, "\n%s := \\\n", name)
for i, v := range val {
if i == len(val)-1 {
fmt.Fprintf(b, " %s\n", v)
} else {
fmt.Fprintf(b, " %s \\\n", v)
}
}
}
func writeGNVariable(b *bytes.Buffer, name string, val []string) {
// Bazel and GN have the same syntax similar syntax.
writeBazelVariable(b, name, val)
}
func jsonTask(targets map[string]OutputTarget, dst string) Task {
return NewSimpleTask(dst, func() ([]byte, error) {
return json.MarshalIndent(targets, "", " ")
})
}
func soongTask(targets map[string]OutputTarget, dst string) Task {
return NewSimpleTask(dst, func() ([]byte, error) {
var b bytes.Buffer
writeHeader(&b, "//")
writeAttribute := func(indent, name string, val []string) {
fmt.Fprintf(&b, "%s%s: [\n", indent, name)
for _, v := range val {
fmt.Fprintf(&b, "%s %q,\n", indent, v)
}
fmt.Fprintf(&b, "%s],\n", indent)
}
for _, name := range sortedKeys(targets) {
target := targets[name]
fmt.Fprintf(&b, "\ncc_defaults {\n")
fmt.Fprintf(&b, " name: %q\n", "boringssl_"+name+"_sources")
if len(target.Srcs) != 0 {
writeAttribute(" ", "srcs", target.Srcs)
}
if len(target.Data) != 0 {
writeAttribute(" ", "data", target.Data)
}
if len(target.Asm) != 0 {
fmt.Fprintf(&b, " target: {\n")
// Only emit asm for Linux. On Windows, BoringSSL requires NASM, which is
// not available in AOSP. On Darwin, the assembly works fine, but it
// conflicts with Android's FIPS build. See b/294399371.
fmt.Fprintf(&b, " linux: {\n")
writeAttribute(" ", "srcs", target.Asm)
fmt.Fprintf(&b, " },\n")
fmt.Fprintf(&b, " darwin: {\n")
fmt.Fprintf(&b, " cflags: [\"-DOPENSSL_NO_ASM\"],\n")
fmt.Fprintf(&b, " },\n")
fmt.Fprintf(&b, " windows: {\n")
fmt.Fprintf(&b, " cflags: [\"-DOPENSSL_NO_ASM\"],\n")
fmt.Fprintf(&b, " },\n")
fmt.Fprintf(&b, " },\n")
}
fmt.Fprintf(&b, "},\n")
}
return b.Bytes(), nil
})
}
func MakeBuildFiles(targets map[string]OutputTarget) []Task {
// TODO(crbug.com/boringssl/542): Generate the build files for the other
// types as well.
return []Task{
buildVariablesTask(targets, "gen/sources.cmake", "#", writeCMakeVariable),
jsonTask(targets, "gen/sources.json"),
}
}