| // Copyright 2026 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package build |
| |
| import ( |
| "context" |
| "errors" |
| "io/fs" |
| "os" |
| "path/filepath" |
| "strings" |
| |
| log "github.com/golang/glog" |
| |
| "go.chromium.org/build/siso/execute" |
| "go.chromium.org/build/siso/execute/localexec" |
| "go.chromium.org/build/siso/o11y/clog" |
| "go.chromium.org/build/siso/path" |
| ) |
| |
| func (b *Builder) tapCanonicalizeCmd(ctx context.Context, cmd *execute.Cmd) error { |
| res, _ := cmd.ActionResult() |
| tapData, tapped := localexec.ExtractTapResult(res) |
| if !tapped { |
| return errors.New("no tap data") |
| } |
| var ninputs, noutputs int |
| // ignore out of workspace root |
| // TODO: use with input root absolute path? |
| // TODO: just use detected inputs? |
| seen := make(map[path.Path]bool) |
| for _, input := range cmd.AllInputs() { |
| seen[input] = true |
| ninputs++ |
| } |
| // os.TempDir() is $TMPDIR or /tmp. |
| ignores := map[path.Path]struct{}{path.New(os.TempDir()): {}} |
| ignored := 0 |
| if len(cmd.Env) > 0 { |
| for _, env := range cmd.Env { |
| tmpdir, ok := strings.CutPrefix(env, "TMPDIR=") |
| if ok { |
| if !filepath.IsAbs(tmpdir) { |
| clog.Warningf(ctx, "ignore TMPDIR: not absolute path: %q", tmpdir) |
| continue |
| } |
| ignores[path.New(tmpdir)] = struct{}{} |
| } |
| } |
| } |
| shouldIgnore := func(op string, p path.Path) bool { |
| for ignore := range ignores { |
| if p.HasPrefix(ignore) { |
| if log.V(1) { |
| clog.Infof(ctx, "ignore %s %q in %q", op, p, ignore) |
| } |
| ignored++ |
| return true |
| } |
| } |
| return false |
| } |
| |
| for _, input := range tapData.Reads { |
| if shouldIgnore("reads", path.New(input)) { |
| continue |
| } |
| rel, err := filepath.Rel(b.path.WorkspaceRoot, input) |
| if err != nil { |
| clog.Warningf(ctx, "reads relpath %q: %v", input, err) |
| continue |
| } |
| if !filepath.IsLocal(rel) { |
| continue |
| } |
| relPath := path.New(rel) |
| _, err = b.hashFS.Stat(ctx, b.path.WorkspaceRoot, relPath) |
| if errors.Is(err, fs.ErrNotExist) { |
| continue |
| } |
| if seen[relPath] { |
| continue |
| } |
| seen[relPath] = true |
| cmd.Inputs = append(cmd.Inputs, relPath) |
| } |
| clear(seen) |
| // need to use both original outputs and detected outputs. |
| // it might not detect output for restat action. |
| // it might detect unspecified outputs. |
| for _, output := range cmd.AllOutputs() { |
| seen[output] = true |
| noutputs++ |
| } |
| for _, output := range tapData.Writes { |
| if shouldIgnore("writes", path.New(output)) { |
| continue |
| } |
| rel, err := filepath.Rel(b.path.WorkspaceRoot, output) |
| if err != nil { |
| clog.Warningf(ctx, "writes relpath %q: %v", output, err) |
| continue |
| } |
| if !filepath.IsLocal(rel) { |
| continue |
| } |
| relPath := path.New(rel) |
| fi, err := b.hashFS.Stat(ctx, b.path.WorkspaceRoot, relPath) |
| if err != nil { |
| continue |
| } |
| if fi.IsDir() { |
| // don't include output directories |
| // as it would forget all entries in the directory |
| // by hashfs Update. |
| continue |
| } |
| if seen[relPath] { |
| continue |
| } |
| seen[relPath] = true |
| cmd.Outputs = append(cmd.Outputs, relPath) |
| } |
| for _, del := range tapData.Deletes { |
| rel, err := filepath.Rel(b.path.WorkspaceRoot, del) |
| if err != nil { |
| clog.Warningf(ctx, "deletes relpath %q: %v", del, err) |
| continue |
| } |
| if !filepath.IsLocal(rel) { |
| continue |
| } |
| relPath := path.New(rel) |
| _, err = b.hashFS.Stat(ctx, b.path.WorkspaceRoot, relPath) |
| if errors.Is(err, fs.ErrNotExist) { |
| continue |
| } |
| b.hashFS.Forget(ctx, b.path.WorkspaceRoot, []path.Path{relPath}) |
| clog.Infof(ctx, "delete %q", relPath) |
| } |
| |
| // now tap results are applied to cmd, so we can consider |
| // this cmd is pure, thus cacheable. |
| cmd.Pure = true |
| clog.Infof(ctx, "tap canonicalized inputs=%d->%d outputs=%d->%d ignored=%d", ninputs, len(cmd.Inputs), noutputs, len(cmd.Outputs), ignored) |
| return nil |
| } |