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 15// pregenerate manages generated files in BoringSSL 16package main 17 18import ( 19 "bytes" 20 "encoding/json" 21 "errors" 22 "flag" 23 "fmt" 24 "os" 25 "path/filepath" 26 "runtime" 27 "slices" 28 "strings" 29 "sync" 30 31 "boringssl.googlesource.com/boringssl/util/build" 32) 33 34var ( 35 check = flag.Bool("check", false, "Check whether any files need to be updated, without actually updating them") 36 numWorkers = flag.Int("num-workers", runtime.NumCPU(), "Runs the given number of workers") 37 dryRun = flag.Bool("dry-run", false, "Skip actually writing any files") 38 perlPath = flag.String("perl", "perl", "Path to the perl command") 39 list = flag.Bool("list", false, "List all generated files, rather than actually run them") 40) 41 42func runTask(t Task) error { 43 expected, err := t.Run() 44 if err != nil { 45 return err 46 } 47 48 dst := t.Destination() 49 dstPath := filepath.FromSlash(dst) 50 if *check { 51 actual, err := os.ReadFile(dstPath) 52 if err != nil { 53 if os.IsNotExist(err) { 54 err = errors.New("missing file") 55 } 56 return err 57 } 58 59 if !bytes.Equal(expected, actual) { 60 return errors.New("file out of date") 61 } 62 return nil 63 } 64 65 if *dryRun { 66 fmt.Printf("Would write %d bytes to %q\n", len(expected), dst) 67 return nil 68 } 69 70 if err := os.MkdirAll(filepath.Dir(dstPath), 0777); err != nil { 71 return err 72 } 73 return os.WriteFile(dstPath, expected, 0666) 74} 75 76type taskError struct { 77 dst string 78 err error 79} 80 81func worker(taskChan <-chan Task, errorChan chan<- taskError, wg *sync.WaitGroup) { 82 defer wg.Done() 83 for t := range taskChan { 84 if err := runTask(t); err != nil { 85 errorChan <- taskError{t.Destination(), err} 86 } 87 } 88} 89 90func run() error { 91 if _, err := os.Stat("BUILDING.md"); err != nil { 92 return fmt.Errorf("must be run from BoringSSL source root") 93 } 94 95 buildJSON, err := os.ReadFile("build.json") 96 if err != nil { 97 return err 98 } 99 100 // Remove comments. For now, just do a very basic preprocessing step. If 101 // needed, we can switch to something well-defined like one of the many 102 // dozen different extended JSONs like JSON5. 103 lines := bytes.Split(buildJSON, []byte("\n")) 104 for i := range lines { 105 if idx := bytes.Index(lines[i], []byte("//")); idx >= 0 { 106 lines[i] = lines[i][:idx] 107 } 108 } 109 buildJSON = bytes.Join(lines, []byte("\n")) 110 111 var targetsIn map[string]InputTarget 112 if err := json.Unmarshal(buildJSON, &targetsIn); err != nil { 113 return fmt.Errorf("error decoding build config: %s", err) 114 } 115 116 var tasks []Task 117 targetsOut := make(map[string]build.Target) 118 for name, targetIn := range targetsIn { 119 targetOut, targetTasks, err := targetIn.Pregenerate(name) 120 if err != nil { 121 return err 122 } 123 targetsOut[name] = targetOut 124 tasks = append(tasks, targetTasks...) 125 } 126 127 tasks = append(tasks, MakeBuildFiles(targetsOut)...) 128 tasks = append(tasks, NewSimpleTask("gen/README.md", func() ([]byte, error) { 129 return []byte(readme), nil 130 })) 131 132 // Filter tasks by command-line argument. 133 if args := flag.Args(); len(args) != 0 { 134 var filtered []Task 135 for _, t := range tasks { 136 dst := t.Destination() 137 for _, arg := range args { 138 if strings.Contains(dst, arg) { 139 filtered = append(filtered, t) 140 break 141 } 142 } 143 } 144 tasks = filtered 145 } 146 147 if *list { 148 paths := make([]string, len(tasks)) 149 for i, t := range tasks { 150 paths[i] = t.Destination() 151 } 152 slices.Sort(paths) 153 for _, p := range paths { 154 fmt.Println(p) 155 } 156 return nil 157 } 158 159 // Schedule tasks in parallel. Perlasm benefits from running in parallel. The 160 // others likely do not, but it is simpler to parallelize them all. 161 var wg sync.WaitGroup 162 taskChan := make(chan Task, *numWorkers) 163 errorChan := make(chan taskError, *numWorkers) 164 for i := 0; i < *numWorkers; i++ { 165 wg.Add(1) 166 go worker(taskChan, errorChan, &wg) 167 } 168 169 go func() { 170 for _, t := range tasks { 171 taskChan <- t 172 } 173 close(taskChan) 174 wg.Wait() 175 close(errorChan) 176 }() 177 178 var failed bool 179 for err := range errorChan { 180 fmt.Fprintf(os.Stderr, "Error in file %q: %s\n", err.dst, err.err) 181 failed = true 182 } 183 if failed { 184 return errors.New("some files had errors") 185 } 186 return nil 187} 188 189func main() { 190 flag.Parse() 191 if err := run(); err != nil { 192 fmt.Fprintf(os.Stderr, "Error: %s\n", err) 193 os.Exit(1) 194 } 195} 196 197const readme = `# Pre-generated files 198 199This directory contains a number of pre-generated build artifacts. To simplify 200downstream builds, they are checked into the repository, rather than dynamically 201generated as part of the build. 202 203When developing on BoringSSL, if any inputs to these files are modified, callers 204must run the following command to update the generated files: 205 206 go run ./util/pregenerate 207 208To check that files are up-to-date without updating files, run: 209 210 go run ./util/pregenerate -check 211 212This is run on CI to ensure the generated files remain up-to-date. 213 214To speed up local iteration, the tool accepts additional arguments to filter the 215files generated. For example, if editing ` + "`aesni-x86_64.pl`" + `, this 216command will only update files with "aesni-x86_64" as a substring. 217 218 go run ./util/pregenerate aesni-x86_64 219 220For convenience, all files in this directory, including this README, are managed 221by the tool. This means the whole directory may be deleted and regenerated from 222scratch at any time. 223` 224