github.com/iDigitalFlame/xmt@v0.5.4/tools/windows_builder.py (about) 1 #!/usr/bin/python3 2 # Copyright (C) 2020 - 2023 iDigitalFlame 3 # 4 # This program is free software: you can redistribute it and/or modify 5 # it under the terms of the GNU General Public License as published by 6 # the Free Software Foundation, either version 3 of the License, or 7 # any later version. 8 # 9 # This program is distributed in the hope that it will be useful, 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 # GNU General Public License for more details. 13 # 14 # You should have received a copy of the GNU General Public License 15 # along with this program. If not, see <https://www.gnu.org/licenses/>. 16 # 17 18 from os import environ 19 from random import choice 20 from tempfile import mkdtemp 21 from sys import stderr, exit 22 from shutil import which, rmtree 23 from traceback import format_exc 24 from argparse import ArgumentParser 25 from os.path import isdir, isfile, join 26 from subprocess import SubprocessError, run 27 from string import ascii_lowercase, Template 28 29 C_SRC = """#define WINVER 0x0501 30 #define _WIN32_WINNT 0x0501 31 32 #define NOWH 33 #define NOMB 34 #define NOMSG 35 #define NONLS 36 #define NOMCX 37 #define NOIME 38 #define NOHELP 39 #define NOCOMM 40 #define NOICONS 41 #define NOCRYPT 42 #define NOKANJI 43 #define NOSOUND 44 #define NOCOLOR 45 #define NOMENUS 46 #define NOCTLMGR 47 #define NOMINMAX 48 #define NOSCROLL 49 #define NODRAWTEXT 50 #define NOMETAFILE 51 #define NOPROFILER 52 #define NOKEYSTATES 53 #define NORASTEROPS 54 #define NOCLIPBOARD 55 #define NOWINSTYLES 56 #define NOSYSMETRICS 57 #define NOWINOFFSETS 58 #define NOSHOWWINDOW 59 #define NOTEXTMETRIC 60 #define NOSYSCOMMANDS 61 #define NOGDICAPMASKS 62 #define NOWINMESSAGES 63 #define NODEFERWINDOWPOS 64 #define NOVIRTUALKEYCODES 65 #define WIN32_LEAN_AND_MEAN 66 67 #define UNICODE 68 #define EXPORT __declspec(dllexport) 69 70 #include <winsock.h> 71 #include <windows.h> 72 73 #include "$name.h" 74 75 """ 76 C_DLL = """DWORD $thread(void) { 77 Sleep(1000); 78 $export(); 79 return 0; 80 } 81 82 EXPORT HRESULT WINAPI VoidFunc(void) { 83 HANDLE c = GetConsoleWindow(); 84 if (c != NULL) { 85 ShowWindow(c, 0); 86 } 87 $export(); 88 } 89 EXPORT HRESULT WINAPI DllCanUnloadNow(void) { 90 // Always return S_FALSE so we can stay loaded. 91 return 1; 92 } 93 EXPORT HRESULT WINAPI DllRegisterServer(void) { 94 HANDLE c = GetConsoleWindow(); 95 if (c != NULL) { 96 ShowWindow(c, 0); 97 } 98 $export(); 99 } 100 EXPORT HRESULT WINAPI DllUnregisterServer(void) { 101 HANDLE c = GetConsoleWindow(); 102 if (c != NULL) { 103 ShowWindow(c, 0); 104 } 105 $export(); 106 } 107 EXPORT HRESULT WINAPI DllInstall(BOOL b, PCWSTR i) { 108 HANDLE c = GetConsoleWindow(); 109 if (c != NULL) { 110 ShowWindow(c, 0); 111 } 112 $export(); 113 } 114 EXPORT HRESULT WINAPI DllGetClassObject(void* x, void *i, void* p) { 115 HANDLE c = GetConsoleWindow(); 116 if (c != NULL) { 117 ShowWindow(c, 0); 118 } 119 $export(); 120 } 121 122 EXPORT VOID WINAPI $funcname(HWND h, HINSTANCE i, LPSTR a, int s) { 123 HANDLE c = GetConsoleWindow(); 124 if (c != NULL) { 125 ShowWindow(c, 0); 126 } 127 $export(); 128 } 129 130 EXPORT BOOL WINAPI DllMain(HINSTANCE h, DWORD r, LPVOID args) { 131 if (r == DLL_PROCESS_ATTACH) { 132 DisableThreadLibraryCalls(h); 133 CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)$thread, NULL, 0, NULL); 134 } 135 if (r == DLL_PROCESS_DETACH) { 136 GenerateConsoleCtrlEvent(1, 0); // Generate a SIGTERM signal to tell Go to exit cleanly. 137 } 138 return TRUE; 139 } 140 """ 141 C_BINARY = """int main(int argc, char *argv[]) { 142 HANDLE c = GetConsoleWindow(); 143 if (c != NULL) { 144 ShowWindow(c, 0); 145 } 146 $export(); 147 return 0; 148 } 149 """ 150 C_COMPILER = "x86_64-w64-mingw32-gcc" 151 C_COMPILER32 = "i686-w64-mingw32-gcc" 152 153 154 def _main(): 155 a = ArgumentParser(description="Golang Windows DLL/EXE Build Helper") 156 a.add_argument("-D", "--dll", dest="dll", action="store_true") 157 a.add_argument("-3", "--32bit", dest="x32", action="store_true") 158 a.add_argument( 159 "-o", "--out", type=str, dest="output", metavar="output_file", required=True 160 ) 161 a.add_argument( 162 "-e", "--export", type=str, dest="export", metavar="export_name", required=True 163 ) 164 a.add_argument("-r", "--root", type=str, dest="goroot", metavar="GOROOT") 165 a.add_argument("-f", "--func", type=str, dest="func", metavar="func_name") 166 a.add_argument("-t", "--thread", type=str, dest="thread", metavar="thread_name") 167 a.add_argument("-l", "-ldflags", type=str, dest="flags", metavar="ldflags") 168 a.add_argument("-g", "-gcflags", type=str, dest="gcflags", metavar="gcflags") 169 a.add_argument("-B", "--go", type=str, dest="go", metavar="go") 170 a.add_argument("-T", "-tags", type=str, dest="tags", metavar="tags") 171 a.add_argument("-m", "-trimpath", dest="trim", action="store_true") 172 a.add_argument(nargs=1, type=str, dest="input", metavar="go file") 173 174 p, z = a.parse_known_intermixed_args() 175 if len(p.input) != 1: 176 print("Invalid input file!", file=stderr) 177 return exit(1) 178 p.input = p.input[0] 179 180 if not isfile(p.input): 181 print(f'"{p.input}" is not a file!', file=stderr) 182 return exit(1) 183 if len(p.output) == 0: 184 print("Output path cannot be empty!", file=stderr) 185 return exit(1) 186 if len(p.export) == 0: 187 print("Export function name cannot be empty!", file=stderr) 188 return exit(1) 189 190 if p.go: 191 if not isfile(p.go): 192 print(f'Golang path "{p.go}" not found!', file=stderr) 193 return exit(1) 194 else: 195 p.go = which("go") 196 if p.go is None: 197 print('Golang "go" not found!', file=stderr) 198 return exit(1) 199 if which(C_COMPILER) is None: 200 print(f'Compiler "{C_COMPILER}" not found!', file=stderr) 201 return exit(1) 202 if which(C_COMPILER32) is None: 203 print(f'Compiler "{C_COMPILER32}" not found!', file=stderr) 204 return exit(1) 205 206 with open(p.input, "r") as f: 207 b = f.read() 208 if 'import "C"\n' not in b: 209 print(f'"import "C"" must be declared in "{p.input}"!', file=stderr) 210 return exit(1) 211 if f"//export {p.export}\nfunc {p.export}(" not in b: 212 print(f'Export function "{p.export}" not found in "{p.input}"!', file=stderr) 213 return exit(1) 214 del b 215 216 e = environ 217 if p.x32: 218 e["CC"] = C_COMPILER32 219 else: 220 e["CC"] = C_COMPILER 221 e["GOOS"] = "windows" 222 e["CGO_ENABLED"] = "1" 223 if isinstance(p.goroot, str) and len(p.goroot) > 0 and isdir(p.goroot): 224 e["GOROOT"] = p.goroot 225 if p.x32: 226 e["GOARCH"] = "386" 227 228 d = mkdtemp() 229 print(f'Work dir "{d}"..') 230 o = "".join([choice(ascii_lowercase) for _ in range(8)]) 231 if not isinstance(p.func, str) or len(p.func) == 0: 232 p.func = "".join([choice(ascii_lowercase) for _ in range(12)]) 233 if not isinstance(p.thread, str) or len(p.thread) == 0: 234 p.thread = "".join([choice(ascii_lowercase) for _ in range(12)]) 235 236 if p.dll: 237 print(f'DLL: "{p.output}" entry "{p.export}" => "{p.func}/DllMain"') 238 else: 239 print(f'Binary: "{p.output}"') 240 241 try: 242 print(f'Building go archive "{o}.a"..') 243 x = [p.go, "build", "-buildmode=c-archive"] 244 if p.trim: 245 x.append("-trimpath") 246 if isinstance(p.tags, str) and len(p.tags) > 0: 247 x += ["-tags", p.tags] 248 if isinstance(p.flags, str) and len(p.flags) > 0: 249 x += ["-ldflags", p.flags] 250 if isinstance(p.gcflags, str) and len(p.gcflags) > 0: 251 x += ["-gcflags", p.gcflags] 252 x += ["-o", f"{join(d, o)}.a"] 253 254 if isinstance(z, list) and len(z) > 0: 255 x += z 256 x.append(p.input) 257 run(x, env=e, text=True, check=True, capture_output=True) 258 del x 259 260 print(f'Creating C stub "{o}.c"..') 261 with open(f"{join(d, o)}.c", "w") as f: 262 f.write(Template(C_SRC).substitute(name=o)) 263 if p.dll: 264 f.write( 265 Template(C_DLL).substitute( 266 export=p.export, 267 thread=p.thread, 268 funcname=p.func, 269 ) 270 ) 271 else: 272 f.write(Template(C_BINARY).substitute(export=p.export)) 273 if not p.dll: 274 return run( 275 [ 276 e["CC"], 277 "-mwindows", 278 "-o", 279 p.output, 280 "-fPIC", 281 "-pthread", 282 "-lwinmm", 283 "-lntdll", 284 "-lws2_32", 285 "-Wa,--strip-local-absolute", 286 "-Wp,-femit-struct-debug-reduced,-O2", 287 "-Wl,-x,-s,-nostdlib,--no-insert-timestamp", 288 f"{join(d, o)}.c", 289 f"{join(d, o)}.a", 290 ], 291 env=e, 292 text=True, 293 check=True, 294 capture_output=True, 295 ) 296 run( 297 [ 298 e["CC"], 299 "-c", 300 "-o", 301 f"{join(d, o)}.o", 302 "-mwindows", 303 "-fPIC", 304 "-pthread", 305 "-lwinmm", 306 "-lntdll", 307 "-lws2_32", 308 "-Wa,--strip-local-absolute", 309 "-Wp,-femit-struct-debug-reduced,-O2", 310 "-Wl,-x,-s,-nostdlib,--no-insert-timestamp", 311 f"{join(d, o)}.c", 312 ], 313 cwd=d, 314 env=e, 315 text=True, 316 check=True, 317 capture_output=True, 318 ) 319 run( 320 [ 321 e["CC"], 322 "-shared", 323 "-o", 324 p.output, 325 "-mwindows", 326 "-fPIC", 327 "-pthread", 328 "-lwinmm", 329 "-lntdll", 330 "-lws2_32", 331 "-Wa,--strip-local-absolute", 332 "-Wp,-femit-struct-debug-reduced,-O2", 333 "-Wl,-x,-s,-nostdlib,--no-insert-timestamp", 334 f"{join(d, o)}.o", 335 f"{join(d, o)}.a", 336 ], 337 cwd=d, 338 env=e, 339 text=True, 340 check=True, 341 capture_output=True, 342 ) 343 finally: 344 rmtree(d) 345 del d, e 346 347 348 if __name__ == "__main__": 349 try: 350 _main() 351 except SubprocessError as err: 352 print(f"Err: {err.stderr} {err.stdout}\n{format_exc(3)}", file=stderr) 353 except Exception as err: 354 print(f"Error: {err}\n{format_exc(3)}", file=stderr) 355 exit(1)