1// Copyright 2024 The BoringSSL Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// https://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 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.git/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.cc. 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 2024 The BoringSSL Authors\n", comment) 172 fmt.Fprintf(b, "%s\n", comment) 173 fmt.Fprintf(b, "%s Licensed under the Apache License, Version 2.0 (the \"License\");\n", comment) 174 fmt.Fprintf(b, "%s you may not use this file except in compliance with the License.\n", comment) 175 fmt.Fprintf(b, "%s You may obtain a copy of the License at\n", comment) 176 fmt.Fprintf(b, "%s\n", comment) 177 fmt.Fprintf(b, "%s https://www.apache.org/licenses/LICENSE-2.0\n", comment) 178 fmt.Fprintf(b, "%s\n", comment) 179 fmt.Fprintf(b, "%s Unless required by applicable law or agreed to in writing, software\n", comment) 180 fmt.Fprintf(b, "%s distributed under the License is distributed on an \"AS IS\" BASIS,\n", comment) 181 fmt.Fprintf(b, "%s WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", comment) 182 fmt.Fprintf(b, "%s See the License for the specific language governing permissions and\n", comment) 183 fmt.Fprintf(b, "%s limitations under the License.\n", comment) 184 fmt.Fprintf(b, "\n") 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 // Prefix the variable names to avoid collisions. make builds often use 238 // by inclusion, so variables may not be scoped. 239 fmt.Fprintf(b, "\nboringssl_%s := \\\n", name) 240 for i, v := range val { 241 if i == len(val)-1 { 242 fmt.Fprintf(b, " %s\n", v) 243 } else { 244 fmt.Fprintf(b, " %s \\\n", v) 245 } 246 } 247} 248 249func writeGNVariable(b *bytes.Buffer, name string, val []string) { 250 fmt.Fprintf(b, "\n%s = [\n", name) 251 for _, v := range val { 252 fmt.Fprintf(b, " %q,\n", v) 253 } 254 fmt.Fprintf(b, "]\n") 255} 256 257func jsonTask(targets map[string]build.Target, dst string) Task { 258 return NewSimpleTask(dst, func() ([]byte, error) { 259 return json.MarshalIndent(targets, "", " ") 260 }) 261} 262 263func soongTask(targets map[string]build.Target, dst string) Task { 264 return NewSimpleTask(dst, func() ([]byte, error) { 265 var b bytes.Buffer 266 writeHeader(&b, "//") 267 268 writeAttribute := func(indent, name string, val []string) { 269 fmt.Fprintf(&b, "%s%s: [\n", indent, name) 270 for _, v := range val { 271 fmt.Fprintf(&b, "%s %q,\n", indent, v) 272 } 273 fmt.Fprintf(&b, "%s],\n", indent) 274 275 } 276 277 for _, name := range sortedKeys(targets) { 278 target := targets[name] 279 fmt.Fprintf(&b, "\ncc_defaults {\n") 280 fmt.Fprintf(&b, " name: %q\n", "boringssl_"+name+"_sources") 281 if len(target.Srcs) != 0 { 282 writeAttribute(" ", "srcs", target.Srcs) 283 } 284 if len(target.Data) != 0 { 285 writeAttribute(" ", "data", target.Data) 286 } 287 if len(target.Asm) != 0 { 288 fmt.Fprintf(&b, " target: {\n") 289 // Only emit asm for Linux. On Windows, BoringSSL requires NASM, which is 290 // not available in AOSP. On Darwin, the assembly works fine, but it 291 // conflicts with Android's FIPS build. See b/294399371. 292 fmt.Fprintf(&b, " linux: {\n") 293 writeAttribute(" ", "srcs", target.Asm) 294 fmt.Fprintf(&b, " },\n") 295 fmt.Fprintf(&b, " darwin: {\n") 296 fmt.Fprintf(&b, " cflags: [\"-DOPENSSL_NO_ASM\"],\n") 297 fmt.Fprintf(&b, " },\n") 298 fmt.Fprintf(&b, " windows: {\n") 299 fmt.Fprintf(&b, " cflags: [\"-DOPENSSL_NO_ASM\"],\n") 300 fmt.Fprintf(&b, " },\n") 301 fmt.Fprintf(&b, " },\n") 302 } 303 fmt.Fprintf(&b, "},\n") 304 } 305 306 return b.Bytes(), nil 307 }) 308} 309 310func MakeBuildFiles(targets map[string]build.Target) []Task { 311 // TODO(crbug.com/boringssl/542): Generate the build files for the other 312 // types as well. 313 return []Task{ 314 buildVariablesTask(targets, "gen/sources.bzl", "#", writeBazelVariable), 315 buildVariablesTask(targets, "gen/sources.cmake", "#", writeCMakeVariable), 316 buildVariablesTask(targets, "gen/sources.gni", "#", writeGNVariable), 317 buildVariablesTask(targets, "gen/sources.mk", "#", writeMakeVariable), 318 jsonTask(targets, "gen/sources.json"), 319 } 320} 321