build_coord.c (58191B)
1 #include <kit/build_coord.h> 2 #include <kit/cas.h> 3 #include <kit/core.h> 4 5 #include <stddef.h> 6 #include <stdint.h> 7 #include <stdio.h> 8 #include <stdlib.h> 9 #include <string.h> 10 11 #include "dist_host.h" 12 #include "driver.h" 13 #include "env.h" 14 #include "env_build.h" 15 16 #define BUILD_TOOL "build" 17 #define BUILD_DEFAULT_DEF "BUILD.kit" 18 #define BUILD_MAX_CONFIG 128u 19 #define BUILD_MAX_CONFIG_KEY 64u 20 #define BUILD_MAX_ARGS 128u 21 22 typedef enum BuildClientFormat { 23 BUILD_CLIENT_FORMAT_PATH = 0, 24 BUILD_CLIENT_FORMAT_ID = 1, 25 BUILD_CLIENT_FORMAT_ID_PATH = 2, 26 } BuildClientFormat; 27 28 typedef struct BuildCli { 29 const char* store; 30 const char* root; 31 const char* def; 32 const char* profile; 33 const char* target; 34 const char* config_default; 35 KitBuildKV config[BUILD_MAX_CONFIG]; 36 char config_keys[BUILD_MAX_CONFIG][BUILD_MAX_CONFIG_KEY]; 37 size_t nconfig; 38 KitSlice args[BUILD_MAX_ARGS]; 39 size_t nargs; 40 int verify; 41 int stats; 42 int trace; 43 int test_mode; 44 int has_config_default; 45 int source_optional; 46 int has_format; 47 BuildClientFormat format; 48 } BuildCli; 49 50 typedef struct BuildListTarget { 51 char label[KIT_BUILD_TARGET_MAX]; 52 } BuildListTarget; 53 54 void driver_help_build(void) { 55 driver_printf( 56 "kit build - content-addressed build coordinator\n" 57 "\n" 58 "USAGE\n" 59 " kit build [--store DIR] [--root DIR] [--def FILE] [--profile NAME]\n" 60 " [--config K=V|env.NAME]... [--env NAME[=V]]...\n" 61 " [--verify] [--stats] [--trace] TARGET [-- ARG...]\n" 62 " kit build test [--store DIR] [--root DIR] [--def FILE] [--profile NAME]\n" 63 " [--config K=V|env.NAME]... [--env NAME[=V]]...\n" 64 " [--verify] [--stats] [--trace] TARGET [-- ARG...]\n" 65 " kit build [--root DIR] [--def FILE] list [--scan-do] [//PACKAGE[/...]]\n" 66 " kit build [--root DIR] repo add NAME URL\n" 67 " [--format tar.gz|kpkg] [--strip-prefix DIR]\n" 68 " kit build [--root DIR] repo fetch NAME\n" 69 " kit build [--root DIR] repo list\n" 70 " kit build [--root DIR] workspace package [--format tar.gz] -o OUT\n" 71 " kit build config-get [--default VALUE] KEY # inside a build recipe\n" 72 " kit build source [--optional] [--format path|id|id-path] PATH\n" 73 " # inside a build recipe\n" 74 " kit build fetch [--format path|id|id-path] BLOB URL...\n" 75 " # inside a build recipe\n" 76 " kit build depfile [--lines] FILE # inside a build recipe\n" 77 " kit build glob PATTERN # inside a build recipe\n" 78 " kit build export-set VAR -- VALUE... # inside a build recipe\n" 79 " kit build export-get TARGET VAR # inside a build recipe\n" 80 " kit build export-collect VAR TARGET... # inside a build recipe\n" 81 " kit build need [--format path|id|id-path]\n" 82 " [--config K=V|env.NAME]... [--env NAME[=V]]...\n" 83 " TARGET [-- ARG...]\n" 84 " # inside a build recipe\n" 85 " kit build need-submit [--config K=V|env.NAME]... [--env NAME[=V]]...\n" 86 " TARGET [-- ARG...]\n" 87 " # inside a build recipe\n" 88 " kit build need-await [--format path|id|id-path] TOKEN\n" 89 " # inside a build recipe\n" 90 "\n" 91 "BUILD DEFINITIONS AND LABELS\n" 92 " The default package definition is BUILD.kit under --root (or the\n" 93 " basename selected by WORKSPACE.kit). A minimal definition is:\n" 94 "\n" 95 " kit-build 1\n" 96 " [target copy]\n" 97 " recipe recipes/copy.sh\n" 98 "\n" 99 " The command-line TARGET names a [target NAME] in the current package.\n" 100 " `copy` at the workspace root is rendered canonically as `//:copy` in\n" 101 " traces. Package paths are resolved relative to --root; recipes and\n" 102 " source paths are package-relative. Arguments after -- participate in\n" 103 " the target key and are available to the recipe protocol.\n" 104 "\n"); 105 driver_printf( 106 "RECIPE PROTOCOL\n" 107 " A recipe is an executable that writes its output tree below\n" 108 " $KIT_BUILD_OUT. It receives a clean environment containing\n" 109 " KIT_BUILD_SOCK, KIT_BUILD_OUT, KIT_BUILD_TARGET, KIT_BUILD_REPO,\n" 110 " KIT_BUILD_PACKAGE, and KIT_BUILD_LOCAL, plus values explicitly\n" 111 " propagated with --env/--config env.NAME.\n" 112 "\n" 113 " Recipe-only `source` records a source dependency and prints its\n" 114 " workspace path or requested id form. `need` records another\n" 115 " target dependency; need-submit/need-await permit parallel requests.\n" 116 " config-get, fetch, depfile, and glob also require KIT_BUILD_SOCK and\n" 117 " are rejected outside a running recipe. export-set publishes ordered\n" 118 " metadata in the output tree; export-get/collect read it through need\n" 119 " dependencies. Use `kit build --client VERB ...` to select recipe\n" 120 " client mode explicitly.\n" 121 "\n" 122 "WORKSPACE COMMANDS\n" 123 " list prints targets in one package; a trailing /... scans packages\n" 124 " recursively. --scan-do lists Redo-style do/*.do rules instead. repo\n" 125 " add hashes a local path or file:// archive and records the pinned\n" 126 " entry in WORKSPACE.kit. repo fetch validates that a configured name\n" 127 " exists; external content is fetched and materialized when its label is\n" 128 " resolved. repo list prints the configured names, formats, and ids.\n" 129 " workspace package writes the workspace as a tar.gz using the hosted\n" 130 " system tar. Signed kpkg creation is provided by `kit pkg create`.\n" 131 "\n" 132 "OPTIONS\n" 133 " --store DIR Build store root (default: Kit platform cache/build)\n" 134 " --root DIR Workspace root (default: .)\n" 135 " --def FILE Package build-file basename (alias: --def-name;\n" 136 " default: WORKSPACE def-name or BUILD.kit)\n" 137 " --profile NAME Apply WORKSPACE.kit [config NAME] after [config default]\n" 138 " --config K=V Seed propagated configuration\n" 139 " --config env.N Seed env.N from current $N\n" 140 " --env N[=V] Shorthand for --config env.N[=V]\n" 141 " --verify Verify the returned output tree in the CAS\n" 142 " --trace Print target-level resolution decisions to stderr\n" 143 " --stats Print build-resolution counters to stderr\n" 144 " -h, --help Show this help and exit\n" 145 " --version Show Kit version and exit\n" 146 "\n" 147 "OUTPUT AND CACHING\n" 148 " A successful top-level build prints `TREE_ID MATERIALIZED_PATH` to\n" 149 " stdout. --trace/--stats write diagnostics and counters to stderr. The\n" 150 " recipe output tree is content-addressed; an unchanged definition,\n" 151 " configuration, arguments, and recorded dependencies may produce a\n" 152 " deep cache hit with recipes_run=0. --verify checks the returned tree.\n"); 153 driver_printf( 154 "\n" 155 "EXAMPLES\n" 156 " mkdir -p demo/recipes\n" 157 " printf '%%s\\n' 'kit-build 1' '[target copy]' \\\n" 158 " 'recipe recipes/copy.sh' > demo/BUILD.kit\n" 159 " printf '%%s\\n' '#!/bin/sh' 'set -eu' \\\n" 160 " 'printf \"%%s\\n\" built > \"$KIT_BUILD_OUT/result.txt\"' \\\n" 161 " > demo/recipes/copy.sh\n" 162 " chmod +x demo/recipes/copy.sh\n" 163 " kit build --root \"$PWD/demo\" \\\n" 164 " --verify --stats copy\n" 165 " # Repeat the same command to observe a cache hit.\n" 166 "\n" 167 "EXIT CODES\n" 168 " 0 success 1 resolution/recipe/CAS/I/O error 2 bad usage\n"); 169 } 170 171 static const char* build_status_name(KitStatus st) { 172 switch (st) { 173 case KIT_OK: return "ok"; 174 case KIT_ERR: return "error"; 175 case KIT_NOMEM: return "out of memory"; 176 case KIT_INVALID: return "invalid input"; 177 case KIT_UNSUPPORTED: return "unsupported"; 178 case KIT_MALFORMED: return "malformed input"; 179 case KIT_IO: return "I/O error"; 180 case KIT_NOT_FOUND: return "not found"; 181 case KIT_AMBIGUOUS: return "ambiguous input"; 182 } 183 return "error"; 184 } 185 186 static int build_join_path(char* out, size_t cap, const char* a, 187 const char* b) { 188 size_t na, nb; 189 int sep; 190 if (!out || cap == 0u || !a || !b) return 1; 191 na = driver_strlen(a); 192 nb = driver_strlen(b); 193 sep = na > 0u && a[na - 1u] != '/' && a[na - 1u] != '\\'; 194 if (na + (sep ? 1u : 0u) + nb + 1u > cap) return 1; 195 memcpy(out, a, na); 196 if (sep) out[na++] = '/'; 197 memcpy(out + na, b, nb); 198 out[na + nb] = '\0'; 199 return 0; 200 } 201 202 static int build_def_name_valid(const char* s) { 203 size_t i; 204 if (!s || !s[0]) return 0; 205 for (i = 0; s[i]; ++i) { 206 unsigned char c = (unsigned char)s[i]; 207 if (c == '/' || c == '\\' || c == ':' || c == '\n' || c == '\r') 208 return 0; 209 } 210 return 1; 211 } 212 213 static int build_load_file(DriverEnv* env, const char* path, DriverLoad* load, 214 KitSlice* bytes) { 215 return driver_load_bytes(&env->file_io, BUILD_TOOL, path, load, bytes); 216 } 217 218 static int build_load_workspace(DriverEnv* env, const char* root, 219 KitBuildWorkspace* ws, int* present) { 220 char path[KIT_BUILD_PATH_MAX]; 221 DriverLoad load; 222 KitSlice bytes; 223 char err[256]; 224 if (present) *present = 0; 225 if (build_join_path(path, sizeof path, root, "WORKSPACE.kit") != 0) return 1; 226 if (!driver_path_exists(path)) { 227 kit_build_workspace_init(ws); 228 return 0; 229 } 230 if (present) *present = 1; 231 if (build_load_file(env, path, &load, &bytes) != 0) return 1; 232 if (kit_build_workspace_parse(bytes.data, bytes.len, ws, err, sizeof err) != 233 KIT_OK) { 234 driver_errf(BUILD_TOOL, "bad WORKSPACE.kit: %s", err); 235 driver_release_bytes(&env->file_io, &load); 236 return 1; 237 } 238 driver_release_bytes(&env->file_io, &load); 239 return 0; 240 } 241 242 static int build_file_write(DriverEnv* env, const char* path, const char* text) { 243 KitWriter* w = NULL; 244 size_t n; 245 if (!env || !path || !text) return 1; 246 if (env->file_io.open_writer(env->file_io.user, path, &w) != KIT_OK || !w) 247 return 1; 248 n = driver_strlen(text); 249 if ((n && kit_writer_write(w, text, n) != KIT_OK) || 250 kit_writer_status(w) != KIT_OK) { 251 kit_writer_close(w); 252 return 1; 253 } 254 kit_writer_close(w); 255 return 0; 256 } 257 258 static int build_file_append(DriverEnv* env, const char* path, 259 const char* text) { 260 DriverLoad load; 261 KitSlice old = KIT_SLICE_NULL; 262 KitWriter* w = NULL; 263 size_t n; 264 int had_old = 0; 265 if (!env || !path || !text) return 1; 266 if (driver_path_exists(path)) { 267 if (build_load_file(env, path, &load, &old) != 0) return 1; 268 had_old = 1; 269 } 270 if (env->file_io.open_writer(env->file_io.user, path, &w) != KIT_OK || !w) { 271 if (had_old) driver_release_bytes(&env->file_io, &load); 272 return 1; 273 } 274 n = driver_strlen(text); 275 if ((old.len && kit_writer_write(w, old.data, old.len) != KIT_OK) || 276 (n && kit_writer_write(w, text, n) != KIT_OK) || 277 kit_writer_status(w) != KIT_OK) { 278 kit_writer_close(w); 279 if (had_old) driver_release_bytes(&env->file_io, &load); 280 return 1; 281 } 282 kit_writer_close(w); 283 if (had_old) driver_release_bytes(&env->file_io, &load); 284 return 0; 285 } 286 287 static int build_file_url_path(const char* url, const char** out) { 288 if (!url || !out || !driver_strneq(url, "file://", 7u)) return 1; 289 *out = url + 7u; 290 return 0; 291 } 292 293 static int build_list_cmp(const void* a, const void* b) { 294 const BuildListTarget* la = (const BuildListTarget*)a; 295 const BuildListTarget* lb = (const BuildListTarget*)b; 296 return strcmp(la->label, lb->label); 297 } 298 299 static int build_list_add(BuildListTarget* out, size_t* n, size_t cap, 300 const char* label) { 301 size_t i; 302 if (!out || !n || !label || !label[0]) return 1; 303 for (i = 0; i < *n; ++i) 304 if (driver_streq(out[i].label, label)) return 0; 305 if (*n >= cap || driver_strlen(label) >= sizeof out[0].label) return 1; 306 snprintf(out[*n].label, sizeof out[*n].label, "%s", label); 307 ++*n; 308 return 0; 309 } 310 311 static void build_pkg_label(char* out, size_t cap, const char* pkg, 312 const char* local) { 313 if (pkg && pkg[0]) 314 snprintf(out, cap, "//%s:%s", pkg, local); 315 else 316 snprintf(out, cap, "//:%s", local); 317 } 318 319 static int build_line_next(KitSlice bytes, size_t* pos, KitSlice* line) { 320 size_t start = *pos; 321 size_t end = start; 322 while (end < bytes.len && bytes.s[end] != '\n') ++end; 323 if (start >= bytes.len) return 1; 324 line->s = bytes.s + start; 325 line->len = end - start; 326 *pos = end < bytes.len ? end + 1u : end; 327 return 0; 328 } 329 330 static int build_slice_starts(KitSlice s, const char* prefix) { 331 size_t n = driver_strlen(prefix); 332 return s.len >= n && memcmp(s.s, prefix, n) == 0; 333 } 334 335 static void build_copy_slice0(char* out, size_t cap, KitSlice s) { 336 size_t n = s.len < cap - 1u ? s.len : cap - 1u; 337 if (n) memcpy(out, s.s, n); 338 out[n] = '\0'; 339 } 340 341 static int build_list_generated_from_projection(DriverEnv* env, 342 const char* root, 343 const char* pkg, 344 KitSlice line, 345 BuildListTarget* out, 346 size_t* n, size_t cap) { 347 char pat[KIT_BUILD_PATH_MAX], tmpl[KIT_BUILD_TARGET_MAX]; 348 char dir_rel[KIT_BUILD_PATH_MAX], dir_abs[KIT_BUILD_PATH_MAX]; 349 char prefix[128], suffix[128], label[KIT_BUILD_TARGET_MAX], local[KIT_BUILD_TARGET_MAX]; 350 DriverDirHandle* dh; 351 size_t i, arrow = (size_t)-1, star = (size_t)-1, slash = (size_t)-1; 352 if (!build_slice_starts(line, "list ")) return 0; 353 line.s += 5u; 354 line.len -= 5u; 355 for (i = 0; i + 3u <= line.len; ++i) { 356 if (line.s[i] == ' ' && line.s[i + 1u] == '-' && line.s[i + 2u] == '>' && 357 line.s[i + 3u] == ' ') { 358 arrow = i; 359 break; 360 } 361 } 362 if (arrow == (size_t)-1) return 0; 363 build_copy_slice0(pat, sizeof pat, (KitSlice){.s = line.s, .len = arrow}); 364 build_copy_slice0(tmpl, sizeof tmpl, 365 (KitSlice){.s = line.s + arrow + 4u, 366 .len = line.len - arrow - 4u}); 367 for (i = 0; pat[i]; ++i) { 368 if (pat[i] == '*') star = i; 369 if (pat[i] == '/') slash = i; 370 } 371 if (star == (size_t)-1) return 0; 372 if (slash != (size_t)-1) { 373 snprintf(dir_rel, sizeof dir_rel, "%s/%.*s", pkg, 374 (int)slash, pat); 375 snprintf(prefix, sizeof prefix, "%.*s", (int)(star - slash - 1u), 376 pat + slash + 1u); 377 } else { 378 snprintf(dir_rel, sizeof dir_rel, "%s", pkg); 379 snprintf(prefix, sizeof prefix, "%.*s", (int)star, pat); 380 } 381 snprintf(suffix, sizeof suffix, "%s", pat + star + 1u); 382 if (build_join_path(dir_abs, sizeof dir_abs, root, dir_rel) != 0) return 1; 383 dh = driver_open_dir(env, dir_abs); 384 if (!dh) return 0; 385 for (i = 0;; ++i) { 386 const char* name; 387 uint32_t name_len; 388 uint64_t ino, size, mtime; 389 uint8_t ftype; 390 size_t pn = driver_strlen(prefix), sn = driver_strlen(suffix); 391 char stem[KIT_BUILD_TARGET_MAX]; 392 if (driver_read_dir_entry(dh, i, &name, &name_len, &ino, &size, &mtime, 393 &ftype) != 0) 394 break; 395 (void)ino; 396 (void)size; 397 (void)mtime; 398 if (ftype != 4u || name_len < pn + sn || 399 memcmp(name, prefix, pn) != 0 || 400 (sn && memcmp(name + name_len - sn, suffix, sn) != 0)) 401 continue; 402 snprintf(stem, sizeof stem, "%.*s", (int)(name_len - pn - sn), 403 name + pn); 404 if (driver_streq(tmpl, "{stem}.o")) 405 snprintf(local, sizeof local, "%s.o", stem); 406 else 407 snprintf(local, sizeof local, "%s", stem); 408 build_pkg_label(label, sizeof label, pkg, local); 409 if (build_list_add(out, n, cap, label) != 0) { 410 driver_close_dir(env, dh); 411 return 1; 412 } 413 } 414 driver_close_dir(env, dh); 415 return 0; 416 } 417 418 static int build_list_package(DriverEnv* env, const char* root, 419 const char* def_name, const char* pkg, 420 int recursive, BuildListTarget* out, size_t* n, 421 size_t cap) { 422 char pkg_dir[KIT_BUILD_PATH_MAX], def_path[KIT_BUILD_PATH_MAX]; 423 DriverLoad load; 424 KitSlice bytes, line; 425 size_t pos = 0; 426 int in_targets = 0; 427 if (build_join_path(pkg_dir, sizeof pkg_dir, root, pkg) != 0 || 428 build_join_path(def_path, sizeof def_path, pkg_dir, def_name) != 0) 429 return 1; 430 if (driver_path_exists(def_path) && 431 build_load_file(env, def_path, &load, &bytes) == 0) { 432 while (build_line_next(bytes, &pos, &line) == 0) { 433 if (build_slice_starts(line, "[target ") && line.len > 9u && 434 line.s[line.len - 1u] == ']') { 435 char local[KIT_BUILD_TARGET_MAX], label[KIT_BUILD_TARGET_MAX]; 436 build_copy_slice0(local, sizeof local, 437 (KitSlice){.s = line.s + 8u, 438 .len = line.len - 9u}); 439 build_pkg_label(label, sizeof label, pkg, local); 440 if (build_list_add(out, n, cap, label) != 0) { 441 driver_release_bytes(&env->file_io, &load); 442 return 1; 443 } 444 in_targets = 0; 445 } else if (build_slice_starts(line, "[targets ")) { 446 in_targets = 1; 447 } else if (line.len && line.s[0] == '[') { 448 in_targets = 0; 449 } else if (in_targets) { 450 if (build_list_generated_from_projection(env, root, pkg, line, out, n, 451 cap) != 0) { 452 driver_release_bytes(&env->file_io, &load); 453 return 1; 454 } 455 } 456 } 457 driver_release_bytes(&env->file_io, &load); 458 } 459 if (recursive) { 460 DriverDirHandle* dh = driver_open_dir(env, pkg_dir); 461 uint64_t idx; 462 if (!dh) return 0; 463 for (idx = 0;; ++idx) { 464 const char* name; 465 uint32_t name_len; 466 uint64_t ino, size, mtime; 467 uint8_t ftype; 468 char child_pkg[KIT_BUILD_PATH_MAX], child_dir[KIT_BUILD_PATH_MAX], 469 child_def[KIT_BUILD_PATH_MAX]; 470 if (driver_read_dir_entry(dh, idx, &name, &name_len, &ino, &size, &mtime, 471 &ftype) != 0) 472 break; 473 (void)ino; 474 (void)size; 475 (void)mtime; 476 if (ftype != 3u) continue; 477 if (pkg && pkg[0]) 478 snprintf(child_pkg, sizeof child_pkg, "%s/%.*s", pkg, (int)name_len, 479 name); 480 else 481 snprintf(child_pkg, sizeof child_pkg, "%.*s", (int)name_len, name); 482 if (build_join_path(child_dir, sizeof child_dir, root, child_pkg) != 0 || 483 build_join_path(child_def, sizeof child_def, child_dir, def_name) != 0) 484 continue; 485 if (driver_path_exists(child_def)) { 486 if (build_list_package(env, root, def_name, child_pkg, 1, out, n, 487 cap) != 0) { 488 driver_close_dir(env, dh); 489 return 1; 490 } 491 } 492 } 493 driver_close_dir(env, dh); 494 } 495 return 0; 496 } 497 498 static int build_add_config(BuildCli* cli, KitSlice key, KitSlice value) { 499 if (!cli || !key.s || key.len == 0u || cli->nconfig >= BUILD_MAX_CONFIG) 500 return 1; 501 cli->config[cli->nconfig].key = key; 502 cli->config[cli->nconfig].value = value; 503 ++cli->nconfig; 504 return 0; 505 } 506 507 static int build_env_name_valid(const char* name, size_t len) { 508 size_t i; 509 if (!name || len == 0u) return 0; 510 for (i = 0; i < len; ++i) { 511 unsigned char c = (unsigned char)name[i]; 512 if (c == '\0' || c == '=' || c == '\n' || c == '\r') return 0; 513 } 514 return 1; 515 } 516 517 static int build_add_env_config(BuildCli* cli, const char* name, size_t name_len, 518 const char* value) { 519 char* key; 520 if (!cli || !build_env_name_valid(name, name_len) || !value || 521 cli->nconfig >= BUILD_MAX_CONFIG || 522 sizeof(KIT_BUILD_ENV_PREFIX) - 1u + name_len >= BUILD_MAX_CONFIG_KEY) 523 return 1; 524 key = cli->config_keys[cli->nconfig]; 525 memcpy(key, KIT_BUILD_ENV_PREFIX, sizeof(KIT_BUILD_ENV_PREFIX) - 1u); 526 memcpy(key + sizeof(KIT_BUILD_ENV_PREFIX) - 1u, name, name_len); 527 key[sizeof(KIT_BUILD_ENV_PREFIX) - 1u + name_len] = '\0'; 528 return build_add_config(cli, kit_slice_cstr(key), kit_slice_cstr(value)); 529 } 530 531 static int build_parse_config(BuildCli* cli, const char* text) { 532 const char* eq; 533 size_t env_prefix_len = sizeof(KIT_BUILD_ENV_PREFIX) - 1u; 534 if (!cli || !text) return 1; 535 eq = driver_strchr(text, '='); 536 if (!eq) { 537 const char* value; 538 if (!driver_strneq(text, KIT_BUILD_ENV_PREFIX, env_prefix_len) || 539 text[env_prefix_len] == '\0') 540 return 1; 541 value = driver_getenv(text + env_prefix_len); 542 if (!value) return 1; 543 return build_add_config(cli, kit_slice_cstr(text), kit_slice_cstr(value)); 544 } 545 if (eq == text) return 1; 546 return build_add_config(cli, (KitSlice){.s = text, .len = (size_t)(eq - text)}, 547 kit_slice_cstr(eq + 1)); 548 } 549 550 static int build_parse_env(BuildCli* cli, const char* text) { 551 const char* eq; 552 if (!cli || !text) return 1; 553 eq = driver_strchr(text, '='); 554 if (!eq) { 555 const char* value = driver_getenv(text); 556 if (!value) return 1; 557 return build_add_env_config(cli, text, driver_strlen(text), value); 558 } 559 return build_add_env_config(cli, text, (size_t)(eq - text), eq + 1); 560 } 561 562 static int build_client_glob_print(void* user, KitSlice path) { 563 (void)user; 564 driver_printf("%.*s\n", KIT_SLICE_ARG(path)); 565 return 0; 566 } 567 568 static int build_client_parse_format(const char* s, BuildClientFormat* out) { 569 if (!s || !out) return 1; 570 if (driver_streq(s, "path")) { 571 *out = BUILD_CLIENT_FORMAT_PATH; 572 return 0; 573 } 574 if (driver_streq(s, "id")) { 575 *out = BUILD_CLIENT_FORMAT_ID; 576 return 0; 577 } 578 if (driver_streq(s, "id-path")) { 579 *out = BUILD_CLIENT_FORMAT_ID_PATH; 580 return 0; 581 } 582 return 1; 583 } 584 585 static void build_client_print_value(BuildClientFormat fmt, 586 const uint8_t id[KIT_BUILD_HASH_LEN], 587 KitSlice path) { 588 char hex[2u * KIT_BUILD_HASH_LEN + 1u]; 589 if (fmt == BUILD_CLIENT_FORMAT_PATH) { 590 driver_printf("%.*s\n", KIT_SLICE_ARG(path)); 591 return; 592 } 593 kit_hex_encode(hex, id, KIT_BUILD_HASH_LEN); 594 if (fmt == BUILD_CLIENT_FORMAT_ID) { 595 driver_printf("%s\n", hex); 596 return; 597 } 598 driver_printf("%s %.*s\n", hex, KIT_SLICE_ARG(path)); 599 } 600 601 static int build_client_verb(const char* s) { 602 return driver_streq(s, "config-get") || driver_streq(s, "source") || 603 driver_streq(s, "fetch") || driver_streq(s, "depfile") || 604 driver_streq(s, "glob") || driver_streq(s, "export-set") || 605 driver_streq(s, "export-get") || driver_streq(s, "export-collect") || 606 driver_streq(s, "need") || 607 driver_streq(s, "need-submit") || driver_streq(s, "need-await"); 608 } 609 610 static int build_parse_u64(const char* s, uint64_t* out) { 611 uint64_t v = 0; 612 size_t i; 613 if (!s || !s[0] || !out) return 1; 614 for (i = 0; s[i]; ++i) { 615 unsigned c = (unsigned char)s[i]; 616 if (c < '0' || c > '9') return 1; 617 if (v > (UINT64_MAX - (uint64_t)(c - '0')) / 10u) return 1; 618 v = v * 10u + (uint64_t)(c - '0'); 619 } 620 *out = v; 621 return 0; 622 } 623 624 static int build_client_need_parse(BuildCli* cli, int argc, char** argv, 625 int first_arg) { 626 int i; 627 memset(cli, 0, sizeof *cli); 628 for (i = first_arg; i < argc; ++i) { 629 const char* a = argv[i]; 630 if (driver_streq(a, "--config") && i + 1 < argc) { 631 if (build_parse_config(cli, argv[++i]) != 0) { 632 driver_errf(BUILD_TOOL, "bad --config, expected K=V or env.NAME"); 633 return 2; 634 } 635 } else if (driver_streq(a, "--env") && i + 1 < argc) { 636 if (build_parse_env(cli, argv[++i]) != 0) { 637 driver_errf(BUILD_TOOL, "bad --env, expected NAME or NAME=VALUE"); 638 return 2; 639 } 640 } else if (driver_streq(a, "--format") && i + 1 < argc) { 641 if (build_client_parse_format(argv[++i], &cli->format) != 0) { 642 driver_errf(BUILD_TOOL, "bad --format, expected path, id, or id-path"); 643 return 2; 644 } 645 cli->has_format = 1; 646 } else if (driver_streq(a, "--")) { 647 if (!cli->target) { 648 driver_errf(BUILD_TOOL, "missing client target before --"); 649 return 2; 650 } 651 while (++i < argc) { 652 if (cli->nargs >= BUILD_MAX_ARGS) { 653 driver_errf(BUILD_TOOL, "too many recipe arguments"); 654 return 2; 655 } 656 cli->args[cli->nargs++] = kit_slice_cstr(argv[i]); 657 } 658 break; 659 } else if (driver_streq(a, "--arg") && i + 1 < argc) { 660 if (cli->nargs >= BUILD_MAX_ARGS) { 661 driver_errf(BUILD_TOOL, "too many --arg values"); 662 return 2; 663 } 664 cli->args[cli->nargs++] = kit_slice_cstr(argv[++i]); 665 } else if (a[0] == '-') { 666 driver_errf(BUILD_TOOL, "unexpected client option: %s", a); 667 return 2; 668 } else if (!cli->target) { 669 cli->target = a; 670 } else { 671 driver_errf(BUILD_TOOL, "unexpected client argument: %s", a); 672 return 2; 673 } 674 } 675 if (!cli->target) { 676 driver_errf(BUILD_TOOL, "missing client target"); 677 return 2; 678 } 679 return 0; 680 } 681 682 static int build_export_set(DriverEnv* env, int argc, char** argv, 683 int first_arg) { 684 const char* out_dir = driver_getenv(KIT_BUILD_ENV_OUT); 685 char kit_dir[KIT_BUILD_PATH_MAX], exports_path[KIT_BUILD_PATH_MAX]; 686 size_t i; 687 if (!out_dir || argc < first_arg + 3 || 688 !driver_streq(argv[first_arg + 1], "--")) { 689 driver_errf(BUILD_TOOL, "usage: kit build export-set VAR -- VALUE..."); 690 return 2; 691 } 692 if (build_join_path(kit_dir, sizeof kit_dir, out_dir, ".kit") != 0 || 693 build_join_path(exports_path, sizeof exports_path, kit_dir, "exports") != 694 0) { 695 driver_errf(BUILD_TOOL, "export path too long"); 696 return 1; 697 } 698 if (driver_mkdir_p(env, kit_dir) != 0) { 699 driver_errf(BUILD_TOOL, "failed to create exports directory"); 700 return 1; 701 } 702 if (!driver_path_exists(exports_path) && 703 build_file_write(env, exports_path, "kit-build-exports 1\n") != 0) { 704 driver_errf(BUILD_TOOL, "failed to write exports file"); 705 return 1; 706 } 707 { 708 char line[KIT_BUILD_VAL_MAX + 32u]; 709 snprintf(line, sizeof line, "[var %s]\n", argv[first_arg]); 710 if (build_file_append(env, exports_path, line) != 0) { 711 driver_errf(BUILD_TOOL, "failed to append exports variable"); 712 return 1; 713 } 714 } 715 for (i = (size_t)first_arg + 2u; i < (size_t)argc; ++i) { 716 char line[KIT_BUILD_VAL_MAX + 2u]; 717 snprintf(line, sizeof line, "%s\n", argv[i]); 718 if (build_file_append(env, exports_path, line) != 0) { 719 driver_errf(BUILD_TOOL, "failed to append exports value"); 720 return 1; 721 } 722 } 723 return 0; 724 } 725 726 static int build_exports_print_var(DriverEnv* env, const char* path, 727 const char* var) { 728 DriverLoad load; 729 KitSlice bytes, line; 730 size_t pos = 0; 731 int active = 0, found = 0; 732 if (build_load_file(env, path, &load, &bytes) != 0) return 1; 733 while (build_line_next(bytes, &pos, &line) == 0) { 734 if (line.len >= 6u && build_slice_starts(line, "[var ") && 735 line.s[line.len - 1u] == ']') { 736 KitSlice name = {.s = line.s + 5u, .len = line.len - 6u}; 737 active = name.len == driver_strlen(var) && 738 memcmp(name.s, var, name.len) == 0; 739 if (active) found = 1; 740 } else if (active && line.len && line.s[0] != '[') { 741 driver_printf("%.*s\n", KIT_SLICE_ARG(line)); 742 } 743 } 744 driver_release_bytes(&env->file_io, &load); 745 return found ? 0 : 1; 746 } 747 748 static int build_export_get_one(DriverEnv* env, KitBuildClient* client, 749 const char* target, const char* var) { 750 KitBuildRequest req; 751 KitBuildResult result; 752 KitStatus st; 753 char exports_path[KIT_BUILD_PATH_MAX], kit_dir[KIT_BUILD_PATH_MAX]; 754 memset(&req, 0, sizeof req); 755 req.target = kit_slice_cstr(target); 756 st = kit_build_client_need(client, &req, &result); 757 if (st != KIT_OK) { 758 driver_errf(BUILD_TOOL, "export-get need failed: %s", build_status_name(st)); 759 return 1; 760 } 761 if (build_join_path(kit_dir, sizeof kit_dir, result.path, ".kit") != 0 || 762 build_join_path(exports_path, sizeof exports_path, kit_dir, "exports") != 763 0 || 764 build_exports_print_var(env, exports_path, var) != 0) { 765 driver_errf(BUILD_TOOL, "export variable not found: %s", var); 766 return 1; 767 } 768 return 0; 769 } 770 771 static int build_client_mode(int argc, char** argv, int verb_index) { 772 DriverEnv env; 773 DriverBuildHost bh; 774 KitContext ctx; 775 KitBuildClient* client = NULL; 776 KitStatus st; 777 int rc = 1; 778 const char* verb; 779 int first_arg = verb_index + 1; 780 if (argc <= verb_index) { 781 driver_errf(BUILD_TOOL, "missing client command"); 782 return 2; 783 } 784 verb = argv[verb_index]; 785 driver_env_init(&env); 786 ctx = driver_env_to_context(&env); 787 if (driver_streq(verb, "export-set")) { 788 rc = build_export_set(&env, argc, argv, first_arg); 789 goto out_env; 790 } 791 if (driver_build_host_init(&bh, &env) != 0) { 792 driver_errf(BUILD_TOOL, "hosted build adapter is unavailable"); 793 goto out_env; 794 } 795 st = kit_build_client_open(&ctx, &bh.transport, &client); 796 if (st != KIT_OK) { 797 driver_errf(BUILD_TOOL, "failed to connect to build coordinator: %s", 798 build_status_name(st)); 799 goto out_host; 800 } 801 if (driver_streq(verb, "config-get")) { 802 KitSlice value; 803 int present = 0; 804 BuildCli cli; 805 int i; 806 memset(&cli, 0, sizeof cli); 807 for (i = first_arg; i < argc; ++i) { 808 if (driver_streq(argv[i], "--default") && i + 1 < argc) { 809 cli.has_config_default = 1; 810 cli.config_default = argv[++i]; 811 } else if (argv[i][0] == '-') { 812 driver_errf(BUILD_TOOL, "unexpected config-get option: %s", argv[i]); 813 rc = 2; 814 goto out_client; 815 } else if (!cli.target) { 816 cli.target = argv[i]; 817 } else { 818 driver_errf(BUILD_TOOL, "unexpected config-get argument: %s", argv[i]); 819 rc = 2; 820 goto out_client; 821 } 822 } 823 if (!cli.target) { 824 driver_errf(BUILD_TOOL, "usage: kit build config-get [--default VALUE] KEY"); 825 rc = 2; 826 goto out_client; 827 } 828 st = kit_build_client_config_get( 829 client, kit_slice_cstr(cli.target), 830 cli.has_config_default ? kit_slice_cstr(cli.config_default) 831 : KIT_SLICE_NULL, 832 cli.has_config_default, &value, &present); 833 if (st != KIT_OK) { 834 driver_errf(BUILD_TOOL, "config-get failed: %s", build_status_name(st)); 835 goto out_client; 836 } 837 if (!present) { 838 if (value.s) { 839 driver_printf("%.*s\n", KIT_SLICE_ARG(value)); 840 rc = 0; 841 goto out_client; 842 } 843 rc = 1; 844 goto out_client; 845 } 846 driver_printf("%.*s\n", KIT_SLICE_ARG(value)); 847 rc = 0; 848 } else if (driver_streq(verb, "source")) { 849 uint8_t blob[KIT_BUILD_HASH_LEN]; 850 KitSlice path; 851 BuildCli cli; 852 int i; 853 memset(&cli, 0, sizeof cli); 854 for (i = first_arg; i < argc; ++i) { 855 if (driver_streq(argv[i], "--optional")) { 856 cli.source_optional = 1; 857 } else if (driver_streq(argv[i], "--format") && i + 1 < argc) { 858 if (build_client_parse_format(argv[++i], &cli.format) != 0) { 859 driver_errf(BUILD_TOOL, "bad --format, expected path, id, or id-path"); 860 rc = 2; 861 goto out_client; 862 } 863 } else if (argv[i][0] == '-') { 864 driver_errf(BUILD_TOOL, "unexpected source option: %s", argv[i]); 865 rc = 2; 866 goto out_client; 867 } else if (!cli.target) { 868 cli.target = argv[i]; 869 } else { 870 driver_errf(BUILD_TOOL, "unexpected source argument: %s", argv[i]); 871 rc = 2; 872 goto out_client; 873 } 874 } 875 if (!cli.target) { 876 driver_errf(BUILD_TOOL, 877 "usage: kit build source [--optional] " 878 "[--format path|id|id-path] PATH"); 879 rc = 2; 880 goto out_client; 881 } 882 st = kit_build_client_source(client, kit_slice_cstr(cli.target), blob, &path); 883 if (st == KIT_NOT_FOUND && cli.source_optional) { 884 rc = 0; 885 goto out_client; 886 } 887 if (st != KIT_OK) { 888 driver_errf(BUILD_TOOL, "source failed: %s", build_status_name(st)); 889 goto out_client; 890 } 891 build_client_print_value(cli.format, blob, path); 892 rc = 0; 893 } else if (driver_streq(verb, "fetch")) { 894 uint8_t blob[KIT_BUILD_HASH_LEN]; 895 KitSlice urls[BUILD_MAX_ARGS]; 896 KitSlice path; 897 size_t nurls = 0; 898 int i; 899 BuildCli cli; 900 int blob_arg = 0; 901 memset(&cli, 0, sizeof cli); 902 for (i = first_arg; i < argc; ++i) { 903 if (driver_streq(argv[i], "--format") && i + 1 < argc) { 904 if (build_client_parse_format(argv[++i], &cli.format) != 0) { 905 driver_errf(BUILD_TOOL, "bad --format, expected path, id, or id-path"); 906 rc = 2; 907 goto out_client; 908 } 909 } else if (!blob_arg) { 910 blob_arg = i; 911 } else { 912 if (nurls >= BUILD_MAX_ARGS) { 913 driver_errf(BUILD_TOOL, "too many fetch URLs"); 914 rc = 2; 915 goto out_client; 916 } 917 urls[nurls++] = kit_slice_cstr(argv[i]); 918 } 919 } 920 if (!blob_arg || nurls == 0u || 921 driver_strlen(argv[blob_arg]) != 2u * KIT_BUILD_HASH_LEN || 922 kit_hex_decode(blob, argv[blob_arg], KIT_BUILD_HASH_LEN) != KIT_OK) { 923 driver_errf(BUILD_TOOL, 924 "usage: kit build fetch [--format path|id|id-path] BLOB URL..."); 925 rc = 2; 926 goto out_client; 927 } 928 st = kit_build_client_fetch(client, blob, urls, nurls, &path); 929 if (st != KIT_OK) { 930 driver_errf(BUILD_TOOL, "fetch failed: %s", build_status_name(st)); 931 goto out_client; 932 } 933 build_client_print_value(cli.format, blob, path); 934 rc = 0; 935 } else if (driver_streq(verb, "depfile")) { 936 DriverLoad load; 937 KitSlice depfile; 938 uint32_t depfile_flags = KIT_BUILD_DEPFILE_DEFAULT; 939 int file_arg = first_arg; 940 if (argc == first_arg + 2 && driver_streq(argv[first_arg], "--lines")) { 941 depfile_flags = KIT_BUILD_DEPFILE_LINES; 942 file_arg = first_arg + 1; 943 } else if (argc != first_arg + 1) { 944 driver_errf(BUILD_TOOL, "usage: kit build depfile [--lines] FILE"); 945 rc = 2; 946 goto out_client; 947 } 948 if (driver_load_bytes(&env.file_io, BUILD_TOOL, argv[file_arg], &load, 949 &depfile) != 0) { 950 rc = 1; 951 goto out_client; 952 } 953 st = kit_build_client_depfile_ex(client, depfile, depfile_flags); 954 driver_release_bytes(&env.file_io, &load); 955 if (st != KIT_OK) { 956 driver_errf(BUILD_TOOL, "depfile failed: %s", build_status_name(st)); 957 goto out_client; 958 } 959 rc = 0; 960 } else if (driver_streq(verb, "glob")) { 961 if (argc != first_arg + 1) { 962 driver_errf(BUILD_TOOL, "usage: kit build glob PATTERN"); 963 rc = 2; 964 goto out_client; 965 } 966 st = kit_build_client_glob(client, kit_slice_cstr(argv[first_arg]), 967 build_client_glob_print, NULL); 968 if (st != KIT_OK) { 969 driver_errf(BUILD_TOOL, "glob failed: %s", build_status_name(st)); 970 goto out_client; 971 } 972 rc = 0; 973 } else if (driver_streq(verb, "need")) { 974 BuildCli cli; 975 KitBuildRequest req; 976 KitBuildResult result; 977 rc = build_client_need_parse(&cli, argc, argv, first_arg); 978 if (rc != 0) goto out_client; 979 memset(&req, 0, sizeof req); 980 req.target = kit_slice_cstr(cli.target); 981 req.config = cli.config; 982 req.nconfig = cli.nconfig; 983 req.argv = cli.args; 984 req.argc = cli.nargs; 985 st = kit_build_client_need(client, &req, &result); 986 if (st != KIT_OK) { 987 driver_errf(BUILD_TOOL, "need failed: %s", build_status_name(st)); 988 rc = 1; 989 goto out_client; 990 } 991 build_client_print_value(cli.format, result.output_tree, 992 kit_slice_cstr(result.path)); 993 rc = 0; 994 } else if (driver_streq(verb, "need-submit")) { 995 BuildCli cli; 996 KitBuildRequest req; 997 KitBuildNeedToken token; 998 rc = build_client_need_parse(&cli, argc, argv, first_arg); 999 if (rc != 0) goto out_client; 1000 if (cli.has_format) { 1001 driver_errf(BUILD_TOOL, "need-submit does not support --format"); 1002 rc = 2; 1003 goto out_client; 1004 } 1005 memset(&req, 0, sizeof req); 1006 req.target = kit_slice_cstr(cli.target); 1007 req.config = cli.config; 1008 req.nconfig = cli.nconfig; 1009 req.argv = cli.args; 1010 req.argc = cli.nargs; 1011 st = kit_build_client_need_submit(client, &req, &token); 1012 if (st != KIT_OK) { 1013 driver_errf(BUILD_TOOL, "need-submit failed: %s", 1014 build_status_name(st)); 1015 rc = 1; 1016 goto out_client; 1017 } 1018 driver_printf("%llu\n", (unsigned long long)token.id); 1019 rc = 0; 1020 } else if (driver_streq(verb, "need-await")) { 1021 KitBuildNeedToken token; 1022 KitBuildResult result; 1023 BuildCli cli; 1024 const char* token_arg = NULL; 1025 int i; 1026 memset(&cli, 0, sizeof cli); 1027 for (i = first_arg; i < argc; ++i) { 1028 if (driver_streq(argv[i], "--format") && i + 1 < argc) { 1029 if (build_client_parse_format(argv[++i], &cli.format) != 0) { 1030 driver_errf(BUILD_TOOL, "bad --format, expected path, id, or id-path"); 1031 rc = 2; 1032 goto out_client; 1033 } 1034 } else if (argv[i][0] == '-') { 1035 driver_errf(BUILD_TOOL, "unexpected need-await option: %s", argv[i]); 1036 rc = 2; 1037 goto out_client; 1038 } else if (!token_arg) { 1039 token_arg = argv[i]; 1040 } else { 1041 driver_errf(BUILD_TOOL, "unexpected need-await argument: %s", argv[i]); 1042 rc = 2; 1043 goto out_client; 1044 } 1045 } 1046 if (!token_arg || build_parse_u64(token_arg, &token.id) != 0) { 1047 driver_errf(BUILD_TOOL, 1048 "usage: kit build need-await [--format path|id|id-path] TOKEN"); 1049 rc = 2; 1050 goto out_client; 1051 } 1052 st = kit_build_client_need_await(client, token, &result); 1053 if (st != KIT_OK) { 1054 driver_errf(BUILD_TOOL, "need-await failed: %s", build_status_name(st)); 1055 rc = 1; 1056 goto out_client; 1057 } 1058 build_client_print_value(cli.format, result.output_tree, 1059 kit_slice_cstr(result.path)); 1060 rc = 0; 1061 } else if (driver_streq(verb, "export-get")) { 1062 if (argc != first_arg + 2) { 1063 driver_errf(BUILD_TOOL, "usage: kit build export-get TARGET VAR"); 1064 rc = 2; 1065 goto out_client; 1066 } 1067 rc = build_export_get_one(&env, client, argv[first_arg], 1068 argv[first_arg + 1]); 1069 } else if (driver_streq(verb, "export-collect")) { 1070 int i; 1071 if (argc < first_arg + 2) { 1072 driver_errf(BUILD_TOOL, "usage: kit build export-collect VAR TARGET..."); 1073 rc = 2; 1074 goto out_client; 1075 } 1076 rc = 0; 1077 for (i = first_arg + 1; i < argc; ++i) { 1078 if (build_export_get_one(&env, client, argv[i], argv[first_arg]) != 0) 1079 rc = 1; 1080 } 1081 } else { 1082 driver_errf(BUILD_TOOL, "unknown client command: %s", verb); 1083 rc = 2; 1084 } 1085 1086 out_client: 1087 kit_build_client_close(client); 1088 out_host: 1089 driver_build_host_fini(&bh); 1090 out_env: 1091 driver_env_fini(&env); 1092 return rc; 1093 } 1094 1095 static int build_parse_args(BuildCli* cli, int argc, char** argv, int first_arg, 1096 int test_mode) { 1097 int i; 1098 memset(cli, 0, sizeof *cli); 1099 cli->root = "."; 1100 cli->test_mode = test_mode; 1101 for (i = first_arg; i < argc; ++i) { 1102 const char* a = argv[i]; 1103 if (driver_streq(a, "--store") && i + 1 < argc) { 1104 cli->store = argv[++i]; 1105 } else if (driver_streq(a, "--root") && i + 1 < argc) { 1106 cli->root = argv[++i]; 1107 } else if ((driver_streq(a, "--def") || driver_streq(a, "--def-name")) && 1108 i + 1 < argc) { 1109 cli->def = argv[++i]; 1110 } else if (driver_streq(a, "--profile") && i + 1 < argc) { 1111 cli->profile = argv[++i]; 1112 } else if (driver_streq(a, "--config") && i + 1 < argc) { 1113 const char* opt = argv[++i]; 1114 if (build_parse_config(cli, opt) != 0) { 1115 driver_errf(BUILD_TOOL, 1116 "bad --config '%s', expected K=V or env.NAME", opt); 1117 return 2; 1118 } 1119 } else if (driver_streq(a, "--env") && i + 1 < argc) { 1120 const char* opt = argv[++i]; 1121 if (build_parse_env(cli, opt) != 0) { 1122 driver_errf(BUILD_TOOL, "bad --env '%s', expected NAME or NAME=VALUE", 1123 opt); 1124 return 2; 1125 } 1126 } else if (driver_streq(a, "--")) { 1127 if (!cli->target) { 1128 driver_errf(BUILD_TOOL, "missing target before --"); 1129 return 2; 1130 } 1131 while (++i < argc) { 1132 if (cli->nargs >= BUILD_MAX_ARGS) { 1133 driver_errf(BUILD_TOOL, "too many recipe arguments"); 1134 return 2; 1135 } 1136 cli->args[cli->nargs++] = kit_slice_cstr(argv[i]); 1137 } 1138 break; 1139 } else if (driver_streq(a, "--arg") && i + 1 < argc) { 1140 if (cli->nargs >= BUILD_MAX_ARGS) { 1141 driver_errf(BUILD_TOOL, "too many --arg values"); 1142 return 2; 1143 } 1144 cli->args[cli->nargs++] = kit_slice_cstr(argv[++i]); 1145 } else if (driver_streq(a, "--stats")) { 1146 cli->stats = 1; 1147 } else if (driver_streq(a, "--trace")) { 1148 cli->trace = 1; 1149 } else if (driver_streq(a, "--verify")) { 1150 cli->verify = 1; 1151 } else if (a[0] == '-') { 1152 driver_errf(BUILD_TOOL, "unexpected option: %s", a); 1153 return 2; 1154 } else if (!cli->target) { 1155 cli->target = a; 1156 } else { 1157 driver_errf(BUILD_TOOL, "unexpected argument: %s", a); 1158 return 2; 1159 } 1160 } 1161 if (!cli->target) { 1162 driver_errf(BUILD_TOOL, "missing target"); 1163 return 2; 1164 } 1165 if (cli->def && !build_def_name_valid(cli->def)) { 1166 driver_errf(BUILD_TOOL, "bad def-name: %s", cli->def); 1167 return 2; 1168 } 1169 return 0; 1170 } 1171 1172 static void build_trace_stderr(void* user, KitSlice line) { 1173 (void)user; 1174 driver_errf(BUILD_TOOL, "trace %.*s", KIT_SLICE_ARG(line)); 1175 } 1176 1177 static void build_print_stats(const KitBuildStats* s) { 1178 if (!s) return; 1179 driver_errf(BUILD_TOOL, 1180 "stats deep_hits=%llu shallow_hits=%llu recipes_run=%llu " 1181 "materialize_misses=%llu object_fetches=%llu trace_pulls=%llu " 1182 "test_runs=%llu test_cache_hits=%llu test_failures=%llu", 1183 (unsigned long long)s->deep_hits, 1184 (unsigned long long)s->shallow_hits, 1185 (unsigned long long)s->recipes_run, 1186 (unsigned long long)s->materialize_misses, 1187 (unsigned long long)s->object_fetches, 1188 (unsigned long long)s->trace_pulls, 1189 (unsigned long long)s->test_runs, 1190 (unsigned long long)s->test_cache_hits, 1191 (unsigned long long)s->test_failures); 1192 } 1193 1194 static int build_verify_tree(DriverEnv* env, const char* store, 1195 const uint8_t tree[KIT_BUILD_HASH_LEN]) { 1196 KitContext ctx = driver_env_to_context(env); 1197 KitCasHost cas_host = driver_cas_host(env); 1198 KitCas* cas = NULL; 1199 char cas_root[KIT_BUILD_PATH_MAX]; 1200 KitStatus st; 1201 if (build_join_path(cas_root, sizeof cas_root, store, "cas") != 0) { 1202 driver_errf(BUILD_TOOL, "store path is too long"); 1203 return 1; 1204 } 1205 st = kit_cas_open(&ctx, &cas_host, cas_root, &cas); 1206 if (st == KIT_OK) st = kit_cas_verify_tree(cas, tree); 1207 if (cas) kit_cas_close(cas); 1208 if (st != KIT_OK) { 1209 driver_errf(BUILD_TOOL, "output tree verification failed: %s", 1210 build_status_name(st)); 1211 return 1; 1212 } 1213 return 0; 1214 } 1215 1216 static int build_find_top_command(int argc, char** argv) { 1217 int i; 1218 for (i = 1; i < argc; ++i) { 1219 if ((driver_streq(argv[i], "--store") || driver_streq(argv[i], "--root") || 1220 driver_streq(argv[i], "--def") || driver_streq(argv[i], "--def-name") || 1221 driver_streq(argv[i], "--profile") || driver_streq(argv[i], "--config") || 1222 driver_streq(argv[i], "--env")) && 1223 i + 1 < argc) { 1224 ++i; 1225 continue; 1226 } 1227 if (driver_streq(argv[i], "--stats") || driver_streq(argv[i], "--trace") || 1228 driver_streq(argv[i], "--verify")) 1229 continue; 1230 if (driver_streq(argv[i], "list") || driver_streq(argv[i], "repo") || 1231 driver_streq(argv[i], "workspace")) 1232 return i; 1233 return -1; 1234 } 1235 return -1; 1236 } 1237 1238 static void build_global_option_defaults(int argc, char** argv, int cmd_index, 1239 const char** root, 1240 const char** def_name, 1241 const char** store) { 1242 int i; 1243 (void)argc; 1244 *root = "."; 1245 *def_name = BUILD_DEFAULT_DEF; 1246 *store = NULL; 1247 for (i = 1; i < cmd_index; ++i) { 1248 if (driver_streq(argv[i], "--root") && i + 1 < cmd_index) { 1249 *root = argv[++i]; 1250 } else if ((driver_streq(argv[i], "--def") || 1251 driver_streq(argv[i], "--def-name")) && 1252 i + 1 < cmd_index) { 1253 *def_name = argv[++i]; 1254 } else if (driver_streq(argv[i], "--store") && i + 1 < cmd_index) { 1255 *store = argv[++i]; 1256 } else if ((driver_streq(argv[i], "--profile") || 1257 driver_streq(argv[i], "--config") || 1258 driver_streq(argv[i], "--env")) && 1259 i + 1 < cmd_index) { 1260 ++i; 1261 } 1262 } 1263 } 1264 1265 static int build_cmd_list(int argc, char** argv, int cmd_index) { 1266 DriverEnv env; 1267 const char *root, *def_name, *store_ignored; 1268 const char* pattern = "//..."; 1269 int scan_do = 0; 1270 int i, recursive = 0; 1271 char pkg[KIT_BUILD_PATH_MAX]; 1272 BuildListTarget targets[512]; 1273 size_t ntargets = 0, j; 1274 (void)store_ignored; 1275 build_global_option_defaults(argc, argv, cmd_index, &root, &def_name, 1276 &store_ignored); 1277 if (!build_def_name_valid(def_name)) { 1278 driver_errf(BUILD_TOOL, "bad def-name"); 1279 return 2; 1280 } 1281 for (i = cmd_index + 1; i < argc; ++i) { 1282 if (driver_streq(argv[i], "--scan-do")) { 1283 scan_do = 1; 1284 } else if (argv[i][0] == '-') { 1285 driver_errf(BUILD_TOOL, "unexpected list option: %s", argv[i]); 1286 return 2; 1287 } else { 1288 pattern = argv[i]; 1289 } 1290 } 1291 memset(pkg, 0, sizeof pkg); 1292 if (!driver_strneq(pattern, "//", 2u)) { 1293 driver_errf(BUILD_TOOL, "list pattern must be //pkg or //pkg/..."); 1294 return 2; 1295 } 1296 { 1297 const char* body = pattern + 2u; 1298 size_t len = driver_strlen(body); 1299 if (len >= 4u && driver_streq(body + len - 4u, "/...")) { 1300 recursive = 1; 1301 len -= 4u; 1302 } 1303 if (len >= sizeof pkg) return 2; 1304 memcpy(pkg, body, len); 1305 pkg[len] = '\0'; 1306 } 1307 driver_env_init(&env); 1308 if (scan_do) { 1309 char pkg_dir[KIT_BUILD_PATH_MAX], do_dir[KIT_BUILD_PATH_MAX]; 1310 DriverDirHandle* dh; 1311 if (build_join_path(pkg_dir, sizeof pkg_dir, root, pkg) != 0 || 1312 build_join_path(do_dir, sizeof do_dir, pkg_dir, "do") != 0) { 1313 driver_env_fini(&env); 1314 return 1; 1315 } 1316 dh = driver_open_dir(&env, do_dir); 1317 if (dh) { 1318 uint64_t idx; 1319 for (idx = 0;; ++idx) { 1320 const char* name; 1321 uint32_t name_len; 1322 uint64_t ino, size, mtime; 1323 uint8_t ftype; 1324 if (driver_read_dir_entry(dh, idx, &name, &name_len, &ino, &size, 1325 &mtime, &ftype) != 0) 1326 break; 1327 (void)ino; 1328 (void)size; 1329 (void)mtime; 1330 if (ftype == 4u && name_len > 3u && 1331 memcmp(name + name_len - 3u, ".do", 3u) == 0) 1332 driver_printf("//%s:%.*s %.*s\n", pkg, (int)name_len, name, 1333 (int)name_len, name); 1334 } 1335 driver_close_dir(&env, dh); 1336 } 1337 } else if (build_list_package(&env, root, def_name, pkg, recursive, targets, 1338 &ntargets, 1339 sizeof targets / sizeof targets[0]) != 0) { 1340 driver_env_fini(&env); 1341 return 1; 1342 } 1343 qsort(targets, ntargets, sizeof targets[0], build_list_cmp); 1344 for (j = 0; j < ntargets; ++j) driver_printf("%s\n", targets[j].label); 1345 driver_env_fini(&env); 1346 return 0; 1347 } 1348 1349 static int build_cmd_repo(int argc, char** argv, int cmd_index) { 1350 DriverEnv env; 1351 const char *root, *def_name_ignored, *store_ignored; 1352 KitBuildWorkspace ws; 1353 int present = 0; 1354 size_t i; 1355 (void)def_name_ignored; 1356 (void)store_ignored; 1357 if (argc <= cmd_index + 1) { 1358 driver_errf(BUILD_TOOL, "usage: kit build repo {add|fetch|list}"); 1359 return 2; 1360 } 1361 build_global_option_defaults(argc, argv, cmd_index, &root, &def_name_ignored, 1362 &store_ignored); 1363 driver_env_init(&env); 1364 if (build_load_workspace(&env, root, &ws, &present) != 0) { 1365 driver_env_fini(&env); 1366 return 1; 1367 } 1368 if (driver_streq(argv[cmd_index + 1], "list")) { 1369 for (i = 0; i < ws.n_externals; ++i) { 1370 char hex[KIT_BUILD_HEX_LEN]; 1371 const char* fmt = ws.externals[i].format == KIT_BUILD_WS_EXT_TREE 1372 ? "tree" 1373 : ws.externals[i].format == KIT_BUILD_WS_EXT_TARGZ 1374 ? "tar.gz" 1375 : "kpkg"; 1376 kit_hex_encode(hex, ws.externals[i].archive, KIT_BUILD_HASH_LEN); 1377 driver_printf("%s %s %s\n", ws.externals[i].name, fmt, hex); 1378 } 1379 } else if (driver_streq(argv[cmd_index + 1], "fetch")) { 1380 if (argc <= cmd_index + 2) { 1381 driver_errf(BUILD_TOOL, "usage: kit build repo fetch NAME"); 1382 driver_env_fini(&env); 1383 return 2; 1384 } 1385 if (!kit_build_workspace_external_find(&ws, kit_slice_cstr(argv[cmd_index + 2]))) { 1386 driver_errf(BUILD_TOOL, "unknown external repo: %s", argv[cmd_index + 2]); 1387 driver_env_fini(&env); 1388 return 1; 1389 } 1390 driver_printf("%s\n", argv[cmd_index + 2]); 1391 } else if (driver_streq(argv[cmd_index + 1], "add")) { 1392 const char* name; 1393 const char* url; 1394 const char* src; 1395 const char* format = "tar.gz"; 1396 const char* strip = NULL; 1397 char ws_path[KIT_BUILD_PATH_MAX], line[2048], hex[KIT_BUILD_HEX_LEN]; 1398 DriverLoad load; 1399 KitSlice bytes; 1400 KitBlobInfo bi; 1401 int a; 1402 if (argc <= cmd_index + 3) { 1403 driver_errf(BUILD_TOOL, "usage: kit build repo add NAME URL"); 1404 driver_env_fini(&env); 1405 return 2; 1406 } 1407 name = argv[cmd_index + 2]; 1408 url = argv[cmd_index + 3]; 1409 for (a = cmd_index + 4; a < argc; ++a) { 1410 if (driver_streq(argv[a], "--format") && a + 1 < argc) { 1411 format = argv[++a]; 1412 } else if (driver_streq(argv[a], "--strip-prefix") && a + 1 < argc) { 1413 strip = argv[++a]; 1414 } 1415 } 1416 if (build_file_url_path(url, &src) != 0 || 1417 build_load_file(&env, src, &load, &bytes) != 0) { 1418 driver_env_fini(&env); 1419 return 1; 1420 } 1421 kit_blob_info(&bi, bytes.data, bytes.len); 1422 driver_release_bytes(&env.file_io, &load); 1423 kit_hex_encode(hex, bi.id, KIT_BUILD_HASH_LEN); 1424 if (build_join_path(ws_path, sizeof ws_path, root, "WORKSPACE.kit") != 0) { 1425 driver_env_fini(&env); 1426 return 1; 1427 } 1428 snprintf(line, sizeof line, 1429 "\n[external %s]\nformat %s\narchive %s\nurl %s\n", name, format, 1430 hex, url); 1431 if (build_file_append(&env, ws_path, line) != 0 || 1432 (strip && (snprintf(line, sizeof line, "strip-prefix %s\n", strip), 1433 build_file_append(&env, ws_path, line) != 0))) { 1434 driver_env_fini(&env); 1435 return 1; 1436 } 1437 driver_printf("%s %s\n", name, hex); 1438 } else { 1439 driver_errf(BUILD_TOOL, "unknown repo command: %s", argv[cmd_index + 1]); 1440 driver_env_fini(&env); 1441 return 2; 1442 } 1443 driver_env_fini(&env); 1444 return 0; 1445 } 1446 1447 static int build_cmd_workspace(int argc, char** argv, int cmd_index) { 1448 DriverEnv env; 1449 DriverBuildHost bh; 1450 const char *root, *def_name_ignored, *store_ignored; 1451 const char* out = NULL; 1452 const char* format = "tar.gz"; 1453 const char* tar_paths[] = {"/usr/bin/tar", "/bin/tar"}; 1454 KitBuildKV envv[1]; 1455 KitBuildProc* proc = NULL; 1456 int exit_code = 127; 1457 int ran = 0; 1458 int i; 1459 (void)def_name_ignored; 1460 (void)store_ignored; 1461 if (argc <= cmd_index + 1 || !driver_streq(argv[cmd_index + 1], "package")) { 1462 driver_errf(BUILD_TOOL, "usage: kit build workspace package -o OUT"); 1463 return 2; 1464 } 1465 build_global_option_defaults(argc, argv, cmd_index, &root, &def_name_ignored, 1466 &store_ignored); 1467 for (i = cmd_index + 2; i < argc; ++i) { 1468 if (driver_streq(argv[i], "--format") && i + 1 < argc) 1469 format = argv[++i]; 1470 else if (driver_streq(argv[i], "-o") && i + 1 < argc) 1471 out = argv[++i]; 1472 } 1473 if (!out || !driver_streq(format, "tar.gz")) { 1474 driver_errf(BUILD_TOOL, "workspace package supports --format tar.gz -o OUT"); 1475 return 2; 1476 } 1477 driver_env_init(&env); 1478 if (driver_build_host_init(&bh, &env) != 0) { 1479 driver_env_fini(&env); 1480 return 1; 1481 } 1482 envv[0].key = kit_slice_cstr("COPYFILE_DISABLE"); 1483 envv[0].value = kit_slice_cstr("1"); 1484 for (i = 0; i < (int)(sizeof tar_paths / sizeof tar_paths[0]); ++i) { 1485 KitSlice tar_argv[6]; 1486 tar_argv[0] = kit_slice_cstr(tar_paths[i]); 1487 tar_argv[1] = kit_slice_cstr("--format"); 1488 tar_argv[2] = kit_slice_cstr("ustar"); 1489 tar_argv[3] = kit_slice_cstr("-czf"); 1490 tar_argv[4] = kit_slice_cstr(out); 1491 tar_argv[5] = kit_slice_cstr("."); 1492 proc = NULL; 1493 { 1494 KitExecOpts eo; 1495 memset(&eo, 0, sizeof eo); 1496 eo.argv = tar_argv; 1497 eo.argc = 6u; 1498 eo.env = envv; 1499 eo.nenv = 1u; 1500 eo.cwd = kit_slice_cstr(root); 1501 if (bh.exec.spawn(bh.exec.user, &eo, &proc) != 0) continue; 1502 } 1503 ran = 1; 1504 if (bh.exec.wait(bh.exec.user, proc, &exit_code, NULL, NULL) != 0) { 1505 driver_build_host_fini(&bh); 1506 driver_env_fini(&env); 1507 return 1; 1508 } 1509 if (exit_code != 127) break; 1510 } 1511 driver_build_host_fini(&bh); 1512 driver_env_fini(&env); 1513 if (!ran || exit_code != 0) { 1514 driver_errf(BUILD_TOOL, "workspace package failed"); 1515 return 1; 1516 } 1517 driver_printf("%s\n", out); 1518 return 0; 1519 } 1520 1521 int driver_build(int argc, char** argv) { 1522 DriverEnv env; 1523 DriverBuildHost bh; 1524 BuildCli cli; 1525 KitContext ctx; 1526 KitBuildOptions opts; 1527 KitBuildRequest req; 1528 KitBuildResult result; 1529 KitBuildTestResult test_result; 1530 KitBuildCoordinator* coord = NULL; 1531 KitStatus st; 1532 char* default_store = NULL; 1533 size_t default_store_size = 0; 1534 char* abs_store = NULL; 1535 char* abs_root = NULL; 1536 size_t abs_store_size = 0; 1537 size_t abs_root_size = 0; 1538 char hex[2u * KIT_BUILD_HASH_LEN + 1u]; 1539 int rc; 1540 1541 if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) { 1542 driver_help_build(); 1543 return 0; 1544 } 1545 if (argc >= 2 && driver_streq(argv[1], "--client")) 1546 return build_client_mode(argc, argv, 2); 1547 if (argc >= 2 && driver_getenv(KIT_BUILD_ENV_SOCK) && 1548 build_client_verb(argv[1])) 1549 return build_client_mode(argc, argv, 1); 1550 { 1551 int cmd_index = build_find_top_command(argc, argv); 1552 if (cmd_index >= 0) { 1553 if (driver_streq(argv[cmd_index], "list")) 1554 return build_cmd_list(argc, argv, cmd_index); 1555 if (driver_streq(argv[cmd_index], "repo")) 1556 return build_cmd_repo(argc, argv, cmd_index); 1557 if (driver_streq(argv[cmd_index], "workspace")) 1558 return build_cmd_workspace(argc, argv, cmd_index); 1559 } 1560 } 1561 1562 rc = build_parse_args(&cli, argc, argv, 1563 argc >= 2 && driver_streq(argv[1], "test") ? 2 : 1, 1564 argc >= 2 && driver_streq(argv[1], "test")); 1565 if (rc != 0) return rc; 1566 1567 driver_env_init(&env); 1568 if (!cli.store) { 1569 default_store = driver_path_join(&env, env.cache_dir, "build", 1570 &default_store_size); 1571 if (!default_store) { 1572 driver_errf(BUILD_TOOL, "out of memory"); 1573 driver_env_fini(&env); 1574 return 1; 1575 } 1576 cli.store = default_store; 1577 } 1578 1579 ctx = driver_env_to_context(&env); 1580 if (driver_build_host_init(&bh, &env) != 0) { 1581 driver_errf(BUILD_TOOL, "hosted build adapter is unavailable"); 1582 rc = 1; 1583 goto out_env; 1584 } 1585 abs_store = driver_build_host_abs_path(&env, cli.store, &abs_store_size); 1586 abs_root = driver_build_host_abs_path(&env, cli.root, &abs_root_size); 1587 if (!abs_store || !abs_root) { 1588 driver_errf(BUILD_TOOL, "failed to resolve build paths"); 1589 rc = 1; 1590 goto out_host_only; 1591 } 1592 1593 memset(&opts, 0, sizeof opts); 1594 opts.workspace_root = kit_slice_cstr(abs_root); 1595 opts.build_def_path = cli.def ? kit_slice_cstr(cli.def) : KIT_SLICE_NULL; 1596 opts.profile = cli.profile ? kit_slice_cstr(cli.profile) : KIT_SLICE_NULL; 1597 opts.jobs = 1; 1598 opts.verify = cli.verify; 1599 if (cli.trace) opts.trace = build_trace_stderr; 1600 1601 st = kit_build_coordinator_open(&ctx, &bh.host, kit_slice_cstr(abs_store), 1602 &opts, &coord); 1603 if (st != KIT_OK) { 1604 driver_errf(BUILD_TOOL, "failed to open coordinator: %s", 1605 build_status_name(st)); 1606 rc = st == KIT_INVALID || st == KIT_MALFORMED ? 2 : 1; 1607 goto out; 1608 } 1609 1610 memset(&req, 0, sizeof req); 1611 req.target = kit_slice_cstr(cli.target); 1612 req.config = cli.config; 1613 req.nconfig = cli.nconfig; 1614 req.argv = cli.args; 1615 req.argc = cli.nargs; 1616 memset(&result, 0, sizeof result); 1617 memset(&test_result, 0, sizeof test_result); 1618 st = cli.test_mode ? kit_build_test(coord, &req, &test_result) 1619 : kit_build(coord, &req, &result); 1620 if (st != KIT_OK) { 1621 driver_errf(BUILD_TOOL, "%s failed: %s", 1622 cli.test_mode ? "build test" : "build", 1623 build_status_name(st)); 1624 rc = 1; 1625 goto out; 1626 } 1627 if (cli.stats) { 1628 KitBuildStats stats; 1629 kit_build_stats(coord, &stats); 1630 build_print_stats(&stats); 1631 } 1632 1633 if (cli.verify && 1634 build_verify_tree(&env, abs_store, 1635 cli.test_mode ? test_result.result_tree 1636 : result.output_tree) != 0) { 1637 rc = 1; 1638 goto out; 1639 } 1640 1641 if (cli.test_mode) { 1642 kit_hex_encode(hex, test_result.result_tree, KIT_BUILD_HASH_LEN); 1643 driver_printf("%s %s %s\n", 1644 test_result.status == KIT_TEST_PASS ? "PASS" : "FAIL", hex, 1645 test_result.path); 1646 rc = test_result.status == KIT_TEST_PASS ? 0 : 1; 1647 } else { 1648 kit_hex_encode(hex, result.output_tree, KIT_BUILD_HASH_LEN); 1649 driver_printf("%s %s\n", hex, result.path); 1650 rc = 0; 1651 } 1652 1653 out: 1654 if (coord) kit_build_coordinator_close(coord); 1655 out_host_only: 1656 if (abs_store) driver_free(&env, abs_store, abs_store_size); 1657 if (abs_root) driver_free(&env, abs_root, abs_root_size); 1658 driver_build_host_fini(&bh); 1659 out_env: 1660 if (default_store) driver_free(&env, default_store, default_store_size); 1661 driver_env_fini(&env); 1662 return rc; 1663 }