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