src/fetch/run.bend
ezx@1.9.0.0 · 124 lines · 0 definitions
1# fetch/run: the interpreter for `ez fetch`. It reads whether there is an
2# ez.toml and the lock's text into a World, with the directory they are in,
3# then loops: it asks the planner (fetch/plan.bend) what is still open,
4# answers each question, and adds the answers to the World, until nothing is
5# left to ask. Then it runs the planner's plan with the lock's effect runner
6# (`Run.exec.plan`), which lays each tree and exits 1 with the reason when
7# the plan refuses. A tree under BEND_LIB is read here as the lock reads one
8# (`Run.answer.lib`), a hub package is fetched as the lock fetches one
9# (`Run.answer`), and a checkout is made as `ez add` makes one
10# (`Run.answer.up` of `Up.Clone`), in a scratch directory outside the project
11# and BEND_LIB that is removed whether or not the clone worked. A name's file
12# under `BEND_LIB/names` is read as it is. So nothing is
13# written while the questions are answered, and a refused fetch leaves
14# BEND_LIB as it found it. It decides nothing; that it reads and executes
15# faithfully is EZ-TRUST-2.
16import Base
17import snap@1.3.0.0/main.bend as R
18import ezx@1.9.0.0/src/io/file.bend as F
19import ezx@1.9.0.0/src/lock/run.bend as Run
20import ezx@1.9.0.0/src/lock/world.bend as W
21import ezx@1.9.0.0/src/lock/up.bend as Up
22import ezx@1.9.0.0/src/fetch/world.bend as FW
23import ezx@1.9.0.0/src/fetch/plan.bend as FP
24
25# a tree's manifest, when there is one: the tree as it names it, and
26# otherwise a miss
27def laid.seen(got: Maybe<&2, String>, +dir: String) -> IO(W.Answer):
28 match got:
29 case None{}:
30 IO.pure(W.Answer, W.Miss{"ez: " ++ dir ++ " is not there"})
31 case Some{text}:
32 Run.answer.lib(dir, text)
33
34# the tree under BEND_LIB for a hash, as its manifest names it, or a miss
35# when there is none
36def laid(+lib: String, +hash: String) -> IO(W.Answer):
37 +dir = lib ++ "/" ++ hash
38 do IO<W.Answer>:
39 m : Maybe<&2, String> <- F.read(dir ++ "/manifest")
40 laid.seen(m, dir)
41
42# one question, answered
43def answer(+lib: String, ask: FW.Ask) -> IO(FW.Answer):
44 match ask:
45 case FW.Laid{hash}:
46 do IO<FW.Answer>:
47 a : W.Answer <- laid(lib, hash)
48 return FW.Read{a}
49 case FW.Git{_hash, q}:
50 do IO<FW.Answer>:
51 a : Up.Answer <- Run.answer.up(lib, q)
52 return FW.Said{a}
53 case FW.Hub{q}:
54 do IO<FW.Answer>:
55 a : W.Answer <- Run.answer(lib, q)
56 return FW.Read{a}
57 case FW.Named{nv}:
58 do IO<FW.Answer>:
59 m : Maybe<&2, String> <- F.read(lib ++ "/names/" ++ nv)
60 return FW.Held{m}
61
62# every question, answered
63def answer.all(+lib: String, asks: List<&2, FW.Ask>) -> IO(List<&2, FW.Reply>):
64 match asks:
65 case []:
66 IO.pure(List<&2, FW.Reply>, [])
67 case +h <> t:
68 do IO<List<&2, FW.Reply>>:
69 a : FW.Answer <- answer(lib, h)
70 rest : List<&2, FW.Reply> <- answer.all(lib, t)
71 return FW.Reply{h, a} <> rest
72
73# the World with more answers in it
74def more(world: FW.World, rs: List<&2, FW.Reply>) -> FW.World:
75 FW.World{here, ledger, lock, replies} = world
76 FW.World{here, ledger, lock, List.append(&2, FW.Reply, replies, rs)}
77
78# the planner's step: its questions answered and the loop gone round again,
79# or its plan run
80def loop.step(st: FP.Step, +lib: String, world: FW.World, go: FW.World -> IO(Unit)) -> IO(Unit):
81 match st:
82 case FP.Asking{asks}:
83 do IO<Unit>:
84 rs : List<&2, FW.Reply> <- answer.all(lib, asks)
85 go(more(world, rs))
86 case FP.Run{pl}:
87 Run.exec.plan(lib, pl)
88
89# the rounds the loop may take. A package asks at most twice, for its tree
90# under BEND_LIB and for its fetch, and a round asks for every package at
91# once, so this is only a guard against a planner that keeps asking, and
92# running out of it dies loudly.
93def rounds() -> Nat:
94 U32.to_nat(10)
95
96# the loop, under fuel. A package asks at most twice, for its tree under
97# BEND_LIB and for its fetch, and every round answers what it asked, so the
98# fuel is only a guard.
99def loop(fuel: Nat, +lib: String, +world: FW.World) -> IO(Unit):
100 match fuel:
101 case 0n:
102 IO.die(Unit, 1, "ez: ez fetch kept asking questions")
103 case 1n+f:
104 loop.step(FP.step(world), lib, world, w => loop(f, lib, w))
105
106# a file of the project, read
107def at(+here: String, +file: String) -> String:
108 here ++ "/" ++ file
109
110# BEND_LIB `lib` filled from the lock of the project in `here`, which is
111# where every relative path in the lock is read from. `ez tool` fills a
112# tool's cache from its checkout's lock this way.
113def fill(+here: String, +lib: String) -> IO(Unit):
114 do IO<Unit>:
115 led : Maybe<&2, String> <- F.read(at(here, FW.toml()))
116 lk : Maybe<&2, String> <- F.read(at(here, FW.lockfile()))
117 loop(rounds(), lib, FW.World{here, Maybe.is_some(&2, String, led), lk, []})
118
119# `ez fetch`: BEND_LIB filled from the lock of the project ez runs in
120def run() -> IO(Unit):
121 do IO<Unit>:
122 lib : String <- Run.bend.lib()
123 +pwd : String <- R.exec(["pwd"])
124 fill(String.trim(R.text(pwd)), lib)