| // 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. |
| |
| // pregenerate manages generated files in BoringSSL |
| package main |
| |
| import ( |
| "bytes" |
| "encoding/json" |
| "errors" |
| "flag" |
| "fmt" |
| "os" |
| "path/filepath" |
| "runtime" |
| "slices" |
| "strings" |
| "sync" |
| |
| "boringssl.googlesource.com/boringssl/util/build" |
| ) |
| |
| var ( |
| check = flag.Bool("check", false, "Check whether any files need to be updated, without actually updating them") |
| numWorkers = flag.Int("num-workers", runtime.NumCPU(), "Runs the given number of workers") |
| dryRun = flag.Bool("dry-run", false, "Skip actually writing any files") |
| perlPath = flag.String("perl", "perl", "Path to the perl command") |
| list = flag.Bool("list", false, "List all generated files, rather than actually run them") |
| ) |
| |
| func runTask(t Task) error { |
| expected, err := t.Run() |
| if err != nil { |
| return err |
| } |
| |
| dst := t.Destination() |
| dstPath := filepath.FromSlash(dst) |
| if *check { |
| actual, err := os.ReadFile(dstPath) |
| if err != nil { |
| if os.IsNotExist(err) { |
| err = errors.New("missing file") |
| } |
| return err |
| } |
| |
| if !bytes.Equal(expected, actual) { |
| return errors.New("file out of date") |
| } |
| return nil |
| } |
| |
| if *dryRun { |
| fmt.Printf("Would write %d bytes to %q\n", len(expected), dst) |
| return nil |
| } |
| |
| if err := os.MkdirAll(filepath.Dir(dstPath), 0777); err != nil { |
| return err |
| } |
| return os.WriteFile(dstPath, expected, 0666) |
| } |
| |
| type taskError struct { |
| dst string |
| err error |
| } |
| |
| func worker(taskChan <-chan Task, errorChan chan<- taskError, wg *sync.WaitGroup) { |
| defer wg.Done() |
| for t := range taskChan { |
| if err := runTask(t); err != nil { |
| errorChan <- taskError{t.Destination(), err} |
| } |
| } |
| } |
| |
| func run() error { |
| if _, err := os.Stat("BUILDING.md"); err != nil { |
| return fmt.Errorf("must be run from BoringSSL source root") |
| } |
| |
| buildJSON, err := os.ReadFile("build.json") |
| if err != nil { |
| return err |
| } |
| |
| // Remove comments. For now, just do a very basic preprocessing step. If |
| // needed, we can switch to something well-defined like one of the many |
| // dozen different extended JSONs like JSON5. |
| lines := bytes.Split(buildJSON, []byte("\n")) |
| for i := range lines { |
| if idx := bytes.Index(lines[i], []byte("//")); idx >= 0 { |
| lines[i] = lines[i][:idx] |
| } |
| } |
| buildJSON = bytes.Join(lines, []byte("\n")) |
| |
| var targetsIn map[string]InputTarget |
| if err := json.Unmarshal(buildJSON, &targetsIn); err != nil { |
| return fmt.Errorf("error decoding build config: %s", err) |
| } |
| |
| var tasks []Task |
| targetsOut := make(map[string]build.Target) |
| for name, targetIn := range targetsIn { |
| targetOut, targetTasks, err := targetIn.Pregenerate(name) |
| if err != nil { |
| return err |
| } |
| targetsOut[name] = targetOut |
| tasks = append(tasks, targetTasks...) |
| } |
| |
| tasks = append(tasks, MakeBuildFiles(targetsOut)...) |
| tasks = append(tasks, NewSimpleTask("gen/README.md", func() ([]byte, error) { |
| return []byte(readme), nil |
| })) |
| |
| // Filter tasks by command-line argument. |
| if args := flag.Args(); len(args) != 0 { |
| var filtered []Task |
| for _, t := range tasks { |
| dst := t.Destination() |
| for _, arg := range args { |
| if strings.Contains(dst, arg) { |
| filtered = append(filtered, t) |
| break |
| } |
| } |
| } |
| tasks = filtered |
| } |
| |
| if *list { |
| paths := make([]string, len(tasks)) |
| for i, t := range tasks { |
| paths[i] = t.Destination() |
| } |
| slices.Sort(paths) |
| for _, p := range paths { |
| fmt.Println(p) |
| } |
| return nil |
| } |
| |
| // Schedule tasks in parallel. Perlasm benefits from running in parallel. The |
| // others likely do not, but it is simpler to parallelize them all. |
| var wg sync.WaitGroup |
| taskChan := make(chan Task, *numWorkers) |
| errorChan := make(chan taskError, *numWorkers) |
| for i := 0; i < *numWorkers; i++ { |
| wg.Add(1) |
| go worker(taskChan, errorChan, &wg) |
| } |
| |
| go func() { |
| for _, t := range tasks { |
| taskChan <- t |
| } |
| close(taskChan) |
| wg.Wait() |
| close(errorChan) |
| }() |
| |
| var failed bool |
| for err := range errorChan { |
| fmt.Fprintf(os.Stderr, "Error in file %q: %s\n", err.dst, err.err) |
| failed = true |
| } |
| if failed { |
| return errors.New("some files had errors") |
| } |
| return nil |
| } |
| |
| func main() { |
| flag.Parse() |
| if err := run(); err != nil { |
| fmt.Fprintf(os.Stderr, "Error: %s\n", err) |
| os.Exit(1) |
| } |
| } |
| |
| const readme = `# Pre-generated files |
| |
| This directory contains a number of pre-generated build artifacts. To simplify |
| downstream builds, they are checked into the repository, rather than dynamically |
| generated as part of the build. |
| |
| When developing on BoringSSL, if any inputs to these files are modified, callers |
| must run the following command to update the generated files: |
| |
| go run ./util/pregenerate |
| |
| To check that files are up-to-date without updating files, run: |
| |
| go run ./util/pregenerate -check |
| |
| This is run on CI to ensure the generated files remain up-to-date. |
| |
| To speed up local iteration, the tool accepts additional arguments to filter the |
| files generated. For example, if editing ` + "`aesni-x86_64.pl`" + `, this |
| command will only update files with "aesni-x86_64" as a substring. |
| |
| go run ./util/pregenerate aesni-x86_64 |
| |
| For convenience, all files in this directory, including this README, are managed |
| by the tool. This means the whole directory may be deleted and regenerated from |
| scratch at any time. |
| ` |