src/doctor/run.bend

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