kit

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

workspace.c (11843B)


      1 #include "workspace.h"
      2 
      3 #include <stdio.h>
      4 #include <string.h>
      5 
      6 static int set_err(char* err, size_t cap, const char* msg) {
      7   if (err && cap) snprintf(err, cap, "%s", msg);
      8   return BUILD_ERR;
      9 }
     10 
     11 static int slice_eq_cstr(KitSlice s, const char* c) {
     12   size_t n = c ? strlen(c) : 0u;
     13   return s.len == n && (n == 0u || memcmp(s.s, c, n) == 0);
     14 }
     15 
     16 static int token_valid(KitSlice s, size_t cap) {
     17   size_t i;
     18   if (!s.s || s.len == 0u || s.len >= cap) return 0;
     19   for (i = 0; i < s.len; ++i) {
     20     unsigned char c = (unsigned char)s.s[i];
     21     if (c == '\0' || c == '\n' || c == '\r' || c == ' ' || c == '\t' ||
     22         c == '/' || c == ':' || c == '\\')
     23       return 0;
     24   }
     25   return 1;
     26 }
     27 
     28 static int value_valid(KitSlice s, size_t cap) {
     29   size_t i;
     30   if (!s.s || s.len >= cap) return 0;
     31   for (i = 0; i < s.len; ++i) {
     32     unsigned char c = (unsigned char)s.s[i];
     33     if (c == '\0' || c == '\n' || c == '\r') return 0;
     34   }
     35   return 1;
     36 }
     37 
     38 static int rel_path_valid(KitSlice s) {
     39   size_t i, start = 0;
     40   if (!s.s || s.len == 0u || s.len >= BUILD_PATH_MAX) return 0;
     41   if (s.s[0] == '/') return 0;
     42   for (i = 0; i <= s.len; ++i) {
     43     if (i == s.len || s.s[i] == '/') {
     44       size_t n = i - start;
     45       if (n == 0u) return 0;
     46       if (n == 1u && s.s[start] == '.') return 0;
     47       if (n == 2u && s.s[start] == '.' && s.s[start + 1u] == '.') return 0;
     48       start = i + 1u;
     49     } else if (s.s[i] == '\\' || s.s[i] == ':') {
     50       return 0;
     51     }
     52   }
     53   return 1;
     54 }
     55 
     56 static int next_line(const uint8_t* data, size_t len, size_t* pos,
     57                      KitSlice* line) {
     58   size_t start = *pos;
     59   size_t end = start;
     60   while (end < len && data[end] != '\n') ++end;
     61   if (end == len) return BUILD_ERR;
     62   line->data = data + start;
     63   line->len = end - start;
     64   *pos = end + 1u;
     65   return BUILD_OK;
     66 }
     67 
     68 static void copy_slice(char* dst, size_t cap, KitSlice s) {
     69   (void)cap;
     70   if (s.len) memcpy(dst, s.s, s.len);
     71   dst[s.len] = '\0';
     72 }
     73 
     74 static int split_word(KitSlice line, KitSlice* key, KitSlice* rest) {
     75   size_t i = 0;
     76   while (i < line.len && line.s[i] != ' ' && line.s[i] != '\t') ++i;
     77   key->s = line.s;
     78   key->len = i;
     79   while (i < line.len && (line.s[i] == ' ' || line.s[i] == '\t')) ++i;
     80   rest->s = line.s + i;
     81   rest->len = line.len - i;
     82   return key->len != 0u ? BUILD_OK : BUILD_ERR;
     83 }
     84 
     85 static int parse_header(KitSlice line, KitSlice* kind, KitSlice* arg) {
     86   size_t i;
     87   if (line.len < 3u || line.s[0] != '[' || line.s[line.len - 1u] != ']')
     88     return BUILD_ERR;
     89   line.s++;
     90   line.len -= 2u;
     91   for (i = 0; i < line.len && line.s[i] != ' ' && line.s[i] != '\t'; ++i) {}
     92   kind->s = line.s;
     93   kind->len = i;
     94   while (i < line.len && (line.s[i] == ' ' || line.s[i] == '\t')) ++i;
     95   arg->s = line.s + i;
     96   arg->len = line.len - i;
     97   return kind->len != 0u ? BUILD_OK : BUILD_ERR;
     98 }
     99 
    100 static int line_is_header(KitSlice line) {
    101   return line.len >= 2u && line.s[0] == '[' && line.s[line.len - 1u] == ']';
    102 }
    103 
    104 void build_workspace_init(BuildWorkspace* ws) {
    105   size_t i;
    106   if (!ws) return;
    107   memset(ws, 0, sizeof *ws);
    108   snprintf(ws->def_name, sizeof ws->def_name, "BUILD.kit");
    109   for (i = 0; i < sizeof ws->profiles / sizeof ws->profiles[0]; ++i)
    110     build_config_init(&ws->profiles[i].cfg, ws->profiles[i].entries,
    111                       sizeof ws->profiles[i].entries /
    112                           sizeof ws->profiles[i].entries[0]);
    113 }
    114 
    115 static int parse_hex_id(KitSlice s, uint8_t out[BUILD_HASH_LEN]) {
    116   char hex[BUILD_HEX_LEN];
    117   if (s.len + 1u != sizeof hex) return BUILD_ERR;
    118   memcpy(hex, s.s, s.len);
    119   hex[s.len] = '\0';
    120   return kit_hex_decode(out, hex, BUILD_HASH_LEN) == KIT_OK ? BUILD_OK
    121                                                             : BUILD_ERR;
    122 }
    123 
    124 int build_workspace_parse(const uint8_t* data, size_t len, BuildWorkspace* out,
    125                           char* err, size_t errcap) {
    126   size_t pos = 0;
    127   KitSlice line;
    128   enum { ST_TOP, ST_CONFIG, ST_EXTERNAL } stanza = ST_TOP;
    129   BuildWorkspaceConfigProfile* cur_cfg = NULL;
    130   BuildWorkspaceExternal* cur_ext = NULL;
    131   if (!data || !out) return set_err(err, errcap, "missing workspace");
    132   build_workspace_init(out);
    133   if (next_line(data, len, &pos, &line) != BUILD_OK ||
    134       !kit_slice_eq_cstr(line, "kit-workspace 1"))
    135     return set_err(err, errcap, "bad workspace magic/version");
    136   while (pos < len) {
    137     KitSlice key, rest, kind, arg;
    138     if (next_line(data, len, &pos, &line) != BUILD_OK)
    139       return set_err(err, errcap, "unterminated workspace line");
    140     if (line.len == 0u) continue;
    141     if (line_is_header(line)) {
    142       if (parse_header(line, &kind, &arg) != BUILD_OK)
    143         return set_err(err, errcap, "bad workspace stanza");
    144       cur_cfg = NULL;
    145       cur_ext = NULL;
    146       if (slice_eq_cstr(kind, "config")) {
    147         if (!token_valid(arg, BUILD_KEY_MAX) ||
    148             out->n_profiles >= sizeof out->profiles / sizeof out->profiles[0])
    149           return set_err(err, errcap, "bad config profile");
    150         cur_cfg = &out->profiles[out->n_profiles++];
    151         build_config_init(&cur_cfg->cfg, cur_cfg->entries,
    152                           sizeof cur_cfg->entries / sizeof cur_cfg->entries[0]);
    153         copy_slice(cur_cfg->name, sizeof cur_cfg->name, arg);
    154         stanza = ST_CONFIG;
    155       } else if (slice_eq_cstr(kind, "external")) {
    156         if (!token_valid(arg, BUILD_KEY_MAX) ||
    157             out->n_externals >=
    158                 sizeof out->externals / sizeof out->externals[0])
    159           return set_err(err, errcap, "bad external repo");
    160         cur_ext = &out->externals[out->n_externals++];
    161         memset(cur_ext, 0, sizeof *cur_ext);
    162         copy_slice(cur_ext->name, sizeof cur_ext->name, arg);
    163         stanza = ST_EXTERNAL;
    164       } else {
    165         return set_err(err, errcap, "unknown workspace stanza");
    166       }
    167       continue;
    168     }
    169     if (split_word(line, &key, &rest) != BUILD_OK)
    170       return set_err(err, errcap, "bad workspace line");
    171     if (stanza == ST_TOP) {
    172       if (slice_eq_cstr(key, "name")) {
    173         if (!token_valid(rest, BUILD_KEY_MAX))
    174           return set_err(err, errcap, "bad workspace name");
    175         copy_slice(out->name, sizeof out->name, rest);
    176       } else if (slice_eq_cstr(key, "version")) {
    177         if (!value_valid(rest, BUILD_VAL_MAX))
    178           return set_err(err, errcap, "bad workspace version");
    179         copy_slice(out->version, sizeof out->version, rest);
    180       } else if (slice_eq_cstr(key, "def-name")) {
    181         if (!rel_path_valid(rest) || memchr(rest.s, '/', rest.len))
    182           return set_err(err, errcap, "bad workspace def-name");
    183         copy_slice(out->def_name, sizeof out->def_name, rest);
    184       } else {
    185         return set_err(err, errcap, "unknown workspace field");
    186       }
    187     } else if (stanza == ST_CONFIG) {
    188       if (!cur_cfg) return set_err(err, errcap, "internal config state");
    189       if (slice_eq_cstr(key, "inherits")) {
    190         if (cur_cfg->inherits[0] || !token_valid(rest, BUILD_KEY_MAX))
    191           return set_err(err, errcap, "bad config inheritance");
    192         copy_slice(cur_cfg->inherits, sizeof cur_cfg->inherits, rest);
    193       } else if (build_config_set(&cur_cfg->cfg, key, rest) != BUILD_OK) {
    194         return set_err(err, errcap, "bad config entry");
    195       }
    196     } else if (stanza == ST_EXTERNAL) {
    197       if (!cur_ext) return set_err(err, errcap, "internal external state");
    198       if (slice_eq_cstr(key, "format")) {
    199         if (slice_eq_cstr(rest, "tree"))
    200           cur_ext->format = BUILD_WS_EXT_TREE;
    201         else if (slice_eq_cstr(rest, "kpkg"))
    202           cur_ext->format = BUILD_WS_EXT_KPKG;
    203         else if (slice_eq_cstr(rest, "tar.gz"))
    204           cur_ext->format = BUILD_WS_EXT_TARGZ;
    205         else
    206           return set_err(err, errcap, "bad external format");
    207       } else if (slice_eq_cstr(key, "archive")) {
    208         if (cur_ext->has_archive ||
    209             parse_hex_id(rest, cur_ext->archive) != BUILD_OK)
    210           return set_err(err, errcap, "bad external archive");
    211         cur_ext->has_archive = 1;
    212       } else if (slice_eq_cstr(key, "package")) {
    213         if (cur_ext->has_package ||
    214             parse_hex_id(rest, cur_ext->package) != BUILD_OK)
    215           return set_err(err, errcap, "bad external package");
    216         cur_ext->has_package = 1;
    217       } else if (slice_eq_cstr(key, "url")) {
    218         if (!value_valid(rest, BUILD_URL_MAX) ||
    219             cur_ext->n_urls >= sizeof cur_ext->urls / sizeof cur_ext->urls[0])
    220           return set_err(err, errcap, "bad external url");
    221         copy_slice(cur_ext->urls[cur_ext->n_urls],
    222                    sizeof cur_ext->urls[cur_ext->n_urls], rest);
    223         ++cur_ext->n_urls;
    224       } else if (slice_eq_cstr(key, "strip-prefix")) {
    225         if (!rel_path_valid(rest))
    226           return set_err(err, errcap, "bad external strip-prefix");
    227         copy_slice(cur_ext->strip_prefix, sizeof cur_ext->strip_prefix, rest);
    228       } else {
    229         return set_err(err, errcap, "unknown external field");
    230       }
    231     }
    232   }
    233   for (pos = 0; pos < out->n_externals; ++pos) {
    234     if (out->externals[pos].format == BUILD_WS_EXT_NONE ||
    235         !out->externals[pos].has_archive)
    236       return set_err(err, errcap, "incomplete external repo");
    237   }
    238   return BUILD_OK;
    239 }
    240 
    241 const BuildWorkspaceConfigProfile* build_workspace_profile_find(
    242     const BuildWorkspace* ws, KitSlice name) {
    243   size_t i;
    244   if (!ws || !token_valid(name, BUILD_KEY_MAX)) return NULL;
    245   for (i = 0; i < ws->n_profiles; ++i) {
    246     if (strlen(ws->profiles[i].name) == name.len &&
    247         memcmp(ws->profiles[i].name, name.s, name.len) == 0)
    248       return &ws->profiles[i];
    249   }
    250   return NULL;
    251 }
    252 
    253 const BuildWorkspaceExternal* build_workspace_external_find(
    254     const BuildWorkspace* ws, KitSlice name) {
    255   size_t i;
    256   if (!ws || !token_valid(name, BUILD_KEY_MAX)) return NULL;
    257   for (i = 0; i < ws->n_externals; ++i) {
    258     if (strlen(ws->externals[i].name) == name.len &&
    259         memcmp(ws->externals[i].name, name.s, name.len) == 0)
    260       return &ws->externals[i];
    261   }
    262   return NULL;
    263 }
    264 
    265 static int apply_profile(const BuildWorkspace* ws,
    266                          const BuildWorkspaceConfigProfile* p,
    267                          BuildConfig* out, int depth, char* err,
    268                          size_t errcap) {
    269   size_t i;
    270   if (!p) return BUILD_OK;
    271   if (depth > 16) return set_err(err, errcap, "config inheritance cycle");
    272   if (p->inherits[0]) {
    273     const BuildWorkspaceConfigProfile* parent =
    274         build_workspace_profile_find(ws, kit_slice_cstr(p->inherits));
    275     if (!parent) return set_err(err, errcap, "missing inherited config");
    276     if (apply_profile(ws, parent, out, depth + 1, err, errcap) != BUILD_OK)
    277       return BUILD_ERR;
    278   }
    279   for (i = 0; i < p->cfg.n; ++i) {
    280     if (build_config_set(out, kit_slice_cstr(p->cfg.entries[i].key),
    281                          kit_slice_cstr(p->cfg.entries[i].value)) != BUILD_OK)
    282       return set_err(err, errcap, "workspace config overflow");
    283   }
    284   return BUILD_OK;
    285 }
    286 
    287 int build_workspace_config_apply(const BuildWorkspace* ws, KitSlice profile,
    288                                  const KitBuildKV* overrides,
    289                                  size_t noverrides, BuildConfig* out,
    290                                  char* err, size_t errcap) {
    291   const BuildWorkspaceConfigProfile* def;
    292   size_t i;
    293   if (!ws || !out) return BUILD_ERR;
    294   out->n = 0;
    295   def = build_workspace_profile_find(ws, KIT_SLICE_LIT("default"));
    296   if (apply_profile(ws, def, out, 0, err, errcap) != BUILD_OK)
    297     return BUILD_ERR;
    298   if (profile.s && profile.len) {
    299     const BuildWorkspaceConfigProfile* p =
    300         build_workspace_profile_find(ws, profile);
    301     if (!p) return set_err(err, errcap, "unknown workspace config profile");
    302     if (apply_profile(ws, p, out, 0, err, errcap) != BUILD_OK)
    303       return BUILD_ERR;
    304   }
    305   for (i = 0; i < noverrides; ++i) {
    306     if (build_config_set(out, overrides[i].key, overrides[i].value) != BUILD_OK)
    307       return set_err(err, errcap, "workspace config override overflow");
    308   }
    309   return BUILD_OK;
    310 }