src/doctor/run.bend

ezx@1.11.0.0 · 145 lines · 13 definitions

1# doctor/run: the interpreter for `ez doctor`. It reads `$CC`, `$BEND_LIB`,
2# the ledger's text and the lock's text into a World, then loops: it asks
3# the planner (doctor/plan.bend) what is still open, answers each question,
4# and adds the answers to the World, until nothing is left to ask. Then it
5# runs the planner's plan with the lock's effect runner (`Run.exec.plan`),
6# which prints each line and exits 1 when the plan fails the command.
7#
8# A version question runs the program with `--version`, except bend, which
9# is asked through run/bend since it has answered the question two ways over
10# its versions. The library is listed with `ls`, the sources are read as
11# `ez lock` reads them (`Run.read.listing`), and a package's tree is read
12# from under BEND_LIB as `ez lock` reads one there (`Run.disk.all`), and
13# never fetched, and a name's file under `BEND_LIB/names` is read as it
14# is. None of these writes anything, and the plan writes nothing either. It
15# decides nothing; that it reads and executes faithfully is EZ-TRUST-2.
16import Base
17import snap@1.4.0.0/main.bend as R
18import ezx@1.11.0.0/src/run/bend.bend as Bend
19import ezx@1.11.0.0/src/io/file.bend as F
20import ezx@1.11.0.0/src/share/args.bend as Args
21import ezx@1.11.0.0/src/lock/run.bend as Run
22import ezx@1.11.0.0/src/lock/world.bend as W
23import ezx@1.11.0.0/src/lock/lock.bend as L
24import ezx@1.11.0.0/src/doctor/world.bend as DW
25import ezx@1.11.0.0/src/doctor/plan.bend as DP
26import ezx@1.11.0.0/src/ws/run.bend as WsRun
27import ezx@1.11.0.0/src/ws/doctor.bend as WsDoc
28
29# how a program is asked for its version, once it is known whether this is
30# bend. Everything else answers the flag.
31def probe.ask(is_bend: Bool, name: String) -> IO(String):
32  match is_bend:
33    case True{}:
34      Bend.version()
35    case False{}:
36      R.exec([name, "--version"])
37
38# a tree under BEND_LIB, as it is: its manifest and every file it names, read
39# from beside it. A file that is not there reads as "", which does not hash
40# to its sum.
41def tree.read(+dir: String, +manifest: String) -> IO(DW.Answer):
42  do IO<DW.Answer>:
43    ss : List<&2, String> <- Run.disk.all(dir, L.manifest.files(String.lines(manifest)))
44    return DW.Laid{manifest, ss}
45
46# a tree whose manifest was looked for; none there is none at all
47def tree.seen(got: Maybe<&2, String>, +dir: String) -> IO(DW.Answer):
48  match got:
49    case Some{text}:
50      tree.read(dir, text)
51    case None{}:
52      IO.pure(DW.Answer, DW.Away{})
53
54# a package's tree under the library directory
55def tree(+dir: String) -> IO(DW.Answer):
56  do IO<DW.Answer>:
57    m : Maybe<&2, String> <- F.read(dir ++ "/manifest")
58    tree.seen(m, dir)
59
60# one question, answered. `lib` is the library directory the planner names.
61def answer(+lib: String, ask: DW.Ask) -> IO(DW.Answer):
62  match ask:
63    case DW.Probe{+name}:
64      do IO<DW.Answer>:
65        +out : String <- probe.ask(String.eq(name, "bend"), name)
66        return DW.Printed{R.ok(out), R.text(out)}
67    case DW.Lib{dir}:
68      do IO<DW.Answer>:
69        +out : String <- R.exec(["ls", dir])
70        return DW.Printed{R.ok(out), R.text(out)}
71    case DW.Sources{}:
72      do IO<DW.Answer>:
73        l : W.Listing <- Run.read.listing()
74        return DW.Listed{l}
75    case DW.Tree{a}:
76      tree(lib ++ "/" ++ W.ask.hash(a))
77    case DW.Name{nv}:
78      do IO<DW.Answer>:
79        m : Maybe<&2, String> <- F.read(lib ++ "/names/" ++ nv)
80        return DW.Held{m}
81
82# every question, answered
83def answer.all(+lib: String, asks: List<&2, DW.Ask>) -> IO(List<&2, DW.Reply>):
84  match asks:
85    case []:
86      IO.pure(List<&2, DW.Reply>, [])
87    case +h <> t:
88      do IO<List<&2, DW.Reply>>:
89        a : DW.Answer <- answer(lib, h)
90        rest : List<&2, DW.Reply> <- answer.all(lib, t)
91        return DW.Reply{h, a} <> rest
92
93# the World with more answers in it
94def more(world: DW.World, rs: List<&2, DW.Reply>) -> DW.World:
95  DW.World{cc, lib, ledger, lock, replies} = world
96  DW.World{cc, lib, ledger, lock, List.append(&2, DW.Reply, replies, rs)}
97
98# the planner's step: its questions answered and the loop gone round again,
99# or its plan run
100def loop.step(st: DP.Step, +lib: String, world: DW.World, go: DW.World -> IO(Unit)) -> IO(Unit):
101  match st:
102    case DP.Asking{asks}:
103      do IO<Unit>:
104        rs : List<&2, DW.Reply> <- answer.all(lib, asks)
105        go(more(world, rs))
106    case DP.Run{pl}:
107      Run.exec.plan("", pl)
108
109# the rounds the loop may take. The lock check's trees follow the sources
110# and each other, and the packages they reach are not known until BEND_LIB
111# has answered, so this is a guard against a planner that keeps asking, not
112# a limit on a project: running out of it dies loudly.
113def rounds() -> Nat:
114  U32.to_nat(100000)
115
116# the loop, under fuel. Every round adds at least one answer, since the
117# planner never asks what the World answers already, and the lock check asks
118# about each package at most once.
119def loop(fuel: Nat, +lib: String, +back: WsDoc.Back, +world: DW.World) -> IO(Unit):
120  match fuel:
121    case 0n:
122      IO.die(Unit, 1, "ez: ez doctor kept asking questions")
123    case 1n+f:
124      loop.step(WsDoc.step(back, world), lib, world, w => loop(f, lib, back, w))
125
126# a variable's text, "" when it is unset
127def var(name: String) -> IO(String):
128  do IO<String>:
129    r : Result<&1, &1, U32 & String, String> <- IO.get_env(name)
130    return Args.text_of(r)
131
132# the loop, on the ledger the doctor judges
133def run.doctored(dd: WsDoc.Doctored, +cc: String, +lib: String, lk: Maybe<&2, String>) -> IO(Unit):
134  WsDoc.Doctored{led, back} = dd
135  loop(rounds(), DP.lib.at(lib), back, DW.World{cc, lib, led, lk, []})
136
137# `ez doctor`: the toolchain and the project, reported on
138def run() -> IO(Unit):
139  do IO<Unit>:
140    cc : String <- var("CC")
141    +lib : String <- var("BEND_LIB")
142    own : Maybe<&2, String> <- F.read(DW.toml())
143    dd : WsDoc.Doctored <- WsRun.doctor.ledger(own)
144    lk : Maybe<&2, String> <- F.read(DW.lockfile())
145    run.doctored(dd, cc, lib, lk)