Rendering Engine 0.2.9
Modular Graphics Rendering Engine | v0.2.9
package_sdk.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# This file is part of the Rendering Engine project.
3# Author: Alexander Obzherin <alexanderobzherin@gmail.com>
4# Copyright (c) 2026 Alexander Obzherin
5# Distributed under the terms of the zlib License. See LICENSE.md for details.
6##
7"""
8@file package_sdk.py
9@brief Build-time helper for assembling the Rendering Engine SDK.
10
11This script is intended to be invoked indirectly via the platform build
12scripts:
13
14 - Windows: build_engine.bat --build-sdk
15 - Unix/FreeBSD: ./build_engine.sh --build-sdk
16
17Responsibilities:
18 - Read engine version from Intermediate/Generated/version.h.
19 - Copy the installed RenderingEngine library, headers, and CMake config
20 into a distributable SDK layout.
21 - Copy vendored headers (glm, boost, nlohmann_json, Vulkan headers if used).
22 - Copy project templates and the create_project.py helper.
23 - Copy and patch ContentExamples into SDK-user mode (RE_DEV_MODE=OFF).
24 - Emit documentation files and a human-readable Manifest.txt.
25 - Create a compressed archive suitable for CI/CD artifacts.
26"""
27
28import os
29import re
30import shutil
31import datetime
32import logging
33import sys
34
35# --------------------------------------------------------------------
36# Configuration
37# --------------------------------------------------------------------
38
39REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../"))
40BUILD_ROOT = os.path.join(REPO_ROOT, "Build")
41INSTALL_ROOT = os.path.join(BUILD_ROOT, "Installed")
42PACKAGE_ROOT = os.path.join(BUILD_ROOT, "Packages")
43SDK_TEMP_ROOT = os.path.join(BUILD_ROOT, "SDK")
44LOGS_ROOT = os.path.join(BUILD_ROOT, "Logs")
45
46VERSION_HEADER = os.path.join(
47 BUILD_ROOT, "Intermediate", "Generated", "version.h"
48)
49
50DOC_FILES = [
51 "README.md",
52 "Doc/app_config_guide.md",
53 "Doc/application_development_guide.md",
54 "Doc/ci_cd_guide.md",
55 "Doc/debugging_guide.md",
56 "Doc/developer_guide.md",
57 "Doc/docker_container.md",
58 "Doc/getting_started.md",
59 "Doc/performance_profiling_guide.md",
60 "Doc/prepare_environment.md",
61 "Doc/project_creation_guide.md",
62 "Doc/project_packaging_guide.md",
63 "Doc/sdk_packaging_guide.md",
64]
65
66# --------------------------------------------------------------------
67# Helpers
68# --------------------------------------------------------------------
69
70
72 """Parse version.h and extract MAJOR.MINOR.PATCH as a string."""
73 if not os.path.isfile(VERSION_HEADER):
74 raise RuntimeError(f"version.h not found at: {VERSION_HEADER}")
75
76 with open(VERSION_HEADER, "r", encoding="utf-8") as f:
77 text = f.read()
78
79 major = re.search(r"#define\s+RENDERING_ENGINE_VERSION_MAJOR\s+(\d+)", text)
80 minor = re.search(r"#define\s+RENDERING_ENGINE_VERSION_MINOR\s+(\d+)", text)
81 patch = re.search(r"#define\s+RENDERING_ENGINE_VERSION_PATCH\s+(\d+)", text)
82
83 if not (major and minor and patch):
84 raise RuntimeError(
85 "version.h missing required version macros "
86 "(RENDERING_ENGINE_VERSION_MAJOR/MINOR/PATCH)."
87 )
88
89 return f"{major.group(1)}.{minor.group(1)}.{patch.group(1)}"
90
91
93 """Return a human-readable platform string."""
94 p = sys.platform
95 if p.startswith("win"):
96 return "Windows"
97 elif p.startswith("linux"):
98 return "Linux"
99 elif p.startswith("freebsd"):
100 return "FreeBSD"
101 return "Unknown"
102
103
104def copy_tree(src, dst, ignore=None):
105 if not os.path.exists(src):
106 logging.warning(f"Source path does not exist, skipping: {src}")
107 return
108 if os.path.exists(dst):
109 shutil.rmtree(dst)
110 shutil.copytree(src, dst, ignore=ignore)
111 logging.info(f"Copied tree: {src} -> {dst}")
112
113
114def ensure_dir(path):
115 os.makedirs(path, exist_ok=True)
116 return path
117
118
119def write_manifest(path, version, platform):
120 with open(path, "w", encoding="utf-8") as f:
121 f.write("Rendering Engine SDK Manifest\n")
122 f.write("=====================================\n\n")
123 f.write(f"Version : {version}\n")
124 f.write(f"Platform: {platform}\n")
125 f.write(f"Date : {datetime.datetime.now()}\n\n")
126 f.write("Layout:\n")
127 f.write("- RenderingEngine/RenderingLibrary : core engine library + headers + cmake files\n")
128 f.write("- RenderingEngine/MaterialCompiler : material compiler tool + local engine DLL\n")
129 f.write("- RenderingEngine/Scripts : project generation scripts + templates\n")
130 f.write("- ContentExamples/ : sample projects\n")
131 f.write("- UserApplications/ : place for user-created apps\n")
132 f.write("- Doc/ : documentation\n")
133 logging.info(f"Manifest written: {path}")
134
135def patch_build_script_for_sdk(build_script_path):
136 """Force RE_DEV_MODE=OFF inside build_project.sh"""
137 if not os.path.isfile(build_script_path):
138 return
139
140 with open(build_script_path, "r", encoding="utf-8") as f:
141 content = f.read()
142
143 # Patch the cmake configuration line:
144 # cmake -S "$PROJECT_SOURCE_DIR" -B "$PROJECT_BUILD_DIR" -DCMAKE_BUILD_TYPE=...
145 content = re.sub(
146 r'cmake\s+-S\s+"\$PROJECT_SOURCE_DIR"\s+-B\s+"\$PROJECT_BUILD_DIR"\s+-DCMAKE_BUILD_TYPE=\$BUILD_MODE',
147 r'cmake -S "$PROJECT_SOURCE_DIR" -B "$PROJECT_BUILD_DIR" -DCMAKE_BUILD_TYPE=$BUILD_MODE -DRE_DEV_MODE=OFF',
148 content
149 )
150
151 with open(build_script_path, "w", encoding="utf-8") as f:
152 f.write(content)
153
154 logging.info(f"Patched build_project.sh for SDK: {build_script_path}")
155
156# --------------------------------------------------------------------
157# Main Packaging Logic
158# --------------------------------------------------------------------
159
160
161def main():
162 # Init logging
163 ensure_dir(LOGS_ROOT)
164 logging.basicConfig(
165 filename=os.path.join(LOGS_ROOT, "packaging.log"),
166 level=logging.INFO,
167 format="%(asctime)s %(levelname)s: %(message)s",
168 )
169 logging.info("=== SDK Packaging Started ===")
170
171 try:
172 version = read_version()
173 platform = detect_platform()
174
175 sdk_name = f"RenderingEngine-v{version}-SDK-{platform}"
176 sdk_root = os.path.join(SDK_TEMP_ROOT, sdk_name)
177
178 # Clean previous SDK for this version/platform
179 if os.path.exists(sdk_root):
180 shutil.rmtree(sdk_root)
181 ensure_dir(sdk_root)
182
183 logging.info(f"Version : {version}")
184 logging.info(f"Platform: {platform}")
185 logging.info(f"SDK root: {sdk_root}")
186
187 # Paths inside SDK
188 sdk_rendering_engine_root = ensure_dir(os.path.join(sdk_root, "RenderingEngine"))
189 sdk_render_lib_root = ensure_dir(os.path.join(sdk_rendering_engine_root, "RenderingLibrary"))
190 sdk_material_compiler_root = ensure_dir(os.path.join(sdk_rendering_engine_root, "MaterialCompiler"))
191 sdk_scripts_root = ensure_dir(os.path.join(sdk_rendering_engine_root, "Scripts"))
192
193 # --------------------------------------------------------------
194 # 1. RenderingLibrary: headers + libs + cmake files
195 # --------------------------------------------------------------
196 installed_engine_root = os.path.join(INSTALL_ROOT, "RenderingEngine")
197
198 # RenderingLibrary
199 installed_render_lib = os.path.join(installed_engine_root, "RenderingLibrary")
200
201 copy_tree(
202 os.path.join(installed_render_lib, "Include"),
203 os.path.join(sdk_render_lib_root, "Include"),
204 )
205
206 copy_tree(
207 os.path.join(installed_render_lib, "Library"),
208 os.path.join(sdk_render_lib_root, "Library"),
209 )
210
211 # --------------------------------------------------------------
212 # 2. MaterialCompiler (tool + its local DLL)
213 # --------------------------------------------------------------
214 installed_mc_root = os.path.join(installed_engine_root, "MaterialCompiler")
215 copy_tree(installed_mc_root, sdk_material_compiler_root)
216
217 # --------------------------------------------------------------
218 # 3. External vendored headers (glm, boost, json, vulkan,...)
219 # --------------------------------------------------------------
220 installed_external_root = os.path.join(installed_engine_root, "External")
221 copy_tree(installed_external_root, os.path.join(sdk_rendering_engine_root, "External"))
222
223 # --------------------------------------------------------------
224 # 4. Scripts (Templates)
225 # --------------------------------------------------------------
226 copy_tree(
227 os.path.join(REPO_ROOT, "RenderingEngine", "Scripts", "Templates"),
228 os.path.join(sdk_scripts_root, "Templates"),
229 )
230
231 # --------------------------------------------------------------
232 # 4. Modify CMakeLists.txt.in inside SDK to set RE_DEV_MODE OFF
233 # --------------------------------------------------------------
234 cmake_template_path = os.path.join(sdk_scripts_root, "Templates", "CMakeLists.txt.in")
235
236 # Always copy the Templates directory
237 copy_tree(
238 os.path.join(REPO_ROOT, "RenderingEngine", "Scripts", "Templates"),
239 os.path.join(sdk_scripts_root, "Templates"),
240 )
241
242 # Always copy create_project.py
243 create_project_src = os.path.join(REPO_ROOT, "RenderingEngine", "Scripts", "create_project.py")
244 if os.path.isfile(create_project_src):
245 shutil.copy2(create_project_src, os.path.join(sdk_scripts_root, "create_project.py"))
246 logging.info("Copied script: create_project.py")
247 else:
248 logging.warning(f"create_project.py not found at {create_project_src}")
249
250 # Modify RE_DEV_MODE default in CMakeLists template
251 cmake_template_path = os.path.join(sdk_scripts_root, "Templates", "CMakeLists.txt.in")
252
253 with open(cmake_template_path, "r", encoding="utf-8") as f:
254 text = f.read()
255
256 text = re.sub(
257 r'option\‍(RE_DEV_MODE\s+"[^"]+"\s+ON\‍)',
258 'option(RE_DEV_MODE "Build using RenderingEngine directly from source tree (Windows only)" OFF)',
259 text
260 )
261
262 with open(cmake_template_path, "w", encoding="utf-8") as f:
263 f.write(text)
264
265 logging.info("Updated CMakeLists.txt.in to set RE_DEV_MODE=OFF")
266
267
268 # --------------------------------------------------------------
269 # 5. Documentation
270 # --------------------------------------------------------------
271 doc_dst = ensure_dir(os.path.join(sdk_root, "Doc"))
272 for doc in DOC_FILES:
273 src = os.path.join(REPO_ROOT, doc)
274 if os.path.isfile(src):
275 shutil.copy2(src, doc_dst)
276 logging.info(f"Copied documentation: {src}")
277 else:
278 logging.warning(f"Documentation file missing, skipped: {src}")
279
280 # --------------------------------------------------------------
281 # 6. Content Examples (copied directly from root-level folder)
282 # --------------------------------------------------------------
283 examples_src_root = os.path.join(REPO_ROOT, "ContentExamples")
284 examples_dst_root = ensure_dir(os.path.join(sdk_root, "ContentExamples"))
285
286 def ignore_example(dirpath, names):
287 ignore = []
288 for n in names:
289 if n in ("Build", "Intermediate", ".vs", ".vscode", "x64"):
290 ignore.append(n)
291 elif n.endswith(".user") or n.endswith(".vcxproj") or n.endswith(".vcxproj.filters"):
292 ignore.append(n)
293 return ignore
294
295 if os.path.isdir(examples_src_root):
296 copy_tree(examples_src_root, examples_dst_root, ignore=ignore_example)
297 logging.info("Copied ContentExamples folder.")
298
299 # Patch build_project.sh in each example
300 for proj in os.listdir(examples_dst_root):
301 proj_path = os.path.join(examples_dst_root, proj)
302 if os.path.isdir(proj_path):
303 build_script = os.path.join(proj_path, "build_project.sh")
304 patch_build_script_for_sdk(build_script)
305 else:
306 logging.warning("No ContentExamples folder found.")
307
308 # --------------------------------------------------------------
309 # 7. UserApplications (empty placeholder)
310 # --------------------------------------------------------------
311 ensure_dir(os.path.join(sdk_root, "UserApplications"))
312
313 # --------------------------------------------------------------
314 # 7. Manifest
315 # --------------------------------------------------------------
316 write_manifest(os.path.join(sdk_root, "Manifest.txt"), version, platform)
317
318 # --------------------------------------------------------------
319 # 8. Create Archive
320 # --------------------------------------------------------------
321 ensure_dir(PACKAGE_ROOT)
322 archive_path = os.path.join(PACKAGE_ROOT, sdk_name)
323
324 # Clean old archive if exists
325 for ext in (".tar.gz", ".zip", ".tgz"):
326 old = archive_path + ext
327 if os.path.exists(old):
328 os.remove(old)
329
330 shutil.make_archive(archive_path, "gztar", root_dir=SDK_TEMP_ROOT)
331 logging.info(f"Archive created: {archive_path}.tar.gz")
332 logging.info("=== SDK Packaging Complete ===")
333
334 except Exception as e:
335 logging.error(f"SDK packaging failed: {e}", exc_info=True)
336 print(f"SDK packaging failed: {e}")
337
338
339if __name__ == "__main__":
340 main()
def copy_tree(src, dst, ignore=None)
Definition: package_sdk.py:104
def patch_build_script_for_sdk(build_script_path)
Definition: package_sdk.py:135
def write_manifest(path, version, platform)
Definition: package_sdk.py:119
def ensure_dir(path)
Definition: package_sdk.py:114
def read_version()
Definition: package_sdk.py:71
def detect_platform()
Definition: package_sdk.py:92