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 +23 -0
- buildgen/cli.py +369 -0
- buildgen/cmake/__init__.py +33 -0
- buildgen/cmake/builder.py +247 -0
- buildgen/cmake/cli.py +228 -0
- buildgen/cmake/functions.py +511 -0
- buildgen/cmake/generator.py +441 -0
- buildgen/cmake/variables.py +121 -0
- buildgen/common/__init__.py +20 -0
- buildgen/common/base.py +212 -0
- buildgen/common/platform.py +147 -0
- buildgen/common/project.py +652 -0
- buildgen/common/utils.py +85 -0
- buildgen/makefile/__init__.py +29 -0
- buildgen/makefile/builder.py +177 -0
- buildgen/makefile/cli.py +208 -0
- buildgen/makefile/functions.py +247 -0
- buildgen/makefile/generator.py +369 -0
- buildgen/makefile/variables.py +57 -0
- buildgen-0.1.1.dist-info/METADATA +243 -0
- buildgen-0.1.1.dist-info/RECORD +23 -0
- buildgen-0.1.1.dist-info/WHEEL +4 -0
- buildgen-0.1.1.dist-info/entry_points.txt +3 -0
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}")
|