kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

resolve.h (10378B)


      1 #ifndef KIT_BUILD_RESOLVE_H
      2 #define KIT_BUILD_RESOLVE_H
      3 
      4 #include <kit/core.h>
      5 #include <stddef.h>
      6 #include <stdint.h>
      7 
      8 #include "build.h"
      9 #include "cfg.h"
     10 #include "coord.h"
     11 
     12 /*
     13  * The resolution algorithm: resolve(T, cfg, chain) -> {output-tree, path,
     14  * leafset}, doing the least work necessary. Three phases (see doc/BUILD_COORDINATOR.md
     15  * §Resolution algorithm):
     16  *
     17  *   Phase 1  deep fast path  - same argv-id, matching target-scope projected
     18  *                              config observations, and an all-clear refresh of
     19  *                              the deepset closure (every source/glob/blob leaf
     20  *                              unchanged AND every node's recipe-id still
     21  *                              recomputes the same) => reuse, no graph walk.
     22  *   Phase 2  shallow path    - config, argv, and/or sources moved: guard on the
     23  *                              recipe-id, argv-id, consumed keys + direct leaves,
     24  *                              then re-resolve each recorded dep under the
     25  *                              current parent config plus its recorded overlay,
     26  *                              with its recorded argv, and compare OUTPUT tree-ids;
     27  *                              a match still skips the recipe (and writes a fresh
     28  *                              deep trace + deepset).
     29  *   Phase 3  run the recipe  - no trace holds (runner.h).
     30  *
     31  * On a local-record miss, resolution first attempts one lazy trace-remote pull
     32  * (build_coord_trace_remote_pull_once) and re-scans before Phase 3. The config-
     33  * dependence is recorded in and verified against the traces; the deep path
     34  * checks target-scope projected consumed keys + argv-id, the shallow path checks
     35  * direct consumed keys + argv-id. Recipe identity rides as a per-node scalar in
     36  * the deepset (Phase 1) and as the recipe-id guard (Phase 2); there is NO
     37  * whole-definition dependency. Cross-path dedup is the coordinator's targets
     38  * memo; per-path cycle detection is the build chain below.
     39  */
     40 
     41 /* One frame of the per-path build chain: the (target, config-id, argv-id) at one
     42  * level, linked to its parent (NULL at the root). The chain is a parent-linked
     43  * "cactus stack", NOT a mutable array — each dispatched `need` extends the
     44  * shared, read-only parent prefix with its own frame, so concurrent siblings
     45  * never copy or mutate one another's chain (the property the future-based `need`
     46  * fan-out requires). A `need` whose triple already appears on the walk from a
     47  * frame up to the root is a dependency cycle; walking the links renders the error
     48  * path (//a -> //b -> //a). Per-path, distinct from the cross-path futures memo:
     49  * a shared dep appears in many chains, never twice in one. Frames are allocated
     50  * off the coordinator's per-build arena and live until the build completes
     51  * (parents outlive their concurrent children by construction — no pop). */
     52 typedef struct BuildChainFrame {
     53   const struct BuildChainFrame* parent; /* NULL at the root request */
     54   BuildActionKind action;
     55   char target[BUILD_TARGET_MAX];
     56   uint8_t config_id[BUILD_HASH_LEN];
     57   uint8_t argv_id[BUILD_HASH_LEN];
     58 } BuildChainFrame;
     59 
     60 /* Extend `parent` (NULL at the root) with (target, config-id, argv-id), returning
     61  * the new tip in *out. Walks parent links to detect a cycle (the triple already
     62  * present) -> BUILD_ERR with the rendered path in err. The frame is allocated off
     63  * the coordinator's per-build arena; there is no pop (frames are immutable). */
     64 int build_chain_extend(KitBuildCoordinator*, const BuildChainFrame* parent,
     65                        BuildActionKind action, KitSlice target,
     66                        const uint8_t config_id[BUILD_HASH_LEN],
     67                        const uint8_t argv_id[BUILD_HASH_LEN],
     68                        const BuildChainFrame** out, char* err, size_t errcap);
     69 
     70 /* Resolve a target under the effective propagated config `cfg` and effective
     71  * local `argv` (cfg carries its config-id; argv hashes to its argv-id) — the
     72  * full (target, config-id, argv-id) identity. `chain` is the parent frame tip
     73  * (NULL at the root); this call extends it. Equivalent to dispatch+await on the
     74  * same thread (used by the top-level request and the fused one-shot `need`). On
     75  * success *out holds the output tree-id, a materialized path, and the deepset
     76  * node (its closure DAG root). Errors (cycle, recipe failure, missing inputs)
     77  * surface through the coordinator's diag. */
     78 int build_resolve(KitBuildCoordinator*, KitSlice target, const BuildConfig* cfg,
     79                   const BuildArgv* argv, const BuildChainFrame* chain,
     80                   BuildResolved* out);
     81 
     82 int build_test_resolve(KitBuildCoordinator*, KitSlice target,
     83                        const BuildConfig* cfg, const BuildArgv* argv,
     84                        const BuildChainFrame* chain, BuildTestResolved* out);
     85 
     86 /* The async half of resolution — the engine behind recipe-side `need_submit`.
     87  * Extends `chain` (cycle-checked here, so submit fails fast on a cycle), interns
     88  * the (target, config-id, argv-id) future, and — if fresh — drives the 3 phases
     89  * on a worker thread (build_coord_spawn) that completes the future; a duplicate
     90  * attaches to the in-flight future with no new thread. Returns the future in
     91  * *out_future WITHOUT blocking, so a recipe can submit many and let them resolve
     92  * concurrently. The result is collected later via build_coord_target_await.
     93  * `build_resolve` is the inline sibling (dispatch's phases run on the caller's
     94  * thread); in sequential mode (no sched) dispatch resolves inline and returns an
     95  * already-completed future, so submit/await stays semantically transparent. */
     96 int build_dispatch(KitBuildCoordinator*, KitSlice target, const BuildConfig* cfg,
     97                    const BuildArgv* argv, const BuildChainFrame* chain,
     98                    BuildTargetFuture** out_future, char* err, size_t errcap);
     99 
    100 /* Phase 3 of resolution: run the recipe — reached when no trace holds, or a
    101  * hit's bytes were evicted everywhere (build_materialize returned BUILD_ERR).
    102  * resolve and runner form one strongly-connected component (resolve calls
    103  * build_run_recipe for Phase 3; runner's service loop calls build_resolve for
    104  * each `need`): NOT a DAG edge. To keep that recursion compiling without a
    105  * resolve.h -> runner.h include, build_run_recipe is DECLARED here (the Phase-3
    106  * continuation of build_resolve) and IMPLEMENTED in runner.c (which owns the
    107  * spawn + service-loop machinery). Same *out contract as build_resolve. */
    108 int build_run_recipe(KitBuildCoordinator*, KitSlice target,
    109                      const BuildConfig* cfg, const BuildArgv* argv,
    110                      const BuildChainFrame* chain, BuildResolved* out);
    111 
    112 /* Verification-only sibling of build_run_recipe: executes the recipe and
    113  * ingests/materializes the fresh output tree, but writes NO shallow/deep trace
    114  * and does not update the target record. Used by verify mode to audit a cache
    115  * hit without replacing the cache's claim before the comparison succeeds. */
    116 int build_run_recipe_probe(KitBuildCoordinator*, KitSlice target,
    117                            const BuildConfig* cfg, const BuildArgv* argv,
    118                            const BuildChainFrame* chain, BuildResolved* out);
    119 int build_run_test_recipe(KitBuildCoordinator*, KitSlice target,
    120                           const BuildConfig* cfg, const BuildArgv* argv,
    121                           const BuildChainFrame* chain, BuildTestResolved* out,
    122                           int record_traces);
    123 
    124 /* The materialization ladder shared by every cache hit:
    125  *   tree cache/ -> local CAS -> remote object fetch.
    126  * A PURE locator: writes the materialized directory path and returns BUILD_OK,
    127  * or BUILD_ERR when the bytes are gone everywhere (not in the cache, not in the
    128  * CAS, no remote supplied them). It NEVER runs a recipe — on BUILD_ERR the
    129  * caller (resolve) falls through to Phase 3, which rebuilds and records the
    130  * correct output. Keeping it recipe-free removes the resolve->runner edge and
    131  * the stale-tree-id hazard a rebuild-inside-materialize would create (the cache
    132  * hit's recorded tree-id always describes the bytes returned). */
    133 int build_materialize(KitBuildCoordinator*,
    134                       const uint8_t tree_id[BUILD_HASH_LEN], char* path_out,
    135                       size_t cap);
    136 
    137 /* Build a parent's deepset node from its DIRECT leaves, awaited dep edges, and
    138  * child nodes. `direct` carries this node's target, recipe-id, and direct leaves
    139  * (its children fields empty); `deps` and `children` are aligned arrays for the
    140  * awaited deps. The node stores this target's projected config closure: direct
    141  * config observations plus child config observations whose keys were not
    142  * overlaid by that dep edge. Computes
    143  * the deep-set-id, stores the canonical node as a CAS blob (kit_cas_add_blob —
    144  * the children's blobs were stored when they were built, so this is incremental
    145  * and deduped), and returns the interned node. This IS the closure union —
    146  * "concat direct leaves + projected config leaves + child ids, hash" — never a
    147  * flat N-way merge. */
    148 int build_leafset_union(KitBuildCoordinator*, const BuildLeafSet* direct,
    149                         const BuildDepEdge* deps,
    150                         const BuildLeafSet* const* children, size_t nchildren,
    151                         const BuildLeafSet** out);
    152 
    153 /* Refresh a deepset node against the live workspace: per node, rehash its direct
    154  * source leaves (absent stays absent), reglob its glob leaves, check its fetched
    155  * blobs are still present in the CAS, check the root node's projected config
    156  * observations against the query config, recompute each node's
    157  * recipe-id through the LIVE definition (catching a dep repointed to a
    158  * different-content recipe, treating same-content as unchanged), and recurse
    159  * into children — all through the per-process memos and the deep-set-id validity
    160  * cache (build_coord_deepset_valid_*), so a shared subtree is checked once and
    161  * an unchanged subtree is skipped by id equality without descending. BUILD_OK +
    162  * *all_match==1 when the whole closure still matches; *all_match==0 on the first
    163  * divergence (Phase 1 then gives up). Phase 1 calls build_coord_deepset_load to
    164  * turn the trace's deep-set-id into the node passed here. */
    165 int build_leafset_refresh(KitBuildCoordinator*, const BuildLeafSet*,
    166                           const BuildConfig*, int* all_match);
    167 
    168 #endif