blob: 5e125df48354cc757a626d00c55e87bbf4595b9a [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package query
import (
"context"
"errors"
"flag"
"fmt"
"os"
"sort"
"github.com/maruel/subcommands"
"go.chromium.org/luci/common/cli"
"infra/build/siso/toolsupport/ninjautil"
)
const inputsUsage = `list all inputs required to rebuild given targets
$ siso query inputs -C <dir> <targets>
prints all inputs required to rebuild given targets.
`
// cmdInputs returns the Command for the `inputs` subcommand provided by this package.
func cmdInputs() *subcommands.Command {
return &subcommands.Command{
UsageLine: "inputs [-C <dir>] [<targets>...]",
ShortDesc: "list all inputs required to rebuild given targets",
LongDesc: inputsUsage,
CommandRun: func() subcommands.CommandRun {
c := &inputsRun{}
c.init()
return c
},
}
}
type inputsRun struct {
subcommands.CommandRunBase
dir string
fname string
}
func (c *inputsRun) init() {
c.Flags.StringVar(&c.dir, "C", ".", "ninja running directory to find build.ninja")
c.Flags.StringVar(&c.fname, "f", "build.ninja", "input build filename (relative to -C)")
}
func (c *inputsRun) Run(a subcommands.Application, args []string, env subcommands.Env) int {
ctx := cli.GetContext(a, c, env)
err := c.run(ctx, args)
if err != nil {
switch {
case errors.Is(err, flag.ErrHelp):
fmt.Fprintf(os.Stderr, "%v\n%s\n", err, inputsUsage)
default:
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
}
return 1
}
return 0
}
func (c *inputsRun) run(ctx context.Context, args []string) error {
state := ninjautil.NewState()
p := ninjautil.NewManifestParser(state)
err := os.Chdir(c.dir)
if err != nil {
return err
}
err = p.Load(ctx, c.fname)
if err != nil {
return err
}
nodes, err := state.Targets(args)
if err != nil {
return err
}
targets := make([]string, 0, len(nodes))
for _, n := range nodes {
targets = append(targets, n.Path())
}
g := &inputsGraph{
seen: make(map[string]bool),
}
isTargets := make(map[string]bool)
for _, t := range targets {
isTargets[t] = true
err := g.Traverse(ctx, state, t)
if err != nil {
return err
}
}
var inputs []string
for t := range g.seen {
if isTargets[t] {
continue
}
inputs = append(inputs, t)
}
sort.Strings(inputs)
for _, in := range inputs {
fmt.Println(in)
}
return nil
}
type inputsGraph struct {
seen map[string]bool
}
func (g *inputsGraph) Traverse(ctx context.Context, state *ninjautil.State, target string) error {
if g.seen[target] {
return nil
}
g.seen[target] = true
n, ok := state.LookupNode(target)
if !ok {
return fmt.Errorf("target not found: %q", target)
}
edge, ok := n.InEdge()
if !ok {
return nil
}
for _, in := range edge.Inputs() {
p := in.Path()
err := g.Traverse(ctx, state, p)
if err != nil {
return err
}
}
return nil
}