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