Introducing LunaBuild to the build flow (#36)
authorLunaixsky <lunaixsky@qq.com>
Thu, 27 Jun 2024 15:11:18 +0000 (16:11 +0100)
committerGitHub <noreply@github.com>
Thu, 27 Jun 2024 15:11:18 +0000 (16:11 +0100)
* add LunaBuild for better control of source file selections

* remove out-dated template based method
* add LBuild files that follow LunaBuild methodology

* integrate LunaBuild into the building flow

* add documentation on general usage of LunaBuild
* fix `make run` does not work properly

* implement and integrate LConfig language

* implement LConfig language for modularised kernel build experience
* integrate LConfig into build flow
* clean up out-dated files

* Add interactive shell to do configuration

* file clean up

* General clean up

* move grub image stuff to scripts/grub
* remove the code format rule file - we don't need these fancy shit!

* refine the config shell experience

* allow accessing node using posix-path
* able to identify whether the configuration is aborted or exit normally

* fix up the dependencies in makefile, rebuild when needed

* make sure the configuration shell only pop up when:
  1) LConfigs changed
  2) config.h disappeared
* add proper implementation on header files change detection
* add missing doc string on some config nodes

81 files changed:
lunaix-os/.clang-format [deleted file]
lunaix-os/.gitignore
lunaix-os/.prettierignore [deleted file]
lunaix-os/.vscode/c_cpp_properties.json
lunaix-os/LBuild [new file with mode: 0644]
lunaix-os/LConfig [new file with mode: 0644]
lunaix-os/arch/LBuild [new file with mode: 0644]
lunaix-os/arch/LConfig [new file with mode: 0644]
lunaix-os/arch/i386/LBuild [new file with mode: 0644]
lunaix-os/arch/i386/LConfig [new file with mode: 0644]
lunaix-os/arch/i386/hal/LBuild [new file with mode: 0644]
lunaix-os/arch/i386/klib/fast_crc.c
lunaix-os/config-grub.sh [deleted file]
lunaix-os/config.h [deleted file]
lunaix-os/flags.h [deleted file]
lunaix-os/hal/LBuild [new file with mode: 0644]
lunaix-os/hal/acpi/LBuild [new file with mode: 0644]
lunaix-os/hal/ahci/LBuild [new file with mode: 0644]
lunaix-os/hal/bus/LBuild [new file with mode: 0644]
lunaix-os/hal/bus/pci.c [moved from lunaix-os/hal/pci.c with 100% similarity]
lunaix-os/hal/char/LBuild [new file with mode: 0644]
lunaix-os/hal/char/uart/LBuild [new file with mode: 0644]
lunaix-os/hal/gfxa/LBuild [new file with mode: 0644]
lunaix-os/hal/gfxa/vga/LBuild [new file with mode: 0644]
lunaix-os/hal/rtc/LBuild [new file with mode: 0644]
lunaix-os/hal/term/LBuild [new file with mode: 0644]
lunaix-os/hal/timer/LBuild [new file with mode: 0644]
lunaix-os/includes/lunaix/mm/pmm.h
lunaix-os/includes/lunaix/spike.h
lunaix-os/includes/sdbg/gdbstub.h
lunaix-os/kernel.mk
lunaix-os/kernel/LBuild [new file with mode: 0644]
lunaix-os/kernel/LConfig [new file with mode: 0644]
lunaix-os/kernel/block/LBuild [new file with mode: 0644]
lunaix-os/kernel/debug/LBuild [new file with mode: 0644]
lunaix-os/kernel/debug/gdbstub.c
lunaix-os/kernel/device/LBuild [new file with mode: 0644]
lunaix-os/kernel/ds/LBuild [new file with mode: 0644]
lunaix-os/kernel/exe/LBuild [new file with mode: 0644]
lunaix-os/kernel/exe/elf32/LBuild [new file with mode: 0644]
lunaix-os/kernel/fs/LBuild [new file with mode: 0644]
lunaix-os/kernel/fs/fs_export.c
lunaix-os/kernel/fs/iso9660/LBuild [new file with mode: 0644]
lunaix-os/kernel/fs/ramfs/LBuild [new file with mode: 0644]
lunaix-os/kernel/fs/twifs/LBuild [new file with mode: 0644]
lunaix-os/kernel/mm/LBuild [new file with mode: 0644]
lunaix-os/kernel/mm/LConfig [new file with mode: 0644]
lunaix-os/kernel/mm/pmalloc_simple.c
lunaix-os/kernel/process/LBuild [new file with mode: 0644]
lunaix-os/ksrc.excludes [deleted file]
lunaix-os/libs/LBuild [new file with mode: 0644]
lunaix-os/libs/klibc/string/strlen.c
lunaix-os/makefile
lunaix-os/scripts/build-tools/README.lbuild.md [new file with mode: 0644]
lunaix-os/scripts/build-tools/integration/__init__.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/integration/config_io.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/integration/lbuild_bridge.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/integration/render_ishell.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lbuild/__init__.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lbuild/api.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lbuild/common.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lbuild/contract.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lcfg/__init__.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lcfg/api.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lcfg/builtins.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lcfg/common.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lcfg/lcnodes.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lcfg/types.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lcfg/utils.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lib/__init__.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lib/sandbox.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/lib/utils.py [new file with mode: 0644]
lunaix-os/scripts/build-tools/luna_build.py [new file with mode: 0755]
lunaix-os/scripts/expand.py [deleted file]
lunaix-os/scripts/grub/GRUB_TEMPLATE [moved from lunaix-os/GRUB_TEMPLATE with 100% similarity]
lunaix-os/scripts/grub/config-grub.sh [new file with mode: 0755]
lunaix-os/scripts/templates/i386/config.json [deleted file]
lunaix-os/scripts/templates/i386/i386_intrhnds.S.j2 [deleted file]
lunaix-os/scripts/templates/i386/i386_isrdef.c.j2 [deleted file]
lunaix-os/scripts/templates/i386/mappings [deleted file]
lunaix-os/scripts/templates/i386/mempart.h.j2 [deleted file]

diff --git a/lunaix-os/.clang-format b/lunaix-os/.clang-format
deleted file mode 100644 (file)
index 240dd9a..0000000
+++ /dev/null
@@ -1,142 +0,0 @@
-# # ---
-# # Language: Cpp
-# # # BasedOnStyle:  Mozilla
-# # AccessModifierOffset: -2
-# # AlignAfterOpenBracket: Align
-# AlignConsecutiveMacros: false
-# AlignConsecutiveAssignments: false
-# AlignConsecutiveDeclarations: false
-# AlignEscapedNewlines: Right
-# AlignOperands: true
-# AlignTrailingComments: true
-# # AllowAllArgumentsOnNextLine: true
-# # AllowAllConstructorInitializersOnNextLine: true
-# # AllowAllParametersOfDeclarationOnNextLine: false
-# AllowShortBlocksOnASingleLine: Never
-# # AllowShortCaseLabelsOnASingleLine: false
-# AllowShortFunctionsOnASingleLine: Inline
-# # AllowShortLambdasOnASingleLine: All
-# # AllowShortIfStatementsOnASingleLine: Never
-# # AllowShortLoopsOnASingleLine: false
-# # AlwaysBreakAfterDefinitionReturnType: TopLevel
-# AlwaysBreakAfterReturnType: TopLevel
-# # AlwaysBreakBeforeMultilineStrings: false
-# AlwaysBreakTemplateDeclarations: Yes
-# BinPackArguments: false
-# BinPackParameters: false
-# BraceWrapping:
-#   AfterCaseLabel: false
-#   AfterClass: true
-#   AfterControlStatement: false
-#   AfterEnum: true
-#   AfterFunction: true
-#   AfterNamespace: false
-#   AfterObjCDeclaration: false
-#   AfterStruct: true
-#   AfterUnion: true
-#   AfterExternBlock: true
-#   BeforeCatch: false
-#   BeforeElse: false
-#   IndentBraces: false
-#   SplitEmptyFunction: true
-#   SplitEmptyRecord: false
-#   SplitEmptyNamespace: true
-# # BreakBeforeBinaryOperators: None
-# BreakBeforeBraces: Mozilla
-# # BreakBeforeInheritanceComma: false
-# # BreakInheritanceList: BeforeComma
-# BreakBeforeTernaryOperators: true
-# # BreakConstructorInitializersBeforeComma: false
-# # BreakConstructorInitializers: BeforeComma
-# # BreakAfterJavaFieldAnnotations: false
-# BreakStringLiterals: true
-# ColumnLimit: 80
-# # CommentPragmas: "^ IWYU pragma:"
-# # CompactNamespaces: false
-# # ConstructorInitializerAllOnOneLineOrOnePerLine: false
-# # ConstructorInitializerIndentWidth: 2
-# ContinuationIndentWidth: 2
-# # Cpp11BracedListStyle: false
-# # DeriveLineEnding: true
-# # DerivePointerAlignment: false
-# # DisableFormat: false
-# # ExperimentalAutoDetectBinPacking: false
-# # FixNamespaceComments: false
-# ForEachMacros:
-#   - foreach
-#   - Q_FOREACH
-#   - BOOST_FOREACH
-# # IncludeBlocks: Preserve
-# # IncludeCategories:
-# #   - Regex: '^"(llvm|llvm-c|clang|clang-c)/'
-# #     Priority: 2
-# #     SortPriority: 0
-# #   - Regex: '^(<|"(gtest|gmock|isl|json)/)'
-# #     Priority: 3
-# #     SortPriority: 0
-# #   - Regex: ".*"
-# #     Priority: 1
-# #     SortPriority: 0
-# # IncludeIsMainRegex: "(Test)?$"
-# # IncludeIsMainSourceRegex: ""
-# IndentCaseLabels: true
-# IndentGotoLabels: true
-# IndentPPDirectives: None
-# IndentWidth: 4
-# # IndentWrappedFunctionNames: false
-# # JavaScriptQuotes: Leave
-# # JavaScriptWrapImports: true
-# KeepEmptyLinesAtTheStartOfBlocks: true
-# # MacroBlockBegin: ""
-# # MacroBlockEnd: ""
-# MaxEmptyLinesToKeep: 1
-# # NamespaceIndentation: None
-# # ObjCBinPackProtocolList: Auto
-# # ObjCBlockIndentWidth: 2
-# # ObjCSpaceAfterProperty: true
-# # ObjCSpaceBeforeProtocolList: false
-# # PenaltyBreakAssignment: 2
-# # PenaltyBreakBeforeFirstCallParameter: 19
-# # PenaltyBreakComment: 300
-# # PenaltyBreakFirstLessLess: 120
-# # PenaltyBreakString: 1000
-# # PenaltyBreakTemplateDeclaration: 10
-# # PenaltyExcessCharacter: 1000000
-# # PenaltyReturnTypeOnItsOwnLine: 200
-# PointerAlignment: Left
-# # ReflowComments: true
-# SortIncludes: true
-# # SortUsingDeclarations: true
-# SpaceAfterCStyleCast: false
-# SpaceAfterLogicalNot: false
-# SpaceAfterTemplateKeyword: false
-# SpaceBeforeAssignmentOperators: true
-# SpaceBeforeCpp11BracedList: false
-# SpaceBeforeCtorInitializerColon: true
-# SpaceBeforeInheritanceColon: true
-# SpaceBeforeParens: ControlStatements
-# SpaceBeforeRangeBasedForLoopColon: true
-# SpaceInEmptyBlock: true
-# SpaceInEmptyParentheses: false
-# SpacesBeforeTrailingComments: 1
-# SpacesInAngles: false
-# SpacesInConditionalStatement: false
-# SpacesInContainerLiterals: true
-# SpacesInCStyleCastParentheses: false
-# SpacesInParentheses: false
-# SpacesInSquareBrackets: false
-# SpaceBeforeSquareBrackets: false
-# Standard: Latest
-# StatementMacros:
-#   - Q_UNUSED
-#   - QT_REQUIRE_VERSION
-# TypenameMacros:
-#   - optimize
-#   - noret
-#   - weak
-#   - weak_alias
-# # TabWidth: 8
-# UseCRLF: false
-# UseTab: Never
-# # ---
-
index 438c74f7ac7ded251dd7b65d1f7dcfa5cd6f1f86..eb55d3df07053e9d27f8d066d73f0e87d0d04415 100644 (file)
@@ -11,6 +11,9 @@ iso_inspect/
 unused/
 __pycache__/
 
+.builder/
+.config.json
+
 .gdb_history
 
 **.o
diff --git a/lunaix-os/.prettierignore b/lunaix-os/.prettierignore
deleted file mode 100644 (file)
index b2eb281..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*.S.inc
\ No newline at end of file
index d3390d0f1417aade0dc079548d6e5f76674f73b9..a0c612f030d2a9cbec741841cb814332b5f5e1d9 100644 (file)
@@ -12,8 +12,7 @@
                 "-ffreestanding",
                 "-D__ARCH__=i386",
                 "-D__LUNAIXOS_DEBUG__",
-                "-include flags.h",
-                "-include config.h"
+                "-include .builder/configs.h"
             ],
             "defines": [],
             "compilerPath": "${HOME}/opt/i686-gcc-12/bin/i686-elf-gcc",
diff --git a/lunaix-os/LBuild b/lunaix-os/LBuild
new file mode 100644 (file)
index 0000000..f4bb7c8
--- /dev/null
@@ -0,0 +1,9 @@
+use("kernel")
+use("libs")
+use("arch")
+use("hal")
+
+headers([
+    "includes",
+    "includes/usr"
+])
\ No newline at end of file
diff --git a/lunaix-os/LConfig b/lunaix-os/LConfig
new file mode 100644 (file)
index 0000000..3d0a179
--- /dev/null
@@ -0,0 +1,32 @@
+import time
+
+include("kernel/LConfig")
+include("arch/LConfig")
+
+@Term("Version")
+@ReadOnly
+def lunaix_ver():
+    """
+    Lunaix kernel version
+    """
+
+    type(str)
+    
+    seq_num = int(time.time() / 3600)
+    default("dev-2024_%d"%(seq_num))
+
+@Collection
+def debug_and_testing():
+    """
+    General settings for kernel debugging feature
+    """
+
+    @Term("Supress assertion")
+    def no_assert():
+        """
+        Supress all assertion fail activity.
+        Note: Enable this is highly NOT recommended and would result system
+              extermly unstable
+        """
+        type(bool)
+        default(False)
\ No newline at end of file
diff --git a/lunaix-os/arch/LBuild b/lunaix-os/arch/LBuild
new file mode 100644 (file)
index 0000000..8a4af8b
--- /dev/null
@@ -0,0 +1,7 @@
+use({
+    config("arch"): {
+        "i386": "i386",
+        "aarch64": "arm",
+        "rv64": "riscv"
+    }
+})
\ No newline at end of file
diff --git a/lunaix-os/arch/LConfig b/lunaix-os/arch/LConfig
new file mode 100644 (file)
index 0000000..30c67b6
--- /dev/null
@@ -0,0 +1,13 @@
+include("i386/LConfig")
+
+@Collection
+def architecture_support():
+    """
+        Config ISA related features
+    """
+
+    @Term
+    def arch():
+        """ Config ISA support """
+        type(["i386", "x86_64", "aarch64", "rv64"])
+        default("i386")
diff --git a/lunaix-os/arch/i386/LBuild b/lunaix-os/arch/i386/LBuild
new file mode 100644 (file)
index 0000000..d4c9a96
--- /dev/null
@@ -0,0 +1,44 @@
+use("hal")
+
+sources([
+    "exceptions/interrupts.c",
+    "exceptions/i386_isrdef.c",
+    "exceptions/intr_routines.c",
+    "exceptions/i386_isrm.c",
+    
+    "exceptions/interrupt.S",
+    "exceptions/intrhnds.S",
+])
+
+sources([
+    "boot/mb_parser.c",
+    "boot/kpt_setup.c",
+    "boot/init32.c",
+
+    "boot/boot.S",
+    "boot/prologue.S"
+])
+
+sources([
+    "mm/fault.c",
+    "mm/tlb.c",
+    "mm/pmm.c",
+    "mm/gdt.c",
+    "mm/vmutils.c"
+])
+
+sources([
+    "klib/fast_crc.c",
+    "klib/fast_str.c",
+    "hart.c",
+    "arch.c",
+    "gdbstub.c",
+    "trace.c",
+
+    "syscall.S",
+    "failsafe.S"
+])
+
+headers([
+    "includes"
+])
\ No newline at end of file
diff --git a/lunaix-os/arch/i386/LConfig b/lunaix-os/arch/i386/LConfig
new file mode 100644 (file)
index 0000000..1445686
--- /dev/null
@@ -0,0 +1,23 @@
+
+@Group
+def x86_configurations():
+    @Term
+    def x86_enable_sse_feature():
+        """ 
+            Config whether to allow using SSE feature for certain
+            optimization
+        """
+        
+        type(bool)
+        default(False)
+
+        add_to_collection(architecture_support)
+
+
+    @Term
+    def x86_boot_options():
+        type(["multiboot"])
+        # type(["multiboot", "none"])
+        default("multiboot")
+
+    return v(arch) == "i386"
\ No newline at end of file
diff --git a/lunaix-os/arch/i386/hal/LBuild b/lunaix-os/arch/i386/hal/LBuild
new file mode 100644 (file)
index 0000000..66fff6a
--- /dev/null
@@ -0,0 +1,9 @@
+sources([
+    "apic.c",
+    "rngx86.c",
+    "cpu.c",
+    "ps2kbd.c",
+    "apic_timer.c",
+    "ioapic.c",
+    "mc146818a.c"
+])
\ No newline at end of file
index 23843900cd7d9767d279370b36d92967683274c2..5332b76aa76498a572886e84da69bdfd0358db75 100644 (file)
@@ -1,7 +1,7 @@
 #include <lunaix/types.h>
 #include <klibc/crc.h>
 
-#ifdef CONFIG_X86_SSE4
+#ifdef CONFIG_X86_ENABLE_SSE_FEATURE
 unsigned int
 crc32b(unsigned char* data, unsigned int size)
 {
diff --git a/lunaix-os/config-grub.sh b/lunaix-os/config-grub.sh
deleted file mode 100755 (executable)
index 956cb4f..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/usr/bin/bash
-
-cat GRUB_TEMPLATE | envsubst > "$1"
\ No newline at end of file
diff --git a/lunaix-os/config.h b/lunaix-os/config.h
deleted file mode 100644 (file)
index 1d5386d..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-#ifndef __LUNAIX_CONFIG_H
-#define __LUNAIX_CONFIG_H
-
-// #define CONFIG_PMALLOC_BUDDY
-// #define CONFIG_PMALLOC_NCONTIG
-#define CONFIG_PMALLOC_SIMPLE
-
-#define CONFIG_PMALLOC_SIMPLE_PO0_THRES     4096
-#define CONFIG_PMALLOC_SIMPLE_PO1_THRES     2048
-#define CONFIG_PMALLOC_SIMPLE_PO2_THRES     2048
-#define CONFIG_PMALLOC_SIMPLE_PO3_THRES     2048
-#define CONFIG_PMALLOC_SIMPLE_PO4_THRES     512
-#define CONFIG_PMALLOC_SIMPLE_PO5_THRES     512
-#define CONFIG_PMALLOC_SIMPLE_PO6_THRES     128
-#define CONFIG_PMALLOC_SIMPLE_PO7_THRES     128
-#define CONFIG_PMALLOC_SIMPLE_PO8_THRES     64
-#define CONFIG_PMALLOC_SIMPLE_PO9_THRES     16
-
-#endif /* __LUNAIX_CONFIG_H */
diff --git a/lunaix-os/flags.h b/lunaix-os/flags.h
deleted file mode 100644 (file)
index 7f9835f..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-#ifndef __LUNAIX_FLAGS_H
-#define __LUNAIX_FLAGS_H
-
-#if __ARCH__ == i386
-#define PLATFORM_TARGET "x86_32"
-#else
-#define PLATFORM_TARGET "unknown"
-#endif
-
-#define LUNAIX_VER "0.0.1-dev"
-
-/*
-    Uncomment below to disable all assertion
-*/
-// #define __LUNAIXOS_NASSERT__
-
-#endif /* __LUNAIX_FLAGS_H */
diff --git a/lunaix-os/hal/LBuild b/lunaix-os/hal/LBuild
new file mode 100644 (file)
index 0000000..c63faf3
--- /dev/null
@@ -0,0 +1,9 @@
+use("acpi")
+use("ahci")
+use("char")
+use("gfxa")
+use("rtc")
+use("term")
+use("timer")
+use("bus")
+
diff --git a/lunaix-os/hal/acpi/LBuild b/lunaix-os/hal/acpi/LBuild
new file mode 100644 (file)
index 0000000..fb5ee72
--- /dev/null
@@ -0,0 +1,5 @@
+sources([
+    "parser/madt_parser.c",
+    "parser/mcfg_parser.c",
+    "acpi.c"
+])
\ No newline at end of file
diff --git a/lunaix-os/hal/ahci/LBuild b/lunaix-os/hal/ahci/LBuild
new file mode 100644 (file)
index 0000000..8c081c6
--- /dev/null
@@ -0,0 +1,9 @@
+sources([
+    "ahci_pci.c",
+    "hbadev_export.c",
+    "ahci.c",
+    "utils.c",
+    "io_event.c",
+    "atapi.c",
+    "ata.c"
+])
\ No newline at end of file
diff --git a/lunaix-os/hal/bus/LBuild b/lunaix-os/hal/bus/LBuild
new file mode 100644 (file)
index 0000000..a485ce7
--- /dev/null
@@ -0,0 +1,3 @@
+sources([
+    "pci.c"
+])
\ No newline at end of file
similarity index 100%
rename from lunaix-os/hal/pci.c
rename to lunaix-os/hal/bus/pci.c
diff --git a/lunaix-os/hal/char/LBuild b/lunaix-os/hal/char/LBuild
new file mode 100644 (file)
index 0000000..88b3310
--- /dev/null
@@ -0,0 +1,8 @@
+use("uart")
+
+sources([
+    "devnull.c",
+    "serial.c",
+    "devzero.c",
+    "lxconsole.c",
+])
\ No newline at end of file
diff --git a/lunaix-os/hal/char/uart/LBuild b/lunaix-os/hal/char/uart/LBuild
new file mode 100644 (file)
index 0000000..1fbc933
--- /dev/null
@@ -0,0 +1,4 @@
+sources([
+    "16550_base.c",
+    "16550_pmio.c"
+])
\ No newline at end of file
diff --git a/lunaix-os/hal/gfxa/LBuild b/lunaix-os/hal/gfxa/LBuild
new file mode 100644 (file)
index 0000000..54d50d1
--- /dev/null
@@ -0,0 +1,5 @@
+use("vga")
+
+sources([
+    "gfxm.c"
+])
\ No newline at end of file
diff --git a/lunaix-os/hal/gfxa/vga/LBuild b/lunaix-os/hal/gfxa/vga/LBuild
new file mode 100644 (file)
index 0000000..b05da8c
--- /dev/null
@@ -0,0 +1,7 @@
+sources([
+    "vga_pmio_ops.c",
+    "vga_gfxm_ops.c",
+    "vga.c",
+    "vga_mmio_ops.c",
+    "vga_pci.c"
+])
\ No newline at end of file
diff --git a/lunaix-os/hal/rtc/LBuild b/lunaix-os/hal/rtc/LBuild
new file mode 100644 (file)
index 0000000..debfd14
--- /dev/null
@@ -0,0 +1,3 @@
+sources([
+    "rtc_device.c"
+])
\ No newline at end of file
diff --git a/lunaix-os/hal/term/LBuild b/lunaix-os/hal/term/LBuild
new file mode 100644 (file)
index 0000000..9325917
--- /dev/null
@@ -0,0 +1,7 @@
+sources([
+    "term.c",
+    "console.c",
+    "term_io.c",
+    "lcntls/ansi_cntl.c",
+    "lcntls/lcntl.c",
+])
\ No newline at end of file
diff --git a/lunaix-os/hal/timer/LBuild b/lunaix-os/hal/timer/LBuild
new file mode 100644 (file)
index 0000000..e5b2256
--- /dev/null
@@ -0,0 +1,3 @@
+sources([
+    "timer_device.c"
+])
\ No newline at end of file
index 60a5c68a4b634b339afc5bb88216163e4380fd83..b5dcb971c796889d5a325d1393bcc4881853dfea 100644 (file)
@@ -25,16 +25,16 @@ struct pmem_pool
     struct ppage* pool_start;
     struct ppage* pool_end;
     
-#if defined(CONFIG_PMALLOC_NCONTIG)
+#if defined(CONFIG_PMALLOC_METHOD_NCONTIG)
 
     struct llist_header idle_page;
     struct llist_header busy_page;
     
-#elif defined(CONFIG_PMALLOC_BUDDY)
+#elif defined(CONFIG_PMALLOC_METHOD_BUDDY)
 
     struct llist_header idle_order[MAX_PAGE_ORDERS];
     
-#elif defined(CONFIG_PMALLOC_SIMPLE)
+#elif defined(CONFIG_PMALLOC_METHOD_SIMPLE)
 
     struct llist_header idle_order[MAX_PAGE_ORDERS];
     int count[MAX_PAGE_ORDERS];
index cf40bab54d4c0079c06b7a8434ad2cdda496a626..e35cfdc52742b0a589d3b32a74949164bfdd7693 100644 (file)
@@ -66,7 +66,7 @@
                                                       : 0)                      \
                              : (31 - clz(x)))
 
-#ifndef __LUNAIXOS_NASSERT__
+#ifndef CONFIG_NO_ASSERT
 #define assert(cond)                                                           \
     do {                                                                       \
         if (unlikely(!(cond))) {                                                         \
@@ -96,7 +96,7 @@ __assert_fail(const char* expr, const char* file, unsigned int line)
 #define assert(cond) (void)(cond);          // assert nothing
 #define assert_msg(cond, msg) (void)(cond); // assert nothing
 
-#endif // __LUNAIXOS_NASSERT__
+#endif // CONFIG_NO_ASSERT
 
 void noret
 panick(const char* msg);
index f036d503d844932b47d007bb53e2749f57d65c37..bd8b6464e9e7fe5630a902e8858a9a86aa62d192 100644 (file)
@@ -3,6 +3,7 @@
 
 #include <lunaix/hart_state.h>
 #include <hal/serial.h>
+#include <sys/gdbstub.h>
 
 struct gdb_state
 {
index 189e97046385d7ffa581e5e52467bbf3cab2f3b3..9399e7154c0e26cf6db7800767f57d3468576a1c 100644 (file)
@@ -1,45 +1,33 @@
 include os.mkinc
 include toolchain.mkinc
 
-kexclusion = $(shell cat ksrc.excludes)
-
-define ksrc_dirs
-       kernel
-       hal
-       libs
-       arch/$(ARCH)
-endef
-
-define kinc_dirs
-       includes
-       includes/usr
-       arch/$(ARCH)/includes
-endef
-
+ksrc_files = $(shell cat .builder/sources.list)
+kinc_dirs  = $(shell cat .builder/includes.list)
+khdr_files = $(shell cat .builder/headers.list)
+khdr_files += .builder/configs.h
 
 kbin_dir := $(BUILD_DIR)
 kbin := $(BUILD_NAME)
 
-ksrc_files := $(foreach f, $(ksrc_dirs), $(shell find $(f) -name "*.[cS]"))
-ksrc_files := $(filter-out $(kexclusion),$(ksrc_files))
 ksrc_objs := $(addsuffix .o,$(ksrc_files))
 ksrc_deps := $(addsuffix .d,$(ksrc_files))
-
+khdr_opts := $(addprefix -include ,$(khdr_files))
 kinc_opts := $(addprefix -I,$(kinc_dirs))
 
 tmp_kbin  := $(BUILD_DIR)/tmpk.bin
 ksymtable := lunaix_ksyms.o
 
-CFLAGS += -include flags.h
-CFLAGS += -include config.h
+CFLAGS += $(khdr_opts)
+
+-include $(ksrc_deps)
 
-%.S.o: %.S
+%.S.o: %.S $(khdr_files) kernel.mk
        $(call status_,AS,$<)
-       @$(CC) $(CFLAGS) $(kinc_opts) -c $< -o $@
+       @$(CC) $(CFLAGS) $(kinc_opts) -MMD -MP -c $< -o $@
 
-%.c.o: %.c
+%.c.o: %.c $(khdr_files) kernel.mk
        $(call status_,CC,$<)
-       @$(CC) $(CFLAGS) $(kinc_opts) -c $< -o $@
+       @$(CC) $(CFLAGS) $(kinc_opts) -MMD -MP -c $< -o $@
 
 $(tmp_kbin): $(ksrc_objs)
        $(call status_,LD,$@)
diff --git a/lunaix-os/kernel/LBuild b/lunaix-os/kernel/LBuild
new file mode 100644 (file)
index 0000000..b52bc29
--- /dev/null
@@ -0,0 +1,21 @@
+use("block")
+use("debug")
+use("device")
+use("ds")
+use("exe")
+use("fs")
+use("mm")
+use("process")
+
+sources([
+    "boot_helper.c",
+    "kcmd.c",
+    "kinit.c",
+    "lunad.c",
+    "spike.c",
+    "tty/tty.c",
+    "kprint/kp_records.c",
+    "kprint/kprintf.c",
+    "time/clock.c",
+    "time/timer.c"
+])
\ No newline at end of file
diff --git a/lunaix-os/kernel/LConfig b/lunaix-os/kernel/LConfig
new file mode 100644 (file)
index 0000000..c820fd0
--- /dev/null
@@ -0,0 +1,6 @@
+@Collection
+def kernel_feature():
+    """ Config kernel features """
+    pass
+
+include("mm/LConfig")
\ No newline at end of file
diff --git a/lunaix-os/kernel/block/LBuild b/lunaix-os/kernel/block/LBuild
new file mode 100644 (file)
index 0000000..252a19e
--- /dev/null
@@ -0,0 +1,6 @@
+sources([
+    "blkpart_gpt.c",
+    "blk_mapping.c",
+    "blkio.c",
+    "block.c"
+])
\ No newline at end of file
diff --git a/lunaix-os/kernel/debug/LBuild b/lunaix-os/kernel/debug/LBuild
new file mode 100644 (file)
index 0000000..d9ddb47
--- /dev/null
@@ -0,0 +1,5 @@
+sources([
+    "failsafe.c",
+    "trace.c",
+    # "gdbstub.c"
+])
\ No newline at end of file
index c88ab8d4b22b9baa8eb5f60535b7963803d42bfa..f84aab9fe1e3bbb2bde3bf70f7b1c3f523fa0680 100644 (file)
@@ -38,7 +38,6 @@
 
 #include <sys/port_io.h>
 #include <sys/cpu.h>
-#include <sys/gdbstub.h>
 
 /*****************************************************************************
  * Macros
diff --git a/lunaix-os/kernel/device/LBuild b/lunaix-os/kernel/device/LBuild
new file mode 100644 (file)
index 0000000..c3c7255
--- /dev/null
@@ -0,0 +1,8 @@
+sources([
+    "device.c",
+    "capability.c",
+    "devdb.c",
+    "devfs.c",
+    "input.c",
+    "poll.c"
+])
diff --git a/lunaix-os/kernel/ds/LBuild b/lunaix-os/kernel/ds/LBuild
new file mode 100644 (file)
index 0000000..e87f3d1
--- /dev/null
@@ -0,0 +1,12 @@
+sources([
+    "waitq.c",
+    "buffer.c",
+    "lru.c",
+    "rbuffer.c",
+    "btrie.c",
+    "semaphore.c",
+    "mutex.c",
+    "hstr.c",
+    "fifo.c",
+    "rwlock.c"
+])
\ No newline at end of file
diff --git a/lunaix-os/kernel/exe/LBuild b/lunaix-os/kernel/exe/LBuild
new file mode 100644 (file)
index 0000000..8b24498
--- /dev/null
@@ -0,0 +1,5 @@
+use("elf32")
+
+sources([
+    "exec.c"
+])
\ No newline at end of file
diff --git a/lunaix-os/kernel/exe/elf32/LBuild b/lunaix-os/kernel/exe/elf32/LBuild
new file mode 100644 (file)
index 0000000..ef8cd25
--- /dev/null
@@ -0,0 +1,4 @@
+sources([
+    "elf32bfmt.c",
+    "ldelf32.c"
+])
\ No newline at end of file
diff --git a/lunaix-os/kernel/fs/LBuild b/lunaix-os/kernel/fs/LBuild
new file mode 100644 (file)
index 0000000..25e8446
--- /dev/null
@@ -0,0 +1,16 @@
+use("twifs")
+use("ramfs")
+use("iso9660")
+
+sources([
+    "twimap.c",
+    "pcache.c",
+    "mount.c",
+    "xattr.c",
+    "vfs.c",
+    "defaults.c",
+    "path_walk.c",
+    "fsm.c",
+    "fs_export.c",
+    "probe_boot.c"
+])
\ No newline at end of file
index bdc02d4c6573d7caa29817f0b4cc01a1abfddb30..dffff0ae70f50b4042084be127a51f32c132fa8b 100644 (file)
@@ -41,9 +41,8 @@ void
 __version_rd(struct twimap* map)
 {
     twimap_printf(map,
-                  "LunaixOS version %s (%s-gnu-gcc %s) %s %s",
-                  LUNAIX_VER,
-                  PLATFORM_TARGET,
+                  "LunaixOS version %s (gnu-cc %s) %s %s",
+                  CONFIG_LUNAIX_VER,
                   __VERSION__,
                   __DATE__,
                   __TIME__);
diff --git a/lunaix-os/kernel/fs/iso9660/LBuild b/lunaix-os/kernel/fs/iso9660/LBuild
new file mode 100644 (file)
index 0000000..aaa5edc
--- /dev/null
@@ -0,0 +1,8 @@
+sources([
+    "inode.c",
+    "file.c",
+    "mount.c",
+    "directory.c",
+    "utils.c",
+    "rockridge.c"
+])
\ No newline at end of file
diff --git a/lunaix-os/kernel/fs/ramfs/LBuild b/lunaix-os/kernel/fs/ramfs/LBuild
new file mode 100644 (file)
index 0000000..fbc9ec0
--- /dev/null
@@ -0,0 +1,3 @@
+sources([
+    "ramfs.c"
+])
\ No newline at end of file
diff --git a/lunaix-os/kernel/fs/twifs/LBuild b/lunaix-os/kernel/fs/twifs/LBuild
new file mode 100644 (file)
index 0000000..b4270ce
--- /dev/null
@@ -0,0 +1,3 @@
+sources([
+    "twifs.c"
+])
\ No newline at end of file
diff --git a/lunaix-os/kernel/mm/LBuild b/lunaix-os/kernel/mm/LBuild
new file mode 100644 (file)
index 0000000..e350f50
--- /dev/null
@@ -0,0 +1,16 @@
+sources([
+    "mmap.c",
+    "valloc.c",
+    "cake.c",
+    "fault.c",
+    "procvm.c",
+    "page.c",
+    "region.c",
+    "pmalloc_simple.c",
+    "vmm.c",
+    "mmio.c",
+    "pmm.c",
+    "cake_export.c",
+    "vmap.c",
+    "dmm.c"
+])
\ No newline at end of file
diff --git a/lunaix-os/kernel/mm/LConfig b/lunaix-os/kernel/mm/LConfig
new file mode 100644 (file)
index 0000000..7db89f5
--- /dev/null
@@ -0,0 +1,92 @@
+
+@Collection
+def memory_subsystem():
+    """ Config the memory subsystem """
+
+    @Collection
+    def physical_mm():
+        """ Physical memory manager  """
+
+        @Term
+        def pmalloc_method():
+            """ Allocation policy for phiscal memory  """
+            
+            type(["simple", "buddy", "ncontig"])
+            default("simple")
+
+        @Group
+        def pmalloc_simple_po_thresholds():
+            
+            @Term
+            def pmalloc_simple_max_po0():
+                """ free list capacity for order-0 pages  """
+                
+                type(int)
+                default(4096)
+
+            @Term
+            def pmalloc_simple_max_po1():
+                """ free list capacity for order-1 pages  """
+
+                type(int)
+                default(2048)
+            
+            @Term
+            def pmalloc_simple_max_po2():
+                """ free list capacity for order-2 pages  """
+
+                type(int)
+                default(2048)
+            
+            @Term
+            def pmalloc_simple_max_po3():
+                """ free list capacity for order-3 pages  """
+                
+                type(int)
+                default(2048)
+            
+            @Term
+            def pmalloc_simple_max_po4():
+                """ free list capacity for order-4 pages  """
+
+                type(int)
+                default(512)
+            
+            @Term
+            def pmalloc_simple_max_po5():
+                """ free list capacity for order-5 pages  """
+
+                type(int)
+                default(512)
+            
+            @Term
+            def pmalloc_simple_max_po6():
+                """ free list capacity for order-6 pages  """
+
+                type(int)
+                default(128)
+            
+            @Term
+            def pmalloc_simple_max_po7():
+                """ free list capacity for order-7 pages  """
+
+                type(int)
+                default(128)
+            
+            @Term
+            def pmalloc_simple_max_po8():
+                """ free list capacity for order-8 pages  """
+
+                type(int)
+                default(64)
+            
+            @Term
+            def pmalloc_simple_max_po9():
+                """ 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
index 37a720b8c05a3b39d417160fe305f506a8af968c..230ae1071b0c380f9f7a9a331c8840b70c91ebe3 100644 (file)
@@ -1,23 +1,23 @@
 #include <lunaix/spike.h>
 #include "pmm_internal.h"
 
-#ifdef CONFIG_PMALLOC_SIMPLE
+#ifdef CONFIG_PMALLOC_METHOD_SIMPLE
 
 // Simple PM Allocator (segregated next fit)
 
 #define INIT_FLAG   0b10
 
 static const int po_limit[] = {
-    CONFIG_PMALLOC_SIMPLE_PO0_THRES,
-    CONFIG_PMALLOC_SIMPLE_PO1_THRES,
-    CONFIG_PMALLOC_SIMPLE_PO2_THRES,
-    CONFIG_PMALLOC_SIMPLE_PO3_THRES,
-    CONFIG_PMALLOC_SIMPLE_PO4_THRES,
-    CONFIG_PMALLOC_SIMPLE_PO5_THRES,
-    CONFIG_PMALLOC_SIMPLE_PO6_THRES,
-    CONFIG_PMALLOC_SIMPLE_PO7_THRES,
-    CONFIG_PMALLOC_SIMPLE_PO8_THRES,
-    CONFIG_PMALLOC_SIMPLE_PO9_THRES,
+    CONFIG_PMALLOC_SIMPLE_MAX_PO0,
+    CONFIG_PMALLOC_SIMPLE_MAX_PO1,
+    CONFIG_PMALLOC_SIMPLE_MAX_PO2,
+    CONFIG_PMALLOC_SIMPLE_MAX_PO3,
+    CONFIG_PMALLOC_SIMPLE_MAX_PO4,
+    CONFIG_PMALLOC_SIMPLE_MAX_PO5,
+    CONFIG_PMALLOC_SIMPLE_MAX_PO6,
+    CONFIG_PMALLOC_SIMPLE_MAX_PO7,
+    CONFIG_PMALLOC_SIMPLE_MAX_PO8,
+    CONFIG_PMALLOC_SIMPLE_MAX_PO9,
 };
 
 static inline bool
diff --git a/lunaix-os/kernel/process/LBuild b/lunaix-os/kernel/process/LBuild
new file mode 100644 (file)
index 0000000..3bb42be
--- /dev/null
@@ -0,0 +1,9 @@
+sources([
+    "signal.c",
+    "sched.c",
+    "fork.c",
+    "process.c",
+    "taskfs.c",
+    "task_attr.c",
+    "thread.c"
+])
\ No newline at end of file
diff --git a/lunaix-os/ksrc.excludes b/lunaix-os/ksrc.excludes
deleted file mode 100644 (file)
index ca3e1a5..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-kernel/debug/sdbg.c
-kernel/debug/gdbstub.c
\ No newline at end of file
diff --git a/lunaix-os/libs/LBuild b/lunaix-os/libs/LBuild
new file mode 100644 (file)
index 0000000..2f647b4
--- /dev/null
@@ -0,0 +1,12 @@
+sources([
+    "crc.c",
+    "hash.c",
+    "klibc/itoa.c",
+    "klibc/ksprintf.c",
+    "klibc/string/mem.c",
+    "klibc/string/strchr.c",
+    "klibc/string/strcmp.c",
+    "klibc/string/strcpy.c",
+    "klibc/string/strlen.c",
+    "klibc/string/trim.c"
+])
\ No newline at end of file
index a1aec5ad4f33f089f52341a34efe1ddbcf1b8a6a..5e09423811a7a9c3840a1ff9fff39bbf702755f6 100644 (file)
@@ -2,7 +2,7 @@
 #include <lunaix/compiler.h>
 
 unsigned long weak
-strlen(const charstr)
+strlen(const char *str)
 {
     unsigned long len = 0;
     while (str[len])
@@ -11,7 +11,7 @@ strlen(const char* str)
 }
 
 unsigned long weak
-strnlen(const charstr, unsigned long max_len)
+strnlen(const char *str, unsigned long max_len)
 {
     unsigned long len = 0;
     while (str[len] && len <= max_len)
index 6e9fdb0b21704a5b9c37c0d770e3d19941ab6256..4e132117404662d024b12331de96a7be27eb3111 100644 (file)
@@ -28,9 +28,13 @@ $(DEPS):
                echo "failed" && exit 1;\
        fi
 
-check-cc:
-       @echo -n "checking target i686-elf.... "
-       @test "`i686-elf-gcc -dumpmachine`" = "i686-elf" && echo ok || (echo "failed" && exit 1)
+define builder_data
+       .builder/sources.list 
+       .builder/headers.list 
+       .builder/includes.list
+endef
+
+all_lconfigs = $(shell find . -name "LConfig")
 
 $(kbuild_dir):
        @mkdir -p $(kbin_dir)
@@ -39,10 +43,18 @@ $(kbuild_dir):
        @mkdir -p $(os_img_dir)/boot/grub
        @mkdir -p $(os_img_dir)/usr
 
+.builder/configs.h: $(all_lconfigs)
+       @echo restarting configuration...
+       @echo 
+       @./scripts/build-tools/luna_build.py --config --lconfig-file LConfig -o $(@D)
+
+.builder/%.list: .builder/configs.h
+       @./scripts/build-tools/luna_build.py LBuild --lconfig-file LConfig -o $(@D)
+
 .PHONY: kernel
 export BUILD_DIR=$(kbin_dir)
 export BUILD_NAME=$(kbin)
-kernel:
+kernel: $(builder_data)
        $(call status,TASK,$(notdir $@))
        @$(MAKE) $(MKFLAGS) -I $(mkinc_dir) -f kernel.mk all
 
@@ -51,14 +63,15 @@ export KCMD=$(CMDLINE)
 export _OS_NAME=$(OS_NAME)
 image: usr/build kernel
        $(call status,TASK,$(notdir $@))
-       @./config-grub.sh $(os_img_dir)/boot/grub/grub.cfg
+       @./scripts/grub/config-grub.sh $(os_img_dir)/boot/grub/grub.cfg
        @cp -r usr/build/* $(os_img_dir)/usr
        @cp -r $(kbin_dir)/* $(os_img_dir)/boot
-       @grub-mkrescue -o $(kimg) $(os_img_dir) -- -volid "$(OS_ID) $(OS_VER)" -system_id "$(OS_NAME)"
+       @grub-mkrescue -o $(kimg) $(os_img_dir)\
+               -- -volid "$(OS_ID) $(OS_VER)" -system_id "$(OS_NAME)"
 
 usr/build: user
 
-check: $(DEPS) check-cc GRUB_TEMPLATE
+check: $(DEPS) check-cc scripts/grub/GRUB_TEMPLATE
 
 prepare: check $(os_img_dir)
 
@@ -80,12 +93,13 @@ instable: all
 all-debug: bootable-debug
 
 clean:
-       @rm -rf $(kbuild_dir) || exit 1
        @$(MAKE) -C usr clean -I $(mkinc_dir)
        @$(MAKE) -f kernel.mk clean -I $(mkinc_dir)
+       @rm -rf $(kbuild_dir) || exit 1
+       @rm -f .builder/*.list || exit 1
 
-run: $(BUILD_DIR)/$(OS_ISO)
-       @qemu-system-i386 $(QEMU_OPTIONS)
+run: all
+       @qemu-system-i386 $(call get_qemu_options,$(kimg))
        @sleep 1
        @telnet 127.0.0.1 $(QEMU_MON_PORT)
 
diff --git a/lunaix-os/scripts/build-tools/README.lbuild.md b/lunaix-os/scripts/build-tools/README.lbuild.md
new file mode 100644 (file)
index 0000000..8a25a8e
--- /dev/null
@@ -0,0 +1,101 @@
+# LunaBuild
+
+LunaBuild is programmable source file selection tool. It does not build things
+by itself, rather, it selects which source file should be feed as input to
+other tranditional build tools such as make. The selection logic is completely
+programmable and convey through `LBuild` file which is essentially a python
+script. As the primary design goal for LunaBuild is simple, lightweight and
+standalone. It just plain python, with some extra predefined functions and
+automatic variables, so does not force user to learn some other weird domain
+specific language (yes, that's you, CMake!).
+
+## Usage
+
+Invoke `./luna_build.py <root_lbuild> -o <out_dir>`. It will output two file
+to the `<out_dir>`: `sources.list` and `headers.list`. Contains all the source
+files and header files to be used by the build process.
+
+## Core Functions
+
+LunaBuild provide following function to help user select source files.
+
+### [func] `use(lbuild_path)`
+
+Include another LBuild file. `lbuild_path` is the path relative to current
+directory, pointed to the file. It can be also pointed to a directory, for
+which the LBuild file is inferred as `$lbuild_path/LBuild`.
+
+For example:
+
+```py
+use("dir")
+use("dir/LBuild")
+```
+
+both are equivalent.
+
+### [func] `sources(src_list)`
+
+Select a list of source files, all paths used are relative to current
+directory. For example,
+
+```py
+sources([ "a.c", "b.c", "c.c" ])
+```
+
+### [func] `headers(src_list)`
+
+Select a list of header file or include directory, all paths used are
+relative to current directory. For example,
+
+```py
+headers([ "includes/", "includes/some.h" ])
+```
+
+### [func] `configured(name)`
+
+Check whether a configuration is present, the name for the configuration
+is defined by external configuration provider.
+
+### [func] `config(name)`
+
+Read the value of a configuration, raise exception is not exists.
+
+### [var] `_script`
+
+Automatic variable, a path to the current build file.
+
+## Short-hands
+
+LunaBuild provide some useful short-hand notations
+
+### Conditional Select
+
+Suppose you have two source files `src_a.c` and `src_b.c`, for which
+their selection will be depends on the value of a configuration
+`WHICH_SRC`. A common way is to use `if-else` construct
+
+```py
+if config("WHICH_SRC") == "select_a":
+    sources("src_a.c")
+elif config("WHICH_SRC") == "select_b":
+    sources("src_b.c")
+```
+
+LunaBuild allow you to short hand it as such:
+
+```py
+sources({
+    config("WHICH_SRC"): {
+        "select_a": "src_a.c",
+        "select_b": "src_b.c",
+        # more...
+    }
+})
+```
+
+It can also be extended easily for multiple choices and allow nesting.
+
+You may also notice we no longer wrap the parameter with square bracket,
+this is also another short-hand, the array notation is not needed when
+there is only one element to add.
diff --git a/lunaix-os/scripts/build-tools/integration/__init__.py b/lunaix-os/scripts/build-tools/integration/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/lunaix-os/scripts/build-tools/integration/config_io.py b/lunaix-os/scripts/build-tools/integration/config_io.py
new file mode 100644 (file)
index 0000000..33079ff
--- /dev/null
@@ -0,0 +1,52 @@
+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, "//")
+            else:
+                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
new file mode 100644 (file)
index 0000000..7566670
--- /dev/null
@@ -0,0 +1,17 @@
+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:
+            self.__env.lookup_value(name)
+            return True
+        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
new file mode 100644 (file)
index 0000000..5eacf55
--- /dev/null
@@ -0,0 +1,296 @@
+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 not rd_val:
+            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/__init__.py b/lunaix-os/scripts/build-tools/lbuild/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/lunaix-os/scripts/build-tools/lbuild/api.py b/lunaix-os/scripts/build-tools/lbuild/api.py
new file mode 100644 (file)
index 0000000..2bc91fa
--- /dev/null
@@ -0,0 +1,9 @@
+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
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/lbuild/common.py b/lunaix-os/scripts/build-tools/lbuild/common.py
new file mode 100644 (file)
index 0000000..c559b0a
--- /dev/null
@@ -0,0 +1,43 @@
+from lib.utils import join_path
+import os
+
+class BuildEnvironment:
+    def __init__(self, workspace_dir) -> None:
+        self.__config_provider = None
+        self.__sources = []
+        self.__headers = []
+        self.__inc_dir = []
+        self.__ws_dir = workspace_dir
+
+    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 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 export(self, out_dir):
+        path = os.path.join(out_dir, "sources.list")
+        with open(path, "w") as f:
+            f.write("\n".join(self.__sources))
+
+        path = os.path.join(out_dir, "headers.list")
+        with open(path, "w") as f:
+            f.write("\n".join(self.__headers))
+
+        path = os.path.join(out_dir, "includes.list")
+        with open(path, "w") as f:
+            f.write("\n".join(self.__inc_dir))
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/lbuild/contract.py b/lunaix-os/scripts/build-tools/lbuild/contract.py
new file mode 100644 (file)
index 0000000..1b818ab
--- /dev/null
@@ -0,0 +1,101 @@
+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)
+        })
+        
+        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.__do_process(self.__srcs))
+        self.__env.add_headers(self.__do_process(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 not isinstance(resolved, str):
+            if isinstance(resolved, dict):
+                resolved = self.expand_select(resolved)
+            else:
+                self.__raise(f"entry with unknown type: {resolved}")
+        
+        resolved = resolved.strip()
+        resolved = join_path(self.__dir, resolved)
+
+        return self.__env.to_wspath(resolved)
+    
+    def import_buildfile(self, path):
+        path = self.__resolve_value(path)
+        path = self.__env.to_wspath(path)
+        
+        if (os.path.isdir(path)):
+            path = os.path.join(path, "LBuild")
+        
+        if not os.path.exists(path):
+            self.__raise("Build file not exist: %s", path)
+
+        if os.path.abspath(path) == os.path.abspath(self.__path):
+            self.__raise("self dependency detected")
+
+        LunaBuildFile(self.__env, path).resolve()
+
+    def export_sources(self, src):
+        if not isinstance(src, list):
+            src = [src]
+        self.__srcs += src
+
+    def export_headers(self, hdr):
+        if not isinstance(hdr, list):
+            hdr = [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/lcfg/__init__.py b/lunaix-os/scripts/build-tools/lcfg/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/lunaix-os/scripts/build-tools/lcfg/api.py b/lunaix-os/scripts/build-tools/lcfg/api.py
new file mode 100644 (file)
index 0000000..1b0b30f
--- /dev/null
@@ -0,0 +1,139 @@
+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
new file mode 100644 (file)
index 0000000..9eb616f
--- /dev/null
@@ -0,0 +1,32 @@
+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.lookup_value(node.get_name())
+
+@contextual(caller_type=[LCModuleNode])
+def include(env, caller, file):
+    fobj = caller.get_fo()
+    path = os.path.dirname(fobj.filename())
+
+    env.resolve_module(join_path(path, file))
+
+@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)
\ 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
new file mode 100644 (file)
index 0000000..b409f1d
--- /dev/null
@@ -0,0 +1,224 @@
+import os.path as path
+import ast, json
+
+from .lcnodes import LCModuleNode
+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)
+            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 = globals()
+        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 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
new file mode 100644 (file)
index 0000000..cf869b0
--- /dev/null
@@ -0,0 +1,390 @@
+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
+
+        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.__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 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
new file mode 100644 (file)
index 0000000..123f6a9
--- /dev/null
@@ -0,0 +1,77 @@
+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: \n",
+            *accepted
+        ])
diff --git a/lunaix-os/scripts/build-tools/lcfg/utils.py b/lunaix-os/scripts/build-tools/lcfg/utils.py
new file mode 100644 (file)
index 0000000..96e9e2b
--- /dev/null
@@ -0,0 +1,55 @@
+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/lib/__init__.py b/lunaix-os/scripts/build-tools/lib/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/lunaix-os/scripts/build-tools/lib/sandbox.py b/lunaix-os/scripts/build-tools/lib/sandbox.py
new file mode 100644 (file)
index 0000000..de28aca
--- /dev/null
@@ -0,0 +1,32 @@
+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
diff --git a/lunaix-os/scripts/build-tools/lib/utils.py b/lunaix-os/scripts/build-tools/lib/utils.py
new file mode 100644 (file)
index 0000000..361b786
--- /dev/null
@@ -0,0 +1,6 @@
+import os
+
+def join_path(stem, path):
+    if os.path.isabs(path):
+        return path
+    return os.path.join(stem, path)
\ No newline at end of file
diff --git a/lunaix-os/scripts/build-tools/luna_build.py b/lunaix-os/scripts/build-tools/luna_build.py
new file mode 100755 (executable)
index 0000000..848ceb6
--- /dev/null
@@ -0,0 +1,87 @@
+#!/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
+
+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.type_factory().regitser(lcfg_type.PrimitiveType)
+    env.type_factory().regitser(lcfg_type.MultipleChoiceType)
+
+    return env
+
+def do_config(lcfg_env):
+    shell = InteractiveShell(lcfg_env)
+    if not shell.render_loop():
+        print("Configuration aborted.")
+        exit(-1)
+
+    lcfg_env.export()
+    lcfg_env.save()
+
+def do_buildfile_gen(opts, lcfg_env):
+    root_path = abspath(opts.root)
+    ws_path = dirname(root_path)
+    root_name = basename(root_path)
+
+    env = BuildEnvironment(ws_path)
+
+    cfg_provider = LConfigProvider(lcfg_env)
+    env.set_config_provider(cfg_provider)
+
+    root = LunaBuildFile(env, root_name)
+
+    try:
+        root.resolve()
+    except Exception as err:
+        print("failed to resolve root build file")
+        raise err
+    
+    env.export(opts.out_dir)
+
+def main():
+    parser = ArgumentParser()
+    parser.add_argument("--config", action="store_true", default=False)
+    parser.add_argument("--lconfig-file", default="LConfig")
+    parser.add_argument("root", nargs="?", default="LBuild")
+    parser.add_argument("-o", "--out-dir", required=True)
+
+    opts = parser.parse_args()
+    out_dir = opts.out_dir
+    if not exists(out_dir):
+        mkdir(out_dir)
+    
+    lcfg_env = prepare_lconfig_env(out_dir)
+    lcfg_env.resolve_module(opts.lconfig_file)
+    lcfg_env.update()
+    lcfg_env.load()
+
+    if opts.config:
+        do_config(lcfg_env)
+    else:
+        do_buildfile_gen(opts, lcfg_env)
+
+if __name__ == "__main__":
+    main()
\ No newline at end of file
diff --git a/lunaix-os/scripts/expand.py b/lunaix-os/scripts/expand.py
deleted file mode 100644 (file)
index 31e06da..0000000
+++ /dev/null
@@ -1,487 +0,0 @@
-import jinja2
-import re
-import argparse
-import math
-import json
-from pathlib import Path
-from abc import ABC, abstractmethod
-
-class Preprocessor:
-    reHex = re.compile(r"^0x([0-9a-fA-F]+)$")
-    reGranuel = re.compile(r"^(?P<num>[0-9]+)@(?P<g>.+)$")
-    reMacroRef = re.compile(r"^\*(?P<var>[a-zA-z0-9]+)$")
-    reInt = re.compile(r"^[0-9]+$")
-    def __init__(self) -> None:
-        pass
-
-    @staticmethod
-    def expand_str(s: str, param_dict):
-        if Preprocessor.reInt.match(s) is not None:
-            return int(s)
-        
-        mo = Preprocessor.reHex.match(s)
-        if mo is not None:
-            return int(s, 16)
-        
-        mo = Preprocessor.reGranuel.match(s)
-        if mo is not None:
-            mg = mo.groupdict()
-            num = int(mg['num'])
-            granuel = param_dict["granule"][mg['g']]
-            return num * granuel
-        
-        mo = Preprocessor.reMacroRef.match(s)
-        if mo is not None:
-            mg = mo.groupdict()
-            return param_dict[mg['var']]
-
-        return s.format_map(param_dict)
-
-class DataObject(ABC):
-    def __init__(self, name, record):
-        self.key = name
-        self._record = record
-        self.user_field = {}
-        self.ctrl_field = {}
-        self._parse(record)
-
-    @staticmethod
-    def create(record):
-        return DataObject.create("", record)
-
-    @staticmethod
-    def create(key, record):
-        if PrimitiveType.can_create(record):
-            return PrimitiveType(record)
-        
-        name = key
-        t = name if "$type" not in record else record['$type']
-        
-        if "$name" in record:
-            name = record["$name"].strip()
-
-        if not key.startswith('@') and "$type" not in record:
-            return PlainOldObject(name, record)
-        
-        t = t.strip("@")
-        if t == "list":
-            return RangedObject(name, record)
-        elif t == "foreach":
-            return ForEachIndexObject(name, record)
-        elif t == "case_range_index":
-            return Condition(record)
-        elif t == "data":
-            return PlainOldObject(name, record)
-        elif t == "define":
-            return VariableDeclarationObject(record)
-        elif t == "memory_map":
-            return MemoryMapObject(record)
-        else:
-            return RawObject(name, record)
-
-    def _parse(self, record):
-        for k, v in record.items():
-            if k.startswith("$"):
-                self.ctrl_field[k.strip("$")] = FieldType.create(k, v)
-            elif k.startswith("@"):
-                self.ctrl_field[k.strip("@")] = DataObject.create(k, v)
-            else:
-                self.user_field[k] = DataObject.create(k, v)
-
-    def expand(self, param={}):
-        obj2 = {}
-        for f in self.ctrl_field.values():
-            if not isinstance(f, DataObject):
-                continue
-            obj2.update(**f.expand(param))
-
-        obj = {}
-        _param = {**param, **obj2}
-        for k, v in self.user_field.items():
-            if isinstance(v, DataObject):
-                obj[k] = v.expand(_param)
-            else:
-                obj[k] = v
-
-        return {**obj, **obj2}
-    
-
-class FieldType:
-    def __init__(self, record) -> None:
-        self._record = record
-        self._parse(record)
-
-    @staticmethod
-    def create(field, value):
-        if field == "$range":
-            return RangeType(value)
-        else:
-            return value
-    
-    @abstractmethod
-    def _parse(self, record):
-        pass
-
-    @abstractmethod
-    def get(self, param):
-        pass
-
-    def getraw(self):
-        return self.__record
-
-class PrimitiveType(DataObject):
-    def __init__(self, record) -> None:
-        super().__init__("", record)
-
-    @staticmethod
-    def can_create(value):
-        return type(value) in (str, int, bool)
-
-    def _parse(self, record):
-        if type(record) not in (str, int, bool):
-            raise Exception(f"{type(self).__name__} require primitive type input")
-        self.val = record
-
-        if type(record) == str:
-            self.__get_fn = self.__process_str
-        else:
-            self.__get_fn = lambda x: self.val
-
-    def __process_str(self, param):
-        return Preprocessor.expand_str(self.val, param)
-    
-    def expand(self, param={}):
-        return self.__get_fn(param)
-
-class RangeType(FieldType):
-    def __init__(self, record) -> None:
-        self.__ranged_component = re.compile(r"^(?P<index>[^.]+)$|^(?P<start>.+?)\.\.(?P<end>.+)$")
-        super().__init__(record)
-
-    def _parse(self, record):
-        return super()._parse(record)
-    
-    def get(self, param):
-        record = self._record.strip('[]')
-        
-        self.__value=[]
-        for component in record.split(','):
-            component = component.strip()
-            mo = self.__ranged_component.match(component)
-            if mo is None:
-                raise Exception(f"value '{component}' is not valid range component")
-            
-            mo = mo.groupdict()
-            if mo["index"] is not None:
-                self.__value.append(Preprocessor.expand_str(mo['index'], param))
-            else:
-                start = Preprocessor.expand_str(mo['start'], param)
-                end = Preprocessor.expand_str(mo['end'], param)
-                self.__value += [x for x in range(start, end + 1)]
-        return self.__value
-
-    def getraw(self):
-        return self._record
-    
-class VariableDeclarationObject(DataObject):
-    def __init__(self, record):
-        super().__init__("", record)
-    
-    def _parse(self, record):
-        return super()._parse(record)
-    
-    def expand(self, param={}):
-        obj = super().expand(param)
-        param.update(**obj)
-        return {}
-
-class Condition(DataObject):
-    def __init__(self, record):
-        super().__init__("", record)
-    
-    def _parse(self, record):
-        super()._parse(record)
-        if "range" not in self.ctrl_field:
-            raise Exception("condition body must contains valid range case")
-        if "true" not in self.ctrl_field:
-            raise Exception("condition body must contains 'True' handling case")
-        
-    
-    def expand(self, param={}):
-        self.__range_lst = self.ctrl_field["range"].get(param)
-        if param["index"] in self.__range_lst:
-            return self.ctrl_field["true"].expand(param)
-        elif "else" in self.ctrl_field:
-            return self.ctrl_field["else"].expand(param)
-        return {}
-    
-class ArrayObject(DataObject):
-    def __init__(self, record, 
-                 nested_array = False, 
-                 el_factory = lambda x: DataObject.create("", x)):
-        self._el_factory = el_factory
-        self._nested_array = nested_array
-
-        super().__init__("", record)
-    
-    def _parse(self, record):
-        if not isinstance(record, list):
-            raise Exception(f"{type(self).__name__} require array input")
-        
-        self.content = []
-        for x in record:
-            self.content.append(self._el_factory(x))
-    
-    def expand(self, param={}):
-        result = []
-        for x in self.content:
-            obj = x.expand(param)
-            if isinstance(obj, list) and not self._nested_array:
-                result += [*obj]
-            else:
-                result.append(obj)
-        
-        return result
-    
-class MemoryMapObject(DataObject):
-    class GranuleObject(DataObject):
-        def __init__(self, record):
-            super().__init__("", record)
-            
-        def _parse(self, record):
-            self.__granules = {}
-            for k, v in record.items():
-                self.__granules[k] = DataObject.create(k, v)
-
-        def expand(self, param={}):
-            granules = {}
-            for k, v in self.__granules.items():
-                val = v.expand(param)
-
-                if not isinstance(val, int):
-                    raise Exception("The granule definition must be either integer or int-castable string")
-                
-                granules[k] = val
-                
-            return {**granules}
-        
-    def __init__(self, record):
-        super().__init__("", record)
-
-    def _parse(self, record):
-        for k, v in record.items():
-            if k.startswith("$"):
-                self.ctrl_field[k.strip("$")] = FieldType.create(k, v)
-            elif k.startswith("@"):
-                self.ctrl_field[k.strip("@")] = DataObject.create(k, v)
-        
-        if "granule" in record:
-            self.__g = MemoryMapObject.GranuleObject(record["granule"])
-
-        if "regions" in record:
-            self.__regions = ArrayObject(record["regions"])
-
-        if "width" in record:
-            self.__width = DataObject.create("width", record["width"])
-
-    def __process(self, start_addr, idx, regions):
-        if idx >= len(regions):
-            raise Exception("Unbounded region definition")
-        
-        e = regions[idx]
-
-        if "start" not in e:
-            ne = regions[idx + 1]
-            if "start" not in ne or "size" not in e:
-                e["start"] = start_addr
-            else:
-                self.__process(start_addr + e["size"], idx + 1, regions)
-                e["start"] = ne['start'] - e["size"]
-
-        if "block" in e:
-            b = e["block"] - 1
-            e["start"] = (e["start"] + b) & ~b
-
-        if e["start"] < start_addr:
-            raise Exception(f"starting addr {hex(e['start'])} overrlapping with {hex(start_addr)}")
-        
-        start_addr = e["start"]
-        
-        if "size" not in e:
-            self.__process(start_addr, idx + 1, regions)
-            ne = regions[idx + 1]
-            e["size"] = ne['start'] - start_addr
-        
-        return start_addr + e["size"]
-    
-    def expand(self, param={}):
-        super().expand(param)
-
-        g = self.__g.expand(param)
-
-        param["granule"] = g
-
-        width = self.__width.expand(param)
-        if not isinstance(width, int):
-            raise Exception("'width' attribute must be integer")
-
-        regions = self.__regions.expand(param)
-        
-        start_addr = 0
-        for i in range(len(regions)):
-            start_addr = self.__process(start_addr, i, regions)
-        
-        if math.log2(start_addr) > width:
-            raise Exception("memory size larger than speicified address width")
-
-        return {
-            "granule": g,
-            "regions": regions
-        }
-
-class ForEachIndexObject(DataObject):
-    def __init__(self, name, record):
-        super().__init__(name, record)
-        self.steps = []
-        for k, v in record.items():
-            self.steps.append(DataObject.create(k, v))
-
-    def _parse(self, record):
-        super()._parse(record)
-    
-    def expand(self, param={}):
-        if "index" not in param:
-            raise Exception(f"'{type(self).__name__}' require parameter 'index'.")
-        obj = {}
-        for cond in self.steps:
-            obj.update(**cond.expand(param))
-        return obj
-    
-class RawObject(DataObject):
-    def __init__(self, name, record):
-        super().__init__(name, record)
-    
-    def _parse(self, record):
-        return super()._parse(record)
-
-    def expand(self, param={}):
-        return super().expand(param)
-
-class PlainOldObject(DataObject):
-    def __init__(self, name, record):
-        super().__init__(name, record)
-
-    def _parse(self, record):
-        return super()._parse(record)
-
-    def expand(self, param={}):
-        return super().expand(param)
-
-class RangedObject(DataObject):
-    def __init__(self, name, record):
-        super().__init__(name, record)
-    
-    def _parse(self, record):
-        super()._parse(record)
-
-    def expand(self, param={}):
-        if "range" not in self.ctrl_field:
-            raise Exception("RangedObject with ranged type must have 'range' field defined")
-        
-        out_lst = []
-        indices = self.ctrl_field["range"].get(param)
-        for i in indices:
-            param["index"] = i
-            out_lst.append(super().expand(param))
-        
-        return out_lst
-    
-def aligned(v, a):
-    return v & ~(a - 1)
-    
-class TemplateExpander:
-    def __init__(self, template_path, project_path, arch) -> None:
-        self.arch = arch
-        self.tbase_path = template_path.joinpath(arch)
-        self.pbase_path = project_path
-
-        self.__helper_fns = {
-            "align": aligned,
-            "hex": lambda x: hex(x)
-        }
-
-        self.__load_config()
-        self.__load_mappings()
-
-    def __load_config(self):
-        self.data = {}
-        cfg_file: Path = self.tbase_path.joinpath("config.json")
-        if not cfg_file.is_file():
-            raise Exception(f"config not found. ({cfg_file})")
-        
-        obj = json.loads(cfg_file.read_text())
-        for k, v in obj.items():
-            o = DataObject.create(k, v).expand()
-            self.data[k] = o
-
-    def __load_mappings(self):
-        self.mapping = {}
-        mapping_file: Path = self.tbase_path.joinpath("mappings")
-        if not mapping_file.is_file():
-            raise Exception(f"config not found. ({mapping_file})")
-        
-        with mapping_file.open() as f:
-            for l in f:
-                l = l.strip()
-
-                if not l:
-                    continue
-
-                src, dest = l.split("->")
-                src = src.strip()
-
-                if src in self.mapping:
-                    raise Exception(f"repeating entry ({src})")
-                
-                self.mapping[src] = dest.strip()
-
-    def render(self, selected = []):
-        for k, v in self.mapping.items():
-            src: Path = self.tbase_path.joinpath(k)
-            dest: Path = self.pbase_path.joinpath(v)
-            if (k not in selected):
-                continue
-
-            if not src.is_file():
-                continue
-
-            if not dest.parent.exists():
-                dest.parent.mkdir(parents=True)
-            
-            self.data["template"] = k
-            template = jinja2.Template(src.read_text(), trim_blocks=True)
-            template.globals.update(**self.__helper_fns)
-            out = template.render(data=self.data)
-
-            dest.write_text(out)
-
-            print(f"rendered: {k} -> {v}")
-
-import pprint
-
-def main():
-    parser = argparse.ArgumentParser()
-    parser.add_argument("selects", nargs="*")
-    parser.add_argument("--arch", default='i386')
-    parser.add_argument("-twd", "--template_dir", default=str(Path.cwd()))
-    parser.add_argument("-pwd", "--project_dir", default=str(Path.cwd()))
-
-    args = parser.parse_args()
-
-    expander = TemplateExpander(Path(args.template_dir), Path(args.project_dir), args.arch)
-    
-    expander.render(args.selects)
-    # pprint.pprint(expander.data)
-
-if __name__ == "__main__":
-    main()
\ No newline at end of file
diff --git a/lunaix-os/scripts/grub/config-grub.sh b/lunaix-os/scripts/grub/config-grub.sh
new file mode 100755 (executable)
index 0000000..311550b
--- /dev/null
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+
+cat "${SCRIPT_DIR}/GRUB_TEMPLATE" | envsubst > "$1"
\ No newline at end of file
diff --git a/lunaix-os/scripts/templates/i386/config.json b/lunaix-os/scripts/templates/i386/config.json
deleted file mode 100644 (file)
index b74d558..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-{
-    "exception": {
-        "@define": {
-            "syscall_iv": 33,
-            "iv_counts": 255
-        },
-        "ivdefs": {
-            "$type": "list",
-            "$range": "[0..*iv_counts]",
-
-            "iv": "{index}",
-    
-            "@foreach": {
-                "errcode": {
-                    "$type": "case_range_index",
-                    "$range": "[8,10..14,17]",
-    
-                    "@true": {
-                        "has_errcode": true
-                    },
-                    "@else": {
-                        "has_errcode": false
-                    }
-                },
-                "dpl": {
-                    "$type": "case_range_index",
-                    "$range": "[*syscall_iv]",
-    
-                    "@true": {
-                        "dpl": 3
-                    },
-                    "@else": {
-                        "dpl": 0
-                    }
-                }
-            }
-        }
-    },
-    "sys_mmap": {
-        "$type": "memory_map",
-        "@define": {
-            "page_mnts": 4,
-            "vms_mnts": 1
-        },
-
-        "width": 32,
-        "granule": {
-            "page": "0x1000",
-            "1M": "0x100000",
-            "4M": "0x400000",
-            "huge": "0x400000",
-            "1G": "0x40000000"
-        },
-        "regions": [
-            {
-                "name": "kstack_area",
-                "start": "1@1M",
-                "size": "3@1M",
-                "stk_align": 16
-            },
-            {
-                "name": "usr_exec",
-                "start": "4@1M",
-                "size": "512@1M"
-            },
-            {
-                "name": "usr_mmap"
-            },
-            {
-                "name": "usr_stack",
-                "size": "64@page",
-                "stk_align": 16
-            },
-            {
-                "name": "kernel_img",
-                "start": "3@1G",
-                "size": "16@4M",
-                "block": "1@page"
-            },
-            {
-                "$type": "list",
-                "$range": "[1..*page_mnts]",
-                "size": "1@page",
-                "name": "pg_mount_{index}"
-            },
-            {
-                "name": "pg_mount_var"
-            },
-            {
-                "name": "vmap",
-                "block": "1@huge"
-            },
-            {
-                "$type": "list",
-                "$range": "[1..*vms_mnts]",
-                "name": "vms_mount_{index}",
-                "size": "1@4M",
-                "block": "1@huge"
-            },
-            {
-                "name": "pd_ref",
-                "start": "1023@4M",
-                "size": "1@4M",
-                "block": "1@huge"
-            }
-        ]
-    }
-}
\ No newline at end of file
diff --git a/lunaix-os/scripts/templates/i386/i386_intrhnds.S.j2 b/lunaix-os/scripts/templates/i386/i386_intrhnds.S.j2
deleted file mode 100644 (file)
index 26067ac..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-/* Generated from {{ data["template"] }}. Do NOT modify */
-
-#define __ASM__
-.macro isr_template vector, no_error_code=1
-    .global _asm_isr\vector
-    .type _asm_isr\vector, @function
-    _asm_isr\vector:
-        .if \no_error_code
-            pushl $0x0
-        .endif
-        pushl $\vector
-        jmp interrupt_wrapper
-.endm
-.section .text
-{% for isrdef in data["exception"]["ivdefs"] %}
-{% if isrdef["has_errcode"] %}
-    isr_template {{ isrdef["iv"] }}, no_error_code=0
-{% else %}
-    isr_template {{ isrdef["iv"] }}, no_error_code=1
-{% endif %}
-{% endfor %}
diff --git a/lunaix-os/scripts/templates/i386/i386_isrdef.c.j2 b/lunaix-os/scripts/templates/i386/i386_isrdef.c.j2
deleted file mode 100644 (file)
index beec564..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-/* Generated from {{ data["template"] }}. Do NOT modify */
-
-#include <lunaix/types.h>
-#include <sys/i386_intr.h>
-
-#define IDT_INTERRUPT 0x70
-#define KERNEL_CS 0x8
-#define IDT_ATTR(dpl, type) (((type) << 5) | ((dpl & 3) << 13) | (1 << 15))
-#define IDT_ENTRY 256
-
-#define DECLARE_ISR(iv) extern void _asm_isr##iv();
-
-#define ISR_INSTALL(idt, iv, isr, dpl)                                   \
-    _idt[iv] = ((ptr_t)isr & 0xffff0000) | IDT_ATTR(dpl, IDT_INTERRUPT); \
-    _idt[iv] <<= 32;                                                     \
-    _idt[iv] |= (KERNEL_CS << 16) | ((ptr_t)isr & 0x0000ffff);           \
-
-u64_t _idt[IDT_ENTRY];
-u16_t _idt_limit = sizeof(_idt) - 1;
-
-{% for isrdef in data["exception"]["ivdefs"] -%}
-    DECLARE_ISR({{ isrdef["iv"] }})
-{% endfor %}
-
-void
-exception_install_handler()
-{
-{% for isrdef in data["exception"]["ivdefs"] %}
-    ISR_INSTALL(_idt, {{ isrdef["iv"] }}, _asm_isr{{ isrdef["iv"] }}, {{ isrdef["dpl"] }})
-{% endfor %}
-}
\ No newline at end of file
diff --git a/lunaix-os/scripts/templates/i386/mappings b/lunaix-os/scripts/templates/i386/mappings
deleted file mode 100644 (file)
index 04d7560..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-i386_isrdef.c.j2 -> arch/i386/exceptions/i386_isrdef.c
-i386_intrhnds.S.j2 -> arch/i386/exceptions/intrhnds.S
-mempart.h.j2 -> arch/i386/includes/sys/mm/mempart.h
\ No newline at end of file
diff --git a/lunaix-os/scripts/templates/i386/mempart.h.j2 b/lunaix-os/scripts/templates/i386/mempart.h.j2
deleted file mode 100644 (file)
index 01af898..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-#ifndef __LUNAIX_MEMPART_H
-#define __LUNAIX_MEMPART_H
-/* Physical Adress Space Partition */
-/* Generated from {{ data["template"] }}. Do NOT modify */
-
-{% for k, v in data["sys_mmap"]["granule"].items() %}
-#define MEM_{{ k.upper() }} {{ hex(v) }}UL
-{% endfor %}
-
-{% for region in data["sys_mmap"]["regions"] %}
-#define {{ region["name"].upper() }} {{ hex(region["start"]) }}UL
-#define {{ region["name"].upper() }}_SIZE {{ hex(region["size"]) }}UL
-{% if "stk_align" in region %}
-#define {{ region["name"].upper() }}_END {{ hex(align(region["start"] + region["size"] - 1, region["stk_align"])) }}UL
-{% else %}
-#define {{ region["name"].upper() }}_END {{ hex((region["start"] + region["size"] - 1)) }}UL
-{% endif %}
-
-{% endfor %}
-#endif
\ No newline at end of file