Skip to content

Commit 16a0f74

Browse files
committed
feat: add --describe flag + fix a bunch of bugs
- Switched to a custom gitignore parser, as the previous one was buggy. - Fixed and improved globbing. - Fixed some parsing bugs.
1 parent ba039ea commit 16a0f74

File tree

10 files changed

+232
-380
lines changed

10 files changed

+232
-380
lines changed

cmd/bit/main.go

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,37 +15,53 @@ import (
1515
"github.com/alecthomas/bit/parser"
1616
)
1717

18-
var cli struct {
18+
type CLI struct {
1919
engine.LogConfig
20-
File *os.File `short:"f" help:"Bitfile to load." required:"" default:"Bitfile"`
21-
Chdir kong.ChangeDirFlag `short:"C" help:"Change to directory before running." placeholder:"DIR"`
22-
Deps bool `xor:"command" help:"Print dependency graph in a make-compatible format."`
23-
Dot bool `xor:"command" help:"Print dependency graph as a .dot file."`
24-
List bool `short:"l" xor:"command" help:"List available targets."`
25-
Clean bool `short:"c" xor:"command" help:"Clean targets."`
26-
Target []string `arg:"" optional:"" help:"Target to run."`
20+
File *os.File `short:"f" help:"Bitfile to load." required:"" default:"Bitfile"`
21+
Chdir kong.ChangeDirFlag `short:"C" help:"Change to directory before running." placeholder:"DIR"`
22+
Timing bool `short:"t" help:"Print timing information."`
23+
Dot bool `xor:"command" help:"Print dependency graph as a .dot file."`
24+
List bool `short:"l" xor:"command" help:"List available targets."`
25+
Describe string `short:"D" xor:"command" help:"Describe an aspect of the Bit build. ${describe_help}" required:"" enum:"files,deps,targets,ignored" placeholder:"ASPECT"`
26+
Clean bool `short:"c" xor:"command" help:"Clean targets."`
27+
Target []string `arg:"" optional:"" help:"Target to run."`
2728
}
2829

30+
const description = `
31+
Bit - A simple yet powerful build tool
32+
`
33+
2934
func main() {
30-
kong.Parse(&cli)
35+
cli := &CLI{}
36+
kong.Parse(cli, kong.Description(description), kong.HelpOptions{
37+
FlagsLast: true,
38+
}, kong.Vars{
39+
"describe_help": `Where ASPECT is one of:
40+
files: list all files Bit has determined are inputs and outputs
41+
deps: show dependency graph
42+
targets: list all targets
43+
ignored: list all loaded ignore patterns (from .gitignore files)
44+
45+
`,
46+
})
3147
defer cli.File.Close()
3248
logger := engine.NewLogger(cli.LogConfig)
3349
bitfile, err := parser.Parse(cli.File.Name(), cli.File)
34-
reportError(logger, err)
50+
reportError(cli.File, logger, err)
3551
eng, err := engine.Compile(logger, bitfile)
36-
reportError(logger, err)
52+
reportError(cli.File, logger, err)
3753

3854
switch {
39-
case cli.List:
55+
case cli.List, cli.Describe == "targets":
4056
for _, target := range eng.Outputs() {
4157
fmt.Println(target)
4258
}
4359

4460
case cli.Clean:
4561
err = eng.Clean(cli.Target)
46-
reportError(logger, err)
62+
reportError(cli.File, logger, err)
4763

48-
case cli.Deps:
64+
case cli.Describe == "deps":
4965
deps := eng.Deps()
5066
for in, deps := range deps {
5167
w := len(in) + 1
@@ -61,6 +77,16 @@ func main() {
6177
fmt.Println()
6278
}
6379

80+
case cli.Describe == "files":
81+
for _, file := range eng.Files() {
82+
fmt.Println(file)
83+
}
84+
85+
case cli.Describe == "ignored":
86+
for _, file := range eng.Ignored() {
87+
fmt.Println(file)
88+
}
89+
6490
case cli.Dot:
6591
fmt.Println("digraph {")
6692
for in, deps := range eng.Deps() {
@@ -72,14 +98,14 @@ func main() {
7298

7399
default:
74100
err = eng.Build(cli.Target)
75-
reportError(logger, err)
101+
reportError(cli.File, logger, err)
76102
err = eng.Close()
77-
reportError(logger, err)
103+
reportError(cli.File, logger, err)
78104
}
79105

80106
}
81107

82-
func reportError(logger *engine.Logger, err error) {
108+
func reportError(file *os.File, logger *engine.Logger, err error) {
83109
if err == nil {
84110
return
85111
}
@@ -89,8 +115,8 @@ func reportError(logger *engine.Logger, err error) {
89115
os.Exit(1)
90116
}
91117

92-
_, _ = cli.File.Seek(0, 0)
93-
scanner := bufio.NewScanner(cli.File)
118+
_, _ = file.Seek(0, 0)
119+
scanner := bufio.NewScanner(file)
94120
line := 1
95121
pos := perr.Position()
96122
prefix := fmt.Sprintf("%s:%d:%d: ", filepath.Base(pos.Filename), pos.Line, pos.Column)

engine/engine.go

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,16 @@ func Compile(logger *Logger, bitfile *parser.Bitfile) (*Engine, error) {
107107
return engine, nil
108108
}
109109

110+
// Files lists all files that are referenced by the engine.
111+
func (e *Engine) Files() []string {
112+
return e.globber.Files()
113+
}
114+
115+
// Ignored returns all files that are ignored by the engine.
116+
func (e *Engine) Ignored() []string {
117+
return e.globber.Ignored()
118+
}
119+
110120
func (e *Engine) analyse(bitfile *parser.Bitfile) error {
111121
for _, entry := range bitfile.Entries {
112122
switch entry := entry.(type) {
@@ -124,6 +134,7 @@ func (e *Engine) analyse(bitfile *parser.Bitfile) error {
124134
target.inputs = &parser.RefList{Pos: entry.Pos}
125135
}
126136
if entry.Outputs == nil {
137+
fmt.Println(entry.Pos)
127138
target.outputs = &parser.RefList{Pos: entry.Pos}
128139
}
129140
logger := e.targetLogger(target)
@@ -141,7 +152,7 @@ func (e *Engine) analyse(bitfile *parser.Bitfile) error {
141152
target.build = directive
142153

143154
case "inputs", "outputs":
144-
refs, err := parser.ParseRefList(directive.Value.Body)
155+
refs, err := parser.ParseRefList(directive.Pos, directive.Value.Body)
145156
if err != nil {
146157
return participle.Errorf(directive.Value.Pos, "failed to parse %s: %s", directive.Command, err)
147158
}
@@ -367,15 +378,23 @@ func (e *Engine) Close() error {
367378
}
368379

369380
func (e *Engine) Build(outputs []string) error {
381+
return e.build(outputs, map[string]bool{})
382+
}
383+
384+
func (e *Engine) build(outputs []string, seen map[string]bool) error {
370385
if len(outputs) == 0 {
371386
outputs = e.Outputs()
372387
}
373388
for _, name := range outputs {
374-
var err error
375-
name, err = e.normalisePath(name)
389+
name, err := e.normalisePath(name) //nolint:govet
376390
if err != nil {
377391
return err
378392
}
393+
if seen[name] {
394+
continue
395+
}
396+
seen[name] = true
397+
379398
log := e.log.Scope(name)
380399

381400
target, err := e.getTarget(name)
@@ -389,15 +408,16 @@ func (e *Engine) Build(outputs []string) error {
389408
}
390409
continue
391410
}
392-
log.Tracef("Building.")
393411

394412
// Build dependencies.
395413
for _, input := range target.inputs.Refs {
396-
if err := e.Build([]string{input.Text}); err != nil {
414+
if err := e.build([]string{input.Text}, seen); err != nil {
397415
return participle.Wrapf(input.Pos, err, "build failed")
398416
}
399417
}
400418

419+
log.Tracef("Building.")
420+
401421
// Build target.
402422
err = target.buildFunc(log, target)
403423
if err != nil {
@@ -437,12 +457,13 @@ func (e *Engine) defaultBuildFunc(log *Logger, target *Target) error {
437457
// A function used to compute a hash of an output.
438458
type outputRefHasher func(target *Target, ref *parser.Ref) (hasher, error)
439459

440-
func (e *Engine) recursivelyComputeHash(target *Target, refHasher outputRefHasher, seen map[string]*parser.Ref, forEach func(*Target, hasher)) (hasher, error) {
460+
func (e *Engine) recursivelyComputeHash(target *Target, refHasher outputRefHasher, seen map[string]bool, forEach func(*Target, hasher)) (hasher, error) {
441461
h := newHasher()
442462
for _, input := range target.inputs.Refs {
443-
// if orig, ok := seen[input.Text]; ok {
444-
// return 0, participle.Errorf(input.Pos, "circular dependency %s", orig.Pos)
445-
// }
463+
if _, ok := seen[input.Text]; ok {
464+
continue
465+
}
466+
seen[input.Text] = true
446467
inputTarget, err := e.getTarget(input.Text)
447468
if err != nil {
448469
return 0, participle.Wrapf(input.Pos, err, "couldn't find matching input")
@@ -454,7 +475,6 @@ func (e *Engine) recursivelyComputeHash(target *Target, refHasher outputRefHashe
454475
h.update(subh)
455476
}
456477
for _, output := range target.outputs.Refs {
457-
seen[output.Text] = output
458478
rh, err := refHasher(target, output)
459479
if err != nil {
460480
return 0, participle.Wrapf(output.Pos, err, "hash failed")
@@ -534,12 +554,11 @@ func (e *Engine) evaluate() error {
534554
return err
535555
}
536556

537-
subRefs, err := parser.ParseRefList(evaluated)
557+
subRefs, err := parser.ParseRefList(ref.Pos, evaluated)
538558
if err != nil {
539559
return participle.Errorf(ref.Pos, "failed to parse output %q: %s", evaluated, err)
540560
}
541561
for _, subRef := range subRefs.Refs {
542-
subRef.Pos = ref.Pos
543562
subRef.Text, err = e.normalisePath(subRef.Text)
544563
if err != nil {
545564
return participle.Errorf(subRef.Pos, "%s", err)
@@ -581,7 +600,7 @@ func (e *Engine) evaluate() error {
581600
if err != nil {
582601
return err
583602
}
584-
innerRefs, err := parser.ParseRefList(evaluated)
603+
innerRefs, err := parser.ParseRefList(ref.Pos, evaluated)
585604
if err != nil {
586605
return participle.Errorf(ref.Pos, "failed to parse input %q: %s", evaluated, err)
587606
}
@@ -615,13 +634,13 @@ func (e *Engine) evaluate() error {
615634
// Second pass - restore hashes from the DB.
616635
for _, target := range e.targets {
617636
logger := e.targetLogger(target)
618-
_, err := e.recursivelyComputeHash(target, e.dbRefHasher, map[string]*parser.Ref{}, func(target *Target, h hasher) {
637+
_, err := e.recursivelyComputeHash(target, e.dbRefHasher, map[string]bool{}, func(target *Target, h hasher) {
619638
target.storedHash = h
620639
})
621640
if err != nil && !errors.Is(err, os.ErrNotExist) {
622641
return err
623642
}
624-
_, err = e.recursivelyComputeHash(target, e.realRefHasher, map[string]*parser.Ref{}, func(target *Target, h hasher) {
643+
_, err = e.recursivelyComputeHash(target, e.realRefHasher, map[string]bool{}, func(target *Target, h hasher) {
625644
target.realHash = h
626645
})
627646
if err != nil && !errors.Is(err, os.ErrNotExist) {

0 commit comments

Comments
 (0)