1 # pub/run: the interpreter for `ez publish`. It reads the directory the 2 # project is in and the ledger's text into a World, then loops: it asks the 3 # planner (pub/plan.bend) what is still open, answers each question, and adds 4 # the answers to the World, until nothing is left to ask. Then it runs the 5 # planner's plan with the lock's effect runner (`Run.exec.plan`), which prints 6 # what a publish that agreed says, or exits 1 with the reason. 7 # 8 # What git says of the project and a file's text are answered as the tool 9 # commands answer them (`ToolRun.answer`). What git tracks is `git ls-files 10 # -v` in the project, with paths written as they are rather than quoted. The 11 # upload is `bend --publish`, followed by the `@` the 12 # planner put in the question when the ledger names one, with the project's 13 # packages on BEND_LIB, the way every other `bend` ez starts has them; the 14 # planner asks for it last, once every other check has passed. Just before it 15 # runs, the line the question carries, the hub description, goes to stderr, 16 # where bend's own notices go, so stdout stays the hash and the import line. 17 # Before that, the lock is read as a file, and a url under the hub is asked 18 # for with the lock's own GET (`Web.get`), whose answer is a status line and 19 # the text: whether the hub answered 200, and the body or why not. 20 # It decides nothing; that it reads and executes faithfully is EZ-TRUST-2. 21 import Base 22 import 0x8e26a6f8c31689519e618374376ee3dd/main.bend as R 23 import ../io/file.bend as F 24 import ../lock/run.bend as Run 25 import ../tool/run.bend as ToolRun 26 import ../tool/world.bend as TW 27 import ../hub/hub.bend as Web 28 import ../hub/get.bend as Net 29 import ../share/env.bend as Env 30 import ./world.bend as PW 31 import ./plan.bend as PP 32 import ../ws/run.bend as WsRun 33 34 # what a program printed, and whether it exited 0 35 def ran(+out: String) -> PW.Answer: 36 PW.Ran{R.ok(out), R.text(out)} 37 38 # one question, answered 39 def answer(+lib: String, ask: PW.Ask) -> IO(PW.Answer): 40 match ask: 41 case PW.Tool{q}: 42 do IO: 43 a : TW.Answer <- ToolRun.answer(q) 44 return PW.Told{a} 45 case PW.Tracked{}: 46 do IO: 47 +out : String <- R.exec(["git", "-c", "core.quotePath=false", "ls-files", "-v"]) 48 return ran(out) 49 case PW.Upload{+entry, +named, says}: 50 do IO: 51 IO.print_err(says) 52 +out : String <- R.exec(Env.line(lib, PP.upload.line(entry, named))) 53 return ran(out) 54 case PW.Lock{}: 55 do IO: 56 at : String <- WsRun.lock.path() 57 m : Maybe<&2, String> <- F.read(at) 58 return PW.Told{TW.Plain{m}} 59 case PW.Hub{+url}: 60 do IO: 61 +out : String <- Net.get(url) 62 return ran(out) 63 64 # every question, answered 65 def answer.all(+lib: String, asks: List<&2, PW.Ask>) -> IO(List<&2, PW.Reply>): 66 match asks: 67 case []: 68 IO.pure(List<&2, PW.Reply>, []) 69 case +h <> t: 70 do IO>: 71 a : PW.Answer <- answer(lib, h) 72 rest : List<&2, PW.Reply> <- answer.all(lib, t) 73 return PW.Reply{h, a} <> rest 74 75 # the World with more answers in it 76 def more(world: PW.World, rs: List<&2, PW.Reply>) -> PW.World: 77 PW.World{here, ledger, replies} = world 78 PW.World{here, ledger, List.append(&2, PW.Reply, replies, rs)} 79 80 # the planner's step: its questions answered and the loop gone round again, 81 # or its plan run 82 def loop.step(st: PP.Step, +lib: String, world: PW.World, go: PW.World -> IO(Unit)) -> IO(Unit): 83 match st: 84 case PP.Asking{asks}: 85 do IO: 86 rs : List<&2, PW.Reply> <- answer.all(lib, asks) 87 go(more(world, rs)) 88 case PP.Run{pl}: 89 Run.exec.plan(lib, pl) 90 91 # the rounds the loop may take. The walk asks for one file a round and cannot 92 # know how many the package holds until it has read them, so this is a guard 93 # against a planner that keeps asking, not a limit on a package: running out 94 # of it dies loudly. 95 def rounds() -> Nat: 96 U32.to_nat(100000) 97 98 # the loop, under fuel. The walk asks for one file a round, so the fuel is a 99 # guard against a planner that keeps asking, not a limit on a package. 100 def loop(fuel: Nat, +lib: String, +world: PW.World) -> IO(Unit): 101 match fuel: 102 case 0n: 103 IO.die(Unit, 1, "ez: ez publish kept asking questions") 104 case 1n+f: 105 loop.step(PP.step(world), lib, world, w => loop(f, lib, w)) 106 107 # `ez publish`: the package the project's ledger names, checked, sent, and 108 # the hash bend answered checked against ez's own 109 def run() -> IO(Unit): 110 do IO: 111 lib : String <- Env.lib() 112 +pwd : String <- R.exec(["pwd"]) 113 led : Maybe<&2, String> <- F.read(PW.toml()) 114 loop(rounds(), lib, PW.World{String.trim(R.text(pwd)), led, []})