zero-build 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. zero/__init__.py +20 -0
  2. zero/analyzers/__init__.py +0 -0
  3. zero/analyzers/cycle_detector.py +146 -0
  4. zero/analyzers/stale_detector.py +167 -0
  5. zero/builder/__init__.py +0 -0
  6. zero/builder/builder.py +175 -0
  7. zero/cli/__init__.py +0 -0
  8. zero/cli/__main__.py +5 -0
  9. zero/cli/args.py +60 -0
  10. zero/cli/main.py +20 -0
  11. zero/compilers/__init__.py +6 -0
  12. zero/compilers/base.py +36 -0
  13. zero/compilers/clang.py +7 -0
  14. zero/compilers/clangxx.py +7 -0
  15. zero/compilers/gcc.py +110 -0
  16. zero/compilers/get.py +25 -0
  17. zero/compilers/gxx.py +7 -0
  18. zero/compilers/types.py +9 -0
  19. zero/errors/__init__.py +4 -0
  20. zero/errors/errors.py +19 -0
  21. zero/graph/__init__.py +0 -0
  22. zero/graph/constructor.py +244 -0
  23. zero/graph/nodes.py +125 -0
  24. zero/graph/printer.py +100 -0
  25. zero/graph/visitor.py +51 -0
  26. zero/interface/__init__.py +0 -0
  27. zero/interface/build.py +49 -0
  28. zero/interface/executable.py +14 -0
  29. zero/interface/headers.py +57 -0
  30. zero/interface/library.py +11 -0
  31. zero/interface/precomp_lib.py +21 -0
  32. zero/interface/shared_lib.py +12 -0
  33. zero/interface/source.py +27 -0
  34. zero/interface/static_lib.py +12 -0
  35. zero/interface/target.py +90 -0
  36. zero/interface/types.py +4 -0
  37. zero/orchestrator/__init__.py +1 -0
  38. zero/orchestrator/config.py +26 -0
  39. zero/orchestrator/executor.py +14 -0
  40. zero/orchestrator/orchestrator.py +204 -0
  41. zero/py.typed +0 -0
  42. zero/reporter/__init__.py +2 -0
  43. zero/reporter/get.py +9 -0
  44. zero/reporter/reporter.py +43 -0
  45. zero/utils/__init__.py +1 -0
  46. zero/utils/module_loader.py +36 -0
  47. zero_build-0.1.0.dist-info/METADATA +64 -0
  48. zero_build-0.1.0.dist-info/RECORD +51 -0
  49. zero_build-0.1.0.dist-info/WHEEL +5 -0
  50. zero_build-0.1.0.dist-info/entry_points.txt +2 -0
  51. zero_build-0.1.0.dist-info/top_level.txt +1 -0
zero/__init__.py ADDED
@@ -0,0 +1,20 @@
1
+ # Main API classes
2
+ from pathlib import Path
3
+
4
+ from src.zero.interface.build import Build
5
+ from src.zero.interface.source import Source
6
+ from src.zero.interface.executable import Executable
7
+ from src.zero.interface.static_lib import StaticLibrary
8
+ from src.zero.interface.shared_lib import SharedLibrary
9
+ from src.zero.interface.precomp_lib import PreCompiledLibrary
10
+
11
+ __all__ = [
12
+ "Path",
13
+
14
+ "Build",
15
+ "Source",
16
+ "Executable",
17
+ "StaticLibrary",
18
+ "SharedLibrary",
19
+ "PreCompiledLibrary"
20
+ ]
File without changes
@@ -0,0 +1,146 @@
1
+ from src.zero.graph.nodes import Node
2
+ from src.zero.graph.visitor import NodeVisitor
3
+ from src.zero.graph.nodes import *
4
+
5
+ from src.zero.reporter import getReporter
6
+
7
+
8
+ class CycleDetector(NodeVisitor):
9
+
10
+
11
+ def __init__(self) -> None:
12
+ self.visited_nodes: set[Node] = set()
13
+ self.tracked_nodes: list[Node] = []
14
+ self.is_cycle_detected: bool = False
15
+
16
+ self.reporter = getReporter()
17
+
18
+
19
+ def getCycle(self, node: Node) -> list[Node]:
20
+
21
+ self.is_cycle_detected = True
22
+
23
+ cycle = []
24
+
25
+ for i in range(len(self.tracked_nodes)):
26
+ if self.tracked_nodes[i] == node:
27
+ cycle = self.tracked_nodes[i:]
28
+ cycle.append(node)
29
+ break
30
+
31
+ return cycle
32
+
33
+
34
+ def visit(self, node: Node):
35
+
36
+ if self.is_cycle_detected:
37
+ return
38
+
39
+ return super().visit(node)
40
+
41
+
42
+ def visitRootNode(self, node: RootNode):
43
+
44
+ if node not in self.visited_nodes:
45
+ self.visited_nodes.add(node)
46
+
47
+ self.tracked_nodes.append(node)
48
+
49
+ for t in node.targets:
50
+ self.visit(t)
51
+
52
+ self.tracked_nodes.pop()
53
+
54
+
55
+ def visitExecutableNode(self, node: ExecutableNode):
56
+ if node not in self.visited_nodes:
57
+ self.visited_nodes.add(node)
58
+
59
+ if node in self.tracked_nodes:
60
+ self.getCycle(node)
61
+ return
62
+
63
+ self.tracked_nodes.append(node)
64
+
65
+ for lib in node.linked_libraries:
66
+ self.visit(lib)
67
+
68
+ for src in node.sources:
69
+ self.visit(src)
70
+
71
+ self.tracked_nodes.pop()
72
+
73
+
74
+ def visitStaticLibraryNode(self, node: StaticLibraryNode):
75
+ if node not in self.visited_nodes:
76
+ self.visited_nodes.add(node)
77
+
78
+ if node in self.tracked_nodes:
79
+ self.getCycle(node)
80
+ return
81
+
82
+ self.tracked_nodes.append(node)
83
+
84
+ for lib in node.linked_libraries:
85
+ self.visit(lib)
86
+
87
+ for src in node.sources:
88
+ self.visit(src)
89
+
90
+ self.tracked_nodes.pop()
91
+
92
+
93
+ def visitSharedLibraryNode(self, node: SharedLibraryNode):
94
+ if node not in self.visited_nodes:
95
+ self.visited_nodes.add(node)
96
+
97
+ if node in self.tracked_nodes:
98
+ self.getCycle(node)
99
+ return
100
+
101
+ self.tracked_nodes.append(node)
102
+
103
+ for lib in node.linked_libraries:
104
+ self.visit(lib)
105
+
106
+ for src in node.sources:
107
+ self.visit(src)
108
+
109
+ self.tracked_nodes.pop()
110
+
111
+
112
+ def visitPreCompiledLibraryNode(self, node: PreCompiledLibraryNode):
113
+ if node not in self.visited_nodes:
114
+ self.visited_nodes.add(node)
115
+
116
+
117
+ def visitSourceNode(self, node: SourceNode):
118
+ if node not in self.visited_nodes:
119
+ self.visited_nodes.add(node)
120
+
121
+ if node in self.tracked_nodes:
122
+ self.getCycle(node)
123
+ return
124
+
125
+ self.tracked_nodes.append(node)
126
+
127
+ for header in node.deps:
128
+ self.visit(header)
129
+
130
+ self.tracked_nodes.pop()
131
+
132
+
133
+ def visitHeaderNode(self, node: HeaderNode):
134
+ if node not in self.visited_nodes:
135
+ self.visited_nodes.add(node)
136
+
137
+ if node in self.tracked_nodes:
138
+ self.getCycle(node)
139
+ return
140
+
141
+ self.tracked_nodes.append(node)
142
+
143
+ for header in node.deps:
144
+ self.visit(header)
145
+
146
+ self.tracked_nodes.pop()
@@ -0,0 +1,167 @@
1
+ from src.zero.graph.nodes import Node
2
+ from src.zero.graph.visitor import NodeVisitor
3
+ from src.zero.graph.nodes import *
4
+
5
+ from src.zero.reporter import getReporter
6
+
7
+
8
+ def markStale(node: Node):
9
+ setattr(node, "_stale", True)
10
+
11
+
12
+ def isStale(node: Node) -> bool:
13
+ return hasattr(node, "_stale")
14
+
15
+
16
+ class StaleDetector(NodeVisitor):
17
+
18
+
19
+ def __init__(self) -> None:
20
+ self.visited_nodes: set[Node] = set()
21
+ self.current_source_outpath: Path | None = None
22
+ self.stale_count = 0
23
+
24
+
25
+ def markStale(self, node: Node):
26
+ if not hasattr(node, "_stale"):
27
+ setattr(node, "_stale", True)
28
+ self.stale_count += 1
29
+
30
+
31
+ def getStaleCount(self) -> int:
32
+ return self.stale_count
33
+
34
+
35
+ def visitRootNode(self, node: RootNode):
36
+
37
+ if node not in self.visited_nodes:
38
+ self.visited_nodes.add(node)
39
+ else:
40
+ return
41
+
42
+
43
+ for t in node.targets:
44
+ self.visit(t)
45
+
46
+
47
+
48
+ def visitExecutableNode(self, node: ExecutableNode):
49
+
50
+ if node not in self.visited_nodes:
51
+ self.visited_nodes.add(node)
52
+ else:
53
+ return
54
+
55
+
56
+ for lib in node.linked_libraries:
57
+ self.visit(lib)
58
+
59
+ if isStale(lib):
60
+ self.markStale(node)
61
+
62
+ for src in node.sources:
63
+ self.visit(src)
64
+
65
+ if isStale(src):
66
+ self.markStale(node)
67
+
68
+
69
+ def visitStaticLibraryNode(self, node: StaticLibraryNode):
70
+
71
+ if node not in self.visited_nodes:
72
+ self.visited_nodes.add(node)
73
+ else:
74
+ return
75
+
76
+ for lib in node.linked_libraries:
77
+ self.visit(lib)
78
+
79
+ if isStale(lib):
80
+ self.markStale(node)
81
+
82
+ for src in node.sources:
83
+ self.visit(src)
84
+
85
+ if isStale(src):
86
+ self.markStale(node)
87
+
88
+
89
+ def visitSharedLibraryNode(self, node: SharedLibraryNode):
90
+
91
+ if node not in self.visited_nodes:
92
+ self.visited_nodes.add(node)
93
+ else:
94
+ return
95
+
96
+
97
+ for lib in node.linked_libraries:
98
+ self.visit(lib)
99
+
100
+ if isStale(lib):
101
+ self.markStale(node)
102
+
103
+ for src in node.sources:
104
+ self.visit(src)
105
+
106
+ if isStale(src):
107
+ self.markStale(node)
108
+
109
+
110
+ def visitPreCompiledLibraryNode(self, node: PreCompiledLibraryNode):
111
+
112
+ if node not in self.visited_nodes:
113
+ self.visited_nodes.add(node)
114
+ else:
115
+ return
116
+
117
+
118
+
119
+ def visitSourceNode(self, node: SourceNode):
120
+
121
+ if node not in self.visited_nodes:
122
+ self.visited_nodes.add(node)
123
+ else:
124
+ return
125
+
126
+
127
+ if not node.outpath.exists():
128
+ self.markStale(node)
129
+ return
130
+
131
+ if node.outpath.stat().st_mtime < node.filepath.stat().st_mtime:
132
+ self.markStale(node)
133
+ return
134
+
135
+ self.current_source_outpath = node.outpath
136
+
137
+ for header in node.deps:
138
+ self.visit(header)
139
+
140
+ if isStale(header):
141
+ self.markStale(node)
142
+ return
143
+
144
+ self.current_source_outpath = None
145
+
146
+
147
+ def visitHeaderNode(self, node: HeaderNode):
148
+
149
+ if node not in self.visited_nodes:
150
+ self.visited_nodes.add(node)
151
+ else:
152
+ return
153
+
154
+ if self.current_source_outpath is None:
155
+ raise RuntimeError("Source path should not have been None. Unexpected.")
156
+
157
+ if self.current_source_outpath.stat().st_mtime < node.filepath.stat().st_mtime:
158
+ markStale(node)
159
+ return
160
+
161
+ for header in node.deps:
162
+ self.visit(header)
163
+
164
+ if isStale(header):
165
+ markStale(node)
166
+ return
167
+
File without changes
@@ -0,0 +1,175 @@
1
+ from src.zero.graph.nodes import *
2
+ from src.zero.graph.nodes import SharedLibraryNode
3
+ from src.zero.graph.visitor import NodeVisitor
4
+
5
+ from src.zero.compilers import BaseCompiler
6
+
7
+ from src.zero.orchestrator.config import BuildConfig
8
+ from src.zero.reporter import getReporter
9
+ from src.zero.analyzers.stale_detector import isStale
10
+
11
+
12
+ class Builder(NodeVisitor):
13
+
14
+ def __init__(self, config: BuildConfig) -> None:
15
+
16
+ super().__init__()
17
+
18
+ self.fresh_build = config.fresh_build
19
+
20
+ self.compiling_shared_lib = False
21
+
22
+ self.include_dirs: list[Path] = []
23
+ self.visited_nodes: set[object] = set()
24
+ self.current_target_arguments: list[str] = []
25
+
26
+ self.reporter = getReporter()
27
+
28
+ self.current_compiler: BaseCompiler
29
+
30
+
31
+ def detectStaleness(self, node: Node) -> bool:
32
+ if self.fresh_build:
33
+ return True
34
+ else:
35
+ return isStale(node)
36
+
37
+
38
+ def visitRootNode(self, node: RootNode):
39
+ self.reporter.startPhase("Compilation", "Compiling")
40
+
41
+ for target in node.targets:
42
+ self.current_compiler = node.target_compilers[target]
43
+ self.visit(target)
44
+
45
+ self.reporter.endPhase("Build complete")
46
+
47
+
48
+ def visitStaticLibraryNode(self, node: StaticLibraryNode):
49
+ if node in self.visited_nodes:
50
+ return
51
+
52
+ if not self.detectStaleness(node):
53
+ return
54
+
55
+ include_dirs = []
56
+
57
+ for lib in node.linked_libraries:
58
+ self.visit(lib)
59
+ include_dirs.extend(lib.public_headers)
60
+
61
+ include_dirs.extend(node.public_headers)
62
+ include_dirs.extend(node.private_headers)
63
+
64
+ self.include_dirs = include_dirs
65
+
66
+ self.current_target_arguments = node.arguments
67
+
68
+ for deps in node.sources:
69
+ self.visit(deps)
70
+
71
+ self.current_compiler.buildStaticLib([n.outpath for n in node.sources], node.libpath)
72
+
73
+ self.reporter.taskDone("Link ", f"{node.libpath.name}")
74
+
75
+ self.visited_nodes.add(node)
76
+
77
+
78
+ def visitSharedLibraryNode(self, node: SharedLibraryNode):
79
+ if node in self.visited_nodes:
80
+ return
81
+
82
+ if not self.detectStaleness(node):
83
+ return
84
+
85
+ self.compiling_shared_lib = True
86
+
87
+ include_dirs = []
88
+
89
+ for lib in node.linked_libraries:
90
+ self.visit(lib)
91
+ include_dirs.extend(lib.public_headers)
92
+
93
+ include_dirs.extend(node.public_headers)
94
+ include_dirs.extend(node.private_headers)
95
+
96
+ self.include_dirs = include_dirs
97
+
98
+ self.current_target_arguments = node.arguments
99
+
100
+ for deps in node.sources:
101
+ self.visit(deps)
102
+
103
+ self.current_compiler.buildSharedLib([n.outpath for n in node.sources], [l.libpath for l in node.linked_libraries], node.libpath)
104
+
105
+ self.compiling_shared_lib = False
106
+
107
+ self.reporter.taskDone("Link ", f"{node.libpath.name}")
108
+
109
+ self.visited_nodes.add(node)
110
+
111
+
112
+ def visitPreCompiledLibraryNode(self, node: PreCompiledLibraryNode):
113
+
114
+ if not node.libpath.exists():
115
+ raise RuntimeError(f"Could not find pre-compiled library: {node.libpath}")
116
+
117
+ self.reporter.taskDone("Found", f"{node.libpath}")
118
+
119
+
120
+ def visitExecutableNode(self, node: ExecutableNode):
121
+ if node in self.visited_nodes:
122
+ return
123
+
124
+ if not self.detectStaleness(node):
125
+ return
126
+
127
+ include_dirs = []
128
+
129
+ for lib in node.linked_libraries:
130
+ self.visit(lib)
131
+ include_dirs.extend(lib.public_headers)
132
+
133
+ include_dirs.extend(node.private_headers)
134
+
135
+ self.include_dirs = include_dirs
136
+
137
+ self.current_target_arguments = node.arguments
138
+
139
+ for deps in node.sources:
140
+ self.visit(deps)
141
+
142
+ self.current_compiler.buildExecutable([n.outpath for n in node.sources], [n.libpath for n in node.linked_libraries], node.targetpath)
143
+
144
+ self.reporter.taskDone("Link ", f"{node.targetpath.name}")
145
+
146
+ self.visited_nodes.add(node)
147
+
148
+
149
+ def visitSourceNode(self, node: SourceNode):
150
+
151
+ if node in self.visited_nodes:
152
+ return
153
+
154
+ if not self.detectStaleness(node):
155
+ return
156
+
157
+ for deps in node.deps:
158
+ self.visit(deps)
159
+
160
+ self.current_compiler.buildFile(
161
+ node.filepath,
162
+ node.outpath,
163
+ for_shared=self.compiling_shared_lib,
164
+ include_dirs=self.include_dirs,
165
+ arguments=self.current_target_arguments
166
+ )
167
+
168
+ self.reporter.taskDone("Built", f"{node.filepath}")
169
+
170
+ self.visited_nodes.add(node)
171
+
172
+
173
+ def visitHeaderNode(self, node: HeaderNode):
174
+ for deps in node.deps:
175
+ self.visit(deps)
zero/cli/__init__.py ADDED
File without changes
zero/cli/__main__.py ADDED
@@ -0,0 +1,5 @@
1
+
2
+ from .main import main
3
+
4
+ if __name__ == "__main__":
5
+ main()
zero/cli/args.py ADDED
@@ -0,0 +1,60 @@
1
+ import argparse
2
+ import sys
3
+
4
+
5
+ def add_make_command(subparsers: argparse._SubParsersAction) -> None:
6
+ make_parser = subparsers.add_parser(
7
+ "make",
8
+ help="Build targets",
9
+ )
10
+ make_parser.add_argument(
11
+ "--fresh",
12
+ action="store_true",
13
+ help="Force a clean rebuild",
14
+ )
15
+ make_parser.add_argument(
16
+ "target",
17
+ nargs="*",
18
+ default=None,
19
+ help="Target to build",
20
+ )
21
+
22
+
23
+ def add_run_command(subparsers: argparse._SubParsersAction) -> None:
24
+ run_parser = subparsers.add_parser(
25
+ "run",
26
+ help="Run executables",
27
+ )
28
+ run_parser.add_argument(
29
+ "--fresh",
30
+ action="store_true",
31
+ help="Force a rebuild before running",
32
+ )
33
+ run_parser.add_argument(
34
+ "executable",
35
+ help="Path or name of the executable",
36
+ )
37
+ run_parser.add_argument(
38
+ "executable_args",
39
+ nargs=argparse.REMAINDER,
40
+ help="Arguments passed directly to the executable",
41
+ )
42
+
43
+
44
+ def setupParser() -> argparse.ArgumentParser:
45
+ parser = argparse.ArgumentParser(prog="zero", description="Zero build system")
46
+ subparsers = parser.add_subparsers(dest="command", required=True)
47
+
48
+ add_make_command(subparsers)
49
+ add_run_command(subparsers)
50
+
51
+ return parser
52
+
53
+
54
+ def parseArguments(
55
+ parser: argparse.ArgumentParser, args: list[str] | None = None
56
+ ) -> argparse.Namespace:
57
+ if args is None:
58
+ args = sys.argv[1:]
59
+
60
+ return parser.parse_args(args)
zero/cli/main.py ADDED
@@ -0,0 +1,20 @@
1
+ from src.zero.orchestrator import Orchestrator
2
+ from src.zero.cli.args import setupParser, parseArguments
3
+
4
+
5
+ def main():
6
+ parser = setupParser()
7
+ args = parseArguments(parser)
8
+
9
+ orchestrator = Orchestrator()
10
+
11
+ if args.command == "make":
12
+ if len(args.target) == 0:
13
+ orchestrator.makeBuild(fresh=args.fresh)
14
+ else:
15
+ orchestrator.makeTargets(args.target, fresh=args.fresh)
16
+
17
+ elif args.command == "run":
18
+ orchestrator.runExecutable(args.executable, args.executable_args, fresh=args.fresh)
19
+
20
+
@@ -0,0 +1,6 @@
1
+ from .base import BaseCompiler
2
+
3
+ from .gcc import GccCompiler
4
+ from .gxx import GxxCompiler
5
+ from .clang import ClangCompiler
6
+ from .clangxx import ClangxxCompiler
zero/compilers/base.py ADDED
@@ -0,0 +1,36 @@
1
+ from abc import ABC, abstractmethod
2
+ from pathlib import Path
3
+
4
+
5
+ class BaseCompiler(ABC):
6
+ """
7
+ Base class for Compilers utilizing pathlib.Path objects.
8
+ """
9
+
10
+ def __init__(self) -> None:
11
+ super().__init__()
12
+
13
+
14
+ @abstractmethod
15
+ def getDependencies(self, filepath: Path, *, include_dirs: list[Path] = []) -> list[Path]:
16
+ pass
17
+
18
+
19
+ @abstractmethod
20
+ def buildFile(self, filepath: Path, outfile: Path, *, for_shared = False, include_dirs: list[Path] = [], arguments: list[str] = []) -> None:
21
+ pass
22
+
23
+
24
+ @abstractmethod
25
+ def buildStaticLib(self, objects: list[Path], outfile: Path) -> None:
26
+ pass
27
+
28
+
29
+ @abstractmethod
30
+ def buildSharedLib(self, objects: list[Path], libraries: list[Path], outfile: Path) -> None:
31
+ pass
32
+
33
+
34
+ @abstractmethod
35
+ def buildExecutable(self, objects: list[Path], libraries: list[Path], outfile: Path) -> None:
36
+ pass
@@ -0,0 +1,7 @@
1
+ from .gcc import GccCompiler
2
+
3
+ class ClangCompiler(GccCompiler):
4
+
5
+ def __init__(self) -> None:
6
+ super().__init__()
7
+ self.compiler_binary = self.linker_binary = "clang"
@@ -0,0 +1,7 @@
1
+ from .gcc import GccCompiler
2
+
3
+ class ClangxxCompiler(GccCompiler):
4
+
5
+ def __init__(self) -> None:
6
+ super().__init__()
7
+ self.compiler_binary = self.linker_binary = "clang++"