Rewrite the lunabuild toolchain with enhanced feature (#60)
[lunaix-os.git] / lunaix-os / scripts / build-tools / lbuild / build.py
1 import ast
2 from pathlib    import Path
3 from .common    import DirectoryTracker
4 from lib.utils  import ConfigAST, Schema
5
6 class LBuildImporter(ast.NodeTransformer):
7     ConfigImportFn = Schema(
8                         ast.Call, 
9                         func=Schema(ast.Name, id="import_"),
10                         args=[ast.Constant])
11     
12     ScopedAccess = Schema(
13                         ast.Attribute,
14                         value=ast.Name)
15     
16     def __init__(self, env, file):
17         super().__init__()
18         self.__parent = file.parent
19         self.__env = env
20
21         with file.open('r') as f:
22             subtree = ast.parse(f.read())
23         
24         self.__tree = [
25             DirectoryTracker.emit_enter(self.__parent),
26             *self.visit(subtree).body,
27             DirectoryTracker.emit_leave()
28         ]
29     
30     def tree(self):
31         return self.__tree
32     
33     def __gen_import_subtree(self, relpath, variants):    
34         block = []
35         for var in variants:
36             p = relpath/ var / "LBuild"
37             block += LBuildImporter(self.__env, p).tree()
38         
39         return ast.If(ast.Constant(True), block, [])
40
41     def visit_ImportFrom(self, node):
42         if ConfigAST.ConfigImport != node:
43             return node
44         
45         module = node.module
46         module = "" if not module else module
47         subpath = Path(*module.split('.'))
48         
49         p = self.__parent / subpath
50         return self.__gen_import_subtree(p, [x.name for x in node.names])
51     
52     def visit_Attribute(self, node):
53         if LBuildImporter.ScopedAccess != node:
54             return self.generic_visit(node)
55         
56         scope = node.value.id
57         subscope = node.attr
58
59         if scope not in self.__env.scopes:
60             return self.generic_visit(node)
61         
62         provider = self.__env.scopes[scope]
63         
64         return ast.Subscript(
65             value=ast.Name(provider.context_name(), ctx=node.value.ctx),
66             slice=ast.Constant(subscope),
67             ctx = node.ctx
68         )
69     
70     def visit_Expr(self, node):
71         val = node.value
72         if LBuildImporter.ConfigImportFn == val:
73             name = val.args[0].value
74             return self.__gen_import_subtree(self.__parent, [name])
75         
76         return self.generic_visit(node)
77
78 class BuildEnvironment:
79     def __init__(self):
80         self.scopes = {}
81     
82     def load(self, rootfile):
83         self.__exec = self.__load(Path(rootfile))
84
85     def register_scope(self, scope):
86         if scope.name in self.scopes:
87             raise Exception(f"{scope.name} already exists")
88         self.scopes[scope.name] = scope
89
90     def __load(self, rootfile):
91         module = ast.Module(
92                     LBuildImporter(self, rootfile).tree(), 
93                     type_ignores=[])
94
95         module = ast.fix_missing_locations(module)
96         return compile(module, rootfile, 'exec')
97     
98     def update(self):
99         g = {
100             "__LBUILD__": True,
101         }
102
103         for scope in self.scopes.values():
104             scope.reset()
105             g[scope.context_name()] = scope
106
107         DirectoryTracker.bind(g)
108         exec(self.__exec, g)