blob: ca0cc96b9c9cc0a83fb1c08a1326329c0b7002fa [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"
"path/filepath"
"slices"
"strings"
"boringssl.googlesource.com/boringssl/util/build"
)
// An InputTarget is a build target with build inputs that still need to be
// pregenerated. All file lists in InputTarget are interpreted with glob
// patterns as in filepath.Glob.
type InputTarget struct {
build.Target
// 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 build.Target, tasks []Task, err error) {
// Expand wildcards.
out.Srcs, err = glob(in.Srcs)
if err != nil {
return
}
out.Hdrs, err = glob(in.Hdrs)
if err != nil {
return
}
out.InternalHdrs, err = glob(in.InternalHdrs)
if err != nil {
return
}
out.Asm, err = glob(in.Asm)
if err != nil {
return
}
out.Nasm, err = glob(in.Nasm)
if err != nil {
return
}
out.Data, err = glob(in.Data)
if err != nil {
return
}
addTask := func(list *[]string, t Task) {
tasks = append(tasks, t)
*list = append(*list, t.Destination())
}
if len(in.ErrData) != 0 {
var inputs []string
inputs, err = glob(in.ErrData)
if err != nil {
return
}
addTask(&out.Srcs, &ErrDataTask{TargetName: name, Inputs: inputs})
}
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"})
addPerlasmTask(&out.Asm, &p, "-linux.S", []string{"elf", "-fPIC"})
addPerlasmTask(&out.Nasm, &p, "-win.asm", []string{"win32n", "-fPIC"})
}
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 glob(paths []string) ([]string, error) {
var ret []string
for _, path := range paths {
if !strings.ContainsRune(path, '*') {
ret = append(ret, path)
continue
}
matches, err := filepath.Glob(path)
if err != nil {
return nil, err
}
if len(matches) == 0 {
return nil, fmt.Errorf("glob matched no files: %q", path)
}
// Switch from Windows to POSIX paths.
for _, match := range matches {
ret = append(ret, strings.ReplaceAll(match, "\\", "/"))
}
}
slices.Sort(ret)
return ret, nil
}
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]build.Target, 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) {
fmt.Fprintf(b, "\n%s = [\n", name)
for _, v := range val {
fmt.Fprintf(b, " %q,\n", v)
}
fmt.Fprintf(b, "]\n")
}
func jsonTask(targets map[string]build.Target, dst string) Task {
return NewSimpleTask(dst, func() ([]byte, error) {
return json.MarshalIndent(targets, "", " ")
})
}
func soongTask(targets map[string]build.Target, 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]build.Target) []Task {
// TODO(crbug.com/boringssl/542): Generate the build files for the other
// types as well.
return []Task{
buildVariablesTask(targets, "gen/sources.bzl", "#", writeBazelVariable),
buildVariablesTask(targets, "gen/sources.cmake", "#", writeCMakeVariable),
buildVariablesTask(targets, "gen/sources.gni", "#", writeGNVariable),
jsonTask(targets, "gen/sources.json"),
}
}