Rewrite the lunabuild toolchain with enhanced feature (#60)
[lunaix-os.git] / lunaix-os / scripts / build-tools / README.lconfig.md
1 # LunaConfig
2
3 LunaConfig is a configuration language build on top of python. It allows user 
4 to define options and the dependencies then reuse these options in their source 
5 code for customisable build.
6
7 Lunaix use LunaConfig to toggle various optional modules, subsystems or features
8 and allow parameterised kernel build.
9
10
11 ## Design Motivation
12
13 LunaConfig is designed as an improvement over the old-school Kconfig, 
14 particularly to address its lack of hierarchical representation. Kconfig 
15 organises options into a large, flat list, even though they appear in a 
16 hierarchical menu structure. As a result, it can be very difficult to 
17 determine which menu a configuration option belongs to without diving into 
18 menuconfig.
19
20 ## Basic Constructs
21
22 LunaConfig is presented as a file named `LConfig` and thus limited to one 
23 LConfig per directory. This is because LunaConfig enforce user to organise each 
24 directory to be a module or logical packaging of set of relavent functionalities.
25
26 Each LunaConfig files is comprised by these major constructs:
27
28 1. Config Component: `Terms` and `Groups`
29 2. Config Import
30 3. Native Python
31
32 We will describe them in details in the following sections
33
34 ### Terms
35
36 > The object that hold the value for customisation
37
38 ```py
39 def feature1() -> bool:
40     return True
41 ```
42
43 This defined an option named `feature1` with type of `bool` and the default
44 value of `True`.
45
46 ### Groups
47
48 > The logical object that is used to organise the `terms` or other `groups`
49
50 A group is similar to term but without return type indication and return 
51 statement
52
53 And it is usually nested with other groups or terms.
54
55 ```py
56 def group1():
57     def feature1() -> bool:
58         return True
59 ```
60
61 ### Import Mechanism
62
63 Multiple `LConfig`s may be defined across different sub-directory in large scale
64 project for better maintainability
65
66 LunaConfig allow you to import content of other LConfig using python's relative 
67 import feature:
68
69 ```py
70 from . import module
71 ```
72
73 This import mechanism works like `#include` directive in C preprocessor,
74 the `from . import` construct will be automatically intercepted by the LConfig 
75 interpreter and be replaced with the content from `./module/LConfig`
76
77 You can also address file in the deeper hierarchy of the directory tree, for 
78 example
79
80 ```py
81 from .sub1.sub2 import module
82 ```
83
84 This will be translated into `./sub1/sub2/module/LConfig`
85
86 It can also be used in any valid python conditional branches to support 
87 conditional import
88
89 ```py
90 if condition_1:
91     from . import module1
92 elif condition_2:
93     from . import module2
94 ```
95
96 ### Native Python
97
98 Native Python code is fully supported in LunaConfig, this include everything 
99 like packages import, functions, and class definitions. LunaConfig has the 
100 ability to distinguish these code transparently from legitimate config code.
101
102 However, there is one exception for this ability. Since LunaConfig treat 
103 function definition as declaration of config component, to define a python 
104 native function you will need to decorate it with `@native`. For example:
105
106 ```py
107 @native
108 def add(a, b):
109     return a + b
110
111 def feature1() -> int:
112     return add(1, 2)
113 ```
114
115 If a native function is nested in a config component, it will not be affected 
116 by the scope and still avaliable globally. But this is not the case if it is 
117 nested by another native function.
118
119 If a config component is nested in a native function, then it is ignored by 
120 LunaConfig
121
122 ## Term Typing
123
124 A config term require it's value type to be specified explicitly. The type can 
125 be a literal type, primitive type or composite type
126
127 ### Literal Typing
128
129 A term can take a literal as it's type, doing this will ensure the value taken 
130 by the term to be exactly same as the given type
131
132 ```py
133 # OK
134 def feature1() -> "value":
135     return "value"
136     # return "value2"
137
138 # Error
139 def feature1() -> "value":
140     return "abc"
141 ```
142
143 ### Primitive Typing
144
145 A term can take any python's primitive type, the value taken by the term will 
146 be type checked rather than value checked
147
148 ```py
149 # OK
150 def feature1() -> int:
151     return 123
152
153 # Error
154 def feature1() -> int:
155     return "abc"
156 ```
157
158 ### Composite Typing
159
160 Any literal type or primitive type can be composite together via some structure 
161 to form composite type. The checking on these type is depends on the composite 
162 structure used. 
163
164 #### Union Structure
165
166 A Union structure realised through binary disjunctive connector `|`. The term 
167 value must satisfy the type check against one of the composite type:
168
169 ```py
170 def feature1() -> "a" | "b" | int:
171     # value can be either:
172     #  "a" or "b" or any integer
173     return "a"
174 ```
175
176 ## Component Attributes
177
178 Each component have set of attributes to modify its behaviour and apperance, 
179 these attributes are conveyed through decorators
180
181 ### Labels
182
183 > usage: `Groups` and `Terms`
184
185 Label provide a user friendly name for a component, which will be the first 
186 choice of the name displayed by the interactive configuration tool
187
188 ```py
189 @"This is feature 1"
190 def feature1() -> bool:
191     return True
192 ```
193
194 ### Readonly
195
196 > usage: `Terms`
197
198 Marking a term to be readonly prevent explicit value update, that is, manual 
199 update by user. Implicit value update initiated by `constrains` (more on this 
200 later) is still allowed. 
201
202 ```py
203 @readonly
204 def feature1() -> bool:
205     return True
206 ```
207
208 ### Visibility
209
210 > usage: `Groups` and `Terms`
211
212 A component can be marked to be hidden thus prevent it from displayed by the 
213 configuration tool, it does not affect the visibility in the code.
214
215 If the decorated target is a group, then it is inherited by all it's 
216 subordinates.
217
218 ```py
219 @hidden
220 def feature1() -> bool:
221     return True
222 ```
223
224 ### Flag
225
226 > usage: `Terms`
227
228 This is a short hand of both `@hidden` and `@readonly`
229
230 ```py
231 @flag
232 def feature1() -> bool:
233     return True
234 ```
235
236 is same as
237
238 ```py
239 @hidden
240 @readonly
241 def feature1() -> bool:
242     return True
243 ```
244
245 ### Parent
246
247 > usage: `Groups` and `Terms`
248
249 Any component can be defined outside of the logical hierachial structure (i.e., 
250 the nested function) but still attached to it's physical hierachial structure.
251
252 ```py
253 @parent := parent_group
254 def feature1() -> bool:
255     return True
256
257 def parent_group():
258     def feature2() -> bool:
259         return False
260 ```
261
262 This will assigned `feature1` to be a subordinate of `parent_group`. Note that 
263 the reference to `parent_group` does not required to be after the declaration.
264
265 It is equivalent to 
266
267 ```py
268 def parent_group():
269     def feature2() -> bool:
270         return False
271     
272     def feature1() -> bool:
273         return True
274 ```
275
276 ### Help Message
277
278 > usage: `Groups` and `Terms`
279
280 A help message will provide explainantion or comment of a component, to be used 
281 and displayed by the configuration tool.
282
283 The form of message is expressed using python's doc-string
284
285 ```py
286 def feature1() -> bool:
287     """
288     This is help message for feature1
289     """
290
291     return True
292 ```
293
294 ## Directives
295
296 There are builtin directives that is used inside the component.
297
298 ### Dependency
299
300 > usage: `Groups` and `Terms`
301
302 The dependency between components is described by various `require(...)` 
303 directives. 
304
305 This directive follows the python function call syntax and accept one argument 
306 of a boolean expression as depedency predicate.
307
308 Multiple `require` directives will be chainned together with logical conjunction 
309 (i.e., `and`)
310
311 ```py
312 def feature1() -> bool:
313     require (feature2 or feature3)
314     require (feature5)
315     
316     return True
317 ```
318
319 This composition is equivalent to `feature5 and (feature2 or feature3)`, 
320 indicate that the `feature1` require presences of both `feature5` and at least 
321 one of `feature2` or `feature3`.
322
323 If a dependency can not be satisfied, then the feature is disabled. This will 
324 cause it neither to be shown in configuration tool nor referencable in source 
325 code.
326
327 Note that the dependency check only perform on the enablement of the node but 
328 not visibility.
329
330 ### Constrains (Inverse Dependency)
331
332 > usage: `Terms` with `bool` value type
333
334 The `when(...)` directive allow changing the default value based on the 
335 predicate evaluation on the value of other terms. Therefore, it can be only 
336 used by `Terms` with `bool` as value type.
337
338 Similar to `require`, it is based on the function call syntax and takes one 
339 argument of conjunctive expression (i.e., boolean expression with only `and` 
340 connectors).
341
342 Multiple `when` will be chained together using logical disjunction (i.e., `or`)
343
344 ```py
345 def feature1() -> bool:
346     when (feature2 is "value1" and feature5 is "value1")
347     when (feature3)
348 ```
349
350 Which means `feature1` will takes value of `True` if one of the following is 
351 satisfied:
352  + both `feature2` and `feature5` has value of `"value1"`
353  + `feature3` has value of True
354
355 Notice that we do not have `return` statement at the end as the precense of 
356 `when` will add a return automatically (or replace it if one exists)
357
358
359 ## Validation
360
361 Since the language is based on Python, it's very tempting to pack in advanced 
362 features or messy logic into the config file, which can make it harder to 
363 follow—especially as the file grows in size.
364
365 LunaConfig recognises this issue and includes a built-in linter to help users 
366 identify such bad practices. It shows warnings by default but can be configured 
367 to raise errors instead.
368
369 Currently, LunaConfig detect the misuses based on these rules:
370
371 + `dynamic-logic`: The presence of conditional branching that could lead to 
372    complex logic. However, pattern matching is allowed.
373 + `while-loop`, `for-loop`: The presence of any loop structure.
374 + `class-def`: The presence of class definition
375 + `complex-struct`: The present of complicated data structure such as `dict`.
376 + `side-effect`: The presence of dynamic assignment of other config terms value.
377 + `non-trivial-value`: The presence of non-trivial value being used as default 
378    value. This include every things **other than**:
379     + literal constant
380     + template literal
381