-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathrun.py
More file actions
304 lines (257 loc) · 11.3 KB
/
run.py
File metadata and controls
304 lines (257 loc) · 11.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
#!/usr/bin/env python3
"""run.py — NextSSL one-shot build + test runner.
Works on Windows, Linux, and macOS without any modification.
Usage
-----
python run.py # auto-detect platform, build + test
python run.py --skip-build # run tests only (skip CMake)
python run.py --skip-test # build only (skip test.py)
python run.py --clean # wipe build dir before configuring
python run.py --shared # shared library (default)
python run.py --static # static library
python run.py --debug # Debug build (default: Release)
python run.py --jobs 4 # parallel jobs (default: CPU count)
python run.py --arch arm64 # override target architecture
python run.py --platform linux # override target platform
python run.py -v # verbose cmake output
Cross-compilation note
----------------------
When --arch differs from the host architecture, the appropriate CMake
cross-compile flags are added automatically. A matching cross-compiler
must be installed (e.g. aarch64-linux-gnu-gcc for Linux → arm64).
"""
import argparse
import os
import platform
import shutil
import subprocess
import sys
from pathlib import Path
# ─── Project root (directory that contains this script) ───────────────────────
ROOT = Path(__file__).resolve().parent
# ─── Platform / arch detection ────────────────────────────────────────────────
def _detect_os() -> str:
"""Return one of: 'win', 'linux', 'macos'."""
s = sys.platform
if s.startswith("win"):
return "win"
if s == "darwin":
return "macos"
return "linux"
def _detect_arch() -> str:
"""Return one of: 'x86_64', 'arm64', 'x86', 'riscv64', ..."""
m = platform.machine().lower()
if m in ("x86_64", "amd64"):
return "x86_64"
if m in ("arm64", "aarch64"):
return "arm64"
if m in ("i686", "i386", "x86"):
return "x86"
return m # pass through (riscv64, s390x, ppc64le, …)
def _find_cmake() -> str:
cmake = shutil.which("cmake")
if not cmake:
print("ERROR: cmake not found on PATH.", file=sys.stderr)
print(" Install CMake from https://cmake.org/download/", file=sys.stderr)
sys.exit(1)
return cmake
def _cpu_count() -> int:
try:
return os.cpu_count() or 4
except Exception:
return 4
# ─── Cross-compilation helpers ────────────────────────────────────────────────
# Map (host_os, target_arch) → extra CMake args
_CROSS_FLAGS: dict[tuple[str, str], list[str]] = {
("linux", "arm64"): [
"-DCMAKE_SYSTEM_NAME=Linux",
"-DCMAKE_SYSTEM_PROCESSOR=aarch64",
"-DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc",
],
("linux", "armv7"): [
"-DCMAKE_SYSTEM_NAME=Linux",
"-DCMAKE_SYSTEM_PROCESSOR=armv7",
"-DCMAKE_C_COMPILER=arm-linux-gnueabihf-gcc",
],
("linux", "riscv64"): [
"-DCMAKE_SYSTEM_NAME=Linux",
"-DCMAKE_SYSTEM_PROCESSOR=riscv64",
"-DCMAKE_C_COMPILER=riscv64-linux-gnu-gcc",
],
("linux", "s390x"): [
"-DCMAKE_SYSTEM_NAME=Linux",
"-DCMAKE_SYSTEM_PROCESSOR=s390x",
"-DCMAKE_C_COMPILER=s390x-linux-gnu-gcc",
],
("linux", "ppc64le"): [
"-DCMAKE_SYSTEM_NAME=Linux",
"-DCMAKE_SYSTEM_PROCESSOR=ppc64le",
"-DCMAKE_C_COMPILER=powerpc64le-linux-gnu-gcc",
],
("linux", "x86"): [
"-DCMAKE_SYSTEM_PROCESSOR=i686",
"-DCMAKE_C_FLAGS=-m32",
],
}
def _cross_flags(host_os: str, host_arch: str, target_arch: str) -> list[str]:
if host_arch == target_arch:
return []
return _CROSS_FLAGS.get((host_os, target_arch), [])
# ─── Generator selection ──────────────────────────────────────────────────────
def _generator_flags(os_name: str) -> list[str]:
"""Prefer Ninja everywhere; fall back to platform default."""
if os_name == "win":
# On Windows with MSVC we do NOT use -G so VS handles multi-arch via -A.
# Ninja is still used for MinGW builds if cl.exe is absent.
if shutil.which("cl"):
return [] # let CMake pick Visual Studio generator
if shutil.which("ninja"):
return ["-G", "Ninja"]
return []
if shutil.which("ninja"):
return ["-G", "Ninja"]
return []
# ─── Build step ───────────────────────────────────────────────────────────────
def build(
*,
os_name: str,
host_arch: str,
target_arch: str,
build_type: str,
shared: bool,
clean: bool,
jobs: int,
verbose: bool,
extra_cmake_args: list[str],
) -> int:
cmake = _find_cmake()
build_dir = ROOT / f"_build_{os_name}_{target_arch}_{build_type.lower()}"
if clean and build_dir.exists():
print(f"[run.py] Cleaning {build_dir.name} ...")
shutil.rmtree(build_dir)
build_dir.mkdir(parents=True, exist_ok=True)
# ── cmake configure ────────────────────────────────────────────────────────
configure_cmd: list[str] = [cmake, str(ROOT)]
configure_cmd += _generator_flags(os_name)
configure_cmd += [f"-DCMAKE_BUILD_TYPE={build_type}"]
configure_cmd += [f"-DNEXTSSL_SHARED={'ON' if shared else 'OFF'}"]
# Windows MSVC: pass -A <arch> for multi-arch support
if os_name == "win" and shutil.which("cl"):
_msvc_arch = {
"x86_64": "x64",
"x86": "Win32",
"arm64": "ARM64",
"arm": "ARM",
}.get(target_arch, "x64")
configure_cmd += ["-A", _msvc_arch]
else:
configure_cmd += _cross_flags(os_name, host_arch, target_arch)
configure_cmd += extra_cmake_args
print(f"[run.py] Configure: {' '.join(configure_cmd)}")
rc = subprocess.run(configure_cmd, cwd=str(build_dir)).returncode
if rc != 0:
print(f"[run.py] CMake configure FAILED (exit {rc})", file=sys.stderr)
return rc
# ── cmake build ────────────────────────────────────────────────────────────
build_cmd: list[str] = [cmake, "--build", ".", "--config", build_type,
"--parallel", str(jobs)]
if verbose:
build_cmd += ["--", "-v"] if shutil.which("ninja") else []
print(f"[run.py] Build: {' '.join(build_cmd)}")
rc = subprocess.run(build_cmd, cwd=str(build_dir)).returncode
if rc != 0:
print(f"[run.py] Build FAILED (exit {rc})", file=sys.stderr)
return rc
# ─── Test step ────────────────────────────────────────────────────────────────
def run_tests() -> int:
test_script = ROOT / "test.py"
if not test_script.exists():
print("[run.py] WARNING: test.py not found — skipping tests.", file=sys.stderr)
return 0
cmd = [sys.executable, str(test_script)]
print(f"[run.py] Tests: {' '.join(cmd)}")
return subprocess.run(cmd, cwd=str(ROOT)).returncode
# ─── Main ─────────────────────────────────────────────────────────────────────
def main() -> int:
host_os = _detect_os()
host_arch = _detect_arch()
parser = argparse.ArgumentParser(
description="NextSSL build + test runner (cross-platform)",
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument(
"--platform",
default=host_os,
choices=["win", "linux", "macos"],
help=f"Target OS (default: {host_os})",
)
parser.add_argument(
"--arch",
default=host_arch,
help=f"Target architecture (default: {host_arch})",
)
parser.add_argument(
"--build-type",
default="Release",
choices=["Release", "Debug", "RelWithDebInfo", "MinSizeRel"],
help="CMake build type (default: Release)",
)
_mode = parser.add_mutually_exclusive_group()
_mode.add_argument("--shared", dest="shared", action="store_true", default=True,
help="Build shared library — .dll/.so/.dylib (default)")
_mode.add_argument("--static", dest="shared", action="store_false",
help="Build static library — .lib/.a")
parser.add_argument("--debug", action="store_true",
help="Shorthand for --build-type Debug")
parser.add_argument("--clean", action="store_true",
help="Remove build directory before configuring")
parser.add_argument("--skip-build", action="store_true",
help="Skip CMake configure + build")
parser.add_argument("--skip-test", action="store_true",
help="Skip test.py")
parser.add_argument("--jobs", type=int, default=_cpu_count(),
help=f"Parallel jobs (default: {_cpu_count()})")
parser.add_argument("-v", "--verbose", action="store_true",
help="Pass verbose flag to build tool")
parser.add_argument(
"cmake_args",
nargs=argparse.REMAINDER,
help="Extra arguments forwarded to cmake configure (after --)",
)
args = parser.parse_args()
build_type = "Debug" if args.debug else args.build_type
target_os = args.platform
target_arch = args.arch
# Strip leading '--' separator if present
extra = [a for a in args.cmake_args if a != "--"]
print(f"[run.py] Platform : {target_os}")
print(f"[run.py] Arch : {target_arch}")
print(f"[run.py] Type : {build_type}")
print(f"[run.py] Shared : {args.shared}")
print()
# ── Build ──────────────────────────────────────────────────────────────────
if not args.skip_build:
rc = build(
os_name=target_os,
host_arch=host_arch,
target_arch=target_arch,
build_type=build_type,
shared=args.shared,
clean=args.clean,
jobs=args.jobs,
verbose=args.verbose,
extra_cmake_args=extra,
)
if rc != 0:
return rc
print()
# ── Tests ──────────────────────────────────────────────────────────────────
if not args.skip_test:
rc = run_tests()
if rc != 0:
print(f"\n[run.py] Tests FAILED (exit {rc})", file=sys.stderr)
return rc
print("\n[run.py] All tests passed.")
return 0
if __name__ == "__main__":
sys.exit(main())