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. 16 import Base 17 import 0x8e26a6f8c31689519e618374376ee3dd/main.bend as R 18 import ../io/file.bend as F 19 import ../lock/run.bend as Run 20 import ../lock/world.bend as W 21 import ../lock/up.bend as Up 22 import ./world.bend as FW 23 import ./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 27 def 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 36 def laid(+lib: String, +hash: String) -> IO(W.Answer): 37 +dir = lib ++ "/" ++ hash 38 do IO: 39 m : Maybe<&2, String> <- F.read(dir ++ "/manifest") 40 laid.seen(m, dir) 41 42 # one question, answered 43 def answer(+lib: String, ask: FW.Ask) -> IO(FW.Answer): 44 match ask: 45 case FW.Laid{hash}: 46 do IO: 47 a : W.Answer <- laid(lib, hash) 48 return FW.Read{a} 49 case FW.Git{_hash, q}: 50 do IO: 51 a : Up.Answer <- Run.answer.up(lib, q) 52 return FW.Said{a} 53 case FW.Hub{q}: 54 do IO: 55 a : W.Answer <- Run.answer(lib, q) 56 return FW.Read{a} 57 case FW.Named{nv}: 58 do IO: 59 m : Maybe<&2, String> <- F.read(lib ++ "/names/" ++ nv) 60 return FW.Held{m} 61 case FW.Tree{_hash, _entry}: 62 do IO: 63 l : W.Listing <- Run.read.listing() 64 return FW.Seen{l} 65 66 # every question, answered 67 def answer.all(+lib: String, asks: List<&2, FW.Ask>) -> IO(List<&2, FW.Reply>): 68 match asks: 69 case []: 70 IO.pure(List<&2, FW.Reply>, []) 71 case +h <> t: 72 do IO>: 73 a : FW.Answer <- answer(lib, h) 74 rest : List<&2, FW.Reply> <- answer.all(lib, t) 75 return FW.Reply{h, a} <> rest 76 77 # the World with more answers in it 78 def more(world: FW.World, rs: List<&2, FW.Reply>) -> FW.World: 79 FW.World{here, ledger, lock, replies} = world 80 FW.World{here, ledger, lock, List.append(&2, FW.Reply, replies, rs)} 81 82 # the planner's step: its questions answered and the loop gone round again, 83 # or its plan run 84 def loop.step(st: FP.Step, +lib: String, world: FW.World, go: FW.World -> IO(Unit)) -> IO(Unit): 85 match st: 86 case FP.Asking{asks}: 87 do IO: 88 rs : List<&2, FW.Reply> <- answer.all(lib, asks) 89 go(more(world, rs)) 90 case FP.Run{pl}: 91 Run.exec.plan(lib, pl) 92 93 # the rounds the loop may take. A package asks at most twice, for its tree 94 # under BEND_LIB and for its fetch, and a round asks for every package at 95 # once, so this is only a guard against a planner that keeps asking, and 96 # running out of it dies loudly. 97 def rounds() -> Nat: 98 U32.to_nat(10) 99 100 # the loop, under fuel. A package asks at most twice, for its tree under 101 # BEND_LIB and for its fetch, and every round answers what it asked, so the 102 # fuel is only a guard. 103 def loop(fuel: Nat, +lib: String, +world: FW.World) -> IO(Unit): 104 match fuel: 105 case 0n: 106 IO.die(Unit, 1, "ez: ez fetch kept asking questions") 107 case 1n+f: 108 loop.step(FP.step(world), lib, world, w => loop(f, lib, w)) 109 110 # a file of the project, read 111 def at(+here: String, +file: String) -> String: 112 here ++ "/" ++ file 113 114 # BEND_LIB `lib` filled from the lock of the project in `here`, which is 115 # where every relative path in the lock is read from. `ez tool` fills a 116 # tool's cache from its checkout's lock this way. 117 def fill(+here: String, +lib: String) -> IO(Unit): 118 do IO: 119 led : Maybe<&2, String> <- F.read(at(here, FW.toml())) 120 lk : Maybe<&2, String> <- F.read(at(here, FW.lockfile())) 121 loop(rounds(), lib, FW.World{here, Maybe.is_some(&2, String, led), lk, []}) 122 123 # `ez fetch`: BEND_LIB filled from the lock of the project ez runs in 124 def run() -> IO(Unit): 125 do IO: 126 lib : String <- Run.bend.lib() 127 +pwd : String <- R.exec(["pwd"]) 128 fill(String.trim(R.text(pwd)), lib)