urbit/lib/ford-turbo.hoon

3110 lines
96 KiB
Plaintext

!:
::
:: sys/zuse/hoon
::
:: |ford: build system vane interface
::
|%
++ ford-api ^?
|%
:: |able:ford: ford's public +move interface
::
++ able ^?
|%
:: +task:able:ford: requests to ford
::
+= task
$% :: %make: perform a build, either live or once
::
$: %make
:: our: who our ship is (remove after cc-release)
::
our=@p
:: plan: the schematic to build
::
=schematic
==
:: %kill: stop a build; send on same duct as original %make request
::
$: %kill
:: our: who our ship is (remove after cc-release)s
::
our=@p
==
:: %wegh: produce memory usage information
::
[%wegh ~]
:: %wipe: clear cache
::
[%wipe ~]
==
:: +gift:able:ford: responses from ford
::
+= gift
$% :: %mass: memory usage; response to %wegh +task
::
[%mass p=mass]
:: %made: build result; response to %make +task
::
$: %made
:: date: formal date of the build
::
date=@da
:: result: result of the build; either complete build, or error
::
$= result
$% :: %complete: contains the result of the completed build
::
[%complete build-result]
:: %incomplete: couldn't finish build; contains error message
::
[%incomplete tang]
== == ==
--
:: +disc: a desk on a ship; can be used as a beak that varies with time
::
+= disc [=ship =desk]
:: +rail: a time-varying full path
::
:: This can be thought of as a +beam without a +case, which is what
:: would specify the time. :spur is flopped just like the +spur in a +beam.
::
+= rail [=disc =spur]
:: +dependency: dependency on a value from the urbit namespace
::
+= dependency
$: :: vane which we are querying
::
vane=?(%c %g)
:: type of request
::
:: TODO: care:clay should be cleaned up in zuse as it is a general
:: type, not a clay specific one.
::
care=care:clay
:: path on which to depend, missing time which will be filled in
::
=rail
==
:: +build-result: the referentially transparent result of a +build
::
:: A +build produces either an error or a result. A result is a tagged
:: union of the various kinds of datatypes a build can produce. The tag
:: represents the sub-type of +schematic that produced the result.
::
+= build-result
$% :: %error: the build produced an error whose description is :message
::
[%error message=tang]
:: %result: result of successful +build, tagged by +schematic sub-type
::
$: %result
$^ [head=build-result tail=build-result]
$% [%$ =cage]
[%pin date=@da =build-result]
[%alts =build-result]
[%bake =cage]
[%bunt =cage]
[%call =vase]
[%cast =cage]
[%core =vase]
[%diff =cage]
[%dude =build-result]
[%hood =scaffold]
[%join =cage]
[%mash =cage]
[%mute =cage]
[%pact =cage]
[%path =rail]
[%plan =vase]
[%reef =vase]
[%ride =vase]
[%same =build-result]
[%scry =cage]
[%slim [=type =nock]]
[%slit =type]
[%vale =cage]
[%volt =cage]
== == ==
::
:: +schematic: plan for building
::
++ schematic
:: If the head of the +schematic is a pair, it's an auto-cons
:: schematic. Its result will be the pair of results of its
:: sub-schematics.
::
$^ [head=schematic tail=schematic]
::
$% :: %$: literal value. Produces its input unchanged.
::
$: %$
:: literal: the value to be produced by the build
::
literal=cage
==
:: %pin: pins a sub-schematic to a date
::
:: There is a difference between live builds and once builds. In
:: live builds, we produce results over and over again and aren't
:: pinned to a specifc time. In once builds, we want to specify a
:: specific date, which we apply recursively to any sub-schematics
:: contained within :schematic.
::
:: If a build has a %pin at the top level, we consider it to be a
:: once build. Otherwise, we consider it to be a live build. We do
:: this so schematics which depend on the result of a once build can
:: be cached, giving the client explicit control over the caching
:: behaviour.
::
$: %pin
:: date: time at which to perform the build
::
date=@da
:: schematic: wrapped schematic of pinned time
::
=schematic
==
:: %alts: alternative build choices
::
:: Try each choice in :choices, in order; accept the first one that
:: succeeds. Note that the result inherits the dependencies of all
:: failed schematics, as well as the successful one.
::
$: %alts
:: choices: list of build options to try
::
choices=(list schematic)
==
:: %bake: run a file through a renderer
::
$: %bake
:: renderer: name of renderer; also its file path in ren/
::
renderer=term
:: query-string: the query string of the renderer's http path
::
query-string=coin
:: path-to-render: full path of file to render
::
path-to-render=rail
==
:: %bunt: produce the default value for a mark
::
$: %bunt
:: disc where in clay to load the mark from
::
=disc
:: mark: name of mark; also its file path in mar/
::
mark=term
==
:: %call: call a gate on a sample
::
$: %call
:: gate: schematic whose result is a gate
::
gate=schematic
:: sample: schematic whose result will be the gate's sample
::
sample=schematic
==
:: %cast: cast the result of a schematic through a mark
::
$: %cast
:: disc where in clay to load the mark from
::
=disc
:: mark: name of mark; also its file path in ren/
::
mark=term
:: input: schematic whose result will be run through the mark
::
input=schematic
==
:: %core: build a hoon program from a source file
::
$: %core
:: source-path: clay path from which to load hoon source
::
source-path=rail
==
:: %diff: produce marked diff from :first to :second
::
$: %diff
:: disc where in clay to load the mark from
::
=disc
:: old: schematic producing data to be used as diff starting point
::
start=schematic
:: new: schematic producing data to be used as diff ending point
::
end=schematic
==
:: %dude: wrap a failure's error message with an extra message
::
$: %dude
:: error: a trap producing an error message to wrap the original
::
error=(trap tank)
:: attempt: the schematic to try, whose error we wrap, if any
::
attempt=schematic
==
:: %hood: create a +hood from a hoon source file
::
$: %hood
:: source-path: clay path from which to load hoon source
::
source-path=rail
==
:: %join: merge two diffs into one diff; produces `~` if conflicts
::
$: %join
:: disc where in clay to load the mark from
::
=disc
:: mark: name of the mark to use for diffs; also file path in mar/
::
mark=term
:: first: schematic producing first diff
::
first=schematic
:: second: schematic producing second diff
::
second=schematic
==
:: %mash: force a merge, annotating any conflicts
::
$: %mash
:: disc where in clay to load the mark from
::
=disc
:: mark: name of mark used in diffs; also file path in mar/
::
mark=term
:: first: schematic producing first diff
::
first=schematic
:: second: schematic producing second diff
::
second=schematic
==
:: %mute: mutate a noun by replacing its wings with new values
::
$: %mute
:: subject: schematic producing the noun to mutate
::
subject=schematic
:: mutations: axes and schematics to produce their new contents
::
mutations=(list (pair wing schematic))
==
:: %pact: patch a marked noun by applying a diff
::
$: %pact
:: disc where in clay to load the mark from
::
=disc
:: mark: name of mark to use in diff; also file path in mar/
::
mark=term
:: start: schematic producing a noun to be patched
::
start=schematic
:: diff: schematic producing the diff to apply to :start
::
diff=schematic
==
:: %path: resolve a path with `-`s to a path with `/`s
::
:: Resolve +file-path to a path containing a file, replacing
:: any `-`s in the path with `/`s if no file exists at the
:: original path. Produces an error if multiple files match,
:: e.g. a/b/c and a/b-c, or a/b/c and a-b/c.
::
:: TODO verify current implementation
::
$: %path
:: disc the +disc within which to resolve :file-path
::
location=disc
:: file-path: the path to resolve
::
file-path=@tas
==
:: %plan: build a hoon program from a preprocessed source file
::
$: %plan
:: source-path: the clay path of the hoon source file
::
source-path=rail
:: query-string: the query string of the http request
::
query-string=coin
:: scaffold: preprocessed hoon source and imports
::
=scaffold
==
:: %reef: produce a hoon+zuse kernel. used internally for caching
::
[%reef ~]
:: %ride: eval hoon as formula with result of a schematic as subject
::
$: %ride
:: formula: a hoon to be evaluated against a subject
::
formula=hoon
:: subject: a schematic whose result will be used as subject
::
subject=schematic
==
:: %same: the identity function
::
:: Functionally used to "unpin" a build for caching reasons. If you
:: run a %pin build, it is treated as a once build and is therefore
:: not cached. Wrapping the %pin schematic in a %same schematic
:: converts it to a live build, which will be cached due to live
:: build subscription semantics.
::
$: %same
:: schematic that we evaluate to
::
=schematic
==
:: %scry: lookup a value from the urbit namespace
::
$: %scry
:: request: the request to be made against the namespace
::
=dependency
==
:: %slim: compile a hoon against a subject type
::
$: %slim
:: compile-time subject type for the :formula
::
subject-type=type
:: formula: a +hoon to be compiled to (pair type nock)
::
formula=hoon
==
:: %slit: get type of gate product
::
$: %slit
:: gate: a vase containing a gate
::
gate=vase
:: sample: a vase containing the :gate's sample
::
sample=vase
==
:: %vale: coerce a noun to a mark, validated
::
$: %vale
:: disc where in clay to load the mark from
::
=disc
:: mark: name of mark to use; also file path in mar/
::
mark=term
:: input: the noun to be converted using the mark
::
input=*
==
:: %volt: coerce a noun to a mark, unsafe
::
$: %volt
:: disc where in clay to load the mark from
::
=disc
:: mark: name of mark to use; also file path in mar/
::
mark=term
:: input: the noun to be converted using the mark
::
input=*
==
==
::
:: +scaffold: program construction in progress
::
:: A source file with all its imports and requirements, which will be
:: built and combined into one final product.
::
+= scaffold
$: :: zuse-version: the kelvin version of the standard library
::
zuse-version=@ud
:: structures: files from %/sur which are included
::
structures=(list cable)
:: libraries: files from %/lib which are included
::
libraries=(list cable)
:: cranes: a list of resources to transform and include
::
cranes=(list crane)
:: sources: hoon sources, either parsed or on the filesystem
::
sources=(list brick)
==
:: +cable: a reference to something on the filesystem
::
+= cable
$: :: expand-namespace: expose internal faces to subject
::
expand-namespace=?
:: file-path: location in clay
::
file-path=term
:: remote-location: if not `~`, remote location of file
::
remote-location=(unit (pair case ship))
==
:: +brick: hoon code, either directly specified or referencing clay
::
+= brick
$% $: :: %direct: inline parsed hoon
::
%direct
source=hoon
==
$: :: %indirect: reference to a hoon file in clay
::
%indirect
location=beam
== ==
:: +truss: late-bound path
::
:: TODO: the +tyke data structure should be rethought, possibly as part
:: of this effort since it is actually a `(list (unit hoon))`, when it
:: only represents @tas. It should be a structure which explicitly
:: represents a path with holes that need to be filled in.
::
+= truss
$: pre=(unit tyke)
pof=(unit [p=@ud q=tyke])
==
:: +crane: parsed rune used to include and transform resources
::
:: Cranes lifting cranes lifting cranes!
::
:: A recursive tree of Ford directives that specifies instructions for
:: including and transforming resources from the Urbit namespace.
::
+= crane
$% $: :: %fssg: `/~` hoon literal
::
:: `/~ <hoon>` produces a crane that evaluates arbitrary hoon.
::
%fssg
=hoon
==
$: :: %fsbc: `/$` process query string
::
:: `/$` will call a gate with the query string supplied to this
:: build. If no query string, this errors.
::
%fsbc
=hoon
==
$: :: %fsbr: `/|` first of many options that succeeds
::
:: `/|` takes a series of cranes and produces the first one
:: (left-to-right) that succeeds. If none succeed, it produces
:: stack traces from all of its arguments.
::
%fsbr
:: choices: cranes to try
::
choices=(list crane)
==
$: :: %fsts: `/=` wrap a face around a crane
::
:: /= runs a crane (usually produced by another ford rune), takes
:: the result of that crane, and wraps a face around it.
::
%fsts
:: face: face to apply
::
face=term
:: crane: internal build step
::
=crane
==
$: :: %fsdt: `/.` null-terminated list
::
:: Produce a null-terminated list from a sequence of cranes,
:: terminated by a `==`.
::
%fsdt
:: items: cranes to evaluate
::
items=(list crane)
==
$: :: %fscm: `/,` switch by path
::
:: `/,` is a switch statement, which picks a branch to evaluate
:: based on whether the current path matches the path in the
:: switch statement. Takes a sequence of pairs of (path, crane)
:: terminated by a `==`.
::
%fscm
:: cases: produces evaluated crane of first +spur match
::
cases=(list (pair spur crane))
==
$: :: %fscn: `/%` propagate extra arguments into renderers
::
:: `/%` will forward extra arguments (usually from Eyre) on to
:: any renderer in :crane. Without this, renderers that use `/$`
:: to read the extra arguments will crash.
::
%fscn
=crane
==
$: :: %fspm: `/&` pass through a series of marks
::
:: `/&` passes a crane through multiple marks, right-to-left.
::
%fspm
:: marks: marks to apply to :crane, in reverse order
::
marks=(list mark)
=crane
==
$: :: %fscb: `/_` run a crane on each file in the current directory
::
:: `/_` takes a crane as an argument. It produces a new crane
:: representing the result of mapping the supplied crane over the
:: list of files in the current directory. The keys in the
:: resulting map are the basenames of the files in the directory,
:: and each value is the result of running that crane on the
:: contents of the file.
::
%fscb
=crane
==
$: :: %fssm: `/;` operate on
::
:: `/;` takes a hoon and a crane. The hoon should evaluate to a
:: gate, which is then called with the result of the crane as its
:: sample.
::
%fssm
=hoon
=crane
==
$: :: %fscl: `/:` evaluate at path
::
:: `/:` takes a path and a +crane, and evaluates the crane with
:: the current path set to the supplied path.
::
%fscl
:: path: late bound path to be resolved relative to current beak
::
:: This becomes current path of :crane
::
path=truss
=crane
==
$: :: %fskt: `/^` cast
::
:: `/^` takes a +mold and a +crane, and casts the result of the
:: crane to the mold.
::
%fskt
:: mold: evaluates to a mold to be applied to :crane
::
mold=hoon
=crane
==
$: :: %fszp: `/!mark/` evaluate as hoon, then pass through mark
::
%fszp
=mark
==
$: :: %fszy: `/mark/` passes current path through :mark
::
%fszy
=mark
== ==
--
--
::
:: sys/ford/hoon
::
:: when ford becomes a real vane, it'll start from a vase
::
:: |= pit=vase
::
=, ford-api
:: ford internal data structures
::
=> =~
=, ford-api :: TODO remove once in vane
|%
:: +move: arvo moves that ford can emit
::
+= move
::
$: :: duct: request identifier
::
=duct
:: card: move contents; either a +note or a +gift:able
::
card=(wind note gift:able)
==
:: +note: private request from ford to another vane
::
+= note
$% :: %c: to clay
::
$: %c
:: %warp: internal (intra-ship) file request
::
$% $: %warp
:: sock: pair of requesting ship, requestee ship
::
=sock
:: riff: clay request contents
::
riff=riff:clay
== == ==
:: %f: to ford itself
::
$: %f
:: %make: perform a build
::
$% $: %make
:: schematic: the schematic to build
::
=schematic
== == ==
:: %g: to gall
::
$: %g
:: %unto: full transmission
::
:: TODO: document more fully
::
$% $: %deal
:: sock: pair of requesting ship, requestee ship
::
=sock
:: cush: gall request contents
::
cush=cush:gall
== == == ==
:: +sign: private response from another vane to ford
::
+= sign
$% :: %c: from clay
::
$: %c
$% :: %writ: internal (intra-ship) file response
::
$: %writ
:: riot: response contents
::
riot=riot:clay
==
:: %wris: response to %mult; many changed files
::
$: %wris
:: case: case of the new files
::
=case
:: care-paths: the +care:clay and +path of each file
::
care-paths=(set [care=care:clay =path])
== == == ==
--
::
=, ford-api :: TODO remove once in vane
::
|%
::
:: +axle: overall ford state
::
+= axle
$: :: date: date at which ford's state was updated to this data structure
::
date=%~2018.3.14
:: state-by-ship: storage for all the @p's this ford has been
::
:: Once the cc-release boot sequence lands, we can remove this
:: mapping, since an arvo will not change @p identities. until
:: then, we need to support a ship booting as a comet before
:: becoming its adult identity.
::
state-by-ship=(map ship ford-state)
==
:: +ford-state: all state that ford maintains for a @p ship identity
::
+= ford-state
$: :: results: all stored build results
::
:: Ford generally stores the result for all the most recently
:: completed live builds, unless it's been asked to wipe its cache.
::
results=(map build cache-line)
:: builds-by-schematic: all attempted builds, sorted by time
::
:: For each schematic we've attempted to build at any time,
:: list the formal dates of all build attempts, sorted newest first.
::
builds-by-schematic=(map schematic (list @da))
:: builds-by-date: all attempted builds, grouped by time
::
builds-by-date=(jug @da schematic)
:: components: bidirectional linkages between sub-builds and clients
::
:: The first of the two jugs maps from a build to its sub-builds.
:: The second of the two jugs maps from a build to its client builds.
::
$= components
$: :: sub-builds: jug from a build to its sub-builds
::
sub-builds=(jug build build)
:: client-builds: jug from a build to its client builds
::
client-builds=(jug build build)
==
:: provisional-components: expected linkage we can't prove yet
::
:: During the +gather step, we promote builds, but our promotion
:: decisions may be wrong. We record our predictions here so we
:: can undo them.
::
$= provisional-components
$: :: sub-builds: jug from a build to its sub-builds
::
sub-builds=(jug build build)
:: client-builds: jug from a build to its client builds
::
client-builds=(jug build build)
==
:: rebuilds: bidirectional links between old and new identical builds
::
:: Old and new build must have the same schematic and result.
:: This can form a chain, like build<-->build<-->build.
::
$= rebuilds
$: :: new: map from old build to new build
::
new=(map build build)
:: old: map from new build to old build
::
old=(map build build)
==
:: blocks: map from +dependency to all builds waiting for its retrieval
::
blocks=(jug dependency build)
:: next-builds: builds to perform in the next iteration
::
next-builds=(set build)
:: candidate-builds: builds which might go into next-builds.
::
candidate-builds=(list build)
:: blocked builds: mappings between blocked and blocking builds
::
$= blocked-builds
$: :: sub-builds: key is client, value is sub-build
::
sub-builds=(jug build build)
:: client-builds: key is sub-build, value is client
::
client-builds=(jug build build)
==
::
:: build request tracking
::
:: listeners: external requests for a build
::
listeners=(jug build listener)
:: builds-by-listener: reverse lookup for :listeners
::
builds-by-listener=(map duct [=build live=?])
:: root-builds: only the root builds, not all builds.
::
root-builds=(jug build listener)
::
:: update tracking
::
:: dependencies: live clay dependencies
::
:: Used for looking up which +dependency's rely on a particular
:: +disc, so that we can send a new Clay subscription with all
:: the resources we care about within that disc.
::
dependencies=(jug disc dependency)
:: latest-by-disc: latest formal date of a completed live build on disc
::
:: Updated each time we complete a build of a +dependency,
:: if the build's formal date is later than the stored value.
::
latest-by-disc=(map disc @da)
:: clay-subscriptions: ducts we'll use to cancel existing clay requests
::
clay-subscriptions=(set disc)
:: dependency-updates: all clay updates we need to know about
::
:: dependency-updates stores all Clay changes at dates that
:: Ford needs to track because Ford is tracking attempted builds with
:: that formal date.
::
dependency-updates=(jug @da dependency)
==
:: +build: a referentially transparent request for a build.
::
:: Each unique +build will always produce the same +build-result
:: when run (if it completes). A live build consists of a sequence of
:: instances of +build with the same :plan and increasing :date.
::
+= build
$: :: date: the formal date of this build; unrelated to time of execution
::
date=@da
:: schematic: the schematic that determines how to run this build
::
=schematic
==
:: +cache-line: a record of our result of running a +build
::
:: Proof that a build has been run. Might include the result if Ford is
:: caching it. If Ford wiped the result from its cache, the result will
:: be replaced with a tombstone so Ford still knows the build has been
:: run before. Otherwise, contains the last accessed time of the result,
:: for use in cache reclamation.
::
+= cache-line
$% :: %result: the result of running a +build, and its last access time
::
$: %result
:: last-accessed: the last time this result was accessed
::
:: Updated every time this result is used in another build or
:: requested in a build request.
::
last-accessed=@da
:: build-result: the referentially transparent result of a +build
::
=build-result
==
:: %tombstone: marker that this build has been run and its result wiped
::
[%tombstone ~]
==
:: +listener: either a :live :duct or a once :duct
::
+= listener
$: :: duct: where to send a response
::
=duct
:: live: whether :duct had requested a live build
::
live=?
==
:: +scry-request: parsed arguments to a scry operation
::
+= scry-request
$: :: vane: the vane from which to make the request
::
:: TODO: use +vane here
::
vane=?(%c %g)
:: care: type of request
::
care=care:clay
:: beam: request path
::
=beam
==
:: +state-diff: changes to ford state made by a build
::
+= state-diff
$: :: build: the build we worked on
::
=build
:: result: the outcome of this build
::
$= result
$% :: %build-result: the build produced a result
::
$: %build-result
=build-result
==
:: %blocks: the build blocked on the following builds/dependency
::
$: %blocks
:: builds: builds that :build blocked on
::
builds=(list build)
:: scry-blocked: dependency that :build blocked on
::
scry-blocked=(unit dependency)
==
==
:: sub-builds: subbuilds of :build
::
:: While running +make on :build, we need to keep track of any
:: sub-builds that we try to access so we can keep track of
:: component linkages and cache access times.
::
sub-builds=(list build)
:: clear-sub-builds: replace previous sub-builds with :sub-builds
::
:: Some schematics will completely rerun their dependency registration,
:: but others will need to add to their previous dependencies.
::
clear-sub-builds=?
==
:: +vane: short names for vanes
::
:: TODO: move to zuse
::
+= vane ?(%a %b %c %d %e %f %g)
--
=, format
|%
:: +build-to-tape: convert :build to a printable format
::
++ build-to-tape
|= =build
^- tape
::
=/ enclose |=(tape "[{+<}]")
=/ date=@da date.build
=/ schematic=schematic schematic.build
::
%- enclose
%+ welp (trip (scot %da date))
%+ welp " "
::
?+ -.schematic
:(welp "[" (trip -.schematic) " {<`@uvI`(mug schematic)>}]")
::
%$
"literal"
::
^
%- enclose
;:(welp $(build [date head.schematic]) " " $(build [date tail.schematic]))
::
%scry
(spud (en-beam (extract-beam dependency.schematic ~)))
::
:: %slim
:: "slim {<subject-type.schematic>} {<formula.schematic>}"
==
:: +unify-jugs: make a new jug, unifying sets for all keys
::
++ unify-jugs
|* [a=(jug) b=(jug)]
^+ a
::
=/ tapped ~(tap by b)
::
|- ^+ a
?~ tapped a
::
=/ key p.i.tapped
=/ vals ~(tap in q.i.tapped)
::
=. a
|- ^+ a
?~ vals a
::
$(vals t.vals, a (~(put ju a) key i.vals))
::
$(tapped t.tapped)
:: +dependency-to-path: encode a +dependency in a +wire
::
:: If :dependency is live, create a +beam from :rail.dependency
:: by using revision 0 as the +case and encode that.
::
++ dependency-to-path
|= =dependency
^- path
=/ term=term (cat 3 [vane care]:dependency)
[term (en-beam (extract-beam dependency date=~))]
:: +path-to-dependency: decode a +dependency from a +wire
::
++ path-to-dependency
|= =path
^- (unit dependency)
::
=/ scry-request=(unit scry-request) (path-to-scry-request path)
?~ scry-request
~
=+ [vane care bem]=u.scry-request
=/ beam=beam bem
=/ rail=rail [disc=[p.beam q.beam] spur=s.beam]
`[vane care rail]
:: +path-to-scry-request: parse :path to a :scry-request
::
++ path-to-scry-request
|= =path
^- (unit scry-request)
::
?. ?=([@ @ *] path)
~
:: parse :path's components into :vane, :care, and :rail
::
=/ vane=(unit ?(%c %g)) ((soft ?(%c %g)) (end 3 1 i.path))
?~ vane
~
=/ care=(unit care:clay) ((soft care:clay) (rsh 3 1 i.path))
?~ care
~
=/ rest=(unit ^path) ((soft ^path) t.path)
?~ rest
~
=/ beam (de-beam u.rest)
?~ beam
~
::
`[u.vane u.care u.beam]
:: +extract-beam: obtain a +beam from a +dependency
::
:: Fills case with [%ud 0] for live dependencies if :date is `~`.
:: For once dependencies, ignore :date.
::
++ extract-beam
|= [=dependency date=(unit @da)] ^- beam
::
=/ case=case ?~(date [%ud 0] [%da u.date])
::
=, rail.dependency
[[ship.disc desk.disc case] spur]
:: +extract-disc: obtain a +disc from a +dependency
::
++ extract-disc
|= =dependency ^- disc
disc.rail.dependency
:: +get-sub-schematics: find any schematics contained within :schematic
::
++ get-sub-schematics
|= =schematic
^- (list ^schematic)
?- -.schematic
^ ~[head.schematic tail.schematic]
%$ ~
%pin ~[schematic.schematic]
%alts choices.schematic
%bake ~
%bunt ~
%call ~[gate.schematic sample.schematic]
%cast ~[input.schematic]
%core ~
%diff ~[start.schematic end.schematic]
%dude ~[attempt.schematic]
%hood ~
%join ~[first.schematic second.schematic]
%mash ~[first.schematic second.schematic]
%mute [subject.schematic (turn mutations.schematic tail)]
%pact ~[start.schematic diff.schematic]
%path ~
%plan ~
%reef ~
%ride ~[subject.schematic]
%same ~[schematic.schematic]
%scry ~
%slim ~
%slit ~
%vale ~
%volt ~
==
:: +result-to-cage
::
:: Maybe we should return vases instead of cages.
::
++ result-to-cage
|= result=build-result
^- cage
?: ?=(%error -.result)
[%tang !>(message.result)]
?- -.+.result
^ [%noun (slop q:$(result head.result) q:$(result tail.result))]
%$ cage.result
%pin $(result build-result.result)
%alts $(result build-result.result)
%bake cage.result
%bunt cage.result
%call [%noun vase.result]
%cast cage.result
%core [%noun vase.result]
%diff cage.result
%dude $(result build-result.result)
%hood [%noun !>(scaffold.result)]
%join cage.result
%mash cage.result
%mute cage.result
%pact cage.result
%path [%noun !>(rail.result)]
%plan [%noun vase.result]
%reef [%noun vase.result]
%ride [%noun vase.result]
%same $(result build-result.result)
%scry cage.result
%slim [%noun !>([type nock]:result)]
%slit [%noun !>(type.result)]
%vale cage.result
%volt cage.result
==
:: +date-from-schematic: finds the latest pin date from this schematic tree.
::
++ date-from-schematic
|= =schematic
^- @da
=+ children=(get-sub-schematics schematic)
=/ dates (turn children date-from-schematic)
=+ children-latest=(roll dates max)
?. ?=(%pin -.schematic)
children-latest
(max date.schematic children-latest)
:: +is-schematic-live:
::
:: A schematic is live if it is not pinned.
::
++ is-schematic-live
|= =schematic
^- ?
!?=(%pin -.schematic)
:: +is-listener-live: helper function for loops
::
++ is-listener-live |=(=listener live.listener)
:: +by-schematic: door for manipulating :builds-by-schematic.ford-state
::
:: The :dates list for each key in :builds is sorted in reverse
:: chronological order. These operations access and mutate keys and values
:: of :builds and maintain that sort order.
::
++ by-schematic
|_ builds=(map schematic (list @da))
:: +put: add a +build to :builds
::
:: If :build already exists in :builds, this is a no-op.
:: Otherwise, replace the value at the key :schematic.build
:: with a new :dates list that contains :date.build.
::
++ put
|= =build
^+ builds
%+ ~(put by builds) schematic.build
::
=/ dates (fall (~(get by builds) schematic.build) ~)
?^ (find [date.build]~ dates)
dates
(sort [date.build dates] gte)
:: +del: remove a +build from :builds
::
:: Removes :build from :builds by replacing the value at
:: the key :schematic.build with a new :dates list with
:: :date.build omitted. If the resulting :dates list is
:: empty, then remove the key-value pair from :builds.
::
++ del
|= =build
^+ builds
=. builds %+ ~(put by builds) schematic.build
::
~| build+build
=/ dates (~(got by builds) schematic.build)
=/ date-index (need (find [date.build]~ dates))
(oust [date-index 1] dates)
:: if :builds has an empty entry for :build, delete it
::
=? builds
=(~ (~(got by builds) schematic.build))
(~(del by builds) schematic.build)
::
builds
:: +find-previous: find the most recent older build with :schematic.build
::
++ find-previous
|= =build
^- (unit ^build)
::
=/ dates=(list @da) (fall (~(get by builds) schematic.build) ~)
::
|- ^- (unit ^build)
?~ dates ~
::
?: (lth i.dates date.build)
`[i.dates schematic.build]
$(dates t.dates)
:: +find-next: find the earliest build of :schematic.build later than :build
::
++ find-next
|= =build
^- (unit ^build)
::
=/ dates=(list @da) (flop (fall (~(get by builds) schematic.build) ~))
::
|- ^- (unit ^build)
?~ dates ~
::
?: (gth i.dates date.build)
`[i.dates schematic.build]
$(dates t.dates)
--
:: +per-event: per-event core
::
++ per-event
:: moves: the moves to be sent out at the end of this event, reversed
::
=| moves=(list move)
:: dirty-discs: discs whose dependencies have changed during this event
::
=| dirty-discs=(set disc)
:: scry-results: responses to scry's to handle in this event
::
:: If a value is `~`, the requested resource is not available.
:: Otherwise, the value will contain a +cage.
::
=| scry-results=(map dependency (unit cage))
:: the +per-event gate; each event will have a different sample
::
:: Not a `|_` because of the `=/`s at the beginning.
:: Produces a core containing four public arms:
:: +start-build, +rebuild, +unblock, and +cancel.
::
|= [[our=@p =duct now=@da scry=sley] state=ford-state]
:: original-clay-subscriptions: outstanding clay subscriptions at event start
::
=/ original-clay-subscriptions clay-subscriptions.state
:: original-dependencies: :dependencies.state at event start
::
=/ original-dependencies dependencies.state
::
|%
:: |entry-points: externally fired arms
::
::+| entry-points
::
:: +start-build: perform a fresh +build, either live or once
::
++ start-build
|= =schematic
^- [(list move) ford-state]
::
=< finalize
::
|^ =+ live=(is-schematic-live schematic)
?: live
start-live-build
start-once-build
::
++ start-live-build
^+ this
=/ build=build [now schematic]
::
=: listeners.state
(~(put ju listeners.state) build [duct %.y])
::
builds-by-listener.state
(~(put by builds-by-listener.state) duct [build %.y])
::
root-builds.state
(~(put ju root-builds.state) build [duct %.y])
==
::
(execute-loop (sy build ~))
::
++ start-once-build
^+ this
=/ pin-date=@da (date-from-schematic schematic)
=/ build=build [pin-date schematic]
:: associate +listener with :build in :state
::
=: listeners.state
(~(put ju listeners.state) build [duct %.n])
::
builds-by-listener.state
(~(put by builds-by-listener.state) duct [build %.n])
::
root-builds.state
(~(put ju root-builds.state) build [duct %.n])
==
::
(execute-loop (sy build ~))
::
--
:: +rebuild: rebuild any live builds based on +dependency updates
::
++ rebuild
|= [=beak care-paths=(set [care=care:clay =path])]
^- [(list move) ford-state]
::
=< finalize
::
=/ date=@da ?>(?=(%da -.r.beak) p.r.beak)
=/ disc=disc [p.beak q.beak]
:: delete the now-dead clay subscription
::
=. clay-subscriptions.state (~(del in clay-subscriptions.state) disc)
::
=/ dependencies=(list dependency)
%+ turn ~(tap in care-paths)
|= [care=care:clay =path] ^- dependency
::
[%c care rail=[disc spur=(flop path)]]
:: store changed dependencies persistently in case rebuilds finish later
::
=. dependency-updates.state
%+ roll dependencies
|= [=dependency dependency-updates=_dependency-updates.state]
::
(~(put ju dependency-updates) date dependency)
:: rebuild dependency builds at the new date
::
%- execute-loop
%- sy
%+ turn dependencies
|=(=dependency `build`[date [%scry dependency]])
:: +unblock: continue builds that had blocked on :dependency
::
++ unblock
|= [=dependency scry-result=(unit cage)]
^- [(list move) ford-state]
::
=< finalize
:: place :scry-result in :scry-results.per-event
::
:: We don't want to call the actual +scry function again,
:: because we already tried that in a previous event and it
:: had no synchronous answer. This +unblock call is a result
:: of the response to the asynchronous request we made to
:: retrieve that resource from another vane.
::
:: Instead, we'll intercept any calls to +scry by looking up
:: the arguments in :scry-results.per-event. This is ok because
:: in this function we attempt to run every +build that had
:: blocked on the resource, so the information is guaranteed
:: to be used during this event before it goes out of scope.
::
=. scry-results (~(put by scry-results) dependency scry-result)
:: find all the :blocked-builds to continue
::
=/ blocked-builds (~(get ju blocks.state) dependency)
::
(execute-loop blocked-builds)
::
++ cancel ^+ [moves state]
::
=< finalize
::
=/ build-and-live (~(get by builds-by-listener.state) duct)
::
?~ build-and-live
~&(no-build-for-duct+duct this)
::
=+ [build live]=u.build-and-live
:: old-rebuilds and listeners don't interact well.
::
:: Have two ducts listening to the same build, causing a promotion.
:: The new build has an old build. Both ducts by :builds-by-listener
:: point to th new build. The new build has an old build and thus
:: never gets cleaned up.
::
=. state (remove-listener-from-build [duct live] build)
::
(cleanup build)
:: +remove-listener-from-build: recursively remove listener from (sub)builds
::
++ remove-listener-from-build
|= [=listener =build]
^+ state
::
=? state (~(has ju root-builds.state) build listener)
%_ state
builds-by-listener
(~(del by builds-by-listener.state) duct.listener)
::
root-builds
(~(del ju root-builds.state) build listener)
==
::
=/ original-build build
=/ builds=(list ^build) ~[build]
::
|- ^+ state
?~ builds
state
::
=. build i.builds
:: are there any clients with this listener?
::
=/ clients-with-listener=?
%+ lien
=- ~(tap in -)
=/ clients=(set ^build)
(fall (~(get by client-builds.components.state) build) ~)
%- ~(uni in clients)
(fall (~(get by client-builds.provisional-components.state) build) ~)
::
|= client=^build
(~(has ju listeners.state) client listener)
:: when there are clients, don't remove the listener from this build
::
?: clients-with-listener
$(builds t.builds)
::
=. listeners.state
(~(del ju listeners.state) build listener)
::
=/ sub-builds (~(get ju sub-builds.components.state) build)
::
=/ provisional-sub-builds
(~(get ju sub-builds.provisional-components.state) build)
::
=/ new-builds=(list ^build)
?: =(build original-build) ~
(drop (~(find-next by-schematic builds-by-schematic.state) build))
::
%_ $
builds
;: welp
t.builds
~(tap in sub-builds)
~(tap in provisional-sub-builds)
new-builds
==
==
:: |construction: arms for performing builds
::
::+| construction
::
:: +execute-loop: +execute repeatedly until :next-builds is `~`
::
++ execute-loop
|= builds=(set build)
^+ ..execute
::
=. ..execute (execute builds)
::
?: ?& ?=(~ next-builds.state)
?=(~ candidate-builds.state)
==
..execute
::
$(builds ~)
:: +execute: main recursive construction algorithm
::
:: Runs +make on :build if necessary, and recurses potentially
:: "upward" to :build's clients and "downward" to :build's sub-builds.
:: Enqueues moves to Clay to request resources for blocked +scry
:: operations and places completed root builds in :done-live-roots
:: to be processed at the end of the event.
::
++ execute
|= builds=(set build)
^+ ..execute
::
|^ ^+ ..execute
::
=. candidate-builds.state
(weld candidate-builds.state ~(tap in builds))
::
=. ..execute gather
::
=/ state-diffs=(list state-diff)
(turn ~(tap in next-builds.state) make)
::
=. next-builds.state ~
::
(reduce state-diffs)
:: +gather: collect builds to be run in a batch: wraps +gather-internal
::
++ gather
^+ ..execute
|-
::
?~ candidate-builds.state
..execute
::
=/ next i.candidate-builds.state
=> .(candidate-builds.state t.candidate-builds.state)
::
$(..execute (gather-build next))
:: +gather-build: looks at a single candidate build
::
:: This gate inspects a single build. It might move it to :next-builds,
:: or promote it using an old build. It also might add this builds
:: sub-builds to :candidate-builds.
::
++ gather-build
|= =build
^+ ..execute
:: normalize :date.build for a %pin schematic
::
=? date.build ?=(%pin -.schematic.build) date.schematic.build
:: if we already have a result for this build, don't rerun the build
::
=^ current-result results.state (access-cache build)
?: ?=([~ %result *] current-result)
..execute
:: place :build in :state if it isn't already there
::
=: builds-by-date.state
(~(put ju builds-by-date.state) date.build schematic.build)
::
builds-by-schematic.state
(~(put by-schematic builds-by-schematic.state) build)
==
:: old-build: most recent previous build with :schematic.build
::
=/ old-build=(unit ^build)
(~(find-previous by-schematic builds-by-schematic.state) build)
:: if no previous builds exist, we need to run :build
::
?~ old-build
..execute(next-builds.state (~(put in next-builds.state) build))
:: copy :old-build's live listeners
::
=/ old-live-listeners=(list listener)
=- (skim - is-listener-live)
=- ~(tap in `(set listener)`(fall - ~))
(~(get by listeners.state) u.old-build)
::
=. state
%+ roll old-live-listeners
|= [=listener state=_state]
::
%_ state
listeners
(~(put ju listeners.state) build listener)
==
:: if any dependencies have changed, we need to rebuild :build
::
?: (dependencies-changed build)
..execute(next-builds.state (~(put in next-builds.state) build))
:: if we don't have :u.old-build's result cached, we need to run :build
::
=^ old-cache-line results.state (access-cache u.old-build)
?~ old-cache-line
..execute(next-builds.state (~(put in next-builds.state) build))
:: if :u.old-build's result has been wiped, we need to run :build
::
?: ?=(%tombstone -.u.old-cache-line)
..execute(next-builds.state (~(put in next-builds.state) build))
:: if any ancestors are pinned, we must rerun
::
:: We can't cleanly promote a once build to a live build because we
:: didn't register its dependencies in the live tracking system.
::
?: (has-pinned-client u.old-build)
..execute(next-builds.state (~(put in next-builds.state) build))
:: old-subs: sub-builds of :u.old-build
::
=/ old-subs=(list ^build)
~(tap in (fall (~(get by sub-builds.components.state) u.old-build) ~))
::
=/ new-subs (turn old-subs |=(^build +<(date date.build)))
:: if all subs are in old.rebuilds.state, promote ourselves
::
?: (levy new-subs ~(has by old.rebuilds.state))
:: link all :new-subs to :build in :components.state
::
=. state
%+ roll new-subs
::
|= [new-sub=^build state=_state]
::
%_ state
sub-builds.components
(~(put ju sub-builds.components.state) build new-sub)
::
client-builds.components
(~(put ju client-builds.components.state) new-sub build)
==
::
=^ wiped-rebuild ..execute (promote-build u.old-build date.build)
=? next-builds.state
?=(^ wiped-rebuild)
(~(put in next-builds.state) u.wiped-rebuild)
::
=^ unblocked-clients state (mark-as-done build)
=. candidate-builds.state
(welp unblocked-clients candidate-builds.state)
::
..execute
:: record sub-builds as provisional
::
:: When we can't directly promote ourselves, we're going to rerun
:: our build. It's possible that the sub-builds are different, in
:: which case we'll need to clean up the current sub-build dependency.
::
=. provisional-components.state
%+ roll `(list ^build)`new-subs
|= [new-sub=^build provisional-components=_provisional-components.state]
::
%_ provisional-components
sub-builds
(~(put ju sub-builds.provisional-components) build new-sub)
::
client-builds
(~(put ju client-builds.provisional-components) new-sub build)
==
:: all new-subs have results, some are not rebuilds
::
=/ uncached-new-subs (skip new-subs is-build-cached)
?~ uncached-new-subs
..execute(next-builds.state (~(put in next-builds.state) build))
:: otherwise, not all new subs have results.
::
:: If all of our sub-builds finish immediately (i.e. promoted),
:: they'll add us back to :candidate-builds.state.
::
=. blocked-builds.state
%+ roll `(list ^build)`uncached-new-subs
|= [new-sub=^build blocked-builds=_blocked-builds.state]
::
%_ blocked-builds
sub-builds
(~(put ju sub-builds.blocked-builds) build new-sub)
::
client-builds
(~(put ju client-builds.blocked-builds) new-sub build)
==
::
%_ ..execute
candidate-builds.state
:(welp uncached-new-subs candidate-builds.state)
==
:: +promote-build: promote result of :build to newer :date
::
:: Also promotes live listeners, links the two builds in :rebuilds.state,
:: sends %made moves for any once listeners on the new build, and
:: calls +send-future-mades to make sure listener tracking is up to date.
::
++ promote-build
|= [old-build=build date=@da]
^- [(unit build) _..execute]
::
=^ old-cache-line results.state (access-cache old-build)
::
?> ?=([~ %result *] old-cache-line)
::
=/ new-build=build [date schematic.old-build]
::
=. results.state (~(put by results.state) new-build u.old-cache-line)
::
=. rebuilds.state (link-rebuilds old-build new-build)
::
=? latest-by-disc.state
?& ?=(%scry -.schematic.old-build)
=/ disc (extract-disc dependency.schematic.old-build)
~| disc+disc
~| latest-by-disc+latest-by-disc.state
(gth date (~(got by latest-by-disc.state) disc))
==
=/ disc (extract-disc dependency.schematic.old-build)
(~(put by latest-by-disc.state) disc date)
::
?> (~(has ju builds-by-date.state) date.new-build schematic.new-build)
::
=. components.state
%+ roll
=- ~(tap in (fall - ~))
(~(get by sub-builds.components.state) old-build)
::
|= [old-sub=build components=_components.state]
::
=/ new-sub=build old-sub(date date)
%_ components
sub-builds
(~(put ju sub-builds.components) new-build new-sub)
::
client-builds
(~(put ju client-builds.components) new-sub new-build)
==
:: promoted builds are no longer provisional
::
=. provisional-components.state
%+ roll
=- ~(tap in (fall - ~))
(~(get by sub-builds.provisional-components.state) new-build)
::
|= [old-sub=build provisional-components=_provisional-components.state]
::
=/ new-sub=build old-sub(date date)
%_ provisional-components
sub-builds
(~(del ju sub-builds.provisional-components) new-build new-sub)
::
client-builds
(~(del ju client-builds.provisional-components) new-sub new-build)
==
:: send mades for the currently existing listeners before we add more
::
=. ..execute (send-mades new-build (root-live-listeners new-build))
::
=. state (promote-live-listeners old-build new-build)
::
=. ..execute (send-mades new-build (root-once-listeners new-build))
::
=. state (delete-root-once-listeners new-build)
::
=^ future ..execute (send-future-mades new-build)
::
=. ..execute (cleanup old-build)
::
[future ..execute]
:: +send-future-mades: send %made moves for future rebuilds
::
:: If a future rebuild has been wiped, then produce it along with
:: a modified `..execute` core.
::
++ send-future-mades
|= =build
^- [(unit ^build) _..execute]
::
=^ result results.state (access-cache build)
::
=/ next (~(find-next by-schematic builds-by-schematic.state) build)
?~ next
:: no future build
::
[~ ..execute]
::
=^ next-result results.state (access-cache u.next)
?~ next-result
:: unfinished future build
::
[~ ..execute]
:: if :next's result hasn't been wiped
::
?: ?& ?=(%result -.u.next-result)
!(has-pinned-client u.next)
==
::
=. state (promote-live-listeners build u.next)
=. ..execute (cleanup build)
:: if the result has changed, send %made moves for live listeners
::
=? ..execute
?& ?=([~ %result *] result)
!=(build-result.u.result build-result.u.next-result)
==
(send-mades u.next (root-live-listeners u.next))
::
$(build u.next)
:: if :next has been wiped, produce it
::
[`u.next ..execute]
:: reduce: TODO
::
++ reduce
|= state-diffs=(list state-diff)
^+ ..execute
::
|- ^+ ..execute
?~ state-diffs ..execute
::
=* made i.state-diffs
:: perform live accounting if :is-live-scry
::
=? ..execute
?& ?=(%scry -.schematic.build.made)
(is-build-live build.made)
==
::
=/ dependency=dependency dependency.schematic.build.made
=/ disc=disc (extract-disc dependency)
::
%_ ..execute
:: link :disc to :dependency
::
dependencies.state
(~(put ju dependencies.state) [disc dependency])
:: mark :disc as dirty
::
dirty-discs
(~(put in dirty-discs) disc)
:: update :latest-by-disc.state if :date.build is later
::
latest-by-disc.state
=/ latest-date (~(get by latest-by-disc.state) disc)
::
?: ?& ?=(^ latest-date)
(lte date.build.made u.latest-date)
==
latest-by-disc.state
::
(~(put by latest-by-disc.state) disc date.build.made)
==
:: clear the components
::
=? ..execute
clear-sub-builds.made
(unlink-sub-builds build.made)
:: process :sub-builds.made
::
=. state
%+ roll sub-builds.made
|= [sub-build=build accumulator=_state]
=. state accumulator
:: freshen cache for sub-build
::
=. results.state +:(access-cache sub-build)
::
%_ state
builds-by-date
(~(put ju builds-by-date.state) date.build.made schematic.sub-build)
::
builds-by-schematic
(~(put by-schematic builds-by-schematic.state) sub-build)
::
sub-builds.components
(~(put ju sub-builds.components.state) build.made sub-build)
::
client-builds.components
(~(put ju client-builds.components.state) sub-build build.made)
::
listeners
::
=/ unified-listeners
%- ~(uni in (fall (~(get by listeners.state) sub-build) ~))
(fall (~(get by listeners.state) build.made) ~)
:: don't put a key with an empty value
::
?~ unified-listeners
listeners.state
::
(~(put by listeners.state) sub-build unified-listeners)
==
::
?- -.result.made
%build-result
::
?> (~(has ju builds-by-date.state) date.build.made schematic.build.made)
::
=. results.state
%+ ~(put by results.state) build.made
[%result last-accessed=now build-result.result.made]
::
=/ client-builds
=- ~(tap in (fall - ~))
(~(get by client-builds.blocked-builds.state) build.made)
::
=^ unblocked-clients state (mark-as-done build.made)
=. next-builds.state (~(gas in next-builds.state) unblocked-clients)
::
=/ previous-build
(~(find-previous by-schematic builds-by-schematic.state) build.made)
::
=^ previous-result results.state
?~ previous-build
[~ results.state]
::
(access-cache u.previous-build)
::
=? state
?& ?=(^ previous-build)
?=(^ previous-result)
:: this is wrong! it can be some other build that has the pin
!(has-pinned-client u.previous-build)
==
(promote-live-listeners u.previous-build build.made)
::
=. ..execute (send-mades build.made (root-once-listeners build.made))
=. state (delete-root-once-listeners build.made)
:: if result is same as previous, note sameness
::
=/ same-result=?
?& ?=(^ previous-build)
!(has-pinned-client u.previous-build)
?=([~ %result *] previous-result)
=(build-result.result.made build-result.u.previous-result)
==
::
=? rebuilds.state
same-result
::
?> ?=(^ previous-build)
(link-rebuilds u.previous-build build.made)
::
=? ..execute
!same-result
(send-mades build.made (root-live-listeners build.made))
:: rerun any old clients, updated to the current time
::
=? state
&(!same-result ?=(^ previous-build))
::
=/ clients-to-rebuild=(list build)
%+ turn
%+ weld
=- ~(tap in (fall - ~))
(~(get by client-builds.components.state) u.previous-build)
::
=/ older-build (~(get by old.rebuilds.state) u.previous-build)
?~ older-build
~
::
=- ~(tap in (fall - ~))
(~(get by client-builds.components.state) u.older-build)
::
|= old-client=build
old-client(date date.build.made)
::
%+ roll clients-to-rebuild
|= [client=build state=_state]
::
%_ state
::
next-builds
(~(put in next-builds.state) client)
::
client-builds.components
(~(put ju client-builds.components.state) build.made client)
::
sub-builds.components
(~(put ju sub-builds.components.state) client build.made)
::
builds-by-date
(~(put ju builds-by-date.state) date.client schematic.client)
::
builds-by-schematic
(~(put by-schematic builds-by-schematic.state) client)
==
:: clean up provisional builds: remove actual builds
::
:: The first step in provisional build cleanup is to remove
:: sub-builds which were actually depended on from the provisional
:: build set because they're no longer provisional.
::
=. provisional-components.state
%+ roll sub-builds.made
|= $: sub-build=build
provisional-components=_provisional-components.state
==
::
%_ provisional-components
sub-builds
(~(del ju sub-builds.provisional-components) build.made sub-build)
::
client-builds
(~(del ju client-builds.provisional-components) sub-build build.made)
==
:: clean up provisional builds: remove orphans
::
:: Any builds left in :provisional-components.state for our build
:: are orphaned builds. However, these builds may have other
:: listeners and we don't want to delete those.
::
=. ..execute
%+ roll
=- ~(tap in (fall - ~))
(~(get by sub-builds.provisional-components.state) build.made)
::
|= [sub-build=build accumulator=_..execute]
=. ..execute accumulator
:: calculate the listeners to remove
::
:: Orphaned sub-builds have a set of listeners attached to them.
:: We want to find the listeners which shouldn't be there and
:: remove them.
::
=/ provisional-client-listeners=(set listener)
(fall (~(get by listeners.state) build.made) ~)
::
=/ all-other-client-listeners=(set listener)
%+ roll
=- ~(tap in -)
=- (~(del in -) build.made)
=- (fall - ~)
(~(get by client-builds.provisional-components.state) sub-build)
|= [=build listeners=(set listener)]
::
%- ~(uni in listeners)
(fall (~(get by listeners.state) build) ~)
::
=/ orphaned-listeners
(~(dif in provisional-client-listeners) all-other-client-listeners)
::
=. state
%+ roll ~(tap in orphaned-listeners)
|= [=listener accumulator=_state]
=. state accumulator
::
(remove-listener-from-build listener sub-build)
:: remove the orphaned build from provisional builds
::
=: sub-builds.provisional-components.state
%+ ~(del ju sub-builds.provisional-components.state)
build.made
sub-build
::
client-builds.provisional-components.state
%+ ~(del ju client-builds.provisional-components.state)
sub-build
build.made
==
::
(cleanup sub-build)
::
=? ..execute
?=(^ previous-build)
(cleanup u.previous-build)
::
=. ..execute (cleanup build.made)
::
=^ wiped-rebuild ..execute (send-future-mades build.made)
?~ wiped-rebuild
$(state-diffs t.state-diffs)
::
=. next-builds.state (~(put in next-builds.state) u.wiped-rebuild)
::
$(state-diffs t.state-diffs)
::
%blocks
=? moves
?=(^ scry-blocked.result.made)
::
=* dependency u.scry-blocked.result.made
:: TODO: handle other vanes
::
?> ?=(%c vane.dependency)
:: construct new move to request blocked resource
::
=/ wire=path
(welp /(scot %p our)/dependency (dependency-to-path dependency))
::
=/ note=note
=/ disc=disc (extract-disc dependency)
=, rail.dependency
:* %c %warp sock=[our their=ship.disc] desk.disc
`[%sing care.dependency case=[%da date.build.made] spur]
==
::
[[duct=~ [%pass wire note]] moves]
:: register dependency block in :blocks.state
::
=? blocks.state
?=(^ scry-blocked.result.made)
::
?> ?=(%scry -.schematic.build.made)
=* dependency dependency.schematic.build.made
::
(~(put ju blocks.state) dependency build.made)
:: register blocks on sub-builds in :blocked-builds.state
::
=. state
%+ roll builds.result.made
|= [block=build state=_state]
:: deal with block already being unblocked
::
:: If :block was run in the same batch as :build.made, and we've
:: already processed its result, then :build.made has already
:: been unblocked. Don't reblock ourselves since nothing will
:: unblock us.
::
?: (~(has by results.state) block)
state
::
%_ state
sub-builds.blocked-builds
(~(put ju sub-builds.blocked-builds.state) build.made block)
::
client-builds.blocked-builds
(~(put ju client-builds.blocked-builds.state) block build.made)
::
candidate-builds
[block candidate-builds.state]
==
::
$(state-diffs t.state-diffs)
==
:: +dependencies-changed: did dependencies change since :previous-build?
::
++ dependencies-changed
|= =build
^- ?
?. ?=(%scry -.schematic.build)
|
::
=/ dependency dependency.schematic.build
::
?. ?=(%c -.dependency)
|
::
=/ updates (fall (~(get by dependency-updates.state) date.build) ~)
::
(~(has in updates) dependency)
:: +link-rebuilds: link old and new same build in :rebuilds.state
::
++ link-rebuilds
|= [old-build=build new-build=build]
^+ rebuilds.state
::
%_ rebuilds.state
old (~(put by old.rebuilds.state) new-build old-build)
new (~(put by new.rebuilds.state) old-build new-build)
==
:: +delete-root-once-listeners: remove once listeners on :build from :state
::
++ delete-root-once-listeners
|= =build
^+ state
::
%+ roll (root-once-listeners build)
|= [=listener accumulator=_state]
=. state accumulator
(remove-listener-from-build listener build)
--
:: +make: attempt to perform :build, non-recursively
::
:: Registers component linkages between :build and its sub-builds.
:: Attempts to perform +scry if necessary. Does not directly enqueue
:: any moves.
::
++ make
|= =build
^- state-diff
:: accessed-builds: builds accessed/depended on during this run.
::
=| accessed-builds=(list ^build)
:: dispatch based on the kind of +schematic in :build
::
|^ ?- -.schematic.build
::
^ (autocons [head tail]:schematic.build)
::
%$ (literal literal.schematic.build)
::
%pin (pin [date schematic]:schematic.build)
%alts (alts choices.schematic.build)
%bake !!
%bunt !!
%call !!
%cast !!
%core !!
%diff !!
%dude !!
%hood !!
%join !!
%mash !!
%mute !!
%pact !!
%path !!
%plan !!
%reef !!
%ride (ride [formula subject]:schematic.build)
%same (same schematic.schematic.build)
%slit !!
%slim (slim [subject-type formula]:schematic.build)
%scry (scry dependency.schematic.build)
%vale !!
%volt !!
==
:: |schematic-handlers:make: implementation of the schematics
::
:: All of these produce a value of the same type as +make itself.
::
:: +| schematic-handlers
::
++ autocons
|= [head=schematic tail=schematic]
^- state-diff
::
=^ head-result accessed-builds (depend-on head)
=^ tail-result accessed-builds (depend-on tail)
::
=| blocks=(list ^build)
=? blocks ?=(~ head-result) [[date.build head] blocks]
=? blocks ?=(~ tail-result) [[date.build tail] blocks]
:: if either build blocked, we're not done
::
?^ blocks
::
[build [%blocks blocks ~] accessed-builds |]
::
?< ?=(~ head-result)
?< ?=(~ tail-result)
::
=- [build [%build-result -] accessed-builds |]
`build-result`[%result u.head-result u.tail-result]
::
++ literal
|= =cage
^- state-diff
[build [%build-result %result %$ cage] accessed-builds |]
::
++ pin
|= [date=@da =schematic]
^- state-diff
::
=^ result accessed-builds (depend-on schematic)
::
?~ result
[build [%blocks [date schematic]~ ~] accessed-builds |]
[build [%build-result %result %pin date u.result] accessed-builds |]
::
++ alts
|= choices=(list schematic)
^- state-diff
::
?~ choices
:* build
[%build-result %error [leaf+"%alts: all options failed"]~]
accessed-builds
&
==
::
=^ result accessed-builds (depend-on i.choices)
?~ result
[build [%blocks [date.build i.choices]~ ~] accessed-builds &]
::
?: ?=([%error *] u.result)
$(choices t.choices)
::
[build [%build-result %result %alts u.result] accessed-builds &]
::
++ ride
|= [formula=hoon =schematic]
^- state-diff
::
=^ result accessed-builds (depend-on schematic)
?~ result
[build [%blocks [date.build schematic]~ ~] accessed-builds |]
::
=* subject u.result
=* subject-cage (result-to-cage subject)
=/ slim-schematic=^schematic [%slim p.q.subject-cage formula]
=^ slim-result accessed-builds (depend-on slim-schematic)
?~ slim-result
[build [%blocks [date.build slim-schematic]~ ~] accessed-builds |]
::
?: ?=(%error -.u.slim-result)
:* build
[%build-result %error [%leaf "%ride: "] message.u.slim-result]
accessed-builds
|
==
::
?> ?=([%result %slim *] u.slim-result)
::
=/ val
(mock [q.q.subject-cage nock.u.slim-result] intercepted-scry)
:: val is a toon, which might be a list of blocks.
::
?- -.val
::
%0
:* build
[%build-result %result %ride [type.u.slim-result p.val]]
accessed-builds
|
==
::
%1
=/ blocked-paths=(list path) ((hard (list path)) p.val)
::
=/ blocks-or-failures=(list (each ^build tank))
%+ turn blocked-paths
|= =path
::
=/ scry-request=(unit scry-request) (path-to-scry-request path)
?~ scry-request
[%| [%leaf "ford: %slim: invalid scry path: {<path>}"]]
::
=* case r.beam.u.scry-request
::
?. ?=(%da -.case)
[%| [%leaf "ford: %slim: invalid case in scry path: {<path>}"]]
::
=/ date=@da p.case
::
=/ dependency=(unit dependency) (path-to-dependency path)
?~ dependency
:- %|
[%leaf "ford: %slim: invalid dependency in scry path: {<path>}"]
::
=/ sub-schematic=^schematic [%pin date %scry u.dependency]
::
[%& `^build`[date sub-schematic]]
::
=/ failed=tang
%+ murn blocks-or-failures
|= block=(each ^build tank)
^- (unit tank)
?- -.block
%& ~
%| `p.block
==
::
?^ failed
:: some failed
::
[build [%build-result %error failed] accessed-builds |]
:: no failures
::
=/ blocks=(list ^build)
%+ turn blocks-or-failures
|= block=(each ^build tank)
?> ?=(%& -.block)
::
p.block
::
=. accessed-builds
%+ roll blocks
|= [block=^build accumulator=_accessed-builds]
=. accessed-builds accumulator
+:(depend-on schematic.block)
::
:: TODO: Here we are passing a single ~ for :scry-blocked. Should we
:: be passing one or multiple dependency back instead? Maybe not? Are
:: we building blocking schematics, which they themselves will scry?
::
[build [%blocks blocks ~] accessed-builds |]
::
%2
=/ message=tang [[%leaf "ford: %ride failed:"] p.val]
[build [%build-result %error message] accessed-builds |]
==
::
++ same
|= =schematic
^- state-diff
::
=^ result accessed-builds (depend-on schematic)
::
?~ result
[build [%blocks [date.build schematic]~ ~] accessed-builds |]
[build [%build-result %result %same u.result] accessed-builds |]
::
++ scry
:: TODO: All accesses to :state which matter happens in this function;
:: those calculations need to be lifted out of +make into +execute.
::
|= =dependency
^- state-diff
:: construct a full +beam to make the scry request
::
=/ beam (extract-beam dependency `date.build)
:: perform scry operation if we don't already know the result
::
:: Look up :dependency in :scry-results.per-event to avoid
:: rerunning a previously blocked +scry.
::
=/ scry-response
?: (~(has by scry-results) dependency)
(~(get by scry-results) dependency)
(^scry [%143 %noun] ~ `@tas`(cat 3 [vane care]:dependency) beam)
:: scry blocked
::
?~ scry-response
:: :build blocked on :dependency
::
:: Enqueue a request +move to fetch the blocked resource.
:: Link :block and :build in :blocks.state so we know
:: which build to rerun in a later event when we +unblock
:: on that +dependency.
::
=/ already-blocked=? (~(has by blocks.state) dependency)
:: store :dependency in persistent state
::
=. blocks.state (~(put ju blocks.state) dependency build)
::
?: already-blocked
:: this dependency was already blocked, so don't duplicate move
::
[build [%blocks ~ ~] accessed-builds |]
::
[build [%blocks ~ `dependency] accessed-builds |]
:: scry failed
::
?~ u.scry-response
=/ error=tang
:~ leaf+"scry failed for"
leaf+"%c{(trip care.dependency)} {<(en-beam beam)>}"
==
[build [%build-result %error error] accessed-builds |]
:: scry succeeded
::
[build [%build-result %result %scry u.u.scry-response] accessed-builds |]
::
++ slim
|= [subject-type=type formula=hoon]
::
=/ compiled=(each (pair type nock) tang)
(mule |.((~(mint ut subject-type) [%noun formula])))
::
:* build
?- -.compiled
%| [%build-result %error [leaf+"%slim failed: " p.compiled]]
%& [%build-result %result %slim p.compiled]
==
accessed-builds
|
==
:: |utilities:make: helper arms
::
::+| utilities
::
++ depend-on
|= kid=schematic
^- [(unit build-result) _accessed-builds]
=/ sub-build=^build [date.build kid]
::
=. accessed-builds [sub-build accessed-builds]
:: +access-cache will mutate :results.state
::
:: It's okay to ignore this because the accessed-builds get gathered
:: and merged during the +reduce step.
::
=/ maybe-cache-line -:(access-cache sub-build)
?~ maybe-cache-line
[~ accessed-builds]
::
=* cache-line u.maybe-cache-line
?: ?=(%tombstone -.cache-line)
[~ accessed-builds]
::
[`build-result.cache-line accessed-builds]
--
:: |utilities:per-event: helper arms
::
::+| utilities
::
++ this .
:: +intercepted-scry: use local results as a scry facade
::
++ intercepted-scry
%- sloy ^- slyd
|= [ref=* (unit (set monk)) =term =beam]
^- (unit (unit (cask)))
?> ?=([@ *] ref)
=/ hoon-version=@ud -.ref
=/ type=type ((hard type) +.ref)
::
~| hoon-version=hoon-version
?> ?=(?(%143 %151) hoon-version)
::
=/ vane=(unit ?(%c %g)) ((soft ?(%c %g)) (end 3 1 term))
?~ vane
~
=/ care=(unit care:clay) ((soft care:clay) (rsh 3 1 term))
?~ care
~
::
=/ dependency=dependency
[u.vane u.care rail=[[p.beam q.beam] s.beam]]
:: TODO: handle other kinds of +case
::
=/ date=@da
~| bad-case+r.beam
?> ?=(%da -.r.beam)
p.r.beam
::
=/ build=build [date %scry dependency]
:: if the actual scry produces a value, use that value; otherwise use local
::
=/ scry-response (scry +<.$)
::
?^ scry-response
scry-response
:: look up the scry result from our permanent state
::
:: Note: we can't freshen this cache entry because we can't modify
:: the state in this gate.
::
=/ local-result (~(get by results.state) build)
?~ local-result
~
?: ?=(%tombstone -.u.local-result)
~
::
=/ local-cage=cage (result-to-cage build-result.u.local-result)
:: if :local-result does not nest in :type, produce an error
::
?. -:(nets:wa type `^type`p.q.local-cage)
[~ ~]
::
[~ ~ `(cask)`local-cage]
:: +mark-as-done: store :build as complete and produce any unblocked clients
::
:: We may not know about these unblocked clients, so we register them in
:: the state.
::
++ mark-as-done
|= =build
^- [(list ^build) _state]
::
=/ client-builds=(list ^build)
~(tap in (fall (~(get by client-builds.blocked-builds.state) build) ~))
::
=. blocked-builds.state
%+ roll client-builds
::
|= [client=^build blocked-builds=_blocked-builds.state]
::
%_ blocked-builds
sub-builds
(~(del ju sub-builds.blocked-builds) client build)
::
client-builds
(~(del ju client-builds.blocked-builds) build client)
==
::
:_ state
::
%+ roll client-builds
::
|= [client=^build next-builds=(list ^build)]
::
?: (is-build-blocked client)
next-builds
[client next-builds]
:: +send-mades: send one %made move for :build per listener in :listeners
::
++ send-mades
|= [=build listeners=(list listener)] ^+ this
::
=^ result results.state (access-cache build)
::
?> ?=([~ %result *] result)
::
%_ this
moves
%+ roll listeners
|= [=listener moves=_moves]
::
:_ moves
:* duct.listener %give
%made date.build %complete build-result.u.result
==
==
:: +unlink-sub-builds
::
++ unlink-sub-builds
|= =build
^+ ..execute
::
=/ kids=(list ^build)
=- ~(tap in -)
%- ~(uni in (~(get ju sub-builds.components.state) build))
(~(get ju sub-builds.provisional-components.state) build)
::
=. components.state
%_ components.state
:: remove the mapping from :build to its sub-builds
::
sub-builds
(~(del by sub-builds.components.state) build)
:: for each +build in :kids, remove :build from its clients
::
client-builds
%+ roll kids
|= [kid=^build clients=_client-builds.components.state]
(~(del ju clients) kid build)
==
::
=. provisional-components.state
%_ provisional-components.state
:: remove the mapping from :build to its sub-builds
::
sub-builds
(~(del by sub-builds.provisional-components.state) build)
:: for each +build in :kids, remove :build from its clients
::
client-builds
%+ roll kids
|= [kid=^build clients=_client-builds.provisional-components.state]
(~(del ju clients) kid build)
==
::
%+ roll kids
|= [kid=^build accumulator=_..execute]
::
=. ..execute accumulator
(cleanup kid)
:: +promote-live-listeners: move live listeners from :old to :new
::
++ promote-live-listeners
|= [old=build new=build]
^+ state
::
=/ old-live-listeners=(list listener)
=- (skim - is-listener-live)
=- ~(tap in `(set listener)`(fall - ~))
(~(get by listeners.state) old)
::
=/ old-root-builds
~(tap in (fall (~(get by root-builds.state) old) ~))
::
=. state
%+ roll old-root-builds
|= [=listener state=_state]
::
?. (is-listener-live listener)
state
%_ state
::
root-builds
=- (~(put ju -) new listener)
(~(del ju root-builds.state) old listener)
::
builds-by-listener
(~(put by builds-by-listener.state) duct.listener [new &])
==
::
%+ roll old-live-listeners
|= [=listener accumulator=_state]
=. state accumulator
:: if :listener ain't live, we wrote this wrong
::
?> live.listener
::
=. listeners.state (~(put ju listeners.state) new listener)
::
(remove-listener-from-build listener old)
:: +root-live-listeners: live listeners for which :build is the root build
::
++ root-live-listeners
|= =build
^- (list listener)
::
(skim (root-listeners build) is-listener-live)
:: +root-once-listeners: once listeners for which :build is the root build
::
++ root-once-listeners
|= =build
^- (list listener)
::
(skip (root-listeners build) is-listener-live)
:: +root-listeners: listeners for which :build is the root build
::
++ root-listeners
|= =build
^- (list listener)
::
=- ~(tap in `(set listener)`(fall - ~))
(~(get by root-builds.state) build)
:: +is-build-blocked: is :build blocked on either builds or a dependency?
::
++ is-build-blocked
|= =build
^- ?
::
?: (~(has by sub-builds.blocked-builds.state) build)
&
?. ?=(%scry -.schematic.build)
|
(~(has by blocks.state) dependency.schematic.build build)
:: +is-build-cached:
::
++ is-build-cached
|= =build
^- ?
?=([~ %result *] (~(get by results.state) build))
:: +is-build-live: whether this is a live or a once build
::
++ is-build-live
|= =build
^- ?
::
?: ?=(%pin -.schematic.build)
%.n
?: (has-pinned-client build)
%.n
:: check if :build has any live listeners
::
=/ listeners ~(tap in (fall (~(get by listeners.state) build) ~))
?~ listeners
%.y
(lien `(list listener)`listeners is-listener-live)
:: +has-pinned-client: %.y if any of our ancestors are a %pin
::
++ has-pinned-client
|= =build
^- ?
:: iterate across all clients recursively, exiting early on %pin
::
=/ clients ~(tap in (~(get ju client-builds.components.state) build))
|-
?~ clients
%.n
?: ?=(%pin -.schematic.i.clients)
%.y
%_ $
clients
%+ weld t.clients
~(tap in (~(get ju client-builds.components.state) i.clients))
==
:: +access-cache: access the +cache-line for :build, updating :last-accessed
::
:: Usage:
:: ```
:: =^ maybe-cache-line results.state (access-cache build)
:: ```
::
++ access-cache
|= =build
^- [(unit cache-line) _results.state]
::
=/ maybe-original=(unit cache-line) (~(get by results.state) build)
?~ maybe-original
[~ results.state]
::
=/ original=cache-line u.maybe-original
::
?: ?=(%tombstone -.original)
[`original results.state]
::
=/ mutant=cache-line original(last-accessed now)
::
[`mutant (~(put by results.state) build mutant)]
:: +finalize: convert per-event state to moves and persistent state
::
:: Converts :done-live-roots to %made +move's, performs +duct
:: accounting, and runs +cleanup on completed once builds and
:: stale live builds.
::
++ finalize
^- [(list move) ford-state]
:: once we're done, +flop :moves to put them in chronological order
::
=< [(flop moves) state]
::
=/ discs ~(tap in dirty-discs)
::
|- ^+ this
?~ discs this
::
=* disc i.discs
:: dependencies: all dependencies on :disc
::
=/ dependencies=(set dependency)
(fall (~(get by dependencies.state) disc) ~)
:: if no dependencies on :disc, don't make a new clay subscription
::
?~ dependencies
:: cancel clay subscriptions when we don't have any dependencies left
::
?: (~(has in original-clay-subscriptions) disc)
=+ [their desk]=disc
=/ note=note
:^ %c %warp sock=[our their]
^- riff:clay
[desk ~]
::
=. moves :_ moves
^- move
[duct=~ [%pass wire=(clay-sub-wire disc) note]]
::
=. clay-subscriptions.state (~(del in clay-subscriptions.state) disc)
::
=. latest-by-disc.state (~(del by latest-by-disc.state) disc)
::
$(discs t.discs)
::
$(discs t.discs)
:: prevent thrashing; don't unsubscribe then immediately resubscribe
::
:: When we send a request to a foreign ship, that ship may have
:: started responding before we send a cancellation. In that case,
:: cancelling and then resubscribing might cause the foreign ship
:: to send the response twice, which would be extra network traffic.
::
?: ?& (~(has in original-clay-subscriptions) disc)
::
(~(has in clay-subscriptions.state) disc)
::
.= (~(get by original-dependencies) disc)
(~(get by dependencies.state) disc)
==
::
$(discs t.discs)
:: request-contents: the set of [care path]s to subscribe to in clay
::
=/ request-contents=(set [care:clay path])
%- sy ^- (list [care:clay path])
%+ murn ~(tap in `(set dependency)`dependencies)
|= =dependency ^- (unit [care:clay path])
::
?. ?=(%c -.dependency) ~
::
`[care.dependency (flop spur.rail.dependency)]
:: if :request-contents is `~`, this code is incorrect
::
?< ?=(~ request-contents)
:: their: requestee +ship
::
=+ [their desk]=disc
=/ latest-date (~(got by latest-by-disc.state) disc)
::
=/ note=note
:^ %c %warp sock=[our their]
^- riff:clay
[desk `[%mult case=[%da latest-date] request-contents]]
::
=. moves :_ moves
^- move
[duct=~ [%pass wire=(clay-sub-wire disc) note]]
::
=. clay-subscriptions.state (~(put in clay-subscriptions.state) disc)
::
$(discs t.discs)
:: +cleanup: try to clean up a build and its sub-builds
::
++ cleanup
|= =build
^+ this
:: does this build even exist?!
::
?. (~(has ju builds-by-date.state) date.build schematic.build)
this
::
:: if something depends on this build, no-op and return
::
?: ?| (~(has by client-builds.components.state) build)
(~(has by client-builds.provisional-components.state) build)
(~(has by old.rebuilds.state) build)
(~(has by listeners.state) build)
==
:: ~& :* %cleanup-no-op
:: build=(build-to-tape build)
:: has-client-builds=(~(has by client-builds.components.state) build)
:: has-provisional=(~(has by client-builds.provisional-components.state) build)
:: has-old-rebuilds=(~(has by old.rebuilds.state) build)
:: listeners=(~(get by listeners.state) build)
:: ==
this
:: ~& [%cleaning-up (build-to-tape build)]
:: remove :build from :state, starting with its cache line
::
=. results.state (~(del by results.state) build)
:: remove :date.build from list of dates for this schematic
::
=. builds-by-schematic.state
(~(del by-schematic builds-by-schematic.state) build)
:: remove :build from :builds-by-date
::
=. builds-by-date.state
(~(del ju builds-by-date.state) date.build schematic.build)
:: if no more builds at this date, remove the date from :dependency-updates
::
=? dependency-updates.state
!(~(has by builds-by-date.state) date.build)
(~(del by dependency-updates.state) date.build)
::
=? blocks.state
::
?=(%scry -.schematic.build)
::
=* dependency dependency.schematic.build
::
(~(del ju blocks.state) dependency build)
:: check if :build depends on a live clay +dependency
::
=/ has-live-dependency ?=([%scry %c *] schematic.build)
:: clean up dependency tracking and maybe cancel clay subscription
::
=? this has-live-dependency
:: type system didn't know, so tell it again
::
?> ?=([%scry %c *] schematic.build)
::
=/ dependency dependency.schematic.build
=/ disc=disc (extract-disc dependency)
::
=/ should-delete-dependency=?
:: checks if there are other live builds of this dependency
::
=/ dates=(list @da)
(fall (~(get by builds-by-schematic.state) schematic.build) ~)
?!
%+ lien dates
|= date=@da
^- ?
=/ other-build [date schematic.build]
=/ listeners=(set listener)
(fall (~(get by listeners.state) other-build) ~)
::
(lien ~(tap in listeners) is-listener-live)
::
=? dependencies.state should-delete-dependency
(~(del ju dependencies.state) disc dependency)
::
=? dirty-discs should-delete-dependency
(~(put in dirty-discs) disc)
::
this
:: this also recurses on our children
::
=. ..execute (unlink-sub-builds build)
:: if there is a newer rebuild of :build, delete the linkage
::
=/ rebuild (~(get by new.rebuilds.state) build)
=? rebuilds.state ?=(^ rebuild)
%_ rebuilds.state
new (~(del by new.rebuilds.state) build)
old (~(del by old.rebuilds.state) u.rebuild)
==
:: recurse on :rebuild; note this must be done after recursing on :kids
::
?~ rebuild
this
(cleanup u.rebuild)
:: +clay-sub-wire: the wire to use for a clay subscription
::
++ clay-sub-wire
|= =disc ^- wire
::
=+ [their desk]=disc
::
/(scot %p our)/clay-sub/(scot %p their)/[desk]
--
--
::
:: end =~
::
. ==
=, ford-api :: TODO remove once in vane
::
:::: vane core
::
=| axle
|= [now=@da eny=@ scry=sley]
:: allow jets to be registered within this core
::
~% %ford-d ..is ~ :: XX why the '-d'?
::
:: ^? :: to be added to real vane
::
|%
:: +call: handle a +task:able from arvo
::
++ call
|= [=duct type=* wrapped-task=(hobo task:able)]
^- [(list move) _this]
:: unwrap :task from :wrapped-task
::
=/ task=task:able
?. ?=(%soft -.wrapped-task)
wrapped-task
((hard task:able) p.wrapped-task)
::
?- -.task
:: %make: request to perform a build
::
%make
:: perform the build indicated by :task
::
:: First, we find or create the :ship-state for :our.task,
:: modifying :state-by-ship as necessary. Then we dispatch to the |ev
:: by constructing :event-args and using them to create :start-build,
:: which performs the build. The result of :start-build is a pair of
:: :moves and a mutant :ship-state. We update our :state-by-ship map
:: with the new :ship-state and produce it along with :moves.
::
=^ ship-state state-by-ship (find-or-create-ship-state our.task)
=* event-args [[our.task duct now scry] ship-state]
=* start-build start-build:(per-event event-args)
=^ moves ship-state (start-build schematic.task)
=. state-by-ship (~(put by state-by-ship) our.task ship-state)
::
[moves this]
::
:: %kill: cancel a %make
::
%kill
::
=/ ship-state ~|(our+our.task (~(got by state-by-ship) our.task))
=* event-args [[our.task duct now scry] ship-state]
=^ moves ship-state cancel:(per-event event-args)
=. state-by-ship (~(put by state-by-ship) our.task ship-state)
::
[moves this]
::
%wipe !!
::
%wegh !!
==
:: +take: receive a response from another vane
::
++ take
|= [=wire =duct wrapped-sign=(hypo sign)]
^- [(list move) _this]
:: unwrap :sign from :wrapped-sign
::
:: TODO: verify wrapped-sign isn't an evil vase?
::
=/ sign=sign q.wrapped-sign
:: TODO: support other responses
::
:: parse :wire into :our, :ship-state, and :dependency
::
?> ?=([@ @ *] wire)
:: we know :our is already in :state-by-ship because we sent this request
::
=/ our=@p (slav %p i.wire)
=/ ship-state ~|(our+our (~(got by state-by-ship) our))
=* event-args [[our duct now scry] ship-state]
:: %clay-sub: response to a clay %mult subscription
::
=^ moves ship-state
?: =(%clay-sub i.t.wire)
::
?> ?=([%c %wris *] sign)
=+ [ship desk]=(raid:wired t.t.wire ~[%p %tas])
::
=* rebuild rebuild:(per-event event-args)
(rebuild [ship desk case.sign] care-paths.sign)
:: %dependency: response to a request for a +dependency
::
?. =(%dependency i.t.wire)
::
~|(unknown-take+i.t.wire !!)
::
?> ?=([%c %writ *] sign)
:: dependency: the +dependency we had previously blocked on
::
=/ dependency
~| [%bad-dependency wire]
(need (path-to-dependency t.t.wire))
:: scry-result: parse a (unit cage) from :sign
::
:: If the result is `~`, the requested resource was not available.
::
=/ scry-result=(unit cage)
?~ riot.sign
~
`r.u.riot.sign
:: unblock the builds that had blocked on :dependency
::
=* unblock unblock:(per-event event-args)
(unblock dependency scry-result)
::
=. state-by-ship (~(put by state-by-ship) our ship-state)
::
[moves this]
:: %utilities
::
::+|
::
++ this .
:: +find-or-create-ship-state: find or create a ford-state for a @p
::
:: Accesses and modifies :state-by-ship.
::
++ find-or-create-ship-state
|= our=@p
^- [ford-state _state-by-ship]
::
=/ existing (~(get by state-by-ship) our)
?^ existing
[u.existing state-by-ship]
::
=| new-state=ford-state
[new-state (~(put by state-by-ship) our new-state)]
--