| // Copyright 2024 The BoringSSL Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package main |
| |
| import ( |
| "bytes" |
| "errors" |
| "fmt" |
| "os" |
| "os/exec" |
| "path" |
| "path/filepath" |
| ) |
| |
| // TaskSkipped indicates the task has been skipped. |
| var TaskSkipped = errors.New("task skipped") |
| |
| // Task is a task the pregenerate system can perform. |
| type Task struct { |
| // Kind is the kind/type of the task in human readable form. |
| Kind string |
| |
| // Destination is the destination path for this task, using forward |
| // slashes and relative to the source directory. That is, use the "path" |
| // package, not "path/filepath". |
| Destination string |
| |
| // Dependencies are the list of tasks this task depends on. |
| Dependencies []*Task |
| |
| // Func is the function the task performs when executed. It only runs once all `Dependencies` have finished. |
| RunFunc func() ([]byte, error) |
| |
| // finishedC gets closed when the task is done. |
| finishedC chan struct{} |
| |
| // err contains the task's status when done. |
| err error |
| } |
| |
| // String returns a human readable name for a task; just using the destination path for now. |
| func (t *Task) String() string { |
| return t.Destination |
| } |
| |
| // Prepare must be called on a task before it can be used. |
| func (t *Task) Prepare() *Task { |
| if t.finishedC == nil { |
| t.finishedC = make(chan struct{}) |
| } |
| return t |
| } |
| |
| // runInternal performs the task's job, not accounting to only run once. |
| func (t *Task) runInternal() (out []byte, err error) { |
| defer func() { |
| if p := recover(); p != nil { |
| err = fmt.Errorf("panic caught: %v", p) |
| } |
| }() |
| for _, dep := range t.Dependencies { |
| err := dep.wait() |
| if err != nil { |
| if errors.Is(err, TaskSkipped) { |
| logV.Printf("task %q dependency %q skipped - carrying on with previously saved data: %v", t, dep, err) |
| continue |
| } |
| return nil, fmt.Errorf("task %q dependency %q unfulfilled: %w", t, dep, err) |
| } |
| } |
| return t.RunFunc() |
| } |
| |
| // Run runs the task, and closes it - unless the task has already been closed. |
| // |
| // Must only be called once, and not concurrently with any `Close()` calls. |
| func (t *Task) Run() ([]byte, error) { |
| select { |
| case <-t.finishedC: |
| return nil, fmt.Errorf("task already closed: %w", t.err) |
| default: |
| } |
| out, err := t.runInternal() |
| t.Close(err) |
| return out, err |
| } |
| |
| // wait waits for the task to finish, and returns its status. |
| func (t *Task) wait() error { |
| <-t.finishedC |
| return t.err |
| } |
| |
| // Close marks the task as done with the given status. |
| func (t *Task) Close(err error) { |
| t.err = err |
| close(t.finishedC) |
| } |
| |
| // NewSimpleTask creates a new task based on a lambda for what it does. |
| func NewSimpleTask(kind, dst string, runFunc func() ([]byte, error), dependencies ...*Task) *Task { |
| return (&Task{ |
| Kind: kind, |
| Destination: dst, |
| Dependencies: dependencies, |
| RunFunc: runFunc, |
| }).Prepare() |
| } |
| |
| // NewPerlasmTask creates a new task that runs perlasm. |
| func NewPerlasmTask(dst, src string, perlasmArgs []string) *Task { |
| return NewSimpleTask("perlasm", dst, func() (data []byte, err error) { |
| if *perlPath == "" { |
| return nil, fmt.Errorf("%w: perl has been disabled by flag", TaskSkipped) |
| } |
| |
| defer func() { |
| if err != nil { |
| err = fmt.Errorf("%w; note that this step can be turned off by passing -perl=", err) |
| } |
| }() |
| |
| base := path.Base(dst) |
| out, err := os.CreateTemp("", "*."+base) |
| if err != nil { |
| return nil, err |
| } |
| defer os.Remove(out.Name()) |
| |
| args := make([]string, 0, 2+len(perlasmArgs)) |
| args = append(args, filepath.FromSlash(src)) |
| args = append(args, perlasmArgs...) |
| args = append(args, out.Name()) |
| cmd := exec.Command(*perlPath, args...) |
| cmd.Stderr = os.Stderr |
| cmd.Stdout = os.Stdout |
| if err := cmd.Run(); err != nil { |
| return nil, err |
| } |
| |
| data, err = os.ReadFile(out.Name()) |
| if err != nil { |
| return nil, err |
| } |
| |
| // On Windows, perl emits CRLF line endings. Normalize this so that the tool |
| // can be run on Windows too. |
| data = bytes.ReplaceAll(data, []byte("\r\n"), []byte("\n")) |
| return data, nil |
| }) |
| } |