-
Notifications
You must be signed in to change notification settings - Fork 1
/
build_release.py
308 lines (257 loc) · 8.72 KB
/
build_release.py
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
305
306
307
308
from __future__ import annotations, print_function
import importlib
import shutil
import os
from platform import system as what_platform
from enum import Enum
from typing import List, NoReturn
"""
TARGET (str):
Target script which is the "Entry Point"
ONE_FILE (boolean):
If "True", a single executable will be generated
REQUIRE_ADMIN (boolean):
Only effects Windows builds.
Only set this to "True" if you experience
application privilege issues on Windows.
ICON (str):
Only applies if ICON field is NOT None.
Sets an executable icon for Windows systems.
ICON should either be None or a string path
to a proper ".ico" file
"""
# IMPORTANT TIP! (VERY SECRET)
# If you run this script ("build_release.py")
# with the option -OO, it will build it in "RELEASE MODE"
# This will remove any assert, __debug__ dependent statements,
# and docstrings from the decompiler.
# This results in a smaller binary (executable), and hopefully
# a bit faster :)
# Unfortunately at the cost of your release being harder to debug.
# But you can always just download the source of the release,
# and run the build script without -OO to have an executable
# fitting for finding out what's wrong when replicating the user's bug.
# So run it like this... with (INTERPRETER) being whatever your Python binary is
# (INTERPRETR) -OO build_release.py
TARGET: str = "__init__.py"
ONE_FILE: bool = True
REQUIRE_ADMIN: bool = False
ICON: str = None
def WASSERT(t: type, x) -> bool:
assert type(x) is t, "Must be type {}".format(t.__name__)
return True
# wrap WASSERT...
def ASSERT_STR(inp: str) -> bool:
return WASSERT(str, inp)
def ASSERT_BOOL(inp: bool) -> bool:
return WASSERT(bool, inp)
def ASSERT_INT(inp: int) -> bool:
return WASSERT(int, inp)
"""
Enum Wrapper
"""
class WEnum(Enum):
@classmethod
def get(cls, attr:str) -> object:
return cls.__dict__[attr].value
"""
Recognized Platforms
"""
class Platforms(WEnum):
win: str = "windows"
lnx: str = "linux"
osx: str = "darwin"
"""
Kinda Redundant Not Gonna Lie
"""
class Platform(object):
__supported_platforms = [
Platforms.get("win"),
Platforms.get("lnx")
]
@staticmethod
def get_platform() -> str:
return what_platform().lower()
@staticmethod
def is_windows(plat: str) -> bool:
return plat == Platforms.get("win")
@staticmethod
def is_linux(plat: str) -> bool:
return plat == Platforms.get("lnx")
@classmethod
def supported_platforms(cls) -> List[str]:
return cls.__supported_platforms
"""
Exception to be thrown by
[dyn_import_build_tool] if not on a supported platform
"""
class DynImportError(BaseException):
def __init__(self, m):
super().__init__(m)
"""
Wrapper for importlib.import_module
"""
def try_import(part: str, main_module: str) -> object or NoReturn:
# In Python versions 3.6 and above, importlib.import_module
# will throw a ModuelNotFoundError (only available in 3.6 and above)
# But, ModuleNotFoundError inherits from ImportError, so we can
# just use that instead for compatibility...
def concat_imp_mod() -> str:
# so i can take advantage of "nonlocal" for once
# lol, must be one of the most unused Python features i swear
#... although this could've been achieved as well with
# functools.partial probably
nonlocal part
nonlocal main_module
return main_module+part
#def dyn_error() -> type:
# v = sys.version_info
# inheritable = None
# if (v.major >= 3 and v.minor >= 6):
# return ModuleNotFoundError
# return ImportError
try:
imp = importlib.import_module(part,main_module)
return imp
except ImportError: #dyn_error():
raise DynImportError(
"Make sure that you have {} installed, sillygoose!".format(
concat_imp_mod()
)
)
except:
raise DynImportError(
"An unknown error occurred whilst trying to import {}".format(
concat_imp_mod()
)
)
"""
Selects the appropriate build tool,
in accordance with the currently selected platform.
Exits with [DynImportError] upon failure.
"""
def dyn_import_build_tool(plat: str) -> object or NoReturn:
if (Platform.is_windows(plat) or Platform.is_linux(plat)):
bt = try_import(".__main__","PyInstaller")
return bt
else:
def gen_supported() -> str:
supported: List[str] = Platform.supported_platforms()
sup: str = ""
for p in supported:
sup += "\t\t"+p+"\n"
return sup
raise DynImportError(
"\n\tNot on a supported platform.\n\tSupported platforms are:\n {}".format(
str(gen_supported())
)
)
"""
Prototype for WBuilder...
(Also Redundant)
"""
class Builder(object):
build_tool = None
current_platform = None
target_script = None
class RM_T(WEnum):pass
def gen_opts(self):pass
def start_build(self):pass
def gen_opts(self):pass
def finalize_build(self):pass
def clean_up(self):pass
class WBuilder(Builder):
def __new__(cls):
# dynamically choose which build tool depending on platform
# currently just PyInstaller for Linux & Windows
print("\nDetecting platform...")
cls.current_platform = Platform.get_platform()
print("You are on {}".format(cls.current_platform.upper()))
cls.build_tool = dyn_import_build_tool(cls.current_platform)
print("Build tool selected: {}\n".format(cls.build_tool.__name__))
global TARGET
ASSERT_STR(TARGET)
cls.target_script = TARGET
return super().__new__(cls)
def __init__(self):
self.OUTPUT_NAME = "MrbDecompiler"
self.entry_script = self.target_script
self.opts = None
self.isw:bool = Platform.is_windows(self.current_platform)
self.isl: bool = Platform.is_linux(self.current_platform)
def start_build(self):
print("\nBuilding...")
(
self
.gen_opts()
.finalize_build()
.clean_up()
)
def gen_opts(self) -> object:
print("Generating options...")
global ONE_FILE, REQUIRE_ADMIN, ICON
ASSERT_BOOL(ONE_FILE)
ASSERT_BOOL(REQUIRE_ADMIN)
# cant assert ICON since it can be None...
if (self.isw or
self.isl):
self.opts = []
self.opts.append(self.entry_script)
self.opts.append("-n={}".format(self.OUTPUT_NAME))
self.opts.append("--clean")
self.opts.append("-y")
self.opts.append("--log-level=ERROR")
if (ONE_FILE):
self.opts.append("--onefile")
if self.isw:
if (ICON != None) and (ASSERT_STR(ICON)):
self.opts.append("-i={}".format(ICON))
if REQUIRE_ADMIN:
self.opts.append("--uac-admin")
return self
def finalize_build(self) -> object:
print("Finalizing Build...")
#print(self.build_tool)
#print(self.opts)
if self.isw or self.isl:
self.build_tool.run(self.opts)
#print(self.build_tool)
return self
"""
Enumeration for type when using
[does_not_exist]
"""
class RM_T(WEnum):
FILE = 0
DIRECTORY = 1
def clean_up(self) -> object:
print("Cleaning up...")
def does_not_exist(n: str, t: str):
tmpl = ["file","directory"]
# no need to assert because it's checked in the wrappers already...
print("\'{}\' {} does not exist, pushing forward..."
.format(
n,tmpl[self.RM_T.get(t)]
))
def rm_dir(dP: str) -> None:
ASSERT_STR(dP)
try:
shutil.rmtree(dP)
except:
does_not_exist(dP,
"DIRECTORY")
def rm_file(fP: str) -> None:
ASSERT_STR(fP)
try:
os.remove(fP)
except:
does_not_exist(fP,
"FILE")
rm_dir("build")
rm_dir("__pycache__")
rm_file(self.OUTPUT_NAME+".spec")
return self
if __name__ == "__main__":
b = WBuilder()
b.start_build()
print("\n\nDone. Build can be found in the \'dist\' folder")