buildgen 0.1.1__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.
buildgen/__init__.py ADDED
@@ -0,0 +1,23 @@
1
+ """buildgen: Build system generator package.
2
+
3
+ Supports generating Makefiles, CMakeLists.txt, and direct compilation.
4
+
5
+ For additional components, import from submodules:
6
+ from buildgen.makefile.variables import Var, SVar, IVar, CVar, AVar
7
+ from buildgen.makefile.functions import makefile_wildcard, makefile_patsubst
8
+ from buildgen.cmake.variables import CMakeVar, CMakeCacheVar
9
+ from buildgen.common.project import TargetConfig, DependencyConfig
10
+ """
11
+
12
+ __version__ = "0.1.1"
13
+
14
+ # Core API - minimal exports for early development flexibility
15
+ from buildgen.common.project import ProjectConfig
16
+ from buildgen.makefile.generator import MakefileGenerator
17
+ from buildgen.cmake.generator import CMakeListsGenerator
18
+
19
+ __all__ = [
20
+ "ProjectConfig",
21
+ "MakefileGenerator",
22
+ "CMakeListsGenerator",
23
+ ]
buildgen/cli.py ADDED
@@ -0,0 +1,369 @@
1
+ """Unified CLI entry point for buildgen."""
2
+
3
+ import argparse
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ from buildgen import __version__
8
+ from buildgen.makefile.cli import add_makefile_subparsers
9
+ from buildgen.cmake.cli import add_cmake_subparsers
10
+ from buildgen.common.project import ProjectConfig
11
+
12
+
13
+ def cmd_project_generate(args: argparse.Namespace) -> None:
14
+ """Generate build files from project configuration."""
15
+ config = ProjectConfig.load(args.config)
16
+
17
+ outputs = []
18
+ if args.makefile or args.all:
19
+ makefile_path = args.makefile_output or "Makefile"
20
+ config.generate_makefile(makefile_path)
21
+ outputs.append(f"Makefile: {makefile_path}")
22
+
23
+ if args.cmake or args.all:
24
+ cmake_path = args.cmake_output or "CMakeLists.txt"
25
+ config.generate_cmake(cmake_path)
26
+ outputs.append(f"CMakeLists.txt: {cmake_path}")
27
+
28
+ if outputs:
29
+ print(f"Generated from {args.config}:")
30
+ for output in outputs:
31
+ print(f" {output}")
32
+ else:
33
+ print("No output format specified. Use --makefile, --cmake, or --all")
34
+
35
+
36
+ BUILD_TYPES = {
37
+ "executable": "Single executable (src/main.cpp)",
38
+ "static": "Static library (src/lib.cpp, include/)",
39
+ "shared": "Shared library with -fPIC",
40
+ "header-only": "Header-only/interface library",
41
+ "library-with-tests": "Static library with test executable",
42
+ "app-with-lib": "Executable linked to internal static library",
43
+ "full": "Library + app + tests with Threads dependency",
44
+ }
45
+
46
+
47
+ def cmd_project_types(args: argparse.Namespace) -> None:
48
+ """List available project build types."""
49
+ print("Available build types:\n")
50
+ for name, description in BUILD_TYPES.items():
51
+ print(f" {name:<20} {description}")
52
+
53
+
54
+ def cmd_project_init(args: argparse.Namespace) -> None:
55
+ """Create a sample project configuration file."""
56
+ from buildgen.common.project import TargetConfig, DependencyConfig
57
+
58
+ output = Path(args.output)
59
+ ext = output.suffix.lower()
60
+ name = args.name or "myproject"
61
+ build_type = args.type
62
+
63
+ targets: list[TargetConfig] = []
64
+ dependencies: list[DependencyConfig] = []
65
+
66
+ if build_type == "executable":
67
+ targets = [
68
+ TargetConfig(
69
+ name=name,
70
+ target_type="executable",
71
+ sources=["src/main.cpp"],
72
+ install=True,
73
+ ),
74
+ ]
75
+
76
+ elif build_type == "static":
77
+ targets = [
78
+ TargetConfig(
79
+ name=name,
80
+ target_type="static",
81
+ sources=["src/lib.cpp"],
82
+ include_dirs=["include"],
83
+ install=True,
84
+ ),
85
+ ]
86
+
87
+ elif build_type == "shared":
88
+ targets = [
89
+ TargetConfig(
90
+ name=name,
91
+ target_type="shared",
92
+ sources=["src/lib.cpp"],
93
+ include_dirs=["include"],
94
+ compile_options=["-fPIC"],
95
+ install=True,
96
+ ),
97
+ ]
98
+
99
+ elif build_type == "header-only":
100
+ targets = [
101
+ TargetConfig(
102
+ name=name,
103
+ target_type="interface",
104
+ sources=[],
105
+ include_dirs=["include"],
106
+ install=True,
107
+ ),
108
+ ]
109
+
110
+ elif build_type == "library-with-tests":
111
+ targets = [
112
+ TargetConfig(
113
+ name=name,
114
+ target_type="static",
115
+ sources=["src/lib.cpp"],
116
+ include_dirs=["include"],
117
+ install=True,
118
+ ),
119
+ TargetConfig(
120
+ name=f"{name}_tests",
121
+ target_type="executable",
122
+ sources=["tests/test_main.cpp"],
123
+ include_dirs=["include"],
124
+ link_libraries=[name],
125
+ ),
126
+ ]
127
+
128
+ elif build_type == "app-with-lib":
129
+ targets = [
130
+ TargetConfig(
131
+ name=f"{name}_lib",
132
+ target_type="static",
133
+ sources=["src/lib.cpp"],
134
+ include_dirs=["include"],
135
+ ),
136
+ TargetConfig(
137
+ name=name,
138
+ target_type="executable",
139
+ sources=["src/main.cpp"],
140
+ link_libraries=[f"{name}_lib"],
141
+ install=True,
142
+ ),
143
+ ]
144
+
145
+ elif build_type == "full":
146
+ dependencies = [
147
+ DependencyConfig(name="Threads"),
148
+ ]
149
+ targets = [
150
+ TargetConfig(
151
+ name=f"{name}_lib",
152
+ target_type="static",
153
+ sources=["src/lib.cpp"],
154
+ include_dirs=["include"],
155
+ install=True,
156
+ ),
157
+ TargetConfig(
158
+ name=name,
159
+ target_type="executable",
160
+ sources=["src/main.cpp"],
161
+ link_libraries=[f"{name}_lib", "Threads::Threads"],
162
+ install=True,
163
+ ),
164
+ TargetConfig(
165
+ name=f"{name}_tests",
166
+ target_type="executable",
167
+ sources=["tests/test_main.cpp"],
168
+ include_dirs=["include"],
169
+ link_libraries=[f"{name}_lib"],
170
+ ),
171
+ ]
172
+
173
+ sample = ProjectConfig(
174
+ name=name,
175
+ version="1.0.0",
176
+ description=f"A {build_type} project",
177
+ cxx_standard=17,
178
+ compile_options=["-Wall", "-Wextra"],
179
+ targets=targets,
180
+ dependencies=dependencies,
181
+ )
182
+
183
+ if ext in (".yaml", ".yml"):
184
+ sample.to_yaml(output)
185
+ else:
186
+ sample.to_json(output)
187
+
188
+ print(f"Created {build_type} project configuration: {output}")
189
+
190
+
191
+ def add_project_subparsers(subparsers: argparse._SubParsersAction) -> None:
192
+ """Add project subcommand parsers."""
193
+ project_parser = subparsers.add_parser(
194
+ "project",
195
+ help="Generate build files from project configuration (JSON/YAML)",
196
+ )
197
+
198
+ project_subparsers = project_parser.add_subparsers(
199
+ dest="project_command", help="Project commands"
200
+ )
201
+
202
+ # project generate
203
+ gen_parser = project_subparsers.add_parser(
204
+ "generate",
205
+ help="Generate Makefile and/or CMakeLists.txt from config",
206
+ )
207
+ gen_parser.add_argument(
208
+ "-c",
209
+ "--config",
210
+ required=True,
211
+ help="Path to project configuration file (JSON or YAML)",
212
+ )
213
+ gen_parser.add_argument(
214
+ "--makefile",
215
+ action="store_true",
216
+ help="Generate Makefile",
217
+ )
218
+ gen_parser.add_argument(
219
+ "--makefile-output",
220
+ default=None,
221
+ help="Output path for Makefile (default: Makefile)",
222
+ )
223
+ gen_parser.add_argument(
224
+ "--cmake",
225
+ action="store_true",
226
+ help="Generate CMakeLists.txt",
227
+ )
228
+ gen_parser.add_argument(
229
+ "--cmake-output",
230
+ default=None,
231
+ help="Output path for CMakeLists.txt (default: CMakeLists.txt)",
232
+ )
233
+ gen_parser.add_argument(
234
+ "--all",
235
+ action="store_true",
236
+ help="Generate both Makefile and CMakeLists.txt",
237
+ )
238
+ gen_parser.set_defaults(func=cmd_project_generate)
239
+
240
+ # project types
241
+ types_parser = project_subparsers.add_parser(
242
+ "types",
243
+ help="List available project build types",
244
+ )
245
+ types_parser.set_defaults(func=cmd_project_types)
246
+
247
+ # project init
248
+ init_parser = project_subparsers.add_parser(
249
+ "init",
250
+ help="Create a sample project configuration file",
251
+ formatter_class=argparse.RawDescriptionHelpFormatter,
252
+ epilog="Use 'buildgen project types' to list available build types.",
253
+ )
254
+ init_parser.add_argument(
255
+ "-o",
256
+ "--output",
257
+ default="project.json",
258
+ help="Output path for configuration file (default: project.json)",
259
+ )
260
+ init_parser.add_argument(
261
+ "-n",
262
+ "--name",
263
+ help="Project name (default: myproject)",
264
+ )
265
+ init_parser.add_argument(
266
+ "-t",
267
+ "--type",
268
+ choices=list(BUILD_TYPES.keys()),
269
+ default="executable",
270
+ help="Project type template (default: executable)",
271
+ )
272
+ init_parser.set_defaults(func=cmd_project_init)
273
+
274
+
275
+ def create_parser() -> argparse.ArgumentParser:
276
+ """Create the main CLI argument parser."""
277
+ parser = argparse.ArgumentParser(
278
+ prog="buildgen",
279
+ description="Build system generator - Makefile, CMake, and more",
280
+ formatter_class=argparse.RawDescriptionHelpFormatter,
281
+ epilog="""
282
+ Examples:
283
+ # Generate a Makefile
284
+ %(prog)s makefile generate -o Makefile \\
285
+ --include-dirs /usr/local/include --ldlibs pthread \\
286
+ --targets "all:main.o:" --phony all clean
287
+
288
+ # Direct compilation (Makefile-style)
289
+ %(prog)s makefile build myprogram --cppfiles main.cpp utils.cpp \\
290
+ --include-dirs /usr/local/include --ldlibs pthread
291
+
292
+ # Generate CMakeLists.txt
293
+ %(prog)s cmake generate -o CMakeLists.txt --project myproject \\
294
+ --cxx-standard 17 --executables "myapp:main.cpp utils.cpp"
295
+
296
+ # Build with CMake
297
+ %(prog)s cmake build -S . -B build --build-type Release
298
+
299
+ # Generate from project config (define once, generate both)
300
+ %(prog)s project init -o project.json
301
+ %(prog)s project generate -c project.json --all
302
+
303
+ Note: Escape dollar signs with backslash when passing Makefile/CMake variables via CLI.
304
+ """,
305
+ )
306
+
307
+ parser.add_argument(
308
+ "-V", "--version", action="version", version=f"%(prog)s {__version__}"
309
+ )
310
+
311
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
312
+
313
+ # Add makefile subcommands
314
+ add_makefile_subparsers(subparsers)
315
+
316
+ # Add cmake subcommands
317
+ add_cmake_subparsers(subparsers)
318
+
319
+ # Add project subcommands
320
+ add_project_subparsers(subparsers)
321
+
322
+ return parser
323
+
324
+
325
+ def main() -> None:
326
+ """Main CLI entry point."""
327
+ parser = create_parser()
328
+ args = parser.parse_args()
329
+
330
+ if not args.command:
331
+ parser.print_help()
332
+ sys.exit(1)
333
+
334
+ # Handle makefile subcommands
335
+ if args.command == "makefile":
336
+ if not hasattr(args, "makefile_command") or not args.makefile_command:
337
+ parser.parse_args(["makefile", "--help"])
338
+ elif hasattr(args, "func"):
339
+ try:
340
+ args.func(args)
341
+ except Exception as e:
342
+ print(f"Error: {e}", file=sys.stderr)
343
+ sys.exit(1)
344
+
345
+ # Handle cmake subcommands
346
+ elif args.command == "cmake":
347
+ if not hasattr(args, "cmake_command") or not args.cmake_command:
348
+ parser.parse_args(["cmake", "--help"])
349
+ elif hasattr(args, "func"):
350
+ try:
351
+ args.func(args)
352
+ except Exception as e:
353
+ print(f"Error: {e}", file=sys.stderr)
354
+ sys.exit(1)
355
+
356
+ # Handle project subcommands
357
+ elif args.command == "project":
358
+ if not hasattr(args, "project_command") or not args.project_command:
359
+ parser.parse_args(["project", "--help"])
360
+ elif hasattr(args, "func"):
361
+ try:
362
+ args.func(args)
363
+ except Exception as e:
364
+ print(f"Error: {e}", file=sys.stderr)
365
+ sys.exit(1)
366
+
367
+
368
+ if __name__ == "__main__":
369
+ main()
@@ -0,0 +1,33 @@
1
+ """CMake generation and building support."""
2
+
3
+ from buildgen.cmake.variables import (
4
+ CMakeVar,
5
+ CMakeCacheVar,
6
+ CMakeOption,
7
+ CMakeEnvVar,
8
+ cmake_var,
9
+ cmake_env_var,
10
+ cmake_cache_var,
11
+ cmake_bool,
12
+ )
13
+ from buildgen.cmake.generator import CMakeListsGenerator, CMakeWriter
14
+ from buildgen.cmake.builder import CMakeBuilder
15
+ from buildgen.cmake.functions import Cm
16
+
17
+ __all__ = [
18
+ # Variables
19
+ "CMakeVar",
20
+ "CMakeCacheVar",
21
+ "CMakeOption",
22
+ "CMakeEnvVar",
23
+ "cmake_var",
24
+ "cmake_env_var",
25
+ "cmake_cache_var",
26
+ "cmake_bool",
27
+ # Generator
28
+ "CMakeListsGenerator",
29
+ "CMakeWriter",
30
+ "CMakeBuilder",
31
+ # Functions
32
+ "Cm",
33
+ ]
@@ -0,0 +1,247 @@
1
+ """CMake builder class for configuring and building with CMake."""
2
+
3
+ import os
4
+ import subprocess
5
+ from pathlib import Path
6
+ from typing import Optional, Union
7
+
8
+ from buildgen.common.utils import UniqueList, PathLike
9
+ from buildgen.common.base import BaseBuilder
10
+
11
+
12
+ def cmake_bool(value: bool) -> str:
13
+ """Convert Python bool to CMake ON/OFF."""
14
+ return "ON" if value else "OFF"
15
+
16
+
17
+ class CMakeBuilder(BaseBuilder):
18
+ """Configure and build projects using CMake."""
19
+
20
+ def __init__(
21
+ self,
22
+ source_dir: PathLike = ".",
23
+ build_dir: PathLike = "build",
24
+ target: Optional[str] = None,
25
+ strict: bool = False,
26
+ ):
27
+ """Initialize CMakeBuilder.
28
+
29
+ Args:
30
+ source_dir: Directory containing CMakeLists.txt
31
+ build_dir: Build output directory
32
+ target: Optional target name (for BaseBuilder compatibility)
33
+ strict: If True, raise errors on duplicate entries
34
+ """
35
+ super().__init__(target or str(build_dir), strict)
36
+ self.source_dir = Path(source_dir)
37
+ self.build_dir = Path(build_dir)
38
+
39
+ # CMake options
40
+ self.generator: Optional[str] = None
41
+ self.cmake_options: dict[str, Union[str, bool, int]] = {}
42
+ self.cache_scripts: UniqueList = UniqueList()
43
+ self.build_config: str = "Release"
44
+ self.parallel_jobs: Optional[int] = None
45
+ self.build_targets: UniqueList = UniqueList()
46
+ self.install_prefix: Optional[str] = None
47
+
48
+ # Environment
49
+ self.env_vars: dict[str, str] = {}
50
+
51
+ def set_generator(self, generator: str) -> None:
52
+ """Set CMake generator (e.g., 'Ninja', 'Unix Makefiles')."""
53
+ self.generator = generator
54
+
55
+ def set_option(self, name: str, value: Union[str, bool, int]) -> None:
56
+ """Set a CMake option (-D)."""
57
+ self.cmake_options[name] = value
58
+
59
+ def set_build_type(self, build_type: str) -> None:
60
+ """Set CMAKE_BUILD_TYPE (Debug, Release, RelWithDebInfo, MinSizeRel)."""
61
+ self.cmake_options["CMAKE_BUILD_TYPE"] = build_type
62
+ self.build_config = build_type
63
+
64
+ def set_install_prefix(self, prefix: str) -> None:
65
+ """Set CMAKE_INSTALL_PREFIX."""
66
+ self.cmake_options["CMAKE_INSTALL_PREFIX"] = prefix
67
+ self.install_prefix = prefix
68
+
69
+ def add_cache_script(self, script_path: str) -> None:
70
+ """Add a cache initialization script (-C)."""
71
+ self.cache_scripts.add(script_path)
72
+
73
+ def add_build_target(self, target: str) -> None:
74
+ """Add a specific target to build."""
75
+ self.build_targets.add(target)
76
+
77
+ def set_parallel_jobs(self, jobs: int) -> None:
78
+ """Set number of parallel build jobs."""
79
+ self.parallel_jobs = jobs
80
+
81
+ def set_env(self, name: str, value: str) -> None:
82
+ """Set environment variable for CMake commands."""
83
+ self.env_vars[name] = value
84
+
85
+ # BaseBuilder interface implementation
86
+ def add_include_dirs(self, *entries) -> None:
87
+ """Add include directories via CMAKE_INCLUDE_PATH."""
88
+ current = self.cmake_options.get("CMAKE_INCLUDE_PATH", "")
89
+ new_dirs = ";".join(entries)
90
+ if current:
91
+ self.cmake_options["CMAKE_INCLUDE_PATH"] = f"{current};{new_dirs}"
92
+ else:
93
+ self.cmake_options["CMAKE_INCLUDE_PATH"] = new_dirs
94
+
95
+ def add_link_dirs(self, *entries) -> None:
96
+ """Add link directories via CMAKE_LIBRARY_PATH."""
97
+ current = self.cmake_options.get("CMAKE_LIBRARY_PATH", "")
98
+ new_dirs = ";".join(entries)
99
+ if current:
100
+ self.cmake_options["CMAKE_LIBRARY_PATH"] = f"{current};{new_dirs}"
101
+ else:
102
+ self.cmake_options["CMAKE_LIBRARY_PATH"] = new_dirs
103
+
104
+ def add_cxxflags(self, *entries) -> None:
105
+ """Add C++ flags via CMAKE_CXX_FLAGS."""
106
+ current = self.cmake_options.get("CMAKE_CXX_FLAGS", "")
107
+ new_flags = " ".join(entries)
108
+ if current:
109
+ self.cmake_options["CMAKE_CXX_FLAGS"] = f"{current} {new_flags}"
110
+ else:
111
+ self.cmake_options["CMAKE_CXX_FLAGS"] = new_flags
112
+
113
+ def add_cflags(self, *entries) -> None:
114
+ """Add C flags via CMAKE_C_FLAGS."""
115
+ current = self.cmake_options.get("CMAKE_C_FLAGS", "")
116
+ new_flags = " ".join(entries)
117
+ if current:
118
+ self.cmake_options["CMAKE_C_FLAGS"] = f"{current} {new_flags}"
119
+ else:
120
+ self.cmake_options["CMAKE_C_FLAGS"] = new_flags
121
+
122
+ def add_ldflags(self, *entries) -> None:
123
+ """Add linker flags via CMAKE_EXE_LINKER_FLAGS."""
124
+ current = self.cmake_options.get("CMAKE_EXE_LINKER_FLAGS", "")
125
+ new_flags = " ".join(entries)
126
+ if current:
127
+ self.cmake_options["CMAKE_EXE_LINKER_FLAGS"] = f"{current} {new_flags}"
128
+ else:
129
+ self.cmake_options["CMAKE_EXE_LINKER_FLAGS"] = new_flags
130
+
131
+ def _get_env(self) -> dict:
132
+ """Get environment for subprocess calls."""
133
+ env = os.environ.copy()
134
+ env.update(self.env_vars)
135
+ return env
136
+
137
+ def _format_cmake_value(self, value: Union[str, bool, int]) -> str:
138
+ """Format a value for CMake command line."""
139
+ if isinstance(value, bool):
140
+ return cmake_bool(value)
141
+ return str(value)
142
+
143
+ def get_configure_cmd(self) -> list[str]:
144
+ """Get the cmake configure command."""
145
+ cmd = ["cmake"]
146
+
147
+ # Source and build directories
148
+ cmd.extend(["-S", str(self.source_dir), "-B", str(self.build_dir)])
149
+
150
+ # Generator
151
+ if self.generator:
152
+ cmd.extend(["-G", self.generator])
153
+
154
+ # Cache scripts
155
+ for script in self.cache_scripts:
156
+ cmd.extend(["-C", script])
157
+
158
+ # Options
159
+ for key, value in self.cmake_options.items():
160
+ cmd.append(f"-D{key}={self._format_cmake_value(value)}")
161
+
162
+ return cmd
163
+
164
+ def get_build_cmd(self) -> list[str]:
165
+ """Get the cmake --build command."""
166
+ cmd = ["cmake", "--build", str(self.build_dir)]
167
+
168
+ # Config (for multi-config generators)
169
+ cmd.extend(["--config", self.build_config])
170
+
171
+ # Parallel jobs
172
+ if self.parallel_jobs:
173
+ cmd.extend(["--parallel", str(self.parallel_jobs)])
174
+
175
+ # Specific targets
176
+ for target in self.build_targets:
177
+ cmd.extend(["--target", target])
178
+
179
+ return cmd
180
+
181
+ def get_install_cmd(self) -> list[str]:
182
+ """Get the cmake --install command."""
183
+ cmd = ["cmake", "--install", str(self.build_dir)]
184
+
185
+ if self.install_prefix:
186
+ cmd.extend(["--prefix", self.install_prefix])
187
+
188
+ return cmd
189
+
190
+ def configure(self, dry_run: bool = False) -> None:
191
+ """Run CMake configuration step."""
192
+ cmd = self.get_configure_cmd()
193
+ cmd_str = " ".join(cmd)
194
+
195
+ if dry_run:
196
+ print(cmd_str)
197
+ return
198
+
199
+ print(f"Configuring: {cmd_str}")
200
+ self.build_dir.mkdir(parents=True, exist_ok=True)
201
+ subprocess.check_call(cmd, env=self._get_env())
202
+
203
+ def build(self, dry_run: bool = False) -> None:
204
+ """Run CMake build step."""
205
+ cmd = self.get_build_cmd()
206
+ cmd_str = " ".join(cmd)
207
+
208
+ if dry_run:
209
+ print(cmd_str)
210
+ return
211
+
212
+ print(f"Building: {cmd_str}")
213
+ subprocess.check_call(cmd, env=self._get_env())
214
+
215
+ def install(self, dry_run: bool = False) -> None:
216
+ """Run CMake install step."""
217
+ cmd = self.get_install_cmd()
218
+ cmd_str = " ".join(cmd)
219
+
220
+ if dry_run:
221
+ print(cmd_str)
222
+ return
223
+
224
+ print(f"Installing: {cmd_str}")
225
+ subprocess.check_call(cmd, env=self._get_env())
226
+
227
+ def configure_and_build(self, dry_run: bool = False) -> None:
228
+ """Run configure followed by build."""
229
+ self.configure(dry_run=dry_run)
230
+ if not dry_run:
231
+ self.build(dry_run=dry_run)
232
+
233
+ def full_build(self, dry_run: bool = False) -> None:
234
+ """Run configure, build, and install."""
235
+ self.configure(dry_run=dry_run)
236
+ if not dry_run:
237
+ self.build(dry_run=dry_run)
238
+ if self.install_prefix:
239
+ self.install(dry_run=dry_run)
240
+
241
+ def clean(self) -> None:
242
+ """Clean build directory."""
243
+ if self.build_dir.exists():
244
+ import shutil
245
+
246
+ shutil.rmtree(self.build_dir)
247
+ print(f"Removed: {self.build_dir}")