rewrite the lunabuild toolchain with enhanced feature
authorLunaixsky <lunaixsky@qq.com>
Wed, 7 May 2025 23:36:13 +0000 (00:36 +0100)
committerLunaixsky <lunaixsky@qq.com>
Wed, 7 May 2025 23:36:13 +0000 (00:36 +0100)
* reduce the boilerplate code in LConfig and LBuild
* introduce new syntax suger for better expressiveness
* support predicate based config depedency checking
* more efficient lazy evaluation

82 files changed:
lunaix-os/LBuild
lunaix-os/LConfig
lunaix-os/arch/LBuild
lunaix-os/arch/LConfig
lunaix-os/arch/generic/LBuild
lunaix-os/arch/x86/LBuild
lunaix-os/arch/x86/LConfig
lunaix-os/arch/x86/hal/LBuild
lunaix-os/hal/LBuild
lunaix-os/hal/LConfig
lunaix-os/hal/acpi/LBuild
lunaix-os/hal/ahci/LBuild
lunaix-os/hal/ahci/LConfig
lunaix-os/hal/bus/LBuild
lunaix-os/hal/bus/LConfig
lunaix-os/hal/char/LBuild
lunaix-os/hal/char/LConfig
lunaix-os/hal/char/uart/LBuild
lunaix-os/hal/char/uart/LConfig
lunaix-os/hal/devtree/LBuild
lunaix-os/hal/gfxa/LBuild
lunaix-os/hal/gfxa/vga/LBuild
lunaix-os/hal/rtc/LBuild
lunaix-os/hal/term/LBuild
lunaix-os/hal/timer/LBuild
lunaix-os/kernel/LBuild
lunaix-os/kernel/LConfig
lunaix-os/kernel/block/LBuild
lunaix-os/kernel/debug/LBuild
lunaix-os/kernel/device/LBuild
lunaix-os/kernel/ds/LBuild
lunaix-os/kernel/exe/LBuild
lunaix-os/kernel/exe/elf-generic/LBuild
lunaix-os/kernel/fs/LBuild
lunaix-os/kernel/fs/LConfig
lunaix-os/kernel/fs/ext2/LBuild
lunaix-os/kernel/fs/ext2/LConfig
lunaix-os/kernel/fs/iso9660/LBuild
lunaix-os/kernel/fs/ramfs/LBuild
lunaix-os/kernel/fs/twifs/LBuild
lunaix-os/kernel/mm/LBuild
lunaix-os/kernel/mm/LConfig
lunaix-os/kernel/process/LBuild
lunaix-os/libs/LBuild
lunaix-os/makeinc/kbuild_deps.mkinc
lunaix-os/makeinc/lunabuild.mkinc
lunaix-os/makeinc/toolchain.mkinc
lunaix-os/scripts/build-tools/integration/build_gen.py [deleted file]
lunaix-os/scripts/build-tools/integration/config_io.py [deleted file]
lunaix-os/scripts/build-tools/integration/lbuild_bridge.py [deleted file]
lunaix-os/scripts/build-tools/integration/render_ishell.py [deleted file]
lunaix-os/scripts/build-tools/lbuild/api.py [deleted file]
lunaix-os/scripts/build-tools/lbuild/build.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lbuild/common.py
lunaix-os/scripts/build-tools/lbuild/contract.py [deleted file]
lunaix-os/scripts/build-tools/lbuild/scope.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lcfg/api.py [deleted file]
lunaix-os/scripts/build-tools/lcfg/builtins.py [deleted file]
lunaix-os/scripts/build-tools/lcfg/common.py [deleted file]
lunaix-os/scripts/build-tools/lcfg/lcnodes.py [deleted file]
lunaix-os/scripts/build-tools/lcfg/types.py [deleted file]
lunaix-os/scripts/build-tools/lcfg/utils.py [deleted file]
lunaix-os/scripts/build-tools/lcfg2/__init__.py [moved from lunaix-os/scripts/build-tools/integration/__init__.py with 100% similarity]
lunaix-os/scripts/build-tools/lcfg2/builder.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lcfg2/common.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lcfg2/config.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lcfg2/lazy.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lcfg2/nodes.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lcfg2/rewriter.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lcfg2/sanitiser.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lib/sandbox.py [deleted file]
lunaix-os/scripts/build-tools/lib/utils.py
lunaix-os/scripts/build-tools/luna_build.py
lunaix-os/scripts/build-tools/shared/__init__.py [moved from lunaix-os/scripts/build-tools/lcfg/__init__.py with 100% similarity]
lunaix-os/scripts/build-tools/shared/build_gen.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/shared/export.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/shared/libmenu.py [moved from lunaix-os/scripts/build-tools/integration/libmenu.py with 100% similarity]
lunaix-os/scripts/build-tools/shared/libtui.py [moved from lunaix-os/scripts/build-tools/integration/libtui.py with 100% similarity]
lunaix-os/scripts/build-tools/shared/lunamenu.py [moved from lunaix-os/scripts/build-tools/integration/lunamenu.py with 100% similarity]
lunaix-os/scripts/build-tools/shared/scopes.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/shared/shconfig.py [new file with mode: 0644]
lunaix-os/usr/LConfig

index dcee304424dc51737ad7ee7b0b83604639f367cb..04a0f54c88797b3ca0f20f08528261d4975514a3 100644 (file)
@@ -1,27 +1,23 @@
-use("kernel")
-use("libs")
-use("arch")
-use("hal")
+from . import kernel, libs, arch, hal
 
 
-headers([
-    "includes",
-    "includes/usr"
-])
+src.h += "includes", "includes/usr"
 
 
-# compliation setting
+flag.cc += "-ffreestanding", "-fno-pie"
 
 
-compile_opts([
-    "-ffreestanding",
-    "-fno-pie"
-])
+flag.cc += (
+    "-Wall -Wextra -Werror",
+    "-Wno-unknown-pragmas",
+    "-Wno-unused-function",
+    "-Wno-unused-variable",
+    "-Wno-unused-but-set-variable",
+    "-Wno-unused-parameter",
+    "-Wno-discarded-qualifiers",
+    "-Werror=incompatible-pointer-types"
+)
 
 
-linking_opts([
-    "-nostdlib", 
-    "-nolibc", 
-    "-z noexecstack", 
-    "-no-pie", 
-])
+flag.cc += (
+    "-fno-omit-frame-pointer",
+    "-finline-small-functions",
+)
 
 
-linking_opts([
-    "-Wl,--build-id=none"
-])
\ No newline at end of file
+flag.ld += "-nostdlib", "-nolibc", "-z noexecstack", "-no-pie", "-Wl,--build-id=none"
\ No newline at end of file
index 0e27e4e4e88ac716d243f9deb1fb313f12f6754b..d66375dbb27fa1da91e1d7c7bbb935ff1c91ec63 100644 (file)
@@ -1,73 +1,64 @@
-import time
 from datetime import datetime, date
 
 from datetime import datetime, date
 
-include("kernel")
-include("arch")
-include("hal")
+from . import kernel, arch, hal
 
 
-@Term("Kernel Version")
-@ReadOnly
-def lunaix_ver():
+@"Kernel Version"
+@readonly
+def lunaix_ver() -> str:
     """
     Lunaix kernel version
     """
     """
     Lunaix kernel version
     """
-
-    type(str)
     
     today = date.today()
     year = today.year
     start_of_year = datetime(year, 1, 1).date()
     seq_num = (today - start_of_year).days
     
     
     today = date.today()
     year = today.year
     start_of_year = datetime(year, 1, 1).date()
     seq_num = (today - start_of_year).days
     
-    default("%s v0.%d%d"%(v(arch), year - 2000, seq_num))
+    return "%s v0.%d%d"%(arch.val, year - 2000, seq_num)
 
 
-@Collection("Kernel Debug and Testing")
+@"Kernel Debug and Testing"
 def debug_and_testing():
     """
     General settings for kernel debugging feature
     """
 
 def debug_and_testing():
     """
     General settings for kernel debugging feature
     """
 
-    @Term("Supress assertion")
-    def no_assert():
+    @"Supress assertion"
+    def no_assert() -> bool:
         """
         Supress all assertion fail activity.
         Note: Enable this is highly NOT recommended and would result system
               extermly unstable
         """
         """
         Supress all assertion fail activity.
         Note: Enable this is highly NOT recommended and would result system
               extermly unstable
         """
-        type(bool)
-        default(False)
 
 
-    @Term("Report on stalled thread")
-    def check_stall():
+        return False
+
+    @"Report on stalled thread"
+    def check_stall() -> bool:
         """
         Check and report on any thread that spend too much time in kernel.
         """
 
         """
         Check and report on any thread that spend too much time in kernel.
         """
 
-        type(bool)
-        default(True)
+        return True
         
         
-    @Term("Max kernel time allowance")
-    def stall_timeout():
+    @"Max kernel time allowance"
+    def stall_timeout() -> int:
         """
         Set the maximum time (in seconds) spent in kernel before considered
         to be stalled.
         """
         """
         Set the maximum time (in seconds) spent in kernel before considered
         to be stalled.
         """
+        require(check_stall)
 
 
-        type(int)
-        default(10)
-
-        return v(check_stall)
+        return 10
     
     
-    @Term("Max number of preemptions")
-    def stall_max_preempts():
+    @"Max number of preemptions"
+    def stall_max_preempts() -> int:
         """
         Set the maximum number of preemptions that a task can take
         before it is considered to be stucked in some loops.
 
         Setting it to 0 disable this check
         """
         """
         Set the maximum number of preemptions that a task can take
         before it is considered to be stucked in some loops.
 
         Setting it to 0 disable this check
         """
+        require(check_stall)
 
 
-        type(int)
-        default(0)
+        return 0
 
 
-        return v(check_stall)
\ No newline at end of file
index 703d6921761d430143348276221e2a9afbf44554..45251daacbfdddbfaa7010f845fb1c2ea513e937 100644 (file)
@@ -1,10 +1,11 @@
-use("generic")
+from . import generic
 
 
-use({
-    config("arch"): {
-        "i386": "x86",
-        "x86_64": "x86",
-        "aarch64": "arm",
-        "rv64": "riscv"
-    }
-})
\ No newline at end of file
+match config.arch:
+    case "i386" | "x86_64":
+        from . import x86
+    # case "aarch64":
+    #     from . import arm64
+    # case "rv64":
+    #     from . import rv64
+    case _:
+        from . import x86
index 7afea85ce59caf5d1bfcf721d14d1dc47f26cf39..78d557a3f856dd691a4f3310e4828fa228c0be4b 100644 (file)
@@ -1,27 +1,34 @@
-include("x86/LConfig")
+from . import x86
 
 
-@Collection("Platform")
+@"Platform"
 def architecture_support():
     """
         Config ISA related features
     """
 
 def architecture_support():
     """
         Config ISA related features
     """
 
-    @Term("Architecture")
-    def arch():
+    @flag
+    def arch_x86_32() -> bool:
+        return arch.val == "i386"
+    
+    @flag
+    def arch_x86_64() -> bool:
+        return arch.val == "x86_64"
+    
+    @flag
+    def arch_x86() -> bool:
+        return arch.val in ["x86_64", "i386"]
+
+    @"Architecture"
+    def arch() -> "i386" | "x86_64":
         """ 
             Config ISA support 
         """
         """ 
             Config ISA support 
         """
-        # type(["i386", "x86_64", "aarch64", "rv64"])
-        type(["i386", "x86_64"])
-        default("x86_64")
-
-        env_val = env("ARCH")
-        if env_val:
-            set_value(env_val)
+        _arch = env("ARCH")
+        return _arch if _arch else "x86_64"
 
 
-    @Term("Base operand size")
-    @ReadOnly
-    def arch_bits():
+    @"Base operand size"
+    @readonly
+    def arch_bits() -> 32 | 64:
         """ 
             Defines the base size of a general register of the 
             current selected ISA.
         """ 
             Defines the base size of a general register of the 
             current selected ISA.
@@ -29,16 +36,15 @@ def architecture_support():
             This the 'bits' part when we are talking about a CPU
         """
 
             This the 'bits' part when we are talking about a CPU
         """
 
-        type(["64", "32"])
-        match v(arch):
+        match arch.val:
             case "i386": 
             case "i386": 
-                default("32")
+                return 32
             case "aarch64": 
             case "aarch64": 
-                default("64")
+                return 64
             case "rv64": 
             case "rv64": 
-                default("64")
+                return 64
             case "x86_64": 
             case "x86_64": 
-                default("64")
+                return 64
             case _:
             case _:
-                default("32")
+                return 32
         
\ No newline at end of file
         
\ No newline at end of file
index b57fdfa00fadea0cf8115d2c1583659a7ef0814f..0a3d37d257d9eb3db53af2d7687b514338eb7eba 100644 (file)
@@ -1,10 +1,10 @@
-headers("includes")
+src.h += "includes"
 
 
-sources([
+src.c += (
     "bootmem.c",
     "trace.c",
     "vmutils.c",
     "procvm.c",
     "arch.c",
     "kremap.c"
     "bootmem.c",
     "trace.c",
     "vmutils.c",
     "procvm.c",
     "arch.c",
     "kremap.c"
-])
\ No newline at end of file
+)
\ No newline at end of file
index ed4b4f8ac9e10f4396fbd1b7b9f0470f311ce447..cd3432807938f3c9a338b341084119712ce206ff 100644 (file)
@@ -1,26 +1,26 @@
-use("hal")
+from . import hal
 
 
-sources([
+src.c += (
     "exceptions/interrupts.c",
     "exceptions/isrdef.c",
     "exceptions/intrhnds.S",
     "exceptions/interrupts.c",
     "exceptions/isrdef.c",
     "exceptions/intrhnds.S",
-])
+)
 
 
-sources([
+src.c += (
     "boot/mb_parser.c",
     "boot/kpt_setup.c",
     "boot/boot_helper.c",
     "boot/mb_parser.c",
     "boot/kpt_setup.c",
     "boot/boot_helper.c",
-])
+)
 
 
-sources([
+src.c += (
     "mm/fault.c",
     "mm/tlb.c",
     "mm/pmm.c",
     "mm/gdt.c",
     "mm/vmutils.c"
     "mm/fault.c",
     "mm/tlb.c",
     "mm/pmm.c",
     "mm/gdt.c",
     "mm/vmutils.c"
-])
+)
 
 
-sources([
+src.c += (
     "klib/fast_crc.c",
     "klib/fast_str.c",
     "exec/exec.c",
     "klib/fast_crc.c",
     "klib/fast_str.c",
     "exec/exec.c",
@@ -31,11 +31,11 @@ sources([
     "hart.c",
     "failsafe.S",
     "syscall_lut.S"
     "hart.c",
     "failsafe.S",
     "syscall_lut.S"
-])
+)
 
 
-sources({
-    config("arch"): {
-        "x86_64": [
+match config.arch:
+    case "x86_64":
+        src.c += (
             "hart64.c",
             "syscall64.S",
             "exceptions/interrupt64.S",
             "hart64.c",
             "syscall64.S",
             "exceptions/interrupt64.S",
@@ -44,8 +44,9 @@ sources({
             "boot/x86_64/prologue64.S",
             "boot/x86_64/kremap64.c",
             "exec/elf64.c"
             "boot/x86_64/prologue64.S",
             "boot/x86_64/kremap64.c",
             "exec/elf64.c"
-        ],
-        "i386": [
+        )
+    case "i386":
+        src.c += (
             "hart32.c",
             "syscall32.S",
             "exceptions/interrupt32.S",
             "hart32.c",
             "syscall32.S",
             "exceptions/interrupt32.S",
@@ -54,28 +55,21 @@ sources({
             "boot/i386/prologue32.S",
             "boot/i386/kremap32.c",
             "exec/elf32.c"
             "boot/i386/prologue32.S",
             "boot/i386/kremap32.c",
             "exec/elf32.c"
-        ]
-    }
-})
+        )
 
 
-headers([
-    "includes"
-])
+src.h += "includes"
 
 
-
-if config("arch") == "x86_64":
-    compile_opts([
+if config.arch == "x86_64":
+    flag.cc += (
         "-m64", 
         "-fno-unwind-tables", 
         "-fno-asynchronous-unwind-tables",
         "-mcmodel=large"
         "-m64", 
         "-fno-unwind-tables", 
         "-fno-asynchronous-unwind-tables",
         "-mcmodel=large"
-    ])
-    linking_opts([
-        "-m64",
-    ])
+    )
+    flag.ld += "-m64"
 else:
 else:
-    compile_opts("-m32")
-    linking_opts("-m32")
+    flag.cc += "-m32"
+    flag.ld += "-m32"
 
 
-if not config("x86_enable_sse_feature"):
-    compile_opts("-mno-sse")
\ No newline at end of file
+if not config.x86_enable_sse_feature:
+    flag.cc += "-mno-sse"
\ No newline at end of file
index 2b345246214d989aa9f5f40c2561a4ad1bb87503..e5f0cd40fd302a613913c3dde213b577e1705c66 100644 (file)
@@ -1,22 +1,28 @@
 
 
-@Group
+@(parent := architecture_support)
 def x86_configurations():
 def x86_configurations():
-    
-    add_to_collection(architecture_support)
 
 
-    @Term("Use SSE2/3/4 extension")
-    def x86_enable_sse_feature():
+    require(arch_x86)
+
+    @flag
+    def x86_bl_mb() -> bool:
+        return x86_bl.val == "mb"
+    
+    @flag
+    def x86_bl_mb2() -> bool:
+        return x86_bl.val == "mb2"
+    
+    @"Use SSE2/3/4 extension"
+    def x86_enable_sse_feature() -> bool:
         """ 
             Config whether to allow using SSE feature for certain
             optimization
         """
         
         """ 
             Config whether to allow using SSE feature for certain
             optimization
         """
         
-        type(bool)
-        default(False)
+        return False
 
 
-
-    @Term("Bootloader Model")
-    def x86_bl():
+    @"Bootloader Model"
+    def x86_bl() -> "mb" | "mb2":
         """
             Select the bootloader interface
             
         """
             Select the bootloader interface
             
@@ -26,9 +32,4 @@ def x86_configurations():
                 none:    do not use any interface
         """
 
                 none:    do not use any interface
         """
 
-        type(["mb"])
-        # type(["mb", "mb2", "none"])
-        default("mb")
-
-
-    return v(arch) in ["i386", "x86_64"]
\ No newline at end of file
+        return "mb"
\ No newline at end of file
index 870f743cf2a499f22421d02504d2c52712bc66ec..d3ba47cd2d68183710d9c639553ab3ca91f24d6a 100644 (file)
@@ -1,4 +1,4 @@
-sources([
+src.c += (
     "apic.c",
     "rngx86.c",
     "cpu.c",
     "apic.c",
     "rngx86.c",
     "cpu.c",
@@ -6,4 +6,4 @@ sources([
     "apic_timer.c",
     "mc146818a.c",
     "pci.c"
     "apic_timer.c",
     "mc146818a.c",
     "pci.c"
-])
\ No newline at end of file
+)
\ No newline at end of file
index 6ff2713d2e5de227f582fd588ce6b683e671e035..5a50d0ef0da53d202632244ea86f8f2804554981 100644 (file)
@@ -1,15 +1,6 @@
-use("acpi")
-use("ahci")
-use("char")
-use("gfxa")
-use("rtc")
-use("term")
-use("timer")
-use("bus")
+from . import acpi, ahci, char, gfxa, rtc, term, timer, bus
 
 
-if config("use_devicetree"):
-    use("devtree")
+if config.use_devicetree:
+    from . import devtree
 
 
-sources([
-    "irq.c"
-])
\ No newline at end of file
+src.c += "irq.c"
\ No newline at end of file
index 9a70d02d05337371993496b90112bb44ec22f9e8..26f53b8815862704ee5aa5b504173128f0af331d 100644 (file)
@@ -1,13 +1,11 @@
-include("char")
-include("bus")
-include("ahci")
+from . import char, bus, ahci
 
 
-@Collection("Devices & Peripherials")
+@"Devices & Peripherials"
 def hal():
     """ Lunaix hardware asbtraction layer """
 
 def hal():
     """ Lunaix hardware asbtraction layer """
 
-    @Term("Devicetree for hardware discovery")
-    def use_devicetree():
+    @"Devicetree for hardware discovery"
+    def use_devicetree() -> bool:
         """
             Decide whether to use Devicetree for platform
             resource topology sensing.
         """
             Decide whether to use Devicetree for platform
             resource topology sensing.
@@ -19,17 +17,14 @@ def hal():
             way.
         """
 
             way.
         """
 
-        type(bool)
-        default(not v(arch).startswith("x86"))
+        return arch.val not in ["x86_64", "i386"]
 
 
-    @ReadOnly
-    @Term("Maximum size of device tree blob (in KiB)")
-    def dtb_maxsize():
+    @"Maximum size of device tree blob (in KiB)"
+    @readonly
+    def dtb_maxsize() -> int:
         """
             Maximum size for a firmware provided device tree blob
         """
         """
             Maximum size for a firmware provided device tree blob
         """
+        require(use_devicetree)
 
 
-        type(int)
-        default(256)
-
-        return v(use_devicetree)
\ No newline at end of file
+        return 256
\ No newline at end of file
index fb5ee72365a389a00ce0908aa164a769c275a961..5a59b3318c0cd64e128d74125aa97aeb739079a6 100644 (file)
@@ -1,5 +1,5 @@
-sources([
+src.c += (
     "parser/madt_parser.c",
     "parser/mcfg_parser.c",
     "acpi.c"
     "parser/madt_parser.c",
     "parser/mcfg_parser.c",
     "acpi.c"
-])
\ No newline at end of file
+)
\ No newline at end of file
index 7da89abe6dd9091e128a120b371f37d8184b83d4..ec6018d3bb6f4520780dd0f0abf635f3f2a3a761 100644 (file)
@@ -1,5 +1,5 @@
-if config("ahci_enable"):
-    sources([
+if config.ahci_enable:
+    src.c += (
         "ahci_pci.c",
         "hbadev_export.c",
         "ahci.c",
         "ahci_pci.c",
         "hbadev_export.c",
         "ahci.c",
@@ -7,4 +7,4 @@ if config("ahci_enable"):
         "io_event.c",
         "atapi.c",
         "ata.c"
         "io_event.c",
         "atapi.c",
         "ata.c"
-    ])
\ No newline at end of file
+    )
\ No newline at end of file
index a199abfce77c858345e5db13b44ab1a60acea305..31277f26165d9a4baa068197f9ca278fcb5e1947 100644 (file)
@@ -1,18 +1,14 @@
 
 
-@Collection("AHCI")
+@"AHCI"
+@(parent := hal)
 def sata_ahci():
 
 def sata_ahci():
 
-    add_to_collection(hal)
-
-    @Term("Enable AHCI support")
-    def ahci_enable():
+    @"Enable AHCI support"
+    def ahci_enable() -> bool:
         """ Enable the support of SATA AHCI. 
             Must require PCI at current stage """
         """ Enable the support of SATA AHCI. 
             Must require PCI at current stage """
+        require(pci_enable)
         
         
-        type(bool)
-        default(True)
-
-        if not v(pci_enable):
-            set_value(False)
+        return True
 
         
\ No newline at end of file
 
         
\ No newline at end of file
index 3b5ff38ba1cdee2a21bd59d94282fb9b20fb8c0a..3e280aa731aa2bd607e65b2fc3fcc839c9fe8917 100644 (file)
@@ -1,3 +1,3 @@
 
 
-if config("pci_enable"):
-    sources("pci.c")
\ No newline at end of file
+if config.pci_enable:
+    src.c += "pci.c"
\ No newline at end of file
index e4281d294cd6018da966ab66f952c6e193f0a89a..edbe2ef0608719ee9ac29fc30a4b0fdbf7b4387b 100644 (file)
@@ -1,35 +1,25 @@
 
 
-@Collection("Buses & Interconnects")
+@"Buses & Interconnects"
+@(parent := hal)
 def bus_if():
     """ System/platform bus interface """
 
 def bus_if():
     """ System/platform bus interface """
 
-    add_to_collection(hal)
-
-    @Term("PCI")
-    def pci_enable():
+    @"PCI"
+    def pci_enable() -> bool:
         """ Peripheral Component Interconnect (PCI) Bus """
         """ Peripheral Component Interconnect (PCI) Bus """
-        type(bool)
-        default(True)
+        return True
 
 
-    @Term("PCI Express")
-    def pcie_ext():
+    @"PCI Express"
+    def pcie_ext() -> bool:
         """ Enable support of PCI-Express extension """
         """ Enable support of PCI-Express extension """
-        type(bool)
-        default(False)
+        require(pci_enable)
 
 
-        return v(pci_enable)
+        return False
 
 
-    @Term("Use PMIO for PCI")
-    def pci_pmio():
+    @"Use PMIO for PCI"
+    def pci_pmio() -> bool:
         """ Use port-mapped I/O interface for controlling PCI """
         """ Use port-mapped I/O interface for controlling PCI """
-        type(bool)
-
-        has_pcie = v(pcie_ext)
-        is_x86 = v(arch) in [ "i386", "x86_64" ]
-
-        default(not has_pcie)
-        
-        if not is_x86 or has_pcie:
-            set_value(False)
+        require(not pcie_ext and pci_enable)
+        require(arch_x86)
 
 
-        return is_x86 and v(pci_enable)
\ No newline at end of file
+        return arch.val in [ "i386", "x86_64" ]
index dbc15be35a3dc8a14f3d344468518dcf57b93dcb..55e3c8271e187971186e4337bb0c4863e140215e 100644 (file)
@@ -1,15 +1,13 @@
-use("uart")
+from . import uart
 
 
-sources([
+if config.vga_console:
+    src.c += "lxconsole.c"
+
+if config.chargame_console:
+    src.c += "chargame.c"
+
+src.c += (
     "devnull.c",
     "serial.c",
     "devzero.c",
     "devnull.c",
     "serial.c",
     "devzero.c",
-])
-
-if config("vga_console"):
-
-    sources("lxconsole.c")
-
-if config("chargame_console"):
-    
-    sources("chargame.c")
\ No newline at end of file
+)
\ No newline at end of file
index 8e662fa5be4494450cfc8e0ca6e5633787ae6576..b4ecff10b63acb187cb0b07b4209c7df1f9b775b 100644 (file)
@@ -1,25 +1,22 @@
-include("uart")
+from . import uart
 
 
-@Collection("Character Devices")
+@"Character Devices"
+@(parent := hal)
 def char_device():
     """ Controlling support of character devices """
 
 def char_device():
     """ Controlling support of character devices """
 
-    add_to_collection(hal)
-
-    @Term("VGA 80x25 text-mode console")
-    def vga_console():
+    @"VGA 80x25 text-mode console"
+    def vga_console() -> bool:
         """ Enable VGA console device (text mode only) """
 
         """ Enable VGA console device (text mode only) """
 
-        type(bool)
-        default(True)
+        return True
 
 
-    @Term("VGA character game device")
-    def chargame_console():
+    @"VGA character game device"
+    def chargame_console() -> bool:
         """ 
             Enable VGA Charactor Game console device (text mode only) 
 
             You normally don't need to include this, unless you want some user space fun ;)
         """
 
         """ 
             Enable VGA Charactor Game console device (text mode only) 
 
             You normally don't need to include this, unless you want some user space fun ;)
         """
 
-        type(bool)
-        default(False)
\ No newline at end of file
+        return False
\ No newline at end of file
index 9ed3119546bcc55d62d4224534141153c2c8b42b..a25cf34735102ad91f37181c28d15f44c5aee6e4 100644 (file)
@@ -1,12 +1,12 @@
-sources([
+src.c += (
     "16x50_base.c",
     "16x50_pmio.c",
     "16x50_mmio.c",
     "16x50_dev.c",
     "16x50_base.c",
     "16x50_pmio.c",
     "16x50_mmio.c",
     "16x50_dev.c",
-])
+)
 
 
-if config("xt_16x50"):
-    sources("16x50_isa.c")
+if config.xt_16x50:
+    src.c += "16x50_isa.c"
 
 
-if config("pci_16x50"):
-    sources("16x50_pci.c")
\ No newline at end of file
+if config.pci_16x50:
+    src.c += "16x50_pci.c"
\ No newline at end of file
index bbb019df967d4a77c3947ee3daf9d4117bb91d7d..831169cfb119c6637ad4fa7f26d427ba0f3755db 100644 (file)
@@ -1,28 +1,17 @@
 
 
-@Collection("16x50 Serial Controller")
+@"16x50 Serial Controller"
+@(parent := char_device)
 def uart_16x50():
     """ 16x50 serial controller  """
 
 def uart_16x50():
     """ 16x50 serial controller  """
 
-    # hal/char/LConfig::char_device
-    add_to_collection(char_device)
-
-    @Term("16x50 XT-Compat")
-    def xt_16x50():
+    @"16x50 XT-Compat"
+    def xt_16x50() -> bool:
         """ Enable the 16x50 for PC-compatible platform  """
 
         """ Enable the 16x50 for PC-compatible platform  """
 
-        type(bool)
-
-        default(True)
-        
-        is_x86 = v(arch) in ["i386", "x86_64"]
-        if not is_x86:
-            set_value(False)
-
-        return is_x86
+        return arch.val in ["i386", "x86_64"]
     
     
-    @Term("16x50 PCI")
-    def pci_16x50():
+    @"16x50 PCI"
+    def pci_16x50() -> bool:
         """ Enable the support of PCI 16x50 """
         """ Enable the support of PCI 16x50 """
-        type(bool)
 
 
-        default(True)
\ No newline at end of file
+        return True
\ No newline at end of file
index 000d52b4ab085169fe27cb818476753cb1afd342..496a325d12c2a8929774ae1e7ca2075a4ff10a42 100644 (file)
@@ -1,6 +1,6 @@
-sources([
+src.c += (
     "dt_interrupt.c",
     "dt.c",
     "dtm.c",
     "dtspec.c"
     "dt_interrupt.c",
     "dt.c",
     "dtm.c",
     "dtspec.c"
-])
\ No newline at end of file
+)
\ No newline at end of file
index 54d50d131eb44d4c60310e87441ea305fb9e4488..980a0a5a452899729fadebde79a010033aee3596 100644 (file)
@@ -1,5 +1,3 @@
-use("vga")
+from . import vga
 
 
-sources([
-    "gfxm.c"
-])
\ No newline at end of file
+src.c += "gfxm.c"
\ No newline at end of file
index dc8991a04f73ed935e3405d8d191d7df1159f555..701a7cfa561702e44797d645fc245f1cdee6bcbb 100644 (file)
@@ -1,8 +1,8 @@
-sources([
+src.c += (
     "vga_pmio_ops.c",
     "vga_gfxm_ops.c",
     "vga.c",
     "vga_mmio_ops.c",
     "vga_pci.c",
     "vga_rawtty.c",
     "vga_pmio_ops.c",
     "vga_gfxm_ops.c",
     "vga.c",
     "vga_mmio_ops.c",
     "vga_pci.c",
     "vga_rawtty.c",
-])
\ No newline at end of file
+)
\ No newline at end of file
index debfd14cb2b1df6a7ce80000a63c046f257ba378..69dd357802bbc16f3decae3d2f43c847b6bb42cd 100644 (file)
@@ -1,3 +1 @@
-sources([
-    "rtc_device.c"
-])
\ No newline at end of file
+src.c += "rtc_device.c"
\ No newline at end of file
index ea5b2c396ea6c616f5ee240319fa0d5a72968e53..554483e3b75e21e7d414563d1c74bf6d384aacb8 100644 (file)
@@ -1,8 +1,8 @@
-sources([
+src.c += (
     "term.c",
     "console.c",
     "term_io.c",
     "default_ops.c",
     "lcntls/ansi_cntl.c",
     "lcntls/lcntl.c",
     "term.c",
     "console.c",
     "term_io.c",
     "default_ops.c",
     "lcntls/ansi_cntl.c",
     "lcntls/lcntl.c",
-])
\ No newline at end of file
+)
\ No newline at end of file
index e5b22561dea8134716822bcbd2484b0aef0ea882..a79fe9aa17be835048dc296b3193e1346fa51548 100644 (file)
@@ -1,3 +1 @@
-sources([
-    "timer_device.c"
-])
\ No newline at end of file
+src.c += "timer_device.c"
\ No newline at end of file
index 3e0a7be39e3a5e81495d66da3d00be896944f868..31428ffb95dd7fec9ed59fcdc544fa5d28c484a9 100644 (file)
@@ -1,13 +1,6 @@
-use("block")
-use("debug")
-use("device")
-use("ds")
-use("exe")
-use("fs")
-use("mm")
-use("process")
+from . import block, debug, device, ds, exe, fs, mm, process
 
 
-sources([
+src.c += (
     "boot_helper.c",
     "kcmd.c",
     "kinit.c",
     "boot_helper.c",
     "kcmd.c",
     "kinit.c",
@@ -21,5 +14,5 @@ sources([
     "kprint/kp_records.c",
     "kprint/kprintf.c",
     "time/clock.c",
     "kprint/kp_records.c",
     "kprint/kprintf.c",
     "time/clock.c",
-    "time/timer.c",
-])
\ No newline at end of file
+    "time/timer.c"
+)
\ No newline at end of file
index a46e85ff827f40b524e755ef1022496fbbfc742a..0fad37b7d3016371786e5f65af6c5d8d5941eabd 100644 (file)
@@ -1,7 +1,6 @@
-@Collection
+from . import fs, mm
+
+@"Kernel Feature"
 def kernel_feature():
     """ Config kernel features """
     pass
 def kernel_feature():
     """ Config kernel features """
     pass
-
-include("fs")
-include("mm")
index 10586c59438048e2bf346ff1dcbe76e9221ce26e..a44b0691702d07a792c505c8030a522b0824c743 100644 (file)
@@ -1,7 +1,7 @@
-sources([
+src.c += (
     "blkpart_gpt.c",
     "blk_mapping.c",
     "blkio.c",
     "block.c",
     "blkbuf.c"
     "blkpart_gpt.c",
     "blk_mapping.c",
     "blkio.c",
     "block.c",
     "blkbuf.c"
-])
\ No newline at end of file
+)
\ No newline at end of file
index d9ddb47cb983c1b846467a2854ef775c7278740a..19831a72c52ea174f93900bb1eb6c71cf3fd0be2 100644 (file)
@@ -1,5 +1,5 @@
-sources([
+src.c += (
     "failsafe.c",
     "trace.c",
     # "gdbstub.c"
     "failsafe.c",
     "trace.c",
     # "gdbstub.c"
-])
\ No newline at end of file
+)
\ No newline at end of file
index 989a81b1bdbeb2e38582b5b4595196ffa7794340..e589701f963f09ec32a1fddf91ba870ad020c94a 100644 (file)
@@ -1,8 +1,8 @@
-sources([
+src.c += (
     "device.c",
     "potentem.c",
     "devdb.c",
     "devfs.c",
     "input.c",
     "poll.c",
     "device.c",
     "potentem.c",
     "devdb.c",
     "devfs.c",
     "input.c",
     "poll.c",
-])
+)
index a5c082fdb26f0e878198a4ef52d2140d95c30e29..867936d8e0310fed0a73b8524b8354690c797323 100644 (file)
@@ -1,4 +1,4 @@
-sources([
+src.c += (
     "waitq.c",
     "buffer.c",
     "rbuffer.c",
     "waitq.c",
     "buffer.c",
     "rbuffer.c",
@@ -8,4 +8,4 @@ sources([
     "hstr.c",
     "fifo.c",
     "rwlock.c"
     "hstr.c",
     "fifo.c",
     "rwlock.c"
-])
\ No newline at end of file
+)
\ No newline at end of file
index 3ab25b49f44a6005ee1612e6787a0f2e34c9135d..1a23f2a7d108d01adb7945422f4759521ce05b46 100644 (file)
@@ -1,5 +1,3 @@
-use("elf-generic")
+import_("elf-generic")
 
 
-sources([
-    "exec.c"
-])
\ No newline at end of file
+src.c += "exec.c"
\ No newline at end of file
index 80f3a1e93ef255872a0d956d9f081750d9b1c63a..3cb3702707dd45a7093ea777b1890642726abf4a 100644 (file)
@@ -1,4 +1,4 @@
-sources([
+src.c += (
     "elfbfmt.c",
     "ldelf.c"
     "elfbfmt.c",
     "ldelf.c"
-])
\ No newline at end of file
+)
\ No newline at end of file
index 638662f8319721f8b3752f743b542d7d5c91c355..0a1d2ac26ef7b65c7fdbb0b5d2d71a122bd98056 100644 (file)
@@ -1,13 +1,12 @@
-use("twifs")
-use("ramfs")
+from . import twifs, ramfs
 
 
-if configured("fs_iso9660"):
-    use("iso9660")
+if config.fs_ext2:
+    from . import ext2
 
 
-if configured("fs_ext2"):
-    use("ext2")
+if config.fs_iso9660:
+    from . import iso9660
 
 
-sources([
+src.c += (
     "twimap.c",
     "pcache.c",
     "mount.c",
     "twimap.c",
     "pcache.c",
     "mount.c",
@@ -17,5 +16,5 @@ sources([
     "path_walk.c",
     "fsm.c",
     "fs_export.c",
     "path_walk.c",
     "fsm.c",
     "fs_export.c",
-])
+)
 
 
index 880d6c68a102c502c6d7f7d66c8d9f8a83d58806..dfa8fe4df79c90efe9fa4b709e88e07d6171ca82 100644 (file)
@@ -1,23 +1,18 @@
+from . import ext2
 
 
-@Collection("File Systems")
+@"File Systems"
+@(parent := kernel_feature)
 def file_system():
     """ Config feature related to file system supports """
 
 def file_system():
     """ Config feature related to file system supports """
 
-    add_to_collection(kernel_feature)
-
-    @Term("ext2 support")
-    def fs_ext2():
+    @"ext2 support"
+    def fs_ext2() -> bool:
         """ Enable ext2 file system support """
 
         """ Enable ext2 file system support """
 
-        type(bool)
-        default(True)
+        return True
     
     
-    @Term("iso9660 support")
-    def fs_iso9660():
+    @"iso9660 support"
+    def fs_iso9660() -> bool:
         """ Enable iso9660 file system support """
 
         """ Enable iso9660 file system support """
 
-        type(bool)
-        default(True)
-
-
-include("ext2")
\ No newline at end of file
+        return True
index a9700fe12d3bdba743336080f7086f56fa7085b8..30129e69a3ab74d9136d7016628b492d8ed38c58 100644 (file)
@@ -1,8 +1,8 @@
-sources([
+src.c += (
     "alloc.c",
     "dir.c",
     "file.c",
     "group.c",
     "inode.c",
     "mount.c"
     "alloc.c",
     "dir.c",
     "file.c",
     "group.c",
     "inode.c",
     "mount.c"
-])
\ No newline at end of file
+)
\ No newline at end of file
index f5cb0a66f1dfb9b3ebb2076af799c673c7328ddc..aa160b37b258419e57763bffbb31f5477cbfc057 100644 (file)
@@ -1,11 +1,10 @@
 
 
-@Collection("ext2")
+@"ext2"
+@(parent := file_system)
 def ext2_fs():
 def ext2_fs():
-    add_to_collection(file_system)
 
 
-    @Term("Debug Messages")
-    def ext2_debug_msg():
-        type(bool)
-        default(False)
+    require(fs_ext2)
 
 
-    return v(fs_ext2)
\ No newline at end of file
+    @"Debug Messages"
+    def ext2_debug_msg() -> bool:
+        return False
\ No newline at end of file
index aaa5edcce3e2b83e6fb253d50850c2f4d94c200a..06eea1c0e3b8594ae9c418be998bdaa1e416541d 100644 (file)
@@ -1,8 +1,8 @@
-sources([
+src.c += (
     "inode.c",
     "file.c",
     "mount.c",
     "directory.c",
     "utils.c",
     "rockridge.c"
     "inode.c",
     "file.c",
     "mount.c",
     "directory.c",
     "utils.c",
     "rockridge.c"
-])
\ No newline at end of file
+)
\ No newline at end of file
index fbc9ec0f8edef23b9e87dfe9b81e9abe73d1a697..85a23f8f37c0e004044b9bb61b76b08e2e7cbe61 100644 (file)
@@ -1,3 +1 @@
-sources([
-    "ramfs.c"
-])
\ No newline at end of file
+src.c += "ramfs.c"
\ No newline at end of file
index b4270ce16ebf3ab8cfa0e495b7bdb9e8a40f674e..32feb297e1e01ce113d6e8582af63dfc65556908 100644 (file)
@@ -1,3 +1 @@
-sources([
-    "twifs.c"
-])
\ No newline at end of file
+src.c += "twifs.c"
\ No newline at end of file
index e350f502168e1db4895071cbc6f95f3d61c4eead..0c9b82be58cc6f854cca2833aa6439fadec6f680 100644 (file)
@@ -1,4 +1,4 @@
-sources([
+src.c += (
     "mmap.c",
     "valloc.c",
     "cake.c",
     "mmap.c",
     "valloc.c",
     "cake.c",
@@ -13,4 +13,4 @@ sources([
     "cake_export.c",
     "vmap.c",
     "dmm.c"
     "cake_export.c",
     "vmap.c",
     "dmm.c"
-])
\ No newline at end of file
+)
\ No newline at end of file
index 17247b0d692ddb8e53e55a838ea6c770252e949b..8f8d4b9f9c8da1308f5f8cfe7352ffacdce64749 100644 (file)
@@ -1,92 +1,92 @@
 
 
-@Collection("Memory Management")
+@"Memory Management"
+@(parent := kernel_feature)
 def memory_subsystem():
     """ Config the memory subsystem """
 
 def memory_subsystem():
     """ Config the memory subsystem """
 
-    @Collection("Physical Memory")
+    @"Physical Memory"
     def physical_mm():
         """ Physical memory manager  """
 
     def physical_mm():
         """ Physical memory manager  """
 
-        @Term("Allocation policy")
-        def pmalloc_method():
+        @flag
+        def pmalloc_method_simple() -> bool:
+            return pmalloc_method.val == "simple"
+        
+        @flag
+        def pmalloc_method_buddy() -> bool:
+            return pmalloc_method.val == "buddy"
+        
+        @flag
+        def pmalloc_method_ncontig() -> bool:
+            return pmalloc_method.val == "ncontig"
+
+        @"Allocation policy"
+        def pmalloc_method() -> "simple" | "buddy" | "ncontig":
             """ Allocation policy for phiscal memory  """
             
             """ Allocation policy for phiscal memory  """
             
-            type(["simple", "buddy", "ncontig"])
-            default("simple")
+            return "simple"
 
 
-        @Group("Simple")
+        @"PMalloc Thresholds"
         def pmalloc_simple_po_thresholds():
         def pmalloc_simple_po_thresholds():
+
+            require(pmalloc_method_simple)
             
             
-            @Term("Maximum cached order-0 free pages")
-            def pmalloc_simple_max_po0():
+            @"Maximum cached order-0 free pages"
+            def pmalloc_simple_max_po0() -> int:
                 """ free list capacity for order-0 pages  """
                 
                 """ free list capacity for order-0 pages  """
                 
-                type(int)
-                default(4096)
+                return 4096
 
 
-            @Term("Maximum cached order-1 free pages")
-            def pmalloc_simple_max_po1():
+            @"Maximum cached order-1 free pages"
+            def pmalloc_simple_max_po1() -> int:
                 """ free list capacity for order-1 pages  """
 
                 """ free list capacity for order-1 pages  """
 
-                type(int)
-                default(2048)
+                return 2048
             
             
-            @Term("Maximum cached order-2 free pages")
-            def pmalloc_simple_max_po2():
+            @"Maximum cached order-2 free pages"
+            def pmalloc_simple_max_po2() -> int:
                 """ free list capacity for order-2 pages  """
 
                 """ free list capacity for order-2 pages  """
 
-                type(int)
-                default(2048)
+                return 2048
             
             
-            @Term("Maximum cached order-3 free pages")
-            def pmalloc_simple_max_po3():
+            @"Maximum cached order-3 free pages"
+            def pmalloc_simple_max_po3() -> int:
                 """ free list capacity for order-3 pages  """
                 
                 """ free list capacity for order-3 pages  """
                 
-                type(int)
-                default(2048)
+                return 2048
             
             
-            @Term("Maximum cached order-4 free pages")
-            def pmalloc_simple_max_po4():
+            @"Maximum cached order-4 free pages"
+            def pmalloc_simple_max_po4() -> int:
                 """ free list capacity for order-4 pages  """
 
                 """ free list capacity for order-4 pages  """
 
-                type(int)
-                default(512)
+                return 512
             
             
-            @Term("Maximum cached order-5 free pages")
-            def pmalloc_simple_max_po5():
+            @"Maximum cached order-5 free pages"
+            def pmalloc_simple_max_po5() -> int:
                 """ free list capacity for order-5 pages  """
 
                 """ free list capacity for order-5 pages  """
 
-                type(int)
-                default(512)
+                return 512
             
             
-            @Term("Maximum cached order-6 free pages")
-            def pmalloc_simple_max_po6():
+            @"Maximum cached order-6 free pages"
+            def pmalloc_simple_max_po6() -> int:
                 """ free list capacity for order-6 pages  """
 
                 """ free list capacity for order-6 pages  """
 
-                type(int)
-                default(128)
+                return 128
             
             
-            @Term("Maximum cached order-7 free pages")
-            def pmalloc_simple_max_po7():
+            @"Maximum cached order-7 free pages"
+            def pmalloc_simple_max_po7() -> int:
                 """ free list capacity for order-7 pages  """
 
                 """ free list capacity for order-7 pages  """
 
-                type(int)
-                default(128)
+                return 128
             
             
-            @Term("Maximum cached order-8 free pages")
-            def pmalloc_simple_max_po8():
+            @"Maximum cached order-8 free pages"
+            def pmalloc_simple_max_po8() -> int:
                 """ free list capacity for order-8 pages  """
 
                 """ free list capacity for order-8 pages  """
 
-                type(int)
-                default(64)
+                return 64
             
             
-            @Term("Maximum cached order-9 free pages")
-            def pmalloc_simple_max_po9():
+            @"Maximum cached order-9 free pages"
+            def pmalloc_simple_max_po9() -> int:
                 """ free list capacity for order-9 pages  """
 
                 """ free list capacity for order-9 pages  """
 
-                type(int)
-                default(32)
-
-            return v(pmalloc_method) == "simple"
-
-    add_to_collection(kernel_feature)
\ No newline at end of file
+                return 32
\ No newline at end of file
index 3fc72ec1a7a028d7f2bef2ae15900780d93cf05f..8386486d511a0054928239787cbf41744016594a 100644 (file)
@@ -1,4 +1,4 @@
-sources([
+src.c += (
     "signal.c",
     "sched.c",
     "fork.c",
     "signal.c",
     "sched.c",
     "fork.c",
@@ -8,4 +8,4 @@ sources([
     "thread.c",
     "preemption.c",
     "switch.c",
     "thread.c",
     "preemption.c",
     "switch.c",
-])
\ No newline at end of file
+)
\ No newline at end of file
index 2f647b4803913cf93d76959339e3a31be7fa37e1..52866eeab60f3ae42f8c1542157c18702ec78e83 100644 (file)
@@ -1,4 +1,4 @@
-sources([
+src.c += (
     "crc.c",
     "hash.c",
     "klibc/itoa.c",
     "crc.c",
     "hash.c",
     "klibc/itoa.c",
@@ -9,4 +9,4 @@ sources([
     "klibc/string/strcpy.c",
     "klibc/string/strlen.c",
     "klibc/string/trim.c"
     "klibc/string/strcpy.c",
     "klibc/string/strlen.c",
     "klibc/string/trim.c"
-])
\ No newline at end of file
+)
\ No newline at end of file
index 92e11b1788faad5ead1c1a9bed7219600bb7ae55..31f8b5ade5c22c699fd1298aebbe90e8902fc9d3 100644 (file)
@@ -3,13 +3,17 @@ include lunabuild.mkinc
 include utils.mkinc
 
 include $(lbuild_mkinc)
 include utils.mkinc
 
 include $(lbuild_mkinc)
+include $(lconfig_mkinc)
 
 
-ksrc_objs := $(addsuffix .o,$(_LBUILD_SRCS))
-ksrc_deps := $(addsuffix .d,$(_LBUILD_SRCS))
-khdr_opts := $(addprefix -include ,$(_LBUILD_HDRS))
-kinc_opts := $(addprefix -I,$(_LBUILD_INCS))
+ksrc_objs := $(addsuffix .o,$(BUILD_SRCS))
+ksrc_deps := $(addsuffix .d,$(BUILD_SRCS))
+khdr_opts := $(addprefix -include ,$(BUILD_HDR))
+kinc_opts := $(addprefix -I,$(BUILD_INC))
 config_h += -include $(lbuild_config_h)
 
 kcflags := $(khdr_opts) $(kinc_opts) $(config_h)
 
 config_h += -include $(lbuild_config_h)
 
 kcflags := $(khdr_opts) $(kinc_opts) $(config_h)
 
+CFLAGS += $(BUILD_CFLAGS)
+LDFLAGS += $(BUILD_LDFLAGS)
+
 -include $(ksrc_deps)
\ No newline at end of file
 -include $(ksrc_deps)
\ No newline at end of file
index aee8e495a70f776a876caf1b2acaa67e90e00433..f4cd27048a531713888b918350c0c0af029c8621 100644 (file)
@@ -1,29 +1,44 @@
 lbuild_dir := $(CURDIR)/.builder
 lbuild_dir := $(CURDIR)/.builder
-lbuild_config_h := $(lbuild_dir)/configs.h
-lbuild_mkinc := $(lbuild_dir)/lbuild.mkinc
+lbuild_config_h := $(lbuild_dir)/config.h
+lbuild_mkinc := $(lbuild_dir)/build.mkinc
+lconfig_mkinc := $(lbuild_dir)/config.mkinc
 lconfig_save := $(CURDIR)/.config.json
 
 lconfig_save := $(CURDIR)/.config.json
 
-lbuild_opts := --lconfig-file LConfig
-
 all_lconfigs = $(shell find $(CURDIR) -name "LConfig")
 all_lconfigs = $(shell find $(CURDIR) -name "LConfig")
+all_lbuilds = $(shell find $(CURDIR) -name "LBuild")
+
+.PHONY: __gen_config __gen_build __gen_both config
 
 
-LCONFIG_FLAGS += --config $(lbuild_opts) --config-save $(lconfig_save)
+define __gen_config
+       @echo restarting configuration...
+       @$(LBUILD) --gen-config $(lbuild_dir)
+endef
+
+define __gen_build
+       @echo restarting configuration...
+       @$(LBUILD) --gen-config $(lbuild_dir)
+endef
+
+define __gen_both
+       @echo restarting configuration...
+       @$(LBUILD) --gen-build --gen-config $(lbuild_dir)
+endef
 
 export
 $(lconfig_save): $(all_lconfigs)
 
 export
 $(lconfig_save): $(all_lconfigs)
-       @echo restarting configuration...
-       @$(LBUILD) $(LCONFIG_FLAGS)  --force -o $(lbuild_dir)/
+       $(call __gen_config)
 
 export
 
 export
-$(lbuild_config_h): $(lconfig_save)
-       @$(LBUILD) $(LCONFIG_FLAGS) -o $(@D)
+$(lconfig_mkinc): $(all_lconfigs)
+       $(call __gen_config)
 
 export
 
 export
-$(lbuild_mkinc): $(lbuild_config_h)
-       @$(LBUILD) LBuild $(lbuild_opts) -o $(@D)
+$(lbuild_config_h): $(all_lbuilds)
+       $(call __gen_build)
 
 
-.PHONY: config
 export
 export
-config: $(all_lconfigs)
-       @$(LBUILD) $(LCONFIG_FLAGS) --force\
-                          -o $(lbuild_dir)/
+$(lbuild_mkinc): $(all_lbuilds)
+       $(call __gen_build)
+
+config: $(all_lbuilds) $(all_lconfigs)
+       $(call __gen_both)
index 246b9d15196ff53d78a318494311f206a1b07d2c..fcc6f423e82bac32796630a6d9b2b4282b2652ab 100644 (file)
@@ -4,29 +4,12 @@ AS := $(CX_PREFIX)as
 AR := $(CX_PREFIX)ar
 LBUILD ?= $(shell realpath ./scripts/build-tools/luna_build.py)
 
 AR := $(CX_PREFIX)ar
 LBUILD ?= $(shell realpath ./scripts/build-tools/luna_build.py)
 
-
-W := -Wall -Wextra -Werror \
-               -Wno-unknown-pragmas \
-               -Wno-unused-function \
-               -Wno-unused-variable\
-               -Wno-unused-but-set-variable \
-               -Wno-unused-parameter \
-               -Wno-discarded-qualifiers\
-               -Werror=incompatible-pointer-types
-
-OFLAGS := -fno-omit-frame-pointer      \
-             -finline-small-functions
-
-CFLAGS := -std=gnu99 $(OFLAGS) $(W) -g
-
 ifeq ($(MODE),debug)
        O := -Og
 else
        O := -O2
 endif
 
 ifeq ($(MODE),debug)
        O := -Og
 else
        O := -O2
 endif
 
-CFLAGS += $(O)
-
 LDFLAGS := $(O)
 LDFLAGS := $(O)
-
+CFLAGS  := -std=gnu99 -g $(O)
 MKFLAGS := --no-print-directory
\ No newline at end of file
 MKFLAGS := --no-print-directory
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/integration/build_gen.py b/lunaix-os/scripts/build-tools/integration/build_gen.py
deleted file mode 100644 (file)
index 6b1cde7..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-from lbuild.api import BuildGenerator
-from lbuild.common import BuildEnvironment
-from lib.utils import join_path
-from os import getenv
-
-class MakefileBuildGen(BuildGenerator):
-    def __init__(self, out_dir, name = "lbuild.mkinc") -> None:
-        self.__path = join_path(out_dir, name)
-    
-    def emit_makearray(self, name, values):
-        r = []
-        r.append(f"{name} :=")
-        for v in values:
-            r.append(f"{v}")
-        return [" ".join(r)]
-
-    def generate(self, env: BuildEnvironment):
-        path = env.to_wspath(self.__path)
-        lines = []
-
-        opts = env.get_object("CC_OPTS", [])
-        lines.append("CFLAGS += %s"%(" ".join(opts)))
-        
-        opts = env.get_object("LD_OPTS", [])
-        lines.append("LDFLAGS += %s"%(" ".join(opts)))
-
-        arr = self.emit_makearray("_LBUILD_SRCS", env.srcs())
-        lines += arr
-
-        arr = self.emit_makearray("_LBUILD_HDRS", env.headers())
-        lines += arr
-
-        arr = self.emit_makearray("_LBUILD_INCS", env.includes())
-        lines += arr
-
-        with open(path, 'w') as f:
-            f.write("\n".join(lines))
-        
-
-def install_lbuild_functions(_env: BuildEnvironment):
-    def set_opts(env: BuildEnvironment, name, opts, override):
-        if not isinstance(opts, list):
-            opts = [opts]
-        
-        _opts = env.get_object(name, [])
-
-        if override:
-            _opts = opts
-        else:
-            _opts += opts
-
-        env.set_object(name, _opts)
-
-    def compile_opts(env: BuildEnvironment, opts, override=False):
-        set_opts(env, "CC_OPTS", opts, override)
-
-    def linking_opts(env: BuildEnvironment, opts, override=False):
-        set_opts(env, "LD_OPTS", opts, override)
-
-    def env(env, name, default=None):
-        return getenv(name, default)
-
-
-    _env.add_external_func(compile_opts)
-    _env.add_external_func(linking_opts)
-    _env.add_external_func(env)
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/integration/config_io.py b/lunaix-os/scripts/build-tools/integration/config_io.py
deleted file mode 100644 (file)
index 15e1ba8..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-from lcfg.api import ConfigIOProvider
-from lcfg.utils import is_basic
-
-import re
-
-class CHeaderConfigProvider(ConfigIOProvider):
-    def __init__(self, header_save, 
-                 header_prefix="CONFIG_") -> None:
-        self.__header_export = header_save
-        self.__prefix = header_prefix
-        self.__re = re.compile(r"^[A-Za-z0-9_]+$")
-
-    def export(self, env, config_dict):
-        lines = []
-        for k, v in config_dict.items():
-            result = [ "#define" ]
-            s = str.upper(k)
-            s = f"{self.__prefix}{s}"
-
-            if isinstance(v, str) and self.__re.match(v):
-                s = f"{s}_{str.upper(v)}"
-                v = ""
-    
-            result.append(s)
-            
-            v = self.serialize_value(v)
-            if v is None or v is False:
-                result.insert(0, "//")
-            elif isinstance(v, str):
-                result.append(v)
-
-            lines.append(" ".join(result))
-            
-        with open(self.__header_export, 'w') as f:
-            f.write("\n".join(lines))
-
-
-    def serialize_value(self, v):
-        if v is None:
-            return None
-        
-        if isinstance(v, bool):
-            return v
-        
-        if v and isinstance(v, str):
-            return f'"{v}"'
-        
-        if is_basic(v):
-            return str(v)
-        
-        raise ValueError(
-                f"serialising {type(v)}: not supported")
diff --git a/lunaix-os/scripts/build-tools/integration/lbuild_bridge.py b/lunaix-os/scripts/build-tools/integration/lbuild_bridge.py
deleted file mode 100644 (file)
index 2d755d7..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-from lbuild.api import ConfigProvider
-from lcfg.common import LConfigEnvironment
-
-class LConfigProvider(ConfigProvider):
-    def __init__(self, lcfg_env: LConfigEnvironment) -> None:
-        super().__init__()
-        self.__env = lcfg_env
-
-    def configured_value(self, name):
-        return self.__env.lookup_value(name)
-    
-    def has_config(self, name):
-        try:
-            v = self.__env.lookup_value(name)
-            return not not v
-        except:
-            return False
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/integration/render_ishell.py b/lunaix-os/scripts/build-tools/integration/render_ishell.py
deleted file mode 100644 (file)
index 4fdee85..0000000
+++ /dev/null
@@ -1,296 +0,0 @@
-from lcfg.api import (
-    RenderContext, 
-    InteractiveRenderer, 
-    Renderable,
-    ConfigTypeCheckError,
-    ConfigLoadException
-)
-from lcfg.common import LConfigEnvironment
-
-import shlex
-from lib.utils import join_path
-from pathlib import Path
-import readline
-
-class ShellException(Exception):
-    def __init__(self, *args: object) -> None:
-        super().__init__(*args)
-
-class ViewElement:
-    def __init__(self, label, node) -> None:
-        self.label = label
-        self.node = node
-
-    def expand(self, sctx):
-        return False
-
-    def read(self, sctx):
-        return None
-    
-    def write(self, sctx, val):
-        return None
-    
-    def get_help(self, sctx):
-        return self.node.help_prompt()
-    
-    def get_type(self, sctx):
-        return "N/A"
-    
-    def draw(self, sctx, canvas):
-        pass
-
-
-class SubviewLink(ViewElement):
-    def __init__(self, label, node, cb) -> None:
-        super().__init__(label, node)
-        self.__callback = cb
-
-    def expand(self, sctx):
-        sctx.clear_view()
-        self.__callback(sctx)
-        return True
-    
-    def draw(self, sctx, canvas):
-        print(f" [{self.label}]")
-
-    def get_type(self, sctx):
-        return f"Collection: {self.label}"
-
-class RootWrapper(ViewElement):
-    def __init__(self, root) -> None:
-        self.__root = root
-        super().__init__("root", None)
-
-    def expand(self, sctx):
-        sctx.clear_view()
-        self.__root.render(sctx)
-        return True
-    
-    def draw(self, sctx, canvas):
-        pass
-
-    def get_type(self, sctx):
-        return ""
-
-class FieldView(ViewElement):
-    def __init__(self, label, node) -> None:
-        super().__init__(label, node)
-
-    def read(self, sctx):
-        return self.node.get_value()
-    
-    def write(self, sctx, val):
-        if self.node.read_only():
-            return None
-        
-        self.node.set_value(val)
-        return val
-    
-    def get_type(self, sctx):
-        return f"Config term: {self.label}\n{self.node.get_type()}"
-    
-    def draw(self, sctx, canvas):
-        suffix = ""
-        if self.node.read_only():
-            suffix = " (ro)"
-        print(f" {self.label}{suffix}")
-
-class ShellContext(RenderContext):
-    def __init__(self) -> None:
-        super().__init__()
-
-        self._view = {}
-        self._subviews = []
-        self._field = []
-    
-    def add_expandable(self, label, node, on_expand_cb):
-        name = node.get_name()
-        self._view[name] = SubviewLink(name, node, on_expand_cb)
-        self._subviews.append(name)
-    
-    def add_field(self, label, node):
-        name = node.get_name()
-        self._view[name] = FieldView(name, node)
-        self._field.append(name)
-    
-    def clear_view(self):
-        self._view.clear()
-        self._subviews.clear()
-        self._field.clear()
-
-    def redraw(self):
-        for v in self._subviews + self._field:
-            self._view[v].draw(self, None)
-
-    def get_view(self, label):
-        if label in self._view:
-            return self._view[label]
-        return None
-
-class InteractiveShell(InteractiveRenderer):
-    def __init__(self, root: Renderable) -> None:
-        super().__init__()
-        self.__levels = [RootWrapper(root)]
-        self.__aborted = True
-        self.__sctx = ShellContext()
-        self.__cmd = {
-            "ls": (
-                "list all config node under current collection",
-                lambda *args: 
-                    self.__sctx.redraw()
-            ),
-            "help": (
-                "print help prompt for given name of node",
-                lambda *args: 
-                    self.print_help(*args)
-            ),
-            "type": (
-                "print the type of a config node",
-                lambda *args: 
-                    self.print_type(*args)
-            ),
-            "cd": (
-                "navigate to a collection node given a unix-like path",
-                lambda *args: 
-                    self.get_in(*args)
-            ),
-            "usage": (
-                "print out the usage",
-                lambda *args:
-                    self.print_usage()
-            )
-        }
-
-    def get_path(self):
-        l = [level.label for level in self.__levels[1:]]
-        return f"/{'/'.join(l)}"
-    
-    def resolve(self, relative):
-        ctx = ShellContext()
-        p = join_path(self.get_path(), relative)
-        ps = Path(p).resolve().parts
-        if ps[0] == '/':
-            ps = ps[1:]
-
-        node = self.__levels[0]
-        levels = [node]
-        for part in ps:
-            if not node.expand(ctx):
-                raise ShellException(f"node is not a collection: {part}")
-            
-            node = ctx.get_view(part)
-            if not node:
-                raise ShellException(f"no such node: {part}")
-
-            levels.append(node)
-        
-        return (node, levels)
-
-    def print_usage(self):
-        for cmd, (desc, _) in self.__cmd.items():
-            print("\n".join([
-                cmd,
-                f"   {desc}",
-                ""
-            ]))
-
-    def print_help(self, node_name):
-        view, _ = self.resolve(node_name)
-        
-        print(view.get_help(self.__sctx))
-
-    def print_type(self, node_name):
-        view, _ = self.resolve(node_name)
-        
-        print(view.get_type(self.__sctx))
-
-    def do_read(self, node_name):
-        view, _ = self.resolve(node_name)
-        rd_val = view.read(self.__sctx)
-        if rd_val is None:
-            raise ShellException(f"config node {view.label} is not readable")
-    
-        print(rd_val)
-
-    def do_write(self, node_name, val):
-        view, _ = self.resolve(node_name)
-        wr = view.write(self.__sctx, val)
-        if not wr:
-            raise ShellException(f"config node {view.label} is read only")
-
-        print(f"write: {val}")
-        self.do_render()
-
-    def get_in(self, node_name):
-        view, lvl = self.resolve(node_name)
-        
-        if not view.expand(self.__sctx):
-            print(f"{node_name} is not a collection")
-            return
-        
-        self.__levels = lvl
-
-    def do_render(self):
-        
-        curr = self.__levels[-1]        
-        curr.expand(self.__sctx)
-
-    def __loop(self):
-        prefix = "config: %s> "%(self.__levels[-1].label)
-
-        line = input(prefix)
-        args = shlex.split(line)
-        if not args:
-            return True
-        
-        cmd = args[0]
-        if cmd in self.__cmd:
-            self.__cmd[cmd][1](*args[1:])
-            return True
-        
-        if cmd == "exit":
-            self.__aborted = False
-            return False
-        
-        if cmd == "abort":
-            return False
-        
-        node = self.resolve(cmd)
-        if not node:
-            raise ShellException(f"unrecognised command {line}")
-        
-        if len(args) == 3 and args[1] == '=':
-            self.do_write(args[0], args[2])
-            return True
-        
-        if len(args) == 1:
-            self.do_read(args[0])
-            return True
-        
-        print(f"unrecognised command {line}")
-        return True
-
-
-    def render_loop(self):
-        self.do_render()
-        print("\n".join([
-            "Interactive LConfig Shell",
-            "   type 'usage' to find out how to use",
-            "   type 'exit' to end (with saving)",
-            "   type 'abort' to abort (without saving)",
-            "   type node name directly to read the value",
-            "   type 'node = val' to set 'val' to 'node'",
-            ""
-        ]))
-        while True:
-            try:
-                if not self.__loop():
-                    break
-            except KeyboardInterrupt:
-                break
-            except ConfigTypeCheckError as e:
-                print(e.args[0])
-            except ShellException as e:
-                print(e.args[0])
-
-        return not self.__aborted
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/lbuild/api.py b/lunaix-os/scripts/build-tools/lbuild/api.py
deleted file mode 100644 (file)
index 9f6f8d4..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-class ConfigProvider:
-    def __init__(self) -> None:
-        pass
-
-    def configured_value(self, name):
-        raise ValueError(f"config '{name}' is undefined or disabled")
-    
-    def has_config(self, name):
-        return False
-    
-class BuildGenerator:
-    def __init__(self) -> None:
-        pass
-
-    def generate(self, env):
-        pass
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/lbuild/build.py b/lunaix-os/scripts/build-tools/lbuild/build.py
new file mode 100644 (file)
index 0000000..93beeca
--- /dev/null
@@ -0,0 +1,108 @@
+import ast
+from pathlib    import Path
+from .common    import DirectoryTracker
+from lib.utils  import ConfigAST, Schema
+
+class LBuildImporter(ast.NodeTransformer):
+    ConfigImportFn = Schema(
+                        ast.Call, 
+                        func=Schema(ast.Name, id="import_"),
+                        args=[ast.Constant])
+    
+    ScopedAccess = Schema(
+                        ast.Attribute,
+                        value=ast.Name)
+    
+    def __init__(self, env, file):
+        super().__init__()
+        self.__parent = file.parent
+        self.__env = env
+
+        with file.open('r') as f:
+            subtree = ast.parse(f.read())
+        
+        self.__tree = [
+            DirectoryTracker.emit_enter(self.__parent),
+            *self.visit(subtree).body,
+            DirectoryTracker.emit_leave()
+        ]
+    
+    def tree(self):
+        return self.__tree
+    
+    def __gen_import_subtree(self, relpath, variants):    
+        block = []
+        for var in variants:
+            p = relpath/ var / "LBuild"
+            block += LBuildImporter(self.__env, p).tree()
+        
+        return ast.If(ast.Constant(True), block, [])
+
+    def visit_ImportFrom(self, node):
+        if ConfigAST.ConfigImport != node:
+            return node
+        
+        module = node.module
+        module = "" if not module else module
+        subpath = Path(*module.split('.'))
+        
+        p = self.__parent / subpath
+        return self.__gen_import_subtree(p, [x.name for x in node.names])
+    
+    def visit_Attribute(self, node):
+        if LBuildImporter.ScopedAccess != node:
+            return self.generic_visit(node)
+        
+        scope = node.value.id
+        subscope = node.attr
+
+        if scope not in self.__env.scopes:
+            return self.generic_visit(node)
+        
+        provider = self.__env.scopes[scope]
+        
+        return ast.Subscript(
+            value=ast.Name(provider.context_name(), ctx=node.value.ctx),
+            slice=ast.Constant(subscope),
+            ctx = node.ctx
+        )
+    
+    def visit_Expr(self, node):
+        val = node.value
+        if LBuildImporter.ConfigImportFn == val:
+            name = val.args[0].value
+            return self.__gen_import_subtree(self.__parent, [name])
+        
+        return self.generic_visit(node)
+
+class BuildEnvironment:
+    def __init__(self):
+        self.scopes = {}
+    
+    def load(self, rootfile):
+        self.__exec = self.__load(Path(rootfile))
+
+    def register_scope(self, scope):
+        if scope.name in self.scopes:
+            raise Exception(f"{scope.name} already exists")
+        self.scopes[scope.name] = scope
+
+    def __load(self, rootfile):
+        module = ast.Module(
+                    LBuildImporter(self, rootfile).tree(), 
+                    type_ignores=[])
+
+        module = ast.fix_missing_locations(module)
+        return compile(module, rootfile, 'exec')
+    
+    def update(self):
+        g = {
+            "__LBUILD__": True,
+        }
+
+        for scope in self.scopes.values():
+            scope.reset()
+            g[scope.context_name()] = scope
+
+        DirectoryTracker.bind(g)
+        exec(self.__exec, g)
\ No newline at end of file
index a973760136bacc09dca68c6e56e4f47f0348c2af..01a226cfa6f333849ca1e05eaa40b419fe420d05 100644 (file)
@@ -1,59 +1,57 @@
-from lib.utils import join_path
-import os
+import ast
+from pathlib import Path
 
 
-class BuildEnvironment:
-    def __init__(self, workspace_dir, generator) -> None:
-        self.__config_provider = None
-        self.__sources = []
-        self.__headers = []
-        self.__inc_dir = []
-        self.__ws_dir = workspace_dir
-        self.__ext_object = {}
-        self.__ext_function = {}
-        self.__generator = generator
+class DirectoryTracker:
+    def __init__(self):
+        self.__stack = []
 
 
-    def set_config_provider(self, provider):
-        self.__config_provider = provider
-    
-    def config_provider(self):
-        return self.__config_provider
-    
-    def add_sources(self, sources):
-        self.__sources += sources
+    def push(self, dir):
+        self.__stack.append(Path(dir))
 
 
-    def add_headers(self, sources):
-        for h in sources:
-            if not os.path.isdir(h):
-                self.__headers.append(h)
-            else:
-                self.__inc_dir.append(h)
-
-    def to_wspath(self, file):
-        path = join_path(self.__ws_dir, file)
-        return os.path.relpath(path, self.__ws_dir)
+    def pop(self):
+        self.__stack.pop()
     
     
-    def export(self):
-        self.__generator.generate(self)
-
-    def get_object(self, key, _default=None):
-        return _default if key not in self.__ext_object else self.__ext_object[key]
+    def active_relative(self):
+        root = self.__stack[0]
+        return self.__stack[-1].relative_to(root)
 
 
-    def set_object(self, key, object):
-        self.__ext_object[key] = object
-
-    def srcs(self):
-        return list(self.__sources)
-    
-    def headers(self):
-        return list(self.__headers)
+    @staticmethod
+    def context_name():
+        return "__FILESTACK__"
     
     
-    def includes(self):
-        return list(self.__inc_dir)
+    @staticmethod
+    def get(context):
+        return context[DirectoryTracker.context_name()]
     
     
-    def add_external_func(self, function):
-        name = function.__name__
-        invk = lambda *args, **kwargs: function(self, *args, **kwargs)
-        self.__ext_function[name] = invk
-
-    def external_func_table(self):
-        return self.__ext_function
\ No newline at end of file
+    @staticmethod
+    def bind(context):
+        name = DirectoryTracker.context_name()
+        context[name] = DirectoryTracker()
+
+    @staticmethod
+    def emit_enter(dir):
+        name = DirectoryTracker.context_name()
+        return ast.Expr(
+                    ast.Call(
+                        func=ast.Attribute(
+                            value=ast.Name(name, ctx=ast.Load()),
+                            attr="push",
+                            ctx=ast.Load()
+                        ),
+                        args=[
+                            ast.Constant(str(dir))
+                        ],
+                        keywords=[]))
+
+    @staticmethod
+    def emit_leave():
+        name = DirectoryTracker.context_name()
+        return ast.Expr(
+                    ast.Call(
+                        func=ast.Attribute(
+                            value=ast.Name(name, ctx=ast.Load()),
+                            attr="pop",
+                            ctx=ast.Load()
+                        ),
+                        args=[],
+                        keywords=[]))
diff --git a/lunaix-os/scripts/build-tools/lbuild/contract.py b/lunaix-os/scripts/build-tools/lbuild/contract.py
deleted file mode 100644 (file)
index 0484947..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-from lib.sandbox import Sandbox
-from lib.utils import join_path
-from .common import BuildEnvironment
-
-import os
-
-class LunaBuildFile(Sandbox):
-    def __init__(self, env: BuildEnvironment, path) -> None:
-        super().__init__({
-            "_script": 
-                path,
-            "sources": 
-                lambda src: self.export_sources(src),
-            "headers":
-                lambda hdr: self.export_headers(hdr),
-            "configured":
-                lambda name: self.check_config(name),
-            "config":
-                lambda name: self.read_config(name),
-            "use":
-                lambda file: self.import_buildfile(file),
-            **env.external_func_table()
-        })
-        
-        self.__srcs = []
-        self.__hdrs = []
-        self.__env  = env
-
-        self.__path = path
-        self.__dir  = os.path.dirname(path)
-        
-    def resolve(self):
-        self.execute(self.__path)
-        self.__env.add_sources(self.__srcs)
-        self.__env.add_headers(self.__hdrs)
-
-    def __do_process(self, list):
-        resolved = []
-        for entry in list:
-            if not entry:
-                continue
-            resolved.append(self.__resolve_value(entry))
-        return resolved
-
-    def expand_select(self, val):
-        tests = list(val.keys())
-        if len(tests) != 1:
-            raise TypeError(
-                "select statement must have exactly one conditional")
-        
-        test = tests[0]
-        outcomes = val[test]
-        if test not in outcomes:
-            self.__raise("unbounded select")
-        return outcomes[test]
-
-    def __resolve_value(self, source):
-        resolved = source
-        while isinstance(resolved, dict):
-            if isinstance(resolved, dict):
-                resolved = self.expand_select(resolved)
-            else:
-                self.__raise(f"entry with unknown type: {resolved}")
-        
-        if isinstance(resolved, list):
-            rs = []
-            for file in resolved:
-                file = join_path(self.__dir, file)
-                file = self.__env.to_wspath(file)
-                rs.append(file)
-        else:
-            rs = join_path(self.__dir, resolved)
-            rs = [self.__env.to_wspath(rs)]
-
-        return rs
-    
-    def import_buildfile(self, path):
-        path = self.__resolve_value(path)
-        for p in path:
-            if (os.path.isdir(p)):
-                p = os.path.join(p, "LBuild")
-            
-            if not os.path.exists(p):
-                self.__raise("Build file not exist: %s", p)
-
-            if os.path.abspath(p) == os.path.abspath(self.__path):
-                self.__raise("self dependency detected")
-
-            LunaBuildFile(self.__env, p).resolve()
-
-    def export_sources(self, src):
-        src = self.__resolve_value(src)
-        self.__srcs += src
-
-    def export_headers(self, hdr):
-        hdr = self.__resolve_value(hdr)
-        self.__hdrs += hdr
-
-    def check_config(self, name):
-        return self.__env.config_provider().has_config(name)
-    
-    def read_config(self, name):
-        return self.__env.config_provider().configured_value(name)
-    
-    def __raise(self, msg, *kargs):
-        raise Exception(msg%kargs)
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/lbuild/scope.py b/lunaix-os/scripts/build-tools/lbuild/scope.py
new file mode 100644 (file)
index 0000000..149aa5a
--- /dev/null
@@ -0,0 +1,96 @@
+import inspect
+from lib.utils import Schema
+from .common import DirectoryTracker
+
+class ScopeProvider:
+    Enumerables = Schema(Schema.Union(list, tuple))
+    Primitives = Schema(Schema.Union(str, int, bool))
+    
+    def __init__(self, name):
+        super().__init__()
+        self.name = name
+        self.__accessors = {}
+
+    def __add_accessor(self, accessor, type):
+        acc = accessor
+        if isinstance(accessor, str):
+            acc = type(accessor)
+        self.__accessors[acc.name] = acc
+
+    def subscope(self, accessor):
+        self.__add_accessor(accessor, ScopeAccessor)
+
+    def file_subscope(self, accessor):
+        self.__add_accessor(accessor, FileScopeAccessor)
+
+    def accessors(self):
+        return self.__accessors.values()
+    
+    def reset(self):
+        for v in self.__accessors.values():
+            v.reset()
+
+    def context_name(self):
+        return f"__SC{self.name}"
+
+    def __getitem__(self, name):
+        if name not in self.__accessors:
+            return None
+        return self.__accessors[name]
+    
+    def __setitem__(self, name, val):
+        pass
+
+class ScopeAccessor:
+    def __init__(self, name):
+        super().__init__()
+        self.name = name
+        self.__values = []
+        self._active_context = None
+
+    def values(self):
+        return self.__values
+    
+    def reset(self):
+        self.__values.clear()
+
+    def add(self, x):
+        self.__load_build_context()
+
+        if ScopeProvider.Enumerables == x:
+            for v in x:
+                self.add(v)
+            return
+        
+        if ScopeProvider.Primitives == x:
+            self.put_value(x)
+            return
+        
+        raise Exception(f"invalid value '{x}' of type ('{type(x).__name__}')")
+    
+    def put_value(self, x):
+        self.__values.append(x)
+
+    def __load_build_context(self):
+        self._active_context = None
+        for f in inspect.stack():
+            if "__LBUILD__" not in f.frame.f_globals:
+                continue
+            self._active_context = f.frame.f_globals
+        
+        if not self._active_context:
+            raise Exception(
+                f"unable to modify '{self.name}' outside build context)")
+
+    def __iadd__(self, other):
+        self.add(other)
+
+class FileScopeAccessor(ScopeAccessor):
+    def __init__(self, name):
+        super().__init__(name)
+
+    def put_value(self, x):
+        tracker = DirectoryTracker.get(self._active_context)
+        x = str(tracker.active_relative() / x)
+        return super().put_value(x)
+
diff --git a/lunaix-os/scripts/build-tools/lcfg/api.py b/lunaix-os/scripts/build-tools/lcfg/api.py
deleted file mode 100644 (file)
index 1b0b30f..0000000
+++ /dev/null
@@ -1,139 +0,0 @@
-from abc import abstractmethod
-
-class ConfigLoadBaseException(Exception):
-    def __init__(self, msg, node, inner=None) -> None:
-        super().__init__(msg)
-        
-        self.__msg = msg
-        self.__node = node
-        self.__inner = inner
-
-    def __str__(self) -> str:
-        return super().__str__()
-    
-class ConfigLoadException(ConfigLoadBaseException):
-    def __init__(self, msg, node = None, inner=None) -> None:
-        super().__init__(msg, node, inner)
-
-class ConfigTypeCheckError(ConfigLoadBaseException):
-    def __init__(self, msg, node, inner=None) -> None:
-        super().__init__(msg, node, inner)
-
-class ConfigIOProvider:
-    def __init__(self) -> None:
-        pass
-
-    @abstractmethod
-    def export(self, env, config_dict):
-        pass
-    
-    @abstractmethod
-    def save(self, env, config_dict):
-        pass
-
-    @abstractmethod
-    def load(self, env):
-        pass
-
-
-class TypeProviderBase:
-    def __init__(self, type) -> None:
-        self._type = type
-
-    @abstractmethod
-    def check(self, val):
-        return True
-    
-    @abstractmethod
-    def serialise(self, val):
-        pass
-
-    @abstractmethod
-    def deserialise(self, val):
-        pass
-
-    @abstractmethod
-    def parse_input(self, input_str):
-        pass
-
-    @abstractmethod
-    def to_input(self, val):
-        pass
-
-    def allow_none(self):
-        return False
-    
-    @staticmethod
-    def typedef_matched(typedef):
-        return False
-    
-    @abstractmethod
-    def __str__(self) -> str:
-        pass
-
-class Renderable:
-    def __init__(self) -> None:
-        pass
-    
-    @abstractmethod
-    def render(self, rctx):
-        pass
-
-class RenderContext:
-    def __init__(self) -> None:
-        pass
-
-    @abstractmethod
-    def add_expandable(self, label, node, on_expand_cb):
-        pass
-
-    @abstractmethod
-    def add_field(self, label, node):
-        pass
-
-class InteractiveRenderer(RenderContext):
-    def __init__(self) -> None:
-        super().__init__()
-
-    @abstractmethod
-    def render_loop(self):
-        pass
-
-
-class LConfigBuiltin:
-    def __init__(self, f, is_contextual, rename=None, caller_type=[]):
-        self.name = f.__name__ if not rename else rename
-        self.__f = f
-        self.__contextual = is_contextual
-        self.__caller_type = caller_type
-
-    def __call__(self, env, *args, **kwds):
-        if not self.__contextual:
-            return self.__f(env, *args, **kwds)
-        
-        caller = env.callframe_at(0)
-        f_name = self.__f.__name__
-
-        if not caller:
-            raise ConfigLoadException(
-                f"contextual function '{f_name}' can not be called contextless",
-                None
-            )
-        
-        if self.__caller_type and not any([isinstance(caller, x) for x in self.__caller_type]):
-            raise ConfigLoadException(
-                f"caller of '{f_name}' ({caller}) must have type of any {self.__caller_type}",
-                caller
-            )
-    
-        return self.__f(env, caller, *args, **kwds)
-    
-def contextual(name=None, caller_type=[]):
-    def wrapper(func):
-        return LConfigBuiltin(func, True, name, caller_type)
-    return wrapper
-
-def builtin(name=None):
-    def wrapper(func):
-        return LConfigBuiltin(func, False, name)
-    return wrapper
diff --git a/lunaix-os/scripts/build-tools/lcfg/builtins.py b/lunaix-os/scripts/build-tools/lcfg/builtins.py
deleted file mode 100644 (file)
index f072303..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-from .api import contextual, builtin
-from .lcnodes import LCFuncNode, LCTermNode, LCModuleNode
-from lib.utils import join_path
-import os
-
-@contextual()
-def v(env, caller, term):
-    node = env.lookup_node(term.__name__)
-    env.dependency().add(node, caller)
-    return env.resolve_symbol(node.get_name())
-
-@contextual(caller_type=[LCModuleNode])
-def include(env, caller, file):
-    fobj = caller.get_fo()
-    path = os.path.dirname(fobj.filename())
-    path = join_path(path, file)
-    
-    if os.path.isdir(path):
-        path = join_path(path, "LConfig")
-
-    env.resolve_module(path)
-
-@contextual("type", caller_type=[LCTermNode])
-def term_type(env, caller, type):
-    caller.set_type(type)
-
-@contextual("add_to_collection", caller_type=[LCFuncNode])
-def parent(env, caller, ref):
-    sym = env.lookup_node(ref.__name__)
-
-    caller.set_parent(sym)
-
-@contextual(caller_type=[LCTermNode])
-def default(env, caller, val):
-    caller.set_default(val)
-
-@contextual(caller_type=[LCTermNode])
-def set_value(env, caller, val):
-    caller.set_value(val)
-
-@builtin()
-def env(env, key, default=None):
-    return os.getenv(key, default)
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/lcfg/common.py b/lunaix-os/scripts/build-tools/lcfg/common.py
deleted file mode 100644 (file)
index 383d1ec..0000000
+++ /dev/null
@@ -1,233 +0,0 @@
-import os.path as path
-import ast, json
-
-from .lcnodes import LCModuleNode, LCTermNode
-from .api import (
-    ConfigLoadException, 
-    Renderable
-)
-
-class LConfigFile:
-    def __init__(self, 
-                 env, 
-                 file) -> None:
-        self.__env = env
-        self.__file = env.to_wspath(file)
-
-        with open(file) as f:
-            tree = ast.parse(f.read(), self.__file, mode='exec')
-            self.__module = LCModuleNode(self, tree)
-
-    def filename(self):
-        return self.__file
-    
-    def env(self):
-        return self.__env
-    
-    def root_module(self):
-        return self.__module
-    
-    def compile_astns(self, astns):
-        if not isinstance(astns, list):
-            astns = [astns]
-
-        return compile(
-            ast.Module(body=astns, type_ignores=[]), 
-            self.__file, mode='exec')
-    
-class DependencyGraph:
-    def __init__(self) -> None:
-        self._edges = {}
-
-    def add(self, dependent, dependee):
-        if dependent in self._edges:
-            if dependee in self._edges[dependent]:
-                return
-            self._edges[dependent].add(dependee)
-        else:
-            self._edges[dependent] = set([dependee])
-        
-        if self.__check_loop(dependee):
-            raise ConfigLoadException(
-                f"loop dependency detected: {dependent.get_name()}",
-                dependee)
-
-    def __check_loop(self, start):
-        visited = set()
-        q = [start]
-        while len(q) > 0:
-            current = q.pop()
-            if current in visited:
-                return True
-            
-            visited.add(current)
-            if current not in self._edges:
-                continue
-            for x in self._edges[current]:
-                q.append(x)
-        
-        return False
-    
-    def cascade(self, start):
-        q = [start]
-        while len(q) > 0:
-            current = q.pop()
-            if current in self._edges:
-                for x in self._edges[current]:
-                    q.append(x)
-            if current != start:
-                current.evaluate()
-
-class ConfigTypeFactory:
-    def __init__(self) -> None:
-        self.__providers = []
-
-    def regitser(self, provider_type):
-        self.__providers.append(provider_type)
-
-    def create(self, typedef):
-        for provider in self.__providers:
-            if not provider.typedef_matched(typedef):
-                continue
-            return provider(typedef)
-        
-        raise ConfigLoadException(
-                f"no type provider defined for type: {typedef}", None)
-
-class LConfigEvaluationWrapper:
-    def __init__(self, env, node) -> None:
-        self.__env = env
-        self.__node = node
-
-    def __enter__(self):
-        self.__env.push_executing_node(self.__node)
-        return self
-
-    def __exit__(self, type, value, tb):
-        self.__env.pop_executing_node()
-
-    def evaluate(self):
-        return self.__env.evaluate_node(self.__node)
-
-class LConfigEnvironment(Renderable):
-    def __init__(self, workspace, config_io) -> None:
-        super().__init__()
-        
-        self.__ws_path = path.abspath(workspace)
-        self.__exec_globl = {}
-        self.__eval_stack = []
-        self.__lc_modules = []
-        self.__config_val = {}
-        self.__node_table = {}
-        self.__deps_graph = DependencyGraph()
-        self.__type_fatry = ConfigTypeFactory()
-        self.__config_io  = config_io
-
-    def to_wspath(self, _path):
-        _path = path.abspath(_path)
-        return path.relpath(_path, self.__ws_path)
-
-    def register_builtin_func(self, func_obj):
-        call = (lambda *arg, **kwargs:
-                     func_obj(self, *arg, **kwargs))
-        
-        self.__exec_globl[func_obj.name] = call
-
-    def resolve_module(self, file):
-        fo = LConfigFile(self, file)
-        self.__lc_modules.insert(0, (fo.root_module()))
-
-    def evaluate_node(self, node):
-        name = node.get_name()
-        
-        return self.get_symbol(name)()
-    
-    def eval_context(self, node):
-        return LConfigEvaluationWrapper(self, node)
-    
-    def push_executing_node(self, node):
-        self.__eval_stack.append(node)
-
-    def pop_executing_node(self):
-        return self.__eval_stack.pop()
-
-    def register_node(self, node):
-        self.__node_table[node.get_name()] = node
-
-        l = {}
-        try:
-            self.push_executing_node(node)
-            exec(node.get_co(), self.__exec_globl, l)
-            self.pop_executing_node()
-        except Exception as e:
-            raise ConfigLoadException("failed to load", node, e)
-        
-        self.__exec_globl.update(l)
-
-    def lookup_node(self, name):
-        if name not in self.__node_table:
-            raise ConfigLoadException(f"node '{name}' undefined")
-        return self.__node_table[name]
-
-    def type_factory(self):
-        return self.__type_fatry
-    
-    def update_value(self, key, value):
-        self.__config_val[key] = value
-
-    def get_symbol(self, name):
-        if name not in self.__exec_globl:
-            raise ConfigLoadException(f"unknown symbol '{name}'")
-        return self.__exec_globl[name]
-
-    def callframe_at(self, traverse_level):
-        try:
-            return self.__eval_stack[traverse_level - 1]
-        except:
-            return None
-    
-    def lookup_value(self, key):
-        return self.__config_val[key]
-    
-    def resolve_symbol(self, sym):
-        term_node = self.__node_table[sym]
-        if isinstance(term_node, LCTermNode):
-            if not term_node.is_ready():
-                term_node.evaluate()
-            return term_node.get_value()
-        raise Exception(f"fail to resolve symbol: {sym}, not resolvable")
-        
-    def dependency(self):
-        return self.__deps_graph
-    
-    def update(self):
-        for mod in self.__lc_modules:
-            mod.evaluate()
-    
-    def export(self):
-        self.__config_io.export(self, self.__config_val)
-    
-    def save(self, _path = ".config.json"):
-        data = {}
-        for mod in self.__lc_modules:
-            mod.serialise(data)
-
-        with open(_path, 'w') as f:
-            json.dump(data, f)
-
-    def load(self, _path = ".config.json"):
-        if not path.exists(_path):
-            return
-        
-        data = {}
-        with open(_path, 'r') as f:
-            data.update(json.load(f))
-        
-        for mod in self.__lc_modules:
-            mod.deserialise(data)
-
-        self.update()
-
-    def render(self, rctx):
-        for mod in self.__lc_modules:
-            mod.render(rctx)
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/lcfg/lcnodes.py b/lunaix-os/scripts/build-tools/lcfg/lcnodes.py
deleted file mode 100644 (file)
index 87aa20d..0000000
+++ /dev/null
@@ -1,397 +0,0 @@
-from .api import (
-    ConfigLoadException,
-    ConfigTypeCheckError,
-    Renderable
-)
-
-from .utils import (
-    extract_decorators, 
-    to_displayable
-)
-
-import ast, textwrap
-from abc import abstractmethod
-
-
-
-class LCNode(Renderable):
-    def __init__(self, fo, astn):
-        super().__init__()
-
-        self._fo = fo
-        self._env = fo.env()
-        self._astn = self.prune_astn(astn)
-        self._co = self.compile_astn()
-        self._parent = None
-
-        self._env.register_node(self)
-    
-    def prune_astn(self, astn):
-        return astn
-
-    def compile_astn(self):
-        return self._fo.compile_astns(self._astn)
-
-    def get_co(self):
-        return self._co
-    
-    def get_fo(self):
-        return self._fo
-    
-    def set_parent(self, new_parent):
-        self._parent = new_parent
-
-    def parent(self):
-        return self._parent
-    
-    def add_child(self, node):
-        pass
-
-    def remove_child(self, node):
-        pass
-    
-    def get_name(self):
-        return f"LCNode: {self.__hash__()}"
-    
-    @abstractmethod
-    def evaluate(self):
-        pass
-
-    def render(self, rctx):
-        pass
-
-    def deserialise(self, dict):
-        pass
-
-    def serialise(self, dict):
-        pass
-
-
-
-class LCModuleNode(LCNode):
-    def __init__(self, fo, astn: ast.Module):
-        self.__nodes = {}
-
-        super().__init__(fo, astn)
-
-    def get_name(self):
-        return f"file: {self._fo.filename()}"
-
-    def prune_astn(self, astn: ast.Module):
-        general_exprs = []
-
-        for b in astn.body:
-            if not isinstance(b, ast.FunctionDef):
-                general_exprs.append(b)
-                continue
-            
-            node = LCFuncNode.construct(self._fo, b)
-            node.set_parent(self)
-
-            self.add_child(node)
-
-        return general_exprs
-    
-    def evaluate(self):
-        with self._env.eval_context(self) as evalc:
-            ls = list(self.__nodes.values())          
-            for node in ls:
-                node.evaluate()
-
-    def add_child(self, node):
-        self.__nodes[node] = node
-
-    def remove_child(self, node):
-        if node in self.__nodes:
-            del self.__nodes[node]
-
-    def deserialise(self, dict):
-        for node in self.__nodes:
-            node.deserialise(dict)
-
-    def serialise(self, dict):
-        for node in self.__nodes:
-            node.serialise(dict)
-
-    def render(self, rctx):
-        for node in self.__nodes:
-            node.render(rctx)
-
-class LCFuncNode(LCNode):
-    def __init__(self, fo, astn) -> None:
-        self._decors = {}
-        self._name = None
-        self._help = ""
-        self._display_name = None
-        self._enabled = True
-
-        super().__init__(fo, astn)
-
-    def prune_astn(self, astn: ast.FunctionDef):
-        self._name = astn.name
-        self._display_name = to_displayable(self._name)
-        
-        maybe_doc = astn.body[0]
-        if isinstance(maybe_doc, ast.Expr):
-            if isinstance(maybe_doc.value, ast.Constant):
-                self._help = maybe_doc.value.value
-                self._help = textwrap.dedent(self._help)
-        
-        decors = extract_decorators(astn)
-        for name, args, kwargs in decors:
-            self._decors[name] = (args, kwargs)
-
-        (args, _) = self._decors[self.mapped_name()]
-        if args:
-            self._display_name = args[0]
-        
-        astn.decorator_list.clear()
-        return astn
-    
-    def get_name(self):
-        return self._name
-    
-    def get_display_name(self):
-        return self._display_name
-    
-    def enabled(self):
-        if isinstance(self._parent, LCFuncNode):
-            return self._enabled and self._parent.enabled()
-        return self._enabled
-
-    def help_prompt(self):
-        return self._help
-    
-    def evaluate(self):
-        with self._env.eval_context(self) as evalc:
-            result = evalc.evaluate()
-            self._enabled = True if result is None else result
-            
-    @staticmethod
-    def mapped_name(self):
-        return None
-    
-    @staticmethod
-    def construct(fo, astn: ast.FunctionDef):
-        nodes = [
-            LCCollectionNode,
-            LCGroupNode,
-            LCTermNode
-        ]
-
-        for node in nodes:
-            if extract_decorators(astn, node.mapped_name(), True):
-                return node(fo, astn)
-        
-        raise ConfigLoadException(
-                f"unknown type for astn type: {type(astn)}")
-
-    def set_parent(self, new_parent):
-        if self._parent:
-            self._parent.remove_child(self)
-        
-        new_parent.add_child(self)
-        super().set_parent(new_parent)
-
-
-class LCTermNode(LCFuncNode):
-    def __init__(self, fo, astn) -> None:
-        self._value = None
-        self._default = None
-        self._type = None
-        self._rdonly = False
-        self._ready = False
-
-        super().__init__(fo, astn)
-
-    @staticmethod
-    def mapped_name():
-        return "Term"
-    
-    def prune_astn(self, astn: ast.FunctionDef):
-        astn = super().prune_astn(astn)
-
-        self._rdonly = "ReadOnly" in self._decors
-
-        return astn
-
-    def set_type(self, type_def):
-        self._type = self._env.type_factory().create(type_def)
-
-    def get_type(self):
-        return self._type
-
-    def __assert_type(self, val):
-        if not self._type:
-            raise ConfigLoadException(
-                    f"config: {self._name} must be typed", self)
-        
-        if self._type.check(val):
-           return
-
-        raise ConfigTypeCheckError(
-                f"value: {val} does not match type {self._type}", self)
-
-    def set_value(self, val):
-        if self._rdonly:
-            return
-        
-        if isinstance(val, str):
-            val = self._type.parse_input(val)
-        
-        self.__assert_type(val)
-        self._value = val
-        
-        self._ready = True
-        self.__update_value()
-        self._env.dependency().cascade(self)
-        
-
-    def set_default(self, val):
-        self.__assert_type(val)
-        self._default = val
-
-        if self._rdonly:
-            self._value = val
-
-    def get_value(self):
-        return self._value
-    
-    def is_ready(self):
-        return self._ready
-    
-    def evaluate(self):
-        super().evaluate()
-        self.__update_value()
-
-    def read_only(self):
-        return self._rdonly
-
-    def render(self, rctx):
-        if self.enabled():
-            rctx.add_field(self._display_name, self)
-
-    def serialise(self, dict):
-        s_val = self._type.serialise(self._value)
-        dict[self._name] = s_val
-
-    def deserialise(self, dict):
-        if self._name not in dict:
-            return
-        
-        s_val = dict[self._name]
-        v = self._type.deserialise(s_val)
-        self.__assert_type(v)
-        self._value = v
-
-
-    def __update_value(self):
-        v = self._value
-
-        if not self.enabled():
-            self._env.update_value(self._name, None)
-            return
-
-        if v is None:
-            v = self._default
-
-        if v is None and not self._type.allow_none():
-            raise ConfigLoadException(
-                    f"config: {self._name} must have a value", self)
-        
-        self._value = v
-        self._env.update_value(self._name, v)
-    
-
-
-
-class LCGroupNode(LCFuncNode):
-    def __init__(self, fo, astn) -> None:
-        self._children = {}
-
-        super().__init__(fo, astn)
-
-    @staticmethod
-    def mapped_name():
-        return "Group"
-
-    def prune_astn(self, astn: ast.FunctionDef):
-        astn = super().prune_astn(astn)
-
-        other_exprs = []
-        for expr in astn.body:
-            if not isinstance(expr, ast.FunctionDef):
-                other_exprs.append(expr)
-                continue
-
-            node = LCFuncNode.construct(self._fo, expr)
-            node.set_parent(self)
-            self._children[node] = node
-
-        if not other_exprs:
-            other_exprs.append(
-                ast.Pass(
-                    lineno=astn.lineno + 1, 
-                    col_offset=astn.col_offset
-                )
-            )
-
-        astn.body = other_exprs
-        return astn
-
-    def evaluate(self):
-        old_enable = super().enabled()
-        super().evaluate()
-
-        new_enabled = super().enabled()
-        if not new_enabled and old_enable == new_enabled:
-            return
-
-        with self._env.eval_context(self) as evalc:
-            children = list(self._children.values())
-            for child in children:
-                child.evaluate()
-        
-    def render(self, rctx):
-        for child in self._children.values():
-            child.render(rctx)
-
-    def serialise(self, dict):
-        sub_dict = {}
-        for child in self._children.values():
-            child.serialise(sub_dict)
-        
-        dict[self._name] = sub_dict
-
-    def deserialise(self, dict):
-        if self._name not in dict:
-            return
-        
-        sub_dict = dict[self._name]
-        for child in self._children.values():
-            child.deserialise(sub_dict)
-    
-    def add_child(self, node):
-        self._children[node] = node
-
-    def remove_child(self, node):
-        if node in self._children:
-            del self._children[node]
-
-
-class LCCollectionNode(LCGroupNode):
-    def __init__(self, fo, astn) -> None:
-        super().__init__(fo, astn)
-
-    @staticmethod
-    def mapped_name():
-        return "Collection"
-    
-    def render(self, rctx):
-        _super = super()
-        rctx.add_expandable(
-            self._display_name,
-            self, 
-            lambda _ctx: 
-                _super.render(_ctx)
-        )
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/lcfg/types.py b/lunaix-os/scripts/build-tools/lcfg/types.py
deleted file mode 100644 (file)
index 8fc373d..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-from .api import TypeProviderBase
-from .utils import is_primitive, is_basic
-
-class PrimitiveType(TypeProviderBase):
-    def __init__(self, type) -> None:
-        super().__init__(type)
-
-    @staticmethod
-    def typedef_matched(typedef):
-        return is_primitive(typedef)
-    
-    def check(self, val):
-        return isinstance(val, self._type)
-    
-    def serialise(self, val):
-        return str(val)
-    
-    def deserialise(self, val):
-        if val.lower() == "true":
-            return True
-        elif val.lower() == "false":
-            return False
-        
-        return self._type(val)
-    
-    def parse_input(self, input_str):
-        return self.deserialise(input_str)
-    
-    def to_input(self, val):
-        return self.serialise(val)
-    
-    def __str__(self) -> str:
-        if isinstance(self._type, type):
-            return f"any with type of {self._type}"
-        return f"exact of value '{self._type}'"
-
-
-class MultipleChoiceType(PrimitiveType):
-    def __init__(self, type) -> None:
-        super().__init__(type)
-
-    @staticmethod
-    def typedef_matched(typedef):
-        if not isinstance(typedef, list):
-            return False
-        return all([is_basic(x) for x in typedef])
-
-    def check(self, val):
-        if not is_basic(val):
-            return False
-        return val in self._type
-    
-    def parse_input(self, input_str):
-        return super().parse_input(input_str)
-
-    def deserialise(self, val):
-        if val.lower() == "true":
-            return True
-        elif val.lower() == "false":
-            return False
-        
-        for sv in self._type:
-            if val != str(sv):
-                continue
-            return type(sv)(val)
-
-        return None
-    
-    def allow_none(self):
-        return None in self._type
-    
-    def __str__(self) -> str:
-        accepted = [f"  * {t}" for t in self._type]
-        return "\n".join([
-            "choose one:",
-            *accepted
-        ])
diff --git a/lunaix-os/scripts/build-tools/lcfg/utils.py b/lunaix-os/scripts/build-tools/lcfg/utils.py
deleted file mode 100644 (file)
index 96e9e2b..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-import ast
-
-def extract_decorators(fn_ast: ast.FunctionDef, fname = None, only_name=False):
-    decors = fn_ast.decorator_list
-    results = []
-    for decor in decors:
-        _args = []
-        _kwargs = []
-        if isinstance(decor, ast.Name):
-            name = decor.id
-        elif isinstance(decor, ast.Call):
-            if not isinstance(decor.func, ast.Name):
-                continue
-            name = decor.func.id
-            _args = decor.args
-            _kwargs = decor.keywords
-        else:
-            continue
-
-        if fname and name != fname:
-            continue
-
-        if only_name:
-            results.append(name)
-            continue
-        
-        unpacked = []
-        kwargs = {}
-        for arg in _args:
-            if isinstance(arg, ast.Constant):
-                unpacked.append(arg.value)
-        
-        for kwarg in _kwargs:
-            if isinstance(kwarg.value, ast.Constant):
-                kwargs[kwarg.arg] = kwarg.value.value
-        
-        results.append((name, unpacked, kwargs))
-
-    return results
-
-def to_displayable(name):
-    l = name.strip().split('_')
-    for i, s in enumerate(l):
-        l[i] = str.upper(s[0]) + s[1:]
-    return " ".join(l)
-
-def is_primitive(val):
-    return val in [int, str, bool]
-
-def is_basic(val):
-    basic = [int, str, bool]
-    return (
-        val in basic or
-        any([isinstance(val, x) for x in basic])
-    )
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/lcfg2/builder.py b/lunaix-os/scripts/build-tools/lcfg2/builder.py
new file mode 100644 (file)
index 0000000..83a49c2
--- /dev/null
@@ -0,0 +1,109 @@
+import ast
+
+from lib.utils  import ConfigAST, ConfigASTVisitor
+from .common     import NodeProperty, ConfigNodeError, ValueTypeConstrain
+from .nodes      import GroupNode, TermNode
+from .sanitiser  import TreeSanitiser
+
+class NodeBuilder(ConfigASTVisitor):
+    def __init__(self, env):
+        super().__init__()
+
+        self.__env = env
+        self.__level = []
+        self.__noncfg_astns = []
+
+    def __pop_and_merge(self):
+        if len(self.__level) == 0:
+            return
+        
+        if len(self.__level) == 1:
+            self.__level.pop()
+            return
+        
+        node = self.__level.pop()
+        prev = self.__level[-1]
+
+        assert isinstance(prev, GroupNode)
+
+        prev.add_child(node)
+
+    def __push(self, cfg_node):
+        self.__level.append(cfg_node)
+
+    def __check_literal_expr(self, node):
+        return (
+            isinstance(node, ast.Expr) 
+            and isinstance(node.value, ast.Constant) 
+            and isinstance(node.value.value, str)
+        )
+
+    def _visit_fndef(self, node):
+        if hasattr(node, "__builtin"):
+            self.__noncfg_astns.append(node)
+            return
+        
+        cfgn_type = TermNode
+        if not node.returns:
+            cfgn_type = GroupNode
+
+        cfgn = cfgn_type(self.__env, self.current_file(), node.name)
+        
+        self.__push(cfgn)
+
+        try:
+            for decor in node.decorator_list:
+                cfgn.apply_decorator(decor)
+            
+            astns = []
+            help_text = ""
+            for sub in node.body:
+                if isinstance(sub, ast.FunctionDef):
+                    self._visit_fndef(sub)
+                    continue
+
+                if self.__check_literal_expr(sub):
+                    help_text += sub.value.value
+                    continue
+
+                astns.append(sub)
+
+            NodeProperty.Token[cfgn] = node
+            NodeProperty.HelpText[cfgn] = help_text
+            
+            if cfgn_type is TermNode:
+                NodeProperty.Type[cfgn] = ValueTypeConstrain(cfgn, node.returns)
+            
+            astns.append(ast.Pass())
+            cfgn.set_node_body(astns)
+            
+            self.__env.register_node(cfgn)
+        
+        except ConfigNodeError as e:
+            raise e
+        except Exception as e:
+            msg = e.args[0] if len(e.args) > 0 else type(e).__name__
+            raise cfgn.config_error(msg)
+
+        self.__pop_and_merge()
+
+    def visit(self, node):
+        super().visit(node)
+
+        if isinstance(node, ast.Module):
+            return
+
+        if not isinstance(node, ast.FunctionDef):
+            self.__noncfg_astns.append(node)
+
+    @staticmethod
+    def build(env, rootfile):
+        build = NodeBuilder(env)
+        ast = ConfigAST(rootfile)
+        
+        env.reset()
+        ast.visit(TreeSanitiser())
+        ast.visit(build)
+
+        env.set_exec_context(build.__noncfg_astns)
+        env.relocate_children()
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/lcfg2/common.py b/lunaix-os/scripts/build-tools/lcfg2/common.py
new file mode 100644 (file)
index 0000000..94cdcea
--- /dev/null
@@ -0,0 +1,135 @@
+import ast
+
+from lib.utils  import SourceLogger, Schema
+
+class NodeProperty:
+    class PropertyAccessor:
+        def __init__(self, key):
+            self.__key = key
+        def __getitem__(self, node):
+            return node.get_property(self.__key)
+        def __setitem__(self, node, value):
+            node.set_property(self.__key, value)
+        def __delitem__(self, node):
+            node.set_property(self.__key, None)
+
+    Token       = PropertyAccessor("$token")
+    Value       = PropertyAccessor("$value")
+    Type        = PropertyAccessor("$type")
+    Enabled     = PropertyAccessor("$enabled")
+    Status      = PropertyAccessor("$status")
+    Dependency  = PropertyAccessor("$depends")
+    Hidden      = PropertyAccessor("hidden")
+    Parent      = PropertyAccessor("parent")
+    Label       = PropertyAccessor("label")
+    Readonly    = PropertyAccessor("readonly")
+    HelpText    = PropertyAccessor("help")
+
+class ConfigNodeError(Exception):
+    def __init__(self, node, *args):
+        super().__init__(*args)
+
+        self.__node = node
+        self.__msg = " ".join([str(x) for x in args])
+
+    def __str__(self):
+        node = self.__node
+        tok: ast.stmt = NodeProperty.Token[node]
+        return (
+            f"{node._filename}:{tok.lineno}:{tok.col_offset}:" +
+            f" fatal error: {node._name}: {self.__msg}"
+        )
+
+
+class ValueTypeConstrain:
+    TypeMap = {
+        "str": str,
+        "int": int,
+        "bool": bool,
+    }
+
+    BinOpOr = Schema(ast.BinOp, op=ast.BitOr)
+
+    def __init__(self, node, rettype):
+        self.__node = node
+        self.__raw = rettype
+        
+        if isinstance(rettype, ast.Expr):
+            value = rettype.value
+        else:
+            value = rettype
+        
+        if isinstance(value, ast.Constant):
+            self.schema = Schema(value.value)
+        
+        elif isinstance(value, ast.Name):
+            self.schema = Schema(self.__parse_type(value.id))
+        
+        elif isinstance(value, ast.BinOp):
+            unions = self.__parse_binop(value)
+            self.schema = Schema(Schema.Union(*unions))
+        
+        else:
+            raise Exception(
+                f"unsupported type definition: {ast.unparse(rettype)}")
+
+    def __parse_type(self, type):
+        if type not in ValueTypeConstrain.TypeMap:
+            raise Exception(f"unknown type: {type}")
+        
+        return ValueTypeConstrain.TypeMap[type]
+    
+    def __parse_binop(self, oproot):
+        if isinstance(oproot, ast.Constant):
+            return [oproot.value]
+        
+        if ValueTypeConstrain.BinOpOr != oproot:
+            SourceLogger.warn(
+                self.__node, self.__raw, 
+                "only OR is allowed. Ignoring...")
+            return []
+        
+        return self.__parse_binop(oproot.left) \
+             + self.__parse_binop(oproot.right)
+    
+    def check_type(self, value):
+        return self.schema.match(value)
+    
+    def ensure_type(self, node, val):
+        if self.check_type(val):
+           return
+
+        raise node.config_error(
+                f"unmatched type:",
+                f"expect: '{self.schema}',",
+                f"got: '{val}' ({type(val)})") 
+
+class NodeDependency:
+    class SimpleWalker(ast.NodeVisitor):
+        def __init__(self, deps):
+            super().__init__()
+            self.__deps = deps
+        
+        def visit_Name(self, node):
+            self.__deps._names.append(node.id)
+    
+    def __init__(self, nodes, expr: ast.expr):
+        self._names = []
+        self._expr  = ast.unparse(expr)
+
+        expr = ast.fix_missing_locations(expr)
+        self.__exec  = compile(ast.Expression(expr), "", mode='eval')
+
+        NodeDependency.SimpleWalker(self).visit(expr)
+
+    def evaluate(self, value_tables) -> bool:        
+        return eval(self.__exec, value_tables)
+    
+    @staticmethod
+    def try_create(node):
+        expr = NodeProperty.Dependency[node]
+        if not isinstance(expr, ast.expr):
+            return
+        
+        dep = NodeDependency(node, expr)
+        NodeProperty.Dependency[node] = dep
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/lcfg2/config.py b/lunaix-os/scripts/build-tools/lcfg2/config.py
new file mode 100644 (file)
index 0000000..cef5559
--- /dev/null
@@ -0,0 +1,114 @@
+import ast, os
+
+from lib.utils import ConfigAST
+from .common import NodeProperty, NodeDependency
+from .nodes import GroupNode, TermNode
+
+
+class ConfigEnvironment:
+    def __init__(self):
+        self.__node_table = {}
+        self.__exec = None
+        self.__globals = {}
+        self.__custom_fn = {
+            "env": lambda x: os.environ.get(x)
+        }
+    
+    def check_dependency(self, node) -> bool:
+        dep = NodeProperty.Dependency[node]
+        
+        if not isinstance(dep, NodeDependency):
+            return node.enabled()
+        
+        value_map = {}
+        for name in dep._names:
+            if name not in self.__node_table:
+                raise node.config_error(f"config: '{name}' does not exists")
+            
+            n = self.__node_table[name]
+            value_map[name] = self.check_dependency(n)
+
+        return node.enabled() and dep.evaluate(value_map)
+    
+    def register_node(self, cfg_node):
+        name = cfg_node._name
+        if name in self.__node_table:
+            raise cfg_node.config_error(f"redefinition of '{name}'")
+        
+        self.__node_table[name] = cfg_node
+    
+    def get_node(self, name):
+        return self.__node_table.get(name)
+    
+    def relocate_children(self):
+        for node in self.__node_table.values():
+            parent = NodeProperty.Parent[node]
+            if not parent:
+                continue
+
+            if not isinstance(parent, str):
+                continue
+            
+            if parent not in self.__node_table:
+                raise Exception(node, "unknow parent: %s"%(parent))
+            
+            parent_node = self.__node_table[parent]
+            if not isinstance(parent_node, GroupNode):
+                raise Exception(node, "not a valid parent: %s"%(parent))
+            
+            parent_node.add_child(node)
+
+    def set_exec_context(self, astns):
+        filtered = []
+        for x in astns:
+            if isinstance(x, ConfigAST.EnterFileMarker):
+                continue
+            if isinstance(x, ConfigAST.LeaveFileMarker):
+                continue
+            filtered.append(x)
+
+        module = ast.Module(filtered, type_ignores=[])
+        module = ast.fix_missing_locations(module)
+
+        self.__exec = compile(module, "", mode='exec')
+
+    def __update_globals(self):
+        g = {}
+
+        exec(self.__exec, g)
+        self.__globals = {
+            **g,
+            **self.__custom_fn
+        }
+
+    def context(self):
+        return self.__globals
+        
+    def refresh(self):
+        self.__update_globals()
+
+        for name, node in self.__node_table.items():
+            node.update()
+
+        for name, node in self.__node_table.items():
+            node.sanity_check()
+            NodeProperty.Enabled[node] = self.check_dependency(node)
+
+    def reset(self):
+        self.__node_table.clear()
+
+    def nodes(self):
+        for node in self.__node_table.values():
+            yield node
+
+    def top_levels(self):
+        for node in self.__node_table.values():
+            if NodeProperty.Parent[node] is None:
+                yield node
+
+    def terms(self):
+        for node in self.__node_table.values():
+            if isinstance(node, TermNode):
+                yield node
+
+import json
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/lcfg2/lazy.py b/lunaix-os/scripts/build-tools/lcfg2/lazy.py
new file mode 100644 (file)
index 0000000..ddc02ed
--- /dev/null
@@ -0,0 +1,96 @@
+import ast
+
+from .common import NodeProperty
+from lib.utils import Schema, SourceLogger
+
+class LazyLookup:
+    def __init__(self):
+        self.__tab = {}
+    
+    def put(self, lazy):
+        self.__tab[lazy.get_key()] = lazy
+
+    def get(self, key):
+        return self.__tab[key] if key in self.__tab else None
+    
+    def __setitem__(self, key, val):
+        lz = self.__tab[key]
+        lz.resolve_set(val)
+
+    def __getitem__(self, key):
+        lz = self.__tab[key]
+        return lz.resolve_get()
+
+class Lazy:
+    NodeValue     = 'val'
+
+    LazyTypes = Schema.Union(NodeValue)
+    Syntax  = Schema(ast.Attribute, attr=LazyTypes, value=ast.Name)
+
+    def __init__(self, source, type, target, env):
+        self.target = target
+        self.type   = type
+        self.env    = env
+        self.source = source
+
+    def __resolve_type(self):
+        if self.type == Lazy.NodeValue:
+            return NodeProperty.Value
+        return None
+        
+    def resolve_get(self):
+        node = self.env.get_node(self.target)
+
+        accessor = self.__resolve_type()
+        if not accessor:
+            return None
+        
+        status = NodeProperty.Status[node]
+        if status == "Updating":
+            tok = NodeProperty.Token[self.source]
+            SourceLogger.warn(self.source, tok, 
+                    f"cyclic dependency detected: {self.source._name} <-> {self.target}." + 
+                    f"Reusing cached value, maybe staled.")
+        else:
+            node.update()
+
+        return accessor[node] if accessor else None
+    
+    def resolve_set(self, val):
+        node = self.env.get_node(self.target)
+        accessor = self.__resolve_type()
+
+        if NodeProperty.Readonly[node]:
+            raise self.source.config_error(
+                f"{self.target} is readonly")
+
+        if accessor:
+            accessor[node] = val
+        else:
+            raise self.source.config_error(
+                f"invalid type {self.type} for {self.target}")
+
+    def get_key(self):
+        return Lazy.get_key_from(self.type, self.target)
+    
+    @staticmethod
+    def get_key_from(type, target):
+        return f"{type}${target}"
+    
+    @staticmethod
+    def from_astn(cfgnode, astn):
+        if Lazy.Syntax != astn:
+            return None
+        
+        type_ = astn.attr
+        target = astn.value.id
+        key = Lazy.get_key_from(type_, target)
+
+        lz = cfgnode._lazy_table.get(key)
+        if lz:
+            return key
+
+        lz = Lazy(cfgnode, type_, target, cfgnode._env)
+        cfgnode._lazy_table.put(lz)
+
+        return key
diff --git a/lunaix-os/scripts/build-tools/lcfg2/nodes.py b/lunaix-os/scripts/build-tools/lcfg2/nodes.py
new file mode 100644 (file)
index 0000000..703b8f9
--- /dev/null
@@ -0,0 +1,176 @@
+import ast
+
+from lib.utils  import SourceLogger, Schema
+from .common     import NodeProperty, ConfigNodeError, NodeDependency
+from .lazy       import LazyLookup
+from .rewriter   import ConfigNodeASTRewriter
+
+
+class ConfigDecorator:
+    Label       = Schema(ast.Constant)
+    Readonly    = Schema(ast.Name, id="readonly")
+    Hidden      = Schema(ast.Name, id="hidden")
+    Flag        = Schema(ast.Name, id="flag")
+    SetParent   = Schema(
+                    ast.NamedExpr, 
+                        target=Schema(ast.Name, id="parent"), 
+                        value=Schema(ast.Name))
+    SetProperty = Schema(
+                    ast.NamedExpr,
+                        target=Schema(ast.Name), 
+                        value=Schema(ast.Name))
+
+
+class ConfigNode:
+    def __init__(self, env, filename, name):
+        self.__props = {}
+        self.__exec = None
+        self._env = env
+        self._filename = filename
+        self._lazy_table = LazyLookup()
+
+        self._name = name
+        
+        NodeProperty.Enabled[self]  = True
+        NodeProperty.Hidden[self]   = False
+        NodeProperty.Readonly[self] = False
+        NodeProperty.Status[self]   = "Empty"
+
+    def set_node_body(self, ast_nodes, rewriter = ConfigNodeASTRewriter):
+        new_ast = rewriter(self).visit(ast.Module(ast_nodes))
+        NodeDependency.try_create(self)
+
+        fn_name = f"__fn_{self._name}"
+        args = ast.arguments([], [], None, [], [], None, [])
+        module = ast.Module([
+            ast.FunctionDef(fn_name, args, new_ast.body, []),
+            ast.Assign(
+                [ast.Name("_result", ctx=ast.Store())],
+                ast.Call(ast.Name(fn_name,ctx=ast.Load()), [], [])
+            )
+        ], [])
+
+        module = ast.fix_missing_locations(module)
+        self.__exec = compile(module, self._filename, mode='exec')
+
+    
+    def apply_decorator(self, decor):
+        if ConfigDecorator.Label == decor:
+            NodeProperty.Label[self] = str(decor.value)
+        
+        elif ConfigDecorator.Readonly == decor:
+            NodeProperty.Readonly[self] = True
+        
+        elif ConfigDecorator.Hidden == decor:
+            NodeProperty.Hidden[self] = True  
+        
+        elif ConfigDecorator.SetParent == decor:
+            NodeProperty.Parent[self] = decor.value.id
+
+        elif ConfigDecorator.Flag == decor:
+            NodeProperty.Hidden[self] = True
+            NodeProperty.Readonly[self] = True
+
+        elif ConfigDecorator.SetProperty == decor:
+            self.set_property(decor.target.id, decor.value.id)
+
+        else:
+            fname = self._filename
+            line  = decor.lineno
+            col   = decor.col_offset
+            msg   = f"unknown decorator: {ast.unparse(decor)}"
+            msg   = SourceLogger.fmt_warning(fname, line, col, msg)
+            print(msg)
+
+    def update(self):
+        self.update_status("Updating")
+        
+        try:
+            env = self._env
+            local = {}
+            globl = {
+                **env.context(),
+                "__lzLut__": self._lazy_table
+            }
+
+            exec(self.__exec, globl, local)
+            self.update_status("Latest")
+        
+            return local["_result"]
+        
+        except ConfigNodeError as e:
+            self.update_status("Error")
+            raise e
+        
+        except Exception as e:
+            self.update_status("Error")
+            raise self.config_error(e)
+        
+    def sanity_check(self):
+        pass
+
+    def get_property(self, key):
+        return self.__props[key] if key in self.__props else None
+    
+    def set_property(self, key, value):
+        if value is None:
+            del self.__props[key]
+        else:
+            self.__props[key] = value
+
+    def enabled(self):
+        val = NodeProperty.Value[self]
+        en  = NodeProperty.Enabled[self]
+        parent = NodeProperty.Parent[self]
+
+        if isinstance(val, bool):
+            en = en and val
+        
+        if isinstance(parent, ConfigNode):
+            en = en and parent.enabled()
+        
+        return en
+    
+    def config_error(self, *args):
+        return ConfigNodeError(self, *args)
+    
+    def update_status(self, status):
+        NodeProperty.Status[self] = status
+
+
+class GroupNode(ConfigNode):
+    def __init__(self, env, filename, name):
+        super().__init__(env, filename, name)
+        
+        self._subnodes = {}
+
+    def add_child(self, node):
+        if node._name in self._subnodes:
+            return
+        
+        NodeProperty.Parent[node] = self
+        self._subnodes[node._name] = node
+
+
+class TermNode(ConfigNode):
+    def __init__(self, env, filename, name):
+        super().__init__(env, filename, name)
+
+    def update(self):
+        result = super().update()
+        
+        if NodeProperty.Readonly[self]:
+            NodeProperty.Value[self] = result
+        
+        elif NodeProperty.Value[self] is None:
+            NodeProperty.Value[self] = result
+        
+        return result
+    
+    def sanity_check(self):
+        val = NodeProperty.Value[self]
+        value_typer = NodeProperty.Type[self]
+        value_typer.ensure_type(self, val)
+        
+        super().sanity_check()
+
diff --git a/lunaix-os/scripts/build-tools/lcfg2/rewriter.py b/lunaix-os/scripts/build-tools/lcfg2/rewriter.py
new file mode 100644 (file)
index 0000000..9f47ad3
--- /dev/null
@@ -0,0 +1,61 @@
+import ast
+
+from lib.utils  import Schema
+from .lazy       import Lazy
+from .common     import NodeProperty
+
+class ConfigNodeASTRewriter(ast.NodeTransformer):
+    Depend = Schema(
+                ast.Call, 
+                    func=Schema(ast.Name, id='require'),
+                    args=[ast.expr])
+
+    def __init__(self, cfg_node):
+        super().__init__()
+
+        self.__cfg_node = cfg_node
+
+    def __gen_accessor(self, orig):
+        key = Lazy.from_astn(self.__cfg_node, orig)
+        if not key:
+            return self.generic_visit(orig)
+        
+        return ast.Subscript(
+            value=ast.Name("__lzLut__", ctx=ast.Load()),
+            slice=ast.Constant(key),
+            ctx=orig.ctx,
+            lineno=orig.lineno,
+            col_offset=orig.col_offset
+        )
+    
+    def __gen_dependency(self, node):
+        cfgn = self.__cfg_node
+        dep_expr = NodeProperty.Dependency[cfgn]
+        if dep_expr is None:
+            NodeProperty.Dependency[cfgn] = node
+            return
+
+        if not isinstance(dep_expr, ast.expr):
+            raise cfgn.config_error(
+                f"invalid dependency state: {dep_expr}")
+        
+        dep_expr = ast.BoolOp(ast.And(), [dep_expr, node])
+        NodeProperty.Dependency[cfgn] = dep_expr
+
+    def visit_Attribute(self, node):
+        return self.__gen_accessor(node)
+    
+    def visit_Expr(self, node):
+        val = node.value
+        
+        if ConfigNodeASTRewriter.Depend != val:
+            return self.generic_visit(node)
+        
+        # Process marker functions
+        name = val.func.id
+        if name == "require":
+            self.__gen_dependency(val.args[0])
+        else:
+            return self.generic_visit(node)
+        
+        return None
diff --git a/lunaix-os/scripts/build-tools/lcfg2/sanitiser.py b/lunaix-os/scripts/build-tools/lcfg2/sanitiser.py
new file mode 100644 (file)
index 0000000..9a32971
--- /dev/null
@@ -0,0 +1,51 @@
+import ast
+
+from lib.utils import Schema, ConfigASTVisitor, SourceLogger
+from .common import NodeProperty
+
+class TreeSanitiser(ConfigASTVisitor):
+    DecoNative = Schema(ast.Name, id="native")
+    DecoName   = Schema(ast.Name)
+    DecoNameE  = Schema(ast.NamedExpr)
+    DecoCall   = Schema(ast.Call)
+    DecoConst  = Schema(ast.Constant)
+
+    def __init__(self):
+        super().__init__()
+        self.logger = SourceLogger(self)
+
+        # TODO
+        self.deco_rules = {}
+
+    def __sanitise_decorator(self, node: ast.FunctionDef):
+        deco_map = {}
+
+        for deco in node.decorator_list:
+            name = ""
+            if TreeSanitiser.DecoNative == deco:
+                setattr(node, "__builtin", True)
+                continue
+            elif TreeSanitiser.DecoCall == deco:
+                name = deco.func
+            elif TreeSanitiser.DecoConst == deco:
+                name = f"{deco.value}"
+            elif TreeSanitiser.DecoName == deco:
+                name = f"{deco.id}"
+            elif TreeSanitiser.DecoNameE == deco:
+                name = f"{ast.unparse(deco)}"
+            else:
+                self.logger.warn(deco, "invalid modifier type")
+                continue
+
+            deco_map[name] = deco
+
+        node.decorator_list = [x for x in deco_map.values()]
+        
+        for desc, rule in self.deco_rules.items():
+            if rule(deco_map):
+                continue
+            self.logger.warn(node, desc)
+
+    def _visit_fndef(self, node):
+        self.__sanitise_decorator(node)
+        super()._visit_fndef(node)
diff --git a/lunaix-os/scripts/build-tools/lib/sandbox.py b/lunaix-os/scripts/build-tools/lib/sandbox.py
deleted file mode 100644 (file)
index de28aca..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-import traceback, sys
-
-class InterpreterException(Exception):
-    pass
-
-class Sandbox:
-    def __init__(self, symbols) -> None:
-        self.__syms = globals()
-        self.__syms.update(symbols)
-
-    def execute(self, file):
-        with open(file) as f:
-            return self.executes(f.read(), file)
-    
-    def executes(self, str, file=""):
-        try:
-            local_ctx = {}
-            glb_ctx = self.__syms.copy()
-            exec(str, glb_ctx, local_ctx)
-            return local_ctx
-        except SyntaxError as err:
-            error_class = err.__class__.__name__
-            detail = err.args[0]
-            line_number = err.lineno
-        except Exception as err:
-            error_class = err.__class__.__name__
-            detail = err.args[0]
-            cl, exc, tb = sys.exc_info()
-            line_number = traceback.extract_tb(tb)[1][1]
-
-        print(f"LunaBuild failed: {error_class} at ./{file}:{line_number}, {detail}")
-        raise InterpreterException("load error")
\ No newline at end of file
index 361b78676bb092f566601207f2757c7d084951f7..0ca46957e1bf9e3b72142d4c72952371808d730e 100644 (file)
@@ -1,6 +1,233 @@
-import os
+import os, ast
+from pathlib import Path
 
 def join_path(stem, path):
     if os.path.isabs(path):
         return path
 
 def join_path(stem, path):
     if os.path.isabs(path):
         return path
-    return os.path.join(stem, path)
\ No newline at end of file
+    return os.path.join(stem, path)
+
+
+class Schema:
+    class Empty:
+        def __init__(self):
+            pass
+
+    class Union:
+        def __init__(self, *args):
+            self.union = [*args]
+
+        def __str__(self):
+            strs = [Schema.get_type_str(t) for t in self.union]
+            return f"{' | '.join(strs)}"
+
+    def __init__(self, type, **kwargs):
+        self.__type = type
+        self.__fields = kwargs
+
+    def __match_list(self, actual, expect):
+        if len(actual) != len(expect):
+            return False
+        
+        for a, b in zip(actual, expect):
+            if not self.__match(a, b):
+                return False
+            
+        return True
+    
+    def __match_union(self, actual, union):
+        for s in union.union:
+            if self.__match(actual, s):
+                return True
+        return False
+
+    def __match(self, val, scheme):
+        if isinstance(scheme, Schema):
+            return scheme.match(val)
+        
+        if isinstance(scheme, list) and isinstance(val, list):
+            return self.__match_list(val, scheme)
+        
+        if isinstance(scheme, Schema.Union):
+            return self.__match_union(val, scheme)
+        
+        if not isinstance(scheme, type):
+            return scheme == val
+        
+        return isinstance(val, scheme)
+    
+    def match(self, instance):
+        if not self.__match(instance, self.__type):
+            return False
+
+        for field, t in self.__fields.items():
+            if not hasattr(instance, field):
+                if isinstance(t, Schema.Empty):
+                    continue
+                return False
+            
+            field_val = getattr(instance, field)
+
+            if not self.__match(field_val, t):
+                return False
+
+        return True
+
+    def __eq__(self, value):
+        if isinstance(value, Schema):
+            return super().__eq__(value)
+        return self.match(value)
+    
+    def __ne__(self, value):
+        return not self.__eq__(value)
+    
+    def __str__(self):
+        fields = ""
+        if len(self.__fields) > 0:
+            fields = ", ".join([
+                f"{name} :: {Schema.get_type_str(t)}" 
+                for name, t in self.__fields.items()])
+            fields = "{%s}"%(fields)
+        
+        return f"{Schema.get_type_str(self.__type)} {fields}"
+    
+    def __repr__(self):
+        return self.__str__()
+    
+    @staticmethod
+    def get_type_str(maybe_type):
+        if isinstance(maybe_type, type):
+            return maybe_type.__name__
+        if isinstance(maybe_type, str):
+            return f'"{maybe_type}"'
+        return str(maybe_type)
+
+class SourceLogger:
+    def __init__(self, visitor):
+        self.__visitor = visitor
+
+    def warn(self, node, fmt, *args):
+        fname = self.__visitor.current_file()
+        line  = node.lineno
+        coln  = node.col_offset
+
+        print(SourceLogger.fmt_warning(fname, line, coln, fmt%args))
+
+    @staticmethod
+    def fmt_message(fname, line, col, level, msg):
+        return "%s:%d:%d: %s: %s"%(fname, line, col, level, msg)
+
+    @staticmethod
+    def fmt_warning(fname, line, col, msg):
+        return SourceLogger.fmt_message(fname, line, col, "warning", msg)
+    
+    @staticmethod
+    def fmt_info(fname, line, col, msg):
+        return SourceLogger.fmt_message(fname, line, col, "info", msg)
+    
+    @staticmethod
+    def fmt_error(fname, line, col, msg):
+        return SourceLogger.fmt_message(fname, line, col, "error", msg)
+    
+    @staticmethod
+    def log(level, node, token, msg):
+        fname = node._filename
+        line  = token.lineno
+        col   = token.col_offset
+        
+        print( SourceLogger.fmt_message(fname, line, col, level, msg))
+        print(f"        at... {ast.unparse(token)}")
+    
+    @staticmethod
+    def warn(node, token, msg):
+        return SourceLogger.log("warning", node, token, msg)
+    
+class ConfigAST:
+    ConfigImport = Schema(ast.ImportFrom, level=1)
+
+    class EnterFileMarker:
+        def __init__(self, filename = None):
+            self.name = filename
+
+    class LeaveFileMarker:
+        def __init__(self):
+            pass
+
+    def __init__(self, root_file, cfg_name = "LConfig"):
+        self.__tree = ast.Module([])
+        self.__cfg_name = cfg_name
+        self.__rootfile = Path(root_file)
+
+        self.__load(self.__rootfile)
+
+    def __load(self, file: Path):
+        parent = file.parent
+        
+        with file.open('r') as f:
+            nodes = ast.parse(f.read()).body
+        
+        relfile = str(file.relative_to(self.__rootfile.parent))
+        marker  = ConfigAST.EnterFileMarker(relfile)
+        self.__append_tree(marker)
+        
+        for node in nodes:
+            if ConfigAST.ConfigImport != node:
+                self.__append_tree(node)
+                continue
+            
+            module = node.module
+            module = "" if not module else module
+            path = parent.joinpath(*module.split('.'))
+            for alia in node.names:
+                p = path / alia.name / self.__cfg_name
+                self.__load(p)
+        
+        self.__append_tree(ConfigAST.LeaveFileMarker())
+    
+    def __append_tree(self, node):
+        self.__tree.body.append(node)    
+
+    def visit(self, visitor):
+        visitor.visit(self.__tree)
+
+class ConfigASTVisitor:
+    def __init__(self):
+        self.__markers = []
+
+    def _visit_fndef(self, node : ast.FunctionDef):
+        self._visit_subtree(node)
+
+    def _visit_leaf(self, node):
+        pass
+
+    def _visit_subtree(self, node):
+        for n in node.body:
+            self.visit(n)
+
+    def _visit_expr(self, node : ast.Expr):
+        pass
+
+    def current_file(self):
+        if len(self.__markers) == 0:
+            return "<root>"
+        return self.__markers[-1].name
+
+    def visit(self, node):
+        if isinstance(node, ConfigAST.EnterFileMarker):
+            self.__markers.append(node)
+            return
+        
+        if isinstance(node, ConfigAST.LeaveFileMarker):
+            self.__markers.pop()
+            return
+
+        if isinstance(node, ast.FunctionDef):
+            self._visit_fndef(node)
+        
+        elif isinstance(node, ast.Expr):
+            self._visit_expr(node)
+        
+        elif hasattr(node, "body"):
+            self._visit_subtree(node)
+        
+        else:
+            self._visit_leaf(node)
index 6b8e8979eaad764bb8a13cc10124dc6ec57c4f4f..efe527e4ed913282b7928c617fc1636a87edcb9f 100755 (executable)
 #!/usr/bin/env python 
 #!/usr/bin/env python 
-
-from lbuild.contract import LunaBuildFile
-from lbuild.common import BuildEnvironment
-
-from lcfg.common import LConfigEnvironment
-from integration.config_io import CHeaderConfigProvider
-from integration.lbuild_bridge import LConfigProvider
-from integration.render_ishell import InteractiveShell
-from integration.build_gen import MakefileBuildGen, install_lbuild_functions
-from integration.lunamenu import menuconfig, TerminalSizeCheckFailed
-
-import lcfg.types as lcfg_type
-import lcfg.builtins as builtin
-
-from os import getcwd
-from os import mkdir
-from os.path import abspath, basename, dirname, exists
-from argparse import ArgumentParser
-from lib.utils import join_path
-
-def prepare_lconfig_env(out_dir):
-    provider = CHeaderConfigProvider(join_path(out_dir, "configs.h"))
-    env = LConfigEnvironment(getcwd(), provider)
-
-    env.register_builtin_func(builtin.v)
-    env.register_builtin_func(builtin.term_type)
-    env.register_builtin_func(builtin.parent)
-    env.register_builtin_func(builtin.default)
-    env.register_builtin_func(builtin.include)
-    env.register_builtin_func(builtin.env)
-    env.register_builtin_func(builtin.set_value)
-
-    env.type_factory().regitser(lcfg_type.PrimitiveType)
-    env.type_factory().regitser(lcfg_type.MultipleChoiceType)
-
-    return env
-
-def do_config(opt, lcfg_env):
-    redo_config = not exists(opt.config_save) or opt.force
-    if not redo_config or opt.quiet:
-        return
-
-    try:
-        clean_quit = menuconfig(lcfg_env)
-    except TerminalSizeCheckFailed as e:
-        least = e.args[0]
-        current = e.args[1]
-        print(
-            f"Your terminal size: {current} is less than minimum requirement of {least}.\n"
-            "menuconfig will not function properly, switch to prompt based.\n")
-
-        shell = InteractiveShell(lcfg_env)
-        clean_quit = shell.render_loop()
+from argparse           import ArgumentParser
+from pathlib            import Path
+
+from lbuild.build       import BuildEnvironment
+from lbuild.scope       import ScopeProvider
+from lcfg2.builder      import NodeBuilder
+from lcfg2.config       import ConfigEnvironment
+
+from shared.export      import ExportJsonFile
+from shared.export      import ExportHeaderFile
+from shared.export      import ExportMakefileRules
+from shared.export      import restore_config_value
+from shared.scopes      import ConfigScope
+from shared.build_gen   import BuildScriptGenerator
+from shared.shconfig    import shconfig
+
+class LunaBuild:
+    def __init__(self, options):
+        self.__lbuilder = BuildEnvironment()
+        self.__lconfig  = ConfigEnvironment()
+        self.__opt = options
+
+        scope = ConfigScope(self.__lconfig)
+        self.__lbuilder.register_scope(scope)
+
+        scope = ScopeProvider("src")
+        scope.file_subscope("c")
+        scope.file_subscope("h")
+        self.__lbuilder.register_scope(scope)
+
+        scope = ScopeProvider("flag")
+        scope.subscope("cc")
+        scope.subscope("ld")
+        self.__lbuilder.register_scope(scope)
+
+        self.__json  = ExportJsonFile(self.__lconfig)
+        self.__make  = ExportMakefileRules(self.__lconfig)
+        self.__headr = ExportHeaderFile(self.__lconfig)
+        self.__build = BuildScriptGenerator(self.__lbuilder)
+
+    def load(self):
+        file = self.__opt.lconfig
+        NodeBuilder.build(self.__lconfig, file)
+
+        file = self.__opt.lbuild
+        self.__lbuilder.load(file)
+
+        self.__lconfig.refresh()
+        self.__lbuilder.update()
     
     
-    if not clean_quit:
-        print("Configuration aborted. Nothing has been saved.")
-        exit(-1)
-
-def do_buildfile_gen(opts, lcfg_env):
-    root_path = abspath(opts.root)
-    ws_path = dirname(root_path)
-    root_name = basename(root_path)
-
-    mkgen = MakefileBuildGen(opts.out_dir)
-    env = BuildEnvironment(ws_path, mkgen)
-
-    install_lbuild_functions(env)
+    def restore(self):
+        save = self.__opt.save
+        if not Path(save).exists():
+            return False
+
+        restore_config_value(self.__lconfig, save)
+        return True
+
+    def save(self):
+        save = self.__opt.save
+        self.__json.export(save)
+        return True
+    
+    def generate(self):
+        outdir = Path(self.__opt.export_dir)
+        if not outdir.exists():
+            outdir.mkdir()
 
 
-    cfg_provider = LConfigProvider(lcfg_env)
-    env.set_config_provider(cfg_provider)
+        if self.__opt.gen_build:
+            self.__build.generate(outdir / "build.mkinc")
 
 
-    root = LunaBuildFile(env, root_name)
+        if self.__opt.gen_config:
+            self.__make.export(outdir / "config.mkinc")
+            self.__headr.export(outdir / "config.h")
 
 
-    try:
-        root.resolve()
-    except Exception as err:
-        print("failed to resolve root build file")
-        raise err
-    
-    env.export()
+    def visual_config(self):
+        if not shconfig(self.__lconfig):
+            print("configuration process aborted")
+            exit(1)
 
 def main():
     parser = ArgumentParser()
 
 def main():
     parser = ArgumentParser()
-    parser.add_argument("--config", action="store_true", default=False)
-    parser.add_argument("--quiet", action="store_true", default=False)
-    parser.add_argument("--lconfig-file", default="LConfig")
-    parser.add_argument("--config-save", default=".config.json")
-    parser.add_argument("--force", action="store_true", default=False)
-    parser.add_argument("root", nargs="?", default="LBuild")
-    parser.add_argument("-o", "--out-dir", required=True)
+    parser.add_argument("--lconfig", default="LConfig")
+    parser.add_argument("--lbuild", default="LBuild")
+    parser.add_argument("--save", default=".config.json")
+    parser.add_argument("--gen-build", action="store_true", default=False)
+    parser.add_argument("--gen-config", action="store_true", default=False)
+    parser.add_argument("export_dir")
 
     opts = parser.parse_args()
 
     opts = parser.parse_args()
-    out_dir = opts.out_dir
-    if not exists(out_dir):
-        mkdir(out_dir)
+    builder = LunaBuild(opts)
+
+    builder.load()
+    builder.restore()
     
     
-    lcfg_env = prepare_lconfig_env(out_dir)
-    require_config = exists(opts.lconfig_file)
-    try:
-        if require_config:
-            lcfg_env.resolve_module(opts.lconfig_file)
-            lcfg_env.update()
-            lcfg_env.load()
-    except Exception as e:
-        print(e)
+    builder.visual_config()
     
     
-    if opts.config:
-        if require_config:
-            do_config(opts, lcfg_env)
-        else:
-            print("No configuration file detected, skipping...")
-        
-        lcfg_env.update()
-        lcfg_env.save(opts.config_save)
-        lcfg_env.export()
-
-    do_buildfile_gen(opts, lcfg_env)
+    builder.save()
+    builder.generate()
+
 
 if __name__ == "__main__":
     main()
\ No newline at end of file
 
 if __name__ == "__main__":
     main()
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/shared/build_gen.py b/lunaix-os/scripts/build-tools/shared/build_gen.py
new file mode 100644 (file)
index 0000000..13f98c2
--- /dev/null
@@ -0,0 +1,77 @@
+from os.path      import isdir, exists
+from lbuild.build import BuildEnvironment
+from lbuild.scope import ScopeAccessor
+
+def gen_source_file(subscope : ScopeAccessor):
+    items = " ".join(subscope.values())
+    return [f"BUILD_SRCS := {items}"]
+
+def gen_header_file(subscope : ScopeAccessor):
+    inc, hdr = [], []
+    for x in subscope.values():
+        if not exists(x):
+            print(f"warning: '{x}' does not exists, skipped")
+            continue
+
+        if isdir(x):
+            inc.append(x)
+        else:
+            hdr.append(x)
+            
+    return [
+        f"BUILD_INC := {' '.join(inc)}",
+        f"BUILD_HDR := {' '.join(hdr)}",
+    ]
+
+def gen_ccflags(subscope : ScopeAccessor):
+    items = " ".join(subscope.values())
+    return [f"BUILD_CFLAGS := {items}"]
+
+def gen_ldflags(subscope : ScopeAccessor):
+    items = " ".join(subscope.values())
+    return [f"BUILD_LDFLAGS := {items}"]
+
+
+class BuildScriptGenerator:
+    def __init__(self, env: BuildEnvironment):
+        self.__gen_policy = {
+            "src": {
+                "c": gen_source_file,
+                "h": gen_header_file
+            },
+            "flag": {
+                "cc": gen_ccflags,
+                "ld": gen_ldflags
+            }
+        }
+
+        self.__env = env
+
+    def __gen_lines(self, scope, subscope):
+        
+        policy = self.__gen_policy.get(scope.name)
+        if policy is None:
+            print( "warning: no associated policy with"
+                  f"'{scope.name}'"
+                   ", skipped")
+            return []
+        
+        policy = policy.get(subscope.name)
+        if policy is None:
+            print( "warning: no associated policy with "
+                  f"'{scope.name}.{subscope.name}' "
+                   ", skipped")
+            return []
+        
+        return policy(subscope)
+    
+    def generate(self, file):
+        lines = []
+
+        for scope in self.__env.scopes.values():
+            for subscope in scope.accessors():
+                lines += self.__gen_lines(scope, subscope)
+
+        with open(file, 'w') as f:
+            f.write('\n'.join(lines))
+        
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/shared/export.py b/lunaix-os/scripts/build-tools/shared/export.py
new file mode 100644 (file)
index 0000000..b2c7bb0
--- /dev/null
@@ -0,0 +1,116 @@
+from lcfg2.config import ConfigEnvironment
+from lcfg2.common import NodeProperty
+from lib.utils import SourceLogger
+import json
+import re
+
+Identifier = re.compile(r"^[A-Za-z_][A-Za-z0-9_]+$")
+
+class Exporter:
+    def __init__(self, env: ConfigEnvironment):
+        self._env = env
+
+    def export(self, file):
+        lines = []
+
+        for node in self._env.terms():
+            for name, val in self.variants(node):
+                line = self._format_line(node, name, val)
+                lines.append(line)
+
+        with open(file, 'w') as f:
+            f.write('\n'.join(lines))
+
+    def _format_line(self, node, name, value):
+        return ""
+
+    def variants(self, node):
+        value    = self.get_value(node)
+        basename = f"CONFIG_{str.upper(node._name)}"
+
+        return [(basename, value)]
+    
+    def get_value(self, node):
+        return ""
+
+
+class ExportMakefileRules(Exporter):
+    def __init__(self, env: ConfigEnvironment):
+        super().__init__(env)
+
+    def _format_line(self, node, name, value):
+        if not node.enabled():
+            return f"# {name} is disabled"
+        else:
+            return f"{name} := {value}"
+
+    def get_value(self, node):
+        tok = NodeProperty.Token[node]
+        val = NodeProperty.Value[node]
+
+        if type(val) is str:
+            return val
+
+        if type(val) is int:
+            return val
+        
+        if type(val) is bool:
+            return ""
+        
+        SourceLogger.warn(node, tok, 
+            f"value: '{val}' can not mapped to makefile primitives")
+        
+        return "## ERROR ##"
+    
+class ExportHeaderFile(Exporter):
+    def __init__(self, env: ConfigEnvironment):
+        super().__init__(env)
+
+    def _format_line(self, node, name, value):
+        if not node.enabled():
+            return f"// {name} is disabled"
+        else:
+            return f"#define {name} {value}"
+
+    def get_value(self, node):
+        tok = NodeProperty.Token[node]
+        val = NodeProperty.Value[node]
+
+        if type(val) is str:
+            return f'"{val}"'
+
+        if type(val) is int:
+            return val
+        
+        if type(val) is bool:
+            return ""
+        
+        SourceLogger.warn(node, tok, 
+            f"value: '{val}' can not mapped to C primitives")
+        
+        return "// Error"
+
+class ExportJsonFile(Exporter):
+    def __init__(self, env):
+        super().__init__(env)
+
+    def export(self, file):
+        obj = {}
+        for n in self._env.terms():
+            obj[n._name] = NodeProperty.Value[n]
+        
+        with open(file, 'w') as f:
+            json.dump(obj, f)
+
+def restore_config_value(env: ConfigEnvironment, json_path):
+    with open(json_path) as f:
+        j = json.load(f)
+        for k, v in j.items():
+            node = env.get_node(k)
+            if node is None:
+                print(f"warning: missing node: '{node}', skipped")
+                continue
+
+            NodeProperty.Value[node] = v
+
+        env.refresh()
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/shared/scopes.py b/lunaix-os/scripts/build-tools/shared/scopes.py
new file mode 100644 (file)
index 0000000..a56c342
--- /dev/null
@@ -0,0 +1,14 @@
+from lbuild.scope import ScopeProvider
+from lcfg2.common import NodeProperty
+
+class ConfigScope(ScopeProvider):
+    def __init__(self, env):
+        super().__init__("config")
+        self.__env = env
+
+    def __getitem__(self, name):
+        node = self.__env.get_node(name)
+        if node is None:
+            raise Exception(f"config '{name}' not exists")
+        
+        return NodeProperty.Value[node]
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/shared/shconfig.py b/lunaix-os/scripts/build-tools/shared/shconfig.py
new file mode 100644 (file)
index 0000000..6431a18
--- /dev/null
@@ -0,0 +1,145 @@
+from lcfg2.config import ConfigEnvironment
+from lcfg2.common import NodeProperty, NodeDependency
+
+import pydoc
+import readline, textwrap
+
+class ShconfigException(Exception):
+    def __init__(self, *args):
+        super().__init__(*args)
+
+    def __str__(self):
+        return str(self.args[0])
+
+def select(val, _if, _else):
+    return _if if val else _else
+
+def get_node(env: ConfigEnvironment, name: str):
+    node_name = name.removeprefix("CONFIG_").lower()
+    node = env.get_node(node_name)
+    if node is None:
+        raise ShconfigException(f"no such config: {name}")
+    return node
+
+def show_configs(env: ConfigEnvironment):
+    aligned = 40
+    lines = [
+        "Display format:",
+        "",
+        "        (flags) CONFIG_NAME ..... VALUE",
+        " ",
+        "   (flags)",
+        "      x    Config is disabled",
+        "      r    Read-Only config",
+        "      h    Hidden config",
+        "",
+        "",
+        "Defined configuration terms",
+        ""
+    ]
+
+    for node in env.terms():
+        name    = f"CONFIG_{node._name.upper()}"
+        value   = NodeProperty.Value[node]
+        enabled = NodeProperty.Enabled[node]
+        hidden  = NodeProperty.Hidden[node]
+        ro      = NodeProperty.Readonly[node]
+
+        status  = f"{select(not enabled, 'x', '.')}"          \
+                  f"{select(ro, 'r', '.')}"          \
+                  f"{select(hidden, 'h', '.')}"      \
+                                    
+        val_txt = f"{value if value is not None else '<?>'}"
+                  
+        line = f"[{status}] {name}"
+        to_pad = max(aligned - len(line), 4)
+        lines.append(f"    {line} {'.' * to_pad} {val_txt}")    
+
+    pydoc.pager("\n".join(lines))
+
+def set_config(env: ConfigEnvironment, name: str, value):
+    node_name = name.removeprefix("CONFIG_").lower()
+    node = env.get_node(node_name)
+    if node is None:
+        raise ShconfigException(f"no such config: {name}")
+    
+    NodeProperty.Value[node] = value
+    env.refresh()
+
+def __print_dep_recursive(env, node, level = 0):
+    indent = " "*(level * 4)
+    dep: NodeDependency = NodeProperty.Dependency[node]
+
+    print(indent, f"+ {node._name} -> {NodeProperty.Value[node]}")
+    if dep is None:
+        return
+    
+    print(indent, f"= {dep._expr}")
+    for name in dep._names:
+        n = env.get_node(name)
+        __print_dep_recursive(env, n, level + 1)
+
+def show_dependency(env: ConfigEnvironment, name: str):
+    node = get_node(env, name)
+    print(node._name)
+    __print_dep_recursive(env, node)
+
+CMDS = {
+    "show": lambda env, *args: show_configs(env),
+    "dep": lambda env, name, *args: show_dependency(env, name)
+}
+
+def next_input(env: ConfigEnvironment):
+    line = input("shconfig> ")
+    parts = line.split(' ')
+
+    if len(parts) == 0:
+        return True
+    
+    if parts[0] in ['q', 'exit']:
+        return False
+    
+    if parts[0].startswith("CONFIG_"):
+        if len(parts) != 2:
+            raise ShconfigException("expect a value")
+        set_config(env, parts[0], parts[1])
+        return True
+
+    cmd = CMDS.get(parts[0])
+    if cmd is None:
+        raise ShconfigException(f"unknow command: {parts[0]}")
+    
+    cmd(env, *parts[1:])
+    return True
+
+def shconfig(env: ConfigEnvironment):
+    print(textwrap.dedent(
+        """ 
+        Lunaix shconfig
+          
+        Usage:
+            show
+                List all configuration
+
+            dep <config>
+                Examine dependency chain for <config>
+          
+            <config> <value>
+                Update value of <config> to <value>
+
+        """
+    ).strip())
+    print()
+
+    while True:
+        try:
+            if not next_input(env):
+                return True
+        except ShconfigException as e:
+            print(str(e))
+            continue
+        except KeyboardInterrupt as e:
+            return False
+        except Exception as e:
+            raise e
+            return False
index 3f44fe28f96955a57a108166f362173d9023ac6d..ad2709cdb222f8b4498732d0429e93ed3aaa36c1 100644 (file)
@@ -1,11 +1,8 @@
-@Term("Architecture")
-def arch():
+@"Architecture"
+def arch() -> "i386" | "x86_64" | "aarch64" | "rv64":
     """
         set the ISA target
     """
     """
         set the ISA target
     """
-    type(["i386", "x86_64", "aarch64", "rv64"])
-    default("i386")
 
 
-    env_val = env("ARCH")
-    if env_val is not None:
-        set_value(env_val)
\ No newline at end of file
+    _arch = env("ARCH")
+    return _arch if _arch else "x86_64"