src/ez/cmd.bend
ezx@1.9.0.0 · 183 lines · 0 definitions
1# ez/cmd: what each subcommand does. Everything that touches the network or the
2# file system goes through a process effect, because bend links no TLS and
3# Base has neither mkdir nor readdir: snap's `exec` for a program whose answer
4# ez reads, and `share/pass.bend` for the one `ez run` hands the terminal to.
5import Base
6import snap@1.3.0.0/main.bend as R
7import ezx@1.9.0.0/src/io/file.bend as F
8import ezx@1.9.0.0/src/lock/run.bend as Run
9import ezx@1.9.0.0/src/lock/plan.bend as P
10import ezx@1.9.0.0/src/share/env.bend as Env
11import ezx@1.9.0.0/src/share/exec.bend as Exec
12import ezx@1.9.0.0/src/share/cap.bend as Cap
13import ezx@1.9.0.0/src/share/args.bend as Args
14import ezx@1.9.0.0/src/ez/ends.bend as E
15import ezx@1.9.0.0/src/ez/start.bend as S
16import ezx@1.9.0.0/src/share/pass.bend as Pass
17import ezx@1.9.0.0/src/init/run.bend as InitRun
18import ezx@1.9.0.0/src/remove/run.bend as RemoveRun
19import ezx@1.9.0.0/src/add/run.bend as AddRun
20import ezx@1.9.0.0/src/fetch/run.bend as FetchRun
21import ezx@1.9.0.0/src/pub/run.bend as PubRun
22
23# the ledger's path, which is fixed
24def toml() -> String:
25 "ez.toml"
26
27# what `ez check`, `ez build` or `ez run` does, decided from ez.toml as it is
28# on disk (`S.start`): a missing or unreadable ledger is refused before
29# anything is started or made
30def started(+ask: S.Ask) -> IO(S.Start):
31 do IO<S.Start>:
32 t : Maybe<&2, String> <- F.read(toml())
33 return S.start(ask, t)
34
35# a refusal said, and the command ended on it with exit 1
36def refused(+why: String) -> IO(Unit):
37 Run.end(P.Refused{why})
38
39# a build's output, printed with a word in front of it for a run the kernel
40# ended rather than the program, and the command ended on it (`E.ran`): a
41# failed build must fail the command, or a script around it learns nothing.
42# bend prints that all terms check before it emits any C, so a build killed at
43# the cap is otherwise indistinguishable from one that worked and wrote
44# nothing.
45def report.capped(+why: String, +out: String) -> IO(Unit):
46 do IO<Unit>:
47 IO.write(why ++ R.text(out))
48 Run.end(E.ran(out))
49
50# `ez check` and `ez run` are not capped, and `ez build` is. The wall
51# `share/cap.bend` exists for is bend's C backend: emitting the C for a large
52# program peaked at 18.8 GB here and an uncapped one reached 35 GB. `ez check`
53# emits JS and `ez run` interprets, so neither reaches that backend; and what
54# `ez run` spends after that is the user's own program spending it, which is
55# not ez's to ration.
56#
57# Whether a check passed is `E.check.passed`: bend exited 0, or said only
58# that the entry has no main.
59
60# a passing check says so, since bend says nothing when it is happy, and a
61# failing one shows what bend said
62def check.said(passed: Bool, entry: String, text: String) -> IO(Unit):
63 match passed:
64 case True{}:
65 IO.print("ok " ++ entry)
66 case False{}:
67 IO.write(text)
68
69# the entry checked, and never run, once `.ez` is there for its JS. The
70# entry is the word after `bend` on the line.
71def check.go(start: S.Start) -> IO(Unit):
72 match start:
73 case S.Refused{why}:
74 refused(why)
75 case S.Starts{+line}:
76 do IO<Unit>:
77 Exec.make(Env.dirs("check", ""))
78 +out : String <- Exec.exec(line)
79 check.said(E.check.passed(out), Args.at(line, 1n), R.text(out))
80 Run.end(E.checked(out))
81
82# `ez check`
83def check() -> IO(Unit):
84 do IO<Unit>:
85 start : S.Start <- started(S.Check{})
86 check.go(start)
87
88# a native binary, named after the package (`S.build.out`), once the
89# directory it lands in is there. This is the one command that reaches bend's
90# C backend, so it is the one that runs under the memory cap.
91def build.go(start: S.Start, +out: String) -> IO(Unit):
92 match start:
93 case S.Refused{why}:
94 refused(why)
95 case S.Starts{line}:
96 do IO<Unit>:
97 Exec.make(Env.dirs("build", out))
98 +cap : Bool <- Cap.ok()
99 Cap.warn(cap)
100 +g : String <- Cap.gb()
101 +bin : String <- Cap.run(cap, line)
102 report.capped(Cap.why(bin, g), bin)
103
104# `ez build [out]`
105def build(+out: String) -> IO(Unit):
106 do IO<Unit>:
107 start : S.Start <- started(S.Build{out})
108 build.go(start, out)
109
110# the program `ez run` starts, on ez's own stdin, stdout and stderr, with the
111# project's BEND_LIB set for it, and the status it exited with; 0 when the
112# run was refused, which starts none
113def run.started(start: S.Start, +at: String) -> IO(U32):
114 match start:
115 case S.Refused{_why}:
116 IO.pure(U32, 0)
117 case S.Starts{line}:
118 Pass.run(Env.line(at, line))
119
120# a run, and how it ends: exit 1 with the reason for a refusal, and the
121# program's own status otherwise (`S.code`, EZ-OUT-1)
122def run.go(+start: S.Start) -> IO(Unit):
123 do IO<Unit>:
124 at : String <- Env.lib()
125 +program : U32 <- run.started(start, at)
126 +code : U32 <- IO.pure(U32, S.code(start, program))
127 Run.end.at(U32.is_eq(code, 0), code, S.why(start))
128
129# `ez run [args..]`: the entry run by bend, which checks it first, with every
130# word after `run`, as `cargo run` runs a project's binary
131def run(as: List<&2, String>) -> IO(Unit):
132 do IO<Unit>:
133 +start : S.Start <- started(S.Run{as})
134 run.go(start)
135
136# `ez publish`, planned by pub/plan.bend: the entry the ledger names, sent to
137# the hub once the tree is clean and git tracks every file of the package,
138# and bend's answer checked against the `0x` name ez computed. A ledger that
139# is missing or does not parse is refused, and nothing is sent.
140def publish() -> IO(Unit):
141 PubRun.run()
142
143# `ez lock`, plain or `--upgrade`, is the planner's
144# (lock/plan.bend, lock/up.bend): a plain lock copies every pin as the ledger
145# has it, refuses one it would have to fill, and never writes ez.toml; an
146# upgrade moves the selected pins, writes ez.toml once, `.gitignore`, the
147# rewritten imports and the lock in one plan. Either writes nothing when it
148# refuses, and a missing ledger is refused. `--package` is only a filter on an
149# upgrade.
150# `--package` without `--upgrade` is refused before anything is read
151# (`E.lock.flags`).
152def lock.go(flags: P.Outcome, up: Bool, +name: String) -> IO(Unit):
153 match flags:
154 case P.Success{}:
155 Run.run(up, name)
156 case P.Refused{why}:
157 Run.end(P.Refused{why})
158
159# `ez lock` and `ez lock --upgrade [--package NAME]`
160def lock(+up: Bool, +name: String) -> IO(Unit):
161 lock.go(E.lock.flags(up, name), up, name)
162
163# `ez fetch`, planned by fetch/plan.bend: BEND_LIB filled from the lock, with
164# nothing re-resolved, and nothing laid unless every package checks
165def fetch() -> IO(Unit):
166 FetchRun.run()
167
168# `ez init [name] [entry.bend] [--description TEXT]`: a new project, planned
169# by init/plan.bend
170def init(+asked: String, +file: String, +desc: String) -> IO(Unit):
171 InitRun.run(asked, file, desc)
172
173# `ez add <target> [rev|tag|branch] [entry.bend] [--rename NAME]`, planned by
174# add/plan.bend. The target is an `owner/repo`, a git url, or a path, the same
175# shapes `ez tool run` takes, or a hub package's `<name>@<version>`
176# (add/hub.bend). A missing ref is resolved, and a missing entry is read from
177# the checkout, or from the package the hub serves.
178def add(+target: String, +ref: String, +entry: String, +rename: String) -> IO(Unit):
179 AddRun.run(target, ref, entry, rename)
180
181# `ez remove <name>`, planned by remove/plan.bend
182def remove(+name: String) -> IO(Unit):
183 RemoveRun.run(name)