11# SPDX-FileCopyrightText: 2023 PrepPipe's Contributors
22# SPDX-License-Identifier: Apache-2.0
33
4+ import os
5+ import subprocess
6+ import sys
7+ import shutil
48from preppipe .irbase import Operation , typing
9+ from preppipe .appdir import get_executable_base_dir
510from .ast import *
611from ..pipeline import *
712from ..irbase import *
13+ from ..exceptions import PPInternalError
814from .export import export_renpy
915from .codegen import codegen_renpy
1016from ..vnmodel import VNModel
11- import shutil
17+
18+
19+ def _find_renpy_sdk () -> str | None :
20+ """查找内嵌的 Ren'Py SDK 目录(含 renpy.py 的 renpy-sdk 目录)。"""
21+ env_path = os .environ .get ('PREPPIPE_RENPY_SDK' )
22+ if env_path and os .path .isdir (env_path ) and os .path .isfile (os .path .join (env_path , 'renpy.py' )):
23+ return os .path .abspath (env_path )
24+ base = get_executable_base_dir ()
25+ for dir_ in (base , os .path .dirname (base ), os .path .dirname (os .path .dirname (base )), '.' ):
26+ candidate = os .path .join (dir_ , 'renpy-sdk' ) if dir_ != '.' else os .path .abspath ('renpy-sdk' )
27+ if dir_ != '.' :
28+ candidate = os .path .abspath (candidate )
29+ if os .path .isdir (candidate ) and os .path .isfile (os .path .join (candidate , 'renpy.py' )):
30+ return candidate
31+ return None
32+
33+
34+ def _get_renpy_python_exe (sdk_dir : str ) -> str :
35+ """返回 Ren'Py SDK 内嵌的 Python 解释器路径。"""
36+ system = sys .platform
37+ if system == 'win32' :
38+ lib_python = os .path .join (sdk_dir , 'lib' , 'py3-windows-x86_64' , 'python.exe' )
39+ elif system == 'darwin' :
40+ lib_python = os .path .join (sdk_dir , 'lib' , 'py3-darwin-x86_64' , 'python' )
41+ if not os .path .isfile (lib_python ):
42+ lib_python = os .path .join (sdk_dir , 'lib' , 'py3-darwin-arm64' , 'python' )
43+ else :
44+ lib_python = os .path .join (sdk_dir , 'lib' , 'py3-linux-x86_64' , 'python' )
45+ if not os .path .isfile (lib_python ):
46+ raise PPInternalError ('Ren\' Py SDK 中未找到对应平台的 Python: ' + lib_python )
47+ return lib_python
48+
49+
50+ def _renpy_launcher_language_from_env () -> str | None :
51+ """根据 PREPPIPE_LANGUAGE 返回 Ren'Py launcher 的 --language 值。仅处理简中/繁中,其他不传(默认英语)。"""
52+ lang = (os .environ .get ('PREPPIPE_LANGUAGE' ) or '' ).strip ().lower ()
53+ if lang in ('zh_cn' , 'schinese' ):
54+ return 'schinese'
55+ if lang in ('zh_hk' , 'tchinese' , 'zh-tw' ):
56+ return 'tchinese'
57+ return None
58+
59+
60+ def _ensure_renpy_project_generated (game_dir : str , language : str | None = None ) -> None :
61+ """
62+ 若 game_dir 下尚无完整 Ren'Py 工程(无 gui.rpy),则使用内嵌 SDK 生成空工程并生成 GUI 图片。
63+ game_dir 为工程下的 game 目录(即输出目录);其父目录为工程根。
64+ language 为 None 时不传 --language,由 Ren'Py 使用默认(英语)。
65+ """
66+ gui_rpy = os .path .join (game_dir , 'gui.rpy' )
67+ if os .path .isfile (gui_rpy ):
68+ return
69+ sdk_dir = _find_renpy_sdk ()
70+ if not sdk_dir :
71+ raise PPInternalError (
72+ '输出目录下未检测到 Ren\' Py 工程(无 gui.rpy),且未找到 Ren\' Py SDK。'
73+ '请设置环境变量 PREPPIPE_RENPY_SDK 或将 SDK 解压到 renpy-sdk 目录。'
74+ )
75+ project_root = os .path .dirname (game_dir )
76+ os .makedirs (game_dir , exist_ok = True )
77+ python_exe = _get_renpy_python_exe (sdk_dir )
78+ renpy_py = os .path .join (sdk_dir , 'renpy.py' )
79+ cmd_generate = [
80+ python_exe , renpy_py , 'launcher' , 'generate_gui' ,
81+ os .path .abspath (project_root ), '--start' ,
82+ ]
83+ if language is not None :
84+ cmd_generate .extend (('--language' , language ))
85+ subprocess .run (cmd_generate , cwd = sdk_dir , check = True )
86+ cmd_gui_images = [python_exe , renpy_py , os .path .abspath (project_root ), 'gui_images' ]
87+ subprocess .run (cmd_gui_images , cwd = sdk_dir , check = True )
88+
89+
90+ def run_renpy_project (project_root : str , sdk_dir : str | None = None ) -> None :
91+ """
92+ 使用内嵌 Ren'Py SDK 运行指定工程(不等待进程结束)。
93+ project_root 为工程根目录,其下应包含 game 目录。
94+ sdk_dir 若提供则优先使用(如 GUI 设置中的默认路径),否则按环境变量与默认目录查找。
95+ 供 GUI「运行项目」等调用。
96+ """
97+ if sdk_dir and os .path .isdir (sdk_dir ) and os .path .isfile (os .path .join (sdk_dir , 'renpy.py' )):
98+ pass
99+ else :
100+ sdk_dir = _find_renpy_sdk ()
101+ if not sdk_dir :
102+ raise PPInternalError (
103+ '未找到 Ren\' Py SDK,无法运行项目。'
104+ '请设置环境变量 PREPPIPE_RENPY_SDK 或将 SDK 解压到 renpy-sdk 目录。'
105+ )
106+ python_exe = _get_renpy_python_exe (sdk_dir )
107+ renpy_py = os .path .join (sdk_dir , 'renpy.py' )
108+ abs_root = os .path .abspath (project_root )
109+ subprocess .Popen (
110+ [python_exe , renpy_py , abs_root ],
111+ cwd = sdk_dir ,
112+ stdin = subprocess .DEVNULL ,
113+ stdout = subprocess .DEVNULL ,
114+ stderr = subprocess .DEVNULL ,
115+ )
12116
13117@FrontendDecl ('test-renpy-build' , input_decl = IODecl (description = '<No Input>' , nargs = 0 ), output_decl = RenPyModel )
14118class _TestVNModelBuild (TransformBase ):
@@ -76,17 +180,19 @@ def handle_arguments(args : argparse.Namespace):
76180 _RenPyExport ._template_dir = _RenPyExport ._template_dir [0 ]
77181 assert isinstance (_RenPyExport ._template_dir , str )
78182 if len (_RenPyExport ._template_dir ) > 0 and not os .path .isdir (_RenPyExport ._template_dir ):
79- raise RuntimeError ('--renpy-export-templatedir: input "' + _RenPyExport ._template_dir + '" is not a valid path' )
183+ raise PPInternalError ('--renpy-export-templatedir: input "' + _RenPyExport ._template_dir + '" is not a valid path' )
80184
81185 def run (self ) -> None :
82186 if len (self ._inputs ) == 0 :
83187 return None
84188 if len (self ._inputs ) > 1 :
85- raise RuntimeError ("renpy-export: exporting multiple input IR is not supported" )
189+ raise PPInternalError ("renpy-export: exporting multiple input IR is not supported" )
86190 out_path = self .output
87191 if os .path .exists (out_path ):
88192 if not os .path .isdir (out_path ):
89- raise RuntimeError ("renpy-export: exporting to non-directory path: " + out_path )
193+ raise PPInternalError ("renpy-export: exporting to non-directory path: " + out_path )
194+ # 若输出目录尚无完整 Ren'Py 工程(无 gui.rpy),则用内嵌 SDK 先生成空工程与 GUI 图片
195+ _ensure_renpy_project_generated (out_path , _renpy_launcher_language_from_env ())
90196 return export_renpy (self .inputs [0 ], out_path , _RenPyExport ._template_dir )
91197
92198@MiddleEndDecl ('renpy-codegen' , input_decl = VNModel , output_decl = RenPyModel )
@@ -95,6 +201,6 @@ def run(self) -> RenPyModel | None:
95201 if len (self ._inputs ) == 0 :
96202 return None
97203 if len (self ._inputs ) > 1 :
98- raise RuntimeError ("renpy-codegen: exporting multiple input IR is not supported" )
204+ raise PPInternalError ("renpy-codegen: exporting multiple input IR is not supported" )
99205 return codegen_renpy (self ._inputs [0 ])
100206 pass
0 commit comments