1 /***********************************************************************************************************************************
2 Parse Configuration Yaml
3 ***********************************************************************************************************************************/
4 #include "build.auto.h"
5 
6 #include <yaml.h>
7 
8 #include "common/log.h"
9 #include "common/type/convert.h"
10 #include "storage/posix/storage.h"
11 
12 #include "build/common/yaml.h"
13 #include "build/config/parse.h"
14 
15 /***********************************************************************************************************************************
16 Command role constants
17 ***********************************************************************************************************************************/
18 STRING_EXTERN(CMD_ROLE_ASYNC_STR,                                   CMD_ROLE_ASYNC);
19 STRING_EXTERN(CMD_ROLE_LOCAL_STR,                                   CMD_ROLE_LOCAL);
20 STRING_EXTERN(CMD_ROLE_MAIN_STR,                                    CMD_ROLE_MAIN);
21 STRING_EXTERN(CMD_ROLE_REMOTE_STR,                                  CMD_ROLE_REMOTE);
22 
23 /***********************************************************************************************************************************
24 Command constants
25 ***********************************************************************************************************************************/
26 STRING_EXTERN(CMD_HELP_STR,                                         CMD_HELP);
27 STRING_EXTERN(CMD_VERSION_STR,                                      CMD_VERSION);
28 
29 /***********************************************************************************************************************************
30 Option type constants
31 ***********************************************************************************************************************************/
32 STRING_EXTERN(OPT_TYPE_BOOLEAN_STR,                                 OPT_TYPE_BOOLEAN);
33 STRING_EXTERN(OPT_TYPE_HASH_STR,                                    OPT_TYPE_HASH);
34 STRING_EXTERN(OPT_TYPE_LIST_STR,                                    OPT_TYPE_LIST);
35 STRING_EXTERN(OPT_TYPE_STRING_STR,                                  OPT_TYPE_STRING);
36 STRING_EXTERN(OPT_TYPE_TIME_STR,                                    OPT_TYPE_TIME);
37 
38 /***********************************************************************************************************************************
39 Option constants
40 ***********************************************************************************************************************************/
41 STRING_EXTERN(OPT_STANZA_STR,                                       OPT_STANZA);
42 
43 /***********************************************************************************************************************************
44 Section constants
45 ***********************************************************************************************************************************/
46 STRING_EXTERN(SECTION_COMMAND_LINE_STR,                             SECTION_COMMAND_LINE);
47 STRING_EXTERN(SECTION_GLOBAL_STR,                                   SECTION_GLOBAL);
48 STRING_EXTERN(SECTION_STANZA_STR,                                   SECTION_STANZA);
49 
50 /***********************************************************************************************************************************
51 Parse command list
52 ***********************************************************************************************************************************/
53 typedef struct BldCfgCommandRaw
54 {
55     const String *const name;                                       // See BldCfgCommand for comments
56     bool logFile;
57     const String *logLevelDefault;
58     bool lockRequired;
59     bool lockRemoteRequired;
60     const String *lockType;
61     bool parameterAllowed;
62     StringList *roleList;
63 } BldCfgCommandRaw;
64 
65 // Helper to parse command roles
66 static StringList *
bldCfgParseCommandRole(Yaml * const yaml)67 bldCfgParseCommandRole(Yaml *const yaml)
68 {
69     StringList *const result = strLstNew();
70 
71     MEM_CONTEXT_TEMP_BEGIN()
72     {
73         yamlEventNextCheck(yaml, yamlEventTypeMapBegin);
74         YamlEvent commandRoleVal = yamlEventNext(yaml);
75 
76         if (commandRoleVal.type != yamlEventTypeMapEnd)
77         {
78             do
79             {
80                 yamlEventCheck(commandRoleVal, yamlEventTypeScalar);
81 
82                 strLstAdd(result, commandRoleVal.value);
83 
84                 yamlEventNextCheck(yaml, yamlEventTypeMapBegin);
85                 yamlEventNextCheck(yaml, yamlEventTypeMapEnd);
86 
87                 commandRoleVal = yamlEventNext(yaml);
88             }
89             while (commandRoleVal.type != yamlEventTypeMapEnd);
90         }
91     }
92     MEM_CONTEXT_TEMP_END();
93 
94     return result;
95 }
96 
97 static List *
bldCfgParseCommandList(Yaml * const yaml)98 bldCfgParseCommandList(Yaml *const yaml)
99 {
100     List *const result = lstNewP(sizeof(BldCfgCommand), .comparator = lstComparatorStr);
101 
102     MEM_CONTEXT_TEMP_BEGIN()
103     {
104         yamlEventNextCheck(yaml, yamlEventTypeScalar);
105         yamlEventNextCheck(yaml, yamlEventTypeMapBegin);
106 
107         YamlEvent cmd = yamlEventNext(yaml);
108 
109         do
110         {
111             yamlEventCheck(cmd, yamlEventTypeScalar);
112 
113             BldCfgCommandRaw cmdRaw =
114             {
115                 .name = cmd.value,
116                 .logFile = true,
117                 .logLevelDefault = strNewZ("info"),
118                 .lockType = strNewZ("none"),
119             };
120 
121             yamlEventNextCheck(yaml, yamlEventTypeMapBegin);
122 
123             YamlEvent cmdDef = yamlEventNext(yaml);
124 
125             if (cmdDef.type == yamlEventTypeScalar)
126             {
127                 do
128                 {
129                     yamlEventCheck(cmdDef, yamlEventTypeScalar);
130 
131                     if (strEqZ(cmdDef.value, "command-role"))
132                     {
133                         cmdRaw.roleList = bldCfgParseCommandRole(yaml);
134                     }
135                     else
136                     {
137                         YamlEvent cmdDefVal = yamlEventNextCheck(yaml, yamlEventTypeScalar);
138 
139                         if (strEqZ(cmdDef.value, "internal"))
140                         {
141                         }
142                         else if (strEqZ(cmdDef.value, "lock-type"))
143                         {
144                             cmdRaw.lockType = cmdDefVal.value;
145                         }
146                         else if (strEqZ(cmdDef.value, "lock-remote-required"))
147                         {
148                             cmdRaw.lockRemoteRequired = yamlBoolParse(cmdDefVal);
149                         }
150                         else if (strEqZ(cmdDef.value, "lock-required"))
151                         {
152                             cmdRaw.lockRequired = yamlBoolParse(cmdDefVal);
153                         }
154                         else if (strEqZ(cmdDef.value, "log-file"))
155                         {
156                             cmdRaw.logFile = yamlBoolParse(cmdDefVal);
157                         }
158                         else if (strEqZ(cmdDef.value, "log-level-default"))
159                         {
160                             cmdRaw.logLevelDefault = strLower(strDup(cmdDefVal.value));
161                         }
162                         else if (strEqZ(cmdDef.value, "parameter-allowed"))
163                         {
164                             cmdRaw.parameterAllowed = strLower(strDup(cmdDefVal.value));
165                         }
166                         else
167                             THROW_FMT(FormatError, "unknown command definition '%s'", strZ(cmdDef.value));
168                     }
169 
170                     cmdDef = yamlEventNext(yaml);
171                 }
172                 while (cmdDef.type != yamlEventTypeMapEnd);
173             }
174             else
175                 yamlEventCheck(cmdDef, yamlEventTypeMapEnd);
176 
177             // Create role list if not defined
178             if (cmdRaw.roleList == NULL)
179                 cmdRaw.roleList = strLstNew();
180 
181             // Add main to the role list and resort
182             strLstAddIfMissing(cmdRaw.roleList, CMD_ROLE_MAIN_STR);
183             strLstSort(cmdRaw.roleList, sortOrderAsc);
184 
185             MEM_CONTEXT_BEGIN(lstMemContext(result))
186             {
187                 lstAdd(
188                     result,
189                     &(BldCfgCommand)
190                     {
191                         .name = strDup(cmdRaw.name),
192                         .logFile = cmdRaw.logFile,
193                         .logLevelDefault = strDup(cmdRaw.logLevelDefault),
194                         .lockRequired = cmdRaw.lockRequired,
195                         .lockRemoteRequired = cmdRaw.lockRemoteRequired,
196                         .lockType = strDup(cmdRaw.lockType),
197                         .parameterAllowed = cmdRaw.parameterAllowed,
198                         .roleList = strLstDup(cmdRaw.roleList),
199                     });
200             }
201             MEM_CONTEXT_END();
202 
203             cmd = yamlEventNext(yaml);
204         }
205         while (cmd.type != yamlEventTypeMapEnd);
206 
207         lstSort(result, sortOrderAsc);
208     }
209     MEM_CONTEXT_TEMP_END();
210 
211     return result;
212 }
213 
214 /***********************************************************************************************************************************
215 Parse option group list
216 ***********************************************************************************************************************************/
217 typedef struct BldCfgOptionGroupRaw
218 {
219     const String *const name;                                       // See BldCfgOptionGroup for comments
220     const Variant *indexTotal;
221 } BldCfgOptionGroupRaw;
222 
223 static List *
bldCfgParseOptionGroupList(Yaml * const yaml)224 bldCfgParseOptionGroupList(Yaml *const yaml)
225 {
226     List *const result = lstNewP(sizeof(BldCfgOptionGroup), .comparator = lstComparatorStr);
227 
228     MEM_CONTEXT_TEMP_BEGIN()
229     {
230         yamlEventNextCheck(yaml, yamlEventTypeScalar);
231         yamlEventNextCheck(yaml, yamlEventTypeMapBegin);
232 
233         YamlEvent optGrp = yamlEventNext(yaml);
234 
235         do
236         {
237             yamlEventCheck(optGrp, yamlEventTypeScalar);
238             BldCfgOptionGroupRaw optGrpRaw = {.name = optGrp.value};
239 
240             yamlEventNextCheck(yaml, yamlEventTypeMapBegin);
241 
242             YamlEvent optGrpDef = yamlEventNext(yaml);
243 
244             do
245             {
246                 yamlEventCheck(optGrpDef, yamlEventTypeScalar);
247                 YamlEvent optGrpDefVal = yamlEventNextCheck(yaml, yamlEventTypeScalar);
248 
249                 if (strEqZ(optGrpDef.value, "indexTotal"))
250                 {
251                     optGrpRaw.indexTotal = varNewUInt(cvtZToUInt(strZ(optGrpDefVal.value)));
252                 }
253                 else if (strEqZ(optGrpDef.value, "prefix"))
254                 {
255                     // ??? This is the same as the name so should be removed
256                 }
257                 else
258                     THROW_FMT(FormatError, "unknown option group definition '%s'", strZ(optGrpDef.value));
259 
260                 optGrpDef = yamlEventNext(yaml);
261             }
262             while (optGrpDef.type != yamlEventTypeMapEnd);
263 
264             // indexTotal is required
265             if (optGrpRaw.indexTotal == NULL)
266                 THROW_FMT(FormatError, "option group '%s' requires 'indexTotal'", strZ(optGrpRaw.name));
267 
268             MEM_CONTEXT_BEGIN(lstMemContext(result))
269             {
270                 lstAdd(result, &(BldCfgOptionGroup){.name = strDup(optGrpRaw.name), .indexTotal = varUInt(optGrpRaw.indexTotal)});
271             }
272             MEM_CONTEXT_END();
273 
274             optGrp = yamlEventNext(yaml);
275         }
276         while (optGrp.type != yamlEventTypeMapEnd);
277 
278         lstSort(result, sortOrderAsc);
279     }
280     MEM_CONTEXT_TEMP_END();
281 
282     return result;
283 }
284 
285 /***********************************************************************************************************************************
286 Parse option list
287 ***********************************************************************************************************************************/
288 typedef struct BldCfgOptionDependRaw
289 {
290     const String *option;                                           // See BldCfgOptionDepend for comments
291     const StringList *valueList;
292 } BldCfgOptionDependRaw;
293 
294 typedef struct BldCfgOptionDeprecateRaw
295 {
296     const String *name;                                             // See BldCfgOptionDeprecate for comments
297     unsigned int index;
298     bool reset;
299 } BldCfgOptionDeprecateRaw;
300 
301 typedef struct BldCfgOptionCommandRaw
302 {
303     const String *name;                                             // See BldCfgOptionCommand for comments
304     const Variant *required;
305     const String *defaultValue;
306     const BldCfgOptionDependRaw *depend;
307     const StringList *allowList;
308     const StringList *roleList;
309 } BldCfgOptionCommandRaw;
310 
311 typedef struct BldCfgOptionRaw
312 {
313     const String *name;                                             // See BldCfgOption for comments
314     const String *type;
315     const String *section;
316     const Variant *required;
317     const Variant *negate;
318     bool reset;
319     const String *defaultValue;
320     bool defaultLiteral;
321     const String *group;
322     bool secure;
323     const BldCfgOptionDependRaw *depend;
324     const StringList *allowList;
325     const String *allowRangeMin;
326     const String *allowRangeMax;
327     const List *cmdList;
328     const StringList *cmdRoleList;
329     const List *deprecateList;
330 } BldCfgOptionRaw;
331 
332 // Helper to parse allow list
333 static const StringList *
bldCfgParseAllowList(Yaml * const yaml,const List * const optList)334 bldCfgParseAllowList(Yaml *const yaml, const List *const optList)
335 {
336     StringList *result = NULL;
337 
338     MEM_CONTEXT_TEMP_BEGIN()
339     {
340         YamlEvent allowListVal = yamlEventNext(yaml);
341 
342         // If allow list is defined
343         if (allowListVal.type == yamlEventTypeSeqBegin)
344         {
345             YamlEvent allowListVal = yamlEventNext(yaml);
346 
347             MEM_CONTEXT_PRIOR_BEGIN()
348             {
349                 result = strLstNew();
350             }
351             MEM_CONTEXT_PRIOR_END();
352 
353             do
354             {
355                 yamlEventCheck(allowListVal, yamlEventTypeScalar);
356                 strLstAdd(result, allowListVal.value);
357 
358                 allowListVal = yamlEventNext(yaml);
359             }
360             while (allowListVal.type != yamlEventTypeSeqEnd);
361         }
362         else
363         {
364 
365             // Else allow list is inherited
366             CHECK(optList != NULL);
367             yamlEventCheck(allowListVal, yamlEventTypeScalar);
368 
369             const BldCfgOptionRaw *const optInherit = lstFind(optList, &allowListVal.value);
370             CHECK(optInherit != NULL);
371 
372             MEM_CONTEXT_PRIOR_BEGIN()
373             {
374                 result = strLstDup(optInherit->allowList);
375             }
376             MEM_CONTEXT_PRIOR_END();
377         }
378 
379         CHECK(result != NULL);
380     }
381     MEM_CONTEXT_TEMP_END();
382 
383     return result;
384 }
385 
386 // Helper to parse allow range
387 static void
bldCfgParseAllowRange(Yaml * const yaml,BldCfgOptionRaw * const opt)388 bldCfgParseAllowRange(Yaml *const yaml, BldCfgOptionRaw *const opt)
389 {
390     MEM_CONTEXT_TEMP_BEGIN()
391     {
392         yamlEventNextCheck(yaml, yamlEventTypeSeqBegin);
393 
394         YamlEvent allowRangeMinVal = yamlEventNextCheck(yaml, yamlEventTypeScalar);
395         YamlEvent allowRangeMaxVal = yamlEventNextCheck(yaml, yamlEventTypeScalar);
396 
397         MEM_CONTEXT_PRIOR_BEGIN()
398         {
399             opt->allowRangeMin = strDup(allowRangeMinVal.value);
400             opt->allowRangeMax = strDup(allowRangeMaxVal.value);
401         }
402         MEM_CONTEXT_PRIOR_END();
403 
404         yamlEventNextCheck(yaml, yamlEventTypeSeqEnd);
405     }
406     MEM_CONTEXT_TEMP_END();
407 }
408 
409 // Helper to parse depend
410 static const BldCfgOptionDependRaw *
bldCfgParseDepend(Yaml * const yaml,const List * const optList)411 bldCfgParseDepend(Yaml *const yaml, const List *const optList)
412 {
413     const BldCfgOptionDependRaw *result = NULL;
414 
415     MEM_CONTEXT_TEMP_BEGIN()
416     {
417         YamlEvent dependVal = yamlEventNext(yaml);
418 
419         if (dependVal.type == yamlEventTypeMapBegin)
420         {
421             YamlEvent dependDef = yamlEventNext(yaml);
422             BldCfgOptionDependRaw optDependRaw = {0};
423 
424             do
425             {
426                 yamlEventCheck(dependDef, yamlEventTypeScalar);
427 
428                 if (strEqZ(dependDef.value, "list"))
429                 {
430                     yamlEventNextCheck(yaml, yamlEventTypeSeqBegin);
431                     YamlEvent dependDefVal = yamlEventNext(yaml);
432 
433                     StringList *const valueList = strLstNew();
434 
435                     do
436                     {
437                         yamlEventCheck(dependDefVal, yamlEventTypeScalar);
438 
439                         strLstAdd(valueList, dependDefVal.value);
440 
441                         dependDefVal = yamlEventNext(yaml);
442                     }
443                     while (dependDefVal.type != yamlEventTypeSeqEnd);
444 
445                     optDependRaw.valueList = valueList;
446                 }
447                 else
448                 {
449                     YamlEvent dependDefVal = yamlEventNext(yaml);
450                     yamlEventCheck(dependDefVal, yamlEventTypeScalar);
451 
452                     if (strEqZ(dependDef.value, "option"))
453                     {
454                         optDependRaw.option = dependDefVal.value;
455                     }
456                     else
457                         THROW_FMT(FormatError, "unknown depend definition '%s'", strZ(dependDef.value));
458                 }
459 
460                 dependDef = yamlEventNext(yaml);
461             }
462             while (dependDef.type != yamlEventTypeMapEnd);
463 
464             MEM_CONTEXT_PRIOR_BEGIN()
465             {
466                 BldCfgOptionDependRaw *optDepend = memNew(sizeof(BldCfgOptionDependRaw));
467 
468                 *optDepend = (BldCfgOptionDependRaw)
469                 {
470                     .option = strDup(optDependRaw.option),
471                     .valueList = strLstDup(optDependRaw.valueList)
472                 };
473 
474                 result = optDepend;
475             }
476             MEM_CONTEXT_PRIOR_END();
477         }
478         else
479         {
480             // Else depend is inherited
481             CHECK(optList != NULL);
482             yamlEventCheck(dependVal, yamlEventTypeScalar);
483 
484             const BldCfgOptionRaw *const optInherit = lstFind(optList, &dependVal.value);
485 
486             if (optInherit == NULL)
487                 THROW_FMT(FormatError, "dependency inherited from option '%s' before it is defined", strZ(dependVal.value));
488 
489             result = optInherit->depend;
490         }
491     }
492     MEM_CONTEXT_TEMP_END();
493 
494     return result;
495 
496 }
497 
498 // Helper to reconcile depend
499 static const BldCfgOptionDepend *
bldCfgParseDependReconcile(const BldCfgOptionDependRaw * const optDependRaw,const List * const optList)500 bldCfgParseDependReconcile(const BldCfgOptionDependRaw *const optDependRaw, const List *const optList)
501 {
502     BldCfgOptionDepend *result = NULL;
503 
504     if (optDependRaw != NULL)
505     {
506         const BldCfgOption *const optDepend = lstFind(optList, &optDependRaw->option);
507 
508         if (optDepend == NULL)
509             THROW_FMT(FormatError, "dependency on undefined option '%s'", strZ(optDependRaw->option));
510 
511         result = memNew(sizeof(BldCfgOptionDepend));
512 
513         memcpy(
514             result,
515             &(BldCfgOptionDepend){.option = optDepend, .valueList = strLstDup(optDependRaw->valueList)},
516             sizeof(BldCfgOptionDepend));
517     }
518 
519     return result;
520 }
521 
522 // Helper to parse deprecate
523 static List *
bldCfgParseOptionDeprecate(Yaml * const yaml)524 bldCfgParseOptionDeprecate(Yaml *const yaml)
525 {
526     List *result = lstNewP(sizeof(BldCfgOptionCommandRaw), .comparator = lstComparatorStr);
527 
528     MEM_CONTEXT_TEMP_BEGIN()
529     {
530         yamlEventNextCheck(yaml, yamlEventTypeMapBegin);
531         YamlEvent optDeprecate = yamlEventNext(yaml);
532 
533         do
534         {
535             yamlEventCheck(optDeprecate, yamlEventTypeScalar);
536             BldCfgOptionDeprecateRaw optDeprecateRaw = {.name = optDeprecate.value, .reset = true};
537 
538             yamlEventNextCheck(yaml, yamlEventTypeMapBegin);
539 
540             YamlEvent optDeprecateDef = yamlEventNext(yaml);
541 
542             if (optDeprecateDef.type == yamlEventTypeScalar)
543             {
544                 do
545                 {
546                     yamlEventCheck(optDeprecateDef, yamlEventTypeScalar);
547                     YamlEvent optDeprecateDefVal = yamlEventNextCheck(yaml, yamlEventTypeScalar);
548 
549                     if (strEqZ(optDeprecateDef.value, "index"))
550                     {
551                         optDeprecateRaw.index = cvtZToUInt(strZ(optDeprecateDefVal.value));
552                     }
553                     else if (strEqZ(optDeprecateDef.value, "reset"))
554                     {
555                         optDeprecateRaw.reset = yamlBoolParse(optDeprecateDefVal);
556                     }
557                     else
558                         THROW_FMT(FormatError, "unknown deprecate definition '%s'", strZ(optDeprecateDef.value));
559 
560                     optDeprecateDef = yamlEventNext(yaml);
561                 }
562                 while (optDeprecateDef.type != yamlEventTypeMapEnd);
563             }
564             else
565                 yamlEventCheck(optDeprecateDef, yamlEventTypeMapEnd);
566 
567             MEM_CONTEXT_PRIOR_BEGIN()
568             {
569                 lstAdd(
570                     result,
571                     &(BldCfgOptionDeprecate)
572                     {
573                         .name = strDup(optDeprecateRaw.name),
574                         .index = optDeprecateRaw.index,
575                         .reset = optDeprecateRaw.reset,
576                     });
577             }
578             MEM_CONTEXT_PRIOR_END();
579 
580             optDeprecate = yamlEventNext(yaml);
581         }
582         while (optDeprecate.type != yamlEventTypeMapEnd);
583 
584         lstSort(result, sortOrderAsc);
585     }
586     MEM_CONTEXT_TEMP_END();
587 
588     return result;
589 }
590 
591 // Helper to reconcile deprecate
592 static List *
bldCfgParseOptionDeprecateReconcile(const List * const optDeprecateRawList)593 bldCfgParseOptionDeprecateReconcile(const List *const optDeprecateRawList)
594 {
595     List *result = NULL;
596 
597     if (optDeprecateRawList != NULL)
598     {
599         result = lstNewP(sizeof(BldCfgOptionDeprecate), .comparator = lstComparatorStr);
600 
601         for (unsigned int optDeprecateRawIdx = 0; optDeprecateRawIdx < lstSize(optDeprecateRawList); optDeprecateRawIdx++)
602         {
603             const BldCfgOptionDeprecateRaw *const optDeprecateRaw = lstGet(optDeprecateRawList, optDeprecateRawIdx);
604 
605             lstAdd(
606                 result,
607                 &(BldCfgOptionDeprecate)
608                 {
609                     .name = strDup(optDeprecateRaw->name),
610                     .index = optDeprecateRaw->index,
611                     .reset = optDeprecateRaw->reset,
612                 });
613         }
614     }
615 
616     return result;
617 }
618 
619 // Helper to parse the option command list
620 static const List *
bldCfgParseOptionCommandList(Yaml * const yaml,const List * const optList)621 bldCfgParseOptionCommandList(Yaml *const yaml, const List *const optList)
622 {
623     const List *result = NULL;
624 
625     MEM_CONTEXT_TEMP_BEGIN()
626     {
627         YamlEvent optCmdVal = yamlEventNext(yaml);
628 
629         // If command list is defined
630         if (optCmdVal.type == yamlEventTypeMapBegin)
631         {
632             List *const optCmdRawList = lstNewP(sizeof(BldCfgOptionCommandRaw), .comparator = lstComparatorStr);
633 
634             YamlEvent optCmd = yamlEventNext(yaml);
635 
636             do
637             {
638                 yamlEventCheck(optCmd, yamlEventTypeScalar);
639                 BldCfgOptionCommandRaw optCmdRaw = {.name = optCmd.value};
640 
641                 yamlEventNextCheck(yaml, yamlEventTypeMapBegin);
642                 YamlEvent optCmdDef = yamlEventNext(yaml);
643 
644                 if (optCmdDef.type == yamlEventTypeScalar)
645                 {
646                     do
647                     {
648                         yamlEventCheck(optCmdDef, yamlEventTypeScalar);
649 
650                         if (strEqZ(optCmdDef.value, "allow-list"))
651                         {
652                             optCmdRaw.allowList = bldCfgParseAllowList(yaml, NULL);
653                         }
654                         else if (strEqZ(optCmdDef.value, "command-role"))
655                         {
656                             optCmdRaw.roleList = bldCfgParseCommandRole(yaml);
657                         }
658                         else if (strEqZ(optCmdDef.value, "depend"))
659                         {
660                             MEM_CONTEXT_BEGIN(lstMemContext(optCmdRawList))
661                             {
662                                 optCmdRaw.depend = bldCfgParseDepend(yaml, optList);
663                             }
664                             MEM_CONTEXT_END();
665                         }
666                         else
667                         {
668                             YamlEvent optCmdDefVal = yamlEventNextCheck(yaml, yamlEventTypeScalar);
669 
670                             if (strEqZ(optCmdDef.value, "default"))
671                             {
672                                 // If an override to inheritance
673                                 if (strEqZ(optCmdDefVal.value, "~"))
674                                 {
675                                     optCmdRaw.defaultValue = NULL;
676                                 }
677                                 // Else set the value
678                                 else
679                                     optCmdRaw.defaultValue = optCmdDefVal.value;
680                             }
681                             else if (strEqZ(optCmdDef.value, "internal"))
682                             {
683                             }
684                             else if (strEqZ(optCmdDef.value, "required"))
685                             {
686                                 optCmdRaw.required = varNewBool(yamlBoolParse(optCmdDefVal));
687                             }
688                             else
689                                 THROW_FMT(FormatError, "unknown option command definition '%s'", strZ(optCmdDef.value));
690                         }
691 
692                         optCmdDef = yamlEventNext(yaml);
693                     }
694                     while (optCmdDef.type != yamlEventTypeMapEnd);
695                 }
696                 else
697                     yamlEventCheck(optCmdDef, yamlEventTypeMapEnd);
698 
699                 MEM_CONTEXT_BEGIN(lstMemContext(optCmdRawList))
700                 {
701                     lstAdd(
702                         optCmdRawList,
703                         &(BldCfgOptionCommandRaw)
704                         {
705                             .name = strDup(optCmdRaw.name),
706                             .required = varDup(optCmdRaw.required),
707                             .defaultValue = strDup(optCmdRaw.defaultValue),
708                             .depend = optCmdRaw.depend,
709                             .allowList = strLstDup(optCmdRaw.allowList),
710                             .roleList = strLstDup(optCmdRaw.roleList),
711                         });
712                 }
713                 MEM_CONTEXT_END();
714 
715                 optCmd = yamlEventNext(yaml);
716             }
717             while (optCmd.type != yamlEventTypeMapEnd);
718 
719             lstSort(optCmdRawList, sortOrderAsc);
720 
721             result = lstMove(optCmdRawList, memContextPrior());
722         }
723         // Else command list is inherited
724         else
725         {
726             CHECK(optList != NULL);
727             yamlEventCheck(optCmdVal, yamlEventTypeScalar);
728 
729             const BldCfgOptionRaw *const optInherit = lstFind(optList, &optCmdVal.value);
730             CHECK(optInherit != NULL);
731 
732             result = optInherit->cmdList;
733         }
734     }
735     MEM_CONTEXT_TEMP_END();
736 
737     return result;
738 }
739 
740 static List *
bldCfgParseOptionList(Yaml * const yaml,const List * const cmdList,const List * const optGrpList)741 bldCfgParseOptionList(Yaml *const yaml, const List *const cmdList, const List *const optGrpList)
742 {
743     List *const result = lstNewP(sizeof(BldCfgOption), .comparator = lstComparatorStr);
744 
745     MEM_CONTEXT_TEMP_BEGIN()
746     {
747         List *const optListRaw = lstNewP(sizeof(BldCfgOptionRaw), .comparator = lstComparatorStr);
748 
749         // Parse raw
750         // -------------------------------------------------------------------------------------------------------------------------
751         yamlEventNextCheck(yaml, yamlEventTypeScalar);
752         yamlEventNextCheck(yaml, yamlEventTypeMapBegin);
753 
754         YamlEvent opt = yamlEventNext(yaml);
755 
756         do
757         {
758             yamlEventCheck(opt, yamlEventTypeScalar);
759             BldCfgOptionRaw optRaw = {.name = opt.value, .required = BOOL_TRUE_VAR};
760             bool inheritFound = false;
761 
762             yamlEventNextCheck(yaml, yamlEventTypeMapBegin);
763 
764             YamlEvent optDef = yamlEventNext(yaml);
765 
766             do
767             {
768                 yamlEventCheck(optDef, yamlEventTypeScalar);
769 
770                 if (strEqZ(optDef.value, "allow-list"))
771                 {
772                     optRaw.allowList = bldCfgParseAllowList(yaml, optListRaw);
773                 }
774                 else if (strEqZ(optDef.value, "allow-range"))
775                 {
776                     bldCfgParseAllowRange(yaml, &optRaw);
777                 }
778                 else if (strEqZ(optDef.value, "command"))
779                 {
780                     optRaw.cmdList = bldCfgParseOptionCommandList(yaml, optListRaw);
781                 }
782                 else if (strEqZ(optDef.value, "command-role"))
783                 {
784                     optRaw.cmdRoleList = bldCfgParseCommandRole(yaml);
785                 }
786                 else if (strEqZ(optDef.value, "depend"))
787                 {
788                     optRaw.depend = bldCfgParseDepend(yaml, optListRaw);
789                 }
790                 else if (strEqZ(optDef.value, "deprecate"))
791                 {
792                     optRaw.deprecateList = bldCfgParseOptionDeprecate(yaml);
793                 }
794                 else
795                 {
796                     YamlEvent optDefVal = yamlEventNextCheck(yaml, yamlEventTypeScalar);
797 
798                     if (strEqZ(optDef.value, "default"))
799                     {
800                         // If an override to inheritance
801                         if (strEqZ(optDefVal.value, "~"))
802                         {
803                             optRaw.defaultValue = NULL;
804                         }
805                         // Else set the value
806                         else
807                             optRaw.defaultValue = optDefVal.value;
808                     }
809                     else if (strEqZ(optDef.value, "default-literal"))
810                     {
811                         optRaw.defaultLiteral = yamlBoolParse(optDefVal);
812                     }
813                     else if (strEqZ(optDef.value, "group"))
814                     {
815                         optRaw.group = optDefVal.value;
816 
817                         if (!lstExists(optGrpList, &optRaw.group))
818                             THROW_FMT(FormatError, "option '%s' has invalid group '%s'", strZ(optRaw.name), strZ(optRaw.group));
819                     }
820                     else if (strEqZ(optDef.value, "inherit"))
821                     {
822                         const BldCfgOptionRaw *const optInherit = lstFind(optListRaw, &optDefVal.value);
823                         CHECK(optInherit != NULL);
824 
825                         optRaw = *optInherit;
826                         optRaw.name = opt.value;
827 
828                         // Deprecations cannot be inherited
829                         optRaw.deprecateList = NULL;
830 
831                         inheritFound = true;
832                     }
833                     else if (strEqZ(optDef.value, "internal"))
834                     {
835                     }
836                     else if (strEqZ(optDef.value, "negate"))
837                     {
838                         optRaw.negate = varNewBool(yamlBoolParse(optDefVal));
839                     }
840                     else if (strEqZ(optDef.value, "required"))
841                     {
842                         optRaw.required = varNewBool(yamlBoolParse(optDefVal));
843                     }
844                     else if (strEqZ(optDef.value, "section"))
845                     {
846                         optRaw.section = optDefVal.value;
847                     }
848                     else if (strEqZ(optDef.value, "secure"))
849                     {
850                         optRaw.secure = yamlBoolParse(optDefVal);
851                     }
852                     else if (strEqZ(optDef.value, "type"))
853                     {
854                         optRaw.type = optDefVal.value;
855                     }
856                     else
857                         THROW_FMT(FormatError, "unknown option definition '%s'", strZ(optDef.value));
858                 }
859 
860                 optDef = yamlEventNext(yaml);
861             }
862             while (optDef.type != yamlEventTypeMapEnd);
863 
864             // Type is required
865             if (optRaw.type == NULL)
866                 THROW_FMT(FormatError, "option '%s' requires 'type'", strZ(optRaw.name));
867 
868             // Set defaults if not inherited
869             if (!inheritFound)
870             {
871                 // Section defaults to command line
872                 if (optRaw.section == NULL)
873                     optRaw.section = SECTION_COMMAND_LINE_STR;
874 
875                 // Set negate default if not defined
876                 if (optRaw.negate == NULL)
877                 {
878                     optRaw.negate = varNewBool(
879                         strEq(optRaw.type, OPT_TYPE_BOOLEAN_STR) && !strEq(optRaw.section, SECTION_COMMAND_LINE_STR));
880                 }
881 
882                 // Build default command list if not defined
883                 if (optRaw.cmdList == NULL)
884                 {
885                     List *optCmdList = lstNewP(sizeof(BldCfgOptionCommandRaw), .comparator = lstComparatorStr);
886 
887                     for (unsigned int cmdIdx = 0; cmdIdx < lstSize(cmdList); cmdIdx++)
888                     {
889                         const BldCfgCommand *const cmd = lstGet(cmdList, cmdIdx);
890 
891                         if (!strEq(cmd->name, CMD_HELP_STR) && !strEq(cmd->name, CMD_VERSION_STR))
892                             lstAdd(optCmdList, &(BldCfgOptionCommandRaw){.name = cmd->name});
893                     }
894 
895                     lstSort(optCmdList, sortOrderAsc);
896                     optRaw.cmdList = optCmdList;
897                 }
898             }
899 
900             // Set reset
901             optRaw.reset = !strEq(optRaw.section, SECTION_COMMAND_LINE_STR);
902 
903             lstAdd(optListRaw, &optRaw);
904 
905             opt = yamlEventNext(yaml);
906         }
907         while (opt.type != yamlEventTypeMapEnd);
908 
909         lstSort(optListRaw, sortOrderAsc);
910 
911         // Copy option list to result
912         // -------------------------------------------------------------------------------------------------------------------------
913         for (unsigned int optRawIdx = 0; optRawIdx < lstSize(optListRaw); optRawIdx++)
914         {
915             const BldCfgOptionRaw *const optRaw = lstGet(optListRaw, optRawIdx);
916 
917             MEM_CONTEXT_BEGIN(lstMemContext(result))
918             {
919                 lstAdd(
920                     result,
921                     &(BldCfgOption)
922                     {
923                         .name = strDup(optRaw->name),
924                         .type = strDup(optRaw->type),
925                         .section = strDup(optRaw->section),
926                         .required = varBool(optRaw->required),
927                         .negate = varBool(optRaw->negate),
928                         .reset = optRaw->reset,
929                         .defaultValue = strDup(optRaw->defaultValue),
930                         .defaultLiteral = optRaw->defaultLiteral,
931                         .group = strDup(optRaw->group),
932                         .secure = optRaw->secure,
933                         .allowList = strLstDup(optRaw->allowList),
934                         .allowRangeMin = strDup(optRaw->allowRangeMin),
935                         .allowRangeMax = strDup(optRaw->allowRangeMax),
936                         .deprecateList = bldCfgParseOptionDeprecateReconcile(optRaw->deprecateList),
937                 });
938             }
939             MEM_CONTEXT_END();
940         }
941 
942         // Reconcile option list
943         // -------------------------------------------------------------------------------------------------------------------------
944         for (unsigned int optRawIdx = 0; optRawIdx < lstSize(optListRaw); optRawIdx++)
945         {
946             const BldCfgOptionRaw *const optRaw = lstGet(optListRaw, optRawIdx);
947 
948             // Reconcile option command list roles
949             List *const cmdOptList = lstNewP(sizeof(BldCfgOptionCommand), .comparator = lstComparatorStr);
950 
951             for (unsigned int optCmdRawIdx = 0; optCmdRawIdx < lstSize(optRaw->cmdList); optCmdRawIdx++)
952             {
953                 BldCfgOptionCommandRaw optCmd = *(BldCfgOptionCommandRaw *)lstGet(optRaw->cmdList, optCmdRawIdx);
954 
955                 // Lookup command
956                 const BldCfgCommand *const cmd = lstFind(cmdList, &optCmd.name);
957 
958                 if (cmd == NULL)
959                 {
960                     THROW_FMT(
961                         FormatError, "invalid command '%s' in option '%s' command list", strZ(optCmd.name), strZ(optRaw->name));
962                 }
963 
964                 // Default required to option required if not defined
965                 if (optCmd.required == NULL)
966                     optCmd.required = optRaw->required;
967 
968                 // Default command role list if not defined
969                 if (optCmd.roleList == NULL)
970                 {
971                     // Use option list as default if defined. It will need to be filtered against roles valid for each command
972                     if (optRaw->cmdRoleList != NULL)
973                     {
974                         StringList *const roleList = strLstNew();
975 
976                         for (unsigned int cmdRoleIdx = 0; cmdRoleIdx < strLstSize(optRaw->cmdRoleList); cmdRoleIdx++)
977                         {
978                             const String *role = strLstGet(optRaw->cmdRoleList, cmdRoleIdx);
979 
980                             if (strLstExists(cmd->roleList, role))
981                                 strLstAdd(roleList, role);
982                         }
983 
984                         optCmd.roleList = roleList;
985                     }
986                     // Else use option command list
987                     else
988                         optCmd.roleList = cmd->roleList;
989                 }
990 
991                 CHECK(optCmd.roleList != NULL);
992 
993                 MEM_CONTEXT_BEGIN(lstMemContext(cmdOptList))
994                 {
995                     lstAdd(
996                         cmdOptList,
997                         &(BldCfgOptionCommand)
998                         {
999                             .name = strDup(optCmd.name),
1000                             .required = varBool(optCmd.required),
1001                             .defaultValue = strDup(optCmd.defaultValue),
1002                             .depend = bldCfgParseDependReconcile(optCmd.depend, result),
1003                             .allowList = strLstDup(optCmd.allowList),
1004                             .roleList = strLstDup(optCmd.roleList),
1005                         });
1006                 }
1007                 MEM_CONTEXT_END();
1008             }
1009 
1010             const BldCfgOption *const opt = lstGet(result, optRawIdx);
1011             CHECK(strEq(opt->name, optRaw->name));
1012 
1013             // Assigning to const pointers this way is definitely cheating, but since we allocated the memory and know exactly where
1014             // it is so this is the easiest way to avoid a lot of indirection due to the dependency of BldCfgOptionDepend on
1015             // BldCfgOption. This would be completely unacceptable in production code but feels OK in this context.
1016             MEM_CONTEXT_BEGIN(lstMemContext(result))
1017             {
1018                 *((List **)&opt->cmdList) = lstMove(cmdOptList, memContextCurrent());
1019                 *((const BldCfgOptionDepend **)&opt->depend) = bldCfgParseDependReconcile(optRaw->depend, result);
1020             }
1021             MEM_CONTEXT_END();
1022         }
1023     }
1024     MEM_CONTEXT_TEMP_END();
1025 
1026     return result;
1027 }
1028 
1029 /***********************************************************************************************************************************
1030 Build option resolve order list
1031 ***********************************************************************************************************************************/
1032 static List *
bldCfgParseOptionResolveList(const List * const optList)1033 bldCfgParseOptionResolveList(const List *const optList)
1034 {
1035     List *const result = lstNewP(sizeof(BldCfgOption *), .comparator = lstComparatorStr);
1036 
1037     // The stanza option will always be resolved first since errors can be confusing when it is missing. That means it must exist
1038     // and cannot have any depedencies.
1039     // -----------------------------------------------------------------------------------------------------------------------------
1040     const BldCfgOption *const optStanza = lstFind(optList, &OPT_STANZA_STR);
1041 
1042     if (optStanza == NULL)
1043         THROW(FormatError, "option '" OPT_STANZA "' must exist");
1044 
1045     if (optStanza->depend != NULL)
1046         THROW(FormatError, "option '" OPT_STANZA "' may not depend on other option");
1047 
1048     for (unsigned int optCmdIdx = 0; optCmdIdx < lstSize(optStanza->cmdList); optCmdIdx++)
1049     {
1050         const BldCfgOptionCommand *const optStanzaCmd = lstGet(optStanza->cmdList, optCmdIdx);
1051 
1052         if (optStanzaCmd->depend != NULL)
1053             THROW_FMT(FormatError, "option '" OPT_STANZA "' command '%s' may not depend on other option", strZ(optStanzaCmd->name));
1054     }
1055 
1056     // Determine resolve order
1057     // -----------------------------------------------------------------------------------------------------------------------------
1058     StringList *const optResolveList = strLstNew();
1059     strLstAdd(optResolveList, OPT_STANZA_STR);
1060 
1061     do
1062     {
1063         // Was at least one option resolved in the loop?
1064         bool resolved = false;
1065 
1066         for (unsigned int optIdx = 0; optIdx < lstSize(optList); optIdx++)
1067         {
1068             const BldCfgOption *const opt = lstGet(optList, optIdx);
1069 
1070             // If the option has already been resolved then continue
1071             if (strLstExists(optResolveList, opt->name))
1072                 continue;
1073 
1074             // If the option dependency has not been resolved then continue
1075             if (opt->depend != NULL && !strLstExists(optResolveList, opt->depend->option->name))
1076                 continue;
1077 
1078             // If the option command dependency has not been resolved then continue
1079             unsigned int optCmdIdx = 0;
1080 
1081             for (; optCmdIdx < lstSize(opt->cmdList); optCmdIdx++)
1082             {
1083                 const BldCfgOptionCommand *const optCmd = lstGet(opt->cmdList, optCmdIdx);
1084 
1085                 if (optCmd->depend != NULL && !strLstExists(optResolveList, optCmd->depend->option->name))
1086                     break;
1087             }
1088 
1089             if (optCmdIdx < lstSize(opt->cmdList))
1090                 continue;
1091 
1092             // Option dependencies have been resolved
1093             strLstAdd(optResolveList, opt->name);
1094             resolved = true;
1095         }
1096 
1097         // If nothing was resolved in the loop then there may be a circular reference
1098         if (!resolved)
1099         {
1100             // Find the options that were not resolved
1101             StringList *const unresolvedList = strLstNew();
1102 
1103             for (unsigned int optIdx = 0; optIdx < lstSize(optList); optIdx++)
1104             {
1105                 const BldCfgOption *const opt = lstGet(optList, optIdx);
1106 
1107                 if (!strLstExists(optResolveList, opt->name))
1108                     strLstAdd(unresolvedList, opt->name);
1109             }
1110 
1111             THROW_FMT(
1112                 FormatError,
1113                 "unable to resolve dependencies for option(s) '%s'\n"
1114                 "HINT: are there circular dependencies?",
1115                 strZ(strLstJoin(unresolvedList, ", ")));
1116         }
1117     }
1118     while (strLstSize(optResolveList) != lstSize(optList));
1119 
1120     // Copy resolved list
1121     for (unsigned int optResolveIdx = 0; optResolveIdx < strLstSize(optResolveList); optResolveIdx++)
1122     {
1123         const String *const optName = strLstGet(optResolveList, optResolveIdx);
1124         const BldCfgOption *const opt = lstFind(optList, &optName);
1125         lstAdd(result, &opt);
1126     }
1127 
1128     return result;
1129 }
1130 
1131 /**********************************************************************************************************************************/
1132 BldCfg
bldCfgParse(const Storage * const storageRepo)1133 bldCfgParse(const Storage *const storageRepo)
1134 {
1135     // Initialize yaml
1136     Yaml *const yaml = yamlNew(storageGetP(storageNewReadP(storageRepo, STRDEF("src/build/config/config.yaml"))));
1137     yamlEventNextCheck(yaml, yamlEventTypeMapBegin);
1138 
1139     // Parse configuration
1140     const List *const cmdList = bldCfgParseCommandList(yaml);
1141     const List *const optGrpList = bldCfgParseOptionGroupList(yaml);
1142     const List *const optList = bldCfgParseOptionList(yaml, cmdList, optGrpList);
1143     const List *const optResolveList = bldCfgParseOptionResolveList(optList);
1144 
1145     return (BldCfg){.cmdList = cmdList, .optGrpList = optGrpList, .optList = optList, .optResolveList = optResolveList};
1146 }
1147